UP
This commit is contained in:
7
rengobot/.gitignore
vendored
Normal file
7
rengobot/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
*.txt
|
||||
*.sgf
|
||||
*.png
|
||||
*.out
|
||||
*.sh
|
||||
__pycache__/
|
||||
rengobot_venv/
|
12
rengobot/README.md
Normal file
12
rengobot/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
A fork of
|
||||
|
||||
https://github.com/ReneCareenium/rengobot
|
||||
|
||||
A discord bot for playing rengo games!
|
||||
|
||||
# Dependencies
|
||||
- sgf-render
|
||||
- python-discord
|
||||
- python-sgfmill
|
||||
|
||||
Make sure to run the bot in an environment with read/write permissions
|
202
rengobot/rengobot.py
Normal file
202
rengobot/rengobot.py
Normal file
@@ -0,0 +1,202 @@
|
||||
import os
|
||||
import ast
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import asyncio
|
||||
|
||||
import sgfengine
|
||||
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
# We don't use fancy slash commands here. It seems there is this library for python but it looks a bit more involved.
|
||||
# https://pypi.org/project/discord-py-slash-command/
|
||||
|
||||
intents = discord.Intents.default()
|
||||
#intents.message_content = True
|
||||
|
||||
bot = commands.Bot(command_prefix='$', help_command=None, intents=intents)
|
||||
|
||||
# People who can start and resign games :O
|
||||
admins=[]
|
||||
awesome_server_id=
|
||||
permitted_channel_id=
|
||||
|
||||
player_id= 0
|
||||
start_time = 0
|
||||
|
||||
with open("token.txt") as f:
|
||||
token = f.readlines()[0] # Get your own token and put it in token.txt
|
||||
|
||||
@bot.command()
|
||||
async def help(ctx):
|
||||
if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id:
|
||||
return
|
||||
|
||||
await ctx.send(
|
||||
'$help : shows this help\n\n'+
|
||||
|
||||
'$play <move>: play a move. For example, `$play Q16`. Passing is not implemented!\n'+
|
||||
'$edit <move>: if you make a mistake in your move\n\n'+
|
||||
|
||||
'$sgf: get the sgf file of the game\n'+
|
||||
'$board: shows the current board\n'+
|
||||
|
||||
'$newgame <handicap> <komi>: starts a game in this channel (admin only!)\n'+
|
||||
'$resign <B/W>: <B/W> resigns the game in this channel. It returns its sgf file (admin only!)'
|
||||
)
|
||||
# ctx has guild, message, author, send, and channel (?)
|
||||
|
||||
@bot.command()
|
||||
async def play(ctx, arg):
|
||||
if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id:
|
||||
return
|
||||
|
||||
channel_id= ctx.channel.id
|
||||
user = ctx.author
|
||||
guild= ctx.guild
|
||||
|
||||
global player_id
|
||||
global start_time
|
||||
|
||||
if not os.path.exists(str(channel_id)+".sgf"):
|
||||
await ctx.send("No active game in this channel!")
|
||||
return
|
||||
|
||||
|
||||
if (player_id == user.id):
|
||||
await ctx.send("No two consecutive moves by the same player!")
|
||||
return
|
||||
|
||||
elapsed_time = time.time() - start_time
|
||||
if elapsed_time < 1:
|
||||
await ctx.send("One second per 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.display_name)
|
||||
except ValueError as e:
|
||||
await ctx.send(str(e))
|
||||
return
|
||||
|
||||
player_id=user.id
|
||||
start_time = time.time()
|
||||
|
||||
file = discord.File(str(ctx.channel.id)+".png")
|
||||
|
||||
await ctx.send(file=file)
|
||||
|
||||
|
||||
@bot.command()
|
||||
async def edit(ctx, arg):
|
||||
if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id:
|
||||
return
|
||||
channel_id= ctx.channel.id
|
||||
user = ctx.author
|
||||
guild= ctx.guild
|
||||
|
||||
global player_id
|
||||
if (player_id == user.id):
|
||||
await ctx.send("Not the player that made the mistake!")
|
||||
return
|
||||
|
||||
if not os.path.exists(str(channel_id)+".sgf"):
|
||||
await ctx.send("No active game in this channel!")
|
||||
return
|
||||
|
||||
colour= sgfengine.next_colour(str(channel_id))
|
||||
|
||||
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.display_name, True)
|
||||
except ValueError as e:
|
||||
await ctx.send(str(e))
|
||||
return
|
||||
|
||||
file = discord.File(str(ctx.channel.id)+".png")
|
||||
await ctx.send(file=file)
|
||||
|
||||
@bot.command()
|
||||
async def board(ctx):
|
||||
if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id:
|
||||
return
|
||||
|
||||
channel_id= ctx.channel.id
|
||||
user = ctx.author
|
||||
guild= ctx.guild
|
||||
|
||||
if not os.path.exists(str(channel_id)+".sgf"):
|
||||
await ctx.send("No active game in this channel!")
|
||||
return
|
||||
|
||||
os.system("/home/nik/.cargo/bin/sgf-render -f png --style fancy --label-sides nesw -o "+str(channel_id)+".png -n last "+str(channel_id)+".sgf")
|
||||
file = discord.File(str(ctx.channel.id)+".png")
|
||||
|
||||
await ctx.send(file=file)
|
||||
|
||||
@bot.command()
|
||||
async def sgf(ctx):
|
||||
if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id:
|
||||
return
|
||||
|
||||
file = discord.File(str(ctx.channel.id)+".sgf")
|
||||
await ctx.send(file=file)
|
||||
|
||||
@bot.command()
|
||||
async def newgame(ctx, handicap=0, komi=6.5):
|
||||
if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id:
|
||||
return
|
||||
|
||||
channel_id= ctx.channel.id
|
||||
user = ctx.author
|
||||
|
||||
if user.id not in admins:
|
||||
await ctx.send("You don't have permissions for this!")
|
||||
return
|
||||
|
||||
sgfengine.new_game(str(channel_id), handicap, komi)
|
||||
|
||||
os.system("/home/nik/.cargo/bin/sgf-render -f png --style fancy --label-sides nesw -o "+str(channel_id)+".png -n last "+str(channel_id)+".sgf")
|
||||
|
||||
file = discord.File(str(channel_id)+".png")
|
||||
|
||||
await ctx.send(file=file, content="A new game has started! Play with `$play <move>`")
|
||||
|
||||
@bot.command()
|
||||
async def resign(ctx, arg):
|
||||
if ctx.guild.id != awesome_server_id or ctx.channel.id != permitted_channel_id:
|
||||
return
|
||||
|
||||
channel_id= ctx.channel.id
|
||||
user = ctx.author
|
||||
|
||||
if user.id not in admins:
|
||||
await ctx.send("You don't have permissions for this!")
|
||||
return
|
||||
|
||||
if arg not in ["W","B"]:
|
||||
await ctx.send("Unrecognized colour! Please try `$resign <B/W>` to resign as Black/White")
|
||||
return
|
||||
|
||||
now=datetime.now()
|
||||
file_name= "rengo_"+now.strftime("%Y_%m_%d_%H_%M_%S_")+ctx.channel.name+".sgf"
|
||||
|
||||
sgfengine.resign(str(channel_id), arg, file_name)
|
||||
|
||||
file = discord.File(file_name)
|
||||
await ctx.send(file=file, content=("Black" if arg=="W" else "White")+" wins!")
|
||||
|
||||
|
||||
print("Running")
|
||||
bot.run(token)
|
105
rengobot/sgfengine.py
Normal file
105
rengobot/sgfengine.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# Needs sgf-render and sgfmill
|
||||
# https://mjw.woodcraft.me.uk/sgfmill/doc/1.1.1/properties.html?highlight=list%20properties
|
||||
import os
|
||||
from sgfmill import sgf, boards, sgf_moves, ascii_boards
|
||||
|
||||
# This file only deals with the png and sgf side of things. To manage users etc go to the main file.
|
||||
|
||||
def new_game(channel_id, handicap=0, komi=6.5):
|
||||
game= sgf.Sgf_game(19)
|
||||
game.root.set("KM", komi)
|
||||
if handicap>=2:
|
||||
game.root.set("HA", handicap)
|
||||
|
||||
handicap_dict={
|
||||
2: [(3,3), (15,15)],
|
||||
3: [(3,3), (15,15), (15,3)],
|
||||
4: [(3,3), (15,15), (15,3), (3,15)],
|
||||
5: [(3,3), (15,15), (15,3), (3,15), (9,9)],
|
||||
6: [(3,3), (15,15), (15,3), (3,15), (9,3), (9,15)],
|
||||
7: [(3,3), (15,15), (15,3), (3,15), (9,3), (9,15), (9,9)],
|
||||
8: [(3,3), (15,15), (15,3), (3,15), (9,3), (9,15), (3,9), (15,9)],
|
||||
9: [(3,3), (15,15), (15,3), (3,15), (9,3), (9,15), (3,9), (15,9), (9,9)]}
|
||||
game.root.set("AB",handicap_dict[handicap])
|
||||
|
||||
with open (channel_id+".sgf", "wb") as f:
|
||||
f.write(game.serialise())
|
||||
f.close()
|
||||
|
||||
os.system("/home/nik/.cargo/bin/sgf-render -f png --style fancy --label-sides nesw -o "+str(channel_id)+".png -n last "+str(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() or "AB" 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):
|
||||
|
||||
thecol= ord(messagestr[0].lower()) - ord('a')
|
||||
if thecol>8: thecol-=1 # Go boards don't have an I column!!
|
||||
therow= int(messagestr[1:]) - 1
|
||||
|
||||
with open(channel_id+".sgf","rb") as f:
|
||||
game = sgf.Sgf_game.from_bytes(f.read())
|
||||
f.close()
|
||||
|
||||
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)
|
||||
|
||||
if (therow, thecol)==koban:
|
||||
raise ValueError("Ko banned move!")
|
||||
|
||||
colour = "w" if ("B" in node.properties() or "AB" in node.properties()) else "b"
|
||||
|
||||
board2= board.copy()
|
||||
try:
|
||||
koban2=board2.play(therow, thecol, colour)
|
||||
except ValueError as e:
|
||||
raise ValueError("Illegal move! There is a stone there.")
|
||||
|
||||
if board2.get(therow,thecol) == None:
|
||||
raise ValueError("Illegal move! No self-captures allowed.")
|
||||
|
||||
node2= node.new_child()
|
||||
node2.set(("B" if colour =='b' else "W"), (therow,thecol))
|
||||
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")
|
||||
if node.has_property("SQ"): node.unset("SQ")
|
||||
|
||||
with open (channel_id+".sgf", "wb") as f:
|
||||
f.write(game.serialise())
|
||||
f.close()
|
||||
|
||||
os.system("/home/nik/.cargo/bin/sgf-render -f png --style fancy --label-sides nesw -o "+str(channel_id)+".png -n last "+str(channel_id)+".sgf")
|
||||
|
||||
# colour is "B" if black resigns, "W" if white resigns
|
||||
def resign(channel_id, colour, file_name):
|
||||
with open(channel_id+".sgf","rb") as f:
|
||||
game = sgf.Sgf_game.from_bytes(f.read())
|
||||
f.close()
|
||||
|
||||
node= game.root
|
||||
node.set("RE", ("B" if colour=="W" else "W")+"+R")
|
||||
|
||||
with open (file_name, "wb") as f:
|
||||
f.write(game.serialise())
|
||||
f.close()
|
||||
|
||||
os.remove(channel_id+".sgf")
|
Reference in New Issue
Block a user