import streamlit as st import random import os from typing import List, Tuple, Dict, Optional # Fix for Hugging Face Spaces - set Streamlit config directory os.environ['STREAMLIT_CONFIG_DIR'] = '/tmp/.streamlit' os.makedirs('/tmp/.streamlit', exist_ok=True) # Streamlit configuration for Hugging Face Spaces st.set_page_config( page_title="Puzzle8 Game", page_icon="🧩", layout="centered", initial_sidebar_state="collapsed" ) # Constants SOLVED_STATE = [1, 2, 3, 4, 5, 6, 7, 8, 0] class Puzzle8Game: def __init__(self): self.puzzle = None self.initial_puzzle = None self.moves = 0 self.game_won = False @staticmethod def pos_to_coords(pos: int) -> Dict[str, int]: """Convert position (1-9) to row, col coordinates (1-3)""" return {"row": (pos - 1) // 3 + 1, "col": (pos - 1) % 3 + 1} @staticmethod def coords_to_pos(row: int, col: int) -> int: """Convert row, col coordinates to position""" return (row - 1) * 3 + col @staticmethod def is_valid_position(row: int, col: int) -> bool: """Check if position is within 3x3 grid""" return 1 <= row <= 3 and 1 <= col <= 3 def create_solvable_puzzle(self, moves: int = 100) -> List[int]: """Create a solvable puzzle by shuffling from solved state""" puzzle = SOLVED_STATE.copy() blank_pos = 9 # Simple shuffling with valid moves for _ in range(moves): coords = self.pos_to_coords(blank_pos) valid_moves = [] # Check all four directions directions = [ {"row": -1, "col": 0}, # Up {"row": 1, "col": 0}, # Down {"row": 0, "col": -1}, # Left {"row": 0, "col": 1} # Right ] for direction in directions: new_row = coords["row"] + direction["row"] new_col = coords["col"] + direction["col"] if self.is_valid_position(new_row, new_col): valid_moves.append(self.coords_to_pos(new_row, new_col)) if valid_moves: move_pos = random.choice(valid_moves) # Swap tiles puzzle[blank_pos - 1], puzzle[move_pos - 1] = puzzle[move_pos - 1], puzzle[blank_pos - 1] blank_pos = move_pos # Ensure puzzle is not already solved if puzzle == SOLVED_STATE: return self.create_solvable_puzzle(moves + 20) return puzzle def is_solved(self, puzzle: List[int]) -> bool: """Check if puzzle is solved""" return puzzle == SOLVED_STATE def get_blank_position(self, puzzle: List[int]) -> int: """Get position of blank tile (value 0)""" return puzzle.index(0) + 1 def get_valid_moves(self, blank_pos: int) -> List[int]: """Get valid moves for current blank position""" coords = self.pos_to_coords(blank_pos) valid_moves = [] directions = [ {"row": -1, "col": 0}, # Up {"row": 1, "col": 0}, # Down {"row": 0, "col": -1}, # Left {"row": 0, "col": 1} # Right ] for direction in directions: new_row = coords["row"] + direction["row"] new_col = coords["col"] + direction["col"] if self.is_valid_position(new_row, new_col): valid_moves.append(self.coords_to_pos(new_row, new_col)) return valid_moves def move_tile(self, puzzle: List[int], tile_position: int) -> Dict: """Move tile with validation""" blank_pos = self.get_blank_position(puzzle) valid_moves = self.get_valid_moves(blank_pos) if tile_position in valid_moves: # Create new puzzle state new_puzzle = puzzle.copy() new_puzzle[blank_pos - 1] = puzzle[tile_position - 1] new_puzzle[tile_position - 1] = 0 return {"puzzle": new_puzzle, "moved": True} return {"puzzle": puzzle, "moved": False} def new_game(self): """Start a new game""" new_puzzle = self.create_solvable_puzzle() self.puzzle = new_puzzle self.initial_puzzle = new_puzzle.copy() self.moves = 0 self.game_won = False def reset_game(self): """Reset to initial puzzle state""" if self.initial_puzzle: self.puzzle = self.initial_puzzle.copy() self.moves = 0 self.game_won = False def make_move(self, position: int): """Make a move and update game state""" if not self.game_won and self.puzzle: result = self.move_tile(self.puzzle, position) if result["moved"]: self.puzzle = result["puzzle"] self.moves += 1 # Check win condition if self.is_solved(self.puzzle): self.game_won = True # Initialize game in session state if 'game' not in st.session_state: st.session_state.game = Puzzle8Game() st.session_state.game.new_game() # Custom CSS for styling st.markdown(""" """, unsafe_allow_html=True) def main(): # Page title st.markdown("""

🧩 Puzzle8

""", unsafe_allow_html=True) # Game container game = st.session_state.game # Status card (moves counter or congratulations) if game.game_won: st.markdown(f"""
🏆 Congratulations!
Puzzle solved in {game.moves} moves!
""", unsafe_allow_html=True) else: st.markdown(f"""
{game.moves}
Moves
""", unsafe_allow_html=True) # Puzzle grid st.markdown('
', unsafe_allow_html=True) # Create 3x3 grid of tiles for row in range(3): cols = st.columns(3) for col in range(3): i = row * 3 + col with cols[col]: if game.puzzle and len(game.puzzle) > i: value = game.puzzle[i] if value == 0: # Empty tile st.markdown('
', unsafe_allow_html=True) else: # Number tile - make it clickable if st.button(str(value), key=f"tile_{i+1}"): game.make_move(i + 1) st.rerun() st.markdown('
', unsafe_allow_html=True) # Control buttons col1, col2 = st.columns(2) with col1: st.markdown('
', unsafe_allow_html=True) if st.button("🎲 New Game", key="new_game", type="primary"): game.new_game() st.rerun() st.markdown('
', unsafe_allow_html=True) with col2: st.markdown('
', unsafe_allow_html=True) if st.button("🔄 Reset", key="reset_game", type="secondary"): game.reset_game() st.rerun() st.markdown('
', unsafe_allow_html=True) # Instructions with st.expander("🎯 How to Play", expanded=False): st.markdown(""" **Goal:** Arrange numbers 1-8 in order with the empty space in the bottom-right corner. **Rules:** - Click on tiles adjacent to the empty space to move them - Only tiles next to the empty space can be moved - Try to solve the puzzle in as few moves as possible! **Target arrangement:** """) # Goal grid visualization st.markdown("""
1
2
3
4
5
6
7
8
""", unsafe_allow_html=True) st.markdown('
', unsafe_allow_html=True) if __name__ == "__main__": main()