diff --git a/algorithms/a_star.py b/algorithms/a_star.py index d615ad9..5e95098 100644 --- a/algorithms/a_star.py +++ b/algorithms/a_star.py @@ -4,10 +4,11 @@ import heapq from dataclasses import dataclass, field from typing import Tuple, Optional, List +from algorithms.genetic.const import MAP_ALIASES from common.constants import ROWS, COLUMNS, LEFT, RIGHT, UP, DOWN from common.helpers import directions -EMPTY_FIELDS = ['s', 'g', ' '] +EMPTY_FIELDS = [MAP_ALIASES.get("SAND"), MAP_ALIASES.get("GRASS"), ' '] TURN_LEFT = 'TURN_LEFT' TURN_RIGHT = 'TURN_RIGHT' diff --git a/algorithms/genetic/common.py b/algorithms/genetic/common.py new file mode 100644 index 0000000..ab4d200 --- /dev/null +++ b/algorithms/genetic/common.py @@ -0,0 +1,142 @@ +from dataclasses import dataclass + +import numpy as np + +from const import * +from typing import List, Dict, Tuple + +import numpy.typing as npt + + +@dataclass +class Position: + row: int + col: int + + +@dataclass +class Area: + position: Position + width: int + height: int + + +AREAS_TO_CROSS = [ + # up above left knights spawn + Area(position=Position(row=0, col=0), + width=KNIGHTS_SPAWN_WIDTH, + height=LEFT_KNIGHTS_SPAWN_FIRST_ROW), + + # down below left knights spawn + Area(position=Position(row=LEFT_KNIGHTS_SPAWN_FIRST_ROW + KNIGHTS_SPAWN_HEIGHT, col=0), + width=KNIGHTS_SPAWN_WIDTH, + height=ROWS - LEFT_KNIGHTS_SPAWN_FIRST_ROW - KNIGHTS_SPAWN_HEIGHT), + + # between left knights spawn and castle + Area(position=Position(row=0, col=KNIGHTS_SPAWN_WIDTH), + width=CASTLE_SPAWN_FIRST_COL - KNIGHTS_SPAWN_WIDTH, + height=ROWS), + + # up above castle + Area(position=Position(row=0, col=CASTLE_SPAWN_FIRST_COL), + width=2, + height=CASTLE_SPAWN_FIRST_ROW), + + # down below castle + Area(position=Position(row=CASTLE_SPAWN_FIRST_ROW + 2, col=CASTLE_SPAWN_FIRST_COL), + width=2, + height=ROWS - CASTLE_SPAWN_FIRST_ROW - 2), + + # between castle and right knights spawn + Area(position=Position(row=0, col=CASTLE_SPAWN_FIRST_COL + 2), + width=RIGHT_KNIGHTS_SPAWN_FIRST_COL - CASTLE_SPAWN_FIRST_COL - 2, + height=ROWS), + + # up above right knights spawn + Area(position=Position(row=0, col=RIGHT_KNIGHTS_SPAWN_FIRST_COL), + width=KNIGHTS_SPAWN_WIDTH, + height=RIGHT_KNIGHTS_SPAWN_FIRST_ROW), + + # down below right knights spawn + Area(position=Position(row=RIGHT_KNIGHTS_SPAWN_FIRST_ROW + KNIGHTS_SPAWN_HEIGHT, col=RIGHT_KNIGHTS_SPAWN_FIRST_COL), + width=KNIGHTS_SPAWN_WIDTH, + height=ROWS - RIGHT_KNIGHTS_SPAWN_FIRST_ROW - KNIGHTS_SPAWN_HEIGHT), +] + + +def dfs(grid: npt.NDArray, visited: Dict[Tuple[int, int], bool], position: Position, rows: int, cols: int) -> None: + visited[(position.row, position.col)] = True + + row_vector = [0, 0, 1, -1] + col_vector = [-1, 1, 0, 0] + + neighbours = [] + for i in range(4): + rr = position.row + row_vector[i] + cc = position.col + col_vector[i] + if rr < 0 or rr >= ROWS: + continue + elif cc < 0 or cc >= COLUMNS: + continue + else: + p = Position(rr, cc) + if (p.row, p.col) in visited: + neighbours.append(p) + + for neighbour in neighbours: + if not visited[(neighbour.row, neighbour.col)]: + dfs(grid, visited, neighbour, rows, cols) + + +def get_islands(grid: npt.NDArray, positions: List[Position], rows: int = ROWS, cols: int = COLUMNS) -> List[Position]: + """it returns list of all islands roots""" + visited = {} + + for position in positions: + visited[(position.row, position.col)] = False + + islands = 0 + roots = [] + for position in positions: + if not visited[(position.row, position.col)]: + dfs(grid, visited, position, rows, cols) + roots.append(position) + islands += 1 + + return roots + + +def find_neighbours(grid: npt.NDArray, col: int, row: int) -> List[Position]: + dr = [-1, 1, 0, 0] + dc = [0, 0, -1, 1] + + neighbours = [] + + for i in range(4): + rr = row + dr[i] + cc = col + dc[i] + + if 0 <= rr < ROWS and 0 <= cc < COLUMNS and grid[rr][cc] == MAP_ALIASES.get('GRASS'): + neighbours.append(Position(row=rr, col=cc)) + + return neighbours + + +def get_tiles_positions(grid: npt.NDArray): + sands = [] + trees = [] + waters = [] + monsters = [] + + for row_num in range(len(grid)): + for col_num in range(len(grid[row_num])): + if grid[row_num][col_num] == MAP_ALIASES.get('WATER'): + waters.append(Position(row=row_num, col=col_num)) + elif grid[row_num][col_num] == MAP_ALIASES.get('TREE'): + trees.append(Position(row=row_num, col=col_num)) + elif grid[row_num][col_num] == MAP_ALIASES.get('SAND'): + sands.append(Position(row=row_num, col=col_num)) + elif grid[row_num][col_num] == MAP_ALIASES.get('MONSTER'): + monsters.append(Position(row=row_num, col=col_num)) + + return sands, trees, waters, monsters diff --git a/algorithms/genetic/const.py b/algorithms/genetic/const.py new file mode 100644 index 0000000..61ff403 --- /dev/null +++ b/algorithms/genetic/const.py @@ -0,0 +1,29 @@ +# map config +KNIGHTS_PER_TEAM_COUNT = 4 +SAND_COUNT = 21 +WATER_COUNT = 21 +TREE_COUNT = 37 +MONSTERS_COUNT = 2 +CASTLES_COUNT = 1 +ROWS = 19 +COLUMNS = 24 +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 +CASTLE_SPAWN_FIRST_ROW = 7 +CASTLE_SPAWN_FIRST_COL = 11 + +# map aliases +MAP_ALIASES = { + "GRASS": 0, + "SAND": 1, + "WATER": 2, + "TREE": 3, + "MONSTER": 4, + "CASTLE": 5, + "KNIGHT_RED": 6, + "KNIGHT_BLUE": 7, +} \ No newline at end of file diff --git a/algorithms/genetic/genome.py b/algorithms/genetic/genome.py new file mode 100644 index 0000000..a5f5c37 --- /dev/null +++ b/algorithms/genetic/genome.py @@ -0,0 +1,166 @@ +import math +import random +from copy import deepcopy +from random import randrange +from typing import List + +import numpy as np +import numpy.typing as npt + +from common import Position, get_islands, AREAS_TO_CROSS, find_neighbours, get_tiles_positions +from const import * + + +class Genome: + grid: npt.NDArray + knights_red: List[Position] + knights_blue: List[Position] + waters: List[Position] + trees: List[Position] + sands: List[Position] + monsters: List[Position] + fitness: int + sand_islands: List[Position] + tree_islands: List[Position] + water_islands: List[Position] + + def __init__(self): + self.grid = np.zeros((ROWS, COLUMNS), dtype=int) + self.fitness = 0 + + self.knights_red = spawn_objects_in_given_area( + grid=self.grid, + object_alias=MAP_ALIASES.get("KNIGHT_RED"), + objects_count=KNIGHTS_PER_TEAM_COUNT, + spawn_position_start=Position(row=LEFT_KNIGHTS_SPAWN_FIRST_ROW, col=LEFT_KNIGHTS_SPAWN_FIRST_COL), + width=KNIGHTS_SPAWN_WIDTH, + height=KNIGHTS_SPAWN_HEIGHT + ) + + self.knights_blue = spawn_objects_in_given_area( + grid=self.grid, + object_alias=MAP_ALIASES.get("KNIGHT_BLUE"), + objects_count=KNIGHTS_PER_TEAM_COUNT, + spawn_position_start=Position(row=RIGHT_KNIGHTS_SPAWN_FIRST_ROW, col=RIGHT_KNIGHTS_SPAWN_FIRST_COL), + width=KNIGHTS_SPAWN_WIDTH, + height=KNIGHTS_SPAWN_HEIGHT + ) + + spawn_objects_in_given_area( + grid=self.grid, + object_alias=MAP_ALIASES.get("CASTLE"), + objects_count=4, + spawn_position_start=Position(row=CASTLE_SPAWN_FIRST_ROW, col=CASTLE_SPAWN_FIRST_COL), + width=2, + height=2 + ) + + self.waters = spawn_objects_in_given_area(grid=self.grid, object_alias=MAP_ALIASES.get("WATER"), + objects_count=WATER_COUNT) + self.trees = spawn_objects_in_given_area(grid=self.grid, object_alias=MAP_ALIASES.get("TREE"), + objects_count=TREE_COUNT) + self.sands = spawn_objects_in_given_area(grid=self.grid, object_alias=MAP_ALIASES.get("SAND"), + objects_count=SAND_COUNT) + self.monsters = spawn_objects_in_given_area(grid=self.grid, object_alias=MAP_ALIASES.get("MONSTER"), + objects_count=MONSTERS_COUNT) + + self.sand_islands = get_islands(self.grid, self.sands) + self.tree_islands = get_islands(self.grid, self.trees) + self.water_islands = get_islands(self.grid, self.waters) + + def update_map(self): + self.sands, self.trees, self.waters, self.monsters = get_tiles_positions(self.grid) + + self.sand_islands = get_islands(self.grid, self.sands) + self.tree_islands = get_islands(self.grid, self.trees) + self.water_islands = get_islands(self.grid, self.waters) + + def calc_fitness(self): + score = SAND_COUNT + TREE_COUNT + WATER_COUNT + score = score - len(self.sand_islands) - len(self.tree_islands) - len(self.water_islands) + + sands, trees, waters, monsters = get_tiles_positions(self.grid) + + if len(monsters) != MONSTERS_COUNT: + self.fitness = 0 + return + + if len(sands) < SAND_COUNT or len(trees) < TREE_COUNT or len(waters) < WATER_COUNT: + self.fitness = 5 + return + + self.fitness = score + + def crossover(self, partner): + # replace a randomly selected part of the grid with partner's part + child = Genome() + child.grid = deepcopy(self.grid) + area_to_cross = random.choice(AREAS_TO_CROSS) + + for row in range(area_to_cross.position.row, area_to_cross.position.row + area_to_cross.height): + for col in range(area_to_cross.position.col, area_to_cross.position.col + area_to_cross.width): + child.grid[row][col] = partner.grid[row][col] + + child.update_map() + + return child + + def mutate(self, mutation_rate: float): + # remove 1 item from a random island and add a neighbor to another island + if random.random() < mutation_rate: + + # select islands of the same, random type + islands_of_same_type = random.choice([self.sand_islands, self.tree_islands, self.water_islands]) + random_index = random.randint(0, len(islands_of_same_type) - 1) + island = islands_of_same_type[random_index] + next_island = islands_of_same_type[(random_index + 1) % len(islands_of_same_type)] + + free_tiles_nearby = find_neighbours(self.grid, next_island.col, next_island.row) + + tile_type = self.grid[island.row][island.col] + self.grid[island.row][island.col] = MAP_ALIASES.get('GRASS') + + # todo: if there are no free tiles around then randomize another next_island + if len(free_tiles_nearby) > 0: + random_free_tile = random.choice(free_tiles_nearby) + island.row = random_free_tile.row + island.col = random_free_tile.col + self.grid[island.row][island.col] = tile_type + + self.update_map() + + +def is_empty(grid: npt.NDArray, position: Position) -> bool: + return grid[position.row, position.col] in [MAP_ALIASES.get("GRASS"), MAP_ALIASES.get("SAND")] + + +def is_invalid_area(spawn_position_start, height, width) -> bool: + return spawn_position_start.row + height - 1 < 0 or \ + spawn_position_start.row + height - 1 >= ROWS or \ + spawn_position_start.col + width - 1 < 0 or \ + spawn_position_start.col + width - 1 >= COLUMNS + + +def spawn_objects_in_given_area(grid: npt.NDArray, + object_alias: str, + objects_count: int = 1, + spawn_position_start: Position = Position(row=0, col=0), + width: int = COLUMNS, + height: int = ROWS) -> List[Position]: + if is_invalid_area(spawn_position_start, height, width): + raise ValueError("Invalid spawn area") + + objects_remaining = int(objects_count) + positions = [] + + while objects_remaining > 0: + row = randrange(spawn_position_start.row, spawn_position_start.row + height) + col = randrange(spawn_position_start.col, spawn_position_start.col + width) + position = Position(row=row, col=col) + + if is_empty(grid=grid, position=position): + grid[position.row, position.col] = object_alias + positions.append(position) + objects_remaining -= 1 + + return positions diff --git a/algorithms/genetic/map_generator.py b/algorithms/genetic/map_generator.py new file mode 100644 index 0000000..80e5b08 --- /dev/null +++ b/algorithms/genetic/map_generator.py @@ -0,0 +1,26 @@ +from algorithms.genetic.genome import Genome +from algorithms.genetic.map_importer_exporter import export_map +from population import Population + + +def main() -> None: + population_size = 500 + mutation_rate = 0.3 + + population = Population(mutation_rate, population_size, 55) + + while not population.evaluate(): + # create next generation + population.generate() + + # calc fitness + population.calc_fitness() + + print(population.best_genome.grid) + print("Fitness of the best: ", population.best_genome.fitness) + + export_map(population.best_genome.grid) + + +if __name__ == '__main__': + main() diff --git a/algorithms/genetic/map_importer_exporter.py b/algorithms/genetic/map_importer_exporter.py new file mode 100644 index 0000000..35796fe --- /dev/null +++ b/algorithms/genetic/map_importer_exporter.py @@ -0,0 +1,42 @@ +import json +import random +import string +from datetime import datetime +from pathlib import Path +import numpy +import numpy.typing as npt +from os import listdir +from os.path import isfile, join + + +# Save map to file +def export_map(grid: npt.NDArray): + json_data = {"map": grid.tolist()} + + now = datetime.now() + file_name = "map_" + now.strftime("%Y_%m_%d_%H_%M_%S") + ".json" + path = Path("../../resources/maps/") + file_to_open = path / file_name + + with open(file_to_open, "w+") as write_file: + json.dump(json_data, write_file) + print("Saved map to file " + file_name) + + +def import_random_map() -> object: + path = "resources/maps" + files = [f for f in listdir(path) if isfile(join(path, f))] + random_map_name = random.choice(files) + return import_map(random_map_name) + + +# Read map from file +def import_map(file_name: string) -> object: + file_to_open = "resources/maps/" + file_name + with open(file_to_open, "r") as read_file: + print("Reading map from file " + file_name) + decoded_json = json.load(read_file) + + decoded_grid = numpy.asarray(decoded_json["map"]) + print(decoded_grid) + return decoded_grid.tolist() diff --git a/algorithms/genetic/population.py b/algorithms/genetic/population.py new file mode 100644 index 0000000..85ca8a4 --- /dev/null +++ b/algorithms/genetic/population.py @@ -0,0 +1,81 @@ +import random +from typing import List + +import numpy as np +import numpy.typing as npt + +from genome import Genome + + +class Population: + population: List[Genome] = [] # array to hold the current population + mating_pool: List[Genome] = [] # array which we will use for our "mating pool" + generations: int = 0 # number of generations + finished: bool = False # are we finished evolving? + mutation_rate: float + perfect_score: int + best_genome: Genome + + def __init__(self, mutation_rate, population_size, perfect_score=20): + self.mutation_rate = mutation_rate + self.perfect_score = perfect_score + + for i in range(0, population_size): + new_genome = Genome() + new_genome.calc_fitness() + self.population.append(new_genome) + + # create a new generation + def generate(self): + max_fitness = 0 + for genome in self.population: + if genome.fitness > max_fitness: + max_fitness = genome.fitness + + print("Max fitness of generation " + str(self.generations) + " = " + str(max_fitness)) + + # refill the population with children from the mating pool + new_population = [] + for genome in self.population: + partner_a = self.accept_reject(max_fitness) + partner_b = self.accept_reject(max_fitness) + child = partner_a.crossover(partner_b) + child.mutate(self.mutation_rate) + new_population.append(child) + + self.population = new_population + self.generations += 1 + + # select random with correct probability from population + def accept_reject(self, max_fitness: int): + safe_flag = 0 + + while safe_flag < 10000: + partner = random.choice(self.population) + r = random.randint(0, max_fitness) + + if r < partner.fitness: + return partner + + safe_flag += 1 + + # compute the current "most fit" member of the population + def evaluate(self): + record = 0 + best_index = 0 + + for index in range(len(self.population)): + genome = self.population[index] + if genome.fitness > record: + record = genome.fitness + best_index = index + + self.best_genome = self.population[best_index] + if record >= self.perfect_score: + self.finished = True + + return self.finished + + def calc_fitness(self): + for genome in self.population: + genome.calc_fitness() diff --git a/algorithms/neural_network/__init__.py b/algorithms/neural_network/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/common/constants.py b/common/constants.py index bcf83ae..d4b40d8 100644 --- a/common/constants.py +++ b/common/constants.py @@ -6,7 +6,7 @@ GAME_TITLE = 'WMICraft' WINDOW_HEIGHT = 800 WINDOW_WIDTH = 1360 FPS_COUNT = 60 -TURN_INTERVAL = 300 +TURN_INTERVAL = 500 GRID_CELL_PADDING = 5 GRID_CELL_SIZE = 36 diff --git a/common/helpers.py b/common/helpers.py index 3b0aa8c..86dcae6 100644 --- a/common/helpers.py +++ b/common/helpers.py @@ -1,6 +1,8 @@ from typing import Tuple, List import pygame + +from algorithms.genetic.const import MAP_ALIASES from common.constants import GRID_CELL_PADDING, GRID_CELL_SIZE, COLUMNS, ROWS, CLASSES, CLASS_TO_ID import csv import os @@ -99,7 +101,7 @@ def castle_neighbors(map, castle_bottom_right_row, castle_bottom_right_col): return neighbors -def find_neighbours(grid: List[List[str]], col: int, row: int) -> List[Tuple[int, int]]: +def find_neighbours(grid: List[List[int]], col: int, row: int) -> List[Tuple[int, int]]: dr = [-1, 1, 0, 0] dc = [0, 0, -1, 1] @@ -111,7 +113,7 @@ def find_neighbours(grid: List[List[str]], col: int, row: int) -> List[Tuple[int if rr < 0 or cc < 0: continue if rr >= ROWS or cc >= COLUMNS: continue - if grid[rr][cc] not in ['g', 's', '.']: continue + if grid[rr][cc] not in [MAP_ALIASES.get("GRASS"), MAP_ALIASES.get("SAND"), '.']: continue neighbours.append((rr, cc)) return neighbours diff --git a/learning/decision_tree.py b/learning/decision_tree.py index 7f92344..57ef4b2 100644 --- a/learning/decision_tree.py +++ b/learning/decision_tree.py @@ -34,7 +34,7 @@ class DecisionTree: self.model = DecisionTreeClassifier(criterion='entropy') self.model.fit(self.train_set.values, self.goals) - def predict_move(self, grid: List[List[str]], current_knight: Knight, castle: Castle, monsters: List[Monster], + def predict_move(self, grid: List[List[int]], current_knight: Knight, castle: Castle, monsters: List[Monster], opponents: List[Knight]) -> \ List[Tuple[int, int]]: distance_to_castle = manhattan_distance(current_knight.position, castle.position) @@ -42,14 +42,15 @@ class DecisionTree: monsters_parsed = [] for monster in monsters: monsters_parsed.append((manhattan_distance(current_knight.position, monster.position), parse_hp( - monster.current_hp))) + monster.health_bar.current_hp))) opponents_parsed = [] for opponent in opponents: opponents_parsed.append( - (manhattan_distance(current_knight.position, opponent.position), parse_hp(opponent.health_bar.current_hp))) + (manhattan_distance(current_knight.position, opponent.position), + parse_hp(opponent.health_bar.current_hp))) - prediction = self.get_prediction(tower_dist=distance_to_castle, tower_hp=castle.current_hp, + prediction = self.get_prediction(tower_dist=distance_to_castle, tower_hp=castle.health_bar.current_hp, mob1_dist=monsters_parsed[0][0], mob1_hp=monsters_parsed[0][1], mob2_dist=monsters_parsed[1][0], mob2_hp=monsters_parsed[1][1], opp1_dist=opponents_parsed[0][0], opp1_hp=opponents_parsed[0][1], @@ -57,7 +58,7 @@ class DecisionTree: opp3_dist=opponents_parsed[2][0], opp3_hp=opponents_parsed[2][1], opp4_dist=opponents_parsed[3][0], opp4_hp=opponents_parsed[3][1], agent_hp=current_knight.health_bar.current_hp) - print(prediction) + print(f'Prediction = {prediction}') if prediction == 'tower': # castle... return castle_neighbors(grid, castle_bottom_right_row=castle.position[0], castle_bottom_right_col=castle.position[1]) diff --git a/logic/level.py b/logic/level.py index 73dcd8e..df2c462 100644 --- a/logic/level.py +++ b/logic/level.py @@ -3,10 +3,11 @@ import random import pygame from algorithms.a_star import a_star, State, TURN_RIGHT, TURN_LEFT, FORWARD +from algorithms.genetic.const import MAP_ALIASES +from algorithms.genetic.map_importer_exporter import import_random_map from common.constants import * from learning.decision_tree import DecisionTree 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 @@ -21,7 +22,7 @@ class Level: # sprite group setup self.sprites = pygame.sprite.LayeredUpdates() - self.map = [['g' for _ in range(COLUMNS)] for y in range(ROWS)] + self.map = [] self.list_knights_blue = [] self.list_knights_red = [] @@ -31,27 +32,11 @@ class Level: self.knights_queue = None def create_map(self): - self.generate_map() + self.map = import_random_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 _ 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 _ 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 _ 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 _ in range(NBR_OF_MONSTERS)]) - def setup_base_tiles(self): textures = [] for texture_path in TILES: @@ -63,15 +48,15 @@ class Level: for col_index, col in enumerate(row): # add base tiles, e.g. water, tree, grass - if col == "w": + if col == MAP_ALIASES.get('WATER'): texture_index = 5 texture_surface = textures[texture_index][1] Tile((col_index, row_index), texture_surface, self.sprites, 'w') - elif col == "t": + elif col == MAP_ALIASES.get('TREE'): texture_index = 6 texture_surface = textures[texture_index][1] Tile((col_index, row_index), texture_surface, self.sprites, 't') - elif col == "s": + elif col == MAP_ALIASES.get('SAND'): texture_index = 4 texture_surface = textures[texture_index][1] Tile((col_index, row_index), texture_surface, self.sprites) @@ -88,19 +73,19 @@ class Level: for col_index, col in enumerate(row): # add objects, e.g. knights, monsters, castle - if col == "k_b": + if col == MAP_ALIASES.get('KNIGHT_BLUE'): knight = Knight(self.screen, (col_index, row_index), self.sprites, "blue") self.map[row_index][col_index] = knight self.list_knights_blue.append(knight) - elif col == "k_r": + elif col == MAP_ALIASES.get('KNIGHT_RED'): knight = Knight(self.screen, (col_index, row_index), self.sprites, "red") self.map[row_index][col_index] = knight self.list_knights_red.append(knight) - elif col == "m": + elif col == MAP_ALIASES.get('MONSTER'): monster = Monster(self.screen, (col_index, row_index), self.sprites) self.map[row_index][col_index] = monster self.list_monsters.append(monster) - elif col == "c": + elif col == MAP_ALIASES.get('CASTLE'): castle_count += 1 if castle_count == 4: castle = Castle(self.screen, (col_index, row_index), self.sprites) @@ -108,15 +93,25 @@ class Level: self.list_castles.append(castle) def handle_turn(self): - print("next turn") current_knight = self.knights_queue.dequeue_knight() + knights_list = self.list_knights_red + self.list_knights_blue + print("next turn " + current_knight.team) knight_pos_x = current_knight.position[0] knight_pos_y = current_knight.position[1] + positions = [] + op_pos_1 = current_knight.position[0] - 1, current_knight.position[1] + positions.append(op_pos_1) + op_pos_2 = current_knight.position[0], current_knight.position[1] - 1 + positions.append(op_pos_2) + op_pos_3 = current_knight.position[0] + 1, current_knight.position[1] + positions.append(op_pos_3) + op_pos_4 = current_knight.position[0], current_knight.position[1] + 1 + positions.append(op_pos_4) goal_list = self.decision_tree.predict_move(grid=self.map, current_knight=current_knight, monsters=self.list_monsters, - opponents=self.list_knights_red - if current_knight.team_alias == 'k_r' else self.list_knights_blue, + opponents=self.list_knights_blue + if current_knight.team_alias() == 'k_r' else self.list_knights_red, castle=self.list_castles[0]) if len(goal_list) == 0: @@ -131,6 +126,11 @@ class Level: return next_action = action_list.pop(0) + for some_knight in knights_list: + for some_position in positions: + if some_knight.position == some_position: + some_knight.health_bar.take_dmg(1) + if next_action == TURN_LEFT: self.logs.enqueue_log(f'AI {current_knight.team}: ObrĂ³t w lewo.') current_knight.rotate_left() @@ -139,7 +139,7 @@ class Level: current_knight.rotate_right() elif next_action == FORWARD: current_knight.step_forward() - self.map[knight_pos_y][knight_pos_x] = 'g' + self.map[knight_pos_y][knight_pos_x] = MAP_ALIASES.get("GRASS") # update knight on map if current_knight.direction.name == UP: diff --git a/models/knight.py b/models/knight.py index 05eabfc..39c2679 100644 --- a/models/knight.py +++ b/models/knight.py @@ -37,7 +37,7 @@ class Knight(pygame.sprite.Sprite): self.attack = random.randint(4, 7) self.defense = random.randint(1, 4) self.points = 1 - self.health_bar = HealthBar(screen, self.rect, current_hp=random.randint(1, self.max_hp), max_hp=self.max_hp, calculate_xy=True, calculate_size=True) + self.health_bar = HealthBar(screen, self.rect, current_hp=self.max_hp, max_hp=self.max_hp, calculate_xy=True, calculate_size=True) def rotate_left(self): self.direction = self.direction.left() diff --git a/requirements.txt b/requirements.txt index 8223619..973091a 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/resources/maps/map_2022_06_02_13_27_18.json b/resources/maps/map_2022_06_02_13_27_18.json new file mode 100644 index 0000000..186d352 --- /dev/null +++ b/resources/maps/map_2022_06_02_13_27_18.json @@ -0,0 +1 @@ +{"map": [[0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], [0, 0, 0, 0, 3, 3, 0, 0, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3], [0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 3], [0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 0, 3], [0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2], [0, 0, 3, 3, 3, 0, 0, 0, 3, 3, 3, 3, 0, 2, 2, 2, 0, 0, 0, 0, 0, 7, 2, 0], [0, 0, 0, 6, 0, 0, 0, 2, 2, 2, 0, 5, 5, 0, 2, 0, 0, 2, 2, 2, 2, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 5, 5, 0, 2, 0, 0, 0, 0, 2, 2, 1, 1, 7], [0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, 1, 7], [6, 0, 0, 6, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 1, 1, 0], [6, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 3, 3, 0, 0, 0, 0, 3, 3, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 3, 3, 0, 1, 1, 7, 0], [0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 3, 3, 3, 1, 1, 1, 0], [0, 3, 3, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 1, 1, 0, 0], [0, 3, 3, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]} \ No newline at end of file diff --git a/resources/maps/map_2022_06_06_14_55_49.json b/resources/maps/map_2022_06_06_14_55_49.json new file mode 100644 index 0000000..86d09a8 --- /dev/null +++ b/resources/maps/map_2022_06_06_14_55_49.json @@ -0,0 +1 @@ +{"map": [[0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0], [0, 3, 3, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 3, 3, 3, 0, 0, 0, 2, 0, 0, 0, 0, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0], [0, 0, 3, 2, 2, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 2, 2, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0], [1, 1, 0, 0, 3, 0, 0, 0, 0, 0, 3, 3, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 6, 3, 3, 0, 0, 0, 0, 0, 3, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 7, 0, 0], [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, 5, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 7, 7, 0, 0], [6, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 3, 7, 0, 0, 0], [0, 0, 6, 6, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0], [0, 2, 2, 2, 2, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0], [2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [2, 0, 2, 0, 0, 0, 0, 0, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]} \ No newline at end of file diff --git a/resources/maps/map_2022_06_06_14_58_11.json b/resources/maps/map_2022_06_06_14_58_11.json new file mode 100644 index 0000000..dbf2de1 --- /dev/null +++ b/resources/maps/map_2022_06_06_14_58_11.json @@ -0,0 +1 @@ +{"map": [[0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 3, 3], [0, 0, 0, 0, 2, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 3, 0], [0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 2, 0], [0, 0, 3, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 2, 0], [0, 0, 3, 0, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 0], [0, 0, 3, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0], [0, 0, 0, 6, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], [0, 6, 0, 0, 0, 0, 3, 0, 0, 0, 0, 5, 5, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 5, 5, 1, 2, 0, 0, 0, 0, 0, 0, 7, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 3, 0, 0, 0, 4, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0], [0, 0, 3, 3, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 4], [6, 0, 0, 6, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 1, 1, 0, 7, 0, 7, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3, 3, 2, 0, 0, 0, 0, 0, 0], [0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0], [0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 3, 2, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]} \ No newline at end of file