Deal with farms and varying gather rates
This commit is contained in:
184
aoe/world.py
184
aoe/world.py
@@ -1,4 +1,6 @@
|
||||
from collections import defaultdict, Counter
|
||||
import heapq
|
||||
import math
|
||||
|
||||
from aoe.exceptions import *
|
||||
from aoe.costs import *
|
||||
@@ -35,15 +37,21 @@ class World():
|
||||
# and, if executed successfully, it adds another event to the queue that
|
||||
# actually adds the unit to the world
|
||||
|
||||
# TODO farms, reseeding, etc.
|
||||
# _check_resource_building, _use_resource_building, _free_resource_building
|
||||
# TODO sheep should die like farms
|
||||
# TODO need some warning, penalty, etc. if villager is assigned to task
|
||||
# without appropriate building (e.g., mining with no camp)
|
||||
# TODO need efficiency penalties on, e.g., lumber camp
|
||||
# too many vils/camp decreases rate
|
||||
# "stale" (old) camp decreases rate
|
||||
# For now, we assume just 1 villager can work a farm at a time.
|
||||
# This shouldn't be a huge deal to change later.
|
||||
# We also assume that a player makes optimal choices about farms --
|
||||
# villagers are removed from farms with the lowest capacity first and added
|
||||
# to farms with the highest capacity.
|
||||
# We keep track of only the closest upcoming farm deletion.
|
||||
|
||||
# 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
|
||||
@@ -102,23 +110,107 @@ class World():
|
||||
pass
|
||||
|
||||
|
||||
def _update_gather_rate(self, task):
|
||||
pass # TODO
|
||||
#num_farms = self.buildings.count(Buildings.FARM)
|
||||
#num_workers = self.villager_tasks[task]
|
||||
#villager_room =
|
||||
def _schedule_next_gather_rate_decrease(self, task):
|
||||
if task in [Tasks.IDLE, Tasks.BUILD]:
|
||||
return
|
||||
|
||||
# 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):
|
||||
# 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:
|
||||
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[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):
|
||||
@@ -129,11 +221,10 @@ class World():
|
||||
|
||||
def _move_villagers_to_build(self, villagers_from_count):
|
||||
for task, count in villagers_from_count.items():
|
||||
self.villager_tasks[task] -= count
|
||||
self.villager_tasks[Tasks.BUILD] += count
|
||||
self.assign_villager(task, 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:
|
||||
villagers_from = [villagers_from]
|
||||
if type(villagers_to) == Tasks:
|
||||
@@ -158,33 +249,39 @@ class World():
|
||||
update_time = self.current_time + build_time
|
||||
|
||||
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._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:
|
||||
self._free_housing()
|
||||
|
||||
if building == Buildings.FARM:
|
||||
# assumes a farm is worked nonstop after being built
|
||||
update_time = self.current_time + times.exist_duration[Buildings.FARM]
|
||||
self.events[update_time].append((self._remove_farm, ()))
|
||||
self._add_farm()
|
||||
elif building in self.gather.buildings_gather_increase.keys():
|
||||
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):
|
||||
self.buildings.remove(Buildings.FARM)
|
||||
heapq.heappop(self.farms)
|
||||
|
||||
num_farms = self.buildings.count(Buildings.FARM)
|
||||
if self.villager_tasks[Tasks.FARM] > num_farms:
|
||||
self.villager_tasks[Tasks.FARM] -= 1
|
||||
self.villager_tasks[Tasks.IDLE] += 1
|
||||
if self.villager_tasks[Tasks.FARM] > len(self.farms):
|
||||
self.assign_villager(Tasks.FARM, Tasks.IDLE)
|
||||
|
||||
|
||||
def advance_by_time(self, duration):
|
||||
@@ -277,6 +374,9 @@ class World():
|
||||
|
||||
|
||||
def _check_gather(self, task, count=1):
|
||||
if task in [Tasks.IDLE, Tasks.BUILD]:
|
||||
return
|
||||
|
||||
required = self.gather.required_gather_buildings[task]
|
||||
have = set(self.buildings)
|
||||
|
||||
@@ -286,8 +386,7 @@ class World():
|
||||
raise GatherBuildingNotAvailableException
|
||||
|
||||
if task == task.FARM:
|
||||
num_farms = self.buildings.count(Buildings.FARM)
|
||||
if self.villager_tasks[task] + count > num_farms:
|
||||
if self.villager_tasks[task] + count > len(self.unused_farms):
|
||||
raise GatherBuildingNotAvailableException
|
||||
|
||||
|
||||
@@ -315,9 +414,7 @@ class World():
|
||||
self.population -= 1
|
||||
|
||||
if unit == Units.VILLAGER:
|
||||
self.villagers -= 1
|
||||
self.villager_tasks[task] -= 1
|
||||
self._update_gather_rate(task)
|
||||
self.assign_villager(task, None)
|
||||
|
||||
self._free_housing()
|
||||
|
||||
@@ -359,9 +456,10 @@ class World():
|
||||
|
||||
def _gather_resources_for_duration(self, duration):
|
||||
for task, count in self.villager_tasks.items():
|
||||
gather_rates = self.gather.gather_rates[task]
|
||||
for resource, rate in gather_rates.items():
|
||||
self.resources[resource] += count*rate*duration
|
||||
rate = self.gather.gather_rates[task]
|
||||
gathered = self.gather.gathered_resources[task]
|
||||
for resource, mult in gathered.items():
|
||||
self.resources[resource] += count*rate*mult*duration
|
||||
|
||||
|
||||
def _handle_events(self):
|
||||
@@ -400,10 +498,14 @@ class World():
|
||||
self.resources = [200, 200, 100, 200]
|
||||
self.research = []
|
||||
self.buildings = [Buildings.TOWNCENTER]
|
||||
self.farms = []
|
||||
self.unused_farms = []
|
||||
|
||||
self.villager_tasks = {t: 0 for t in Tasks}
|
||||
self.villager_tasks[Tasks.IDLE] = self.villagers
|
||||
|
||||
self.rate_decrease_event_times = {t: None for t in Tasks}
|
||||
|
||||
self.units_being_built = 0
|
||||
self.production_available = {b: 0 for b in Buildings}
|
||||
self.production_available[Buildings.TOWNCENTER] = 1
|
||||
|
||||
Reference in New Issue
Block a user