Moonchess: vanishing tic-tac-toe, online and practice modes
Online play over WebSockets with any-string game codes, seats and spectators, and a 1-second heartbeat. Practice mode against Columbina, backed by a full solver, with an accuracy slider, spoilers, undo and piece freshness. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
c52f30dc59
18 files changed
+1743
No files matched your search
@@ -0,0 +1,92 @@
|
||||
import unittest
|
||||
|
||||
from game import Game
|
||||
|
||||
|
||||
def play_all(game, cells):
|
||||
for cell in cells:
|
||||
err = game.play(game.turn, cell)
|
||||
assert err is None, (cell, err)
|
||||
|
||||
|
||||
class GameTest(unittest.TestCase):
|
||||
def test_plain_win(self):
|
||||
g = Game()
|
||||
play_all(g, [0, 3, 1, 4, 2])
|
||||
self.assertEqual(g.winner, 0)
|
||||
self.assertEqual(g.win_line, [0, 1, 2])
|
||||
|
||||
def test_oldest_vanishes_as_turn_starts(self):
|
||||
g = Game()
|
||||
play_all(g, [0, 8, 1, 7, 5]) # X: 0,1,5 O: 8,7
|
||||
self.assertEqual(g.doomed(), 0) # X's oldest, dimmed during O's turn
|
||||
play_all(g, [3]) # O: 8,7,3; X's turn starts
|
||||
self.assertIsNone(g.board[0])
|
||||
self.assertEqual(g.history[0], [1, 5])
|
||||
self.assertEqual(g.doomed(), 8) # O's oldest is next
|
||||
|
||||
def test_can_place_where_own_piece_vanished(self):
|
||||
g = Game()
|
||||
play_all(g, [0, 8, 1, 7, 5, 3])
|
||||
self.assertIsNone(g.play(0, 0))
|
||||
self.assertEqual(g.history[0], [1, 5, 0])
|
||||
self.assertIsNone(g.board[8]) # and O's 8 went as O's turn started
|
||||
|
||||
def test_opponents_dimmed_piece_is_taken(self):
|
||||
g = Game()
|
||||
play_all(g, [0, 8, 1, 7, 5, 3])
|
||||
self.assertEqual(g.play(0, 8), "That square is taken")
|
||||
|
||||
def test_vanishing_piece_does_not_count_toward_win(self):
|
||||
g = Game()
|
||||
# X: 0, 4, 2 ... then 1 would make 0-1-2, but 0 vanishes when 1 is placed.
|
||||
play_all(g, [0, 3, 4, 6, 2, 7])
|
||||
self.assertIsNone(g.board[0])
|
||||
play_all(g, [1])
|
||||
self.assertIsNone(g.winner)
|
||||
self.assertEqual(g.history[0], [4, 2, 1])
|
||||
|
||||
def test_win_with_rotated_pieces(self):
|
||||
g = Game()
|
||||
play_all(g, [0, 3, 1, 4, 8, 6]) # X: 0,1,8 O: 3,4,6
|
||||
play_all(g, [2]) # X: 1,8,2 (0 vanished) -> no line
|
||||
self.assertIsNone(g.winner)
|
||||
play_all(g, [5]) # O: 4,6,5 (3 vanished)
|
||||
play_all(g, [0]) # X: 8,2,0 (1 vanished)
|
||||
play_all(g, [3]) # O: 6,5,3 (4 vanished)
|
||||
play_all(g, [4]) # X: 2,0,4 (8 vanished)
|
||||
play_all(g, [7]) # O: 5,3,7 (6 vanished)
|
||||
play_all(g, [6]) # X: 0,4,6 (2 vanished)
|
||||
self.assertIsNone(g.winner)
|
||||
play_all(g, [8]) # O: 3,7,8 (5 vanished)
|
||||
play_all(g, [2]) # X: 4,6,2 (0 vanished) -> diagonal 2-4-6
|
||||
self.assertEqual(g.winner, 0)
|
||||
self.assertEqual(g.win_line, [2, 4, 6])
|
||||
|
||||
def test_rejects_bad_moves(self):
|
||||
g = Game()
|
||||
self.assertEqual(g.play(1, 0), "Not your turn")
|
||||
self.assertEqual(g.play(0, 9), "Invalid square")
|
||||
self.assertEqual(g.play(0, "3"), "Invalid square")
|
||||
play_all(g, [4])
|
||||
self.assertEqual(g.play(1, 4), "That square is taken")
|
||||
|
||||
def test_rematch_needs_both_and_alternates_first(self):
|
||||
g = Game()
|
||||
play_all(g, [0, 3, 1, 4, 2])
|
||||
g.vote_rematch(0)
|
||||
self.assertEqual(g.winner, 0)
|
||||
g.vote_rematch(1)
|
||||
self.assertIsNone(g.winner)
|
||||
self.assertEqual(g.board, [None] * 9)
|
||||
self.assertEqual(g.turn, 1)
|
||||
|
||||
def test_rematch_ignored_mid_round(self):
|
||||
g = Game()
|
||||
g.vote_rematch(0)
|
||||
g.vote_rematch(1)
|
||||
self.assertEqual(g.first, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user