Spaces:
Running
Galaxies Load State Dict Implementation Plan
This document outlines the plan for implementing load_state_dict functionality for the Galaxies puzzle, following the template from LOAD_STATE_DICT_IMPLEMENTATION_GUIDE.md.
Clarifying Questions
Before proceeding with implementation, we need to clarify the following:
1. Canonical Fields Identification
Question: Which fields from get_puzzle_state_galaxies() are truly canonical vs derived?
Current get_puzzle_state_galaxies() returns:
w,h- Canonical (from params)sx,sy- Derived (computed as(w*2)+1,(h*2)+1)grid- Canonical (array of space structs with flags, dotx, doty, nassoc)completed- Derived (computed bycheck_complete())used_solve- UI state (not canonical)ndots- Derived (computed bygame_update_dots())dots_indices- Derived (computed from grid by scanning for F_DOT flags)cdiff- Derived (difficulty of current puzzle, not needed for reconstruction)
Proposed canonical fields for repr struct:
w,h- dimensionsgrid- array of space structs (only canonical fields: x, y, type, flags, dotx, doty, nassoc)
Questions:
- Should we include
sx,syin the repr, or always compute them fromw,h? compute from w,h - Should we store
dots_indicesor reconstruct thedotsarray from grid by callinggame_update_dots()? - Which flags in the
space.flagsfield are canonical vs solver/UI flags? (F_DOT, F_EDGE_SET, F_TILE_ASSOC are canonical; F_MARK, F_REACHABLE, F_SCRATCH, F_MULTIPLE, F_DOT_HOLD, F_GOOD, F_DOT_BLACK are likely not)
2. Space Struct Fields
Question: Which fields in the space struct are canonical?
The space struct has:
x,y- position (can be recomputed from index)type- s_tile, s_edge, s_vertex (can be recomputed from x, y position)flags- bitfield (needs careful filtering)dotx,doty- if F_TILE_ASSOC is set (canonical)nassoc- if F_DOT is set (canonical)
Proposed approach:
- Store only:
flags(filtered),dotx,doty,nassoc - Recompute
x,y,typeduring reconstruction
3. Solver Flags to Clear
Question: Which flags need to be cleared after verification?
From the code, solver flags that should be cleared:
F_MARK(0x10) - scratch flagF_REACHABLE(0x20)F_SCRATCH(0x40)F_MULTIPLE(0x80)F_DOT_HOLD(0x100)F_GOOD(0x200)
Canonical flags to preserve:
F_DOT(0x1)F_EDGE_SET(0x2)F_TILE_ASSOC(0x4)F_DOT_BLACK(0x8) - UI flag, but might be needed?
4. Reconstruction Steps
Question: What internal structures need to be rebuilt?
From analyzing the code:
dotsarray - needs to be rebuilt from grid usinggame_update_dots()ndots- computed bygame_update_dots()completed- computed bycheck_complete()sx,sy- computed fromw,hinblank_game()
Proposed reconstruction order:
- Create params from repr (w, h, diff=0)
- Create blank state using
blank_game()or similar - Copy grid array (filtering flags)
- Call
game_update_dots()to rebuild dots array - Call
check_complete()to setcompletedflag - Clear solver flags from grid
5. Validation Requirements
Question: What validation is needed in the Python layer?
- Validate
w,hare integers and within reasonable bounds - Validate
gridis a list with lengthsx * sywheresx = (w*2)+1,sy = (h*2)+1 - Validate each space in grid has required fields (flags, dotx, doty, nassoc)
- Validate
dotx,dotyare within bounds ifF_TILE_ASSOCis set - Validate
nassocis non-negative ifF_DOTis set
Implementation Plan
Phase 1: C Layer - Repr Structs
File: puzzles/galaxies.c
Location: Before game_state struct (around line 178)
/* Repr structs for Python state dict loading */
typedef struct galaxies_space_repr {
unsigned int flags; /* Only canonical flags: F_DOT, F_EDGE_SET, F_TILE_ASSOC */
int dotx, doty; /* If flags & F_TILE_ASSOC */
int nassoc; /* If flags & F_DOT */
} galaxies_space_repr;
typedef struct galaxies_state_repr {
int w;
int h;
const galaxies_space_repr *grid; /* Length: (w*2+1) * (h*2+1) */
} galaxies_state_repr;
Key Decisions:
- Only store canonical flags (F_DOT, F_EDGE_SET, F_TILE_ASSOC)
- Store dotx, doty, nassoc as they are part of the game state
- Do NOT store x, y, type (recomputed from position)
- Do NOT store sx, sy (recomputed from w, h)
Phase 2: C Layer - Reconstruction Function
File: puzzles/galaxies.c
Location: After load_game() function (around line 1757)
Function: galaxies_state_from_repr()
Implementation steps:
- Validate input (check r != NULL)
- Create params:
w = r->w,h = r->h,diff = 0 - Create blank state using
blank_game(w, h) - Compute
sx = (w*2)+1,sy = (h*2)+1,sz = sx * sy - Copy grid array:
- For each space in repr->grid:
- Set
flags(only canonical flags) - Set
dotx,dotyifF_TILE_ASSOCis set - Set
nassocifF_DOTis set - Preserve
x,y,typefrom blank_game
- Set
- For each space in repr->grid:
- Call
game_update_dots(state)to rebuild dots array - Call
check_complete(state, NULL, NULL)to setcompletedflag - Clear solver flags from grid:
for (i = 0; i < sz; i++) { state->grid[i].flags &= ~(F_MARK | F_REACHABLE | F_SCRATCH | F_MULTIPLE | F_DOT_HOLD | F_GOOD); } - Set
used_solve = false,cdiff = -1 - Return state pointer
Phase 3: Python ctypes Layer - Struct Definitions
File: rlp/specific_api.py
Location: In set_api_structures_puzzle() function, add before the function:
class GalaxiesSpaceRepr(c.Structure):
pass
class GalaxiesStateRepr(c.Structure):
pass
Inside set_api_structures_puzzle() function:
if puzzle_name == "galaxies":
GalaxiesSpaceRepr._fields_ = [
("flags", c.c_uint),
("dotx", c.c_int),
("doty", c.c_int),
("nassoc", c.c_int),
]
GalaxiesStateRepr._fields_ = [
("w", c.c_int),
("h", c.c_int),
("grid", CT_PTR(GalaxiesSpaceRepr)),
]
Phase 4: Python API Layer - Wire C Function
File: rlp/puzzle.py
Location: In Puzzle.__init__() method, add after undead section (around line 188):
elif self.puzzle_name == "galaxies":
# Add state_from_repr function
from rlp import specific_api as specific
self._state_from_repr = wrap_function(
self._lib, "galaxies_state_from_repr",
api.specific.GAMESTATE_PTR,
[c.POINTER(specific.GalaxiesStateRepr)]
)
else:
self._text_parse = None
self._state_from_repr = None
Phase 5: Python API Layer - Load Function
File: rlp/specific_api.py
Location: After load_state_dict_undead() function (around line 3460)
Function: load_state_dict_galaxies()
Implementation:
- Validate required fields:
w,h,grid - Validate types:
w,hare ints,gridis a list - Compute
sx = (w*2)+1,sy = (h*2)+1,sz = sx * sy - Validate
gridlength matchessz - Validate each space in grid:
- Has
flags,dotx,doty,nassocfields flagsis an integerdotx,doty,nassocare integers- If
F_TILE_ASSOCis set, validatedotx,dotyare within bounds - If
F_DOTis set, validatenassoc >= 0
- Has
- Create ctypes arrays:
grid_arr = (GalaxiesSpaceRepr * sz)(...)
- Build
GalaxiesStateReprstruct - Call C function
galaxies_state_from_repr() - Return state pointer
Phase 6: Python API Layer - Add Puzzle Method
File: rlp/puzzle.py
Location: In Puzzle class, add load_state_dict() method (around line 530)
Update existing method to handle galaxies:
def load_state_dict(self, state_dict: dict) -> api.specific.GAMESTATE_PTR:
"""
Load a puzzle game state from a Python state dict.
Args:
state_dict: Dictionary containing state information (from get_puzzle_state)
Returns:
GameState pointer (caller must free it using game.free_game())
Raises:
ValueError: If puzzle doesn't support this or if state dict is invalid
"""
if self.puzzle_name not in ("bridges", "undead", "galaxies"):
raise ValueError(f"load_state_dict is only supported for bridges, undead, and galaxies puzzles, not {self.puzzle_name}")
if self._state_from_repr is None:
raise ValueError("puzzle_state_from_repr function not available")
from rlp import specific_api as specific
# Call the load function
if self.puzzle_name == "galaxies":
state_ptr = specific.load_state_dict_galaxies(state_dict, self._lib)
elif self.puzzle_name == "undead":
state_ptr = specific.load_state_dict_undead(state_dict, self._lib)
elif self.puzzle_name == "bridges":
state_ptr = specific.load_state_dict_bridges(state_dict, self._lib)
return state_ptr
Phase 7: Testing
File: test_load_state_dict_galaxies.py (new file)
Test cases:
test_load_problem_state- Load initial problem state, verify it matchestest_load_solution_state- Load solved state, verifycompleted=Truetest_round_trip_problem- Problem state → dict → load → dict → comparetest_round_trip_solution- Solution state → dict → load → dict → comparetest_multiple_sizes- Test with different puzzle sizes (5x5, 7x7, 10x10)test_state_verification- Verifycheck_complete()results matchtest_swap_states_between_instances- Swap states between two puzzle instancestest_validation_errors- Test invalid state dicts raise appropriate exceptionstest_dots_reconstruction- Verify dots array is correctly rebuilttest_solver_flags_cleared- Verify solver flags are cleared after loading
Ignore fields for comparison:
sx,sy(computed from w, h)ndots(computed from grid)dots_indices(computed from grid)completed(computed by check_complete, but should match)used_solve(UI state)cdiff(derived)
Open Questions Requiring Answers
Should
F_DOT_BLACKbe considered canonical? It's a UI flag but might affect puzzle state representation.Should we validate that
dots_indicesin the state dict matches the actual dots found in grid? This could catch inconsistencies.How should we handle the
dotsarray pointer comparison? The pointers will be different after reconstruction, but the contents should match.Should
sx,sybe included in the repr for validation purposes, even though they're derived?What are the bounds for
dotx,doty? Should they be validated againstsx,syorw,h?
Implementation Checklist
- Define C repr structs (
galaxies_space_repr,galaxies_state_repr) - Implement C reconstruction function (
galaxies_state_from_repr)- Create params from repr
- Create blank state
- Copy grid array (filtering flags)
- Rebuild dots array (
game_update_dots) - Run verification (
check_complete) - Clear solver flags
- Define Python ctypes structs (match C exactly)
- Wire C function in
puzzle.py - Implement Python load function with validation
- Update
Puzzle.load_state_dict()method - Create comprehensive tests
- Recompile library (
cmake --build rlp/lib --target libgalaxies) - Run tests and verify all pass
Files to Modify
puzzles/galaxies.c:
- Add repr structs
- Implement
galaxies_state_from_repr()function
rlp/specific_api.py:
- Add
GalaxiesSpaceReprandGalaxiesStateReprctypes structures - Implement
load_state_dict_galaxies()function
- Add
rlp/puzzle.py:
- Wire
galaxies_state_from_reprin__init__() - Update
load_state_dict()method to handle galaxies
- Wire
test_load_state_dict_galaxies.py (new):
- Create comprehensive test suite
Next Steps
- Answer clarifying questions above
- Review and approve this plan
- Implement Phase 1-2 (C layer)
- Test C layer with simple test cases
- Implement Phase 3-6 (Python layer)
- Create and run tests (Phase 7)
- Fix any issues found during testing
- Document any deviations from the plan