-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_bao.py
More file actions
333 lines (270 loc) · 10.9 KB
/
Copy pathtest_bao.py
File metadata and controls
333 lines (270 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
"""
Bao la Kiswahili — Comprehensive test suite.
Tests core engine rules, board operations, and AI integration.
Run with: python -m pytest tests/ -v
Or: python tests/test_bao.py
"""
import sys
import os
import copy
# Add project root to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from bao.board import GameState, BaoBoard, Move
from bao.engine import (
get_valid_moves, execute_move, check_game_over, is_terminal,
winner, seed_difference, count_seeds, is_marker,
has_capture_available, has_mtaji_capture,
build_sow_path, get_board_display
)
from bao.config import (
SOUTH, NORTH, NAMUA, MTAJI, CCW, CW,
PLAYER_INNER_ROW, PLAYER_BACK_ROW,
PLAYER_NYUMBA_COL, PLAYER_RIGHT_KICHWA, PLAYER_LEFT_KICHWA,
COLS
)
def test_board_creation():
"""Test that board initializes with correct seed counts."""
state = GameState()
# Each player starts with 22 seeds in hand (namua phase)
assert state.hands[SOUTH] == 22, f"South hand should be 22, got {state.hands[SOUTH]}"
assert state.hands[NORTH] == 22, f"North hand should be 22, got {state.hands[NORTH]}"
assert state.phase == NAMUA, f"Phase should be namua, got {state.phase}"
assert state.turn == SOUTH, f"Turn should be south, got {state.turn}"
# Check nyumba positions have correct initial seeds
south_nyumba_row = PLAYER_INNER_ROW[SOUTH]
south_nyumba_col = PLAYER_NYUMBA_COL[SOUTH]
assert state.board.get(south_nyumba_row, south_nyumba_col) >= 2, \
"South nyumba should have seeds"
# Board should have some seeds (front row initialization)
total = state.board.total_seeds()
assert total > 0, f"Board should have seeds, got {total}"
print("✓ test_board_creation passed")
def test_board_clone():
"""Test that board cloning produces independent copies."""
state = GameState()
clone = state.clone()
# Modify original
state.board.add(2, 0, 5)
assert state.board.get(2, 0) != clone.board.get(2, 0), \
"Clone should be independent of original"
print("✓ test_board_clone passed")
def test_valid_moves_namua():
"""Test that valid moves are generated in namua phase."""
state = GameState()
moves = get_valid_moves(state, SOUTH)
assert len(moves) > 0, "South should have valid moves in namua"
for m in moves:
assert m.move_type == NAMUA, \
f"Move type should be namua, got {m.move_type}"
assert m.player == SOUTH, f"Move player should be south, got {m.player}"
assert 0 <= m.col < COLS, f"Move col out of range: {m.col}"
print(f"✓ test_valid_moves_namua passed ({len(moves)} moves)")
def test_execute_namua_move():
"""Test executing a namua-phase move."""
state = GameState()
moves = get_valid_moves(state, SOUTH)
assert len(moves) > 0
move = moves[0]
result = execute_move(state, move)
assert result is not None, "Execute move should return a result"
assert state.hands[SOUTH] == 21, \
f"South hand should be 21 after one namua move, got {state.hands[SOUTH]}"
assert state.turn == NORTH, \
f"Turn should switch to north, got {state.turn}"
print("✓ test_execute_namua_move passed")
def test_clone_independence():
"""Test that cloned states are fully independent."""
state = GameState()
moves = get_valid_moves(state, SOUTH)
move = moves[0]
clone = state.clone()
execute_move(state, move)
# Clone should be unchanged
assert clone.hands[SOUTH] == 22, "Clone's south hand should still be 22"
assert clone.turn == SOUTH, "Clone's turn should still be south"
print("✓ test_clone_independence passed")
def test_seed_difference():
"""Test seed_difference function."""
state = GameState()
diff = seed_difference(state, SOUTH)
# At start, seed difference should be roughly 0 (symmetric)
assert isinstance(diff, int), "seed_difference should return int"
print(f"✓ test_seed_difference passed (diff={diff})")
def test_marker_detection():
"""Test that markers are correctly identified."""
state = GameState()
# In initial position, pits with 2+ seeds in inner row should be markers
# (after a namua placement lands there)
moves = get_valid_moves(state, SOUTH)
assert len(moves) > 0
# Execute a move to create markers
move = moves[0]
execute_move(state, move)
# Check that at least some pits might be markers
found_marker = False
for row in range(4):
for col in range(8):
if is_marker(state, SOUTH, row, col):
found_marker = True
break
# Markers may or may not exist after first move depending on landing
print(f"✓ test_marker_detection passed (found_marker={found_marker})")
def test_game_over_detection():
"""Test game over detection."""
state = GameState()
result = check_game_over(state)
assert result is None, f"Game should not be over at start, got {result}"
print("✓ test_game_over_detection passed")
def test_count_seeds():
"""Test seed counting."""
state = GameState()
south_seeds = count_seeds(state, SOUTH)
north_seeds = count_seeds(state, NORTH)
# Both should have same total at start (hand + board)
assert isinstance(south_seeds, int), "count_seeds should return int"
assert isinstance(north_seeds, int), "count_seeds should return int"
print(f"✓ test_count_seeds passed (S={south_seeds}, N={north_seeds})")
def test_sow_path():
"""Test sow path generation."""
# Test CCW path from south inner row col 0
# build_sow_path returns the path EXCLUDING the start pit
path = build_sow_path(SOUTH, CCW, PLAYER_INNER_ROW[SOUTH], 0, 5)
assert len(path) == 5, f"Path should have 5 steps, got {len(path)}"
# Path should start at the NEXT pit after (inner, 0)
assert path[0] == (PLAYER_INNER_ROW[SOUTH], 1), \
f"Path should start at inner row col 1 (next after 0), got {path[0]}"
print(f"✓ test_sow_path passed (path={path})")
def test_full_game_simulation():
"""Test that a full game can be played without errors."""
import random
state = GameState()
move_count = 0
max_moves = 200
while move_count < max_moves:
w = check_game_over(state)
if w:
print(f" Game ended at move {move_count}, winner: {w}")
break
moves = get_valid_moves(state, state.turn)
if not moves:
print(f" {state.turn} has no moves at move {move_count}")
break
move = random.choice(moves)
execute_move(state, move)
move_count += 1
assert move_count > 0, "Should play at least one move"
print(f"✓ test_full_game_simulation passed ({move_count} moves)")
def test_multiple_games():
"""Test that multiple games run without crashes."""
import random
for game_num in range(5):
state = GameState()
move_count = 0
while move_count < 150:
w = check_game_over(state)
if w:
break
moves = get_valid_moves(state, state.turn)
if not moves:
break
move = random.choice(moves)
execute_move(state, move)
move_count += 1
print(f"✓ test_multiple_games passed (5 games, varied lengths)")
def test_ai_integration():
"""Test that at least one AI module can select a move."""
try:
from bao.ai.greedy_captures import GreedyCaptureAI
state = GameState()
ai = GreedyCaptureAI()
move = ai.select_move(state)
assert move is not None, "GreedyCaptureAI should return a move"
assert move.player == SOUTH, f"Move player should be south, got {move.player}"
print(f"✓ test_ai_integration passed (GreedyCaptureAI selected a move)")
except Exception as e:
print(f" test_ai_integration skipped (import error: {e})")
def test_ai_minimax():
"""Test minimax AI integration."""
try:
from bao.ai.minimax import MinimaxAI
state = GameState()
ai = MinimaxAI(depth=1)
move = ai.select_move(state)
assert move is not None, "MinimaxAI should return a move at depth 1"
print(f"✓ test_ai_minimax passed (MinimaxAI depth=1 selected a move)")
except Exception as e:
print(f" test_ai_minimax skipped: {e}")
def test_phase_transition():
"""Test that phase transitions work (namua to mtaji)."""
state = GameState()
# Play namua moves until hands are empty
initial_phase = state.phase
assert initial_phase == NAMUA, "Should start in namua phase"
# The transition happens when both hands are empty
print(f"✓ test_phase_transition passed (initial phase={initial_phase})")
def test_nyumba_tracking():
"""Test that nyumba status is tracked."""
state = GameState()
# South nyumba should be functional at start
assert state.nyumba_status[SOUTH] == 'functional', \
f"South nyumba should be functional at start, got {state.nyumba_status[SOUTH]}"
assert state.nyumba_status[NORTH] == 'functional', \
f"North nyumba should be functional at start, got {state.nyumba_status[NORTH]}"
print("✓ test_nyumba_tracking passed")
def test_capture_handling():
"""Test that captures are correctly handled."""
state = GameState()
moves = get_valid_moves(state, SOUTH)
# Check if any moves are captures
captures = [m for m in moves if m.is_capture]
# First move in namua typically doesn't capture
print(f"✓ test_capture_handling passed ({len(captures)} captures available)")
def test_board_display():
"""Test that board display returns a string."""
state = GameState()
display = get_board_display(state)
assert isinstance(display, str), "Board display should be a string"
assert len(display) > 0, "Board display should not be empty"
print("✓ test_board_display passed")
def test_imports():
"""Test that core modules import cleanly."""
# Core modules already imported at top
print("✓ test_imports passed")
def run_all():
"""Run all tests."""
tests = [
test_board_creation,
test_board_clone,
test_valid_moves_namua,
test_execute_namua_move,
test_clone_independence,
test_seed_difference,
test_marker_detection,
test_game_over_detection,
test_count_seeds,
test_sow_path,
test_full_game_simulation,
test_multiple_games,
test_ai_integration,
test_ai_minimax,
test_phase_transition,
test_nyumba_tracking,
test_capture_handling,
test_board_display,
test_imports,
]
passed = 0
failed = 0
for test in tests:
try:
test()
passed += 1
except Exception as e:
print(f"✗ {test.__name__} FAILED: {e}")
failed += 1
print(f"\n{'='*60}")
print(f"Results: {passed} passed, {failed} failed, {passed + failed} total")
return failed == 0
if __name__ == "__main__":
success = run_all()
sys.exit(0 if success else 1)