import curses import chess import chess.engine import os import random # Initialize Stockfish engine stockfish_path = "/usr/games/stockfish" engine = chess.engine.SimpleEngine.popen_uci(stockfish_path) # Define ASCII/Nerd Font pieces PIECE_SYMBOLS = { 'P': '♙', 'N': '♘', 'B': '♗', 'R': '♖', 'Q': '♕', 'K': '♔', 'p': '♟', 'n': '♞', 'b': '♝', 'r': '♜', 'q': '♛', 'k': '♚' } def draw_board(stdscr, board, cell_width, cell_height, start_y, start_x, selected_square=None, move_history=[], legal_moves=[], player_color=None): stdscr.clear() max_y, max_x = stdscr.getmaxyx() # Draw the move history move_history_y = 2 stdscr.addstr(0, 0, "Move History:") for index, move in enumerate(move_history[-(max_y // 2 - 2):]): # Display limited history stdscr.addstr(move_history_y + index, 0, move) # Draw the chessboard oriented towards the player for board_y in range(8): actual_row = board_y if player_color == chess.BLACK else 7 - board_y # Orient the row correctly based on player color for board_x in range(8): actual_col = board_x if player_color == chess.WHITE else 7 - board_x # Orient the column correctly piece = board.piece_at(chess.square(actual_col, actual_row)) display_piece = PIECE_SYMBOLS[piece.symbol()] if piece else ' ' # Determine square color square_color = curses.color_pair(1) if (actual_row + actual_col) % 2 == 0 else curses.color_pair(2) is_selected = (selected_square == chess.square(actual_col, actual_row)) is_legal_move = chess.square(actual_col, actual_row) in legal_moves if is_selected: square_color = curses.color_pair(3) # Highlight selected square elif is_legal_move: square_color = curses.color_pair(4) # Highlight legal move square stdscr.attron(square_color) # Ensure that the piece is drawn centered in the cell for line in range(cell_height): if line == cell_height // 2: # Only draw the piece on the middle line cell_content = display_piece.center(cell_width) else: cell_content = ' ' * cell_width # Clear remaining lines in the cell try: stdscr.addstr(start_y + board_y * cell_height + line, start_x + board_x * cell_width, cell_content) except curses.error: pass stdscr.attroff(square_color) stdscr.refresh() def get_square_from_position(mouse_x, mouse_y, cell_width, cell_height, start_x, start_y, player_color): row = (mouse_y - start_y) // cell_height col = (mouse_x - start_x) // cell_width if player_color == chess.BLACK: col = 7 - col # Reverse column for black orientation if player_color == chess.WHITE: row = 7 - row # Reverse row for white orientation if 0 <= row < 8 and 0 <= col < 8: return chess.square(col, row) return None def main(stdscr): curses.curs_set(0) stdscr.clear() curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION) curses.start_color() curses.init_pair(1, curses.COLOR_WHITE, 94) # Light brown background curses.init_pair(2, curses.COLOR_BLACK, 58) # Dark brown background curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE) # Highlight selected square in grey curses.init_pair(4, curses.COLOR_GREEN, curses.COLOR_WHITE) # Highlight legal move square # Get difficulty input stdscr.addstr(0, 0, "Enter difficulty level (1-20): ") stdscr.refresh() while True: try: difficulty_input = stdscr.getstr().decode().strip() difficulty = int(difficulty_input) if 1 <= difficulty <= 20: break else: stdscr.addstr(1, 0, "Invalid input. Please enter a number between 1 and 20.") except ValueError: stdscr.addstr(1, 0, "Invalid input. Please enter a valid integer.") stdscr.refresh() stdscr.getch() stdscr.clear() stdscr.addstr(0, 0, "Enter difficulty level (1-20): ") # Randomly assign player color player_color = chess.WHITE if random.choice([True, False]) else chess.BLACK player_side = "White" if player_color == chess.WHITE else "Black" stdscr.clear() stdscr.addstr(0, 0, f"You are playing as: {player_side}") stdscr.refresh() stdscr.getch() board = chess.Board() move_history = [] undo_stack = [] redo_stack = [] cell_width = 5 # Adjusted size for larger icons cell_height = 3 selected_square = None piece_selected = False moving_mode = False legal_moves = [] original_square = None while True: rows, cols = stdscr.getmaxyx() cell_width = max(5, min(6, (cols - 20) // 16)) # Ensure room for the move history cell_height = 3 start_y = (rows - cell_height * 8) // 2 start_x = 20 # Start drawing the board from column 20 to make space for move history draw_board(stdscr, board, cell_width, cell_height, start_y, start_x, selected_square, move_history, legal_moves, player_color) if board.is_game_over(): stdscr.addstr(rows - 1, 0, "Game Over! Press any key to exit.") stdscr.getch() break player_turn = board.turn == player_color while player_turn: try: key = stdscr.getch() if key == ord('q'): engine.quit() return if key == ord('u'): # Undo both your move and the computer's move if len(board.move_stack) > 1: move1 = board.pop() move2 = board.pop() undo_stack.append((move1, move2)) redo_stack.clear() # Clear redo stack as we made a new undo move_history.pop() move_history.pop() draw_board(stdscr, board, cell_width, cell_height, start_y, start_x, selected_square, move_history, legal_moves, player_color) break if key == ord('r'): # Redo both your move and the computer's move if len(undo_stack) > 0: move1, move2 = undo_stack.pop() board.push(move2) # Push computer's move first board.push(move1) # Push your move redo_stack.append((move1, move2)) move_history.append(f"{'White' if player_color == chess.WHITE else 'Black'}: {move1}") move_history.append(f"{'Black' if player_color == chess.WHITE else 'White'}: {move2}") draw_board(stdscr, board, cell_width, cell_height, start_y, start_x, selected_square, move_history, legal_moves, player_color) break if key == curses.KEY_MOUSE: _, mouse_x, mouse_y, _, button_state = curses.getmouse() if button_state & curses.BUTTON1_CLICKED: square = get_square_from_position(mouse_x, mouse_y, cell_width, cell_height, start_x, start_y, player_color) if square is not None: if not piece_selected and board.piece_at(square) and board.piece_at(square).color == player_color: selected_square = square original_square = selected_square piece_selected = True moving_mode = False legal_moves = [move.to_square for move in board.legal_moves if move.from_square == selected_square] draw_board(stdscr, board, cell_width, cell_height, start_y, start_x, selected_square, move_history, legal_moves, player_color) elif piece_selected and selected_square != square: move = chess.Move(selected_square, square) if move in board.legal_moves: board.push(move) move_history.append(f"{player_side}: {move}") selected_square = None piece_selected = False redo_stack.clear() # Clear redo stack when a new move is made player_turn = False break selected_square = None piece_selected = False elif piece_selected: if key in [curses.KEY_UP, curses.KEY_DOWN, curses.KEY_LEFT, curses.KEY_RIGHT]: rank = chess.square_rank(selected_square) file = chess.square_file(selected_square) if key == curses.KEY_UP: rank += 1 if player_color == chess.WHITE else -1 elif key == curses.KEY_DOWN: rank -= 1 if player_color == chess.WHITE else 1 elif key == curses.KEY_LEFT: file -= 1 if player_color == chess.WHITE else -1 elif key == curses.KEY_RIGHT: file += 1 if player_color == chess.WHITE else -1 if 0 <= rank < 8 and 0 <= file < 8: selected_square = chess.square(file, rank) draw_board(stdscr, board, cell_width, cell_height, start_y, start_x, selected_square, move_history, legal_moves, player_color) elif key == ord('\n'): if not moving_mode: moving_mode = True legal_moves = [move.to_square for move in board.legal_moves if move.from_square == selected_square] original_square = selected_square draw_board(stdscr, board, cell_width, cell_height, start_y, start_x, selected_square, move_history, legal_moves, player_color) else: if selected_square != original_square: move = chess.Move(original_square, selected_square) if move in board.legal_moves: board.push(move) move_history.append(f"{player_side}: {move}") selected_square = None piece_selected = False moving_mode = False redo_stack.clear() # Clear the redo stack when a new move is made player_turn = False break selected_square = original_square moving_mode = False except curses.error: pass if not player_turn: result = engine.play(board, chess.engine.Limit(time=1.0 + (difficulty - 1) * 0.1)) board.push(result.move) move_history.append(f"{'White' if not player_color else 'Black'}: {result.move}") undo_stack.clear() # Clear undo stack after computer move engine.quit() if __name__ == "__main__": curses.wrapper(main)