Skip to content

Commit 7e277a0

Browse files
committed
added cutoff options to playgame and ants, tracking winning and ranking turn in db
1 parent c02356f commit 7e277a0

10 files changed

+174
-106
lines changed

ants/ants.py

+67-38
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from random import randrange, choice, shuffle, randint, seed
33
from math import sqrt
44
import os
5-
from collections import deque, defaultdict
5+
from collections import deque, defaultdict, OrderedDict
66
from fractions import Fraction
77
import operator
88
import string
@@ -54,14 +54,16 @@ def __init__(self, options=None):
5454
if type(self.food_visible) in (list, tuple):
5555
self.food_visible = randrange(*self.food_visible)
5656
self.food_extra = Fraction(0,1)
57+
58+
self.cutoff_percent = options.get('cutoff_percent', 0.90)
59+
self.cutoff_turn = options.get('cutoff_turn', 100)
5760

5861
self.do_attack = {
59-
'occupied': self.do_attack_power,
60-
'power': self.do_attack_power,
62+
'focus': self.do_attack_focus,
6163
'closest': self.do_attack_closest,
6264
'support': self.do_attack_support,
6365
'damage': self.do_attack_damage
64-
}.get(options.get('attack'), self.do_attack_power)
66+
}.get(options.get('attack'), self.do_attack_focus)
6567

