#include "tictactoe.hpp" // using namespace eosio should really be here rather than in the header void tictactoe::create(const name &challenger, name &host) { require_auth(host); check(challenger != host, "Challenger should not be the same as the host."); // Check if game already exists games existingHostGames(get_self(), host.value); auto itr = existingHostGames.find(challenger.value); check(itr == existingHostGames.end(), "Game already exists."); existingHostGames.emplace(host, [&](auto &g) { g.challenger = challenger; g.host = host; g.turn = host; }); } void tictactoe::restart(const name &challenger, const name &host, const name &by) { check(has_auth(by), "Only " + by.to_string() + "can restart the game."); // Check if game exists games existingHostGames(get_self(), host.value); auto itr = existingHostGames.find(challenger.value); check(itr != existingHostGames.end(), "Game does not exist."); // Check if this game belongs to the action sender check(by == itr->host || by == itr->challenger, "This is not your game."); // Reset game existingHostGames.modify(itr, itr->host, [](auto &g) { g.resetGame(); }); } void tictactoe::close(const name &challenger, const name &host) { check(has_auth(host), "Only the host can close the game."); require_auth(host); // Check if game exists games existingHostGames(get_self(), host.value); auto itr = existingHostGames.find(challenger.value); check(itr != existingHostGames.end(), "Game does not exist."); // Remove game existingHostGames.erase(itr); } bool tictactoe::isEmptyCell(const uint8_t &cell) { return cell == 0; } bool tictactoe::isValidMove(const uint16_t &row, const uint16_t &column, const std::vector &board) { uint32_t movementLocation = row * game::boardWidth + column; bool isValid = movementLocation < board.size() && isEmptyCell(board[movementLocation]); return isValid; } name tictactoe::getWinner(const game ¤tGame) { auto &board = currentGame.board; bool isBoardFull = true; // Use bitwise AND operator to determine the consecutive values of each column, row and diagonal // Since 3 == 0b11, 2 == 0b10, 1 = 0b01, 0 = 0b00 std::vector consecutiveColumn(game::boardWidth, 3); std::vector consecutiveRow(game::boardHeight, 3); uint32_t consecutiveDiagonalBackslash = 3; uint32_t consecutiveDiagonalSlash = 3; for (uint32_t i = 0; i < board.size(); i++) { isBoardFull &= isEmptyCell(board[i]); uint16_t row = uint16_t(i / game::boardWidth); uint16_t column = uint16_t(i % game::boardWidth); // Calculate consecutive row and column value consecutiveRow[column] = consecutiveRow[column] & board[i]; consecutiveColumn[row] = consecutiveColumn[row] & board[i]; // Calculate consecutive diagonal \ value if (row == column) { consecutiveDiagonalBackslash = consecutiveDiagonalBackslash & board[i]; } // Calculate consecutive diagonal / value if (row + column == game::boardWidth - 1) { consecutiveDiagonalSlash = consecutiveDiagonalSlash & board[i]; } } // Inspect the value of all consecutive row, column, and diagonal and determine winner std::vector aggregate = {consecutiveDiagonalBackslash, consecutiveDiagonalSlash}; aggregate.insert(aggregate.end(), consecutiveColumn.begin(), consecutiveColumn.end()); aggregate.insert(aggregate.end(), consecutiveRow.begin(), consecutiveRow.end()); for (auto value : aggregate) { if (value == 1) { return currentGame.host; } else if (value == 2) { return currentGame.challenger; } } // Draw if the board is full, otherwise the winner is not determined yet return isBoardFull ? draw : none; } void tictactoe::move(const name &challenger, const name &host, const name &by, const uint16_t &row, const uint16_t &column) { check(has_auth(by), "The next move should be made by " + by.to_string()); // Check if game exists games existingHostGames(get_self(), host.value); auto itr = existingHostGames.find(challenger.value); check(itr != existingHostGames.end(), "Game does not exist."); // Check if this game hasn't ended yet check(itr->winner == none, "The game has ended."); // Check if this game belongs to the action sender check(by == itr->host || by == itr->challenger, "This is not your game."); // Check if this is the action sender's turn check(by == itr->turn, "it's not your turn yet!"); // Check if user makes a valid movement check(isValidMove(row, column, itr->board), "Not a valid movement."); // Fill the cell, 1 for host, 2 for challenger //TODO could use constant for 1 and 2 as well const uint8_t cellValue = itr->turn == itr->host ? 1 : 2; const auto turn = itr->turn == itr->host ? itr->challenger : itr->host; existingHostGames.modify(itr, itr->host, [&](auto &g) { g.board[row * game::boardWidth + column] = cellValue; g.turn = turn; g.winner = getWinner(g); }); }