{"project_id":"prj_a6fd2ff7-7394-4f8a-a6f1-c42f0bc9e6ca","revision":1,"parent_revision":0,"bundle_sha256":"55f9602ec09e60f4fd0aac1f6c7b8a5192372007d814481b541db069fc85138b","bytes":11116,"file_count":2,"author_muse_id":"9","note":"Engineering task session: source, tests and bounded generated artifacts","created_at":"2026-10-02T00:25:11.365Z","files":{"src/TicTacToeDuel.sol":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\ncontract TicTacToeDuel {\n    enum GameStatus {\n        None,\n        Created,\n        Active,\n        Won,\n        Draw\n    }\n\n    struct Game {\n        address playerX;\n        address playerO;\n        address turn;\n        address winner;\n        GameStatus status;\n        uint8 moveCount;\n        uint8[9] board; // 0 = empty, 1 = X, 2 = O\n    }\n\n    uint256 public nextGameId = 1;\n    mapping(uint256 => Game) private games;\n\n    event DuelCreated(uint256 indexed gameId, address indexed creator, address indexed opponent);\n    event DuelAccepted(uint256 indexed gameId, address indexed playerX, address indexed playerO);\n    event MovePlayed(uint256 indexed gameId, address indexed player, uint8 position, uint8 piece);\n    event GameWon(uint256 indexed gameId, address indexed winner);\n    event GameDraw(uint256 indexed gameId);\n\n    function createDuel(address opponent) external returns (uint256 gameId) {\n        require(opponent != address(0), \"Invalid opponent\");\n        require(opponent != msg.sender, \"Cannot duel self\");\n\n        gameId = nextGameId++;\n        Game storage game = games[gameId];\n        game.playerX = msg.sender;\n        game.playerO = opponent;\n        game.status = GameStatus.Created;\n\n        emit DuelCreated(gameId, msg.sender, opponent);\n    }\n\n    function acceptDuel(uint256 gameId) external {\n        Game storage game = games[gameId];\n        require(game.status == GameStatus.Created, \"Game not pending acceptance\");\n        require(msg.sender == game.playerO, \"Only designated opponent can accept\");\n\n        game.status = GameStatus.Active;\n        game.turn = game.playerX;\n\n        emit DuelAccepted(gameId, game.playerX, game.playerO);\n    }\n\n    function playMove(uint256 gameId, uint8 position) external {\n        Game storage game = games[gameId];\n        require(game.status == GameStatus.Active, \"Game is not active\");\n        require(msg.sender == game.turn, \"Not your turn\");\n        require(position < 9, \"Position out of bounds\");\n        require(game.board[position] == 0, \"Square occupied\");\n\n        uint8 piece = (msg.sender == game.playerX) ? 1 : 2;\n        game.board[position] = piece;\n        game.moveCount++;\n\n        emit MovePlayed(gameId, msg.sender, position, piece);\n\n        if (_checkWin(game.board, piece)) {\n            game.status = GameStatus.Won;\n            game.winner = msg.sender;\n            emit GameWon(gameId, msg.sender);\n            return;\n        }\n\n        if (game.moveCount == 9) {\n            game.status = GameStatus.Draw;\n            emit GameDraw(gameId);\n            return;\n        }\n\n        game.turn = (msg.sender == game.playerX) ? game.playerO : game.playerX;\n    }\n\n    function getBoard(uint256 gameId) external view returns (uint8[9] memory) {\n        require(games[gameId].status != GameStatus.None, \"Game does not exist\");\n        return games[gameId].board;\n    }\n\n    function getGame(uint256 gameId)\n        external\n        view\n        returns (\n            address playerX,\n            address playerO,\n            address turn,\n            address winner,\n            GameStatus status,\n            uint8 moveCount,\n            uint8[9] memory board\n        )\n    {\n        Game storage game = games[gameId];\n        require(game.status != GameStatus.None, \"Game does not exist\");\n        return (\n            game.playerX,\n            game.playerO,\n            game.turn,\n            game.winner,\n            game.status,\n            game.moveCount,\n            game.board\n        );\n    }\n\n    function _checkWin(uint8[9] storage board, uint8 piece) internal view returns (bool) {\n        // Rows\n        if (board[0] == piece && board[1] == piece && board[2] == piece) return true;\n        if (board[3] == piece && board[4] == piece && board[5] == piece) return true;\n        if (board[6] == piece && board[7] == piece && board[8] == piece) return true;\n\n        // Columns\n        if (board[0] == piece && board[3] == piece && board[6] == piece) return true;\n        if (board[1] == piece && board[4] == piece && board[7] == piece) return true;\n        if (board[2] == piece && board[5] == piece && board[8] == piece) return true;\n\n        // Diagonals\n        if (board[0] == piece && board[4] == piece && board[8] == piece) return true;\n        if (board[2] == piece && board[4] == piece && board[6] == piece) return true;\n\n        return false;\n    }\n}\n","test/TicTacToeDuel.t.sol":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\nimport \"../src/TicTacToeDuel.sol\";\n\ninterface Vm {\n    function prank(address) external;\n    function expectRevert(bytes calldata) external;\n}\n\ncontract TicTacToeDuelTest {\n    Vm constant vm = Vm(address(uint160(uint256(keccak256(\"hevm cheat code\")))));\n\n    TicTacToeDuel duel;\n    address playerA = address(0x1111);\n    address playerB = address(0x2222);\n    address playerC = address(0x3333);\n\n    function setUp() public {\n        duel = new TicTacToeDuel();\n    }\n\n    function test_CreateAndAcceptDuel() public {\n        vm.prank(playerA);\n        uint256 gameId = duel.createDuel(playerB);\n        require(gameId == 1, \"GameId should be 1\");\n\n        (\n            address px,\n            address po,\n            address turn,\n            address winner,\n            TicTacToeDuel.GameStatus status,\n            uint8 moveCount,\n            \n        ) = duel.getGame(gameId);\n\n        require(px == playerA, \"playerX mismatch\");\n        require(po == playerB, \"playerO mismatch\");\n        require(turn == address(0), \"turn should be 0 before accept\");\n        require(winner == address(0), \"winner should be 0\");\n        require(status == TicTacToeDuel.GameStatus.Created, \"status should be Created\");\n        require(moveCount == 0, \"moveCount should be 0\");\n\n        // Player C cannot accept\n        vm.prank(playerC);\n        vm.expectRevert(\"Only designated opponent can accept\");\n        duel.acceptDuel(gameId);\n\n        // Player B accepts\n        vm.prank(playerB);\n        duel.acceptDuel(gameId);\n\n        (, , turn, , status, , ) = duel.getGame(gameId);\n        require(turn == playerA, \"turn should be playerA after accept\");\n        require(status == TicTacToeDuel.GameStatus.Active, \"status should be Active\");\n    }\n\n    function test_FullGameWin_PlayerX() public {\n        vm.prank(playerA);\n        uint256 gameId = duel.createDuel(playerB);\n        vm.prank(playerB);\n        duel.acceptDuel(gameId);\n\n        // A plays 0 (top-left)\n        vm.prank(playerA);\n        duel.playMove(gameId, 0);\n\n        // B plays 3 (mid-left)\n        vm.prank(playerB);\n        duel.playMove(gameId, 3);\n\n        // A plays 1 (top-mid)\n        vm.prank(playerA);\n        duel.playMove(gameId, 1);\n\n        // B plays 4 (center)\n        vm.prank(playerB);\n        duel.playMove(gameId, 4);\n\n        // A plays 2 (top-right) -> Win!\n        vm.prank(playerA);\n        duel.playMove(gameId, 2);\n\n        (\n            ,\n            ,\n            ,\n            address winner,\n            TicTacToeDuel.GameStatus status,\n            uint8 moveCount,\n            \n        ) = duel.getGame(gameId);\n\n        require(status == TicTacToeDuel.GameStatus.Won, \"Game should be won\");\n        require(winner == playerA, \"Winner should be playerA\");\n        require(moveCount == 5, \"Move count should be 5\");\n\n        uint8[9] memory liveBoard = duel.getBoard(gameId);\n        require(liveBoard[0] == 1, \"0 should be X\");\n        require(liveBoard[1] == 1, \"1 should be X\");\n        require(liveBoard[2] == 1, \"2 should be X\");\n        require(liveBoard[3] == 2, \"3 should be O\");\n        require(liveBoard[4] == 2, \"4 should be O\");\n        require(liveBoard[5] == 0, \"5 should be empty\");\n\n        // Further move rejected because game finished\n        vm.prank(playerB);\n        vm.expectRevert(\"Game is not active\");\n        duel.playMove(gameId, 5);\n    }\n\n    function test_FullGameDraw() public {\n        vm.prank(playerA);\n        uint256 gameId = duel.createDuel(playerB);\n        vm.prank(playerB);\n        duel.acceptDuel(gameId);\n\n        /*\n           Board layout target (Draw):\n           X | O | X   -> 0, 1, 2\n           X | O | O   -> 3, 4, 5\n           O | X | X   -> 6, 7, 8\n\n           Move sequence:\n           1. X -> 0\n           2. O -> 1\n           3. X -> 2\n           4. O -> 4\n           5. X -> 3\n           6. O -> 5\n           7. X -> 7\n           8. O -> 6\n           9. X -> 8\n        */\n        vm.prank(playerA);\n        duel.playMove(gameId, 0); // X at 0\n        vm.prank(playerB);\n        duel.playMove(gameId, 1); // O at 1\n        vm.prank(playerA);\n        duel.playMove(gameId, 2); // X at 2\n        vm.prank(playerB);\n        duel.playMove(gameId, 4); // O at 4\n        vm.prank(playerA);\n        duel.playMove(gameId, 3); // X at 3\n        vm.prank(playerB);\n        duel.playMove(gameId, 5); // O at 5\n        vm.prank(playerA);\n        duel.playMove(gameId, 7); // X at 7\n        vm.prank(playerB);\n        duel.playMove(gameId, 6); // O at 6\n        vm.prank(playerA);\n        duel.playMove(gameId, 8); // X at 8\n\n        (\n            ,\n            ,\n            ,\n            address winner,\n            TicTacToeDuel.GameStatus status,\n            uint8 moveCount,\n            \n        ) = duel.getGame(gameId);\n\n        require(status == TicTacToeDuel.GameStatus.Draw, \"Game should be Draw\");\n        require(winner == address(0), \"Draw should have no winner\");\n        require(moveCount == 9, \"Move count should be 9\");\n\n        // Cannot move on finished draw game\n        vm.prank(playerB);\n        vm.expectRevert(\"Game is not active\");\n        duel.playMove(gameId, 0);\n    }\n\n    function test_IllegalMovesRejected() public {\n        vm.prank(playerA);\n        uint256 gameId = duel.createDuel(playerB);\n\n        // Move before acceptance rejected\n        vm.prank(playerA);\n        vm.expectRevert(\"Game is not active\");\n        duel.playMove(gameId, 0);\n\n        // Accept\n        vm.prank(playerB);\n        duel.acceptDuel(gameId);\n\n        // Out-of-turn: Player B tries to play first\n        vm.prank(playerB);\n        vm.expectRevert(\"Not your turn\");\n        duel.playMove(gameId, 0);\n\n        // Player A plays 0\n        vm.prank(playerA);\n        duel.playMove(gameId, 0);\n\n        // Out-of-turn: Player A tries to play again\n        vm.prank(playerA);\n        vm.expectRevert(\"Not your turn\");\n        duel.playMove(gameId, 1);\n\n        // Occupied square: Player B tries square 0\n        vm.prank(playerB);\n        vm.expectRevert(\"Square occupied\");\n        duel.playMove(gameId, 0);\n\n        // Position out of bounds\n        vm.prank(playerB);\n        vm.expectRevert(\"Position out of bounds\");\n        duel.playMove(gameId, 9);\n\n        // Unrelated address plays\n        vm.prank(playerC);\n        vm.expectRevert(\"Not your turn\");\n        duel.playMove(gameId, 4);\n    }\n\n    function test_InvalidDuelCreation() public {\n        vm.prank(playerA);\n        vm.expectRevert(\"Invalid opponent\");\n        duel.createDuel(address(0));\n\n        vm.prank(playerA);\n        vm.expectRevert(\"Cannot duel self\");\n        duel.createDuel(playerA);\n    }\n}\n"}}