diff --git a/README.md b/README.md index fe7a62b..686eaee 100644 --- a/README.md +++ b/README.md @@ -1 +1,131 @@ -Considition-2020 +# StarterKit Python Considition 2020 +This is the StarterKit for Considition 2020, a help to get going as quickly as possible with the competition. The StarterKit contains four main parts. + + - **The Main Program:** This is where you implement your solution. There is already an example solution implemented that works out of the box, but you will have to develop it further to get a better score. + - **The Game Layer:** A wrapper between the API and the Main Program. Helps you with formatting the input to the API and keep an updated game state. + - **The API:** A representation of the REST-API that the game is played with. Can be used directly or through the Game Layer. + - **The Game State:** A representation of the Game State and the information about the current game. + +Each part us described in greater detail below. The competition itself is also described on [Considition.com/rules](considition.com/rules). + +# Installation and running +Run *main.py* + +# Main Program +The Main Program is a simple loop. Each run of the program does the following: + - Create a new game + - Start the game + - Take 700 actions + - This is where you can implement your solution. Depending on the current Game State, take actions that maximize your final score. + - Print the final score +# Game Layer +The game layer has all the functions you need to play the game. + +**Game Setup** +- **New Game** Create a new game and get the *Game Info*. +- **Start Game** Start an already created game and get the initial *Game State*. +- **End Game** End a started game prematurely. Since there is a limit on how many games can be active at the same time for each team, you might have to use this to free up slots. +- **Score** Get the score for a finished game. The final turn of the game has to have happened for the score to be available. +- **Game Info** This can be used to get the *Game Info* of an already started game, for example if you want to resume an ongoing game after making changes. +- **Game State** This can be used to get the current *Game State* of an ongoing game. + +**Actions** +Each Action returns an updated *Game State* +- **Place Foundation** Places a new building on a free spot on the map. Is used both for *Residences* and *Utility Buildings*. A building must be in either the *Available Residences* or *Available Utilities* and the current *Turn* has to be at least equal to the buildings *Release Tick* for it to be placed. The building has to be constructed before it has any effect on the game. +- **Build** Builds an already placed building. A building is finished when its *Build Progess* reaches 100. +- **Demolish** Demolishes a building. If a building has the attribute *Can Be Demolished* set to false, the demolish action will have no effect on that building. +- **Buy Upgrade** Buys and activates an upgrade for a *Residence*. If the building is already affected by the *Effect* of the upgrade, this will have no effect. +- **Maintenance** Repairs a *Residence*, resetting its *Health* to 100. +- **Adjust Energy** Sets the *Requested Energy In* for a *Residence* to the specified value. This is used to control the temperature of a building and costs **150 Funds** each time. + +**Helpers** +- **Get Blueprint** Returns a blueprint matching the name of a building. This is useful to look up the static information of a building. There are variants for both *Residences* and *Utilities*. +- **Get Effect** Returns an effect matching that name. This is useful to look up what the effects on a building does. + +# API +The definition of the API and what it returns can be found on [game.Considition.com/swagger](game.considition.com/swagger), or on [Considition.com/rules](considition.com/rules). +If you want to view the Replay of a game, use [visualizer.Considition.com/swagger](visualizer.considition.com). + +# Game State +The Game State is split into two parts. One that is static per map, called *Game Info* and one that changes depending on your actions, called *Game State*. + +**Game Info** +- **Game Id** The ID of the current game, useful if you want to view the replay or you have multiple games going at the same time. +- **Map Name** The name of the map you are currently playing on. +- **Max Turns** The maximum number of turns the game will go on for. +- **Max Temp** Approximately the highest temperature that can be reached on this map. +- **Min Temp** Approximately the lowest temperature that can be reached ont his map. +- **Map** An array of arrays of integers representing the map. Buildable slots are **0**'s. The map is not changing and thus will not reflect where buildings get placed. +- **Energy Levels** A list of the types of energy that can be purchased. + - **Energy Threshold** If the amount of energy you buy is above this threshold, this is the type of energy you have to buy. This changes depending on the map. + - **Cost per Mwh** The cost to buy the energy. This is the same even among different maps. + - **Co2 per Mwh** The amount of Co2 released when buying this energy. This is the same even among different maps. +- **Available Residences** The residences that can be bought on this map. Some buildings might not be available right away, depending on their *Release Tick*. + - **Building Name** The name of the building. + - **Cost** Cost in funds to buy the building. + - **Co2 Cost** Cost in Co2 to buy the building. + - **Base Energy Need** The lowest amount of energy needed to sustain the building. You always have to buy at least this much energy if able. + - **Build Speed** How much the build progress is increased each time you call **Build** on this building. + - **Type** A residence or a utility. + - **Release Tick** When is the building available on this map. + - **Max Pop** The maximum number of pops that can live in this residenceat the same time. + - **Income Per Pop** The income you gain each turn for each pop in this residence. + - **Emissivity** A multiplier on how much heat the residence loses to the environment depending on the difference in temperature. + - **Maintenance Cost** How much it costs to do the **Maintenance** action on this residence. + - **Decay Rate** How much *Health*, on average, this residence loses each turn. + - **Max Happinesss** The maximum happiness each pop can generate each turn in this residence. +- **Available Utilities** The utility buildings that can be bought on this map. Some buildings might not be available right away, depending on their *Release Tick*. + - **Building Name** The name of the building. + - **Cost** Cost in funds to buy the building. + - **Co2 Cost** Cost in Co2 to buy the building. + - **Base Energy Need** The lowest amount of energy needed to sustain the building. You always have to buy at least this much energy if able. + - **Build Speed** How much the build progress is increased each time you call **Build** on this building. + - **Type** A residence or a utility. + - **Release Tick** When is the building available on this map. + - **Effects** A list of *Effect Name* which describes which effects this utility will produce when finished. + - **Queue Increase** How much faster the *Queue* will grow, on average, if this utility is finished. Can only be applied once per type of utility. +- **Upgrades** A list of the upgrades that can be bought for residences. + - **Name** The name of the upgrade. This is the name that you use when calling **Buy Upgrade**. + - **Effect** The name of the effect this upgrade has. + - **Cost** The cost in funds to purhcase this upgrade. +- **Effects** A list of all effects. + - **Name** The name of the effect. + - **Radius** If the effect has a radius of 0 it only affects the building it is on. The radius is measured in manhattan distance. + - **Emissivity Multiplier** Changes the emissivity of the affected building. + - **Decay Multiplier** Changes the decay rate of the affected building. + - **Building Income Increase** Changes the amount of funds generated by this building. Can be negative. + - **Max Happiness Increase** Increases the maximum happiness each pop in this building can generate. + - **Mwh Production** Generates free energy that is automatically removed from the *Requested Energy* + - **Base Energy Mwh Increase** Increases the *Base Energy Need* of a building. + - **Co2 per Pop Increase** Increases the amount of Co2 generated by each pop in this building. Can be negative. + - **Decay Increase** A flat increase in the decay rate. Is applied before the **Decay Multiplier**. + +**Game State** +- **Turn** The current turn of the game. +- **Funds** The amount of funds available to purchase energy, buildings, upgrades and take actions. +- **Total Co2** The total amount of Co2 generated during the game. This is used to help calculate your score. +- **Total Happinesss** The total amount of happiness generated during the game. This is used to help calculate your score. +- **Current Temp** The current outdoor temperature. +- **Queue Happiness** How happy the pops in the queue are. Keep the queue short to make them happier. +- **Housing Queue** How many pops are currently in the queue. +- **Residences** The built residences. On some maps there are already pre-constructed residences when the map starts. + - **Building Name** The name of the building. This matches the name in *Available Buildings* and in *Get Blueprint*. + - **Position X** The buildings x-position on the map. + - **Position Y** The buildings y-position on the map. + - **Effective Energy In** How much energy the building receives. This can differ from the *Requested Energy In* if you can't afford to purchase all your energy. + - **Build Progress** How close to finished the building is. At 100 the building is done. + - **Can Be Demolished** If the building can be demolished or not. + - **Effects** A list of *Effect Name* which tells which effects are currently active. + - **Current Pop** The number of pops currently living in the residence. + - **Temperature** The indoor temperature of the residence. + - **Requested Energy In** The energy this residence wants. Can be changed by calling **Adjust Energy**. + - **Happiness Per Tick Per Pop** The amount of happiness each pop generate on each turn. If the happiness is too low, pops will start to move out. + - **Health** The current health of the building. If it's too low the pops will start to become unhappy. +- **Utilities** The built utilities. On some maps there are already pre-constructed utilities when the map starts. + - **Building Name** The name of the building. This matches the name in *Available Buildings* and in *Get Blueprint*. + - **Position X** The buildings x-position on the map. + - **Position Y** The buildings y-position on the map. + - **Effective Energy In** How much energy the building receives. This can differ from the *Base Energy Need* if you can't afford to purchase all your energy. + - **Build Progress** How close to finished the building is. At 100 the building is done. + - **Can Be Demolished** If the building can be demolished or not. + - **Effects** A list of *Effect Name* which tells which effects are currently active. diff --git a/api.py b/api.py new file mode 100644 index 0000000..2306ed3 --- /dev/null +++ b/api.py @@ -0,0 +1,276 @@ +import requests +from requests import RequestException + +base_api_path = "https://game.considition.com/api/game/" +sess = None + + +def new_game(api_key, game_options=""): + try: + global sess + if not sess: + sess = requests.Session() + response = sess.post(base_api_path + "new", json=game_options, headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + + print("Fatal Error: could not create new game") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not create new game") + print("Something went wrong with the request: " + str(e)) + + +def start_game(api_key, game_id=None): + if game_id: + game_id = "?GameId=" + game_id + else: + game_id = "" + try: + global sess + if not sess: + sess = requests.Session() + response = sess.get(base_api_path + "start" + game_id, headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + + print("Fatal Error: could not start game") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not start game") + print("Something went wrong with the request: " + str(e)) + + +def end_game(api_key, game_id=None): + if game_id: + game_id = "?GameId=" + game_id + else: + game_id = "" + try: + global sess + if not sess: + sess = requests.Session() + response = sess.get(base_api_path + "end" + game_id, headers={"x-api-key": api_key}) + if response.status_code == 200: + return + + print("Fatal Error: could not end game") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not end game") + print("Something went wrong with the request: " + str(e)) + + +def get_score(api_key, game_id=None): + if game_id: + game_id = "?GameId=" + game_id + else: + game_id = "" + try: + global sess + if not sess: + sess = requests.Session() + response = sess.get(base_api_path + "score" + game_id, headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + + print("Fatal Error: could not get score") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not get score") + print("Something went wrong with the request: " + str(e)) + + +def get_game_info(api_key, game_id=None): + if game_id: + game_id = "?GameId=" + game_id + else: + game_id = "" + try: + global sess + if not sess: + sess = requests.Session() + response = sess.get(base_api_path + "gameInfo" + game_id, headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + + print("Fatal Error: could not get game info") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not get game info") + print("Something went wrong with the request: " + str(e)) + + +def place_foundation(api_key, foundation, game_id=None): + if game_id: + game_id = "?GameId=" + game_id + else: + game_id = "" + try: + global sess + if not sess: + sess = requests.Session() + response = sess.post(base_api_path + "action/startBuild" + game_id, json=foundation, headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + + print("Fatal Error: could not do action place foundation") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not do action place foundation") + print("Something went wrong with the request: " + str(e)) + + +def build(api_key, pos, game_id=None): + if game_id: + game_id = "?GameId=" + game_id + else: + game_id = "" + try: + global sess + if not sess: + sess = requests.Session() + response = sess.post(base_api_path + "action/Build" + game_id, json=pos, headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + + print("Fatal Error: could not do action build") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not do action build") + print("Something went wrong with the request: " + str(e)) + + +def maintenance(api_key, pos, game_id=None): + if game_id: + game_id = "?GameId=" + game_id + else: + game_id = "" + try: + global sess + if not sess: + sess = requests.Session() + response = sess.post(base_api_path + "action/maintenance" + game_id, json=pos, headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + + print("Fatal Error: could not do action maintenance") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not do action maintenance") + print("Something went wrong with the request: " + str(e)) + + +def demolish(api_key, pos, game_id=None): + if game_id: + game_id = "?GameId=" + game_id + else: + game_id = "" + try: + global sess + if not sess: + sess = requests.Session() + response = sess.post(base_api_path + "action/demolish" + game_id, json=pos, headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + + print("Fatal Error: could not do action demolish") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not do action demolish") + print("Something went wrong with the request: " + str(e)) + + +def wait(api_key, game_id=None): + if game_id: + game_id = "?GameId=" + game_id + else: + game_id = "" + try: + global sess + if not sess: + sess = requests.Session() + response = sess.post(base_api_path + "action/wait" + game_id, headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + + print("Fatal Error: could not do action wait") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not do action wait") + print("Something went wrong with the request: " + str(e)) + + +def adjust_energy(api_key, energy_level, game_id=None): + if game_id: + game_id = "?GameId=" + game_id + else: + game_id = "" + try: + global sess + if not sess: + sess = requests.Session() + response = sess.post(base_api_path + "action/adjustEnergy" + game_id, json=energy_level, headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + print("Fatal Error: could not do action adjust energy level") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not do action adjust energy level") + print("Something went wrong with the request: " + str(e)) + + +def buy_upgrades(api_key, upgrade, game_id=None): + if game_id: + game_id = "?GameId=" + game_id + else: + game_id = "" + try: + global sess + if not sess: + sess = requests.Session() + response = sess.post(base_api_path + "action/buyUpgrade" + game_id, json=upgrade, headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + + print("Fatal Error: could not do action buy upgrades") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not do action buy upgrades") + print("Something went wrong with the request: " + str(e)) + + +def get_game_state(api_key, game_id=None): + if game_id: + game_id = "?GameId=" + game_id + else: + game_id = "" + try: + global sess + if not sess: + sess = requests.Session() + response = sess.get(base_api_path + "gameState" + game_id, headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + + print("Fatal Error: could not get game state") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not do get game state") + print("Something went wrong with the request: " + str(e)) + + +def get_games(api_key): + try: + global sess + if not sess: + sess = requests.Session() + response = sess.get(base_api_path + "games", headers={"x-api-key": api_key}) + if response.status_code == 200: + return response.json() + + print("Fatal Error: could not get games") + print(str(response.status_code) + " " + response.reason + ": " + response.text) + except RequestException as e: + print("Fatal Error: could not get games") + print("Something went wrong with the request: " + str(e)) diff --git a/game_layer.py b/game_layer.py new file mode 100644 index 0000000..a20cdcf --- /dev/null +++ b/game_layer.py @@ -0,0 +1,155 @@ +from typing import Tuple + +import api +from game_state import GameState + + +class GameLayer: + def __init__(self, api_key): + self.game_state: GameState = None + self.api_key: str = api_key + + def new_game(self, map_name: str = "training0"): + """ + Create a new game. + """ + if map_name: + game_options = {"mapName": map_name} + else: + game_options = "" + + self.game_state = GameState(api.new_game(self.api_key, game_options)) + + def end_game(self): + """ + End the current game + """ + api.end_game(self.api_key, self.game_state.game_id) + + def start_game(self): + """ + Starts the game. + """ + self.game_state.update_state(api.start_game(self.api_key, self.game_state.game_id)) + + def place_foundation(self, pos: Tuple[int, int], building_name: str): + """ + Places a building with name building_name at the given position. + :param pos: (int, int) - the position + :param building_name: string - the name, check available_residence_buildings or available_residence_utilities for which buildings are available + """ + position = {'X': pos[0], 'Y': pos[1]} + foundation = {'Position': position, 'BuildingName': building_name} + self.game_state.update_state(api.place_foundation(self.api_key, foundation, self.game_state.game_id)) + + def build(self, pos: Tuple[int, int]): + """ + Continues the construction of a building at the given position. + :param pos: (int, int) - the position + """ + position = {'position': {"X": pos[0], "Y": pos[1]}} + self.game_state.update_state(api.build(self.api_key, position, self.game_state.game_id)) + + def maintenance(self, pos: Tuple[int, int]): + """ + Performs maintenance on the building at the given position. + :param pos: (int, int) - the position + """ + position = {'position': {"x": pos[0], "y": pos[1]}} + self.game_state.update_state(api.maintenance(self.api_key, position, self.game_state.game_id)) + + def demolish(self, pos: Tuple[int, int]): + """ + Demolishes the building at the given position. + :param pos: (int, int) - the position + """ + position = {'position': {"x": pos[0], "y": pos[1]}} + self.game_state.update_state(api.demolish(self.api_key, position, self.game_state.game_id)) + + def adjust_energy_level(self, pos: Tuple[int, int], value: float): + """ + Adjusts the requested energy to value on the building at the given position. + :param pos: (int, int) - the position + :param value: float - the new requested value + """ + position = {"x": pos[0], "y": pos[1]} + self.game_state.update_state(api.adjust_energy(self.api_key, {"position": position, "value": value}, self.game_state.game_id)) + + def wait(self): + """ + Advances the game by one turn. + """ + self.game_state.update_state(api.wait(self.api_key, self.game_state.game_id)) + + def buy_upgrade(self, pos: Tuple[int, int], upgrade: str): + """ + Adds the specified upgrade to the building at the given position. + You can find the available upgrades in available_upgrades + Parameters + ---------- + :param pos: (int, int) - the position + :param upgrade: string - the upgrade to purchase + """ + position = {"x": pos[0], "y": pos[1]} + self.game_state.update_state(api.buy_upgrades(self.api_key, {"position": position, "upgradeAction": upgrade}, self.game_state.game_id)) + + def get_score(self): + """ + Gets the score for the game. + :return An object with partial and total scores. + """ + return api.get_score(self.api_key, self.game_state.game_id) + + def get_game_info(self, game_id: str): + """ + Gets the game info of an already ongoing game and updates the state. + :param game_id: string - the id of the game to get info about. + """ + self.game_state = GameState(api.get_game_info(self.api_key, game_id)) + + def get_game_state(self, game_id: str): + """ + Gets the game state of an already ongoing game and updates the state. Can be used to resume a game. + :param game_id: string - the id of the game to get the state. + """ + self.game_state.update_state(api.get_game_state(self.api_key, game_id)) + + def get_blueprint(self, building_name: str): + """ + Returns the matching blueprint for a building + :param building_name: string - the name of the building to get a blueprint. + """ + res_blueprint = self.get_residence_blueprint(building_name) + if res_blueprint: + return res_blueprint + return self.get_utility_blueprint(building_name) + + def get_residence_blueprint(self, building_name: str): + """ + Returns the matching blueprint for a residence + :param building_name: string - the name of the building to get a blueprint. + """ + for blueprint in self.game_state.available_residence_buildings: + if blueprint.building_name == building_name: + return blueprint + return None + + def get_utility_blueprint(self, building_name: str): + """ + Return the matching blueprint for a utility building + :param building_name: string - the name of the building to get a blueprint. + """ + for blueprint in self.game_state.available_utility_buildings: + if blueprint.building_name == building_name: + return blueprint + return None + + def get_effect(self, effect_name: str): + """ + Return the matching effect for an effect name. + :param effect_name: string - the name of the effect to get. + """ + for effect in self.game_state.effects: + if effect.name == effect_name: + return effect + return None diff --git a/game_state.py b/game_state.py new file mode 100644 index 0000000..bec38a9 --- /dev/null +++ b/game_state.py @@ -0,0 +1,138 @@ +from typing import List + + +class GameState: + def __init__(self, map_values): + self.game_id: str = map_values["gameId"] + self.map_name: str = map_values["mapName"] + self.max_turns: int = map_values["maxTurns"] + self.max_temp: float = map_values["maxTemp"] + self.min_temp: float = map_values["minTemp"] + self.map: List[List[int]] = map_values["map"] + self.energy_levels: List[EnergyLevel] = [] + for level in map_values["energyLevels"]: + self.energy_levels.append(EnergyLevel(level)) + self.available_residence_buildings: List[BlueprintResidenceBuilding] = [] + for building in map_values["availableResidenceBuildings"]: + self.available_residence_buildings.append(BlueprintResidenceBuilding(building)) + self.available_utility_buildings: List[BlueprintUtilityBuilding] = [] + for building in map_values["availableUtilityBuildings"]: + self.available_utility_buildings.append(BlueprintUtilityBuilding(building)) + self.available_upgrades: List[Upgrade] = [] + for upgrade in map_values['availableUpgrades']: + self.available_upgrades.append(Upgrade(upgrade)) + self.effects: List[Effect] = [] + for effect in map_values['effects']: + self.effects.append(Effect(effect)) + + self.turn: int = 0 + self.funds: float = 0 + self.total_co2: float = 0 + self.total_happiness: float = 0 + self.current_temp: float = 0 + self.queue_happiness: float = 0 + self.housing_queue: int = 0 + self.residences: List[Residence] = [] + self.utilities: List[Utility] = [] + self.errors: List[str] = [] + self.messages: List[str] = [] + + def update_state(self, state): + self.turn = state['turn'] + self.funds = state['funds'] + self.total_co2 = state['totalCo2'] + self.total_happiness = state['totalHappiness'] + self.current_temp = state['currentTemp'] + self.queue_happiness = state['queueHappiness'] + self.housing_queue = state['housingQueue'] + self.residences = [] + for building in state['residenceBuildings']: + self.residences.append(Residence(building)) + self.utilities = [] + for building in state['utilityBuildings']: + self.utilities.append(Utility(building)) + self.errors = state['errors'] + self.messages = state['messages'] + + +class EnergyLevel: + def __init__(self, level_values): + self.energy_threshold: int = level_values['energyThreshold'] + self.cost_per_mwh: float = level_values['costPerMwh'] + self.co2_per_mwh: float = level_values['tonCo2PerMwh'] + + +class Blueprint: + def __init__(self, blueprint): + self.building_name: str = blueprint['buildingName'] + self.cost: int = blueprint['cost'] + self.co2_cost: int = blueprint['co2Cost'] + self.base_energy_need: float = blueprint['baseEnergyNeed'] + self.build_speed: int = blueprint['buildSpeed'] + self.type: str = blueprint['type'] + self.release_tick: int = blueprint['releaseTick'] + + +class BlueprintUtilityBuilding(Blueprint): + def __init__(self, blueprint_building): + super().__init__(blueprint_building) + self.effects: [str] = blueprint_building['effects'] + self.queue_increase: float = blueprint_building['queueIncrease'] + + +class BlueprintResidenceBuilding(Blueprint): + def __init__(self, blueprint_building): + super().__init__(blueprint_building) + self.max_pop: int = blueprint_building['maxPop'] + self.income_per_pop: float = blueprint_building['incomePerPop'] + self.emissivity: float = blueprint_building['emissivity'] + self.maintenance_cost: int = blueprint_building['maintenanceCost'] + self.decay_rate: float = blueprint_building['decayRate'] + self.max_happiness = blueprint_building['maxHappiness'] + + +class Upgrade: + def __init__(self, upgrade): + self.name: str = upgrade['name'] + self.effect: str = upgrade['effect'] + self.cost: int = upgrade['cost'] + + +class Effect: + def __init__(self, effect): + self.name: str = effect['name'] + self.radius: int = effect['radius'] + self.emissivity_multiplier: float = effect['emissivityMultiplier'] + self.decay_multiplier: float = effect['decayMultiplier'] + self.building_income_increase: float = effect['buildingIncomeIncrease'] + self.max_happiness_increase: float = effect['maxHappinessIncrease'] + self.mwh_production: float = effect['mwhProduction'] + self.base_energy_mwh_increase: float = effect['baseEnergyMwhIncrease'] + self.co2_per_pop_increase: float = effect['co2PerPopIncrease'] + self.decay_increase: float = effect['decayIncrease'] + + +class Building: + def __init__(self, building): + self.building_name: str = building['buildingName'] + self.X: int = building['position']['x'] + self.Y: int = building['position']['y'] + self.effective_energy_in: float = building['effectiveEnergyIn'] + self.build_progress: int = building['buildProgress'] + self.can_be_demolished: bool = building['canBeDemolished'] + self.effects: List[str] = building['effects'] + + +class Residence(Building): + def __init__(self, residence): + super().__init__(residence) + self.current_pop: int = residence['currentPop'] + self.temperature: float = residence['temperature'] + self.requested_energy_in: float = residence['requestedEnergyIn'] + self.happiness_per_tick_per_pop: float = residence['happinessPerTickPerPop'] + self.health: int = residence['health'] + + +class Utility(Building): + def __init__(self, utility): + super().__init__(utility) diff --git a/main.py b/main.py new file mode 100644 index 0000000..80c2eb3 --- /dev/null +++ b/main.py @@ -0,0 +1,64 @@ +import api +from game_layer import GameLayer + +api_key = "" # TODO: Your api key here +# The different map names can be found on considition.com/rules +map_name = "training1" # TODO: You map choice here. If left empty, the map "training1" will be selected. + +game_layer: GameLayer = None + + +def main(): + game_layer.new_game(map_name) + print("Starting game: " + game_layer.game_state.game_id) + game_layer.start_game() + while game_layer.game_state.turn < game_layer.game_state.max_turns: + take_turn() + print("Done with game: " + game_layer.game_state.game_id) + print("Final score was: " + str(game_layer.get_score()["finalScore"])) + + +def take_turn(): + # TODO Implement your artificial intelligence here. + # TODO Take one action per turn until the game ends. + # TODO The following is a short example of how to use the StarterKit + + state = game_layer.game_state + if len(state.residences) < 1: + for i in range(len(state.map)): + for j in range(len(state.map)): + if state.map[i][j] == 0: + x = i + y = j + break + game_layer.place_foundation((x, y), game_layer.game_state.available_residence_buildings[0].building_name) + else: + the_only_residence = state.residences[0] + if the_only_residence.build_progress < 100: + game_layer.build((the_only_residence.X, the_only_residence.Y)) + elif the_only_residence.health < 50: + game_layer.maintenance((the_only_residence.X, the_only_residence.Y)) + elif the_only_residence.temperature < 18: + blueprint = game_layer.get_residence_blueprint(the_only_residence.building_name) + energy = blueprint.base_energy_need + 0.5 \ + + (the_only_residence.temperature - state.current_temp) * blueprint.emissivity / 1 \ + - the_only_residence.current_pop * 0.04 + game_layer.adjust_energy_level((the_only_residence.X, the_only_residence.Y), energy) + elif the_only_residence.temperature > 24: + blueprint = game_layer.get_residence_blueprint(the_only_residence.building_name) + energy = blueprint.base_energy_need - 0.5 \ + + (the_only_residence.temperature - state.current_temp) * blueprint.emissivity / 1 \ + - the_only_residence.current_pop * 0.04 + game_layer.adjust_energy_level((the_only_residence.X, the_only_residence.Y), energy) + elif state.available_upgrades[0].name not in the_only_residence.effects: + game_layer.buy_upgrade((the_only_residence.X, the_only_residence.Y), state.available_upgrades[0].name) + else: + game_layer.wait() + for message in game_layer.game_state.messages: + print(message) + for error in game_layer.game_state.errors: + print("Error: " + error) + + +if __name__ == "__main__": + main()