diff options
author | Markus Uhlin <markus@nifty-networks.net> | 2023-12-07 21:31:49 +0100 |
---|---|---|
committer | Markus Uhlin <markus@nifty-networks.net> | 2023-12-07 21:31:49 +0100 |
commit | 79b59f9b30fb6a1fdf8c3efb446271f7cb00d434 (patch) | |
tree | f6ade4ccbc3af20d825edacfd12b5da8ded8d240 /FICS/formula.c |
FICS 1.6.2
Diffstat (limited to 'FICS/formula.c')
-rw-r--r-- | FICS/formula.c | 593 |
1 files changed, 593 insertions, 0 deletions
diff --git a/FICS/formula.c b/FICS/formula.c new file mode 100644 index 0000000..dbdd798 --- /dev/null +++ b/FICS/formula.c @@ -0,0 +1,593 @@ +/* Formula program for FICS. Written by Dave Herscovici + (dhersco@math.nps.navy.mil) + Edited by DAV to include wild,non-std and untimed + lightning flags + + Operators allowed, in order of precedence: + ! (not), - (unary), + *, /, + +, -, + <, <=, =<, >, >=, =>, (<= and =< are equivalent, as are >= and =>) + =, ==, !=, <>, (two ways of saying both equals and not equals) + &, &&, (both "and") + |, ||, (both "or"). + + Parentheses () are also allowed, as is a pound sign '#' for comments. + The program will divide by a fudge factor of .001 instead of 0. + + Variables allowed: + time, inc, rating, myrating, rated, blitz, standard, lightning, registered, + assesswin, assessdraw, assessloss, timeseal, + ratingdiff = rating - myrating, private, wild, untimed, nonstandard, + maxtime(n) = maximum time n moves will take (in seconds), + mymaxtime(n) = same, but just count my time. + f1, f2, f3, f4, f5, f6, f7, f8, f9. + + The code for translating blitz and standard variables may have to be + redone. F1 through f9 are user-defined formula variables. They can + be used to avoid having to retype your entire formula when you want + to change one part of it, or to compensate for the lack of a 'mood' + variable. + + For example: + set f1 rated & time=5 & inc=0 # rated 5 minute games + set f2 rating - myrating + set f3 # this line is a random comment. + set f4 f2>400 # I want a REAL fight + + Then you can type: + set formula f1 # rated 5 min. games only + set formula etime >= 10 & f2 > -100 # long games, decent competition + set formula f1 & !f4 + or set formula f2 >= 0 | blitz + depending on your mood. + + Further modifications could account for starting positions, time + odds games, provisional or established opponents, etc. Maybe f0 + should be reserved for your formula upon logging in; i.e. if f0 + is set, your formula is automatically set to f0 when you log in. +*/ + +#include <ctype.h> +#include "config.h" +#include "formula.h" +#include "playerdb.h" +#include "gamedb.h" +#include "command.h" +#include "common.h" +#include "stdinclude.h" +#include "utils.h" +#include "rmalloc.h" +#include "lists.h" +#include "network.h" +#include "ratings.h" + +extern player parray[]; + +PRIVATE char *GetPlayersFormula (player *pWho, int clause) +{ + if (clause==MAX_FORMULA) return (pWho->formula); + else return (pWho->formulaLines[clause]); +} + +/* In MoveIndexPastString, *i is treated as the index into string[]; + this function returns 1 if the character string beginning with + string[*i] match *text, and 0 otherwise. In the former case, *i + is incremented to move the index past *text. +*/ +int MoveIndexPastString (char *string, int *i, char *text) +{ + int n = strlen(text); + if (strncasecmp(text, string + *i, n)) return (0); + *i += n; + return (n); +} /* end of function MoveIndexPastString. */ + +/* GetRating simply chooses between blitz, standard and ratings. */ +int GetRating(player *p, int gametype) +{ + if (gametype == TYPE_BLITZ) return (p->b_stats.rating); + else if (gametype == TYPE_STAND) return (p->s_stats.rating); + else if (gametype == TYPE_WILD) return (p->w_stats.rating); + else if (gametype == TYPE_LIGHT) return (p->l_stats.rating); + else if (gametype == TYPE_BUGHOUSE) return (p->bug_stats.rating); + else return 0; +} /* end of function GetRating. */ + +int GetNumberInsideParens (game *g, int clause, int *i, int *token, + int eval) +{ + char *string = GetPlayersFormula(&parray[g->black], clause); + int ret; + + while (string[*i] != '\0' && isspace(string[*i])) (*i)++; + if (!MoveIndexPastString(string, i, "(")) return (ERR_BADCHAR); + + ret = CheckFormula(g, clause, i, OPTYPE_PAREN, token, eval); + if (ret != ERR_NONE) + return (ret); + if (MoveIndexPastString(string, i, ")")) return (ERR_NONE); + else return (ERR_PAREN); +} + +int Maxtime (game *g, int numMoves, int numPlayers) +{ + int max; + + if ((g->bInitTime == g->wInitTime) && (g->bIncrement == g->wIncrement)) { + max = numPlayers * (60 * g->wInitTime + numMoves * g->wIncrement); + if ((g->type != TYPE_UNTIMED) && (g->wInitTime == 0)) + max +=10 * numPlayers; /* 0 x start with ten secs */ + + } else if (numPlayers == 2) { + max = 60 * (g->wInitTime + g->bInitTime) + + numMoves * (g->wIncrement + g->bIncrement); + if ((g->type != TYPE_UNTIMED) && (g->wInitTime == 0)) + max +=10; /* 0 x start with ten secs */ + if ((g->type != TYPE_UNTIMED) && (g->bInitTime == 0)) + max +=10; /* 0 x start with ten secs */ + + } else { + max = 60 * g->bInitTime + numMoves * g->bIncrement; + if ((g->type != TYPE_UNTIMED) && (g->bInitTime == 0)) + max +=10; /* 0 x start with ten secs */ + } + + return max; +} + + +/* The black player in game *g has been challenged. S/he has a list + of formulas, and we're checking the one given by the index clause + (MAX_FORMULA denotes the actual formula; if clause is smaller, + we're looking at one of the user-defined formulas). We're at + position *i in the formula. If we find a legitimate variable there + and eval is 1, VarToToken puts its value in *tok; if eval is 0, + just move past the variable. Returns 1 or 0, depending on whether + a legitimate variable was found. +*/ +int VarToToken (game *g, int clause, char *string, int *i, int *tok, int eval) +{ + player *me = &parray[g->black]; + player *you = &parray[g->white]; + int index=0, c; + double dummy_sterr; + + /* We list possible variables with the longest names first. */ + if (MoveIndexPastString(string, i, "registered")) *tok=you->registered; + else if (MoveIndexPastString(string, i, "ratingdiff")) + *tok = GetRating(you, g->type) - GetRating(me, g->type); + else if (MoveIndexPastString(string, i, "assessloss")) + { + if (g->rated) + rating_sterr_delta(g->black, g->white, g->type, + time(0), RESULT_LOSS, tok, &dummy_sterr); + else *tok = 0; + } + else if (MoveIndexPastString(string, i, "assessdraw")) + { + if (g->rated) + rating_sterr_delta(g->black, g->white, g->type, + time(0), RESULT_DRAW, tok, &dummy_sterr); + else *tok = 0; + } + else if (MoveIndexPastString(string, i, "assesswin")) + { + if (g->rated) + rating_sterr_delta(g->black, g->white, g->type, + time(0), RESULT_WIN, tok, &dummy_sterr); + else *tok = 0; + } + else if (MoveIndexPastString(string, i, "mymaxtime")) + { + if (GetNumberInsideParens(g, clause, i, tok, eval) != ERR_NONE) + return (0); + *tok = Maxtime (g, *tok, 1); + } +#ifdef TIMESEAL + else if (MoveIndexPastString(string, i, "timeseal")) + *tok = con[you->socket].timeseal; +#endif + else if (MoveIndexPastString(string, i, "myrating")) + *tok = GetRating(me, g->type); + else if (MoveIndexPastString(string, i, "computer")) + *tok = in_list(-1, L_COMPUTER, you->name); + else if (MoveIndexPastString(string, i, "standard")) + *tok = (g->type == TYPE_STAND); + else if (MoveIndexPastString(string, i, "private")) + *tok = you->private; + else if (MoveIndexPastString(string, i, "maxtime")) + { + if (GetNumberInsideParens(g, clause, i, tok, eval) != ERR_NONE) + return (0); + *tok = Maxtime (g, *tok, 2); + } + else if (MoveIndexPastString(string, i, "abuser")) + *tok = in_list(-1, L_ABUSER, you->name); + else if (MoveIndexPastString(string, i, "rating")) + *tok = GetRating(you, g->type); + else if (MoveIndexPastString(string, i, "rated")) *tok=g->rated; + else if (MoveIndexPastString(string, i, "blitz")) *tok = (g->type == TYPE_BLITZ); + else if (MoveIndexPastString(string, i, "wild")) *tok = (g->type == TYPE_WILD); + else if (MoveIndexPastString(string, i, "lightning")) *tok = (g->type == TYPE_LIGHT); + else if (MoveIndexPastString(string, i, "nonstandard")) *tok = (g->type == TYPE_NONSTANDARD); + else if (MoveIndexPastString(string, i, "untimed")) *tok = (g->type == TYPE_UNTIMED); + else if (MoveIndexPastString(string, i, "time")) *tok=g->wInitTime; + else if (MoveIndexPastString(string, i, "inc")) *tok=g->wIncrement; + else if (tolower(string[*i])=='f' && isdigit(string[*i+1]) && + (c = (string[*i+1]-'1')) >= 0 && clause > c) + { + *i += 2; + return (!CheckFormula (g, c, &index, OPTYPE_NONE, tok, eval)); + } + else return (0); + return (1); +} /* end of function VarToToken. */ + +/* ScanForOp checks for an operator at position *i in string[]. */ +int ScanForOp (char *string, int *i) +{ + while (string[*i] != '\0' && isspace(string[*i])) (*i)++; + + if (string[*i] == '\0') return (OP_NONE); + if (string[*i] == '#') return (OP_NONE); + if (string[*i] == ')') return (OP_RTPAREN); + + /* Two char operators and longer first. */ + if (MoveIndexPastString(string, i, "and")) return (OP_AND); + if (MoveIndexPastString(string, i, "or")) return (OP_OR); + if (MoveIndexPastString(string, i, "||")) return (OP_OR); + if (MoveIndexPastString(string, i, "&&")) return (OP_AND); + if (MoveIndexPastString(string, i, "==")) return (OP_EQ); + if (MoveIndexPastString(string, i, "!=")) return (OP_NEQ); + if (MoveIndexPastString(string, i, "<>")) return (OP_NEQ); + if (MoveIndexPastString(string, i, ">=")) return (OP_GE); + if (MoveIndexPastString(string, i, "=>")) return (OP_GE); + if (MoveIndexPastString(string, i, "<=")) return (OP_LE); + if (MoveIndexPastString(string, i, "=<")) return (OP_LE); + + /* One char operators now. */ + if (MoveIndexPastString(string, i, "|")) return (OP_OR); + if (MoveIndexPastString(string, i, "&")) return (OP_AND); + if (MoveIndexPastString(string, i, ">")) return (OP_GT); + if (MoveIndexPastString(string, i, "<")) return (OP_LT); + if (MoveIndexPastString(string, i, "=")) return (OP_EQ); + if (MoveIndexPastString(string, i, "+")) return (OP_PLUS); + if (MoveIndexPastString(string, i, "-")) return (OP_MINUS); + if (MoveIndexPastString(string, i, "*")) return (OP_MULT); + if (MoveIndexPastString(string, i, "/")) return (OP_DIV); + return (OP_BAD); +} /* end of function ScanForOp. */ + +/* OpType returns the precedence of the operator op. */ +int OpType (int op) +{ + switch (op) + { + case OP_BAD: return (OPTYPE_BAD); + case OP_NONE: return (OPTYPE_NONE); + case OP_RTPAREN: return (OPTYPE_RPAREN); + case OP_OR: return (OPTYPE_OR); + case OP_AND: return (OPTYPE_AND); + case OP_EQ: case OP_NEQ: return (OPTYPE_COMPEQ); + case OP_GT: case OP_GE: case OP_LT: case OP_LE: return (OPTYPE_COMPGL); + case OP_PLUS: case OP_MINUS: return (OPTYPE_ADD); + case OP_MULT: case OP_DIV: return (OPTYPE_MULT); + case OP_NEGATE: case OP_NOT: return (OPTYPE_UNARY); + default: return (OPTYPE_BAD); + } +} /* end of function OpType. */ + +/* In EvalBinaryOp, *left is the left operand; and op is the + current operator. *g and clause specify which formula string to + look at (we're checking parray[g->black].formulaLines[clause]), + and *i tells us where we are in the string. We look for a right + operand from position *i in the string, and place the expression + (*left op right) in *left. For example, if *left=6, op = + OP_MULT, and we pull off right = 4, we replace *left with + 6*4=24. Returns 0 if no error; otherwise indicates the error. +*/ +int EvalBinaryOp (int *left, int op, game *g, int clause, int *i) +{ + int right, ret; + if ((op==OP_OR) && (*left != 0)) /* Nothing to decide. */ + { + *left = 1; + return (CheckFormula (g, clause, i, OpType(op), &right, 0)); + } + else if ((op==OP_AND) && (*left == 0)) /* Nothing to decide. */ + return (CheckFormula (g, clause, i, OpType(op), &right, 0)); + + else { + ret = CheckFormula (g, clause, i, OpType(op), &right, 1); + if (ret != ERR_NONE) return (ret); + } + switch (op) + { + default: case OP_BAD: return (ERR_BADOP); + case OP_NONE: case OP_RTPAREN: return (ERR_NONE); + case OP_OR: *left = (*left || right); return (ERR_NONE); + case OP_AND: *left = (*left && right); return (ERR_NONE); + case OP_EQ: *left = (*left == right); return (ERR_NONE); + case OP_NEQ: *left = (*left != right); return (ERR_NONE); + case OP_GT: *left = (*left > right); return (ERR_NONE); + case OP_GE: *left = (*left >= right); return (ERR_NONE); + case OP_LT: *left = (*left < right); return (ERR_NONE); + case OP_LE: *left = (*left <= right); return (ERR_NONE); + case OP_PLUS: *left += right; return (ERR_NONE); + case OP_MINUS: *left -= right; return (ERR_NONE); + case OP_MULT: *left *= right; return (ERR_NONE); + case OP_DIV: + if (right != 0) + { + *left /= right; + return (ERR_NONE); + } + else + { + pprintf(g->black, "Dividing by %lf instead or zero in formula.\n", +FUDGE_FACTOR); + *left /= FUDGE_FACTOR; + return (ERR_NONE); + } + } +} /* end of function EvalBinaryOp. */ + +/* If eval is 1, ScanForNumber evaluates the number at position *i + in the formula given by *g and clause, and place this value in + *token. op_type is the precedence of the operator preceding the + *i'th position. If we come to an operator of higher precedence, + we must keep going before we can leave this function. If eval + is 0, just move past the number we would evaluate. Returns 0 if + no error; otherwise return code indicates the error. +*/ +int ScanForNumber (game *g, int clause, int *i, int op_type, + int *token, int eval) +{ + char *string = GetPlayersFormula (&parray[g->black], clause); + char c; + int ret; + + while ((string[*i] != '\0') && (isspace(string[*i]))) (*i)++; + switch (c = string[*i]) + { + case '\0': case '#': + if (op_type != OPTYPE_NONE) return (ERR_EOF); + else *token=1; + return (ERR_NONE); + + case ')': + if (op_type != OPTYPE_PAREN) return (ERR_PAREN); + else *token=1; + return(ERR_NONE); + + case '(': return GetNumberInsideParens(g,clause,i,token,eval); + + case '-': case '!': + ++(*i); + if (c==string[*i]) return(ERR_UNARY); + ret = ScanForNumber(g,clause,i,OPTYPE_UNARY,token,eval); + if (ret != ERR_NONE) + return (ret); + if (c == '-') *token = -(*token); + else if (c == '!') *token = !(*token); + return (ERR_NONE); + + default: + if (isdigit(string[*i])) + { + *token = 0; + + do *token = 10 * (*token) + string[(*i)++] - '0'; + while (isdigit(string[*i])); + + while (string[*i] != '\0' && isspace(string[*i])) (*i)++; + if (MoveIndexPastString (string, i, "minutes")) + *token *= 60; + return (ERR_NONE); + } + else if (isalpha(string[*i])) + { + if (!VarToToken (g, clause, string, i, token, eval)) + return (ERR_BADVAR); + return(ERR_NONE); + } + else return (ERR_NONESENSE); + } +} /* end of function ScanForNumber. */ + +/* If eval is 1, CheckFormula looks for the next token in the + formula given by *g, clause, and *i; usually this is the right + side of an expression whose operator has precedence OpType; if + eval is 0, just go to the end of an expression. Return 0 if no + error; otherwise, return error type. +*/ +int CheckFormula (game *g, int clause, int *i, int op_type, + int *result, int eval) +{ + int token, ret, nextOp, lastPos; + char *string = GetPlayersFormula (&parray[g->black], clause); + + if (string == NULL) + { + *result = 1; + return (ERR_NONE); + } + ret = ScanForNumber (g, clause, i, op_type, &token, eval); + if (ret != ERR_NONE) + return (ret); + lastPos = *i; + nextOp = ScanForOp (string, i); + while (OpType(nextOp) > op_type) /* higher precedence. */ + { + if (nextOp==OP_RTPAREN) return (ERR_PAREN); + if (!eval) + ret = CheckFormula(g, clause, i, OpType(nextOp), &token, 0); + else ret = EvalBinaryOp (&token, nextOp, g, clause, i); + if (ret != ERR_NONE) return (ret); + lastPos = *i; + nextOp = ScanForOp (string, i); + } + if (nextOp == OP_BAD) return(ERR_BADOP); + *result = token; + + /* move back unless we're at the end or at a right paren, in which + case we never went forward. */ + if (nextOp != OP_NONE && nextOp != OP_RTPAREN) *i=lastPos; + return (ERR_NONE); +} /* end of function CheckFormula. */ + +/* which clauses are relevant for a player's formula. */ +PRIVATE int ChooseClauses(player *who, char *formula) +{ + int i, which, ret=0; + + if (formula == NULL) + return ret; + for (i=0; formula[i] != '\0' && formula[i] != '#'; i++) { + if (formula[i] != 'f' || (i > 0 && isalnum(formula[i-1])) + || !isdigit(formula[i+1])) + continue; + sscanf(&formula[i], "f%d", &which); + ret |= (1 << (which - 1)); + } + /* now scan clauses found as part of the formula. */ + for (i = MAX_FORMULA - 1; i >= 0; i--) { + if (ret & (1 << i)) { + ret |= ChooseClauses(who, who->formulaLines[i]); + } + } + return ret; +} /* end of function ChooseClauses. */ + +void ExplainFormula (game *g, textlist **clauses) +{ + int i, which, dummy_index, value; + char txt[20]; + player *challenged = &parray[g->black]; + textlist **Cur = clauses; + + which = ChooseClauses(challenged, challenged->formula); + + for (i = 0; i < MAX_FORMULA; i++) { + if ((which & (1 << i)) == 0) + continue; + dummy_index = 0; + CheckFormula (g, i, &dummy_index, OPTYPE_NONE, &value, 1); + sprintf(txt, "%d", value); + SaveTextListEntry (Cur, txt, i); + Cur = &(*Cur)->next; + } +} + +#if 0 +void ExplainFormula (game *g, textlist *eval) +{ + int i, newPos, value; + char Head[MAX_STRING_LENGTH], + Tail[MAX_STRING_LENGTH]; + + strcpy (strRet, GetPlayersFormula(&parray[g->black], clause)); + for (i=0; strRet[i] != '\0'; i++) + { + if (strRet[i] == '#') + { + strRet[i] = '\0'; + break; + } + if (!isalpha(strRet[i])) continue; + newPos = i; + if (!VarToToken(g, clause, strRet, &newPos, &value, 1)) + i = newPos; + else + { + strcpy (Head, strRet); + strcpy (Tail, strRet + newPos); + Head[i] = '\0'; + + sprintf(strRet, "%s%d%s", Head, value, Tail); + while (isalpha(strRet[i])) i++; + } + } +} +#endif + +/* GameMatchesFormula converts parameters to a game structure + and passes a pointer to this game to CheckFormula for + evaluation. It returns the evaluated formula. +*/ +PUBLIC int GameMatchesFormula (int w, int b, int wTime, int wInc, int bTime, + int bInc, int rated, int gametype, textlist **clauseList) +{ + game g; + int index=0, ret; + + g.white = w; g.black = b; + g.wInitTime = wTime; g.bInitTime = bTime; + g.wIncrement = wInc; g.bIncrement = bInc; + g.rated = rated; + g.type = gametype; + + if (CheckFormula(&g, MAX_FORMULA, &index, OPTYPE_NONE, &ret, 1) != ERR_NONE) + return (0); + if (ret == 0) ExplainFormula (&g, clauseList); + return (ret); +} /* end of function GameMatchesFormula. */ + +/* SetValidFormula sets a clause of player p and creates a game structure + to check whether that new formula is legitimate. If so, return 1; + otherwise, reset the formula and return 0. +*/ +int SetValidFormula (int p, int clause, char *string) +{ + game g; + int index=0, ret, err=ERR_NONE; + char *Old=NULL, **Cur; + player *me = &parray[p]; + + if (clause==MAX_FORMULA) + Cur = &me->formula; + else + Cur = &me->formulaLines[clause]; + + Old = *Cur; + + if (string != NULL) { + string = eatwhite(string); + *Cur = (*string != '\0' ? strdup (string) : NULL); + } + else *Cur = NULL; + + if (*Cur==NULL) { + if (Old != NULL) rfree(Old); + return 1; + } + + g.white = g.black = p; + g.wInitTime = g.bInitTime = me->d_time; + g.wIncrement = g.bIncrement = me->d_inc; + g.rated = me->rated; + g.type = TYPE_BLITZ; + err = CheckFormula (&g, clause, &index, OPTYPE_NONE, &ret, 0); + + if (err != ERR_NONE) { + /* Bad formula; reset it. */ + rfree(*Cur); + *Cur = Old; + } else { + if (Old != NULL) rfree(Old); + } + return (err == ERR_NONE); +} + +PUBLIC void ShowClauses (int p, int p1, textlist *clauses) +{ + textlist *Cur; + + if (clauses != NULL) + pprintf(p, "\n"); + for (Cur = clauses; Cur != NULL; Cur = Cur->next) { + pprintf(p, "f%d=%s: %s\n", Cur->index + 1, Cur->text, + parray[p1].formulaLines[Cur->index]); + } +} |