second version now with teams and some bugs fixead
This commit is contained in:
parent
7328cf8280
commit
8aebbeb711
Binary file not shown.
196
rengobot.py
196
rengobot.py
@ -17,7 +17,7 @@ bot = commands.Bot(command_prefix='$', help_command=None)
|
||||
|
||||
min_time_player= timedelta(seconds=1) # in random games, min time between same player plays (default days=1)
|
||||
time_to_skip= timedelta(days=1) # in queue games, how much time to wait for the next move
|
||||
min_players = 3
|
||||
min_players = 2
|
||||
|
||||
# People who can start and resign games :O
|
||||
# Later we might replace this with checking for a role.
|
||||
@ -27,12 +27,15 @@ admins=[ 756220448118669463, # Young Sun
|
||||
477895596141707264 # René
|
||||
]
|
||||
|
||||
white_stone= "<:white_stone:882731089548939314>"
|
||||
black_stone= "<:black_stone:882730888453046342>"
|
||||
|
||||
with open("token.txt") as f:
|
||||
token = f.readlines()[0] # Get your own token and put it in token.txt
|
||||
|
||||
format="%Y_%m_%d_%H_%M_%S_%f"
|
||||
|
||||
# The state is a list of tuples (channel_id, "queue"/"random", last_players, last_times, [player_id])
|
||||
# The state is a list of tuples (channel_id, "queue"/"random", last_players, last_times, [black_queue, white_queue])
|
||||
|
||||
@bot.command()
|
||||
async def help(ctx):
|
||||
@ -57,6 +60,7 @@ async def help(ctx):
|
||||
async def play(ctx, arg):
|
||||
channel_id= ctx.channel.id
|
||||
user = ctx.author
|
||||
guild= ctx.guild
|
||||
|
||||
# lowest effort serialization
|
||||
with open("state.txt") as f: state = ast.literal_eval(f.read())
|
||||
@ -68,15 +72,18 @@ async def play(ctx, arg):
|
||||
|
||||
i= filter_state[0]
|
||||
|
||||
if state[i][1] == "queue" and user.id not in state[i][4]:
|
||||
if state[i][1] == "queue" and user.id not in state[i][4][0]+state[i][4][1]:
|
||||
await ctx.send("Player hasn't joined yet! Join us with `$join`")
|
||||
return
|
||||
|
||||
if state[i][1] == "queue" and len(state[i][4]) <min_players:
|
||||
await ctx.send("Waiting for {} more players to join".format(min_players-len(state[i][4])))
|
||||
if state[i][1] == "queue" and (len(state[i][4][0])<min_players or len(state[i][4][1]) <min_players):
|
||||
|
||||
await ctx.send("Waiting for more players to join! Minimum {} per team".format(min_players))
|
||||
return
|
||||
|
||||
if state[i][1] == "queue" and user.id!= state[i][4][0]:
|
||||
colour= sgfengine.next_colour(str(channel_id))
|
||||
|
||||
if state[i][1] == "queue" and user.id!= state[i][4][colour][0]:
|
||||
await ctx.send("It is not your turn yet!")
|
||||
return
|
||||
|
||||
@ -104,7 +111,7 @@ async def play(ctx, arg):
|
||||
return
|
||||
|
||||
try:
|
||||
sgfengine.play_move(str(channel_id), arg, user.name)
|
||||
sgfengine.play_move(str(channel_id), arg, user.display_name)
|
||||
except ValueError as e:
|
||||
await ctx.send(str(e))
|
||||
return
|
||||
@ -114,12 +121,12 @@ async def play(ctx, arg):
|
||||
state[i][3].append(datetime.now().strftime(format))
|
||||
|
||||
if state[i][1] == "queue":
|
||||
state[i][4].pop(0)
|
||||
state[i][4].append(user.id)
|
||||
state[i][4][colour].pop(0)
|
||||
state[i][4][colour].append(user.id)
|
||||
|
||||
file = discord.File(str(ctx.channel.id)+".png")
|
||||
if state[i][1]=="queue":
|
||||
next_player=(await bot.fetch_user(state[i][4][0]))
|
||||
next_player=(await guild.fetch_member(state[i][4][1-colour][0]))
|
||||
await ctx.send(file=file, content="{}'s turn! ⭐".format(next_player.mention))
|
||||
else:
|
||||
await ctx.send(file=file)
|
||||
@ -131,6 +138,7 @@ async def edit(ctx, arg): #literally play but with less things
|
||||
# It should wait until the queue has 4 players or so
|
||||
channel_id= ctx.channel.id
|
||||
user = ctx.author
|
||||
guild= ctx.guild
|
||||
|
||||
# lowest effort serialization
|
||||
with open("state.txt") as f: state = ast.literal_eval(f.read())
|
||||
@ -142,15 +150,13 @@ async def edit(ctx, arg): #literally play but with less things
|
||||
|
||||
i= filter_state[0]
|
||||
|
||||
if state[i][1] == "queue" and user.id not in state[i][4]:
|
||||
if state[i][1] == "queue" and user.id not in (state[i][4][0]+ state[i][4][1]):
|
||||
await ctx.send("Player hasn't joined yet! Join us with `$join`")
|
||||
return
|
||||
|
||||
if state[i][1] == "queue" and user.id!= state[i][4][0]:
|
||||
await ctx.send("It is not your turn yet!")
|
||||
return
|
||||
colour= sgfengine.next_colour(str(channel_id))
|
||||
|
||||
if state[i][2][-1] != user.id or datetime.now()-datetime.strptime(state[i][3][-1],format) > timedelta(minutes=5):
|
||||
if len(state[i][2])==0 or state[i][2][-1] != user.id or datetime.now()-datetime.strptime(state[i][3][-1],format) > timedelta(minutes=5):
|
||||
await ctx.send("You cannot edit this move!")
|
||||
return
|
||||
|
||||
@ -161,15 +167,15 @@ async def edit(ctx, arg): #literally play but with less things
|
||||
return
|
||||
|
||||
try:
|
||||
sgfengine.play_move(str(channel_id), arg, user.name, True)
|
||||
sgfengine.play_move(str(channel_id), arg, user.display_name, True)
|
||||
except ValueError as e:
|
||||
await ctx.send(str(e))
|
||||
return
|
||||
|
||||
file = discord.File(str(ctx.channel.id)+".png")
|
||||
if state[i][1]=="queue":
|
||||
next_player=(await bot.fetch_user(state[i][4][0]))
|
||||
await ctx.send(file=file, content="{}'s turn! ⭐".format(next_player.name))
|
||||
next_player=(await guild.fetch_member(state[i][4][colour][0]))
|
||||
await ctx.send(file=file, content="{}'s turn! ⭐".format(next_player.display_name))
|
||||
else:
|
||||
await ctx.send(file=file)
|
||||
|
||||
@ -179,6 +185,7 @@ async def edit(ctx, arg): #literally play but with less things
|
||||
async def board(ctx):
|
||||
channel_id= ctx.channel.id
|
||||
user = ctx.author
|
||||
guild= ctx.guild
|
||||
|
||||
with open("state.txt") as f: state = ast.literal_eval(f.read())
|
||||
|
||||
@ -188,11 +195,17 @@ async def board(ctx):
|
||||
return
|
||||
|
||||
i= filter_state[0]
|
||||
colour= sgfengine.next_colour(str(channel_id))
|
||||
|
||||
os.system("sgf-render --style fancy -o "+str(channel_id)+".png -n last "+str(channel_id)+".sgf")
|
||||
|
||||
file = discord.File(str(ctx.channel.id)+".png")
|
||||
if state[i][1]=="queue":
|
||||
next_player=(await bot.fetch_user(state[i][4][0]))
|
||||
await ctx.send(file=file, content="{}'s turn! ⭐".format(next_player.name))
|
||||
if len(state[i][4][colour]) > 0:
|
||||
next_player=(await guild.fetch_member(state[i][4][colour][0]))
|
||||
await ctx.send(file=file, content="{}'s turn! ⭐".format(next_player.display_name))
|
||||
else:
|
||||
await ctx.send(file=file, content="Waiting for players to join!")
|
||||
else:
|
||||
await ctx.send(file=file)
|
||||
|
||||
@ -211,7 +224,7 @@ async def join(ctx):
|
||||
|
||||
i= filter_state[0]
|
||||
|
||||
if user.id in state[i][4]:
|
||||
if user.id in (state[i][4][0]+state[i][4][1]):
|
||||
await ctx.send("Player already in this game!")
|
||||
return
|
||||
|
||||
@ -219,45 +232,13 @@ async def join(ctx):
|
||||
await ctx.send("This game has no queue! No need to join, just `$play` whenever you want :P")
|
||||
return
|
||||
|
||||
state[i][4].append(user.id)
|
||||
colour = 0 if len(state[i][4][0])<=len(state[i][4][1]) else 1
|
||||
state[i][4][colour].append(user.id)
|
||||
|
||||
await ctx.send("User joined!")
|
||||
await ctx.send("User {} joined Team {}!".format(user.display_name, ("Black" if colour==0 else "White")))
|
||||
|
||||
with open("state.txt", "w") as f: f.write(repr(state))
|
||||
|
||||
@bot.command()
|
||||
async def queue(ctx):
|
||||
channel_id= ctx.channel.id
|
||||
|
||||
# lowest effort serialization
|
||||
with open("state.txt") as f: state = ast.literal_eval(f.read())
|
||||
|
||||
filter_state= [i for i in range(len(state)) if state[i][0] == channel_id]
|
||||
if not filter_state:
|
||||
await ctx.send("No active game in this channel!")
|
||||
return
|
||||
|
||||
i= filter_state[0]
|
||||
|
||||
if state[i][1] != "queue":
|
||||
await ctx.send("This game has no queue! No need to join, just `$play` whenever you want :P")
|
||||
return
|
||||
|
||||
output= "Player list:\n"
|
||||
for j, player_id in enumerate(state[i][4]):
|
||||
player_name=(await bot.fetch_user(player_id)).name
|
||||
output+=str(j+1)+". "+ player_name+"\n"
|
||||
|
||||
if state[i][4]==[]:
|
||||
output+="Nobody yet! Join us with `$join`"
|
||||
|
||||
await ctx.send(output)
|
||||
|
||||
@bot.command()
|
||||
async def sgf(ctx):
|
||||
file = discord.File(str(ctx.channel.id)+".sgf")
|
||||
await ctx.send(file=file)
|
||||
|
||||
@bot.command()
|
||||
async def leave(ctx):
|
||||
channel_id= ctx.channel.id
|
||||
@ -273,7 +254,7 @@ async def leave(ctx):
|
||||
|
||||
i= filter_state[0]
|
||||
|
||||
if user.id not in state[i][4]:
|
||||
if user.id not in (state[i][4][0]+state[i][4][1]):
|
||||
await ctx.send("Player not in this game!")
|
||||
return
|
||||
|
||||
@ -281,12 +262,91 @@ async def leave(ctx):
|
||||
await ctx.send("This game has no queue! No need to leave!")
|
||||
return
|
||||
|
||||
state[i][4].remove(user.id) #TODO What happens with this and the skipping?
|
||||
colour = 0 if (user.id in state[i][4][0]) else 1
|
||||
state[i][4][colour].remove(user.id)
|
||||
|
||||
await ctx.send("User left :(")
|
||||
await ctx.send("User {} left :(".format(user.display_name))
|
||||
|
||||
with open("state.txt", "w") as f: f.write(repr(state))
|
||||
|
||||
@bot.command()
|
||||
async def queue(ctx):
|
||||
channel_id= ctx.channel.id
|
||||
channel= bot.get_channel(channel_id) # thonk the order
|
||||
guild = channel.guild
|
||||
|
||||
# lowest effort serialization
|
||||
with open("state.txt") as f: state = ast.literal_eval(f.read())
|
||||
|
||||
filter_state= [i for i in range(len(state)) if state[i][0] == channel_id]
|
||||
if not filter_state:
|
||||
await ctx.send("No active game in this channel!")
|
||||
return
|
||||
|
||||
i= filter_state[0]
|
||||
colour= sgfengine.next_colour(str(channel_id))
|
||||
|
||||
if state[i][1] != "queue":
|
||||
await ctx.send("This game has no queue! No need to join, just `$play` whenever you want :P")
|
||||
return
|
||||
|
||||
output= "Player list:\n"
|
||||
if state[i][4][0]==[] and state[i][4][1] == []:
|
||||
output+="Nobody yet! Join us with `$join`"
|
||||
await ctx.send(output)
|
||||
return
|
||||
|
||||
if state[i][4][0] == []:
|
||||
for j, player_id in enumerate(state[i][4][1]):
|
||||
player_name=(await guild.fetch_member(player_id)).display_name
|
||||
output+=white_stone+str(j+1).rjust(3)+". "+ player_name+"\n"
|
||||
output+="\n Team Black needs more members!"
|
||||
await ctx.send(output)
|
||||
return
|
||||
|
||||
if state[i][4][1] == []:
|
||||
for j, player_id in enumerate(state[i][4][0]):
|
||||
player_name=(await guild.fetch_member(player_id)).display_name
|
||||
output+=black_stone+str(j+1).rjust(3)+". "+ player_name+"\n"
|
||||
output+="\n Team White needs more members!"
|
||||
await ctx.send(output)
|
||||
return
|
||||
|
||||
# Which team has more members? Or in case of a tie, which team goes first?
|
||||
if len(state[i][4][colour]) > len(state[i][4][1-colour]):
|
||||
last_player = state[i][4][colour][-1]
|
||||
else: last_player= state[i][4][1-colour][-1]
|
||||
|
||||
j=1
|
||||
pointers=[0,0]
|
||||
while(True):
|
||||
#print(channel_id, j, pointers, colour, state[i][0], state[i][4])
|
||||
output+= white_stone if ((colour+1) % 2 ==0) else black_stone
|
||||
output+= str(j).rjust(3)+". "
|
||||
|
||||
player_name= (await guild.fetch_member(state[i][4][colour][pointers[colour]])).display_name
|
||||
output+= player_name+"\n"
|
||||
|
||||
if state[i][4][colour][pointers[colour]] == last_player: break
|
||||
|
||||
pointers[colour] = (pointers[colour]+1) % len(state[i][4][colour])
|
||||
colour=1-colour
|
||||
|
||||
j+=1
|
||||
|
||||
if len(state[i][4][0])<min_players:
|
||||
output+="\n Team Black needs more members!"
|
||||
|
||||
if len(state[i][4][1])<min_players:
|
||||
output+="\n Team White needs more members!"
|
||||
|
||||
await ctx.send(output)
|
||||
|
||||
@bot.command()
|
||||
async def sgf(ctx):
|
||||
file = discord.File(str(ctx.channel.id)+".sgf")
|
||||
await ctx.send(file=file)
|
||||
|
||||
@bot.command()
|
||||
async def newgame(ctx, arg):
|
||||
channel_id= ctx.channel.id
|
||||
@ -308,7 +368,7 @@ async def newgame(ctx, arg):
|
||||
return
|
||||
|
||||
sgfengine.new_game(str(ctx.channel.id))
|
||||
state.append((ctx.channel.id, arg, [], [], []))
|
||||
state.append((ctx.channel.id, arg, [], [], [[],[]]))
|
||||
|
||||
file = discord.File(str(ctx.channel.id)+".png")
|
||||
if arg=="queue":
|
||||
@ -350,7 +410,7 @@ async def background_task():
|
||||
print("bot ready!")
|
||||
|
||||
guild=discord.utils.get(bot.guilds, name="Awesome Baduk")
|
||||
game=discord.Game("multiplayer Baduk! Everyone can $play <move>! $help for command list")
|
||||
game=discord.Game("multiplayer Baduk! $help for command list")
|
||||
await bot.change_presence(status=discord.Status.online, activity=game)
|
||||
|
||||
while not bot.is_closed():
|
||||
@ -366,20 +426,22 @@ async def background_task():
|
||||
channel_id= state[i][0]
|
||||
channel= bot.get_channel(channel_id)
|
||||
|
||||
colour = sgfengine.next_colour(str(channel_id))
|
||||
|
||||
last_time= datetime.strptime(state[i][3][-1],format)
|
||||
time_left= last_time + time_to_skip-datetime.now()
|
||||
|
||||
if time_left < time_to_skip * 2/3 and time_left > time_to_skip*2/3-timedelta(seconds=10): # Probably remove? Depends on how passive aggressive it is
|
||||
next_user = await bot.fetch_user(state[i][4][0])
|
||||
next_user = await guild.fetch_member(state[i][4][colour][0])
|
||||
await channel.send("{}'s turn! Time is running up!".format(next_user.mention))#, time_left.total_seconds()/3600) )
|
||||
if time_left < timedelta():
|
||||
state[i][3][-1]= datetime.strftime(datetime.now(),format)
|
||||
state[i][2][-1]= None
|
||||
user_id= state[i][4][0]
|
||||
state[i][4].pop(0)
|
||||
state[i][4].append(user_id)
|
||||
next_player=(await bot.fetch_user(state[i][4][0]))
|
||||
user_id= state[i][4][colour][0]
|
||||
state[i][4][colour].pop(0)
|
||||
state[i][4][colour].append(user_id)
|
||||
next_player=(await guild.fetch_member(state[i][4][colour][0]))
|
||||
await channel.send(content="{}'s turn! ⭐".format(next_player.mention))
|
||||
#Should I output the board too?
|
||||
|
||||
with open("state.txt", "w") as f: f.write(repr(state))
|
||||
await asyncio.sleep(10)
|
||||
|
13
sgfengine.py
13
sgfengine.py
@ -14,6 +14,15 @@ def new_game(channel_id):
|
||||
|
||||
os.system("sgf-render --style fancy -o "+channel_id+".png -n last "+channel_id+".sgf")
|
||||
|
||||
#0 if black to play, 1 if white to play
|
||||
def next_colour(channel_id):
|
||||
with open(channel_id+".sgf","rb") as f:
|
||||
game = sgf.Sgf_game.from_bytes(f.read())
|
||||
f.close()
|
||||
|
||||
node= game.get_last_node()
|
||||
return 1 if ("B" in node.properties()) else 0
|
||||
|
||||
# Could be an illegal move, or maybe I don't understand the message
|
||||
# outputs to <channel_id>.png
|
||||
def play_move(channel_id, messagestr, player, overwrite=False):
|
||||
@ -47,10 +56,10 @@ def play_move(channel_id, messagestr, player, overwrite=False):
|
||||
try:
|
||||
koban2=board2.play(therow, thecol, colour)
|
||||
except ValueError as e:
|
||||
raise ValueError("Illegal move1!")
|
||||
raise ValueError("Illegal move! There is a stone there.")
|
||||
|
||||
if board2.get(therow,thecol) == None:
|
||||
raise ValueError("Illegal move2!")
|
||||
raise ValueError("Illegal move! No self-captures allowed.")
|
||||
|
||||
node2= node.new_child()
|
||||
node2.set(("B" if colour =='b' else "W"), (therow,thecol))
|
||||
|
Loading…
x
Reference in New Issue
Block a user