import { action, computed, pattern } from "commonfabric"; import Scrabble, { type Letter } from "./scrabble.tsx"; const tile = (char: string, id: string): Letter => ({ char, points: char === "D" || char === "G" ? 2 : 1, id, isBlank: false, }); const TEST_BAG: Letter[] = [ tile("A", "tile-a"), tile("T", "tile-t"), tile("E", "tile-e"), tile("S", "tile-s"), tile("R", "tile-r"), tile("N", "tile-n"), tile("O", "tile-o"), tile("D", "tile-d"), tile("G", "tile-g"), ]; const rackLetter = (rack: readonly Letter[], char: string) => rack.find((letter) => letter.char === char)!; export default pattern(() => { const scrabble = Scrabble({ gameName: "Test Scrabble", bag: TEST_BAG, bagIndex: 0, }); const action_join_alice = action(() => { scrabble.joinWithName.send("Alice"); }); const action_try_rename_after_join = action(() => { scrabble.joinWithName.send("Bob"); }); const action_submit_off_center_word = action(() => { const a = rackLetter(scrabble.rack, "A"); const t = rackLetter(scrabble.rack, "T"); scrabble.placeTile.send({ letterId: a.id, row: 0, col: 0 }); scrabble.placeTile.send({ letterId: t.id, row: 0, col: 1 }); scrabble.submitTurn.send(); }); const action_submit_center_word = action(() => { const a = rackLetter(scrabble.rack, "A"); const t = rackLetter(scrabble.rack, "T"); scrabble.placeTile.send({ letterId: a.id, row: 7, col: 7 }); scrabble.placeTile.send({ letterId: t.id, row: 7, col: 8 }); scrabble.submitTurn.send(); }); const action_clear_unsubmitted_tiles = action(() => { const first = scrabble.rack[0]; const second = scrabble.rack[1]; scrabble.placeTile.send({ letterId: first.id, row: 8, col: 7 }); scrabble.placeTile.send({ letterId: second.id, row: 8, col: 8 }); scrabble.clearBoard.send(); }); const action_reset_game = action(() => { scrabble.resetGame.send(); }); const assert_off_center_word_rejected = computed(() => scrabble.players.length === 1 && scrabble.players[0]?.name === "Alice" && scrabble.board.length === 0 && scrabble.placed.length === 0 && scrabble.rack.length === 7 && scrabble.bagIndex === 7 && scrabble.message === "The first word must cover the center star." ); const assert_joined_name_is_immutable = computed(() => scrabble.players.length === 1 && scrabble.players[0]?.name === "Alice" && scrabble.myName === "Alice" && scrabble.rack.length === 7 && scrabble.message === "You already joined as Alice." ); const assert_center_word_submitted = computed(() => { const board = scrabble.board; const player = scrabble.players[0]; const lastEvent = scrabble.gameEvents.at(-1); return board.length === 2 && board[0].letter.char === "A" && board[0].row === 7 && board[0].col === 7 && board[1].letter.char === "T" && board[1].row === 7 && board[1].col === 8 && scrabble.placed.length === 0 && scrabble.rack.length === 7 && scrabble.bagIndex === 9 && player?.score === 4 && lastEvent?.type === "word" && lastEvent.details.includes("AT (+4)") && scrabble.message.startsWith("Scored 4:"); }); const assert_clear_returns_tiles = computed(() => scrabble.placed.length === 0 && scrabble.rack.length === 7 && scrabble.message === "Cleared your unsubmitted tiles." ); const assert_reset_clears_game = computed(() => scrabble.board.length === 0 && scrabble.players.length === 0 && scrabble.gameEvents.length === 0 && scrabble.rack.length === 0 && scrabble.placed.length === 0 && scrabble.bagIndex === 0 && scrabble.message === "Game reset. Join again to draw a fresh rack." ); return { tests: [ { action: action_join_alice }, { action: action_try_rename_after_join }, { assertion: assert_joined_name_is_immutable }, { action: action_submit_off_center_word }, { assertion: assert_off_center_word_rejected }, { action: action_submit_center_word }, { assertion: assert_center_word_submitted }, { action: action_clear_unsubmitted_tiles }, { assertion: assert_clear_returns_tiles }, { action: action_reset_game }, { assertion: assert_reset_clears_game }, ], scrabble, }; });