@@ -33,6 +33,17 @@ static const int kEmptyTile = -1;
33
33
static const int kPlayerOne = 0 ;
34
34
static const int kPlayerTwo = 1 ;
35
35
static const int kNumberOfPlayers = 2 ;
36
+
37
+ // End game outcomes.
38
+ static const enum kGameOutcome {
39
+ kGameWon = 0 ,
40
+ kGameLost ,
41
+ kGameTied ,
42
+ kGameDisbanded
43
+ };
44
+ static const std::array<string, 4 > kGameOverStrings = {
45
+ " you won!" , " you lost." , " you tied." , " user left." };
46
+
36
47
// Game board dimensions.
37
48
extern const int kTilesX ;
38
49
extern const int kTilesY ;
@@ -46,6 +57,7 @@ static const double kTileHeight = (kScreenHeight / kTilesY);
46
57
static const int kEndGameFramesMax = 120 ;
47
58
// Image file paths.
48
59
static const char * kBoardImageFileName = " tic_tac_toe_board.png" ;
60
+ static const char * kLeaveButtonFileName = " leave_button.png" ;
49
61
static std::array<const char *, kNumberOfPlayers > kPlayerTokenFileNames = {
50
62
" tic_tac_toe_x.png" , " tic_tac_toe_o.png" };
51
63
@@ -245,19 +257,14 @@ static bool GameOver(int board[][kTilesY]) {
245
257
TicTacToeLayer::TicTacToeLayer (string game_uuid) {
246
258
join_game_uuid = game_uuid;
247
259
current_player_index = kPlayerOne ;
260
+ game_outcome = kGameWon ;
248
261
LogMessage (" Initialized Firebase App." );
249
262
auto app = ::firebase::App::Create ();
250
263
LogMessage (" Initialize Firebase Auth and Firebase Database." );
251
264
// Use ModuleInitializer to initialize both Auth and Database, ensuring no
252
265
// dependencies are missing.
253
266
firebase::ModuleInitializer initializer;
254
267
255
- // / Firebase Auth, used for logging into Firebase.
256
- firebase::auth::Auth* auth;
257
-
258
- // / Firebase Realtime Database, the entry point to all database operations.
259
- firebase::database::Database* database;
260
-
261
268
database = nullptr ;
262
269
auth = nullptr ;
263
270
void * initialize_targets[] = {&auth, &database};
@@ -326,32 +333,46 @@ TicTacToeLayer::TicTacToeLayer(string game_uuid) {
326
333
ref.Child (" total_players" ).SetValue (1 );
327
334
future_current_player_index =
328
335
ref.Child (" current_player_index" ).SetValue (kPlayerOne );
336
+ future_game_over = ref.Child (" game_over" ).SetValue (false );
337
+ WaitForCompletion (future_game_over, " setGameOver" );
329
338
WaitForCompletion (future_current_player_index, " setCurrentPlayerIndex" );
330
339
WaitForCompletion (future_create_game, " createGame" );
331
340
player_index = kPlayerOne ;
332
341
awaiting_opponenet_move = false ;
333
342
} else {
334
- ref = database->GetReference (" game_data" ).Child (join_game_uuid);
335
- auto fIncrementTotalUsers = ref.RunTransaction ([](MutableData* data) {
336
- auto total_players = data->Child (" total_players" ).value ();
337
- // Completing the transaction based on the returned mutable data value.
338
- if (total_players.is_null ()) {
339
- // Must return this if the transaction was unsuccessful.
340
- return TransactionResult::kTransactionResultAbort ;
341
- }
342
- int new_total_players = total_players.int64_value () + 1 ;
343
- if (new_total_players > kNumberOfPlayers ) {
344
- // Must return this if the transaction was unsuccessful.
345
- return TransactionResult::kTransactionResultAbort ;
346
- }
347
- data->Child (" total_players" ).set_value (new_total_players);
348
- // Must call this if the transaction was successful.
349
- return TransactionResult::kTransactionResultSuccess ;
350
- });
351
- WaitForCompletion (fIncrementTotalUsers , " JoinGameTransaction" );
352
-
353
- player_index = kPlayerTwo ;
354
- awaiting_opponenet_move = true ;
343
+ // Checks whether the join_uuid map exists. If it does not it set the
344
+ // initialization to failed.
345
+ auto future_game_uuid =
346
+ database->GetReference (" game_data" ).Child (join_game_uuid).GetValue ();
347
+ WaitForCompletion (future_game_uuid, " GetGameDataMap" );
348
+ auto game_uuid_snapshot = future_game_uuid.result ();
349
+ if (!game_uuid_snapshot->value ().is_map ()) {
350
+ initialization_failed = true ;
351
+ } else {
352
+ ref = database->GetReference (" game_data" ).Child (join_game_uuid);
353
+ auto future_increment_total_users =
354
+ ref.RunTransaction ([](MutableData* data) {
355
+ auto total_players = data->Child (" total_players" ).value ();
356
+ // Completing the transaction based on the returned mutable data
357
+ // value.
358
+ if (total_players.is_null ()) {
359
+ // Must return this if the transaction was unsuccessful.
360
+ return TransactionResult::kTransactionResultAbort ;
361
+ }
362
+ int new_total_players = total_players.int64_value () + 1 ;
363
+ if (new_total_players > kNumberOfPlayers ) {
364
+ // Must return this if the transaction was unsuccessful.
365
+ return TransactionResult::kTransactionResultAbort ;
366
+ }
367
+ data->Child (" total_players" ).set_value (new_total_players);
368
+ // Must call this if the transaction was successful.
369
+ return TransactionResult::kTransactionResultSuccess ;
370
+ });
371
+ WaitForCompletion (future_increment_total_users, " JoinGameTransaction" );
372
+
373
+ player_index = kPlayerTwo ;
374
+ awaiting_opponenet_move = true ;
375
+ }
355
376
}
356
377
357
378
// Creating the board sprite , setting the position to the bottom left of the
@@ -365,6 +386,48 @@ TicTacToeLayer::TicTacToeLayer(string game_uuid) {
365
386
board_sprite->setPosition (0 , 0 );
366
387
board_sprite->setAnchorPoint (Vec2 (0.0 , 0.0 ));
367
388
389
+ leave_button_sprite = Sprite::create (kLeaveButtonFileName );
390
+ if (!leave_button_sprite) {
391
+ log (" kLeaveButtonSprite: %s file not found." , kLeaveButtonFileName );
392
+ exit (true );
393
+ }
394
+ leave_button_sprite->setPosition (450 , 585 );
395
+ leave_button_sprite->setAnchorPoint (Vec2 (0.0 , 0.0 ));
396
+ leave_button_sprite->setScale (.35 );
397
+
398
+ // Create a button listener to handle the touch event.
399
+ auto leave_button_sprite_touch_listener =
400
+ EventListenerTouchOneByOne::create ();
401
+ // Setting the onTouchBegan event up to a lambda will swap scenes and modify
402
+ // total_players
403
+ leave_button_sprite_touch_listener->onTouchBegan =
404
+ [this ](Touch* touch, Event* event) -> bool {
405
+ auto bounds = event->getCurrentTarget ()->getBoundingBox ();
406
+ auto point = touch->getLocation ();
407
+ // Replaces the scene with a new TicTacToe scene if the touched point is
408
+ // within the bounds of the button.
409
+ if (bounds.containsPoint (point)) {
410
+ // Update the game_outcome to reflect if the you rage quit or left
411
+ // pre-match.
412
+ if (remaining_tiles.size () == kNumberOfTiles ) {
413
+ game_outcome = kGameDisbanded ;
414
+ } else {
415
+ game_outcome = kGameLost ;
416
+ }
417
+
418
+ WaitForCompletion (ref.Child (" game_over" ).SetValue (true ), " setGameOver" );
419
+ }
420
+
421
+ return true ;
422
+ };
423
+ // Attaching the touch listener to the create game button.
424
+ Director::getInstance ()
425
+ ->getEventDispatcher ()
426
+ ->addEventListenerWithSceneGraphPriority (
427
+ leave_button_sprite_touch_listener, leave_button_sprite);
428
+
429
+ board_sprite->addChild (leave_button_sprite, 1 );
430
+
368
431
// TODO(grantpostma@): Modify these numbers to be based on the extern window
369
432
// size & label size dimensions.
370
433
cocos2d::Label* game_uuid_label =
@@ -385,10 +448,13 @@ TicTacToeLayer::TicTacToeLayer(string game_uuid) {
385
448
LogMessage (" total_player_listener" );
386
449
total_player_listener =
387
450
std::make_unique<ExpectValueListener>(kNumberOfPlayers );
451
+ game_over_listener = std::make_unique<ExpectValueListener>(true );
452
+
388
453
current_player_index_listener = std::make_unique<SampleValueListener>();
389
454
last_move_listener = std::make_unique<SampleValueListener>();
390
455
LogMessage (" %i" , total_player_listener->got_value ());
391
456
ref.Child (" total_players" ).AddValueListener (total_player_listener.get ());
457
+ ref.Child (" game_over" ).AddValueListener (game_over_listener.get ());
392
458
ref.Child (" current_player_index" )
393
459
.AddValueListener (current_player_index_listener.get ());
394
460
ref.Child (" last_move" ).AddValueListener (last_move_listener.get ());
@@ -436,20 +502,20 @@ TicTacToeLayer::TicTacToeLayer(string game_uuid) {
436
502
current_player_index;
437
503
remaining_tiles.erase (selected_tile);
438
504
current_player_index = (current_player_index + 1 ) % kNumberOfPlayers ;
439
- fLastMove = ref.Child (" last_move" ).SetValue (selected_tile);
505
+ future_last_move = ref.Child (" last_move" ).SetValue (selected_tile);
440
506
future_current_player_index =
441
507
ref.Child (" current_player_index" ).SetValue (current_player_index);
442
- WaitForCompletion (fLastMove , " setLastMove" );
508
+ WaitForCompletion (future_last_move , " setLastMove" );
443
509
WaitForCompletion (future_current_player_index, " setCurrentPlayerIndex" );
444
510
awaiting_opponenet_move = true ;
445
511
waiting_label->setString (" waiting" );
446
512
if (GameOver (board)) {
447
513
// Set game_over_label to reflect the use won.
448
514
game_over_label->setString (" you won!" );
449
515
} else if (remaining_tiles.size () == 0 ) {
450
- // Set game_over_label to reflect the game ended in a tie .
451
- game_over_label-> setString ( " you tied. " );
452
- // Changing back to the main menu scene.
516
+ // Update game_outcome to reflect the user tied .
517
+ WaitForCompletion (ref. Child ( " game_over " ). SetValue ( true ), " setGameOver " );
518
+ game_outcome = kGameTied ;
453
519
}
454
520
}
455
521
return true ;
@@ -466,13 +532,9 @@ TicTacToeLayer::TicTacToeLayer(string game_uuid) {
466
532
467
533
// Called automatically every frame. The update is scheduled in constructor.
468
534
void TicTacToeLayer::update (float /* delta*/ ) {
469
- // Shows the end game label for kEndGameFramesMax to show the result of the
470
- // game.
471
- if (game_over_label->getString ().empty () == false ) {
472
- end_game_frames++;
473
- if (end_game_frames > kEndGameFramesMax ) {
474
- Director::getInstance ()->replaceScene (MainMenuScene::createScene ());
475
- }
535
+ // Replacing the scene with MainMenuScene if the initialization fails.
536
+ if (initialization_failed == true ) {
537
+ Director::getInstance ()->replaceScene (MainMenuScene::createScene ());
476
538
}
477
539
// Performs the actions of the other player when the
478
540
// current_player_index_listener is equal to the player index.
@@ -501,11 +563,29 @@ void TicTacToeLayer::update(float /*delta*/) {
501
563
awaiting_opponenet_move = false ;
502
564
current_player_index = player_index;
503
565
if (GameOver (board)) {
504
- // Set game_over_label to reflect the use lost.
505
- game_over_label-> setString ( " you lost. " ) ;
566
+ // Set game_outcome to reflect the use lost.
567
+ game_outcome = kGameLost ;
506
568
} else if (remaining_tiles.size () == 0 ) {
507
- // Set game_over_label to reflect the game ended in a tie.
508
- game_over_label->setString (" you tied." );
569
+ // Set game_outcome to reflect the game ended in a tie.
570
+ game_outcome = kGameTied ;
571
+ }
572
+ }
573
+ // Shows the end game label for kEndGameFramesMax to show the result of the
574
+ // game.
575
+ else if (game_over_listener->got_value ()) {
576
+ if (game_outcome == kGameDisbanded &&
577
+ remaining_tiles.size () != kNumberOfTiles ) {
578
+ game_outcome = kGameWon ;
579
+ }
580
+ game_over_label->setString (kGameOverStrings [game_outcome]);
581
+ end_game_frames++;
582
+ if (end_game_frames > kEndGameFramesMax ) {
583
+ // TODO(grantpostma): Update authenticated users record.
584
+ WaitForCompletion (database->GetReference (" game_data" )
585
+ .Child (join_game_uuid)
586
+ .RemoveValue (),
587
+ " removeGameUuid" );
588
+ Director::getInstance ()->replaceScene (MainMenuScene::createScene ());
509
589
}
510
590
}
511
591
// Updates the waiting label to signify it is this players move.
0 commit comments