fabro(01KTA1FC5W5W0BHTQSV865A1H6): impl_logic (succeeded)

Fabro-Run: 01KTA1FC5W5W0BHTQSV865A1H6
Fabro-Completed: 9
Fabro-Checkpoint: 516516a770

⚒️ Generated with [Fabro](https://fabro.sh)
This commit is contained in:
Fabro 2026-06-04 19:39:27 +00:00
parent 094a552a5c
commit e73e5322ac
2 changed files with 182 additions and 0 deletions

View file

@ -78,3 +78,89 @@ def test_validate_move_sequence():
assert not valid
assert "Insufficient empty FreeCells" in reason
def test_validate_move_to_freecell():
state = GameState()
state.free_cells = [None] * 4
state.tableau[0] = [Card(Rank.ACE, Suit.SPADES)]
# Valid move of single card to empty FreeCell
move_valid = Move('C', 0, 'F', 0, 1)
valid, reason = validate_move(state, move_valid)
assert valid
# Cannot move a sequence to a FreeCell
state.tableau[0] = [Card(Rank.TWO, Suit.HEARTS), Card(Rank.ACE, Suit.SPADES)]
move_seq = Move('C', 0, 'F', 1, 2)
valid, reason = validate_move(state, move_seq)
assert not valid
assert "Cannot move a sequence to a FreeCell" in reason
# Cannot move to an occupied FreeCell
state.free_cells[2] = Card(Rank.KING, Suit.CLUBS)
move_occ = Move('C', 0, 'F', 2, 1)
valid, reason = validate_move(state, move_occ)
assert not valid
assert "Target FreeCell is occupied" in reason
def test_validate_move_to_foundation():
state = GameState()
state.free_cells = [None] * 4
state.foundations = {suit: [] for suit in Suit}
state.tableau[0] = [Card(Rank.ACE, Suit.SPADES)]
state.tableau[1] = [Card(Rank.TWO, Suit.SPADES)]
# Moving Ace of Spades to empty Foundation is valid
move_ace = Move('C', 0, 'A', 0, 1)
valid, reason = validate_move(state, move_ace)
assert valid
# Moving Two of Spades to empty Foundation is invalid
move_two_invalid = Move('C', 1, 'A', 0, 1)
valid, reason = validate_move(state, move_two_invalid)
assert not valid
assert "Foundations must start with an Ace" in reason
# Place Ace of Spades in foundation first
state.foundations[Suit.SPADES] = [Card(Rank.ACE, Suit.SPADES)]
# Now, moving Two of Spades to foundation is valid
move_two_valid = Move('C', 1, 'A', 0, 1)
valid, reason = validate_move(state, move_two_valid)
assert valid
# Moving a non-consecutive card (e.g. Four of Spades) is invalid
state.tableau[2] = [Card(Rank.FOUR, Suit.SPADES)]
move_four_invalid = Move('C', 2, 'A', 0, 1)
valid, reason = validate_move(state, move_four_invalid)
assert not valid
assert "Must be next rank up" in reason
# Cannot move a sequence to a Foundation
state.tableau[3] = [Card(Rank.THREE, Suit.HEARTS), Card(Rank.TWO, Suit.SPADES)]
move_seq = Move('C', 3, 'A', 0, 2)
valid, reason = validate_move(state, move_seq)
assert not valid
assert "Cannot move a sequence to a Foundation" in reason
def test_validate_move_sequence_invalid_alternating_color():
state = GameState()
state.free_cells = [None] * 4
state.tableau[0] = [Card(Rank.JACK, Suit.HEARTS), Card(Rank.TEN, Suit.DIAMONDS)]
# Hearts and Diamonds are both RED. Sequence J♥, 10♦ is invalid!
move = Move('C', 0, 'C', 1, 2)
valid, reason = validate_move(state, move)
assert not valid
assert "alternating color descending sequence" in reason
def test_validate_move_sequence_invalid_ranks():
state = GameState()
state.free_cells = [None] * 4
state.tableau[0] = [Card(Rank.JACK, Suit.HEARTS), Card(Rank.NINE, Suit.SPADES)]
# Jack (11) and Nine (9) is invalid sequence (should be Jack and Ten)
move = Move('C', 0, 'C', 1, 2)
valid, reason = validate_move(state, move)
assert not valid
assert "alternating color descending sequence" in reason

View file

@ -127,3 +127,99 @@ def test_is_lost():
# Thus, no legal moves are possible!
assert state.is_lost()
def test_execute_invalid_move_no_history():
state = GameState()
state.tableau = [[] for _ in range(8)]
state.free_cells = [None] * 4
state.tableau[0] = [Card(Rank.KING, Suit.HEARTS)]
state.tableau[1] = [Card(Rank.TEN, Suit.DIAMONDS)]
# Attempt invalid move (cannot place 10♦ on K♥)
move = Move('C', 1, 'C', 0, 1)
success, reason = state.execute_move(move)
assert not success
assert not state.history # History should remain empty
def test_multiple_undo_redo():
state = GameState()
state.tableau = [[] for _ in range(8)]
state.free_cells = [None] * 4
state.foundations = {suit: [] for suit in Suit}
# Set up some cards
state.tableau[0] = [Card(Rank.ACE, Suit.SPADES)]
state.tableau[1] = [Card(Rank.TWO, Suit.SPADES)]
# 1. Move Ace of Spades to foundation
success, _ = state.execute_move(Move('C', 0, 'A', 0, 1))
assert success
assert len(state.foundations[Suit.SPADES]) == 1
# 2. Move Two of Spades to foundation (will auto-home, but we move manually)
success, _ = state.execute_move(Move('C', 1, 'A', 0, 1))
assert success
assert len(state.foundations[Suit.SPADES]) == 2
# Check state before undo
assert not state.tableau[0]
assert not state.tableau[1]
# Undo 2nd move
assert state.undo()
assert len(state.foundations[Suit.SPADES]) == 1
assert state.tableau[1] == [Card(Rank.TWO, Suit.SPADES)]
# Undo 1st move
assert state.undo()
assert len(state.foundations[Suit.SPADES]) == 0
assert state.tableau[0] == [Card(Rank.ACE, Suit.SPADES)]
# No more history
assert not state.undo()
# Redo 1st move
assert state.redo()
assert len(state.foundations[Suit.SPADES]) == 1
assert not state.tableau[0]
# Redo 2nd move
assert state.redo()
assert len(state.foundations[Suit.SPADES]) == 2
assert not state.tableau[1]
# No more redo history
assert not state.redo()
def test_is_lost_false_if_freecell_can_move_to_tableau():
state = GameState()
# 8 columns filled with 5♠ -> no moves between columns
state.tableau = [[Card(Rank.FIVE, Suit.SPADES)] for _ in range(8)]
# All FreeCells but one occupied by King of Hearts. One has Six of Hearts
state.free_cells = [
Card(Rank.KING, Suit.HEARTS),
Card(Rank.KING, Suit.HEARTS),
Card(Rank.KING, Suit.HEARTS),
Card(Rank.FOUR, Suit.HEARTS) # Four of Hearts can go on Five of Spades!
]
state.foundations = {suit: [] for suit in Suit}
# Not lost because 4♥ in FreeCell can move onto 5♠ on any Tableau column
assert not state.is_lost()
def test_is_lost_false_if_freecell_can_move_to_foundation():
state = GameState()
# 8 columns filled with 5♠ -> no moves between columns
state.tableau = [[Card(Rank.FIVE, Suit.SPADES)] for _ in range(8)]
# FreeCells occupied, one contains Ace of Spades
state.free_cells = [
Card(Rank.KING, Suit.HEARTS),
Card(Rank.KING, Suit.HEARTS),
Card(Rank.KING, Suit.HEARTS),
Card(Rank.ACE, Suit.SPADES) # Ace can go to foundation
]
state.foundations = {suit: [] for suit in Suit}
# Not lost because Ace can go to empty Spades Foundation
assert not state.is_lost()