diff --git a/src/shellmate/ssh/server.py b/src/shellmate/ssh/server.py index 6d6ba49..9ef2217 100644 --- a/src/shellmate/ssh/server.py +++ b/src/shellmate/ssh/server.py @@ -275,139 +275,108 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon except Exception as e: logger.warning(f"Stockfish not available: {e}") - # Unicode pieces - large and visible + # Unicode pieces PIECES = { 'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙', 'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟', } def render_board(): + """Clean Gambit-style board rendering - simple and elegant.""" nonlocal status_msg session._update_size() session.clear() - writer = ProcessWriter(session) - console = Console(file=writer, width=session.width, height=session.height, - force_terminal=True, color_system="truecolor") - - # 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("\033[1;32m♔ S H E L L M A T E ♔\033[0m") 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]") - - 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]") + # Column labels + lines.append(" A B C D E F G H") + # Top border + lines.append(" ┌───┬───┬───┬───┬───┬───┬───┬───┐") + + for rank in range(7, -1, -1): + row = f" \033[36m{rank + 1}\033[0m │" + + for file in range(8): + square = chess.square(file, rank) + piece = board.piece_at(square) + is_light = (rank + file) % 2 == 1 + + if piece: + char = PIECES.get(piece.symbol(), '?') + # White pieces bright, black pieces yellow + if piece.color == chess.WHITE: + row += f" \033[1;37m{char}\033[0m │" + else: + row += f" \033[1;33m{char}\033[0m │" + else: + # Dot for dark squares, space for light + if is_light: + row += " │" + else: + row += " \033[90m·\033[0m │" + + row += f" \033[36m{rank + 1}\033[0m" + lines.append(row) + + # Row separator + if rank > 0: + lines.append(" ├───┼───┼───┼───┼───┼───┼───┼───┤") + + # Bottom border + lines.append(" └───┴───┴───┴───┴───┴───┴───┴───┘") + + # Column labels again + lines.append(" A B C D E F G H") lines.append("") # 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.turn == chess.WHITE: + lines.append(" \033[1;37mWhite ♔\033[0m to move") + else: + lines.append(" \033[1;33mBlack ♚\033[0m to move") if board.is_check(): - lines.append(" [bold red]⚠ CHECK! ⚠[/bold red]") + lines.append(" \033[1;31m⚠ CHECK! ⚠\033[0m") if move_history: last_moves = move_history[-5:] - lines.append(f" [dim]Recent: {' '.join(last_moves)}[/dim]") + lines.append(f" \033[90mRecent: {' '.join(last_moves)}\033[0m") if status_msg: - lines.append(f" [bright_yellow]{status_msg}[/bright_yellow]") + lines.append(f" \033[33m{status_msg}\033[0m") status_msg = "" 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(" \033[32mMove:\033[0m e2e4 \033[90m│\033[0m \033[31mq\033[0muit \033[90m│\033[0m \033[31mr\033[0mesign") lines.append("") - # Vertical center - total_lines = len(lines) - top_pad = max(0, (session.height - total_lines - 2) // 2) + # Calculate centering + board_width = 37 # Width of the board + total_height = len(lines) + # Horizontal padding + left_pad = max(0, (session.width - board_width) // 2) + pad = " " * left_pad + + # Vertical centering + top_pad = max(0, (session.height - total_height) // 2) + + # Output for _ in range(top_pad): - console.print() + session.write("\r\n") for line in lines: - console.print(Align.center(Text.from_markup(line))) + session.write(pad + line + "\r\n") # Move prompt - pad = " " * max(0, (session.width - 20) // 2) - session.write(f"{pad}[cyan]Move: [/cyan]") + session.write(pad + " \033[36mMove: \033[0m") session.show_cursor() render_board()