diff --git a/demos/TicTacToe/Classes/AppDelegate.cpp b/demos/TicTacToe/Classes/AppDelegate.cpp index 33db150b..15f1ce66 100644 --- a/demos/TicTacToe/Classes/AppDelegate.cpp +++ b/demos/TicTacToe/Classes/AppDelegate.cpp @@ -4,8 +4,10 @@ #include "TicTacToeScene.h" USING_NS_CC; +// Set based on the image width. const float kFrameWidth = 600; -const float kFrameHeight = 600; +// Set based on the image height plus 40 for windows bar. +const float kFrameHeight = 640; AppDelegate::AppDelegate() {} diff --git a/demos/TicTacToe/Classes/MainMenuScene.cpp b/demos/TicTacToe/Classes/MainMenuScene.cpp index c9376469..297f3471 100644 --- a/demos/TicTacToe/Classes/MainMenuScene.cpp +++ b/demos/TicTacToe/Classes/MainMenuScene.cpp @@ -2,6 +2,10 @@ #include "TicTacToeScene.h" +static const char* kCreateGameImage = "create_game.png"; +static const char* kTextFieldBorderImage = "text_field_border.png"; +static const char* kJoinButtonImage = "join_game.png"; + USING_NS_CC; Scene* MainMenuScene::createScene() { @@ -9,7 +13,6 @@ Scene* MainMenuScene::createScene() { // and can have sprites, labels and layers added onto it. auto scene = Scene::create(); auto layer = MainMenuScene::create(); - scene->addChild(layer); return scene; @@ -21,8 +24,9 @@ bool MainMenuScene::init() { } // Creates a sprite for the create button and sets its position to the center // of the screen. TODO(grantpostma): Dynamically choose the location. - auto create_button = Sprite::create("create_game.png"); - create_button->setPosition(300, 350); + auto create_button = Sprite::create(kCreateGameImage); + create_button->setPosition(25, 200); + create_button->setAnchorPoint(Vec2(0, 0)); // Create a button listener to handle the touch event. auto create_button_touch_listener = EventListenerTouchOneByOne::create(); // Setting the onTouchBegan event up to a lambda tha will replace the MainMenu @@ -45,17 +49,21 @@ bool MainMenuScene::init() { ->getEventDispatcher() ->addEventListenerWithSceneGraphPriority(create_button_touch_listener, create_button); - // Creating, setting the position and assigning a placeholder to the text // field for entering the join game uuid. TextFieldTTF* join_text_field = cocos2d::TextFieldTTF::textFieldWithPlaceHolder( - "Join Game url", cocos2d::Size(400, 200), TextHAlignment::RIGHT, - "Arial", 42.0); - join_text_field->setPosition(100, 100); - join_text_field->setColorSpaceHolder(Color3B::GRAY); + "code", cocos2d::Size(200, 100), TextHAlignment::LEFT, "Arial", 55.0); + join_text_field->setPosition(420, 45); + join_text_field->setAnchorPoint(Vec2(0, 0)); + join_text_field->setColorSpaceHolder(Color3B::WHITE); join_text_field->setDelegate(this); + auto text_field_border = Sprite::create(kTextFieldBorderImage); + text_field_border->setPosition(390, 50); + text_field_border->setAnchorPoint(Vec2(0, 0)); + text_field_border->setScale(.53f); + this->addChild(text_field_border, 0); // Create a touch listener to handle the touch event. TODO(grantpostma): add a // focus bar when selecting inside the text field's bounding box. auto text_field_touch_listener = EventListenerTouchOneByOne::create(); @@ -68,8 +76,13 @@ bool MainMenuScene::init() { // Show the on screen keyboard and places character inputs into the text // field. auto str = join_text_field->getString(); - auto textField = dynamic_cast(event->getCurrentTarget()); - textField->attachWithIME(); + auto text_field = dynamic_cast(event->getCurrentTarget()); + text_field->setCursorEnabled(true); + text_field->attachWithIME(); + } else { + auto text_field = dynamic_cast(event->getCurrentTarget()); + text_field->setCursorEnabled(false); + text_field->detachWithIME(); } return true; @@ -82,10 +95,12 @@ bool MainMenuScene::init() { join_text_field); // Creates a sprite for the join button and sets its position to the center - // of the screen. TODO(grantpostma): Dynamically choose the location. - auto join_button = Sprite::create("join_game.png"); - join_button->setPosition(450, 100); - + // of the screen. TODO(grantpostma): Dynamically choose the location and set + // size(). + auto join_button = Sprite::create(kJoinButtonImage); + join_button->setPosition(25, 50); + join_button->setAnchorPoint(Vec2(0, 0)); + join_button->setScale(1.3f); // Create a button listener to handle the touch event. auto join_button_touch_listener = EventListenerTouchOneByOne::create(); // Setting the onTouchBegan event up to a lambda tha will replace the MainMenu @@ -95,11 +110,14 @@ bool MainMenuScene::init() { auto bounds = event->getCurrentTarget()->getBoundingBox(); auto point = touch->getLocation(); if (bounds.containsPoint(point)) { - // Getting and converting the join_text_field string to a char*. + // Getting the string from join_text_field. std::string join_text_field_string = join_text_field->getString(); - - Director::getInstance()->replaceScene( - TicTacToe::createScene(join_text_field_string)); + if (join_text_field_string.length() == 4) { + Director::getInstance()->replaceScene( + TicTacToe::createScene(join_text_field_string)); + } else { + join_text_field->setString(""); + } } return true; }; @@ -112,7 +130,7 @@ bool MainMenuScene::init() { // MainMenu scene. this->addChild(create_button); this->addChild(join_button); - this->addChild(join_text_field); + this->addChild(join_text_field, 1); return true; } diff --git a/demos/TicTacToe/Classes/TicTacToeLayer.cpp b/demos/TicTacToe/Classes/TicTacToeLayer.cpp index a1a5320b..03334b6e 100644 --- a/demos/TicTacToe/Classes/TicTacToeLayer.cpp +++ b/demos/TicTacToe/Classes/TicTacToeLayer.cpp @@ -33,6 +33,17 @@ static const int kEmptyTile = -1; static const int kPlayerOne = 0; static const int kPlayerTwo = 1; static const int kNumberOfPlayers = 2; + +// End game outcomes. +static const enum kGameOutcome { + kGameWon = 0, + kGameLost, + kGameTied, + kGameDisbanded +}; +static const std::array kGameOverStrings = { + "you won!", "you lost.", "you tied.", "user left."}; + // Game board dimensions. extern const int kTilesX; extern const int kTilesY; @@ -46,6 +57,7 @@ static const double kTileHeight = (kScreenHeight / kTilesY); static const int kEndGameFramesMax = 120; // Image file paths. static const char* kBoardImageFileName = "tic_tac_toe_board.png"; +static const char* kLeaveButtonFileName = "leave_button.png"; static std::array kPlayerTokenFileNames = { "tic_tac_toe_x.png", "tic_tac_toe_o.png"}; @@ -245,6 +257,7 @@ static bool GameOver(int board[][kTilesY]) { TicTacToeLayer::TicTacToeLayer(string game_uuid) { join_game_uuid = game_uuid; current_player_index = kPlayerOne; + game_outcome = kGameWon; LogMessage("Initialized Firebase App."); auto app = ::firebase::App::Create(); LogMessage("Initialize Firebase Auth and Firebase Database."); @@ -252,12 +265,6 @@ TicTacToeLayer::TicTacToeLayer(string game_uuid) { // dependencies are missing. firebase::ModuleInitializer initializer; - /// Firebase Auth, used for logging into Firebase. - firebase::auth::Auth* auth; - - /// Firebase Realtime Database, the entry point to all database operations. - firebase::database::Database* database; - database = nullptr; auth = nullptr; void* initialize_targets[] = {&auth, &database}; @@ -326,32 +333,46 @@ TicTacToeLayer::TicTacToeLayer(string game_uuid) { ref.Child("total_players").SetValue(1); future_current_player_index = ref.Child("current_player_index").SetValue(kPlayerOne); + future_game_over = ref.Child("game_over").SetValue(false); + WaitForCompletion(future_game_over, "setGameOver"); WaitForCompletion(future_current_player_index, "setCurrentPlayerIndex"); WaitForCompletion(future_create_game, "createGame"); player_index = kPlayerOne; awaiting_opponenet_move = false; } else { - ref = database->GetReference("game_data").Child(join_game_uuid); - auto fIncrementTotalUsers = ref.RunTransaction([](MutableData* data) { - auto total_players = data->Child("total_players").value(); - // Completing the transaction based on the returned mutable data value. - if (total_players.is_null()) { - // Must return this if the transaction was unsuccessful. - return TransactionResult::kTransactionResultAbort; - } - int new_total_players = total_players.int64_value() + 1; - if (new_total_players > kNumberOfPlayers) { - // Must return this if the transaction was unsuccessful. - return TransactionResult::kTransactionResultAbort; - } - data->Child("total_players").set_value(new_total_players); - // Must call this if the transaction was successful. - return TransactionResult::kTransactionResultSuccess; - }); - WaitForCompletion(fIncrementTotalUsers, "JoinGameTransaction"); - - player_index = kPlayerTwo; - awaiting_opponenet_move = true; + // Checks whether the join_uuid map exists. If it does not it set the + // initialization to failed. + auto future_game_uuid = + database->GetReference("game_data").Child(join_game_uuid).GetValue(); + WaitForCompletion(future_game_uuid, "GetGameDataMap"); + auto game_uuid_snapshot = future_game_uuid.result(); + if (!game_uuid_snapshot->value().is_map()) { + initialization_failed = true; + } else { + ref = database->GetReference("game_data").Child(join_game_uuid); + auto future_increment_total_users = + ref.RunTransaction([](MutableData* data) { + auto total_players = data->Child("total_players").value(); + // Completing the transaction based on the returned mutable data + // value. + if (total_players.is_null()) { + // Must return this if the transaction was unsuccessful. + return TransactionResult::kTransactionResultAbort; + } + int new_total_players = total_players.int64_value() + 1; + if (new_total_players > kNumberOfPlayers) { + // Must return this if the transaction was unsuccessful. + return TransactionResult::kTransactionResultAbort; + } + data->Child("total_players").set_value(new_total_players); + // Must call this if the transaction was successful. + return TransactionResult::kTransactionResultSuccess; + }); + WaitForCompletion(future_increment_total_users, "JoinGameTransaction"); + + player_index = kPlayerTwo; + awaiting_opponenet_move = true; + } } // Creating the board sprite , setting the position to the bottom left of the @@ -365,6 +386,48 @@ TicTacToeLayer::TicTacToeLayer(string game_uuid) { board_sprite->setPosition(0, 0); board_sprite->setAnchorPoint(Vec2(0.0, 0.0)); + leave_button_sprite = Sprite::create(kLeaveButtonFileName); + if (!leave_button_sprite) { + log("kLeaveButtonSprite: %s file not found.", kLeaveButtonFileName); + exit(true); + } + leave_button_sprite->setPosition(450, 585); + leave_button_sprite->setAnchorPoint(Vec2(0.0, 0.0)); + leave_button_sprite->setScale(.35); + + // Create a button listener to handle the touch event. + auto leave_button_sprite_touch_listener = + EventListenerTouchOneByOne::create(); + // Setting the onTouchBegan event up to a lambda will swap scenes and modify + // total_players + leave_button_sprite_touch_listener->onTouchBegan = + [this](Touch* touch, Event* event) -> bool { + auto bounds = event->getCurrentTarget()->getBoundingBox(); + auto point = touch->getLocation(); + // Replaces the scene with a new TicTacToe scene if the touched point is + // within the bounds of the button. + if (bounds.containsPoint(point)) { + // Update the game_outcome to reflect if the you rage quit or left + // pre-match. + if (remaining_tiles.size() == kNumberOfTiles) { + game_outcome = kGameDisbanded; + } else { + game_outcome = kGameLost; + } + + WaitForCompletion(ref.Child("game_over").SetValue(true), "setGameOver"); + } + + return true; + }; + // Attaching the touch listener to the create game button. + Director::getInstance() + ->getEventDispatcher() + ->addEventListenerWithSceneGraphPriority( + leave_button_sprite_touch_listener, leave_button_sprite); + + board_sprite->addChild(leave_button_sprite, 1); + // TODO(grantpostma@): Modify these numbers to be based on the extern window // size & label size dimensions. cocos2d::Label* game_uuid_label = @@ -385,10 +448,13 @@ TicTacToeLayer::TicTacToeLayer(string game_uuid) { LogMessage("total_player_listener"); total_player_listener = std::make_unique(kNumberOfPlayers); + game_over_listener = std::make_unique(true); + current_player_index_listener = std::make_unique(); last_move_listener = std::make_unique(); LogMessage("%i", total_player_listener->got_value()); ref.Child("total_players").AddValueListener(total_player_listener.get()); + ref.Child("game_over").AddValueListener(game_over_listener.get()); ref.Child("current_player_index") .AddValueListener(current_player_index_listener.get()); ref.Child("last_move").AddValueListener(last_move_listener.get()); @@ -436,10 +502,10 @@ TicTacToeLayer::TicTacToeLayer(string game_uuid) { current_player_index; remaining_tiles.erase(selected_tile); current_player_index = (current_player_index + 1) % kNumberOfPlayers; - fLastMove = ref.Child("last_move").SetValue(selected_tile); + future_last_move = ref.Child("last_move").SetValue(selected_tile); future_current_player_index = ref.Child("current_player_index").SetValue(current_player_index); - WaitForCompletion(fLastMove, "setLastMove"); + WaitForCompletion(future_last_move, "setLastMove"); WaitForCompletion(future_current_player_index, "setCurrentPlayerIndex"); awaiting_opponenet_move = true; waiting_label->setString("waiting"); @@ -447,9 +513,9 @@ TicTacToeLayer::TicTacToeLayer(string game_uuid) { // Set game_over_label to reflect the use won. game_over_label->setString("you won!"); } else if (remaining_tiles.size() == 0) { - // Set game_over_label to reflect the game ended in a tie. - game_over_label->setString("you tied."); - // Changing back to the main menu scene. + // Update game_outcome to reflect the user tied. + WaitForCompletion(ref.Child("game_over").SetValue(true), "setGameOver"); + game_outcome = kGameTied; } } return true; @@ -466,13 +532,9 @@ TicTacToeLayer::TicTacToeLayer(string game_uuid) { // Called automatically every frame. The update is scheduled in constructor. void TicTacToeLayer::update(float /*delta*/) { - // Shows the end game label for kEndGameFramesMax to show the result of the - // game. - if (game_over_label->getString().empty() == false) { - end_game_frames++; - if (end_game_frames > kEndGameFramesMax) { - Director::getInstance()->replaceScene(MainMenuScene::createScene()); - } + // Replacing the scene with MainMenuScene if the initialization fails. + if (initialization_failed == true) { + Director::getInstance()->replaceScene(MainMenuScene::createScene()); } // Performs the actions of the other player when the // current_player_index_listener is equal to the player index. @@ -501,11 +563,29 @@ void TicTacToeLayer::update(float /*delta*/) { awaiting_opponenet_move = false; current_player_index = player_index; if (GameOver(board)) { - // Set game_over_label to reflect the use lost. - game_over_label->setString("you lost."); + // Set game_outcome to reflect the use lost. + game_outcome = kGameLost; } else if (remaining_tiles.size() == 0) { - // Set game_over_label to reflect the game ended in a tie. - game_over_label->setString("you tied."); + // Set game_outcome to reflect the game ended in a tie. + game_outcome = kGameTied; + } + } + // Shows the end game label for kEndGameFramesMax to show the result of the + // game. + else if (game_over_listener->got_value()) { + if (game_outcome == kGameDisbanded && + remaining_tiles.size() != kNumberOfTiles) { + game_outcome = kGameWon; + } + game_over_label->setString(kGameOverStrings[game_outcome]); + end_game_frames++; + if (end_game_frames > kEndGameFramesMax) { + // TODO(grantpostma): Update authenticated users record. + WaitForCompletion(database->GetReference("game_data") + .Child(join_game_uuid) + .RemoveValue(), + "removeGameUuid"); + Director::getInstance()->replaceScene(MainMenuScene::createScene()); } } // Updates the waiting label to signify it is this players move. diff --git a/demos/TicTacToe/Classes/TicTacToeLayer.h b/demos/TicTacToe/Classes/TicTacToeLayer.h index a25d4451..1fa708b0 100644 --- a/demos/TicTacToe/Classes/TicTacToeLayer.h +++ b/demos/TicTacToe/Classes/TicTacToeLayer.h @@ -18,6 +18,7 @@ using cocos2d::LayerColor; using cocos2d::Point; using cocos2d::Sprite; using cocos2d::Touch; +using firebase::Future; using firebase::database::DataSnapshot; using firebase::database::MutableData; using firebase::database::TransactionResult; @@ -35,9 +36,19 @@ class TicTacToeLayer : public Layer { TicTacToeLayer(std::string); ~TicTacToeLayer(); virtual void TicTacToeLayer::update(float); + // Tracks whether the board was unable to build. + bool initialization_failed = false; + // Tracks the game outcome. + int game_outcome; // Creating a string for the join game code and initializing the database // reference. std::string join_game_uuid; + /// Firebase Auth, used for logging into Firebase. + firebase::auth::Auth* auth; + + /// Firebase Realtime Database, the entry point to all database operations. + firebase::database::Database* database; + firebase::database::DatabaseReference ref; // Creating listeners for database values. // The database schema has a top level game_uuid object which includes @@ -45,13 +56,16 @@ class TicTacToeLayer : public Layer { std::unique_ptr current_player_index_listener; std::unique_ptr last_move_listener; std::unique_ptr total_player_listener; - // Creating lables and a sprite `for the board + std::unique_ptr game_over_listener; + // Creating lables and a sprites. Sprite* board_sprite; + Sprite* leave_button_sprite; cocos2d::Label* game_over_label; cocos2d::Label* waiting_label; // Creating firebase futures for last_move and current_player_index - firebase::Future fLastMove; - firebase::Future future_current_player_index; + Future future_last_move; + Future future_current_player_index; + Future future_game_over; // Creating the board, remaining available tile set and player index // variables. int current_player_index; diff --git a/demos/TicTacToe/Resources/create_game.png b/demos/TicTacToe/Resources/create_game.png new file mode 100644 index 00000000..c7b08bba Binary files /dev/null and b/demos/TicTacToe/Resources/create_game.png differ diff --git a/demos/TicTacToe/Resources/join_game.png b/demos/TicTacToe/Resources/join_game.png new file mode 100644 index 00000000..8d39fcd7 Binary files /dev/null and b/demos/TicTacToe/Resources/join_game.png differ diff --git a/demos/TicTacToe/Resources/leave_button.png b/demos/TicTacToe/Resources/leave_button.png new file mode 100644 index 00000000..1f29fe57 Binary files /dev/null and b/demos/TicTacToe/Resources/leave_button.png differ diff --git a/demos/TicTacToe/Resources/text_field_border.png b/demos/TicTacToe/Resources/text_field_border.png new file mode 100644 index 00000000..cba85f27 Binary files /dev/null and b/demos/TicTacToe/Resources/text_field_border.png differ diff --git a/demos/TicTacToe/Resources/tic_tac_toe_o.png b/demos/TicTacToe/Resources/tic_tac_toe_o.png index 7b37d103..c1d37bce 100644 Binary files a/demos/TicTacToe/Resources/tic_tac_toe_o.png and b/demos/TicTacToe/Resources/tic_tac_toe_o.png differ