paulo@0: /* Speed curve tables 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: #include "lj.h" paulo@0: #include "ljcontrol.h" paulo@0: paulo@0: static const LJFixed initialGravity[LJSPD_N_CURVES] = { paulo@0: [LJSPD_RHYTHMZERO] = 0, paulo@0: [LJSPD_RHYTHM] = LJITOFIX(20) paulo@0: }; paulo@0: paulo@0: /** paulo@0: * The default speed curve for each gimmick. A negative value paulo@0: * means that the gimmick uses the player's chosen speed curve. paulo@0: */ paulo@0: static const signed char defaultSpeedCurve[LJGM_N_GIMMICKS] = { paulo@0: [LJGM_ATYPE] = -1, paulo@0: [LJGM_BTYPE] = -1, paulo@0: [LJGM_ULTRA] = -1, paulo@0: [LJGM_DRILL] = -1, paulo@0: [LJGM_ITEMS] = -1, paulo@0: [LJGM_BABY] = LJSPD_ZERO paulo@0: }; paulo@0: paulo@0: /* The new speed curve code ****************************************/ paulo@0: paulo@0: #define SCGRAV(n, d) ((n) * 2048 / (d)) paulo@0: paulo@0: enum { paulo@0: SPEEDTYPE_RHYTHM, paulo@0: SPEEDTYPE_TGM, paulo@0: SPEEDTYPE_LINES, paulo@0: SPEEDTYPE_PIECES paulo@0: }; paulo@0: paulo@0: typedef struct LJSpeedStep { paulo@0: unsigned short level, gravity; paulo@0: signed char are, das, lock, line; paulo@0: } LJSpeedStep; paulo@0: paulo@0: typedef struct LJSpeedCurve { paulo@0: char name[32]; paulo@0: unsigned char type, sectionLen; paulo@0: unsigned char nSteps; paulo@0: const LJSpeedStep steps[]; paulo@0: } LJSpeedCurve; paulo@0: paulo@0: const LJSpeedCurve scMaster = { paulo@0: "Master", paulo@0: SPEEDTYPE_TGM, 100, paulo@0: 33, paulo@0: { paulo@0: { 0, SCGRAV( 4,256),25,16,30,40}, paulo@0: { 30, SCGRAV( 6,256),25,16,30,40}, paulo@0: { 35, SCGRAV( 8,256),25,16,30,40}, paulo@0: { 40, SCGRAV( 10,256),25,16,30,40}, paulo@0: { 50, SCGRAV( 12,256),25,16,30,40}, paulo@0: { 60, SCGRAV( 16,256),25,16,30,40}, paulo@0: { 70, SCGRAV( 32,256),25,16,30,40}, paulo@0: { 80, SCGRAV( 48,256),25,16,30,40}, paulo@0: { 90, SCGRAV( 64,256),25,16,30,40}, paulo@0: {100, SCGRAV( 80,256),25,16,30,40}, paulo@0: {120, SCGRAV( 96,256),25,16,30,40}, paulo@0: {140, SCGRAV(112,256),25,16,30,40}, paulo@0: {160, SCGRAV(128,256),25,16,30,40}, paulo@0: {170, SCGRAV(144,256),25,16,30,40}, paulo@0: {200, SCGRAV( 4,256),25,16,30,40}, paulo@0: {220, SCGRAV( 32,256),25,16,30,40}, paulo@0: {230, SCGRAV( 64,256),25,16,30,40}, paulo@0: {233, SCGRAV( 96,256),25,16,30,40}, paulo@0: {236, SCGRAV(128,256),25,16,30,40}, paulo@0: {239, SCGRAV(160,256),25,16,30,40}, paulo@0: {243, SCGRAV(192,256),25,16,30,40}, paulo@0: {247, SCGRAV(224,256),25,16,30,40}, paulo@0: {251, SCGRAV( 1,1), 25,16,30,40}, paulo@0: {300, SCGRAV( 2,1), 25,16,30,40}, paulo@0: {330, SCGRAV( 3,1), 25,16,30,40}, paulo@0: {360, SCGRAV( 4,1), 25,16,30,40}, paulo@0: {400, SCGRAV( 5,1), 25,16,30,40}, paulo@0: {450, SCGRAV( 3,1), 25,16,30,40}, paulo@0: {500, SCGRAV(20,1), 25,10,30,25}, paulo@0: {600, SCGRAV(20,1), 25,10,30,16}, paulo@0: {700, SCGRAV(20,1), 16,10,30,12}, paulo@0: {800, SCGRAV(20,1), 12,10,30, 6}, paulo@0: {900, SCGRAV(20,1), 12, 8,17, 6} paulo@0: } paulo@0: }; paulo@0: paulo@0: const LJSpeedCurve scDeath = { paulo@0: "Death", paulo@0: SPEEDTYPE_TGM, 100, paulo@0: 6, paulo@0: { paulo@0: { 0, SCGRAV(20,1), 18,12,30, 8}, paulo@0: {100, SCGRAV(20,1), 14,12,26, 0}, paulo@0: {200, SCGRAV(20,1), 14,11,22, 0}, paulo@0: {300, SCGRAV(20,1), 8,10,18, 6}, paulo@0: {400, SCGRAV(20,1), 7, 8,15, 5}, paulo@0: {500, SCGRAV(20,1), 6, 8,15, 4} paulo@0: } paulo@0: }; paulo@0: paulo@0: const LJSpeedCurve scNES = { paulo@0: "NES", paulo@0: SPEEDTYPE_LINES, 10, paulo@0: 15, paulo@0: { paulo@0: { 0, SCGRAV(1,48), 10,-1, 0,30}, paulo@0: { 10, SCGRAV(1,43), 10,-1, 0,30}, paulo@0: { 20, SCGRAV(1,38), 10,-1, 0,30}, paulo@0: { 30, SCGRAV(1,33), 10,-1, 0,30}, paulo@0: { 40, SCGRAV(1,28), 10,-1, 0,30}, paulo@0: { 50, SCGRAV(1,23), 10,-1, 0,30}, paulo@0: { 60, SCGRAV(1,18), 10,-1, 0,30}, paulo@0: { 70, SCGRAV(1,13), 10,-1, 0,30}, paulo@0: { 80, SCGRAV(1, 8), 10,-1, 0,30}, paulo@0: { 90, SCGRAV(1, 6), 10,-1, 0,30}, paulo@0: {100, SCGRAV(1, 5), 10,-1, 0,30}, paulo@0: {130, SCGRAV(1, 4), 10,-1, 0,30}, paulo@0: {160, SCGRAV(1, 3), 10,-1, 0,30}, paulo@0: {190, SCGRAV(1, 2), 10,-1, 0,30}, paulo@0: {290, SCGRAV(1, 1), 10,-1, 0,30}, paulo@0: } paulo@0: }; paulo@0: paulo@0: const LJSpeedCurve scGB = { paulo@0: "Game Boy", paulo@0: SPEEDTYPE_LINES, 10, paulo@0: 18, paulo@0: { paulo@0: { 0, SCGRAV(1,53), 0,-1, 0,90}, paulo@0: { 10, SCGRAV(1,49), 0,-1, 0,90}, paulo@0: { 20, SCGRAV(1,45), 0,-1, 0,90}, paulo@0: { 30, SCGRAV(1,41), 0,-1, 0,90}, paulo@0: { 40, SCGRAV(1,37), 0,-1, 0,90}, paulo@0: { 50, SCGRAV(1,33), 0,-1, 0,90}, paulo@0: { 60, SCGRAV(1,28), 0,-1, 0,90}, paulo@0: { 70, SCGRAV(1,22), 0,-1, 0,90}, paulo@0: { 80, SCGRAV(1,17), 0,-1, 0,90}, paulo@0: { 90, SCGRAV(1,11), 0,-1, 0,90}, paulo@0: {100, SCGRAV(1,10), 0,-1, 0,90}, paulo@0: {110, SCGRAV(1, 9), 0,-1, 0,90}, paulo@0: {120, SCGRAV(1, 8), 0,-1, 0,90}, paulo@0: {130, SCGRAV(1, 7), 0,-1, 0,90}, paulo@0: {140, SCGRAV(1, 6), 0,-1, 0,90}, paulo@0: {160, SCGRAV(1, 5), 0,-1, 0,90}, paulo@0: {180, SCGRAV(1, 4), 0,-1, 0,90}, paulo@0: {200, SCGRAV(1, 3), 0,-1, 0,90}, paulo@0: } paulo@0: }; paulo@0: paulo@0: const LJSpeedCurve scZero = { paulo@0: "Zero", paulo@0: SPEEDTYPE_LINES, 10, paulo@0: 1, paulo@0: { paulo@0: { 0, SCGRAV(0, 1),-1,-1,40,-1} paulo@0: } paulo@0: }; paulo@0: paulo@0: const LJSpeedCurve scExponential = { paulo@0: "Exponential", paulo@0: SPEEDTYPE_PIECES, 60, paulo@0: 34, paulo@0: { paulo@0: { 0, SCGRAV( 1,60),-1,-1,40,-1 }, paulo@0: { 30, SCGRAV( 1,42),-1,-1,40,-1 }, paulo@0: { 60, SCGRAV( 2,60),-1,-1,40,-1 }, paulo@0: { 90, SCGRAV( 2,42),-1,-1,40,-1 }, paulo@0: {120, SCGRAV( 4,60),-1,-1,40,-1 }, paulo@0: {150, SCGRAV( 4,42),-1,-1,40,-1 }, paulo@0: {180, SCGRAV( 8,60),-1,-1,40,-1 }, paulo@0: {210, SCGRAV( 8,42),-1,-1,40,-1 }, paulo@0: {240, SCGRAV( 16,60),-1,-1,40,-1 }, paulo@0: {270, SCGRAV( 16,42),-1,-1,40,-1 }, paulo@0: {300, SCGRAV( 32,60),-1,-1,40,-1 }, paulo@0: {330, SCGRAV( 32,42),-1,-1,40,-1 }, paulo@0: {360, SCGRAV( 64,60),-1,-1,40,-1 }, paulo@0: {390, SCGRAV( 64,42),-1,-1,40,-1 }, paulo@0: {420, SCGRAV(128,60),-1,-1,40,-1 }, paulo@0: {450, SCGRAV(128,42),-1,-1,40,-1 }, paulo@0: {480, SCGRAV(256,60),-1,-1,40,-1 }, paulo@0: {510, SCGRAV(256,42),-1,-1,40,-1 }, paulo@0: {540, SCGRAV(512,60),-1,-1,40,-1 }, paulo@0: {570, SCGRAV(512,42),-1,-1,40,-1 }, paulo@0: {600, SCGRAV( 20, 1),-1,-1,40,-1 }, paulo@0: {630, SCGRAV( 20, 1),-1,-1,30,-1 }, paulo@0: {660, SCGRAV( 20, 1),-1,-1,24,-1 }, paulo@0: {690, SCGRAV( 20, 1),-1,-1,20,-1 }, paulo@0: {720, SCGRAV( 20, 1),-1,-1,17,-1 }, paulo@0: {750, SCGRAV( 20, 1),-1,-1,15,-1 }, paulo@0: {780, SCGRAV( 20, 1),-1,-1,13,-1 }, paulo@0: {810, SCGRAV( 20, 1),-1,-1,12,-1 }, paulo@0: {840, SCGRAV( 20, 1),-1,-1,11,-1 }, paulo@0: {870, SCGRAV( 20, 1),-1,-1,10,-1 }, paulo@0: {900, SCGRAV( 20, 1),-1,-1, 9,-1 }, paulo@0: {930, SCGRAV( 20, 1),-1,-1, 8,-1 }, paulo@0: {960, SCGRAV( 20, 1),-1,-1, 7,-1 }, paulo@0: {990, SCGRAV( 20, 1),-1,-1, 6,-1 } paulo@0: } paulo@0: }; paulo@0: paulo@0: static const LJSpeedCurve *const newSpeedCurves[LJSPD_N_CURVES] = { paulo@0: [LJSPD_EXP] = &scExponential, paulo@0: [LJSPD_ZERO] = &scZero, paulo@0: [LJSPD_TGM] = &scMaster, paulo@0: [LJSPD_DEATH] = &scDeath, paulo@0: [LJSPD_DEATH300] = &scDeath, paulo@0: [LJSPD_NES] = &scNES, paulo@0: [LJSPD_GB] = &scGB, paulo@0: [LJSPD_GBHEART] = &scGB, paulo@0: }; paulo@0: paulo@0: /** paulo@0: * Performs a fast binary search of a speed step table. paulo@0: */ paulo@0: static const LJSpeedStep *getSpeedStep(const LJSpeedStep *steps, paulo@0: size_t nSteps, int level) { paulo@0: unsigned int lo = 0; paulo@0: unsigned int hi = nSteps; paulo@0: paulo@0: while (hi - lo > 1) { paulo@0: size_t mid = (hi + lo) / 2; paulo@0: unsigned int here = steps[mid].level; paulo@0: if (here == level) { paulo@0: return &(steps[mid]); paulo@0: } else if (here < level) { paulo@0: lo = mid; paulo@0: } else { paulo@0: hi = mid; paulo@0: } paulo@0: } paulo@0: return &(steps[lo]); paulo@0: } paulo@0: paulo@0: /** paulo@0: * Updates the level after each piece has retired. paulo@0: * @return nonzero iff a new section has happened paulo@0: */ paulo@0: int updLevelAfterPiece(LJField *p) { paulo@0: int curveID = p->speedState.curve; paulo@0: const LJSpeedCurve *curve = newSpeedCurves[curveID]; paulo@0: if (!curve) { paulo@0: return 0; paulo@0: } paulo@0: unsigned int sectionLen = curve->sectionLen; paulo@0: unsigned int oldLevel = p->speedState.level; paulo@0: unsigned int oldSection = oldLevel / sectionLen; paulo@0: unsigned int oldSectionPos = oldLevel % sectionLen; paulo@0: paulo@0: switch (curve->type) { paulo@0: case SPEEDTYPE_TGM: paulo@0: if (oldSectionPos + 1 >= sectionLen) { paulo@0: return 0; paulo@0: } paulo@0: // otherwise fall through to +1 per piece paulo@0: paulo@0: case SPEEDTYPE_PIECES: paulo@0: p->speedState.level = oldLevel + 1; paulo@0: break; paulo@0: paulo@0: default: paulo@0: return 0; paulo@0: } paulo@0: paulo@0: unsigned int newSection = p->speedState.level / sectionLen; paulo@0: return newSection > oldSection; paulo@0: } paulo@0: paulo@0: /** paulo@0: * Updates the level after lines have been cleared. paulo@0: * @return nonzero iff a new section has happened paulo@0: */ paulo@0: int updLevelAfterLines(LJField *p, unsigned int nLines) { paulo@0: int curveID = p->speedState.curve; paulo@0: const LJSpeedCurve *curve = newSpeedCurves[curveID]; paulo@0: if (!curve) { paulo@0: return 0; paulo@0: } paulo@0: unsigned int sectionLen = curve->sectionLen; paulo@0: unsigned int oldLevel = p->speedState.level; paulo@0: unsigned int oldSection = oldLevel / sectionLen; paulo@0: paulo@0: switch (curve->type) { paulo@0: case SPEEDTYPE_TGM: paulo@0: case SPEEDTYPE_LINES: paulo@0: p->speedState.level = oldLevel + nLines; paulo@0: break; paulo@0: paulo@0: default: paulo@0: return 0; paulo@0: } paulo@0: paulo@0: unsigned int newSection = p->speedState.level / sectionLen; paulo@0: return newSection > oldSection; paulo@0: } paulo@0: paulo@0: void setSpeedNew(LJField *p, LJControl *c) { paulo@0: int curveID = p->speedState.curve; paulo@0: const LJSpeedCurve *curve = newSpeedCurves[curveID]; paulo@0: if (!curve) { paulo@0: return; paulo@0: } paulo@0: const LJSpeedStep *step = paulo@0: getSpeedStep(curve->steps, curve->nSteps, p->speedState.level); paulo@0: paulo@0: p->speed.gravity = step->gravity << 5; paulo@0: paulo@0: p->speed.entryDelay = step->are; paulo@0: if (p->speed.entryDelay > p->areStyle) { paulo@0: p->speed.entryDelay = p->areStyle; paulo@0: } paulo@0: paulo@0: if (step->das > 0 && c->dasDelay > step->das) { paulo@0: c->dasDelay = step->das; paulo@0: } paulo@0: paulo@0: if (p->setLockDelay >= 128) { paulo@0: p->speed.lockDelay = 127; paulo@0: } else if (p->setLockDelay > 0) { paulo@0: p->speed.lockDelay = p->setLockDelay; paulo@0: } else if (step->lock > 0) { paulo@0: p->speed.lockDelay = step->lock; paulo@0: } paulo@0: paulo@0: if (p->setLineDelay > 0) { paulo@0: p->speed.lineDelay = p->setLineDelay; paulo@0: } else if (step->line >= 0) { paulo@0: p->speed.lineDelay = step->line; paulo@0: } else { paulo@0: p->speed.lineDelay = p->speed.entryDelay; paulo@0: } paulo@0: } paulo@0: paulo@0: paulo@0: paulo@0: /* Old speed curve system is below this line ***********************/ paulo@0: paulo@0: paulo@0: paulo@0: void initSpeed(LJField *p) { paulo@0: if (defaultSpeedCurve[p->gimmick] >= 0) { paulo@0: p->speedState.curve = defaultSpeedCurve[p->gimmick]; paulo@0: } paulo@0: p->speed.gravity = initialGravity[p->speedState.curve]; paulo@0: paulo@0: switch (p->speedState.curve) { paulo@0: case LJSPD_RHYTHM: paulo@0: case LJSPD_RHYTHMZERO: paulo@0: p->speedState.level = 60; paulo@0: p->bpmCounter = 0; paulo@0: p->speedupCounter = 0; paulo@0: break; paulo@0: case LJSPD_TGM: paulo@0: case LJSPD_DEATH: paulo@0: case LJSPD_EXP: paulo@0: p->speedState.level = -1; paulo@0: break; paulo@0: case LJSPD_DEATH300: paulo@0: p->speedState.level = 300; paulo@0: break; paulo@0: case LJSPD_GBHEART: paulo@0: p->speedState.level = 100; paulo@0: break; paulo@0: default: paulo@0: p->speedState.level = 0; paulo@0: break; paulo@0: } paulo@0: } paulo@0: paulo@0: paulo@0: void setSpeed(LJField *p, LJControl *c) { paulo@0: p->speed.entryDelay = p->areStyle; paulo@0: paulo@0: // Default line delay is equal to the entry delay, paulo@0: // but speed curves and setLineDelay can override this paulo@0: p->speed.lineDelay = p->speed.entryDelay; paulo@0: switch (p->speedState.curve) { paulo@0: paulo@0: case LJSPD_RHYTHM: paulo@0: case LJSPD_RHYTHMZERO: paulo@0: // If we've already banked five pieces' worth of time, paulo@0: // add 20 points instead of banking another. paulo@0: if (p->bpmCounter <= -18000) { paulo@0: // removed in 0.21 because other curves don't reward for drops paulo@0: // p->score += 20; paulo@0: } else { paulo@0: p->bpmCounter -= 3600; // number of frames per minute paulo@0: } paulo@0: p->speed.lockDelay = 3600 / p->speedState.level; paulo@0: p->speed.gravity = (p->speedState.curve == LJSPD_RHYTHM) ? ljitofix(20) : 0; paulo@0: break; paulo@0: paulo@0: default: paulo@0: if (updLevelAfterPiece(p)) { paulo@0: p->sounds |= LJSND_SECTIONUP; paulo@0: } paulo@0: setSpeedNew(p, c); paulo@0: break; paulo@0: } paulo@0: paulo@0: if (p->setLockDelay >= 128) { paulo@0: p->speed.lockDelay = 127; paulo@0: } else if (p->setLockDelay > 0) { paulo@0: p->speed.lockDelay = p->setLockDelay; paulo@0: } paulo@0: if (p->setLineDelay > 0) { paulo@0: p->speed.lineDelay = p->setLineDelay; paulo@0: } paulo@0: } paulo@0: