diff --git a/demos/TicTacToe/CMakeLists.txt b/demos/TicTacToe/CMakeLists.txt new file mode 100644 index 00000000..630ad65e --- /dev/null +++ b/demos/TicTacToe/CMakeLists.txt @@ -0,0 +1,177 @@ +#/**************************************************************************** + # Copyright (c) 2013-2014 cocos2d-x.org + # Copyright (c) 2015-2017 Chukong Technologies Inc. + # + # http://www.cocos2d-x.org + # + # Permission is hereby granted, free of charge, to any person obtaining a copy + # of this software and associated documentation files (the "Software"), to deal + # in the Software without restriction, including without limitation the rights + # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + # copies of the Software, and to permit persons to whom the Software is + # furnished to do so, subject to the following conditions: + + # The above copyright notice and this permission notice shall be included in + # all copies or substantial portions of the Software. + + # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + # THE SOFTWARE. +# ****************************************************************************/ + +cmake_minimum_required(VERSION 3.6) + +set(APP_NAME tictactoe_demo) + +project(${APP_NAME}) + +# User settings for Firebase samples. +# Path to Firebase SDK. +# Try to read the path to the Firebase C++ SDK from an environment variable. +if (NOT "$ENV{FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(DEFAULT_FIREBASE_CPP_SDK_DIR "$ENV{FIREBASE_CPP_SDK_DIR}") +else() + set(DEFAULT_FIREBASE_CPP_SDK_DIR "firebase_cpp_sdk") +endif() +if ("${FIREBASE_CPP_SDK_DIR}" STREQUAL "") + set(FIREBASE_CPP_SDK_DIR ${DEFAULT_FIREBASE_CPP_SDK_DIR}) +endif() +if(NOT EXISTS ${FIREBASE_CPP_SDK_DIR}) + message(FATAL_ERROR "The Firebase C++ SDK directory does not exist: ${FIREBASE_CPP_SDK_DIR}. See the readme.md for more information") +endif() + +# Build a desktop application. +# Windows runtime mode, either MD or MT depending on whether you are using +# /MD or /MT. For more information see: +# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx +set(MSVC_RUNTIME_MODE MD) + +if(APPLE) + set(ADDITIONAL_LIBS gssapi_krb5 pthread "-framework CoreFoundation" "-framework Foundation" "-framework GSS" "-framework Security") +elseif(MSVC) + set(ADDITIONAL_LIBS advapi32 ws2_32 crypt32 iphlpapi psapi userenv) +else() + set(ADDITIONAL_LIBS pthread) +endif() + +# If a config file is present, copy it into the binary location so that it's +# possible to create the default Firebase app. +set(FOUND_JSON_FILE FALSE) +foreach(config "google-services-desktop.json" "google-services.json") + if (EXISTS ${config}) + add_custom_command( + TARGET ${APP_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${config} $) + set(FOUND_JSON_FILE TRUE) + break() + endif() +endforeach() +if(NOT FOUND_JSON_FILE) + message(WARNING "Failed to find either google-services-desktop.json or google-services.json. See the readme.md for more information.") +endif() +#Target name change to cocos +# Add the Firebase libraries to the target using the function from the SDK. +add_subdirectory(${FIREBASE_CPP_SDK_DIR} bin/ EXCLUDE_FROM_ALL) + +# # Note that firebase_app needs to be last in the list. +set(firebase_libs firebase_database firebase_auth firebase_app) + +if(XCODE) + if(NOT DEFINED CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET) + SET (CMAKE_XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET 8.0) + endif() +endif() + +if(NOT DEFINED BUILD_ENGINE_DONE) # to test install_test into root project + set(COCOS2DX_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cocos2d) + set(CMAKE_MODULE_PATH ${COCOS2DX_ROOT_PATH}/cmake/Modules/) + + include(CocosBuildSet) + add_subdirectory(${COCOS2DX_ROOT_PATH}/cocos ${ENGINE_BINARY_PATH}/cocos/core) +endif() + +# record sources, headers, resources... +set(GAME_HEADER) + +set(GAME_RES_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/Resources") + +if(APPLE OR WINDOWS) + cocos_mark_multi_resources(common_res_files RES_TO "Resources" FOLDERS ${GAME_RES_FOLDER}) +endif() + +# add cross-platforms source files and header files +list(APPEND GAME_SOURCE Classes/AppDelegate.cpp Classes/TicTacToeScene.cpp) +list(APPEND GAME_HEADER Classes/AppDelegate.h Classes/TicTacToeScene.h) + +if(ANDROID) + # change APP_NAME to the share library name for Android, it's value depend on AndroidManifest.xml + set(APP_NAME MyGame) + list(APPEND GAME_SOURCE proj.android/app/jni/hellocpp/main.cpp) +elseif(LINUX) + list(APPEND GAME_SOURCE proj.linux/main.cpp) +elseif(WINDOWS) + list(APPEND GAME_HEADER proj.win32/main.h proj.win32/resource.h) + list(APPEND GAME_SOURCE proj.win32/main.cpp proj.win32/game.rc ${common_res_files}) +elseif(APPLE) + if(IOS) + list(APPEND GAME_HEADER proj.ios_mac/ios/AppController.h + proj.ios_mac/ios/RootViewController.h) + set(APP_UI_RES proj.ios_mac/ios/LaunchScreen.storyboard + proj.ios_mac/ios/LaunchScreenBackground.png + proj.ios_mac/ios/Images.xcassets) + list(APPEND GAME_SOURCE proj.ios_mac/ios/main.m proj.ios_mac/ios/AppController.mm + proj.ios_mac/ios/RootViewController.mm proj.ios_mac/ios/Prefix.pch ${APP_UI_RES}) + elseif(MACOSX) + set(APP_UI_RES proj.ios_mac/mac/Icon.icns proj.ios_mac/mac/Info.plist) + list(APPEND GAME_SOURCE proj.ios_mac/mac/main.cpp + proj.ios_mac/mac/Prefix.pch ${APP_UI_RES}) + endif() + list(APPEND GAME_SOURCE ${common_res_files}) +endif() + +# mark app complie info and libs info +set(all_code_files ${GAME_HEADER} ${GAME_SOURCE}) +if(NOT ANDROID) + add_executable(${APP_NAME} ${all_code_files}) +else() + add_library(${APP_NAME} SHARED ${all_code_files}) + add_subdirectory(${COCOS2DX_ROOT_PATH}/cocos/platform/android + ${ENGINE_BINARY_PATH}/cocos/platform) + target_link_libraries(${APP_NAME} -Wl,--whole-archive cpp_android_spec + -Wl,--no-whole-archive) +endif() + +target_link_libraries(${APP_NAME} cocos2d "${firebase_libs}" ${ADDITIONAL_LIBS}) +target_include_directories(${APP_NAME} PRIVATE Classes + PRIVATE ${COCOS2DX_ROOT_PATH}/cocos/audio/include/) + +# mark app resources +setup_cocos_app_config(${APP_NAME}) +if(APPLE) + set_target_properties(${APP_NAME} PROPERTIES RESOURCE "${APP_UI_RES}") + if(MACOSX) + set_xcode_property(${APP_NAME} INFOPLIST_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/proj.ios_mac/mac/Info.plist") + elseif(IOS) + set_xcode_property(${APP_NAME} INFOPLIST_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/proj.ios_mac/ios/Info.plist") + set_xcode_property(${APP_NAME} ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon") + endif() + +# For code-signing, set the DEVELOPMENT_TEAM: +#set_xcode_property(${APP_NAME} DEVELOPMENT_TEAM "GRLXXXX2K9") +elseif(WINDOWS) + cocos_copy_target_dll(${APP_NAME}) +endif() + +if(LINUX OR WINDOWS) + cocos_get_resource_path(APP_RES_DIR ${APP_NAME}) + cocos_copy_target_res(${APP_NAME} LINK_TO ${APP_RES_DIR} + FOLDERS ${GAME_RES_FOLDER}) +endif() + diff --git a/demos/TicTacToe/Classes/AppDelegate.cpp b/demos/TicTacToe/Classes/AppDelegate.cpp index 775b5981..33db150b 100644 --- a/demos/TicTacToe/Classes/AppDelegate.cpp +++ b/demos/TicTacToe/Classes/AppDelegate.cpp @@ -1,7 +1,7 @@ #include "AppDelegate.h" +#include "MainMenuScene.h" #include "TicTacToeScene.h" - USING_NS_CC; const float kFrameWidth = 600; @@ -10,7 +10,6 @@ const float kFrameHeight = 600; AppDelegate::AppDelegate() {} AppDelegate::~AppDelegate() {} - bool AppDelegate::applicationDidFinishLaunching() { auto director = Director::getInstance(); auto glview = director->getOpenGLView(); @@ -20,7 +19,7 @@ bool AppDelegate::applicationDidFinishLaunching() { director->setOpenGLView(glview); } - auto scene = TicTacToe::createScene(); + auto scene = MainMenuScene::createScene(); director->runWithScene(scene); return true; diff --git a/demos/TicTacToe/Classes/MainMenuScene.cpp b/demos/TicTacToe/Classes/MainMenuScene.cpp new file mode 100644 index 00000000..c9376469 --- /dev/null +++ b/demos/TicTacToe/Classes/MainMenuScene.cpp @@ -0,0 +1,118 @@ +#include "MainMenuScene.h" + +#include "TicTacToeScene.h" + +USING_NS_CC; + +Scene* MainMenuScene::createScene() { + // Builds a simple scene that uses the bottom left cordinate point as (0,0) + // and can have sprites, labels and layers added onto it. + auto scene = Scene::create(); + auto layer = MainMenuScene::create(); + + scene->addChild(layer); + + return scene; +} + +bool MainMenuScene::init() { + if (!Layer::init()) { + return false; + } + // 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); + // 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 + // scene with a TicTacToe scene. + create_button_touch_listener->onTouchBegan = [](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)) { + Director::getInstance()->replaceScene( + TicTacToe::createScene(std::string())); + } + + return true; + }; + // Attaching the touch listener to the create game button. + Director::getInstance() + ->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); + join_text_field->setDelegate(this); + + // 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(); + + text_field_touch_listener->onTouchBegan = + [join_text_field](cocos2d::Touch* touch, cocos2d::Event* event) -> bool { + auto bounds = event->getCurrentTarget()->getBoundingBox(); + auto point = touch->getLocation(); + if (bounds.containsPoint(point)) { + // 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(); + } + + return true; + }; + + // Attaching the touch listener to the text field. + Director::getInstance() + ->getEventDispatcher() + ->addEventListenerWithSceneGraphPriority(text_field_touch_listener, + 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); + + // 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 + // scene with a TicTacToe scene and pass in join_text_field string. + join_button_touch_listener->onTouchBegan = + [join_text_field](Touch* touch, Event* event) -> bool { + auto bounds = event->getCurrentTarget()->getBoundingBox(); + auto point = touch->getLocation(); + if (bounds.containsPoint(point)) { + // Getting and converting the join_text_field string to a char*. + std::string join_text_field_string = join_text_field->getString(); + + Director::getInstance()->replaceScene( + TicTacToe::createScene(join_text_field_string)); + } + return true; + }; + // Attaching the touch listener to the join button. + Director::getInstance() + ->getEventDispatcher() + ->addEventListenerWithSceneGraphPriority(join_button_touch_listener, + join_button); + // Attaching the create button, join button and join text field to the + // MainMenu scene. + this->addChild(create_button); + this->addChild(join_button); + this->addChild(join_text_field); + + return true; +} diff --git a/demos/TicTacToe/Classes/MainMenuScene.h b/demos/TicTacToe/Classes/MainMenuScene.h new file mode 100644 index 00000000..28a78fb1 --- /dev/null +++ b/demos/TicTacToe/Classes/MainMenuScene.h @@ -0,0 +1,19 @@ +#ifndef TICTACTOE_DEMO_CLASSES_MAINMENU_SCENE_H_ +#define TICTACTOE_DEMO_CLASSES_MAINMENU_SCENE_H_ + +#include "cocos2d.h" +#include "ui/CocosGUI.h" + +class MainMenuScene : public cocos2d::Layer, public cocos2d::TextFieldDelegate { + public: + // Builds a simple scene that uses the bottom left cordinate point as (0,0) + // and can have sprites, labels and nodes added onto it. + static cocos2d::Scene* createScene(); + // Initializes the instance of a Node and returns a boolean based on if it was + // successful in doing so. + virtual bool init(); + // Defines a create type for a specific type, in this case a Layer. + CREATE_FUNC(MainMenuScene); +}; + +#endif // TICTACTOE_DEMO_CLASSES_MAINMENU_SCENE_H_ diff --git a/demos/TicTacToe/Classes/TicTacToeLayer.cpp b/demos/TicTacToe/Classes/TicTacToeLayer.cpp new file mode 100644 index 00000000..a1a5320b --- /dev/null +++ b/demos/TicTacToe/Classes/TicTacToeLayer.cpp @@ -0,0 +1,523 @@ +#include "TicTacToeLayer.h" + +#include "MainMenuScene.h" +#include "TicTacToeScene.h" +#include "firebase/app.h" +#include "firebase/auth.h" +#include "firebase/database.h" +#include "firebase/future.h" +#include "firebase/util.h" + +// Thin OS abstraction layer. +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using firebase::database::DataSnapshot; +using firebase::database::MutableData; +using firebase::database::TransactionResult; +using std::string; + +USING_NS_CC; + +// Player constants. +static const int kEmptyTile = -1; +static const int kPlayerOne = 0; +static const int kPlayerTwo = 1; +static const int kNumberOfPlayers = 2; +// Game board dimensions. +extern const int kTilesX; +extern const int kTilesY; +static const int kNumberOfTiles = kTilesX * kTilesY; +// Screen dimensions. +static const double kScreenWidth = 600; +static const double kScreenHeight = 600; +static const double kTileWidth = (kScreenWidth / kTilesX); +static const double kTileHeight = (kScreenHeight / kTilesY); +// The screen will display the end game text for 2 seconds (120frames/60fps). +static const int kEndGameFramesMax = 120; +// Image file paths. +static const char* kBoardImageFileName = "tic_tac_toe_board.png"; +static std::array kPlayerTokenFileNames = { + "tic_tac_toe_x.png", "tic_tac_toe_o.png"}; + +void LogMessage(const char* format, ...) { + va_list list; + va_start(list, format); + vprintf(format, list); + va_end(list); + printf("\n"); + fflush(stdout); +} + +bool ProcessEvents(int msec) { +#ifdef _WIN32 + Sleep(msec); +#else + usleep(msec * 1000); +#endif // _WIN32 + return false; +} + +// An example of a ValueListener object. This specific version will +// simply log every value it sees, and store them in a list so we can +// confirm that all values were received. +class SampleValueListener : public firebase::database::ValueListener { + public: + void OnValueChanged( + const firebase::database::DataSnapshot& snapshot) override { + LogMessage(" ValueListener.OnValueChanged(%s)", + snapshot.value().AsString().string_value()); + last_seen_value_ = snapshot.value(); + seen_values_.push_back(snapshot.value()); + } + void OnCancelled(const firebase::database::Error& error_code, + const char* error_message) override { + LogMessage("ERROR: SampleValueListener canceled: %d: %s", error_code, + error_message); + } + const firebase::Variant& last_seen_value() { return last_seen_value_; } + bool seen_value(const firebase::Variant& value) { + return std::find(seen_values_.begin(), seen_values_.end(), value) != + seen_values_.end(); + } + size_t num_seen_values() { return seen_values_.size(); } + + private: + firebase::Variant last_seen_value_; + std::vector seen_values_; +}; + +// An example ChildListener class. +class SampleChildListener : public firebase::database::ChildListener { + public: + void OnChildAdded(const firebase::database::DataSnapshot& snapshot, + const char* previous_sibling) override { + LogMessage(" ChildListener.OnChildAdded(%s)", snapshot.key()); + events_.push_back(std::string("added ") + snapshot.key()); + } + void OnChildChanged(const firebase::database::DataSnapshot& snapshot, + const char* previous_sibling) override { + LogMessage(" ChildListener.OnChildChanged(%s)", snapshot.key()); + events_.push_back(std::string("changed ") + snapshot.key()); + } + void OnChildMoved(const firebase::database::DataSnapshot& snapshot, + const char* previous_sibling) override { + LogMessage(" ChildListener.OnChildMoved(%s)", snapshot.key()); + events_.push_back(std::string("moved ") + snapshot.key()); + } + void OnChildRemoved( + const firebase::database::DataSnapshot& snapshot) override { + LogMessage(" ChildListener.OnChildRemoved(%s)", snapshot.key()); + events_.push_back(std::string("removed ") + snapshot.key()); + } + void OnCancelled(const firebase::database::Error& error_code, + const char* error_message) override { + LogMessage("ERROR: SampleChildListener canceled: %d: %s", error_code, + error_message); + } + + // Get the total number of Child events this listener saw. + size_t total_events() { return events_.size(); } + + // Get the number of times this event was seen. + int num_events(const std::string& event) { + int count = 0; + for (int i = 0; i < events_.size(); i++) { + if (events_[i] == event) { + count++; + } + } + return count; + } + + public: + // Vector of strings defining the events we saw, in order. + std::vector events_; +}; + +// A ValueListener that expects a specific value to be set. +class ExpectValueListener : public firebase::database::ValueListener { + public: + explicit ExpectValueListener(firebase::Variant wait_value) + : wait_value_(wait_value.AsString()), got_value_(false) {} + void OnValueChanged( + const firebase::database::DataSnapshot& snapshot) override { + if (snapshot.value().AsString() == wait_value_) { + got_value_ = true; + } else { + LogMessage( + "FAILURE: ExpectValueListener did not receive the expected result."); + } + } + void OnCancelled(const firebase::database::Error& error_code, + const char* error_message) override { + LogMessage("ERROR: ExpectValueListener canceled: %d: %s", error_code, + error_message); + } + + bool got_value() { return got_value_; } + + private: + firebase::Variant wait_value_; + bool got_value_; +}; + +// Wait for a Future to be completed. If the Future returns an error, it will +// be logged. +void WaitForCompletion(const firebase::FutureBase& future, const char* name) { + while (future.status() == firebase::kFutureStatusPending) { + ProcessEvents(100); + } + if (future.status() != firebase::kFutureStatusComplete) { + LogMessage("ERROR: %s returned an invalid result.", name); + } else if (future.error() != 0) { + LogMessage("ERROR: %s returned error %d: %s", name, future.error(), + future.error_message()); + } +} +std::string GenerateGameUuid(std::size_t length) { + const std::string kCharacters = "0123456789abcdefghjkmnpqrstuvwxyz"; + + std::random_device random_device; + std::mt19937 generator(random_device()); + std::uniform_int_distribution<> distribution(0, kCharacters.size() - 1); + + std::string GenerateGameUuid; + + for (std::size_t i = 0; i < length; ++i) { + GenerateGameUuid += kCharacters[distribution(generator)]; + } + + return GenerateGameUuid; +} + +// A function that returns true if any of the row +// is crossed with the same player's move +static bool RowCrossed(int board[][kTilesY]) { + for (int i = 0; i < kTilesY; i++) { + if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && + board[i][0] != kEmptyTile) + return true; + } + return false; +} + +// A function that returns true if any of the column +// is crossed with the same player's move +static bool ColumnCrossed(int board[][kTilesY]) { + for (int i = 0; i < kTilesX; i++) { + if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && + board[0][i] != kEmptyTile) + return (true); + } + return (false); +} + +// A function that returns true if any of the diagonal +// is crossed with the same player's move +static bool DiagonalCrossed(int board[][kTilesY]) { + if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && + board[0][0] != kEmptyTile) + return (true); + + if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && + board[0][2] != kEmptyTile) + return (true); + + return (false); +} + +// A function that returns true if the game is over +// else it returns a false +static bool GameOver(int board[][kTilesY]) { + return (RowCrossed(board) || ColumnCrossed(board) || DiagonalCrossed(board)); +} + +TicTacToeLayer::TicTacToeLayer(string game_uuid) { + join_game_uuid = game_uuid; + current_player_index = kPlayerOne; + LogMessage("Initialized Firebase App."); + auto app = ::firebase::App::Create(); + LogMessage("Initialize Firebase Auth and Firebase Database."); + // Use ModuleInitializer to initialize both Auth and Database, ensuring no + // 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}; + + const firebase::ModuleInitializer::InitializerFn initializers[] = { + [](::firebase::App* app, void* data) { + LogMessage("Attempt to initialize Firebase Auth."); + void** targets = reinterpret_cast(data); + ::firebase::InitResult result; + *reinterpret_cast<::firebase::auth::Auth**>(targets[0]) = + ::firebase::auth::Auth::GetAuth(app, &result); + return result; + }, + [](::firebase::App* app, void* data) { + LogMessage("Attempt to initialize Firebase Database."); + void** targets = reinterpret_cast(data); + ::firebase::InitResult result; + *reinterpret_cast<::firebase::database::Database**>(targets[1]) = + ::firebase::database::Database::GetInstance(app, &result); + return result; + }}; + + initializer.Initialize(app, initialize_targets, initializers, + sizeof(initializers) / sizeof(initializers[0])); + + WaitForCompletion(initializer.InitializeLastResult(), "Initialize"); + + if (initializer.InitializeLastResult().error() != 0) { + LogMessage("Failed to initialize Firebase libraries: %s", + initializer.InitializeLastResult().error_message()); + ProcessEvents(2000); + return; + } + LogMessage("Successfully initialized Firebase Auth and Firebase Database."); + + database->set_persistence_enabled(true); + + // Sign in using Auth before accessing the database. + // The default Database permissions allow anonymous users access. This will + // work as long as your project's Authentication permissions allow anonymous + // sign-in. + { + firebase::Future sign_in_future = + auth->SignInAnonymously(); + WaitForCompletion(sign_in_future, "SignInAnonymously"); + if (sign_in_future.error() == firebase::auth::kAuthErrorNone) { + LogMessage("Auth: Signed in anonymously."); + } else { + LogMessage("ERROR: Could not sign in anonymously. Error %d: %s", + sign_in_future.error(), sign_in_future.error_message()); + LogMessage( + " Ensure your application has the Anonymous sign-in provider " + "enabled in Firebase Console."); + LogMessage( + " Attempting to connect to the database anyway. This may fail " + "depending on the security settings."); + } + } + // Splits on the if depending on if the player created or joined the game. + // Additionally sets the player_index and total_players based on joining or + // creating a game. + if (join_game_uuid.empty()) { + join_game_uuid = GenerateGameUuid(4); + ref = database->GetReference("game_data").Child(join_game_uuid); + firebase::Future future_create_game = + ref.Child("total_players").SetValue(1); + future_current_player_index = + ref.Child("current_player_index").SetValue(kPlayerOne); + 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; + } + + // Creating the board sprite , setting the position to the bottom left of the + // frame (0,0), and finally moving the anchor point from the center of the + // image(default) to the bottom left, Vec2(0.0,0.0). + board_sprite = Sprite::create(kBoardImageFileName); + if (!board_sprite) { + log("kBoardImageFileName: %s file not found.", kBoardImageFileName); + exit(true); + } + board_sprite->setPosition(0, 0); + board_sprite->setAnchorPoint(Vec2(0.0, 0.0)); + + // TODO(grantpostma@): Modify these numbers to be based on the extern window + // size & label size dimensions. + cocos2d::Label* game_uuid_label = + Label::createWithSystemFont(join_game_uuid, "Arial", 30); + game_uuid_label->setPosition(Vec2(40, 20)); + board_sprite->addChild(game_uuid_label, 1); + waiting_label = Label::createWithSystemFont("waiting", "Arial", 30); + waiting_label->setPosition(Vec2(530, 20)); + + board_sprite->addChild(waiting_label, 1); + game_over_label = Label::createWithSystemFont("", "Arial", 80); + game_over_label->setPosition(Vec2(300, 300)); + board_sprite->addChild(game_over_label, 1); + + // total_player_listener and CurrentPlayerIndexListener listener is set up + // to recognise when the desired players have connected & when turns + // alternate + LogMessage("total_player_listener"); + total_player_listener = + std::make_unique(kNumberOfPlayers); + 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("current_player_index") + .AddValueListener(current_player_index_listener.get()); + ref.Child("last_move").AddValueListener(last_move_listener.get()); + + // A 3*3 Tic-Tac-Toe board for playing + for (int i = 0; i < kTilesY; i++) { + for (int j = 0; j < kTilesX; j++) { + board[i][j] = kEmptyTile; + remaining_tiles.insert((i * kTilesX) + j); + }; + } + // Adding a function to determine which tile was selected to the onTouchBegan + // listener. + auto touch_listener = EventListenerTouchOneByOne::create(); + touch_listener->onTouchBegan = [this](Touch* touch, + Event* event) mutable -> bool { + if (!total_player_listener->got_value()) return true; + if (current_player_index_listener->last_seen_value() != player_index) + return true; + + auto bounds = event->getCurrentTarget()->getBoundingBox(); + // Checking to make sure the touch location is within the bounds of the + // board. + if (bounds.containsPoint(touch->getLocation())) { + // Calculates the tile number [0-8] which corresponds to the touch + // location. + int selected_tile = floor(touch->getLocation().x / kTileWidth) + + kTilesX * floor(touch->getLocation().y / kTileHeight); + if (remaining_tiles.find(selected_tile) == remaining_tiles.end()) + return true; + + auto sprite = Sprite::create(kPlayerTokenFileNames[current_player_index]); + if (sprite == NULL) { + log("kPlayerTokenFileNames: %s file not found.", + kPlayerTokenFileNames[current_player_index]); + exit(true); + } + // Calculates and sets the position of the sprite based on the + // move_tile and the constant screen variables. + sprite->setPosition((.5 + selected_tile % kTilesX) * kTileWidth, + (.5 + selected_tile / kTilesY) * kTileHeight); + board_sprite->addChild(sprite); + // Modifying local game state variables to reflect this most recent move + board[selected_tile / kTilesX][selected_tile % kTilesX] = + 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_current_player_index = + ref.Child("current_player_index").SetValue(current_player_index); + WaitForCompletion(fLastMove, "setLastMove"); + WaitForCompletion(future_current_player_index, "setCurrentPlayerIndex"); + awaiting_opponenet_move = true; + waiting_label->setString("waiting"); + if (GameOver(board)) { + // 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. + } + } + return true; + }; + + Director::getInstance() + ->getEventDispatcher() + ->addEventListenerWithSceneGraphPriority(touch_listener, board_sprite); + + this->addChild(board_sprite); + // Schedule the update method for this scene. + this->scheduleUpdate(); +} + +// 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()); + } + } + // Performs the actions of the other player when the + // current_player_index_listener is equal to the player index. + else if (current_player_index_listener->last_seen_value() == player_index && + awaiting_opponenet_move == true) { + int last_move = + last_move_listener->last_seen_value().AsInt64().int64_value(); + // Placing the players move on the board. + board[last_move / kTilesX][last_move % kTilesX] = current_player_index; + // Removing the tile from the tile unordered set. + remaining_tiles.erase(last_move); + auto sprite = Sprite::create(kPlayerTokenFileNames[current_player_index]); + if (sprite == NULL) { + log("kPlayerTokenFileNames: %s file not found.", + kPlayerTokenFileNames[current_player_index]); + exit(true); + } + // Calculates and sets the position of the sprite based on the + // move_tile and the constant screen variables. + sprite->setPosition((.5 + last_move % kTilesX) * kTileWidth, + (.5 + last_move / kTilesY) * kTileHeight); + board_sprite->addChild(sprite); + // Modifying local game state variables to reflect this most recent move. + board[last_move / kTilesX][last_move % kTilesX] = current_player_index; + remaining_tiles.erase(last_move); + 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."); + } else if (remaining_tiles.size() == 0) { + // Set game_over_label to reflect the game ended in a tie. + game_over_label->setString("you tied."); + } + } + // Updates the waiting label to signify it is this players move. + else if (total_player_listener->got_value() && + awaiting_opponenet_move == false) { + waiting_label->setString("your move"); + } +} + +TicTacToeLayer::~TicTacToeLayer() { + // release our sprite and layer so that it gets deallocated + CC_SAFE_RELEASE_NULL(this->board_sprite); + CC_SAFE_RELEASE_NULL(this->game_over_label); + CC_SAFE_RELEASE_NULL(this->waiting_label); +} diff --git a/demos/TicTacToe/Classes/TicTacToeLayer.h b/demos/TicTacToe/Classes/TicTacToeLayer.h new file mode 100644 index 00000000..a25d4451 --- /dev/null +++ b/demos/TicTacToe/Classes/TicTacToeLayer.h @@ -0,0 +1,64 @@ +#ifndef TICTACTOE_DEMO_CLASSES_TICTACTOELAYER_SCENE_H_ +#define TICTACTOE_DEMO_CLASSES_TICTACTOELAYER_SCENE_H_ + +#include + +#include "TicTacToeScene.h" +#include "cocos2d.h" +#include "firebase/app.h" +#include "firebase/auth.h" +#include "firebase/database.h" +#include "firebase/future.h" +#include "firebase/util.h" + +using cocos2d::Director; +using cocos2d::Event; +using cocos2d::Layer; +using cocos2d::LayerColor; +using cocos2d::Point; +using cocos2d::Sprite; +using cocos2d::Touch; +using firebase::database::DataSnapshot; +using firebase::database::MutableData; +using firebase::database::TransactionResult; + +static const int kTilesX = 3; +static const int kTilesY = 3; +class SampleValueListener; +class ExpectValueListener; +class TicTacToeLayer : public Layer { + private: + typedef TicTacToeLayer self; + typedef Layer super; + + public: + TicTacToeLayer(std::string); + ~TicTacToeLayer(); + virtual void TicTacToeLayer::update(float); + // Creating a string for the join game code and initializing the database + // reference. + std::string join_game_uuid; + firebase::database::DatabaseReference ref; + // Creating listeners for database values. + // The database schema has a top level game_uuid object which includes + // last_move, total_players and current_player_index fields. + 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 + Sprite* board_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; + // Creating the board, remaining available tile set and player index + // variables. + int current_player_index; + int player_index; + bool awaiting_opponenet_move; + int board[kTilesX][kTilesY]; + std::unordered_set remaining_tiles; + int end_game_frames = 0; +}; +#endif // TICTACTOE_DEMO_CLASSES_TICTACTOELAYER_SCENE_H_ diff --git a/demos/TicTacToe/Classes/TicTacToeScene.cpp b/demos/TicTacToe/Classes/TicTacToeScene.cpp index 92649a7a..127b25d3 100644 --- a/demos/TicTacToe/Classes/TicTacToeScene.cpp +++ b/demos/TicTacToe/Classes/TicTacToeScene.cpp @@ -1,96 +1,20 @@ #include "TicTacToeScene.h" -#include -#include - +#include "MainMenuScene.h" +#include "TicTacToeLayer.h" #include "cocos2d.h" +using cocos2d::Scene; -USING_NS_CC; - -static const int kTilesX = 3; -static const int kTilesY = 3; -static const int kNumberOfTiles = kTilesX * kTilesY; -static const int kMaxMovesPerPlayer = 1 + kNumberOfTiles / 2; -static const double kScreenWidth = 600; -static const double kScreenHeight = 600; -static const double kTileWidth = (kScreenWidth / kTilesX); -static const double kTileHeight = (kScreenHeight / kTilesY); -static const int kNumberOfPlayers = 2; -static const char* kBoardImageFileName = "tic_tac_toe_board.png"; -std::array kPlayerTokenFileNames = { - "tic_tac_toe_x.png", "tic_tac_toe_o.png"}; - -Scene* TicTacToe::createScene() { +Scene* TicTacToe::createScene(const std::string& game_uuid) { + // Sets the join_game_uuid to the passed in game_uuid. // Builds a simple scene that uses the bottom left cordinate point as (0,0) // and can have sprites, labels and layers added onto it. Scene* scene = Scene::create(); // Builds a layer to be placed onto the scene which has access to TouchEvents. - TicTacToe* tic_tac_toe_layer = TicTacToe::create(); - + // This TicTacToe layer being created is owned by scene. + auto tic_tac_toe_layer = new TicTacToeLayer(game_uuid); scene->addChild(tic_tac_toe_layer); return scene; } - -bool TicTacToe::init() { - if (!Layer::init()) { - return false; - } - int current_player_index = 0; - auto file_names_it = std::begin(kPlayerTokenFileNames); - - // TODO(grantpostma): This should reflect the size that is set in AppDelegate. - // (GetVisableSize) Should modify kTileWidth and kTileHeight based on that - // size. auto kScreenWidth = Director::getInstance()->getWinSize().width; auto - // kScreenHeight = Director::getInstance()->getWinSize().height; - - // Creating the board sprite , setting the position to the bottom left of the - // frame (0,0), and finally moving the anchor point from the center of the - // image(default) to the bottom left, Vec2(0.0,0.0). - Sprite* board_sprite = Sprite::create(kBoardImageFileName); - if (!board_sprite) { - log("kBoardImageFileName: %s file not found.", kBoardImageFileName); - exit(true); - } - board_sprite->setPosition(0, 0); - board_sprite->setAnchorPoint(Vec2(0.0, 0.0)); - - // Adding a function to determine which tile was selected to the onTouchBegan - // listener. - auto touch_listener = EventListenerTouchOneByOne::create(); - touch_listener->onTouchBegan = [board_sprite, current_player_index]( - Touch* touch, - Event* event) mutable -> bool { - auto bounds = event->getCurrentTarget()->getBoundingBox(); - - if (bounds.containsPoint(touch->getLocation())) { - // Calculates the tile number [0-8] which corresponds to the touch - // location. - int selected_tile = floor(touch->getLocation().x / kTileWidth) + - kTilesX * floor(touch->getLocation().y / kTileHeight); - - auto sprite = Sprite::create(kPlayerTokenFileNames[current_player_index]); - if (sprite == NULL) { - log("kPlayerTokenFileNames: %s file not found.", - kPlayerTokenFileNames[current_player_index]); - exit(true); - } - // Calculate and set the position of the sprite based on the - // move_tile and the constant screen variables. - sprite->setPosition((.5 + selected_tile % kTilesX) * kTileWidth, - (.5 + selected_tile / kTilesY) * kTileHeight); - board_sprite->addChild(sprite); - current_player_index = (current_player_index + 1) % kNumberOfPlayers; - } - return true; - }; - - Director::getInstance() - ->getEventDispatcher() - ->addEventListenerWithSceneGraphPriority(touch_listener, board_sprite); - - this->addChild(board_sprite); - - return true; -} diff --git a/demos/TicTacToe/Classes/TicTacToeScene.h b/demos/TicTacToe/Classes/TicTacToeScene.h index ba304520..ffdc8651 100644 --- a/demos/TicTacToe/Classes/TicTacToeScene.h +++ b/demos/TicTacToe/Classes/TicTacToeScene.h @@ -1,15 +1,13 @@ #ifndef TICTACTOE_DEMO_CLASSES_TICTACTOE_SCENE_H_ #define TICTACTOE_DEMO_CLASSES_TICTACTOE_SCENE_H_ + #include "cocos2d.h" class TicTacToe : public cocos2d::Layer { public: // Builds a simple scene that uses the bottom left cordinate point as (0,0) // and can have sprites, labels and nodes added onto it. - static cocos2d::Scene* createScene(); - // Initializes the instance of a Node and returns a boolean based on if it was - // successful in doing so. - bool init() override; + static cocos2d::Scene* createScene(const std::string&); // Defines a create type for a specific type, in this case a Layer. CREATE_FUNC(TicTacToe); }; diff --git a/demos/TicTacToe/Resources/fonts/Marker Felt.ttf b/demos/TicTacToe/Resources/fonts/Marker Felt.ttf deleted file mode 100644 index 3752ef31..00000000 Binary files a/demos/TicTacToe/Resources/fonts/Marker Felt.ttf and /dev/null differ diff --git a/demos/TicTacToe/Resources/fonts/arial.ttf b/demos/TicTacToe/Resources/fonts/arial.ttf deleted file mode 100644 index abc899cd..00000000 Binary files a/demos/TicTacToe/Resources/fonts/arial.ttf and /dev/null differ diff --git a/demos/TicTacToe/Resources/res/.gitkeep b/demos/TicTacToe/Resources/res/.gitkeep deleted file mode 100644 index e69de29b..00000000