second iteration

This commit is contained in:
ReneCareenium 2021-08-30 19:16:38 +02:00
parent 74567e1135
commit 028e7524f9
5 changed files with 150 additions and 40 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
token.txt
state.txt
*.sgf
*.png

Binary file not shown.

View File

@ -13,6 +13,11 @@ from discord.ext import commands
# https://pypi.org/project/discord-py-slash-command/
bot = commands.Bot(command_prefix='$', help_command=None)
format="%Y_%m_%d_%H_%M_%S_%f"
min_time_player= timedelta(minutes=1)
time_to_skip= timedelta(seconds=30) # in queue games, how much time to wait for the next move
min_players = 2
# People who can start and resign games :O
# Later we might replace this with checking for a role.
admins=[ 756220448118669463, # Young Sun
@ -23,24 +28,25 @@ admins=[ 756220448118669463, # Young Sun
with open("token.txt") as f:
token = f.readlines()[0] # Get your own token and put it in token.txt
f.close()
#client= discord.Client()
# The state is a list of tuples (channel_id, "queue"/"random", last_players, last_times, [player_id])
#@bot.command()
#async def test(ctx, arg1, arg2):
# await ctx.send('You passed {} and {}'.format(arg1, arg2))
@bot.command()
async def help(ctx):
print(ctx.message.content)
await ctx.send(
'$help : shows this help\n\n'+
'$join : join the game in this channel\n'+
'$leave: leave the game in this channel\n'+
'$play <move>: play a move. For example, `$play Q16`\n\n'+
'$play <move>: play a move. For example, `$play Q16`\n'+
'$edit <move>: if you make a mistake in your move, you have 5 minutes to correct it with this command\n\n'+
'$sgf: get the sgf file of the game\n'+
'$board: shows the current board\n'+
'$queue: get the queue of players\n\n'+
'$newgame <queue/random>: starts a game in this channel (admin only!)\n'+
'$resign <B/W>: resigns the game in this channel (admin only!)'
)
@ -53,7 +59,6 @@ async def play(ctx, arg):
# lowest effort serialization
with open("state.txt") as f: state = ast.literal_eval(f.read())
f.close()
filter_state= [i for i in range(len(state)) if state[i][0] == channel_id]
if not filter_state:
@ -62,30 +67,54 @@ 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]:
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]:
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])))
return
if state[i][1] == "queue" and user.id!= state[i][4][0]:
await ctx.send("It is not your turn yet!")
return
legal_moves=[chr(col+ord('A')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)]
if state[i][1] == "random":
assert( len(state[i][2]) == len(state[i][3]))
if len(state[i][2])>0 and state[i][2][-1] == user.id:
await ctx.send("No two consecutive moves per player!")
return
for j in range(len(state[i][2])):
if (state[i][2][j] == user.id and
datetime.now() - datetime.strptime(state[i][3][j],format) < min_time_player):
await ctx.send("At most one move per player per day!")
return
if state[i][3] != [] and datetime.now()-datetime.strptime(state[i][3][-1],format)<timedelta(seconds=4):
return #silent error
legal_moves=[chr(col+ord('A')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)]
legal_moves+=[chr(col+ord('a')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)]
if arg not in legal_moves:
await ctx.send("I don't understand the move! Please input it in the format `$play Q16`")
return
# TODO test for last player to move and time in free games
# perhaps we could switch it to max one move per player every 5 turns, put the last few players in state[i][4]
try:
sgfengine.play_move(str(channel_id), arg, user.name)
except ValueError as e:
await ctx.send(str(e))
return
state[i][4].pop(0)
state[i][4].append(user.id)
# move registered, let's do the other things
state[i][2].append(user.id)
state[i][3].append(datetime.now().strftime(format))
if state[i][1] == "queue":
state[i][4].pop(0)
state[i][4].append(user.id)
file = discord.File(str(ctx.channel.id)+".png")
if state[i][1]=="queue":
@ -95,7 +124,76 @@ async def play(ctx, arg):
await ctx.send(file=file)
with open("state.txt", "w") as f: f.write(repr(state))
f.close()
@bot.command()
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
# 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" and user.id not in state[i][4]:
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
if 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
legal_moves=[chr(col+ord('A')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)]
legal_moves+=[chr(col+ord('a')-1)+str(row) for col in range(1,21) if col!=9 for row in range(1,20)]
if arg not in legal_moves:
await ctx.send("I don't understand the move! Please input it in the format `$play Q16`")
return
try:
sgfengine.play_move(str(channel_id), arg, user.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))
else:
await ctx.send(file=file)
with open("state.txt", "w") as f: f.write(repr(state))
@bot.command()
async def board(ctx):
channel_id= ctx.channel.id
user = ctx.author
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]
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))
else:
await ctx.send(file=file)
@bot.command()
async def join(ctx):
@ -104,7 +202,6 @@ async def join(ctx):
# lowest effort serialization
with open("state.txt") as f: state = ast.literal_eval(f.read())
f.close()
filter_state= [i for i in range(len(state)) if state[i][0] == channel_id]
if not filter_state:
@ -126,7 +223,6 @@ async def join(ctx):
await ctx.send("User joined!")
with open("state.txt", "w") as f: f.write(repr(state))
f.close()
@bot.command()
async def queue(ctx):
@ -134,7 +230,6 @@ async def queue(ctx):
# lowest effort serialization
with open("state.txt") as f: state = ast.literal_eval(f.read())
f.close()
filter_state= [i for i in range(len(state)) if state[i][0] == channel_id]
if not filter_state:
@ -169,7 +264,6 @@ async def leave(ctx):
# lowest effort serialization
with open("state.txt") as f: state = ast.literal_eval(f.read())
f.close()
filter_state= [i for i in range(len(state)) if state[i][0] == channel_id]
if not filter_state:
@ -191,7 +285,6 @@ async def leave(ctx):
await ctx.send("User left :(")
with open("state.txt", "w") as f: f.write(repr(state))
f.close()
@bot.command()
async def newgame(ctx, arg):
@ -208,14 +301,13 @@ async def newgame(ctx, arg):
# lowest effort serialization
with open("state.txt") as f: state = ast.literal_eval(f.read())
f.close()
if ctx.channel.id in [ ch for (ch,_,_,_,_) in state]:
await ctx.send("A game is already active in this channel!")
return
sgfengine.new_game(str(ctx.channel.id))
state.append((ctx.channel.id, arg, None, None, []))
state.append((ctx.channel.id, arg, [], [], []))
file = discord.File(str(ctx.channel.id)+".png")
if arg=="queue":
@ -224,7 +316,6 @@ async def newgame(ctx, arg):
await ctx.send(file=file, content="A new game has started! Play with `$play <move>`")
with open("state.txt", "w") as f: f.write(repr(state))
f.close()
@bot.command()
async def resign(ctx, arg):
@ -240,7 +331,6 @@ async def resign(ctx, arg):
return
with open("state.txt") as f: state = ast.literal_eval(f.read())
f.close()
now=datetime.now()
file_name= "rengo_"+now.strftime("%Y_%m_%d_%H_%M_%S_")+ctx.channel.name+".sgf" #remove the hour minute and second later
@ -253,7 +343,6 @@ async def resign(ctx, arg):
state = [s for s in state if s[0]!=channel_id]
with open("state.txt", "w") as f: f.write(repr(state))
f.close()
async def background_task():
await bot.wait_until_ready()
@ -263,21 +352,37 @@ async def background_task():
while not bot.is_closed():
try:
print("ping")
await asyncio.sleep(1000)
# lowest effort serialization
with open("state.txt") as f: state = ast.literal_eval(f.read())
f.close()
#print(state)
#TODO find who has to move, skip players accordingly, notify if any has to move
for i in range(len(state)):
if state[i][3] == [] or state[i][1]!="queue": continue
with open("state.txt") as f: f.write(repr(state))
f.close()
channel_id= state[i][0]
channel= bot.get_channel(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: # Probably remove? Depends on how passive aggressive it is
next_user = await bot.fetch_user(state[i][4][0])
await channel.send("{}'s turn!".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]))
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)
except ConnectionResetError:
print("Connection error")
bot.loop.create_task(background_task())
bot.run(token)

