1
0
Fork 0
mirror of https://gitlab.com/oloturia/damastodon.git synced 2025-01-06 21:47:15 +01:00

Merge branch 'connect4' into 'main'

Connect4

See merge request oloturia/damastodon!10
This commit is contained in:
Mattioli 2022-05-02 00:43:08 +00:00
commit ce93acc6d2
4 changed files with 430 additions and 88 deletions

136
.gitignore vendored Normal file
View file

@ -0,0 +1,136 @@
TOKEN
TOKEN.LOCAL
TOKEN.OLD
DAMA.SECRET
DAMA.SECRET.LOCAL
DAMA.SECRET.OLD
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/

View file

@ -2,6 +2,7 @@
from mastodon import Mastodon
import draughts_engine as dama
import four_engine
import login
import pickle
import random
@ -15,7 +16,10 @@ import logging
api_url = sys.argv[1]
save_position = "/tmp/"
CLEANR = re.compile('<.*?>')
botname = "@damastodon "
#botname = "@damastodon "
botname = "@dama "
available_games = ("draughts","conn4")
#board appearence
frstrow = "🇻1⃣ 2⃣ 3⃣ 4⃣ 5⃣ 6⃣ 7⃣ 8\n"
@ -27,6 +31,9 @@ black_norm="◾ "
black_knight=""
empty="🟦 "
#conn4
conn4row = "1⃣ 2⃣ 3⃣ 4⃣ 5⃣ 6⃣ 7\n"
#logging config
logging.basicConfig(filename="/tmp/dama.log",level=logging.DEBUG)
@ -34,6 +41,101 @@ def cleanHTML(raw):
cleanText = re.sub(CLEANR, '',raw)
return cleanText
def lobby(notification,content,account,extension):
try:
challenged = notification["status"]["mentions"][1]["acct"] #If there isn't another account cited, then the request is malformed
except:
mastodon.status_post("Hello @"+account+" \n your request is not valid",visibility="direct")
return
if challenged == account: #The user tried to challenge him/herself
mastodon.status_post("Hello @"+account+"\n You can't challenge yourself",visibility="direct")
return
if content.split(" ")[0].lower() == extension: #Challenge someone, check if a savegame is already existing
if os.path.exists(save_position+challenged+"."+extension):
mastodon.status_post("Hello @"+account+" \n the user you challenged is already playing a match",visibility="direct")
return
else:
with open(save_position+account+"."+extension,"wb") as f: #The request is valid, writes a savegame with the first element as False, that marks that the game isn't started yet
pickle.dump("WAIT",f)
ident = mastodon.status_post("Hello @"+challenged+" \n@"+account+" challenged you to a match of "+extension+"! Reply \n @"+account+" OK "+extension.upper()+"\n to accept the challenge or \n@"+account+" NO "+extension.upper()+"\n to cancel.",visibility="direct")
return
elif content.split(" ")[1].lower() == "ok" and content.split(" ")[2].lower() == extension: #The opponent accepted the match
try:
challenger = notification["status"]["mentions"][1]["acct"]
except:
mastodon.status_post("Hello @"+account+" \n your request is not valid",visibility="direct") #Somehow, the opponent answered with only one account
return
try:
with open(save_position+challenger+"."+extension,"rb") as f:
start = pickle.load(f)
except FileNotFoundError:
mastodon.status_post("Hello @"+account+" \n unfortunately, your savegame is corrupted or missing",visibility="direct") #The file has moved or corrupted
logging.warning("%s file not found",account)
return
if start != "WAIT":
mastodon.status_post("Hello @"+account+" \n your request is not valid",visibility="direct") #The user that challenged us is playing a game with someone else
return
os.symlink(save_position+challenger+"."+extension,save_position+account+"."+extension) #Linked the two savegames together
if extension == "draughts": #Draughts init
board = dama.init_board()
mastodon.status_post("◾: @"+account+" ◽: @"+challenger+" \nturn ◽\n"+dama.draw_checkerboard(board,space,white_norm,white_knight,black_norm,black_knight,empty,column,frstrow),visibility="direct")
elif extension == "conn4": #Conn4 init
board = four_engine.initChequerboard()
mastodon.status_post("⚪: @"+account+" \n⚫: @"+challenger+" \nturn ⚪\n"+four_engine.drawChequerboard(board,players=[white_knight,black_knight],space=empty,toprow=conn4row),visibility="direct")
with open(save_position+account+"."+extension,"wb") as f: #Writing the file with the status of the game
pickle.dump("START",f) #Now the game has started
pickle.dump("@"+account,f)
pickle.dump("@"+challenger,f)
pickle.dump(False,f)
pickle.dump(board,f)
return
elif content.split(" ")[1].lower() == "no" and content.split(" ")[2].lower == extension: #The opponent refused the game
try:
challenger = notification["status"]["mentions"][1]["acct"]
except:
mastodon.status_post("Hello @"+account+" \n your request is not valid",visibility="direct") #Only one user in the message
return
os.remove(save_position+challenger+"."+extension)
mastodon.status_post("@"+account+" you cancelled the challenge from @"+challenger,visibility="direct") #Game is cancelled, free to challenge someone else
return
else:
mastodon.status_post("Hello @"+account+" \nI can't understand your command or you're not in a match.\nWrite HELP to see the list of available commands.",visibility="direct") #Every other command for the lobby ends here
return
def load_status(account,extension,content):
with open(save_position+account+"."+extension,"rb") as f: #Open the status file - extension is the type of the game
try:
start = pickle.load(f)
except EOFError: #Something wrong happened
mastodon.status_post("Hello @"+account+" \n unfortunately your savegame is corrupted or missing. The game is cancelled.",visibility="direct")
os.remove(save_position+account+"."+extension)
logging.warning("% file corrupted or missing",account+"."+extension)
return False
player_1 = pickle.load(f) #Read status from file
player_2 = pickle.load(f)
turn = pickle.load(f)
board = pickle.load(f)
if (start == "WAIT"): #The game is not started yet
if "quit" in content.lower(): #Game withdrawn
os.remove(save_position+account+"."+extension)
mastodon.status_post("Hello @"+account+" \nthe challenge has been withdrawn.",visibility="direct")
else: #Lobby is disabled if a challenge request is active
mastodon.status_post("Hello @"+account+" \nyou have already challenged someone, type QUIT to withdraw,",visibility="direct")
return False
if "quit" in content.lower(): #The game is quitted
os.remove(save_position+player_1[1:]+"."+extension)
os.remove(save_position+player_2[1:]+"."+extension)
mastodon.status_post(player_2+" "+player_1+" the match was cancelled.",visibility="direct")
return False
return True,player_1,player_2,turn,board
def check_message(notification):
account = notification["account"]["acct"]
try:
@ -41,101 +143,60 @@ def check_message(notification):
except KeyError:
return
content = content[len(botname):]
saves = os.listdir(save_position)
if "help" in content.lower(): #Ask for help
mastodon.status_post("Hello @"+account+" \nchallenge an user by writing to me\nCHALL <USERNAME>\nEx. \"CHALL @someone@mastdn.inst.wxyz\"\nThe challenger takes WHITE and begins the match.\nFor movements and jumps, write the coords separated by spaces.\nEx. \"A4 B5\" (normal movement) or \"A4 C6 D8\" (double jump)\nQUIT ends the match.\nCommands are NOT case sensitive.",visibility="direct")
mastodon.status_post("Hello @"+account+" \nchallenge an user in a game of draughts by writing to me\nDRAUGHTS <USERNAME>\nEx. \"DRAUGHTS @someone@mastdn.inst.wxyz\"\nThe challenger takes WHITE and begins the match.\nFor movements and jumps, write the coords separated by spaces.\nEx. \"A4 B5\" (normal movement) or \"A4 C6 D8\" (double jump)\nQUIT ends the match.\nCommands are NOT case sensitive.\nTo challenge someone in a game of Connect 4 write CONN4 <USERNAME>.",visibility="direct")
return
if not os.path.exists(save_position+account): #If there is no a savegame file, then lobby mode is activated
try:
challenged = notification["status"]["mentions"][1]["acct"] #If there isn't another account cited, then the request is malformed
except:
mastodon.status_post("Hello @"+account+" \n your request is not valid",visibility="direct")
#Conn4
if os.path.exists(save_position+account+".conn4"):
start,player_1,player_2,turn,board = load_status(account,"conn4",content)
if not(start):
return
if content[:5].lower() == "chall": #Challenge someone, check if a savegame is already existing
file_save_white = [sv for sv in saves if account in sv]
file_save_black = [sv for sv in saves if challenged in sv]
if len(file_save_white) > 0: #We are playing a match
mastodon.status_post("Hello @"+account+" \n you're already playing a match",visibility="direct")
return
elif len(file_save_black) > 0: #Our opponent is already playing with someone
mastodon.status_post("Hello @"+account+" \n the user you challenged is already playing a match",visibility="direct")
if (player_2 == "@"+account and turn == 1) or (player_1 == "@"+account and turn == 0):
board,win = four_engine.dropChip(board,content.lower()[-1],turn+1)
if not(board):
mastodon.status_post("@"+account+" \nInvalid move.",visibility="direct")
return
else:
with open(save_position+account,"wb") as f: #The request is valid, writes a savegame with the first element as False, that marks that the game isn't started yet
pickle.dump("WAIT",f)
ident = mastodon.status_post("Hello @"+challenged+" \n@"+account+" challenged you to a match of draughts! Answer \n@"+account+" OK\n to accept the chellenge or \n@"+account+" NO\n to cancel.",visibility="direct")
return
elif content.split(" ")[1].lower() == "ok": #The opponent accepted the match
try:
challenger = notification["status"]["mentions"][1]["acct"]
except:
mastodon.status_post("Hello @"+account+" \n your request is not valid",visibility="direct") #Somehow, the opponent answered with only one account
return
try:
with open(save_position+challenger,"rb") as f:
start = pickle.load(f)
except FileNotFoundError:
mastodon.status_post("Hello @"+account+" \n unfortunately, your savegame is corrupted or missing",visibility="direct") #The file has moved or corrupted
logging.warning("%s file not found",account)
return
if start != "WAIT":
mastodon.status_post("Hello @"+account+" \n your request is not valid",visibility="direct") #The user that challenged us is playing a game with someone else
return
os.symlink(save_position+challenger,save_position+account) #Linked the two savegames together
board = dama.init_board()
with open(save_position+account,"wb") as f:
pickle.dump("START",f) #Now the game has started
pickle.dump("@"+account,f)
pickle.dump("@"+challenger,f)
pickle.dump(False,f)
pickle.dump(board,f)
mastodon.status_post("◾: @"+account+" ◽: @"+challenger+" \nturn ◽\n"+dama.draw_checkerboard(board,space,white_norm,white_knight,black_norm,black_knight,empty,column,frstrow),visibility="direct")
return
elif content.split(" ")[1].lower() == "no": #The opponent refused the game
try:
challenger = notification["status"]["mentions"][1]["acct"]
except:
mastodon.status_post("Hello @"+account+" \n your request is not valid",visibility="direct") #Only one user in the message
return
os.remove(save_position+challenger)
mastodon.status_post("@"+account+" you cancelled the challenge from @"+challenger,visibility="direct") #Game is cancelled, free to challenge someone else
return
else:
mastodon.status_post("Hello @"+account+" \nI can't understand your command or you're not in a match.\nWrite HELP to see the list of available commands.",visibility="direct") #Every other command for the lobby ends here
return
else: #We are in a game, so movements are parsed and lobby commands are disabled
with open(save_position+account,"rb") as f:
try:
start = pickle.load(f)
except EOFError: # Something went very wrong, file is corrupt?
mastodon.status_post("Hello @"+account+" \n unfortunately, your savegame is corrupted or missing",visibility="direct") #The file has been moved or is corrupted
os.remove(save_position+account)
logging.warning("%s file corrupted",account)
return
if start: #The game is started, load other parameters
black = pickle.load(f)
white = pickle.load(f)
turn = pickle.load(f)
board = pickle.load(f)
if (start == "WAIT"): #The game is not started yet
if "quit" in content.lower(): #Game withdrawn
os.remove(save_position+account)
mastodon.status_post("Hello @"+account+" \nthe challenge has been withdrawn.",visibility="direct")
else: #Lobby is disabled if a challenge request is active
mastodon.status_post("Hello @"+account+" \nyou have already challenged someone, type QUIT to withdraw,",visibility="direct")
return
if "quit" in content.lower(): #The game is quitted
os.remove(save_position+black[1:])
os.remove(save_position+white[1:])
mastodon.status_post(black+" "+white+" the match was cancelled.",visibility="direct")
with open(save_position+account+".conn4","wb") as f:
pickle.dump("START",f)
turn = not turn
pickle.dump(player_1,f)
pickle.dump(player_2,f)
pickle.dump(turn,f)
pickle.dump(board,f)
if turn == 0: #the first is the current turn, the second is the last turn
colour = (white_knight,black_knight)
else:
colour = (black_knight,white_knight)
if win == 0:
mastodon.status_post("⚪: "+player_1+" \n⚫: "+player_2+" \nturn "+colour[0]+"\n"+four_engine.drawChequerboard(board,players=[white_knight,black_knight],space=empty,toprow=conn4row),visibility="direct")
return
else: #Someone won!
mastodon.status_post("⚪: "+player_1+" \n⚫: "+player_2+" \n"+colour[1]+" WINS!\n"+four_engine.drawChequerboard(board,players=[white_knight,black_knight],space=empty,toprow=conn4row),visibility="direct")
os.remove(save_position+player_1[1:]+".conn4")
os.remove(save_position+player_2[1:]+".conn4")
return
else: #We moved in a wrong turn
mastodon.status_post("@"+account+" \nIt's not your turn.",visibility="direct")
return
#Draughts
if os.path.exists(save_position+account+".draughts"): #We are in a game, so movements are parsed and lobby commands are disabled
start,black,white,turn,board = load_status(account,"conn4")
if not(start):
return
if (black == "@"+account and turn == 1) or (white == "@"+account and turn == 0): #Check if the turn is right
board = dama.valid_move(content.lower(),turn,board,inversion=True) #Function dama.valid_move parses the input for couples of letter and number
if board == -1: #We made an invalid move
mastodon.status_post("@"+account+" \nInvalid move.",visibility="direct")
return
else:
with open(save_position+account,"wb") as f: #Save the updated board
with open(save_position+account+".draughts","wb") as f: #Save the updated board
pickle.dump("START",f)
turn = not turn
pickle.dump(black,f)
@ -155,13 +216,19 @@ def check_message(notification):
winner_t = "WHITE"
else:
winner_t = "BLACK"
os.remove(save_position+black[1:])
os.remove(save_position+white[1:])
os.remove(save_position+black[1:]+".draughts")
os.remove(save_position+white[1:]+".draughts")
mastodon.status_post("◾: "+black+" ◽: "+white+"\n"+winner_t+" WINS!\n"+dama.draw_checkerboard(board,space,white_norm,white_knight,black_norm,black_knight,empty,column,frstrow),visibility="direct")
return
else: #We moved in a wrong turn
mastodon.status_post("@"+account+" \nIt's not your turn.",visibility="direct")
return
return
#Lobby
for game in available_games:
if game in content.lower():
if not os.path.exists(save_position+account+"."+game): #If there is no a savegame file, then lobby mode is activated
lobby(notification,content,account,game)
if __name__ == "__main__":
if api_url[:4] != "http":

