diff --git a/assets/sprites/music_bg1.png b/assets/sprites/music_bg1.png new file mode 100644 index 0000000..ce59ab6 Binary files /dev/null and b/assets/sprites/music_bg1.png differ diff --git a/assets/sprites/music_bg2.png b/assets/sprites/music_bg2.png new file mode 100644 index 0000000..bd28396 Binary files /dev/null and b/assets/sprites/music_bg2.png differ diff --git a/assets/sprites/music_bg3.png b/assets/sprites/music_bg3.png new file mode 100644 index 0000000..bb3b887 Binary files /dev/null and b/assets/sprites/music_bg3.png differ diff --git a/assets/sprites/music_bg4.png b/assets/sprites/music_bg4.png new file mode 100644 index 0000000..40615a2 Binary files /dev/null and b/assets/sprites/music_bg4.png differ diff --git a/assets/sprites/reset1.png b/assets/sprites/reset1.png index 9b63620..9ef91b9 100644 Binary files a/assets/sprites/reset1.png and b/assets/sprites/reset1.png differ diff --git a/assets/sprites/reset2.png b/assets/sprites/reset2.png index 09ba318..c5dd8e8 100644 Binary files a/assets/sprites/reset2.png and b/assets/sprites/reset2.png differ diff --git a/assets/sprites/undo1.png b/assets/sprites/undo1.png index 993df87..95f8b2e 100644 Binary files a/assets/sprites/undo1.png and b/assets/sprites/undo1.png differ diff --git a/assets/sprites/undo2.png b/assets/sprites/undo2.png index 247befc..021d532 100644 Binary files a/assets/sprites/undo2.png and b/assets/sprites/undo2.png differ diff --git a/board.py b/board.py index 8e95854..cab0091 100644 --- a/board.py +++ b/board.py @@ -3,11 +3,10 @@ """ from __future__ import annotations - from enum import Enum from collections import defaultdict import pickle -from typing import Dict +from typing import Dict,Self from search import dhokla_first_search, best_first_search, bread_first_search from utils import Position from search import Node @@ -16,6 +15,22 @@ import argparse +def construct_matrix_from_hashmap(board_hashmap): + n=7 + matrix = [[board_hashmap[Position(i,j)] for j in range(n)] for i in range(n)] + return matrix + + +def rotate90(mat_mat): + n= len(mat_mat) + matrix = [[mat_mat[j][i] for j in range(n)] for i in range(n)] + # Reverse each row + for i in range(n): + matrix[i].reverse() + return matrix + +def str_matrix(matrix:list[list[NodeState]]): + return "".join(["".join([str(s) for s in row]) for row in matrix]) class Move: """ @@ -107,7 +122,14 @@ def construct_from_string(cls, s: str): def __hash__(self) -> int: # to allow for hashing of the board state, we use the string representation - return hash(str(self)) + # create a list of all rotated states + top = construct_matrix_from_hashmap(self._board) + hashed = hash(str_matrix(top)) + for _ in range(3): + top = rotate90(top) + hashed+=hash(str_matrix(top)) + + return hashed def __str__(self) -> str: s = "" @@ -128,38 +150,43 @@ def __getitem__(self, pos: Position) -> NodeState: def __setitem__(self, pos: Position, value: NodeState): self._board[pos] = value - def __le__(self, other): - return self.num_marbles <= other.num_marbles + def __le__(self, other:Self): + #return self.num_marbles <= other.num_marbles + return self._distance_from_center() <= other._distance_from_center() def __eq__(self, other): return hash(self) == hash(other) + def _total_possible_moves(self): + marble_positions = [pos for pos, state in self._board.items() if state == NodeState.FILLED] + total = 0 + for marble in marble_positions: + total+=len(self.get_possible_move_locations(marble)) + return total + + def _distance_from_center(self): + marble_positions = [pos for pos, state in self._board.items() if state == NodeState.FILLED] + manhattan = 0 + for position in marble_positions: + diff = self._CENTER - position + manhattan += abs(diff.row) + abs(diff.column) + return manhattan + def solvable(self) -> bool: """ - Returns True if current arrangment of marbles allows for some marbles to be eliminated, else False. + Returns True if moves are still possible, else False. """ if self.num_marbles == 1: # solved! return True - if self.num_marbles > 4: - # some moves can still be made in this state - return True - marble_positions = [ pos for pos, state in self._board.items() if state == NodeState.FILLED ] # compute the distance between this marble and every other marble for marble in marble_positions: - for other in marble_positions: - if other == marble: - continue - distance = marble - other - # if either difference in column or row is odd we can eliminate a marble - if distance.row == 0 and distance.column % 2 == 1: - return True - if distance.column == 0 and distance.row % 2 == 1: - return True + if len(self.get_possible_move_locations(marble)) != 0: + return True return False def make_move(self, move: Move) -> Board | None: @@ -219,21 +246,23 @@ def make_move(self, move: Move) -> Board | None: return new_board - def get_possible_move_locations(self, pos: Position) -> list[Position]: + def get_possible_move_locations(self, src: Position) -> list[Position]: """ Returns a list of possible move-to positions from the given position """ - positions = [] + moves = [] for i in range(-2, 3, 2): if i == 0: # to avoid self moves continue - if self[pos + Position(i, 0)] == NodeState.EMPTY: - positions.append(pos + Position(i, 0)) - if self[pos + Position(0, i)] == NodeState.EMPTY: - positions.append(pos + Position(0, i)) + if self[src + Position(i, 0)] == NodeState.EMPTY: + moves.append(Move(src,src + Position(i, 0))) + if self[src + Position(0, i)] == NodeState.EMPTY: + moves.append(Move(src,src + Position(0, i))) + + # filter the positions based on if the in-between has a marble - return positions + return [move.dst for move in moves if self[move.get_in_between_pos()] == NodeState.FILLED] def move_gen(self) -> list[Board]: """ @@ -265,7 +294,7 @@ def goal_test(self) -> bool: """ Returns True if the game is over """ - return self.num_marbles == 1 + return self.num_marbles == 1 and self[Position(3,3)] == NodeState.FILLED """ diff --git a/constants.py b/constants.py index 3a520c2..151f9d3 100644 --- a/constants.py +++ b/constants.py @@ -63,8 +63,10 @@ SPRT_BESTFS_BTN = pygame.image.load(PATH_SPRITES / "bst_button1.png") SPRT_BESTFS_BTN_CLICKED = pygame.image.load(PATH_SPRITES / "bst_button2.png") -SPRT_MUSIC_OFF_BTN = pygame.image.load(PATH_SPRITES / "music_off.png") -SPRT_MUSIC_ON_BTN = pygame.image.load(PATH_SPRITES / "music_on.png") +SPRT_MUSIC_OFF_BTN_HOVERED = pygame.image.load(PATH_SPRITES / "music_bg4.png") +SPRT_MUSIC_OFF_BTN = pygame.image.load(PATH_SPRITES / "music_bg3.png") +SPRT_MUSIC_ON_BTN_HOVERED = pygame.image.load(PATH_SPRITES / "music_bg2.png") +SPRT_MUSIC_ON_BTN = pygame.image.load(PATH_SPRITES / "music_bg1.png") SPRT_RESTART_BTN = pygame.image.load(PATH_SPRITES / "reset1.png") SPRT_RESTART_BTN_CLICKED = pygame.image.load(PATH_SPRITES / "reset2.png") diff --git a/main.py b/main.py index ce857bf..e6c96c9 100644 --- a/main.py +++ b/main.py @@ -103,6 +103,8 @@ def __init__( (30 + 64 + 5, 400), c.SPRT_MUSIC_ON_BTN, c.SPRT_MUSIC_OFF_BTN, + c.SPRT_MUSIC_ON_BTN_HOVERED, + c.SPRT_MUSIC_OFF_BTN_HOVERED, ) self.undo_button = widgets.ImageButton( (99 + 64 + 5, 400), diff --git a/search/astar.py b/search/astar.py deleted file mode 100644 index e69de29..0000000 diff --git a/search/dfs.py b/search/dfs.py index 767fcf1..d85ddc9 100644 --- a/search/dfs.py +++ b/search/dfs.py @@ -7,7 +7,8 @@ def dhokla_first_search(start_node: Node): prev_marble_count = start_node.board.num_marbles open: list[Node] = [start_node] - closed: list = [] + closed: set = set() + while open != []: parent = open.pop() if parent.board.goal_test(): @@ -17,7 +18,7 @@ def dhokla_first_search(start_node: Node): prev_marble_count = parent.board.num_marbles print(f"Marbles left: {parent.board.num_marbles}") - closed.append(parent.board) + closed.add(parent.board) children: list = parent.board.move_gen() children = [child for child in children if child not in closed] @@ -43,7 +44,7 @@ def stepped_dhokla_first_search(node: Node, open: list[Node] | None, closed: set children: list = parent.board.move_gen() children = [child for child in children if child not in closed] - + for child in children: open.append(Node(child, parent))