危险区
用Python编写,并与Sparr编写的非Java代码包装器接口。
用纯Python进行所有数学运算,并且完全没有优化。有点慢
高度可配置和可扩展。
对过去的提交表现很好。对于或,每输掉一场,就赢得2:1的战斗,并赢得与其他竞争者的所有战斗的98%以上的战斗。Crossfire
PredictAndAvoid
包括自己的可选可视化工具:
战斗Crossfire
/ PredictAndAvoid
,在周围的体积中可视化同名危险区域等级:
性能:
在排行榜上排名前八的其他算法进行1000次回合:
SCORE: DumbPlanes: 0 Dangerzoner: 1000
SCORE: Crossfire: 132 Dangerzoner: 367
SCORE: PredictAndAvoid: 165 Dangerzoner: 465
SCORE: Wee: 0 Dangerzoner: 1000
SCORE: Whirligig: 0 Dangerzoner: 989
SCORE: MoveAndShootPlane: 0 Dangerzoner: 1000
SCORE: Starfox: 4 Dangerzoner: 984
SCORE: DumbPy: 0 Dangerzoner: 1000
SCORES:
DumbPlanes: 2 points.
Crossfire: 12 points.
PredictAndAvoid: 14 points.
Wee: 10 points.
Whirligig: 8 points.
MoveAndShootPlane: 6 points.
Starfox: 4 points.
DumbPy: 0 points.
Dangerzoner: 16 points.
THE OVERALL WINNER(S): Dangerzoner
With 16 points.
码:
#!/usr/bin/env python3
"""
DangerZoner
Each turn:
1) Make a list of all possible locations to move to, explicitly excluding suicidal positions that will collide with the walls, an ally, or an ally's bullet.
2) Rate each possible location using heuristics that estimate the approximate danger in that zone, accounting for the following factors:
-Proximity to walls. (Manoeuvring constrictions and risk of collision.)
-Proximity to fronts of planes. (Risk of mid-air collisions.)
-High distance from enemy planes. (Risk of enemies easily turning to shoot.)
-Intersection with all enemy attack vectors. (Explicit safety on the next round.)
-Proximity to enemy forward vectors. (Approximate probability of being targeted in upcoming rounds.)
3) If certain respective thresholds are met in the possible moves' danger ratings, then do the following if possible:
-Take a potshot at a random position that an enemy might move to next turn (but never shoot an ally).
-Take a potshot at an extrapolated position that an enemy will likely move to next turn if they keep up their current rate of turn (but never shoot an ally).
-Turn to pursue the closest enemy.
-Move randomly to confound enemy predictive mechanisms. (Disabled since implementing explicit enemy attack vectors in danger zone calculation.)
4) If none of those thresholds are met, then choose the move rated as least dangerous.
"""
import math, random, functools, sys
#import NGrids
NGrids = lambda: None
class NSpace(object):
"""Object for representing an n-dimensional space parameterized by a list of extents in each dimension."""
def __init__(self, dimensions):
self.dimensions = tuple(dimensions)
def check_coordshape(self, coord):
return len(coord) == len(self.dimensions)
def enforce_coordshape(self, coord):
if not self.check_coordshape(coord):
raise ValueError(f"Attempted to access {len(coord)}-coordinate point from {len(self.dimensions)}-coordinate space: {coord}")
def check_coordrange(self, coord):
return all((0 <= c <= b) for c, b in zip(coord, self.dimensions))
def enforce_coordrange(self, coord):
if not self.check_coordrange(coord):
raise ValueError(f"Attempted to access coordinate point out of range of {'x'.join(str(d) for d in self.dimensions)} space: {coord}")
def check_coordtype(self, coord):
return True
def enforce_coordtype(self, coord):
if not self.check_coordtype(coord):
raise TypeError(f"Attempted to access grid point with invalid coordinates for {type(self).__name__}(): {coord}")
def enforce_coord(self, coord):
for f in (self.enforce_coordshape, self.enforce_coordrange, self.enforce_coordtype):
f(coord)
def coords_grid(self, step=None):
if step is None:
step = tuple(1 for i in self.dimensions)
self.enforce_coord(step)
counts = [math.ceil(d/s) for d, s in zip(self.dimensions, step)]
intervals = [1]
for c in counts:
intervals.append(intervals[-1]*c)
for i in range(intervals[-1]):
yield tuple((i//l)*s % (c*s) for s, l, c in zip(step, intervals, counts))
NGrids.NSpace = NSpace
def Pythagorean(*coords):
return math.sqrt(sum(c**2 for c in coords))
class Plane(object):
"""Object for representing a single dogfighting plane."""
def __init__(self, alive, coord, vec, cooldown=None, name=None):
self.alive = alive
self.set_alive(alive)
self.coord = coord
self.set_coord(coord)
self.vec = vec
self.set_vec(vec)
self.cooldown = cooldown
self.set_cooldown(cooldown)
self.name = name
def set_alive(self, alive):
self.lastalive = self.alive
self.alive = alive
def set_coord(self, coord):
self.lastcoord = self.coord
self.coord = coord
def set_vec(self, vec):
self.lastvec = self.vec
self.vec = vec
def set_cooldown(self, cooldown):
self.lastcooldown = self.cooldown
self.cooldown = cooldown
def update(self, alive=None, coord=None, vec=None, cooldown=None):
if alive is not None:
self.set_alive(alive)
if coord is not None:
self.set_coord(coord)
if vec is not None:
self.set_vec(vec)
if cooldown is not None:
self.set_cooldown(cooldown)
def get_legalvecs(self):
return getNeighbouringVecs(self.vec)
def get_legalcoords(self):
return {tuple(self.coord[i]+v for i, v in enumerate(vec)) for vec in self.get_legalvecs()}
def get_legalfutures(self):
return (lambda r: r.union((c, self.vec) for c, v in r))({(vecAdd(self.coord, vec),vec) for vec in self.get_legalvecs()})
class DangerZones(NGrids.NSpace):
"""Arena object for representing an n-dimensional volume with both enemy and allied planes in it and estimating the approximate safety/danger of positions within it. """
def __init__(self, dimensions=(13,13,13), walldanger=18.0, walldistance=3.5, wallexpo=2.0, walluniformity=5.0, planedanger=8.5, planeexpo=8.0, planeoffset=1.5, planedistance=15.0, planedistancedanger=2.0, planedistanceexpo=1.5, firedanger=9.0, collisiondanger=10.0, collisiondirectionality=0.6, collisiondistance=2.5, collisionexpo=0.2):
NGrids.NSpace.__init__(self, dimensions)
self.walldanger = walldanger
self.walldistance = walldistance
self.wallexpo = wallexpo
self.walluniformity = walluniformity
self.planedanger = planedanger
self.planeexpo = planeexpo
self.planeoffset = planeoffset
self.planedistance = planedistance
self.planedistancedanger = planedistancedanger
self.planedistanceexpo = planedistanceexpo
self.firedanger = firedanger
self.collisiondanger = collisiondanger
self.collisiondirectionality = collisiondirectionality
self.collisiondistance = collisiondistance
self.collisionexpo = collisionexpo
self.set_planes()
self.set_allies()
self.clear_expectedallies()
def filteractiveplanes(self, planes=None):
if planes is None:
planes = self.planes
return (p for p in planes if all((p.alive, p.coord, p.vec)))
def rate_walldanger(self, coord):
self.enforce_coordshape(coord)
return (lambda d: (max(d)*self.walluniformity+sum(d))/(self.walluniformity+1))((1-min(1, (self.dimensions[i]/2-abs(v-self.dimensions[i]/2))/self.walldistance)) ** self.wallexpo * self.walldanger for i, v in enumerate(coord))
def rate_planedanger(self, coord, planecoord, planevec):
for v in (planecoord, planevec, coord):
self.enforce_coordshape(v)
return max(0, (1 - vecAngle(planevec, vecSub(coord, vecSub(planecoord, vecMult(planevec, (self.planeoffset,)*len(self.dimensions)))) ) / math.pi)) ** self.planeexpo * self.planedanger
offsetvec = convertVecTrinary(planevec, length=self.planeoffset)
relcoord = [v-(planecoord[i]-offsetvec[i]) for i, v in enumerate(coord)]
nrelcoord = (lambda m: [(v/m if m else 0) for v in relcoord])(Pythagorean(*relcoord))
planevec = (lambda m: [(v/m if m else 0) for v in planevec])(Pythagorean(*planevec))
return max(0, sum(d*p for d, p in zip(planevec, nrelcoord))+2)/2 ** self.planeexpo * self.planedanger + min(1, Pythagorean(*relcoord)/self.planedistance) ** self.planedistanceexpo * self.planedistancedanger
def rate_planedistancedanger(self, coord, planecoord, planevec):
return Pythagorean(*vecSub(planecoord, coord))/self.planedistance ** self.planedistanceexpo * self.planedistancedanger
def rate_firedanger(self, coord, plane):
return (min(vecAngle(vecSub(coord, c), v) for c, v in plane.get_legalfutures()) < 0.05) * self.firedanger
def rate_collisiondanger(self, coord, planecoord, planevec):
if coord == planecoord:
return self.collisiondanger
offsetvec = tuple(p-c for p,c in zip(planecoord, coord))
return max(0, vecAngle(planevec, offsetvec)/math.pi)**self.collisiondirectionality * max(0, 1-Pythagorean(*offsetvec)/self.collisiondistance)**self.collisionexpo*self.collisiondanger
def set_planes(self, *planes):
self.planes = planes
def set_allies(self, *allies):
self.allies = allies
def rate_planesdanger(self, coord, planes=None):
if planes is None:
planes = {*self.planes}
return max((0, *(self.rate_planedanger(coord, planecoord=p.coord, planevec=p.vec) for p in self.filteractiveplanes(planes))))
def rate_planedistancesdanger(self, coord, planes=None):
if planes is None:
planes = {*self.planes}
return max((0, *(self.rate_planedistancedanger(coord, planecoord=p.coord, planevec=p.vec) for p in self.filteractiveplanes(planes))))
def rate_firesdanger(self, coord, planes=None):
if planes is None:
planes = {*self.planes}
return sum(self.rate_firedanger(coord, p) for p in self.filteractiveplanes(planes))
def rate_collisionsdanger(self, coord, pself=None, planes=None):
if planes is None:
planes = {*self.planes, *self.allies}
return max((0, *(self.rate_collisiondanger(coord , planecoord=p.coord, planevec=p.vec) for p in self.filteractiveplanes(planes) if p is not pself)))
def rate_sumdanger(self, coord, pself=None, planes=None):
return max((self.rate_walldanger(coord), self.rate_planesdanger(coord, planes=planes), self.rate_planedistancesdanger(coord, planes=planes), self.rate_firesdanger(coord, planes=planes), self.rate_collisionsdanger(coord, pself=pself, planes=planes)))
def get_expectedallies(self):
return {*self.expectedallies}
def clear_expectedallies(self):
self.expectedallies = set()
def add_expectedallies(self, *coords):
self.expectedallies.update(coords)
def get_expectedshots(self):
return {*self.expectedshots}
def clear_expectedshots(self):
self.expectedshots = set()
def add_expectedshots(self, *rays):
self.expectedshots.update(rays)
def tickturn(self):
self.clear_expectedallies()
self.clear_expectedshots()
def stringException(exception):
import traceback
return ''.join(traceback.format_exception(type(exception), exception, exception.__traceback__))
try:
import matplotlib.pyplot, matplotlib.cm, mpl_toolkits.mplot3d, time
class PlottingDangerZones(DangerZones):
"""Arena object for calculating danger ratings and rendering 3D visualizations of the arena state and contents to both files and an interactive display on each turn."""
plotparams = {'dangersize': 80, 'dangersizebase': 0.2, 'dangersizeexpo': 2.0, 'dangeralpha': 0.2, 'dangerres': 1, 'dangervrange': (0, 10), 'dangercmap': matplotlib.cm.nipy_spectral, 'dangermarker': 'o', 'allymarker': 's', 'enemymarker': 'D', 'vectormarker': 'x', 'planesize': 60, 'vectorsize': 50, 'planecolour': 'black', 'deathmarker': '*', 'deathsize': 700, 'deathcolours': ('darkorange', 'red'), 'deathalpha': 0.65, 'shotlength': 4, 'shotcolour': 'darkviolet', 'shotstyle': 'dashed'}
enabledplots = ('enemies', 'allies', 'vectors', 'danger', 'deaths', 'shots', 'names')
def __init__(self, dimensions=(13,13,13), plotparams=None, plotautoturn=0, plotsavedir=None, enabledplots=None, disabledplots=None, tickwait=0.0, plotcycle=0.001, **kwargs):
DangerZones.__init__(self, dimensions, **kwargs)
self.figure = None
self.axes = None
self.frame = None
self.plotobjs = {}
self.plotshown = False
if plotparams:
self.set_plotparams(plotparams)
self.plotautoturn = plotautoturn
self.plotsavedir = plotsavedir
if enabledplots:
self.enabledplots = tuple(enabledplots)
if disabledplots:
self.enabledplots = tuple(m for m in self.enabledplots if m not in disabledplots)
self.tickwait = tickwait
self.plotcycle = plotcycle
self.lasttick = time.time()
def set_plotparams(self, plotparams):
self.plotparams = {**self.plotparams, **plotparams}
def prepare_plotaxes(self, figure=None, clear=True):
if self.figure is None and figure is None:
self.figure = matplotlib.pyplot.figure()
self.frame = 0
if self.axes is None:
self.axes = self.figure.add_subplot(projection='3d')
elif clear:
self.axes.clear()
for d, h in zip((self.axes.set_xlim, self.axes.set_ylim, self.axes.set_zlim), self.dimensions):
d(0, h)
return (self.figure, self.axes)
def plotter(kind):
def plotterd(funct):
def plott(self):
kws = dict(getattr(self, funct.__name__.replace('plot_', 'plotparams_'))())
if '*args' in kws:
args = tuple(kws.pop('*args'))
else:
args = tuple()
if False and funct.__name__ in self.plotobjs:
self.plotobjs[funct.__name__].set(**kws)
else:
self.plotobjs[funct.__name__] = getattr(self.axes, kind)(*args, **kws)
return plott
return plotterd
def plotparams_enemies(self):
r = {'xs': tuple(), 'ys': tuple(), 'zs': tuple(), 'marker': self.plotparams['enemymarker'], 's': self.plotparams['planesize'], 'c': self.plotparams['planecolour']}
planes = tuple(self.filteractiveplanes(self.planes))
if planes:
r['xs'], r['ys'], r['zs'] = zip(*(p.coord for p in planes))
return r
def plotparams_allies(self):
r = {'xs': tuple(), 'ys': tuple(), 'zs': tuple(), 'marker': self.plotparams['allymarker'], 's': self.plotparams['planesize'], 'c': self.plotparams['planecolour']}
planes = tuple(self.filteractiveplanes(self.allies))
if planes:
r['xs'], r['ys'], r['zs'] = zip(*(p.coord for p in planes))
return r
def plotparams_vectors(self):
r = {'xs': tuple(), 'ys': tuple(), 'zs': tuple(), 'marker': self.plotparams['vectormarker'], 's': self.plotparams['vectorsize'], 'c': self.plotparams['planecolour']}
planes = tuple(self.filteractiveplanes(self.allies+self.planes))
if planes:
r['xs'], r['ys'], r['zs'] = zip(*(vecAdd(p.coord, p.vec) for p in planes))
return r
def plotparams_danger(self):
r = {'xs': tuple(), 'ys': tuple(), 'zs': tuple(), 'marker': self.plotparams['dangermarker'], 'cmap': self.plotparams['dangercmap'], 'alpha': self.plotparams['dangeralpha']}
coords = tuple(self.coords_grid((self.plotparams['dangerres'],)*len(self.dimensions)))
r['xs'], r['ys'], r['zs'] = zip(*coords)
r['c'] = tuple(self.rate_sumdanger(c) for c in coords)
m = max(r['c'])
r['s'] = tuple((d/m)**self.plotparams['dangersizeexpo']*self.plotparams['dangersize']+self.plotparams['dangersizebase'] for d in r['c'])
if self.plotparams['dangervrange']:
r['vmin'], r['vmax'] = self.plotparams['dangervrange']
return r
def plotparams_deaths(self):
r = {'xs': tuple(), 'ys': tuple(), 'zs': tuple(), 'marker': self.plotparams['deathmarker'], 's': self.plotparams['deathsize'], 'c': self.plotparams['deathcolours'][0], 'linewidths': self.plotparams['deathsize']/180, 'edgecolors': self.plotparams['deathcolours'][1], 'alpha': self.plotparams['deathalpha']}
deaths = tuple(p.lastcoord for p in self.planes+self.allies if p.lastalive and not p.alive)
if deaths:
r['xs'], r['ys'], r['zs'] = zip(*deaths)
return r
def plotparams_shots(self):
r = {'length': self.plotparams['shotlength'], 'linestyles': self.plotparams['shotstyle'], 'color': self.plotparams['shotcolour'], 'arrow_length_ratio': 0.0, '*args': []}
planes = tuple(p for p in self.filteractiveplanes(self.allies+self.planes) if not (p.lastcooldown is None or p.cooldown is None) and (p.cooldown > p.lastcooldown))
if planes:
for s in zip(*(p.coord for p in planes)):
r['*args'].append(s)
for s in zip(*(p.vec for p in planes)):
r['*args'].append(s)
else:
for i in range(6):
r['*args'].append(tuple())
return r
@plotter('scatter')
def plot_enemies(self):
pass
@plotter('scatter')
def plot_allies(self):
pass
@plotter('scatter')
def plot_vectors(self):
pass
@plotter('scatter')
def plot_danger(self):
pass
@plotter('scatter')
def plot_deaths(self):
pass
@plotter('quiver')
def plot_shots(self):
pass
def plot_names(self):
if 'plot_names' in self.plotobjs:
pass
self.plotobjs['plot_names'] = [self.axes.text(*p.coord, s=f"{p.name}") for i, p in enumerate(self.filteractiveplanes(self.allies+self.planes))]
def plotall(self):
for m in self.enabledplots:
getattr(self, f'plot_{m}')()
def updateallplots(self):
self.prepare_plotaxes()
self.plotall()
if self.plotautoturn:
self.axes.view_init(30, -60+self.frame*self.plotautoturn)
matplotlib.pyplot.draw()
if self.plotsavedir:
import os
os.makedirs(self.plotsavedir, exist_ok=True)
self.figure.savefig(os.path.join(self.plotsavedir, f'{self.frame}.png'))
self.frame += 1
if not self.plotshown:
matplotlib.pyplot.ion()
matplotlib.pyplot.show()#block=False)
self.plotshown = True
def tickturn(self):
DangerZones.tickturn(self)
self.updateallplots()
matplotlib.pyplot.pause(max(self.plotcycle, self.lasttick+self.tickwait-time.time()))
self.lasttick = time.time()
except Exception as e:
print(f"Could not define matplotlib rendering dangerzone handler:\n{stringException(e)}", file=sys.stderr)
def vecEquals(vec1, vec2):
return tuple(vec1) == tuple(vec2)
def vecAdd(*vecs):
return tuple(sum(p) for p in zip(*vecs))
def vecSub(vec1, vec2):
return tuple(a-b for a, b in zip(vec1, vec2))
def vecMult(*vecs):
return tuple(functools.reduce(lambda a, b: a*b, p) for p in zip(*vecs))
def vecDiv(vec1, vec2):
return tuple(a-b for a, b in zip(vec1, vec2))
def vecDotProduct(*vecs):
return sum(vecMult(*vecs))
#return sum(d*p for d, p in zip(vec1, vec2))
def vecAngle(vec1, vec2):
try:
if all(c == 0 for c in vec1) or all(c == 0 for c in vec2):
return math.nan
return math.acos(max(-1, min(1, vecDotProduct(vec1, vec2)/Pythagorean(*vec1)/Pythagorean(*vec2))))
except Exception as e:
raise ValueError(f"{e!s}: {vec1} {vec2}")
def convertVecTrinary(vec, length=1):
return tuple((max(-length, min(length, v*math.inf)) if v else v) for v in vec)
def getNeighbouringVecs(vec):
vec = convertVecTrinary(vec, length=1)
return {ve for ve in (tuple(v+(i//3**n%3-1) for n, v in enumerate(vec)) for i in range(3**len(vec))) if all(v in (-1,0,1) for v in ve) and any(v and v==vec[i] for i, v in enumerate(ve))}
def getVecRotation(vec1, vec2):
#Just do a cross product/perpendicular to tangential plane/normal?
pass
def applyVecRotation(vec, rotation):
pass
class DangerZoner(Plane):
"""Dogfighting plane control object."""
def __init__(self, arena, snipechance=0.60, snipechoices=3, firesafety=7.5, chasesafety=5.0, jinkdanger=math.inf, jink=0, name=None):
Plane.__init__(self, True, None, None)
self.arena = arena
self.lookahead = 1
self.snipechance = snipechance
self.snipechoices = snipechoices
self.firesafety = firesafety
self.chasesafety = chasesafety
self.jinkdanger = jinkdanger
self.jink = jink
self.vec = None
self.name = name
def get_enemies(self):
return (p for p in self.arena.filteractiveplanes(self.arena.planes))
def get_vecsuicidal(self, vec, coord=None, steps=5):
if coord is None:
coord = self.coord
if all(3 < c < self.arena.dimensions[i]-3 for i, c in enumerate(coord)):
return False
if not all(0 < c < self.arena.dimensions[i] for i, c in enumerate(coord)):
return True
elif steps >= 0:
return all(self.get_vecsuicidal(v, coord=vecAdd(coord, vec), steps=steps-1) for v in getNeighbouringVecs(vec))
return False
def get_sanevecs(self):
legalvecs = self.get_legalvecs()
s = {vec for vec in legalvecs if vecAdd(self.coord, vec) not in self.arena.get_expectedallies() and not any(vecAngle(vecSub(vecAdd(self.coord, vec), sc), sv) < 0.05 for sc, sv in self.arena.get_expectedshots()) and not self.get_vecsuicidal(vec, coord=vecAdd(self.coord, vec))}
if not s:
return legalvecs
raise Exception()
return s
def rate_vec(self, vec, lookahead=None):
if lookahead is None:
lookahead = self.lookahead
return self.arena.rate_sumdanger(tuple(c+v*lookahead for v, c in zip(vec, self.coord)), pself=self)
def get_validshots(self, snipe=True):
if snipe and random.random() < self.snipechance:
enemypossibilities = set.union(*({vecAdd(p.coord, p.vec)} if not p.lastvec or vecEquals(p.vec, p.lastvec) else {vecAdd(p.coord, ve) for ve in sorted(p.get_legalvecs(), key=lambda v: -vecAngle(v, p.lastvec))[:self.snipechoices]} for p in self.get_enemies()))
else:
enemypossibilities = set().union(*(p.get_legalcoords() for p in self.get_enemies()))
validshots = []
if self.cooldown:
return validshots
for vec in self.get_sanevecs():
coord = tuple(c + v for c, v in zip(self.coord, vec))
if any(vecAngle(tuple(n-v for n, v in zip(t, self.coord)), self.vec) < 0.1 for t in enemypossibilities if t != self.coord) and not any(vecAngle(vecSub(a, coord), self.vec) < 0.05 for a in self.arena.get_expectedallies()):
validshots.append({'vec': vec, 'turn': False, 'fire': True})
if any(vecAngle(tuple(n-v for n, v in zip(t, self.coord)), vec) < 0.1 for t in enemypossibilities if t != self.coord) and not any(vecAngle(vecSub(a, coord), vec) < 0.05 for a in self.arena.get_expectedallies()):
validshots.append({'vec': vec, 'turn': True, 'fire': True})
if snipe and not validshots:
validshots = self.get_validshots(snipe=False)
return validshots
def get_chase(self):
enemydirs = {vecSub(vecAdd(p.coord, p.vec), self.coord) for p in self.get_enemies()}
paths = sorted(self.get_sanevecs(), key=lambda vec: min([vecAngle(vec, e) for e in enemydirs if not all(v == 0 for v in e)]+[math.inf]))
if paths:
return paths[0]
def get_move(self):
if not self.alive:
return {'vec': (1,1,1), 'turn': False, 'fire': False}
fires = self.get_validshots()
if fires:
fires = sorted(fires, key=lambda d: self.rate_vec(d['vec']))
if self.rate_vec(fires[0]['vec']) <= self.firesafety:
return fires[0]
vec = self.get_chase()
if vec is None or self.rate_vec(vec) > self.chasesafety:
vec = sorted(self.get_sanevecs(), key=self.rate_vec)
vec = vec[min(len(vec)-1, random.randint(0,self.jink)) if self.rate_vec(vec[0]) > self.jinkdanger else 0]
return {'vec': vec, 'turn': True, 'fire': False}
def move(self):
move = self.get_move()
coord = vecAdd(self.coord, move['vec'])
self.arena.add_expectedallies(coord)
if move['fire']:
self.arena.add_expectedshots((coord, move['vec'] if move['turn'] else self.vec))
return move
VecsCarts = {(0,-1):'N', (0,1):'S', (1,1):'E', (1,-1):'W', (2,1):'U', (2,-1):'D'}
def translateCartVec(cartesian):
vec = [0]*3
for v,l in VecsCarts.items():
if l in cartesian:
vec[v[0]] = v[1]
return tuple(vec)
def translateVecCart(vec):
vec = convertVecTrinary(vec)
return ''.join(VecsCarts[(i,v)] for i, v in enumerate(vec) if v != 0)
def parsePlaneState(text):
return (lambda d: {'alive':{'alive': True, 'dead': False}[d[0]], 'coord':tuple(int(c) for c in d[1:4]), 'vec':translateCartVec(d[4]), 'cooldown': int(d[5])})(text.split(' '))
def encodePlaneInstruction(vec, turn, fire):
return f"{translateVecCart(vec)} {int(bool(turn))!s} {int(bool(fire))!s}"
class CtrlReceiver:
"""Object for interacting through STDIN and STDOUT in a dogfight with an arena, controlled planes, and enemy planes."""
def __init__(self, logname='danger_log.txt', arenatype=DangerZones, arenaconf=None, planetype=DangerZoner, planeconf=None, enemyname='Enemy', stdin=sys.stdin, stdout=sys.stdout):
self.logname = logname
self.arenatype = arenatype
self.arenaconf = dict(arenaconf) if arenaconf else dict()
self.planetype = planetype
self.planeconf = dict(planeconf) if planeconf else dict()
self.enemyname = enemyname
self.stdin = stdin
self.stdout = stdout
self.log = open('danger_log.txt', 'w')
def __enter__(self):
return self
def __exit__(self, *exc):
self.log.__exit__()
def getin(self):
l = self.stdin.readline()
self.log.write(f"IN: {l}")
return l
def putout(self, content):
self.log.write(f"OUT: {content}\n")
print(content, file=self.stdout, flush=True)
def logout(self, content):
self.log.write(f"MSG: {content}\n")
def logerr(self, content):
self.log.write(f"ERR: {content}\n")
def run_setup(self, arenasize, rounds):
self.arena = self.arenatype(dimensions=(arenasize,)*3, **self.arenaconf)
self.planes = [self.planetype(arena=self.arena, name=f"{self.planetype.__name__} #{i}", **self.planeconf) for i in range(2)]
self.arena.set_planes(*(Plane(True, None, None, name=f"{self.enemyname} #{i}") for i in range(2)))
self.arena.set_allies(*self.planes)
def run_move(self):
self.arena.tickturn()
for p in self.planes:
p.update(**parsePlaneState(self.getin()))
for p in self.arena.planes:
p.update(**parsePlaneState(self.getin()))
for p in self.planes:
self.putout(encodePlaneInstruction(**p.move()))
def run(self):
line = ''
while not line.startswith('NEW CONTEST '):
line = self.getin()
self.run_setup(arenasize=int(line.split(' ')[2])-1, rounds=None)
while True:
line = self.getin()
if line.startswith('NEW TURN'):
self.run_move()
if True and __name__ == '__main__' and not sys.flags.interactive:
import time
DoPlot = False
#Use the arena object that visualizes progress every turn.
DangerPlot = True
#Compute and render a voxel cloud of danger ratings within the arena each turn if visualizing it.
SparseDangerPlot = False
#Use a lower resolution for the voxel cloud if visualizing danger ratings.
TurntablePlot = True
#Apply a fixed animation to the interactive visualization's rotation if visualizing the arena.
with CtrlReceiver(logname='danger_log.txt', arenatype=PlottingDangerZones if DoPlot else DangerZones, arenaconf=dict(disabledplots=None if DangerPlot else ('danger'), plotparams=dict(dangerres=2) if SparseDangerPlot else dict(dangeralpha=0.1), plotautoturn=1 if TurntablePlot else 0, plotsavedir=f'PngFrames') if DoPlot else None, planetype=DangerZoner) as run:
try:
run.run()
except Exception as e:
run.logerr(stringException(e))
```