checkpoint

⚒️ Generated with [Fabro](https://fabro.sh)
This commit is contained in:
Fabro 2026-06-04 14:45:59 -04:00
parent 74d93db932
commit b3ea9f2728
7 changed files with 1011 additions and 221 deletions

848
run.json

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,325 @@
diff --git a/card-game-app/README.md b/card-game-app/README.md
new file mode 100644
index 000000000..e12418971
--- /dev/null
+++ b/card-game-app/README.md
@@ -0,0 +1,32 @@
+# FreeCell Solitaire TUI
+
+A terminal-based FreeCell Solitaire game built in Python using the `curses` standard library.
+
+## Project Structure
+
+- `main.py`: CLI entrypoint.
+- `src/card_game_tui/`: Core package.
+ - `domain.py`: Game rules, logic, state, and card representations (fully decoupled from UI).
+ - `tui.py`: Curses-based TUI and interface logic.
+- `tests/`: Automated unit and integration tests.
+
+## Installation & Running
+
+This project requires Python 3.10+ and no external dependencies for core play.
+
+To run:
+```bash
+python3 main.py
+```
+
+For smoke-test mode:
+```bash
+python3 main.py --smoke
+```
+
+## Running Tests
+
+To run tests:
+```bash
+pytest
+```
diff --git a/card-game-app/main.py b/card-game-app/main.py
new file mode 100644
index 000000000..2fe159c3a
--- /dev/null
+++ b/card-game-app/main.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+import sys
+import os
+
+# Add src to python path to make importing easier
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), 'src')))
+
+from card_game_tui.domain import GameState, Position, LocationType
+
+def run_smoke_test() -> int:
+ print("Running non-interactive smoke test probe...")
+ try:
+ # 1. Initialize GameState with a deterministic seed
+ seed = 42
+ state = GameState(seed=seed)
+ print(f"Initialized GameState with seed {seed}")
+
+ # 2. Basic assertions to verify components compile and behave as expected
+ # Let's verify we have 8 tableau columns
+ if len(state.tableaus) != 8:
+ print(f"Error: Expected 8 tableaus, found {len(state.tableaus)}", file=sys.stderr)
+ return 1
+
+ # 3. Validate a mock move
+ from_pos = Position(LocationType.TABLEAU, 0)
+ to_pos = Position(LocationType.FREECELL, 0)
+
+ is_valid = state.validate_move(from_pos, to_pos)
+ print(f"Validating move from Tableau 0 to FreeCell 0: {is_valid}")
+
+ success = state.execute_move(from_pos, to_pos)
+ print(f"Executed move from Tableau 0 to FreeCell 0: {success}")
+
+ # Check win state
+ is_win = state.check_win()
+ print(f"Win checked: {is_win}")
+
+ print("SMOKE TEST SUCCESSFUL")
+ return 0
+ except Exception as e:
+ print(f"Smoke test failed with error: {e}", file=sys.stderr)
+ return 1
+
+def main():
+ if "--smoke" in sys.argv:
+ sys.exit(run_smoke_test())
+ else:
+ print("FreeCell Solitaire TUI")
+ print("To run the smoke test, execute with --smoke")
+ print("Starting interactive UI (Placeholder)...")
+ # In future milestones, this will invoke the curses UI
+ from card_game_tui.tui import TUIApp
+ app = TUIApp()
+ app.run()
+
+if __name__ == "__main__":
+ main()
diff --git a/card-game-app/pyproject.toml b/card-game-app/pyproject.toml
new file mode 100644
index 000000000..132e2172b
--- /dev/null
+++ b/card-game-app/pyproject.toml
@@ -0,0 +1,19 @@
+[project]
+name = "card-game-tui"
+version = "0.1.0"
+description = "A terminal-based FreeCell solitaire game in Python"
+readme = "README.md"
+requires-python = ">=3.10"
+dependencies = []
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.pytest.ini_options]
+minversion = "6.0"
+addopts = "-ra -q"
+testpaths = [
+ "tests",
+]
+pythonpath = ["src"]
diff --git a/card-game-app/src/card_game_tui/__init__.py b/card-game-app/src/card_game_tui/__init__.py
new file mode 100644
index 000000000..6b01371db
--- /dev/null
+++ b/card-game-app/src/card_game_tui/__init__.py
@@ -0,0 +1 @@
+# Card Game TUI Package
diff --git a/card-game-app/src/card_game_tui/domain.py b/card-game-app/src/card_game_tui/domain.py
new file mode 100644
index 000000000..db4a2fa8f
--- /dev/null
+++ b/card-game-app/src/card_game_tui/domain.py
@@ -0,0 +1,129 @@
+import random
+from dataclasses import dataclass
+from enum import Enum, auto
+from typing import List, Union, Dict
+
+class Suit(Enum):
+ HEARTS = "H"
+ DIAMONDS = "D"
+ CLUBS = "C"
+ SPADES = "S"
+
+ @property
+ def color(self) -> str:
+ return "RED" if self in (Suit.HEARTS, Suit.DIAMONDS) else "BLACK"
+
+ @property
+ def symbol(self) -> str:
+ return {
+ Suit.HEARTS: "♥",
+ Suit.DIAMONDS: "♦",
+ Suit.CLUBS: "♣",
+ Suit.SPADES: "♠"
+ }[self]
+
+class Rank(Enum):
+ ACE = 1
+ TWO = 2
+ THREE = 3
+ FOUR = 4
+ FIVE = 5
+ SIX = 6
+ SEVEN = 7
+ EIGHT = 8
+ NINE = 9
+ TEN = 10
+ JACK = 11
+ QUEEN = 12
+ KING = 13
+
+ @property
+ def label(self) -> str:
+ if self.value == 1: return "A"
+ if self.value == 11: return "J"
+ if self.value == 12: return "Q"
+ if self.value == 13: return "K"
+ return str(self.value)
+
+@dataclass(frozen=True)
+class Card:
+ suit: Suit
+ rank: Rank
+
+ def __repr__(self) -> str:
+ return f"{self.rank.label}{self.suit.symbol}"
+
+class Deck:
+ def __init__(self, seed: int = None):
+ self.cards = [Card(suit, rank) for suit in Suit for rank in Rank]
+ if seed is not None:
+ random.seed(seed)
+ random.shuffle(self.cards)
+
+ def deal(self) -> List[List[Card]]:
+ """Deals 52 cards into 8 columns."""
+ tableaus: List[List[Card]] = [[] for _ in range(8)]
+ for i, card in enumerate(self.cards):
+ tableaus[i % 8].append(card)
+ return tableaus
+
+class LocationType(Enum):
+ TABLEAU = auto()
+ FREECELL = auto()
+ FOUNDATION = auto()
+
+@dataclass(frozen=True)
+class Position:
+ type: LocationType
+ index: int # 0-7 for Tableau, 0-3 for FreeCell, 0-3 for Foundation
+
+@dataclass
+class MoveRecord:
+ from_pos: Position
+ to_pos: Position
+ cards: List[Card] # Captured for single or sequence moves
+ auto_moves: List['MoveRecord'] = None # Nested moves triggered by auto-homing
+
+class GameState:
+ def __init__(self, seed: int = None):
+ self.tableaus: List[List[Card]] = Deck(seed).deal()
+ self.freecells: List[Union[Card, None]] = [None] * 4
+ self.foundations: Dict[Suit, List[Card]] = {suit: [] for suit in Suit}
+ self.undo_stack: List[MoveRecord] = []
+ self.redo_stack: List[MoveRecord] = []
+
+ def get_card_at(self, position: Position) -> Union[Card, None]:
+ if position.type == LocationType.FREECELL:
+ return self.freecells[position.index]
+ elif position.type == LocationType.FOUNDATION:
+ pile = self.foundations[list(Suit)[position.index]]
+ return pile[-1] if pile else None
+ elif position.type == LocationType.TABLEAU:
+ col = self.tableaus[position.index]
+ return col[-1] if col else None
+ return None
+
+ def validate_move(self, from_pos: Position, to_pos: Position, count: int = 1) -> bool:
+ """
+ Calculates whether moving 'count' cards from from_pos to to_pos is legal.
+ """
+ # Placeholder for validation
+ return True
+
+ def execute_move(self, from_pos: Position, to_pos: Position, count: int = 1) -> bool:
+ """
+ Executes a move, saves it to the undo stack, runs auto-homing, and clears the redo stack.
+ """
+ if not self.validate_move(from_pos, to_pos, count):
+ return False
+ return True
+
+ def undo(self) -> bool:
+ """Reverts the last move, including nested auto-homing steps."""
+ if not self.undo_stack:
+ return False
+ return True
+
+ def check_win(self) -> bool:
+ """Returns True if all 52 cards are in the Foundations."""
+ return all(len(self.foundations[suit]) == 13 for suit in Suit)
diff --git a/card-game-app/src/card_game_tui/tui.py b/card-game-app/src/card_game_tui/tui.py
new file mode 100644
index 000000000..5bb460b18
--- /dev/null
+++ b/card-game-app/src/card_game_tui/tui.py
@@ -0,0 +1,8 @@
+# TUI module placeholder for FreeCell Solitaire TUI
+
+class TUIApp:
+ def __init__(self):
+ pass
+
+ def run(self) -> None:
+ print("TUI App placeholder running.")
diff --git a/card-game-app/tests/__init__.py b/card-game-app/tests/__init__.py
new file mode 100644
index 000000000..d4839a6b1
--- /dev/null
+++ b/card-game-app/tests/__init__.py
@@ -0,0 +1 @@
+# Tests package
diff --git a/card-game-app/tests/test_domain.py b/card-game-app/tests/test_domain.py
new file mode 100644
index 000000000..3ddb4344b
--- /dev/null
+++ b/card-game-app/tests/test_domain.py
@@ -0,0 +1,20 @@
+from card_game_tui.domain import GameState, Suit, Rank, Card, Deck, LocationType, Position
+
+def test_deck_deals_52_cards():
+ deck = Deck()
+ assert len(deck.cards) == 52
+
+def test_game_state_initialization():
+ state = GameState()
+ assert len(state.tableaus) == 8
+ # 4 columns should have 7 cards, 4 should have 6 cards
+ lengths = [len(col) for col in state.tableaus]
+ assert sorted(lengths) == [6, 6, 6, 6, 7, 7, 7, 7]
+
+def test_get_card_at():
+ state = GameState(seed=123)
+ # Check if we can get cards correctly
+ pos = Position(LocationType.TABLEAU, 0)
+ card = state.get_card_at(pos)
+ assert card is not None
+ assert isinstance(card, Card)
diff --git a/status.json b/status.json
index ee6907d7f..905fb370f 100644
--- a/status.json
+++ b/status.json
@@ -1,3 +1,3 @@
{
"outcome": "succeeded"
-}
\ No newline at end of file
+}

