diff src/gimmicks.c @ 0:c84446dfb3f5

initial add
author paulo@localhost
date Fri, 13 Mar 2009 00:39:12 -0700
parents
children
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/gimmicks.c	Fri Mar 13 00:39:12 2009 -0700
     1.3 @@ -0,0 +1,295 @@
     1.4 +/* Gimmick code for LOCKJAW, an implementation of the Soviet Mind Game
     1.5 +
     1.6 +Copyright (C) 2006-2007 Damian Yerrick <tepples+lj@spamcop.net>
     1.7 +
     1.8 +This work is free software; you can redistribute it and/or modify
     1.9 +it under the terms of the GNU General Public License as published by
    1.10 +the Free Software Foundation; either version 2 of the License, or
    1.11 +(at your option) any later version.
    1.12 +
    1.13 +This program is distributed in the hope that it will be useful,
    1.14 +but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.15 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.16 +GNU General Public License for more details.
    1.17 +
    1.18 +You should have received a copy of the GNU General Public License
    1.19 +along with this program; if not, write to the Free Software
    1.20 +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    1.21 +
    1.22 +Original game concept and design by Alexey Pajitnov.
    1.23 +The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
    1.24 +or The Tetris Company LLC.
    1.25 +
    1.26 +*/
    1.27 +
    1.28 +#include "lj.h"
    1.29 +#include "ljcontrol.h"
    1.30 +
    1.31 +void initSpeed(LJField *p);  // in speed.c
    1.32 +void setSpeed(LJField *p, LJControl *c);  // in speed.c
    1.33 +int updLevelAfterPiece(LJField *p);  // in speed.c
    1.34 +int updLevelAfterLines(LJField *p, size_t nLines);  // in speed.c
    1.35 +
    1.36 +void initGimmicks(LJField *p) {
    1.37 +  initSpeed(p);
    1.38 +  p->speed.entryDelay = 0;  /* new pieces will increase this */
    1.39 +  p->speed.lineDelay = 0;
    1.40 +  if (p->garbageStyle == LJGARBAGE_DRILL
    1.41 +      || p->gimmick == LJGM_DRILL) {
    1.42 +    p->garbageRandomness = 255;
    1.43 +    p->garbage = p->ceiling - 2;
    1.44 +  } else if (p->garbageStyle == LJGARBAGE_ZIGZAG) {
    1.45 +    setupZigzagField(p, p->ceiling * 3 / 4);
    1.46 +  }
    1.47 +
    1.48 +}
    1.49 +
    1.50 +LJBits gimmicks(LJField *p, LJControl *c) {
    1.51 +  LJBits changed = 0;
    1.52 +
    1.53 +  // In rhythm, lock the tetromino if the player
    1.54 +  // isn't keeping up with the rate
    1.55 +  if (p->speedState.curve == LJSPD_RHYTHM
    1.56 +      || p->speedState.curve == LJSPD_RHYTHMZERO) {
    1.57 +    p->bpmCounter += p->speedState.level;
    1.58 +
    1.59 +    if (p->bpmCounter >= 0) {
    1.60 +      if (p->state == LJS_LANDED) {
    1.61 +        p->stateTime = 0;
    1.62 +      } else if (p->state == LJS_FALLING) {
    1.63 +        p->speed.gravity = ljitofix(LJ_PF_HT);
    1.64 +      }
    1.65 +    }
    1.66 +
    1.67 +    // In rhythm, increase BPM periodically
    1.68 +    p->speedupCounter += p->speedState.level;
    1.69 +    if(p->speedupCounter >= 60 * 60 * 64) {
    1.70 +      p->speedupCounter -= 60 * 60 * 64;
    1.71 +      p->speedState.level += 10;
    1.72 +      p->sounds |= LJSND_SECTIONUP;
    1.73 +    }
    1.74 +  }
    1.75 +
    1.76 +  // For each piece, set the entry and line delays.
    1.77 +  // Don't set it twice when spawning to replace the
    1.78 +  // first held piece (both LJSND_SPAWN and LJSND_HOLD
    1.79 +  // on the same piece).
    1.80 +  if ((p->sounds & (LJSND_SPAWN | LJSND_HOLD))
    1.81 +      == LJSND_SPAWN) {
    1.82 +    setSpeed(p, c);
    1.83 +  }
    1.84 +
    1.85 +  if (p->sounds & LJSND_LOCK) {
    1.86 +    if (p->gimmick == LJGM_ITEMS) {
    1.87 +      if (p->nPieces >= 7) {
    1.88 +        p->canRotate = 0;
    1.89 +        p->speed.gravity = ljitofix(1);
    1.90 +      }
    1.91 +    }
    1.92 +    
    1.93 +    // Garbage in simulated multiplayer
    1.94 +    int simGarbage = p->nPieces >= 7
    1.95 +                     && p->garbageStyle >= LJGARBAGE_1
    1.96 +                     && p->garbageStyle <= LJGARBAGE_4
    1.97 +                     && (p->curPiece[1] == LJP_I
    1.98 +                         || ((p->pieceSet == LJRAND_SZ
    1.99 +                              || p->pieceSet == LJRAND_JLOSTZ)
   1.100 +                             && p->nPieces % 7 == 3));
   1.101 +    if (simGarbage) {
   1.102 +      p->garbage += p->garbageStyle;
   1.103 +    }
   1.104 +
   1.105 +    // Banana attack in "Vs. with Items" gimmick    
   1.106 +    if (p->gimmick == LJGM_ITEMS
   1.107 +        && (ljRand(p) & 0xF00) == 0xF00) {
   1.108 +      shuffleColumns(p);
   1.109 +      changed |= (1 << LJ_PF_HT) - 1;
   1.110 +    }
   1.111 +  }
   1.112 +  return changed;
   1.113 +}
   1.114 +
   1.115 +
   1.116 +
   1.117 +const char hotlineRows[LJ_PF_HT] =
   1.118 +{ 
   1.119 +  0, 0, 0, 0, 1,
   1.120 +  0, 0, 0, 0, 2,
   1.121 +  0, 0, 0, 3, 0,
   1.122 +  0, 4, 0, 5, 6,
   1.123 +  0, 0, 0, 0
   1.124 +};
   1.125 +
   1.126 +static const char garbageScoreTable[] = { 0, 0, 1, 2, 4 };
   1.127 +static const char tSpinGarbageScoreTable[] = { 0, 2, 4, 6, 6 };
   1.128 +static const unsigned char squareScoreTable[] =
   1.129 + {1, 1, 1, 2, 3, 5, 8, 13,
   1.130 +  21, 34, 55, 89, 144, 200};
   1.131 +
   1.132 +static const char tdsScoreTable[] = {0, 1, 3, 5, 8};
   1.133 +static const char tdsTSScoreTable[] = {4, 8, 12, 16};
   1.134 +static const char nesScoreTable[] = {0, 4, 10, 30, 120};
   1.135 +
   1.136 +
   1.137 +/**
   1.138 + * Computes the score and outgoing garbage for lines
   1.139 + * and adds them to the player's total.
   1.140 + * @param p The playfield
   1.141 + * @param lines Bit array where 1 means a line clear on this row.
   1.142 + */
   1.143 +void addLinesScore(LJField *p, LJBits lines) {
   1.144 +  const int nLines = countOnes(lines);
   1.145 +  int oldLines = p->nLinesThisPiece;
   1.146 +  int tdsSection = p->lines / 10 + 1;
   1.147 +
   1.148 +  p->lines += nLines;
   1.149 +  if (updLevelAfterLines(p, nLines)) {
   1.150 +    p->sounds |= LJSND_SECTIONUP;
   1.151 +  }
   1.152 +
   1.153 +  switch (p->scoreStyle) {
   1.154 +  
   1.155 +  case LJSCORE_TNT64:
   1.156 +    if (nLines < 1) {
   1.157 +      return;
   1.158 +    }
   1.159 +    for (int i = nLines; i > 0; --i) {
   1.160 +      if (oldLines > sizeof(squareScoreTable) - 1) {
   1.161 +        oldLines = sizeof(squareScoreTable) - 1;
   1.162 +      }
   1.163 +      p->score += 100 * squareScoreTable[oldLines++];
   1.164 +    } break;
   1.165 +    
   1.166 +  case LJSCORE_NES:
   1.167 +    {
   1.168 +      int garbageLevel = (nLines > 4) ? 4 : nLines;
   1.169 +      int value = nesScoreTable[garbageLevel];
   1.170 +
   1.171 +      p->score += 10 * tdsSection * value;
   1.172 +      if (nLines >= 4) {
   1.173 +        p->sounds |= LJSND_SETB2B;
   1.174 +      }
   1.175 +    } break;
   1.176 +
   1.177 +  case LJSCORE_TDS:
   1.178 +    {
   1.179 +      // Garbage based scoring system.
   1.180 +      int garbageLevel = (nLines > 4) ? 4 : nLines;
   1.181 +      int chain, value;
   1.182 +      
   1.183 +      if (p->isSpin) {
   1.184 +        chain = (nLines >= 1);
   1.185 +        value = tdsTSScoreTable[garbageLevel];
   1.186 +        
   1.187 +        // TDS gives fewer points for a T-spin single
   1.188 +        // that involves a wall kick.
   1.189 +        if (p->isSpin == 2 && nLines < 2) {
   1.190 +          value >>= 2;
   1.191 +        }
   1.192 +      } else {
   1.193 +        chain = (nLines >= 4);
   1.194 +        value = tdsScoreTable[garbageLevel];
   1.195 +      }
   1.196 +      if (chain && p->chain && nLines >= 1) {  // b2b
   1.197 +        value = value * 3 / 2;
   1.198 +      }
   1.199 +
   1.200 +      if (tdsSection > 20) {
   1.201 +        tdsSection = 20;
   1.202 +      }
   1.203 +      p->score += 100 * tdsSection * value;
   1.204 +      if (nLines >= 1) {
   1.205 +        if (chain) {
   1.206 +          p->sounds |= LJSND_SETB2B;
   1.207 +          if (p->chain) {
   1.208 +            p->sounds |= LJSND_B2B;
   1.209 +          }
   1.210 +        }
   1.211 +        p->chain = chain;
   1.212 +      }
   1.213 +    } break;
   1.214 +
   1.215 +  case LJSCORE_LJ:
   1.216 +  case LJSCORE_LJ_NERF:
   1.217 +    if (nLines >= 1) {
   1.218 +      // Garbage based scoring system.
   1.219 +      int garbageLevel = (nLines > 4) ? 4 : nLines;
   1.220 +      unsigned int chain, garbage;
   1.221 +
   1.222 +      if (p->isSpin) {
   1.223 +        chain = (nLines >= 1);
   1.224 +        garbage = tSpinGarbageScoreTable[garbageLevel];
   1.225 +        if (p->scoreStyle == LJSCORE_LJ_NERF) {
   1.226 +          garbage /= 2;
   1.227 +        }
   1.228 +        garbage += (chain && p->chain);
   1.229 +      } else {
   1.230 +        chain = (nLines >= 4);
   1.231 +        garbage = garbageScoreTable[garbageLevel]
   1.232 +                      + (chain && p->chain);
   1.233 +      }
   1.234 +      p->score += 100 * nLines + 200 * garbage;
   1.235 +      p->outGarbage += garbage;
   1.236 +      if (chain) {
   1.237 +        p->sounds |= LJSND_SETB2B;
   1.238 +        if (p->chain) {
   1.239 +          p->sounds |= LJSND_B2B;
   1.240 +        }
   1.241 +      }
   1.242 +      p->chain = chain;
   1.243 +    } break;
   1.244 +  
   1.245 +  case LJSCORE_HOTLINE:
   1.246 +    for (int y = 0; y < 24; ++y) {
   1.247 +      if (lines & (1 << y)) {
   1.248 +        p->score += 100 * hotlineRows[y];
   1.249 +      }
   1.250 +    } break;
   1.251 +
   1.252 +  }
   1.253 +
   1.254 +  p->nLinesThisPiece += nLines;
   1.255 +}
   1.256 +
   1.257 +/**
   1.258 + * Puts a zigzag pattern of garbage into the playfield.
   1.259 + * Useful as a test suite for calcZigzagGrade() in debrief.c.
   1.260 + * @param p the playfield to set up
   1.261 + * @param height the number of rows to set up
   1.262 + */
   1.263 +void setupZigzagField(LJField *p, size_t height) {
   1.264 +  unsigned int hole = p->leftWall;
   1.265 +  int delta = 1;
   1.266 +
   1.267 +  if (height > p->ceiling) {
   1.268 +    height = p->ceiling;
   1.269 +  }
   1.270 +
   1.271 +  // set up example pattern
   1.272 +  for (size_t y = 0; y < height; ++y) {
   1.273 +    for (size_t x = p->leftWall; x < p->rightWall; ++x) {
   1.274 +
   1.275 +      // A block should be in all cells of the row
   1.276 +      // except for the hole cell, which should be empty.
   1.277 +      if (x == hole) {
   1.278 +        p->b[y][x] = 0;
   1.279 +      } else {
   1.280 +        p->b[y][x] = 0x80;
   1.281 +      }
   1.282 +    }
   1.283 +
   1.284 +    if (hole == p->rightWall - 1) {
   1.285 +      delta = -1;
   1.286 +    } else if (hole == p->leftWall) {
   1.287 +      delta = 1;
   1.288 +    }
   1.289 +    hole += delta;
   1.290 +  }
   1.291 +
   1.292 +  // clear rows above pattern
   1.293 +  for (size_t y = height; y < LJ_PF_HT; ++y) {
   1.294 +    for (size_t x = p->leftWall; x < p->rightWall; ++x) {
   1.295 +      p->b[y][x] = 0;
   1.296 +    }
   1.297 +  }
   1.298 +}