View File

@ -16,7 +16,7 @@ def new_game(channel_id):
# 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):
def play_move(channel_id, messagestr, player, overwrite=False):
thecol= ord(messagestr[0].lower()) - ord('a')
if thecol>8: thecol-=1 # Go boards don't have an I column!!
@ -29,6 +29,12 @@ def play_move(channel_id, messagestr, player):
koban=None
node= game.get_last_node()
board, moves= sgf_moves.get_setup_and_moves(game)
if overwrite:
node2= node.parent
node.delete()
node= node2
moves= moves[:-1]
for (colour, (row, col)) in moves:
koban=board.play(row,col,colour)
@ -39,18 +45,16 @@ def play_move(channel_id, messagestr, player):
board2= board.copy()
try:
board2.play(therow, thecol, colour)
koban2=board2.play(therow, thecol, colour)
except ValueError as e:
raise ValueError("Illegal move1!")
if ascii_boards.render_board(board)== ascii_boards.render_board(board2):
if board2.get(therow,thecol) == None:
raise ValueError("Illegal move2!")
#print(moves)
node2= node.new_child()
node2.set(("B" if colour =='b' else "W"), (therow,thecol))
if koban is not None: node2.set("SQ", [koban])
if koban2 is not None: node2.set("SQ", [koban2])
node2.set("CR", [(therow, thecol)])
node2.set("C", player) # I think this would be fun for the review
if node.has_property("CR"): node.unset("CR")

View File

@ -1 +1 @@
[(881537306073387091, 'queue', None, None, [477895596141707264])]
[(870604751354613770, 'random', [477895596141707264], ['2021_08_30_19_03_29_444139'], [])]