/* * Copyright (C) 2000-2003 Oystein Johansen * Copyright (C) 2001-2023 the AUTHORS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #include #include #include "backgammon.h" #include "drawboard.h" #if USE_GTK #include "gtkgame.h" #endif #include "file.h" #include "positionid.h" #include "matchequity.h" #if !GLIB_CHECK_VERSION (2,26,0) #ifdef WIN32 #define GStatBuf struct _g_stat_struct #else typedef struct stat GStatBuf; #endif #endif static int ParseSnowieTxt(char *sz, int *pnMatchTo, int *pfJacoby, int *pfUnused1, int *pfUnused2, int *pfTurn, char aszPlayer[2][MAX_NAME_LEN], int *pfCrawfordGame, int anScore[2], int *pnCube, int *pfCubeOwner, TanBoard anBoard, int anDice[2]); static int IsValidMove(const TanBoard anBoard, const int anMove[8]) { TanBoard anBoardTemp; int anMoveTemp[8]; memcpy(anBoardTemp, anBoard, 2 * 25 * sizeof(int)); memcpy(anMoveTemp, anMove, 8 * sizeof(int)); if (!ApplyMove(anBoardTemp, anMoveTemp, TRUE)) return 1; else return 0; } static void ParseSetDate(char *szFilename) { /* GridGammon mat files contain date in filename. * for other files use last file access date */ GStatBuf filestat; struct tm *matchdate = NULL; #if HAVE_STRPTIME char *pch; struct tm mdate; for (pch = szFilename; *pch != '\0'; pch++); /* goto end of filename */ while (pch != szFilename) { if (*--pch == '-') { /* go backwards until '-' */ /* check if 8 digits before - are valid date */ if (strptime(pch - 8, "%Y%m%d", &mdate) == pch) { matchdate = &mdate; /* GamesGrid closed at the end of May 2008, GridGammon opened some time later */ if (mdate.tm_year <= 107 || (mdate.tm_year == 108 && mdate.tm_mon <= 5)) SetMatchInfo(&mi.pchPlace, "GamesGrid", NULL); else SetMatchInfo(&mi.pchPlace, "GridGammon", NULL); } } } #endif /* date could not be parsed, use date of last modification */ if (matchdate == NULL) { if (g_stat(szFilename, &filestat) == 0) { matchdate = localtime((time_t *) & filestat.st_mtime); } } if (matchdate != NULL) { mi.nYear = matchdate->tm_year + 1900; mi.nMonth = matchdate->tm_mon + 1; mi.nDay = matchdate->tm_mday; } } static int ReadInt16(FILE * pf, int *n) { /* Read a little-endian, signed (2's complement) 16-bit integer. * This is inefficient on hardware which is already little-endian * or 2's complement, but at least it's portable. */ /* FIXME what about error handling? */ unsigned char auch[2]; if (fread(auch, 2, 1, pf) != 1) return FALSE; *n = auch[0] | (auch[1] << 8); if (*n >= 0x8000) *n -= 0x10000; return TRUE; } static int ParseJF(FILE * fp, int *pnMatchTo, int *pfJacoby, int *pfTurn, char aszPlayer[2][MAX_NAME_LEN], int *pfCrawfordGame, int *pfPostCrawford, int anScore[2], int *pnCube, int *pfCubeOwner, TanBoard anBoard, int anDice[2], int *pfCubeUse, int *pfBeavers) { int nVersion, nCubeOwner, nOnRoll, nMovesLeft, nMovesRight; int nGameOrMatch, nOpponent, nLevel, nScore1, nScore2; int nDie1, nDie2; int fCaution, fSwapDice, fJFplayedLast; int i, idx, anNew[26], anOld[26]; char szLastMove[25]; unsigned char c; int no_val; int val_crawford; if (!ReadInt16(fp, &nVersion)) goto read_failed; if (nVersion < 124 || nVersion > 126) { outputl(_("File not recognised as Jellyfish file.")); return -1; } if (nVersion == 126) { /* 3.0 */ if (!ReadInt16(fp, &fCaution)) goto read_failed; if (!ReadInt16(fp, &no_val)) goto read_failed; /* Not in use */ } if (nVersion == 125 || nVersion == 126) { /* 1.6 or newer */ /* 3 variables not used by older version */ if (!ReadInt16(fp, pfCubeUse)) goto read_failed; if (!ReadInt16(fp, pfJacoby)) goto read_failed; if (!ReadInt16(fp, pfBeavers)) goto read_failed; if (nVersion == 125) { /* If reading, caution can be set here use caution = */ } } if (nVersion == 124) { /* 1.0, 1.1 or 1.2 If reading, the other variables can be set here */ /* use cube = jacoby = beaver = use caution = */ } if (!ReadInt16(fp, pnCube)) goto read_failed; if (!ReadInt16(fp, &nCubeOwner)) goto read_failed; /* Owner: 1 or 2 is player 1 or 2, respectively, 0 means cube in the middle */ *pfCubeOwner = nCubeOwner - 1; if (!ReadInt16(fp, &nOnRoll)) goto read_failed; /* 0 means starting position. If you add 2 to the player (to get 3 or 4) Sure? it means that the player is on * roll but the dice have not been rolled yet. */ if (!ReadInt16(fp, &nMovesLeft)) goto read_failed; if (!ReadInt16(fp, &nMovesRight)) goto read_failed; /* These two variables are used when you use movement #1, (two buttons) and tells how many moves you have left * to play with the left and the right die, respectively. Initialized to 1 (if you roll a double, left = 4 and * right = 0). If movement #2 (one button), only the first one (left) is used to store both dice. */ if (!ReadInt16(fp, &no_val)) /* Not in use */ goto read_failed; if (!ReadInt16(fp, &nGameOrMatch)) goto read_failed; /* 1 = match, 3 = game */ if (!ReadInt16(fp, &nOpponent)) goto read_failed; /* 1 = 2 players, 2 = JF plays one side */ if (!ReadInt16(fp, &nLevel)) goto read_failed; if (!ReadInt16(fp, pnMatchTo)) goto read_failed; /* 0 if single game */ if (nGameOrMatch == 3) *pnMatchTo = 0; if (!ReadInt16(fp, &nScore1)) goto read_failed; if (!ReadInt16(fp, &nScore2)) goto read_failed; /* Can be whatever if match length = 0 */ anScore[0] = nScore1; anScore[1] = nScore2; if (fread(&c, 1, 1, fp) != 1) goto read_failed; for (i = 0; i < c; i++) { if (fread(&aszPlayer[0][i], 1, 1, fp) != 1) goto read_failed; } aszPlayer[0][c] = '\0'; if (nOpponent == 2) strcpy(aszPlayer[0], "Jellyfish"); if (fread(&c, 1, 1, fp) != 1) goto read_failed; for (i = 0; i < c; i++) { if (fread(&aszPlayer[1][i], 1, 1, fp) != 1) goto read_failed; } aszPlayer[1][c] = '\0'; if (!ReadInt16(fp, &fSwapDice)) goto read_failed; /* TRUE if lower die is to be drawn to the left */ if (!ReadInt16(fp, &val_crawford)) goto read_failed; switch (val_crawford) { case 2: /* Crawford */ *pfPostCrawford = FALSE; *pfCrawfordGame = TRUE; break; case 3: /* post-Crawford */ *pfPostCrawford = TRUE; *pfCrawfordGame = FALSE; break; default: *pfCrawfordGame = *pfPostCrawford = FALSE; break; } if (nGameOrMatch == 3) *pfCrawfordGame = *pfPostCrawford = FALSE; if (!ReadInt16(fp, &fJFplayedLast)) goto read_failed; if (fread(&c, 1, 1, fp) != 1) goto read_failed; for (i = 0; i < c; i++) { if (fread(&szLastMove[i], 1, 1, fp) != 1) goto read_failed; } szLastMove[c] = '\0'; /* Stores whether the last move was played by JF If so, the move is stored in a string to be displayed in the * 'Show last' dialog box */ if (!ReadInt16(fp, &nDie1)) goto read_failed; if (!ReadInt16(fp, &nDie2)) goto read_failed; nDie1 = abs(nDie1); /* if ( nDie1 < 0 ) { nDie1=65536; } *//* What?? */ /* In the end the position itself is stored, as well as the old position to be able to undo. The two position * arrays can be read like this: */ for (i = 0; i < 26; i++) { if (!ReadInt16(fp, &(anNew[i]))) goto read_failed; if (!ReadInt16(fp, &(anOld[i]))) goto read_failed; anNew[i] -= 20; anOld[i] -= 20; /* 20 has been added to each number when storing */ } /* Player 1's checkers are represented with negative numbers, player 2's with positive. The arrays are * representing the 26 different points on the board, starting with anNew[0] which is the upper bar and ending * with anNew[25] which is the bottom bar. The remaining numbers are in the opposite direction of the numbers * you see if you choose 'Numbers' from the 'View' menu, so anNew[1] is marked number 24 on the screen. */ if (nOnRoll == 1 || nOnRoll == 3) idx = 0; else idx = 1; *pfTurn = idx; if (nOnRoll == 0) *pfTurn = -1; anDice[0] = nDie1; anDice[1] = nDie2; for (i = 0; i < 25; i++) { if (anNew[i + 1] < 0) anBoard[idx][i] = -anNew[i + 1]; else anBoard[idx][i] = 0; if (anNew[24 - i] > 0) anBoard[!idx][i] = anNew[24 - i]; else anBoard[!idx][i] = 0; } SwapSides(anBoard); return 0; read_failed: outputerr(_("Error reading Jellyfish file")); fclose(fp); return -2; } static int ImportJF(FILE * fp, char *UNUSED(szFileName)) { moverecord *pmr; int nMatchTo = 0; int fJacobyRule = FALSE; int fTurn = 0; int fCrawfordGame = FALSE; int fPostCrawfordGame = FALSE; int anScore[2] = { 0, 0 }; int nCube = 0; int fCubeOwner = 0; int anDice[2] = { 0, 0 }; TanBoard anBoard; int fCubeUsage = FALSE; int fBeavers = 0; char aszPlayer[2][MAX_NAME_LEN]; int i; if (!get_input_discard()) return -1; #if USE_GTK if (fX) { /* Clear record to avoid ugly updates */ GTKClearMoveRecord(); GTKFreeze(); } #endif if (ParseJF(fp, &nMatchTo, &fJacobyRule, &fTurn, aszPlayer, &fCrawfordGame, &fPostCrawfordGame, anScore, &nCube, &fCubeOwner, anBoard, anDice, &fCubeUsage, &fBeavers) < 0) { outputl(_("This file is not a valid Jellyfish .pos file!\n")); return -1; } FreeMatch(); ClearMatch(); InitBoard(ms.anBoard, ms.bgv); ClearMoveRecord(); ListInsert(&lMatch, plGame); /* game info */ pmr = NewMoveRecord(); pmr->mt = MOVE_GAMEINFO; pmr->g.i = 0; pmr->g.nMatch = nMatchTo; pmr->g.anScore[0] = anScore[0]; pmr->g.anScore[1] = anScore[1]; pmr->g.fCrawford = TRUE; pmr->g.fCrawfordGame = fCrawfordGame; pmr->g.fJacoby = fJacobyRule; pmr->g.fWinner = -1; pmr->g.nPoints = 0; pmr->g.fResigned = FALSE; pmr->g.nAutoDoubles = 0; pmr->g.bgv = VARIATION_STANDARD; /* assume standard backgammon */ pmr->g.fCubeUse = fCubeUsage; /* assume use of cube */ IniStatcontext(&pmr->g.sc); AddMoveRecord(pmr); ms.fTurn = ms.fMove = fTurn; for (i = 0; i < 2; ++i) strcpy(ap[i].szName, aszPlayer[i]); /* dice */ if (anDice[0]) { pmr = NewMoveRecord(); pmr->mt = MOVE_SETDICE; pmr->fPlayer = fTurn; pmr->anDice[0] = anDice[0]; pmr->anDice[1] = anDice[1]; pmr->lt = LUCK_NONE; pmr->rLuck = (float) ERR_VAL; AddMoveRecord(pmr); } /* board */ pmr = NewMoveRecord(); pmr->mt = MOVE_SETBOARD; if (fTurn) SwapSides(anBoard); PositionKey((ConstTanBoard) anBoard, &pmr->sb.key); AddMoveRecord(pmr); /* cube value */ pmr = NewMoveRecord(); pmr->mt = MOVE_SETCUBEVAL; pmr->scv.nCube = nCube; AddMoveRecord(pmr); /* cube position */ pmr = NewMoveRecord(); pmr->mt = MOVE_SETCUBEPOS; pmr->scp.fCubeOwner = fCubeOwner; AddMoveRecord(pmr); /* update menus etc */ UpdateSettings(); #if USE_GTK if (fX) { GTKThaw(); GTKSet(ap); } #endif return 0; } static int fWarned, fPostCrawford; static int ExpandMatMove(const TanBoard anBoard, int anMove[8], int *pc, const unsigned int anDice[2]) { int i; int c = *pc; if (anDice[0] != anDice[1]) { if ((unsigned int) (anMove[0] - anMove[1]) == (anDice[0] + anDice[1])) { int an[8]; /* consolidated move, e.g. 61: 11/4. Let's hope that the notation cannot * be as bad as 61: 11/4* meaning 11/5* 5/4 */ for (i = 0; i < 2; ++i) { an[0] = anMove[0]; an[1] = an[0] - anDice[i]; an[2] = an[1]; an[3] = an[2] - anDice[!i]; an[4] = -1; an[5] = -1; /* no hits on the first part of the move */ if (anBoard[0][23 - an[1]] != 0) continue; if (IsValidMove(anBoard, an)) { memcpy(anMove, an, sizeof(an)); ++*pc; break; } } } else if (anMove[1] == -1 && (unsigned int) anMove[0] >= anDice[0] && (unsigned int) anMove[0] >= anDice[1]) { int an[8]; /* consolidated bearoff move with overage, like 65: 9/Off */ for (i = 0; i < 2; ++i) { an[0] = anMove[0]; an[1] = an[0] - anDice[i]; an[2] = an[1]; an[3] = -1; an[4] = -1; an[5] = -1; /* no hits on the first part of the move */ if (anBoard[0][23 - an[1]] != 0) continue; if (IsValidMove(anBoard, an)) { memcpy(anMove, an, sizeof(an)); ++*pc; break; } } } } else { for (i = 0; i < c; ++i) { /* if this is a consolidated move then "expand" it */ int j, k; j = (anDice[0] - 1 + anMove[2 * i] - anMove[2 * i + 1]) / anDice[0]; for (k = 1; k < j; ++k) { /* new move */ anMove[2 * *pc] = anMove[2 * *pc + 1] = anMove[2 * i]; anMove[2 * *pc + 1] -= anDice[0]; /* fix old move */ anMove[2 * i] -= anDice[0]; ++*pc; if (*pc > 4) /* something is wrong */ return -1; /* terminator */ if (*pc < 4) { anMove[2 * *pc] = -1; anMove[2 * *pc + 1] = -1; } } } } if (c != *pc) /* reorder moves */ CanonicalMoveOrder(anMove); return 0; } static void ParseMatMove(char *sz, int iPlayer, int *warned) { char *pch; moverecord *pmr; int c; static int fBeaver = FALSE; sz += strspn(sz, " \t"); if (!*sz) return; for (pch = strchr(sz, 0) - 1; pch >= sz && (*pch == ' ' || *pch == '\t'); pch--) *pch = 0; if (sz[0] >= '1' && sz[0] <= '6' && sz[1] >= '1' && sz[1] <= '6' && sz[2] == ':') { int dice1 = sz[0] - '0'; int dice2 = sz[1] - '0'; if (fBeaver) { /* looks like the previous beaver was taken */ moverecord *prev; pmr = NewMoveRecord(); pmr->mt = MOVE_TAKE; if (!(prev = LinkToDouble(pmr))) { outputl(_("Take record found but doesn't follow a double")); g_free(pmr); return; } pmr->fPlayer = !prev->fPlayer; AddMoveRecord(pmr); } fBeaver = FALSE; if (!StrNCaseCmp(sz + 4, "???", 3)) { /* * Apparently XG writes this when the player resigned after rolling * Put it back to ' ' and fall through to "standard" .mat handling */ sz[4] = sz[5] = sz[6] = ' '; /* * Sometimes it puts a 4th one... */ if (!StrNCaseCmp(sz + 7, "?", 1)) sz[7] = ' '; } if (!StrNCaseCmp(sz + 4, "Cannot Move", 11)) { /* * Sometimes it writes this instead of nothing for dancing rolls */ sz[4] = 0; } /* XG can have additional spaces between roll and "Illegal play" */ while (sz[4] == ' ') sz++; if (!StrNCaseCmp(sz + 4, "illegal play", 12)) { /* Snowie type illegal play */ int nMatchTo, fJacobyRule, fUnused1, fUnused2, fTurn = 0, fCrawfordGame; int anScore[2], nCube, fCubeOwner, anDice[2]; TanBoard anBoard; char aszPlayer[2][MAX_NAME_LEN]; if ((pch = strchr(sz + 4, '(')) == 0) { outputl(_("No '(' following text 'Illegal play'")); return; } /* step over '(' */ ++pch; if (ParseSnowieTxt(pch, &nMatchTo, &fJacobyRule, &fUnused1, &fUnused2, &fTurn, aszPlayer, &fCrawfordGame, anScore, &nCube, &fCubeOwner, anBoard, anDice)) { outputl(_("Illegal Snowie position format for illegal play.")); return; } /* FIXME: we only handle illegal moves; not illegal cubes, * changes to the match score etc. */ pmr = NewMoveRecord(); pmr->mt = MOVE_SETDICE; pmr->fPlayer = iPlayer; pmr->anDice[0] = dice1; pmr->anDice[1] = dice2; AddMoveRecord(pmr); pmr = NewMoveRecord(); pmr->mt = MOVE_SETBOARD; if (fTurn) SwapSides(anBoard); PositionKey((ConstTanBoard) anBoard, &pmr->sb.key); AddMoveRecord(pmr); return; } pmr = NewMoveRecord(); pmr->mt = MOVE_NORMAL; pmr->anDice[0] = dice1; pmr->anDice[1] = dice2; pmr->fPlayer = iPlayer; c = ParseMove(sz + 3, pmr->n.anMove); FixMatchState(&ms, pmr); if (c > 0) { /* moves found */ int i; for (i = 0; i < (c << 1); i++) pmr->n.anMove[i]--; if (c < 4) pmr->n.anMove[c << 1] = pmr->n.anMove[(c << 1) | 1] = -1; /* remove consolidation */ if (ExpandMatMove(msBoard(), pmr->n.anMove, &c, pmr->anDice)) outputf(_("WARNING: Expand move failed. This file " "contains garbage!")); /* check if move is valid */ if (!IsValidMove(msBoard(), pmr->n.anMove)) { if (*warned == -1) outputf(_("WARNING: Invalid move: \"%s\" encountered\n"), sz + 3); else *warned = TRUE; return; } AddMoveRecord(pmr); return; } else if (!c) { /* roll but no move found; if there are legal moves assume * a resignation */ movelist ml; int anDice[2]; anDice[0] = pmr->anDice[0]; anDice[1] = pmr->anDice[1]; switch (GenerateMoves(&ml, msBoard(), pmr->anDice[0], pmr->anDice[1], FALSE)) { case 0: /* no legal moves; record the movenormal */ pmr->n.anMove[0] = pmr->n.anMove[1] = -1; AddMoveRecord(pmr); return; case 1: /* forced move; record the movenormal */ memcpy(pmr->n.anMove, ml.amMoves[0].anMove, sizeof(pmr->n.anMove)); AddMoveRecord(pmr); return; default: /* legal moves; just record roll */ g_free(pmr); /* free movenormal from above */ pmr = NewMoveRecord(); pmr->mt = MOVE_SETDICE; pmr->fPlayer = iPlayer; pmr->anDice[0] = anDice[0]; pmr->anDice[1] = anDice[1]; AddMoveRecord(pmr); return; } } } if (!StrNCaseCmp(sz, "double", 6) || !StrNCaseCmp(sz, "beaver", 6) || !StrNCaseCmp(sz, "raccoon", 7)) { pmr = NewMoveRecord(); pmr->mt = MOVE_DOUBLE; pmr->fPlayer = iPlayer; LinkToDouble(pmr); AddMoveRecord(pmr); fBeaver = !StrNCaseCmp(sz, "beaver", 6); } else if (!StrNCaseCmp(sz, "take", 4)) { pmr = NewMoveRecord(); pmr->mt = MOVE_TAKE; pmr->fPlayer = iPlayer; if (!LinkToDouble(pmr)) { outputl(_("Take record found but doesn't follow a double")); g_free(pmr); return; } AddMoveRecord(pmr); fBeaver = FALSE; } else if (!StrNCaseCmp(sz, "drop", 4)) { pmr = NewMoveRecord(); pmr->mt = MOVE_DROP; pmr->fPlayer = iPlayer; if (!LinkToDouble(pmr)) { outputl(_("Drop record found but doesn't follow a double")); g_free(pmr); return; } AddMoveRecord(pmr); } else if (!StrNCaseCmp(sz, "win", 3)) { if (ms.gs == GAME_PLAYING) { /* Neither a drop nor a bearoff to win, so we presume the loser * resigned. */ int n = atoi(sz + 4); pmr = NewMoveRecord(); pmr->mt = MOVE_RESIGN; pmr->fPlayer = !iPlayer; if (n <= ms.nCube) pmr->r.nResigned = 1; else if (n <= 2 * ms.nCube) pmr->r.nResigned = 2; else pmr->r.nResigned = 3; AddMoveRecord(pmr); } } else if (!StrNCaseCmp(sz, "resign", 6)) { return; /* Ignore this .gam line */ } else if (!fWarned) { outputf(_("Unrecognised move \"%s\" in .mat file.\n"), sz); fWarned = TRUE; } } #define START_STRING "Game " #define START_STRING_LEN 5 static char * GetMatLine(FILE * fp) { static char szLine[1024]; do { if (feof(fp) || !fgets(szLine, sizeof(szLine), fp)) return NULL; } while (strspn(szLine, " \n\r\t") == strlen(szLine)); return szLine; } static int ImportGame(FILE * fp, int iGame, int nLength, bgvariation bgVariation, int fCubeUsage, int *warned) { char sz0[MAX_NAME_LEN], sz1[MAX_NAME_LEN], *pch, *pchLeft, *pchRight = NULL, *szLine; int n0, n1; char *colon, *psz, *pszEnd; moverecord *pmr; /* Process player score line, avoid fscanf(%nn) as buffer may overrun */ szLine = GetMatLine(fp); if (!szLine) return 1; psz = szLine; while (isspace(*psz)) psz++; /* 1st player name */ colon = strchr(psz, ':'); if (!colon) return 0; pszEnd = colon - 1; while (isspace(*pszEnd)) pszEnd--; psz[MIN(pszEnd - psz + 1, MAX_NAME_LEN - 1)] = '\0'; strcpy(sz0, psz); /* 1st score */ psz = colon + 1; while (isspace(*psz)) psz++; n0 = atoi(psz); /* 2nd player name */ while (!isspace(*psz)) psz++; while (isspace(*psz)) psz++; colon = strchr(psz, ':'); if (!colon) return 0; pszEnd = colon - 1; while (isspace(*pszEnd)) pszEnd--; psz[MIN(pszEnd - psz + 1, MAX_NAME_LEN - 1)] = '\0'; strcpy(sz1, psz); /* 2nd score */ psz = colon + 1; while (isspace(*psz)) psz++; n1 = atoi(psz); if (nLength && (n0 >= nLength || n1 >= nLength)) /* match is already over -- ignore extra games */ return 1; InitBoard(ms.anBoard, bgVariation); ClearMoveRecord(); ListInsert(&lMatch, plGame); for (pch = sz0; *pch; pch++); for (pch--; pch >= sz0 && isspace(*pch);) *pch-- = 0; for (; pch >= sz0; pch--) if (isspace(*pch)) *pch = ' '; else if (*pch == ',' && strtol(pch + 1, NULL, 10) > 0) { /* GridGammon mat files have ratings */ *pch = '\0'; /* after player name */ SetMatchInfo(&mi.pchRating[0], pch + 1, NULL); } strcpy(ap[0].szName, sz0); for (pch = sz1; *pch; pch++); for (pch--; pch >= sz1 && isspace(*pch);) *pch-- = 0; for (; pch >= sz1; pch--) if (isspace(*pch)) *pch = ' '; else if (*pch == ',' && strtol(pch + 1, NULL, 10) > 0) { /* GridGammon mat files have ratings */ *pch = '\0'; /* after player name */ SetMatchInfo(&mi.pchRating[1], pch + 1, NULL); } strcpy(ap[1].szName, sz1); pmr = NewMoveRecord(); pmr->mt = MOVE_GAMEINFO; pmr->g.i = iGame; pmr->g.nMatch = nLength; pmr->g.anScore[0] = n0; pmr->g.anScore[1] = n1; pmr->g.fCrawford = (nLength > 0); /* assume JF always uses Crawford rule */ if ((pmr->g.fCrawfordGame = !fPostCrawford && (n0 == nLength - 1) ^ (n1 == nLength - 1))) fPostCrawford = TRUE; pmr->g.fJacoby = fJacoby && (nLength == 0); pmr->g.fWinner = -1; pmr->g.nPoints = 0; pmr->g.fResigned = FALSE; pmr->g.nAutoDoubles = 0; pmr->g.bgv = bgVariation; pmr->g.fCubeUse = fCubeUsage; IniStatcontext(&pmr->g.sc); AddMoveRecord(pmr); while ((szLine = GetMatLine(fp)) && !g_strrstr(szLine, START_STRING)) { if ((pch = strpbrk(szLine, "\n\r")) != 0) *pch = 0; if (!strncmp(szLine, "; Set Pos=", 10) && szLine[36] == '/') { /* XG Set Pos extension */ char *pos, *posid; TanBoard anBoard; char szcv[5], szco[2]; pos = g_strndup(szLine + 10, 26); PositionFromXG(anBoard, pos); g_free(pos); ms.gs = GAME_PLAYING; ms.fMove = 0; if (szLine[37] == '0') { sprintf(szcv, "1"); CommandSetCubeCentre(NULL); } else if (szLine[37] == '-') { sprintf(szcv, "%d", 1 << (szLine[38] - '0')); /* FIXME: * CommandSetCubeOwner() needs its argument to be writable * CommandSetCubeOwner("1") coredumps */ sprintf(szco, "0"); CommandSetCubeOwner(szco); } else { sprintf(szcv, "%d", 1 << (szLine[37] - '0')); sprintf(szco, "1"); CommandSetCubeOwner(szco); } CommandSetCubeValue(szcv); ms.fMove = 0; posid = g_strdup(PositionID((ConstTanBoard) anBoard)); CommandSetBoard(posid); g_free(posid); continue; } if ((pchLeft = strchr(szLine, ':')) && (pchRight = strchr(pchLeft + 1, ':')) && pchRight > szLine + 3) *((pchRight -= 2) - 1) = 0; else if (strlen(szLine) > 15 && (pchRight = strstr(szLine + 15, " "))) *pchRight++ = 0; if ((pchLeft = strchr(szLine, ')')) != 0) pchLeft++; else pchLeft = szLine; ParseMatMove(pchLeft, 0, warned); if (pchRight && *warned < TRUE) ParseMatMove(pchRight, 1, warned); if (*warned == TRUE) return 1; } AddGame(pmr); return (ms.nMatchTo && (ms.anScore[0] >= ms.nMatchTo || ms.anScore[1] >= ms.nMatchTo)); } static int ImportMatVariation(FILE * fp, char *szFilename, bgvariation bgVariation, int warned) { int n = 0, nLength = -1, game; char ch; gchar *pchComment = NULL; char *szLine; int fCubeUsage = TRUE; fWarned = fPostCrawford = FALSE; do { szLine = GetMatLine(fp); if (!szLine) { outputerrf(_("%s: not a valid .mat file"), szFilename); g_free(pchComment); return -1; } if (*szLine == (char) 0xef && *(szLine + 1) == (char) 0xbb && *(szLine + 2) == (char) 0xbf) szLine += 3; /* skip UTF junk at the start of XG mat files */ if (*szLine == '#' || *szLine == ';') { /* comment */ char *pchOld = pchComment; char *pch; if ((pch = strpbrk(szLine, "\n\r")) != 0) *pch = 0; pch = szLine + 1; while (isspace(*pch)) ++pch; if (*pch) { gchar *p; if (g_str_has_prefix(pch, "[Event ")) { if ((p = g_strrstr(pch, "\"]"))) *p = 0; SetMatchInfo(&mi.pchEvent, pch + 8, NULL); } else if (g_str_has_prefix(pch, "[Round ")) { if ((p = g_strrstr(pch, "\"]"))) *p = 0; SetMatchInfo(&mi.pchRound, pch + 8, NULL); } else if (g_str_has_prefix(pch, "[Site ")) { if ((p = g_strrstr(pch, "\"]"))) *p = 0; SetMatchInfo(&mi.pchPlace, pch + 7, NULL); } else if (g_str_has_prefix(pch, "[Transcriber ")) { if ((p = g_strrstr(pch, "\"]"))) *p = 0; SetMatchInfo(&mi.pchAnnotator, pch + 14, NULL); } else if (g_str_has_prefix(pch, "[EventDate ")) { sscanf(pch + 12, "%4u.%2u.%2u", &mi.nYear, &mi.nMonth, &mi.nDay); } else if (g_str_has_prefix(pch, "[EventTime ")) { /* discard ; we don't have this in matchinfo */ } else if (g_str_has_prefix(pch, "[Unrated ")) { /* discard */ } else if (g_str_has_prefix(pch, "[Player ")) { /* discard. We don't have player names * in matchinfo and rely on games * headers. Elos (even if not left at * default 1600) are useless if their * source is unknown. Maybe keep them * for known Site values ? */ } else if (g_str_has_prefix(pch, "[Match ID ")) { /* discard */ } else if (g_str_has_prefix(pch, "[CubeLimit \"1\"")) { fCubeUsage = FALSE; } else if (g_str_has_prefix(pch, "[CubeLimit ")) { /* discard ; maybe keep as comment if != 1024 ? */ } else if (g_str_has_prefix(pch, "[Variation \"Backgammon")) { bgVariation = VARIATION_STANDARD; } else if (g_str_has_prefix(pch, "[Variation \"NackGammon")) { bgVariation = VARIATION_NACKGAMMON; } else if (g_str_has_prefix(pch, "[Variation \"HyperGammon (1)")) { bgVariation = VARIATION_HYPERGAMMON_1; } else if (g_str_has_prefix(pch, "[Variation \"HyperGammon (2)")) { bgVariation = VARIATION_HYPERGAMMON_2; } else if (g_str_has_prefix(pch, "[Variation \"HyperGammon (3)")) { bgVariation = VARIATION_HYPERGAMMON_3; } else if (g_str_has_prefix(pch, "[Crawford ")) { /* discard for now */ } else if (g_str_has_prefix(pch, "[Jacoby ")) { /* discard for now */ } else if (g_str_has_prefix(pch, "[Beaver ")) { /* discard for now */ } else if (g_str_has_prefix(pch, "[Game ")) { /* Discard useless BGNJ comment. Its format is : * ; [Game 1, Move 1: Dice set to Manual Rolls] * or * ; [Game 2, Move 1: Dice set to Mersenne Twister, seed=1290023973, rollNum=586] */ gchar **token; token = g_strsplit(pch, " ", 8); if (!strcmp(token[2], "Move") && !strcmp(token[4], "Dice") && !strcmp(token[5], "set") && !strcmp(token[6], "to")); /* discard */ else { pchComment = g_strconcat(pchComment ? pchComment : "", pch, "\n", NULL); g_free(pchOld); } g_strfreev(token); } else { pchComment = g_strconcat(pchComment ? pchComment : "", pch, "\n", NULL); g_free(pchOld); } } } else /* Look for start of mat file */ n = sscanf(szLine, "%10d %*1[Pp]oint %*1[Mm]atch%c", &nLength, &ch); } while (n != 2); if (nLength < 0 || nLength > MAXSCORE) { outputerrf(_("Invalid match length %d found in mat file"), nLength); outputerrf(_("Match length must be between 0 (unlimited session) and %d\n"), MAXSCORE); return -1; } #if USE_GTK if (fX) { /* Clear record to avoid ugly updates */ GTKClearMoveRecord(); GTKFreeze(); } #endif if (mi.nYear == 0) ParseSetDate(szFilename); if (pchComment) mi.pchComment = g_strdup(pchComment); /* no need to free mi.pchComment as it's * already done in ClearMatch */ g_free(pchComment); szLine = GetMatLine(fp); while (szLine) { if (g_strrstr(szLine, START_STRING)) { game = atoi(g_strchug(szLine) + START_STRING_LEN); if (!game) outputf(_("WARNING! Unrecognized line in mat file: '%s'\n"), szLine); { if (ImportGame(fp, game - 1, nLength, bgVariation, fCubeUsage, &warned)) break; /* import failed */ } } else szLine = GetMatLine(fp); } UpdateSettings(); #if USE_GTK if (fX) { GTKSet(ap); GTKThaw(); } #endif return (warned > 0); } static int ImportMat(FILE * fp, char *szFilename) { bgvariation bgv; if (!get_input_discard()) return -1; FreeMatch(); ClearMatch(); for (bgv = VARIATION_STANDARD; bgv < NUM_VARIATIONS; bgv++) { gchar *str; if (ImportMatVariation(fp, szFilename, bgv, FALSE) == 0) return 0; str = g_strdup_printf(N_("Import as a %s match had errors. Try a different variation?"), aszVariations[bgv]); if (!GetInputYN(str)) { g_free(str); return 0; } g_free(str); /* reset and try a different format */ if (fseek(fp, 0L, SEEK_SET) != 0) return -1; FreeMatch(); ClearMatch(); } return ImportMatVariation(fp, szFilename, VARIATION_STANDARD, -1); } static int isAscending(const int anMove[8]) { if (anMove[0] < 0) return FALSE; if (anMove[0] != 24) { /* not moving from the bar */ if (anMove[1] > -1) /* not moving off */ return anMove[0] < anMove[1]; else return anMove[0] > 17; } else return anMove[1] < 6; } static void ParseOldmove(char *sz, int fInvert) { int iPlayer, i, c; moverecord *pmr; char *pch; switch (*sz) { case 'X': iPlayer = fInvert; break; case 'O': iPlayer = !fInvert; break; default: goto error; } if (sz[3] == '(' && strlen(sz) >= 8) { pmr = NewMoveRecord(); pmr->mt = MOVE_NORMAL; pmr->anDice[0] = sz[4] - '0'; pmr->anDice[1] = sz[6] - '0'; pmr->fPlayer = iPlayer; if (!StrNCaseCmp(sz + 9, "can't move", 10)) c = 0; else { for (pch = sz + 9; *pch; pch++) if (*pch == '-') *pch = '/'; c = ParseMove(sz + 9, pmr->n.anMove); } if (c >= 0) { for (i = 0; i < (c << 1); i++) pmr->n.anMove[i]--; /* if the moves are ascending invert it. * In theory this is only necessary when iPlayer == fInvert, * but some programs may write incompatible files. * Joseph's fibs2html is one such program. */ if (c && isAscending(pmr->n.anMove)) { /* ascending moves: invert */ for (i = 0; i < (c << 1); ++i) if (pmr->n.anMove[i] > -1 && pmr->n.anMove[i] < 24) /* invert numbers (other than bar=24 and off=-1) */ pmr->n.anMove[i] = 23 - pmr->n.anMove[i]; } if (c < 4) pmr->n.anMove[c << 1] = pmr->n.anMove[(c << 1) | 1] = -1; /* We have to order the moves before calling AddMoveRecord * Think about the opening roll of 6 5, and the legal move * 18/13 24/18 is entered. AddMoveRecord will then silently * fail in the sub-call to PlayMove(). This problem should * maybe be fixed in PlayMove (drawboard.c). Opinions? */ CanonicalMoveOrder(pmr->n.anMove); if (!IsValidMove(msBoard(), pmr->n.anMove)) { outputf(_("WARNING! Illegal or invalid move: '%s'\n"), sz); g_free(pmr); } else { /* Now we're ready */ AddMoveRecord(pmr); } } else g_free(pmr); return; } else if (!StrNCaseCmp(sz + 3, "doubles", 7)) { pmr = NewMoveRecord(); pmr->mt = MOVE_DOUBLE; pmr->fPlayer = iPlayer; if (!LinkToDouble(pmr)) { pmr->CubeDecPtr = &pmr->CubeDec; pmr->CubeDecPtr->esDouble.et = EVAL_NONE; pmr->nAnimals = 0; } pmr->stCube = SKILL_NONE; AddMoveRecord(pmr); return; } else if (!StrNCaseCmp(sz + 3, "accepts", 7)) { pmr = NewMoveRecord(); pmr->mt = MOVE_TAKE; pmr->fPlayer = iPlayer; if (!LinkToDouble(pmr)) { outputl(_("Take record found but doesn't follow a double")); g_free(pmr); return; } pmr->stCube = SKILL_NONE; AddMoveRecord(pmr); return; } else if (!StrNCaseCmp(sz + 3, "rejects", 7)) { pmr = NewMoveRecord(); pmr->mt = MOVE_DROP; pmr->fPlayer = iPlayer; if (!LinkToDouble(pmr)) { outputl(_("Drop record found but doesn't follow a double")); g_free(pmr); return; } pmr->stCube = SKILL_NONE; AddMoveRecord(pmr); return; } else if (!StrNCaseCmp(sz + 3, "wants to resign", 15)) { /* ignore */ return; } else if (!StrNCaseCmp(sz + 3, "wins", 4)) { if (ms.gs == GAME_PLAYING) { /* Neither a drop nor a bearoff to win, so we presume the loser * resigned. */ pmr = NewMoveRecord(); pmr->mt = MOVE_RESIGN; pmr->r.esResign.et = EVAL_NONE; pmr->fPlayer = !iPlayer; pmr->r.nResigned = 1; /* FIXME determine from score */ AddMoveRecord(pmr); } return; } error: if (!fWarned) { outputf(_("Unrecognised move \"%s\" in oldmoves file.\n"), sz); fWarned = TRUE; } } static int NewPlayers(const char *szOld0, const char *szOld1, const char *szNew0, const char *szNew1) { if (!strcmp(szOld0, szNew0)) /* player 0 have not changed */ return strcmp(szOld1, szNew1); else if (!strcmp(szOld0, szNew1)) /* swap of player1 and player 0 */ return strcmp(szOld1, szNew0); else /* definitely a new player */ return 1; } static int ImportOldmovesGame(FILE * pf, int iGame, int nLength, int n0, int n1) { char sz[80], sz0[MAX_NAME_LEN], sz1[MAX_NAME_LEN], *pch; char buf[256], *pNext; moverecord *pmr; int fInvert; static int anExpectedScore[2]; /* FIXME's: * * what about multiple matches? * pmr->r.nResigned is not correct in ParseOldMove * * Possible solution: read through file one time and collect match scores. * If the score is decreasing or the players's names changes stop. * * Since we now know the score after every game it's easy to * have pmr->r.nResigned correct. * */ /* Process player score line, avoid fscanf(%nn) as buffer may overrun */ if (fgets(buf, sizeof(buf), pf) == NULL) { outputerr(_("Error reading oldmoves file")); return 0; } pch = strstr(buf, "is X"); if (!pch) return 0; pNext = pch + strlen("is X -"); do pch--; while (isspace(*pch)); pch[1] = '\0'; pch = buf; while (isspace(*pch)) pch++; strcpy(sz0, pch); pch = strstr(pNext, "is O"); if (!pch) return 0; do pch--; while (isspace(*pch)); pch[1] = '\0'; pch = pNext; while (isspace(*pch)) pch++; strcpy(sz1, pch); /* consistency checks */ if (iGame) { if (NewPlayers(ap[0].szName, ap[1].szName, sz0, sz1)) return 1; if (ms.nMatchTo != nLength) /* match length have changed */ return 1; } /* initialise */ InitBoard(ms.anBoard, ms.bgv); ClearMoveRecord(); ListInsert(&lMatch, plGame); if (!iGame) { strcpy(ap[0].szName, sz0); strcpy(ap[1].szName, sz1); fInvert = FALSE; } else fInvert = strcmp(ap[0].szName, sz0) != 0; /* add game info */ pmr = NewMoveRecord(); pmr->mt = MOVE_GAMEINFO; pmr->g.i = iGame; pmr->g.nMatch = nLength; pmr->g.anScore[fInvert] = n0; pmr->g.anScore[!fInvert] = n1; pmr->g.fCubeUse = TRUE; /* always use cube */ /* check if score is swapped (due to fibs oldmoves bug) */ if (iGame && (pmr->g.anScore[0] == anExpectedScore[1] || pmr->g.anScore[1] == anExpectedScore[0])) { int n = pmr->g.anScore[0]; pmr->g.anScore[0] = pmr->g.anScore[1]; pmr->g.anScore[1] = n; } pmr->g.fCrawford = fAutoCrawford && nLength; if ((pmr->g.fCrawfordGame = !fPostCrawford && (n0 == nLength - 1) ^ (n1 == nLength - 1))) fPostCrawford = TRUE; pmr->g.fJacoby = fJacoby && !nLength; pmr->g.fWinner = -1; pmr->g.nPoints = 0; pmr->g.fResigned = FALSE; pmr->g.nAutoDoubles = 0; pmr->g.bgv = VARIATION_STANDARD; /* only standard bg on FIBS */ IniStatcontext(&pmr->g.sc); AddMoveRecord(pmr); do if (!fgets(sz, 80, pf)) { sz[0] = 0; break; } while (strspn(sz, " \n\r\t") == strlen(sz)); do { if ((pch = strpbrk(sz, "\n\r")) != 0) *pch = 0; ParseOldmove(sz, fInvert); if (ms.gs != GAME_PLAYING) break; if (!fgets(sz, 80, pf)) break; } while (strspn(sz, " \n\r\t") != strlen(sz)); anExpectedScore[0] = pmr->g.anScore[0]; anExpectedScore[1] = pmr->g.anScore[1]; if (pmr->g.fWinner >= 0) anExpectedScore[pmr->g.fWinner] += pmr->g.nPoints; AddGame(pmr); return 0; } #define MAXLINE 1024 static char * FindScoreIs(FILE * pf, char *buffer) { while (fgets(buffer, MAXLINE, pf)) { char *p; if ((p = strstr(buffer, "Score is ")) != 0) return p; } return 0; } static int ImportOldmoves(FILE * pf, char *szFilename) { char buffer[1024]; char *p; int n, n0, n1, nLength, i; if (!get_input_discard()) return -1; fWarned = fPostCrawford = FALSE; p = FindScoreIs(pf, buffer); if (p == 0) { outputerrf(_("%s: not a valid oldmoves file"), szFilename); return -1; } if ((n = sscanf(p, "Score is %10d-%10d in a %10d", &n0, &n1, &nLength)) < 2) { outputerrf(_("%s: not a valid oldmoves file"), szFilename); return -1; } if (n == 2) { /* assume a money game */ nLength = 0; } #if USE_GTK if (fX) { /* Clear record to avoid ugly updates */ GTKClearMoveRecord(); GTKFreeze(); } #endif FreeMatch(); ClearMatch(); i = 0; while (ImportOldmovesGame(pf, i++, nLength, n0, n1) == 0) { p = FindScoreIs(pf, buffer); if (p == 0) break; n = sscanf(p, "Score is %10d-%10d in a %10d", &n0, &n1, &nLength); if (n < 2) break; } UpdateSettings(); #if USE_GTK if (fX) { GTKThaw(); GTKSet(ap); } #endif return 0; } static void ImportSGGGame(FILE * pf, int i, int nLength, int n0, int n1, int fCrawford, int fCrawfordRule, int UNUSED(fAutoDoubles), int fJacobyRule, bgvariation bgv, int fCubeUsage) { char sz[1024]; char *pch = NULL; int c, fPlayer = 0, anRoll[2]; moverecord *pmgi, *pmr; char *szComment = NULL; int fBeaver = FALSE; int fPlayerOld, nMoveOld; int nMove = -1; int fResigned = -1; InitBoard(ms.anBoard, ms.bgv); ClearMoveRecord(); ListInsert(&lMatch, plGame); pmgi = NewMoveRecord(); pmgi->mt = MOVE_GAMEINFO; pmgi->g.i = i - 1; pmgi->g.nMatch = nLength; pmgi->g.anScore[0] = n0; pmgi->g.anScore[1] = n1; pmgi->g.fCrawford = fCrawfordRule && nLength; pmgi->g.fCrawfordGame = fCrawford; pmgi->g.fJacoby = fJacobyRule && !nLength; pmgi->g.fWinner = -1; pmgi->g.nPoints = 0; pmgi->g.fResigned = FALSE; pmgi->g.nAutoDoubles = 0; /* we check for automatic doubles later */ pmgi->g.bgv = bgv; pmgi->g.fCubeUse = fCubeUsage; IniStatcontext(&pmgi->g.sc); AddMoveRecord(pmgi); anRoll[0] = 0; anRoll[1] = 0; while (fgets(sz, 1024, pf)) { char *pchtmp; nMoveOld = nMove; fPlayerOld = fPlayer; /* check for game over */ for (pch = sz; *pch; pch++) if ((pchtmp = strstr(pch, " wins "))) { pch = pchtmp; goto finished; } else if (*pch == ':' || *pch == ' ') break; if (isdigit(*sz)) { nMove = atoi(sz); for (pch = sz; isdigit(*pch); pch++); if (*pch++ == '\t') { /* we have a move! */ if (anRoll[0]) { /* check for * 1 43: * 1 43: <---- check for this line * 1 43: 13/9, 13/10 */ if (*(pch + 4) == '\n' || !*(pch + 4)) continue; /* when beavered there is no explicit "take" */ if (fBeaver) { pmr = NewMoveRecord(); pmr->mt = MOVE_TAKE; pmr->sz = szComment; pmr->fPlayer = fPlayer; if (!LinkToDouble(pmr)) { outputl(_("Beaver record found but doesn't follow a double")); g_free(pmr); return; } AddMoveRecord(pmr); szComment = NULL; fBeaver = FALSE; } if (!StrNCaseCmp(pch, "resign", 6) || (strlen(pch) > 4 && !StrNCaseCmp(pch + 4, "resign", 6))) { /* resignation after rolling dice */ fResigned = fPlayer; continue; } pmr = NewMoveRecord(); pmr->mt = MOVE_NORMAL; pmr->sz = szComment; pmr->anDice[0] = anRoll[0]; pmr->anDice[1] = anRoll[1]; pmr->fPlayer = fPlayer; if ((c = ParseMove(pch + 4, pmr->n.anMove)) >= 0) { for (i = 0; i < (c << 1); i++) pmr->n.anMove[i]--; if (c < 4) pmr->n.anMove[c << 1] = pmr->n.anMove[(c << 1) | 1] = -1; AddMoveRecord(pmr); } else g_free(pmr); anRoll[0] = 0; szComment = NULL; } else { if ((fPlayer = *pch == '\t') != 0) pch++; if (*pch == ' ' && pch[1] == '-') { /* * Closeout. The SGG file doesn't contain any dice * number but GNUbg needs a roll. Set it to 6-6 then * fall through to normal processing. */ pch[0] = pch[1] = '6'; } if (*pch >= '1' && *pch <= '6' && pch[1] >= '1' && pch[1] <= '6') { /* dice roll */ anRoll[0] = *pch - '0'; anRoll[1] = pch[1] - '0'; if (strstr(pch, "O-O")) { if (nMove == nMoveOld && fPlayerOld == fPlayer) { /* handle duplicate dances: * 24 62: O-O * 24 62: O-O */ anRoll[0] = 0; continue; } /* when beavered there is no explicit "take" */ if (fBeaver) { pmr = NewMoveRecord(); pmr->mt = MOVE_TAKE; pmr->sz = szComment; pmr->fPlayer = fPlayer; if (!LinkToDouble(pmr)) { outputl(_("Take record found but doesn't follow a double")); g_free(pmr); return; } AddMoveRecord(pmr); szComment = NULL; } pmr = NewMoveRecord(); pmr->mt = MOVE_NORMAL; pmr->sz = szComment; pmr->anDice[0] = anRoll[0]; pmr->anDice[1] = anRoll[1]; pmr->fPlayer = fPlayer; AddMoveRecord(pmr); anRoll[0] = 0; szComment = NULL; fBeaver = FALSE; } else { for (pch += 3; *pch; pch++) if (!isspace(*pch)) break; if (*pch && StrNCaseCmp(pch, "resign", 6)) { /* Apparently SGG files can contain spurious * duplicate moves -- the only thing we can * do is ignore them. */ /* this catches: * 1 43: * 1 43: 13/9, 13/10 * 1 43: 13/9, 13/10 */ anRoll[0] = 0; continue; } /* when beavered there is no explicit "take" */ if (fBeaver) { pmr = NewMoveRecord(); pmr->mt = MOVE_TAKE; pmr->sz = szComment; pmr->fPlayer = fPlayer; if (!LinkToDouble(pmr)) { outputl(_("Take record found but doesn't follow a double")); g_free(pmr); return; } AddMoveRecord(pmr); szComment = NULL; } pmr = NewMoveRecord(); pmr->mt = MOVE_SETDICE; /* we do not want comments on MOVE_SETDICE */ pmr->fPlayer = fPlayer; pmr->anDice[0] = anRoll[0]; pmr->anDice[1] = anRoll[1]; AddMoveRecord(pmr); fBeaver = FALSE; if (!StrNCaseCmp(pch, "resign", 6)) { /* resignation after rolling dice */ fResigned = fPlayer; continue; } } } else { if (!StrNCaseCmp(pch, "double", 6)) { if (ms.fDoubled && ms.fTurn != fPlayer) /* Presumably a duplicated move in the * SGG file -- ignore */ continue; pmr = NewMoveRecord(); pmr->mt = MOVE_DOUBLE; pmr->sz = szComment; pmr->fPlayer = fPlayer; LinkToDouble(pmr); AddMoveRecord(pmr); fBeaver = FALSE; szComment = NULL; } else if (!StrNCaseCmp(pch, "beaver", 6)) { /* Beaver to 4 */ if (ms.fDoubled && ms.fTurn != fPlayer) /* Presumably a duplicated move in the * SGG file -- ignore */ continue; pmr = NewMoveRecord(); pmr->mt = MOVE_DOUBLE; pmr->sz = szComment; pmr->fPlayer = fPlayer; if (!LinkToDouble(pmr)) { outputl(_("Beaver record found but doesn't follow a double")); g_free(pmr); return; } AddMoveRecord(pmr); szComment = NULL; fBeaver = TRUE; } else if (!StrNCaseCmp(pch, "raccoon", 7)) { /* Raccoon to 8 */ if (ms.fDoubled && ms.fTurn != fPlayer) /* Presumably a duplicated move in the * SGG file -- ignore */ continue; pmr = NewMoveRecord(); pmr->mt = MOVE_DOUBLE; pmr->sz = szComment; pmr->fPlayer = fPlayer; if (!LinkToDouble(pmr)) { outputl(_("Raccoon record found but doesn't follow a double")); g_free(pmr); return; } AddMoveRecord(pmr); szComment = NULL; fBeaver = TRUE; } else if (!StrNCaseCmp(pch, "accept", 6)) { if (!ms.fDoubled) continue; pmr = NewMoveRecord(); pmr->mt = MOVE_TAKE; pmr->sz = szComment; pmr->fPlayer = fPlayer; if (!LinkToDouble(pmr)) { outputl(_("Take record found but doesn't follow a double")); g_free(pmr); return; } AddMoveRecord(pmr); szComment = NULL; fBeaver = FALSE; } else if (!StrNCaseCmp(pch, "pass", 4)) { pmr = NewMoveRecord(); pmr->mt = MOVE_DROP; pmr->sz = szComment; pmr->fPlayer = fPlayer; if (!LinkToDouble(pmr)) { outputl(_("Drop record found but doesn't follow a double")); g_free(pmr); return; } AddMoveRecord(pmr); szComment = NULL; fBeaver = FALSE; } else if (!StrNCaseCmp(pch, "resign", 4)) { /* resignation */ fResigned = fPlayer; } } } } /* *pch++ == '\t' */ else { /* check for automatic doubles: * * 11: Automatic double * */ if (strstr(pch, "Automatic double")) { ++pmgi->g.nAutoDoubles; /* we've already called AddMoveRecord for pmgi, * so we manually update the cube value */ ms.nCube *= 2; } } } /* isdigit */ else { /* the text is most likely a comment */ if (*sz != '\n' && *sz != '\r') { /* non-empty line */ if (!szComment) szComment = g_strdup(sz); else { szComment = (char *) g_realloc(szComment, (gulong) strlen(sz) + strlen(szComment) + 1); strcat(szComment, sz); } } } } finished: if (fResigned > -1) { /* game was terminated by an resignation */ /* parse string, e.g., MosheTissona_MC wins 2 points. */ long nr; pch += 6; /* step over ' wins ' */ nr = strtol(pch, NULL, 10); if (nr == 0) outputerrf(_("Unparseable line: %s"), sz); /* This doesn't really bother us as we rely * on the initial score of the next game. * The MOVE_RESIGN record below may be inaccurate, though */ pmr = NewMoveRecord(); pmr->mt = MOVE_RESIGN; pmr->sz = szComment; pmr->fPlayer = fResigned; pmr->r.nResigned = (int) nr / ms.nCube; if (pmr->r.nResigned > 3) pmr->r.nResigned = 3; if (pmr->r.nResigned < 1) pmr->r.nResigned = 1; szComment = NULL; AddMoveRecord(pmr); } AddGame(pmgi); /* garbage collect */ if (szComment) g_free(szComment); } static int ParseSGGGame(char *pch, int *pi, int *pn0, int *pn1, int *pfCrawford, int *pnLength) { *pfCrawford = FALSE; if (strncmp(pch, "Game ", 5)) return -1; pch += 5; *pi = (int) strtol(pch, &pch, 10); if (*pch != '.') return -1; pch++; *pn0 = (int) strtol(pch, &pch, 10); if (*pch == '*') { pch++; *pfCrawford = TRUE; } if (*pch != '-') return -1; pch++; *pn1 = (int) strtol(pch, &pch, 10); if (*pch == '*') { pch++; *pfCrawford = TRUE; } if (*pch != '/') { /* assume money session */ *pnLength = 0; return 0; } pch++; *pnLength = (int) strtol(pch, &pch, 10); return 0; } static char * GetValue(const char *sz, char *szValue) { const char *pc; char *pc2; /* skip "Keyword:" */ pc = strchr(sz, ':'); if (!pc) return NULL; ++pc; /* string leading blanks */ while (isspace(*pc)) ++pc; if (!*pc) return NULL; strcpy(szValue, pc); /* strip trailing blanks */ pc2 = strchr(szValue, 0) - 1; while (pc2 >= szValue && isspace(*pc2)) *pc2-- = 0; return szValue; } static void ParseSGGDate(const char *sz, unsigned int *pnDay, unsigned int *pnMonth, unsigned int *pnYear) { static const char *aszMonths[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; int i; char szMonth[80]; unsigned int nDay, nYear; *pnDay = 1; *pnMonth = 1; *pnYear = 1900; if (sscanf(sz, "%*s %79s %2u, %4u", szMonth, &nDay, &nYear) != 3) return; *pnDay = nDay; *pnYear = nYear; for (i = 0; i < 12; ++i) { if (!strcmp(szMonth, aszMonths[i])) { *pnMonth = i + 1; break; } } } static void ParseSGGOptions(const char *sz, matchinfo * pmi, int *pfCrawfordRule, int *pfAutoDoubles, int *pfJacobyRule, bgvariation * pbgv, int *pfCubeUse) { char szTemp[80]; int i; static const char *aszOptions[] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday", "GamesGrid", "ELO:", "Jacoby:", "Automatic Doubles:", "Crawford:", "Beavers:", "Raccoons:", "Doubling Cube:", "Rated:", "Match Length:", "Variant:", NULL }; double arRating[2]; int anExp[2]; const char *pc; for (i = 0, pc = aszOptions[i]; pc; ++i, pc = aszOptions[i]) if (!strncmp(sz, pc, strlen(pc))) break; switch (i) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: /* date */ ParseSGGDate(sz, &pmi->nDay, &pmi->nMonth, &pmi->nYear); break; case 7: /* place */ /* GamesGrid closed at the end of May 2008, GridGammon opened some time later */ if (pmi->nYear <= 2007 || (pmi->nYear == 2008 && pmi->nMonth <= 5)) pmi->pchPlace = g_strdup("GamesGrid"); else pmi->pchPlace = g_strdup("GridGammon"); break; case 8: /* ratings */ /* Players are swapped later (at the end of ImportSGG()) and ratings * must be swapped here as well to be correctly attributed at the end * Order in SGG file is player1 player0 */ if ((sscanf(sz, "%*s %20lf %10d %20lf %10d", &arRating[0], &anExp[0], &arRating[1], &anExp[1])) != 4) break; for (i = 0; i < 2; ++i) { g_free(pmi->pchRating[i]); sprintf(szTemp, "%.6g (Exp %d)", arRating[i], anExp[i]); pmi->pchRating[i] = g_strdup(szTemp); } break; case 9: /* Jacoby rule */ if (!GetValue(sz, szTemp)) break; *pfJacobyRule = !strcmp(szTemp, "Yes"); break; case 10: /* automatic doubles */ if (!GetValue(sz, szTemp)) break; *pfAutoDoubles = !strcmp(szTemp, "Yes"); break; case 11: /* Crawford rule */ if (!GetValue(sz, szTemp)) break; *pfCrawfordRule = !strcmp(szTemp, "Yes"); break; case 12: case 13: /* beavers & raccons */ /* ignored as they are not used internally in GNUbg */ break; case 14: /* doubling cube */ if (!GetValue(sz, szTemp)) break; *pfCubeUse = !strcmp(szTemp, "Yes"); break; case 15: /* rated */ break; case 16: /* match length */ break; case 17: /* variant */ if (!GetValue(sz, szTemp)) break; if (!StrCaseCmp(szTemp, "HyperGammon")) *pbgv = VARIATION_HYPERGAMMON_3; else if (!StrCaseCmp(szTemp, "Nackgammon")) *pbgv = VARIATION_NACKGAMMON; else if (!StrCaseCmp(szTemp, "Backgammon")) *pbgv = VARIATION_STANDARD; else { outputl(_("Unknown variant in SGG file")); outputl(_("Please send the SGG file to bug-gnubg@gnu.org")); outputx(); g_assert_not_reached(); } break; default: g_assert_not_reached(); } } static int ImportSGG(FILE * pf, char *szFilename) { char sz[80], sz0[MAX_NAME_LEN], sz1[MAX_NAME_LEN]; int n0 = 0, n1 = 0, nLength = 0, i = 0, fCrawford = FALSE; int fCrawfordRule = TRUE; int fJacobyRule = TRUE; int fAutoDoubles = 0; int fCubeUsage = TRUE; char buf[256], *pNext, *psz; bgvariation bgv = VARIATION_STANDARD; if (!get_input_discard()) return -1; fWarned = FALSE; *sz0 = '\0'; while (fgets(buf, sizeof(buf), pf)) { psz = strstr(buf, "vs."); if (psz) { /* Found player line, avoid fscanf(%nn) as buffer may overrun */ pNext = psz + strlen("vs."); do psz--; while (isspace(*psz)); psz[1] = '\0'; psz = buf; while (isspace(*psz)) psz++; strcpy(sz0, psz); psz = pNext + strlen(pNext); do psz--; while (isspace(*psz)); psz[1] = '\0'; psz = pNext; while (isspace(*psz)) psz++; strcpy(sz1, psz); break; } } if (!*sz0) { /* No player line found */ outputerrf(_("%s: not a valid SGG file"), szFilename); return -1; } #if USE_GTK if (fX) { /* Clear record to avoid ugly updates */ GTKClearMoveRecord(); GTKFreeze(); } #endif FreeMatch(); ClearMatch(); strcpy(ap[0].szName, sz0); strcpy(ap[1].szName, sz1); while (fgets(sz, 80, pf)) { if (!ParseSGGGame(sz, &i, &n0, &n1, &fCrawford, &nLength)) break; ParseSGGOptions(sz, &mi, &fCrawfordRule, &fAutoDoubles, &fJacobyRule, &bgv, &fCubeUsage); } while (!feof(pf)) { ImportSGGGame(pf, i, nLength, n0, n1, fCrawford, fCrawfordRule, fAutoDoubles, fJacobyRule, bgv, fCubeUsage); while (fgets(sz, 80, pf)) if (!ParseSGGGame(sz, &i, &n0, &n1, &fCrawford, &nLength)) break; } UpdateSettings(); /* swap players */ CommandSwapPlayers(NULL); #if USE_GTK if (fX) { GTKThaw(); GTKSet(ap); } #endif return 0; } /* * Parse TMG files * */ static int ParseTMGOptions(const char *sz, matchinfo * pmi, int *pfCrawfordRule, int *UNUSED(pfAutoDoubles), int *pfJacobyRule, int *pnLength, bgvariation * pbgv, int *pfCubeUse) { char szTemp[80]; int i, j; static const char *aszOptions[] = { "MatchID:", "Player1:", "Player2:", "Stake:", "RakePct:", "MaxRakeAbs:", "Startdate:", "Jacoby:", "AutoDistrib:", "Beavers:", "Raccoons:", "Crawford:", "Cube:", "MaxCube:", "Length:", "MaxGames:", "Variant:", "PlayMoney:", NULL }; const char *pc; char *pc2; char szName[80]; for (i = 0, pc = aszOptions[i]; pc; ++i, pc = aszOptions[i]) if (!strncmp(sz, pc, strlen(pc))) break; switch (i) { case 0: /* MatchID */ pc = strchr(sz, ':') + 2; sprintf(szTemp, _("TMG MatchID: %s"), pc); if ((pc2 = strchr(szTemp, '\n')) != 0) *pc2 = 0; pmi->pchComment = g_strdup(szTemp); return 0; case 1: /* Player1: */ case 2: /* Player2: */ if (sscanf(sz, "Player%1d: %*d %79s %*f", &j, szName) == 2) if ((j == 1 || j == 2) && *szName) strcpy(ap[j - 1].szName, szName); return 0; case 3: /* Stake: */ /* ignore, however, we could read into pmi->szComment, but I guess * people do not want to see their stakes in, say, html export */ break; case 4: /* RakePct: */ case 5: /* MaxRakeAbs: */ case 8: /* AutoDistrib */ case 13: /* MaxCube */ case 15: /* MaxGames */ /* ignore */ return 0; case 6: /* Startdate */ if ((sscanf(sz, "Startdate: %4u-%2u-%2u", &pmi->nYear, &pmi->nMonth, &pmi->nDay)) != 3) pmi->nYear = pmi->nMonth = pmi->nDay = 0; return 0; case 7: /* Jacoby */ if ((pc = strchr(sz, ':')) == 0) return 0; *pfJacobyRule = atoi(pc + 2); return 0; case 9: /* Beavers */ break; case 10: /* Raccoons */ break; case 11: /* Crawford */ if ((pc = strchr(sz, ':')) == 0) return 0; *pfCrawfordRule = atoi(pc + 2); return 0; case 12: /* Cube */ if ((pc = strchr(sz, ':')) == 0) return 0; *pfCubeUse = atoi(pc + 2); return 0; case 14: /* Length */ if ((pc = strchr(sz, ':')) == 0) return 0; *pnLength = atoi(pc + 2); return 0; case 16: /* Variant */ if ((pc = strchr(sz, ':')) == 0) return 0; switch (atoi(pc + 2)) { case 1: *pbgv = VARIATION_STANDARD; break; case 2: *pbgv = VARIATION_NACKGAMMON; break; default: outputl(_("Unknown variation in TMG file")); outputl(_("Please send the TMG file to bug-gnubg@gnu.org")); outputx(); g_assert_not_reached(); return -1; } return 0; case 17: /* PlayMoney */ break; } /* code not reachable */ return -1; } static int ParseTMGGame(const char *sz, int *piGame, int *pn0, int *pn1, int *pfCrawford, int *post_crawford, const int nLength) { int i = sscanf(sz, "Game %10d: %10d-%10d", piGame, pn0, pn1) == 3; if (!i) return FALSE; if (nLength) { if (!*post_crawford) *post_crawford = *pfCrawford = (*pn0 == (nLength - 1)) || (*pn1 == (nLength - 1)); else *pfCrawford = FALSE; } return TRUE; } static void ImportTMGGame(FILE * pf, int i, int nLength, int n0, int n1, int fCrawford, int fCrawfordRule, int UNUSED(fAutoDoubles), int fJacobyRule, bgvariation bgv, int fCubeUsage) { char sz[1024]; char *pch; int c, anRoll[2]; moverecord *pmgi, *pmr; int j; typedef enum { TMG_ROLL = 1, TMG_AUTO_DOUBLE = 2, TMG_MOVE = 4, TMG_DOUBLE = 5, TMG_TAKE = 7, TMG_BEAVER = 8, TMG_PASS = 10, TMG_WIN_SINGLE = 14, TMG_WIN_GAMMON = 15, TMG_WIN_BACKGAMMON = 16, TMG_OUT_OF_TIME = 17, TMG_TABLE_STAKE = 19, TMG_OUT_OF_TIME_1 = 22 } tmgrecordtype; tmgrecordtype trt; InitBoard(ms.anBoard, ms.bgv); ClearMoveRecord(); ListInsert(&lMatch, plGame); pmgi = NewMoveRecord(); pmgi->mt = MOVE_GAMEINFO; pmgi->g.i = i - 1; pmgi->g.nMatch = nLength; pmgi->g.anScore[0] = n0; pmgi->g.anScore[1] = n1; pmgi->g.fCrawford = fCrawfordRule && nLength; pmgi->g.fCrawfordGame = fCrawford; pmgi->g.fJacoby = fJacobyRule && !nLength; pmgi->g.fWinner = -1; pmgi->g.nPoints = 0; pmgi->g.fResigned = FALSE; pmgi->g.nAutoDoubles = 0; /* we check for automatic doubles later */ pmgi->g.bgv = bgv; pmgi->g.fCubeUse = fCubeUsage; IniStatcontext(&pmgi->g.sc); AddMoveRecord(pmgi); anRoll[0] = 0; anRoll[1] = 0; while (fgets(sz, 1024, pf)) { /* skip white space */ for (pch = sz; isspace(*pch); ++pch); /* * is it: * -1 4 13/11 24/23 * or * Beavers: 0 */ if (isdigit(*pch) || *pch == '-') { int fPlayer = (*pch == '-'); /* player: +n for player 0, * -n for player 1 */ /* step over move number */ for (; !isspace(*pch); ++pch); for (; isspace(*pch); ++pch); /* determine record type */ trt = (tmgrecordtype) atoi(pch); /* step over record type */ for (; !isspace(*pch); ++pch); for (; isspace(*pch); ++pch); switch (trt) { case TMG_ROLL: /* roll: 1 1 21: */ if (*pch == '?') { /* no roll recorded, because the game is lost on time out */ continue; } anRoll[0] = *pch - '0'; anRoll[1] = *(pch + 1) - '0'; pmr = NewMoveRecord(); pmr->mt = MOVE_SETDICE; pmr->fPlayer = fPlayer; pmr->anDice[0] = anRoll[0]; pmr->anDice[1] = anRoll[1]; AddMoveRecord(pmr); break; case TMG_AUTO_DOUBLE: /* automatic double: 0 2 Automatic to 2 */ ++pmgi->g.nAutoDoubles; /* we've already called AddMoveRecord for pmgi, * so we manually update the cube value */ ms.nCube *= 2; break; case TMG_MOVE: /* move: 1 4 24/18 13/10 */ pmr = NewMoveRecord(); pmr->mt = MOVE_NORMAL; pmr->anDice[0] = anRoll[0]; pmr->anDice[1] = anRoll[1]; pmr->fPlayer = fPlayer; if (!strncmp(pch, "0/0", 3)) { /* See if fan is legal (i.e. no moves available) - otherwise skip */ movelist ml; if (GenerateMoves(&ml, msBoard(), pmr->anDice[0], pmr->anDice[1], FALSE) == 0) { /* fans */ AddMoveRecord(pmr); } } else if ((c = ParseMove(pch, pmr->n.anMove)) >= 0) { for (i = 0; i < (c << 1); i++) pmr->n.anMove[i]--; if (c < 4) pmr->n.anMove[c << 1] = pmr->n.anMove[(c << 1) | 1] = -1; AddMoveRecord(pmr); } else g_free(pmr); break; case TMG_DOUBLE: /* double: -7 5 Double to 2 */ case TMG_BEAVER: /* beaver: 25 8 Beaver to 8 */ pmr = NewMoveRecord(); pmr->mt = MOVE_DOUBLE; pmr->fPlayer = fPlayer; LinkToDouble(pmr); AddMoveRecord(pmr); break; case TMG_TAKE: /* take: -6 7 Take */ pmr = NewMoveRecord(); pmr->mt = MOVE_TAKE; pmr->fPlayer = fPlayer; if (!LinkToDouble(pmr)) { outputl(_("Take record found but doesn't follow a double")); g_free(pmr); return; } AddMoveRecord(pmr); break; case TMG_PASS: /* pass: 5 10 Pass */ pmr = NewMoveRecord(); pmr->mt = MOVE_DROP; pmr->fPlayer = fPlayer; if (!LinkToDouble(pmr)) { outputl(_("Drop record found but doesn't follow a double")); g_free(pmr); return; } AddMoveRecord(pmr); break; case TMG_WIN_SINGLE: /* win single: 4 14 1 thj wins 1 point */ case TMG_WIN_GAMMON: /* resign: -30 15 2 Saltyzoo7 wins 2 points */ case TMG_WIN_BACKGAMMON: /* win backgammon: ??? */ if (ms.gs == GAME_PLAYING) { /* game is in progress: the opponent resigned */ pmr = NewMoveRecord(); pmr->mt = MOVE_RESIGN; pmr->fPlayer = !fPlayer; pmr->r.nResigned = (atoi(pch) + ms.nCube - 1) / ms.nCube; /* rounding up */ AddMoveRecord(pmr); } goto finished; case TMG_TABLE_STAKE: /* Table stake: 0 19 57.68 0.00 43.57 0.00 Settlement */ /* ignore: FIXME: this should be stored in order to analyse * tables stakes correctly */ break; case TMG_OUT_OF_TIME: case TMG_OUT_OF_TIME_1: break; default: outputl(_("Please send the TMG file to bug-gnubg@gnu.org")); outputx(); g_assert_not_reached(); } } else { /* it's most likely an option: the players may enable or * disable beavers and auto doubles during play */ ParseTMGOptions(sz, &mi, &pmgi->g.fCrawford, &j, &pmgi->g.fJacoby, &j, &bgv, &fCubeUsage); } } finished: AddGame(pmgi); } static int ImportTMG(FILE * pf, const char *UNUSED(szFilename)) { int fCrawfordRule = TRUE; int fJacobyRule = TRUE; int fAutoDoubles = 0; int nLength = 0; int fCrawford = FALSE; int fCubeUsage = TRUE; int n0 = 0, n1 = 0; int i = 0, j; int post_crawford = FALSE; char sz[80]; bgvariation bgv = VARIATION_STANDARD; #if USE_GTK if (fX) { /* Clear record to avoid ugly updates */ GTKClearMoveRecord(); GTKFreeze(); } #endif FreeMatch(); ClearMatch(); /* clear matchinfo */ for (j = 0; j < 2; ++j) SetMatchInfo(&mi.pchRating[j], NULL, NULL); SetMatchInfo(&mi.pchPlace, "TrueMoneyGames", NULL); SetMatchInfo(&mi.pchEvent, NULL, NULL); SetMatchInfo(&mi.pchRound, NULL, NULL); SetMatchInfo(&mi.pchAnnotator, NULL, NULL); SetMatchInfo(&mi.pchComment, NULL, NULL); mi.nYear = mi.nMonth = mi.nDay = 0; /* search for options (until first game is found) */ while (fgets(sz, 80, pf)) { if (ParseTMGGame(sz, &i, &n0, &n1, &fCrawford, &post_crawford, nLength)) break; ParseTMGOptions(sz, &mi, &fCrawfordRule, &fAutoDoubles, &fJacobyRule, &nLength, &bgv, &fCubeUsage); } /* set remainder of match info */ while (!feof(pf)) { ImportTMGGame(pf, i, nLength, n0, n1, fCrawford, fCrawfordRule, fAutoDoubles, fJacobyRule, bgv, fCubeUsage); while (fgets(sz, 80, pf)) if (ParseTMGGame(sz, &i, &n0, &n1, &fCrawford, &post_crawford, nLength)) break; } UpdateSettings(); /* swap players */ if (ms.gs != GAME_NONE) CommandSwapPlayers(NULL); #if USE_GTK if (fX) { GTKThaw(); GTKSet(ap); } #endif return 0; } static int ParseSnowieTxt(char *sz, int *pnMatchTo, int *pfJacoby, int *UNUSED(p1), int *UNUSED(p2), int *pfTurn, char aszPlayer[2][MAX_NAME_LEN], int *pfCrawfordGame, int anScore[2], int *pnCube, int *pfCubeOwner, TanBoard anBoard, int anDice[2]) { int c; char *pc; int i, j; memset(anBoard, 0, 2 * 25 * sizeof(int)); c = 0; i = 0; pc = NextTokenGeneral(&sz, ";"); while (pc) { switch (c) { case 0: /* match length */ *pnMatchTo = atoi(pc); break; case 1: /* Jacoby rule */ *pfJacoby = atoi(pc); break; case 2: case 3: /* unknown??? */ break; case 4: /* player on roll */ *pfTurn = atoi(pc); break; case 5: case 6: /* player names */ j = *pfTurn; if (c == 6) j = !j; memset(aszPlayer[j], 0, MAX_NAME_LEN); if (*pc) g_strlcpy(aszPlayer[j], pc, MAX_NAME_LEN); else sprintf(aszPlayer[j], "Player %d", j); break; case 7: /* Crawford Game */ *pfCrawfordGame = atoi(pc); break; case 8: /* Score */ anScore[*pfTurn] = atoi(pc); break; case 9: /* Score */ anScore[!*pfTurn] = atoi(pc); break; case 10: /* cube value */ *pnCube = atoi(pc); break; case 11: /* cube owner */ j = atoi(pc); if (j == 1) *pfCubeOwner = *pfTurn; else if (j == -1) *pfCubeOwner = !*pfTurn; else *pfCubeOwner = -1; break; case 12: /* chequers on bar for player on roll */ anBoard[1][24] = abs(atoi(pc)); break; case 37: /* chequers on bar for player opponent */ anBoard[0][24] = abs(atoi(pc)); break; case 38: case 39: /* dice rolled */ anDice[c - 38] = atoi(pc); break; default: /* points */ j = atoi(pc); anBoard[j < 0][(j < 0) ? 23 - i : i] = abs(j); ++i; break; } pc = NextTokenGeneral(&sz, ";"); ++c; if (c == 40) break; } if (c != 40) return -1; return 0; } /* * Snowie .txt files * * The files are a single line with fields separated by * semicolons. Fields are numeric, except for player names. * * Field no meaning * 0 length of match (0 in money games) * 1 1 if Jacoby enabled, 0 in match play or disabled * 2 Don't know, all samples had 0. Maybe it's Nack gammon or * some other variant? * 3 Don't know. It was one in all money game samples, 0 in * match samples * 4 Player on roll 0 = 1st player * 5,6 Player names * 7 1 = Crawford game * 8,9 Scores for player 0, 1 * 10 Cube value * 11 Cube owner 1 = player on roll, 0 = centred, -1 opponent * 12 Chequers on bar for player on roll * 13..36 Points 1..24 from player on roll's point of view * 0 = empty, positive nos. for player on roll, negative * for opponent * 37 Chequers on bar for opponent * 38.39 Current roll (0,0 if not rolled) * */ static int ImportSnowieTxt(FILE * pf) { char sz[2048]; char *pc; int c; moverecord *pmr; moverecord *pmgi; int nMatchTo, fJacobyRule, fUnused1, fUnused2, fTurn, fCrawfordGame; int fCubeOwner, nCube; int anScore[2], anDice[2]; TanBoard anBoard; char aszPlayer[2][MAX_NAME_LEN]; if (!get_input_discard()) return -1; #if USE_GTK if (fX) { /* Clear record to avoid ugly updates */ GTKClearMoveRecord(); GTKFreeze(); } #endif /* * Import Snowie .txt file */ /* read file into string */ pc = sz; while ((c = fgetc(pf)) > -1) { *pc++ = (char) c; if ((pc - sz) == (sizeof(sz) - 2)) break; } *pc = 0; /* parse string */ if (ParseSnowieTxt(sz, &nMatchTo, &fJacobyRule, &fUnused1, &fUnused2, &fTurn, aszPlayer, &fCrawfordGame, anScore, &nCube, &fCubeOwner, anBoard, anDice) < 0) { outputl(_("This file is not a valid Snowie .txt file!")); return -1; } FreeMatch(); ClearMatch(); InitBoard(ms.anBoard, ms.bgv); ClearMoveRecord(); ListInsert(&lMatch, plGame); /* Game info */ pmgi = NewMoveRecord(); pmgi->mt = MOVE_GAMEINFO; pmgi->g.i = 0; pmgi->g.nMatch = nMatchTo; pmgi->g.anScore[0] = anScore[0]; pmgi->g.anScore[1] = anScore[1]; pmgi->g.fCrawford = TRUE; pmgi->g.fCrawfordGame = fCrawfordGame; pmgi->g.fJacoby = fJacobyRule; pmgi->g.fWinner = -1; pmgi->g.nPoints = 0; pmgi->g.fResigned = FALSE; pmgi->g.nAutoDoubles = 0; pmgi->g.bgv = VARIATION_STANDARD; /* assume standard backgammon */ pmgi->g.fCubeUse = TRUE; /* assume use of cube */ IniStatcontext(&pmgi->g.sc); AddMoveRecord(pmgi); ms.fTurn = ms.fMove = fTurn; for (c = 0; c < 2; ++c) strcpy(ap[c].szName, aszPlayer[c]); /* dice */ if (anDice[0]) { pmr = NewMoveRecord(); pmr->mt = MOVE_SETDICE; pmr->fPlayer = fTurn; pmr->anDice[0] = anDice[0]; pmr->anDice[1] = anDice[1]; AddMoveRecord(pmr); } /* board */ pmr = NewMoveRecord(); pmr->mt = MOVE_SETBOARD; if (!fTurn) SwapSides(anBoard); PositionKey((ConstTanBoard) anBoard, &pmr->sb.key); AddMoveRecord(pmr); /* cube value */ pmr = NewMoveRecord(); pmr->mt = MOVE_SETCUBEVAL; pmr->scv.nCube = nCube; AddMoveRecord(pmr); /* cube position */ pmr = NewMoveRecord(); pmr->mt = MOVE_SETCUBEPOS; pmr->scp.fCubeOwner = fCubeOwner; AddMoveRecord(pmr); /* update menus etc */ UpdateSettings(); #if USE_GTK if (fX) { GTKThaw(); GTKSet(ap); } #endif return 0; } static int ImportGAM(FILE * fp, char *UNUSED(szFilename)) { char *pchLeft, *pchRight, *szLine; moverecord *pmgi; int warned = -1; #if USE_GTK if (fX) { /* Clear record to avoid ugly updates */ GTKClearMoveRecord(); GTKFreeze(); } #endif FreeMatch(); ClearMatch(); while ((szLine = GetMatLine(fp)) != 0) { szLine += strspn(szLine, " \t"); if (!StrNCaseCmp(szLine, "win", 3)) continue; /* Skip this line (in between games) */ /* Read player names */ pchRight = szLine; while (*pchRight != ' ' && *pchRight != '\t') pchRight++; *pchRight = '\0'; strcpy(ap[0].szName, szLine); pchLeft = pchRight + 1; while (*pchLeft == ' ' || *pchLeft == '\t') pchLeft++; pchRight = pchLeft; while (*pchRight != ' ' && *pchRight != '\t' && *pchRight != '\n') pchRight++; *pchRight = '\0'; strcpy(ap[1].szName, pchLeft); InitBoard(ms.anBoard, ms.bgv); ClearMoveRecord(); ListInsert(&lMatch, plGame); /* Game info */ pmgi = NewMoveRecord(); pmgi->mt = MOVE_GAMEINFO; pmgi->g.i = ms.cGames; pmgi->g.nMatch = 0; pmgi->g.anScore[0] = ms.anScore[0]; pmgi->g.anScore[1] = ms.anScore[1]; pmgi->g.fCrawford = FALSE; pmgi->g.fCrawfordGame = FALSE; pmgi->g.fJacoby = FALSE; pmgi->g.fWinner = -1; pmgi->g.nPoints = 0; pmgi->g.fResigned = FALSE; pmgi->g.nAutoDoubles = 0; pmgi->g.bgv = VARIATION_STANDARD; /* assume standard backgammon */ pmgi->g.fCubeUse = TRUE; /* assume use of cube */ IniStatcontext(&pmgi->g.sc); AddMoveRecord(pmgi); /* Read game */ while ((szLine = GetMatLine(fp)) != 0) { char *pch; pchRight = pchLeft = NULL; if ((pch = strpbrk(szLine, "\n\r")) != 0) *pch = 0; if ((pchLeft = strchr(szLine, ':')) && (pchRight = strchr(pchLeft + 1, ':')) && pchRight > szLine + 3) *((pchRight -= 2) - 1) = 0; else if (strlen(szLine) > 15 && (pchRight = strstr(szLine + 15, " "))) *pchRight++ = 0; if ((pchLeft = strchr(szLine, ')')) != 0) pchLeft++; else pchLeft = szLine; ParseMatMove(pchLeft, 0, &warned); if (pchRight) ParseMatMove(pchRight, 1, &warned); if (ms.gs != GAME_PLAYING) { AddGame(pmgi); break; } } } UpdateSettings(); #if USE_GTK if (fX) { GTKThaw(); GTKSet(ap); } #endif return TRUE; } static void WritePartyGame(FILE * fp, char *gameStr, int ns) { char *move; char *data = gameStr; int moveNum = 1; int side = -1; while ((move = NextTokenGeneral(&data, "#")) != NULL) { char buf[100]; char *moveStr = NULL; side = (move[0] == '2'); if ((side == 0) || (moveNum == 1 && side == 1)) { fprintf(fp, "%3d) ", moveNum); if (moveNum == 1 && side == 1) fprintf(fp, "%28s", " "); moveNum++; } move = strchr(move, ' '); g_assert(move); if (move) { move++; if (isdigit(*move)) { char *roll; move = strchr(move, ' ') + 1; move = strchr(move, ' ') + 1; roll = move; move = strchr(move, ' '); if (move) { char *dest, *src; *move = '\0'; moveStr = move + 1; /* Change bar -> 25, off -> 0 */ src = dest = moveStr; while (*src) { if (*src == 'b') { *dest++ = '2'; *dest++ = '5'; src += 3; } else if (*src == 'o') { *dest++ = '0'; src += 3; } else { *dest = *src; dest++; src++; } } *dest = '\0'; } sprintf(buf, "%s: %s", roll, moveStr ? moveStr : ""); } else /* !isdigit(*move) */ strcpy(buf, move); /* Double/Take */ } if (side == 0) fprintf(fp, "%-30s ", buf); else fprintf(fp, "%s\n", buf); } if (side == 0 && ns < 0) fprintf(fp, "\n "); else if (side == 1 && ns > 0) fprintf(fp, "%36s", " "); fprintf(fp, "Wins %d point%s\n\n", abs(ns), (abs(ns) == 1) ? "" : "s"); } typedef struct { int s1, s2; char *gameStr; } PartyGame; static int ConvertPartyGammonFileToMat(FILE * partyFP, FILE * matFP) { PartyGame pg = { -1, -1, NULL }; int matchLen = -1; char p1[MAX_NAME_LEN] = "", p2[MAX_NAME_LEN] = ""; GList *games = NULL; char buffer[1024 * 10]; while (fgets(buffer, sizeof(buffer), partyFP) != NULL) { char *value, *key; value = buffer; key = NextTokenGeneral(&value, "="); if (key) { if (!StrNCaseCmp(key, "GAME_", strlen("GAME_"))) { key += strlen("GAME_"); do key++; while (key[-1] != '_'); if (g_ascii_iscntrl(value[strlen(value) - 1])) value[strlen(value) - 1] = '\0'; if (!StrCaseCmp(key, "PLAYER_1")) strcpy(p1, value); else if (!StrCaseCmp(key, "PLAYER_2")) strcpy(p2, value); else if (!StrCaseCmp(key, "GAMEPLAY")) pg.gameStr = g_strdup(value); else if (!StrCaseCmp(key, "SCORE1")) pg.s1 = atoi(value); else if (!StrCaseCmp(key, "SCORE2")) { PartyGame *newGame; pg.s2 = atoi(value); /* Add this game */ newGame = g_malloc(sizeof(PartyGame)); memcpy(newGame, &pg, sizeof(PartyGame)); games = g_list_append(games, newGame); } } if (!StrCaseCmp(key, "MATCHLENGTH")) matchLen = atoi(value); } } if (g_list_length(games) > 0 || matchLen == -1) { /* Write out mat file */ unsigned int i; int s1 = 0, s2 = 0; GList *pl; fprintf(matFP, " %d point match\n", matchLen); for (i = 0, pl = g_list_first(games); i < g_list_length(games); i++, pl = g_list_next(pl)) { int pts; PartyGame *pGame = (PartyGame *) (pl->data); fprintf(matFP, "\n Game %u\n", i + 1); fprintf(matFP, " %s : %d %14s %s : %d\n", p1, s1, " ", p2, s2); pts = pGame->s2 - s2 - pGame->s1 + s1; WritePartyGame(matFP, pGame->gameStr, pts); s1 = pGame->s1; s2 = pGame->s2; g_free(pGame->gameStr); g_free(pGame); } g_free(pg.gameStr); g_list_free(pl); return TRUE; } if (ferror(partyFP)) outputerr(_("File error while processing PartyGammon match")); g_free(pg.gameStr); return FALSE; } extern void CommandImportJF(char *sz) { FILE *pf; sz = NextToken(&sz); if (!sz || !*sz) { outputl(_("You must specify a position file to import (see `help " "import pos').")); return; } if ((pf = g_fopen(sz, "rb"))) { int rc = ImportJF(pf, sz); fclose(pf); if (rc) /* no file imported */ return; setDefaultFileName(sz); if (fUseKeyNames) SmartSit(); } else outputerr(sz); ShowBoard(); } extern void CommandImportMat(char *sz) { FILE *pf; sz = NextToken(&sz); if (!sz || !*sz) { outputl(_("You must specify a match file to import (see `help " "import mat').")); return; } if ((pf = g_fopen(sz, "r")) != 0) { int rc = ImportMat(pf, sz); fclose(pf); if (rc) /* no file imported */ return; setDefaultFileName(sz); if (fUseKeyNames) SmartSit(); if (fGotoFirstGame) CommandFirstGame(NULL); } else outputerr(sz); } extern void CommandImportOldmoves(char *sz) { FILE *pf; sz = NextToken(&sz); if (!sz || !*sz) { outputl(_("You must specify an oldmoves file to import (see `help " "import oldmoves').")); return; } if ((pf = g_fopen(sz, "r")) != 0) { int rc = ImportOldmoves(pf, sz); fclose(pf); if (rc) /* no file imported */ return; setDefaultFileName(sz); if (fUseKeyNames) SmartSit(); if (fGotoFirstGame) CommandFirstGame(NULL); } else outputerr(sz); } extern void CommandImportSGG(char *sz) { FILE *pf; sz = NextToken(&sz); if (!sz || !*sz) { outputl(_("You must specify an SGG file to import (see `help " "import sgg').")); return; } if ((pf = g_fopen(sz, "r")) != 0) { int rc = ImportSGG(pf, sz); fclose(pf); if (rc) /* no file imported */ return; setDefaultFileName(sz); if (fUseKeyNames) SmartSit(); if (fGotoFirstGame) CommandFirstGame(NULL); } else outputerr(sz); } extern void CommandImportTMG(char *sz) { FILE *pf; sz = NextToken(&sz); if (!sz || !*sz) { outputl(_("You must specify an TMG file to import (see `help " "import tmg').")); return; } if ((pf = g_fopen(sz, "r")) != 0) { int rc = ImportTMG(pf, sz); fclose(pf); if (rc) /* no file imported */ return; setDefaultFileName(sz); if (fUseKeyNames) SmartSit(); if (fGotoFirstGame) CommandFirstGame(NULL); } else outputerr(sz); } extern void CommandImportSnowieTxt(char *sz) { FILE *pf; sz = NextToken(&sz); if (!sz || !*sz) { outputl(_("You must specify a Snowie Text file to import (see `help " "import snowietxt').")); return; } if ((pf = g_fopen(sz, "r")) != 0) { int rc = ImportSnowieTxt(pf); fclose(pf); if (rc) /* no file imported */ return; setDefaultFileName(sz); if (fUseKeyNames) SmartSit(); } else outputerr(sz); } extern void CommandImportEmpire(char *sz) { FILE *pf; sz = NextToken(&sz); if (!sz || !*sz) { outputl(_("You must specify a GammonEmpire file to import (see `help " "import empire').")); return; } if ((pf = g_fopen(sz, "r")) != 0) { int res = ImportGAM(pf, sz); fclose(pf); if (!res) /* no file imported */ return; setDefaultFileName(sz); if (fUseKeyNames) SmartSit(); } else outputerr(sz); } extern void CommandImportParty(char *sz) { FILE *gamf, *matf; char *tmpfile; sz = NextToken(&sz); if (!sz || !*sz) { outputl(_("You must specify a PartyGammon file to import (see `help " "import party').")); return; } if ((gamf = g_fopen(sz, "r")) == NULL) { outputerr(sz); return; } tmpfile = g_strdup_printf("%s.mat", sz); if (g_file_test(tmpfile, G_FILE_TEST_EXISTS)) { outputerrf(_("%s already exists. Cannot import %s\n"), tmpfile, sz); g_free(tmpfile); fclose(gamf); return; } if ((matf = g_fopen(tmpfile, "w")) == NULL) { outputerr(tmpfile); g_free(tmpfile); fclose(gamf); return; } if (ConvertPartyGammonFileToMat(gamf, matf)) { FILE *pf; if ((pf = g_fopen(tmpfile, "r")) != NULL) { int rc = ImportMat(pf, tmpfile); fclose(pf); if (!rc) { setDefaultFileName(tmpfile); if (fUseKeyNames) SmartSit(); if (fGotoFirstGame) CommandFirstGame(NULL); } } else outputerr(tmpfile); } else outputerrf(_("Failed to convert BGRoom gam file to mat\n")); g_unlink(tmpfile); g_free(tmpfile); fclose(matf); fclose(gamf); } extern void CommandImportAuto(char *sz) { FilePreviewData *fdp; gchar *cmd; /* Careful! NextToken breaks the Windows file/directory names with spaces! */ //outputerrf("before=%s\n", sz); sz = NextToken(&sz); //outputerrf("after=%s\n", sz); if (!sz || !*sz) { outputerrf(_("You must specify a file to import (see `help " "import auto').")); return; } if (!g_file_test(sz, G_FILE_TEST_EXISTS)) { outputerrf(_("The file `%s' doesn't exist"), sz); return; } fdp = ReadFilePreview(sz); if (!fdp) { outputerrf(_("`%s' is not a backgammon file"), sz); g_free(fdp); return; } else if (fdp->type == N_IMPORT_TYPES) { outputf(_("The format of '%s' is not recognized"), sz); g_free(fdp); return; } else if (fdp->type == IMPORT_SGF) cmd = g_strdup_printf("load match \"%s\"", sz); else cmd = g_strdup_printf("import %s \"%s\"", import_format[fdp->type].clname, sz); HandleCommand(cmd, acTop); g_free(cmd); g_free(fdp); } #define BGR_STRING "BGF version" static int moveNumBGR; static void OutputMove(FILE * fpOut, int side, const char *outBuf) { if ((side == 0) || (moveNumBGR == 1)) { fprintf(fpOut, "%3d) ", moveNumBGR); if (side == 1) fprintf(fpOut, "%28s", " "); moveNumBGR++; } if (side == 0) fprintf(fpOut, "%-27s ", outBuf); else fprintf(fpOut, "%s\n", outBuf); } static int ConvertBGRoomFileToMat(FILE * bgrFP, FILE * matFP) { char *player1; char *player2; int p1Score = 0, p2Score = 0; int dice1, dice2; int doubled, stake, side = 0; int gameCount = 0, moveCount; char buffer[1024 * 4]; player1 = g_malloc(sizeof(char) * 128); player2 = g_malloc(sizeof(char) * 128); while (fgets(buffer, sizeof(buffer), bgrFP) != NULL) { if (strncmp(buffer, BGR_STRING, strlen(BGR_STRING)) == 0) break; } if (ferror(bgrFP)) { outputerr(_("Error opening BGRoom file")); g_free(player2); g_free(player1); return FALSE; } if (feof(bgrFP)) { g_free(player2); g_free(player1); return FALSE; } while (!feof(bgrFP)) { char *value, *ptr, *tempptr; gboolean fSwap; do { if (fgets(buffer, sizeof(buffer), bgrFP) == NULL) { if (ferror(bgrFP)) outputerr(_("Error reading BGRoom file")); g_free(player2); g_free(player1); return FALSE; } if (strstr(buffer, "Win the Match")) goto done; } while (strncmp(buffer, "Game ", strlen("Game "))); value = buffer + strlen("Game "); gameCount++; ptr = NextTokenGeneral(&value, " "); if (atoi(ptr) != gameCount) { outputerrf(_("Error parsing BGRoom file. Wrong game count: expected %d, got %s"), gameCount, ptr); g_assert_not_reached(); } ptr = NextTokenGeneral(&value, " "); /* Skip '-' */ if (strcmp(ptr, "-")) { outputerrf(_("Error parsing BGRoom file. Expected '-', got '%s'"), ptr); g_assert_not_reached(); } strcpy(player1, NextTokenGeneral(&value, " ")); ptr = NextTokenGeneral(&value, " "); /* Skip '(0|X)' */ fSwap = *(ptr + 1) == '0'; ptr = NextTokenGeneral(&value, " "); /* Skip 'vs.' */ if (strcmp(ptr, "vs.")) { outputerrf(_("Error parsing BGRoom file. Expected 'vs.', got '%s'"), ptr); g_assert_not_reached(); } strcpy(player2, NextTokenGeneral(&value, " ")); if (fSwap) { tempptr = player1; player1 = player2; player2 = tempptr; } if (fgets(buffer, sizeof(buffer), bgrFP) == NULL) { if (ferror(bgrFP)) outputerr(_("Error reading BGRoom file")); g_free(player2); g_free(player1); return FALSE; } value = buffer; ptr = NextTokenGeneral(&value, " "); if (strcmp(ptr, "Single")) { outputerrf(_("Error parsing BGRoom file. Expected 'Single', got '%s'"), ptr); g_assert_not_reached(); } fprintf(matFP, " 0 point match\n\n Game %d\n %s : %-22d %s : %d\n", gameCount, player1, p1Score, player2, p2Score); /* Game Moves */ moveCount = 0; moveNumBGR = 1; doubled = FALSE; stake = 1; while (!feof(bgrFP)) { char outBuf[100]; if (fgets(buffer, sizeof(buffer), bgrFP) == NULL) { if (ferror(bgrFP)) outputerr(_("Error reading BGRoom file")); g_free(player2); g_free(player1); return FALSE; } while (buffer[strlen(buffer) - 1] == '\n' || buffer[strlen(buffer) - 1] == '\r') buffer[strlen(buffer) - 1] = '\0'; if (!*buffer) { if (doubled) { /* Drop */ side = !side; OutputMove(matFP, side, " Drops"); } if (side) fprintf(matFP, "%36s", ""); fprintf(matFP, " Wins %d points\n", stake); break; /* end of game */ } moveCount++; if (doubled) { /* Taken, peek ahead to see if it is a beaver */ if (strcmp(value, "Beaver")) { stake *= 2; OutputMove(matFP, !side, " Takes"); } else { stake *= 4; sprintf(outBuf, " Beavers => %d", stake); OutputMove(matFP, !side, outBuf); } side = !side; doubled = FALSE; } value = buffer; ptr = NextTokenGeneral(&value, "."); if (atoi(ptr) != moveCount) { outputerrf(_("Error parsing BGroom file. Wrong move count: expected %d, got %s"), moveCount, ptr); g_assert_not_reached(); } ptr = NextTokenGeneral(&value, ":"); if (!strcmp(ptr, "X")) side = 0; else if (!strcmp(ptr, "0")) side = 1; else { outputf(_("Unrecognised data in file: %s"), ptr); continue; } if (!strcmp(value, "Double")) { doubled = TRUE; sprintf(outBuf, " Doubles => %d", stake * 2); } else if (!strcmp(value, "Beaver")) { /* already handled above */ continue; } else { g_assert(*value == '('); value++; ptr = NextTokenGeneral(&value, " "); dice1 = atoi(ptr); ptr = NextTokenGeneral(&value, ")"); dice2 = atoi(ptr); if (!strcmp(value, "can't move")) value[0] = '\0'; sprintf(outBuf, "%d%d: %s", dice1, dice2, value); } OutputMove(matFP, side, outBuf); } } done: g_free(player2); g_free(player1); return TRUE; } extern void CommandImportBGRoom(char *sz) { FILE *gamf, *matf; char *matfile; sz = NextToken(&sz); if (!sz || !*sz) { outputl(_("You must specify a BGRoom file to import (see `help " "import bgroom').")); return; } if ((gamf = g_fopen(sz, "r")) == NULL) { outputerr(sz); return; } matfile = g_strdup_printf("%s.mat", sz); if ((matf = g_fopen(matfile, "w")) == NULL) { outputerr(matfile); g_free(matfile); fclose(gamf); return; } if (ConvertBGRoomFileToMat(gamf, matf)) { FILE *pf; if ((pf = g_fopen(matfile, "r")) != NULL) { int rc = ImportMat(pf, matfile); fclose(pf); if (!rc) { setDefaultFileName(matfile); if (fUseKeyNames) SmartSit(); if (fGotoFirstGame) CommandFirstGame(NULL); } } else outputerr(matfile); } else outputerrf(_("Failed to convert BGRoom gam file to mat\n")); g_unlink(matfile); g_free(matfile); fclose(matf); fclose(gamf); }