diff --git a/contracts/tic-tac-toe.clar b/contracts/tic-tac-toe.clar
index 4bc01e3..0b5d524 100644
--- a/contracts/tic-tac-toe.clar
+++ b/contracts/tic-tac-toe.clar
@@ -18,7 +18,22 @@
bet-amount: uint,
board: (list 9 uint),
- winner: (optional principal)
+ winner: (optional principal),
+
+ ;; NEW: Track total number of moves made in this game
+ move-count: uint
+ }
+)
+
+;; NEW: Map to store individual move history
+;; Key: {game-id, move-number}, Value: {player, move-index, move-value, block-height}
+(define-map move-history
+ { game-id: uint, move-number: uint }
+ {
+ player: principal,
+ move-index: uint,
+ move-value: uint,
+ block-height: uint
}
)
@@ -37,7 +52,8 @@
is-player-one-turn: false,
bet-amount: bet-amount,
board: game-board,
- winner: none
+ winner: none,
+ move-count: u1 ;; NEW: Initialize with 1 move (the creation move)
})
)
@@ -55,6 +71,17 @@
;; Increment the Game ID counter
(var-set latest-game-id (+ game-id u1))
+ ;; NEW: Record the first move in history
+ (map-set move-history
+ { game-id: game-id, move-number: u0 }
+ {
+ player: contract-caller,
+ move-index: move-index,
+ move-value: move,
+ block-height: stacks-block-height
+ }
+ )
+
;; Log the creation of the new game
(print { action: "create-game", data: game-data})
;; Return the Game ID of the new game
@@ -67,6 +94,8 @@
(original-game-data (unwrap! (map-get? games game-id) (err ERR_GAME_NOT_FOUND)))
;; Get the original board from the game data
(original-board (get board original-game-data))
+ ;; Get current move count
+ (current-move-count (get move-count original-game-data))
;; Update the game board by placing the player's move at the specified index
(game-board (unwrap! (replace-at? original-board move-index move) (err ERR_INVALID_MOVE)))
@@ -74,7 +103,8 @@
(game-data (merge original-game-data {
board: game-board,
player-two: (some contract-caller),
- is-player-one-turn: true
+ is-player-one-turn: true,
+ move-count: (+ current-move-count u1) ;; NEW: Increment move count
}))
)
@@ -91,6 +121,17 @@
;; Update the games map with the new game data
(map-set games game-id game-data)
+ ;; NEW: Record this move in history
+ (map-set move-history
+ { game-id: game-id, move-number: current-move-count }
+ {
+ player: contract-caller,
+ move-index: move-index,
+ move-value: move,
+ block-height: stacks-block-height
+ }
+ )
+
;; Log the joining of the game
(print { action: "join-game", data: game-data})
;; Return the Game ID of the game
@@ -103,6 +144,8 @@
(original-game-data (unwrap! (map-get? games game-id) (err ERR_GAME_NOT_FOUND)))
;; Get the original board from the game data
(original-board (get board original-game-data))
+ ;; Get current move count
+ (current-move-count (get move-count original-game-data))
;; Is it player one's turn?
(is-player-one-turn (get is-player-one-turn original-game-data))
@@ -120,7 +163,8 @@
(game-data (merge original-game-data {
board: game-board,
is-player-one-turn: (not is-player-one-turn),
- winner: (if is-now-winner (some player-turn) none)
+ winner: (if is-now-winner (some player-turn) none),
+ move-count: (+ current-move-count u1) ;; NEW: Increment move count
}))
)
@@ -137,6 +181,17 @@
;; Update the games map with the new game data
(map-set games game-id game-data)
+ ;; NEW: Record this move in history
+ (map-set move-history
+ { game-id: game-id, move-number: current-move-count }
+ {
+ player: contract-caller,
+ move-index: move-index,
+ move-value: move,
+ block-height: stacks-block-height
+ }
+ )
+
;; Log the action of a move being made
(print {action: "play", data: game-data})
;; Return the Game ID of the game
@@ -151,6 +206,21 @@
(var-get latest-game-id)
)
+;; NEW: Get a specific move from game history
+;; Returns the move details or none if move doesn't exist
+(define-read-only (get-move (game-id uint) (move-number uint))
+ (map-get? move-history { game-id: game-id, move-number: move-number })
+)
+
+;; NEW: Get the total number of moves made in a game
+;; Returns the move count or 0 if game doesn't exist
+(define-read-only (get-move-count (game-id uint))
+ (match (map-get? games game-id)
+ game-data (ok (get move-count game-data))
+ (ok u0)
+ )
+)
+
(define-private (validate-move (board (list 9 uint)) (move-index uint) (move uint))
(let (
;; Validate that the move is being played within range of the board
diff --git a/frontend/app/game/[gameId]/page.tsx b/frontend/app/game/[gameId]/page.tsx
index bcaf380..844f8ef 100644
--- a/frontend/app/game/[gameId]/page.tsx
+++ b/frontend/app/game/[gameId]/page.tsx
@@ -1,23 +1,58 @@
import { PlayGame } from "@/components/play-game";
-import { getGame } from "@/lib/contract";
+import { GameHistory } from "@/components/game-history";
+import { MoveReplay } from "@/components/move-replay";
+import { getGame, getGameHistory } from "@/lib/contract";
type Params = Promise<{ gameId: string }>;
export default async function GamePage({ params }: { params: Params }) {
const gameId = (await params).gameId;
const game = await getGame(parseInt(gameId));
- if (!game) return
Game not found
;
+
+ if (!game) {
+ return (
+
+
Game not found
+
The game ID {gameId} does not exist.
+
+ );
+ }
+
+ // Fetch game history
+ const history = await getGameHistory(parseInt(gameId));
return (
-
-
+
+
Game #{gameId}
- Play the game with your opponent
+ Play the game or review the move history
+ {/* Main game board and controls */}
+
+ {/* Move History Section */}
+ {history.length > 0 && (
+ <>
+
+
+ {/* Move Replay Animation */}
+
+
+
+
+
+
+ {/* Detailed Move History Table */}
+
+ >
+ )}
);
-}
+}
\ No newline at end of file
diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx
index f370348..b9746e9 100644
--- a/frontend/app/page.tsx
+++ b/frontend/app/page.tsx
@@ -1,7 +1,8 @@
import { GamesList } from "@/components/games-list";
import { getAllGames } from "@/lib/contract";
-export const dynamic = "force-dynamic";
+// Cache the page for 30 seconds to avoid repeated API calls
+export const revalidate = 30;
export default async function Home() {
const games = await getAllGames();
@@ -18,4 +19,4 @@ export default async function Home() {
);
-}
+}
\ No newline at end of file
diff --git a/frontend/components/game-history.tsx b/frontend/components/game-history.tsx
new file mode 100644
index 0000000..ca01b82
--- /dev/null
+++ b/frontend/components/game-history.tsx
@@ -0,0 +1,93 @@
+"use client";
+
+import { MoveHistoryEntry } from "@/lib/contract";
+import { abbreviateAddress } from "@/lib/stx-utils";
+import Link from "next/link";
+
+interface GameHistoryProps {
+ history: MoveHistoryEntry[];
+ playerOne: string;
+ playerTwo: string | null;
+}
+
+export function GameHistory({ history, playerOne, playerTwo }: GameHistoryProps) {
+ if (history.length === 0) {
+ return (
+