From f4ec5b586986d33a7ff96e6dbe0bc228623159af Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Mon, 11 Apr 2022 19:18:03 +0200 Subject: [PATCH 01/15] feat: models --- algorithms/a_star.py | 25 +++++++++++++++++++++++++ algorithms/bfs.py | 2 +- common/constants.py | 1 + logic/spawner.py | 10 +++++----- 4 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 algorithms/a_star.py diff --git a/algorithms/a_star.py b/algorithms/a_star.py new file mode 100644 index 0000000..d3e3077 --- /dev/null +++ b/algorithms/a_star.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Tuple, Optional + +from common.constants import Direction + + +@dataclass +class State: + position: Tuple[int, int] + direction: Direction + + +@dataclass +class Node: + state: State + parent: Optional[Node] + action: str + cost: int = field(init=False) + depth: int = field(init=False) + + def __post_init__(self) -> None: + self.cost = 0 if not self.parent else self.parent.cost + 1 + self.depth = self.cost diff --git a/algorithms/bfs.py b/algorithms/bfs.py index e900faa..b2ed3d3 100644 --- a/algorithms/bfs.py +++ b/algorithms/bfs.py @@ -114,7 +114,7 @@ def go(row, column, direction): def is_valid_move(map, target_row, target_column): - if 0 <= target_row < ROWS and 0 <= target_column < COLUMNS and map[target_row][target_column] == ' ': + if 0 <= target_row < ROWS and 0 <= target_column < COLUMNS and map[target_row][target_column] in ['g', 's', ' ']: return True return False diff --git a/common/constants.py b/common/constants.py index 37dd0ae..692c32d 100644 --- a/common/constants.py +++ b/common/constants.py @@ -29,6 +29,7 @@ CASTLE_SPAWN_FIRST_COL = 9 NBR_OF_WATER = 16 NBR_OF_TREES = 20 NBR_OF_MONSTERS = 2 +NBR_OF_SANDS = 15 TILES = [ 'grass1.png', diff --git a/logic/spawner.py b/logic/spawner.py index 67431e4..1e91795 100644 --- a/logic/spawner.py +++ b/logic/spawner.py @@ -8,7 +8,7 @@ class Spawner: self.map = map def __is_free_field(self, field): - return field == ' ' + return field in ['g', 's', ' '] def spawn_in_area(self, objects: list, spawn_area_pos_row=0, spawn_area_pos_column=0, spawn_area_width=0, spawn_area_height=0, size=1): @@ -17,17 +17,17 @@ class Spawner: while spawned_objects_count != len(objects): x = random.randint(0, spawn_area_height) + spawn_area_pos_row y = random.randint(0, spawn_area_width) + spawn_area_pos_column - if x < ROWS-1 and y < COLUMNS-1 and self.__is_free_field(self.map[x][y]): + if x < ROWS - 1 and y < COLUMNS - 1 and self.__is_free_field(self.map[x][y]): for i in range(size): for j in range(size): - self.map[x-i][y-j] = objects[spawned_objects_count] + self.map[x - i][y - j] = objects[spawned_objects_count] spawned_objects_count += 1 def spawn_where_possible(self, objects: list): spawned_objects_count = 0 while spawned_objects_count != len(objects): - x = random.randint(0, ROWS-1) - y = random.randint(0, COLUMNS-1) + x = random.randint(0, ROWS - 1) + y = random.randint(0, COLUMNS - 1) if self.__is_free_field(self.map[x][y]): self.map[x][y] = objects[spawned_objects_count] spawned_objects_count += 1 From 10485ce39d6264ca46c6362049ec53be3325a05d Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Mon, 11 Apr 2022 19:49:32 +0200 Subject: [PATCH 02/15] feat: enums --- algorithms/a_star.py | 50 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index d3e3077..c593f69 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -1,9 +1,23 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Tuple, Optional +from enum import Enum, unique +from typing import Tuple, Optional, List -from common.constants import Direction + +@unique +class Direction(Enum): + LEFT = (0, -1) + RIGHT = (0, 1) + UP = (-1, 0) + DOWN = (1, 0) + + +@unique +class Action(Enum): + TURN_LEFT = 'TURN_LEFT' + TURN_RIGHT = 'TURN_RIGHT' + FORWARD = 'FORWARD' @dataclass @@ -16,10 +30,40 @@ class State: class Node: state: State parent: Optional[Node] - action: str + action: Action cost: int = field(init=False) depth: int = field(init=False) def __post_init__(self) -> None: self.cost = 0 if not self.parent else self.parent.cost + 1 self.depth = self.cost + + def __eq__(self, other: Node) -> bool: + return self.state == other.state + + def __hash__(self) -> int: + return hash(self.state) + + +def child_node(action: Action) -> Node: + pass + + +def actions(state: State) -> List[str]: + pass + + +def result(state: State, action: Action) -> State: + pass + + +def goal_test(state: State, goal_list: List[Tuple[int, int]]) -> bool: + pass + + +def h(state: State) -> int: + pass + + +def f(state: State) -> int: + pass From 019b3c86bead4a415953e8496939350298dccb5c Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Mon, 11 Apr 2022 20:04:53 +0200 Subject: [PATCH 03/15] feat: expand and child node --- algorithms/a_star.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index c593f69..c18872d 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -45,11 +45,16 @@ class Node: return hash(self.state) -def child_node(action: Action) -> Node: - pass +def expand(node: Node) -> List[Node]: + return [child_node(node=node, action=action) for action in actions(node.state)] -def actions(state: State) -> List[str]: +def child_node(node: Node, action: Action) -> Node: + next_state = result(state=node.state, action=action) + return Node(state=next_state, parent=node, action=action) + + +def actions(state: State) -> List[Action]: pass From b68013a0cd62c5431b482c70937388ecd25ce6b5 Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Mon, 11 Apr 2022 21:52:21 +0200 Subject: [PATCH 04/15] feat: actions --- algorithms/a_star.py | 56 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index c18872d..82c9812 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -4,6 +4,10 @@ from dataclasses import dataclass, field from enum import Enum, unique from typing import Tuple, Optional, List +from common.constants import ROWS, COLUMNS + +FREE_FIELD = ' ' + @unique class Direction(Enum): @@ -54,21 +58,57 @@ def child_node(node: Node, action: Action) -> Node: return Node(state=next_state, parent=node, action=action) -def actions(state: State) -> List[Action]: - pass +def next_position(current_position: Tuple[int, int], direction: Direction) -> Tuple[int, int]: + x1, y1 = direction.value + x2, y2 = current_position + return x1 + x2, y1 + y2 -def result(state: State, action: Action) -> State: +def valid_move(position: Tuple[int, int], grid: List[List[str]]) -> bool: + row, col = position + return grid[row][col] == FREE_FIELD + + +def actions(state: State, grid: List[List[str]]) -> List[Action]: + possible_actions = [Action.FORWARD, Action.TURN_LEFT, Action.TURN_RIGHT] + row, col = state.position + direction = state.direction + + if direction == Direction.UP and row == 0: + remove_forward(possible_actions) + if direction == Direction.DOWN and row == ROWS - 1: + remove_forward(possible_actions) + if direction == Direction.LEFT and col == 0: + remove_forward(possible_actions) + if direction == Direction.RIGHT and col == COLUMNS - 1: + remove_forward(possible_actions) + + if not valid_move(next_position(state.position, direction), grid): + remove_forward(possible_actions) + + return possible_actions + + +def remove_forward(possible_actions: List[Action]) -> None: + if Action.FORWARD in possible_actions: + possible_actions.remove(Action.FORWARD) + + +def result(state: State, action: Action, grid: List[List[str]]) -> State: pass def goal_test(state: State, goal_list: List[Tuple[int, int]]) -> bool: - pass + return state.position in goal_list -def h(state: State) -> int: - pass +def h(state: State, goal: Tuple[int, int]) -> int: + """heuristics that calculates Manhattan distance between current position and goal""" + x1, y1 = state.position + x2, y2 = goal + return abs(x1 - x2) + abs(y1 - y2) # Manhattan distance -def f(state: State) -> int: - pass +def f(current_node: Node, goal: Tuple[int, int]) -> int: + """f(n) = g(n) + h(n), g stands for current cost, h for heuristics""" + return current_node.cost + h(state=current_node.state, goal=goal) From fbbb521a4bf26c717e60451c52026a293cc1369d Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Mon, 11 Apr 2022 22:52:58 +0200 Subject: [PATCH 05/15] fix: actions --- algorithms/a_star.py | 65 ++++++++++++++++++++++---------------------- logic/level.py | 4 +++ 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index 82c9812..46da7e6 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -1,40 +1,39 @@ from __future__ import annotations from dataclasses import dataclass, field -from enum import Enum, unique from typing import Tuple, Optional, List from common.constants import ROWS, COLUMNS FREE_FIELD = ' ' +LEFT = 'LEFT' +RIGHT = 'RIGHT' +UP = 'UP' +DOWN = 'DOWN' +directions = { + LEFT: (0, -1), + RIGHT: (0, 1), + UP: (-1, 0), + DOWN: (1, 0) +} -@unique -class Direction(Enum): - LEFT = (0, -1) - RIGHT = (0, 1) - UP = (-1, 0) - DOWN = (1, 0) - - -@unique -class Action(Enum): - TURN_LEFT = 'TURN_LEFT' - TURN_RIGHT = 'TURN_RIGHT' - FORWARD = 'FORWARD' +TURN_LEFT = 'TURN_LEFT' +TURN_RIGHT = 'TURN_RIGHT' +FORWARD = 'FORWARD' @dataclass class State: position: Tuple[int, int] - direction: Direction + direction: str @dataclass class Node: state: State parent: Optional[Node] - action: Action + action: str cost: int = field(init=False) depth: int = field(init=False) @@ -53,15 +52,15 @@ def expand(node: Node) -> List[Node]: return [child_node(node=node, action=action) for action in actions(node.state)] -def child_node(node: Node, action: Action) -> Node: +def child_node(node: Node, action: str) -> Node: next_state = result(state=node.state, action=action) return Node(state=next_state, parent=node, action=action) -def next_position(current_position: Tuple[int, int], direction: Direction) -> Tuple[int, int]: - x1, y1 = direction.value - x2, y2 = current_position - return x1 + x2, y1 + y2 +def next_position(current_position: Tuple[int, int], direction: str) -> Tuple[int, int]: + next_row, next_col = directions[direction] + row, col = current_position + return next_row + row, next_col + col def valid_move(position: Tuple[int, int], grid: List[List[str]]) -> bool: @@ -69,32 +68,32 @@ def valid_move(position: Tuple[int, int], grid: List[List[str]]) -> bool: return grid[row][col] == FREE_FIELD -def actions(state: State, grid: List[List[str]]) -> List[Action]: - possible_actions = [Action.FORWARD, Action.TURN_LEFT, Action.TURN_RIGHT] +def actions(state: State, grid: List[List[str]]) -> List[str]: + possible_actions = [FORWARD, TURN_LEFT, TURN_RIGHT] row, col = state.position direction = state.direction - if direction == Direction.UP and row == 0: + if direction == UP and row == 0: remove_forward(possible_actions) - if direction == Direction.DOWN and row == ROWS - 1: + if direction == DOWN and row == ROWS - 1: remove_forward(possible_actions) - if direction == Direction.LEFT and col == 0: + if direction == LEFT and col == 0: remove_forward(possible_actions) - if direction == Direction.RIGHT and col == COLUMNS - 1: + if direction == RIGHT and col == COLUMNS - 1: remove_forward(possible_actions) - if not valid_move(next_position(state.position, direction), grid): + if FORWARD not in possible_actions and not valid_move(next_position(state.position, direction), grid): remove_forward(possible_actions) return possible_actions -def remove_forward(possible_actions: List[Action]) -> None: - if Action.FORWARD in possible_actions: - possible_actions.remove(Action.FORWARD) +def remove_forward(possible_actions: List[str]) -> None: + if FORWARD in possible_actions: + possible_actions.remove(FORWARD) -def result(state: State, action: Action, grid: List[List[str]]) -> State: +def result(state: State, action: str, grid: List[List[str]]) -> State: pass @@ -106,7 +105,7 @@ def h(state: State, goal: Tuple[int, int]) -> int: """heuristics that calculates Manhattan distance between current position and goal""" x1, y1 = state.position x2, y2 = goal - return abs(x1 - x2) + abs(y1 - y2) # Manhattan distance + return abs(x1 - x2) + abs(y1 - y2) def f(current_node: Node, goal: Tuple[int, int]) -> int: diff --git a/logic/level.py b/logic/level.py index 17db1f0..4e738ea 100644 --- a/logic/level.py +++ b/logic/level.py @@ -2,6 +2,7 @@ import random import pygame +import algorithms.a_star as a_s from algorithms.bfs import graphsearch, State from common.constants import * from common.helpers import castle_neighbors @@ -110,6 +111,9 @@ class Level: castle_cords = (self.list_castles[0].position[0], self.list_castles[0].position[1]) goal_list = castle_neighbors(self.map, castle_cords[0], castle_cords[1]) # list of castle neighbors + print(knight_pos_x, knight_pos_y) + st = a_s.State((knight_pos_x, knight_pos_y), a_s.UP) + print(f'Actions: {a_s.actions(st, self.map)}') action_list = graphsearch(state, self.map, goal_list) print(action_list) From 6c271999881351b47a265b76b23afb6bbc0e266e Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Mon, 11 Apr 2022 23:53:50 +0200 Subject: [PATCH 06/15] feat: result function --- algorithms/a_star.py | 46 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index 46da7e6..524eca5 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -11,6 +11,10 @@ RIGHT = 'RIGHT' UP = 'UP' DOWN = 'DOWN' +TURN_LEFT = 'TURN_LEFT' +TURN_RIGHT = 'TURN_RIGHT' +FORWARD = 'FORWARD' + directions = { LEFT: (0, -1), RIGHT: (0, 1), @@ -18,10 +22,6 @@ directions = { DOWN: (1, 0) } -TURN_LEFT = 'TURN_LEFT' -TURN_RIGHT = 'TURN_RIGHT' -FORWARD = 'FORWARD' - @dataclass class State: @@ -93,8 +93,42 @@ def remove_forward(possible_actions: List[str]) -> None: possible_actions.remove(FORWARD) -def result(state: State, action: str, grid: List[List[str]]) -> State: - pass +def result(state: State, action: str) -> State: + new_state = State(state.position, state.direction) + + if state.direction == UP: + if action == TURN_LEFT: + new_state.direction = LEFT + elif action == TURN_RIGHT: + new_state.direction = RIGHT + elif action == FORWARD: + new_state.position = next_position(state.position, UP) + + elif state.direction == DOWN: + if action == TURN_LEFT: + new_state.direction = RIGHT + elif action == TURN_RIGHT: + new_state.direction = LEFT + elif action == FORWARD: + new_state.position = next_position(state.position, DOWN) + + elif state.direction == LEFT: + if action == TURN_LEFT: + new_state.direction = DOWN + elif action == TURN_RIGHT: + new_state.direction = UP + elif action == FORWARD: + new_state.position = next_position(state.position, LEFT) + + elif state.direction == RIGHT: + if action == TURN_LEFT: + new_state.direction = UP + elif action == TURN_RIGHT: + new_state.direction = DOWN + elif action == FORWARD: + new_state.position = next_position(state.position, RIGHT) + + return new_state def goal_test(state: State, goal_list: List[Tuple[int, int]]) -> bool: From 2bfaff8a3031295856c70019865fa78b8951f949 Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Mon, 11 Apr 2022 23:56:35 +0200 Subject: [PATCH 07/15] update: changed variable name in a_star.py --- algorithms/a_star.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index 524eca5..0d3ba5f 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -94,41 +94,41 @@ def remove_forward(possible_actions: List[str]) -> None: def result(state: State, action: str) -> State: - new_state = State(state.position, state.direction) + next_state = State(state.position, state.direction) if state.direction == UP: if action == TURN_LEFT: - new_state.direction = LEFT + next_state.direction = LEFT elif action == TURN_RIGHT: - new_state.direction = RIGHT + next_state.direction = RIGHT elif action == FORWARD: - new_state.position = next_position(state.position, UP) + next_state.position = next_position(state.position, UP) elif state.direction == DOWN: if action == TURN_LEFT: - new_state.direction = RIGHT + next_state.direction = RIGHT elif action == TURN_RIGHT: - new_state.direction = LEFT + next_state.direction = LEFT elif action == FORWARD: - new_state.position = next_position(state.position, DOWN) + next_state.position = next_position(state.position, DOWN) elif state.direction == LEFT: if action == TURN_LEFT: - new_state.direction = DOWN + next_state.direction = DOWN elif action == TURN_RIGHT: - new_state.direction = UP + next_state.direction = UP elif action == FORWARD: - new_state.position = next_position(state.position, LEFT) + next_state.position = next_position(state.position, LEFT) elif state.direction == RIGHT: if action == TURN_LEFT: - new_state.direction = UP + next_state.direction = UP elif action == TURN_RIGHT: - new_state.direction = DOWN + next_state.direction = DOWN elif action == FORWARD: - new_state.position = next_position(state.position, RIGHT) + next_state.position = next_position(state.position, RIGHT) - return new_state + return next_state def goal_test(state: State, goal_list: List[Tuple[int, int]]) -> bool: From 544c5276d582f2de94c422a1c128cb15e7647dfc Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Tue, 12 Apr 2022 00:05:47 +0200 Subject: [PATCH 08/15] feat: added a_star method --- algorithms/a_star.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index 0d3ba5f..c657c1e 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -145,3 +145,7 @@ def h(state: State, goal: Tuple[int, int]) -> int: def f(current_node: Node, goal: Tuple[int, int]) -> int: """f(n) = g(n) + h(n), g stands for current cost, h for heuristics""" return current_node.cost + h(state=current_node.state, goal=goal) + + +def a_star(state: State, grid: List[List[str]], goals: List[Tuple[int, int]]) -> List[str]: + return [] From 286164c1dd30d8006287191090b07136bf98f588 Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Tue, 12 Apr 2022 00:21:30 +0200 Subject: [PATCH 09/15] feat: a_star implementation --- algorithms/a_star.py | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index c657c1e..d6f630a 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -1,5 +1,6 @@ from __future__ import annotations +import heapq from dataclasses import dataclass, field from typing import Tuple, Optional, List @@ -33,7 +34,7 @@ class State: class Node: state: State parent: Optional[Node] - action: str + action: Optional[str] cost: int = field(init=False) depth: int = field(init=False) @@ -48,8 +49,8 @@ class Node: return hash(self.state) -def expand(node: Node) -> List[Node]: - return [child_node(node=node, action=action) for action in actions(node.state)] +def expand(node: Node, grid: List[List[str]]) -> List[Node]: + return [child_node(node=node, action=action) for action in actions(node.state, grid)] def child_node(node: Node, action: str) -> Node: @@ -147,5 +148,37 @@ def f(current_node: Node, goal: Tuple[int, int]) -> int: return current_node.cost + h(state=current_node.state, goal=goal) +def get_path_from_start(node: Node) -> List[str]: + path = [node] + + while node.parent is not None: + node = node.parent + path.append(node.action) + + path.reverse() + return path + + def a_star(state: State, grid: List[List[str]], goals: List[Tuple[int, int]]) -> List[str]: + node = Node(state=state, parent=None, action=None) + + frontier = list() + heapq.heappush(frontier, (f(node, goals[0]), node)) + explored = set() + + while frontier: + r, node = heapq.heappop(frontier) + + if goal_test(node.state, goals): + return get_path_from_start(node) + + explored.add(node.state) + + for child in expand(node, grid): + p = f(child, goals[0]) + if child.state not in explored and (p, child) not in frontier: + heapq.heappush(frontier, (p, child)) + elif (r, child) in frontier and r > p: + heapq.heappush(frontier, (p, child)) + return [] From 6d4891fab8e7d2df9d6715a86e8d8de8b8d94705 Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Tue, 12 Apr 2022 00:35:01 +0200 Subject: [PATCH 10/15] fix: integration with level --- algorithms/a_star.py | 9 ++++++++ logic/level.py | 52 ++++++++++++++++++++++---------------------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index d6f630a..5d45dd3 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -29,6 +29,12 @@ class State: position: Tuple[int, int] direction: str + def __lt__(self, state): + return self.position < state.position + + def __hash__(self) -> int: + return hash(self.position) + @dataclass class Node: @@ -38,6 +44,9 @@ class Node: cost: int = field(init=False) depth: int = field(init=False) + def __lt__(self, node): + return self.state < node.state + def __post_init__(self) -> None: self.cost = 0 if not self.parent else self.parent.cost + 1 self.depth = self.cost diff --git a/logic/level.py b/logic/level.py index 4e738ea..1485f6f 100644 --- a/logic/level.py +++ b/logic/level.py @@ -2,8 +2,7 @@ import random import pygame -import algorithms.a_star as a_s -from algorithms.bfs import graphsearch, State +from algorithms.a_star import a_star, State from common.constants import * from common.helpers import castle_neighbors from logic.knights_queue import KnightsQueue @@ -107,37 +106,38 @@ class Level: current_knight = self.knights_queue.dequeue_knight() knight_pos_x = current_knight.position[0] knight_pos_y = current_knight.position[1] - state = State(knight_pos_y, knight_pos_x, current_knight.direction) castle_cords = (self.list_castles[0].position[0], self.list_castles[0].position[1]) goal_list = castle_neighbors(self.map, castle_cords[0], castle_cords[1]) # list of castle neighbors - print(knight_pos_x, knight_pos_y) - st = a_s.State((knight_pos_x, knight_pos_y), a_s.UP) - print(f'Actions: {a_s.actions(st, self.map)}') - action_list = graphsearch(state, self.map, goal_list) - print(action_list) + + state = State((knight_pos_y, knight_pos_x), current_knight.direction.name) + action_list = a_star(state, self.map, goal_list) + + for action in action_list: + print(action) + print() if len(action_list) == 0: return - next_action = action_list.pop(0) - if next_action == ACTION.get("rotate_left"): - current_knight.rotate_left() - elif next_action == ACTION.get("rotate_right"): - current_knight.rotate_right() - elif next_action == ACTION.get("go"): - current_knight.step_forward() - self.map[knight_pos_y][knight_pos_x] = ' ' - - # update knight on map - if current_knight.direction.name == 'UP': - self.map[knight_pos_y - 1][knight_pos_x] = current_knight - elif current_knight.direction.name == 'RIGHT': - self.map[knight_pos_y][knight_pos_x + 1] = current_knight - elif current_knight.direction.name == 'DOWN': - self.map[knight_pos_y + 1][knight_pos_x] = current_knight - elif current_knight.direction.name == 'LEFT': - self.map[knight_pos_y][knight_pos_x - 1] = current_knight + # next_action = action_list.pop(0) + # if next_action == ACTION.get("rotate_left"): + # current_knight.rotate_left() + # elif next_action == ACTION.get("rotate_right"): + # current_knight.rotate_right() + # elif next_action == ACTION.get("go"): + # current_knight.step_forward() + # self.map[knight_pos_y][knight_pos_x] = ' ' + # + # # update knight on map + # if current_knight.direction.name == 'UP': + # self.map[knight_pos_y - 1][knight_pos_x] = current_knight + # elif current_knight.direction.name == 'RIGHT': + # self.map[knight_pos_y][knight_pos_x + 1] = current_knight + # elif current_knight.direction.name == 'DOWN': + # self.map[knight_pos_y + 1][knight_pos_x] = current_knight + # elif current_knight.direction.name == 'LEFT': + # self.map[knight_pos_y][knight_pos_x - 1] = current_knight def update(self): bg_width = (GRID_CELL_PADDING + GRID_CELL_SIZE) * COLUMNS + BORDER_WIDTH From 7131d1f905c1680990c9347fa08ec3da9228e7d4 Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Tue, 12 Apr 2022 19:12:41 +0200 Subject: [PATCH 11/15] feat: movement --- algorithms/a_star.py | 13 +++++++------ logic/level.py | 43 ++++++++++++++++++++----------------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index 5d45dd3..67f0924 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -29,6 +29,9 @@ class State: position: Tuple[int, int] direction: str + def __eq__(self, other: State) -> bool: + return other.position == self.position and self.direction == other.direction + def __lt__(self, state): return self.position < state.position @@ -44,16 +47,13 @@ class Node: cost: int = field(init=False) depth: int = field(init=False) - def __lt__(self, node): + def __lt__(self, node) -> None: return self.state < node.state def __post_init__(self) -> None: self.cost = 0 if not self.parent else self.parent.cost + 1 self.depth = self.cost - def __eq__(self, other: Node) -> bool: - return self.state == other.state - def __hash__(self) -> int: return hash(self.state) @@ -158,11 +158,12 @@ def f(current_node: Node, goal: Tuple[int, int]) -> int: def get_path_from_start(node: Node) -> List[str]: - path = [node] + path = [node.action] while node.parent is not None: node = node.parent - path.append(node.action) + if node.action: + path.append(node.action) path.reverse() return path diff --git a/logic/level.py b/logic/level.py index 1485f6f..e657bc3 100644 --- a/logic/level.py +++ b/logic/level.py @@ -2,7 +2,7 @@ import random import pygame -from algorithms.a_star import a_star, State +from algorithms.a_star import a_star, State, TURN_RIGHT, TURN_LEFT, FORWARD, UP, DOWN, LEFT, RIGHT from common.constants import * from common.helpers import castle_neighbors from logic.knights_queue import KnightsQueue @@ -112,32 +112,29 @@ class Level: state = State((knight_pos_y, knight_pos_x), current_knight.direction.name) action_list = a_star(state, self.map, goal_list) - - for action in action_list: - print(action) - print() + print(action_list) if len(action_list) == 0: return - # next_action = action_list.pop(0) - # if next_action == ACTION.get("rotate_left"): - # current_knight.rotate_left() - # elif next_action == ACTION.get("rotate_right"): - # current_knight.rotate_right() - # elif next_action == ACTION.get("go"): - # current_knight.step_forward() - # self.map[knight_pos_y][knight_pos_x] = ' ' - # - # # update knight on map - # if current_knight.direction.name == 'UP': - # self.map[knight_pos_y - 1][knight_pos_x] = current_knight - # elif current_knight.direction.name == 'RIGHT': - # self.map[knight_pos_y][knight_pos_x + 1] = current_knight - # elif current_knight.direction.name == 'DOWN': - # self.map[knight_pos_y + 1][knight_pos_x] = current_knight - # elif current_knight.direction.name == 'LEFT': - # self.map[knight_pos_y][knight_pos_x - 1] = current_knight + next_action = action_list.pop(0) + if next_action == TURN_LEFT: + current_knight.rotate_left() + elif next_action == TURN_RIGHT: + current_knight.rotate_right() + elif next_action == FORWARD: + current_knight.step_forward() + self.map[knight_pos_y][knight_pos_x] = ' ' + + # update knight on map + if current_knight.direction.name == UP: + self.map[knight_pos_y - 1][knight_pos_x] = current_knight + elif current_knight.direction.name == RIGHT: + self.map[knight_pos_y][knight_pos_x + 1] = current_knight + elif current_knight.direction.name == DOWN: + self.map[knight_pos_y + 1][knight_pos_x] = current_knight + elif current_knight.direction.name == LEFT: + self.map[knight_pos_y][knight_pos_x - 1] = current_knight def update(self): bg_width = (GRID_CELL_PADDING + GRID_CELL_SIZE) * COLUMNS + BORDER_WIDTH From 04f17e32930e04efbb26c037c065d7b67f897c44 Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Tue, 12 Apr 2022 20:21:29 +0200 Subject: [PATCH 12/15] feat: a_star without tile costs --- algorithms/a_star.py | 2 +- common/constants.py | 2 +- logic/level.py | 8 ++++---- models/knight.py | 6 +++++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index 67f0924..ffcf543 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -92,7 +92,7 @@ def actions(state: State, grid: List[List[str]]) -> List[str]: if direction == RIGHT and col == COLUMNS - 1: remove_forward(possible_actions) - if FORWARD not in possible_actions and not valid_move(next_position(state.position, direction), grid): + if FORWARD in possible_actions and not valid_move(next_position(state.position, direction), grid): remove_forward(possible_actions) return possible_actions diff --git a/common/constants.py b/common/constants.py index 692c32d..a4ab9f0 100644 --- a/common/constants.py +++ b/common/constants.py @@ -4,7 +4,7 @@ GAME_TITLE = 'WMICraft' WINDOW_HEIGHT = 800 WINDOW_WIDTH = 1360 FPS_COUNT = 60 -TURN_INTERVAL = 1000 +TURN_INTERVAL = 300 GRID_CELL_PADDING = 5 GRID_CELL_SIZE = 36 diff --git a/logic/level.py b/logic/level.py index e657bc3..a3f4a06 100644 --- a/logic/level.py +++ b/logic/level.py @@ -128,13 +128,13 @@ class Level: # update knight on map if current_knight.direction.name == UP: - self.map[knight_pos_y - 1][knight_pos_x] = current_knight + self.map[knight_pos_y - 1][knight_pos_x] = current_knight.team_alias() elif current_knight.direction.name == RIGHT: - self.map[knight_pos_y][knight_pos_x + 1] = current_knight + self.map[knight_pos_y][knight_pos_x + 1] = current_knight.team_alias() elif current_knight.direction.name == DOWN: - self.map[knight_pos_y + 1][knight_pos_x] = current_knight + self.map[knight_pos_y + 1][knight_pos_x] = current_knight.team_alias() elif current_knight.direction.name == LEFT: - self.map[knight_pos_y][knight_pos_x - 1] = current_knight + self.map[knight_pos_y][knight_pos_x - 1] = current_knight.team_alias() def update(self): bg_width = (GRID_CELL_PADDING + GRID_CELL_SIZE) * COLUMNS + BORDER_WIDTH diff --git a/models/knight.py b/models/knight.py index f9bac44..2f69f71 100644 --- a/models/knight.py +++ b/models/knight.py @@ -1,6 +1,7 @@ -import pygame.image import random +import pygame.image + from common.constants import GRID_CELL_SIZE, Direction from common.helpers import parse_cord @@ -56,3 +57,6 @@ class Knight(pygame.sprite.Sprite): elif self.direction.name == 'LEFT': self.position = (self.position[0] - 1, self.position[1]) self.rect.x = self.rect.x - GRID_CELL_SIZE - 5 + + def team_alias(self) -> str: + return "k_b" if self.team == "blue" else "k_r" From af2f61984a94627fbc5a69b0f95f7cff42097cd0 Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Tue, 12 Apr 2022 20:44:27 +0200 Subject: [PATCH 13/15] feat: logs queue --- logic/game.py | 6 +++--- logic/level.py | 10 ++++++++-- ui/logs.py | 29 +++++++++++++++++++++-------- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/logic/game.py b/logic/game.py index 755a851..7bff1b8 100644 --- a/logic/game.py +++ b/logic/game.py @@ -19,12 +19,13 @@ class Game: pygame.display.set_icon(pygame.image.load('./resources/icons/sword.png')) self.screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) + self.logs = Logs(self.screen) self.clock = pygame.time.Clock() self.bg = pygame.image.load("./resources/textures/bg.jpg") self.screens = {'credits': Credits(self.screen, self.clock), 'options': Options(self.screen, self.clock)} - self.level = Level(self.screen) + self.level = Level(self.screen, self.logs) def main_menu(self): menu = MainMenu(self.screen, self.clock, self.bg, @@ -35,7 +36,6 @@ class Game: def game(self): stats = Stats() - logs = Logs() # setup clock for rounds NEXT_TURN = pygame.USEREVENT + 1 @@ -62,7 +62,7 @@ class Game: self.level.handle_turn() stats.draw(self.screen) - logs.draw(self.screen) + self.logs.draw() self.level.update() diff --git a/logic/level.py b/logic/level.py index a3f4a06..9e893da 100644 --- a/logic/level.py +++ b/logic/level.py @@ -14,9 +14,9 @@ from models.tile import Tile class Level: - def __init__(self, screen): + def __init__(self, screen, logs): self.screen = screen - + self.logs = logs # sprite group setup self.sprites = pygame.sprite.Group() @@ -119,8 +119,10 @@ class Level: next_action = action_list.pop(0) if next_action == TURN_LEFT: + self.logs.enqueue_log(f'AI {current_knight.team}: Obrót w lewo.') current_knight.rotate_left() elif next_action == TURN_RIGHT: + self.logs.enqueue_log(f'AI {current_knight.team}: Obrót w prawo.') current_knight.rotate_right() elif next_action == FORWARD: current_knight.step_forward() @@ -128,12 +130,16 @@ class Level: # update knight on map if current_knight.direction.name == UP: + self.logs.enqueue_log(f'AI {current_knight.team}: Ruch do góry.') self.map[knight_pos_y - 1][knight_pos_x] = current_knight.team_alias() elif current_knight.direction.name == RIGHT: + self.logs.enqueue_log(f'AI {current_knight.team}: Ruch w prawo.') self.map[knight_pos_y][knight_pos_x + 1] = current_knight.team_alias() elif current_knight.direction.name == DOWN: + self.logs.enqueue_log(f'AI {current_knight.team}: Ruch w dół.') self.map[knight_pos_y + 1][knight_pos_x] = current_knight.team_alias() elif current_knight.direction.name == LEFT: + self.logs.enqueue_log(f'AI {current_knight.team}: Ruch w lewo.') self.map[knight_pos_y][knight_pos_x - 1] = current_knight.team_alias() def update(self): diff --git a/ui/logs.py b/ui/logs.py index 166264e..bbd0e8a 100644 --- a/ui/logs.py +++ b/ui/logs.py @@ -1,3 +1,5 @@ +from queue import Queue + import pygame from common.colors import FONT_DARK, ORANGE, WHITE @@ -6,20 +8,31 @@ from common.helpers import draw_text class Logs: - def __init__(self): - self.grid = [] + def __init__(self, screen): + self.log_queue = Queue(maxsize=7) + self.screen = screen - def draw(self, screen): + def draw(self): x = (GRID_CELL_PADDING + GRID_CELL_SIZE) * COLUMNS + BORDER_WIDTH + 15 y = 470 # background - pygame.draw.rect(screen, WHITE, pygame.Rect(x, y, 340, 323), 0, BORDER_RADIUS) + pygame.draw.rect(self.screen, WHITE, pygame.Rect(x, y, 340, 323), 0, BORDER_RADIUS) # title - draw_text('LOGS', FONT_DARK, screen, x + 120, y + 10, 36) - pygame.draw.rect(screen, ORANGE, pygame.Rect(x, y + 65, 340, 3)) + draw_text('LOGS', FONT_DARK, self.screen, x + 120, y + 10, 36) + pygame.draw.rect(self.screen, ORANGE, pygame.Rect(x, y + 65, 340, 3)) # texts - draw_text('AI Blue: Zniszczyła fortecę (4, 8).', FONT_DARK, screen, x + 35, y + 90, 16) - draw_text('AI Red: Zniszczyła fortecę (12, 5).', FONT_DARK, screen, x + 35, y + 120, 16) + next_y = y + 90 + i = 0 + start = len(self.log_queue.queue) - 1 + for idx in range(start, -1, -1): + draw_text(self.log_queue.queue[idx], FONT_DARK, self.screen, x + 35, next_y + i * 30, 16) + i = i + 1 + + def enqueue_log(self, text): + if self.log_queue.full(): + self.log_queue.get() + self.log_queue.put(text) + self.draw() From 1dbb1a0e4c1eed043cf7af1045e69999b4670fb3 Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Wed, 13 Apr 2022 18:35:02 +0200 Subject: [PATCH 14/15] feat: field costs --- algorithms/a_star.py | 20 ++++++++++++-------- common/constants.py | 2 +- logic/level.py | 21 +++++++++++++-------- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index ffcf543..2400e5a 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -6,7 +6,7 @@ from typing import Tuple, Optional, List from common.constants import ROWS, COLUMNS -FREE_FIELD = ' ' +EMPTY_FIELDS = ['s', 'g', ' '] LEFT = 'LEFT' RIGHT = 'RIGHT' UP = 'UP' @@ -44,6 +44,7 @@ class Node: state: State parent: Optional[Node] action: Optional[str] + grid: List[List[str]] cost: int = field(init=False) depth: int = field(init=False) @@ -51,20 +52,23 @@ class Node: return self.state < node.state def __post_init__(self) -> None: - self.cost = 0 if not self.parent else self.parent.cost + 1 - self.depth = self.cost + if self.grid[self.state.position[0]][self.state.position[1]] == 'g': + self.cost = 1 if not self.parent else self.parent.cost + 1 + else: + self.cost = 3 if not self.parent else self.parent.cost + 3 + self.depth = 0 if not self.parent else self.parent.depth + 1 def __hash__(self) -> int: return hash(self.state) def expand(node: Node, grid: List[List[str]]) -> List[Node]: - return [child_node(node=node, action=action) for action in actions(node.state, grid)] + return [child_node(node=node, action=action, grid=grid) for action in actions(node.state, grid)] -def child_node(node: Node, action: str) -> Node: +def child_node(node: Node, action: str, grid: List[List[str]]) -> Node: next_state = result(state=node.state, action=action) - return Node(state=next_state, parent=node, action=action) + return Node(state=next_state, parent=node, action=action, grid=grid) def next_position(current_position: Tuple[int, int], direction: str) -> Tuple[int, int]: @@ -75,7 +79,7 @@ def next_position(current_position: Tuple[int, int], direction: str) -> Tuple[in def valid_move(position: Tuple[int, int], grid: List[List[str]]) -> bool: row, col = position - return grid[row][col] == FREE_FIELD + return grid[row][col] in EMPTY_FIELDS def actions(state: State, grid: List[List[str]]) -> List[str]: @@ -170,7 +174,7 @@ def get_path_from_start(node: Node) -> List[str]: def a_star(state: State, grid: List[List[str]], goals: List[Tuple[int, int]]) -> List[str]: - node = Node(state=state, parent=None, action=None) + node = Node(state=state, parent=None, action=None, grid=grid) frontier = list() heapq.heappush(frontier, (f(node, goals[0]), node)) diff --git a/common/constants.py b/common/constants.py index a4ab9f0..eca3096 100644 --- a/common/constants.py +++ b/common/constants.py @@ -29,7 +29,7 @@ CASTLE_SPAWN_FIRST_COL = 9 NBR_OF_WATER = 16 NBR_OF_TREES = 20 NBR_OF_MONSTERS = 2 -NBR_OF_SANDS = 15 +NBR_OF_SANDS = 35 TILES = [ 'grass1.png', diff --git a/logic/level.py b/logic/level.py index 9e893da..f9a88be 100644 --- a/logic/level.py +++ b/logic/level.py @@ -20,7 +20,7 @@ class Level: # sprite group setup self.sprites = pygame.sprite.Group() - self.map = [[' ' for x in range(COLUMNS)] for y in range(ROWS)] + self.map = [['g' for _ in range(COLUMNS)] for y in range(ROWS)] self.list_knights_blue = [] self.list_knights_red = [] @@ -37,18 +37,19 @@ class Level: def generate_map(self): spawner = Spawner(self.map) - spawner.spawn_where_possible(['w' for x in range(NBR_OF_WATER)]) - spawner.spawn_where_possible(['t' for x in range(NBR_OF_TREES)]) + spawner.spawn_where_possible(['w' for _ in range(NBR_OF_WATER)]) + spawner.spawn_where_possible(['t' for _ in range(NBR_OF_TREES)]) + spawner.spawn_where_possible(['s' for _ in range(NBR_OF_SANDS)]) - spawner.spawn_in_area(['k_b' for x in range(4)], LEFT_KNIGHTS_SPAWN_FIRST_ROW, LEFT_KNIGHTS_SPAWN_FIRST_COL, + spawner.spawn_in_area(['k_b' for _ in range(4)], LEFT_KNIGHTS_SPAWN_FIRST_ROW, LEFT_KNIGHTS_SPAWN_FIRST_COL, KNIGHTS_SPAWN_WIDTH, KNIGHTS_SPAWN_HEIGHT) - spawner.spawn_in_area(['k_r' for x in range(4)], RIGHT_KNIGHTS_SPAWN_FIRST_ROW, RIGHT_KNIGHTS_SPAWN_FIRST_COL, + spawner.spawn_in_area(['k_r' for _ in range(4)], RIGHT_KNIGHTS_SPAWN_FIRST_ROW, RIGHT_KNIGHTS_SPAWN_FIRST_COL, KNIGHTS_SPAWN_WIDTH, KNIGHTS_SPAWN_HEIGHT) spawner.spawn_in_area(['c'], CASTLE_SPAWN_FIRST_ROW, CASTLE_SPAWN_FIRST_COL, CASTLE_SPAWN_WIDTH, CASTLE_SPAWN_HEIGHT, 2) - spawner.spawn_where_possible(['m' for x in range(NBR_OF_MONSTERS)]) + spawner.spawn_where_possible(['m' for _ in range(NBR_OF_MONSTERS)]) def setup_base_tiles(self): textures = [] @@ -69,8 +70,12 @@ class Level: texture_index = 6 texture_surface = textures[texture_index][1] Tile((col_index, row_index), texture_surface, self.sprites, 't') + elif col == "s": + texture_index = 4 + texture_surface = textures[texture_index][1] + Tile((col_index, row_index), texture_surface, self.sprites) else: - texture_index = random.randint(0, 4) + texture_index = random.randint(0, 3) texture_surface = textures[texture_index][1] Tile((col_index, row_index), texture_surface, self.sprites) @@ -126,7 +131,7 @@ class Level: current_knight.rotate_right() elif next_action == FORWARD: current_knight.step_forward() - self.map[knight_pos_y][knight_pos_x] = ' ' + self.map[knight_pos_y][knight_pos_x] = 'g' # update knight on map if current_knight.direction.name == UP: From 55c3ea0aa82f67ba139c9fad008ed5f08406989c Mon Sep 17 00:00:00 2001 From: korzepadawid Date: Wed, 13 Apr 2022 18:56:39 +0200 Subject: [PATCH 15/15] fix: changed sand cost --- algorithms/a_star.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/algorithms/a_star.py b/algorithms/a_star.py index 2400e5a..1b8db00 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -55,7 +55,7 @@ class Node: if self.grid[self.state.position[0]][self.state.position[1]] == 'g': self.cost = 1 if not self.parent else self.parent.cost + 1 else: - self.cost = 3 if not self.parent else self.parent.cost + 3 + self.cost = 2 if not self.parent else self.parent.cost + 2 self.depth = 0 if not self.parent else self.parent.depth + 1 def __hash__(self) -> int: