paulo@0: /* Header file for the engine of LOCKJAW, an implementation of the Soviet Mind Game paulo@0: paulo@0: Copyright (C) 2006 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: #ifndef LJ_H paulo@0: #define LJ_H paulo@0: paulo@0: #include paulo@0: #include "ljtypes.h" paulo@0: paulo@0: enum { paulo@0: CONNECT_U = 0x01, paulo@0: CONNECT_R = 0x02, paulo@0: CONNECT_D = 0x04, paulo@0: CONNECT_L = 0x08, paulo@0: CONNECT_UD = CONNECT_U | CONNECT_D, paulo@0: CONNECT_UL = CONNECT_U | CONNECT_L, paulo@0: CONNECT_DL = CONNECT_D | CONNECT_L, paulo@0: CONNECT_UR = CONNECT_U | CONNECT_R, paulo@0: CONNECT_DR = CONNECT_D | CONNECT_R, paulo@0: CONNECT_LR = CONNECT_L | CONNECT_R, paulo@0: CONNECT_UDL = CONNECT_UD | CONNECT_L, paulo@0: CONNECT_UDR = CONNECT_UD | CONNECT_R, paulo@0: CONNECT_ULR = CONNECT_UL | CONNECT_R, paulo@0: CONNECT_DLR = CONNECT_DL | CONNECT_R, paulo@0: CONNECT_UDLR = CONNECT_UD | CONNECT_LR, paulo@0: CONNECT_MASK = CONNECT_UDLR, paulo@0: COLOR_MASK = 0xF0 paulo@0: }; paulo@0: paulo@0: typedef unsigned char LJBlock; paulo@0: paulo@0: typedef enum LJState { paulo@0: LJS_INACTIVE, paulo@0: LJS_NEW_PIECE, /* delay is ARE */ paulo@0: LJS_FALLING, /* delay is fall delay; NOT IMPLEMENTED */ paulo@0: LJS_LANDED, /* delay is lock delay */ paulo@0: LJS_LINES, /* delay is line delay */ paulo@0: LJS_LINES_FALLING, /* delay is fall delay per line; NOT IMPLEMENTED */ paulo@0: LJS_GAMEOVER, /* game over animation */ paulo@0: LJS_EXPLODE /* used for bombliss extension */ paulo@0: } LJState; paulo@0: paulo@0: // for other paulo@0: enum { paulo@0: LJI_HOLD = 0x01, paulo@0: LJI_LOCK = 0x02 paulo@0: }; paulo@0: paulo@0: // for dirty bits paulo@0: enum { paulo@0: LJ_DIRTY_NEXT = 0x01000000, paulo@0: LJ_DIRTY_SCORE = 0x02000000 paulo@0: }; paulo@0: paulo@0: // for gimmick (game mode) paulo@0: enum { paulo@0: LJGM_ATYPE, // marathon paulo@0: LJGM_BTYPE, // 40 lines paulo@0: LJGM_ULTRA, // 180 seconds paulo@0: LJGM_DRILL, // clear bottom line paulo@0: LJGM_ITEMS, // no rotation + no next + fast drop + garbage + banana paulo@0: LJGM_BABY, // 300 keypresses paulo@0: LJGM_N_GIMMICKS paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJP_I = 0, paulo@0: LJP_J, paulo@0: LJP_L, paulo@0: LJP_O, paulo@0: LJP_S, paulo@0: LJP_T, paulo@0: LJP_Z, paulo@0: LJP_I2, paulo@0: LJP_I3, paulo@0: LJP_L3, paulo@0: LJP_GARBAGE = 7, paulo@0: LJP_MASK = 0x0F paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJSND_ROTATE = 0x0001, paulo@0: LJSND_SHIFT = 0x0002, paulo@0: LJSND_DROP = 0x0004, paulo@0: LJSND_LAND = 0x0008, paulo@0: LJSND_LOCK = 0x0010, paulo@0: LJSND_LINE = 0x0020, // a line was scored paulo@0: LJSND_SETB2B = 0x0040, // this line is b2b worthy (tetris or t-spin) paulo@0: LJSND_B2B = 0x0080, // this line AND last line were b2b worthy paulo@0: LJSND_SPAWN = 0x0100, // next piece was moved up paulo@0: LJSND_HOLD = 0x0200, // hold piece was activated paulo@0: LJSND_IRS = 0x0400, // initial rotation: spawn last frame and rotation this frame paulo@0: LJSND_SQUARE = 0x0800, // formed 4x4 square paulo@0: LJSND_COUNTDOWN = 0x4000, // countdown value changed paulo@0: LJSND_SECTIONUP = 0x8000, // section increased paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJRAND_PURE, paulo@0: LJRAND_BAG, paulo@0: LJRAND_BAGPLUS1, paulo@0: LJRAND_2BAG, paulo@0: LJRAND_HIST_INF, paulo@0: LJRAND_HIST_6, paulo@0: LJRAND_N_RANDS paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJRAND_4BLK, paulo@0: LJRAND_JLOSTZ, paulo@0: LJRAND_SZ, paulo@0: LJRAND_I, paulo@0: LJRAND_234BLK, paulo@0: LJRAND_T, paulo@0: LJRAND_N_PIECE_SETS paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJTS_OFF = 0, paulo@0: LJTS_TNT, paulo@0: LJTS_TDS, paulo@0: LJTS_TDS_NO_KICK, paulo@0: LJTS_N_ALGOS paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJSPD_ZERO = 0, paulo@0: LJSPD_RHYTHMZERO, paulo@0: LJSPD_EXP, paulo@0: LJSPD_RHYTHM, paulo@0: LJSPD_TGM, paulo@0: LJSPD_DEATH, paulo@0: LJSPD_DEATH300, paulo@0: LJSPD_NES, paulo@0: LJSPD_GB, paulo@0: LJSPD_GBHEART, paulo@0: LJSPD_N_CURVES paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJLOCK_NOW = 0, paulo@0: LJLOCK_SPAWN, paulo@0: LJLOCK_STEP, paulo@0: LJLOCK_MOVE, paulo@0: LJLOCK_N_STYLES paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJGRAV_NAIVE = 0, paulo@0: LJGRAV_STICKY, paulo@0: LJGRAV_STICKY_BY_COLOR, paulo@0: LJGRAV_CASCADE, paulo@0: LJGRAV_N_ALGOS paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJSCORE_LJ, paulo@0: LJSCORE_TNT64, paulo@0: LJSCORE_HOTLINE, paulo@0: LJSCORE_TDS, paulo@0: LJSCORE_NES, paulo@0: LJSCORE_LJ_NERF, paulo@0: LJSCORE_N_STYLES, paulo@0: LJSCORE_WORLDS, paulo@0: LJSCORE_TGM1, paulo@0: LJSCORE_IPOD paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJDROP_NOSCORE, paulo@0: LJDROP_NES, paulo@0: LJDROP_1CELL, paulo@0: LJDROP_1S_2H, paulo@0: LJDROP_N_STYLES paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJGARBAGE_NONE, paulo@0: LJGARBAGE_1, paulo@0: LJGARBAGE_2, paulo@0: LJGARBAGE_3, paulo@0: LJGARBAGE_4, paulo@0: LJGARBAGE_HRDERBY, paulo@0: LJGARBAGE_DRILL, paulo@0: LJGARBAGE_ZIGZAG, paulo@0: paulo@0: LJGARBAGE_N_STYLES paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJHOLD_NONE, paulo@0: LJHOLD_EMPTY, paulo@0: LJHOLD_TNT, paulo@0: LJHOLD_TO_NEXT, paulo@0: LJHOLD_N_STYLES paulo@0: }; paulo@0: paulo@0: enum { paulo@0: LJGLUING_NONE, paulo@0: LJGLUING_SQUARE, paulo@0: LJGLUING_STICKY, paulo@0: LJGLUING_STICKY_BY_COLOR, paulo@0: LJGLUING_N_STYLES paulo@0: }; paulo@0: paulo@0: // Guideline says 10 wide, but we want to support tetrinet mode paulo@0: #define LJ_PF_WID ((size_t)12) paulo@0: #define LJ_SPAWN_X ((LJ_PF_WID - 3) / 2) paulo@0: #define LJ_PF_HT ((size_t)24) paulo@0: #define LJ_PF_VIS_HT ((size_t)20) paulo@0: #define LJ_NEXT_PIECES 8 paulo@0: #define LJ_MAX_LINES_PER_PIECE 8 paulo@0: paulo@0: typedef struct LJBlkSpec paulo@0: { paulo@0: signed char x, y, conn, reserved; paulo@0: } LJBlkSpec; paulo@0: paulo@0: typedef struct LJInput { paulo@0: signed char rotation, movement; paulo@0: unsigned char gravity; /* 5.3 format */ paulo@0: unsigned char other; paulo@0: } LJInput; paulo@0: paulo@0: typedef struct LJSpeedSetting { paulo@0: LJFixed gravity; paulo@0: unsigned char entryDelay, dasDelay, lockDelay, lineDelay; paulo@0: } LJSpeedSetting; paulo@0: paulo@0: typedef struct SpeedStateBase { paulo@0: unsigned char curve, section; paulo@0: unsigned char pad[2]; paulo@0: signed int level; paulo@0: } SpeedStateBase; paulo@0: paulo@0: #define MAX_BAG_LEN 10 paulo@0: #define MAX_OMINO 4 paulo@0: paulo@0: typedef struct LJField paulo@0: { paulo@0: /* Game state */ paulo@0: LJBlock b[LJ_PF_HT][LJ_PF_WID]; paulo@0: LJBlock c[LJ_PF_HT][LJ_PF_WID]; paulo@0: LJBits clearedLines; paulo@0: LJBits sounds; paulo@0: LJBits tempRows; paulo@0: unsigned char curPiece[1 + LJ_NEXT_PIECES]; paulo@0: signed char permuPiece[2 * MAX_BAG_LEN], permuPhase; paulo@0: unsigned short nLineClears[LJ_MAX_LINES_PER_PIECE]; // [n-1] = # of n-line clears paulo@0: LJFixed y; paulo@0: LJState state; paulo@0: signed char stateTime; paulo@0: unsigned char theta; paulo@0: signed char x; paulo@0: signed char hardDropY; paulo@0: char alreadyHeld; paulo@0: char isSpin; paulo@0: char nLinesThisPiece; paulo@0: char canRotate; paulo@0: unsigned char upwardKicks; paulo@0: paulo@0: /* Persist from piece to piece */ paulo@0: int score, lines; paulo@0: unsigned int gameTime; // number of frames paulo@0: unsigned int activeTime; // number of frames paulo@0: signed short holdPiece; paulo@0: char chain; paulo@0: signed char garbage; paulo@0: unsigned char dropDist; paulo@0: unsigned char garbageX; paulo@0: unsigned short nPieces, outGarbage; paulo@0: unsigned short monosquares, multisquares; paulo@0: paulo@0: /* Determined by gimmick */ paulo@0: signed char gimmick; paulo@0: signed int bpmCounter; paulo@0: unsigned int speedupCounter; paulo@0: unsigned int goalCount; paulo@0: unsigned int seed; paulo@0: unsigned char goalType; paulo@0: paulo@0: LJSpeedSetting speed; paulo@0: SpeedStateBase speedState; paulo@0: paulo@0: unsigned char lockReset; // lockdown reset rule type paulo@0: unsigned char areStyle; paulo@0: unsigned char setLockDelay; // Overridden lock delay (255 = never) paulo@0: unsigned char setLineDelay; // Overridden line delay paulo@0: unsigned char garbageStyle; paulo@0: unsigned char ceiling; paulo@0: unsigned char enterAbove; paulo@0: unsigned char leftWall, rightWall; paulo@0: unsigned char pieceSet; paulo@0: unsigned char randomizer; paulo@0: unsigned char rotationSystem; paulo@0: unsigned char garbageRandomness; // 64: change garbageX in 1/4 of rows; 255: change all paulo@0: unsigned char tSpinAlgo; // 0: off, 1: TNT, 2: TDS paulo@0: unsigned char clearGravity; paulo@0: unsigned char gluing; // 0: off; 1: square paulo@0: unsigned char scoreStyle; paulo@0: unsigned char dropScoreStyle; paulo@0: unsigned char maxUpwardKicks; paulo@0: unsigned char holdStyle; paulo@0: unsigned char bottomBlocks; paulo@0: unsigned char reloaded; // 0: played through; 1: reloaded from saved state paulo@0: } LJField; paulo@0: paulo@0: /** paulo@0: * Names of the supported rotation systems (wktables.c). paulo@0: */ paulo@0: enum { paulo@0: LJROT_SRS, paulo@0: LJROT_SEGA, paulo@0: LJROT_ARIKA, paulo@0: LJROT_ATARI, paulo@0: LJROT_NES, paulo@0: LJROT_GB, paulo@0: LJROT_TOD, paulo@0: LJROT_TDX, paulo@0: N_ROTATION_SYSTEMS paulo@0: }; paulo@0: paulo@0: #define N_PIECE_SHAPES 10 paulo@0: #define KICK_TABLE_LEN 5 paulo@0: extern const char pieceColors[N_PIECE_SHAPES]; paulo@0: typedef unsigned char WallKickTable[4][KICK_TABLE_LEN]; paulo@0: typedef struct LJRotSystem { paulo@0: /** paulo@0: * Color scheme for this rotation system (0: SRS; 1: Sega) paulo@0: * Use colorset 0 (SRS) if all free-space kicks are WK(0, 0). paulo@0: * Otherwise, your rotation system has what Eddie Rogers has called paulo@0: * a topknot, and you should use Sega colors. paulo@0: */ paulo@0: unsigned char colorScheme; paulo@0: unsigned char reserved1[3]; paulo@0: unsigned char entryOffset[N_PIECE_SHAPES]; paulo@0: unsigned char entryTheta[N_PIECE_SHAPES]; paulo@0: /* These control which kick table is used for each piece. paulo@0: * If negative, no kick table is present. paulo@0: */ paulo@0: signed char kicksL[N_PIECE_SHAPES]; paulo@0: signed char kicksR[N_PIECE_SHAPES]; paulo@0: WallKickTable kickTables[]; paulo@0: } LJRotSystem; paulo@0: paulo@0: extern const LJRotSystem *const rotSystems[N_ROTATION_SYSTEMS]; paulo@0: paulo@0: #ifdef LJ_INTERNAL paulo@0: // Used for defining wall kick tables: paulo@0: // WK(x, y) builds a 1-byte record with 2 coordinates, at 4 bits paulo@0: // per coordinate. paulo@0: // WKX() and WKY() retrieve the X or Y coordinate from this record. paulo@0: // The ((stuff ^ 8) - 8) is sign extension magick paulo@0: #define WK(x, y) (((x) & 0x0F) | ((y) & 0x0F) << 4) paulo@0: #define WKX(wk) ((((wk) & 0x0F) ^ 8) - 8) paulo@0: #define WKY(wk) WKX((wk) >> 4) paulo@0: // If ARIKA_IF_NOT_CENTER is specified, check the free space paulo@0: // rotation for blocks in columns other than 1, and stop if paulo@0: // none of them are filled. This obsoletes the old SKIP_IF. paulo@0: #define SKIP_IF 0x80 paulo@0: #define SKIP_IF3 0x82 paulo@0: paulo@0: #define ARIKA_IF_NOT_CENTER 0x83 paulo@0: #define WK_END 0x8F paulo@0: #if 0 paulo@0: extern const WallKickTable *const wkTablesL[N_ROTATION_SYSTEMS][N_PIECE_SHAPES]; paulo@0: extern const WallKickTable *const wkTablesR[N_ROTATION_SYSTEMS][N_PIECE_SHAPES]; paulo@0: #endif paulo@0: #endif paulo@0: paulo@0: /** paulo@0: * Expands a tetromino to the blocks that make it up. paulo@0: * @param out an array of length 4 paulo@0: * @param p the field into which the tetromino will be spawned paulo@0: * (used for default spawn orientation) paulo@0: * @param piece a piece number paulo@0: * @param xBase x coordinate of base position of the tetromino paulo@0: * @param yBase y coordinate of base position of the tetromino paulo@0: * @param theta the orientation of the tetromino paulo@0: * (0-3: use this; 4: use default spawn rotation) paulo@0: */ paulo@0: void expandPieceToBlocks(LJBlkSpec out[], paulo@0: const LJField *p, paulo@0: int piece, int xBase, int yBase, int theta); paulo@0: paulo@0: /** paulo@0: * Tests whether a particular block is occupied. paulo@0: * @param p the playfield paulo@0: * @param x the column paulo@0: * @param y the row (0 = bottom) paulo@0: * @return zero iff the space is open paulo@0: */ paulo@0: int isOccupied(const LJField *p, int x, int y); paulo@0: paulo@0: /** paulo@0: * Tests whether a particular tetromino would overlap one or more paulo@0: * blocks, a side wall, or the floor paulo@0: * @param p the playfield paulo@0: * @param x the column of the left side of the piece's bounding 4x4 paulo@0: * @param y the row of the bottom of the piece's bounding 4x4 (0 = bottom) paulo@0: * @return zero iff the space is open paulo@0: */ paulo@0: int isCollision(const LJField *p, int x, int y, int theta); paulo@0: paulo@0: /** paulo@0: * Blanks a playfield and prepares it for a new game. paulo@0: * @param p the playfield paulo@0: */ paulo@0: void newGame(LJField *p); paulo@0: paulo@0: /** paulo@0: * Runs one frame of S.M.G. paulo@0: * @param p the playfield paulo@0: * @param in the player's input paulo@0: * @return the rows that were modified paulo@0: */ paulo@0: LJBits frame(LJField *p, const LJInput *in); paulo@0: paulo@0: /** paulo@0: * Applies gimmicks to a new game. paulo@0: * @param p the playfield paulo@0: * @param in the player's input paulo@0: */ paulo@0: void initGimmicks(LJField *p); paulo@0: paulo@0: /** paulo@0: * Runs gimmicks for one frame of S.M.G. paulo@0: * @param p the playfield paulo@0: * @param c the control settings paulo@0: * @return the rows that were modified paulo@0: */ paulo@0: struct LJControl; paulo@0: LJBits gimmicks(LJField *p, struct LJControl *c); paulo@0: paulo@0: /** paulo@0: * Sets up the randomizer. paulo@0: * @return the number of this piece paulo@0: */ paulo@0: void initRandomize(LJField *p); paulo@0: paulo@0: /** paulo@0: * Chooses a pseudo-random piece. paulo@0: * @param the field on which the piece will be generated paulo@0: * @return the number of this piece paulo@0: * (0=i, 1=j, 2=l, 3=o, 4=s, 5=t, 6=z) paulo@0: */ paulo@0: unsigned int randomize(LJField *p); paulo@0: paulo@0: /** paulo@0: * Counts the number of 1 bits in a bitfield. paulo@0: * @param p the bitfield paulo@0: * @return the number of bits in p with a value of 1. paulo@0: */ paulo@0: unsigned int countOnes(LJBits b); paulo@0: paulo@0: /** paulo@0: * Shuffles the columns of blocks in the playfield. paulo@0: * @param p the playfield paulo@0: */ paulo@0: void shuffleColumns(LJField *p); paulo@0: paulo@0: /** paulo@0: * Random number generator. Use this for all random events paulo@0: * that affect the game state. paulo@0: * @param field paulo@0: * @return uniformly distributed number in 0 to 0x7FFF paulo@0: */ paulo@0: unsigned int ljRand(LJField *p); paulo@0: paulo@0: /** paulo@0: * List of lines used for hotline scoring. paulo@0: * A line clear completed at row hotlineRows[i] is worth 100*(i + 1). paulo@0: */ paulo@0: extern const char hotlineRows[LJ_PF_HT]; paulo@0: paulo@0: /** paulo@0: * Counts the number of trailing zero bits on an integer. paulo@0: * For instance, 00000000 00000000 00000001 11111000 paulo@0: * has 3 trailing zeroes. paulo@0: */ paulo@0: unsigned int bfffo(LJBits rowBits); paulo@0: paulo@0: void setupZigzagField(LJField *p, size_t height); paulo@0: unsigned int calcZigzagGrade(const LJField *p); paulo@0: paulo@0: #endif