diff --git a/assets/atlas.png b/assets/atlas.png index 07f1d2d..46a2db2 100644 Binary files a/assets/atlas.png and b/assets/atlas.png differ diff --git a/assets/srgb_profile_fix.py b/assets/srgb_profile_fix.py index 8869722..72c4d16 100644 --- a/assets/srgb_profile_fix.py +++ b/assets/srgb_profile_fix.py @@ -1,4 +1,5 @@ import os + import pygame as pg for i in os.listdir('.'): diff --git a/assets/stevenson.png b/assets/stevenson.png index 9caa64f..90c91bd 100644 Binary files a/assets/stevenson.png and b/assets/stevenson.png differ diff --git a/survival/__init__.py b/survival/__init__.py index b0e66dc..6a9abf7 100644 --- a/survival/__init__.py +++ b/survival/__init__.py @@ -3,9 +3,9 @@ import pygame from settings import SCREEN_WIDTH, SCREEN_HEIGHT from survival.camera import Camera from survival.game_map import GameMap -from survival.player_generator import PlayerGenerator -from survival.resource_generator import ResourceGenerator -from survival.world_generator import WorldGenerator +from survival.generators.player_generator import PlayerGenerator +from survival.generators.resource_generator import ResourceGenerator +from survival.generators.world_generator import WorldGenerator if __name__ == '__main__': pygame.init() diff --git a/survival/biomes/biome_data.py b/survival/biomes/biome_data.py new file mode 100644 index 0000000..eae1966 --- /dev/null +++ b/survival/biomes/biome_data.py @@ -0,0 +1,9 @@ +from survival.biomes.biome_preset import BiomePreset + + +class BiomeData: + def __init__(self, preset: BiomePreset): + self.biome = preset + + def get_diff_value(self, height: float, moisture: float, heat: float): + return (height - self.biome.min_height) + (moisture - self.biome.min_moisture) + (heat - self.biome.min_heat) diff --git a/survival/biomes/biome_preset.py b/survival/biomes/biome_preset.py new file mode 100644 index 0000000..b11cd3c --- /dev/null +++ b/survival/biomes/biome_preset.py @@ -0,0 +1,22 @@ +import random + +from survival.tile import Tile + + +class BiomePreset: + def __init__(self, name, min_height: float, min_moisture: float, min_heat: float, tiles: list[Tile]): + self.name = name + self.min_height = min_height + self.min_moisture = min_moisture + self.min_heat = min_heat + self.tiles = tiles + + def get_new_tile(self): + tile = random.choice(self.tiles) + return Tile(origin=tile.origin, cost=tile.cost, biome=self) + + def get_tile_sprite(self): + pass + + def match_conditions(self, height, moisture, heat): + return height >= self.min_height and moisture >= self.min_moisture and heat >= self.min_heat diff --git a/survival/biomes/noise.py b/survival/biomes/noise.py new file mode 100644 index 0000000..7635935 --- /dev/null +++ b/survival/biomes/noise.py @@ -0,0 +1,6 @@ +from perlin_noise import PerlinNoise + + +def generate_noise(width: int, height: int, octaves, seed): + noise_map = PerlinNoise(octaves=octaves, seed=seed) + return [[noise_map([x / width, y / height]) for y in range(width)] for x in range(height)] diff --git a/survival/esper.py b/survival/esper.py index 04f5d52..ed92a64 100644 --- a/survival/esper.py +++ b/survival/esper.py @@ -1,13 +1,11 @@ import time as _time - from functools import lru_cache as _lru_cache +from typing import Any as _Any +from typing import Iterable as _Iterable from typing import List as _List +from typing import Tuple as _Tuple from typing import Type as _Type from typing import TypeVar as _TypeVar -from typing import Any as _Any -from typing import Tuple as _Tuple -from typing import Iterable as _Iterable - version = '1.3' diff --git a/survival/game_map.py b/survival/game_map.py index b8563be..f8dca9e 100644 --- a/survival/game_map.py +++ b/survival/game_map.py @@ -24,3 +24,6 @@ class GameMap: def is_colliding(self, pos): return pos[0] < 0 or pos[0] >= self.width or pos[1] < 0 or pos[1] >= self.height or self.entity_layer.is_colliding(pos) + + def get_cost(self, pos): + return self.tile_layer.get_cost(pos) diff --git a/survival/building_generator.py b/survival/generators/building_generator.py similarity index 100% rename from survival/building_generator.py rename to survival/generators/building_generator.py diff --git a/survival/player_generator.py b/survival/generators/player_generator.py similarity index 100% rename from survival/player_generator.py rename to survival/generators/player_generator.py diff --git a/survival/resource_generator.py b/survival/generators/resource_generator.py similarity index 100% rename from survival/resource_generator.py rename to survival/generators/resource_generator.py diff --git a/survival/generators/tile_generator.py b/survival/generators/tile_generator.py new file mode 100644 index 0000000..3fc133d --- /dev/null +++ b/survival/generators/tile_generator.py @@ -0,0 +1,73 @@ +import random + +from survival.biomes.biome_data import BiomeData +from survival.biomes.biome_preset import BiomePreset +from survival.biomes.noise import generate_noise +from survival.tile import Tile + + +class TileGenerator: + Tiles = { + "Grass1": Tile(origin=(0, 0), cost=1), + "Grass2": Tile(origin=(32, 0), cost=1), + "Grass3": Tile(origin=(64, 0), cost=1), + "Grass4": Tile(origin=(96, 0), cost=1), + "Sand": Tile(origin=(64, 64), cost=20), + "Puddle": Tile(origin=(96, 64), cost=20), + } + + TilesValues = list(Tiles.values()) + + Biomes = [ + BiomePreset("Desert", min_height=0.2, min_moisture=0, min_heat=0.5, tiles=[Tiles["Grass1"], Tiles["Grass2"], + Tiles["Grass3"], Tiles["Grass4"]]), + BiomePreset("Forest", min_height=0.2, min_moisture=0.4, min_heat=0.4, tiles=[Tiles["Sand"]]), + BiomePreset("Grassland", min_height=0.2, min_moisture=0.5, min_heat=0.3, tiles=[Tiles["Sand"]]), + BiomePreset("Marsh", min_height=0.3, min_moisture=0.5, min_heat=0.62, tiles=[Tiles["Puddle"]]), + BiomePreset("Ocean", min_height=0, min_moisture=0, min_heat=0, tiles=[Tiles["Sand"]]), + BiomePreset("Tundra", min_height=0.2, min_moisture=0, min_heat=0, tiles=[Tiles["Puddle"]]) + ] + + @staticmethod + def get_random_tile(): + tile = random.choice(TileGenerator.TilesValues) + return Tile(origin=tile.origin, cost=tile.cost) + + @staticmethod + def generate_random_tiles(width: int, height: int) -> list[list[Tile]]: + return [[TileGenerator.get_random_tile() for _ in range(width)] for _ in range(height)] + + @staticmethod + def generate_biome_tiles(width: int, height: int): + seed = random.randint(0, 9999999) + octaves = 10 + height_map = generate_noise(width, height, octaves, seed) + moisture_map = generate_noise(width, height, octaves, seed) + heat_map = generate_noise(width, height, octaves, seed) + + return [[TileGenerator.get_biome(height_map[y][x], moisture_map[y][x], heat_map[y][x]).get_new_tile() for x in + range(width)] for y in range(height)] + + @staticmethod + def get_biome(height, moisture, heat) -> BiomePreset: + matching_biomes = list() + + for biome in TileGenerator.Biomes: + if biome.match_conditions(height, moisture, heat): + matching_biomes.append(BiomeData(biome)) + + current_value = 0 + found_biome = None + + for biome in matching_biomes: + if found_biome is None: + found_biome = biome.biome + current_value = biome.get_diff_value(height, moisture, heat) + elif biome.get_diff_value(height, moisture, heat) < current_value: + found_biome = biome.biome + current_value = biome.get_diff_value(height, moisture, heat) + + if found_biome is None: + found_biome = TileGenerator.Biomes[0] + + return found_biome diff --git a/survival/world_generator.py b/survival/generators/world_generator.py similarity index 100% rename from survival/world_generator.py rename to survival/generators/world_generator.py diff --git a/survival/graph_search.py b/survival/graph_search.py index 92054e7..77b4138 100644 --- a/survival/graph_search.py +++ b/survival/graph_search.py @@ -1,4 +1,5 @@ from enum import Enum +from queue import PriorityQueue from survival import GameMap from survival.components.position_component import PositionComponent @@ -12,67 +13,84 @@ class Action(Enum): class State: - def __init__(self, position, direction): + def __init__(self, position: tuple[int, int], direction: Direction): self.position = position self.direction = direction class Node: - def __init__(self, state: State, parent=None, action=None): + def __init__(self, state: State, parent=None, action=None, cost=None): self.state = state self.parent = parent self.action = action + self.cost = cost + + def __lt__(self, other): + return self.cost < other.cost + + def __eq__(self, other): + return self.cost == other.cost -def get_moved_position(position, direction): +def get_moved_position(position: tuple[int, int], direction: Direction): vector = Direction.get_vector(direction) return position[0] + vector[0], position[1] + vector[1] -def get_states(state: State, game_map: GameMap): +def get_states(state: State, game_map: GameMap) -> list[tuple[Action, State, int]]: states = list() - states.append((Action.ROTATE_LEFT, State(state.position, state.direction.rotate_left(state.direction)))) - states.append((Action.ROTATE_RIGHT, State(state.position, state.direction.rotate_right(state.direction)))) + states.append((Action.ROTATE_LEFT, State(state.position, state.direction.rotate_left(state.direction)), 1)) + states.append((Action.ROTATE_RIGHT, State(state.position, state.direction.rotate_right(state.direction)), 1)) - target_state = get_moved_position(state.position, state.direction) - if not game_map.is_colliding(target_state): - states.append((Action.MOVE, State(target_state, state.direction))) + target_position = get_moved_position(state.position, state.direction) + if not game_map.is_colliding(target_position): + states.append((Action.MOVE, State(target_position, state.direction), game_map.get_cost(target_position))) return states +def build_path(node: Node): + actions = [node.action] + parent = node.parent + + while parent is not None: + if parent.action is not None: + actions.append(parent.action) + parent = parent.parent + + actions.reverse() + return actions + + +def heuristic(new_node: Node, goal: tuple[int, int]): + return abs(new_node.state.position[0] - goal[0]) + abs(new_node.state.position[1] - goal[1]) + + def graph_search(game_map: GameMap, start: PositionComponent, goal: tuple): - fringe = list() + fringe = PriorityQueue() explored = list() explored_states = set() - fringe_states = set() + fringe_states = set() # Stores positions and directions of states start = State(start.grid_position, start.direction) - fringe.append(Node(start)) + fringe.put((0, Node(start, cost=0))) fringe_states.add((tuple(start.position), start.direction)) while True: # No solutions found - if not any(fringe): + if fringe.empty(): return [] - node = fringe.pop(0) + node = fringe.get() + node_priority = node[0] + node = node[1] fringe_states.remove((tuple(node.state.position), node.state.direction)) # Check goal if node.state.position == goal: - actions = [node.action] - parent = node.parent - - while parent is not None: - if parent.action is not None: - actions.append(parent.action) - parent = parent.parent - - actions.reverse() - return actions + return build_path(node) explored.append(node) explored_states.add((tuple(node.state.position), node.state.direction)) @@ -80,9 +98,16 @@ def graph_search(game_map: GameMap, start: PositionComponent, goal: tuple): # Get all possible states for state in get_states(node.state, game_map): sub_state = (tuple(state[1].position), state[1].direction) + new_node = Node(state=state[1], + parent=node, + action=state[0], + cost=(state[2] + node.cost)) + + priority = new_node.cost + heuristic(new_node, goal) if sub_state not in fringe_states and sub_state not in explored_states: - new_node = Node(state=state[1], - parent=node, - action=state[0]) - fringe.append(new_node) + fringe.put((priority, new_node)) + fringe_states.add((tuple(new_node.state.position), new_node.state.direction)) + elif sub_state in fringe_states and node.cost > new_node.cost: + fringe.get(node) + fringe.put((priority, new_node)) fringe_states.add((tuple(new_node.state.position), new_node.state.direction)) diff --git a/survival/quad_tree.py b/survival/quad_tree.py index 44a35ce..e36efd6 100644 --- a/survival/quad_tree.py +++ b/survival/quad_tree.py @@ -1,4 +1,3 @@ -import pygame from pygame.rect import Rect diff --git a/survival/systems/movement_system.py b/survival/systems/movement_system.py index 5f0d868..47a5ad1 100644 --- a/survival/systems/movement_system.py +++ b/survival/systems/movement_system.py @@ -21,14 +21,3 @@ class MovementSystem(esper.Processor): pos.position[1] - moving.target[1] * 32) < 0.1 * mov.speed: pos.position = [moving.target[0] * 32, moving.target[1] * 32] self.world.remove_component(ent, MovingComponent) - - # if moving.direction[0] != 0: - # pos.position[0] += moving.direction[0] * mov.speed * dt / 100 - # if abs(moving.movement_target[0] * 32 - pos.position[0]) < 0.1 * mov.speed: - # pos.position = [moving.movement_target[0] * 32, moving.movement_target[1] * 32] - # self.world.remove_component(ent, MovingComponent) - # else: - # pos.position[1] += moving.direction[1] * mov.speed * dt / 100 - # if abs(pos.position[1] - moving.movement_target[1] * 32) < 0.1 * mov.speed: - # pos.position = [moving.movement_target[0] * 32, moving.movement_target[1] * 32] - # self.world.remove_component(ent, MovingComponent) diff --git a/survival/systems/pathfinding_movement_system.py b/survival/systems/pathfinding_movement_system.py index 551abd4..c9d6078 100644 --- a/survival/systems/pathfinding_movement_system.py +++ b/survival/systems/pathfinding_movement_system.py @@ -5,7 +5,6 @@ from survival.components.moving_component import MovingComponent from survival.components.position_component import PositionComponent from survival.enums import Direction from survival.graph_search import graph_search, Action -from survival.pathfinding import breadth_first_search from survival.systems.input_system import PathfindingComponent diff --git a/survival/tile.py b/survival/tile.py index 590eb26..00aa7fc 100644 --- a/survival/tile.py +++ b/survival/tile.py @@ -1,9 +1,6 @@ -import random - - class Tile: - origins = [(0, 0), (32, 0), (64, 0), (96, 0)] - - def __init__(self, origin=(0, 0)): - self.origin = random.choice(Tile.origins) + def __init__(self, origin: tuple = (0, 0), cost: int = 1, biome=None): + self.origin = origin + self.cost = cost + self.biome = biome self.image = None diff --git a/survival/tile_layer.py b/survival/tile_layer.py index fa4aa6c..f53d1e2 100644 --- a/survival/tile_layer.py +++ b/survival/tile_layer.py @@ -1,3 +1,4 @@ +from survival.generators.tile_generator import TileGenerator from survival.image import Image from survival.tile import Tile @@ -6,7 +7,8 @@ class TileLayer: def __init__(self, width, height): self.width = width self.height = height - self.tiles = [[Tile() for x in range(self.width)] for y in range(self.height)] + self.tiles: list[list[Tile]] = TileGenerator.generate_biome_tiles(width, height) + # self.tiles: list[list[Tile]] = TileGenerator.generate_random_tiles(width, height) self.image = Image('atlas.png') def draw(self, camera, visible_area): @@ -18,3 +20,6 @@ class TileLayer: self.image.pos = (x * 32, y * 32) self.image.origin = self.tiles[y][x].origin camera.draw(self.image) + + def get_cost(self, pos): + return self.tiles[pos[1]][pos[0]].cost