Supporting multiple games

Our contract currently stores the state of a single, global game using several related state variables:

    uint256[9] public board;
    address public playerX;
    address public playerO;
    uint256 internal _turns;

We can instead use a struct to group these together in a data structure that represents an individual game:

struct Game {
    address playerX;
    address playerO;
    uint256[9] board;
    uint256 turns;

And a mapping to store many instances of a Game by an integer ID:

mapping(uint256 => Game) gamesById;

Finally, we'll need to parameterize our existing functions to take a game ID as an argument. For example, here's our existing markSpace function:

    function markSpace(uint256 space) public {
        require(_validPlayer(), "Unauthorized");
        require(_validTurn(), "Not your turn");
        require(_emptySpace(space), "Already marked");
        board[space] = _getSymbol(msg.sender);

We'll now need to pass in a game ID, and probably pass it through to some of our internal helpers:

    function markSpace(uint256 gameId, uint256 space) public {
        require(_validPlayer(gameId), "Unauthorized");
        require(_validTurn(gameId), "Not your turn");
        require(_emptySpace(gameId, space), "Already marked");
        gamesById[gameId].board[space] = _getSymbol(gameId, msg.sender);

Let's approach this change in three steps. First, we'll refactor to use a struct and mapping internally, without changing our external contract interface. Next, we'll update the interface to expect a game ID as an argument. Finally, we'll add functionality to create new games.