Deal with farms and varying gather rates
This commit is contained in:
@@ -10,6 +10,7 @@ class Gameplay():
|
|||||||
|
|
||||||
|
|
||||||
def print_status(self):
|
def print_status(self):
|
||||||
|
print('-'*80)
|
||||||
print(f'Current time: {self.world.current_time}')
|
print(f'Current time: {self.world.current_time}')
|
||||||
print()
|
print()
|
||||||
print(f'Civ: {self.world.civ}')
|
print(f'Civ: {self.world.civ}')
|
||||||
@@ -24,6 +25,8 @@ class Gameplay():
|
|||||||
print(f'Buildings: {self.world.buildings}')
|
print(f'Buildings: {self.world.buildings}')
|
||||||
print()
|
print()
|
||||||
print(f'Research: {self.world.research}')
|
print(f'Research: {self.world.research}')
|
||||||
|
print('-'*80)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
def build(self, action_time, building, villagers_from, villagers_to):
|
def build(self, action_time, building, villagers_from, villagers_to):
|
||||||
|
|||||||
244
aoe/gather.py
244
aoe/gather.py
@@ -1,3 +1,5 @@
|
|||||||
|
import copy
|
||||||
|
|
||||||
from aoe.types import *
|
from aoe.types import *
|
||||||
|
|
||||||
|
|
||||||
@@ -5,23 +7,174 @@ from aoe.types import *
|
|||||||
class Gather():
|
class Gather():
|
||||||
|
|
||||||
|
|
||||||
|
# Gathering occurs when a villager is assigned to some relevant Task.
|
||||||
|
#
|
||||||
|
# Each Task consumes a map resource (e.g., a boar) at some rate.
|
||||||
|
# That rate is recorded in gather_rates.
|
||||||
|
#
|
||||||
|
# Map resources such as boars should not be confused with a player's
|
||||||
|
# resource stockpile. For example, Wood as a map resource is consumed by
|
||||||
|
# gathering, whereas the player's Wood resource amount is increased. As
|
||||||
|
# another example, a boar is a map resource that is consumed and leads to
|
||||||
|
# an increase in Food in the player's stockpile.
|
||||||
|
#
|
||||||
|
# When a Task is performed and a map resource is consumed, one or more
|
||||||
|
# resources are added to the player's stockpile. The typical case is to
|
||||||
|
# add one resource. E.g., when chopping wood, wood is added to the
|
||||||
|
# player's stockpile. However, for some civs or technologies, more than one
|
||||||
|
# resource may be gathered by a single Task. For example, there is a
|
||||||
|
# technology that allows woodcutters to generate food as they gather wood.
|
||||||
|
#
|
||||||
|
# The resources gathered by a Task are recorded in gathered_resources.
|
||||||
|
# Each Task is associated with 0 or more Resource->Int key-value pairs.
|
||||||
|
# A Task gathers all resources associated with itself. The integer values
|
||||||
|
# in the aforementioned pairs are multipliers. A value of 1 indicates
|
||||||
|
# that the corresponding resource will be added to the player's stockpile
|
||||||
|
# at the same rate that the world's resource is consumed. A higher value
|
||||||
|
# indicates that the resource is added at a more favorable ratio to the
|
||||||
|
# player, whereas a lower value indicates a less favorable ratio.
|
||||||
|
#
|
||||||
|
# To reiterate, a value in gather_rates indicates how quickly a world
|
||||||
|
# resource is removed. It may have been better named "consumption_rates"
|
||||||
|
# or something similar. On the other hand, to determine how quickly a
|
||||||
|
# resource is actually added to the player's stockpile, you must consider
|
||||||
|
# the gather rate from gather_rates *and* the corresponding multiplier from
|
||||||
|
# gathered_resources.
|
||||||
|
#
|
||||||
|
# Buildings #
|
||||||
|
#
|
||||||
|
# Most gathering cannot be performed without one or more buildings. In
|
||||||
|
# many cases, there are a few options for a building or set of buildings
|
||||||
|
# that will allow the resource to be gathered. The dictionary
|
||||||
|
# required_gather_buildings records which buildings are necessary and
|
||||||
|
# sufficient to perform each gathering Task. For a given Task, there is a
|
||||||
|
# corresponding list of sets. If the world contains *all* of the buildings
|
||||||
|
# in *any* of those sets, then the Task can be performed. Otherwise, it
|
||||||
|
# cannot.
|
||||||
|
#
|
||||||
|
# Varying Rates and Resource Exhaustion #
|
||||||
|
#
|
||||||
|
# The rate of consumption in gather_rates is not fixed over time. In
|
||||||
|
# general, as a task is performed, the rate will either slow down or stop
|
||||||
|
# entirely. For example, a lumber camp gets "stale" and the rate will drop
|
||||||
|
# unless a new camp is built, and a gold mine will be exhausted and the
|
||||||
|
# rate will effectively drop to zero until a new mine is built.
|
||||||
|
#
|
||||||
|
# Several data structures are involved in the implementation of this idea.
|
||||||
|
# We assume the world at any given time has some amount of consumables
|
||||||
|
# accessible to a player, which is stored in gatherable_amounts. This is
|
||||||
|
# *not* the total amount of, e.g., wood available on the map. It is
|
||||||
|
# instead the amount of, e.g., wood that can be consumed *at the current
|
||||||
|
# rate* given the available buildings. This amount goes down as resources
|
||||||
|
# are gathered and can go up as new buildings, such as lumber camps, are
|
||||||
|
# built. Resources are subtracted from gatherable_amounts at a rate
|
||||||
|
# determined by gather_rates and the number of villagers performing a Task.
|
||||||
|
#
|
||||||
|
# When the amount drops below 0, either the gather rate decreases (as in
|
||||||
|
# the case of cutting wood) or that Task stops entirely (as in the case of
|
||||||
|
# mining gold). If the rate merely decreases, the process repeats itself.
|
||||||
|
# That is, the value in gatherable_amounts is increased, the value in
|
||||||
|
# gather_rates is decreased, and another decrease will occur when the new
|
||||||
|
# amount in gatherable_amounts is exhausted. The dictionary
|
||||||
|
# gather_decrease_rates contains the multipliers by which the gather rate
|
||||||
|
# is decreased when resource exhaustion occurs. A value of 0 indicates
|
||||||
|
# that Task is stopped entirely.
|
||||||
|
#
|
||||||
|
# When a new building relevant for gathering, such as a lumber camp, is
|
||||||
|
# created, the amount of the corresponding consumable in gatherable_amounts
|
||||||
|
# is increased. If the gather rate was already at its original value, then
|
||||||
|
# some fixed amount is simply added to the available consumable, which will
|
||||||
|
# delay the next rate decrease. If the corresponding gather rate had
|
||||||
|
# previously been decreased, that rate is reset to its ideal value. In
|
||||||
|
# that case, the amount of available consumable is set to a value rather
|
||||||
|
# than added to the current value because the current value was available
|
||||||
|
# at a lower gather rate.
|
||||||
|
#
|
||||||
|
# A given building can provide access to various resources and may provide
|
||||||
|
# access to more than one. For example, a mining camp could provide access
|
||||||
|
# to gold, stone, both, or neither. The dictionary
|
||||||
|
# buildings_gather_increase stores information about which Task or group of
|
||||||
|
# Tasks can have their consumable amount increased when a particular
|
||||||
|
# building is built. A user can specify which Tasks, if any, benefit from
|
||||||
|
# a particular instance of a building, but the selected Tasks must be
|
||||||
|
# available for that building according to buildings_gather_increase for
|
||||||
|
# the choice to be considered valid. This is especially valuable for Town
|
||||||
|
# Centers, which can be built near any number of resources. That same data
|
||||||
|
# structure also stores the amount by which the relevant consumables are
|
||||||
|
# increased (which is also the reset value for that entry of
|
||||||
|
# gatherable_amounts in the case where a rate decrease has already
|
||||||
|
# occurred).
|
||||||
|
#
|
||||||
|
# Note that gathered_resources, which contains the multipliers for
|
||||||
|
# gather_rates and determines how much of a resource the player actually
|
||||||
|
# receives, is not relevant for determining how quickly a resource is
|
||||||
|
# exhausted. That calculation involves solely gatherable_amounts,
|
||||||
|
# gather_rates, the number of villagers performing the Task, and the amount
|
||||||
|
# of simulated time that passes.
|
||||||
|
#
|
||||||
|
# Known Limitations #
|
||||||
|
#
|
||||||
|
# There is currently no penalty for having too many villagers gathering at
|
||||||
|
# a particular building (e.g., 100 lumberjacks at a single wood camp).
|
||||||
|
# However, if there are not enough lumber camps, the scheduled decrease
|
||||||
|
# will happen quickly in that case, which will help penalize it somewhat.
|
||||||
|
#
|
||||||
|
# We also do not put a ceiling on the amount of a world resource that can
|
||||||
|
# be accessed. For example, we do not have a fixed amount of berries. If
|
||||||
|
# a user continues building mills and assigning more villagers to berries,
|
||||||
|
# they can do so indefinitely. We are trusting the user to know how many
|
||||||
|
# resources are available on their map.
|
||||||
|
#
|
||||||
|
# Farming #
|
||||||
|
#
|
||||||
|
# Farming is a little different, and I don't feel like explaining it right
|
||||||
|
# now.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
BIG_NUMBER = 1e6
|
||||||
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.gather_rates = {
|
self.gather_rates = {
|
||||||
Tasks.BERRIES: {Resources.FOOD: 18.6/60},
|
Tasks.BERRIES: 18.6/60,
|
||||||
Tasks.SHEEP: {Resources.FOOD: 19.8/60},
|
Tasks.SHEEP: 19.8/60,
|
||||||
Tasks.BOAR: {Resources.FOOD: 24.6/60},
|
Tasks.BOAR: 24.6/60,
|
||||||
Tasks.HUNT: {Resources.FOOD: 24.6/60},
|
Tasks.HUNT: 24.6/60,
|
||||||
Tasks.FARM: {Resources.FOOD: 20/60},
|
Tasks.FARM: 20/60,
|
||||||
Tasks.FISH_VILLAGER: {Resources.FOOD: 25.8/60},
|
Tasks.FISH_VILLAGER: 25.8/60,
|
||||||
Tasks.FISH_SHIP_SHORE: {Resources.FOOD: 16.8/60},
|
Tasks.FISH_SHIP_SHORE: 16.8/60,
|
||||||
Tasks.FISH_SHIP_DEEP: {Resources.FOOD: 29.4/60},
|
Tasks.FISH_SHIP_DEEP: 29.4/60,
|
||||||
Tasks.FISHTRAP: {Resources.FOOD: 21/60},
|
Tasks.FISHTRAP: 21/60,
|
||||||
Tasks.WOOD: {Resources.WOOD: 23.4/60},
|
Tasks.WOOD: 23.4/60,
|
||||||
Tasks.GOLD: {Resources.GOLD: 22.8/60},
|
Tasks.GOLD: 22.8/60,
|
||||||
Tasks.TRADECART: {Resources.GOLD: 25.9/60},
|
Tasks.TRADECART: 25.9/60,
|
||||||
Tasks.TRADECOG: {Resources.GOLD: 25.9/60},
|
Tasks.TRADECOG: 25.9/60,
|
||||||
Tasks.RELIC: {Resources.GOLD: 30/60},
|
Tasks.RELIC: 30/60,
|
||||||
Tasks.STONE: {Resources.STONE: 21.6/60},
|
Tasks.STONE: 21.6/60,
|
||||||
|
Tasks.BUILD: 0,
|
||||||
|
Tasks.IDLE: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.original_gather_rates = copy.deepcopy(self.gather_rates)
|
||||||
|
|
||||||
|
self.gathered_resources = {
|
||||||
|
Tasks.BERRIES: {Resources.FOOD: 1},
|
||||||
|
Tasks.SHEEP: {Resources.FOOD: 1},
|
||||||
|
Tasks.BOAR: {Resources.FOOD: 1},
|
||||||
|
Tasks.HUNT: {Resources.FOOD: 1},
|
||||||
|
Tasks.FARM: {Resources.FOOD: 1},
|
||||||
|
Tasks.FISH_VILLAGER: {Resources.FOOD: 1},
|
||||||
|
Tasks.FISH_SHIP_SHORE: {Resources.FOOD: 1},
|
||||||
|
Tasks.FISH_SHIP_DEEP: {Resources.FOOD: 1},
|
||||||
|
Tasks.FISHTRAP: {Resources.FOOD: 1},
|
||||||
|
Tasks.WOOD: {Resources.WOOD: 1},
|
||||||
|
Tasks.GOLD: {Resources.GOLD: 1},
|
||||||
|
Tasks.TRADECART: {Resources.GOLD: 1},
|
||||||
|
Tasks.TRADECOG: {Resources.GOLD: 1},
|
||||||
|
Tasks.RELIC: {Resources.GOLD: 1},
|
||||||
|
Tasks.STONE: {Resources.STONE: 1},
|
||||||
Tasks.BUILD: {},
|
Tasks.BUILD: {},
|
||||||
Tasks.IDLE: {},
|
Tasks.IDLE: {},
|
||||||
}
|
}
|
||||||
@@ -56,4 +209,63 @@ class Gather():
|
|||||||
Tasks.IDLE: [],
|
Tasks.IDLE: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
self.improved_gather_buildings = {}
|
self.gatherable_amounts = {
|
||||||
|
# These will vary based on the map.
|
||||||
|
# I am assuming Arabia with:
|
||||||
|
# 6 forage bushes
|
||||||
|
# 8 sheep
|
||||||
|
# 2 boars
|
||||||
|
# 4 deer
|
||||||
|
Tasks.BERRIES: 6*125,
|
||||||
|
Tasks.SHEEP: 8*100,
|
||||||
|
Tasks.BOAR: 2*340,
|
||||||
|
Tasks.HUNT: 4*140,
|
||||||
|
Tasks.FARM: 0,
|
||||||
|
# TODO next two should be shared
|
||||||
|
Tasks.FISH_VILLAGER: 0,
|
||||||
|
Tasks.FISH_SHIP_SHORE: 0,
|
||||||
|
Tasks.FISH_SHIP_DEEP: 0,
|
||||||
|
Tasks.FISHTRAP: 0,
|
||||||
|
Tasks.WOOD: 0,
|
||||||
|
Tasks.GOLD: 0,
|
||||||
|
Tasks.TRADECART: Gather.BIG_NUMBER,
|
||||||
|
Tasks.TRADECOG: Gather.BIG_NUMBER,
|
||||||
|
Tasks.RELIC: Gather.BIG_NUMBER,
|
||||||
|
Tasks.STONE: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.buildings_gather_increase = {
|
||||||
|
# Made-up numbers for now
|
||||||
|
Buildings.LUMBERCAMP: {Tasks.WOOD: 1000},
|
||||||
|
Buildings.MILL: {Tasks.HUNT: 4*140,
|
||||||
|
Tasks.BERRIES: 6*125},
|
||||||
|
Buildings.MININGCAMP: {Tasks.GOLD: 4*800,
|
||||||
|
Tasks.STONE: 4*350},
|
||||||
|
Buildings.FARM: {Tasks.FARM: 175},
|
||||||
|
#Buildings.DOCK: {Tasks.WOOD: 100},
|
||||||
|
Buildings.TOWNCENTER: {Tasks.WOOD: 2000,
|
||||||
|
Tasks.GOLD: 4*800,
|
||||||
|
Tasks.STONE: 4*350,
|
||||||
|
Tasks.BERRIES: 6*125,
|
||||||
|
Tasks.HUNT: 4*140}
|
||||||
|
#Buildings.FISHTRAP: {Tasks.WOOD: 100},
|
||||||
|
}
|
||||||
|
|
||||||
|
self.gather_decrease_rates = {
|
||||||
|
# Made-up numbers for now
|
||||||
|
Tasks.BERRIES: 0,
|
||||||
|
Tasks.SHEEP: 0,
|
||||||
|
Tasks.BOAR: 0,
|
||||||
|
Tasks.HUNT: 0,
|
||||||
|
Tasks.FARM: 0,
|
||||||
|
Tasks.FISH_VILLAGER: 0.5,
|
||||||
|
Tasks.FISH_SHIP_SHORE: 0.5,
|
||||||
|
Tasks.FISH_SHIP_DEEP: 0,
|
||||||
|
Tasks.FISHTRAP: 0,
|
||||||
|
Tasks.WOOD: 0.85,
|
||||||
|
Tasks.GOLD: 0,
|
||||||
|
Tasks.TRADECART: 1,
|
||||||
|
Tasks.TRADECOG: 1,
|
||||||
|
Tasks.RELIC: 1,
|
||||||
|
Tasks.STONE: 0,
|
||||||
|
}
|
||||||
|
|||||||
184
aoe/world.py
184
aoe/world.py
@@ -1,4 +1,6 @@
|
|||||||
from collections import defaultdict, Counter
|
from collections import defaultdict, Counter
|
||||||
|
import heapq
|
||||||
|
import math
|
||||||
|
|
||||||
from aoe.exceptions import *
|
from aoe.exceptions import *
|
||||||
from aoe.costs import *
|
from aoe.costs import *
|
||||||
@@ -35,15 +37,21 @@ class World():
|
|||||||
# and, if executed successfully, it adds another event to the queue that
|
# and, if executed successfully, it adds another event to the queue that
|
||||||
# actually adds the unit to the world
|
# actually adds the unit to the world
|
||||||
|
|
||||||
# TODO farms, reseeding, etc.
|
# For now, we assume just 1 villager can work a farm at a time.
|
||||||
# _check_resource_building, _use_resource_building, _free_resource_building
|
# This shouldn't be a huge deal to change later.
|
||||||
# TODO sheep should die like farms
|
# We also assume that a player makes optimal choices about farms --
|
||||||
# TODO need some warning, penalty, etc. if villager is assigned to task
|
# villagers are removed from farms with the lowest capacity first and added
|
||||||
# without appropriate building (e.g., mining with no camp)
|
# to farms with the highest capacity.
|
||||||
# TODO need efficiency penalties on, e.g., lumber camp
|
# We keep track of only the closest upcoming farm deletion.
|
||||||
# too many vils/camp decreases rate
|
|
||||||
# "stale" (old) camp decreases rate
|
|
||||||
# TODO research
|
# TODO research
|
||||||
|
# TODO allow automation:
|
||||||
|
# reseeding of farms
|
||||||
|
# queuing of villagers
|
||||||
|
# possibly other things
|
||||||
|
# TODO gathering boars and hunting
|
||||||
|
# spend villager time in exchange for adding gatherable resource
|
||||||
|
# TODO relics
|
||||||
|
|
||||||
|
|
||||||
MAX_WORLD_TIME = 3*60*60 # 3 hours
|
MAX_WORLD_TIME = 3*60*60 # 3 hours
|
||||||
@@ -102,23 +110,107 @@ class World():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _update_gather_rate(self, task):
|
def _schedule_next_gather_rate_decrease(self, task):
|
||||||
pass # TODO
|
if task in [Tasks.IDLE, Tasks.BUILD]:
|
||||||
#num_farms = self.buildings.count(Buildings.FARM)
|
return
|
||||||
#num_workers = self.villager_tasks[task]
|
|
||||||
#villager_room =
|
# remove old decrease
|
||||||
|
scheduled_decrease = self.rate_decrease_event_times[task]
|
||||||
|
if scheduled_decrease is not None:
|
||||||
|
decrease_index = None
|
||||||
|
for i, event in enumerate(self.events[scheduled_decrease]):
|
||||||
|
if event[0] == self._decrease_gather_rate:
|
||||||
|
decrease_index = i
|
||||||
|
break
|
||||||
|
del self.events[scheduled_decrease][i]
|
||||||
|
|
||||||
|
rate = self.gather.gather_rates[task]
|
||||||
|
|
||||||
|
if task == Tasks.FARM:
|
||||||
|
available = self.farms[0]
|
||||||
|
villagers = min(self.villager_tasks[task], 1)
|
||||||
|
else:
|
||||||
|
available = self.gather.gatherable_amounts[task]
|
||||||
|
villagers = self.villager_tasks[task]
|
||||||
|
|
||||||
|
try:
|
||||||
|
next_decrease = self.current_time + available/(villagers*rate)
|
||||||
|
except ZeroDivisionError:
|
||||||
|
# either villagers or rate was 0, so no next decrease should
|
||||||
|
# be scheduled
|
||||||
|
self.rate_decrease_event_times[task] = None
|
||||||
|
else:
|
||||||
|
# rounding error will give 1 extra second of gathering
|
||||||
|
next_decrease = math.ceil(self.current_time + next_decrease)
|
||||||
|
|
||||||
|
self.rate_decrease_event_times[task] = next_decrease
|
||||||
|
|
||||||
|
if task == Tasks.FARM:
|
||||||
|
self.events[next_decrease].append((self._remove_farm, ()))
|
||||||
|
else:
|
||||||
|
self.events[next_decrease].append((self._decrease_gather_rate,
|
||||||
|
(task,)))
|
||||||
|
|
||||||
|
|
||||||
|
def _decrease_gather_rate(self, task):
|
||||||
|
# assumes rate decrease should trigger; does not check
|
||||||
|
new_rate = self.gather_decrease_rates[task]*self.gather_rates[task]
|
||||||
|
self.gather_rates[task] = new_rate
|
||||||
|
|
||||||
|
if new_rate > 0:
|
||||||
|
self._schedule_next_gather_rate_decrease(task)
|
||||||
|
else:
|
||||||
|
self.assign_villager(task, task.IDLE, self.villager_tasks[task])
|
||||||
|
|
||||||
|
|
||||||
|
def _improve_gather_rate(self, building, tasks):
|
||||||
|
if building == Buildings.FARM:
|
||||||
|
raise ValueError('Do not call this routine with a farm')
|
||||||
|
|
||||||
|
if tasks is None:
|
||||||
|
tasks = self.gather.buildings_gather_increase[building].keys()
|
||||||
|
|
||||||
|
for task in tasks:
|
||||||
|
amount = self.gather.buildings_gather_increase[building][task]
|
||||||
|
if self.gather.gather_rates[task] < self.gather.original_gather_rates[task]:
|
||||||
|
self.gather.gather_rates[task] = self.gather.original_gather_rates[task]
|
||||||
|
self.gather.gatherable_amounts[task] = amount
|
||||||
|
else:
|
||||||
|
self.gather.gatherable_amounts[task] += amount
|
||||||
|
self._schedule_next_gather_rate_decrease(task)
|
||||||
|
|
||||||
|
|
||||||
def assign_villager(self, previous_task, new_task, count=1):
|
def assign_villager(self, previous_task, new_task, count=1):
|
||||||
|
# new_task of None means kill the villager
|
||||||
|
|
||||||
|
# villager_tasks must be modified only in this function
|
||||||
|
# if it is changed elsewhere, the rate-decrease schedule will be wrong
|
||||||
if self.villager_tasks[previous_task] < count:
|
if self.villager_tasks[previous_task] < count:
|
||||||
raise IllegalVillagerMoveException
|
raise IllegalVillagerMoveException
|
||||||
|
|
||||||
self._check_gather(new_task, count)
|
if new_task:
|
||||||
|
self._check_gather(new_task, count)
|
||||||
|
self.villager_tasks[new_task] += count
|
||||||
|
else:
|
||||||
|
self.villagers -= 1
|
||||||
|
|
||||||
self.villager_tasks[previous_task] -= count
|
self.villager_tasks[previous_task] -= count
|
||||||
self.villager_tasks[new_task] += count
|
|
||||||
|
|
||||||
self._update_gather_rate(new_task)
|
if previous_task == Tasks.FARM:
|
||||||
|
# remove villagers from farms with lowest remaining capacity
|
||||||
|
for i in range(count):
|
||||||
|
heapq.heappush_max(self.unused_farms,
|
||||||
|
heapq.heappop(self.farms))
|
||||||
|
|
||||||
|
if new_task == Tasks.FARM:
|
||||||
|
# add villagers to farms with highest remaining capacity
|
||||||
|
for i in range(count):
|
||||||
|
heapq.heappush(self.farms,
|
||||||
|
heapq.heappop_max(self.unused_farms))
|
||||||
|
|
||||||
|
self._schedule_next_gather_rate_decrease(previous_task)
|
||||||
|
if new_task:
|
||||||
|
self._schedule_next_gather_rate_decrease(new_task)
|
||||||
|
|
||||||
|
|
||||||
def _check_villager_move(self, villagers_from_count):
|
def _check_villager_move(self, villagers_from_count):
|
||||||
@@ -129,11 +221,10 @@ class World():
|
|||||||
|
|
||||||
def _move_villagers_to_build(self, villagers_from_count):
|
def _move_villagers_to_build(self, villagers_from_count):
|
||||||
for task, count in villagers_from_count.items():
|
for task, count in villagers_from_count.items():
|
||||||
self.villager_tasks[task] -= count
|
self.assign_villager(task, Tasks.BUILD, count)
|
||||||
self.villager_tasks[Tasks.BUILD] += count
|
|
||||||
|
|
||||||
|
|
||||||
def build(self, building, villagers_from, villagers_to):
|
def build(self, building, villagers_from, villagers_to, building_tasks=None):
|
||||||
if type(villagers_from) == Tasks:
|
if type(villagers_from) == Tasks:
|
||||||
villagers_from = [villagers_from]
|
villagers_from = [villagers_from]
|
||||||
if type(villagers_to) == Tasks:
|
if type(villagers_to) == Tasks:
|
||||||
@@ -158,33 +249,39 @@ class World():
|
|||||||
update_time = self.current_time + build_time
|
update_time = self.current_time + build_time
|
||||||
|
|
||||||
self.events[update_time].append((self._add_building,
|
self.events[update_time].append((self._add_building,
|
||||||
(building, villagers_to_count,)))
|
(building,
|
||||||
|
villagers_to_count,
|
||||||
|
building_tasks,)))
|
||||||
|
|
||||||
|
|
||||||
def _add_building(self, building, villagers_to):
|
def _add_building(self, building, villagers_to, tasks_improved):
|
||||||
|
|
||||||
self.buildings.append(building)
|
self.buildings.append(building)
|
||||||
self._free_production_building(building)
|
self._free_production_building(building)
|
||||||
|
|
||||||
for task, count in villagers_to.items():
|
|
||||||
self.villager_tasks[Tasks.BUILD] -= count
|
|
||||||
self.villager_tasks[task] += count
|
|
||||||
|
|
||||||
if building == Buildings.HOUSE:
|
if building == Buildings.HOUSE:
|
||||||
self._free_housing()
|
self._free_housing()
|
||||||
|
|
||||||
if building == Buildings.FARM:
|
if building == Buildings.FARM:
|
||||||
# assumes a farm is worked nonstop after being built
|
self._add_farm()
|
||||||
update_time = self.current_time + times.exist_duration[Buildings.FARM]
|
elif building in self.gather.buildings_gather_increase.keys():
|
||||||
self.events[update_time].append((self._remove_farm, ()))
|
self._improve_gather_rate(building, tasks_improved)
|
||||||
|
|
||||||
|
for task, count in villagers_to.items():
|
||||||
|
self.assign_villager(Tasks.BUILD, task, count)
|
||||||
|
|
||||||
|
|
||||||
|
def _add_farm(self):
|
||||||
|
self.buildings.append(Buildings.FARM)
|
||||||
|
new_farm_value = self.gather.buildings_gather_increase[Buildings.FARM][Tasks.FARM]
|
||||||
|
heapq.heappush(self.unused_farms, new_farm_value)
|
||||||
|
|
||||||
|
|
||||||
def _remove_farm(self):
|
def _remove_farm(self):
|
||||||
self.buildings.remove(Buildings.FARM)
|
self.buildings.remove(Buildings.FARM)
|
||||||
|
heapq.heappop(self.farms)
|
||||||
|
|
||||||
num_farms = self.buildings.count(Buildings.FARM)
|
if self.villager_tasks[Tasks.FARM] > len(self.farms):
|
||||||
if self.villager_tasks[Tasks.FARM] > num_farms:
|
self.assign_villager(Tasks.FARM, Tasks.IDLE)
|
||||||
self.villager_tasks[Tasks.FARM] -= 1
|
|
||||||
self.villager_tasks[Tasks.IDLE] += 1
|
|
||||||
|
|
||||||
|
|
||||||
def advance_by_time(self, duration):
|
def advance_by_time(self, duration):
|
||||||
@@ -277,6 +374,9 @@ class World():
|
|||||||
|
|
||||||
|
|
||||||
def _check_gather(self, task, count=1):
|
def _check_gather(self, task, count=1):
|
||||||
|
if task in [Tasks.IDLE, Tasks.BUILD]:
|
||||||
|
return
|
||||||
|
|
||||||
required = self.gather.required_gather_buildings[task]
|
required = self.gather.required_gather_buildings[task]
|
||||||
have = set(self.buildings)
|
have = set(self.buildings)
|
||||||
|
|
||||||
@@ -286,8 +386,7 @@ class World():
|
|||||||
raise GatherBuildingNotAvailableException
|
raise GatherBuildingNotAvailableException
|
||||||
|
|
||||||
if task == task.FARM:
|
if task == task.FARM:
|
||||||
num_farms = self.buildings.count(Buildings.FARM)
|
if self.villager_tasks[task] + count > len(self.unused_farms):
|
||||||
if self.villager_tasks[task] + count > num_farms:
|
|
||||||
raise GatherBuildingNotAvailableException
|
raise GatherBuildingNotAvailableException
|
||||||
|
|
||||||
|
|
||||||
@@ -315,9 +414,7 @@ class World():
|
|||||||
self.population -= 1
|
self.population -= 1
|
||||||
|
|
||||||
if unit == Units.VILLAGER:
|
if unit == Units.VILLAGER:
|
||||||
self.villagers -= 1
|
self.assign_villager(task, None)
|
||||||
self.villager_tasks[task] -= 1
|
|
||||||
self._update_gather_rate(task)
|
|
||||||
|
|
||||||
self._free_housing()
|
self._free_housing()
|
||||||
|
|
||||||
@@ -359,9 +456,10 @@ class World():
|
|||||||
|
|
||||||
def _gather_resources_for_duration(self, duration):
|
def _gather_resources_for_duration(self, duration):
|
||||||
for task, count in self.villager_tasks.items():
|
for task, count in self.villager_tasks.items():
|
||||||
gather_rates = self.gather.gather_rates[task]
|
rate = self.gather.gather_rates[task]
|
||||||
for resource, rate in gather_rates.items():
|
gathered = self.gather.gathered_resources[task]
|
||||||
self.resources[resource] += count*rate*duration
|
for resource, mult in gathered.items():
|
||||||
|
self.resources[resource] += count*rate*mult*duration
|
||||||
|
|
||||||
|
|
||||||
def _handle_events(self):
|
def _handle_events(self):
|
||||||
@@ -400,10 +498,14 @@ class World():
|
|||||||
self.resources = [200, 200, 100, 200]
|
self.resources = [200, 200, 100, 200]
|
||||||
self.research = []
|
self.research = []
|
||||||
self.buildings = [Buildings.TOWNCENTER]
|
self.buildings = [Buildings.TOWNCENTER]
|
||||||
|
self.farms = []
|
||||||
|
self.unused_farms = []
|
||||||
|
|
||||||
self.villager_tasks = {t: 0 for t in Tasks}
|
self.villager_tasks = {t: 0 for t in Tasks}
|
||||||
self.villager_tasks[Tasks.IDLE] = self.villagers
|
self.villager_tasks[Tasks.IDLE] = self.villagers
|
||||||
|
|
||||||
|
self.rate_decrease_event_times = {t: None for t in Tasks}
|
||||||
|
|
||||||
self.units_being_built = 0
|
self.units_being_built = 0
|
||||||
self.production_available = {b: 0 for b in Buildings}
|
self.production_available = {b: 0 for b in Buildings}
|
||||||
self.production_available[Buildings.TOWNCENTER] = 1
|
self.production_available[Buildings.TOWNCENTER] = 1
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ from aoe.gameplay import *
|
|||||||
# TODO need some warning, penalty, etc. if villager is assigned to task
|
# TODO need some warning, penalty, etc. if villager is assigned to task
|
||||||
# without appropriate building (e.g., mining with no camp)
|
# without appropriate building (e.g., mining with no camp)
|
||||||
# TODO building time assumes just 1 vil is working for now
|
# TODO building time assumes just 1 vil is working for now
|
||||||
|
# TODO need efficiency penalties on, e.g., "stale" lumber camp
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Set up world
|
# Set up world
|
||||||
@@ -39,6 +41,9 @@ game.build(45, Buildings.LUMBERCAMP, Tasks.WOOD, Tasks.WOOD)
|
|||||||
game.queue_villager(45, Tasks.WOOD)
|
game.queue_villager(45, Tasks.WOOD)
|
||||||
game.queue_villager(45, Tasks.WOOD)
|
game.queue_villager(45, Tasks.WOOD)
|
||||||
|
|
||||||
|
game.play_until(46)
|
||||||
|
game.print_status()
|
||||||
|
|
||||||
game.build(100, Buildings.HOUSE, [Tasks.SHEEP, Tasks.WOOD], [Tasks.IDLE]*2)
|
game.build(100, Buildings.HOUSE, [Tasks.SHEEP, Tasks.WOOD], [Tasks.IDLE]*2)
|
||||||
#game.assign_villager(Build.Stable(), "food", wait_until_finished=True)
|
#game.assign_villager(Build.Stable(), "food", wait_until_finished=True)
|
||||||
|
|
||||||
@@ -46,11 +51,13 @@ game.build(100, Buildings.HOUSE, [Tasks.SHEEP, Tasks.WOOD], [Tasks.IDLE]*2)
|
|||||||
game.queue_unit(s1, Scout)
|
game.queue_unit(s1, Scout)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
game.assign_villager(130, Tasks.IDLE, Tasks.WOOD, 2)
|
||||||
|
|
||||||
# should generate exception
|
# should generate exception
|
||||||
#game.build(200, Buildings.FARM, [Tasks.SHEEP], [Tasks.WOOD])
|
#game.build(200, Buildings.FARM, [Tasks.SHEEP], [Tasks.WOOD])
|
||||||
|
|
||||||
game.build(200, Buildings.MILL, [Tasks.SHEEP], [Tasks.WOOD])
|
game.build(200, Buildings.MILL, [Tasks.SHEEP], [Tasks.WOOD])
|
||||||
game.build(250, Buildings.FARM, [Tasks.SHEEP, Tasks.WOOD], [Tasks.IDLE])
|
game.build(250, Buildings.FARM, [Tasks.SHEEP, Tasks.WOOD], [Tasks.IDLE]*2)
|
||||||
|
|
||||||
# Evaluate game
|
# Evaluate game
|
||||||
game.play_until(800)
|
game.play_until(800)
|
||||||
|
|||||||
Reference in New Issue
Block a user