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