dimanche 2 décembre 2018

How to implement functions based on TestCases?

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