rance/board.py

170 lines
6.7 KiB
Python

import random as ra
from . import drawing
from . import decos
class Board():
def __init__(self,p1,p2,p1k,p2k,p1u,p2u):
self.grid=[None for _ in range(12)]
# p1u/p2u split from original units is because now they can both be in the form 0-5 and it JUST WORKS™
# Haha it had a bug anyway. P2's units were upside down.
for k,v in p1u.items(): self.grid[k]=v
for k,v in p2u.items(): self.grid[6+(3+k)%6]=v
self.p1={}; self.p2={}
self.p1['name']=p1; self.p2['name']=p2
self.p1['key']=p1k; self.p2['key']=p2k
self.p1['score']=0; self.p2['score']=0
for i in range(len(self.grid)):
if self.grid[i] is not None: self.grid[i].code=i
for i in self.grid:
if i is None: continue
# Just gonna sneak this into the name as well, until I support actual names.
if i.name=='': i.name=str(i.code)
i.board=self
if i.cls=='guardian': i.status['guard']=0.5
if i.cls=='tactician':
start=i.code//6%2*6
targs=[n for n in self.getunits(*list(range(start,start+6)),codes=True)]
bufftargs=[]
for n in targs:
bufftargs.extend([(n,m) for m in ['atk','def','int','spd'] if m not in n.status])
bufftargs=ra.sample(bufftargs,3)
for n in bufftargs: n[0].status[n[1]]=0.1*i.int
i.speedval=i.aspd(70)+ra.random()*0.0001
self.turn=0
def controller(self):
# The main game loop thing.
# The two yield statements do serve a purpose.
# Sometimes we don't want anything back in (when a charged action is to occur)
# So we send a code that says "don't send us anything next step" along with the result
# Setup stuff goes here? Maybe?
while self.uptobat():
try: u=self.getactive()
except AttributeError: break
try:
if 'charging' in u.status:
action=yield None
result=self.act2(u,*u.status['action'])
u.status.pop('action',None)
u.status.pop('charging',None)
else:
actviews=[]
lerst=self.bs()
for action in u.actions:
actd=actinfo[action]
spd=u.aspd(actd.get('startup',actd['recovery']))
after=self.bs(spd)
actviews.append((action,f"flags: {actd['flags']}",f"re-q: {after} (after {lerst[after-1].name})"))
action=yield ((self.p1['name'],self.p1['key']) if u.code<6 else (self.p2['name'],self.p2['key']), u.name, actviews)
u.status.pop('action',None) # If he got cancelled while charging, clean up
result=self.act1(u,*action)
except decos.RanceException as ex: result=str(ex)
yield result
return f"Good game, {self.p1['name'] if self.p1['score']>self.p2['score'] else self.p2['name'] if self.p1['score']<self.p2['score'] else 'nobody'} wins."
def uptobat(self):
self.p1['dead']=len(self.getunits(*list(range(0,6)),codes=True,dead=False))==0
self.p2['dead']=len(self.getunits(*list(range(6,12)),codes=True,dead=False))==0
if self.p1['dead']: self.p1['score']=-1
if self.p2['dead']: self.p2['score']=-1
if self.p1['dead'] or self.p2['dead']: return False # gg, a player died
if not self.getunits(*list(range(3,6)),codes=True,dead=False):
self.twizzle(True)
if not self.getunits(*list(range(6,9)),codes=True,dead=False):
self.twizzle(False)
try: tiny=self.bs()[0].speedval
except IndexError: return False # There's nobody left to act, just give up.
if tiny==0: return True # We're already where we should be, don't increment turn counter or w/e.
for k in self.grid:
if k is None: continue
k.speedval-=tiny
self.turn+=1
if self.turn>30: return False # Game's over.
return True
def twizzle(self,leftside):
# This is very broken, needs to fix turn order stuff.
# It's less broken now but it's still kinda bad.
if leftside:
temp=self.grid[0:3]
self.grid[0:3]=self.grid[3:6]
self.grid[3:6]=temp
else:
temp=self.grid[9:12]
self.grid[9:12]=self.grid[6:9]
self.grid[6:9]=temp
for i in range(12):
try: self.grid[i].code=i
except AttributeError: continue
def act1(self,u,action,target=None,*_):
actd=actinfo[action]
t=self.getunits(target,action=action)
if t is not None: t=t[0]
if u.curflags<actd['flags'] or u.curflags<=0: return f'Unit {u.name} too exhausted for {action}'
if 'startup' in actd:
# Charged action, don't actually do it, just fuck off to some other routine.
u.status['charging']=True
u.status['action']=(action,target)
u.speedval+=u.aspd(actd['startup'])
return f"{u.name} begins charging"
return self.act2(u,action,t)
def act2(self,u,action,t,*_):
actd=actinfo[action]
# The important line
# Also it's not a class/object method lol, so need to explicitly pass self
try: scores=u.actions[action](u,t)
except IndexError: raise decos.RanceException(f'Unit {u.name} doesn\'t have {action} action')
if u.code<6: self.p1['score']+=scores[0];self.p2['score']+=scores[1]
else: self.p2['score']+=scores[0];self.p1['score']+=scores[1]
if not (action=='guard' or action=='rest'):
u.status.pop('guard',None); u.status.pop('defend',None)
# I should probably be setting this instead of adding it.
u.speedval+=u.aspd(actd['recovery'])
# No action will ever cost more than 10 flags. And, more importantly, no unit will ever have more than 10 flags.
u.curflags-=min(actd['flags'] if actd['flags']>=0 else 10,u.curflags)
#if u.curflags==0: self.order.pop(u.code)
return scores[2]
def getunits(self,*units,action=None,codes=False,dead=False):
# The mega get function. No grid access should occur without this being called.
if action:
# We're in targeting mode.
if 'target' not in actinfo[action]['attrs']: return None
if len(units)!=1 or units[0] is None: raise decos.RanceException(f'You must target one unit with {action}')
out=self.resolvenames(*units,codes=codes,dead=dead)
if not dead: out=[u for u in out if 'dead' not in u.status] # Not sure why this line is here, if dead is False it should be correctly filtered out in resolvenames.
if action and not out: raise decos.RanceException(f'Invalid target for {action}')
return out
def resolvenames(self,*units,codes=False,dead=False):
if codes: return [u for u in self.grid if u is not None and u.code in units and ((not dead) or 'dead' not in u.status)]
names={u:u.name for u in self.grid if u is not None}
out=[]
for unit in units:
res=[k for k,v in names.items() if v==unit]
if len(res)!=1:
try: o=self.grid[int(unit)]
except ValueError: raise decos.RanceException(f'Name {unit} is ambiguous; matches {len(res)} units')
if o is not None: out.append(o)
else: out.append(res[0])
return out
def __str__(self):
return drawing.drawboard(self)
def getactive(self):
try: return self.bs()[0]
except IndexError: return None
def bs(self,sim=None):
out=[x for x in self.grid if x is not None and x.curflags>0]
if sim is not None:
out2={x.code:x.speedval for x in out}
out2[-1]=sim
ind=sorted(out2.items(),key=lambda x:x[1]).index((-1,sim))
return ind
return sorted(out,key=lambda x:x.speedval)