1 /*
2  * Block.cxx
3  * Daniel Nelson - 8/21/0
4  *
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.
11  *
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.
16  *
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.
20  *
21  * Daniel Nelson - aluminumangel.org
22  * 174 W. 18th Ave.
23  * Columbus, OH  43210
24  *
25  * The block object, of which each block is one.
26  */
27 
28 #include "Game.h"
29 #include "Block.h"
30 #include "BlockManager.h"
31 #include "ComboTabulator.h"
32 #include "Garbage.h"
33 #include "Grid.h"
34 #include "Random.h"
35 #include "Swapper.h"
36 #include "SparkleManager.h"
37 #include "X.h"
38 #include "Sound.h"
39 
40 using namespace std;
41 
initializeStatic(int _x,int _y,int _flavor)42 void Block::initializeStatic ( int _x, int _y, int _flavor )
43 {
44   x = _x;
45   y = _y;
46   flavor = _flavor;
47   f_y = 0;
48 
49   state = BS_STATIC;
50   alarm = 0;
51   // pop_alarm = 0;
52   current_combo = null;
53 
54   // add ourselves to the grid
55   Grid::addBlock(x, y, this, GR_BLOCK);
56 
57   // if we're wild, the extreme code needs to know
58   if (flavor == BF_WILD)
59     X::activateWild(*this);
60   else if (BlockManager::isSpecialColorFlavor(flavor))
61     X::activateSpecialColor(*this);
62 }
63 
initializeAwaking(int _x,int _y,int _flavor,int pop_delay,int awake_delay,ComboTabulator * combo,int _pop_color)64 void Block::initializeAwaking ( int _x, int _y, int _flavor, int pop_delay,
65  int awake_delay, ComboTabulator *combo, int _pop_color )
66 {
67   x = _x;
68   y = _y;
69   flavor = _flavor;
70   f_y = 0;
71 
72   state = BS_AWAKING;
73   alarm = Game::time_step + awake_delay;
74   pop_alarm = Game::time_step + pop_delay;
75   pop_direction = BlockManager::generatePopDirection();
76   pop_color = _pop_color;
77   current_combo = combo;
78 
79   // let the combo know we're involved
80   current_combo->incrementInvolvement();
81 
82   // change the game state
83   Game::awaking_count++;
84 
85   // add ourselves to the grid
86   Grid::addBlock(x, y, this, GR_IMMUTABLE);
87 }
88 
timeStep()89 void Block::timeStep (   )
90 {
91   if (state & BS_STATIC) {
92     // We may have to fall.
93 
94     if (Grid::stateAt(x, y - 1) & GR_EMPTY)
95       startFalling();
96     else
97       return;
98 
99   } else if (state & BS_AWAKING) {
100     // The alarm has been set to go off when we're done awaking.  When the
101     // pop alarm goes off, we only switch our appearence.
102     if (pop_alarm == Game::time_step) {
103 #ifdef AUDIO_ENABLED
104       Sound::play( GC_SOUND_BLOCK_AWAKING, 5 );
105 #endif
106       pop_alarm = 0;
107     }
108 
109     if (alarm == Game::time_step) {
110 
111       // change the game state
112       Game::awaking_count--;
113 
114       // change our state; startFalling() and eliminations check for
115       // BS_STATIC state
116       state = BS_STATIC;
117 
118       // if we're going to fall
119       if (Grid::stateAt(x, y - 1) & GR_EMPTY)
120         startFalling(current_combo, true);
121 
122       else {
123         // update the grid
124         Grid::changeState(x, y, this, GR_BLOCK);
125 
126         // register for elimination checking
127         Grid::requestEliminationCheck(*this, current_combo);
128       }
129 
130     } else
131       return;
132   }
133 
134   // Deal with all other states.
135 
136   if (state & BS_FALLING) {
137     // We are assured that the timeStep() of any blocks below us has already
138     // been called.  Note that to start a fall, all we have to do is set our
139     // state to BS_FALLING.  This code will deal with the rest.
140 
141     if (alarm == Game::time_step)
142       // hang alarm goes off
143       alarm = 0;
144 
145     // if the hang alarm has gone off
146     if (alarm == 0) {
147 
148       // if we're at the bottom of a grid element
149       if (f_y == 0) {
150 
151         // if we're still going to fall
152         if (Grid::stateAt(x, y - 1) & GR_EMPTY) {
153 
154           // shift our grid position down to the next row
155           y--;
156           f_y = GC_STEPS_PER_GRID;
157 
158           // update the grid
159           Grid::remove(x, y + 1, this);
160           Grid::addBlock(x, y, this, GR_FALLING);
161 
162         // if we've landed
163         } else {
164 
165           // change our state
166           state = BS_STATIC;
167 #ifdef AUDIO_ENABLED
168           Sound::play( GC_SOUND_BLOCK_FALLEN, 2 );
169 #endif
170 
171           // update the grid
172           Grid::changeState(x, y, this, GR_BLOCK);
173 
174           // register for elimination checking
175           Grid::requestEliminationCheck(*this, current_combo);
176 
177           // if the block below us is swapping, we may have to switch it's combo
178           if (current_combo)
179             Swapper::notifyLanding(x, y, *this, current_combo);
180         }
181       }
182 
183       // if we still are, fall
184       if (state & BS_FALLING)
185         f_y -= GC_FALL_VELOCITY;
186     }
187 
188   } else if (state & BS_DYING) {
189     // The alarm has been set to go off when we're done dying.
190 
191     if (--alarm == 0) {
192 
193       // change the game state
194       Game::dying_count--;
195       Game::dying_count_2--;
196 
197       // update the grid
198       Grid::remove(x, y, this);
199 
200       // tell our upward neighbor to start a combo fall
201       if (y < GC_PLAY_HEIGHT - 1) {
202         if (Grid::stateAt(x, y + 1) & GR_BLOCK)
203           Grid::blockAt(x, y + 1).startFalling(current_combo);
204         else if (Grid::stateAt(x, y + 1) & GR_GARBAGE)
205           Grid::garbageAt(x, y + 1).startFalling(current_combo);
206       }
207 
208       // let the combo know we're out
209       current_combo->decrementInvolvement();
210 
211       // generate some sparkles; pop_alarm stores the number
212       if (flavor != BF_WILD)
213         SparkleManager::createBlockDeathSpark(x, y, flavor, pop_alarm);
214       else
215         SparkleManager::createBlockDeathSpark(x, y, X::wildFlavor(*this),
216          pop_alarm);
217 
218       // if we're wild, the extreme code needs to know
219       if (flavor == BF_WILD)
220         X::deactivateWild(*this);
221       else if (BlockManager::isSpecialColorFlavor(flavor))
222         X::deactivateSpecialColor();
223 
224       // delete ourselves
225       BlockManager::deleteBlock(this);
226 
227     // if we just started dying
228     } else if (alarm == GC_DYING_DELAY - 1)
229       // grab the elimination magnitude from our combo
230       pop_alarm = current_combo->latest_magnitude;
231   }
232 }
233 
startFalling(ComboTabulator * combo,bool no_hang)234 void Block::startFalling ( ComboTabulator *combo, bool no_hang )
235 /*
236  * Although blocks will fall on their own, this must be called to syncronize
237  * and connect that falling with a elimination combo.
238  */
239 {
240   if (!(state & BS_STATIC)) return;
241 
242   // change our state
243   state = BS_FALLING;
244 
245   // set the hang alarm and update the grid
246   if (no_hang) {
247     alarm = 0;
248     Grid::changeState(x, y, this, GR_FALLING);
249 
250   } else {
251     alarm = Game::time_step + GC_HANG_DELAY;
252     Grid::changeState(x, y, this, GR_HANGING | GR_FALLING);
253   }
254 
255   // let the combo know we're involved
256   if (combo)
257     beginComboInvolvement(combo);
258 
259   // tell our upward neighbor to start a combo fall
260   if (y < GC_PLAY_HEIGHT - 1) {
261     if (Grid::stateAt(x, y + 1) & GR_BLOCK)
262       Grid::blockAt(x, y + 1).startFalling(current_combo, no_hang);
263     else if (Grid::stateAt(x, y + 1) & GR_GARBAGE)
264       Grid::garbageAt(x, y + 1).startFalling(current_combo, no_hang);
265   }
266 }
267 
startDying(ComboTabulator * combo,int spark_number)268 void Block::startDying ( ComboTabulator *combo, int spark_number )
269 {
270   // change the game state
271   Game::dying_count++;
272   Game::dying_count_2++;
273 #ifdef AUDIO_ENABLED
274   Sound::play( GC_SOUND_BLOCK_DYING, spark_number / 3 );
275 #endif
276 
277   // let the combo know we're in
278   beginComboInvolvement(combo);
279 
280   // change our state
281   state = BS_DYING;
282 
283   // set the alarm; the alarm works this way due to display needs
284   alarm = GC_DYING_DELAY;
285 
286   // update the grid
287   Grid::changeState(x, y, this, GR_IMMUTABLE);
288 
289   // generate a random rotation axis
290   Random::angle(axis_x, axis_y);
291 }
292 
startSwapping(int direction)293 void Block::startSwapping ( int direction )
294 {
295   // change our state and swap direction
296   state = BS_SWAPPING | (direction & SA_RIGHT ? BS_SWAP_DIRECTION_MASK : 0);
297 
298   // update the grid
299   Grid::changeState(x, y, this, GR_IMMUTABLE);
300 }
301 
finishSwapping(int s_x)302 void Block::finishSwapping ( int s_x )
303 {
304   // change our state
305   state = BS_STATIC;
306   x = s_x;
307 
308   // update the grid
309   Grid::addBlock(x, y, this, GR_BLOCK);
310 }
311 
312