1 // Brain Party
2 // Copyright (C) 2010 Paul Hudson (http://www.tuxradar.com/brainparty)
3
4 // Brain Party is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 3
7 // of the License, or (at your option) any later version.
8
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 #include "jeweljam.h"
19 #include "Minigame.h"
20
BPMiniGame_JewelJam(BPGame * game)21 BPMiniGame_JewelJam::BPMiniGame_JewelJam(BPGame* game) : BPMiniGame(game) {
22 sfcBackground = TheGame->LoadBitmap("jeweljam", 320, 416);
23
24 SelectedRow = NULL;
25 Locked = false; // when true disallow movement
26 TimeStarted = 0;
27 SuccessTime = -1;
28
29 sfcClock = sfcScoreStr = NULL;
30
31 DisappearTime = 350;
32
33 Score = 0;
34 SetScore();
35
36 GameTitle = "Jewel Jam";
37 GameHelp = "Drag the rows of gems left and right, matching three or more of the same type scores points and more gems will fall. The more you match in one move, the more you score. But hurry - you have only two minutes!";
38 GameHelp2 = "The key in this game is that you can only slide left and right, so you need to start looking for vertical columns of gems to match. Generally it's best to work near the bottom of the screen, because that way falling gems are more likely to have a chain reaction and earn you more points.";
39
40 MiniGameType = PUZZLE;
41
42 ColoursLo.Add(TheGame->LoadBitmap("gem1_40", 40, 40));
43 ColoursLo.Add(TheGame->LoadBitmap("gem2_40", 40, 40));
44 ColoursLo.Add(TheGame->LoadBitmap("gem3_40", 40, 40));
45 ColoursLo.Add(TheGame->LoadBitmap("gem4_40", 40, 40));
46 ColoursLo.Add(TheGame->LoadBitmap("gem5_40", 40, 40));
47 ColoursLo.Add(TheGame->LoadBitmap("gem6_40", 40, 40));
48 ColoursLo.Add(TheGame->LoadBitmap("gem7_40", 40, 40));
49 ColoursLo.Add(TheGame->LoadBitmap("gem8_40", 40, 40));
50
51 ColoursHi.Add(TheGame->LoadBitmap("gem1_hi_40", 40, 40));
52 ColoursHi.Add(TheGame->LoadBitmap("gem2_hi_40", 40, 40));
53 ColoursHi.Add(TheGame->LoadBitmap("gem3_hi_40", 40, 40));
54 ColoursHi.Add(TheGame->LoadBitmap("gem4_hi_40", 40, 40));
55 ColoursHi.Add(TheGame->LoadBitmap("gem5_hi_40", 40, 40));
56 ColoursHi.Add(TheGame->LoadBitmap("gem6_hi_40", 40, 40));
57 ColoursHi.Add(TheGame->LoadBitmap("gem7_hi_40", 40, 40));
58 ColoursHi.Add(TheGame->LoadBitmap("gem8_hi_40", 40, 40));
59
60 for (int j = 0; j < 9; ++j) {
61 vector<BPMiniGame_JewelJam_Box*>* row = new vector<BPMiniGame_JewelJam_Box*>();
62
63 for (int i = 0; i < 8; ++i) {
64 BPMiniGame_JewelJam_Box* box = new BPMiniGame_JewelJam_Box();
65 box->X = i * BoxSize;
66 box->Y = -((9 - j) * BoxSize * i);
67 box->DestY = 77 + (j * BoxSize);
68 box->Colour = TheGame->RandomRange(0, ColoursLo.Count - 1);
69 row->push_back(box);
70 }
71
72 Boxes.push_back(row);
73 }
74 }
75
~BPMiniGame_JewelJam()76 BPMiniGame_JewelJam::~BPMiniGame_JewelJam() {
77 SAFE_DELETE(sfcBackground);
78
79 BPMiniGame_JewelJam_Box* box;
80
81 for (int i = 0; i < Boxes.size(); ++i) {
82 vector<BPMiniGame_JewelJam_Box*>* row = Boxes[i];
83
84 for (int i = row->size() - 1; i >= 0; --i) {
85 box = (*row)[i];
86 SAFE_DELETE(box);
87 }
88
89 row->clear();
90 SAFE_DELETE(row);
91 }
92
93 Boxes.clear();
94
95 ColoursLo.Clear();
96 ColoursHi.Clear();
97
98 if (SelectedRow != NULL) {
99 // shouldn't have to do this, because it's just pointers to things that have already been deleted
100 //SelectedRow->clear();
101 SAFE_DELETE(SelectedRow);
102 }
103
104 SAFE_DELETE(sfcScoreStr);
105 SAFE_DELETE(sfcClock);
106 }
107
OnMouseMove()108 void BPMiniGame_JewelJam::OnMouseMove() {
109 if (Locked) return;
110 if (SelectedRow == NULL) return;
111
112 Locked = true;
113
114 BPMiniGame_JewelJam_Box* box;
115
116 if (LastPos != TheGame->EmptyPoint && LastPos != TouchEvent) {
117 if (TouchEvent.X < LastPos.X) {
118 // move left
119
120 for (int i = SelectedRow->size() - 1; i >= 0; --i) {
121 box = (*SelectedRow)[i];
122
123 box->X += TouchEvent.X - LastPos.X;
124
125 if (box->X <= -BoxSize) {
126 box->X = MiniGameWidth + box->X;
127 SelectedRow->erase(SelectedRow->begin() + i);
128 SelectedRow->push_back(box);
129 }
130 }
131
132 } else {
133 // move right
134
135 for (int i = 0; i < SelectedRow->size(); ++i) {
136 box = (*SelectedRow)[i];
137
138 box->X += TouchEvent.X - LastPos.X;
139
140 if (box->X >= MiniGameWidth) {
141 box->X -= MiniGameWidth;
142 SelectedRow->erase(SelectedRow->begin() + i);
143 SelectedRow->insert(SelectedRow->begin() + 0, box);
144 }
145 }
146 }
147 }
148
149 LastPos = TouchEvent;
150
151 Locked = false;
152 }
153
OnMouseDown()154 void BPMiniGame_JewelJam::OnMouseDown() {
155 if (Locked) return;
156 Locked = true;
157
158 LastPos = TouchEvent;
159
160 for (int i = 0; i < Boxes.size(); ++i) {
161 if (TheGame->PointOverRect(TouchEvent.X, TouchEvent.Y, 0, 57 + (i * BoxSize), 320, BoxSize)) {
162 // move this row
163 SelectedRow = Boxes[i];
164 break;
165 }
166 }
167
168 Locked = false;
169 }
170
OnMouseUp()171 void BPMiniGame_JewelJam::OnMouseUp() {
172 if (Locked) return;
173 if (SelectedRow == NULL) return;
174
175 BPMiniGame_JewelJam_Box* box;
176
177 Locked = true;
178
179 // we've let go of a row - snap it into place!
180 int xoffset = (*SelectedRow)[0]->X;
181
182 int remainder = xoffset % BoxSize;
183 if (remainder != 0) {
184 int adjust = 0;
185
186 if (remainder > HalfBoxSize || (remainder < 0 && remainder > -HalfBoxSize)) {
187 // snap to the right
188 if (remainder > 0) {
189 adjust = BoxSize - remainder;
190 } else {
191 adjust = abs(remainder);
192 }
193
194 for (int i = 0; i < SelectedRow->size(); ++i) {
195 box = (*SelectedRow)[i];
196
197 box->X += adjust;
198
199 if (box->X >= MiniGameWidth) {
200 box->X -= MiniGameWidth;
201 SelectedRow->erase(SelectedRow->begin() + i);
202 SelectedRow->insert(SelectedRow->begin() + 0, box);
203 }
204 }
205 } else {
206 // snap to the left
207 if (remainder < 0) {
208 adjust = abs(remainder) - BoxSize;
209 } else {
210 adjust = -remainder;
211 }
212
213 for (int i = SelectedRow->size() - 1; i >= 0; --i) {
214 box = (*SelectedRow)[i];
215
216 box->X += adjust;
217
218 if (box->X <= -BoxSize) {
219 box->X = MiniGameWidth + box->X;
220 SelectedRow->erase(SelectedRow->begin() + i);
221 SelectedRow->push_back(box);
222 }
223 }
224
225 }
226 }
227
228 SelectedRow = NULL;
229
230 CheckForMatches();
231
232 // subtract one point for each move made
233 if (Score > 0) {
234 --Score;
235 SetScore();
236 }
237 }
238
Start()239 void BPMiniGame_JewelJam::Start() {
240 TimeStarted = TheGame->TickCount;
241 }
242
GetWeight()243 int BPMiniGame_JewelJam::GetWeight() {
244 return MinMax(round(Score / 6));
245 }
246
Render()247 void BPMiniGame_JewelJam::Render() {
248 TheGame->DrawImage(sfcBackground, 0, 0);
249
250 if (!MarathonMode) {
251 int TimePassed = TheGame->TickCount - TimeStarted;
252 TimePassed = 120000 - TimePassed;
253
254 if (TimePassed <= 0) {
255 if (SuccessTime == -1) {
256 SuccessTime = TheGame->TickCount;
257 Success();
258 }
259 }
260
261 if (sfcClock == NULL || RedrawClock()) {
262 TheGame->AllocString(&sfcClock, TheGame->TicksToTime(TimePassed)->c_str(), LARGE, 160, 50, RIGHT);
263 }
264
265 TheGame->DrawString(sfcClock, WHITE, 150, 4);
266 }
267
268 TheGame->DrawString(sfcScoreStr, WHITE, 6, 4);
269
270 BPMiniGame_JewelJam_Box* box;
271
272 for (int i = 0; i < Boxes.size(); ++i) {
273 vector<BPMiniGame_JewelJam_Box*>* row = Boxes[i];
274
275 for (int i = row->size() - 1; i >= 0; --i) {
276 box = (*row)[i];
277
278 // HACK: There's an unknown bug in this minigame that occasionally puts boxes in the wrong X pos.
279 // To reproduce, just keep matching stuff while dragging around on the screen manically.
280 // This force-resets the X position for each box to the correct value. What a waste of CPU time!
281 if (SelectedRow != row) box->X = i * BoxSize;
282
283 if (box->MatchTime == -1) {
284 TheGame->DrawImage(ColoursLo[box->Colour], box->X + HalfBoxSize, box->Y, 0.0f, 1.0f, (*TheGame->White));
285
286 if (box->X < 0) {
287 TheGame->DrawImage(ColoursLo[box->Colour], MiniGameWidth + box->X + HalfBoxSize, box->Y, 0.0f, 1.0f, (*TheGame->White));
288 } else if (box->X > MiniGameWidth - BoxSize) {
289 TheGame->DrawImage(ColoursLo[box->Colour], HalfBoxSize + box->X - MiniGameWidth, box->Y, 0.0f, 1.0f, (*TheGame->White));
290 }
291 } else {
292 float diff = TheGame->TickCount - box->MatchTime;
293
294 if (diff <= DisappearTime) {
295 float step = diff / DisappearTime; // get a value between 0 and 1
296 Colour col = Colour(1.0f, 1.0f, 1.0f, 1 - step);
297
298 TheGame->DrawImage(ColoursHi[box->Colour], box->X + HalfBoxSize, box->Y, 0.0f, TheGame->SmoothStep(1.0f, 3.0f, step), col);
299
300 if (box->X < 0) {
301 TheGame->DrawImage(ColoursHi[box->Colour], MiniGameWidth + box->X + HalfBoxSize, box->Y, 0.0f, TheGame->SmoothStep(1.0f, 3.0f, step), (*TheGame->White));
302 } else if (box->X > MiniGameWidth - BoxSize) {
303 TheGame->DrawImage(ColoursHi[box->Colour], HalfBoxSize + box->X - MiniGameWidth, box->Y, 0.0f, TheGame->SmoothStep(1.0f, 3.0f, step), (*TheGame->White));
304 }
305 }
306 }
307 }
308 }
309 }
310
Tick()311 void BPMiniGame_JewelJam::Tick() {
312 vector<BPMiniGame_JewelJam_Box*>* row;
313 BPMiniGame_JewelJam_Box* box;
314 BPMiniGame_JewelJam_Box* copybox;
315
316 int MatchTimeout = TheGame->TickCount - 150;
317
318 bool AllStill = true;
319
320 for (int i = Boxes.size() - 1; i >= 0; --i) {
321 row = Boxes[i];
322
323 for (int j = row->size() - 1; j >= 0; --j) {
324 box = (*row)[j];
325
326 if (box->MatchTime != -1) AllStill = false;
327
328 if (box->Y != box->DestY) {
329 AllStill = false;
330
331 box->YSpeed += 2.0f;
332 box->Y += box->YSpeed;
333 if (box->Y > box->DestY) {
334 box->Y = box->DestY;
335 box->YSpeed = 0;
336 }
337 }
338
339 if (box->MatchTime != -1 && box->MatchTime < MatchTimeout) {
340 SAFE_DELETE(box);
341
342 row->erase(row->begin() + j);
343 // move all boxes down above me
344
345 for (int k = i - 1; k >= 0; --k) {
346 copybox = (*Boxes[k])[j];
347 Boxes[k]->erase(Boxes[k]->begin() + j);
348 Boxes[k + 1]->insert(Boxes[k + 1]->begin() + j, copybox);
349 copybox->DestY = 77 + ((k + 1) * BoxSize);
350 }
351
352 BPMiniGame_JewelJam_Box* newbox = new BPMiniGame_JewelJam_Box();
353 newbox->X = j * BoxSize;
354 newbox->Y = -((9 - j) * BoxSize);
355 newbox->DestY = 77;
356 newbox->Colour = TheGame->RandomRange(0, ColoursLo.Count - 1);
357 Boxes[0]->insert(Boxes[0]->begin() + j, newbox);
358 }
359 }
360 }
361
362 if (AllStill) {
363 if (Locked) {
364 CheckForMatches();
365 }
366 } else {
367 Locked = true;
368 }
369 }
370
CheckForMatches()371 void BPMiniGame_JewelJam::CheckForMatches() {
372 Locked = true;
373
374 int ThisScore = 0;
375
376 // if any three boxes are in a line, match them and any others touching them of the same colour
377 bool got_match = false;
378
379 vector<BPMiniGame_JewelJam_Box*>* row;
380 BPMiniGame_JewelJam_Box* box;
381
382 for (int i = Boxes.size() - 1; i >= 0; --i) {
383 row = Boxes[i];
384
385 for (int j = row->size() - 1; j >= 0; --j) {
386 // does this box match?
387 box = (*row)[j];
388
389 // check two boxes to the left
390 if (j > 1) {
391 if ((*row)[j - 1]->Colour == box->Colour && (*row)[j - 2]->Colour == box->Colour) {
392 got_match = true;
393 ThisScore += MatchBox(box, i, j);
394 continue;
395 }
396 }
397
398 if (j > 0 && j < 7) {
399 // try matching one to the left and one to the right
400 if ((*row)[j - 1]->Colour == box->Colour && (*row)[j + 1]->Colour == box->Colour) {
401 got_match = true;
402 ThisScore += MatchBox(box, i, j);
403 continue;
404 }
405 }
406
407 // check two boxes to the right
408 if (j < 6) {
409 if ((*row)[j + 1]->Colour == box->Colour && (*row)[j + 2]->Colour == box->Colour) {
410 got_match = true;
411 ThisScore += MatchBox(box, i, j);
412 continue;
413 }
414 }
415
416
417 // check two boxes above
418 if (i > 1) {
419 if ((*Boxes[i - 1])[j]->Colour == box->Colour && (*Boxes[i - 2])[j]->Colour == box->Colour) {
420 got_match = true;
421 ThisScore += MatchBox(box, i, j);
422 continue;
423 }
424 }
425
426 // try matching one box above and one box below
427 if (i > 0 && i < 7) {
428 if ((*Boxes[i - 1])[j]->Colour == box->Colour && (*Boxes[i + 1])[j]->Colour == box->Colour) {
429 got_match = true;
430 ThisScore += MatchBox(box, i, j);
431 continue;
432 }
433 }
434
435 // check two boxes below
436 if (i < 6) {
437 if ((*Boxes[i + 1])[j]->Colour == box->Colour && (*Boxes[i + 2])[j]->Colour == box->Colour) {
438 got_match = true;
439 ThisScore += MatchBox(box, i, j);
440 continue;
441 }
442 }
443
444 }
445 }
446
447 if (ThisScore != 0) {
448 Score += min(512, (int)round(pow(2.0f, ThisScore)));
449 SetScore();
450 }
451
452 Locked = got_match;
453
454 if (got_match) {
455 TheGame->PlaySound("gem_select");
456 }
457 }
458
MatchBox(BPMiniGame_JewelJam_Box * box,int row,int col)459 int BPMiniGame_JewelJam::MatchBox(BPMiniGame_JewelJam_Box* box, int row, int col) {
460 if (box->MatchTime == -1) {
461 box->MatchTime = TheGame->TickCount;
462
463 int thisscore = 1;
464
465 // now match all other identical boxes around it
466 if (row > 0) {
467 if ((*Boxes[row - 1])[col]->Colour == box->Colour && (*Boxes[row - 1])[col]->MatchTime == -1) {
468 thisscore += MatchBox((*Boxes[row - 1])[col], row - 1, col);
469 }
470 }
471
472 if (row < 7) {
473 if ((*Boxes[row + 1])[col]->Colour == box->Colour && (*Boxes[row + 1])[col]->MatchTime == -1) {
474 thisscore += MatchBox((*Boxes[row + 1])[col], row + 1, col);
475 }
476 }
477
478 if (col > 0) {
479 if ((*Boxes[row])[col - 1]->Colour == box->Colour && (*Boxes[row])[col - 1]->MatchTime == -1) {
480 thisscore += MatchBox((*Boxes[row])[col - 1], row, col - 1);
481 }
482 }
483
484 if (col < 7) {
485 if ((*Boxes[row])[col + 1]->Colour == box->Colour && (*Boxes[row])[col + 1]->MatchTime == -1) {
486 thisscore += MatchBox((*Boxes[row])[col + 1], row, col + 1);
487 }
488 }
489
490 return thisscore;
491 } else {
492 return 0;
493 }
494 }
495
SetMarathon()496 void BPMiniGame_JewelJam::SetMarathon() {
497 MarathonMode = true;
498 GameHelp = "Drag rows of jewels left and right, matching three or more to score points; the more you match, the more you score.";
499 }
500
SetScore()501 void BPMiniGame_JewelJam::SetScore() {
502 if (Score > 0) {
503 ostringstream score;
504 string scorestr = TheGame->SeparateThousands(Score);
505 score << "Score: " << scorestr;
506 TheGame->AllocString(&sfcScoreStr, score.str().c_str(), LARGE, 270, 50, LEFT);
507 } else {
508 TheGame->AllocString(&sfcScoreStr, "Score: 0", LARGE, 270, 50, LEFT);
509 }
510 }
511