diff --git a/src/shellmate/ssh/server.py b/src/shellmate/ssh/server.py index 8424ccc..6d6ba49 100644 --- a/src/shellmate/ssh/server.py +++ b/src/shellmate/ssh/server.py @@ -275,7 +275,7 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon except Exception as e: logger.warning(f"Stockfish not available: {e}") - # Unicode pieces with better styling + # Unicode pieces - large and visible PIECES = { 'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙', 'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟', @@ -283,73 +283,133 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon def render_board(): nonlocal status_msg + session._update_size() session.clear() - # Simple text-based board that works reliably over SSH - board_lines = [] - board_lines.append(" ♔ SHELLMATE CHESS ♔") - board_lines.append("") - board_lines.append(" a b c d e f g h") - board_lines.append(" +---+---+---+---+---+---+---+---+") + writer = ProcessWriter(session) + console = Console(file=writer, width=session.width, height=session.height, + force_terminal=True, color_system="truecolor") - for rank in range(7, -1, -1): - row = f" {rank + 1} |" - for file in range(8): - square = chess.square(file, rank) - piece = board.piece_at(square) - - if piece: - char = PIECES.get(piece.symbol(), '?') - row += f" {char} |" - else: - # Use dots for dark squares, spaces for light - is_light = (rank + file) % 2 == 1 - row += " . |" if not is_light else " |" + # Decide on board size based on terminal + use_big = session.width >= 70 and session.height >= 35 + + lines = [] + + # Title + lines.append("") + lines.append("[bold bright_green]♔ S H E L L M A T E C H E S S ♔[/bold bright_green]") + lines.append("[bright_blue]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[/bright_blue]") + lines.append("") + + if use_big: + # BIG BEAUTIFUL BOARD (5-char wide cells, 2 lines tall) + col_labels = " " + "".join(f" {c} " for c in "abcdefgh") + lines.append(f"[bold cyan]{col_labels}[/bold cyan]") + lines.append("[bright_blue] ╔══════╤══════╤══════╤══════╤══════╤══════╤══════╤══════╗[/bright_blue]") - row += f" {rank + 1}" - board_lines.append(row) - board_lines.append(" +---+---+---+---+---+---+---+---+") + for rank in range(7, -1, -1): + top_row = f"[bold cyan] {rank + 1} [/bold cyan][bright_blue]║[/bright_blue]" + bot_row = f" [bright_blue]║[/bright_blue]" + + for file in range(8): + square = chess.square(file, rank) + piece = board.piece_at(square) + is_light = (rank + file) % 2 == 1 + bg = "on grey30" if is_light else "on grey15" + + if piece: + char = PIECES.get(piece.symbol(), '?') + fg = "bold white" if piece.color == chess.WHITE else "bold bright_yellow" + top_row += f"[{fg} {bg}] {char} [/{fg} {bg}]" + bot_row += f"[{bg}] [/{bg}]" + else: + top_row += f"[{bg}] [/{bg}]" + bot_row += f"[{bg}] [/{bg}]" + + if file < 7: + top_row += "[bright_blue]│[/bright_blue]" + bot_row += "[bright_blue]│[/bright_blue]" + + top_row += f"[bright_blue]║[/bright_blue][bold cyan] {rank + 1}[/bold cyan]" + bot_row += "[bright_blue]║[/bright_blue]" + lines.append(top_row) + lines.append(bot_row) + + if rank > 0: + lines.append("[bright_blue] ╟──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────╢[/bright_blue]") + + lines.append("[bright_blue] ╚══════╧══════╧══════╧══════╧══════╧══════╧══════╧══════╝[/bright_blue]") + lines.append(f"[bold cyan]{col_labels}[/bold cyan]") + else: + # COMPACT BOARD (4-char wide cells) + col_labels = " " + "".join(f" {c} " for c in "abcdefgh") + lines.append(f"[bold cyan]{col_labels}[/bold cyan]") + lines.append("[bright_blue] ╔════╤════╤════╤════╤════╤════╤════╤════╗[/bright_blue]") + + for rank in range(7, -1, -1): + row = f"[bold cyan] {rank + 1} [/bold cyan][bright_blue]║[/bright_blue]" + + for file in range(8): + square = chess.square(file, rank) + piece = board.piece_at(square) + is_light = (rank + file) % 2 == 1 + bg = "on grey30" if is_light else "on grey15" + + if piece: + char = PIECES.get(piece.symbol(), '?') + fg = "bold white" if piece.color == chess.WHITE else "bold bright_yellow" + row += f"[{fg} {bg}] {char} [/{fg} {bg}]" + else: + row += f"[{bg}] [/{bg}]" + + if file < 7: + row += "[bright_blue]│[/bright_blue]" + + row += f"[bright_blue]║[/bright_blue][bold cyan] {rank + 1}[/bold cyan]" + lines.append(row) + + if rank > 0: + lines.append("[bright_blue] ╟────┼────┼────┼────┼────┼────┼────┼────╢[/bright_blue]") + + lines.append("[bright_blue] ╚════╧════╧════╧════╧════╧════╧════╧════╝[/bright_blue]") + lines.append(f"[bold cyan]{col_labels}[/bold cyan]") - board_lines.append(" a b c d e f g h") + lines.append("") - # Add status info - board_lines.append("") - turn = "White ♔" if board.turn == chess.WHITE else "Black ♚" - board_lines.append(f" {turn} to move") + # Status + turn = "[bold white]White ♔[/bold white]" if board.turn == chess.WHITE else "[bold bright_yellow]Black ♚[/bold bright_yellow]" + lines.append(f" {turn} to move") if board.is_check(): - board_lines.append(" *** CHECK! ***") + lines.append(" [bold red]⚠ CHECK! ⚠[/bold red]") if move_history: - last_moves = move_history[-3:] - board_lines.append(f" Moves: {' '.join(last_moves)}") + last_moves = move_history[-5:] + lines.append(f" [dim]Recent: {' '.join(last_moves)}[/dim]") if status_msg: - board_lines.append(f" {status_msg}") + lines.append(f" [bright_yellow]{status_msg}[/bright_yellow]") status_msg = "" - board_lines.append("") - board_lines.append(" Enter move (e.g. e2e4) | [q]uit | [r]esign") - board_lines.append(" Move: ") + lines.append("") + lines.append(" [green]Enter move (e.g. e2e4)[/green] [dim]│[/dim] [red]q[/red]uit [dim]│[/dim] [red]r[/red]esign") + lines.append("") - # Calculate centering - board_width = 42 - total_height = len(board_lines) + # Vertical center + total_lines = len(lines) + top_pad = max(0, (session.height - total_lines - 2) // 2) - # Horizontal centering - left_pad = max(0, (session.width - board_width) // 2) - pad = " " * left_pad - - # Vertical centering - top_pad = max(0, (session.height - total_height) // 2) - - # Output with centering for _ in range(top_pad): - session.write("\r\n") + console.print() - for line in board_lines: - session.write(pad + line + "\r\n") + for line in lines: + console.print(Align.center(Text.from_markup(line))) + # Move prompt + pad = " " * max(0, (session.width - 20) // 2) + session.write(f"{pad}[cyan]Move: [/cyan]") + session.show_cursor() + render_board() move_buffer = ""