mirror of
https://github.com/ghndrx/shellmate.git
synced 2026-02-10 06:45:02 +00:00
Fix all ruff linting errors
- Fix import ordering (isort) - Remove unused imports - Fix line too long (>100 chars) - Fix variable naming (PIECES -> piece_chars) - Fix bare except clause - Remove unused variable
This commit is contained in:
@@ -1,8 +1,7 @@
|
|||||||
"""Stockfish AI engine wrapper with move explanations."""
|
"""Stockfish AI engine wrapper with move explanations."""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
|
||||||
import chess
|
import chess
|
||||||
from stockfish import Stockfish
|
from stockfish import Stockfish
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
|
||||||
import chess
|
import chess
|
||||||
import chess.pgn
|
import chess.pgn
|
||||||
|
|
||||||
@@ -25,8 +25,8 @@ class Move:
|
|||||||
fen_after: str
|
fen_after: str
|
||||||
timestamp: datetime = field(default_factory=datetime.utcnow)
|
timestamp: datetime = field(default_factory=datetime.utcnow)
|
||||||
think_time_ms: int = 0
|
think_time_ms: int = 0
|
||||||
evaluation: Optional[float] = None
|
evaluation: float | None = None
|
||||||
explanation: Optional[str] = None
|
explanation: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -51,7 +51,7 @@ class ChessGame:
|
|||||||
"""Return the ID of the player whose turn it is."""
|
"""Return the ID of the player whose turn it is."""
|
||||||
return self.white_player_id if self.board.turn == chess.WHITE else self.black_player_id
|
return self.white_player_id if self.board.turn == chess.WHITE else self.black_player_id
|
||||||
|
|
||||||
def make_move(self, uci: str) -> Optional[Move]:
|
def make_move(self, uci: str) -> Move | None:
|
||||||
"""Make a move and return Move object if valid."""
|
"""Make a move and return Move object if valid."""
|
||||||
try:
|
try:
|
||||||
chess_move = chess.Move.from_uci(uci)
|
chess_move = chess.Move.from_uci(uci)
|
||||||
@@ -86,7 +86,10 @@ class ChessGame:
|
|||||||
def _check_game_end(self) -> None:
|
def _check_game_end(self) -> None:
|
||||||
"""Check if the game has ended and set result."""
|
"""Check if the game has ended and set result."""
|
||||||
if self.board.is_checkmate():
|
if self.board.is_checkmate():
|
||||||
self.result = GameResult.BLACK_WINS if self.board.turn == chess.WHITE else GameResult.WHITE_WINS
|
if self.board.turn == chess.WHITE:
|
||||||
|
self.result = GameResult.BLACK_WINS
|
||||||
|
else:
|
||||||
|
self.result = GameResult.WHITE_WINS
|
||||||
elif self.board.is_stalemate() or self.board.is_insufficient_material():
|
elif self.board.is_stalemate() or self.board.is_insufficient_material():
|
||||||
self.result = GameResult.DRAW
|
self.result = GameResult.DRAW
|
||||||
elif self.board.can_claim_draw():
|
elif self.board.can_claim_draw():
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
class PlayerType(Enum):
|
class PlayerType(Enum):
|
||||||
@@ -25,7 +24,7 @@ class Player:
|
|||||||
losses: int = 0
|
losses: int = 0
|
||||||
draws: int = 0
|
draws: int = 0
|
||||||
created_at: datetime = field(default_factory=datetime.utcnow)
|
created_at: datetime = field(default_factory=datetime.utcnow)
|
||||||
last_seen: Optional[datetime] = None
|
last_seen: datetime | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def winrate(self) -> float:
|
def winrate(self) -> float:
|
||||||
|
|||||||
@@ -3,11 +3,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
from typing import Optional
|
|
||||||
import asyncssh
|
|
||||||
|
|
||||||
from shellmate.tui.app import ShellMateApp
|
import asyncssh
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -16,13 +13,13 @@ class ShellMateSSHServer(asyncssh.SSHServer):
|
|||||||
"""SSH server that launches ShellMate TUI for each connection."""
|
"""SSH server that launches ShellMate TUI for each connection."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._username: Optional[str] = None
|
self._username: str | None = None
|
||||||
|
|
||||||
def connection_made(self, conn: asyncssh.SSHServerConnection) -> None:
|
def connection_made(self, conn: asyncssh.SSHServerConnection) -> None:
|
||||||
peername = conn.get_extra_info('peername')
|
peername = conn.get_extra_info('peername')
|
||||||
logger.info(f"SSH connection from {peername}")
|
logger.info(f"SSH connection from {peername}")
|
||||||
|
|
||||||
def connection_lost(self, exc: Optional[Exception]) -> None:
|
def connection_lost(self, exc: Exception | None) -> None:
|
||||||
if exc:
|
if exc:
|
||||||
logger.error(f"SSH connection error: {exc}")
|
logger.error(f"SSH connection error: {exc}")
|
||||||
else:
|
else:
|
||||||
@@ -130,15 +127,13 @@ async def handle_client(process: asyncssh.SSHServerProcess) -> None:
|
|||||||
|
|
||||||
async def run_simple_menu(process, session: TerminalSession, username: str, mode: str) -> None:
|
async def run_simple_menu(process, session: TerminalSession, username: str, mode: str) -> None:
|
||||||
"""Beautiful Rich-based menu that scales to terminal size."""
|
"""Beautiful Rich-based menu that scales to terminal size."""
|
||||||
|
|
||||||
|
from rich.align import Align
|
||||||
|
from rich.box import ROUNDED
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.text import Text
|
|
||||||
from rich.align import Align
|
|
||||||
from rich.box import HEAVY, ROUNDED, DOUBLE
|
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
from rich.layout import Layout
|
from rich.text import Text
|
||||||
from rich.style import Style
|
|
||||||
import io
|
|
||||||
|
|
||||||
class ProcessWriter:
|
class ProcessWriter:
|
||||||
def __init__(self, sess):
|
def __init__(self, sess):
|
||||||
@@ -154,7 +149,10 @@ async def run_simple_menu(process, session: TerminalSession, username: str, mode
|
|||||||
session._update_size()
|
session._update_size()
|
||||||
|
|
||||||
writer = ProcessWriter(session)
|
writer = ProcessWriter(session)
|
||||||
console = Console(file=writer, width=session.width, height=session.height, force_terminal=True, color_system="truecolor")
|
console = Console(
|
||||||
|
file=writer, width=session.width, height=session.height,
|
||||||
|
force_terminal=True, color_system="truecolor"
|
||||||
|
)
|
||||||
|
|
||||||
session.clear()
|
session.clear()
|
||||||
|
|
||||||
@@ -186,10 +184,10 @@ async def run_simple_menu(process, session: TerminalSession, username: str, mode
|
|||||||
menu_table.add_column(justify="center")
|
menu_table.add_column(justify="center")
|
||||||
menu_table.add_row(Text(f"Welcome, {username}!", style="cyan"))
|
menu_table.add_row(Text(f"Welcome, {username}!", style="cyan"))
|
||||||
menu_table.add_row("")
|
menu_table.add_row("")
|
||||||
menu_table.add_row("[bright_white on blue] 1 [/bright_white on blue] Play vs AI [dim]♔ vs ♚[/dim]")
|
menu_table.add_row("[bright_white on blue] 1 [/] Play vs AI [dim]♔ vs ♚[/]")
|
||||||
menu_table.add_row("[bright_white on magenta] 2 [/bright_white on magenta] Play vs Human [dim]♔ vs ♔[/dim]")
|
menu_table.add_row("[bright_white on magenta] 2 [/] Play vs Human [dim]♔ vs ♔[/]")
|
||||||
menu_table.add_row("[bright_white on green] 3 [/bright_white on green] Learn & Practice [dim]📖[/dim]")
|
menu_table.add_row("[bright_white on green] 3 [/] Learn & Practice [dim]📖[/]")
|
||||||
menu_table.add_row("[bright_white on red] q [/bright_white on red] Quit [dim]👋[/dim]")
|
menu_table.add_row("[bright_white on red] q [/] Quit [dim]👋[/]")
|
||||||
menu_table.add_row("")
|
menu_table.add_row("")
|
||||||
menu_table.add_row(Text("Press a key to select...", style="dim italic"))
|
menu_table.add_row(Text("Press a key to select...", style="dim italic"))
|
||||||
|
|
||||||
@@ -205,7 +203,8 @@ async def run_simple_menu(process, session: TerminalSession, username: str, mode
|
|||||||
|
|
||||||
# Footer
|
# Footer
|
||||||
console.print()
|
console.print()
|
||||||
console.print(Align.center(Text(f"Terminal: {session.width}×{session.height}", style="dim")))
|
term_info = f"Terminal: {session.width}×{session.height}"
|
||||||
|
console.print(Align.center(Text(term_info, style="dim")))
|
||||||
|
|
||||||
render_menu()
|
render_menu()
|
||||||
|
|
||||||
@@ -248,11 +247,10 @@ async def run_simple_menu(process, session: TerminalSession, username: str, mode
|
|||||||
async def run_chess_game(process, session: TerminalSession, username: str, opponent: str) -> None:
|
async def run_chess_game(process, session: TerminalSession, username: str, opponent: str) -> None:
|
||||||
"""Run a beautiful chess game session with Stockfish AI."""
|
"""Run a beautiful chess game session with Stockfish AI."""
|
||||||
import chess
|
import chess
|
||||||
|
from rich.align import Align
|
||||||
|
from rich.box import ROUNDED
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.align import Align
|
|
||||||
from rich.text import Text
|
|
||||||
from rich.box import ROUNDED
|
|
||||||
|
|
||||||
class ProcessWriter:
|
class ProcessWriter:
|
||||||
def __init__(self, sess):
|
def __init__(self, sess):
|
||||||
@@ -276,7 +274,7 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
|
|||||||
logger.warning(f"Stockfish not available: {e}")
|
logger.warning(f"Stockfish not available: {e}")
|
||||||
|
|
||||||
# Unicode pieces
|
# Unicode pieces
|
||||||
PIECES = {
|
piece_chars = {
|
||||||
'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙',
|
'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙',
|
||||||
'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟',
|
'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟',
|
||||||
}
|
}
|
||||||
@@ -331,7 +329,7 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
|
|||||||
bg, bg_end = get_cell_style(square, piece, is_light)
|
bg, bg_end = get_cell_style(square, piece, is_light)
|
||||||
|
|
||||||
if piece:
|
if piece:
|
||||||
char = PIECES.get(piece.symbol(), '?')
|
char = piece_chars.get(piece.symbol(), '?')
|
||||||
if piece.color == chess.WHITE:
|
if piece.color == chess.WHITE:
|
||||||
piece_row += f"{bg} \033[1;97m{char}\033[0m{bg} {bg_end}"
|
piece_row += f"{bg} \033[1;97m{char}\033[0m{bg} {bg_end}"
|
||||||
else:
|
else:
|
||||||
@@ -378,9 +376,9 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
|
|||||||
# Instructions based on state
|
# Instructions based on state
|
||||||
if selected_square is not None:
|
if selected_square is not None:
|
||||||
sq_name = chess.square_name(selected_square).upper()
|
sq_name = chess.square_name(selected_square).upper()
|
||||||
lines.append(f" \033[36mSelected: {sq_name}\033[0m → Type destination (or ESC to cancel)")
|
lines.append(f" \033[36mSelected: {sq_name}\033[0m → Type dest (ESC cancel)")
|
||||||
else:
|
else:
|
||||||
lines.append(" Type \033[36msquare\033[0m (e.g. E2) to select │ \033[31mQ\033[0m = quit")
|
lines.append(" Type \033[36msquare\033[0m (e.g. E2) │ \033[31mQ\033[0m quit")
|
||||||
|
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
@@ -398,9 +396,9 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
|
|||||||
|
|
||||||
# Input prompt
|
# Input prompt
|
||||||
if selected_square is not None:
|
if selected_square is not None:
|
||||||
session.write(pad + f" \033[32m→ \033[0m")
|
session.write(pad + " \033[32m→ \033[0m")
|
||||||
else:
|
else:
|
||||||
session.write(pad + f" \033[36m> \033[0m")
|
session.write(pad + " \033[36m> \033[0m")
|
||||||
session.show_cursor()
|
session.show_cursor()
|
||||||
|
|
||||||
def parse_square(text):
|
def parse_square(text):
|
||||||
@@ -497,9 +495,14 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
|
|||||||
# Check for promotion
|
# Check for promotion
|
||||||
piece = board.piece_at(selected_square)
|
piece = board.piece_at(selected_square)
|
||||||
if piece and piece.piece_type == chess.PAWN:
|
if piece and piece.piece_type == chess.PAWN:
|
||||||
if (piece.color == chess.WHITE and chess.square_rank(sq) == 7) or \
|
is_white_promo = (piece.color == chess.WHITE and
|
||||||
(piece.color == chess.BLACK and chess.square_rank(sq) == 0):
|
chess.square_rank(sq) == 7)
|
||||||
move = chess.Move(selected_square, sq, promotion=chess.QUEEN)
|
is_black_promo = (piece.color == chess.BLACK and
|
||||||
|
chess.square_rank(sq) == 0)
|
||||||
|
if is_white_promo or is_black_promo:
|
||||||
|
move = chess.Move(
|
||||||
|
selected_square, sq, promotion=chess.QUEEN
|
||||||
|
)
|
||||||
|
|
||||||
board.push(move)
|
board.push(move)
|
||||||
move_history.append(move.uci())
|
move_history.append(move.uci())
|
||||||
@@ -516,7 +519,7 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
|
|||||||
stockfish_engine.set_fen_position(board.fen())
|
stockfish_engine.set_fen_position(board.fen())
|
||||||
best_move = stockfish_engine.get_best_move()
|
best_move = stockfish_engine.get_best_move()
|
||||||
ai_move = chess.Move.from_uci(best_move)
|
ai_move = chess.Move.from_uci(best_move)
|
||||||
except:
|
except Exception:
|
||||||
import random
|
import random
|
||||||
ai_move = random.choice(list(board.legal_moves))
|
ai_move = random.choice(list(board.legal_moves))
|
||||||
else:
|
else:
|
||||||
@@ -560,7 +563,10 @@ async def run_chess_game(process, session: TerminalSession, username: str, oppon
|
|||||||
if board.is_game_over():
|
if board.is_game_over():
|
||||||
session.clear()
|
session.clear()
|
||||||
writer = ProcessWriter(session)
|
writer = ProcessWriter(session)
|
||||||
console = Console(file=writer, width=session.width, height=session.height, force_terminal=True)
|
console = Console(
|
||||||
|
file=writer, width=session.width, height=session.height,
|
||||||
|
force_terminal=True
|
||||||
|
)
|
||||||
|
|
||||||
console.print()
|
console.print()
|
||||||
if board.is_checkmate():
|
if board.is_checkmate():
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
"""Main TUI application."""
|
"""Main TUI application."""
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
from textual.app import App, ComposeResult
|
|
||||||
from textual.containers import Container, Horizontal, Vertical
|
|
||||||
from textual.widgets import Footer, Header, Static
|
|
||||||
from textual.binding import Binding
|
|
||||||
from textual.screen import Screen
|
|
||||||
import chess
|
import chess
|
||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.binding import Binding
|
||||||
|
from textual.containers import Container, Horizontal, Vertical
|
||||||
|
from textual.screen import Screen
|
||||||
|
from textual.widgets import Footer, Header
|
||||||
|
|
||||||
from shellmate.tui.widgets import (
|
from shellmate.tui.widgets import (
|
||||||
ChessBoardWidget,
|
ChessBoardWidget,
|
||||||
MoveInput,
|
|
||||||
MoveListWidget,
|
|
||||||
GameStatusWidget,
|
GameStatusWidget,
|
||||||
MainMenu,
|
MainMenu,
|
||||||
|
MoveInput,
|
||||||
|
MoveListWidget,
|
||||||
)
|
)
|
||||||
from shellmate.tui.widgets.move_input import parse_move
|
from shellmate.tui.widgets.move_input import parse_move
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
"""TUI widgets for ShellMate."""
|
"""TUI widgets for ShellMate."""
|
||||||
|
|
||||||
from .board import ChessBoardWidget
|
from .board import ChessBoardWidget
|
||||||
|
from .menu import MainMenu
|
||||||
from .move_input import MoveInput
|
from .move_input import MoveInput
|
||||||
from .move_list import MoveListWidget
|
from .move_list import MoveListWidget
|
||||||
from .status import GameStatusWidget
|
from .status import GameStatusWidget
|
||||||
from .menu import MainMenu
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"ChessBoardWidget",
|
"ChessBoardWidget",
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
"""Chess board widget with Unicode pieces - Hyper-polished version."""
|
"""Chess board widget with Unicode pieces - Hyper-polished version."""
|
||||||
|
|
||||||
from textual.widget import Widget
|
|
||||||
from textual.reactive import reactive
|
|
||||||
from textual.geometry import Size
|
|
||||||
from rich.text import Text
|
|
||||||
from rich.style import Style
|
|
||||||
from rich.console import RenderableType
|
|
||||||
import chess
|
import chess
|
||||||
|
from rich.console import RenderableType
|
||||||
|
from rich.style import Style
|
||||||
|
from rich.text import Text
|
||||||
|
from textual.geometry import Size
|
||||||
|
from textual.reactive import reactive
|
||||||
|
from textual.widget import Widget
|
||||||
|
|
||||||
# Unicode chess pieces - using filled variants for better visibility
|
# Unicode chess pieces - using filled variants for better visibility
|
||||||
PIECES_FILLED = {
|
PIECES_FILLED = {
|
||||||
@@ -120,7 +119,6 @@ class ChessBoardWidget(Widget):
|
|||||||
def _render_compact(self) -> RenderableType:
|
def _render_compact(self) -> RenderableType:
|
||||||
"""Compact board for small terminals."""
|
"""Compact board for small terminals."""
|
||||||
text = Text()
|
text = Text()
|
||||||
cw = 3 # Fixed cell width for compact
|
|
||||||
|
|
||||||
files = "abcdefgh" if not self.flipped else "hgfedcba"
|
files = "abcdefgh" if not self.flipped else "hgfedcba"
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
"""Main menu widget."""
|
"""Main menu widget."""
|
||||||
|
|
||||||
|
from textual.containers import Vertical
|
||||||
|
from textual.message import Message
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import Button, Static
|
from textual.widgets import Button, Static
|
||||||
from textual.containers import Vertical, Center
|
|
||||||
from textual.message import Message
|
|
||||||
from rich.text import Text
|
|
||||||
from rich.console import RenderableType
|
|
||||||
|
|
||||||
|
|
||||||
class MainMenu(Widget):
|
class MainMenu(Widget):
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""Move input widget."""
|
"""Move input widget."""
|
||||||
|
|
||||||
from textual.widgets import Input
|
|
||||||
from textual.message import Message
|
|
||||||
import chess
|
import chess
|
||||||
|
from textual.message import Message
|
||||||
|
from textual.widgets import Input
|
||||||
|
|
||||||
|
|
||||||
class MoveInput(Input):
|
class MoveInput(Input):
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
"""Move list/history widget."""
|
"""Move list/history widget."""
|
||||||
|
|
||||||
from textual.widget import Widget
|
|
||||||
from textual.reactive import reactive
|
|
||||||
from rich.text import Text
|
|
||||||
from rich.console import RenderableType
|
from rich.console import RenderableType
|
||||||
import chess
|
from rich.text import Text
|
||||||
|
from textual.reactive import reactive
|
||||||
|
from textual.widget import Widget
|
||||||
|
|
||||||
|
|
||||||
class MoveListWidget(Widget):
|
class MoveListWidget(Widget):
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
"""Game status widget."""
|
"""Game status widget."""
|
||||||
|
|
||||||
from textual.widget import Widget
|
|
||||||
from textual.reactive import reactive
|
|
||||||
from rich.text import Text
|
|
||||||
from rich.panel import Panel
|
|
||||||
from rich.console import RenderableType
|
|
||||||
import chess
|
import chess
|
||||||
|
from rich.console import RenderableType
|
||||||
|
from rich.text import Text
|
||||||
|
from textual.widget import Widget
|
||||||
|
|
||||||
|
|
||||||
class GameStatusWidget(Widget):
|
class GameStatusWidget(Widget):
|
||||||
@@ -43,7 +41,7 @@ class GameStatusWidget(Widget):
|
|||||||
|
|
||||||
if self._is_checkmate:
|
if self._is_checkmate:
|
||||||
winner = self._black_name if self._turn == chess.WHITE else self._white_name
|
winner = self._black_name if self._turn == chess.WHITE else self._white_name
|
||||||
text.append(f"♚ CHECKMATE!\n", style="bold red")
|
text.append("♚ CHECKMATE!\n", style="bold red")
|
||||||
text.append(f"{winner} wins!", style="bold yellow")
|
text.append(f"{winner} wins!", style="bold yellow")
|
||||||
elif self._is_stalemate:
|
elif self._is_stalemate:
|
||||||
text.append("½ STALEMATE\n", style="bold yellow")
|
text.append("½ STALEMATE\n", style="bold yellow")
|
||||||
@@ -70,9 +68,13 @@ class GameStatusWidget(Widget):
|
|||||||
text.append(f"⚫ {black_time_str}", style=black_style)
|
text.append(f"⚫ {black_time_str}", style=black_style)
|
||||||
|
|
||||||
# Evaluation (if available)
|
# Evaluation (if available)
|
||||||
if self._evaluation is not None and not (self._is_checkmate or self._is_stalemate or self._is_draw):
|
game_ended = self._is_checkmate or self._is_stalemate or self._is_draw
|
||||||
|
if self._evaluation is not None and not game_ended:
|
||||||
text.append("\n\n")
|
text.append("\n\n")
|
||||||
eval_str = f"+{self._evaluation:.1f}" if self._evaluation > 0 else f"{self._evaluation:.1f}"
|
if self._evaluation > 0:
|
||||||
|
eval_str = f"+{self._evaluation:.1f}"
|
||||||
|
else:
|
||||||
|
eval_str = f"{self._evaluation:.1f}"
|
||||||
bar = self._eval_bar(self._evaluation)
|
bar = self._eval_bar(self._evaluation)
|
||||||
text.append(f"Eval: {eval_str}\n", style="dim")
|
text.append(f"Eval: {eval_str}\n", style="dim")
|
||||||
text.append(bar)
|
text.append(bar)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"""Tests for SSH server functionality."""
|
"""Tests for SSH server functionality."""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import pytest
|
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
class TestShellMateSSHServer:
|
class TestShellMateSSHServer:
|
||||||
"""Test SSH server authentication."""
|
"""Test SSH server authentication."""
|
||||||
@@ -113,9 +113,10 @@ class TestChessBoard:
|
|||||||
|
|
||||||
def test_board_initialization(self):
|
def test_board_initialization(self):
|
||||||
"""Board should initialize with standard position."""
|
"""Board should initialize with standard position."""
|
||||||
from shellmate.tui.widgets.board import ChessBoardWidget
|
|
||||||
import chess
|
import chess
|
||||||
|
|
||||||
|
from shellmate.tui.widgets.board import ChessBoardWidget
|
||||||
|
|
||||||
widget = ChessBoardWidget()
|
widget = ChessBoardWidget()
|
||||||
|
|
||||||
assert widget.board.fen() == chess.STARTING_FEN
|
assert widget.board.fen() == chess.STARTING_FEN
|
||||||
@@ -136,9 +137,10 @@ class TestChessBoard:
|
|||||||
|
|
||||||
def test_square_selection(self):
|
def test_square_selection(self):
|
||||||
"""Selecting a square should show legal moves."""
|
"""Selecting a square should show legal moves."""
|
||||||
from shellmate.tui.widgets.board import ChessBoardWidget
|
|
||||||
import chess
|
import chess
|
||||||
|
|
||||||
|
from shellmate.tui.widgets.board import ChessBoardWidget
|
||||||
|
|
||||||
widget = ChessBoardWidget()
|
widget = ChessBoardWidget()
|
||||||
|
|
||||||
# Select e2 pawn
|
# Select e2 pawn
|
||||||
@@ -152,9 +154,10 @@ class TestChessBoard:
|
|||||||
|
|
||||||
def test_square_deselection(self):
|
def test_square_deselection(self):
|
||||||
"""Deselecting should clear legal moves."""
|
"""Deselecting should clear legal moves."""
|
||||||
from shellmate.tui.widgets.board import ChessBoardWidget
|
|
||||||
import chess
|
import chess
|
||||||
|
|
||||||
|
from shellmate.tui.widgets.board import ChessBoardWidget
|
||||||
|
|
||||||
widget = ChessBoardWidget()
|
widget = ChessBoardWidget()
|
||||||
widget.select_square(chess.E2)
|
widget.select_square(chess.E2)
|
||||||
widget.select_square(None)
|
widget.select_square(None)
|
||||||
@@ -251,9 +254,10 @@ class TestIntegration:
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_server_starts(self):
|
async def test_server_starts(self):
|
||||||
"""Server should start without errors."""
|
"""Server should start without errors."""
|
||||||
from shellmate.ssh.server import start_server
|
|
||||||
import tempfile
|
|
||||||
import os
|
import os
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
from shellmate.ssh.server import start_server
|
||||||
|
|
||||||
# Create a temporary host key
|
# Create a temporary host key
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
"""Tests for UI rendering - catches Rich markup errors before deployment."""
|
"""Tests for UI rendering - catches Rich markup errors before deployment."""
|
||||||
|
|
||||||
import pytest
|
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
|
from rich.align import Align
|
||||||
|
from rich.box import ROUNDED
|
||||||
from rich.console import Console
|
from rich.console import Console
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.table import Table
|
from rich.table import Table
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
from rich.align import Align
|
|
||||||
from rich.box import ROUNDED
|
|
||||||
|
|
||||||
|
|
||||||
def test_menu_render_no_markup_errors():
|
def test_menu_render_no_markup_errors():
|
||||||
"""Test that the main menu renders without Rich markup errors."""
|
"""Test that the main menu renders without Rich markup errors."""
|
||||||
# Simulate the menu rendering code
|
# Simulate the menu rendering code
|
||||||
output = StringIO()
|
output = StringIO()
|
||||||
console = Console(file=output, width=80, height=24, force_terminal=True, color_system="truecolor")
|
console = Console(
|
||||||
|
file=output, width=80, height=24, force_terminal=True, color_system="truecolor"
|
||||||
|
)
|
||||||
|
|
||||||
username = "testuser"
|
username = "testuser"
|
||||||
width = 80
|
width = 80
|
||||||
@@ -35,10 +37,18 @@ def test_menu_render_no_markup_errors():
|
|||||||
menu_table.add_column(justify="center")
|
menu_table.add_column(justify="center")
|
||||||
menu_table.add_row(Text(f"Welcome, {username}!", style="cyan"))
|
menu_table.add_row(Text(f"Welcome, {username}!", style="cyan"))
|
||||||
menu_table.add_row("")
|
menu_table.add_row("")
|
||||||
menu_table.add_row("[bright_white on blue] 1 [/bright_white on blue] Play vs AI [dim]♔ vs ♚[/dim]")
|
menu_table.add_row(
|
||||||
menu_table.add_row("[bright_white on magenta] 2 [/bright_white on magenta] Play vs Human [dim]♔ vs ♔[/dim]")
|
"[bright_white on blue] 1 [/] Play vs AI [dim]♔ vs ♚[/dim]"
|
||||||
menu_table.add_row("[bright_white on green] 3 [/bright_white on green] Learn & Practice [dim]📖[/dim]")
|
)
|
||||||
menu_table.add_row("[bright_white on red] q [/bright_white on red] Quit [dim]👋[/dim]")
|
menu_table.add_row(
|
||||||
|
"[bright_white on magenta] 2 [/] Play vs Human [dim]♔ vs ♔[/dim]"
|
||||||
|
)
|
||||||
|
menu_table.add_row(
|
||||||
|
"[bright_white on green] 3 [/] Learn & Practice [dim]📖[/dim]"
|
||||||
|
)
|
||||||
|
menu_table.add_row(
|
||||||
|
"[bright_white on red] q [/] Quit [dim]👋[/dim]"
|
||||||
|
)
|
||||||
menu_table.add_row("")
|
menu_table.add_row("")
|
||||||
menu_table.add_row(Text("Press a key to select...", style="dim italic"))
|
menu_table.add_row(Text("Press a key to select...", style="dim italic"))
|
||||||
|
|
||||||
@@ -93,7 +103,7 @@ def test_chess_board_render():
|
|||||||
"""Test that chess board renders without errors."""
|
"""Test that chess board renders without errors."""
|
||||||
output = StringIO()
|
output = StringIO()
|
||||||
|
|
||||||
PIECES = {
|
piece_map = {
|
||||||
'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙',
|
'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙',
|
||||||
'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟',
|
'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟',
|
||||||
}
|
}
|
||||||
@@ -106,8 +116,8 @@ def test_chess_board_render():
|
|||||||
# Render rank 8 with pieces
|
# Render rank 8 with pieces
|
||||||
pieces_row = ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']
|
pieces_row = ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']
|
||||||
row = " 8 |"
|
row = " 8 |"
|
||||||
for file, piece_char in enumerate(pieces_row):
|
for piece_char in pieces_row:
|
||||||
char = PIECES.get(piece_char, '?')
|
char = piece_map.get(piece_char, '?')
|
||||||
row += f" {char} |"
|
row += f" {char} |"
|
||||||
row += " 8"
|
row += " 8"
|
||||||
lines.append(row)
|
lines.append(row)
|
||||||
|
|||||||
Reference in New Issue
Block a user