annotate src/lj.c @ 0:c84446dfb3f5

initial add
author paulo@localhost
date Fri, 13 Mar 2009 00:39:12 -0700
parents
children
rev   line source
paulo@0 1 /* Engine of LOCKJAW, an implementation of the Soviet Mind Game
paulo@0 2
paulo@0 3 Copyright (C) 2006 Damian Yerrick <tepples+lj@spamcop.net>
paulo@0 4
paulo@0 5 This work is free software; you can redistribute it and/or modify
paulo@0 6 it under the terms of the GNU General Public License as published by
paulo@0 7 the Free Software Foundation; either version 2 of the License, or
paulo@0 8 (at your option) any later version.
paulo@0 9
paulo@0 10 This program is distributed in the hope that it will be useful,
paulo@0 11 but WITHOUT ANY WARRANTY; without even the implied warranty of
paulo@0 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
paulo@0 13 GNU General Public License for more details.
paulo@0 14
paulo@0 15 You should have received a copy of the GNU General Public License
paulo@0 16 along with this program; if not, write to the Free Software
paulo@0 17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
paulo@0 18
paulo@0 19 Original game concept and design by Alexey Pajitnov.
paulo@0 20 The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
paulo@0 21 or The Tetris Company LLC.
paulo@0 22
paulo@0 23
paulo@0 24 */
paulo@0 25
paulo@0 26 #define LJ_INTERNAL
paulo@0 27 #include "lj.h"
paulo@0 28
paulo@0 29 unsigned int ljRand(LJField *p) {
paulo@0 30 p->seed = p->seed * 2147001325 + 715136305;
paulo@0 31 return p->seed >> 17;
paulo@0 32 }
paulo@0 33
paulo@0 34 static inline void ljAssert(LJField *p, int shouldBeTrue, const char *reason) {
paulo@0 35 if (!shouldBeTrue) {
paulo@0 36 p->state = LJS_GAMEOVER;
paulo@0 37 }
paulo@0 38 }
paulo@0 39
paulo@0 40 static const char xShapes[N_PIECE_SHAPES][4][4] = {
paulo@0 41 { {0,1,2,3}, {2,2,2,2}, {3,2,1,0}, {1,1,1,1} }, // I
paulo@0 42 { {0,1,2,0}, {1,1,1,2}, {2,1,0,2}, {1,1,1,0} }, // J
paulo@0 43 { {0,1,2,2}, {1,1,1,2}, {2,1,0,0}, {1,1,1,0} }, // L
paulo@0 44 { {1,1,2,2}, {1,2,2,1}, {2,2,1,1}, {2,1,1,2} }, // O
paulo@0 45 { {0,1,1,2}, {1,1,2,2}, {2,1,1,0}, {1,1,0,0} }, // S
paulo@0 46 { {0,1,2,1}, {1,1,1,2}, {2,1,0,1}, {1,1,1,0} }, // T
paulo@0 47 { {0,1,1,2}, {2,2,1,1}, {2,1,1,0}, {0,0,1,1} }, // Z
paulo@0 48 { {0,1,2,3}, {2,2,2,2}, {3,2,1,0}, {1,1,1,1} }, // I2
paulo@0 49 { {0,1,2,0}, {1,1,1,2}, {2,1,0,2}, {1,1,1,0} }, // I3
paulo@0 50 { {1,1,2,2}, {1,2,2,1}, {2,2,1,1}, {2,1,1,2} }, // L3
paulo@0 51 };
paulo@0 52
paulo@0 53 static const char yShapes[N_PIECE_SHAPES][4][4] = {
paulo@0 54 { {2,2,2,2}, {3,2,1,0}, {1,1,1,1}, {0,1,2,3} }, // I
paulo@0 55 { {2,2,2,3}, {3,2,1,3}, {2,2,2,1}, {1,2,3,1} }, // J
paulo@0 56 { {2,2,2,3}, {3,2,1,1}, {2,2,2,1}, {1,2,3,3} }, // L
paulo@0 57 { {2,3,3,2}, {3,3,2,2}, {3,2,2,3}, {2,2,3,3} }, // O
paulo@0 58 { {2,2,3,3}, {3,2,2,1}, {2,2,1,1}, {1,2,2,3} }, // S
paulo@0 59 { {2,2,2,3}, {3,2,1,2}, {2,2,2,1}, {1,2,3,2} }, // T
paulo@0 60 { {3,3,2,2}, {3,2,2,1}, {1,1,2,2}, {1,2,2,3} }, // Z
paulo@0 61 { {2,2,2,2}, {3,2,1,0}, {1,1,1,1}, {0,1,2,3} }, // I2
paulo@0 62 { {2,2,2,3}, {3,2,1,3}, {2,2,2,1}, {1,2,3,1} }, // I3
paulo@0 63 { {2,3,3,2}, {3,3,2,2}, {3,2,2,3}, {2,2,3,3} }, // L3
paulo@0 64 };
paulo@0 65
paulo@0 66 const char pieceColors[N_PIECE_SHAPES] = {
paulo@0 67 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
paulo@0 68 0x40, 0x10, 0x20
paulo@0 69 };
paulo@0 70
paulo@0 71 static const signed char connShapes[N_PIECE_SHAPES][4] = {
paulo@0 72 {CONNECT_R, CONNECT_LR, CONNECT_LR, CONNECT_L}, // I
paulo@0 73 {CONNECT_UR, CONNECT_LR, CONNECT_L, CONNECT_D}, // J
paulo@0 74 {CONNECT_R, CONNECT_LR, CONNECT_UL, CONNECT_D}, // L
paulo@0 75 {CONNECT_UR, CONNECT_DR, CONNECT_DL, CONNECT_UL}, // O
paulo@0 76 {CONNECT_R, CONNECT_UL, CONNECT_DR, CONNECT_L}, // S
paulo@0 77 {CONNECT_R, CONNECT_ULR, CONNECT_L, CONNECT_D}, // T
paulo@0 78 {CONNECT_R, CONNECT_DL, CONNECT_UR, CONNECT_L}, // Z
paulo@0 79 {-1, CONNECT_R, CONNECT_L, -1}, // I2
paulo@0 80 {CONNECT_R, CONNECT_LR, CONNECT_L, -1}, // I3
paulo@0 81 {CONNECT_UR, CONNECT_D, -1, CONNECT_L}, // L3
paulo@0 82 };
paulo@0 83
paulo@0 84 static inline int pieceToFieldBlock(int piece, int conn) {
paulo@0 85 return conn | pieceColors[piece];
paulo@0 86 }
paulo@0 87
paulo@0 88 /**
paulo@0 89 * @theta rotation state: 0-3 normal, 4 for next piece
paulo@0 90 */
paulo@0 91 void expandPieceToBlocks(LJBlkSpec out[],
paulo@0 92 const LJField *p, int piece, int xBase, int yBase, int theta) {
paulo@0 93 int shape = piece & LJP_MASK;
paulo@0 94
paulo@0 95 if (theta >= 4) {
paulo@0 96 const LJRotSystem *rs = rotSystems[p->rotationSystem];
paulo@0 97 int kickData = rs->entryOffset[shape];
paulo@0 98 xBase += WKX(kickData);
paulo@0 99 yBase += WKY(kickData);
paulo@0 100 kickData = rs->entryOffset[LJP_I];
paulo@0 101 xBase -= WKX(kickData);
paulo@0 102 yBase -= WKY(kickData);
paulo@0 103 theta = rs->entryTheta[shape];
paulo@0 104 }
paulo@0 105
paulo@0 106 const char *xBl = xShapes[shape][theta];
paulo@0 107 const char *yBl = yShapes[shape][theta];
paulo@0 108
paulo@0 109 for (int blk = 0; blk < 4; ++blk) {
paulo@0 110 if (connShapes[shape][blk] == -1) {
paulo@0 111 out[blk].conn = 0;
paulo@0 112 } else {
paulo@0 113 int conn = connShapes[shape][blk] << theta;
paulo@0 114 int connRotated = (conn | (conn >> 4)) & CONNECT_MASK;
paulo@0 115 int color = ((0x10 << blk) & piece) ? 8 : piece;
paulo@0 116 out[blk].y = yBl[blk] + yBase;
paulo@0 117 out[blk].x = xBl[blk] + xBase;
paulo@0 118 out[blk].conn = pieceToFieldBlock(color, connRotated);
paulo@0 119 }
paulo@0 120 }
paulo@0 121 }
paulo@0 122
paulo@0 123 int isOccupied(const LJField *p, int x, int y) {
paulo@0 124 if (x < p->leftWall || x >= p->rightWall || y < 0)
paulo@0 125 return 1;
paulo@0 126 if (y > LJ_PF_HT)
paulo@0 127 return 0;
paulo@0 128 return p->b[y][x] > 1;
paulo@0 129 }
paulo@0 130
paulo@0 131 int isCollision(const LJField *p, int x, int y, int theta) {
paulo@0 132 LJBlkSpec blocks[4];
paulo@0 133 int piece = p->curPiece[0];
paulo@0 134
paulo@0 135 expandPieceToBlocks(blocks, p, piece, x, y, theta);
paulo@0 136
paulo@0 137 for (int blk = 0; blk < 4; ++blk) {
paulo@0 138 if (blocks[blk].conn
paulo@0 139 && isOccupied(p, blocks[blk].x, blocks[blk].y))
paulo@0 140 return 1;
paulo@0 141 }
paulo@0 142 return 0;
paulo@0 143 }
paulo@0 144
paulo@0 145 /**
paulo@0 146 * Determines whether the piece that just landed was a T-spin.
paulo@0 147 * Must be called just BEFORE lockdown writes the blocks to the
paulo@0 148 * playfield; otherwise TNT will break.
paulo@0 149 */
paulo@0 150 static int isTspin(const LJField *p) {
paulo@0 151 int blks = 0;
paulo@0 152 int x = p->x;
paulo@0 153 int y = p->hardDropY;
paulo@0 154
paulo@0 155 switch (p->tSpinAlgo) {
paulo@0 156 case LJTS_TNT:
paulo@0 157 if (!isCollision(p, x, y + 1, p->theta)
paulo@0 158 || !isCollision(p, x - 1, y, p->theta)
paulo@0 159 || !isCollision(p, x + 1, y, p->theta)) {
paulo@0 160 return 0;
paulo@0 161 }
paulo@0 162 return p->isSpin;
paulo@0 163 case LJTS_TDS_NO_KICK:
paulo@0 164
paulo@0 165 // If t-spin involved wall kick, don't count it
paulo@0 166 if (p->isSpin == 2) {
paulo@0 167 return 0;
paulo@0 168 }
paulo@0 169
paulo@0 170 // otherwise fall through
paulo@0 171 case LJTS_TDS:
paulo@0 172 // 1. T tetromino
paulo@0 173 if ((p->curPiece[0] & LJP_MASK) != LJP_T) {
paulo@0 174 return 0;
paulo@0 175 }
paulo@0 176
paulo@0 177 // 2. Last move was spin
paulo@0 178 if (!p->isSpin) {
paulo@0 179 return 0;
paulo@0 180 }
paulo@0 181
paulo@0 182 // 3. At least three cells around the rotation center are full
paulo@0 183 if (isOccupied(p, x, y + 1)) {
paulo@0 184 ++blks;
paulo@0 185 }
paulo@0 186 if (isOccupied(p, x, y + 3)) {
paulo@0 187 ++blks;
paulo@0 188 }
paulo@0 189 if (isOccupied(p, x + 2, y + 1)) {
paulo@0 190 ++blks;
paulo@0 191 }
paulo@0 192 if (isOccupied(p, x + 2, y + 3)) {
paulo@0 193 ++blks;
paulo@0 194 }
paulo@0 195 if (blks < 3) {
paulo@0 196 return 0;
paulo@0 197 }
paulo@0 198
paulo@0 199 // 3. Last move was spin
paulo@0 200 return p->isSpin;
paulo@0 201 default:
paulo@0 202 return 0;
paulo@0 203 }
paulo@0 204 }
paulo@0 205
paulo@0 206 /**
paulo@0 207 * Calculates where the active piece in a playfield will fall
paulo@0 208 * if dropped, and writes it back to the playfield.
paulo@0 209 * This value is used for ghost piece, gravity, soft drop, and
paulo@0 210 * hard drop. Call this after the active piece has been spawned,
paulo@0 211 * moved, or rotated.
paulo@0 212 * @param p the playfield
paulo@0 213 */
paulo@0 214 static void updHardDropY(LJField *p) {
paulo@0 215 int x = p->x;
paulo@0 216 int y = ljfixfloor(p->y);
paulo@0 217 int theta = p->theta;
paulo@0 218
paulo@0 219 if (p->bottomBlocks) {
paulo@0 220 y = -2;
paulo@0 221 while (y < (int)LJ_PF_HT
paulo@0 222 && y < ljfixfloor(p->y)
paulo@0 223 && isCollision(p, x, y, theta)) {
paulo@0 224 ++y;
paulo@0 225 }
paulo@0 226 }
paulo@0 227 else {
paulo@0 228 while (!isCollision(p, x, y - 1, theta)) {
paulo@0 229 --y;
paulo@0 230 }
paulo@0 231 }
paulo@0 232 p->hardDropY = y;
paulo@0 233 }
paulo@0 234
paulo@0 235
paulo@0 236 /**
paulo@0 237 * Look for a TNT square in this position.
paulo@0 238 * @param x column of left side, such that 0 <= x <= playfield width - 4
paulo@0 239 * @param y row of bottom block, such that 0 <= y <= playfield height - 4
paulo@0 240 * @param isMulti nonzero for multisquares; 0 for monosquares
paulo@0 241 * @return nonzero if found; 0 if not found
paulo@0 242 */
paulo@0 243 static int isSquareAt(LJField *p, int x, int y, int isMulti)
paulo@0 244 {
paulo@0 245 int firstColor = p->b[y][x] & COLOR_MASK;
paulo@0 246
paulo@0 247 // Check the frame to make sure it isn't connected to anything else
paulo@0 248 for(int i = 0; i <= 3; i++)
paulo@0 249 {
paulo@0 250 /* don't allow squares within parts of squares */
paulo@0 251 if((p->b[y + i][x] & COLOR_MASK) >= 0x80)
paulo@0 252 return 0;
paulo@0 253 /* the block doesn't connect on the left */
paulo@0 254 if(p->b[y + i][x] & CONNECT_L)
paulo@0 255 return 0;
paulo@0 256 /* the block doesn't connect on the right */
paulo@0 257 if(p->b[y + i][x + 3] & CONNECT_R)
paulo@0 258 return 0;
paulo@0 259 /* the block doesn't connect on the bottom */
paulo@0 260 if(p->b[y][x + i] & CONNECT_D)
paulo@0 261 return 0;
paulo@0 262 /* the block doesn't connect on the top */
paulo@0 263 if(p->b[y + 3][x + i] & CONNECT_U)
paulo@0 264 return 0;
paulo@0 265 }
paulo@0 266
paulo@0 267 for(int ySub = 0; ySub < 4; ++ySub)
paulo@0 268 {
paulo@0 269 for(int xSub = 0; xSub <= 3; ++xSub)
paulo@0 270 {
paulo@0 271 int blkHere = p->b[y + ySub][x + xSub];
paulo@0 272
paulo@0 273 /* the square contains no nonexistent blocks */
paulo@0 274 if(!blkHere)
paulo@0 275 return 0;
paulo@0 276 /* the square contains no blocks of garbage or broken pieces */
paulo@0 277 if((blkHere & COLOR_MASK) == 0x80)
paulo@0 278 return 0;
paulo@0 279 /* if looking for monosquares, disallow multisquares */
paulo@0 280 if(isMulti == 0 && (blkHere & COLOR_MASK) != firstColor)
paulo@0 281 return 0;
paulo@0 282 }
paulo@0 283 }
paulo@0 284 return 1;
paulo@0 285 }
paulo@0 286
paulo@0 287 /**
paulo@0 288 * Replaces the 4x4 blocks with a 4x4 square.
paulo@0 289 * @param x column of left side, such that 0 <= x <= playfield width - 4
paulo@0 290 * @param y row of bottom block, such that 0 <= y <= playfield height - 4
paulo@0 291 * @param isMulti nonzero for multisquares; 0 for monosquares
paulo@0 292 * @return the rows that were changed
paulo@0 293 */
paulo@0 294 static LJBits markSquare(LJField *p, int x, int y, int isMulti)
paulo@0 295 {
paulo@0 296 int baseBlk = (isMulti ? 0xA0 : 0xB0)
paulo@0 297 | CONNECT_MASK;
paulo@0 298 for(int i = 0; i < 4; ++i)
paulo@0 299 {
paulo@0 300 int c;
paulo@0 301
paulo@0 302 if(i == 0)
paulo@0 303 c = baseBlk & ~CONNECT_D;
paulo@0 304 else if(i == 3)
paulo@0 305 c = baseBlk & ~CONNECT_U;
paulo@0 306 else
paulo@0 307 c = baseBlk;
paulo@0 308
paulo@0 309 p->b[y + i][x + 0] = c & ~CONNECT_L;
paulo@0 310 p->b[y + i][x + 1] = c;
paulo@0 311 p->b[y + i][x + 2] = c;
paulo@0 312 p->b[y + i][x + 3] = c & ~CONNECT_R;
paulo@0 313 }
paulo@0 314
paulo@0 315 if (isMulti) {
paulo@0 316 ++p->multisquares;
paulo@0 317 } else {
paulo@0 318 ++p->monosquares;
paulo@0 319 }
paulo@0 320
paulo@0 321 return 0x0F << y;
paulo@0 322 }
paulo@0 323
paulo@0 324
paulo@0 325 /**
paulo@0 326 * Marks all 4x4 squares in the playfield.
paulo@0 327 * In the case that a single tetromino forms multiple overlapping
paulo@0 328 * squares, prefers gold over silver, high over low, left over right.
paulo@0 329 * @param isMulti nonzero for multisquares; 0 for monosquares
paulo@0 330 * @return the rows that were changed
paulo@0 331 */
paulo@0 332 static LJBits findSquares(LJField *p, int isMulti) {
paulo@0 333 LJBits changed = 0;
paulo@0 334
paulo@0 335 for (int y = LJ_PF_HT - 4; y >= 0; --y) {
paulo@0 336 for (int x = p->leftWall; x <= p->rightWall - 4; ++x) {
paulo@0 337 int baseBlk = p->b[y][x];
paulo@0 338
paulo@0 339 // If this block is filled in and not connected on the left or right
paulo@0 340 // then do stuff to it
paulo@0 341 if (baseBlk
paulo@0 342 && !(baseBlk & (CONNECT_D | CONNECT_L))
paulo@0 343 && isSquareAt(p, x, y, isMulti)) {
paulo@0 344 changed |= markSquare(p, x, y, isMulti);
paulo@0 345 p->sounds |= LJSND_SQUARE;
paulo@0 346 }
paulo@0 347 }
paulo@0 348 }
paulo@0 349 return changed;
paulo@0 350 }
paulo@0 351
paulo@0 352 static LJBits stickyGluing(LJField *p) {
paulo@0 353 LJBits changed = 0;
paulo@0 354 int byColor = (p->gluing == LJGLUING_STICKY_BY_COLOR);
paulo@0 355
paulo@0 356 for (int y = 0; y < LJ_PF_HT; ++y) {
paulo@0 357 for (int x = p->leftWall; x < p->rightWall - 1; ++x) {
paulo@0 358 int l = p->b[y][x];
paulo@0 359 int r = p->b[y][x + 1];
paulo@0 360 if (l && r
paulo@0 361 && (!(l & CONNECT_R) || !(r & CONNECT_L))
paulo@0 362 && (!byColor || !((l ^ r) & COLOR_MASK))) {
paulo@0 363 p->b[y][x] = l | CONNECT_R;
paulo@0 364 p->b[y][x + 1] = r | CONNECT_L;
paulo@0 365 changed |= 1 << y;
paulo@0 366 }
paulo@0 367 }
paulo@0 368 }
paulo@0 369
paulo@0 370 for (int y = 0; y < LJ_PF_HT - 1; ++y) {
paulo@0 371 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 372 int b = p->b[y][x];
paulo@0 373 int t = p->b[y + 1][x];
paulo@0 374 if (b && t
paulo@0 375 && (!(b & CONNECT_U) || !(t & CONNECT_D))
paulo@0 376 && (!byColor || !((b ^ t) & COLOR_MASK))) {
paulo@0 377 p->b[y][x] = b | CONNECT_U;
paulo@0 378 p->b[y + 1][x] = t | CONNECT_D;
paulo@0 379 changed |= 3 << y;
paulo@0 380 }
paulo@0 381 }
paulo@0 382 }
paulo@0 383
paulo@0 384 return changed;
paulo@0 385 }
paulo@0 386
paulo@0 387 /**
paulo@0 388 * Locks the current tetromino in the playfield.
paulo@0 389 * @param p the playfield
paulo@0 390 * @return the rows in which the tetromino was placed
paulo@0 391 */
paulo@0 392 static LJBits lockPiece(LJField *p) {
paulo@0 393 LJBits rows = 0;
paulo@0 394 int xBase = p->x;
paulo@0 395 int yBase = ljfixfloor(p->y);
paulo@0 396 LJBlkSpec blocks[4];
paulo@0 397 int piece = p->curPiece[0];
paulo@0 398 expandPieceToBlocks(blocks, p, piece, xBase, yBase, p->theta);
paulo@0 399
paulo@0 400 p->isSpin = isTspin(p);
paulo@0 401
paulo@0 402 for (int blk = 0; blk < 4; ++blk) {
paulo@0 403 int blkY = blocks[blk].y;
paulo@0 404 int blkX = blocks[blk].x;
paulo@0 405 int blkValue = blocks[blk].conn;
paulo@0 406
paulo@0 407 if(blkValue && blkY >= 0 && blkY < LJ_PF_HT) {
paulo@0 408 rows |= 1 << blkY;
paulo@0 409 if (blkX >= p->leftWall && blkX < p->rightWall) {
paulo@0 410 p->b[blkY][blkX] = blkValue;
paulo@0 411 }
paulo@0 412 }
paulo@0 413 }
paulo@0 414 p->sounds |= LJSND_LOCK;
paulo@0 415 p->alreadyHeld = 0;
paulo@0 416 p->nLinesThisPiece = 0;
paulo@0 417
paulo@0 418 return rows;
paulo@0 419 }
paulo@0 420
paulo@0 421 void shuffleColumns(LJField *p) {
paulo@0 422 unsigned int permu[LJ_PF_WID];
paulo@0 423
paulo@0 424 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 425 permu[x] = x;
paulo@0 426 }
paulo@0 427 for (int x = p->rightWall - 1; x > p->rightWall + 1; --x) {
paulo@0 428 int r = ljRand(p) % x;
paulo@0 429 int t = permu[x];
paulo@0 430 permu[x] = permu[r];
paulo@0 431 permu[r] = t;
paulo@0 432 }
paulo@0 433
paulo@0 434 for (int y = 0; y < LJ_PF_HT; ++y) {
paulo@0 435 unsigned int blk[LJ_PF_WID];
paulo@0 436
paulo@0 437 // Copy blocks to temporary buffer, eliminating left and right connections
paulo@0 438 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 439 blk[x] = p->b[y][permu[x]] & ~(CONNECT_L | CONNECT_R);
paulo@0 440 }
paulo@0 441 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 442 p->b[y][x] = blk[x];
paulo@0 443 }
paulo@0 444 }
paulo@0 445 }
paulo@0 446
paulo@0 447 /**
paulo@0 448 * Rotates and shifts a new piece per the rotation system.
paulo@0 449 */
paulo@0 450 static void rotateNewPiece(LJField *p) {
paulo@0 451 const LJRotSystem *rs = rotSystems[p->rotationSystem];
paulo@0 452 int shape = p->curPiece[0] & LJP_MASK;
paulo@0 453 int kickData = rs->entryOffset[shape];
paulo@0 454 p->x += WKX(kickData);
paulo@0 455 p->y += ljitofix(WKY(kickData));
paulo@0 456 p->theta = rs->entryTheta[shape];
paulo@0 457
paulo@0 458 /* Find extents of piece so that it doesn't enter already collided
paulo@0 459 with the walls (otherwise, in Tengen rotation and well width 4,
paulo@0 460 spawning I causes block out) */
paulo@0 461
paulo@0 462 LJBlkSpec blocks[4];
paulo@0 463 int minX = LJ_PF_WID - 1, maxX = 0, maxY = 0;
paulo@0 464
paulo@0 465 expandPieceToBlocks(blocks, p, p->curPiece[0],
paulo@0 466 p->x, ljfixfloor(p->y), p->theta);
paulo@0 467 for (int blk = 0; blk < 4; ++blk) {
paulo@0 468 if (blocks[blk].conn) {
paulo@0 469 if (maxY < blocks[blk].y) {
paulo@0 470 maxY = blocks[blk].y;
paulo@0 471 }
paulo@0 472 if (maxX < blocks[blk].x) {
paulo@0 473 maxX = blocks[blk].x;
paulo@0 474 }
paulo@0 475 if (minX > blocks[blk].x) {
paulo@0 476 minX = blocks[blk].x;
paulo@0 477 }
paulo@0 478 }
paulo@0 479 }
paulo@0 480
paulo@0 481 if (minX < p->leftWall) {
paulo@0 482 p->x += minX - p->leftWall;
paulo@0 483 } else if (maxX >= p->rightWall) {
paulo@0 484 p->x -= (maxX + 1) - p->rightWall;
paulo@0 485 }
paulo@0 486 if (!p->enterAbove && maxY >= p->ceiling) {
paulo@0 487 p->y -= ljitofix(maxY - p->ceiling) + 1;
paulo@0 488 }
paulo@0 489 }
paulo@0 490
paulo@0 491
paulo@0 492 /**
paulo@0 493 * Spawns a tetromino onto the playfield.
paulo@0 494 * @param p the playfield
paulo@0 495 * @param hold 0 for spawning from next; nonzero for swapping with hold
paulo@0 496 * @return the rows that were changed by banana effect
paulo@0 497 */
paulo@0 498 static LJBits newPiece(LJField *p, int hold) {
paulo@0 499 LJBits changed = 0;
paulo@0 500 int initial = p->state != LJS_FALLING
paulo@0 501 && p->state != LJS_LANDED;
paulo@0 502 int ihs = initial && hold;
paulo@0 503
paulo@0 504 if (hold) {
paulo@0 505 if (p->state == LJS_LANDED && p->lockReset == LJLOCK_SPAWN) {
paulo@0 506 p->speed.lockDelay = p->stateTime;
paulo@0 507 }
paulo@0 508 } else {
paulo@0 509 p->upwardKicks = 0;
paulo@0 510 }
paulo@0 511 p->x = (LJ_PF_WID - 4) / 2;
paulo@0 512 p->dropDist = 0;
paulo@0 513 if (!ihs) {
paulo@0 514 p->state = LJS_FALLING;
paulo@0 515 p->stateTime = 0;
paulo@0 516 }
paulo@0 517 p->isSpin = 0;
paulo@0 518 p->y = ljitofix(p->ceiling - 2);
paulo@0 519
paulo@0 520 /* Note: The gimmick sets the gravity speed after frame() finishes. */
paulo@0 521
paulo@0 522 if (hold) {
paulo@0 523 int temp;
paulo@0 524
paulo@0 525 if (p->holdStyle != LJHOLD_TO_NEXT) {
paulo@0 526 temp = p->holdPiece;
paulo@0 527 p->holdPiece = p->curPiece[ihs];
paulo@0 528 } else {
paulo@0 529 temp = p->curPiece[ihs + 1];
paulo@0 530 p->curPiece[ihs + 1] = p->curPiece[ihs];
paulo@0 531 }
paulo@0 532 p->curPiece[ihs] = temp;
paulo@0 533 p->alreadyHeld = 1;
paulo@0 534 p->sounds |= LJSND_HOLD;
paulo@0 535
paulo@0 536 // If a negative number was swapped into the current piece,
paulo@0 537 // then there was nothing in the hold space (e.g. at the
paulo@0 538 // beginning of a round). In this case, we'll need to fall
paulo@0 539 // through and generate a new piece.
paulo@0 540 if (temp >= 0) {
paulo@0 541 rotateNewPiece(p);
paulo@0 542 return changed;
paulo@0 543 }
paulo@0 544 }
paulo@0 545
paulo@0 546 // Shift the next pieces down
paulo@0 547 for (int i = 0; i < LJ_NEXT_PIECES; i++) {
paulo@0 548 p->curPiece[i] = p->curPiece[i + 1];
paulo@0 549 }
paulo@0 550 p->sounds |= LJSND_SPAWN;
paulo@0 551
paulo@0 552 p->curPiece[LJ_NEXT_PIECES] = randomize(p);
paulo@0 553 ++p->nPieces;
paulo@0 554 if (!p->canRotate) {
paulo@0 555 p->theta = (ljRand(p) >> 12) & 0x03;
paulo@0 556 } else {
paulo@0 557 rotateNewPiece(p);
paulo@0 558 }
paulo@0 559
paulo@0 560 return changed;
paulo@0 561 }
paulo@0 562
paulo@0 563 void newGame(LJField *p) {
paulo@0 564
paulo@0 565 // Clear playfield
paulo@0 566 for (int y = 0; y < LJ_PF_HT; y++) {
paulo@0 567 for(int x = 0; x < LJ_PF_WID; x++) {
paulo@0 568 p->b[y][x] = 0;
paulo@0 569 }
paulo@0 570 }
paulo@0 571
paulo@0 572 for (int y = 0; y < LJ_MAX_LINES_PER_PIECE; ++y) {
paulo@0 573 p->nLineClears[y] = 0;
paulo@0 574 }
paulo@0 575
paulo@0 576 if (p->holdStyle == LJHOLD_TNT) {
paulo@0 577 initRandomize(p);
paulo@0 578 p->holdPiece = randomize(p);
paulo@0 579 } else {
paulo@0 580 p->holdPiece = -1; // sentinel for no piece in hold box
paulo@0 581 }
paulo@0 582
paulo@0 583 // Generate pieces
paulo@0 584 initRandomize(p);
paulo@0 585 for(int i = 0; i < LJ_NEXT_PIECES; i++) {
paulo@0 586 newPiece(p, 0);
paulo@0 587 }
paulo@0 588 p->clearedLines = 0;
paulo@0 589 p->nPieces = 0;
paulo@0 590 p->state = LJS_NEW_PIECE;
paulo@0 591 p->stateTime = 1;
paulo@0 592 p->garbage = 0;
paulo@0 593 p->outGarbage = 0;
paulo@0 594 p->score = 0;
paulo@0 595 p->gameTime = 0;
paulo@0 596 p->activeTime = 0;
paulo@0 597 p->lines = 0;
paulo@0 598 p->alreadyHeld = 0;
paulo@0 599 p->chain = 0;
paulo@0 600 p->theta = 0;
paulo@0 601 p->nLinesThisPiece = 0;
paulo@0 602 p->canRotate = 1;
paulo@0 603 p->speed.entryDelay = 0;
paulo@0 604 p->garbageRandomness = 64;
paulo@0 605 p->reloaded = 0;
paulo@0 606 p->monosquares = 0;
paulo@0 607 p->multisquares = 0;
paulo@0 608
paulo@0 609 p->garbageX = ljRand(p) % (p->rightWall - p->leftWall)
paulo@0 610 + p->leftWall;
paulo@0 611 }
paulo@0 612
paulo@0 613 /**
paulo@0 614 * Handles scoring for hard and soft drops.
paulo@0 615 * @return 0 for no change or LJ_DIRTY_SCORE for change
paulo@0 616 */
paulo@0 617 LJBits scoreDropRows(LJField *p, LJFixed gravity, LJFixed newY) {
paulo@0 618 LJBits changed = 0;
paulo@0 619 if (gravity > 0) {
paulo@0 620 int fallDist = ljfixfloor(p->y) - ljfixfloor(newY);
paulo@0 621
paulo@0 622 p->dropDist += fallDist;
paulo@0 623
paulo@0 624 // Double scoring for hard drop
paulo@0 625 if (p->dropScoreStyle == LJDROP_1S_2H
paulo@0 626 && gravity >= ljitofix(p->ceiling)) {
paulo@0 627 fallDist *= 2;
paulo@0 628 }
paulo@0 629 if (p->dropScoreStyle == LJDROP_1CELL
paulo@0 630 || p->dropScoreStyle == LJDROP_1S_2H) {
paulo@0 631 p->score += fallDist;
paulo@0 632 changed |= LJ_DIRTY_SCORE;
paulo@0 633 }
paulo@0 634
paulo@0 635 // Handle scoring for continuous drop
paulo@0 636 if (p->dropScoreStyle == LJDROP_NES
paulo@0 637 && newY <= ljitofix(p->hardDropY)) {
paulo@0 638 p->score += p->dropDist;
paulo@0 639 changed |= LJ_DIRTY_SCORE;
paulo@0 640 p->dropDist = 0;
paulo@0 641 }
paulo@0 642 } else {
paulo@0 643 p->dropDist = 0;
paulo@0 644 }
paulo@0 645 return changed;
paulo@0 646 }
paulo@0 647
paulo@0 648 /**
paulo@0 649 * Handles gravity.
paulo@0 650 * @param p the playfield
paulo@0 651 * @param gravity amount of additional gravity applied by the player
paulo@0 652 * @param otherKeys other LJI_* keys being pressed by the player
paulo@0 653 * (specifically LJI_LOCK)
paulo@0 654 */
paulo@0 655 static LJBits doPieceGravity(LJField *p, LJFixed gravity, LJBits otherKeys) {
paulo@0 656 int changedRows = 0;
paulo@0 657
paulo@0 658 LJFixed newY = p->y - gravity - p->speed.gravity;
paulo@0 659
paulo@0 660 // Check for landed
paulo@0 661 if (newY <= ljitofix(p->hardDropY)) {
paulo@0 662 newY = ljitofix(p->hardDropY);
paulo@0 663
paulo@0 664 // Downward movement does not result in a T-spin
paulo@0 665 if (ljfixfloor(newY) < ljfixfloor(p->y)) {
paulo@0 666 p->isSpin = 0;
paulo@0 667 }
paulo@0 668
paulo@0 669 changedRows |= scoreDropRows(p, gravity, newY);
paulo@0 670 p->y = newY;
paulo@0 671
paulo@0 672 if (p->state == LJS_FALLING) {
paulo@0 673 p->state = LJS_LANDED;
paulo@0 674 p->stateTime = p->speed.lockDelay;
paulo@0 675 p->sounds |= LJSND_LAND;
paulo@0 676 }
paulo@0 677 if (p->stateTime > 0 && !(otherKeys & LJI_LOCK)) {
paulo@0 678 // lock delay > 128 is a special case for don't lock at all
paulo@0 679 if (p->setLockDelay < 128) {
paulo@0 680 --p->stateTime;
paulo@0 681 }
paulo@0 682 } else {
paulo@0 683 LJBits lockRows = lockPiece(p);
paulo@0 684 p->state = LJS_LINES;
paulo@0 685 p->stateTime = 0;
paulo@0 686 changedRows |= lockRows | LJ_DIRTY_NEXT;
paulo@0 687
paulo@0 688 // LOCK OUT rule: If a piece locks
paulo@0 689 // completely above the ceiling, the game is over.
paulo@0 690 if (!(lockRows & ((1 << p->ceiling) - 1))) {
paulo@0 691 p->state = LJS_GAMEOVER;
paulo@0 692 }
paulo@0 693 }
paulo@0 694 } else {
paulo@0 695 changedRows |= scoreDropRows(p, gravity, newY);
paulo@0 696 p->state = LJS_FALLING;
paulo@0 697
paulo@0 698 // Downward movement does not result in a T-spin
paulo@0 699 if (ljfixfloor(newY) < ljfixfloor(p->y)) {
paulo@0 700 p->isSpin = 0;
paulo@0 701 }
paulo@0 702 }
paulo@0 703 p->y = newY;
paulo@0 704 return changedRows;
paulo@0 705 }
paulo@0 706
paulo@0 707 static void updateLockDelayOnMove(LJField *p, int isUpwardKick) {
paulo@0 708 if (p->state == LJS_LANDED) {
paulo@0 709
paulo@0 710 // if tetromino can move down, go back to falling state;
paulo@0 711 // otherwise, reset lock delay.
paulo@0 712 if (!isCollision(p, p->x, ljfixfloor(p->y) - 1, p->theta)) {
paulo@0 713 p->state = LJS_FALLING;
paulo@0 714 if (p->lockReset == LJLOCK_SPAWN) {
paulo@0 715 p->speed.lockDelay = p->stateTime;
paulo@0 716 }
paulo@0 717 p->stateTime = 0;
paulo@0 718 } else {
paulo@0 719 p->state = LJS_LANDED;
paulo@0 720 if (p->lockReset == LJLOCK_MOVE
paulo@0 721 || (p->lockReset == LJLOCK_STEP && isUpwardKick)) {
paulo@0 722 p->stateTime = p->speed.lockDelay;
paulo@0 723 }
paulo@0 724 }
paulo@0 725 }
paulo@0 726 }
paulo@0 727
paulo@0 728 static int doRotate(LJField *p,
paulo@0 729 int newDir,
paulo@0 730 const WallKickTable *const pieceTable,
paulo@0 731 int withKicks) {
paulo@0 732 int baseX = p->x;
paulo@0 733 int baseY = ljfixfloor(p->y);
paulo@0 734
paulo@0 735 withKicks = withKicks ? KICK_TABLE_LEN : 1;
paulo@0 736
paulo@0 737 // allow specifying null tables for O
paulo@0 738 if (!pieceTable) {
paulo@0 739 if (!isCollision(p, baseX, baseY, newDir)) {
paulo@0 740 p->theta = newDir;
paulo@0 741 p->sounds |= LJSND_ROTATE;
paulo@0 742 return 1;
paulo@0 743 }
paulo@0 744 return 0;
paulo@0 745 }
paulo@0 746
paulo@0 747 const unsigned char *const table = (*pieceTable)[newDir];
paulo@0 748 int baseKickY = -1000; // sentinel for uninitialized
paulo@0 749
paulo@0 750 for (int kick = 0; kick < withKicks; kick++) {
paulo@0 751 unsigned int kickData = table[kick];
paulo@0 752 if (kickData == WK_END) {
paulo@0 753 break;
paulo@0 754 } else if (kickData == ARIKA_IF_NOT_CENTER) {
paulo@0 755
paulo@0 756 // Compute the free space position
paulo@0 757 kickData = table[0];
paulo@0 758 int kickX = WKX(kickData) + baseX;
paulo@0 759 int kickY = WKY(kickData) + baseY;
paulo@0 760 LJBlkSpec blocks[4];
paulo@0 761 int allowed = 0;
paulo@0 762
paulo@0 763 // If a block other than the center column of this position
paulo@0 764 // is occupied, go to the next step (that is,
paulo@0 765 // allow subsequent kicks)
paulo@0 766 expandPieceToBlocks(blocks, p, p->curPiece[0],
paulo@0 767 kickX, kickY, newDir);
paulo@0 768
paulo@0 769 for (int blk = 0; blk < 4; ++blk) {
paulo@0 770 if (blocks[blk].conn
paulo@0 771 && blocks[blk].x != baseX + 1
paulo@0 772 && isOccupied(p, blocks[blk].x, blocks[blk].y)) {
paulo@0 773 allowed = 1;
paulo@0 774 break;
paulo@0 775 }
paulo@0 776 }
paulo@0 777
paulo@0 778 // Otherwise, only blocks of the center column are occupied,
paulo@0 779 // and these cannot kick the piece.
paulo@0 780 if (!allowed) {
paulo@0 781 return 0;
paulo@0 782 }
paulo@0 783 } else {
paulo@0 784 int kickX = WKX(kickData) + baseX;
paulo@0 785 int kickY = WKY(kickData) + baseY;
paulo@0 786
paulo@0 787 // If this is the first
paulo@0 788 if (baseKickY == -1000) {
paulo@0 789 baseKickY = kickY;
paulo@0 790 }
paulo@0 791 if ((kickY <= baseKickY || p->upwardKicks < p->maxUpwardKicks)
paulo@0 792 && !isCollision(p, kickX, kickY, newDir)) {
paulo@0 793 p->theta = newDir;
paulo@0 794 p->x = kickX;
paulo@0 795 if (kickY > baseKickY) {
paulo@0 796 p->y = ljitofix(kickY);
paulo@0 797
paulo@0 798 // on the FIRST floor kick of a piece, reset lock delay
paulo@0 799 if (p->upwardKicks == 0) {
paulo@0 800 updateLockDelayOnMove(p, 1);
paulo@0 801 }
paulo@0 802 ++p->upwardKicks;
paulo@0 803 } else if (kickY < baseKickY) {
paulo@0 804 p->y = ljitofix(kickY) + 0xFFFF;
paulo@0 805 } else {
paulo@0 806 p->y = ljitofix(kickY) + (p->y & 0xFFFF);
paulo@0 807 }
paulo@0 808 p->sounds |= LJSND_ROTATE;
paulo@0 809 return 1;
paulo@0 810 }
paulo@0 811 }
paulo@0 812 }
paulo@0 813 return 0;
paulo@0 814 }
paulo@0 815
paulo@0 816
paulo@0 817 /**
paulo@0 818 * Tries to rotate the current piece 90 degrees counterclockwise,
paulo@0 819 * using the counterclockwise wall kick tables.
paulo@0 820 * @param p the playfield
paulo@0 821 * @param withKicks nonzero for wall kick, zero for none
paulo@0 822 */
paulo@0 823 static int doRotateLeft(LJField *p, int withKicks) {
paulo@0 824 int newDir = (p->theta + 3) & 0x03;
paulo@0 825 const LJRotSystem *rs = rotSystems[p->rotationSystem];
paulo@0 826 signed int tableNo = rs->kicksL[p->curPiece[0] & LJP_MASK];
paulo@0 827 const WallKickTable *pieceTable = tableNo >= 0
paulo@0 828 ? &(rs->kickTables[tableNo])
paulo@0 829 : NULL;
paulo@0 830 return doRotate(p, newDir, pieceTable, withKicks);
paulo@0 831 }
paulo@0 832
paulo@0 833 /**
paulo@0 834 * Tries to rotate the current piece 90 degrees clockwise,
paulo@0 835 * using the clockwise wall kick tables.
paulo@0 836 * @param p the playfield
paulo@0 837 * @param withKicks nonzero for wall kick, zero for none
paulo@0 838 */
paulo@0 839 static int doRotateRight(LJField *p, int withKicks) {
paulo@0 840 int newDir = (p->theta + 1) & 0x03;
paulo@0 841 const LJRotSystem *rs = rotSystems[p->rotationSystem];
paulo@0 842 signed int tableNo = rs->kicksR[p->curPiece[0] & LJP_MASK];
paulo@0 843 const WallKickTable *pieceTable = tableNo >= 0
paulo@0 844 ? &(rs->kickTables[tableNo])
paulo@0 845 : NULL;
paulo@0 846 return doRotate(p, newDir, pieceTable, withKicks);
paulo@0 847 }
paulo@0 848
paulo@0 849 static LJBits checkLines(const LJField *p, LJBits checkRows) {
paulo@0 850 LJBits foundLines = 0;
paulo@0 851 for (int y = 0;
paulo@0 852 y < LJ_PF_HT && checkRows != 0;
paulo@0 853 ++y, checkRows >>= 1) {
paulo@0 854 if (checkRows & 1) {
paulo@0 855 const unsigned char *row = p->b[y];
paulo@0 856 int found = 1;
paulo@0 857
paulo@0 858 for (int x = p->leftWall; x < p->rightWall && found; ++x) {
paulo@0 859 found = found && (row[x] != 0);
paulo@0 860 }
paulo@0 861 if (found) {
paulo@0 862 foundLines |= 1 << y;
paulo@0 863 }
paulo@0 864 }
paulo@0 865 }
paulo@0 866
paulo@0 867 return foundLines;
paulo@0 868 }
paulo@0 869
paulo@0 870 static void fillCLoop(LJField *p, int x, int y, unsigned int src, unsigned int dst)
paulo@0 871 {
paulo@0 872 int fillL, fillR, i;
paulo@0 873
paulo@0 874 fillL = fillR = x;
paulo@0 875 do {
paulo@0 876 p->c[y][fillL] = dst;
paulo@0 877 fillL--;
paulo@0 878 } while ((fillL >= p->leftWall) && (p->c[y][fillL] == src));
paulo@0 879 fillL++;
paulo@0 880
paulo@0 881 do {
paulo@0 882 p->c[y][fillR] = dst;
paulo@0 883 fillR++;
paulo@0 884 } while ((fillR < p->rightWall) && (p->c[y][fillR] == src));
paulo@0 885 fillR--;
paulo@0 886
paulo@0 887 for(i = fillL; i <= fillR; i++)
paulo@0 888 {
paulo@0 889 if(y > 0 && p->c[y - 1][i] == src) {
paulo@0 890 fillCLoop(p, i, y - 1, src, dst);
paulo@0 891 }
paulo@0 892 if(y < LJ_PF_HT - 1 && p->c[y + 1][i] == src) {
paulo@0 893 fillCLoop(p, i, y + 1, src, dst);
paulo@0 894 }
paulo@0 895 }
paulo@0 896 }
paulo@0 897
paulo@0 898
paulo@0 899 static void fillC(LJField *p, int x, int y, int dstC) {
paulo@0 900 if (p->c[y][x] != dstC) {
paulo@0 901 fillCLoop(p, x, y, p->c[y][x], dstC);
paulo@0 902 }
paulo@0 903 }
paulo@0 904
paulo@0 905 /**
paulo@0 906 * Locks the block regions that have landed.
paulo@0 907 * @param p the playfield
paulo@0 908 */
paulo@0 909 void lockLandedRegions(LJField *p) {
paulo@0 910 // Look for regions that are on top of ground regions, where
paulo@0 911 // "ground regions" are any block that is solid and whose region ID is 0.
paulo@0 912 for (int landed = 1; landed != 0; ) {
paulo@0 913 landed = 0;
paulo@0 914 // If something hit the ground, erase its floating bit
paulo@0 915 for (int y = 0; y < LJ_PF_HT; ++y) {
paulo@0 916 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 917 // If there's a floating block here, and a not-floating block below,
paulo@0 918 // erase this block group's floatiness
paulo@0 919 if (p->c[y][x] &&
paulo@0 920 (y == 0 || (!p->c[y - 1][x] && p->b[y - 1][x]))) {
paulo@0 921 fillC(p, x, y, 0);
paulo@0 922 p->sounds |= LJSND_LAND;
paulo@0 923 landed = 1;
paulo@0 924 }
paulo@0 925 }
paulo@0 926 }
paulo@0 927 }
paulo@0 928 }
paulo@0 929
paulo@0 930 /**
paulo@0 931 * Separates the playfield into regions that shall fall separately.
paulo@0 932 * @param p the playfield
paulo@0 933 * @param byColors Zero: Touching blocks form a region.
paulo@0 934 * Nonzero: Touching blocks of a single color form a region.
paulo@0 935 */
paulo@0 936 static void stickyMark(LJField *p, int byColors) {
paulo@0 937 for (unsigned int y = 0; y < LJ_PF_HT; ++y) {
paulo@0 938 for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 939 int blkHere = p->b[y][x] & COLOR_MASK;
paulo@0 940
paulo@0 941 if (!byColors) {
paulo@0 942 blkHere = blkHere ? 0x10 : 0;
paulo@0 943 }
paulo@0 944 p->c[y][x] = blkHere;
paulo@0 945 }
paulo@0 946 }
paulo@0 947
paulo@0 948 if (byColors) {
paulo@0 949 lockLandedRegions(p);
paulo@0 950 } else {
paulo@0 951 // mark the bottom row as landed
paulo@0 952 for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 953 if (p->c[0][x]) {
paulo@0 954 fillC(p, x, 0, 0);
paulo@0 955 }
paulo@0 956 }
paulo@0 957 }
paulo@0 958
paulo@0 959 //p->stateTime = 5;
paulo@0 960 }
paulo@0 961
paulo@0 962
paulo@0 963 /**
paulo@0 964 * Sets the color of a piece to gray/garbage (0x80).
paulo@0 965 * @param x column of a block in the piece
paulo@0 966 * @param y row of a block in the piece
paulo@0 967 * @param rgn the region ID
paulo@0 968 */
paulo@0 969 static void cascadeMarkPiece(LJField *p, int x, int y, int rgn) {
paulo@0 970 int blkHere = p->b[y][x];
paulo@0 971
paulo@0 972 if (blkHere && !p->c[y][x]) {
paulo@0 973 p->c[y][x] = rgn;
paulo@0 974 if((blkHere & CONNECT_D) && y > 0)
paulo@0 975 cascadeMarkPiece(p, x, y - 1, rgn);
paulo@0 976 if((blkHere & CONNECT_U) && y < LJ_PF_HT - 1)
paulo@0 977 cascadeMarkPiece(p, x, y + 1, rgn);
paulo@0 978 if((blkHere & CONNECT_L) && x > p->leftWall)
paulo@0 979 cascadeMarkPiece(p, x - 1, y, rgn);
paulo@0 980 if((blkHere & CONNECT_R) && x < p->rightWall - 1 )
paulo@0 981 cascadeMarkPiece(p, x + 1, y, rgn);
paulo@0 982 }
paulo@0 983 }
paulo@0 984
paulo@0 985 static void cascadeMark(LJField *p) {
paulo@0 986 int rgn = 0;
paulo@0 987
paulo@0 988 for (unsigned int y = 0; y < LJ_PF_HT; ++y) {
paulo@0 989 for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 990 p->c[y][x] = 0;
paulo@0 991 }
paulo@0 992 }
paulo@0 993 for (unsigned int y = 0; y < LJ_PF_HT; ++y) {
paulo@0 994 for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 995 cascadeMarkPiece(p, x, y, ++rgn);
paulo@0 996 }
paulo@0 997 }
paulo@0 998 lockLandedRegions(p);
paulo@0 999 //p->stateTime = 5;
paulo@0 1000 }
paulo@0 1001
paulo@0 1002 static void breakEverything(LJField *p) {
paulo@0 1003 for (unsigned int y = 0; y < LJ_PF_HT; ++y) {
paulo@0 1004 for (unsigned int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 1005 if (p->b[y][x]) {
paulo@0 1006 p->c[y][x] = x + 1;
paulo@0 1007 p->b[y][x] = 0x80;
paulo@0 1008 } else {
paulo@0 1009 p->c[y][x] = 0;
paulo@0 1010 }
paulo@0 1011 }
paulo@0 1012 }
paulo@0 1013
paulo@0 1014 // fill bottom row
paulo@0 1015 for (unsigned int x = x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 1016 if (p->c[0][x]) {
paulo@0 1017 fillC(p, x, 0, 0);
paulo@0 1018 }
paulo@0 1019 }
paulo@0 1020 p->stateTime = 5;
paulo@0 1021 }
paulo@0 1022
paulo@0 1023 /**
paulo@0 1024 * Sets the color of a piece to gray/garbage (0x80).
paulo@0 1025 * @param x column of a block in the piece
paulo@0 1026 * @param y row of a block in the piece
paulo@0 1027 */
paulo@0 1028 static LJBits breakPiece(LJField *p, int x, int y) {
paulo@0 1029 LJBits changed = 0;
paulo@0 1030 int blkHere = p->b[y][x];
paulo@0 1031 int colorHere = blkHere & COLOR_MASK;
paulo@0 1032 int connHere = blkHere & CONNECT_MASK;
paulo@0 1033
paulo@0 1034 if (colorHere != 0x80) {
paulo@0 1035 p->b[y][x] = connHere | 0x80;
paulo@0 1036 changed |= 1 << y;
paulo@0 1037 if((blkHere & CONNECT_D) && y > 0)
paulo@0 1038 changed |= breakPiece(p, x, y - 1);
paulo@0 1039 if((blkHere & CONNECT_U) && y < LJ_PF_HT - 1)
paulo@0 1040 changed |= breakPiece(p, x, y + 1);
paulo@0 1041 if((blkHere & CONNECT_L) && x > p->leftWall)
paulo@0 1042 changed |= breakPiece(p, x - 1, y);
paulo@0 1043 if((blkHere & CONNECT_R) && x < p->rightWall - 1 )
paulo@0 1044 changed |= breakPiece(p, x + 1, y);
paulo@0 1045 }
paulo@0 1046 return changed;
paulo@0 1047 }
paulo@0 1048
paulo@0 1049 /**
paulo@0 1050 * Removes blocks in cleared lines from the playfield and marks
paulo@0 1051 * remaining blocks for gravity.
paulo@0 1052 * @param the lines to be cleared
paulo@0 1053 * @return the rows that were changed
paulo@0 1054 */
paulo@0 1055 static LJBits clearLines(LJField *p, LJBits foundLines) {
paulo@0 1056 LJBits changed = foundLines;
paulo@0 1057
paulo@0 1058 p->clearedLines = foundLines;
paulo@0 1059 if (foundLines != 0) {
paulo@0 1060 p->sounds |= LJSND_LINE;
paulo@0 1061 }
paulo@0 1062 for (int y = 0;
paulo@0 1063 y < LJ_PF_HT && foundLines != 0;
paulo@0 1064 ++y, foundLines >>= 1) {
paulo@0 1065 if (foundLines & 1) {
paulo@0 1066
paulo@0 1067 // In square mode, turn broken pieces (but not 4x4 squares)
paulo@0 1068 // into garbage blocks
paulo@0 1069 if (p->gluing == LJGLUING_SQUARE) {
paulo@0 1070 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 1071 if (p->b[y][x] < 0x80) {
paulo@0 1072 changed |= breakPiece(p, x, y);
paulo@0 1073 } else if ((p->b[y][x] & (0xF0 | CONNECT_R)) == 0xA0) {
paulo@0 1074 p->score += 500;
paulo@0 1075 changed |= LJ_DIRTY_SCORE;
paulo@0 1076 } else if ((p->b[y][x] & (0xF0 | CONNECT_R)) == 0xB0) {
paulo@0 1077 p->score += 1000;
paulo@0 1078 changed |= LJ_DIRTY_SCORE;
paulo@0 1079 }
paulo@0 1080 }
paulo@0 1081 }
paulo@0 1082
paulo@0 1083 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 1084 p->b[y][x] = 0;
paulo@0 1085 }
paulo@0 1086
paulo@0 1087 // break connections up and down (like Tengen Tetyais)
paulo@0 1088 if (y > 0) {
paulo@0 1089 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 1090 p->b[y - 1][x] &= ~CONNECT_U;
paulo@0 1091 }
paulo@0 1092 changed |= 1 << (y - 1);
paulo@0 1093 }
paulo@0 1094 if (y < LJ_PF_HT - 1) {
paulo@0 1095 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 1096 p->b[y + 1][x] &= ~CONNECT_D;
paulo@0 1097 }
paulo@0 1098 changed |= 1 << (y + 1);
paulo@0 1099 }
paulo@0 1100 }
paulo@0 1101 }
paulo@0 1102 if (p->gluing == LJGLUING_SQUARE && p->isSpin) {
paulo@0 1103 breakEverything(p);
paulo@0 1104 changed |= (1 << LJ_PF_HT) - 1;
paulo@0 1105 } else if (p->clearGravity == LJGRAV_STICKY) {
paulo@0 1106 stickyMark(p, 0);
paulo@0 1107 } else if (p->clearGravity == LJGRAV_STICKY_BY_COLOR) {
paulo@0 1108 stickyMark(p, 1);
paulo@0 1109 } else if (p->clearGravity == LJGRAV_CASCADE) {
paulo@0 1110 cascadeMark(p);
paulo@0 1111 } else {
paulo@0 1112 p->stateTime = 0;
paulo@0 1113 }
paulo@0 1114
paulo@0 1115 return changed;
paulo@0 1116 }
paulo@0 1117
paulo@0 1118 static unsigned int stickyFallLines(LJField *p) {
paulo@0 1119 int minY = LJ_PF_HT;
paulo@0 1120
paulo@0 1121 // Move floating stuff down by one block
paulo@0 1122 for (int y = 1; y < LJ_PF_HT; ++y) {
paulo@0 1123 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 1124 int c = p->c[y][x];
paulo@0 1125 if (c) {
paulo@0 1126 p->c[y - 1][x] = c;
paulo@0 1127 p->c[y][x] = 0;
paulo@0 1128 p->b[y - 1][x] = p->b[y][x];
paulo@0 1129 p->b[y][x] = 0;
paulo@0 1130
paulo@0 1131 if (minY > y) {
paulo@0 1132 minY = y;
paulo@0 1133 }
paulo@0 1134 }
paulo@0 1135 }
paulo@0 1136 }
paulo@0 1137
paulo@0 1138 // If we're done, skip all the rest
paulo@0 1139 if (minY >= LJ_PF_HT) {
paulo@0 1140 return LJ_PF_HT;
paulo@0 1141 }
paulo@0 1142
paulo@0 1143 lockLandedRegions(p);
paulo@0 1144 return minY - 1;
paulo@0 1145 }
paulo@0 1146
paulo@0 1147
paulo@0 1148 unsigned int bfffo(LJBits rowBits) {
paulo@0 1149 unsigned int lineRow = 0;
paulo@0 1150
paulo@0 1151 if (!rowBits) {
paulo@0 1152 return 32;
paulo@0 1153 }
paulo@0 1154 if ((rowBits & 0xFFFF) == 0) {
paulo@0 1155 rowBits >>= 16;
paulo@0 1156 lineRow += 16;
paulo@0 1157 }
paulo@0 1158 if ((rowBits & 0xFF) == 0) {
paulo@0 1159 rowBits >>= 8;
paulo@0 1160 lineRow += 8;
paulo@0 1161 }
paulo@0 1162 if ((rowBits & 0xF) == 0) {
paulo@0 1163 rowBits >>= 4;
paulo@0 1164 lineRow += 4;
paulo@0 1165 }
paulo@0 1166 if ((rowBits & 0x3) == 0) {
paulo@0 1167 rowBits >>= 2;
paulo@0 1168 lineRow += 2;
paulo@0 1169 }
paulo@0 1170 if ((rowBits & 0x1) == 0) {
paulo@0 1171 rowBits >>= 1;
paulo@0 1172 lineRow += 1;
paulo@0 1173 }
paulo@0 1174 return lineRow;
paulo@0 1175 }
paulo@0 1176
paulo@0 1177 static unsigned int fallLines(LJField *p) {
paulo@0 1178 LJBits rowBits = p->tempRows;
paulo@0 1179 unsigned int lineRow = 0;
paulo@0 1180
paulo@0 1181 if (p->clearGravity != LJGRAV_NAIVE
paulo@0 1182 || (p->gluing == LJGLUING_SQUARE && p->isSpin)) {
paulo@0 1183 return stickyFallLines(p);
paulo@0 1184 }
paulo@0 1185
paulo@0 1186 if (rowBits == 0) {
paulo@0 1187 return LJ_PF_HT;
paulo@0 1188 }
paulo@0 1189
paulo@0 1190 lineRow = bfffo(rowBits);
paulo@0 1191 p->tempRows = (p->tempRows & (-2 << lineRow)) >> 1;
paulo@0 1192 if (!(p->tempRows & (1 << lineRow))) {
paulo@0 1193 p->sounds |= LJSND_LAND;
paulo@0 1194 }
paulo@0 1195
paulo@0 1196 // Move stuff down by 1 row
paulo@0 1197 for (int y = lineRow; y < LJ_PF_HT - 1; ++y) {
paulo@0 1198 unsigned char *row0 = p->b[y];
paulo@0 1199 const unsigned char *row1 = p->b[y + 1];
paulo@0 1200 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 1201 row0[x] = row1[x];
paulo@0 1202 }
paulo@0 1203 }
paulo@0 1204
paulo@0 1205 // Clear top row
paulo@0 1206 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 1207 p->b[LJ_PF_HT - 1][x] = 0;
paulo@0 1208 }
paulo@0 1209
paulo@0 1210 return lineRow;
paulo@0 1211 }
paulo@0 1212
paulo@0 1213 /**
paulo@0 1214 * Counts the bits in a bit vector that are true (1).
paulo@0 1215 * @param b a bit vector
paulo@0 1216 * @return the number of 1 bits
paulo@0 1217 */
paulo@0 1218 unsigned int countOnes(LJBits b) {
paulo@0 1219 unsigned int ones = 0;
paulo@0 1220
paulo@0 1221 while(b) {
paulo@0 1222 ++ones;
paulo@0 1223 b &= b - 1;
paulo@0 1224 }
paulo@0 1225 return ones;
paulo@0 1226 }
paulo@0 1227
paulo@0 1228 static unsigned int addGarbage(LJField *p) {
paulo@0 1229 // Move stuff up by 1 row
paulo@0 1230 for (int y = LJ_PF_HT - 2; y >= 0; --y) {
paulo@0 1231 unsigned char *row1 = p->b[y + 1];
paulo@0 1232 const unsigned char *row0 = p->b[y];
paulo@0 1233 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 1234 row1[x] = row0[x];
paulo@0 1235 }
paulo@0 1236 }
paulo@0 1237
paulo@0 1238 // Garbage in bottom row
paulo@0 1239 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 1240 p->b[0][x] = 0x80;
paulo@0 1241 }
paulo@0 1242
paulo@0 1243 // Randomize location of garbage hole
paulo@0 1244 int r = (ljRand(p) >> 7) & 0xFF;
paulo@0 1245 int garbageX = (r <= p->garbageRandomness)
paulo@0 1246 ? (ljRand(p) % (p->rightWall - p->leftWall)) + p->leftWall
paulo@0 1247 : p->garbageX;
paulo@0 1248 p->b[0][garbageX] = 0;
paulo@0 1249 p->garbageX = garbageX;
paulo@0 1250
paulo@0 1251 // Horizontally connect the blocks that make up garbage in bottom row
paulo@0 1252 for (int x = p->leftWall; x < p->rightWall - 1; ++x) {
paulo@0 1253 if (p->b[0][x] && p->b[0][x + 1]) {
paulo@0 1254 p->b[0][x] |= CONNECT_R;
paulo@0 1255 p->b[0][x + 1] |= CONNECT_L;
paulo@0 1256 }
paulo@0 1257 }
paulo@0 1258
paulo@0 1259 // Vertically connect the blocks that make up garbage in bottom row
paulo@0 1260 for (int x = p->leftWall; x < p->rightWall; ++x) {
paulo@0 1261 if (p->b[0][x]
paulo@0 1262 && ((p->b[1][x] & COLOR_MASK) == 0x80)) {
paulo@0 1263 p->b[0][x] |= CONNECT_U;
paulo@0 1264 p->b[1][x] |= CONNECT_D;
paulo@0 1265 }
paulo@0 1266 }
paulo@0 1267
paulo@0 1268 return (1 << LJ_PF_VIS_HT) - 1;
paulo@0 1269 }
paulo@0 1270
paulo@0 1271 /**
paulo@0 1272 * Computes the score and outgoing garbage for lines
paulo@0 1273 * and adds them to the player's total.
paulo@0 1274 * @param p The playfield
paulo@0 1275 * @param lines Bit array where 1 means a line clear on this row.
paulo@0 1276 */
paulo@0 1277 void addLinesScore(LJField *p, LJBits lines);
paulo@0 1278
paulo@0 1279 /**
paulo@0 1280 * Things to do just before launching a new piece.
paulo@0 1281 */
paulo@0 1282 void prepareForNewPiece(LJField *p) {
paulo@0 1283 int nLines = p->nLinesThisPiece;
paulo@0 1284
paulo@0 1285 // Add to number of clears of each number of lines.
paulo@0 1286 int idx;
paulo@0 1287
paulo@0 1288 if (p->clearGravity == LJGRAV_NAIVE) {
paulo@0 1289 // In naive gravity, T-spin single, double, and triple counts
paulo@0 1290 // are stored in 5-row, 6-row, and 7-row slots, and T-spins
paulo@0 1291 // that clear 0 lines are not counted.
paulo@0 1292 idx = (nLines > 4)
paulo@0 1293 ? 4
paulo@0 1294 : nLines;
paulo@0 1295
paulo@0 1296 if (nLines >= 1 && p->isSpin) {
paulo@0 1297 idx += 4;
paulo@0 1298 }
paulo@0 1299 } else {
paulo@0 1300 idx = (nLines > LJ_MAX_LINES_PER_PIECE)
paulo@0 1301 ? LJ_MAX_LINES_PER_PIECE
paulo@0 1302 : nLines;
paulo@0 1303 }
paulo@0 1304
paulo@0 1305 if (nLines < 4 && !p->isSpin && p->garbageStyle == LJGARBAGE_HRDERBY) {
paulo@0 1306 p->garbage += nLines;
paulo@0 1307 }
paulo@0 1308
paulo@0 1309 ljAssert(p,
paulo@0 1310 idx <= LJ_MAX_LINES_PER_PIECE,
paulo@0 1311 "Number of lines cleared with last piece out of bounds in prepareForNewPiece");
paulo@0 1312 if (idx > 0) {
paulo@0 1313 p->nLineClears[idx - 1] += 1;
paulo@0 1314 }
paulo@0 1315
paulo@0 1316 p->state = LJS_NEW_PIECE;
paulo@0 1317 p->stateTime = p->speed.entryDelay;
paulo@0 1318 }
paulo@0 1319
paulo@0 1320 LJBits frame(LJField *p, const LJInput *in) {
paulo@0 1321 LJBits changedRows = 0;
paulo@0 1322 LJBits tempRows;
paulo@0 1323 int distance;
paulo@0 1324 int moved = 0;
paulo@0 1325 int isFirstFrame = (p->sounds & (LJSND_SPAWN | LJSND_HOLD))
paulo@0 1326 ? 1 : 0;
paulo@0 1327
paulo@0 1328 p->sounds = 0;
paulo@0 1329
paulo@0 1330 // Make hold work at ANY time.
paulo@0 1331 if ((in->other & LJI_HOLD)
paulo@0 1332 && p->holdStyle != LJHOLD_NONE
paulo@0 1333 && !p->alreadyHeld) {
paulo@0 1334 changedRows |= newPiece(p, 1) | LJ_DIRTY_NEXT;
paulo@0 1335 updHardDropY(p);
paulo@0 1336 }
paulo@0 1337
paulo@0 1338 switch(p->state) {
paulo@0 1339 case LJS_NEW_PIECE:
paulo@0 1340 if (p->garbage > 0) {
paulo@0 1341 changedRows |= addGarbage(p);
paulo@0 1342 --p->garbage;
paulo@0 1343 break;
paulo@0 1344 }
paulo@0 1345
paulo@0 1346 // ARE
paulo@0 1347 if (p->stateTime > 0) {
paulo@0 1348 --p->stateTime;
paulo@0 1349 }
paulo@0 1350 if (p->stateTime > 0) {
paulo@0 1351 break;
paulo@0 1352 }
paulo@0 1353
paulo@0 1354 changedRows |= newPiece(p, 0);
paulo@0 1355 updHardDropY(p);
paulo@0 1356 changedRows |= LJ_DIRTY_NEXT;
paulo@0 1357
paulo@0 1358 /* If the piece spawns over blocks, this is a "block out" and a
paulo@0 1359 loss under most rules. But skip checking it now so that
paulo@0 1360 the player can IRS out of block out. */
paulo@0 1361
paulo@0 1362 break;
paulo@0 1363
paulo@0 1364 // the following executes for both falling and landed
paulo@0 1365 case LJS_FALLING:
paulo@0 1366 case LJS_LANDED:
paulo@0 1367 ++p->activeTime;
paulo@0 1368 if (p->canRotate) {
paulo@0 1369 int oldX = p->x;
paulo@0 1370 int oldY = ljfixfloor(p->y);
paulo@0 1371 distance = in->rotation;
paulo@0 1372 for(; distance < 0; ++distance) {
paulo@0 1373
paulo@0 1374 // 0.43: Do not apply wall kicks on the first frame (IRS)
paulo@0 1375 if (doRotateLeft(p, !isFirstFrame)) {
paulo@0 1376 moved = 1;
paulo@0 1377
paulo@0 1378 // isSpin == 1: twist in place
paulo@0 1379 // isSpin == 2: twist with kick
paulo@0 1380 // if (p->tSpinAlgo == LJTS_TDS_NO_KICK)
paulo@0 1381 // then only isSpin == 1 is worth points.
paulo@0 1382 if (p->x == oldX && ljfixfloor(p->y) == oldY) {
paulo@0 1383 p->isSpin = 1;
paulo@0 1384 } else {
paulo@0 1385 p->isSpin = 2;
paulo@0 1386 }
paulo@0 1387 } else {
paulo@0 1388 break;
paulo@0 1389 }
paulo@0 1390 }
paulo@0 1391 for(; distance > 0; --distance) {
paulo@0 1392 if (doRotateRight(p, !isFirstFrame)) {
paulo@0 1393 moved = 1;
paulo@0 1394 if (p->x == oldX && ljfixfloor(p->y) == oldY) {
paulo@0 1395 p->isSpin = 1;
paulo@0 1396 } else {
paulo@0 1397 p->isSpin = 2;
paulo@0 1398 }
paulo@0 1399 } else {
paulo@0 1400 break;
paulo@0 1401 }
paulo@0 1402 }
paulo@0 1403 }
paulo@0 1404
paulo@0 1405 /* If the piece spawns over blocks, this is a "block out" and a
paulo@0 1406 loss under most rules. Check it now, after rotation, so that
paulo@0 1407 the player can IRS out of block out. */
paulo@0 1408 if (isFirstFrame
paulo@0 1409 && isCollision(p, p->x, ljfixfloor(p->y), p->theta)) {
paulo@0 1410 changedRows |= lockPiece(p);
paulo@0 1411 p->state = LJS_GAMEOVER;
paulo@0 1412 }
paulo@0 1413
paulo@0 1414 distance = in->movement;
paulo@0 1415 for(; distance < 0; ++distance) {
paulo@0 1416 if (!isCollision(p, p->x - 1, ljfixfloor(p->y), p->theta)) {
paulo@0 1417 --p->x;
paulo@0 1418 p->sounds |= LJSND_SHIFT;
paulo@0 1419 moved = 1;
paulo@0 1420 p->isSpin = 0;
paulo@0 1421 }
paulo@0 1422 }
paulo@0 1423 for(; distance > 0; --distance) {
paulo@0 1424 if (!isCollision(p, p->x + 1, ljfixfloor(p->y), p->theta)) {
paulo@0 1425 ++p->x;
paulo@0 1426 p->sounds |= LJSND_SHIFT;
paulo@0 1427 moved = 1;
paulo@0 1428 p->isSpin = 0;
paulo@0 1429 }
paulo@0 1430 }
paulo@0 1431 updHardDropY(p);
paulo@0 1432 if (p->state != LJS_GAMEOVER) {
paulo@0 1433 if (moved) {
paulo@0 1434 updateLockDelayOnMove(p, 0);
paulo@0 1435 }
paulo@0 1436 tempRows = doPieceGravity(p, ljitofix(in->gravity) >> 3, in->other);
paulo@0 1437 p->tempRows = tempRows;
paulo@0 1438 changedRows |= tempRows;
paulo@0 1439 }
paulo@0 1440
paulo@0 1441 // At this point, if the piece locked,
paulo@0 1442 // p->tempRows holds the rows in which the piece landed.
paulo@0 1443 break;
paulo@0 1444
paulo@0 1445 case LJS_LINES:
paulo@0 1446 if (p->stateTime > 0) {
paulo@0 1447 --p->stateTime;
paulo@0 1448 }
paulo@0 1449 if (p->stateTime > 0) {
paulo@0 1450 break;
paulo@0 1451 }
paulo@0 1452 if (p->gluing == LJGLUING_SQUARE) {
paulo@0 1453 LJBits gluedRows = findSquares(p, 0);
paulo@0 1454 gluedRows |= findSquares(p, 1);
paulo@0 1455 changedRows |= gluedRows;
paulo@0 1456 if (gluedRows) {
paulo@0 1457
paulo@0 1458 // When a 4x4 block square is formed, a delay
paulo@0 1459 // equal to the line delay is added.
paulo@0 1460 p->stateTime += p->speed.lineDelay;
paulo@0 1461 break;
paulo@0 1462 }
paulo@0 1463 } else if (p->gluing == LJGLUING_STICKY
paulo@0 1464 || p->gluing == LJGLUING_STICKY_BY_COLOR) {
paulo@0 1465 changedRows |= stickyGluing(p);
paulo@0 1466 }
paulo@0 1467
paulo@0 1468 // At this point, p->tempRows holds the rows in which
paulo@0 1469 // a line could possibly have been made.
paulo@0 1470 tempRows = p->tempRows;
paulo@0 1471 tempRows = checkLines(p, tempRows);
paulo@0 1472 p->tempRows = tempRows;
paulo@0 1473 // At this point, p->tempRows holds the rows in which
paulo@0 1474 // a line HAS been made.
paulo@0 1475 addLinesScore(p, tempRows);
paulo@0 1476 changedRows |= LJ_DIRTY_SCORE;
paulo@0 1477
paulo@0 1478 // At this point, p->tempRows holds the rows in which a line
paulo@0 1479 // HAS been made.
paulo@0 1480 p->clearedLines = tempRows;
paulo@0 1481 if (!tempRows) {
paulo@0 1482 prepareForNewPiece(p);
paulo@0 1483 break;
paulo@0 1484 }
paulo@0 1485
paulo@0 1486 changedRows |= clearLines(p, tempRows);
paulo@0 1487
paulo@0 1488 p->state = LJS_LINES_FALLING;
paulo@0 1489 p->stateTime += p->speed.lineDelay;
paulo@0 1490 break;
paulo@0 1491
paulo@0 1492 case LJS_LINES_FALLING:
paulo@0 1493 if (p->stateTime > 0) {
paulo@0 1494 --p->stateTime;
paulo@0 1495 }
paulo@0 1496 if (p->stateTime > 0) {
paulo@0 1497 break;
paulo@0 1498 }
paulo@0 1499 moved = fallLines(p);
paulo@0 1500 if (moved >= LJ_PF_HT) {
paulo@0 1501 p->state = LJS_LINES;
paulo@0 1502 p->tempRows = (1 << LJ_PF_HT) - 1;
paulo@0 1503 }
paulo@0 1504 changedRows |= (~0 << moved) & ((1 << LJ_PF_VIS_HT) - 1);
paulo@0 1505 break;
paulo@0 1506
paulo@0 1507 default:
paulo@0 1508 break;
paulo@0 1509
paulo@0 1510 }
paulo@0 1511
paulo@0 1512 ++p->gameTime;
paulo@0 1513 return changedRows;
paulo@0 1514 }
paulo@0 1515
paulo@0 1516