rance/games.py

207 lines
11 KiB
Python
Raw Permalink Normal View History

from objects.rance import automate
import random as ra
import asyncio
import discord
excluded=False
commands={'rance':[], 'rancequit':[], 'rancehack':[], 'rancerank':[], 'azul':[]}
rancegames={}
async def rance(args, channel, message):
if channel.id in rancegames and not type(rancegames[channel.id])==tuple:
await channel.send("Game already in progress, just watch or pick another channel")
return
p1=message.author
if len(args)==1:
try:
num=int(args[0])
except ValueError: num=None
if num is not None and 0<num<7: rancegames[channel.id]=(p1,num); await channel.send("Game is open! Awaiting other party!")
return
elif len(args)==0 and type(rancegames[channel.id])==tuple:
p1=rancegames[channel.id][0]; p2=message.author; num=rancegames[channel.id][1]
else:
p2=u.findobj(args[0],channel.guild.members)
if p2 is None:
await channel.send("Challenging players directly doesn't work anymore, thanks to intents.")
#await channel.send("First argument should name another player (mention, username, or id)")
return
try:
num=int(args[1])
except ValueError:
await channel.send("Second argument should be number of units (1-6)")
return
if not 1<=num<=6:
await channel.send("Number of units should be between 1 and 6, inclusive")
return
await rancesetup(p1,p2,channel,num)
await playrance(channel)
async def rancerank(args,channel,message):
if channel.id in rancegames: await channel.send("Game already in progress, just watch or pick another channel"); return
if len(args)==0: await channel.send("You need to challenge an opponent with this."); return
p2=u.findobj(' '.join(args),channel.guild.members)
if p2 is None: await channel.send("Couldn't find opponent; try mentioning them."); return
p1=message.author
rancegames[channel.id]=True
def validateunits(x):
n=x.split('\n')
for m in n[1:]:
if len(m.split(','))!=9: return f"line {m} is invalid somehow, please redo it"
return True
units=await getnp("Please enter your name, then the units you wish to bring to battle, in the following format:\npos,class,atk,def,int,spd,troops,flags,name\nPlease enter one unit per line, but the first line should be your name.\nAlso note that this will be logged; this function is intended for tournament play, where you have an existing roster. If cheating is suspected, the logs will be reviewed. Try not to use it frivolously during tournaments.",[p1,p2],[[],[]],[validateunits])
u.log('',*units) # That just makes the logs look a bit neater.
units=units[0].split('\n'),units[1].split('\n')
names=units[0][0],units[1][0]
units=[n.split(',') for n in units[0][1:]],[n.split(',') for n in units[1][1:]]
rancegames[channel.id]=board=automate.Board(names[0],names[1],p1.id,p2.id,{int(m[0])-1:automate.unit.BattleUnit(m[1],*[int(n) for n in m[2:-1]],name=m[-1]) for m in units[0]},{int(m[0])-1:automate.unit.BattleUnit(m[1],*[int(n) for n in m[2:-1]],name=m[-1]) for m in units[1]})
await p1.send(f"Game's ready in <#{channel.id}>")
await p2.send(f"Game's ready in <#{channel.id}>")
await playrance(channel)
async def rancesetup(p1,p2,channel,num):
# All checks finally out of the way zzzz
rancegames[channel.id]=seed=ra.random()
await channel.send("ゲーム・スタート!")
# Fire off the first set of listeners; the player names.
try:
names=await getnp("Please enter player name.",[p1,p2],[[],[]],[])
except (discord.Forbidden,discord.HTTPException) as ex:
await channel.send("Couldn't DM one of the players (can't tell who yet), maybe their DM settings are wrong; perhaps try DMing me first.")
rancegames.pop(channel.id)
return
if not rancestatus(channel.id,seed): return
berd=automate.blindstart(num,*names)
classlist=next(berd)
# https://stackoverflow.com/a/44780467
nl='\n'
longth=lambda x:len(x.split(' '))==num or "Wrong number of units provided"
def incls(x):
out=[]
for n in x.split(' '):
if n not in automate.classlist: out.append(n)
if out: return f"Invalid class names: {', '.join(out)}. Please note they're case sensitive."
return True
classes=await getnp(f"What classes would you like for your **{num}** units? (Space separated)\nList of melee classes (work best in front row):\n{nl.join(classlist[0])}\n\nRanged (work best in back row):\n{nl.join(classlist[1])}",[p1,p2],[[],[]],[longth,incls])
if not rancestatus(channel.id,seed): return
units=berd.send(tuple([m.lower() for m in n.split(' ')] for n in classes))
def isint(x):
try: int(x); return True
except ValueError: return f"'{x}' isn't a valid integer"
rerolls=await getnp("Your units:\n{0}\n Please pick a number (from 1 to {1}) to reroll; you can discard the reroll, don't worry",[p1,p2],[['\n'.join(units[0]),num],['\n'.join(units[1]),num]],[isint,lambda x:int(x) in range(1,num+1) or f"{x} isn't a valid unit number"])
rerolls=tuple(int(n)-1 for n in rerolls)
if not rancestatus(channel.id,seed): return
news=berd.send(rerolls)
boolaliases={'new':True,'old':False, 'yes':True,'no':False, 'true':True,'false':False, '1':True,'0':False, 'y':True,'n':False}
confirms=await getnp("Old unit: {0}\nNew unit: {1}\nWhich would you like to keep (old/new)?",[p1,p2],[[units[0][rerolls[0]],news[0]],[units[1][rerolls[1]],news[1]]],[lambda x:x in boolaliases or "Please specify either `old` or `new`"])
if not rancestatus(channel.id,seed): return
_=berd.send(tuple(boolaliases[n.lower()] for n in confirms))
def checknames(x):
if x in ('-','`-`'): return True
try: a={int(n.split(':')[0]):n.split(':')[1] for n in x.split(' ')}
except: return 'The format was weird in some way, please try again.'
if min(list(a))<1 or max(list(a))>num+1: return f"Numbers should be between 1 and {num} inclusive."
return True
unames=await getnp('You may provide names for your units, if you wish. Please provide them as num:name pairs, eg. `2:Ran 3:Rin`\nUnnamed units will be assigned a number as their name. Say `-` if you don\'t want to name any units.',[p1,p2],[[],[]],[checknames])
if not rancestatus(channel.id,seed): return
def isints(x):
for n in x.split(' '):
try: int(n)
except ValueError: return f"'{n}' isn't a valid integer"
if int(n) not in range(1,7): return f"{n} isn't a valid position"
return True
units=berd.send(({int(n.split(':')[0])-1:n.split(':')[1] for n in unames[0].split(' ')} if unames[0]!='-' else {},{int(n.split(':')[0])-1:n.split(':')[1] for n in unames[1].split(' ')} if unames[1]!='-' else {}))
positions=await getnp("Final unit list:\n{0}\nPlease enter the positions for these units (1-3 back row (ranged units should go here), 4-6 front row (melee units should go here), space separated, positions should be unique!)",[p1,p2],[['\n'.join(units[0])],['\n'.join(units[1])]],[lambda x:len(x.split(' '))==num or f"You should provide {num} position numbers", isints, lambda x:len(set(x.split(' ')))==len(x.split(' ')) or "Position numbers must be unique"])
if not rancestatus(channel.id,seed): return
_=berd.send(([int(n)-1 for n in positions[0].split(' ')],[int(n)-1 for n in positions[1].split(' ')]))
try: berd.send((p1.id,p2.id))
except StopIteration as ex:
board=ex.value
# We have a board!
rancegames[channel.id]=board
await p1.send(f"Game's ready in <#{channel.id}>")
await p2.send(f"Game's ready in <#{channel.id}>")
async def playrance(channel,board=None):
nl='\n'
if board is None: board=rancegames[channel.id]
controller=board.controller()
await channel.send("If a unit wasn't named, its unit code became its name.\nファイト!")
# No auto quit yet.
while True:
if not rancestatus(channel.id,board): return
# We can discard this during charged actions but if we don't get it shit goes wrong.
try: active=next(controller)
except StopIteration as ex: end=ex; break
if active is None:
# Charged action is happening. Don't wait for action or anything.
action=None
else:
await channel.send(f'```{board}```')
await channel.send(f"Available actions for {active[0][0]}'s unit {active[1]}:\n{nl.join([', '.join(n) for n in active[2]])}")
msg=await client.wait_for('message',check=lambda x:x.author.id==active[0][1] and x.channel.id==channel.id and x.content.split(' ')[0] in [n[0] for n in active[2]])
action=msg.content.split(' ')
try: res=controller.send(action); await channel.send(res)
except Exception as ex: await channel.send(str(ex)+'\nThis might have broken the game state; please confirm and quit if so')
await channel.send(f'```{board}```')
await channel.send(end)
rancegames.pop(channel.id)
async def getnp(question,players,formats,conditions):
# Each condition is a function that will return a string if the content failed validation.
# That string should be fired back to the user, for them to retry.
# The conditions should accept a string.
solos=[get1p(question,players[i],formats[i],conditions) for i in range(len(players))]
return [n.content.replace(' ',' ') for n in await asyncio.gather(*solos)]
async def get1p(question,player,forma,conditions):
# Probably easier to do it like this.
await player.send(question.format(*forma))
valid=False
while not valid:
msg=await client.wait_for('message',check=lambda x:x.author.id==player.id and x.channel==x.author.dm_channel)
content=msg.content.replace(' ',' ')
valid=True
for con in conditions:
res=con(content)
if res!=True: valid=False; await player.send(f"Please try again:\n{res}"); break
return msg
async def rancequit(args, channel, message):
# Get noobed
try: del rancegames[channel.id]; rancegames.pop(channel.id)
except KeyError: pass
# I might make this use the playrance function later.
async def rancehack(args, channel, message):
rancegames[channel.id]=board=automate.Board('a','b','','',{0:automate.unit.BattleUnit('tactician',1,9,3,9,400,4,{},'Ram'),2:automate.unit.BattleUnit('tactician',1,9,3,3,400,4,{},'Rem')},{4:automate.unit.BattleUnit('tactician',1,9,3,3,400,4,{},'Rom'),
2:automate.unit.BattleUnit('tactician',1,9,3,3,400,4,{},'Rim')})
board.turn=2
controller=board.controller()
nl='\n'
while True:
if not rancestatus(channel.id,board): return
# We can discard this during charged actions but if we don't get it shit goes wrong.
try: active=next(controller)
except StopIteration as ex: end=ex; break
if active is None:
# Charged action is happening. Don't wait for action or anything.
action=None
else:
await channel.send(f'```{board}```')
await channel.send(f"Available actions for {active[0][0]}'s unit {active[1]}:\n{nl.join([', '.join(n) for n in active[2]])}")
msg=await client.wait_for('message',check=lambda x:x.author!=client.user and x.channel.id==channel.id and x.content.split(' ')[0] in [n[0] for n in active[2]])
action=msg.content.split(' ')
res=controller.send(action); await channel.send(res)
#except Exception as ex: await channel.send(str(ex)+'\nThis might have broken the game state; please confirm and quit if so')
await channel.send(f'```{board}```')
await channel.send(end)
rancegames.pop(channel.id)
def rancestatus(cid,seed):
return cid in rancegames and rancegames[cid]==seed
async def azul(args, channel, message):
pass