From a6a6f4b7288e3f7359c2df6dc819457f9332921b Mon Sep 17 00:00:00 2001 From: Ben Ryder Date: Sat, 22 Feb 2020 21:25:18 +0000 Subject: [PATCH 1/6] GH-8: Cleaning excess path seperators from path constants. --- paths.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/paths.py b/paths.py index 7e1f153..912072d 100644 --- a/paths.py +++ b/paths.py @@ -5,17 +5,17 @@ dataPath = os.getcwd() + os.sep + "data" + os.sep assetPath = os.getcwd() + os.sep + "assets" + os.sep -gamePath = dataPath + os.sep + "saved" + os.sep -mapPath = dataPath + os.sep + "maps" + os.sep +gamePath = dataPath + "saved" + os.sep +mapPath = dataPath + "maps" + os.sep -fontPath = assetPath + os.sep + "fonts" + os.sep +fontPath = assetPath + "fonts" + os.sep imagePath = assetPath + "images" + os.sep -tilePath = imagePath + os.sep + "tiles" + os.sep -unitPath = imagePath + os.sep + "units" + os.sep +tilePath = imagePath + "tiles" + os.sep +unitPath = imagePath + "units" + os.sep -uiPath = imagePath + os.sep + "UI" + os.sep # general, throughout sections. -uiMenuPath = imagePath + os.sep + "menu" + os.sep -uiGamePath = imagePath + os.sep + "game" + os.sep +uiPath = imagePath + "UI" + os.sep # general, throughout sections. +uiMenuPath = imagePath + "menu" + os.sep +uiGamePath = imagePath + "game" + os.sep From f5e22097b6fb0c29c79274233d7020fd034ec291 Mon Sep 17 00:00:00 2001 From: Ben Ryder Date: Wed, 18 Mar 2020 22:09:18 +0000 Subject: [PATCH 2/6] Adding project wiki as git submodule. --- .gitmodules | 3 +++ docs | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 docs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..bd54953 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "docs"] + path = docs + url = https://github.com/Ben-Ryder/Conqueror-of-Empires.wiki.git diff --git a/docs b/docs new file mode 160000 index 0000000..48926a6 --- /dev/null +++ b/docs @@ -0,0 +1 @@ +Subproject commit 48926a652f3b8f38d57c2473b7dd7a8c2a49c252 From 776740b72f8a862146519583e6d4f266f7161224 Mon Sep 17 00:00:00 2001 From: Ben Ryder Date: Sat, 2 May 2020 23:47:33 +0100 Subject: [PATCH 3/6] GH-5: Refactored project to use JSON save format. This includes how data is loaded, used and saved. --- project/data.py | 35 ++-- project/game/controller.py | 8 +- project/game/gui.py | 41 ++--- project/game/model.py | 318 +++++++++++++++++++++++-------------- project/game/new.py | 109 ++++++++++++- project/game/view.py | 54 ++++--- project/menus/loadgame.py | 6 +- 7 files changed, 390 insertions(+), 181 deletions(-) diff --git a/project/data.py b/project/data.py index 1a7ff31..f260030 100644 --- a/project/data.py +++ b/project/data.py @@ -1,24 +1,41 @@ # Ben-Ryder 2019 -# Currently using PICKLE as method of file save +# Currently using JSON as method of file save -import pickle +import json import os +import constants -def save(game, filename): - with open(filename, "wb") as file: - pickle.dump(game, file) + +def save(data, filename): + with open(filename + ".json", "w") as file: + json.dump(data, file, indent=2) def load(filename): - with open(filename, "rb") as file: - game = pickle.load(file) - return game + with open(filename + ".json", "r") as file: + data = json.load(file) + return data def delete(filename): - os.remove(filename) + os.remove(filename + ".json") def check_exists(filename): return os.path.isfile(filename) + + +def load_map_format(map_file): + with open(map_file, "r") as file: + grid = file.read().split("\n") + grid = [i.replace(",", "") for i in grid] + + # Converting for referencing as [row][col] as split by "/n" gives [col][row] + new_grid = [] + for row in range(constants.MAP_SIZE[0]): + new_grid.append([]) + for col in grid: + new_grid[len(new_grid) - 1].append(col[row]) + + return new_grid diff --git a/project/game/controller.py b/project/game/controller.py index a887d49..c1e28d0 100644 --- a/project/game/controller.py +++ b/project/game/controller.py @@ -2,6 +2,7 @@ import pygame +import project.game.model as model import project.game.gui as gui import project.menus.leaderboard as leaderboard import project.data as data @@ -15,8 +16,9 @@ def __init__(self, display, game_reference): self.display = display self.game_reference = game_reference - # General Game Setup - self.game_model = data.load(paths.gamePath + self.game_reference) + # Game Model Setup + save_data = data.load(paths.gamePath + self.game_reference) + self.game_model = model.Model(save_data) # View + GUI Setup self.GUI = gui.GameGui(self, self.display, self.game_model) @@ -39,7 +41,7 @@ def quit(self): self.save() def save(self): - data.save(self.game_model, paths.gamePath + self.game_reference) + data.save(self.game_model.get_save_data(), paths.gamePath + self.game_reference) def get_state(self): return self.state diff --git a/project/game/gui.py b/project/game/gui.py index 3764025..e6cdbcc 100644 --- a/project/game/gui.py +++ b/project/game/gui.py @@ -56,7 +56,7 @@ def __init__(self, control, display, model): self.passive_guis = [] # acts as normal list # Welcome Message - if self.model_link.current_player.get_turn() == 0: + if self.model_link.get_current_player().get_turn() == 0: self.launch_welcome_message() def run(self): @@ -171,8 +171,9 @@ def launch_welcome_message(self): self.persistent_guis.append(WelcomeMessage(self)) def next_turn_message(self): + next_player = self.model_link.get_player(self.model_link.get_next_player()) self.persistent_guis.append(NextTurnMessage(self, "Next Turn", - ["Get ready %s!" % self.model_link.get_next_player().get_name(), + ["Get ready %s!" % next_player.get_name(), "It's your turn up next."])) def game_over_message(self): @@ -199,13 +200,13 @@ def next_turn(self): self.save() def save(self): - self.model_link.current_player.set_camera_focus(self.camera.get_position()) + self.model_link.get_current_player().set_camera_focus(self.camera.get_position()) self.control_link.save() def update_camera_focus(self): - if self.model_link.current_player.get_camera_focus() != [None, None]: # not set until end of first turn. + if self.model_link.get_current_player().get_camera_focus() != [None, None]: # not set until end of first turn. # TODO: set initial camera_focus on settlement assignment, so this isn't needed. - self.camera.set_position(self.model_link.current_player.get_camera_focus()) + self.camera.set_position(self.model_link.get_current_player().get_camera_focus()) def end_game(self): # natural end, with a winner not quit. self.state = "ended" @@ -341,7 +342,7 @@ def check_clicked(self, mouse_x, mouse_y): def draw(self, display): self.background_panel.draw(display) - pygame.draw.ellipse(display, constants.COLOURS[self.model_link.current_player.get_colour()], + pygame.draw.ellipse(display, constants.COLOURS[self.model_link.get_current_player().get_colour()], [self.x + 10, self.y + 5, 15, 15]) self.city_name_text.draw(display) @@ -784,19 +785,20 @@ def __init__(self, model): self.player_values_panel.rect[0] + 180, 2) def update_player(self): - self.current_player_name_text.change_text(self.model_link.current_player.get_name() + "'s turn") + current_player = self.model_link.get_current_player() + self.current_player_name_text.change_text(current_player.get_name() + "'s turn") self.topleft_panel.reset_width(self.name_padding + self.current_player_name_text.get_rect()[2]) - self.current_turn_text.change_text(str(self.model_link.current_player.get_turn())) + self.current_turn_text.change_text(str(current_player.get_turn())) - self.update_player_score() - self.update_player_ap() + self.update_player_score(current_player) + self.update_player_ap(current_player) - def update_player_score(self): - self.current_score_text.change_text("{:,}".format(self.model_link.current_player.get_score())) + def update_player_score(self, player): + self.current_score_text.change_text("{:,}".format(player.get_score())) - def update_player_ap(self): - ap_gain = " (+" + str(self.model_link.current_player.get_turn_ap()) + ")" - self.current_ap_text.change_text(str(self.model_link.current_player.get_ap()) + ap_gain) + def update_player_ap(self, player): + ap_gain = " (+" + str(player.get_turn_ap()) + ")" + self.current_ap_text.change_text(str(player.get_ap()) + ap_gain) def draw(self, display): self.topleft_panel.draw(display) @@ -933,7 +935,8 @@ def __init__(self, model): rect = [x + self.padding, y + self.padding, self.tile_size - self.padding*2, self.tile_size - self.padding*2] if tile.current_holder is not None: - colour = tile.current_holder.get_colour() + player = self.model_link.get_player(tile.current_holder) + colour = player.get_colour() self.tiles.append(MiniMapTile(rect, constants.COLOURS[colour])) else: self.tiles.append(MiniMapTile(rect, (200, 200, 200))) @@ -957,15 +960,15 @@ def refresh(self): self.__init__(self.model_link) def is_visible(self): - return self.model_link.current_player.get_minimap_status() + return self.model_link.get_current_player().get_minimap_status() def handle_click(self): if self.is_visible(): if self.hide_button.check_clicked(): - self.model_link.current_player.set_minimap_status(False) + self.model_link.get_current_player().set_minimap_status(False) else: if self.show_button.check_clicked(): - self.model_link.current_player.set_minimap_status(True) + self.model_link.get_current_player().set_minimap_status(True) def draw(self, display): if self.is_visible(): diff --git a/project/game/model.py b/project/game/model.py index 299741a..5d36261 100644 --- a/project/game/model.py +++ b/project/game/model.py @@ -9,48 +9,69 @@ class Model: """ holds all the data and interface to manipulate the game """ - def __init__(self, game_name, map_name, players): # only when creating new game - self.game_name = game_name - self.map_name = map_name - self.game_end = False + def __init__(self, save_data): + self.game_name = save_data["game_name"] + self.map_name = save_data["map_name"] + self.game_end = save_data["game_end"] - self.players = [Player(p["name"], p["colour"]) for p in players] - self.current_player = self.players[0] + self.players = [Player(self, player_data) for player_data in save_data["players"]] - self.world = World(self.map_name, self.players) # assigns settlements to players + self.world = World(self, save_data["world"]) # assigns settlements to players - self.current_player.start_turn() + # If the game is new set the current player, else just load it in. + if save_data["current_player"] is None: + self.current_player_name = self.players[0].get_name() + self.get_current_player().start_turn() + else: + self.current_player_name = save_data["current_player"] + + def get_save_data(self): + return { + "game_name": self.game_name, + "map_name": self.map_name, + "game_end": self.game_end, + "current_player": self.current_player_name, # store name, player data is stored in "players" + "players": [player.get_save_data() for player in self.players], + "world": self.world.get_save_data() + } + + def get_player(self, name): + for player in self.players: + if player.get_name() == name: + return player + return None def all_units(self): return [unit for player in self.players for unit in player.units] def next_turn(self): - self.current_player.end_turn() + self.get_current_player().end_turn() # Getting new turn if not self.is_winner(): # if more than 1 player alive - self.current_player = self.get_next_player() + self.current_player_name = self.get_next_player() else: self.game_end = True - self.current_player.start_turn() + self.get_current_player().start_turn() def get_next_player(self): valid_choice = False - player = self.current_player + player = self.get_current_player() while not valid_choice: # wont be infinite, as to be called at least two players are left. if self.players.index(player) < len(self.players) - 1: player = self.players[self.players.index(player) + 1] else: player = self.players[0] valid_choice = not player.is_dead() - return player + return player.get_name() def try_spawn(self, unit_type, position): if not self.get_unit(position): - if self.current_player.get_ap() - constants.UNIT_SPECS[unit_type]["spawn_cost"] >= 0: - self.current_player.add_unit(Unit(unit_type, position, self.get_current_player())) - self.current_player.take_ap(constants.UNIT_SPECS[unit_type]["spawn_cost"]) + current_player = self.get_current_player() + if current_player.get_ap() - constants.UNIT_SPECS[unit_type]["spawn_cost"] >= 0: + current_player.add_unit(Unit(unit_type, position, current_player.get_name())) + current_player.take_ap(constants.UNIT_SPECS[unit_type]["spawn_cost"]) return True return False @@ -58,22 +79,23 @@ def make_attack(self, attacker, defender): attacker.set_attacked() killed_units = calculations.apply_attack(attacker, defender) for unit in killed_units: # could be both units - unit.owner.units.remove(unit) + self.get_player(unit.owner).units.remove(unit) def check_conquer(self, unit): if (self.world.get_tile(unit.position).get_type() == "c" and - self.world.get_tile(unit.position).get_holder() != self.get_current_player()): + self.world.get_tile(unit.position).get_holder() != self.get_current_player().get_name()): if not unit.has_moved(): # must stay in settlement for a turn cycle return True return False def conquer(self, position): settlement = self.world.get_tile(position) + if settlement.current_holder is not None: - settlement.current_holder.remove_settlement(settlement) + self.get_player(settlement.current_holder).remove_settlement(settlement.get_position()) - settlement.change_holder(self.get_current_player()) - self.current_player.add_settlement(settlement) + settlement.change_holder(self.get_current_player().get_name()) + self.get_current_player().add_settlement(settlement.get_position()) def handle_death(self): for player in self.players: @@ -96,10 +118,10 @@ def is_winner(self): return len([player for player in self.players if not player.is_dead()]) == 1 def get_winner(self): - return self.current_player.get_name() # will always end on current player, as they take last city. + return self.get_current_player().get_name() # will always end on current player, as they take last city. def get_current_player(self): - return self.current_player + return self.get_player(self.current_player_name) def unit_selected(self, position): for unit in self.all_units(): @@ -115,7 +137,7 @@ def get_unit(self, position): def settlement_selected(self, position): tile = self.world.get_tile(position) - if tile.get_type() == "c" and tile.get_holder() == self.get_current_player(): + if tile.get_type() == "c" and tile.get_holder() == self.current_player_name: return True return False @@ -141,39 +163,47 @@ def get_attacks(self, unit): for x in range(unit.position[0] - unit.reach, unit.position[0] + unit.reach + 1): for y in range(unit.position[1] - unit.reach, unit.position[1] + unit.reach + 1): if [x, y] != unit.position and self.get_unit([x, y]): - if self.get_unit([x, y]).owner != self.get_current_player(): + if self.get_unit([x, y]).owner != self.current_player_name: attacks.append([x, y]) return attacks class Player: """ Each player of the game, which holds their units, key values and links to settlements etc""" - def __init__(self, name, colour): - self.name = name - self.colour = colour - self.camera_focus = [None, None] # TODO: system to auto-scroll to spawn - self.show_minimap = False - - self.units = [] - self.settlements = [] - - self.turn = 0 - self.ap = 3 # initial ap, not per turn. (first turn ap = self.ap + self.get_turn_ap() - self.dead = False - self.max_score = self.ap - - # self.wood = 0 - # self.stone = 0 - # self.metal = 0 - - # def add_wood(self, amount): - # self.wood += amount - # - # def add_stone(self, amount): - # self.stone += amount - # - # def add_metal(self, amount): - # self.metal += amount + def __init__(self, model_link, saved_data): + self.model_link = model_link + + self.name = saved_data["name"] + self.colour = saved_data["colour"] + self.camera_focus = saved_data["camera_focus"] + self.show_minimap = saved_data["show_minimap"] + + # Units have a shared interface for creating and loading interface, so here we satisfy the creation interface + # with the first 3 parameters, then p[ass all the data which overrules the parameters passed. + self.units = [Unit(unit_data["type"], unit_data["position"], unit_data["owner"], unit_data) for unit_data in saved_data["units"]] + + self.settlements = [settlement_position for settlement_position in saved_data["settlements"]] + + self.turn = saved_data["turn"] + self.ap = saved_data["ap"] + self.dead = saved_data["dead"] + self.max_score = saved_data["max_score"] + + def get_save_data(self): + return { + "name": self.get_name(), + "colour": self.get_colour(), + "camera_focus": self.get_camera_focus(), + "show_minimap": self.get_minimap_status(), + + "units": [unit.get_save_data() for unit in self.units], + "settlements": [settlement for settlement in self.settlements], + + "turn": self.get_turn(), + "ap": self.get_ap(), + "dead": self.is_dead(), + "max_score": self.get_max_score(), + } def get_name(self): return self.name @@ -183,6 +213,7 @@ def is_dead(self): def kill(self): self.dead = True + self.units.clear() def get_colour(self): return self.colour @@ -194,7 +225,8 @@ def get_score(self): # score workout = Each city's score + turn*5 + each unit's health. score = 0 score += self.turn * 5 - for city in self.settlements: + for city_position in self.settlements: + city = self.model_link.world.get_tile(city_position) score += city.get_score() for unit in self.units: @@ -212,7 +244,8 @@ def get_ap(self): def get_turn_ap(self): ap = 0 - for city in self.settlements: + for city_position in self.settlements: + city = self.model_link.world.get_tile(city_position) ap += city.get_ap_value() # changed from generate_ap return ap @@ -229,11 +262,11 @@ def start_turn(self): def end_turn(self): self.turn += 1 - def add_settlement(self, reference): - self.settlements.append(reference) + def add_settlement(self, city_position): + self.settlements.append(city_position) - def remove_settlement(self, reference): - self.settlements.remove(reference) + def remove_settlement(self, city_position): + self.settlements.remove(city_position) def add_unit(self, unit): self.units.append(unit) @@ -255,11 +288,17 @@ def set_minimap_status(self, show): class Tile: - def __init__(self, tile_type, position): - self.type = tile_type - self.position = position + def __init__(self, save_data): + self.type = save_data["type"] + self.position = save_data["position"] # self.wood, self.stone, self.metal = constants.TILE_DATA[tile_type] + def get_save_data(self): + return { + "type": self.type, + "position": self.position + } + def get_type(self): return self.type @@ -292,14 +331,33 @@ def get_position(self): class City: - def __init__(self, name, position): - self.type = "c" - self.name = name - self.position = position - self.current_holder = None - self.level = 1 # score and ap generated from level - self.sub_level = 0 # SUB LEVEL STARTS FROM 0 - self.max_level = len(constants.LEVELS) + def __init__(self, model_link, save_data): + self.model_link = model_link + + self.type = save_data["type"] + self.name = save_data["name"] + self.position = save_data["position"] + self.current_holder = save_data["current_holder"] + self.level = save_data["level"] + self.sub_level = save_data["sub_level"] + self.max_level = save_data["max_level"] + + def get_save_data(self): + save_data = { + "type": self.type, + "name": self.name, + "position": self.position, + + "level": self.level, + "sub_level": self.sub_level, + "max_level": self.max_level, + } + if self.current_holder is not None: + save_data["current_holder"] = self.current_holder + else: + save_data["current_holder"] = None + + return save_data def get_name(self): return self.name @@ -315,7 +373,8 @@ def get_type(self): def get_holder_colour(self): if self.current_holder is not None: - return self.current_holder.get_colour() + current_holder = self.model_link.get_player(self.current_holder) + return current_holder.get_colour() return None def get_ap_value(self, level=None): @@ -333,7 +392,9 @@ def add_level(self): # not to be used directly, add level via add sub_level. self.level += 1 def add_sub_level(self): - self.current_holder.take_ap(self.get_upgrade_cost()) + current_holder = self.model_link.get_player(self.current_holder) + + current_holder.take_ap(self.get_upgrade_cost()) self.sub_level += 1 if not self.at_max(): if self.sub_level == len(constants.LEVELS[self.level - 1]): @@ -344,7 +405,9 @@ def get_upgrade_cost(self): return constants.LEVELS[self.level - 1][self.sub_level] def afford_upgrade(self): - if self.current_holder.get_ap() - self.get_upgrade_cost() >= 0: + current_holder = self.model_link.get_player(self.current_holder) + + if current_holder.get_ap() - self.get_upgrade_cost() >= 0: return True return False @@ -373,25 +436,27 @@ def get_world(map_name): class World: """ holds all the map tiles, be that a Tile or City, in a 2d-array """ - def __init__(self, map_name, players): # __init__ creates new world - self.format = get_world(map_name) + def __init__(self, model_link, save_data): # __init__ creates new world or loads from save_data + self.model_link = model_link - self.city_names = CityPicker() + self.format = save_data["format"] - # Make Tiles + # Load Tiles self.tiles = [] - for row in range(len(self.format[0])): # assumes col 0, is same len as all others. + for row in save_data["tiles"]: # assumes col 0, is same len as all others. self.tiles.append([]) - for col in range(len(self.format)): - if self.format[row][col] == "c": - name = self.city_names.get_new() - self.tiles[-1].append(City(name, [row, col])) + for tile_data in row: + if tile_data["type"] == "c": + self.tiles[-1].append(City(self.model_link, tile_data)) else: - self.tiles[-1].append(Tile(self.format[row][col], [row, col])) + self.tiles[-1].append(Tile(tile_data)) # tile has no additional save data - # Set player spawns - self.set_spawns(players) + def get_save_data(self): + return { + "format": self.format, + "tiles": [[tile.get_save_data() for tile in row] for row in self.tiles] + } def get_tile(self, position): return self.tiles[position[0]][position[1]] @@ -399,50 +464,69 @@ def get_tile(self, position): def get_format(self): return self.format - def set_spawns(self, players): # only time world needs player knowledge, no link made. - spawn_choices = [tile for row in self.tiles for tile in row if tile.type == "c"] - for player in players: - city = random.choice(spawn_choices) - spawn_choices.remove(city) - # There is a two way relationship, so both must know of each other. - player.add_settlement(city) - city.change_holder(player) +class Unit: + def __init__(self, unit_type, position, owner, save_data=None): + # UNits might be made during the game, or be loaded in. + if save_data is None: + self.type = unit_type + self.position = position + # Unit Specs + self.max_health = constants.UNIT_SPECS[unit_type]["max_health"] + self.health = self.max_health + self.attack = constants.UNIT_SPECS[unit_type]["attack"] + self.defence = constants.UNIT_SPECS[unit_type]["defence"] + self.movement = constants.UNIT_SPECS[unit_type]["movement"] + self.reach = constants.UNIT_SPECS[unit_type]["reach"] -class CityPicker: - """ used to randomly assign names to cities """ - def __init__(self): - # Load Name Choices - with open(paths.dataPath + "city_names") as file: - self.name_choices = file.read().split("\n") + self.allowed_moves = constants.UNIT_SPECS[unit_type]["moves"] - def get_new(self): - choice = random.choice(self.name_choices) - self.name_choices.remove(choice) - return choice + # all set to True, so unit cannot act when it is spawned, must wait till next go (EFFECTIVELY BLOCKS SPAWN) + self.moved = True + self.attacked = True + self.owner = owner # TODO: getters for attributes? -class Unit: - def __init__(self, unit_type, position, owner): - self.type = unit_type - self.position = position + else: + self.type = save_data["type"] + self.position = save_data["position"] - # Unit Specs - self.max_health = constants.UNIT_SPECS[unit_type]["max_health"] - self.health = self.max_health - self.attack = constants.UNIT_SPECS[unit_type]["attack"] - self.defence = constants.UNIT_SPECS[unit_type]["defence"] - self.movement = constants.UNIT_SPECS[unit_type]["movement"] - self.reach = constants.UNIT_SPECS[unit_type]["reach"] + # Unit Specs + self.max_health = save_data["max_health"] + self.health = save_data["health"] + self.attack = save_data["attack"] + self.defence = save_data["defence"] + self.movement = save_data["movement"] + self.reach = save_data["reach"] - self.allowed_moves = constants.UNIT_SPECS[unit_type]["moves"] + self.allowed_moves = save_data["allowed_moves"] - # all set to True, so unit cannot act when it is spawned, must wait till next go (EFFECTIVELY BLOCKS SPAWN) - self.moved = True - self.attacked = True + # all set to True, so unit cannot act when it is spawned, must wait till next go (EFFECTIVELY BLOCKS SPAWN) + self.moved = save_data["moved"] + self.attacked = save_data["attacked"] + + self.owner = owner # TODO: getters for attributes? + + def get_save_data(self): + return { + "type": self.type, + "position": self.position, + + "max_health": self.max_health, + "health": self.health, + "attack": self.attack, + "defence": self.defence, + "movement": self.movement, + "reach": self.reach, + + "allowed_moves": self.allowed_moves, + + "moved": self.moved, + "attacked": self.attacked, - self.owner = owner # TODO: getters for attributes? + "owner": self.owner, + } def move(self, position): self.position = position diff --git a/project/game/new.py b/project/game/new.py index 8a5b6a2..fd55efb 100644 --- a/project/game/new.py +++ b/project/game/new.py @@ -1,18 +1,115 @@ # Ben-Ryder 2019 import os +import random -import project.game.model as model -import project.data as data +import constants import paths +import project.data as data + + +# Make a new game, by adding the base data to the json save format. +def make(game_name, map_name, players): + # Generating Game Data + game_data = { + "game_name": game_name, + "map_name": map_name, + "game_end": False, + "current_player": None, # will be generated on load. + "players": [get_player_data(player["name"], player["colour"]) for player in players], + "world": get_world_data(map_name) + } -def make(game_name, map_name, players): - """ where players = [[name, colour], [name,colour]...]""" - game = model.Model(game_name, map_name, players) # creates "blank" game + game_data = assign_spawns(game_data) # Creating 'saved' directory if it doesn't exist. if not os.path.exists(paths.gamePath): os.mkdir(paths.gamePath) - data.save(game, paths.gamePath + game_name) + data.save(game_data, paths.gamePath + game_name) + + +def get_player_data(player_name, player_colour): + return { + "name": player_name, + "colour": player_colour, + "camera_focus": [None, None], # will be generated on load. + "show_minimap": True, + + "units": [], + "settlements": [], + + "turn": 0, + "ap": 0, + "dead": False, + "max_score": 0 + } + + +def get_world_data(map_name): + world_data = { + "format": data.load_map_format(paths.mapPath + map_name + ".csv"), + } + world_data["tiles"] = get_world_tiles_data(world_data["format"]) + + return world_data + + +class CityPicker: + """ used to randomly assign names to cities """ + def __init__(self): + # Load Name Choices + with open(paths.dataPath + "city_names") as file: + self.name_choices = file.read().split("\n") + + def get_new(self): + choice = random.choice(self.name_choices) + self.name_choices.remove(choice) + return choice + + +def get_world_tiles_data(map_format): + + city_names = CityPicker() # A small wrapper around the city_names file allowing the selection of unique names. + + # Make Tiles + tiles = [] + for row in range(len(map_format[0])): # assumes col 0, is same len as all others! + tiles.append([]) + + for col in range(len(map_format)): + tile_type = map_format[row][col] + tile_position = [row, col] + + if tile_type == "c": + tiles[-1].append({ + "type": tile_type, + "position": tile_position, + + "name": city_names.get_new(), + "current_holder": None, + "level": 1, + "sub_level": 0, + "max_level": len(constants.LEVELS), + }) + else: + tiles[-1].append({ + "type": tile_type, + "position": tile_position + }) + + return tiles + + +def assign_spawns(game_data): + spawn_choices = [tile["position"] for row in game_data["world"]["tiles"] for tile in row if tile["type"] == "c"] + for player in game_data["players"]: + city_position = random.choice(spawn_choices) + spawn_choices.remove(city_position) + + # There is a two way relationship, so both must know of each other. + player["settlements"].append(city_position) + game_data["world"]["tiles"][city_position[0]][city_position[1]]["current_holder"] = player["name"] + + return game_data diff --git a/project/game/view.py b/project/game/view.py index ae6f955..4d1f876 100644 --- a/project/game/view.py +++ b/project/game/view.py @@ -16,7 +16,7 @@ class PhysicalGame: """ heavily linked to the GUI, but responsible for map-level interactions. """ def __init__(self, display, model, GUI): self.display = display - self.model = model + self.model_link = model self.GUI = GUI # Background @@ -27,7 +27,7 @@ def __init__(self, display, model, GUI): self.game_surface.main_surface.set_colorkey(constants.COLOURS["black"]) # so stars drawn to display first are seen # Map Setup - self.world = VisualWorld(self.model) + self.world = VisualWorld(self.model_link) # Focuses self.tile_focus = None # record of the current tile clicked @@ -83,9 +83,9 @@ def map_clicked(self, mouse_x, mouse_y): return False def handled_unit_click(self): - if self.model.unit_selected(self.tile_focus): - if self.model.get_unit(self.tile_focus).owner == self.model.get_current_player(): - self.active_unit = self.model.get_unit(self.tile_focus) + if self.model_link.unit_selected(self.tile_focus): + if self.model_link.get_unit(self.tile_focus).owner == self.model_link.current_player_name: + self.active_unit = self.model_link.get_unit(self.tile_focus) return True return False @@ -101,20 +101,20 @@ def handle_unit_actions(self): def handle_conquer(self): conquered = False - if self.model.check_conquer(self.active_unit) and self.tile_focus == self.active_unit.position: - self.model.conquer(self.active_unit.position) + if self.model_link.check_conquer(self.active_unit) and self.tile_focus == self.active_unit.position: + self.model_link.conquer(self.active_unit.position) self.active_unit.make_inactive() conquered = True # Death Checking - might have destroyed players last city - killed_player = self.model.handle_death() + killed_player = self.model_link.handle_death() if killed_player is not None: self.GUI.send_message("Destroyed!", ["Unlucky %s " % killed_player, "You have been destroyed and are", "out of the game."]) self.GUI.send_message("Conquered!", ["You have successfully gained control", - "of %s" % self.model.world.get_tile(self.tile_focus).get_name()]) + "of %s" % self.model_link.world.get_tile(self.tile_focus).get_name()]) self.GUI.player_tracker.update_player() self.GUI.mini_map.refresh() self.world.get_tile(self.tile_focus).update_owner() @@ -123,22 +123,22 @@ def handle_conquer(self): def handle_movement(self): moved = False - if self.tile_focus in self.model.get_moves(self.active_unit): - self.model.move_unit(self.tile_focus, self.active_unit) + if self.tile_focus in self.model_link.get_moves(self.active_unit): + self.model_link.move_unit(self.tile_focus, self.active_unit) self.GUI.player_tracker.update_player() # ap and score will have changed return moved def handle_attacking(self): attacked = False - if self.tile_focus in self.model.get_attacks(self.active_unit): - defending_unit = self.model.get_unit(self.tile_focus) - self.model.make_attack(self.active_unit, defending_unit) + if self.tile_focus in self.model_link.get_attacks(self.active_unit): + defending_unit = self.model_link.get_unit(self.tile_focus) + self.model_link.make_attack(self.active_unit, defending_unit) self.GUI.player_tracker.update_player() # ap and score will have changed return attacked def handled_settlement_click(self): - if self.model.settlement_selected(self.tile_focus): + if self.model_link.settlement_selected(self.tile_focus): self.GUI.launch_settlement_menu(self.tile_focus, pygame.mouse.get_pos()) return True return False @@ -158,14 +158,14 @@ def draw(self, display): self.game_surface.draw(display) def draw_units(self): - for unit in self.model.all_units(): + for unit in self.model_link.all_units(): x, y = isometric.get_iso(unit.position[0], unit.position[1], self.game_surface.get_position()) # Setting Unit Health Text self.unit_health_text.change_text(str(unit.health)) self.unit_health_text.x = x + constants.TILE_WIDTH / 2 - 6 self.unit_health_text.y = y + constants.TILE_HEIGHT - 15 # Drawing Unit - image_name = unit.owner.get_colour() + "-" + unit.type + image_name = self.model_link.get_player(unit.owner).get_colour() + "-" + unit.type self.game_surface.main_surface.blit(self.unit_images["base-unit"], [x, y]) #self.game_surface.main_surface.blit(self.unit_images["unit-counter"], [x, y]) self.game_surface.main_surface.blit(self.unit_images[image_name], [x, y]) @@ -173,8 +173,8 @@ def draw_units(self): self.unit_health_text.draw(self.game_surface.main_surface) def draw_action_overlay(self, unit): - possible_moves = self.model.get_moves(unit) - possible_attacks = self.model.get_attacks(unit) # both lists of positions [[row,col]...] + possible_moves = self.model_link.get_moves(unit) + possible_attacks = self.model_link.get_attacks(unit) # both lists of positions [[row,col]...] for move in possible_moves: x, y = isometric.get_iso(move[0], move[1], self.game_surface.get_position()) @@ -184,7 +184,7 @@ def draw_action_overlay(self, unit): x, y = isometric.get_iso(attack[0], attack[1], self.game_surface.get_position()) self.game_surface.main_surface.blit(self.unit_action_images["attack"], [x, y]) - if self.model.check_conquer(unit): + if self.model_link.check_conquer(unit): x, y = isometric.get_iso(unit.position[0], unit.position[1], self.game_surface.get_position()) self.game_surface.main_surface.blit(self.unit_action_images["conquer"], [x, y]) @@ -235,7 +235,8 @@ def draw(self, surface): class VisualCityTile: - def __init__(self, city_link): + def __init__(self, city_link, model_link): + self.model_link = model_link self.city_link = city_link self.image = self.get_image() self.x, self.y = get_tile_position(self.city_link.get_position()[0], self.city_link.get_position()[1]) @@ -249,8 +250,9 @@ def get_image(self): def get_indicator_image(self): if self.city_link.current_holder is not None: - indicator = paths.tilePath + "l%s-%s.png" % (self.city_link.get_level(), - self.city_link.current_holder.get_colour()) # ie: l1-blue... + player = self.model_link.get_player(self.city_link.current_holder) + + indicator = paths.tilePath + "l%s-%s.png" % (self.city_link.get_level(), player.get_colour()) # ie: l1-blue... return pygame.image.load(indicator).convert_alpha() # returns none to self.ownership_indicator, but wont be drawn at this point anyway. @@ -270,8 +272,8 @@ def draw(self, surface): class VisualWorld: def __init__(self, model): - self.model = model - model_tiles = self.model.world.tiles + self.model_link = model + model_tiles = self.model_link.world.tiles self.tiles = [] self.settlement_names = [] @@ -279,7 +281,7 @@ def __init__(self, model): self.tiles.append([]) for tile in row: if tile.get_type() == "c": - new_tile = VisualCityTile(tile) + new_tile = VisualCityTile(tile, self.model_link) # Create City Text settlement_text = pygame_gui.Text(new_tile.city_link.get_name(), constants.FONTS["sizes"]["small"], (255, 255, 255), constants.FONTS["main"], diff --git a/project/menus/loadgame.py b/project/menus/loadgame.py index 0a2c8f1..d2ebd14 100644 --- a/project/menus/loadgame.py +++ b/project/menus/loadgame.py @@ -117,6 +117,10 @@ def draw(self): pygame.display.update() +def remove_file_extension(filename): + return filename.split(".")[0] + + class FileSelector: """ Responsible for the list of files seen on screen """ def __init__(self, control, origin): @@ -125,7 +129,7 @@ def __init__(self, control, origin): self.max_amount = 6 # split into lists of amount (pages of so many games) # Load Game Names From Directory - self.games = sorted([file for file in os.listdir(paths.gamePath)]) + self.games = sorted([remove_file_extension(file) for file in os.listdir(paths.gamePath)]) try: self.games.remove(".gitignore") # .gitignore present to stop data being pushed/pulled but still in directory. except ValueError: From 2be244b014bac02e444ae91d5ddca9dd841e30f5 Mon Sep 17 00:00:00 2001 From: Ben Ryder Date: Thu, 7 May 2020 20:29:03 +0100 Subject: [PATCH 4/6] GH-5: Updating version and readme infomation. --- README.md | 2 +- constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5a1f2a5..5bac46a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ The game was inspired by [Polytopia](http://midjiwan.com/polytopia.html) and [Ci To download executables see [releases](https://github.com/Ben-Ryder/Conqueror-of-Empires/releases). *** -**v1.0.1**: Release version 1.0.1 brings a proper fix for game crashes where the `saved` directory doesn't exist. +**v1.1**: Release version 1.1 changes the data save format to JSON. *** ## Playing the Game diff --git a/constants.py b/constants.py index 1440a24..10dacab 100644 --- a/constants.py +++ b/constants.py @@ -12,7 +12,7 @@ version = subprocess.run(["git", "describe", "--tags"], stdout=subprocess.PIPE).stdout.decode("utf-8") assert version != "" except Exception: # seems to be so dependent on system and versions, easier to do a catch all - version = "v1.0" # git not installed, or older lib version, so revert to hardcoded version + version = "v1.1" # git not installed, or older lib version, so revert to hardcoded version # configuration for pygame.display From 5354510a7a6b4369242e7c05b7277ac404a79f7c Mon Sep 17 00:00:00 2001 From: Ben-Ryder Date: Mon, 30 Aug 2021 17:06:42 +0100 Subject: [PATCH 5/6] Removing my name from files. It isn't needed anymore. --- constants.py | 2 -- main.py | 1 - paths.py | 4 ---- project/background.py | 2 -- project/control/__init__.py | 2 -- project/control/controller.py | 2 -- project/data.py | 1 - project/game/calculations.py | 2 -- project/game/controller.py | 2 -- project/game/gui.py | 1 - project/game/isometric.py | 2 -- project/game/model.py | 2 -- project/game/new.py | 2 -- project/game/scroll.py | 2 -- project/game/surface.py | 2 -- project/game/view.py | 2 -- project/menus/__init__.py | 2 -- project/menus/leaderboard.py | 1 - project/menus/loadgame.py | 2 -- project/menus/menu.py | 2 -- project/menus/newgame.py | 2 -- pygame_gui/__init__.py | 2 -- pygame_gui/button.py | 2 -- pygame_gui/checkbox.py | 2 -- pygame_gui/cursor.py | 2 -- pygame_gui/entry.py | 2 -- pygame_gui/image.py | 2 -- pygame_gui/panel.py | 2 -- pygame_gui/text.py | 2 -- pygame_gui/text_button.py | 3 --- 30 files changed, 59 deletions(-) diff --git a/constants.py b/constants.py index 10dacab..70353ab 100644 --- a/constants.py +++ b/constants.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import sys import subprocess import paths diff --git a/main.py b/main.py index 4724284..0453ffe 100755 --- a/main.py +++ b/main.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# Ben-Ryder 2019 import logging logging.basicConfig(filename='main.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s') diff --git a/paths.py b/paths.py index 912072d..a22a4c8 100644 --- a/paths.py +++ b/paths.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import os dataPath = os.getcwd() + os.sep + "data" + os.sep @@ -17,5 +15,3 @@ uiPath = imagePath + "UI" + os.sep # general, throughout sections. uiMenuPath = imagePath + "menu" + os.sep uiGamePath = imagePath + "game" + os.sep - - diff --git a/project/background.py b/project/background.py index d800c88..9e2d672 100644 --- a/project/background.py +++ b/project/background.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import pygame import random diff --git a/project/control/__init__.py b/project/control/__init__.py index 7b2f55a..2503319 100644 --- a/project/control/__init__.py +++ b/project/control/__init__.py @@ -1,3 +1 @@ -# Ben-Ryder 2019 - from project.control.controller import * \ No newline at end of file diff --git a/project/control/controller.py b/project/control/controller.py index 94b5c55..adb9200 100644 --- a/project/control/controller.py +++ b/project/control/controller.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import constants import paths diff --git a/project/data.py b/project/data.py index f260030..a56ce86 100644 --- a/project/data.py +++ b/project/data.py @@ -1,4 +1,3 @@ -# Ben-Ryder 2019 # Currently using JSON as method of file save import json diff --git a/project/game/calculations.py b/project/game/calculations.py index 9d75923..7035ce2 100644 --- a/project/game/calculations.py +++ b/project/game/calculations.py @@ -1,7 +1,5 @@ -# Ben-Ryder 2019 # Currently a direct subtraction of attack from health - def apply_attack(attacker, defender): defender.health -= attacker.attack diff --git a/project/game/controller.py b/project/game/controller.py index c1e28d0..90adcd6 100644 --- a/project/game/controller.py +++ b/project/game/controller.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import pygame import project.game.model as model diff --git a/project/game/gui.py b/project/game/gui.py index e6cdbcc..ca93125 100644 --- a/project/game/gui.py +++ b/project/game/gui.py @@ -1,4 +1,3 @@ -# Ben-Ryder 2019 import pygame import time diff --git a/project/game/isometric.py b/project/game/isometric.py index d64b472..e1bb4be 100644 --- a/project/game/isometric.py +++ b/project/game/isometric.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import constants diff --git a/project/game/model.py b/project/game/model.py index 5d36261..dc6457d 100644 --- a/project/game/model.py +++ b/project/game/model.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import random import paths import constants diff --git a/project/game/new.py b/project/game/new.py index fd55efb..7108308 100644 --- a/project/game/new.py +++ b/project/game/new.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import os import random diff --git a/project/game/scroll.py b/project/game/scroll.py index a5b5108..09479b6 100644 --- a/project/game/scroll.py +++ b/project/game/scroll.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import pygame diff --git a/project/game/surface.py b/project/game/surface.py index a6f968b..f8baf80 100644 --- a/project/game/surface.py +++ b/project/game/surface.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import pygame diff --git a/project/game/view.py b/project/game/view.py index 4d1f876..4076d0f 100644 --- a/project/game/view.py +++ b/project/game/view.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2018 - import pygame import project.game.isometric as isometric diff --git a/project/menus/__init__.py b/project/menus/__init__.py index 132e62a..5d43a86 100644 --- a/project/menus/__init__.py +++ b/project/menus/__init__.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - from project.menus.menu import * from project.menus.newgame import * from project.menus.loadgame import * diff --git a/project/menus/leaderboard.py b/project/menus/leaderboard.py index 0e767a3..145b734 100644 --- a/project/menus/leaderboard.py +++ b/project/menus/leaderboard.py @@ -1,4 +1,3 @@ -# Ben-Ryder 2019 import pygame import constants diff --git a/project/menus/loadgame.py b/project/menus/loadgame.py index d2ebd14..e7a2d1e 100644 --- a/project/menus/loadgame.py +++ b/project/menus/loadgame.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import pygame import os diff --git a/project/menus/menu.py b/project/menus/menu.py index 4acf691..c520b63 100644 --- a/project/menus/menu.py +++ b/project/menus/menu.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import webbrowser import pygame diff --git a/project/menus/newgame.py b/project/menus/newgame.py index 7e110e4..9ca2d1a 100644 --- a/project/menus/newgame.py +++ b/project/menus/newgame.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import os import pygame import random diff --git a/pygame_gui/__init__.py b/pygame_gui/__init__.py index 9b09c44..ee474ae 100644 --- a/pygame_gui/__init__.py +++ b/pygame_gui/__init__.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - from pygame_gui.text import * from pygame_gui.image import * from pygame_gui.button import * diff --git a/pygame_gui/button.py b/pygame_gui/button.py index 3b91833..54a269a 100644 --- a/pygame_gui/button.py +++ b/pygame_gui/button.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import pygame import pygame_gui.image diff --git a/pygame_gui/checkbox.py b/pygame_gui/checkbox.py index d80c08b..35586e1 100644 --- a/pygame_gui/checkbox.py +++ b/pygame_gui/checkbox.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import pygame import pygame_gui.image diff --git a/pygame_gui/cursor.py b/pygame_gui/cursor.py index f63ba7b..9295cd3 100644 --- a/pygame_gui/cursor.py +++ b/pygame_gui/cursor.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import pygame diff --git a/pygame_gui/entry.py b/pygame_gui/entry.py index 767ef0a..d713faa 100644 --- a/pygame_gui/entry.py +++ b/pygame_gui/entry.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import pygame import pygame_gui.text import pygame_gui.image diff --git a/pygame_gui/image.py b/pygame_gui/image.py index d4974eb..6d33337 100644 --- a/pygame_gui/image.py +++ b/pygame_gui/image.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import pygame diff --git a/pygame_gui/panel.py b/pygame_gui/panel.py index e326c24..87f385e 100644 --- a/pygame_gui/panel.py +++ b/pygame_gui/panel.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import pygame diff --git a/pygame_gui/text.py b/pygame_gui/text.py index a5397d0..73206cc 100644 --- a/pygame_gui/text.py +++ b/pygame_gui/text.py @@ -1,5 +1,3 @@ -# Ben-Ryder 2019 - import pygame diff --git a/pygame_gui/text_button.py b/pygame_gui/text_button.py index fc8920e..952d4a8 100644 --- a/pygame_gui/text_button.py +++ b/pygame_gui/text_button.py @@ -1,6 +1,3 @@ -# Ben-Ryder 2019 - - import pygame import pygame_gui.text From 0ddf72f34319a7b290ad33c039713a1bb7b21614 Mon Sep 17 00:00:00 2001 From: Ben Ryder Date: Sat, 11 Sep 2021 22:29:21 +0100 Subject: [PATCH 6/6] Revert adding wiki submodule from project, it will be kept seperate instead. This reverts commit f5e22097b6fb0c29c79274233d7020fd034ec291. --- .gitmodules | 3 --- docs | 1 - 2 files changed, 4 deletions(-) delete mode 100644 .gitmodules delete mode 160000 docs diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index bd54953..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "docs"] - path = docs - url = https://github.com/Ben-Ryder/Conqueror-of-Empires.wiki.git diff --git a/docs b/docs deleted file mode 160000 index 48926a6..0000000 --- a/docs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 48926a652f3b8f38d57c2473b7dd7a8c2a49c252