250 lines
12 KiB
Python
250 lines
12 KiB
Python
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)
|
|
|