6668
self.do_food = {
6769
'none': self.do_food_none,
@@ -86,13 +88,17 @@ def __init__(self, options=None):
8688
self.score = [Fraction(0,1)]*self.num_players
8789
self.score_history = [[s] for s in self.score]
8890
self.bonus = [0 for s in self.score]
89-
self.pop_count = {i: 0 for i in range(self.num_players)}
90-
self.pop_count[FOOD] = 0
91-
self.winning_ant = LAND # Can be ant owner, FOOD or LAND
92-
self.winning_turns = 0
93-
self.cutoff_percent = 0.90
94-
self.cutoff_turns = 100
95-
91+
92+
# used to cutoff games early
93+
self.cutoff_bot = LAND # Can be ant owner, FOOD or LAND
94+
self.cutoff_turns = 0
95+
# used to calculate the turn when the winner took the lead
96+
self.winning_bot = None
97+
self.winning_turn = 0
98+
# used to calculate when the player rank last changed
99+
self.ranking_bots = None
100+
self.ranking_turn = 0
101+
96102
# initialise size
97103
self.height, self.width = map_data['size']
98104
self.land_area = self.height*self.width - len(map_data['water'])
@@ -766,12 +772,12 @@ def do_attack_support(self):
766772
for enemy in enemies:
767773
self.score[enemy.owner] += Fraction(1, score_share)
768774

769-
def do_attack_power(self):
775+
def do_attack_focus(self):
770776
""" Kill ants which are the most surrounded by enemies
771777
772-
For a given ant define: Power = 1/NumOpponents
778+
For a given ant define: Focus = 1/NumOpponents
773779
An ant's Opponents are enemy ants which are within the attackradius.
774-
Ant alive if its Power is greater than Power of any of his Opponents.
780+
Ant alive if its Focus is greater than Focus of any of his Opponents.
775781
If an ant dies 1 point is shared equally between its Opponents.
776782
"""
777783

@@ -783,12 +789,12 @@ def do_attack_power(self):
783789
# determine which ants to kill
784790
ants_to_kill = []
785791
for ant in self.current_ants.values():
786-
# determine this ants weakness (1/power)
792+
# determine this ants weakness (1/focus)
787793
weakness = len(nearby_enemies[ant])
788794
# an ant with no enemies nearby can't be attacked
789795
if weakness == 0:
790796
continue
791-
# determine the most powerful nearby enemy
797+
# determine the most focused nearby enemy
792798
min_enemy_weakness = min(len(nearby_enemies[enemy]) for enemy in nearby_enemies[ant])
793799
# ant dies if it is weak as or weaker than an enemy weakness
794800
if min_enemy_weakness <= weakness:
@@ -1113,7 +1119,7 @@ def game_over(self):
11131119
"""
11141120
if self.remaining_players() <= 1:
11151121
return True
1116-
if self.winning_turns >= self.cutoff_turns:
1122+
if self.cutoff_turns >= self.cutoff_turn:
11171123
return True
11181124
return False
11191125

@@ -1134,11 +1140,13 @@ def finish_game(self):
11341140
""" Called by engine at the end of the game """
11351141
players = [p for p in range(self.num_players) if self.is_alive(p)]
11361142

1137-
# food bonus is split between the surviving players
1138-
# if there is more than one player, then the game either
1139-
# went the full length and there is no bonus
1140-
# or the game was cut off
1141-
if self.turn < self.turns:
1143+
# if there is exactly one player remaining they get food bonus
1144+
# if the game ends early there is no bonus
1145+
# the bonus only exists to ensure a lone survivor the best chance at winning
1146+
# simply removing the bonus is the best disincentive to not ending the game
1147+
# games ending do to inactivity would not have the player rank changed by splitting the bonus
1148+
if len(players) == 1:
1149+
player = players[0]
11421150
# the food bonus represents the maximum points a bot can get with perfect play
11431151
# remaining food and food to be spawned would be 1 point
11441152
# either from collecting the food and spawning an ant
@@ -1150,12 +1158,11 @@ def finish_game(self):
11501158
+ self.food_extra
11511159
+ len(self.current_food) # food that hasn't been collected
11521160
# enemy ants (player ants already received point when spawned)
1153-
+ len([ant for ant in self.current_ants.values() if not ant.owner in players])
1161+
+ len([ant for ant in self.current_ants.values() if ant.owner != player])
11541162
)
1155-
for player in players:
1156-
self.score[player] += food_bonus // len(players)
1157-
# separate bonus from score history
1158-
self.bonus[player] = food_bonus // len(players)
1163+
self.score[player] += food_bonus
1164+
# separate bonus from score history
1165+
self.bonus[player] = food_bonus
11591166

11601167
def start_turn(self):
11611168
""" Called by engine at the start of the turn """
@@ -1202,17 +1209,27 @@ def finish_turn(self):
12021209
pop_total = sum(pop_count.values())
12031210
for owner, count in pop_count.items():
12041211
if count >= pop_total * self.cutoff_percent:
1205-
if self.winning_ant == owner:
1206-
self.winning_turns += 1
1212+
if self.cutoff_bot == owner:
1213+
self.cutoff_turns += 1
12071214
else:
1208-
self.winning_ant = owner
1209-
self.winning_turns = 1
1215+
self.cutoff_bot = owner
1216+
self.cutoff_turns = 1
12101217
break
12111218
else:
1212-
self.winning_ant = LAND
1213-
self.winning_turns = 0
1214-
self.pop_count = pop_count
1215-
1219+
self.cutoff_bot = LAND
1220+
self.cutoff_turns = 0
1221+
1222+
scores = [int(score) for score in self.score]
1223+
ranking_bots = [sorted(set(scores), reverse=True).index(x) for x in scores]
1224+
if self.ranking_bots != ranking_bots:
1225+
self.ranking_turn = self.turn
1226+
self.ranking_bots = ranking_bots
1227+
1228+
winning_bot = [p for p in range(len(scores)) if scores[p] == max(scores)]
1229+
if self.winning_bot != winning_bot:
1230+
self.winning_turn = self.turn
1231+
self.winning_bot = winning_bot
1232+
12161233
def get_state(self):
12171234
""" Get all state changes
12181235
@@ -1308,9 +1325,18 @@ def get_stats(self):
13081325
ant_count = [0 for i in range(self.num_players+1)]
13091326
for loc, ant in self.current_ants.items():
13101327
ant_count[ant.owner] += 1
1311-
ant_count[-1] = len(self.current_food)
1312-
return {'ant_count': ant_count, 'winning_ant': self.winning_ant, 'winning_turns': self.winning_turns}
1313-
1328+
stats = OrderedDict()
1329+
stats['ant_count'] = ant_count
1330+
stats['food'] = len(self.current_food)
1331+
stats['cutoff'] = 'Food' if self.cutoff_bot == FOOD else '-' if self.cutoff_bot == LAND else self.cutoff_bot
1332+
stats['c_turns'] = self.cutoff_turns
1333+
stats['winning'] = self.winning_bot
1334+
stats['w_turn'] = self.winning_turn
1335+
stats['ranking_bots'] = self.ranking_bots
1336+
stats['r_turn'] = self.ranking_turn
1337+
stats['score'] = map(int, self.score)
1338+
return stats
1339+
13141340
def get_replay(self):
13151341
""" Return a summary of the entire game
13161342
@@ -1368,6 +1394,9 @@ def get_replay(self):
13681394
# score_history contains Fraction objects, so round down with int function
13691395
replay['scores'] = [map(int, s) for s in self.score_history]
13701396
replay['bonus'] = map(int, self.bonus)
1397+
replay['winning_turn'] = self.winning_turn
1398+
replay['ranking_turn'] = self.ranking_turn
1399+
replay['cutoff'] = bool(self.remaining_players() <= 1 and self.turns > self.turn)
13711400

13721401
return replay
13731402

ants/play_one_game.cmd

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
@echo off
2-
python %~dp0playgame.py --player_seed 42 --end_wait=0.25 --verbose --log_dir game_logs --turns 1000 --map_file %~dp0maps\symmetric_maps\symmetric_10.map %* "python %~dp0dist\sample_bots\python\HoldBot.py" "python %~dp0dist\sample_bots\python\HoldBot.py" "python %~dp0dist\sample_bots\python\HunterBot.py" "python %~dp0dist\sample_bots\python\LeftyBot.py"
2+
python %~dp0playgame.py --engine_seed 42 --player_seed 42 --end_wait=0.25 --verbose --log_dir game_logs --turns 1000 --map_file %~dp0maps\symmetric_maps\symmetric_10.map %* "python %~dp0dist\sample_bots\python\HunterBot.py" "python %~dp0dist\sample_bots\python\LeftyBot.py" "python %~dp0dist\sample_bots\python\HunterBot.py" "python %~dp0dist\sample_bots\python\LeftyBot.py"
33

ants/playgame.py

+71-49
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import sys
55
import os
66
import time
7-
from optparse import OptionParser
7+
from optparse import OptionParser, OptionGroup
88
import random
99
import cProfile
1010
import visualizer.visualize_locally
@@ -133,21 +133,35 @@ def main(argv):
133133
help='Player position for first bot specified')
134134

135135
# ants specific game options
136-
parser.add_option("--attack", dest="attack",
137-
default="occupied",
138-
help="Attack method to use for engine. (closest, occupied, support, damage)")
139-
parser.add_option("--food", dest="food",
140-
default="symmetric",
141-
help="Food spawning method. (none, random, sections, symmetric)")
142-
parser.add_option("--viewradius2", dest="viewradius2",
143-
default=55, type="int",
144-
help="Vision radius of ants squared")
145-
parser.add_option("--spawnradius2", dest="spawnradius2",
146-
default=1, type="int",
147-
help="Spawn radius of ants squared")
148-
parser.add_option("--attackradius2", dest="attackradius2",
149-
default=5, type="int",
150-
help="Attack radius of ants squared")
136+
game_group = OptionGroup(parser, "Game Options", "Options that affect the game mechanics for ants")
137+
game_group.add_option("--attack", dest="attack",
138+
default="focus",
139+
help="Attack method to use for engine. (closest, focus, support, damage)")
140+
game_group.add_option("--food", dest="food",
141+
default="symmetric",
142+
help="Food spawning method. (none, random, sections, symmetric)")
143+
game_group.add_option("--viewradius2", dest="viewradius2",
144+
default=55, type="int",
145+
help="Vision radius of ants squared")
146+
game_group.add_option("--spawnradius2", dest="spawnradius2",
147+
default=1, type="int",
148+
help="Spawn radius of ants squared")
149+
game_group.add_option("--attackradius2", dest="attackradius2",
150+
default=5, type="int",
151+
help="Attack radius of ants squared")
152+
game_group.add_option("--food_rate", dest="food_rate", nargs=2, type="int", default=(2,8),
153+
help="Numerator of food per turn per player rate")
154+
game_group.add_option("--food_turn", dest="food_turn", nargs=2, type="int", default=(12,30),
155+
help="Denominator of food per turn per player rate")
156+
game_group.add_option("--food_start", dest="food_start", nargs=2, type="int", default=(75,175),
157+
help="One over percentage of land area filled with food at start")
158+
game_group.add_option("--food_visible", dest="food_visible", nargs=2, type="int", default=(1,3),
159+
help="Amount of food guaranteed to be visible to starting ants")
160+
game_group.add_option("--cutoff_turn", dest="cutoff_turn", type="int", default=100,
161+
help="Number of turns cutoff percentage is maintained to end game early")
162+
game_group.add_option("--cutoff_percent", dest="cutoff_percent", type="float", default=0.90,
163+
help="Number of turns cutoff percentage is maintained to end game early")
164+
parser.add_option_group(game_group)
151165

152166
# the log directory must be specified for any logging to occur, except:
153167
# bot errors to stderr
@@ -156,43 +170,45 @@ def main(argv):
156170
# the log directory will contain
157171
# the replay or stream file used by the visualizer, if requested
158172
# the bot input/output/error logs, if requested
159-
parser.add_option("-g", "--game", dest="game_id", default=0, type='int',
160-
help="game id to start at when numbering log files")
161-
parser.add_option("-l", "--log_dir", dest="log_dir", default=None,
162-
help="Directory to dump replay files to.")
163-
parser.add_option('-R', '--log_replay', dest='log_replay',
164-
action='store_true', default=False),
165-
parser.add_option('-S', '--log_stream', dest='log_stream',
166-
action='store_true', default=False),
167-
parser.add_option("-I", "--log_input", dest="log_input",
168-
action="store_true", default=False,
169-
help="Log input streams sent to bots")
170-
parser.add_option("-O", "--log_output", dest="log_output",
171-
action="store_true", default=False,
172-
help="Log output streams from bots")
173-
parser.add_option("-E", "--log_error", dest="log_error",
174-
action="store_true", default=False,
175-
help="log error streams from bots")
176-
parser.add_option('-e', '--log_stderr', dest='log_stderr',
177-
action='store_true', default=False,
178-
help='additionally log bot errors to stderr')
179-
parser.add_option('-o', '--log_stdout', dest='log_stdout',
180-
action='store_true', default=False,
181-
help='additionally log replay/stream to stdout')
173+
log_group = OptionGroup(parser, "Logging Options", "Options that control the logging")
174+
log_group.add_option("-g", "--game", dest="game_id", default=0, type='int',
175+
help="game id to start at when numbering log files")
176+
log_group.add_option("-l", "--log_dir", dest="log_dir", default=None,
177+
help="Directory to dump replay files to.")
178+
log_group.add_option('-R', '--log_replay', dest='log_replay',
179+
action='store_true', default=False),
180+
log_group.add_option('-S', '--log_stream', dest='log_stream',
181+
action='store_true', default=False),
182+
log_group.add_option("-I", "--log_input", dest="log_input",
183+
action="store_true", default=False,
184+
help="Log input streams sent to bots")
185+
log_group.add_option("-O", "--log_output", dest="log_output",
186+
action="store_true", default=False,
187+
help="Log output streams from bots")
188+
log_group.add_option("-E", "--log_error", dest="log_error",
189+
action="store_true", default=False,
190+
help="log error streams from bots")
191+
log_group.add_option('-e', '--log_stderr', dest='log_stderr',
192+
action='store_true', default=False,
193+
help='additionally log bot errors to stderr')
194+
log_group.add_option('-o', '--log_stdout', dest='log_stdout',
195+
action='store_true', default=False,
196+
help='additionally log replay/stream to stdout')
182197
# verbose will not print bot input/output/errors
183198
# only info+debug will print bot error output
184-
parser.add_option("-v", "--verbose", dest="verbose",
185-
action='store_true', default=False,
186-
help="Print out status as game goes.")
187-
parser.add_option("--profile", dest="profile",
188-
action="store_true", default=False,
189-
help="Run under the python profiler")
199+
log_group.add_option("-v", "--verbose", dest="verbose",
200+
action='store_true', default=False,
201+
help="Print out status as game goes.")
202+
log_group.add_option("--profile", dest="profile",
203+
action="store_true", default=False,
204+
help="Run under the python profiler")
190205
parser.add_option("--nolaunch", dest="nolaunch",
191206
action='store_true', default=False,
192207
help="Prevent visualizer from launching")
193-
parser.add_option("--html", dest="html_file",
194-
default=None,
195-
help="Output file name for an html replay")
208+
log_group.add_option("--html", dest="html_file",
209+
default=None,
210+
help="Output file name for an html replay")
211+
parser.add_option_group(log_group)
196212

197213
(opts, args) = parser.parse_args(argv)
198214
if opts.map is None or not os.path.exists(opts.map):
@@ -253,7 +269,13 @@ def get_cmd_name(cmd):
253269
"turntime": opts.turntime,
254270
"turns": opts.turns,
255271
"player_seed": opts.player_seed,
256-
"engine_seed": opts.engine_seed }
272+
"engine_seed": opts.engine_seed,
273+
"food_rate": opts.food_rate,
274+
"food_turn": opts.food_turn,
275+
"food_start": opts.food_start,
276+
"food_visible": opts.food_visible,
277+
"cutoff_turn": opts.cutoff_turn,
278+
"cutoff_percent": opts.cutoff_percent }
257279
engine_options = {
258280
"loadtime": opts.loadtime,
259281
"turntime": opts.turntime,

setup/server_info.php.template

+5-3
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ $server_info = array(
1616
"api_create_key" => "",
1717
"api_log" => "{log_dir}/api_log.txt",
1818
"game_options" => array (
19-
"turns" => 2000,
19+
"turns" => 1500,
2020
"loadtime" => 3000,
2121
"turntime" => 500,
22-
"viewradius2" => 55,
22+
"viewradius2" => 77,
2323
"attackradius2" => 5,
2424
"spawnradius2" => 1,
2525
"location" => "{api_url}",
@@ -29,7 +29,9 @@ $server_info = array(
2929
"food_start" => array(75,175),
3030
"food_visible" => array(1,3),
3131
"food" => "symmetric",
32-
"attack" => "power"
32+
"attack" => "focus",
33+
"cutoff_turn" => 100,
34+
"cutoff_percent" => 0.90
3335
)
3436
);
3537

0 commit comments

Comments
 (0)