Below are few functions which need to be implemented based on the TestCases mentioned as in the source code.
createGame()
addPlayer()
makeMove()
Would it be possible to suggest, how can I achieve this?
Here is the full source code what I have.
/**********************************
The goal is to implement the following functions (each one documented further down):
createGame()
addPlayer()
makeMove()
Already-implemented tests call these functions and check that they work.
Initially the tests fail, your goal to make them all pass.
The tests check both valid and invalid inputs, ensuring that error handling is correct.
Take as much time as you need (within reason), but in general this is expected to take about an hour.
The rules of tic tac toe are:
- Two players take turns. Each turn someone takes a spot on a 3x3 grid.
- The first player to take 3 colinear adjacent spots (vertically, horizontally, or diagonally) wins.
- The game can end in draw if all 9 spots are taken and no one wins.
Notes:
- Any C++ features may be used up to C++17. This base code requires at least C++11.
- Use only the standard library and self-written code, don't add any other thirdparty dependencies.
- You will not be judged on the code formatting style. Use any style that works for you.
- Do not worry about threading.
- Do not worry about security measures, this is not a real game server.
- Design your implementation cleanly, as if someone else were to maintain this code after you implement it.
- Comments are encouraged where useful, but generally things should be simple enough to not warrant many.
- "X" vs "O" are not really important. This is about game logic, not visuals.
*/
// Aliases
// These double as result codes for simplicty of the API
using GameId = int; // Valid game ids are >= 0
using PlayerId = int; // Valid player ids are >= 0
// Result codes
// These are listed in order: if multiple are applicable, the higher one takes precedence
// For example, if the game and the player does't exist, the GAME_DOESNT_EXIST shall be returned
constexpr int GAME_DOESNT_EXIST = -2;
constexpr int GAME_NOT_STARTED = -3;
constexpr int GAME_ENDED = -4;
constexpr int GAME_ONGOING = -5;
constexpr int PLAYER_DOESNT_EXIST = -6;
constexpr int WRONG_TURN = -7;
constexpr int INVALID_LOCATION = -8;
// Creates a new game. Multiple games may be running simultaneously.
//
// Returns:
// - A valid unique ID for the new game
// Errors:
// None
GameId createGame() noexcept
{
// IMPLEMENT ME!
return GAME_DOESNT_EXIST;
}
// Adds a player to the a game that has been created but not started.
// This function starts the game automatically once the 2nd player has joined.
// Once the game starts, the first player's turn begins (the one identified first call to addPlayer)
//
// Returns:
// A valid ID for the new player, unique to this game, which may be any integer greater than zero
//
// GAME_DOESNT_EXIST if the game id does not identify a valid game
// GAME_ENDED if the game has ended
// GAME_ONGOING if the game has alreedy begun
PlayerId addPlayer(GameId gameId) noexcept
{
// IMPLEMENT ME!
return GAME_DOESNT_EXIST;
}
// Allows a player to make a move
//
// After each valid move, the turn switches to the other player.
// If the move completes the game, the game status shall be considered ended.
// No early detection of draws is done. Game must fully play out (9 moves) to reach a draw.
//
// Returns:
// GAME_ONGOING if no one has won yet
// The id of the current player if he won with this move (game is then ended)
// The id of the other player if the game ended in a draw (game is then ended)
//
// GAME_DOESNT_EXIST if the game id does not identify a valid game
// GAME_NOT_STARTED if the game has not started
// GAME_ENDED if the game has already ended before this was called
// PLAYER_DOESNT_EXIST if the player id is not valid for this game
// WRONG_TURN if this is not player A turn
// INVALID_LOCATION if boardX or boardY is outside the range of [0, 2], or if that spot has been used already
//
PlayerId makeMove(GameId gameId, PlayerId playerId, int boardX, int boardY) noexcept
{
// IMPLEMENT ME!
return GAME_DOESNT_EXIST;
}
//////////////////////////////////////////////////////
// Nothing below this point needs to be changed ------
// Below is main() and tests -------------------------
//////////////////////////////////////////////////////
#include <iostream>
#include <map>
#include <utility>
#include <vector>
namespace Test
{
bool isValidId(int id) { return id >= 0; }
struct GameInfo
{
PlayerId players[2] = {-1, -1};
bool used = false;
};
std::map<GameId, GameInfo> gameIds;
void check(const bool expr, const char* const text)
{
if (!expr)
throw text; // Using c-string throws as a simple mechanism to indicate failure
}
GameId createGame() // Wrapper for createGame that does some basic checks
{
const GameId gameId = ::createGame();
check(gameId >= 0, "Negative game id");
check(gameIds.emplace(gameId, GameInfo{}).second, "Duplicate game id");
return gameId;
}
GameId useGame() // Wrapper for createGame that does some basic checks
{
for (auto& game : gameIds)
{
if (game.second.used)
continue;
game.second.used = true;
return game.first;
}
createGame();
return useGame();
}
GameId invalidGameId()
{
for (GameId i = 200;; ++i)
if (gameIds.find(i) == gameIds.end())
return i;
}
PlayerId addPlayer(const GameId gameId)
{
const PlayerId playerId = ::addPlayer(gameId);
if (playerId < 0)
return playerId;
const auto gameIt = gameIds.find(gameId);
check(gameIt != gameIds.end(), "Invalid gameID accepted to addPlayer");
GameInfo& game = gameIt->second;
check(game.used, "Internal test error");
if (!isValidId(game.players[0]))
game.players[0] = playerId;
else if (game.players[0] == playerId)
throw "Duplicate player id in game";
else if (!isValidId(game.players[1]))
game.players[1] = playerId;
else
throw "Received a player id from a full game";
return playerId;
}
void testCreateGame()
{
for (int i = 0; i < 10; ++i)
createGame();
}
void testInvalidGameIds()
{
// Test some negative ids
for (GameId i = -1; i > -10; --i)
check(addPlayer(i) == GAME_DOESNT_EXIST, "Negative game id should not be valid");
// Test an invalid positive id
check(addPlayer(invalidGameId()) == GAME_DOESNT_EXIST, "Invalid game id was accepted");
// Test making a move on an invalid game
check(makeMove(invalidGameId(), 0, 0, 0) == GAME_DOESNT_EXIST, "Wrong player move accepted");
}
void testAddPlayer()
{
const GameId gameId = useGame();
// Make sure we cant play a move before adding players
check(makeMove(gameId, 0, 0, 0) == GAME_NOT_STARTED, "Made a move with no players added");
// Add the first player
PlayerId player1Id;
check(isValidId(player1Id = addPlayer(gameId)), "Negative game id should not be valid");
// Make sure we cant play a move after adding the first player
check(makeMove(gameId, player1Id, 0, 0) == GAME_NOT_STARTED, "Made a move with only one player added");
// Add the second player
PlayerId player2Id;
check(isValidId(player2Id = addPlayer(gameId)), "Negative game id should not be valid");
// Make sure we can't move with the 2nd player, but that the game has started
check(makeMove(gameId, player2Id, 0, 0) == WRONG_TURN, "Made a move with only one player added");
// Make sure that we can't add more players after that
for (int i = 0; i < 10; ++i)
check(addPlayer(gameId) == GAME_ONGOING, "Adding players after the second should return GAME_ONGOING");
}
// A series of full where play
const std::vector<std::pair<int, int>> wins[] =
{
// xo_
// xo_
// x__
{
{0, 0},
{1, 0},
{0, 1},
{1, 1},
{0, 2},
},
// xox
// oxo
// x__
{
{2, 0},
{2, 1},
{0, 0},
{1, 0},
{0, 2},
{0, 1},
{1, 1},
},
// oxx
// ooo
// xx_
{
{0, 2},
{1, 1},
{2, 0},
{0, 1},
{1, 2},
{0, 0},
{1, 0},
{2, 1},
},
// ox_
// _o_
// xx_
{
{1, 0},
{1, 1},
{0, 2},
{0, 0},
{1, 2},
{2, 2},
},
};
const std::vector<std::pair<int, int>> draws[] =
{
// xxo
// oox
// xxo
{
{1, 2},
{1, 1},
{0, 2},
{2, 2},
{0, 0},
{0, 1},
{2, 1},
{2, 0},
{1, 0},
},
// oxo
// xxo
// xox
{
{1, 1},
{0, 0},
{0, 2},
{2, 0},
{1, 0},
{1, 2},
{0, 1},
{2, 1},
{2, 2},
},
};
void testFullGame(const std::vector<std::pair<int, int>>& game, const bool isDraw)
{
const GameId gameId = useGame();
const PlayerId player1Id = addPlayer(gameId);
const PlayerId b = addPlayer(gameId);
for (size_t i = 0; i < game.size(); ++i)
{
const PlayerId currentPlayer = i % 2 == 0 ? player1Id : b;
const PlayerId otherPlayer = i % 2 == 0 ? b : player1Id;
const std::pair<int, int> nextMove = game[i];
// Test that we can add players at no point during the game
check(addPlayer(gameId) == GAME_ONGOING, "Adding players during the game should return GAME_ONGOING");
// Test that the wrong player cant move
check(makeMove(gameId, otherPlayer, nextMove.first, nextMove.second) == WRONG_TURN, "Wrong player move accepted");
// Test that the right player can't move to any previously used spot
for (size_t i2 = 0; i2 < i; ++i2)
check(makeMove(gameId, currentPlayer, game[i2].first, game[i2].second) == INVALID_LOCATION, "Wrong player move accepted");
// Test that the correct player cant move to a spot outside the board
check(makeMove(gameId, currentPlayer, -1 - static_cast<int>(i), 0) == INVALID_LOCATION, "Invalid board location accepted");
check(makeMove(gameId, currentPlayer, 0, static_cast<int>(i) + 3) == INVALID_LOCATION, "Invalid board location accepted");
// Make move
if (i + 1 < game.size())
{
// Not-final move
check(makeMove(gameId, currentPlayer, nextMove.first, nextMove.second) == GAME_ONGOING, "Valid move rejected");
}
else
{
PlayerId expectedResult = otherPlayer; // Returning the other player means draw
const char* text = "Game should have been a draw";
if (!isDraw)
{
expectedResult = currentPlayer;
text = i % 2 == 0 ? "Player 1 should have won" : "Player 2 should have won";
}
// Final move of the game
check(makeMove(gameId, currentPlayer, nextMove.first, nextMove.second) == expectedResult, text);
// Test that after the game is complete, addPlayer and makeMove return GAME_ENDED
check(addPlayer(gameId) == GAME_ENDED, "Adding a player after the game ended should return GAME_ENDED");
check(makeMove(gameId, i % 2 == 0 ? b : player1Id, game[i].first, game[i].second) == GAME_ENDED, "Making a move after the game ended shoudl return GAME_ENDED");
}
}
}
template <typename TestFunc, typename... Args>
void runTest(const char* const name, const TestFunc func, Args&&... args) try
{
func(std::forward<Args>(args)...);
std::cout << "[PASSED] " << name << std::endl;
}
catch (const char* text)
{
std::cout << "[FAILED] " << name << ": " << text << std::endl;
}
void runTests()
{
runTest("testCreateGame", &testCreateGame);
runTest("testInvalidGameIds", &testInvalidGameIds);
runTest("testAddPlayer", &testAddPlayer);
for (auto& game : wins)
runTest(game.size() % 2 == 0 ? "testPlayer1Win" : "testPlayer2Win", &testFullGame, game, false);
for (auto& game : draws)
runTest("testDraw", &testFullGame, game, true);
}
} // namespace Test
int main()
{
Test::runTests();
}
Aucun commentaire:
Enregistrer un commentaire