python template
This commit is contained in:
parent
802338fcad
commit
936469c1a5
132
README.md
132
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.
|
||||||
|
276
api.py
Normal file
276
api.py
Normal file
@ -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))
|
155
game_layer.py
Normal file
155
game_layer.py
Normal file
@ -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
|
138
game_state.py
Normal file
138
game_state.py
Normal file
@ -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)
|
64
main.py
Normal file
64
main.py
Normal file
@ -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()
|
Reference in New Issue
Block a user