paulo@0: /* Replay functionality for LOCKJAW, an implementation of the Soviet Mind Game paulo@0: paulo@0: Copyright (C) 2006-2008 Damian Yerrick paulo@0: paulo@0: This work is free software; you can redistribute it and/or modify paulo@0: it under the terms of the GNU General Public License as published by paulo@0: the Free Software Foundation; either version 2 of the License, or paulo@0: (at your option) any later version. paulo@0: paulo@0: This program is distributed in the hope that it will be useful, paulo@0: but WITHOUT ANY WARRANTY; without even the implied warranty of paulo@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the paulo@0: GNU General Public License for more details. paulo@0: paulo@0: You should have received a copy of the GNU General Public License paulo@0: along with this program; if not, write to the Free Software paulo@0: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA paulo@0: paulo@0: Original game concept and design by Alexey Pajitnov. paulo@0: The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, paulo@0: or The Tetris Company LLC. paulo@0: paulo@0: */ paulo@0: paulo@0: #include "ljreplay.h" paulo@0: #include paulo@0: paulo@0: #define FORMAT_VERSION 0x20080525 paulo@0: #define LAST_FORMAT_VERSION 20080420 paulo@0: #define DEBUG_OFFSETS 0 paulo@0: paulo@0: #if DEBUG_OFFSETS paulo@0: #include paulo@0: #endif paulo@0: paulo@0: struct LJReplayFrame { paulo@0: char length; paulo@0: char reserved; paulo@0: unsigned short keys; paulo@0: LJInput x; paulo@0: }; paulo@0: paulo@0: struct LJReplay { paulo@0: FILE *file; paulo@0: }; paulo@0: paulo@0: static void fput32(unsigned int src, FILE *dst) { paulo@0: fputc(src >> 24, dst); paulo@0: fputc(src >> 16, dst); paulo@0: fputc(src >> 8, dst); paulo@0: fputc(src, dst); paulo@0: } paulo@0: paulo@0: static unsigned int fget32(FILE *src) { paulo@0: int c0 = fgetc(src) & 0xFF; paulo@0: c0 = (c0 << 8) | (fgetc(src) & 0xFF); paulo@0: c0 = (c0 << 8) | (fgetc(src) & 0xFF); paulo@0: c0 = (c0 << 8) | (fgetc(src) & 0xFF); paulo@0: return c0; paulo@0: } paulo@0: static void fput16(unsigned int src, FILE *dst) { paulo@0: fputc(src >> 8, dst); paulo@0: fputc(src, dst); paulo@0: } paulo@0: paulo@0: static unsigned int fget16(FILE *src) { paulo@0: int c0 = fgetc(src) & 0xFF; paulo@0: c0 = (c0 << 8) | (fgetc(src) & 0xFF); paulo@0: return c0; paulo@0: } paulo@0: paulo@0: static void LJField_serialize(const LJField *p, FILE *fp) { paulo@0: fput32(FORMAT_VERSION, fp); paulo@0: paulo@0: for (int y = 0; y < LJ_PF_HT; ++y) { paulo@0: for (int x = 0; x < LJ_PF_WID; ++x) { paulo@0: fputc(p->b[y][x], fp); paulo@0: } paulo@0: } paulo@0: for (int y = 0; y < LJ_PF_HT; ++y) { paulo@0: for (int x = 0; x < LJ_PF_WID; ++x) { paulo@0: fputc(p->c[y][x], fp); paulo@0: } paulo@0: } paulo@0: fput32(p->clearedLines, fp); paulo@0: fput32(p->sounds, fp); paulo@0: fput32(p->tempRows, fp); paulo@0: for (int y = 0; y < 1 + LJ_NEXT_PIECES; ++y) { paulo@0: fputc(p->curPiece[y], fp); paulo@0: } paulo@0: for (int y = 0; y < 2 * MAX_BAG_LEN; ++y) { paulo@0: fputc(p->permuPiece[y], fp); paulo@0: } paulo@0: fputc(p->permuPhase, fp); paulo@0: for (int y = 0; y < LJ_MAX_LINES_PER_PIECE; ++y) { paulo@0: fput16(p->nLineClears[y], fp); paulo@0: } paulo@0: fput32(p->y, fp); paulo@0: fputc(p->state, fp); paulo@0: fputc(p->stateTime, fp); paulo@0: fputc(p->theta, fp); paulo@0: fputc(p->x, fp); paulo@0: fputc(p->hardDropY, fp); paulo@0: fputc(p->alreadyHeld, fp); paulo@0: fputc(p->isSpin, fp); paulo@0: fputc(p->nLinesThisPiece, fp); paulo@0: fputc(p->canRotate, fp); paulo@0: paulo@0: #if DEBUG_OFFSETS paulo@0: allegro_message("before score, offset = %u\n", (unsigned int)ftell(fp)); paulo@0: #endif paulo@0: fput32(p->score, fp); paulo@0: fput32(p->lines, fp); paulo@0: fput32(p->gameTime, fp); paulo@0: fput32(p->activeTime, fp); paulo@0: fput16(p->holdPiece, fp); paulo@0: fputc(p->chain, fp); paulo@0: fputc(p->garbage, fp); paulo@0: fputc(p->garbageX, fp); paulo@0: fput16(p->nPieces, fp); paulo@0: fput16(p->outGarbage, fp); paulo@0: paulo@0: fputc(p->gimmick, fp); paulo@0: fput32(p->speedState.level, fp); paulo@0: fput32(p->bpmCounter, fp); paulo@0: fput32(p->speedupCounter, fp); paulo@0: fput32(p->goalCount, fp); paulo@0: fput32(p->seed, fp); paulo@0: fput32(p->speed.gravity, fp); paulo@0: fputc(p->speedState.curve, fp); paulo@0: fputc(p->goalType, fp); paulo@0: fputc(p->speed.entryDelay, fp); paulo@0: fputc(p->areStyle, fp); paulo@0: fputc(p->lockReset, fp); paulo@0: fputc(p->speed.lockDelay, fp); paulo@0: fputc(p->speed.lineDelay, fp); paulo@0: fputc(p->ceiling, fp); paulo@0: fputc(p->enterAbove, fp); paulo@0: fputc(p->leftWall, fp); paulo@0: fputc(p->rightWall, fp); paulo@0: fputc(p->pieceSet, fp); paulo@0: fputc(p->randomizer, fp); paulo@0: fputc(p->rotationSystem, fp); paulo@0: fputc(p->garbageRandomness, fp); paulo@0: fputc(p->tSpinAlgo, fp); paulo@0: fputc(p->clearGravity, fp); paulo@0: fputc(p->gluing, fp); paulo@0: fputc(p->scoreStyle, fp); paulo@0: fputc(p->setLockDelay, fp); paulo@0: fputc(p->upwardKicks, fp); paulo@0: fputc(p->maxUpwardKicks, fp); paulo@0: fputc(p->setLineDelay, fp); paulo@0: fputc(p->garbageStyle, fp); paulo@0: fputc(p->holdStyle, fp); paulo@0: fputc(p->bottomBlocks, fp); paulo@0: paulo@0: fput16(p->multisquares, fp); paulo@0: fput16(p->monosquares, fp); paulo@0: paulo@0: #if DEBUG_OFFSETS paulo@0: allegro_message("final offset = %u\n", (unsigned int)ftell(fp)); paulo@0: #endif paulo@0: } paulo@0: paulo@0: static int LJField_deserialize(LJField *p, FILE *fp) { paulo@0: int format = fget32(fp); paulo@0: if (format != FORMAT_VERSION paulo@0: && format != LAST_FORMAT_VERSION) { paulo@0: return -1; paulo@0: } paulo@0: paulo@0: for (int y = 0; y < LJ_PF_HT; ++y) { paulo@0: for (int x = 0; x < LJ_PF_WID; ++x) { paulo@0: p->b[y][x] = fgetc(fp); paulo@0: } paulo@0: } paulo@0: for (int y = 0; y < LJ_PF_HT; ++y) { paulo@0: for (int x = 0; x < LJ_PF_WID; ++x) { paulo@0: p->c[y][x] = fgetc(fp); paulo@0: } paulo@0: } paulo@0: p->clearedLines = fget32(fp); paulo@0: p->sounds = fget32(fp); paulo@0: p->tempRows = fget32(fp); paulo@0: for (int y = 0; y < 1 + LJ_NEXT_PIECES; ++y) { paulo@0: p->curPiece[y] = fgetc(fp); paulo@0: } paulo@0: for (int y = 0; y < 2 * MAX_BAG_LEN; ++y) { paulo@0: p->permuPiece[y] = fgetc(fp); paulo@0: } paulo@0: p->permuPhase = fgetc(fp); paulo@0: for (int y = 0; y < LJ_MAX_LINES_PER_PIECE; ++y) { paulo@0: p->nLineClears[y] = fget16(fp); paulo@0: } paulo@0: p->y = fget32(fp); paulo@0: p->state = fgetc(fp); paulo@0: p->stateTime = fgetc(fp); paulo@0: p->theta = fgetc(fp); paulo@0: p->x = fgetc(fp); paulo@0: p->hardDropY = fgetc(fp); paulo@0: p->alreadyHeld = fgetc(fp); paulo@0: p->isSpin = fgetc(fp); paulo@0: p->nLinesThisPiece = fgetc(fp); paulo@0: p->canRotate = fgetc(fp); paulo@0: paulo@0: #if DEBUG_OFFSETS paulo@0: allegro_message("before score, offset = %u\n", (unsigned int)ftell(fp)); paulo@0: #endif paulo@0: p->score = fget32(fp); paulo@0: p->lines = fget32(fp); paulo@0: p->gameTime = fget32(fp); paulo@0: p->activeTime = fget32(fp); paulo@0: p->holdPiece = fget16(fp); paulo@0: p->chain = fgetc(fp); paulo@0: p->garbage = fgetc(fp); paulo@0: p->garbageX = fgetc(fp); paulo@0: p->nPieces = fget16(fp); paulo@0: p->outGarbage = fget16(fp); paulo@0: paulo@0: p->gimmick = fgetc(fp); paulo@0: p->speedState.level = fget32(fp); paulo@0: p->bpmCounter = fget32(fp); paulo@0: p->speedupCounter = fget32(fp); paulo@0: p->goalCount = fget32(fp); paulo@0: p->seed = fget32(fp); paulo@0: p->speed.gravity = fget32(fp); paulo@0: p->speedState.curve = fgetc(fp); paulo@0: p->goalType = fgetc(fp); paulo@0: p->speed.entryDelay = fgetc(fp); paulo@0: p->areStyle = fgetc(fp); paulo@0: p->lockReset = fgetc(fp); paulo@0: p->speed.lockDelay = fgetc(fp); paulo@0: p->speed.lineDelay = fgetc(fp); paulo@0: p->ceiling = fgetc(fp); paulo@0: p->enterAbove = fgetc(fp); paulo@0: p->leftWall = fgetc(fp); paulo@0: p->rightWall = fgetc(fp); paulo@0: p->pieceSet = fgetc(fp); paulo@0: p->randomizer = fgetc(fp); paulo@0: p->rotationSystem = fgetc(fp); paulo@0: p->garbageRandomness = fgetc(fp); paulo@0: p->tSpinAlgo = fgetc(fp); paulo@0: p->clearGravity = fgetc(fp); paulo@0: p->gluing = fgetc(fp); paulo@0: p->scoreStyle = fgetc(fp); paulo@0: p->setLockDelay = fgetc(fp); paulo@0: p->upwardKicks = fgetc(fp); paulo@0: p->maxUpwardKicks = fgetc(fp); paulo@0: p->setLineDelay = fgetc(fp); paulo@0: p->garbageStyle = fgetc(fp); paulo@0: p->holdStyle = fgetc(fp); paulo@0: p->bottomBlocks = fgetc(fp); paulo@0: paulo@0: if (format == FORMAT_VERSION) { paulo@0: p->multisquares = fget16(fp); paulo@0: p->monosquares = fget16(fp); paulo@0: } paulo@0: #if DEBUG_OFFSETS paulo@0: allegro_message("final offset = %u\n", (unsigned int)ftell(fp)); paulo@0: #endif paulo@0: return 0; paulo@0: } paulo@0: paulo@0: paulo@0: LJReplay *newReplay(const char *filename, LJField *p) { paulo@0: LJReplay *r = malloc(sizeof(struct LJReplay)); paulo@0: paulo@0: if (!r) { paulo@0: return NULL; paulo@0: } paulo@0: r->file = fopen(filename, "wb"); paulo@0: if (!r->file) { paulo@0: free(r); paulo@0: return NULL; paulo@0: } paulo@0: LJField_serialize(p, r->file); paulo@0: return r; paulo@0: } paulo@0: paulo@0: void replayRecord(LJReplay *r, LJBits keys, const LJInput *in) { paulo@0: fputc(0, r->file); paulo@0: fputc(0, r->file); paulo@0: fputc(keys >> 8, r->file); paulo@0: fputc(keys, r->file); paulo@0: fputc(in->rotation, r->file); paulo@0: fputc(in->movement, r->file); paulo@0: fputc(in->gravity, r->file); paulo@0: fputc(in->other, r->file); paulo@0: } paulo@0: paulo@0: LJReplay *openReplay(const char *filename, LJField *p) { paulo@0: LJReplay *r = malloc(sizeof(struct LJReplay)); paulo@0: paulo@0: if (!r) { paulo@0: return NULL; paulo@0: } paulo@0: r->file = fopen(filename, "rb"); paulo@0: if (!r->file) { paulo@0: free(r); paulo@0: return NULL; paulo@0: } paulo@0: paulo@0: /* This deserialization is still NOT robust. */ paulo@0: if (LJField_deserialize(p, r->file) < 0) { paulo@0: fclose(r->file); paulo@0: free(r); paulo@0: return 0; paulo@0: } paulo@0: return r; paulo@0: } paulo@0: paulo@0: int getReplayFrame(LJReplay *r, LJInput *d) { paulo@0: fgetc(r->file); paulo@0: fgetc(r->file); paulo@0: int keys = fgetc(r->file); paulo@0: paulo@0: if (keys == EOF) { paulo@0: return LJREPLAY_EOF; paulo@0: } paulo@0: keys = (keys << 8 & 0xFF) | (fgetc(r->file) & 0xFF); paulo@0: d->rotation = fgetc(r->file); paulo@0: d->movement = fgetc(r->file); paulo@0: d->gravity = fgetc(r->file); paulo@0: d->other = fgetc(r->file); paulo@0: return keys; paulo@0: } paulo@0: paulo@0: void replayClose(LJReplay *r) { paulo@0: if (r) { paulo@0: if (r->file) { paulo@0: fclose(r->file); paulo@0: } paulo@0: free(r); paulo@0: } paulo@0: }