electro-sb's picture
first commit
100a6dd
# chess_engine/api/cli.py
import chess
import argparse
import sys
import os
from typing import Optional, Dict, Any
import time
from chess_engine.api.game_controller import GameController, GameOptions
from chess_engine.ai.stockfish_wrapper import DifficultyLevel
class ChessCLI:
"""Command-line interface for chess engine"""
def __init__(self):
self.controller = None
self.options = None
def setup_game(self, args):
"""Setup game with command line arguments"""
# Parse color
player_color = chess.WHITE
if args.color and args.color.lower() == 'black':
player_color = chess.BLACK
# Parse difficulty
difficulty_map = {
'beginner': DifficultyLevel.BEGINNER,
'easy': DifficultyLevel.EASY,
'medium': DifficultyLevel.MEDIUM,
'hard': DifficultyLevel.HARD,
'expert': DifficultyLevel.EXPERT,
'master': DifficultyLevel.MASTER
}
difficulty = difficulty_map.get(args.difficulty.lower(), DifficultyLevel.MEDIUM)
# Create options
self.options = GameOptions(
player_color=player_color,
difficulty=difficulty,
time_limit=args.time,
use_opening_book=not args.no_book,
enable_analysis=not args.no_analysis,
stockfish_path=args.stockfish_path
)
# Create controller
self.controller = GameController(self.options)
def print_board(self, unicode: bool = True):
"""Print chess board to console"""
board = self.controller.board.board
# Get board as string
if unicode:
board_str = str(board)
else:
board_str = board.unicode()
print("\n" + board_str + "\n")
# Print whose turn it is
turn = "White" if board.turn == chess.WHITE else "Black"
print(f"{turn} to move")
# Print check status
if board.is_check():
print("CHECK!")
def print_analysis(self, analysis: Dict[str, Any]):
"""Print position analysis"""
if not analysis:
return
eval_data = analysis.get("evaluation", {})
best_moves = analysis.get("best_moves", [])
print("\nPosition Analysis:")
print(f"Evaluation: {eval_data.get('total', 0):.2f} pawns")
print(f"Stockfish: {eval_data.get('stockfish', 0):.2f} pawns")
if best_moves:
print("\nBest moves:")
for i, (move, eval_score) in enumerate(best_moves[:3], 1):
print(f"{i}. {move} ({eval_score:.2f})")
def run(self):
"""Run the CLI game loop"""
# Start new game
result = self.controller.start_new_game()
if result["status"] == "error":
print(f"Error: {result['message']}")
return
print("Chess game started!")
print("Enter moves in UCI notation (e.g., 'e2e4') or SAN (e.g., 'e4')")
print("Commands: 'quit', 'help', 'hint', 'undo', 'board', 'resign'")
self.print_board()
# Game loop
while True:
# Get user input
try:
user_input = input("\nYour move: ").strip()
except (KeyboardInterrupt, EOFError):
print("\nGame terminated.")
break
# Process commands
if user_input.lower() in ('quit', 'exit', 'q'):
print("Game terminated.")
break
elif user_input.lower() == 'help':
print("\nCommands:")
print(" <move> - Make a move (e.g., 'e2e4' or 'e4')")
print(" board - Display the board")
print(" hint - Get a move suggestion")
print(" undo - Undo last move")
print(" resign - Resign the game")
print(" quit - Exit the game")
continue
elif user_input.lower() == 'board':
self.print_board()
continue
elif user_input.lower() == 'hint':
hint_result = self.controller.get_hint()
if hint_result["status"] == "success":
print(f"\nHint: {hint_result['hint']}")
print(f"Explanation: {hint_result['explanation']}")
else:
print(f"Error: {hint_result['message']}")
continue
elif user_input.lower() == 'undo':
undo_result = self.controller.undo_move()
if undo_result["status"] in ("success", "partial"):
print("Move undone.")
self.print_board()
else:
print(f"Error: {undo_result['message']}")
continue
elif user_input.lower() == 'resign':
resign_result = self.controller.resign()
print("You resigned. Game over.")
break
# Process move
move_result = self.controller.make_player_move(user_input)
if move_result["status"] == "error":
print(f"Error: {move_result['message']}")
continue
# Print board after move
self.print_board()
# Print AI's move
if "ai_move" in move_result and move_result["ai_move"]:
print(f"AI plays: {move_result['ai_move']}")
# Print analysis if available
if "analysis" in move_result and move_result["analysis"]:
self.print_analysis(move_result["analysis"])
# Check if game is over
if move_result["status"] == "game_over":
result = move_result["result"]
reason = move_result["reason"]
print("\nGame over!")
if result == "draw":
print(f"Result: Draw ({reason})")
else:
winner = move_result["winner"].capitalize()
print(f"Result: {winner} wins by {reason}")
break
# Clean up
if self.controller:
self.controller.close()
def main(args=None):
"""
Main entry point for CLI
Args:
args: Command-line arguments (optional, parsed from sys.argv if None)
"""
if args is None:
# Parse arguments only if not provided
parser = argparse.ArgumentParser(description="Chess Engine CLI")
parser.add_argument("--color", "-c", choices=["white", "black"], default="white",
help="Player's color (default: white)")
parser.add_argument("--difficulty", "-d",
choices=["beginner", "easy", "medium", "hard", "expert", "master"],
default="medium", help="AI difficulty level (default: medium)")
parser.add_argument("--time", "-t", type=float, default=1.0,
help="Time limit for AI moves in seconds (default: 1.0)")
parser.add_argument("--stockfish-path", "-s", type=str, default=None,
help="Path to Stockfish executable")
parser.add_argument("--no-book", action="store_true",
help="Disable opening book")
parser.add_argument("--no-analysis", action="store_true",
help="Disable position analysis")
args = parser.parse_args()
cli = ChessCLI()
cli.setup_game(args)
cli.run()
if __name__ == "__main__":
main()