aboutsummaryrefslogtreecommitdiffstats
path: root/FICS/formula.c
diff options
context:
space:
mode:
Diffstat (limited to 'FICS/formula.c')
-rw-r--r--FICS/formula.c705
1 files changed, 705 insertions, 0 deletions
diff --git a/FICS/formula.c b/FICS/formula.c
new file mode 100644
index 0000000..dd5ff23
--- /dev/null
+++ b/FICS/formula.c
@@ -0,0 +1,705 @@
+/*
+ * 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:
+ * assessdraw, assessloss, assesswin, blitz,
+ * f1, f2, f3, f4, f5, f6, f7, f8, f9,
+ * inc, lightning,
+ * maxtime(n) = maximum time n moves will take (in seconds),
+ * mymaxtime(n) = same, but just count my time,
+ * myrating, nonstandard, private, rated, rating,
+ * ratingdiff = rating - myrating,
+ * registered, standard, time, timeseal, untimed, wild.
+ *
+ * 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 "stdinclude.h"
+#include "common.h"
+
+#include <ctype.h>
+
+#include "command.h"
+#include "config.h"
+#include "formula.h"
+#include "gamedb.h"
+#include "lists.h"
+#include "network.h"
+#include "playerdb.h"
+#include "ratings.h"
+#include "rmalloc.h"
+#include "utils.h"
+
+PRIVATE char *
+GetPlayersFormula(player *pWho, int clause)
+{
+ if (clause == MAX_FORMULA)
+ return pWho->formula;
+ return pWho->formulaLines[clause];
+}
+
+PRIVATE int
+MoveIndexPastString(char *string, int *i, char *text)
+{
+ int n = strlen(text);
+
+ if (strncasecmp(text, string + *i, n))
+ return 0;
+ *i += n;
+ return n;
+}
+
+PUBLIC 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;
+}
+
+PRIVATE 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;
+}
+
+PRIVATE 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);
+ } 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;
+ if (g->type != TYPE_UNTIMED && g->bInitTime == 0)
+ max += 10;
+ } else {
+ max = (60 * g->bInitTime + numMoves * g->bIncrement);
+
+ if (g->type != TYPE_UNTIMED && g->bInitTime == 0)
+ max += 10;
+ }
+ 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'. This
+ * function returns 1 or 0 depending on whether a legitimate variable
+ * was found.
+ */
+PRIVATE int
+VarToToken(game *g, int clause, char *string, int *i, int *tok, int eval)
+{
+ double dummy_sterr;
+ int index = 0, c;
+ player *me = &parray[g->black];
+ player *you = &parray[g->white];
+
+ /*
+ * 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(NULL), 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(NULL), 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(NULL), 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;
+}
+
+/*
+ * ScanForOp() checks for an operator at position 'i' in 'string'.
+ */
+PRIVATE 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;
+}
+
+/*
+ * OpType() returns the precedence of the operator 'op'.
+ */
+PRIVATE 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;
+ }
+}
+
+/*
+ * In EvalBinaryOp() 'left' is the left operand, and 'op' is the
+ * current operator. 'g' and 'clause' specify which formula string to
+ * look at, 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 in 'left'. This function returns 0 if no error.
+ * Otherwise an error code is returned.
+ */
+PRIVATE 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;
+ }
+ } /* switch */
+}
+
+/*
+ * If 'eval' is 1, ScanForNumber() evaluates the number at position
+ * 'i' in the formula given by 'g' and 'clause', and places 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. This function
+ * returns 0 if no error. Otherwise an error code is returned.
+ */
+PRIVATE 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;
+ } /* switch */
+}
+
+/*
+ * 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.
+ */
+PUBLIC int
+CheckFormula(game *g, int clause, int *i, int op_type, int *result, int eval)
+{
+ char *string = GetPlayersFormula(&parray[g->black], clause);
+ int token, ret, nextOp, lastPos;
+
+ 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;
+}
+
+/*
+ * 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 ((i > 0 && isalnum(formula[i - 1])) || formula[i] != 'f' ||
+ !isdigit(formula[i + 1]) ||
+ sscanf(&formula[i], "f%d", &which) != 1)
+ continue;
+
+ 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;
+}
+
+PRIVATE void
+ExplainFormula(game *g, textlist **clauses)
+{
+ char txt[20] = { '\0' };
+ int i, which, dummy_index, value;
+ 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);
+ snprintf(txt, sizeof txt, "%d", value);
+ SaveTextListEntry(Cur, txt, i);
+ Cur = &(*Cur)->next;
+ }
+}
+
+/*
+ * 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;
+}
+
+/*
+ * 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.
+ */
+PUBLIC int
+SetValidFormula(int p, int clause, char *string)
+{
+ char *Old = NULL, **Cur;
+ game g;
+ int index = 0, ret, err = ERR_NONE;
+ 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' ? xstrdup(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)
+{
+ if (clauses != NULL)
+ pprintf(p, "\n");
+ for (textlist *Cur = clauses; Cur != NULL; Cur = Cur->next) {
+ pprintf(p, "f%d=%s: %s\n", (Cur->index + 1), Cur->text,
+ parray[p1].formulaLines[Cur->index]);
+ }
+}