From 4b4ab92d04f6707ab4fe7cb234b302c119fd9f50 Mon Sep 17 00:00:00 2001 From: markmental Date: Wed, 18 Mar 2026 21:10:30 -0400 Subject: [PATCH] Added responsiveness to board layout --- linux-freecell.py | 101 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/linux-freecell.py b/linux-freecell.py index a1875e3..5777487 100644 --- a/linux-freecell.py +++ b/linux-freecell.py @@ -817,6 +817,64 @@ TOP_Y = 1 COL_Y = 6 +@dataclass(frozen=True) +class BoardLayout: + """Computed horizontal positions for the current terminal width.""" + + freecell_xs: Tuple[int, ...] + foundation_xs: Tuple[int, ...] + tableau_xs: Tuple[int, ...] + freecells_label_x: int + foundations_label_x: int + tableau_label_x: int + + +def spread_positions(count: int, start_x: int, end_x: int) -> Tuple[int, ...]: + """Evenly spread fixed-width slots between two inclusive bounds.""" + if count <= 1: + return (start_x,) + + span = max(0, end_x - start_x) + step = span / (count - 1) + positions = [] + for index in range(count): + positions.append(int(round(start_x + step * index))) + return tuple(positions) + + +def compute_board_layout(max_x: int) -> BoardLayout: + """Compute a centered, width-aware board layout.""" + left_margin = 2 + right_margin = max(left_margin, max_x - CARD_W - 2) + + if max_x < 72: + top_start = left_margin + top_end = max(top_start, max_x - CARD_W - 2) + top_positions = spread_positions(8, top_start, top_end) + freecell_xs = top_positions[:4] + foundation_xs = top_positions[4:] + else: + center_gap = max(10, min(20, max_x // 6)) + left_end = max(left_margin, (max_x // 2) - (center_gap // 2) - CARD_W) + right_start = min(right_margin, (max_x // 2) + (center_gap // 2)) + freecell_xs = spread_positions(4, left_margin, left_end) + foundation_xs = spread_positions(4, right_start, right_margin) + + tableau_xs = spread_positions(8, left_margin, right_margin) + freecells_label_x = freecell_xs[0] + foundations_label_x = foundation_xs[0] + tableau_label_x = tableau_xs[0] + + return BoardLayout( + freecell_xs=freecell_xs, + foundation_xs=foundation_xs, + tableau_xs=tableau_xs, + freecells_label_x=freecells_label_x, + foundations_label_x=foundations_label_x, + tableau_label_x=tableau_label_x, + ) + + def init_colors() -> None: """Initialize curses color pairs.""" curses.start_color() @@ -859,14 +917,24 @@ def draw_card_label(card: Optional[Card]) -> str: return card.short_name() -def draw_top_row(stdscr, game: FreeCellGame) -> None: +def draw_top_row(stdscr, game: FreeCellGame, layout: BoardLayout) -> None: """Draw freecells and foundations.""" - stdscr.addstr(TOP_Y, 2, "Free Cells", curses.color_pair(1) | curses.A_BOLD) - stdscr.addstr(TOP_Y, 40, "Foundations", curses.color_pair(1) | curses.A_BOLD) + stdscr.addstr( + TOP_Y, + layout.freecells_label_x, + "Free Cells", + curses.color_pair(1) | curses.A_BOLD, + ) + stdscr.addstr( + TOP_Y, + layout.foundations_label_x, + "Foundations", + curses.color_pair(1) | curses.A_BOLD, + ) # Freecells at indexes 0..3 for i in range(4): - x = 2 + i * (CARD_W + 1) + x = layout.freecell_xs[i] selected = game.cursor_zone == "top" and game.cursor_index == i hinted = game.hinted_freecell(i) attr = ( @@ -891,7 +959,7 @@ def draw_top_row(stdscr, game: FreeCellGame) -> None: # Foundations at indexes 4..7 in cursor space for i in range(4): - x = 40 + i * (CARD_W + 1) + x = layout.foundation_xs[i] selected = game.cursor_zone == "top" and game.cursor_index == i + 4 hinted = game.hinted_foundation(i) attr = ( @@ -923,14 +991,19 @@ def draw_top_row(stdscr, game: FreeCellGame) -> None: ) -def draw_columns(stdscr, game: FreeCellGame) -> None: +def draw_columns(stdscr, game: FreeCellGame, layout: BoardLayout) -> None: """Draw the tableau columns.""" - stdscr.addstr(COL_Y - 1, 2, "Tableau", curses.color_pair(1) | curses.A_BOLD) + stdscr.addstr( + COL_Y - 1, + layout.tableau_label_x, + "Tableau", + curses.color_pair(1) | curses.A_BOLD, + ) max_height = max((len(col) for col in game.columns), default=0) for col_idx in range(8): - x = 2 + col_idx * 9 + x = layout.tableau_xs[col_idx] selected = game.cursor_zone == "bottom" and game.cursor_index == col_idx hinted_dest = game.hinted_column_destination(col_idx) header_attr = ( @@ -1042,6 +1115,7 @@ def render(stdscr, game: FreeCellGame) -> None: """Redraw the full screen.""" stdscr.erase() max_y, max_x = stdscr.getmaxyx() + layout = compute_board_layout(max_x) title = "mcfreecell 0.1" if len(title) > max_x - 4: @@ -1056,10 +1130,15 @@ def render(stdscr, game: FreeCellGame) -> None: ) if game.visual_mode: - stdscr.addstr(0, 30, "-- VISUAL --", curses.color_pair(5) | curses.A_BOLD) + visual_text = "-- VISUAL --" + visual_x = min(max_x - len(visual_text) - 2, title_x + len(title) + 3) + if visual_x > title_x + len(title): + stdscr.addstr( + 0, visual_x, visual_text, curses.color_pair(5) | curses.A_BOLD + ) - draw_top_row(stdscr, game) - draw_columns(stdscr, game) + draw_top_row(stdscr, game, layout) + draw_columns(stdscr, game, layout) draw_held(stdscr, game, max_y) draw_status(stdscr, game, max_y) draw_help(stdscr, max_y)