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