Adds timer to gameplay
This commit is contained in:
parent
3b3cfa9d8a
commit
ee14d51d35
1 changed files with 48 additions and 3 deletions
|
|
@ -28,6 +28,7 @@ Notes
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
import random
|
import random
|
||||||
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import List, Optional, Tuple
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
|
|
@ -141,8 +142,21 @@ class FreeCellGame:
|
||||||
self.cursor_index = 0
|
self.cursor_index = 0
|
||||||
|
|
||||||
self.status = "Arrow keys move, v selects a stack, ? shows a hint."
|
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()
|
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:
|
def clear_hint(self) -> None:
|
||||||
"""Drop the currently remembered hint highlight."""
|
"""Drop the currently remembered hint highlight."""
|
||||||
self.hint_move = None
|
self.hint_move = None
|
||||||
|
|
@ -211,7 +225,9 @@ class FreeCellGame:
|
||||||
|
|
||||||
move = self.move_history[-1]
|
move = self.move_history[-1]
|
||||||
if not self._remove_cards_from_destination(move):
|
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
|
return
|
||||||
|
|
||||||
self.move_history.pop()
|
self.move_history.pop()
|
||||||
|
|
@ -917,7 +933,9 @@ class FreeCellGame:
|
||||||
else:
|
else:
|
||||||
self.freecells[source[1]] = None
|
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."
|
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))
|
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:
|
def render(stdscr, game: FreeCellGame) -> None:
|
||||||
"""Redraw the full screen."""
|
"""Redraw the full screen."""
|
||||||
stdscr.erase()
|
stdscr.erase()
|
||||||
max_y, max_x = stdscr.getmaxyx()
|
max_y, max_x = stdscr.getmaxyx()
|
||||||
layout = compute_board_layout(max_x)
|
layout = compute_board_layout(max_x)
|
||||||
|
elapsed_text = f"Time: {format_elapsed_time(game.elapsed_time_seconds())}"
|
||||||
|
|
||||||
title = "mcfreecell 0.1"
|
title = "mcfreecell 0.1"
|
||||||
if len(title) > max_x - 4:
|
if len(title) > max_x - 4:
|
||||||
|
|
@ -1247,6 +1272,16 @@ def render(stdscr, game: FreeCellGame) -> None:
|
||||||
curses.color_pair(1) | curses.A_BOLD,
|
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:
|
if game.visual_mode:
|
||||||
visual_text = "-- VISUAL --"
|
visual_text = "-- VISUAL --"
|
||||||
visual_x = min(max_x - len(visual_text) - 2, title_x + len(title) + 3)
|
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)
|
draw_help(stdscr, max_y)
|
||||||
|
|
||||||
if game.is_won():
|
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()
|
stdscr.refresh()
|
||||||
|
|
||||||
|
|
@ -1395,6 +1437,7 @@ def curses_main(stdscr) -> None:
|
||||||
"""Main UI entry point."""
|
"""Main UI entry point."""
|
||||||
curses.curs_set(0)
|
curses.curs_set(0)
|
||||||
stdscr.keypad(True)
|
stdscr.keypad(True)
|
||||||
|
stdscr.timeout(100)
|
||||||
|
|
||||||
init_colors()
|
init_colors()
|
||||||
|
|
||||||
|
|
@ -1403,6 +1446,8 @@ def curses_main(stdscr) -> None:
|
||||||
while True:
|
while True:
|
||||||
render(stdscr, game)
|
render(stdscr, game)
|
||||||
ch = stdscr.getch()
|
ch = stdscr.getch()
|
||||||
|
if ch == -1:
|
||||||
|
continue
|
||||||
if not handle_key(game, ch):
|
if not handle_key(game, ch):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue