Adds timer to gameplay

This commit is contained in:
markmental 2026-03-22 17:05:01 -04:00
commit ee14d51d35

View file

@ -28,6 +28,7 @@ Notes
import curses
import random
import time
from dataclasses import dataclass
from typing import List, Optional, Tuple
@ -141,8 +142,21 @@ class FreeCellGame:
self.cursor_index = 0
self.status = "Arrow keys move, v selects a stack, ? shows a hint."
self.start_time = time.monotonic()
self.finished_time_seconds: Optional[int] = None
self._deal_new_game()
def elapsed_time_seconds(self) -> int:
"""Return elapsed play time, freezing it after a win."""
if self.finished_time_seconds is not None:
return self.finished_time_seconds
elapsed = int(time.monotonic() - self.start_time)
if self.is_won():
self.finished_time_seconds = elapsed
return self.finished_time_seconds
return elapsed
def clear_hint(self) -> None:
"""Drop the currently remembered hint highlight."""
self.hint_move = None
@ -211,7 +225,9 @@ class FreeCellGame:
move = self.move_history[-1]
if not self._remove_cards_from_destination(move):
self.status = "Undo failed because the board no longer matches the last move."
self.status = (
"Undo failed because the board no longer matches the last move."
)
return
self.move_history.pop()
@ -917,7 +933,9 @@ class FreeCellGame:
else:
self.freecells[source[1]] = None
self.record_move(source[0], source[1], "foundation", SUITS.index(card.suit), [card])
self.record_move(
source[0], source[1], "foundation", SUITS.index(card.suit), [card]
)
self.status = f"Moved {card.short_name()} to foundation."
@ -1229,11 +1247,18 @@ def draw_help(stdscr, max_y: int) -> None:
stdscr.addnstr(y, 2, help_text, max(0, curses.COLS - 4), curses.color_pair(1))
def format_elapsed_time(seconds: int) -> str:
"""Format elapsed seconds as MM:SS."""
minutes, seconds = divmod(max(0, seconds), 60)
return f"{minutes:02d}:{seconds:02d}"
def render(stdscr, game: FreeCellGame) -> None:
"""Redraw the full screen."""
stdscr.erase()
max_y, max_x = stdscr.getmaxyx()
layout = compute_board_layout(max_x)
elapsed_text = f"Time: {format_elapsed_time(game.elapsed_time_seconds())}"
title = "mcfreecell 0.1"
if len(title) > max_x - 4:
@ -1247,6 +1272,16 @@ def render(stdscr, game: FreeCellGame) -> None:
curses.color_pair(1) | curses.A_BOLD,
)
timer_x = max(2, max_x - len(elapsed_text) - 2)
if timer_x > title_x + len(title):
stdscr.addnstr(
0,
timer_x,
elapsed_text,
max(0, max_x - timer_x - 1),
curses.color_pair(1) | curses.A_BOLD,
)
if game.visual_mode:
visual_text = "-- VISUAL --"
visual_x = min(max_x - len(visual_text) - 2, title_x + len(title) + 3)
@ -1262,7 +1297,14 @@ def render(stdscr, game: FreeCellGame) -> None:
draw_help(stdscr, max_y)
if game.is_won():
stdscr.addstr(3, 2, "You won.", curses.color_pair(5) | curses.A_BOLD)
win_text = f"You won in {format_elapsed_time(game.elapsed_time_seconds())}."
stdscr.addnstr(
3,
2,
win_text,
max(0, max_x - 4),
curses.color_pair(5) | curses.A_BOLD,
)
stdscr.refresh()
@ -1395,6 +1437,7 @@ def curses_main(stdscr) -> None:
"""Main UI entry point."""
curses.curs_set(0)
stdscr.keypad(True)
stdscr.timeout(100)
init_colors()
@ -1403,6 +1446,8 @@ def curses_main(stdscr) -> None:
while True:
render(stdscr, game)
ch = stdscr.getch()
if ch == -1:
continue
if not handle_key(game, ch):
break