139
four_engine.py Executable file
View file

@ -0,0 +1,139 @@
#!/usr/bin/python3
def drawChequerboard(status,players=[],space="",toprow="1234567"):
bstr = ""
bstr += toprow+"\n"
for row in status:
for cell in row:
if cell == 0:
bstr += space
else:
try:
bstr += players[cell-1]
except IndexError:
bstr += str(cell)
bstr += "\n"
return bstr
def initChequerboard(cols=7,rows=6):
board = list()
for row in range(rows):
board.append([0]*7)
return board
def dropChip(board,move_str,player):
failure = (False,0)
try:
move = int(move_str)-1
except ValueError:
return failure
if move < 0 or move >= len(board[0]):
return failure
free_space = -1
for row in board:
if row[move] != 0:
break
else:
free_space += 1
if free_space == -1:
return failure
board[free_space][move] = player
return board, checkFour(board,free_space,move)
def checkFour(board,row,col):
sumOr = lambda a,b : (a[0]+b[0], a[1]+b[1])
orients = { "N":(1,0),"S":(-1,0),"E":(0,1),"W":(0,-1) }
player = board[row][col]
points = 0
count = 0
count += countTokens(board,orients["N"],row,col)
count += countTokens(board,orients["S"],row,col)
if count >= 3:
points +=1
count = 0
count += countTokens(board,orients["E"],row,col)
count += countTokens(board,orients["W"],row,col)
if count >= 3:
points +=1
count = 0
count += countTokens(board,sumOr( orients["N"],orients["E"] ),row,col )
count += countTokens(board,sumOr( orients["S"],orients["W"] ),row,col )
if count >= 3:
points +=1
count = 0
count += countTokens(board,sumOr( orients["N"],orients["W"] ),row,col )
count += countTokens(board,sumOr( orients["S"],orients["E"] ),row,col )
if count >= 3:
points +=1
return points
def countTokens(board,check,row,col):
player = board[row][col]
offX = check[0]
offY = check[1]
count = 0
while True:
if row+offY < 0 or row+offY >= len(board[row])-1 or col+offX < 0 or col+offX > len(board) or (board[row+offY][col+offX] != player) :
break
else:
count += 1
offX += check[0]
offY += check[1]
return count
def checkPly(board,row,col,player):
if row < 0 or col < 0:
return False
try:
if board[row][col] == player:
return True
else:
return False
except IndexError:
return False
if __name__ == "__main__":
board = initChequerboard()
match = True
player = 1
points_1 = 0
points_2 = 0
while match:
print( drawChequerboard(board) )
print("Player 1:"+str(points_1))
print("Player 2:"+str(points_2))
move = input("Player "+str(player)+" turn:")
if move == "q":
print("quitting")
quit()
valid, point = dropChip(board,move,player)
if not(valid):
print("Invalid move")
else:
board = valid
if player == 2:
points_2 += point
player = 1
else:
points_1 += point
player = 2
match = False
for row in board:
for cell in row:
if cell == 0:
match = True
print("Match over")
print( drawChequerboard(board) )
print("Player 1 scored "+str(points_1)+" points")
print("Player 2 scored "+str(points_2)+" points")
if points_1 > points_2:
print("Player 1 won!")
elif points_2 > points_1:
print("Player 2 won!")
else:
print("Draw!")

View file

@ -6,7 +6,7 @@ fileDir = fileDir +"/"
def login(url):
mastodon = Mastodon(
access_token = fileDir+"DAMA.SECRET",
access_token = fileDir+"DAMA.SECRET.LOCAL",
api_base_url = url
)
return mastodon