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