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
|