Adds undo feature
This commit is contained in:
parent
6ca7417011
commit
2600a40280
2 changed files with 105 additions and 2 deletions
|
|
@ -9,6 +9,7 @@ This branch keeps the implementation within a Python-1.5.2-friendly subset and f
|
||||||
- FreeCell rules with 8 tableau columns, 4 free cells, and 4 foundations
|
- FreeCell rules with 8 tableau columns, 4 free cells, and 4 foundations
|
||||||
- keyboard-driven curses interface
|
- keyboard-driven curses interface
|
||||||
- visual stack selection mode
|
- visual stack selection mode
|
||||||
|
- single-step undo for completed moves
|
||||||
- lightweight `?` hint system
|
- lightweight `?` hint system
|
||||||
- ASCII-only suit tags for old terminals and fonts
|
- ASCII-only suit tags for old terminals and fonts
|
||||||
- compatibility fallbacks for weak or incomplete curses implementations
|
- compatibility fallbacks for weak or incomplete curses implementations
|
||||||
|
|
@ -19,6 +20,7 @@ This branch keeps the implementation within a Python-1.5.2-friendly subset and f
|
||||||
- `v` - enter or exit tableau selection mode
|
- `v` - enter or exit tableau selection mode
|
||||||
- `y` - pick up selected card or stack
|
- `y` - pick up selected card or stack
|
||||||
- `p` - drop held card or stack
|
- `p` - drop held card or stack
|
||||||
|
- `u` - undo the last completed move
|
||||||
- `Enter` / `Space` - alternate pick up / drop
|
- `Enter` / `Space` - alternate pick up / drop
|
||||||
- `?` - show a suggested move
|
- `?` - show a suggested move
|
||||||
- `f` - quick move selected card to a free cell
|
- `f` - quick move selected card to a free cell
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ Arrow keys / h j k l : Move cursor
|
||||||
v : Enter / exit tableau visual selection
|
v : Enter / exit tableau visual selection
|
||||||
y : Pick up selected card / stack
|
y : Pick up selected card / stack
|
||||||
p : Drop held card / stack
|
p : Drop held card / stack
|
||||||
|
u : Undo the last completed move
|
||||||
? : Show a suggested move
|
? : Show a suggested move
|
||||||
Space or Enter : Pick up / drop a card or stack
|
Space or Enter : Pick up / drop a card or stack
|
||||||
f : Quick move selected card to a free cell
|
f : Quick move selected card to a free cell
|
||||||
|
|
@ -252,6 +253,15 @@ class HintMove:
|
||||||
self.score = score
|
self.score = score
|
||||||
|
|
||||||
|
|
||||||
|
class MoveRecord:
|
||||||
|
def __init__(self, source_type, source_index, dest_type, dest_index, cards):
|
||||||
|
self.source_type = source_type
|
||||||
|
self.source_index = source_index
|
||||||
|
self.dest_type = dest_type
|
||||||
|
self.dest_index = dest_index
|
||||||
|
self.cards = cards
|
||||||
|
|
||||||
|
|
||||||
class BoardLayout:
|
class BoardLayout:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
|
@ -277,6 +287,7 @@ class FreeCellGame:
|
||||||
self.foundations = [[], [], [], []]
|
self.foundations = [[], [], [], []]
|
||||||
self.held = None
|
self.held = None
|
||||||
self.hint_move = None
|
self.hint_move = None
|
||||||
|
self.move_history = []
|
||||||
self.visual_mode = 0
|
self.visual_mode = 0
|
||||||
self.visual_row = 0
|
self.visual_row = 0
|
||||||
self.cursor_zone = "bottom"
|
self.cursor_zone = "bottom"
|
||||||
|
|
@ -307,6 +318,63 @@ class FreeCellGame:
|
||||||
if had_hint:
|
if had_hint:
|
||||||
self.request_full_repaint()
|
self.request_full_repaint()
|
||||||
|
|
||||||
|
def record_move(self, source_type, source_index, dest_type, dest_index, cards):
|
||||||
|
self.move_history.append(
|
||||||
|
MoveRecord(source_type, source_index, dest_type, dest_index, cards[:])
|
||||||
|
)
|
||||||
|
|
||||||
|
def _remove_cards_from_destination(self, move):
|
||||||
|
cards = move.cards
|
||||||
|
if move.dest_type == "col":
|
||||||
|
column = self.columns[move.dest_index]
|
||||||
|
if len(column) < len(cards):
|
||||||
|
return 0
|
||||||
|
if column[-len(cards) :] != cards:
|
||||||
|
return 0
|
||||||
|
del column[-len(cards) :]
|
||||||
|
return 1
|
||||||
|
if move.dest_type == "free":
|
||||||
|
if self.freecells[move.dest_index] != cards[0]:
|
||||||
|
return 0
|
||||||
|
self.freecells[move.dest_index] = None
|
||||||
|
return 1
|
||||||
|
foundation = self.foundations[move.dest_index]
|
||||||
|
if not foundation or foundation[-1] != cards[0]:
|
||||||
|
return 0
|
||||||
|
foundation.pop()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def _restore_cards_to_source(self, move):
|
||||||
|
cards = move.cards
|
||||||
|
if move.source_type == "col":
|
||||||
|
self.columns[move.source_index].extend(cards)
|
||||||
|
return
|
||||||
|
self.freecells[move.source_index] = cards[0]
|
||||||
|
|
||||||
|
def undo_last_move(self):
|
||||||
|
if self.held is not None:
|
||||||
|
self.status = "Drop or cancel the held cards before undoing a move."
|
||||||
|
return
|
||||||
|
if not self.move_history:
|
||||||
|
self.status = "Nothing to undo."
|
||||||
|
return
|
||||||
|
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."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
del self.move_history[-1]
|
||||||
|
self._restore_cards_to_source(move)
|
||||||
|
self.clear_hint()
|
||||||
|
if self.visual_mode:
|
||||||
|
self.exit_visual_mode(None)
|
||||||
|
self.request_full_repaint()
|
||||||
|
if len(move.cards) == 1:
|
||||||
|
self.status = "Undid move of %s." % move.cards[0].short_name()
|
||||||
|
else:
|
||||||
|
self.status = "Undid move of %d cards." % len(move.cards)
|
||||||
|
|
||||||
def _deal_new_game(self):
|
def _deal_new_game(self):
|
||||||
deck = []
|
deck = []
|
||||||
for suit in SUITS:
|
for suit in SUITS:
|
||||||
|
|
@ -807,6 +875,13 @@ class FreeCellGame:
|
||||||
if self.can_place_stack_on_column(cards, self.cursor_index):
|
if self.can_place_stack_on_column(cards, self.cursor_index):
|
||||||
self.clear_hint()
|
self.clear_hint()
|
||||||
self.columns[self.cursor_index].extend(cards)
|
self.columns[self.cursor_index].extend(cards)
|
||||||
|
self.record_move(
|
||||||
|
self.held.source_type,
|
||||||
|
self.held.source_index,
|
||||||
|
"col",
|
||||||
|
self.cursor_index,
|
||||||
|
cards,
|
||||||
|
)
|
||||||
if moving_count == 1:
|
if moving_count == 1:
|
||||||
self.status = "Placed %s on column %d." % (
|
self.status = "Placed %s on column %d." % (
|
||||||
moving_top.short_name(),
|
moving_top.short_name(),
|
||||||
|
|
@ -829,6 +904,13 @@ class FreeCellGame:
|
||||||
if self.freecells[self.cursor_index] is None:
|
if self.freecells[self.cursor_index] is None:
|
||||||
self.clear_hint()
|
self.clear_hint()
|
||||||
self.freecells[self.cursor_index] = moving_top
|
self.freecells[self.cursor_index] = moving_top
|
||||||
|
self.record_move(
|
||||||
|
self.held.source_type,
|
||||||
|
self.held.source_index,
|
||||||
|
"free",
|
||||||
|
self.cursor_index,
|
||||||
|
cards,
|
||||||
|
)
|
||||||
self.status = "Placed %s in free cell %d." % (
|
self.status = "Placed %s in free cell %d." % (
|
||||||
moving_top.short_name(),
|
moving_top.short_name(),
|
||||||
self.cursor_index + 1,
|
self.cursor_index + 1,
|
||||||
|
|
@ -845,6 +927,13 @@ class FreeCellGame:
|
||||||
self.clear_hint()
|
self.clear_hint()
|
||||||
foundation = self.foundation_for_suit(moving_top.suit.name)
|
foundation = self.foundation_for_suit(moving_top.suit.name)
|
||||||
foundation.append(moving_top)
|
foundation.append(moving_top)
|
||||||
|
self.record_move(
|
||||||
|
self.held.source_type,
|
||||||
|
self.held.source_index,
|
||||||
|
"foundation",
|
||||||
|
find_suit_index(moving_top.suit.name),
|
||||||
|
cards,
|
||||||
|
)
|
||||||
self.status = "Moved %s to foundation." % moving_top.short_name()
|
self.status = "Moved %s to foundation." % moving_top.short_name()
|
||||||
self.held = None
|
self.held = None
|
||||||
self.request_full_repaint()
|
self.request_full_repaint()
|
||||||
|
|
@ -868,7 +957,9 @@ class FreeCellGame:
|
||||||
self.status = "Quick freecell move uses only the bottom card."
|
self.status = "Quick freecell move uses only the bottom card."
|
||||||
return
|
return
|
||||||
self.clear_hint()
|
self.clear_hint()
|
||||||
self.freecells[free_idx] = col.pop()
|
moved_card = col.pop()
|
||||||
|
self.freecells[free_idx] = moved_card
|
||||||
|
self.record_move("col", self.cursor_index, "free", free_idx, [moved_card])
|
||||||
self.status = "Moved card to free cell %d." % (free_idx + 1)
|
self.status = "Moved card to free cell %d." % (free_idx + 1)
|
||||||
self.request_full_repaint()
|
self.request_full_repaint()
|
||||||
return
|
return
|
||||||
|
|
@ -907,6 +998,13 @@ class FreeCellGame:
|
||||||
self.columns[source[1]].pop()
|
self.columns[source[1]].pop()
|
||||||
else:
|
else:
|
||||||
self.freecells[source[1]] = None
|
self.freecells[source[1]] = None
|
||||||
|
self.record_move(
|
||||||
|
source[0],
|
||||||
|
source[1],
|
||||||
|
"foundation",
|
||||||
|
find_suit_index(card.suit.name),
|
||||||
|
[card],
|
||||||
|
)
|
||||||
self.status = "Moved %s to foundation." % card.short_name()
|
self.status = "Moved %s to foundation." % card.short_name()
|
||||||
self.request_full_repaint()
|
self.request_full_repaint()
|
||||||
|
|
||||||
|
|
@ -1261,7 +1359,7 @@ def draw_help(stdscr, max_y, max_x):
|
||||||
stdscr.clrtoeol()
|
stdscr.clrtoeol()
|
||||||
except curses.error:
|
except curses.error:
|
||||||
pass
|
pass
|
||||||
help_text = "Move arrows/hjkl v select y/p/ENT/SPC move ? hint f freecell d foundation Esc cancel q quit"
|
help_text = "Move arrows/hjkl v select y/p/ENT/SPC move u undo ? hint f freecell d foundation Esc cancel q quit"
|
||||||
safe_addnstr(stdscr, y, 2, help_text, max_x - 4, pair_attr(1))
|
safe_addnstr(stdscr, y, 2, help_text, max_x - 4, pair_attr(1))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1382,6 +1480,9 @@ def handle_key(game, ch):
|
||||||
else:
|
else:
|
||||||
game.drop()
|
game.drop()
|
||||||
return 1
|
return 1
|
||||||
|
if ch == ord("u") or ch == ord("U"):
|
||||||
|
game.undo_last_move()
|
||||||
|
return 1
|
||||||
if ch == ord("?"):
|
if ch == ord("?"):
|
||||||
game.show_hint()
|
game.show_hint()
|
||||||
return 1
|
return 1
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue