Didn't set the version as a string
[crack-attack.git] / src / Grid.cxx
blob6dca694909203bb9761bcad9ea5d35944e29d449
1 /*
2 * Grid.cxx
3 * Daniel Nelson - 8/23/0
5 * Copyright (C) 2000 Daniel Nelson
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 * Daniel Nelson - aluminumangel.org
22 * 174 W. 18th Ave.
23 * Columbus, OH 43210
25 * Holds a map of the play area and also deals with elimination checks.
28 using namespace std;
30 #include "Game.h"
31 #include "Grid.h"
32 #include "Garbage.h"
33 #include "ComboManager.h"
34 #include "LevelLights.h"
35 #include "Swapper.h"
36 #include "BlockManager.h"
37 #include "Random.h"
39 GridElement Grid::grid[GC_PLAY_WIDTH][GC_PLAY_HEIGHT];
40 CheckRegistryElement Grid::check_registry[GC_GRID_SIZE];
41 int Grid::check_count;
42 int Grid::shatter_count;
43 int Grid::shatter_top;
44 int Grid::shatter_bottom;
45 bool Grid::gray_shatter;
46 int Grid::top_occupied_row;
47 int Grid::top_effective_row;
49 void Grid::gameStart ( )
51 check_count = 0;
53 top_occupied_row = 0;
55 for (int i = GC_PLAY_WIDTH; i--; )
56 for (int j = GC_PLAY_HEIGHT; j--; ) {
57 grid[i][j].state = GR_EMPTY;
58 grid[i][j].resident_type = GR_EMPTY;
59 grid[i][j].resident = null;
62 // Generate the initial blocks.
64 int short_collumn = Random::number(GC_PLAY_WIDTH);
66 for (int x = GC_PLAY_WIDTH; x--; ) {
68 // generate the height
69 int height = (short_collumn == x ? 2 : 7) + Random::number2(2);
70 if (height - 1 > top_occupied_row)
71 top_occupied_row = height - 1;
73 for (int y = height; --y; ) {
75 // determine the flavor
76 int flavor;
77 do {
78 flavor = Random::number(BF_NUMBER_NORMAL);
80 if (!(Grid::stateAt(x, y + 1) & GR_EMPTY)
81 && Grid::blockAt(x, y + 1).flavor == flavor) continue;
83 if (x == GC_PLAY_WIDTH - 1) break;
85 if (!(Grid::stateAt(x + 1, y) & GR_EMPTY)
86 && Grid::blockAt(x + 1, y).flavor == flavor) continue;
88 break;
89 } while (true);
91 // set the block manager block creation state
92 if (y == 2)
93 BlockManager::second_to_last_row_c[x] = flavor;
94 else if (y == 1)
95 BlockManager::last_row_c[x] = flavor;
97 // create the block
98 BlockManager::newBlock(x, y, flavor);
102 top_effective_row = top_occupied_row;
105 void Grid::timeStep ( )
107 // Process elimination check requests.
109 // loop through the request registry
110 for (int n = 0; check_count; n++)
111 if (check_registry[n].mark) {
112 check_registry[n].mark = false;
113 check_count--;
115 Block &block = BlockManager::blockStore[n];
117 // insure that the block is still static
118 if (!(block.state & BS_STATIC)) continue;
120 // use the block's combo, if it has one
121 handleEliminationCheckRequest(block,
122 block.current_combo ? block.current_combo : check_registry[n].combo);
125 // Update top_occupied_row.
126 top_occupied_row++;
127 bool flag = true;
128 do {
129 top_occupied_row--;
130 for (int x = GC_PLAY_WIDTH; x--; )
131 if (!(stateAt(x, top_occupied_row) & GR_EMPTY)) {
132 flag = false;
133 break;
135 } while (flag);
137 // Update top_effective_row.
138 int o = top_effective_row;
139 top_effective_row++;
140 flag = true;
141 do {
142 top_effective_row--;
143 for (int x = GC_PLAY_WIDTH; x--; )
144 if (!(residentTypeAt(x, top_effective_row) & GR_EMPTY)
145 && !((residentTypeAt(x, top_effective_row) & GR_GARBAGE)
146 && garbageAt(x, top_effective_row).initial_fall)) {
147 flag = false;
148 break;
150 } while (flag);
151 if (top_effective_row < o)
152 LevelLights::levelLower(top_effective_row);
155 void Grid::handleEliminationCheckRequest ( Block &block, ComboTabulator *combo )
157 int x = block.x;
158 int y = block.y;
160 // Look in four directions for matching lines.
162 int l = x;
163 while (l > 0) {
164 if (!(stateAt(l - 1, y) & GR_BLOCK)) break;
165 if (!matchAt(l - 1, y, block)) break;
166 l--;
169 int r = x + 1;
170 while (r < GC_PLAY_WIDTH) {
171 if (!(stateAt(r, y) & GR_BLOCK)) break;
172 if (!matchAt(r, y, block)) break;
173 r++;
176 int b = y;
177 while (b > 1) {
178 if (!(stateAt(x, b - 1) & GR_BLOCK)) break;
179 if (!matchAt(x, b - 1, block)) break;
180 b--;
183 int t = y + 1;
184 while (t < GC_PLAY_HEIGHT) {
185 if (!(stateAt(x, t) & GR_BLOCK)) break;
186 if (!matchAt(x, t, block)) break;
187 t++;
190 int w = r - l;
191 int h = t - b;
193 int magnitude = 0;
194 int pattern = 0;
195 if (w >= GC_MIN_PATTERN_LENGTH) {
196 pattern |= PT_HORIZONTAL;
197 magnitude += w;
199 if (h >= GC_MIN_PATTERN_LENGTH) {
200 pattern |= PT_VERTICAL;
201 magnitude += h;
204 // if no pattern found
205 if (pattern == 0) {
206 block.endComboInvolvement(combo);
207 return;
210 // create a combo for the elimination
211 if (!combo)
212 combo = &ComboManager::newComboTabulator();
214 // if pattern matches both directions
215 if (pattern == (PT_HORIZONTAL | PT_VERTICAL)) magnitude--;
217 // Kill the pattern's blocks and look for touching garbage.
219 shatter_count = 0;
220 shatter_top = 0;
221 shatter_bottom = GC_PLAY_HEIGHT;
223 // we need to know if it's a gray shatter in order to know if black
224 // garbage should shatter
225 if (!BlockManager::isColorlessFlavor(block.flavor))
226 gray_shatter = false;
227 else
228 gray_shatter = true;
230 ComboManager::specialBlockTally(*combo, block);
231 block.startDying(combo, magnitude);
233 if (pattern & PT_HORIZONTAL) {
234 // kill the pattern's blocks
235 for (int k_x = l; k_x < r; k_x++)
236 if (k_x != x) {
237 ComboManager::specialBlockTally(*combo, blockAt(k_x, y));
238 blockAt(k_x, y).startDying(combo, magnitude);
241 // look for garbage to the left and below the pattern
242 if (l > 0)
243 shatterGarbage(l - 1, y);
244 if (y > 1)
245 for (int k_x = l; k_x < r; k_x++)
246 shatterGarbage(k_x, y - 1);
248 // look for garbage to the right and above the pattern
249 if (r < GC_PLAY_WIDTH)
250 shatterGarbage(r, y);
251 if (y < GC_PLAY_HEIGHT - 1)
252 for (int k_x = l; k_x < r; k_x++)
253 shatterGarbage(k_x, y + 1);
256 if (pattern & PT_VERTICAL) {
257 // kill the pattern's blocks
258 for (int k_y = b; k_y < t; k_y++)
259 if (k_y != y) {
260 ComboManager::specialBlockTally(*combo, blockAt(x, k_y));
261 blockAt(x, k_y).startDying(combo, magnitude);
264 // look for garbage to the left and below the pattern
265 if (b > 1)
266 shatterGarbage(x, b - 1);
267 if (x > 0)
268 for (int k_y = b; k_y < t; k_y++)
269 shatterGarbage(x - 1, k_y);
271 // look for garbage to the right and above the pattern
272 if (t < GC_PLAY_HEIGHT)
273 shatterGarbage(x, t);
274 if (x < GC_PLAY_WIDTH - 1)
275 for (int k_y = b; k_y < t; k_y++)
276 shatterGarbage(x + 1, k_y);
279 // The grid now contains shattered elements. We now traverse the grid and
280 // syncronize the shattering garbages' timers. The garbage itself deals with
281 // the creation of blocks and such, as it's behavior may varry.
283 // delay until the new blocks fall
284 int awaken_delay = GC_INITIAL_POP_DELAY + GC_FINAL_POP_DELAY
285 + GC_INTERNAL_POP_DELAY * (shatter_count - 1);
287 // delay until the next block pops
288 int pop_delay = GC_INITIAL_POP_DELAY;
290 // traverse the shatter area; s_x and pop_delay will be advanced during
291 // startShattering()
292 for (int s_y = shatter_bottom; s_y < shatter_top; s_y++)
293 for (int s_x = 0; s_x < GC_PLAY_WIDTH; )
294 if (stateAt(s_x, s_y) & GR_SHATTERING)
295 garbageAt(s_x, s_y).startShattering(s_x, s_y, pop_delay, awaken_delay,
296 combo);
297 else
298 s_x++;
300 // notify the combo of the pattern match
301 combo->reportElimination(magnitude, block);
304 void Grid::shatterGarbage_inline_split_ ( int x, int y, Garbage *due_to )
306 Garbage &garbage = garbageAt(x, y);
308 // ask the garbage about shattering; we have to do black ourselves
309 if (!garbage.considerShattering(due_to)) return;
310 if (garbage.flavor == GF_BLACK && !gray_shatter) return;
312 // keep track of the bounds on the shattered area
313 if (y + garbage.height > shatter_top)
314 shatter_top = y + garbage.height;
315 if (y < shatter_bottom)
316 shatter_bottom = y;
318 shatter_count += garbage.width * garbage.height;
320 // mark the garbage grid locations as shattered
321 for (int h = garbage.height; h--; )
322 for (int w = garbage.width; w--; )
323 grid[garbage.x + w][garbage.y + h].state = GR_SHATTERING;
325 // look for connecting garbage recursively
326 if (garbage.x > 0)
327 for (int h = garbage.height; h--; )
328 shatterGarbage(garbage.x - 1, garbage.y + h, &garbage);
329 if (garbage.x + garbage.width < GC_PLAY_WIDTH)
330 for (int h = garbage.height; h--; )
331 shatterGarbage(garbage.x + garbage.width, garbage.y + h, &garbage);
332 if (garbage.y > 1)
333 for (int w = garbage.width; w--; )
334 shatterGarbage(garbage.x + w, garbage.y - 1, &garbage);
335 if (garbage.y + garbage.height < GC_PLAY_HEIGHT)
336 for (int w = garbage.width; w--; )
337 shatterGarbage(garbage.x + w, garbage.y + garbage.height, &garbage);
340 bool Grid::shiftGridUp ( )
342 if (top_occupied_row == GC_PLAY_HEIGHT - 1) return false;
344 // shift the grid
345 for (int y = top_occupied_row + 1; y--; )
346 for (int x = GC_PLAY_WIDTH; x--; )
347 grid[x][y + 1] = grid[x][y];
349 #ifndef NDEBUG
350 // otherwise the assert() will tag us
351 for (int x = GC_PLAY_WIDTH; x--; )
352 grid[x][0].state = GR_EMPTY;
353 #endif
355 top_occupied_row++;
356 top_effective_row++;
357 LevelLights::levelRaise(top_effective_row);
359 // shift blocks and garbage up with the grid
360 BlockManager::shiftUp();
361 GarbageManager::shiftUp();
363 // shift swapper up
364 Swapper::shiftUp();
366 return true;