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 "perfectpaths.h"
19 #include "Minigame.h"
20 
BPMiniGame_PerfectPaths(BPGame * game)21 BPMiniGame_PerfectPaths::BPMiniGame_PerfectPaths(BPGame* game) : BPMiniGame(game) {
22 	sfcBackground = TheGame->LoadBitmap("perfectpaths", 320, 416);
23 
24 	sfcPerfect = TheGame->LoadBitmap("perfect", 320, 92);
25 	sfcOK = TheGame->LoadBitmap("ok", 320, 92);
26 
27 	CurrentScore = BestScore = TotalDiff = TimeStarted = CurrentLevel = 0;
28 	sfcScore = NULL;
29 	SetScore();
30 
31 	GameTitle = "Perfect Paths";
32 	GameHelp = "Tap squares to make a path from the top white square to the bottom one, using the route with the lowest numbers along the way, then press Go when you're ready to advance. You can't move diagonally!";
33 	GameHelp2 = "Each square has a number on signifying how \"hard\" that square is to move over, and that number is used to calculate the total move difficulty. At the bottom of the screen you'll see the best possible score that you can get, along with your current score.";
34 
35 	MiniGameType = LIVELY;
36 
37 	GameState = WAITING;
38 
39 	LoNumbers.Add(NULL);
40 	LoNumbers.Add(TheGame->LoadBitmap("pp_1_lo", 44, 44));
41 	LoNumbers.Add(NULL);
42 	LoNumbers.Add(TheGame->LoadBitmap("pp_3_lo", 44, 44));
43 	LoNumbers.Add(NULL);
44 	LoNumbers.Add(TheGame->LoadBitmap("pp_5_lo", 44, 44));
45 	LoNumbers.Add(NULL);
46 	LoNumbers.Add(TheGame->LoadBitmap("pp_7_lo", 44, 44));
47 	LoNumbers.Add(NULL);
48 	LoNumbers.Add(TheGame->LoadBitmap("pp_9_lo", 44, 44));
49 
50 	HiNumbers.Add(NULL);
51 	HiNumbers.Add(TheGame->LoadBitmap("pp_1_hi", 44, 44));
52 	HiNumbers.Add(NULL);
53 	HiNumbers.Add(TheGame->LoadBitmap("pp_3_hi", 44, 44));
54 	HiNumbers.Add(NULL);
55 	HiNumbers.Add(TheGame->LoadBitmap("pp_5_hi", 44, 44));
56 	HiNumbers.Add(NULL);
57 	HiNumbers.Add(TheGame->LoadBitmap("pp_7_hi", 44, 44));
58 	HiNumbers.Add(NULL);
59 	HiNumbers.Add(TheGame->LoadBitmap("pp_9_hi", 44, 44));
60 
61 	LevelUp();
62 }
63 
~BPMiniGame_PerfectPaths()64 BPMiniGame_PerfectPaths::~BPMiniGame_PerfectPaths() {
65 	SAFE_DELETE(sfcBackground);
66 	StartPositions.Clear();
67 	EndPositions.Clear();
68 	LoNumbers.Clear();
69 	HiNumbers.Clear();
70 //	SAFE_DELETE(StartPos);
71 //	SAFE_DELETE(EndPos);
72 
73 	SAFE_DELETE(sfcPerfect);
74 	SAFE_DELETE(sfcOK);
75 
76 	Squares.Clear();
77 
78 	SAFE_DELETE(sfcScore);
79 }
80 
Start()81 void BPMiniGame_PerfectPaths::Start() {
82 	TimeStarted = TheGame->TickCount;
83 }
84 
GetWeight()85 int BPMiniGame_PerfectPaths::GetWeight() {
86 	float TimePassed = (TheGame->TickCount - TimeStarted) / 1000.0f;
87 	return MinMax(520 - (TotalDiff * 5) - round(TimePassed));
88 }
89 
Render()90 void BPMiniGame_PerfectPaths::Render() {
91 	TheGame->DrawImage(sfcBackground, 0, 0);
92 
93 	for (int i = 0; i < Squares.Count; ++i) {
94 		BPMiniGame_PerfectPaths_Square* square = Squares[i];
95 
96 		if (square == EndPos || square == StartPos) {
97 			TheGame->FillRectangle((*TheGame->White), square->XPos, square->YPos, 44, 44);
98 		} else {
99 			if (Moves.Contains(square)) {
100 				if (square != StartPos) TheGame->DrawImage(HiNumbers[square->Difficulty], square->XPos, square->YPos);
101 			} else {
102 				TheGame->DrawImage(LoNumbers[square->Difficulty], square->XPos, square->YPos);
103 			}
104 		}
105 	}
106 
107 	TheGame->DrawString(sfcScore, WHITE, 6, 377);
108 
109 	if (GameState == CORRECT) {
110 		switch (LastDiff) {
111 			case 0:
112 				RenderPerfect();
113 				break;
114 
115 			case 1:
116 			case 2:
117 				RenderCorrect();
118 				break;
119 
120 			case 3:
121 			case 4:
122 			case 5:
123 				RenderOK();
124 				break;
125 
126 			default:
127 				RenderWrong();
128 				break;
129 		}
130 	}
131 }
132 
Tick()133 void BPMiniGame_PerfectPaths::Tick() {
134 	if (GameState == CORRECT && LastStateChange + 700 < TheGame->TickCount) {
135 		LevelUp();
136 	}
137 
138 	if (CurrentLevel >= 6) {
139 		Success();
140 	}
141 }
142 
OnMouseUp()143 void BPMiniGame_PerfectPaths::OnMouseUp() {
144 	if (GameState != WAITING) return;
145 
146 	if (TheGame->PointOverRect(TouchEvent.X, TouchEvent.Y, 187, 368, 137, 47)) {
147 		CheckResult();
148 	} else if (TheGame->PointOverRect(TouchEvent.X, TouchEvent.Y, 0, 0, 320, 364)) {
149 		// making a move
150 		for (int i = 0; i < Squares.Count; ++i) {
151 			BPMiniGame_PerfectPaths_Square* square = Squares[i];
152 
153 			if (TheGame->PointOverRect(TouchEvent.X, TouchEvent.Y, square->XPos, square->YPos, 44, 44)) {
154 				if (square == StartPos || square == EndPos) continue;
155 
156 				if (Moves.Contains(square)) {
157 					if (Moves[Moves.Count - 1] == square) {
158 						// they can only remove a square if it was the last one to be added
159 						Moves.Remove(square);
160 						CalculateOurScore();
161 					} else {
162 						MessageBox::Show("You can only remove the last position in your path.", "Oops!");
163 					}
164 				} else {
165 					BPMiniGame_PerfectPaths_Square* last = Moves[Moves.Count - 1];
166 
167 					if (CanMove(square, last)) {
168 						Moves.Add(square);
169 						CalculateOurScore();
170 					} else {
171 						if (Moves.Count == 1) {
172 							MessageBox::Show("You can only move to a square that's horizontally or vertically next to your previous move, starting from the top white square.", "Oops!");
173 						} else {
174 							MessageBox::Show("You can only move to a square that's horizontally or vertically next to your previous move.", "Oops!");
175 						}
176 					}
177 				}
178 
179 				break;
180 			}
181 		}
182 	} else if (TheGame->PointOverRect(TouchEvent.X, TouchEvent.Y, 0, 273, 72, 18)) {
183 		MessageBox::Show("This shows the current number of moves it will take to get from the top to the bottom. The closer this is to the Best score, the more points you get.", GameTitle);
184 	} else if (TheGame->PointOverRect(TouchEvent.X, TouchEvent.Y, 80, 273, 72, 18)) {
185 		MessageBox::Show("This shows the best possible number of moves between the top white square and the bottom white square. If you want to get the highest score, you must match this every time.", GameTitle);
186 	}
187 }
188 
OnMouseMove()189 void BPMiniGame_PerfectPaths::OnMouseMove() {
190 
191 }
192 
OnMouseDown()193 void BPMiniGame_PerfectPaths::OnMouseDown() {
194 
195 }
196 
CalculateOurScore()197 void BPMiniGame_PerfectPaths::CalculateOurScore() {
198 	CurrentScore = 0;
199 
200 	for (int i = 0; i < Moves.Count; ++i) {
201 		BPMiniGame_PerfectPaths_Square* square = Moves[i];
202 		if (square == StartPos) continue;
203 		CurrentScore += square->Difficulty;
204 	}
205 
206 	SetScore();
207 }
208 
CheckResult()209 void BPMiniGame_PerfectPaths::CheckResult() {
210 	BPMiniGame_PerfectPaths_Square* last = Moves[Moves.Count - 1];
211 
212 	if (CanMove(EndPos, last)) {
213 		LastDiff = abs(BestScore - CurrentScore);
214 
215 		TotalDiff += LastDiff;
216 
217 		GameState = CORRECT;
218 		LastStateChange = TheGame->TickCount;
219 
220 		switch (LastDiff) {
221 			case 0:
222 			case 1:
223 			case 2:
224 			case 3:
225 				TheGame->PlaySound("correct");
226 				break;
227 
228 			default:
229 				TheGame->PlaySound("down");
230 				break;
231 		}
232 	} else {
233 		MessageBox::Show("That doesn't work - your path needs to connect the white square at the top with the white square at the bottom!", "Try again");
234 	}
235 }
236 
LevelUp()237 void BPMiniGame_PerfectPaths::LevelUp() {
238 	++CurrentLevel;
239 	CurrentScore = 0;
240 
241 	if (CurrentLevel >= 6) {
242 		return;
243 	}
244 
245 	Squares.Clear();
246 	Moves.Clear();
247 	MoveSquares.Clear();
248 
249 	int max_difficulty = 7;
250 
251 	switch (CurrentLevel) {
252 		case 1:
253 			max_difficulty = 7;
254 			break;
255 
256 		case 2:
257 			max_difficulty = 8;
258 			break;
259 
260 		case 3:
261 			max_difficulty = 9;
262 			break;
263 
264 		case 4:
265 			max_difficulty = 11;
266 			break;
267 
268 		case 5:
269 			max_difficulty = 12;
270 			break;
271 	}
272 
273 	for (int i = 0; i < 7; ++i) {
274 		for (int j = 0; j < 8; ++j) {
275 			BPMiniGame_PerfectPaths_Square* square = new BPMiniGame_PerfectPaths_Square();
276 			square->Difficulty = TheGame->RandomRange(0, max_difficulty);
277 
278 			switch (square->Difficulty) {
279 				case 0:
280 				case 1:
281 				case 2:
282 					square->Difficulty = 1;
283 					square->DifficultyStr = "1";
284 					square->Col = TheGame->Green;
285 					break;
286 
287 				case 3:
288 				case 4:
289 				case 5:
290 				case 6:
291 					square->Difficulty = 3;
292 					square->DifficultyStr = "3";
293 					square->Col = TheGame->Orange;
294 					break;
295 
296 				case 7:
297 				case 8:
298 				case 9:
299 					square->Difficulty = 5;
300 					square->DifficultyStr = "5";
301 					square->Col = TheGame->Red;
302 					break;
303 
304 				case 10:
305 				case 11:
306 					square->Difficulty = 7;
307 					square->DifficultyStr = "7";
308 					square->Col = TheGame->DarkRed;
309 					break;
310 
311 				case 12:
312 					square->Difficulty = 9;
313 					square->DifficultyStr = "9";
314 					square->Col = TheGame->DarkGrey;
315 					break;
316 			}
317 
318 			square->X = i;
319 			square->Y = j;
320 			square->XPos = 6 + (i * 44);
321 			square->YPos = 6 + (j * 44);
322 			square->Pos = Squares.Count;
323 
324 			Squares.Add(square);
325 		}
326 	}
327 
328 	StartPositions.Clear();
329 	EndPositions.Clear();
330 
331 	if (TheGame->RandomRange(0, 1) == 0) {
332 		StartPositions.Add(8);
333 		StartPositions.Add(16);
334 		StartPositions.Add(24);
335 		EndPositions.Add(39);
336 		EndPositions.Add(47);
337 		EndPositions.Add(55);
338 	} else {
339 		StartPositions.Add(32);
340 		StartPositions.Add(40);
341 		StartPositions.Add(48);
342 		EndPositions.Add(7);
343 		EndPositions.Add(15);
344 		EndPositions.Add(23);
345 	}
346 
347 	StartPositions.Shuffle();
348 	EndPositions.Shuffle();
349 
350 	StartPos = Squares[StartPositions[0]];
351 	EndPos = Squares[EndPositions[0]];
352 
353 	Moves.Add(StartPos);
354 
355 	CalculateBestMove();
356 
357 	SetScore();
358 
359 	GameState = WAITING;
360 	LastStateChange = TheGame->TickCount;
361 }
362 
CalculateBestMove()363 void BPMiniGame_PerfectPaths::CalculateBestMove() {
364 	MoveSquares.Add(StartPos);
365 	StartPos->MoveCalc = 0;
366 
367 	while (MoveSquares.Count != 0) {
368 		BPMiniGame_PerfectPaths_Square* square = MoveSquares[0];
369 		MoveSquares.RemoveAt(0);
370 		FloodFill(square);
371 	}
372 
373 	int probable_best = 999;
374 
375 	// now we need to figure out what the best score was: what were the lowest numbers around the end position?
376 	if (EndPos->X > 0) probable_best = Squares[EndPos->Pos - 8]->MoveCalc;
377 	if (EndPos->X < 6 && Squares[EndPos->Pos + 8]->MoveCalc < probable_best) probable_best = Squares[EndPos->Pos + 8]->MoveCalc;
378 	if (EndPos->Y > 0 && Squares[EndPos->Pos - 1]->MoveCalc < probable_best) probable_best = Squares[EndPos->Pos - 1]->MoveCalc;
379 	if (EndPos->Y < 6 && Squares[EndPos->Pos + 1]->MoveCalc < probable_best) probable_best = Squares[EndPos->Pos + 1]->MoveCalc;
380 
381 	BestScore = probable_best;
382 }
383 
FloodFill(BPMiniGame_PerfectPaths_Square * square)384 void BPMiniGame_PerfectPaths::FloodFill(BPMiniGame_PerfectPaths_Square* square) {
385 	int Plus1 = square->Pos + 1;
386 	int Minus1 = square->Pos - 1;
387 	int Plus10 = square->Pos + 8;
388 	int Minus10 = square->Pos - 8;
389 
390 	if (square->X > 0) {
391 		// check square to the left
392 		if (square->MoveCalc + Squares[Minus10]->Difficulty < Squares[Minus10]->MoveCalc) {
393 			Squares[Minus10]->MoveCalc = square->MoveCalc + Squares[Minus10]->Difficulty;
394 			MoveSquares.Add(Squares[Minus10]);
395 		}
396 	}
397 
398 	if (square->X < 6) {
399 		// check square to the right
400 		if (square->MoveCalc + Squares[Plus10]->Difficulty < Squares[Plus10]->MoveCalc) {
401 			Squares[Plus10]->MoveCalc = square->MoveCalc + Squares[Plus10]->Difficulty;
402 			MoveSquares.Add(Squares[Plus10]);
403 		}
404 	}
405 
406 	if (square->Y > 0) {
407 		// check square above
408 		if (square->MoveCalc + Squares[Minus1]->Difficulty < Squares[Minus1]->MoveCalc) {
409 			Squares[Minus1]->MoveCalc = square->MoveCalc + Squares[Minus1]->Difficulty;
410 			MoveSquares.Add(Squares[Minus1]);
411 		}
412 	}
413 
414 	if (square->Y < 7) {
415 		// check square below
416 		if (square->MoveCalc + Squares[Plus1]->Difficulty < Squares[Plus1]->MoveCalc) {
417 			Squares[Plus1]->MoveCalc = square->MoveCalc + Squares[Plus1]->Difficulty;
418 			MoveSquares.Add(Squares[Plus1]);
419 		}
420 	}
421 }
422 
CanMove(BPMiniGame_PerfectPaths_Square * square1,BPMiniGame_PerfectPaths_Square * square2)423 bool BPMiniGame_PerfectPaths::CanMove(BPMiniGame_PerfectPaths_Square* square1, BPMiniGame_PerfectPaths_Square* square2) {
424 	// return true if this square is adjacent to the last move square
425 
426 	return (abs(square1->X - square2->X) + abs(square1->Y - square2->Y)) == 1;
427 }
428 
SetScore()429 void BPMiniGame_PerfectPaths::SetScore() {
430 	ostringstream score;
431 
432 	score << "Move: " << CurrentScore << "      Best: " << BestScore;
433 
434 	TheGame->AllocString(&sfcScore, score.str().c_str(), NORMAL, 239, 47, LEFT);
435 }
436 
RenderPerfect()437 void BPMiniGame_PerfectPaths::RenderPerfect() {
438 	TheGame->DrawImage(sfcPerfect, 0, 172);
439 }
440 
RenderOK()441 void BPMiniGame_PerfectPaths::RenderOK() {
442 	TheGame->DrawImage(sfcOK, 0, 172);
443 }
444