View file

@ -0,0 +1,15 @@
I have successfully built the Python project skeleton for the terminal-based FreeCell solitaire game.
Here is a summary of the steps completed:
1. **Created `card-game-app/` Project Structure**:
- `pyproject.toml` with pytest configured and `pythonpath = ["src"]`.
- `main.py` entrypoint supporting `--smoke` test mode (without curses dependency) and regular mode.
- `src/card_game_tui/` package containing `__init__.py`, `domain.py` (decoupled core game logic: `Suit`, `Rank`, `Card`, `Deck`, `LocationType`, `Position`, `MoveRecord`, and `GameState` class stubs), and `tui.py` (TUI layer placeholder).
- `tests/` directory with `__init__.py` and `test_domain.py` containing initial unit tests.
- `README.md` stub explaining structure and usage.
2. **Verified Code Compilation**:
- Executed: `cd card-game-app && python3 -m py_compile main.py src/card_game_tui/*.py` which compiled cleanly with exit code 0.
3. **Smoke Tested the CLI**:
- Executed: `cd card-game-app && python3 main.py --smoke` which ran successfully, verifying the integrity of the domain model layout and assertions.
4. **Wrote `status.json`**:
- Created `status.json` at the workspace root containing `{"outcome": "succeeded"}` as the project skeleton exists and compiles perfectly.

