170 lines
6.7 KiB
Python
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)
|