From ed484e27a2a760cc93a2ff79f1355232e4c7bc17 Mon Sep 17 00:00:00 2001 From: Greg Hendrickson Date: Tue, 27 Jan 2026 18:53:54 +0000 Subject: [PATCH] Simplify chess board rendering - plain text that works over SSH - Remove complex Rich styling from board (caused rendering issues) - Use simple ASCII box drawing (+---+) instead of Unicode box chars - Use dots for dark squares, spaces for light - Plain text status display - Much more reliable over SSH terminals --- src/shellmate/ssh/server.py | 92 ++++++++++++++----------------------- tests/test_ui_render.py | 36 +++++++-------- 2 files changed, 52 insertions(+), 76 deletions(-) diff --git a/src/shellmate/ssh/server.py b/src/shellmate/ssh/server.py index 2bdf392..4e0fc9f 100644 --- a/src/shellmate/ssh/server.py +++ b/src/shellmate/ssh/server.py @@ -272,88 +272,66 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon def render_board(): nonlocal status_msg - writer = ProcessWriter(session) - console = Console(file=writer, width=session.width, height=session.height, force_terminal=True) - session.clear() - # Calculate centering - board_width = 42 - left_pad = max(0, (session.width - board_width) // 2) - pad = " " * left_pad - - # Title - console.print() - console.print(Align.center(Text("♔ SHELLMATE CHESS ♔", style="bold cyan"))) - console.print() - - # Board with better colors - console.print(f"{pad} a b c d e f g h") - console.print(f"{pad} ┌───┬───┬───┬───┬───┬───┬───┬───┐") + # Simple text-based board that works reliably over SSH + lines = [] + lines.append("") + lines.append(" ♔ SHELLMATE CHESS ♔") + lines.append("") + lines.append(" a b c d e f g h") + lines.append(" +---+---+---+---+---+---+---+---+") for rank in range(7, -1, -1): - row = f"{pad}{rank + 1} │" + row = f" {rank + 1} |" for file in range(8): square = chess.square(file, rank) piece = board.piece_at(square) - is_light = (rank + file) % 2 == 1 - # Green theme for board - if is_light: - bg = "on #769656" # Light green - else: - bg = "on #4a7c3f" # Dark green - if piece: char = PIECES.get(piece.symbol(), '?') - # White pieces in white/cream, black pieces in dark - if piece.color == chess.WHITE: - fg = "#ffffff bold" - else: - fg = "#1a1a1a bold" - row += f"[{fg} {bg}] {char} [/]│" + row += f" {char} |" else: - row += f"[{bg}] [/]│" + # Use dots for dark squares, spaces for light + is_light = (rank + file) % 2 == 1 + row += " . |" if not is_light else " |" row += f" {rank + 1}" - console.print(row) - - if rank > 0: - console.print(f"{pad} ├───┼───┼───┼───┼───┼───┼───┼───┤") + lines.append(row) + lines.append(" +---+---+---+---+---+---+---+---+") - console.print(f"{pad} └───┴───┴───┴───┴───┴───┴───┴───┘") - console.print(f"{pad} a b c d e f g h") - console.print() + lines.append(" a b c d e f g h") + lines.append("") - # Status panel + # Center the board + board_width = 42 + left_pad = max(0, (session.width - board_width) // 2) + pad = " " * left_pad + + for line in lines: + session.write(pad + line + "\r\n") + + # Use Rich only for the status panel (simpler) + + # Simple status display turn = "White ♔" if board.turn == chess.WHITE else "Black ♚" - turn_style = "bold white" if board.turn == chess.WHITE else "bold" - - status_lines = [] - status_lines.append(f"[{turn_style}]{turn} to move[/]") + session.write(pad + f" {turn} to move\r\n") if board.is_check(): - status_lines.append("[red bold]⚠️ CHECK![/red bold]") + session.write(pad + " *** CHECK! ***\r\n") if move_history: last_moves = move_history[-3:] - status_lines.append(f"[dim]Moves: {' '.join(last_moves)}[/dim]") + session.write(pad + f" Moves: {' '.join(last_moves)}\r\n") if status_msg: - status_lines.append(f"[yellow]{status_msg}[/yellow]") + session.write(pad + f" {status_msg}\r\n") status_msg = "" - status_lines.append("") - status_lines.append("[dim]Enter move (e.g. e2e4) │ [q]uit │ [r]esign[/dim]") - - status_panel = Panel( - "\n".join(status_lines), - box=ROUNDED, - border_style="blue", - width=min(50, session.width - 4), - title="[bold]Game Status[/bold]" - ) - console.print(Align.center(status_panel)) + session.write("\r\n") + session.write(pad + " Enter move (e.g. e2e4) | [q]uit | [r]esign\r\n") + session.write(pad + " Move: ") + session.show_cursor() # Show cursor for input session.show_cursor() diff --git a/tests/test_ui_render.py b/tests/test_ui_render.py index 4ed5708..9a8b229 100644 --- a/tests/test_ui_render.py +++ b/tests/test_ui_render.py @@ -92,36 +92,34 @@ def test_game_status_render(): def test_chess_board_render(): """Test that chess board renders without errors.""" output = StringIO() - console = Console(file=output, width=80, height=24, force_terminal=True, color_system="truecolor") PIECES = { 'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙', 'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟', } - # Render a simple board section - console.print(" a b c d e f g h") - console.print(" ┌───┬───┬───┬───┬───┬───┬───┬───┐") - - # Render one rank with colored squares - row = "8 │" - for file in range(8): - is_light = (7 + file) % 2 == 1 - if is_light: - bg = "on #769656" - else: - bg = "on #4a7c3f" - - # Starting position pieces - pieces_row = ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'] - char = PIECES.get(pieces_row[file], ' ') - row += f"[#1a1a1a bold {bg}] {char} [/]│" + # Simplified board rendering (plain text) + lines = [] + lines.append(" a b c d e f g h") + lines.append(" +---+---+---+---+---+---+---+---+") + # Render rank 8 with pieces + pieces_row = ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'] + row = " 8 |" + for file, piece_char in enumerate(pieces_row): + char = PIECES.get(piece_char, '?') + row += f" {char} |" row += " 8" - console.print(row) + lines.append(row) + lines.append(" +---+---+---+---+---+---+---+---+") + + for line in lines: + output.write(line + "\n") rendered = output.getvalue() assert "a b c" in rendered + assert "♜" in rendered # Black rook + assert "+---+" in rendered def test_narrow_terminal_render():