Beautiful big chess board with colors

- Auto-scales: big board for large terminals, compact for smaller
- Dark/light square colors with background shading
- White pieces in white, black pieces in yellow for contrast
- Unicode box-drawing characters for elegant borders
- Centered vertically and horizontally
- Better status display
This commit is contained in:
Greg Hendrickson
2026-01-27 20:27:44 +00:00
parent 1663fe965e
commit 8f8888214a

View File

@@ -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 = ""