diff --git a/global_config.py b/global_config.py new file mode 100644 index 0000000..19895d7 --- /dev/null +++ b/global_config.py @@ -0,0 +1,39 @@ +""" +global_config.py + +author: Caleb Scott + +Configuration file for global constants which are relevant for both +game scene and game logic. +""" + +# CONSTANTS --------------------------------------------------------- + +# Size of game board +BOARD_WIDTH = 30 +BOARD_HEIGHT = 20 + +# Object Types (applies to both tiles and game objects) +# Structure Layer +LOGGER = "Logger" +CROPS = "Crops" +HYDRO_POWER = "HydroPower" +HOUSING = "Housing" +MINE = "Mine" +FACTORY = "Factory" +JUNCTION = "Junction" +WOODSILO = "WoodSilo" +ROCKSILO = "RockSilo" +METALSILO = "MetalSilo" +CAPACITOR = "Capacitor" +WATER_TOWER = "WaterTower" + +# Foundation Layer +FOUNDATION = "Foundation" + +# Environment Layer +GROUND = "Ground" +WATER = "Water" +METAL = "Metal" +TREES = "Trees" +ROCKS = "Rocks" \ No newline at end of file diff --git a/lib/engine/game_board.py b/lib/engine/game_board.py new file mode 100644 index 0000000..a653819 --- /dev/null +++ b/lib/engine/game_board.py @@ -0,0 +1,61 @@ +""" +game_board.py + +author: Caleb Scott + +Representation of the game board/space. Ideally, this module can be +modified/swapped if the geometry of the game changes (2D-3D, etc.) +""" + +# CLASSES ----------------------------------------------------------- + +class FlatWorld: + + def __init__(self, init_space: list): + + # We assume that the initial space is a rectangular 2D array + # Initialize the world space (2D array) + self.space = init_space + self.width = len(init_space[0]) + self.height = len(init_space) + + def get_at(self, x, y): + """ + Returns object located at (x,y) coordinate of space. + """ + if (x >= 0 and x < self.width) and (y >= 0 and y < self.height): + return self.space[x][y] + else: + return None + + def set_at(self, x, y, object): + """ + Sets object located at (x,y) coordinate of space. + """ + if (x >= 0 and x < self.width) and (y >= 0 and y < self.height): + self.space[x][y] = object + return True + else: + return False + +class LayeredFlatWorld: + + def __init__(self, layers: dict): + + # We assume that layers is a dictionary of 2D arrays. + # Initialize the layers of 2D space + self.layers = {} + for layer in layers.keys(): + self.layers[layer] = FlatWorld(layers[layer]) + + def get_at(self, x, y, layer): + """ + Gets object at (x,y) coordinate at layer + """ + return self.layers[layer].get_at(x, y) + + def set_at(self, x, y, layer, object): + """ + Sets object located at (x,y) coordinate at layer + """ + self.layers[layer].set_at(x, y, object) diff --git a/lib/engine/game_config.py b/lib/engine/game_config.py index 781414d..e9f4e90 100644 --- a/lib/engine/game_config.py +++ b/lib/engine/game_config.py @@ -7,4 +7,21 @@ Configuration file for game objects (like setting default rates, types, etc.). This is not the same as scene_config.py, which configures the scene (visuals, sprites, etc.). """ -# CONSTANTS --------------------------------------------------------- \ No newline at end of file +# CONSTANTS --------------------------------------------------------- + +# Game Starting Resources +STARTING_RESOURCES = { + "people": 10, + "food": 200, + "metal": 50, + "wood": 100, + "rock": 100 +} + +# Resource types +RESOURCE_MANPOWER = "Manpower" +RESOURCE_WATER = "Water" +RESOURCE_METALS = "Metals" +RESOURCE_ROCKS = "Rocks" +RESOURCE_WOOD = "Wood" +RESOURCE_POWER = "Power" \ No newline at end of file diff --git a/lib/engine/game_logic.py b/lib/engine/game_logic.py index e82d361..1f60dfe 100644 --- a/lib/engine/game_logic.py +++ b/lib/engine/game_logic.py @@ -9,100 +9,108 @@ visual elements; this is handles in the scene/ python files # IMPORTS ----------------------------------------------------------- -import arcade -from lib.scene.scene_config import * -from lib.scene.scene_tiles import * +from lib.engine.game_config import * +from lib.engine.game_board import LayeredFlatWorld + +# FUNCTIONS --------------------------------------------------------- + +def init_gb_from_scene_info(scene_info: dict): + """ + Takes a dictionary of 2D arrays (organized by layer name), and + converts them into their proper game board of game objects. + """ + # CLASSES ----------------------------------------------------------- class GameLogic: - def __init__(self, scene, starting_resources): + def __init__(self, layers, starting_resources=STARTING_RESOURCES): # Initializes the game board (from arcade Scene) # and starting resources - self.scene = scene + self.game_board = LayeredFlatWorld(layers) self.resources = starting_resources - def place_structure(self, struct_type, x, y): - """ - Game logic for placing a structure, if valid. - """ - # First check and see if we already have a structure there. - s_tiles = arcade.get_sprites_at_point((x,y), self.scene[LAYER_STRUCTURES]) - if len(s_tiles) == 1: - return "Structure already in this space." + # def place_structure(self, struct_type, x, y): + # """ + # Game logic for placing a structure, if valid. + # """ + # # First check and see if we already have a structure there. + # s_tiles = arcade.get_sprites_at_point((x,y), self.scene[LAYER_STRUCTURES]) + # if len(s_tiles) == 1: + # return "Structure already in this space." - # Get the centered location of the tile chosen - # We use the environment layer instead to determine if placement - # is legal. - e_tiles = arcade.get_sprites_at_point((x,y), self.scene[LAYER_ENVIRONMENT]) - if len(e_tiles) == 1: - print(e_tiles[0].properties) - env_tile_type = e_tiles[0].properties['class'] - c_x, c_y = e_tiles[0].center_x, e_tiles[0].center_y + # # Get the centered location of the tile chosen + # # We use the environment layer instead to determine if placement + # # is legal. + # e_tiles = arcade.get_sprites_at_point((x,y), self.scene[LAYER_ENVIRONMENT]) + # if len(e_tiles) == 1: + # print(e_tiles[0].properties) + # env_tile_type = e_tiles[0].properties['class'] + # c_x, c_y = e_tiles[0].center_x, e_tiles[0].center_y - # Determine what sprite object to place - if struct_type == LOGGER: - if env_tile_type == TREES: - logger_sprite = LoggerTile(c_x, c_y) - self.scene.add_sprite(LAYER_STRUCTURES, logger_sprite) - return "" - else: - return "Logger must be placed on trees tile." - elif struct_type == CROPS: - if env_tile_type == GROUND: - crops_sprite = CropsTile(c_x, c_y) - self.scene.add_sprite(LAYER_STRUCTURES, crops_sprite) - return "" - else: - return "Crops must be placed on a ground tile." - elif struct_type == HYDROPOWER: - if env_tile_type == WATER: - hydro_sprite = HyroPowerTile(c_x, c_y) - self.scene.add_sprite(LAYER_STRUCTURES, hydro_sprite) - return "" - else: - return "Hydro Power must be placed on a water tile." - elif struct_type == HOUSING: - if env_tile_type == GROUND: - housing_sprite = HousingTile(c_x, c_y) - self.scene.add_sprite(LAYER_STRUCTURES, housing_sprite) - return "" - else: - return "Housing must be placed on a ground tile." - elif struct_type == MINER: - if env_tile_type == IRON: - miner_sprite = MinerTile(c_x, c_y) - self.scene.add_sprite(LAYER_STRUCTURES, miner_sprite) - return "" - else: - return "Miner must be placed on an iron tile." - elif struct_type == FACTORY: - if env_tile_type == GROUND: - factory_sprite = FactoryTile(c_x, c_y) - self.scene.add_sprite(LAYER_STRUCTURES, factory_sprite) - return "" - else: - return "Factory must be placed on a ground tile." - elif struct_type == JUNCTION: - if env_tile_type == GROUND: - junction_sprite = JunctionTile(c_x, c_y) - self.scene.add_sprite(LAYER_STRUCTURES, junction_sprite) - return "" - else: - return "Junctions must be placed on a ground tile." - else: - return "" + # # Determine what sprite object to place + # if struct_type == LOGGER: + # if env_tile_type == TREES: + # logger_sprite = LoggerTile(c_x, c_y) + # self.scene.add_sprite(LAYER_STRUCTURES, logger_sprite) + # return "" + # else: + # return "Logger must be placed on trees tile." + # elif struct_type == CROPS: + # if env_tile_type == GROUND: + # crops_sprite = CropsTile(c_x, c_y) + # self.scene.add_sprite(LAYER_STRUCTURES, crops_sprite) + # return "" + # else: + # return "Crops must be placed on a ground tile." + # elif struct_type == HYDROPOWER: + # if env_tile_type == WATER: + # hydro_sprite = HyroPowerTile(c_x, c_y) + # self.scene.add_sprite(LAYER_STRUCTURES, hydro_sprite) + # return "" + # else: + # return "Hydro Power must be placed on a water tile." + # elif struct_type == HOUSING: + # if env_tile_type == GROUND: + # housing_sprite = HousingTile(c_x, c_y) + # self.scene.add_sprite(LAYER_STRUCTURES, housing_sprite) + # return "" + # else: + # return "Housing must be placed on a ground tile." + # elif struct_type == MINER: + # if env_tile_type == IRON: + # miner_sprite = MinerTile(c_x, c_y) + # self.scene.add_sprite(LAYER_STRUCTURES, miner_sprite) + # return "" + # else: + # return "Miner must be placed on an iron tile." + # elif struct_type == FACTORY: + # if env_tile_type == GROUND: + # factory_sprite = FactoryTile(c_x, c_y) + # self.scene.add_sprite(LAYER_STRUCTURES, factory_sprite) + # return "" + # else: + # return "Factory must be placed on a ground tile." + # elif struct_type == JUNCTION: + # if env_tile_type == GROUND: + # junction_sprite = JunctionTile(c_x, c_y) + # self.scene.add_sprite(LAYER_STRUCTURES, junction_sprite) + # return "" + # else: + # return "Junctions must be placed on a ground tile." + # else: + # return "" - def delete_structure(self, x, y): - """ - Game logic for deleting a structure, if valid. - """ - s_tiles = arcade.get_sprites_at_point((x,y), self.scene[LAYER_STRUCTURES]) - if len(s_tiles) == 1: - struct_sprites = self.scene[LAYER_STRUCTURES] - struct_sprites.remove(s_tiles[0]) - return "" - else: - return "No structure on that space." \ No newline at end of file + # def delete_structure(self, x, y): + # """ + # Game logic for deleting a structure, if valid. + # """ + # s_tiles = arcade.get_sprites_at_point((x,y), self.scene[LAYER_STRUCTURES]) + # if len(s_tiles) == 1: + # struct_sprites = self.scene[LAYER_STRUCTURES] + # struct_sprites.remove(s_tiles[0]) + # return "" + # else: + # return "No structure on that space." \ No newline at end of file diff --git a/lib/engine/game_objects.py b/lib/engine/game_objects.py index bba965a..c59a435 100644 --- a/lib/engine/game_objects.py +++ b/lib/engine/game_objects.py @@ -12,14 +12,50 @@ between the game scene/rendering and game logic/objects. import math -# CONSTANTS --------------------------------------------------------- +from lib.engine.game_config import * +from global_config import * -MANPOWER = "Manpower" -WATER = "Water" -METALS = "Metals" -ROCKS = "Rocks" -WOOD = "Wood" -POWER = "Power" +# FUNCTIONS --------------------------------------------------------- + +def str_to_game_object(game_obj_str: str): + """ + Factory method: given a game object string + (usually from tileset arrays), return the corresponding object. + """ + if game_obj_str == JUNCTION: + return Junction() + elif game_obj_str == HOUSING: + return Housing() + elif game_obj_str == ROCKSILO: + return Silo(RESOURCE_ROCKS) + elif game_obj_str == METALSILO: + return Silo(RESOURCE_METALS) + elif game_obj_str == WOODSILO: + return Silo(RESOURCE_WOOD) + elif game_obj_str == WATER_TOWER: + return WaterTower() + elif game_obj_str == CAPACITOR: + return Capacitor() + elif game_obj_str == HYDRO_POWER: + return HydroPower() + elif game_obj_str == CROPS: + return Crops() + elif game_obj_str == FACTORY: + return Factory() + elif game_obj_str == FOUNDATION: + return Foundation() + elif game_obj_str == WATER: + return Water() + elif game_obj_str == TREES: + return Trees() + elif game_obj_str == GROUND: + return Ground() + elif game_obj_str == METAL: + return Metals() + elif game_obj_str == ROCKS: + return Rocks() + else: + return None # CLASSES ----------------------------------------------------------- @@ -55,17 +91,17 @@ class Junction(GameObject): class Housing(Storage): def __init__(self): - super().__init__("Housing", MANPOWER, 20.0) + super().__init__("Housing", RESOURCE_MANPOWER, 20.0) class WaterTower(Storage): def __init__(self): - super().__init__("WaterTower", WATER, 600.0) + super().__init__("WaterTower", RESOURCE_WATER, 600.0) class Capacitor(Storage): def __init__(self): - super().__init__("Capacitor", POWER, 100.0) + super().__init__("Capacitor", RESOURCE_POWER, 100.0) class Silo(Storage): @@ -94,6 +130,16 @@ class Quarry(Producer): def __init__(self): super().__init__("Quarry", 0.0) +class Crops(Producer): + + def __init__(self): + super().__init__("Crops", 0.0) + +class Factory(Producer): + + def __init__(self): + super().__init__("Factory", 0.0) + ## LAYER: Foundations class Foundation(GameObject): @@ -105,7 +151,7 @@ class Foundation(GameObject): class Water(Storage): def __init__(self): - super().__init__("Water", WATER, math.inf, math.inf) + super().__init__("Water", RESOURCE_WATER, math.inf, math.inf) class Ground(GameObject): @@ -115,14 +161,14 @@ class Ground(GameObject): class Trees(Storage): def __init__(self): - super().__init__("Trees", WOOD, 500.0, 500.0) + super().__init__("Trees", RESOURCE_WOOD, 500.0, 500.0) class Metals(Storage): def __init__(self): - super().__init__("Metals", METALS, 500.0, 500.0) + super().__init__("Metals", RESOURCE_METALS, 500.0, 500.0) class Rocks(Storage): def __init__(self): - super().__init__("Rocks", ROCKS, 500.0, 500.0) \ No newline at end of file + super().__init__("Rocks", RESOURCE_ROCKS, 500.0, 500.0) \ No newline at end of file diff --git a/lib/scene/scene_config.py b/lib/scene/scene_config.py index 943d64a..1ed0eaf 100644 --- a/lib/scene/scene_config.py +++ b/lib/scene/scene_config.py @@ -5,7 +5,7 @@ author: Caleb Scott Configuration file for modifying the visual scene of the game. This is not the same as engine/game_config.py, which is used to adjust -game objects (apart from their visuals, like sprites) +game objects (apart from their visuals) """ # CONSTANTS --------------------------------------------------------- @@ -18,7 +18,7 @@ SCREEN_WIDTH = 960 SCREEN_HEIGHT = 960 SCREEN_TITLE = "Miniopolis Demo" -# World size +# World size in pixels WORLD_WIDTH = 960 WORLD_HEIGHT = 640 @@ -41,30 +41,30 @@ JUNCTION_RES = RES + "connector.png" MAPS = "maps/" TEST_MAP = MAPS + "default-map.json" +# Tile property which gets its name as string +TILE_NAME = 'class' + # Layers for Map LAYER_STRUCTURES = "Structures" +LAYER_FOUNDATIONS = "Foundations" LAYER_ENVIRONMENT = "Environment" -# Tile Types -LOGGER = "LoggerTile" -CROPS = "CropsTile" -HYDROPOWER = "HydroPowerTile" -HOUSING = "HousingTile" -MINER = "MinerTile" -FACTORY = "FactoryTile" -JUNCTION = "JunctionTile" +# Layer ordering (bottom to top) +LAYERS = [ + LAYER_ENVIRONMENT, + LAYER_FOUNDATIONS, + LAYER_STRUCTURES +] -FOUNDATION = "FoundationTile" - -GROUND = "GroundTile" -WATER = "WaterTile" -METAL = "MetalTile" -TREES = "TreesTile" - -# Game Starting Resources -STARTING_RESOURCES = { - "metal": 50, - "wood": 100, - "people": 10, - "food": 200 +# Layer Options (for spatial hashing) +LAYER_OPTIONS = { + LAYER_ENVIRONMENT: { + "use_spatial_hash": True + }, + LAYER_FOUNDATIONS: { + "use_spatial_hash": True + }, + LAYER_STRUCTURES: { + "use_spatial_hash": True + } } \ No newline at end of file diff --git a/lib/scene/scene_logic.py b/lib/scene/scene_logic.py new file mode 100644 index 0000000..da89ae2 --- /dev/null +++ b/lib/scene/scene_logic.py @@ -0,0 +1,36 @@ +""" +scene_logic.py + +author: Caleb Scott + +Logic for interpreting the visual elements of the game scene. +This includes importing tilesets and passing them to the game logic module, +interpreting player inputs on the screen, etc. +""" + +# IMPORTS ----------------------------------------------------------- + +from arcade import Scene + +from scene_config import * +from global_config import * + +# FUNCTIONS --------------------------------------------------------- + +def info_from_layered_tilemap(scene: Scene): + """ + Takes tilemap from arcade scene, and initializes arrays of + tile information from the tile objects. + + This can then be passed to the game engine to convert these into + game objects + """ + scene_info = {} + for layer in LAYERS: + # Initialize empty game board (2D array) + scene_info[layer] = [[""] * BOARD_HEIGHT for _ in range(BOARD_WIDTH)] + + # Populate the layer info as 2D arrays + for i, S in enumerate(scene[layer]): + scene_info[layer][i//BOARD_WIDTH][i%BOARD_HEIGHT] = scene[layer].properties[TILE_NAME] + return scene_info \ No newline at end of file diff --git a/lib/scene/scene_tiles.py b/lib/scene/scene_tiles.py deleted file mode 100644 index b4ce7c7..0000000 --- a/lib/scene/scene_tiles.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -scene_tiles.py - -author: Caleb Scott - -Contains all classes of tiles used in miniopolis -""" - -# IMPORTS ----------------------------------------------------------- - -import arcade -from lib.scene.scene_config import * - -# CLASSES ----------------------------------------------------------- - -class HousingTile(arcade.Sprite): - - def __init__(self, x, y): - self.image_file_name = HOUSING_RES - super().__init__(self.image_file_name, TILE_SCALE) - - # Set custom properties - self.properties["type"] = HOUSING - self.center_x = x - self.center_y = y - -class LoggerTile(arcade.Sprite): - - def __init__(self, x, y): - self.image_file_name = LOGGER_RES - super().__init__(self.image_file_name, TILE_SCALE) - - # Set custom properties - self.properties["type"] = LOGGER - self.center_x = x - self.center_y = y - -class CropsTile(arcade.Sprite): - - def __init__(self, x, y): - self.image_file_name = CROPS_RES - super().__init__(self.image_file_name, TILE_SCALE) - - # Set custom properties - self.properties["type"] = CROPS - self.center_x = x - self.center_y = y - -class HyroPowerTile(arcade.Sprite): - - def __init__(self, x, y): - self.image_file_name = HYDRO_RES - super().__init__(self.image_file_name, TILE_SCALE) - - # Set custom properties - self.properties["type"] = HYDROPOWER - self.center_x = x - self.center_y = y - -class MinerTile(arcade.Sprite): - - def __init__(self, x, y): - self.image_file_name = MINING_RES - super().__init__(self.image_file_name, TILE_SCALE) - - # Set custom properties - self.properties["type"] = MINER - self.center_x = x - self.center_y = y - -class FactoryTile(arcade.Sprite): - - def __init__(self, x, y): - self.image_file_name = FACTORY_RES - super().__init__(self.image_file_name, TILE_SCALE) - - # Set custom properties - self.properties["type"] = FACTORY - self.center_x = x - self.center_y = y - -class JunctionTile(arcade.Sprite): - - def __init__(self, x, y): - self.image_file_name = JUNCTION_RES - super().__init__(self.image_file_name, TILE_SCALE) - - # Set custom properties - self.properties["type"] = JUNCTION - self.center_x = x - self.center_y = y \ No newline at end of file diff --git a/miniopolis.py b/miniopolis.py index 7fd0e34..1bf8cf4 100644 --- a/miniopolis.py +++ b/miniopolis.py @@ -11,7 +11,6 @@ First attempt to make a tile-based colony-sim game. import arcade from lib.scene.scene_config import * -from lib.engine.game_logic import GameLogic # CLASSES ----------------------------------------------------------- @@ -28,22 +27,16 @@ class GameBoard(arcade.Window): Used for starting up the game board """ # Obtain world tilemap - # Layer options come from https://api.arcade.academy/en/platformer_tutorial_revamp/tutorials/platform_tutorial/step_12.html - layer_options = { - LAYER_ENVIRONMENT: { - "use_spatial_hash": True - }, - LAYER_STRUCTURES: { - "use_spatial_hash": True - } - } self.tile_map = arcade.load_tilemap( TEST_MAP, scaling=TILE_SCALE, - layer_options=layer_options + layer_options=LAYER_OPTIONS ) self.scene = arcade.Scene.from_tilemap(self.tile_map) + for layer in self.scene.keys(): + print(layer) + # Tiles selection self.selected_struct_tile = "[Nothing]" self.selected_env_tile = GROUND @@ -84,7 +77,7 @@ class GameBoard(arcade.Window): ) # Initialize the Game Logic class - self.game_logic = GameLogic(self.scene, STARTING_RESOURCES) + # self.game_logic = GameLogic(self.scene, None) def on_key_release(self, symbol, modifiers): if symbol == arcade.key.ESCAPE: