rev |
line source |
paulo@0
|
1 /* Gimmick code for LOCKJAW, an implementation of the Soviet Mind Game
|
paulo@0
|
2
|
paulo@0
|
3 Copyright (C) 2006-2007 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 #include "lj.h"
|
paulo@0
|
26 #include "ljcontrol.h"
|
paulo@0
|
27
|
paulo@0
|
28 void initSpeed(LJField *p); // in speed.c
|
paulo@0
|
29 void setSpeed(LJField *p, LJControl *c); // in speed.c
|
paulo@0
|
30 int updLevelAfterPiece(LJField *p); // in speed.c
|
paulo@0
|
31 int updLevelAfterLines(LJField *p, size_t nLines); // in speed.c
|
paulo@0
|
32
|
paulo@0
|
33 void initGimmicks(LJField *p) {
|
paulo@0
|
34 initSpeed(p);
|
paulo@0
|
35 p->speed.entryDelay = 0; /* new pieces will increase this */
|
paulo@0
|
36 p->speed.lineDelay = 0;
|
paulo@0
|
37 if (p->garbageStyle == LJGARBAGE_DRILL
|
paulo@0
|
38 || p->gimmick == LJGM_DRILL) {
|
paulo@0
|
39 p->garbageRandomness = 255;
|
paulo@0
|
40 p->garbage = p->ceiling - 2;
|
paulo@0
|
41 } else if (p->garbageStyle == LJGARBAGE_ZIGZAG) {
|
paulo@0
|
42 setupZigzagField(p, p->ceiling * 3 / 4);
|
paulo@0
|
43 }
|
paulo@0
|
44
|
paulo@0
|
45 }
|
paulo@0
|
46
|
paulo@0
|
47 LJBits gimmicks(LJField *p, LJControl *c) {
|
paulo@0
|
48 LJBits changed = 0;
|
paulo@0
|
49
|
paulo@0
|
50 // In rhythm, lock the tetromino if the player
|
paulo@0
|
51 // isn't keeping up with the rate
|
paulo@0
|
52 if (p->speedState.curve == LJSPD_RHYTHM
|
paulo@0
|
53 || p->speedState.curve == LJSPD_RHYTHMZERO) {
|
paulo@0
|
54 p->bpmCounter += p->speedState.level;
|
paulo@0
|
55
|
paulo@0
|
56 if (p->bpmCounter >= 0) {
|
paulo@0
|
57 if (p->state == LJS_LANDED) {
|
paulo@0
|
58 p->stateTime = 0;
|
paulo@0
|
59 } else if (p->state == LJS_FALLING) {
|
paulo@0
|
60 p->speed.gravity = ljitofix(LJ_PF_HT);
|
paulo@0
|
61 }
|
paulo@0
|
62 }
|
paulo@0
|
63
|
paulo@0
|
64 // In rhythm, increase BPM periodically
|
paulo@0
|
65 p->speedupCounter += p->speedState.level;
|
paulo@0
|
66 if(p->speedupCounter >= 60 * 60 * 64) {
|
paulo@0
|
67 p->speedupCounter -= 60 * 60 * 64;
|
paulo@0
|
68 p->speedState.level += 10;
|
paulo@0
|
69 p->sounds |= LJSND_SECTIONUP;
|
paulo@0
|
70 }
|
paulo@0
|
71 }
|
paulo@0
|
72
|
paulo@0
|
73 // For each piece, set the entry and line delays.
|
paulo@0
|
74 // Don't set it twice when spawning to replace the
|
paulo@0
|
75 // first held piece (both LJSND_SPAWN and LJSND_HOLD
|
paulo@0
|
76 // on the same piece).
|
paulo@0
|
77 if ((p->sounds & (LJSND_SPAWN | LJSND_HOLD))
|
paulo@0
|
78 == LJSND_SPAWN) {
|
paulo@0
|
79 setSpeed(p, c);
|
paulo@0
|
80 }
|
paulo@0
|
81
|
paulo@0
|
82 if (p->sounds & LJSND_LOCK) {
|
paulo@0
|
83 if (p->gimmick == LJGM_ITEMS) {
|
paulo@0
|
84 if (p->nPieces >= 7) {
|
paulo@0
|
85 p->canRotate = 0;
|
paulo@0
|
86 p->speed.gravity = ljitofix(1);
|
paulo@0
|
87 }
|
paulo@0
|
88 }
|
paulo@0
|
89
|
paulo@0
|
90 // Garbage in simulated multiplayer
|
paulo@0
|
91 int simGarbage = p->nPieces >= 7
|
paulo@0
|
92 && p->garbageStyle >= LJGARBAGE_1
|
paulo@0
|
93 && p->garbageStyle <= LJGARBAGE_4
|
paulo@0
|
94 && (p->curPiece[1] == LJP_I
|
paulo@0
|
95 || ((p->pieceSet == LJRAND_SZ
|
paulo@0
|
96 || p->pieceSet == LJRAND_JLOSTZ)
|
paulo@0
|
97 && p->nPieces % 7 == 3));
|
paulo@0
|
98 if (simGarbage) {
|
paulo@0
|
99 p->garbage += p->garbageStyle;
|
paulo@0
|
100 }
|
paulo@0
|
101
|
paulo@0
|
102 // Banana attack in "Vs. with Items" gimmick
|
paulo@0
|
103 if (p->gimmick == LJGM_ITEMS
|
paulo@0
|
104 && (ljRand(p) & 0xF00) == 0xF00) {
|
paulo@0
|
105 shuffleColumns(p);
|
paulo@0
|
106 changed |= (1 << LJ_PF_HT) - 1;
|
paulo@0
|
107 }
|
paulo@0
|
108 }
|
paulo@0
|
109 return changed;
|
paulo@0
|
110 }
|
paulo@0
|
111
|
paulo@0
|
112
|
paulo@0
|
113
|
paulo@0
|
114 const char hotlineRows[LJ_PF_HT] =
|
paulo@0
|
115 {
|
paulo@0
|
116 0, 0, 0, 0, 1,
|
paulo@0
|
117 0, 0, 0, 0, 2,
|
paulo@0
|
118 0, 0, 0, 3, 0,
|
paulo@0
|
119 0, 4, 0, 5, 6,
|
paulo@0
|
120 0, 0, 0, 0
|
paulo@0
|
121 };
|
paulo@0
|
122
|
paulo@0
|
123 static const char garbageScoreTable[] = { 0, 0, 1, 2, 4 };
|
paulo@0
|
124 static const char tSpinGarbageScoreTable[] = { 0, 2, 4, 6, 6 };
|
paulo@0
|
125 static const unsigned char squareScoreTable[] =
|
paulo@0
|
126 {1, 1, 1, 2, 3, 5, 8, 13,
|
paulo@0
|
127 21, 34, 55, 89, 144, 200};
|
paulo@0
|
128
|
paulo@0
|
129 static const char tdsScoreTable[] = {0, 1, 3, 5, 8};
|
paulo@0
|
130 static const char tdsTSScoreTable[] = {4, 8, 12, 16};
|
paulo@0
|
131 static const char nesScoreTable[] = {0, 4, 10, 30, 120};
|
paulo@0
|
132
|
paulo@0
|
133
|
paulo@0
|
134 /**
|
paulo@0
|
135 * Computes the score and outgoing garbage for lines
|
paulo@0
|
136 * and adds them to the player's total.
|
paulo@0
|
137 * @param p The playfield
|
paulo@0
|
138 * @param lines Bit array where 1 means a line clear on this row.
|
paulo@0
|
139 */
|
paulo@0
|
140 void addLinesScore(LJField *p, LJBits lines) {
|
paulo@0
|
141 const int nLines = countOnes(lines);
|
paulo@0
|
142 int oldLines = p->nLinesThisPiece;
|
paulo@0
|
143 int tdsSection = p->lines / 10 + 1;
|
paulo@0
|
144
|
paulo@0
|
145 p->lines += nLines;
|
paulo@0
|
146 if (updLevelAfterLines(p, nLines)) {
|
paulo@0
|
147 p->sounds |= LJSND_SECTIONUP;
|
paulo@0
|
148 }
|
paulo@0
|
149
|
paulo@0
|
150 switch (p->scoreStyle) {
|
paulo@0
|
151
|
paulo@0
|
152 case LJSCORE_TNT64:
|
paulo@0
|
153 if (nLines < 1) {
|
paulo@0
|
154 return;
|
paulo@0
|
155 }
|
paulo@0
|
156 for (int i = nLines; i > 0; --i) {
|
paulo@0
|
157 if (oldLines > sizeof(squareScoreTable) - 1) {
|
paulo@0
|
158 oldLines = sizeof(squareScoreTable) - 1;
|
paulo@0
|
159 }
|
paulo@0
|
160 p->score += 100 * squareScoreTable[oldLines++];
|
paulo@0
|
161 } break;
|
paulo@0
|
162
|
paulo@0
|
163 case LJSCORE_NES:
|
paulo@0
|
164 {
|
paulo@0
|
165 int garbageLevel = (nLines > 4) ? 4 : nLines;
|
paulo@0
|
166 int value = nesScoreTable[garbageLevel];
|
paulo@0
|
167
|
paulo@0
|
168 p->score += 10 * tdsSection * value;
|
paulo@0
|
169 if (nLines >= 4) {
|
paulo@0
|
170 p->sounds |= LJSND_SETB2B;
|
paulo@0
|
171 }
|
paulo@0
|
172 } break;
|
paulo@0
|
173
|
paulo@0
|
174 case LJSCORE_TDS:
|
paulo@0
|
175 {
|
paulo@0
|
176 // Garbage based scoring system.
|
paulo@0
|
177 int garbageLevel = (nLines > 4) ? 4 : nLines;
|
paulo@0
|
178 int chain, value;
|
paulo@0
|
179
|
paulo@0
|
180 if (p->isSpin) {
|
paulo@0
|
181 chain = (nLines >= 1);
|
paulo@0
|
182 value = tdsTSScoreTable[garbageLevel];
|
paulo@0
|
183
|
paulo@0
|
184 // TDS gives fewer points for a T-spin single
|
paulo@0
|
185 // that involves a wall kick.
|
paulo@0
|
186 if (p->isSpin == 2 && nLines < 2) {
|
paulo@0
|
187 value >>= 2;
|
paulo@0
|
188 }
|
paulo@0
|
189 } else {
|
paulo@0
|
190 chain = (nLines >= 4);
|
paulo@0
|
191 value = tdsScoreTable[garbageLevel];
|
paulo@0
|
192 }
|
paulo@0
|
193 if (chain && p->chain && nLines >= 1) { // b2b
|
paulo@0
|
194 value = value * 3 / 2;
|
paulo@0
|
195 }
|
paulo@0
|
196
|
paulo@0
|
197 if (tdsSection > 20) {
|
paulo@0
|
198 tdsSection = 20;
|
paulo@0
|
199 }
|
paulo@0
|
200 p->score += 100 * tdsSection * value;
|
paulo@0
|
201 if (nLines >= 1) {
|
paulo@0
|
202 if (chain) {
|
paulo@0
|
203 p->sounds |= LJSND_SETB2B;
|
paulo@0
|
204 if (p->chain) {
|
paulo@0
|
205 p->sounds |= LJSND_B2B;
|
paulo@0
|
206 }
|
paulo@0
|
207 }
|
paulo@0
|
208 p->chain = chain;
|
paulo@0
|
209 }
|
paulo@0
|
210 } break;
|
paulo@0
|
211
|
paulo@0
|
212 case LJSCORE_LJ:
|
paulo@0
|
213 case LJSCORE_LJ_NERF:
|
paulo@0
|
214 if (nLines >= 1) {
|
paulo@0
|
215 // Garbage based scoring system.
|
paulo@0
|
216 int garbageLevel = (nLines > 4) ? 4 : nLines;
|
paulo@0
|
217 unsigned int chain, garbage;
|
paulo@0
|
218
|
paulo@0
|
219 if (p->isSpin) {
|
paulo@0
|
220 chain = (nLines >= 1);
|
paulo@0
|
221 garbage = tSpinGarbageScoreTable[garbageLevel];
|
paulo@0
|
222 if (p->scoreStyle == LJSCORE_LJ_NERF) {
|
paulo@0
|
223 garbage /= 2;
|
paulo@0
|
224 }
|
paulo@0
|
225 garbage += (chain && p->chain);
|
paulo@0
|
226 } else {
|
paulo@0
|
227 chain = (nLines >= 4);
|
paulo@0
|
228 garbage = garbageScoreTable[garbageLevel]
|
paulo@0
|
229 + (chain && p->chain);
|
paulo@0
|
230 }
|
paulo@0
|
231 p->score += 100 * nLines + 200 * garbage;
|
paulo@0
|
232 p->outGarbage += garbage;
|
paulo@0
|
233 if (chain) {
|
paulo@0
|
234 p->sounds |= LJSND_SETB2B;
|
paulo@0
|
235 if (p->chain) {
|
paulo@0
|
236 p->sounds |= LJSND_B2B;
|
paulo@0
|
237 }
|
paulo@0
|
238 }
|
paulo@0
|
239 p->chain = chain;
|
paulo@0
|
240 } break;
|
paulo@0
|
241
|
paulo@0
|
242 case LJSCORE_HOTLINE:
|
paulo@0
|
243 for (int y = 0; y < 24; ++y) {
|
paulo@0
|
244 if (lines & (1 << y)) {
|
paulo@0
|
245 p->score += 100 * hotlineRows[y];
|
paulo@0
|
246 }
|
paulo@0
|
247 } break;
|
paulo@0
|
248
|
paulo@0
|
249 }
|
paulo@0
|
250
|
paulo@0
|
251 p->nLinesThisPiece += nLines;
|
paulo@0
|
252 }
|
paulo@0
|
253
|
paulo@0
|
254 /**
|
paulo@0
|
255 * Puts a zigzag pattern of garbage into the playfield.
|
paulo@0
|
256 * Useful as a test suite for calcZigzagGrade() in debrief.c.
|
paulo@0
|
257 * @param p the playfield to set up
|
paulo@0
|
258 * @param height the number of rows to set up
|
paulo@0
|
259 */
|
paulo@0
|
260 void setupZigzagField(LJField *p, size_t height) {
|
paulo@0
|
261 unsigned int hole = p->leftWall;
|
paulo@0
|
262 int delta = 1;
|
paulo@0
|
263
|
paulo@0
|
264 if (height > p->ceiling) {
|
paulo@0
|
265 height = p->ceiling;
|
paulo@0
|
266 }
|
paulo@0
|
267
|
paulo@0
|
268 // set up example pattern
|
paulo@0
|
269 for (size_t y = 0; y < height; ++y) {
|
paulo@0
|
270 for (size_t x = p->leftWall; x < p->rightWall; ++x) {
|
paulo@0
|
271
|
paulo@0
|
272 // A block should be in all cells of the row
|
paulo@0
|
273 // except for the hole cell, which should be empty.
|
paulo@0
|
274 if (x == hole) {
|
paulo@0
|
275 p->b[y][x] = 0;
|
paulo@0
|
276 } else {
|
paulo@0
|
277 p->b[y][x] = 0x80;
|
paulo@0
|
278 }
|
paulo@0
|
279 }
|
paulo@0
|
280
|
paulo@0
|
281 if (hole == p->rightWall - 1) {
|
paulo@0
|
282 delta = -1;
|
paulo@0
|
283 } else if (hole == p->leftWall) {
|
paulo@0
|
284 delta = 1;
|
paulo@0
|
285 }
|
paulo@0
|
286 hole += delta;
|
paulo@0
|
287 }
|
paulo@0
|
288
|
paulo@0
|
289 // clear rows above pattern
|
paulo@0
|
290 for (size_t y = height; y < LJ_PF_HT; ++y) {
|
paulo@0
|
291 for (size_t x = p->leftWall; x < p->rightWall; ++x) {
|
paulo@0
|
292 p->b[y][x] = 0;
|
paulo@0
|
293 }
|
paulo@0
|
294 }
|
paulo@0
|
295 }
|