bfs #19
129
algorithms/bfs.py
Normal file
129
algorithms/bfs.py
Normal file
@ -0,0 +1,129 @@
|
||||
from __future__ import annotations
|
||||
from typing import List
|
||||
|
||||
# temporary goal for testing
|
||||
from common.constants import ACTION, Direction, ROWS, COLUMNS
|
||||
|
||||
GOAL = (9, 9)
|
||||
|
||||
|
||||
class State:
|
||||
def __init__(self, row, column, direction):
|
||||
self.row = row
|
||||
self.column = column
|
||||
self.direction = direction
|
||||
|
||||
|
||||
class Node:
|
||||
def __init__(self, state, parent=None, action=None):
|
||||
self.state = state
|
||||
self.parent = parent
|
||||
self.action = action
|
||||
|
||||
|
||||
def goal_test(state: State):
|
||||
if (state.row, state.column) == GOAL:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_successors(state: State, map):
|
||||
successors = list()
|
||||
|
||||
state_left = State(state.row, state.column, state.direction.right())
|
||||
successors.append((ACTION.get("rotate_left"), state_left))
|
||||
|
||||
state_right = State(state.row, state.column, state.direction.left())
|
||||
successors.append((ACTION.get("rotate_right"), state_right))
|
||||
|
||||
target = go(state.row, state.column, state.direction)
|
||||
|
||||
if is_valid_move(map, target[0], target[1]):
|
||||
state_go = State(target[0], target[1], state.direction)
|
||||
successors.append((ACTION.get("go"), state_go))
|
||||
|
||||
return successors
|
||||
|
||||
|
||||
def graphsearch(initial_state: State, map, fringe: List[Node] = None, explored: List[Node] = None,
|
||||
tox: int = None, toy: int = None):
|
||||
# fringe and explored initialization
|
||||
global GOAL
|
||||
if fringe is None:
|
||||
fringe = list()
|
||||
if explored is None:
|
||||
explored = list()
|
||||
if tox is not None and toy is not None:
|
||||
GOAL = (tox, toy)
|
||||
explored_states = set()
|
||||
fringe_states = set()
|
||||
|
||||
# root Node
|
||||
fringe.append(Node(initial_state))
|
||||
fringe_states.add((initial_state.row, initial_state.column, initial_state.direction))
|
||||
|
||||
while True:
|
||||
# fringe empty -> solution not found
|
||||
if not any(fringe):
|
||||
print("Brak rozwiazania")
|
||||
return []
|
||||
|
||||
# get first element from fringe
|
||||
element = fringe.pop(0)
|
||||
fringe_states.remove((element.state.row, element.state.column, element.state.direction))
|
||||
|
||||
# if solution was found, prepare and return actions sequence
|
||||
if goal_test(element.state):
|
||||
actions_sequence = [element.action]
|
||||
parent = element.parent
|
||||
|
||||
while parent is not None:
|
||||
# root's action will be None, don't add it
|
||||
if parent.action is not None:
|
||||
actions_sequence.append(parent.action)
|
||||
parent = parent.parent
|
||||
|
||||
actions_sequence.reverse()
|
||||
return actions_sequence
|
||||
|
||||
# add current node to explored (prevents infinite cycles)
|
||||
explored.append(element)
|
||||
explored_states.add((element.state.row, element.state.column, element.state.direction))
|
||||
|
||||
# loop through every possible next action
|
||||
for successor in get_successors(element.state, map):
|
||||
|
||||
# make sure not to fall into a cycle
|
||||
successor_state = (successor[1].row, successor[1].column, successor[1].direction)
|
||||
if successor_state not in fringe_states and \
|
||||
successor_state not in explored_states:
|
||||
# create new Node and add it at the end of fringe
|
||||
new_node = Node(state=successor[1],
|
||||
parent=element,
|
||||
action=successor[0])
|
||||
fringe.append(new_node)
|
||||
fringe_states.add((new_node.state.row, new_node.state.column, new_node.state.direction))
|
||||
|
||||
|
||||
# TEMPORARY METHOD
|
||||
def go(row, column, direction):
|
||||
target = tuple()
|
||||
|
||||
if direction == Direction.RIGHT:
|
||||
target = row, column + 1
|
||||
elif direction == Direction.LEFT:
|
||||
target = row, column - 1
|
||||
elif direction == Direction.UP:
|
||||
target = row - 1, column
|
||||
elif direction == Direction.DOWN:
|
||||
target = row + 1, column
|
||||
|
||||
return target
|
||||
|
||||
|
||||
def is_valid_move(map, target_row, target_column):
|
||||
# TODO: check collisions with objects
|
||||
if 0 <= target_row < ROWS and 0 <= target_column < COLUMNS:
|
||||
return True
|
||||
|
||||
return False
|
@ -1,7 +1,10 @@
|
||||
from enum import Enum
|
||||
|
||||
GAME_TITLE = 'WMICraft'
|
||||
WINDOW_HEIGHT = 800
|
||||
WINDOW_WIDTH = 1360
|
||||
FPS_COUNT = 60
|
||||
TURN_INTERVAL = 1000
|
||||
|
||||
GRID_CELL_PADDING = 5
|
||||
GRID_CELL_SIZE = 36
|
||||
@ -19,9 +22,7 @@ RIGHT_KNIGHTS_SPAWN_FIRST_ROW = 6
|
||||
RIGHT_KNIGHTS_SPAWN_FIRST_COL = 20
|
||||
|
||||
CASTLE_SPAWN_WIDTH = 6
|
||||
# CASTLE_SPAWN_WIDTH = 0
|
||||
CASTLE_SPAWN_HEIGHT = 5
|
||||
# CASTLE_SPAWN_HEIGHT = 0
|
||||
CASTLE_SPAWN_FIRST_ROW = 7
|
||||
CASTLE_SPAWN_FIRST_COL = 9
|
||||
|
||||
@ -38,3 +39,25 @@ TILES = [
|
||||
'water.png',
|
||||
'grass_with_tree.jpg',
|
||||
]
|
||||
|
||||
|
||||
class Direction(Enum):
|
||||
UP = 0
|
||||
RIGHT = 1
|
||||
DOWN = 2
|
||||
LEFT = 3
|
||||
|
||||
def left(self):
|
||||
v = (self.value + 1) % 4
|
||||
return Direction(v)
|
||||
|
||||
def right(self):
|
||||
v = (self.value - 1) % 4
|
||||
return Direction(v)
|
||||
|
||||
|
||||
ACTION = {
|
||||
"rotate_left": -1,
|
||||
"rotate_right": 1,
|
||||
"go": 0,
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import pygame
|
||||
from common.constants import GRID_CELL_PADDING, GRID_CELL_SIZE, COLUMNS, ROWS
|
||||
|
||||
|
||||
def draw_text(text, color, surface, x, y, text_size=30, is_bold=False):
|
||||
@ -10,3 +11,20 @@ def draw_text(text, color, surface, x, y, text_size=30, is_bold=False):
|
||||
textrect = textobj.get_rect()
|
||||
textrect.topleft = (x, y)
|
||||
surface.blit(textobj, textrect)
|
||||
|
||||
|
||||
def print_numbers():
|
||||
display_surface = pygame.display.get_surface()
|
||||
font = pygame.font.SysFont('Arial', 16)
|
||||
|
||||
for row_index in range(ROWS):
|
||||
for col_index in range(COLUMNS):
|
||||
x = (GRID_CELL_PADDING + GRID_CELL_SIZE) * col_index + GRID_CELL_PADDING + 7
|
||||
y = (GRID_CELL_PADDING + GRID_CELL_SIZE) * row_index + GRID_CELL_PADDING + 16
|
||||
display_surface.blit(font.render(f'[{col_index}, {row_index}]', True, (255, 0, 0)), (x, y))
|
||||
pygame.display.update()
|
||||
|
||||
|
||||
# parse array index to screen x or y coordinate
|
||||
def parse_cord(cord):
|
||||
return (GRID_CELL_PADDING + GRID_CELL_SIZE) * cord + GRID_CELL_PADDING + 7
|
||||
|
@ -1,15 +0,0 @@
|
||||
import pygame.display
|
||||
|
||||
from common.constants import GRID_CELL_PADDING, GRID_CELL_SIZE, COLUMNS, ROWS
|
||||
|
||||
|
||||
def print_numbers():
|
||||
display_surface = pygame.display.get_surface()
|
||||
font = pygame.font.SysFont('Arial', 16)
|
||||
|
||||
for row_index in range(ROWS):
|
||||
for col_index in range(COLUMNS):
|
||||
x = (GRID_CELL_PADDING + GRID_CELL_SIZE) * col_index + GRID_CELL_PADDING + 7
|
||||
y = (GRID_CELL_PADDING + GRID_CELL_SIZE) * row_index + GRID_CELL_PADDING + 16
|
||||
display_surface.blit(font.render(f'[{col_index}, {row_index}]', True, (255, 0, 0)), (x, y))
|
||||
pygame.display.update()
|
@ -3,7 +3,8 @@ import sys
|
||||
import pygame
|
||||
|
||||
from common.constants import *
|
||||
from helpers.debug import print_numbers
|
||||
from common.helpers import print_numbers
|
||||
from logic.knights_queue import KnightsQueue
|
||||
from logic.level import Level
|
||||
from ui.logs import Logs
|
||||
from ui.screens.credits import Credits
|
||||
@ -37,6 +38,13 @@ class Game:
|
||||
stats = Stats()
|
||||
logs = Logs()
|
||||
|
||||
# setup clock for rounds
|
||||
NEXT_TURN = pygame.USEREVENT + 1
|
||||
pygame.time.set_timer(NEXT_TURN, TURN_INTERVAL)
|
||||
|
||||
# create level
|
||||
self.level.create_map()
|
||||
|
||||
print_numbers_flag = False
|
||||
running = True
|
||||
while running:
|
||||
@ -51,11 +59,13 @@ class Game:
|
||||
running = False
|
||||
if event.key == 110: # clicked n letter on keyboard
|
||||
print_numbers_flag = not print_numbers_flag
|
||||
if event.type == NEXT_TURN: # is called every 't' milliseconds
|
||||
self.level.handle_turn()
|
||||
|
||||
stats.draw(self.screen)
|
||||
logs.draw(self.screen)
|
||||
|
||||
self.level.run()
|
||||
self.level.update()
|
||||
|
||||
if print_numbers_flag:
|
||||
print_numbers()
|
||||
|
100
logic/level.py
100
logic/level.py
@ -2,7 +2,9 @@ import random
|
||||
|
||||
import pygame
|
||||
|
||||
from algorithms.bfs import graphsearch, State
|
||||
from common.constants import *
|
||||
from logic.knights_queue import KnightsQueue
|
||||
from logic.spawner import Spawner
|
||||
from models.castle import Castle
|
||||
from models.knight import Knight
|
||||
@ -17,18 +19,29 @@ class Level:
|
||||
# sprite group setup
|
||||
self.sprites = pygame.sprite.Group()
|
||||
|
||||
self.create_map()
|
||||
self.map = [[' ' for x in range(COLUMNS)] for y in range(ROWS)]
|
||||
|
||||
self.list_knights_blue = []
|
||||
self.list_knights_red = []
|
||||
self.list_monsters = []
|
||||
self.list_castles = []
|
||||
|
||||
self.knights_queue = None
|
||||
|
||||
def create_map(self):
|
||||
self.generate_map()
|
||||
self.setup_base_tiles()
|
||||
self.setup_objects()
|
||||
self.knights_queue = KnightsQueue(self.list_knights_blue, self.list_knights_red)
|
||||
|
||||
def generate_map(self):
|
||||
map = [[' ' for x in range(COLUMNS)] for y in range(ROWS)]
|
||||
|
||||
spawner = Spawner(map)
|
||||
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_in_area(['k' for x in range(4)], LEFT_KNIGHTS_SPAWN_FIRST_ROW, LEFT_KNIGHTS_SPAWN_FIRST_COL,
|
||||
spawner.spawn_in_area(['k_b' for x in range(4)], LEFT_KNIGHTS_SPAWN_FIRST_ROW, LEFT_KNIGHTS_SPAWN_FIRST_COL,
|
||||
KNIGHTS_SPAWN_WIDTH, KNIGHTS_SPAWN_HEIGHT)
|
||||
spawner.spawn_in_area(['k' for x in range(4)], RIGHT_KNIGHTS_SPAWN_FIRST_ROW, RIGHT_KNIGHTS_SPAWN_FIRST_COL,
|
||||
spawner.spawn_in_area(['k_r' for x 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,
|
||||
@ -36,50 +49,87 @@ class Level:
|
||||
|
||||
spawner.spawn_where_possible(['m' for x in range(NBR_OF_MONSTERS)])
|
||||
|
||||
return map
|
||||
|
||||
def create_map(self):
|
||||
map = self.generate_map()
|
||||
|
||||
def setup_base_tiles(self):
|
||||
textures = []
|
||||
for texture_path in TILES:
|
||||
converted_texture = pygame.image.load('resources/textures/' + texture_path).convert_alpha()
|
||||
converted_texture = pygame.transform.scale(converted_texture, (40, 40))
|
||||
textures.append((texture_path, converted_texture))
|
||||
|
||||
castle_count = 0 # TODO: find some smarter method to print castle
|
||||
|
||||
for row_index, row in enumerate(map):
|
||||
print(row)
|
||||
for row_index, row in enumerate(self.map):
|
||||
for col_index, col in enumerate(row):
|
||||
x = (GRID_CELL_PADDING + GRID_CELL_SIZE) * col_index + GRID_CELL_PADDING + 7
|
||||
y = (GRID_CELL_PADDING + GRID_CELL_SIZE) * row_index + GRID_CELL_PADDING + 7
|
||||
|
||||
# add base tiles, e.g. water, tree, grass
|
||||
if col == "w":
|
||||
texture_index = 5
|
||||
texture_surface = textures[texture_index][1]
|
||||
Tile((x, y), texture_surface, self.sprites, 'w')
|
||||
Tile((row_index, col_index), texture_surface, self.sprites, 'w')
|
||||
elif col == "t":
|
||||
texture_index = 6
|
||||
texture_surface = textures[texture_index][1]
|
||||
Tile((x, y), texture_surface, self.sprites, 't')
|
||||
Tile((row_index, col_index), texture_surface, self.sprites, 't')
|
||||
else:
|
||||
texture_index = random.randint(0, 4)
|
||||
texture_surface = textures[texture_index][1]
|
||||
Tile((x, y), texture_surface, self.sprites)
|
||||
Tile((row_index, col_index), texture_surface, self.sprites)
|
||||
|
||||
def setup_objects(self):
|
||||
castle_count = 0 # TODO: find some smarter method to print castle
|
||||
|
||||
for row_index, row in enumerate(self.map):
|
||||
print(row)
|
||||
for col_index, col in enumerate(row):
|
||||
|
||||
# add objects, e.g. knights, monsters, castle
|
||||
if col == "k":
|
||||
map[row_index][col_index] = Knight((x, y), self.sprites)
|
||||
if col == "k_b":
|
||||
knight = Knight((row_index, col_index), self.sprites, "blue")
|
||||
self.map[row_index][col_index] = knight
|
||||
self.list_knights_blue.append(knight)
|
||||
elif col == "k_r":
|
||||
knight = Knight((row_index, col_index), self.sprites, "red")
|
||||
self.map[row_index][col_index] = knight
|
||||
self.list_knights_red.append(knight)
|
||||
elif col == "m":
|
||||
map[row_index][col_index] = Monster((x, y), self.sprites)
|
||||
monster = Monster((row_index, col_index), self.sprites)
|
||||
self.map[row_index][col_index] = monster
|
||||
self.list_monsters.append(monster)
|
||||
elif col == "c":
|
||||
castle_count += 1
|
||||
if castle_count == 4:
|
||||
map[row_index][col_index] = Castle((x, y), self.sprites)
|
||||
castle = Castle((row_index, col_index), self.sprites)
|
||||
self.map[row_index][col_index] = castle
|
||||
self.list_castles.append(castle)
|
||||
|
||||
def run(self):
|
||||
def handle_turn(self):
|
||||
print("next turn")
|
||||
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_x, knight_pos_y, current_knight.direction)
|
||||
action_list = graphsearch(state, self.map)
|
||||
print(action_list)
|
||||
|
||||
if len(action_list) == 0:
|
||||
return
|
||||
|
||||
if action_list[0] == ACTION.get("rotate_left"):
|
||||
current_knight.rotate_left()
|
||||
elif action_list[0] == ACTION.get("rotate_right"):
|
||||
current_knight.rotate_right()
|
||||
elif action_list[0] == ACTION.get("go"):
|
||||
current_knight.step_forward()
|
||||
self.map[knight_pos_x][knight_pos_y] = ' '
|
||||
|
||||
if current_knight.direction.name == 'UP':
|
||||
self.map[knight_pos_x][knight_pos_y - 1] = current_knight
|
||||
elif current_knight.direction.name == 'RIGHT':
|
||||
self.map[knight_pos_x + 1][knight_pos_y] = current_knight
|
||||
elif current_knight.direction.name == 'DOWN':
|
||||
self.map[knight_pos_x][knight_pos_y + 1] = current_knight
|
||||
elif current_knight.direction.name == 'LEFT':
|
||||
self.map[knight_pos_x - 1][knight_pos_y] = current_knight
|
||||
|
||||
def update(self):
|
||||
bg_width = (GRID_CELL_PADDING + GRID_CELL_SIZE) * COLUMNS + BORDER_WIDTH
|
||||
bg_height = (GRID_CELL_PADDING + GRID_CELL_SIZE) * ROWS + BORDER_WIDTH
|
||||
pygame.draw.rect(self.screen, (255, 255, 255), pygame.Rect(5, 5, bg_width, bg_height), 0, BORDER_RADIUS)
|
||||
|
@ -1,10 +1,13 @@
|
||||
import pygame.image
|
||||
|
||||
from common.helpers import parse_cord
|
||||
|
||||
|
||||
class Castle(pygame.sprite.Sprite):
|
||||
def __init__(self, position, group):
|
||||
super().__init__(group)
|
||||
self.image = pygame.image.load("./resources/textures/castle.png").convert_alpha()
|
||||
self.image = pygame.transform.scale(self.image, (78, 78))
|
||||
self.rect = self.image.get_rect(center=position)
|
||||
position_in_px = (parse_cord(position[1]), parse_cord(position[0]))
|
||||
self.rect = self.image.get_rect(center=position_in_px)
|
||||
self.health = 80
|
||||
|
@ -1,7 +1,8 @@
|
||||
import pygame.image
|
||||
import random
|
||||
|
||||
from common.constants import GRID_CELL_SIZE
|
||||
from common.constants import GRID_CELL_SIZE, Direction
|
||||
from common.helpers import parse_cord
|
||||
|
||||
|
||||
def load_knight_textures():
|
||||
@ -17,32 +18,37 @@ def load_knight_textures():
|
||||
|
||||
class Knight(pygame.sprite.Sprite):
|
||||
|
||||
def __init__(self, position, group):
|
||||
def __init__(self, position, group, team):
|
||||
super().__init__(group)
|
||||
|
||||
self.direction = 2
|
||||
self.direction = Direction.DOWN
|
||||
self.states = load_knight_textures()
|
||||
|
||||
self.image = self.states[self.direction]
|
||||
self.rect = self.image.get_rect(topleft=position)
|
||||
self.image = self.states[self.direction.value]
|
||||
self.position = position
|
||||
position_in_px = (parse_cord(position[1]), parse_cord(position[0]))
|
||||
self.rect = self.image.get_rect(topleft=position_in_px)
|
||||
|
||||
self.team = team
|
||||
self.health = random.randint(7, 12)
|
||||
self.attack = random.randint(4, 7)
|
||||
self.defense = random.randint(1, 4)
|
||||
self.points = 1
|
||||
|
||||
# direction arg = -1 to rotate left
|
||||
# direction arg = 1 to rotate right
|
||||
def rotate(self, direction):
|
||||
self.direction = (self.direction + direction) % 4
|
||||
self.image = self.states[self.direction]
|
||||
def rotate_left(self):
|
||||
self.direction = self.direction.right()
|
||||
self.image = self.states[self.direction.value]
|
||||
|
||||
def rotate_right(self):
|
||||
self.direction = self.direction.left()
|
||||
self.image = self.states[self.direction.value]
|
||||
|
||||
def step_forward(self):
|
||||
if self.direction == 0:
|
||||
self.rect.y = self.rect.y - GRID_CELL_SIZE
|
||||
elif self.direction == 1:
|
||||
self.rect.x = self.rect.x + GRID_CELL_SIZE
|
||||
elif self.direction == 2:
|
||||
self.rect.y = self.rect.y + GRID_CELL_SIZE
|
||||
else:
|
||||
self.rect.x = self.rect.x - GRID_CELL_SIZE
|
||||
if self.direction.name == 'UP':
|
||||
self.rect.y = self.rect.y - GRID_CELL_SIZE - 5
|
||||
elif self.direction.name == 'RIGHT':
|
||||
self.rect.x = self.rect.x + GRID_CELL_SIZE + 5
|
||||
elif self.direction.name == 'DOWN':
|
||||
self.rect.y = self.rect.y + GRID_CELL_SIZE + 5
|
||||
elif self.direction.name == 'LEFT':
|
||||
self.rect.x = self.rect.x - GRID_CELL_SIZE - 5
|
||||
|
@ -1,6 +1,8 @@
|
||||
import pygame.image
|
||||
import random
|
||||
|
||||
from common.helpers import parse_cord
|
||||
|
||||
monster_images = [
|
||||
pygame.image.load("./resources/textures/dragon2.png"),
|
||||
pygame.image.load("./resources/textures/birb.png"),
|
||||
@ -14,7 +16,8 @@ class Monster(pygame.sprite.Sprite):
|
||||
super().__init__(group)
|
||||
self.image = random.choice(monster_images)
|
||||
self.image = pygame.transform.scale(self.image, (40, 40))
|
||||
self.rect = self.image.get_rect(topleft=position)
|
||||
position_in_px = (parse_cord(position[1]), parse_cord(position[0]))
|
||||
self.rect = self.image.get_rect(topleft=position_in_px)
|
||||
|
||||
self.health = random.randrange(15, 25)
|
||||
self.attack = random.randrange(2, 10)
|
||||
|
@ -1,9 +1,12 @@
|
||||
import pygame
|
||||
|
||||
from common.helpers import parse_cord
|
||||
|
||||
|
||||
class Tile(pygame.sprite.Sprite):
|
||||
def __init__(self, position, image, group, tile_type=' '):
|
||||
super().__init__(group)
|
||||
self.image = image
|
||||
self.rect = self.image.get_rect(topleft=position)
|
||||
position_in_px = (parse_cord(position[1]), parse_cord(position[0]))
|
||||
self.rect = self.image.get_rect(topleft=position_in_px)
|
||||
self.tile_type = tile_type
|
Loading…
Reference in New Issue
Block a user