paulo@0: /* Gimmick code for LOCKJAW, an implementation of the Soviet Mind Game paulo@0: paulo@0: Copyright (C) 2006-2007 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 "lj.h" paulo@0: #include "ljcontrol.h" paulo@0: paulo@0: void initSpeed(LJField *p); // in speed.c paulo@0: void setSpeed(LJField *p, LJControl *c); // in speed.c paulo@0: int updLevelAfterPiece(LJField *p); // in speed.c paulo@0: int updLevelAfterLines(LJField *p, size_t nLines); // in speed.c paulo@0: paulo@0: void initGimmicks(LJField *p) { paulo@0: initSpeed(p); paulo@0: p->speed.entryDelay = 0; /* new pieces will increase this */ paulo@0: p->speed.lineDelay = 0; paulo@0: if (p->garbageStyle == LJGARBAGE_DRILL paulo@0: || p->gimmick == LJGM_DRILL) { paulo@0: p->garbageRandomness = 255; paulo@0: p->garbage = p->ceiling - 2; paulo@0: } else if (p->garbageStyle == LJGARBAGE_ZIGZAG) { paulo@0: setupZigzagField(p, p->ceiling * 3 / 4); paulo@0: } paulo@0: paulo@0: } paulo@0: paulo@0: LJBits gimmicks(LJField *p, LJControl *c) { paulo@0: LJBits changed = 0; paulo@0: paulo@0: // In rhythm, lock the tetromino if the player paulo@0: // isn't keeping up with the rate paulo@0: if (p->speedState.curve == LJSPD_RHYTHM paulo@0: || p->speedState.curve == LJSPD_RHYTHMZERO) { paulo@0: p->bpmCounter += p->speedState.level; paulo@0: paulo@0: if (p->bpmCounter >= 0) { paulo@0: if (p->state == LJS_LANDED) { paulo@0: p->stateTime = 0; paulo@0: } else if (p->state == LJS_FALLING) { paulo@0: p->speed.gravity = ljitofix(LJ_PF_HT); paulo@0: } paulo@0: } paulo@0: paulo@0: // In rhythm, increase BPM periodically paulo@0: p->speedupCounter += p->speedState.level; paulo@0: if(p->speedupCounter >= 60 * 60 * 64) { paulo@0: p->speedupCounter -= 60 * 60 * 64; paulo@0: p->speedState.level += 10; paulo@0: p->sounds |= LJSND_SECTIONUP; paulo@0: } paulo@0: } paulo@0: paulo@0: // For each piece, set the entry and line delays. paulo@0: // Don't set it twice when spawning to replace the paulo@0: // first held piece (both LJSND_SPAWN and LJSND_HOLD paulo@0: // on the same piece). paulo@0: if ((p->sounds & (LJSND_SPAWN | LJSND_HOLD)) paulo@0: == LJSND_SPAWN) { paulo@0: setSpeed(p, c); paulo@0: } paulo@0: paulo@0: if (p->sounds & LJSND_LOCK) { paulo@0: if (p->gimmick == LJGM_ITEMS) { paulo@0: if (p->nPieces >= 7) { paulo@0: p->canRotate = 0; paulo@0: p->speed.gravity = ljitofix(1); paulo@0: } paulo@0: } paulo@0: paulo@0: // Garbage in simulated multiplayer paulo@0: int simGarbage = p->nPieces >= 7 paulo@0: && p->garbageStyle >= LJGARBAGE_1 paulo@0: && p->garbageStyle <= LJGARBAGE_4 paulo@0: && (p->curPiece[1] == LJP_I paulo@0: || ((p->pieceSet == LJRAND_SZ paulo@0: || p->pieceSet == LJRAND_JLOSTZ) paulo@0: && p->nPieces % 7 == 3)); paulo@0: if (simGarbage) { paulo@0: p->garbage += p->garbageStyle; paulo@0: } paulo@0: paulo@0: // Banana attack in "Vs. with Items" gimmick paulo@0: if (p->gimmick == LJGM_ITEMS paulo@0: && (ljRand(p) & 0xF00) == 0xF00) { paulo@0: shuffleColumns(p); paulo@0: changed |= (1 << LJ_PF_HT) - 1; paulo@0: } paulo@0: } paulo@0: return changed; paulo@0: } paulo@0: paulo@0: paulo@0: paulo@0: const char hotlineRows[LJ_PF_HT] = paulo@0: { paulo@0: 0, 0, 0, 0, 1, paulo@0: 0, 0, 0, 0, 2, paulo@0: 0, 0, 0, 3, 0, paulo@0: 0, 4, 0, 5, 6, paulo@0: 0, 0, 0, 0 paulo@0: }; paulo@0: paulo@0: static const char garbageScoreTable[] = { 0, 0, 1, 2, 4 }; paulo@0: static const char tSpinGarbageScoreTable[] = { 0, 2, 4, 6, 6 }; paulo@0: static const unsigned char squareScoreTable[] = paulo@0: {1, 1, 1, 2, 3, 5, 8, 13, paulo@0: 21, 34, 55, 89, 144, 200}; paulo@0: paulo@0: static const char tdsScoreTable[] = {0, 1, 3, 5, 8}; paulo@0: static const char tdsTSScoreTable[] = {4, 8, 12, 16}; paulo@0: static const char nesScoreTable[] = {0, 4, 10, 30, 120}; paulo@0: paulo@0: paulo@0: /** paulo@0: * Computes the score and outgoing garbage for lines paulo@0: * and adds them to the player's total. paulo@0: * @param p The playfield paulo@0: * @param lines Bit array where 1 means a line clear on this row. paulo@0: */ paulo@0: void addLinesScore(LJField *p, LJBits lines) { paulo@0: const int nLines = countOnes(lines); paulo@0: int oldLines = p->nLinesThisPiece; paulo@0: int tdsSection = p->lines / 10 + 1; paulo@0: paulo@0: p->lines += nLines; paulo@0: if (updLevelAfterLines(p, nLines)) { paulo@0: p->sounds |= LJSND_SECTIONUP; paulo@0: } paulo@0: paulo@0: switch (p->scoreStyle) { paulo@0: paulo@0: case LJSCORE_TNT64: paulo@0: if (nLines < 1) { paulo@0: return; paulo@0: } paulo@0: for (int i = nLines; i > 0; --i) { paulo@0: if (oldLines > sizeof(squareScoreTable) - 1) { paulo@0: oldLines = sizeof(squareScoreTable) - 1; paulo@0: } paulo@0: p->score += 100 * squareScoreTable[oldLines++]; paulo@0: } break; paulo@0: paulo@0: case LJSCORE_NES: paulo@0: { paulo@0: int garbageLevel = (nLines > 4) ? 4 : nLines; paulo@0: int value = nesScoreTable[garbageLevel]; paulo@0: paulo@0: p->score += 10 * tdsSection * value; paulo@0: if (nLines >= 4) { paulo@0: p->sounds |= LJSND_SETB2B; paulo@0: } paulo@0: } break; paulo@0: paulo@0: case LJSCORE_TDS: paulo@0: { paulo@0: // Garbage based scoring system. paulo@0: int garbageLevel = (nLines > 4) ? 4 : nLines; paulo@0: int chain, value; paulo@0: paulo@0: if (p->isSpin) { paulo@0: chain = (nLines >= 1); paulo@0: value = tdsTSScoreTable[garbageLevel]; paulo@0: paulo@0: // TDS gives fewer points for a T-spin single paulo@0: // that involves a wall kick. paulo@0: if (p->isSpin == 2 && nLines < 2) { paulo@0: value >>= 2; paulo@0: } paulo@0: } else { paulo@0: chain = (nLines >= 4); paulo@0: value = tdsScoreTable[garbageLevel]; paulo@0: } paulo@0: if (chain && p->chain && nLines >= 1) { // b2b paulo@0: value = value * 3 / 2; paulo@0: } paulo@0: paulo@0: if (tdsSection > 20) { paulo@0: tdsSection = 20; paulo@0: } paulo@0: p->score += 100 * tdsSection * value; paulo@0: if (nLines >= 1) { paulo@0: if (chain) { paulo@0: p->sounds |= LJSND_SETB2B; paulo@0: if (p->chain) { paulo@0: p->sounds |= LJSND_B2B; paulo@0: } paulo@0: } paulo@0: p->chain = chain; paulo@0: } paulo@0: } break; paulo@0: paulo@0: case LJSCORE_LJ: paulo@0: case LJSCORE_LJ_NERF: paulo@0: if (nLines >= 1) { paulo@0: // Garbage based scoring system. paulo@0: int garbageLevel = (nLines > 4) ? 4 : nLines; paulo@0: unsigned int chain, garbage; paulo@0: paulo@0: if (p->isSpin) { paulo@0: chain = (nLines >= 1); paulo@0: garbage = tSpinGarbageScoreTable[garbageLevel]; paulo@0: if (p->scoreStyle == LJSCORE_LJ_NERF) { paulo@0: garbage /= 2; paulo@0: } paulo@0: garbage += (chain && p->chain); paulo@0: } else { paulo@0: chain = (nLines >= 4); paulo@0: garbage = garbageScoreTable[garbageLevel] paulo@0: + (chain && p->chain); paulo@0: } paulo@0: p->score += 100 * nLines + 200 * garbage; paulo@0: p->outGarbage += garbage; paulo@0: if (chain) { paulo@0: p->sounds |= LJSND_SETB2B; paulo@0: if (p->chain) { paulo@0: p->sounds |= LJSND_B2B; paulo@0: } paulo@0: } paulo@0: p->chain = chain; paulo@0: } break; paulo@0: paulo@0: case LJSCORE_HOTLINE: paulo@0: for (int y = 0; y < 24; ++y) { paulo@0: if (lines & (1 << y)) { paulo@0: p->score += 100 * hotlineRows[y]; paulo@0: } paulo@0: } break; paulo@0: paulo@0: } paulo@0: paulo@0: p->nLinesThisPiece += nLines; paulo@0: } paulo@0: paulo@0: /** paulo@0: * Puts a zigzag pattern of garbage into the playfield. paulo@0: * Useful as a test suite for calcZigzagGrade() in debrief.c. paulo@0: * @param p the playfield to set up paulo@0: * @param height the number of rows to set up paulo@0: */ paulo@0: void setupZigzagField(LJField *p, size_t height) { paulo@0: unsigned int hole = p->leftWall; paulo@0: int delta = 1; paulo@0: paulo@0: if (height > p->ceiling) { paulo@0: height = p->ceiling; paulo@0: } paulo@0: paulo@0: // set up example pattern paulo@0: for (size_t y = 0; y < height; ++y) { paulo@0: for (size_t x = p->leftWall; x < p->rightWall; ++x) { paulo@0: paulo@0: // A block should be in all cells of the row paulo@0: // except for the hole cell, which should be empty. paulo@0: if (x == hole) { paulo@0: p->b[y][x] = 0; paulo@0: } else { paulo@0: p->b[y][x] = 0x80; paulo@0: } paulo@0: } paulo@0: paulo@0: if (hole == p->rightWall - 1) { paulo@0: delta = -1; paulo@0: } else if (hole == p->leftWall) { paulo@0: delta = 1; paulo@0: } paulo@0: hole += delta; paulo@0: } paulo@0: paulo@0: // clear rows above pattern paulo@0: for (size_t y = height; y < LJ_PF_HT; ++y) { paulo@0: for (size_t x = p->leftWall; x < p->rightWall; ++x) { paulo@0: p->b[y][x] = 0; paulo@0: } paulo@0: } paulo@0: }