diff --git a/algorithms/bfs.py b/algorithms/bfs.py new file mode 100644 index 0000000..e900faa --- /dev/null +++ b/algorithms/bfs.py @@ -0,0 +1,120 @@ +from __future__ import annotations +from typing import List + +from common.constants import ACTION, Direction, ROWS, COLUMNS + + +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(goal_list, state: State): + if (state.row, state.column) in goal_list: + return True + return False + + +def get_successors(state: State, map): + successors = list() + + state_left = State(state.row, state.column, state.direction.left()) + successors.append((ACTION.get("rotate_left"), state_left)) + + state_right = State(state.row, state.column, state.direction.right()) + 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, goal_list, fringe: List[Node] = None, explored: List[Node] = None): + # fringe and explored initialization + if fringe is None: + fringe = list() + if explored is None: + explored = list() + 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(goal_list, 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): + if 0 <= target_row < ROWS and 0 <= target_column < COLUMNS and map[target_row][target_column] == ' ': + return True + + return False diff --git a/common/constants.py b/common/constants.py index 31fd0a9..37dd0ae 100644 --- a/common/constants.py +++ b/common/constants.py @@ -1,35 +1,63 @@ +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_WIDTH = 36 -GRID_CELL_HEIGHT = 36 +GRID_CELL_SIZE = 36 ROWS = 19 COLUMNS = 24 + BORDER_WIDTH = 10 BORDER_RADIUS = 5 -FPS_COUNT = 60 + KNIGHTS_SPAWN_WIDTH = 4 KNIGHTS_SPAWN_HEIGHT = 7 LEFT_KNIGHTS_SPAWN_FIRST_ROW = 6 LEFT_KNIGHTS_SPAWN_FIRST_COL = 0 RIGHT_KNIGHTS_SPAWN_FIRST_ROW = 6 RIGHT_KNIGHTS_SPAWN_FIRST_COL = 20 -NBR_OF_WATER = 10 -NBR_OF_TREES = 16 CASTLE_SPAWN_WIDTH = 6 CASTLE_SPAWN_HEIGHT = 5 CASTLE_SPAWN_FIRST_ROW = 7 CASTLE_SPAWN_FIRST_COL = 9 + +NBR_OF_WATER = 16 +NBR_OF_TREES = 20 +NBR_OF_MONSTERS = 2 + TILES = [ 'grass1.png', 'grass2.png', 'grass3.png', - # 'grass4.png', - # 'grass5.png', - # 'grass6.png', + 'grass4.png', 'sand.png', 'water.png', 'grass_with_tree.jpg', ] + + +class Direction(Enum): + UP = 0 + RIGHT = 1 + DOWN = 2 + LEFT = 3 + + def right(self): + v = (self.value + 1) % 4 + return Direction(v) + + def left(self): + v = (self.value - 1) % 4 + return Direction(v) + + +ACTION = { + "rotate_left": -1, + "rotate_right": 1, + "go": 0, +} diff --git a/common/helpers.py b/common/helpers.py index 63238b6..dd13e31 100644 --- a/common/helpers.py +++ b/common/helpers.py @@ -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,37 @@ 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 + + +def castle_neighbors(map, castle_bottom_right_row, castle_bottom_right_col): + neighbors = [] + for row_add in range(-2, 2): + new_row = castle_bottom_right_row + row_add + if 0 <= new_row <= len(map) - 1: + for col_add in range(-2, 2): + new_col = castle_bottom_right_col + col_add + if 0 <= new_col <= len(map) - 1: + if (new_col == castle_bottom_right_col - 1 and new_row == castle_bottom_right_row - 1) \ + or (new_col == castle_bottom_right_col and new_row == castle_bottom_right_row - 1) \ + or (new_col == castle_bottom_right_col - 1 and new_row == castle_bottom_right_row) \ + or (new_col == castle_bottom_right_col and new_row == castle_bottom_right_row): + continue + neighbors.append((new_col, new_row)) + return neighbors diff --git a/logic/field.py b/logic/field.py deleted file mode 100644 index 0faa309..0000000 --- a/logic/field.py +++ /dev/null @@ -1,28 +0,0 @@ -import pygame - -from logic.exceptions import FieldNotWalkable - - -class Field(pygame.sprite.Sprite): - def __init__(self, texture_path, converted_texture, img, rect, row=0, column=0): - super().__init__() - self.texture_path = texture_path - self.converted_texture = converted_texture - self.image = img - self.row = row - self.column = column - self.rect = rect - self.busy = False - self.busy_detection() - - def busy_detection(self): - if self.texture_path == 'water.png' or self.texture_path == 'grass_with_tree.jpg': - self.busy = True - - def put_on(self, obj): # ustawia objekt na srodku pola - if not self.busy: - obj.rect = obj.rect.clamp(self.rect) - self.busy = True - obj.update() - else: - raise FieldNotWalkable diff --git a/logic/game.py b/logic/game.py index 436d514..755a851 100644 --- a/logic/game.py +++ b/logic/game.py @@ -2,19 +2,14 @@ import sys import pygame -from common.colors import FONT_DARK, WHITE from common.constants import * -from common.helpers import draw_text -from logic.knights_queue import KnightsQueue -from models.castle import Castle -from models.knight import Knight -from models.monster import Monster +from common.helpers import print_numbers +from logic.level import Level from ui.logs import Logs from ui.screens.credits import Credits +from ui.screens.main_menu import MainMenu from ui.screens.options import Options from ui.stats import Stats -from .grid import Grid -from .spawner import Spawner class Game: @@ -26,99 +21,31 @@ class Game: self.screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) self.clock = pygame.time.Clock() - self.tiles = [] - for tile_path in TILES: - converted_tile = pygame.image.load('resources/textures/' + tile_path).convert_alpha() - self.tiles.append((tile_path, converted_tile)) - self.bg = pygame.image.load("./resources/textures/bg.jpg") self.screens = {'credits': Credits(self.screen, self.clock), 'options': Options(self.screen, self.clock)} - click = False + self.level = Level(self.screen) def main_menu(self): - while True: - self.screen.blit(self.bg, (0, 0)) - - pygame.draw.rect(self.screen, (255, 255, 255), pygame.Rect(800, 100, 400, 500), 0, 5) - draw_text('MAIN MENU', FONT_DARK, self.screen, 850, 150, 30, True) - - mx, my = pygame.mouse.get_pos() - - button_1 = pygame.Rect(850, 250, 300, 50) - button_2 = pygame.Rect(850, 350, 300, 50) - button_3 = pygame.Rect(850, 450, 300, 50) - if button_1.collidepoint((mx, my)): - if click: - self.game() - if button_2.collidepoint((mx, my)): - if click: - self.screens['options'].display_screen() - if button_3.collidepoint((mx, my)): - if click: - self.screens['credits'].display_screen() - pygame.draw.rect(self.screen, (0, 191, 255), button_1, 0, 4) - draw_text('PLAY', WHITE, self.screen, 870, 255) - - pygame.draw.rect(self.screen, (0, 191, 255), button_2, 0, 4) - draw_text('OPTIONS', WHITE, self.screen, 870, 355) - - pygame.draw.rect(self.screen, (0, 191, 255), button_3, 0, 4) - draw_text('CREDITS', WHITE, self.screen, 870, 455) - - click = False - for event in pygame.event.get(): - if event.type == pygame.QUIT: - pygame.quit() - sys.exit() - if event.type == pygame.KEYDOWN: - if event.key == pygame.K_ESCAPE: - pygame.quit() - sys.exit() - if event.type == pygame.MOUSEBUTTONDOWN: - if event.button == 1: - click = True - - pygame.display.update() - self.clock.tick(60) + menu = MainMenu(self.screen, self.clock, self.bg, + self.game, + self.screens['options'].display_screen, + self.screens['credits'].display_screen) + menu.display_screen() def game(self): - running = True - grid = Grid(self.tiles) stats = Stats() logs = Logs() - knights_sprite_group = pygame.sprite.Group() - monsters_sprite_group = pygame.sprite.Group() - castle_sprite_group = pygame.sprite.Group() + # setup clock for rounds + NEXT_TURN = pygame.USEREVENT + 1 + pygame.time.set_timer(NEXT_TURN, TURN_INTERVAL) - knights_left = self.generate_knights_team(knights_sprite_group) - knights_right = self.generate_knights_team(knights_sprite_group) - - knights_queue = KnightsQueue(knights_left, knights_right) - - spawn_left_team = Spawner(grid, knights_left, KNIGHTS_SPAWN_WIDTH, KNIGHTS_SPAWN_HEIGHT, - LEFT_KNIGHTS_SPAWN_FIRST_ROW, LEFT_KNIGHTS_SPAWN_FIRST_COL) - spawn_right_team = Spawner(grid, knights_right, KNIGHTS_SPAWN_WIDTH, KNIGHTS_SPAWN_HEIGHT, - RIGHT_KNIGHTS_SPAWN_FIRST_ROW, - RIGHT_KNIGHTS_SPAWN_FIRST_COL) - - spawn_left_team.spawn() - spawn_right_team.spawn() - - spawned_monsters = self.generate_monster(monsters_sprite_group) - monster_spawn = Spawner(grid, spawned_monsters, spawn_where_possible=True) - - monster_spawn.spawn() - - spawned_castle = self.generate_castle(castle_sprite_group) - castle_spawn = Spawner(grid, [spawned_castle], CASTLE_SPAWN_WIDTH, CASTLE_SPAWN_HEIGHT, - CASTLE_SPAWN_FIRST_ROW, CASTLE_SPAWN_FIRST_COL) - - castle_spawn.spawn() - - #grid.put_on_tile(0, 0, knights_left[0]) + # create level + self.level.create_map() + print_numbers_flag = False + running = True while running: self.screen.blit(self.bg, (0, 0)) @@ -129,37 +56,18 @@ class Game: if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: 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 'TURN_INTERVAL' milliseconds + self.level.handle_turn() - grid.update(self.screen) stats.draw(self.screen) logs.draw(self.screen) - knights_sprite_group.draw(self.screen) - monsters_sprite_group.draw(self.screen) - castle_sprite_group.draw(self.screen) + self.level.update() + + if print_numbers_flag: + print_numbers() + pygame.display.update() self.clock.tick(FPS_COUNT) - - @staticmethod - def generate_knights_team(knights_sprite_group): - knights = [] - for i in range(4): - knight = Knight("./resources/textures/knight.png") - knights.append(knight) - knights_sprite_group.add(knight) - return knights - - @staticmethod - def generate_monster(monsters_sprite_group): - monsters = [] - for i in range(2): - monster = Monster() - monsters.append(monster) - monsters_sprite_group.add(monster) - return monsters - - @staticmethod - def generate_castle(castle_sprite_group): - castle = Castle() - castle_sprite_group.add(castle) - return castle diff --git a/logic/grid.py b/logic/grid.py deleted file mode 100644 index 8074713..0000000 --- a/logic/grid.py +++ /dev/null @@ -1,86 +0,0 @@ -import random -import numpy as np -import pygame - -from common.constants import ROWS, COLUMNS, GRID_CELL_PADDING, GRID_CELL_WIDTH, GRID_CELL_HEIGHT, BORDER_WIDTH, \ - BORDER_RADIUS, NBR_OF_TREES, NBR_OF_WATER -from .exceptions import FieldNotWalkable -from .field import Field - - -class Grid: - def __init__(self, textures): - self.textures = textures - self.grid = [] - self.free_fields = [] - self.busy_fields = [] - podkladka = np.zeros((ROWS, COLUMNS)) - for drzewa in range(0, NBR_OF_TREES): - rzad = random.randint(0, ROWS - 1) - kolumna = random.randint(0, COLUMNS - 1) - if podkladka[rzad][kolumna] == 0: - podkladka[rzad][kolumna] = 1 - else: - drzewa = drzewa - 1 - for i in range(0, NBR_OF_WATER): - rzad = random.randint(0, ROWS - 1) - kolumna = random.randint(0, COLUMNS - 1) - if podkladka[rzad][kolumna] == 0: - podkladka[rzad][kolumna] = 2 - else: - drzewa = drzewa - 1 - - for row in range(ROWS): - self.grid.append([]) - for column in range(COLUMNS): - box_rect = [(GRID_CELL_PADDING + GRID_CELL_WIDTH) * column + GRID_CELL_PADDING + 7, - (GRID_CELL_PADDING + GRID_CELL_HEIGHT) * row + GRID_CELL_PADDING + 7, - GRID_CELL_WIDTH, - GRID_CELL_HEIGHT] - if podkladka[row][column] == 1: - texture_path, converted_texture = self.textures[-1] - elif podkladka[row][column] == 2: - texture_path, converted_texture = self.textures[-2] - else: - texture_path, converted_texture = self.get_random_texture() - field = Field( - texture_path, - converted_texture, - pygame.transform.scale(converted_texture, - (GRID_CELL_WIDTH, - GRID_CELL_HEIGHT)), - pygame.Rect(box_rect), - row=row, - column=column - ) - if field.busy: - self.busy_fields.append(field) - else: - self.free_fields.append(field) - - self.grid[row].append(pygame.sprite.Group(field)) - - def update(self, screen): - self.draw(screen) - - def get_random_texture(self): - texture_index = random.randint(0, len(self.textures) - 3) - return self.textures[texture_index] - - def get_tile(self, row, column): - return pygame.sprite.Group.sprites(self.grid[row][column])[0] # iteruj kolumny i wiersze od 0 - - def put_on_tile(self, row, column, obj): # iteruj kolumny i wiersze od 0 - try: - self.get_tile(row, column).put_on(obj) - except FieldNotWalkable: - print("Field busy") - - def draw(self, screen): - bg_width = (GRID_CELL_PADDING + GRID_CELL_WIDTH) * COLUMNS + BORDER_WIDTH - bg_height = (GRID_CELL_PADDING + GRID_CELL_HEIGHT) * ROWS + BORDER_WIDTH - pygame.draw.rect(screen, (255, 255, 255), pygame.Rect(5, 5, bg_width, bg_height), 0, BORDER_RADIUS) - - for fields_row in self.grid: - for fields in fields_row: - fields.draw(screen) diff --git a/logic/level.py b/logic/level.py new file mode 100644 index 0000000..17db1f0 --- /dev/null +++ b/logic/level.py @@ -0,0 +1,144 @@ +import random + +import pygame + +from algorithms.bfs import graphsearch, State +from common.constants import * +from common.helpers import castle_neighbors +from logic.knights_queue import KnightsQueue +from logic.spawner import Spawner +from models.castle import Castle +from models.knight import Knight +from models.monster import Monster +from models.tile import Tile + + +class Level: + def __init__(self, screen): + self.screen = screen + + # sprite group setup + self.sprites = pygame.sprite.Group() + + 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): + 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_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_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, + CASTLE_SPAWN_HEIGHT, 2) + + spawner.spawn_where_possible(['m' for x in range(NBR_OF_MONSTERS)]) + + 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)) + + for row_index, row in enumerate(self.map): + for col_index, col in enumerate(row): + + # add base tiles, e.g. water, tree, grass + if col == "w": + texture_index = 5 + texture_surface = textures[texture_index][1] + Tile((col_index, row_index), texture_surface, self.sprites, 'w') + elif col == "t": + texture_index = 6 + texture_surface = textures[texture_index][1] + Tile((col_index, row_index), texture_surface, self.sprites, 't') + else: + texture_index = random.randint(0, 4) + texture_surface = textures[texture_index][1] + Tile((col_index, row_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_b": + knight = Knight((col_index, row_index), self.sprites, "blue") + self.map[row_index][col_index] = knight + self.list_knights_blue.append(knight) + elif col == "k_r": + knight = Knight((col_index, row_index), self.sprites, "red") + self.map[row_index][col_index] = knight + self.list_knights_red.append(knight) + elif col == "m": + monster = Monster((col_index, row_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: + castle = Castle((col_index, row_index), self.sprites) + self.map[row_index][col_index] = castle + self.list_castles.append(castle) + + 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_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 + action_list = graphsearch(state, self.map, goal_list) + 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 + + 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) + + # update and draw the game + self.sprites.draw(self.screen) diff --git a/logic/spawner.py b/logic/spawner.py index 69286ce..67431e4 100644 --- a/logic/spawner.py +++ b/logic/spawner.py @@ -1,36 +1,33 @@ import random -from common.constants import GRID_CELL_PADDING, GRID_CELL_WIDTH, GRID_CELL_HEIGHT +from common.constants import COLUMNS, ROWS + class Spawner: - def __init__(self, grid, objs_to_spawn_list: list, spawn_area_width=0, spawn_area_height=0, spawn_area_pos_row=0, - spawn_area_pos_column=0, spawn_where_possible=False): # kolumny i wiersze liczymy od 0; - self.objs_to_spawn_list = objs_to_spawn_list - self.spawn_area_width = spawn_area_width - self.spawn_area_height = spawn_area_height - self.spawn_area_pos_row = spawn_area_pos_row - self.spawn_area_pos_column = spawn_area_pos_column - self.grid = grid - self.spawn_where_possible = spawn_where_possible + def __init__(self, map): + self.map = map - def spawn(self): - if self.spawn_where_possible: - possible_fields = self.grid.free_fields - else: - possible_fields = [] - for field in self.grid.free_fields: - if (self.spawn_area_pos_column <= field.column < (self.spawn_area_width + self.spawn_area_pos_column)) \ - and (self.spawn_area_pos_row <= field.row < (self.spawn_area_height + self.spawn_area_pos_row)): - possible_fields.append(field) + def __is_free_field(self, field): + return field == ' ' - for obj in self.objs_to_spawn_list: - ran_index = random.randint(0, len(possible_fields)-1) - ran_field = possible_fields[ran_index] - ran_field.busy = True - obj.rect = obj.rect.clamp(ran_field) - self.grid.busy_fields.append(ran_field) - if ran_field in self.grid.free_fields: - self.grid.free_fields.remove(ran_field) - if ran_field in possible_fields: - possible_fields.remove(ran_field) - obj.update() + 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): + spawned_objects_count = 0 + + 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]): + for i in range(size): + for j in range(size): + 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) + if self.__is_free_field(self.map[x][y]): + self.map[x][y] = objects[spawned_objects_count] + spawned_objects_count += 1 diff --git a/models/castle.py b/models/castle.py index 5c0b4af..629f524 100644 --- a/models/castle.py +++ b/models/castle.py @@ -1,15 +1,14 @@ import pygame.image +from common.helpers import parse_cord + class Castle(pygame.sprite.Sprite): - images = [] - - def __init__(self): - super().__init__() - - self.image = pygame.image.load("./resources/textures/castle.png") + 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.images.append(self.image) - self.rect = self.image.get_rect() - castle_list = pygame.sprite.Group() + self.position = position + position_in_px = (parse_cord(position[0]), parse_cord(position[1])) + self.rect = self.image.get_rect(center=position_in_px) self.health = 80 diff --git a/models/knight.py b/models/knight.py index a4e264c..f9bac44 100644 --- a/models/knight.py +++ b/models/knight.py @@ -1,17 +1,58 @@ import pygame.image import random -class Knight(pygame.sprite.Sprite): - def __init__(self, img): - super().__init__() - self.images = [] - self.image = pygame.image.load("./resources/textures/knight.png") - self.image = pygame.transform.scale(self.image, (40, 40)) - self.images.append(self.image) - self.rect = self.image.get_rect() +from common.constants import GRID_CELL_SIZE, Direction +from common.helpers import parse_cord + +def load_knight_textures(): + random_index = random.randint(1, 4) + states = [ + pygame.image.load(f'resources/textures/knight_{random_index}_up.png').convert_alpha(), # up = 0 + pygame.image.load(f'resources/textures/knight_{random_index}_right.png').convert_alpha(), # right = 1 + pygame.image.load(f'resources/textures/knight_{random_index}_down.png').convert_alpha(), # down = 2 + pygame.image.load(f'resources/textures/knight_{random_index}_left.png').convert_alpha(), # left = 3 + ] + return states + + +class Knight(pygame.sprite.Sprite): + + def __init__(self, position, group, team): + super().__init__(group) + + self.direction = Direction.DOWN + self.states = load_knight_textures() + + self.image = self.states[self.direction.value] + self.position = position + position_in_px = (parse_cord(position[0]), parse_cord(position[1])) + 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.defense = random.randint(1, 4) self.points = 1 + def rotate_left(self): + self.direction = self.direction.left() + self.image = self.states[self.direction.value] + + def rotate_right(self): + self.direction = self.direction.right() + self.image = self.states[self.direction.value] + + def step_forward(self): + if self.direction.name == 'UP': + self.position = (self.position[0], self.position[1] - 1) + self.rect.y = self.rect.y - GRID_CELL_SIZE - 5 + elif self.direction.name == 'RIGHT': + self.position = (self.position[0] + 1, self.position[1]) + self.rect.x = self.rect.x + GRID_CELL_SIZE + 5 + elif self.direction.name == 'DOWN': + self.position = (self.position[0], self.position[1] + 1) + self.rect.y = self.rect.y + GRID_CELL_SIZE + 5 + elif self.direction.name == 'LEFT': + self.position = (self.position[0] - 1, self.position[1]) + self.rect.x = self.rect.x - GRID_CELL_SIZE - 5 diff --git a/models/monster.py b/models/monster.py index bec0315..fa42177 100644 --- a/models/monster.py +++ b/models/monster.py @@ -1,47 +1,39 @@ 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"), + pygame.image.load("./resources/textures/wolfart.png"), + pygame.image.load("./resources/textures/goblin.png"), +] + class Monster(pygame.sprite.Sprite): - monster_images = [] - - def __init__(self): - super().__init__() - - self.monster_image1 = pygame.image.load("./resources/textures/dragon2.png") - self.monster_image1 = pygame.transform.scale(self.monster_image1, (40, 40)) - self.monster_image2 = pygame.image.load("./resources/textures/birb.png") - self.monster_image2 = pygame.transform.scale(self.monster_image2, (40, 40)) - self.monster_image3 = pygame.image.load("./resources/textures/wolfart.png") - self.monster_image3 = pygame.transform.scale(self.monster_image3, (40, 40)) - self.monster_image4 = pygame.image.load("./resources/textures/goblin.png") - self.monster_image4 = pygame.transform.scale(self.monster_image4, (40, 40)) - self.monster_images.append(self.monster_image1) - self.monster_images.append(self.monster_image2) - self.monster_images.append(self.monster_image3) - self.monster_images.append(self.monster_image4) - #self.images.append(self.image1) - self.image = random.choice(self.monster_images) - self.rect = self.image.get_rect() - monster_list = pygame.sprite.Group() - + def __init__(self, position, group): + super().__init__(group) + self.image = random.choice(monster_images) + self.image = pygame.transform.scale(self.image, (40, 40)) + position_in_px = (parse_cord(position[0]), parse_cord(position[1])) + self.rect = self.image.get_rect(topleft=position_in_px) self.health = random.randrange(15, 25) self.attack = random.randrange(2, 10) - if self.image == self.monster_images[0]: + if self.image == monster_images[0]: self.health = 20 self.attack = 9 self.points = 10 - elif self.image == self.monster_images[1]: + elif self.image == monster_images[1]: self.health = 15 self.attack = 7 self.points = 7 - elif self.image == self.monster_images[2]: + elif self.image == monster_images[2]: self.health = 10 self.attack = 4 self.points = 4 - elif self.image == self.monster_images[3]: + elif self.image == monster_images[3]: self.health = 7 self.attack = 2 self.points = 2 - diff --git a/models/tile.py b/models/tile.py new file mode 100644 index 0000000..ccbefa6 --- /dev/null +++ b/models/tile.py @@ -0,0 +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 + position_in_px = (parse_cord(position[0]), parse_cord(position[1])) + self.rect = self.image.get_rect(topleft=position_in_px) + self.tile_type = tile_type \ No newline at end of file diff --git a/resources/textures/knight_1_down.png b/resources/textures/knight_1_down.png new file mode 100644 index 0000000..bfe5036 Binary files /dev/null and b/resources/textures/knight_1_down.png differ diff --git a/resources/textures/knight_1_left.png b/resources/textures/knight_1_left.png new file mode 100644 index 0000000..b489745 Binary files /dev/null and b/resources/textures/knight_1_left.png differ diff --git a/resources/textures/knight_1_right.png b/resources/textures/knight_1_right.png new file mode 100644 index 0000000..6374b9d Binary files /dev/null and b/resources/textures/knight_1_right.png differ diff --git a/resources/textures/knight_1_up.png b/resources/textures/knight_1_up.png new file mode 100644 index 0000000..3a465c9 Binary files /dev/null and b/resources/textures/knight_1_up.png differ diff --git a/resources/textures/knight_2_down.png b/resources/textures/knight_2_down.png new file mode 100644 index 0000000..288481c Binary files /dev/null and b/resources/textures/knight_2_down.png differ diff --git a/resources/textures/knight_2_left.png b/resources/textures/knight_2_left.png new file mode 100644 index 0000000..0c83300 Binary files /dev/null and b/resources/textures/knight_2_left.png differ diff --git a/resources/textures/knight_2_right.png b/resources/textures/knight_2_right.png new file mode 100644 index 0000000..905a751 Binary files /dev/null and b/resources/textures/knight_2_right.png differ diff --git a/resources/textures/knight_2_up.png b/resources/textures/knight_2_up.png new file mode 100644 index 0000000..2cef1c9 Binary files /dev/null and b/resources/textures/knight_2_up.png differ diff --git a/resources/textures/knight_3_down.png b/resources/textures/knight_3_down.png new file mode 100644 index 0000000..f8313b4 Binary files /dev/null and b/resources/textures/knight_3_down.png differ diff --git a/resources/textures/knight_3_left.png b/resources/textures/knight_3_left.png new file mode 100644 index 0000000..f89d6d1 Binary files /dev/null and b/resources/textures/knight_3_left.png differ diff --git a/resources/textures/knight_3_right.png b/resources/textures/knight_3_right.png new file mode 100644 index 0000000..8958ba5 Binary files /dev/null and b/resources/textures/knight_3_right.png differ diff --git a/resources/textures/knight_3_up.png b/resources/textures/knight_3_up.png new file mode 100644 index 0000000..8221933 Binary files /dev/null and b/resources/textures/knight_3_up.png differ diff --git a/resources/textures/knight_4_down.png b/resources/textures/knight_4_down.png new file mode 100644 index 0000000..776415e Binary files /dev/null and b/resources/textures/knight_4_down.png differ diff --git a/resources/textures/knight_4_left.png b/resources/textures/knight_4_left.png new file mode 100644 index 0000000..c8d3407 Binary files /dev/null and b/resources/textures/knight_4_left.png differ diff --git a/resources/textures/knight_4_right.png b/resources/textures/knight_4_right.png new file mode 100644 index 0000000..9ca3e51 Binary files /dev/null and b/resources/textures/knight_4_right.png differ diff --git a/resources/textures/knight_4_up.png b/resources/textures/knight_4_up.png new file mode 100644 index 0000000..242610a Binary files /dev/null and b/resources/textures/knight_4_up.png differ diff --git a/ui/logs.py b/ui/logs.py index a3455e9..166264e 100644 --- a/ui/logs.py +++ b/ui/logs.py @@ -1,7 +1,7 @@ import pygame from common.colors import FONT_DARK, ORANGE, WHITE -from common.constants import COLUMNS, GRID_CELL_PADDING, GRID_CELL_WIDTH, BORDER_WIDTH, BORDER_RADIUS +from common.constants import COLUMNS, GRID_CELL_PADDING, GRID_CELL_SIZE, BORDER_WIDTH, BORDER_RADIUS from common.helpers import draw_text @@ -10,7 +10,7 @@ class Logs: self.grid = [] def draw(self, screen): - x = (GRID_CELL_PADDING + GRID_CELL_WIDTH) * COLUMNS + BORDER_WIDTH + 15 + x = (GRID_CELL_PADDING + GRID_CELL_SIZE) * COLUMNS + BORDER_WIDTH + 15 y = 470 # background diff --git a/ui/screens/main_menu.py b/ui/screens/main_menu.py new file mode 100644 index 0000000..1ee4f0e --- /dev/null +++ b/ui/screens/main_menu.py @@ -0,0 +1,66 @@ +import sys + +import pygame + +from common.colors import WHITE, FONT_DARK +from common.helpers import draw_text +from ui.screens.credits import Credits +from ui.screens.options import Options +from ui.screens.screen import Screen + + +class MainMenu(Screen): + def __init__(self, screen, clock, bg, btn_play_action, btn_options_action, btn_credits_action): + super().__init__('main_menu', screen, clock) + self.click = False + self.bg = bg + self.btn_play_action = btn_play_action + self.btn_options_action = btn_options_action + self.btn_credits_action = btn_credits_action + + def display_screen(self): + running = True + while running: + self.screen.blit(self.bg, (0, 0)) + + pygame.draw.rect(self.screen, (255, 255, 255), pygame.Rect(800, 100, 400, 500), 0, 5) + draw_text('MAIN MENU', FONT_DARK, self.screen, 850, 150, 30, True) + + mx, my = pygame.mouse.get_pos() + + button_1 = pygame.Rect(850, 250, 300, 50) + button_2 = pygame.Rect(850, 350, 300, 50) + button_3 = pygame.Rect(850, 450, 300, 50) + if button_1.collidepoint((mx, my)): + if self.click: + self.btn_play_action() + if button_2.collidepoint((mx, my)): + if self.click: + self.btn_options_action() + if button_3.collidepoint((mx, my)): + if self.click: + self.btn_credits_action() + pygame.draw.rect(self.screen, (0, 191, 255), button_1, 0, 4) + draw_text('PLAY', WHITE, self.screen, 870, 255) + + pygame.draw.rect(self.screen, (0, 191, 255), button_2, 0, 4) + draw_text('OPTIONS', WHITE, self.screen, 870, 355) + + pygame.draw.rect(self.screen, (0, 191, 255), button_3, 0, 4) + draw_text('CREDITS', WHITE, self.screen, 870, 455) + + self.click = False + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + pygame.quit() + sys.exit() + if event.type == pygame.MOUSEBUTTONDOWN: + if event.button == 1: + self.click = True + + pygame.display.update() + self.clock.tick(60) diff --git a/ui/stats.py b/ui/stats.py index 30be6cb..521b901 100644 --- a/ui/stats.py +++ b/ui/stats.py @@ -1,7 +1,7 @@ import pygame from common.colors import FONT_DARK, ORANGE, WHITE, RED -from common.constants import COLUMNS, GRID_CELL_PADDING, GRID_CELL_WIDTH, BORDER_WIDTH, BORDER_RADIUS +from common.constants import COLUMNS, GRID_CELL_PADDING, GRID_CELL_SIZE, BORDER_WIDTH, BORDER_RADIUS from common.helpers import draw_text @@ -10,7 +10,7 @@ class Stats: self.grid = [] def draw(self, screen): - x = (GRID_CELL_PADDING + GRID_CELL_WIDTH) * COLUMNS + BORDER_WIDTH + 15 + x = (GRID_CELL_PADDING + GRID_CELL_SIZE) * COLUMNS + BORDER_WIDTH + 15 y = 5 # background