View file

@ -0,0 +1,6 @@
{
"outcome": "succeeded",
"notes": "Stage completed: impl_setup",
"failure_reason": null,
"timestamp": "2026-06-04T18:44:57.769454Z"
}

View file

@ -0,0 +1,24 @@
Goal: Build a terminal-based FreeCell solitaire game in Python
## Completed stages
- **expand_spec**: succeeded
- Model: gemini-3.5-flash, 85.9k tokens in / 8.3k out
- Files: /home/daytona/workspace/fabro/.ai/card-game-spec.md, /home/daytona/workspace/fabro/status.json
- **impl_setup**: succeeded
- Model: gemini-3.5-flash, 144.1k tokens in / 6.8k out
- Files: /home/daytona/workspace/fabro/card-game-app/README.md, /home/daytona/workspace/fabro/card-game-app/main.py, /home/daytona/workspace/fabro/card-game-app/pyproject.toml, /home/daytona/workspace/fabro/card-game-app/src/card_game_tui/__init__.py, /home/daytona/workspace/fabro/card-game-app/src/card_game_tui/domain.py, /home/daytona/workspace/fabro/card-game-app/src/card_game_tui/tui.py, /home/daytona/workspace/fabro/card-game-app/tests/__init__.py, /home/daytona/workspace/fabro/card-game-app/tests/test_domain.py, /home/daytona/workspace/fabro/status.json
Verify setup for the card game app.
Check:
1. card-game-app/pyproject.toml exists
2. card-game-app/main.py exists
3. card-game-app/src/card_game_tui exists
4. Python files compile
Run:
cd card-game-app && python3 -m py_compile main.py src/card_game_tui/*.py
Write findings to .ai/verify_setup.md.
Write status.json at workspace root: outcome=succeeded if all checks pass, outcome=failed with failure_reason otherwise.

View file

@ -0,0 +1,5 @@
{
"mode": "agent",
"provider": "gemini",
"model": "gemini-3.5-flash"
}

View file

@ -0,0 +1,9 @@
I have successfully verified the setup for the card game app.
### Verified Checklist:
1. **`card-game-app/pyproject.toml` exists:** Verified.
2. **`card-game-app/main.py` exists:** Verified.
3. **`card-game-app/src/card_game_tui` exists:** Verified.
4. **Python files compile:** Verified by executing `cd card-game-app && python3 -m py_compile main.py src/card_game_tui/*.py` (which completed with exit code `0`).
Findings have been written to `.ai/verify_setup.md` and `status.json` has been updated with `"outcome": "succeeded"` at the workspace root.