diff src/lj.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/lj.c	Fri Mar 13 00:39:12 2009 -0700
     1.3 @@ -0,0 +1,1516 @@
     1.4 +/* Engine of LOCKJAW, an implementation of the Soviet Mind Game
     1.5 +
     1.6 +Copyright (C) 2006 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 +
    1.29 +#define LJ_INTERNAL
    1.30 +#include "lj.h"
    1.31 +
    1.32 +unsigned int ljRand(LJField *p) {
    1.33 +  p->seed = p->seed * 2147001325 + 715136305;
    1.34 +  return p->seed >> 17;
    1.35 +}
    1.36 +
    1.37 +static inline void ljAssert(LJField *p, int shouldBeTrue, const char *reason) {
    1.38 +  if (!shouldBeTrue) {
    1.39 +    p->state = LJS_GAMEOVER;
    1.40 +  }
    1.41 +}
    1.42 +
    1.43 +static const char xShapes[N_PIECE_SHAPES][4][4] = {
    1.44 +  { {0,1,2,3}, {2,2,2,2}, {3,2,1,0}, {1,1,1,1} }, // I
    1.45 +  { {0,1,2,0}, {1,1,1,2}, {2,1,0,2}, {1,1,1,0} }, // J
    1.46 +  { {0,1,2,2}, {1,1,1,2}, {2,1,0,0}, {1,1,1,0} }, // L
    1.47 +  { {1,1,2,2}, {1,2,2,1}, {2,2,1,1}, {2,1,1,2} }, // O
    1.48 +  { {0,1,1,2}, {1,1,2,2}, {2,1,1,0}, {1,1,0,0} }, // S
    1.49 +  { {0,1,2,1}, {1,1,1,2}, {2,1,0,1}, {1,1,1,0} }, // T
    1.50 +  { {0,1,1,2}, {2,2,1,1}, {2,1,1,0}, {0,0,1,1} }, // Z
    1.51 +  { {0,1,2,3}, {2,2,2,2}, {3,2,1,0}, {1,1,1,1} }, // I2
    1.52 +  { {0,1,2,0}, {1,1,1,2}, {2,1,0,2}, {1,1,1,0} }, // I3
    1.53 +  { {1,1,2,2}, {1,2,2,1}, {2,2,1,1}, {2,1,1,2} }, // L3
    1.54 +};
    1.55 +
    1.56 +static const char yShapes[N_PIECE_SHAPES][4][4] = {
    1.57 +  { {2,2,2,2}, {3,2,1,0}, {1,1,1,1}, {0,1,2,3} }, // I
    1.58 +  { {2,2,2,3}, {3,2,1,3}, {2,2,2,1}, {1,2,3,1} }, // J
    1.59 +  { {2,2,2,3}, {3,2,1,1}, {2,2,2,1}, {1,2,3,3} }, // L
    1.60 +  { {2,3,3,2}, {3,3,2,2}, {3,2,2,3}, {2,2,3,3} }, // O
    1.61 +  { {2,2,3,3}, {3,2,2,1}, {2,2,1,1}, {1,2,2,3} }, // S
    1.62 +  { {2,2,2,3}, {3,2,1,2}, {2,2,2,1}, {1,2,3,2} }, // T
    1.63 +  { {3,3,2,2}, {3,2,2,1}, {1,1,2,2}, {1,2,2,3} }, // Z
    1.64 +  { {2,2,2,2}, {3,2,1,0}, {1,1,1,1}, {0,1,2,3} }, // I2
    1.65 +  { {2,2,2,3}, {3,2,1,3}, {2,2,2,1}, {1,2,3,1} }, // I3
    1.66 +  { {2,3,3,2}, {3,3,2,2}, {3,2,2,3}, {2,2,3,3} }, // L3
    1.67 +};
    1.68 +
    1.69 +const char pieceColors[N_PIECE_SHAPES] = {
    1.70 +  0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
    1.71 +  0x40, 0x10, 0x20
    1.72 +};
    1.73 +
    1.74 +static const signed char connShapes[N_PIECE_SHAPES][4] = {
    1.75 +  {CONNECT_R,   CONNECT_LR,  CONNECT_LR,  CONNECT_L},  // I
    1.76 +  {CONNECT_UR,  CONNECT_LR,  CONNECT_L,   CONNECT_D},  // J
    1.77 +  {CONNECT_R,   CONNECT_LR,  CONNECT_UL,  CONNECT_D},  // L
    1.78 +  {CONNECT_UR,  CONNECT_DR,  CONNECT_DL,  CONNECT_UL}, // O
    1.79 +  {CONNECT_R,   CONNECT_UL,  CONNECT_DR,  CONNECT_L},  // S
    1.80 +  {CONNECT_R,   CONNECT_ULR, CONNECT_L,   CONNECT_D},  // T
    1.81 +  {CONNECT_R,   CONNECT_DL,  CONNECT_UR,  CONNECT_L},  // Z
    1.82 +  {-1,          CONNECT_R,   CONNECT_L,   -1},         // I2
    1.83 +  {CONNECT_R,   CONNECT_LR,  CONNECT_L,   -1},         // I3
    1.84 +  {CONNECT_UR,  CONNECT_D,   -1,          CONNECT_L},  // L3
    1.85 +};
    1.86 +
    1.87 +static inline int pieceToFieldBlock(int piece, int conn) {
    1.88 +  return conn | pieceColors[piece];
    1.89 +}
    1.90 +
    1.91 +/**
    1.92 + * @theta rotation state: 0-3 normal, 4 for next piece
    1.93 + */
    1.94 +void expandPieceToBlocks(LJBlkSpec out[],
    1.95 +                         const LJField *p, int piece, int xBase, int yBase, int theta) {
    1.96 +  int shape = piece & LJP_MASK;
    1.97 +  
    1.98 +  if (theta >= 4) {
    1.99 +    const LJRotSystem *rs = rotSystems[p->rotationSystem];
   1.100 +    int kickData = rs->entryOffset[shape];
   1.101 +    xBase += WKX(kickData);
   1.102 +    yBase += WKY(kickData);
   1.103 +    kickData = rs->entryOffset[LJP_I];
   1.104 +    xBase -= WKX(kickData);
   1.105 +    yBase -= WKY(kickData);
   1.106 +    theta = rs->entryTheta[shape];
   1.107 +  }
   1.108 +  
   1.109 +  const char *xBl = xShapes[shape][theta];
   1.110 +  const char *yBl = yShapes[shape][theta];
   1.111 +
   1.112 +  for (int blk = 0; blk < 4; ++blk) {
   1.113 +    if (connShapes[shape][blk] == -1) {
   1.114 +      out[blk].conn = 0;
   1.115 +    } else {
   1.116 +      int conn = connShapes[shape][blk] << theta;
   1.117 +      int connRotated = (conn | (conn >> 4)) & CONNECT_MASK;
   1.118 +      int color = ((0x10 << blk) & piece) ? 8 : piece;
   1.119 +      out[blk].y = yBl[blk] + yBase;
   1.120 +      out[blk].x = xBl[blk] + xBase;
   1.121 +      out[blk].conn = pieceToFieldBlock(color, connRotated);
   1.122 +    }
   1.123 +  }
   1.124 +}
   1.125 +
   1.126 +int isOccupied(const LJField *p, int x, int y) {
   1.127 +  if (x < p->leftWall || x >= p->rightWall || y < 0)
   1.128 +    return 1;
   1.129 +  if (y > LJ_PF_HT)
   1.130 +    return 0;
   1.131 +  return p->b[y][x] > 1;
   1.132 +}
   1.133 +
   1.134 +int isCollision(const LJField *p, int x, int y, int theta) {
   1.135 +  LJBlkSpec blocks[4];
   1.136 +  int piece = p->curPiece[0];
   1.137 +
   1.138 +  expandPieceToBlocks(blocks, p, piece, x, y, theta);
   1.139 +
   1.140 +  for (int blk = 0; blk < 4; ++blk) {
   1.141 +    if (blocks[blk].conn
   1.142 +        && isOccupied(p, blocks[blk].x, blocks[blk].y))
   1.143 +      return 1;
   1.144 +  }
   1.145 +  return 0;
   1.146 +}
   1.147 +
   1.148 +/**
   1.149 + * Determines whether the piece that just landed was a T-spin.
   1.150 + * Must be called just BEFORE lockdown writes the blocks to the
   1.151 + * playfield; otherwise TNT will break.
   1.152 + */
   1.153 +static int isTspin(const LJField *p) {
   1.154 +  int blks = 0;
   1.155 +  int x = p->x;
   1.156 +  int y = p->hardDropY;
   1.157 +
   1.158 +  switch (p->tSpinAlgo) {
   1.159 +  case LJTS_TNT:
   1.160 +    if (!isCollision(p, x, y + 1, p->theta)
   1.161 +        || !isCollision(p, x - 1, y, p->theta)
   1.162 +        || !isCollision(p, x + 1, y, p->theta)) {
   1.163 +      return 0;
   1.164 +    }    
   1.165 +    return p->isSpin;
   1.166 +  case LJTS_TDS_NO_KICK:
   1.167 +  
   1.168 +    // If t-spin involved wall kick, don't count it
   1.169 +    if (p->isSpin == 2) {
   1.170 +      return 0;
   1.171 +    }
   1.172 +    
   1.173 +    // otherwise fall through
   1.174 +  case LJTS_TDS:
   1.175 +    // 1. T tetromino
   1.176 +    if ((p->curPiece[0] & LJP_MASK) != LJP_T) {
   1.177 +      return 0;
   1.178 +    }
   1.179 +
   1.180 +    // 2. Last move was spin
   1.181 +    if (!p->isSpin) {
   1.182 +      return 0;
   1.183 +    }
   1.184 +
   1.185 +    // 3. At least three cells around the rotation center are full
   1.186 +    if (isOccupied(p, x, y + 1)) {
   1.187 +      ++blks;
   1.188 +    }
   1.189 +    if (isOccupied(p, x, y + 3)) {
   1.190 +      ++blks;
   1.191 +    }
   1.192 +    if (isOccupied(p, x + 2, y + 1)) {
   1.193 +      ++blks;
   1.194 +    }
   1.195 +    if (isOccupied(p, x + 2, y + 3)) {
   1.196 +      ++blks;
   1.197 +    }
   1.198 +    if (blks < 3) {
   1.199 +      return 0;
   1.200 +    }
   1.201 +    
   1.202 +    // 3. Last move was spin
   1.203 +    return p->isSpin;
   1.204 +  default:
   1.205 +    return 0;
   1.206 +  }
   1.207 +}
   1.208 +
   1.209 +/**
   1.210 + * Calculates where the active piece in a playfield will fall
   1.211 + * if dropped, and writes it back to the playfield.
   1.212 + * This value is used for ghost piece, gravity, soft drop, and
   1.213 + * hard drop. Call this after the active piece has been spawned,
   1.214 + * moved, or rotated.
   1.215 + * @param p the playfield
   1.216 + */
   1.217 +static void updHardDropY(LJField *p) {
   1.218 +  int x = p->x;
   1.219 +  int y = ljfixfloor(p->y);
   1.220 +  int theta = p->theta;
   1.221 +
   1.222 +  if (p->bottomBlocks) {
   1.223 +    y = -2;
   1.224 +    while (y < (int)LJ_PF_HT
   1.225 +           && y < ljfixfloor(p->y)
   1.226 +           && isCollision(p, x, y, theta)) {
   1.227 +      ++y;
   1.228 +    }
   1.229 +  }
   1.230 +  else {
   1.231 +    while (!isCollision(p, x, y - 1, theta)) {
   1.232 +      --y;
   1.233 +    }
   1.234 +  }
   1.235 +  p->hardDropY = y;
   1.236 +}
   1.237 +
   1.238 +
   1.239 +/**
   1.240 + * Look for a TNT square in this position.
   1.241 + * @param x column of left side, such that 0 <= x <= playfield width - 4
   1.242 + * @param y row of bottom block, such that 0 <= y <= playfield height - 4
   1.243 + * @param isMulti nonzero for multisquares; 0 for monosquares
   1.244 + * @return nonzero if found; 0 if not found
   1.245 + */
   1.246 +static int isSquareAt(LJField *p, int x, int y, int isMulti)
   1.247 +{
   1.248 +  int firstColor = p->b[y][x] & COLOR_MASK;
   1.249 +
   1.250 +  // Check the frame to make sure it isn't connected to anything else
   1.251 +  for(int i = 0; i <= 3; i++)
   1.252 +  {
   1.253 +    /* don't allow squares within parts of squares */
   1.254 +    if((p->b[y + i][x] & COLOR_MASK) >= 0x80)
   1.255 +      return 0;
   1.256 +    /* the block doesn't connect on the left */
   1.257 +    if(p->b[y + i][x] & CONNECT_L)
   1.258 +      return 0;
   1.259 +    /* the block doesn't connect on the right */
   1.260 +    if(p->b[y + i][x + 3] & CONNECT_R)
   1.261 +      return 0;
   1.262 +    /* the block doesn't connect on the bottom */
   1.263 +    if(p->b[y][x + i] & CONNECT_D)
   1.264 +      return 0;
   1.265 +    /* the block doesn't connect on the top */
   1.266 +    if(p->b[y + 3][x + i] & CONNECT_U)
   1.267 +      return 0;
   1.268 +  }
   1.269 +
   1.270 +  for(int ySub = 0; ySub < 4; ++ySub)
   1.271 +  {
   1.272 +    for(int xSub = 0; xSub <= 3; ++xSub)
   1.273 +    {
   1.274 +      int blkHere = p->b[y + ySub][x + xSub];
   1.275 +
   1.276 +      /* the square contains no nonexistent blocks */
   1.277 +      if(!blkHere)
   1.278 +        return 0;
   1.279 +      /* the square contains no blocks of garbage or broken pieces */
   1.280 +      if((blkHere & COLOR_MASK) == 0x80)
   1.281 +        return 0;
   1.282 +      /* if looking for monosquares, disallow multisquares */
   1.283 +      if(isMulti == 0 && (blkHere & COLOR_MASK) != firstColor)
   1.284 +        return 0;
   1.285 +    }
   1.286 +  }
   1.287 +  return 1;
   1.288 +}
   1.289 +
   1.290 +/**
   1.291 + * Replaces the 4x4 blocks with a 4x4 square.
   1.292 + * @param x column of left side, such that 0 <= x <= playfield width - 4
   1.293 + * @param y row of bottom block, such that 0 <= y <= playfield height - 4
   1.294 + * @param isMulti nonzero for multisquares; 0 for monosquares
   1.295 + * @return the rows that were changed
   1.296 + */
   1.297 +static LJBits markSquare(LJField *p, int x, int y, int isMulti)
   1.298 +{
   1.299 +  int baseBlk = (isMulti ? 0xA0 : 0xB0)
   1.300 +                | CONNECT_MASK;
   1.301 +  for(int i = 0; i < 4; ++i)
   1.302 +  {
   1.303 +    int c;
   1.304 +
   1.305 +    if(i == 0)
   1.306 +      c = baseBlk & ~CONNECT_D;
   1.307 +    else if(i == 3)
   1.308 +      c = baseBlk & ~CONNECT_U;
   1.309 +    else
   1.310 +      c = baseBlk;
   1.311 +
   1.312 +    p->b[y + i][x + 0] = c & ~CONNECT_L;
   1.313 +    p->b[y + i][x + 1] = c;
   1.314 +    p->b[y + i][x + 2] = c;
   1.315 +    p->b[y + i][x + 3] = c & ~CONNECT_R;
   1.316 +  }
   1.317 +
   1.318 +  if (isMulti) {
   1.319 +    ++p->multisquares;
   1.320 +  } else {
   1.321 +    ++p->monosquares;
   1.322 +  }
   1.323 +
   1.324 +  return 0x0F << y;
   1.325 +}
   1.326 +
   1.327 +
   1.328 +/**
   1.329 + * Marks all 4x4 squares in the playfield.
   1.330 + * In the case that a single tetromino forms multiple overlapping
   1.331 + * squares, prefers gold over silver, high over low, left over right.
   1.332 + * @param isMulti nonzero for multisquares; 0 for monosquares
   1.333 + * @return the rows that were changed
   1.334 + */
   1.335 +static LJBits findSquares(LJField *p, int isMulti) {
   1.336 +  LJBits changed = 0;
   1.337 +
   1.338 +  for (int y = LJ_PF_HT - 4; y >= 0; --y) {
   1.339 +    for (int x = p->leftWall; x <= p->rightWall - 4; ++x) {
   1.340 +      int baseBlk = p->b[y][x];
   1.341 +
   1.342 +      // If this block is filled in and not connected on the left or right
   1.343 +      // then do stuff to it
   1.344 +      if (baseBlk
   1.345 +          && !(baseBlk & (CONNECT_D | CONNECT_L))
   1.346 +          && isSquareAt(p, x, y, isMulti)) {
   1.347 +        changed |= markSquare(p, x, y, isMulti);
   1.348 +        p->sounds |= LJSND_SQUARE;
   1.349 +      }
   1.350 +    }
   1.351 +  }
   1.352 +  return changed;
   1.353 +}
   1.354 +
   1.355 +static LJBits stickyGluing(LJField *p) {
   1.356 +  LJBits changed = 0;
   1.357 +  int byColor = (p->gluing == LJGLUING_STICKY_BY_COLOR);
   1.358 +
   1.359 +  for (int y = 0; y < LJ_PF_HT; ++y) {
   1.360 +    for (int x = p->leftWall; x < p->rightWall - 1; ++x) {
   1.361 +      int l = p->b[y][x];
   1.362 +      int r = p->b[y][x + 1];
   1.363 +      if (l && r
   1.364 +          && (!(l & CONNECT_R) || !(r & CONNECT_L))
   1.365 +          && (!byColor || !((l ^ r) & COLOR_MASK))) {
   1.366 +        p->b[y][x] = l | CONNECT_R;
   1.367 +        p->b[y][x + 1] = r | CONNECT_L;
   1.368 +        changed |= 1 << y;
   1.369 +      }
   1.370 +    }
   1.371 +  }
   1.372 +  
   1.373 +  for (int y = 0; y < LJ_PF_HT - 1; ++y) {
   1.374 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
   1.375 +      int b = p->b[y][x];
   1.376 +      int t = p->b[y + 1][x];
   1.377 +      if (b && t
   1.378 +          && (!(b & CONNECT_U) || !(t & CONNECT_D))
   1.379 +          && (!byColor || !((b ^ t) & COLOR_MASK))) {
   1.380 +        p->b[y][x] = b | CONNECT_U;
   1.381 +        p->b[y + 1][x] = t | CONNECT_D;
   1.382 +        changed |= 3 << y;
   1.383 +      }
   1.384 +    }
   1.385 +  }
   1.386 +  
   1.387 +  return changed;
   1.388 +}
   1.389 +
   1.390 +/**
   1.391 + * Locks the current tetromino in the playfield.
   1.392 + * @param p the playfield
   1.393 + * @return  the rows in which the tetromino was placed
   1.394 + */
   1.395 +static LJBits lockPiece(LJField *p) {
   1.396 +  LJBits rows = 0;
   1.397 +  int xBase = p->x;
   1.398 +  int yBase = ljfixfloor(p->y);
   1.399 +  LJBlkSpec blocks[4];
   1.400 +  int piece = p->curPiece[0];
   1.401 +  expandPieceToBlocks(blocks, p, piece, xBase, yBase, p->theta);
   1.402 +  
   1.403 +  p->isSpin = isTspin(p);
   1.404 +
   1.405 +  for (int blk = 0; blk < 4; ++blk) {
   1.406 +    int blkY = blocks[blk].y;
   1.407 +    int blkX = blocks[blk].x;
   1.408 +    int blkValue = blocks[blk].conn;
   1.409 +
   1.410 +    if(blkValue && blkY >= 0 && blkY < LJ_PF_HT) {
   1.411 +      rows |= 1 << blkY;
   1.412 +      if (blkX >= p->leftWall && blkX < p->rightWall) {
   1.413 +        p->b[blkY][blkX] = blkValue;
   1.414 +      }
   1.415 +    }
   1.416 +  }
   1.417 +  p->sounds |= LJSND_LOCK;
   1.418 +  p->alreadyHeld = 0;
   1.419 +  p->nLinesThisPiece = 0;
   1.420 +
   1.421 +  return rows;
   1.422 +}
   1.423 +
   1.424 +void shuffleColumns(LJField *p) {
   1.425 +  unsigned int permu[LJ_PF_WID];
   1.426 +
   1.427 +  for (int x = p->leftWall; x < p->rightWall; ++x) {
   1.428 +    permu[x] = x;
   1.429 +  }
   1.430 +  for (int x = p->rightWall - 1; x > p->rightWall + 1; --x) {
   1.431 +    int r = ljRand(p) % x;
   1.432 +    int t = permu[x];
   1.433 +    permu[x] = permu[r];
   1.434 +    permu[r] = t;
   1.435 +  }
   1.436 +
   1.437 +  for (int y = 0; y < LJ_PF_HT; ++y) {
   1.438 +    unsigned int blk[LJ_PF_WID];
   1.439 +    
   1.440 +    // Copy blocks to temporary buffer, eliminating left and right connections
   1.441 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
   1.442 +      blk[x] = p->b[y][permu[x]] & ~(CONNECT_L | CONNECT_R);
   1.443 +    }
   1.444 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
   1.445 +      p->b[y][x] = blk[x];
   1.446 +    }
   1.447 +  }
   1.448 +}
   1.449 +
   1.450 +/**
   1.451 + * Rotates and shifts a new piece per the rotation system.
   1.452 + */
   1.453 +static void rotateNewPiece(LJField *p) {
   1.454 +  const LJRotSystem *rs = rotSystems[p->rotationSystem];
   1.455 +  int shape = p->curPiece[0] & LJP_MASK;
   1.456 +  int kickData = rs->entryOffset[shape];
   1.457 +  p->x += WKX(kickData);
   1.458 +  p->y += ljitofix(WKY(kickData));
   1.459 +  p->theta = rs->entryTheta[shape];
   1.460 +  
   1.461 +  /* Find extents of piece so that it doesn't enter already collided
   1.462 +     with the walls (otherwise, in Tengen rotation and well width 4,
   1.463 +     spawning I causes block out) */
   1.464 +
   1.465 +  LJBlkSpec blocks[4];
   1.466 +  int minX = LJ_PF_WID - 1, maxX = 0, maxY = 0;
   1.467 +
   1.468 +  expandPieceToBlocks(blocks, p, p->curPiece[0],
   1.469 +                      p->x, ljfixfloor(p->y), p->theta);
   1.470 +  for (int blk = 0; blk < 4; ++blk) {
   1.471 +    if (blocks[blk].conn) {
   1.472 +      if (maxY < blocks[blk].y) {
   1.473 +        maxY = blocks[blk].y;
   1.474 +      }
   1.475 +      if (maxX < blocks[blk].x) {
   1.476 +        maxX = blocks[blk].x;
   1.477 +      }
   1.478 +      if (minX > blocks[blk].x) {
   1.479 +        minX = blocks[blk].x;
   1.480 +      }
   1.481 +    }
   1.482 +  }
   1.483 +
   1.484 +  if (minX < p->leftWall) {
   1.485 +    p->x += minX - p->leftWall;
   1.486 +  } else if (maxX >= p->rightWall) {
   1.487 +    p->x -= (maxX + 1) - p->rightWall;
   1.488 +  }
   1.489 +  if (!p->enterAbove && maxY >= p->ceiling) {
   1.490 +    p->y -= ljitofix(maxY - p->ceiling) + 1;
   1.491 +  }
   1.492 +}
   1.493 +
   1.494 +
   1.495 +/**
   1.496 + * Spawns a tetromino onto the playfield.
   1.497 + * @param p    the playfield
   1.498 + * @param hold 0 for spawning from next; nonzero for swapping with hold
   1.499 + * @return     the rows that were changed by banana effect
   1.500 + */
   1.501 +static LJBits newPiece(LJField *p, int hold) {
   1.502 +  LJBits changed = 0;
   1.503 +  int initial = p->state != LJS_FALLING
   1.504 +                && p->state != LJS_LANDED;
   1.505 +  int ihs = initial && hold;
   1.506 +
   1.507 +  if (hold) {
   1.508 +    if (p->state == LJS_LANDED && p->lockReset == LJLOCK_SPAWN) {
   1.509 +      p->speed.lockDelay = p->stateTime;
   1.510 +    }
   1.511 +  } else {
   1.512 +    p->upwardKicks = 0;
   1.513 +  }
   1.514 +  p->x = (LJ_PF_WID - 4) / 2;
   1.515 +  p->dropDist = 0;
   1.516 +  if (!ihs) {
   1.517 +    p->state = LJS_FALLING;
   1.518 +    p->stateTime = 0;
   1.519 +  }
   1.520 +  p->isSpin = 0;
   1.521 +  p->y = ljitofix(p->ceiling - 2);
   1.522 +  
   1.523 +  /* Note: The gimmick sets the gravity speed after frame() finishes. */
   1.524 +
   1.525 +  if (hold) {
   1.526 +    int temp;
   1.527 +
   1.528 +    if (p->holdStyle != LJHOLD_TO_NEXT) {
   1.529 +      temp = p->holdPiece;
   1.530 +      p->holdPiece = p->curPiece[ihs];
   1.531 +    } else {
   1.532 +      temp = p->curPiece[ihs + 1];
   1.533 +      p->curPiece[ihs + 1] = p->curPiece[ihs];
   1.534 +    }
   1.535 +    p->curPiece[ihs] = temp;
   1.536 +    p->alreadyHeld = 1;
   1.537 +    p->sounds |= LJSND_HOLD;
   1.538 +
   1.539 +    // If a negative number was swapped into the current piece,
   1.540 +    // then there was nothing in the hold space (e.g. at the
   1.541 +    // beginning of a round).  In this case, we'll need to fall
   1.542 +    // through and generate a new piece.
   1.543 +    if (temp >= 0) {
   1.544 +      rotateNewPiece(p);
   1.545 +      return changed;
   1.546 +    }
   1.547 +  }
   1.548 +
   1.549 +  // Shift the next pieces down
   1.550 +  for (int i = 0; i < LJ_NEXT_PIECES; i++) {
   1.551 +    p->curPiece[i] = p->curPiece[i + 1];
   1.552 +  }
   1.553 +  p->sounds |= LJSND_SPAWN;
   1.554 +
   1.555 +  p->curPiece[LJ_NEXT_PIECES] = randomize(p);
   1.556 +  ++p->nPieces;
   1.557 +  if (!p->canRotate) {
   1.558 +    p->theta = (ljRand(p) >> 12) & 0x03;
   1.559 +  } else {
   1.560 +    rotateNewPiece(p);
   1.561 +  }
   1.562 +
   1.563 +  return changed;
   1.564 +}
   1.565 +
   1.566 +void newGame(LJField *p) {
   1.567 +
   1.568 +  // Clear playfield
   1.569 +  for (int y = 0; y < LJ_PF_HT; y++) {
   1.570 +    for(int x = 0; x < LJ_PF_WID; x++) {
   1.571 +      p->b[y][x] = 0;
   1.572 +    }
   1.573 +  }
   1.574 +  
   1.575 +  for (int y = 0; y < LJ_MAX_LINES_PER_PIECE; ++y) {
   1.576 +    p->nLineClears[y] = 0;
   1.577 +  }
   1.578 +
   1.579 +  if (p->holdStyle == LJHOLD_TNT) {
   1.580 +    initRandomize(p);
   1.581 +    p->holdPiece = randomize(p);
   1.582 +  } else {
   1.583 +    p->holdPiece = -1;  // sentinel for no piece in hold box
   1.584 +  }
   1.585 +
   1.586 +  // Generate pieces
   1.587 +  initRandomize(p);
   1.588 +  for(int i = 0; i < LJ_NEXT_PIECES; i++) {
   1.589 +    newPiece(p, 0);
   1.590 +  }
   1.591 +  p->clearedLines = 0;
   1.592 +  p->nPieces = 0;
   1.593 +  p->state = LJS_NEW_PIECE;
   1.594 +  p->stateTime = 1;
   1.595 +  p->garbage = 0;
   1.596 +  p->outGarbage = 0;
   1.597 +  p->score = 0;
   1.598 +  p->gameTime = 0;
   1.599 +  p->activeTime = 0;
   1.600 +  p->lines = 0;
   1.601 +  p->alreadyHeld = 0;
   1.602 +  p->chain = 0;
   1.603 +  p->theta = 0;
   1.604 +  p->nLinesThisPiece = 0;
   1.605 +  p->canRotate = 1;
   1.606 +  p->speed.entryDelay = 0;
   1.607 +  p->garbageRandomness = 64;
   1.608 +  p->reloaded = 0;
   1.609 +  p->monosquares = 0;
   1.610 +  p->multisquares = 0;
   1.611 +
   1.612 +  p->garbageX = ljRand(p) % (p->rightWall - p->leftWall)
   1.613 +                + p->leftWall;
   1.614 +}
   1.615 +
   1.616 +/**
   1.617 + * Handles scoring for hard and soft drops.
   1.618 + * @return 0 for no change or LJ_DIRTY_SCORE for change
   1.619 + */
   1.620 +LJBits scoreDropRows(LJField *p, LJFixed gravity, LJFixed newY) {
   1.621 +  LJBits changed = 0;
   1.622 +  if (gravity > 0) {
   1.623 +    int fallDist = ljfixfloor(p->y) - ljfixfloor(newY);
   1.624 +      
   1.625 +    p->dropDist += fallDist;
   1.626 +
   1.627 +    // Double scoring for hard drop
   1.628 +    if (p->dropScoreStyle == LJDROP_1S_2H
   1.629 +        && gravity >= ljitofix(p->ceiling)) {
   1.630 +      fallDist *= 2;
   1.631 +    }
   1.632 +    if (p->dropScoreStyle == LJDROP_1CELL
   1.633 +        || p->dropScoreStyle == LJDROP_1S_2H) {
   1.634 +      p->score += fallDist;
   1.635 +      changed |= LJ_DIRTY_SCORE;
   1.636 +    }
   1.637 +
   1.638 +    // Handle scoring for continuous drop    
   1.639 +    if (p->dropScoreStyle == LJDROP_NES
   1.640 +        && newY <= ljitofix(p->hardDropY)) {
   1.641 +      p->score += p->dropDist;
   1.642 +      changed |= LJ_DIRTY_SCORE;
   1.643 +      p->dropDist = 0;
   1.644 +    }
   1.645 +  } else {
   1.646 +    p->dropDist = 0;
   1.647 +  }
   1.648 +  return changed;
   1.649 +}
   1.650 +
   1.651 +/**
   1.652 + * Handles gravity.
   1.653 + * @param p         the playfield
   1.654 + * @param gravity   amount of additional gravity applied by the player
   1.655 + * @param otherKeys other LJI_* keys being pressed by the player
   1.656 + *                  (specifically LJI_LOCK)
   1.657 + */
   1.658 +static LJBits doPieceGravity(LJField *p, LJFixed gravity, LJBits otherKeys) {
   1.659 +  int changedRows = 0;
   1.660 +
   1.661 +  LJFixed newY = p->y - gravity - p->speed.gravity;
   1.662 +
   1.663 +  // Check for landed
   1.664 +  if (newY <= ljitofix(p->hardDropY)) {
   1.665 +    newY = ljitofix(p->hardDropY);
   1.666 +    
   1.667 +    // Downward movement does not result in a T-spin
   1.668 +    if (ljfixfloor(newY) < ljfixfloor(p->y)) {
   1.669 +      p->isSpin = 0;
   1.670 +    }
   1.671 +
   1.672 +    changedRows |= scoreDropRows(p, gravity, newY);
   1.673 +    p->y = newY;
   1.674 +
   1.675 +    if (p->state == LJS_FALLING) {
   1.676 +      p->state = LJS_LANDED;
   1.677 +      p->stateTime = p->speed.lockDelay;
   1.678 +      p->sounds |= LJSND_LAND;
   1.679 +    }
   1.680 +    if (p->stateTime > 0 && !(otherKeys & LJI_LOCK)) {
   1.681 +      // lock delay > 128 is a special case for don't lock at all
   1.682 +      if (p->setLockDelay < 128) {
   1.683 +        --p->stateTime;
   1.684 +      }
   1.685 +    } else {
   1.686 +      LJBits lockRows = lockPiece(p);
   1.687 +      p->state = LJS_LINES;
   1.688 +      p->stateTime = 0;
   1.689 +      changedRows |= lockRows | LJ_DIRTY_NEXT;
   1.690 +      
   1.691 +      // LOCK OUT rule: If a piece locks
   1.692 +      // completely above the ceiling, the game is over.
   1.693 +      if (!(lockRows & ((1 << p->ceiling) - 1))) {
   1.694 +        p->state = LJS_GAMEOVER;
   1.695 +      }
   1.696 +    }
   1.697 +  } else {
   1.698 +    changedRows |= scoreDropRows(p, gravity, newY);
   1.699 +    p->state = LJS_FALLING;
   1.700 +
   1.701 +    // Downward movement does not result in a T-spin
   1.702 +    if (ljfixfloor(newY) < ljfixfloor(p->y)) {
   1.703 +      p->isSpin = 0;
   1.704 +    }
   1.705 +  }
   1.706 +  p->y = newY;
   1.707 +  return changedRows;
   1.708 +}
   1.709 +
   1.710 +static void updateLockDelayOnMove(LJField *p, int isUpwardKick) {
   1.711 +  if (p->state == LJS_LANDED) {
   1.712 +
   1.713 +    // if tetromino can move down, go back to falling state;
   1.714 +    // otherwise, reset lock delay.
   1.715 +    if (!isCollision(p, p->x, ljfixfloor(p->y) - 1, p->theta)) {
   1.716 +      p->state = LJS_FALLING;
   1.717 +      if (p->lockReset == LJLOCK_SPAWN) {
   1.718 +        p->speed.lockDelay = p->stateTime;
   1.719 +      }
   1.720 +      p->stateTime = 0;
   1.721 +    } else {
   1.722 +      p->state = LJS_LANDED;
   1.723 +      if (p->lockReset == LJLOCK_MOVE
   1.724 +          || (p->lockReset == LJLOCK_STEP && isUpwardKick)) {
   1.725 +        p->stateTime = p->speed.lockDelay;
   1.726 +      }
   1.727 +    }
   1.728 +  }
   1.729 +}
   1.730 +
   1.731 +static int doRotate(LJField *p,
   1.732 +                    int newDir,
   1.733 +                    const WallKickTable *const pieceTable,
   1.734 +                    int withKicks) {
   1.735 +  int baseX = p->x;
   1.736 +  int baseY = ljfixfloor(p->y);
   1.737 +  
   1.738 +  withKicks = withKicks ? KICK_TABLE_LEN : 1;
   1.739 +  
   1.740 +  // allow specifying null tables for O
   1.741 +  if (!pieceTable) {
   1.742 +    if (!isCollision(p, baseX, baseY, newDir)) {
   1.743 +      p->theta = newDir;
   1.744 +      p->sounds |= LJSND_ROTATE;
   1.745 +      return 1;
   1.746 +    }
   1.747 +    return 0;
   1.748 +  }
   1.749 +  
   1.750 +  const unsigned char *const table = (*pieceTable)[newDir];
   1.751 +  int baseKickY = -1000;  // sentinel for uninitialized
   1.752 +
   1.753 +  for (int kick = 0; kick < withKicks; kick++) {
   1.754 +    unsigned int kickData = table[kick];
   1.755 +    if (kickData == WK_END) {
   1.756 +      break;
   1.757 +    } else if (kickData == ARIKA_IF_NOT_CENTER) {
   1.758 +    
   1.759 +      // Compute the free space position
   1.760 +      kickData = table[0];
   1.761 +      int kickX = WKX(kickData) + baseX;
   1.762 +      int kickY = WKY(kickData) + baseY;
   1.763 +      LJBlkSpec blocks[4];
   1.764 +      int allowed = 0;
   1.765 +      
   1.766 +      // If a block other than the center column of this position
   1.767 +      // is occupied, go to the next step (that is,
   1.768 +      // allow subsequent kicks)
   1.769 +      expandPieceToBlocks(blocks, p, p->curPiece[0],
   1.770 +                          kickX, kickY, newDir);
   1.771 +                          
   1.772 +      for (int blk = 0; blk < 4; ++blk) {
   1.773 +        if (blocks[blk].conn
   1.774 +            && blocks[blk].x != baseX + 1
   1.775 +            && isOccupied(p, blocks[blk].x, blocks[blk].y)) {
   1.776 +          allowed = 1;
   1.777 +          break;
   1.778 +        }
   1.779 +      }
   1.780 +
   1.781 +      // Otherwise, only blocks of the center column are occupied,
   1.782 +      // and these cannot kick the piece.
   1.783 +      if (!allowed) {
   1.784 +        return 0;
   1.785 +      }
   1.786 +    } else {
   1.787 +      int kickX = WKX(kickData) + baseX;
   1.788 +      int kickY = WKY(kickData) + baseY;
   1.789 +
   1.790 +      // If this is the first 
   1.791 +      if (baseKickY == -1000) {
   1.792 +        baseKickY = kickY;
   1.793 +      }
   1.794 +      if ((kickY <= baseKickY || p->upwardKicks < p->maxUpwardKicks)
   1.795 +          && !isCollision(p, kickX, kickY, newDir)) {
   1.796 +        p->theta = newDir;
   1.797 +        p->x = kickX;
   1.798 +        if (kickY > baseKickY) {
   1.799 +          p->y = ljitofix(kickY);
   1.800 +          
   1.801 +          // on the FIRST floor kick of a piece, reset lock delay
   1.802 +          if (p->upwardKicks == 0) {
   1.803 +            updateLockDelayOnMove(p, 1);
   1.804 +          }
   1.805 +          ++p->upwardKicks;
   1.806 +        } else if (kickY < baseKickY) {
   1.807 +          p->y = ljitofix(kickY) + 0xFFFF;
   1.808 +        } else {
   1.809 +          p->y = ljitofix(kickY) + (p->y & 0xFFFF);
   1.810 +        }
   1.811 +        p->sounds |= LJSND_ROTATE;
   1.812 +        return 1;
   1.813 +      }
   1.814 +    }
   1.815 +  }
   1.816 +  return 0;
   1.817 +}
   1.818 +
   1.819 +
   1.820 +/**
   1.821 + * Tries to rotate the current piece 90 degrees counterclockwise,
   1.822 + * using the counterclockwise wall kick tables.
   1.823 + * @param p  the playfield
   1.824 + * @param withKicks nonzero for wall kick, zero for none
   1.825 + */
   1.826 +static int doRotateLeft(LJField *p, int withKicks) {
   1.827 +  int newDir = (p->theta + 3) & 0x03;
   1.828 +  const LJRotSystem *rs = rotSystems[p->rotationSystem];
   1.829 +  signed int tableNo = rs->kicksL[p->curPiece[0] & LJP_MASK];
   1.830 +  const WallKickTable *pieceTable = tableNo >= 0
   1.831 +                                    ? &(rs->kickTables[tableNo])
   1.832 +                                    : NULL;
   1.833 +  return doRotate(p, newDir, pieceTable, withKicks);
   1.834 +}
   1.835 +
   1.836 +/**
   1.837 + * Tries to rotate the current piece 90 degrees clockwise,
   1.838 + * using the clockwise wall kick tables.
   1.839 + * @param p  the playfield
   1.840 + * @param withKicks nonzero for wall kick, zero for none
   1.841 + */
   1.842 +static int doRotateRight(LJField *p, int withKicks) {
   1.843 +  int newDir = (p->theta + 1) & 0x03;
   1.844 +  const LJRotSystem *rs = rotSystems[p->rotationSystem];
   1.845 +  signed int tableNo = rs->kicksR[p->curPiece[0] & LJP_MASK];
   1.846 +  const WallKickTable *pieceTable = tableNo >= 0
   1.847 +                                    ? &(rs->kickTables[tableNo])
   1.848 +                                    : NULL;
   1.849 +  return doRotate(p, newDir, pieceTable, withKicks);
   1.850 +}
   1.851 +
   1.852 +static LJBits checkLines(const LJField *p, LJBits checkRows) {
   1.853 +  LJBits foundLines = 0;
   1.854 +  for (int y = 0;
   1.855 +       y < LJ_PF_HT && checkRows != 0;
   1.856 +       ++y, checkRows >>= 1) {
   1.857 +    if (checkRows & 1) {
   1.858 +      const unsigned char *row = p->b[y];
   1.859 +      int found = 1;
   1.860 +
   1.861 +      for (int x = p->leftWall; x < p->rightWall && found; ++x) {
   1.862 +        found = found && (row[x] != 0);
   1.863 +      }
   1.864 +      if (found) {
   1.865 +        foundLines |= 1 << y;
   1.866 +      }
   1.867 +    }
   1.868 +  }
   1.869 +
   1.870 +  return foundLines;
   1.871 +}
   1.872 +
   1.873 +static void fillCLoop(LJField *p, int x, int y, unsigned int src, unsigned int dst)
   1.874 +{
   1.875 +  int fillL, fillR, i;
   1.876 +
   1.877 +  fillL = fillR = x;
   1.878 +  do {
   1.879 +    p->c[y][fillL] = dst;
   1.880 +    fillL--;
   1.881 +  } while ((fillL >= p->leftWall) && (p->c[y][fillL] == src));
   1.882 +  fillL++;
   1.883 +
   1.884 +  do {
   1.885 +    p->c[y][fillR] = dst;
   1.886 +    fillR++;
   1.887 +  } while ((fillR < p->rightWall) && (p->c[y][fillR] == src));
   1.888 +  fillR--;
   1.889 +
   1.890 +  for(i = fillL; i <= fillR; i++)
   1.891 +  {
   1.892 +    if(y > 0 && p->c[y - 1][i] == src) {
   1.893 +	  fillCLoop(p, i, y - 1, src, dst);
   1.894 +    }
   1.895 +    if(y < LJ_PF_HT - 1 && p->c[y + 1][i] == src) {
   1.896 +	  fillCLoop(p, i, y + 1, src, dst);
   1.897 +    }
   1.898 +  }
   1.899 +}
   1.900 +
   1.901 +
   1.902 +static void fillC(LJField *p, int x, int y, int dstC) {
   1.903 +  if (p->c[y][x] != dstC) {
   1.904 +    fillCLoop(p, x, y, p->c[y][x], dstC);
   1.905 +  }
   1.906 +}
   1.907 +
   1.908 +/**
   1.909 + * Locks the block regions that have landed.
   1.910 + * @param p the playfield
   1.911 + */
   1.912 +void lockLandedRegions(LJField *p) {
   1.913 +  // Look for regions that are on top of ground regions, where
   1.914 +  // "ground regions" are any block that is solid and whose region ID is 0.
   1.915 +  for (int landed = 1; landed != 0; ) {
   1.916 +    landed = 0;
   1.917 +    // If something hit the ground, erase its floating bit
   1.918 +    for (int y = 0; y < LJ_PF_HT; ++y) {
   1.919 +      for (int x = p->leftWall; x < p->rightWall; ++x) {
   1.920 +        // If there's a floating block here, and a not-floating block below,
   1.921 +        // erase this block group's floatiness
   1.922 +        if (p->c[y][x] &&
   1.923 +            (y == 0 || (!p->c[y - 1][x] && p->b[y - 1][x]))) {
   1.924 +          fillC(p, x, y, 0);
   1.925 +          p->sounds |= LJSND_LAND;
   1.926 +          landed = 1;
   1.927 +        }
   1.928 +      }
   1.929 +    }
   1.930 +  }
   1.931 +}
   1.932 +
   1.933 +/**
   1.934 + * Separates the playfield into regions that shall fall separately.
   1.935 + * @param p the playfield
   1.936 + * @param byColors Zero: Touching blocks form a region.
   1.937 + *               Nonzero: Touching blocks of a single color form a region.
   1.938 + */
   1.939 +static void stickyMark(LJField *p, int byColors) {
   1.940 +  for (unsigned int y = 0; y < LJ_PF_HT; ++y) {
   1.941 +    for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
   1.942 +      int blkHere = p->b[y][x] & COLOR_MASK;
   1.943 +
   1.944 +      if (!byColors) {
   1.945 +        blkHere = blkHere ? 0x10 : 0;
   1.946 +      }
   1.947 +      p->c[y][x] = blkHere;
   1.948 +    }
   1.949 +  }
   1.950 +  
   1.951 +  if (byColors) {
   1.952 +    lockLandedRegions(p);
   1.953 +  } else {
   1.954 +    // mark the bottom row as landed
   1.955 +    for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
   1.956 +      if (p->c[0][x]) {
   1.957 +        fillC(p, x, 0, 0);
   1.958 +      }
   1.959 +    }
   1.960 +  }
   1.961 +
   1.962 +  //p->stateTime = 5;
   1.963 +}
   1.964 +
   1.965 +
   1.966 +/**
   1.967 + * Sets the color of a piece to gray/garbage (0x80).
   1.968 + * @param x column of a block in the piece
   1.969 + * @param y row of a block in the piece
   1.970 + * @param rgn the region ID
   1.971 + */
   1.972 +static void cascadeMarkPiece(LJField *p, int x, int y, int rgn) {
   1.973 +  int blkHere = p->b[y][x];
   1.974 +  
   1.975 +  if (blkHere && !p->c[y][x]) {
   1.976 +    p->c[y][x] = rgn;
   1.977 +    if((blkHere & CONNECT_D) && y > 0)
   1.978 +      cascadeMarkPiece(p, x, y - 1, rgn);
   1.979 +    if((blkHere & CONNECT_U) && y < LJ_PF_HT - 1)
   1.980 +      cascadeMarkPiece(p, x, y + 1, rgn);
   1.981 +    if((blkHere & CONNECT_L) && x > p->leftWall)
   1.982 +      cascadeMarkPiece(p, x - 1, y, rgn);
   1.983 +    if((blkHere & CONNECT_R) && x < p->rightWall - 1 )
   1.984 +      cascadeMarkPiece(p, x + 1, y, rgn);
   1.985 +  }
   1.986 +}
   1.987 +
   1.988 +static void cascadeMark(LJField *p) {
   1.989 +  int rgn = 0;
   1.990 +
   1.991 +  for (unsigned int y = 0; y < LJ_PF_HT; ++y) {
   1.992 +    for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
   1.993 +      p->c[y][x] = 0;
   1.994 +    }
   1.995 +  }
   1.996 +  for (unsigned int y = 0; y < LJ_PF_HT; ++y) {
   1.997 +    for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
   1.998 +      cascadeMarkPiece(p, x, y, ++rgn);
   1.999 +    }
  1.1000 +  }
  1.1001 +  lockLandedRegions(p);
  1.1002 +  //p->stateTime = 5;
  1.1003 +}
  1.1004 +
  1.1005 +static void breakEverything(LJField *p) {
  1.1006 +  for (unsigned int y = 0; y < LJ_PF_HT; ++y) {
  1.1007 +    for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
  1.1008 +      if (p->b[y][x]) {
  1.1009 +        p->c[y][x] = x + 1;
  1.1010 +        p->b[y][x] = 0x80;
  1.1011 +      } else {
  1.1012 +        p->c[y][x] = 0;
  1.1013 +      }
  1.1014 +    }
  1.1015 +  }
  1.1016 +  
  1.1017 +  // fill bottom row
  1.1018 +  for (unsigned int x = x = p->leftWall; x < p->rightWall; ++x) {
  1.1019 +    if (p->c[0][x]) {
  1.1020 +      fillC(p, x, 0, 0);
  1.1021 +    }
  1.1022 +  }
  1.1023 +  p->stateTime = 5;
  1.1024 +}
  1.1025 +
  1.1026 +/**
  1.1027 + * Sets the color of a piece to gray/garbage (0x80).
  1.1028 + * @param x column of a block in the piece
  1.1029 + * @param y row of a block in the piece
  1.1030 + */
  1.1031 +static LJBits breakPiece(LJField *p, int x, int y) {
  1.1032 +  LJBits changed = 0;
  1.1033 +  int blkHere = p->b[y][x];
  1.1034 +  int colorHere = blkHere & COLOR_MASK;
  1.1035 +  int connHere = blkHere & CONNECT_MASK;
  1.1036 +  
  1.1037 +  if (colorHere != 0x80) {
  1.1038 +    p->b[y][x] = connHere | 0x80;
  1.1039 +    changed |= 1 << y;
  1.1040 +    if((blkHere & CONNECT_D) && y > 0)
  1.1041 +      changed |= breakPiece(p, x, y - 1);
  1.1042 +    if((blkHere & CONNECT_U) && y < LJ_PF_HT - 1)
  1.1043 +      changed |= breakPiece(p, x, y + 1);
  1.1044 +    if((blkHere & CONNECT_L) && x > p->leftWall)
  1.1045 +      changed |= breakPiece(p, x - 1, y);
  1.1046 +    if((blkHere & CONNECT_R) && x < p->rightWall - 1 )
  1.1047 +      changed |= breakPiece(p, x + 1, y);
  1.1048 +  }
  1.1049 +  return changed;
  1.1050 +}
  1.1051 +
  1.1052 +/**
  1.1053 + * Removes blocks in cleared lines from the playfield and marks
  1.1054 + * remaining blocks for gravity.
  1.1055 + * @param the lines to be cleared
  1.1056 + * @return the rows that were changed
  1.1057 + */
  1.1058 +static LJBits clearLines(LJField *p, LJBits foundLines) {
  1.1059 +  LJBits changed = foundLines;
  1.1060 +
  1.1061 +  p->clearedLines = foundLines;
  1.1062 +  if (foundLines != 0) {
  1.1063 +    p->sounds |= LJSND_LINE;
  1.1064 +  }
  1.1065 +  for (int y = 0;
  1.1066 +       y < LJ_PF_HT && foundLines != 0;
  1.1067 +       ++y, foundLines >>= 1) {
  1.1068 +    if (foundLines & 1) {
  1.1069 +
  1.1070 +      // In square mode, turn broken pieces (but not 4x4 squares)
  1.1071 +      // into garbage blocks
  1.1072 +      if (p->gluing == LJGLUING_SQUARE) {
  1.1073 +        for (int x = p->leftWall; x < p->rightWall; ++x) {
  1.1074 +          if (p->b[y][x] < 0x80) {
  1.1075 +            changed |= breakPiece(p, x, y);
  1.1076 +          } else if ((p->b[y][x] & (0xF0 | CONNECT_R)) == 0xA0) {
  1.1077 +            p->score += 500;
  1.1078 +            changed |= LJ_DIRTY_SCORE;
  1.1079 +          } else if ((p->b[y][x] & (0xF0 | CONNECT_R)) == 0xB0) {
  1.1080 +            p->score += 1000;
  1.1081 +            changed |= LJ_DIRTY_SCORE;
  1.1082 +          }
  1.1083 +        }
  1.1084 +      }
  1.1085 +
  1.1086 +      for (int x = p->leftWall; x < p->rightWall; ++x) {
  1.1087 +        p->b[y][x] = 0;
  1.1088 +      }
  1.1089 +
  1.1090 +      // break connections up and down (like Tengen Tetyais)
  1.1091 +      if (y > 0) {
  1.1092 +        for (int x = p->leftWall; x < p->rightWall; ++x) {
  1.1093 +          p->b[y - 1][x] &= ~CONNECT_U;
  1.1094 +        }
  1.1095 +        changed |= 1 << (y - 1);
  1.1096 +      }
  1.1097 +      if (y < LJ_PF_HT - 1) {
  1.1098 +        for (int x = p->leftWall; x < p->rightWall; ++x) {
  1.1099 +          p->b[y + 1][x] &= ~CONNECT_D;
  1.1100 +        }
  1.1101 +        changed |= 1 << (y + 1);
  1.1102 +      }
  1.1103 +    }
  1.1104 +  }
  1.1105 +  if (p->gluing == LJGLUING_SQUARE && p->isSpin) {
  1.1106 +    breakEverything(p);
  1.1107 +    changed |= (1 << LJ_PF_HT) - 1;
  1.1108 +  } else if (p->clearGravity == LJGRAV_STICKY) {
  1.1109 +    stickyMark(p, 0);
  1.1110 +  } else if (p->clearGravity == LJGRAV_STICKY_BY_COLOR) {
  1.1111 +    stickyMark(p, 1);
  1.1112 +  } else if (p->clearGravity == LJGRAV_CASCADE) {
  1.1113 +    cascadeMark(p);
  1.1114 +  } else {
  1.1115 +    p->stateTime = 0;
  1.1116 +  }
  1.1117 +  
  1.1118 +  return changed;
  1.1119 +}
  1.1120 +
  1.1121 +static unsigned int stickyFallLines(LJField *p) {
  1.1122 +  int minY = LJ_PF_HT;
  1.1123 +
  1.1124 +  // Move floating stuff down by one block
  1.1125 +  for (int y = 1; y < LJ_PF_HT; ++y) {
  1.1126 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
  1.1127 +      int c = p->c[y][x];
  1.1128 +      if (c) {
  1.1129 +        p->c[y - 1][x] = c;
  1.1130 +        p->c[y][x] = 0;
  1.1131 +        p->b[y - 1][x] = p->b[y][x];
  1.1132 +        p->b[y][x] = 0;
  1.1133 +
  1.1134 +        if (minY > y) {
  1.1135 +          minY = y;
  1.1136 +        }
  1.1137 +      }
  1.1138 +    }
  1.1139 +  }
  1.1140 +
  1.1141 +  // If we're done, skip all the rest
  1.1142 +  if (minY >= LJ_PF_HT) {
  1.1143 +    return LJ_PF_HT;
  1.1144 +  }
  1.1145 +
  1.1146 +  lockLandedRegions(p);
  1.1147 +  return minY - 1;
  1.1148 +}
  1.1149 +
  1.1150 +
  1.1151 +unsigned int bfffo(LJBits rowBits) {
  1.1152 +  unsigned int lineRow = 0;
  1.1153 +
  1.1154 +  if (!rowBits) {
  1.1155 +    return 32;
  1.1156 +  }
  1.1157 +  if ((rowBits & 0xFFFF) == 0) {
  1.1158 +    rowBits >>= 16;
  1.1159 +    lineRow += 16;
  1.1160 +  }
  1.1161 +  if ((rowBits & 0xFF) == 0) {
  1.1162 +    rowBits >>= 8;
  1.1163 +    lineRow += 8;
  1.1164 +  }
  1.1165 +  if ((rowBits & 0xF) == 0) {
  1.1166 +    rowBits >>= 4;
  1.1167 +    lineRow += 4;
  1.1168 +  }
  1.1169 +  if ((rowBits & 0x3) == 0) {
  1.1170 +    rowBits >>= 2;
  1.1171 +    lineRow += 2;
  1.1172 +  }
  1.1173 +  if ((rowBits & 0x1) == 0) {
  1.1174 +    rowBits >>= 1;
  1.1175 +    lineRow += 1;
  1.1176 +  }
  1.1177 +  return lineRow;
  1.1178 +}
  1.1179 +
  1.1180 +static unsigned int fallLines(LJField *p) {
  1.1181 +  LJBits rowBits = p->tempRows;
  1.1182 +  unsigned int lineRow = 0;
  1.1183 +  
  1.1184 +  if (p->clearGravity != LJGRAV_NAIVE
  1.1185 +      || (p->gluing == LJGLUING_SQUARE && p->isSpin)) {
  1.1186 +    return stickyFallLines(p);
  1.1187 +  }
  1.1188 +
  1.1189 +  if (rowBits == 0) {
  1.1190 +    return LJ_PF_HT;
  1.1191 +  }
  1.1192 +
  1.1193 +  lineRow = bfffo(rowBits);
  1.1194 +  p->tempRows = (p->tempRows & (-2 << lineRow)) >> 1;
  1.1195 +  if (!(p->tempRows & (1 << lineRow))) {
  1.1196 +    p->sounds |= LJSND_LAND;
  1.1197 +  }
  1.1198 +
  1.1199 +  // Move stuff down by 1 row
  1.1200 +  for (int y = lineRow; y < LJ_PF_HT - 1; ++y) {
  1.1201 +    unsigned char *row0 = p->b[y];
  1.1202 +    const unsigned char *row1 = p->b[y + 1];
  1.1203 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
  1.1204 +      row0[x] = row1[x];
  1.1205 +    }
  1.1206 +  }
  1.1207 +
  1.1208 +  // Clear top row
  1.1209 +  for (int x = p->leftWall; x < p->rightWall; ++x) {
  1.1210 +    p->b[LJ_PF_HT - 1][x] = 0;
  1.1211 +  }
  1.1212 +
  1.1213 +  return lineRow;
  1.1214 +}
  1.1215 +
  1.1216 +/**
  1.1217 + * Counts the bits in a bit vector that are true (1).
  1.1218 + * @param b a bit vector
  1.1219 + * @return the number of 1 bits
  1.1220 + */
  1.1221 +unsigned int countOnes(LJBits b) {
  1.1222 +  unsigned int ones = 0;
  1.1223 +
  1.1224 +  while(b) {
  1.1225 +    ++ones;
  1.1226 +    b &= b - 1;
  1.1227 +  }
  1.1228 +  return ones;
  1.1229 +}
  1.1230 +
  1.1231 +static unsigned int addGarbage(LJField *p) {
  1.1232 +  // Move stuff up by 1 row
  1.1233 +  for (int y = LJ_PF_HT - 2; y >= 0; --y) {
  1.1234 +    unsigned char *row1 = p->b[y + 1];
  1.1235 +    const unsigned char *row0 = p->b[y];
  1.1236 +    for (int x = p->leftWall; x < p->rightWall; ++x) {
  1.1237 +      row1[x] = row0[x];
  1.1238 +    }
  1.1239 +  }
  1.1240 +
  1.1241 +  // Garbage in bottom row
  1.1242 +  for (int x = p->leftWall; x < p->rightWall; ++x) {
  1.1243 +    p->b[0][x] = 0x80;
  1.1244 +  }
  1.1245 +
  1.1246 +  // Randomize location of garbage hole
  1.1247 +  int r = (ljRand(p) >> 7) & 0xFF;
  1.1248 +  int garbageX = (r <= p->garbageRandomness)
  1.1249 +                 ? (ljRand(p) % (p->rightWall - p->leftWall)) + p->leftWall
  1.1250 +                 : p->garbageX;
  1.1251 +  p->b[0][garbageX] = 0;
  1.1252 +  p->garbageX = garbageX;
  1.1253 +
  1.1254 +  // Horizontally connect the blocks that make up garbage in bottom row
  1.1255 +  for (int x = p->leftWall; x < p->rightWall - 1; ++x) {
  1.1256 +    if (p->b[0][x] && p->b[0][x + 1]) {
  1.1257 +      p->b[0][x] |= CONNECT_R;
  1.1258 +      p->b[0][x + 1] |= CONNECT_L;
  1.1259 +    }
  1.1260 +  }
  1.1261 +
  1.1262 +  // Vertically connect the blocks that make up garbage in bottom row
  1.1263 +  for (int x = p->leftWall; x < p->rightWall; ++x) {
  1.1264 +    if (p->b[0][x]
  1.1265 +        && ((p->b[1][x] & COLOR_MASK) == 0x80)) {
  1.1266 +      p->b[0][x] |= CONNECT_U;
  1.1267 +      p->b[1][x] |= CONNECT_D;
  1.1268 +    }
  1.1269 +  }
  1.1270 +
  1.1271 +  return (1 << LJ_PF_VIS_HT) - 1;
  1.1272 +}
  1.1273 +
  1.1274 +/**
  1.1275 + * Computes the score and outgoing garbage for lines
  1.1276 + * and adds them to the player's total.
  1.1277 + * @param p The playfield
  1.1278 + * @param lines Bit array where 1 means a line clear on this row.
  1.1279 + */
  1.1280 +void addLinesScore(LJField *p, LJBits lines);
  1.1281 +
  1.1282 +/**
  1.1283 + * Things to do just before launching a new piece.
  1.1284 + */
  1.1285 +void prepareForNewPiece(LJField *p) {
  1.1286 +  int nLines = p->nLinesThisPiece;
  1.1287 +
  1.1288 +  // Add to number of clears of each number of lines.
  1.1289 +  int idx;
  1.1290 +
  1.1291 +  if (p->clearGravity == LJGRAV_NAIVE) {
  1.1292 +    // In naive gravity, T-spin single, double, and triple counts
  1.1293 +    // are stored in 5-row, 6-row, and 7-row slots, and T-spins
  1.1294 +    // that clear 0 lines are not counted.
  1.1295 +    idx = (nLines > 4)
  1.1296 +          ? 4
  1.1297 +          : nLines;
  1.1298 +
  1.1299 +    if (nLines >= 1 && p->isSpin) {
  1.1300 +      idx += 4;
  1.1301 +    }
  1.1302 +  } else {
  1.1303 +    idx = (nLines > LJ_MAX_LINES_PER_PIECE)
  1.1304 +          ? LJ_MAX_LINES_PER_PIECE
  1.1305 +          : nLines;
  1.1306 +  }
  1.1307 +  
  1.1308 +  if (nLines < 4 && !p->isSpin && p->garbageStyle == LJGARBAGE_HRDERBY) {
  1.1309 +    p->garbage += nLines;
  1.1310 +  }
  1.1311 +
  1.1312 +  ljAssert(p,
  1.1313 +           idx <= LJ_MAX_LINES_PER_PIECE,
  1.1314 +           "Number of lines cleared with last piece out of bounds in prepareForNewPiece");
  1.1315 +  if (idx > 0) {
  1.1316 +    p->nLineClears[idx - 1] += 1;
  1.1317 +  }
  1.1318 +  
  1.1319 +  p->state = LJS_NEW_PIECE;
  1.1320 +  p->stateTime = p->speed.entryDelay;
  1.1321 +}
  1.1322 +
  1.1323 +LJBits frame(LJField *p, const LJInput *in) {
  1.1324 +  LJBits changedRows = 0;
  1.1325 +  LJBits tempRows;
  1.1326 +  int distance;
  1.1327 +  int moved = 0;
  1.1328 +  int isFirstFrame = (p->sounds & (LJSND_SPAWN | LJSND_HOLD))
  1.1329 +                     ? 1 : 0;
  1.1330 +
  1.1331 +  p->sounds = 0;
  1.1332 +
  1.1333 +  // Make hold work at ANY time.
  1.1334 +  if ((in->other & LJI_HOLD)
  1.1335 +      && p->holdStyle != LJHOLD_NONE
  1.1336 +      && !p->alreadyHeld) {
  1.1337 +    changedRows |= newPiece(p, 1) | LJ_DIRTY_NEXT;
  1.1338 +    updHardDropY(p);
  1.1339 +  }
  1.1340 +
  1.1341 +  switch(p->state) {
  1.1342 +  case LJS_NEW_PIECE:
  1.1343 +    if (p->garbage > 0) { 
  1.1344 +      changedRows |= addGarbage(p);
  1.1345 +      --p->garbage;
  1.1346 +      break;
  1.1347 +    }
  1.1348 +    
  1.1349 +    // ARE
  1.1350 +    if (p->stateTime > 0) {
  1.1351 +      --p->stateTime;
  1.1352 +    }
  1.1353 +    if (p->stateTime > 0) {
  1.1354 +      break;
  1.1355 +    }
  1.1356 +
  1.1357 +    changedRows |= newPiece(p, 0);
  1.1358 +    updHardDropY(p);
  1.1359 +    changedRows |= LJ_DIRTY_NEXT;
  1.1360 +    
  1.1361 +    /* If the piece spawns over blocks, this is a "block out" and a
  1.1362 +       loss under most rules.  But skip checking it now so that
  1.1363 +       the player can IRS out of block out. */
  1.1364 +
  1.1365 +    break;
  1.1366 +      
  1.1367 +  // the following executes for both falling and landed
  1.1368 +  case LJS_FALLING:
  1.1369 +  case LJS_LANDED:
  1.1370 +    ++p->activeTime;
  1.1371 +    if (p->canRotate) {
  1.1372 +      int oldX = p->x;
  1.1373 +      int oldY = ljfixfloor(p->y);
  1.1374 +      distance = in->rotation;
  1.1375 +      for(; distance < 0; ++distance) {
  1.1376 +
  1.1377 +        // 0.43: Do not apply wall kicks on the first frame (IRS)
  1.1378 +        if (doRotateLeft(p, !isFirstFrame)) {
  1.1379 +          moved = 1;
  1.1380 +
  1.1381 +          // isSpin == 1: twist in place
  1.1382 +          // isSpin == 2: twist with kick
  1.1383 +          // if (p->tSpinAlgo == LJTS_TDS_NO_KICK)
  1.1384 +          // then only isSpin == 1 is worth points.
  1.1385 +          if (p->x == oldX && ljfixfloor(p->y) == oldY) {
  1.1386 +            p->isSpin = 1;
  1.1387 +          } else {
  1.1388 +            p->isSpin = 2;
  1.1389 +          }
  1.1390 +        } else {
  1.1391 +          break;
  1.1392 +        }
  1.1393 +      }
  1.1394 +      for(; distance > 0; --distance) {
  1.1395 +        if (doRotateRight(p, !isFirstFrame)) {
  1.1396 +          moved = 1;
  1.1397 +          if (p->x == oldX && ljfixfloor(p->y) == oldY) {
  1.1398 +            p->isSpin = 1;
  1.1399 +          } else {
  1.1400 +            p->isSpin = 2;
  1.1401 +          }
  1.1402 +        } else {
  1.1403 +          break;
  1.1404 +        }
  1.1405 +      }
  1.1406 +    }
  1.1407 +    
  1.1408 +    /* If the piece spawns over blocks, this is a "block out" and a
  1.1409 +       loss under most rules.  Check it now, after rotation, so that
  1.1410 +       the player can IRS out of block out. */
  1.1411 +    if (isFirstFrame
  1.1412 +        && isCollision(p, p->x, ljfixfloor(p->y), p->theta)) {
  1.1413 +      changedRows |= lockPiece(p);
  1.1414 +      p->state = LJS_GAMEOVER;
  1.1415 +    }
  1.1416 +
  1.1417 +    distance = in->movement;
  1.1418 +    for(; distance < 0; ++distance) {
  1.1419 +      if (!isCollision(p, p->x - 1, ljfixfloor(p->y), p->theta)) {
  1.1420 +        --p->x;
  1.1421 +        p->sounds |= LJSND_SHIFT;
  1.1422 +        moved = 1;
  1.1423 +        p->isSpin = 0;
  1.1424 +      }
  1.1425 +    }
  1.1426 +    for(; distance > 0; --distance) {
  1.1427 +      if (!isCollision(p, p->x + 1, ljfixfloor(p->y), p->theta)) {
  1.1428 +        ++p->x;
  1.1429 +        p->sounds |= LJSND_SHIFT;
  1.1430 +        moved = 1;
  1.1431 +        p->isSpin = 0;
  1.1432 +      }
  1.1433 +    }
  1.1434 +    updHardDropY(p);
  1.1435 +    if (p->state != LJS_GAMEOVER) {
  1.1436 +      if (moved) {
  1.1437 +        updateLockDelayOnMove(p, 0);
  1.1438 +      }
  1.1439 +      tempRows = doPieceGravity(p, ljitofix(in->gravity) >> 3, in->other);
  1.1440 +      p->tempRows = tempRows;
  1.1441 +      changedRows |= tempRows;
  1.1442 +    }
  1.1443 +
  1.1444 +    // At this point, if the piece locked,
  1.1445 +    // p->tempRows holds the rows in which the piece landed.
  1.1446 +    break;
  1.1447 +
  1.1448 +  case LJS_LINES:
  1.1449 +    if (p->stateTime > 0) {
  1.1450 +      --p->stateTime;
  1.1451 +    }
  1.1452 +    if (p->stateTime > 0) {
  1.1453 +      break;
  1.1454 +    }
  1.1455 +    if (p->gluing == LJGLUING_SQUARE) {
  1.1456 +      LJBits gluedRows = findSquares(p, 0);
  1.1457 +      gluedRows |= findSquares(p, 1);
  1.1458 +      changedRows |= gluedRows;
  1.1459 +      if (gluedRows) {
  1.1460 +      
  1.1461 +        // When a 4x4 block square is formed, a delay
  1.1462 +        // equal to the line delay is added.
  1.1463 +        p->stateTime += p->speed.lineDelay;
  1.1464 +        break;
  1.1465 +      }
  1.1466 +    } else if (p->gluing == LJGLUING_STICKY
  1.1467 +               || p->gluing == LJGLUING_STICKY_BY_COLOR) {
  1.1468 +      changedRows |= stickyGluing(p);
  1.1469 +    }
  1.1470 +
  1.1471 +    // At this point, p->tempRows holds the rows in which
  1.1472 +    // a line could possibly have been made.
  1.1473 +    tempRows = p->tempRows;
  1.1474 +    tempRows = checkLines(p, tempRows);
  1.1475 +    p->tempRows = tempRows;
  1.1476 +    // At this point, p->tempRows holds the rows in which
  1.1477 +    // a line HAS been made.
  1.1478 +    addLinesScore(p, tempRows);
  1.1479 +    changedRows |= LJ_DIRTY_SCORE;
  1.1480 +
  1.1481 +    // At this point, p->tempRows holds the rows in which a line
  1.1482 +    // HAS been made.
  1.1483 +    p->clearedLines = tempRows;
  1.1484 +    if (!tempRows) {
  1.1485 +      prepareForNewPiece(p);
  1.1486 +      break;
  1.1487 +    }
  1.1488 +
  1.1489 +    changedRows |= clearLines(p, tempRows);
  1.1490 +
  1.1491 +    p->state = LJS_LINES_FALLING;
  1.1492 +    p->stateTime += p->speed.lineDelay;
  1.1493 +    break;
  1.1494 +
  1.1495 +  case LJS_LINES_FALLING:
  1.1496 +    if (p->stateTime > 0) {
  1.1497 +      --p->stateTime;
  1.1498 +    }
  1.1499 +    if (p->stateTime > 0) {
  1.1500 +      break;
  1.1501 +    }
  1.1502 +    moved = fallLines(p);
  1.1503 +    if (moved >= LJ_PF_HT) {
  1.1504 +      p->state = LJS_LINES;
  1.1505 +      p->tempRows = (1 << LJ_PF_HT) - 1;
  1.1506 +    }
  1.1507 +    changedRows |= (~0 << moved) & ((1 << LJ_PF_VIS_HT) - 1);
  1.1508 +    break;
  1.1509 +
  1.1510 +  default:
  1.1511 +    break;
  1.1512 +
  1.1513 +  }
  1.1514 +
  1.1515 +  ++p->gameTime;
  1.1516 +  return changedRows;
  1.1517 +}
  1.1518 +
  1.1519 +