Files
dark_hal/test_chess_integration.py

270 lines
9.3 KiB
Python
Raw Permalink Normal View History

2026-03-13 12:56:43 -07:00
#!/usr/bin/env python3
"""
Integration test for the chess game with LLM.
This simulates the chess logic without the GUI.
"""
import sys
import os
sys.path.append('.')
try:
import chess
print("✓ python-chess imported successfully")
except ImportError:
print("✗ python-chess not available")
sys.exit(1)
class MockSettings:
"""Mock settings manager for testing."""
def __init__(self):
self.settings = {
'paths.last_model_path': '/path/to/model.gguf', # Mock path
'model_settings.default_n_ctx': 4096,
'model_settings.default_n_gpu_layers': 0
}
def get(self, key, default=None):
return self.settings.get(key, default)
class MockLlama:
"""Mock LLM for testing chess integration."""
def __init__(self, *args, **kwargs):
self.moves_to_suggest = ['e2e4', 'g1f3', 'd2d4', 'b1c3'] # Common opening moves
self.call_count = 0
def __call__(self, prompt, **kwargs):
"""Simulate LLM response with chess moves."""
# Cycle through suggested moves
move = self.moves_to_suggest[self.call_count % len(self.moves_to_suggest)]
self.call_count += 1
print(f"Mock LLM responding with: {move}")
return {
'choices': [{'text': move}]
}
class ChessGameSimulator:
"""Simplified chess game simulator based on our implementation."""
def __init__(self):
self.board = chess.Board()
self.settings = MockSettings()
self.llm_cache = None
self.move_history = []
def _get_llm_instance(self):
"""Get mock LLM instance."""
if not self.llm_cache:
# Use mock instead of real LLM
self.llm_cache = MockLlama()
return self.llm_cache
def _parse_move_from_response(self, text):
"""Parse chess move from LLM response using multiple methods."""
text = text.strip().lower()
legal_moves = list(self.board.legal_moves)
legal_uci = [move.uci() for move in legal_moves]
# Method 1: Direct UCI format match
for move_uci in legal_uci:
if move_uci in text:
return chess.Move.from_uci(move_uci)
# Method 2: Look for 4-5 character sequences that could be UCI
import re
uci_pattern = r'\b[a-h][1-8][a-h][1-8][qrbn]?\b'
matches = re.findall(uci_pattern, text)
for match in matches:
if match in legal_uci:
return chess.Move.from_uci(match)
# Method 3: Try to find SAN notation and convert
for move in legal_moves:
san = self.board.san(move).lower()
san_clean = san.replace('+', '').replace('#', '').replace('x', '')
if san_clean in text or san in text:
return move
return None
def _create_chess_prompt(self):
"""Create a chess-specific prompt for the LLM."""
fen = self.board.fen()
legal_moves = [move.uci() for move in self.board.legal_moves]
move_history = [self.board.san(move) for move in self.board.move_stack[-6:]]
ai_color = "white" if self.board.turn == chess.WHITE else "black"
current_turn = "white" if self.board.turn == chess.WHITE else "black"
board_unicode = self.board.unicode()
prompt = f"""You are a professional chess player and you play as {ai_color}. Now is your turn to make a move.
Current board position:
{board_unicode}
Position (FEN): {fen}
Turn: {current_turn}
Recent moves: {' '.join(move_history) if move_history else 'Game start'}
Legal moves available (UCI format): {', '.join(legal_moves)}
As an expert chess player, choose the BEST move considering:
- King safety and piece protection
- Center control and piece development
- Tactical opportunities (captures, forks, pins, skewers)
- Positional advantages
- Endgame principles if material is low
Reply with ONLY the move in UCI format (examples: e2e4, g1f3, e7e8q):"""
return prompt
def _query_llm_for_move(self):
"""Query the LLM for a chess move."""
try:
llm = self._get_llm_instance()
if not llm:
return None
prompt = self._create_chess_prompt()
# Use multiple attempts with different temperatures
max_attempts = 3
temperatures = [0.1, 0.3, 0.5]
for attempt in range(max_attempts):
try:
response = llm(
prompt,
max_tokens=20,
temperature=temperatures[attempt],
stop=["\n", " ", ".", ",", "because", "since", "as", "the"],
echo=False
)
text = response['choices'][0]['text'].strip().lower()
move = self._parse_move_from_response(text)
if move and move in self.board.legal_moves:
print(f"✓ LLM suggested valid move: {move.uci()} (attempt {attempt + 1})")
return move
elif move:
print(f"✗ LLM suggested illegal move: {move.uci()}")
else:
print(f"✗ Could not parse move from response: '{text}'")
except Exception as e:
print(f"✗ LLM attempt {attempt + 1} failed: {e}")
continue
return None
except Exception as e:
print(f"✗ LLM query failed: {e}")
return None
def _get_strategic_move(self, legal_moves):
"""Get strategic move using chess heuristics."""
import random
scored_moves = []
for move in legal_moves:
score = 0
# Prefer central squares
to_square = move.to_square
file = chess.square_file(to_square)
rank = chess.square_rank(to_square)
center_distance = abs(3.5 - file) + abs(3.5 - rank)
score += (7 - center_distance) * 2
# Prefer piece development
piece = self.board.piece_at(move.from_square)
if piece and piece.piece_type in [chess.KNIGHT, chess.BISHOP]:
if chess.square_rank(move.from_square) in [0, 7]:
score += 15
scored_moves.append((move, score))
scored_moves.sort(key=lambda x: x[1] + random.random() * 2, reverse=True)
return scored_moves[0][0]
def get_ai_move(self):
"""Get AI move with LLM integration and fallback."""
legal_moves = list(self.board.legal_moves)
if not legal_moves:
return None
# Try LLM first
llm_move = self._query_llm_for_move()
if llm_move and llm_move in legal_moves:
return llm_move
# Fallback to strategic heuristics
print("Using strategic fallback move")
return self._get_strategic_move(legal_moves)
def make_move(self, move):
"""Make a move on the board."""
if move in self.board.legal_moves:
san = self.board.san(move)
self.board.push(move)
self.move_history.append(san)
return True
return False
def play_moves(self, num_moves=4):
"""Play a few moves to test the integration."""
print(f"\n=== Playing {num_moves} moves ===")
for i in range(num_moves):
if self.board.is_game_over():
print("Game over!")
break
current_turn = "White" if self.board.turn == chess.WHITE else "Black"
print(f"\nMove {i+1} - {current_turn} to play")
print(f"Current position: {self.board.fen()}")
ai_move = self.get_ai_move()
if ai_move:
success = self.make_move(ai_move)
if success:
print(f"✓ Played: {self.move_history[-1]} ({ai_move.uci()})")
else:
print(f"✗ Failed to make move: {ai_move.uci()}")
break
else:
print("✗ No move found")
break
print(f"\nFinal position after {len(self.move_history)} moves:")
print(self.board.unicode())
print(f"Move history: {' '.join(self.move_history)}")
def main():
"""Main test function."""
print("Testing Chess Integration with Mock LLM")
print("=" * 50)
try:
game = ChessGameSimulator()
game.play_moves(6) # Play 6 moves to test the integration
print("\n" + "=" * 50)
print("✓ Chess integration test completed successfully!")
print("\nKey features tested:")
print("- LLM querying with multiple attempts")
print("- Move parsing from LLM responses")
print("- Strategic fallback when LLM fails")
print("- Proper move validation")
print("- Chess position tracking")
except Exception as e:
print(f"✗ Integration test failed: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()