2021-10-29 03:11:27 +02:00
#!/usr/bin/python3
from mastodon import Mastodon
import draughts_engine as dama
2022-05-01 04:06:09 +02:00
import four_engine
2021-10-29 03:11:27 +02:00
import login
import pickle
import random
import os
import time
import sys
2021-10-30 03:06:31 +02:00
import re
2021-11-03 13:25:44 +01:00
import logging
2021-10-29 03:11:27 +02:00
2021-11-03 03:34:24 +01:00
#configuration server
2021-10-29 03:11:27 +02:00
api_url = sys . argv [ 1 ]
2021-10-29 15:08:56 +02:00
save_position = " /tmp/ "
2021-10-29 03:11:27 +02:00
CLEANR = re . compile ( ' <.*?> ' )
2022-05-01 04:06:09 +02:00
#botname = "@damastodon "
botname = " @dama "
available_games = ( " draughts " , " conn4 " )
2021-10-29 03:11:27 +02:00
2021-11-03 03:34:24 +01:00
#board appearence
2021-11-03 02:04:39 +01:00
frstrow = " 🇻1️ ⃣ 2️ ⃣ 3️ ⃣ 4️ ⃣ 5️ ⃣ 6️ ⃣ 7️ ⃣ 8️ ⃣ \n "
column = " 🇦🇧🇨🇩🇪🇫🇬🇭 "
space = " 🟥 "
white_norm = " ◽ "
white_knight = " ⚪ "
black_norm = " ◾ "
black_knight = " ⚫ "
empty = " 🟦 "
2022-05-01 04:06:09 +02:00
#conn4
2022-05-02 02:41:46 +02:00
conn4row = " 1️ ⃣ 2️ ⃣ 3️ ⃣ 4️ ⃣ 5️ ⃣ 6️ ⃣ 7️ ⃣ \n "
2022-05-01 04:06:09 +02:00
2021-11-03 13:25:44 +01:00
#logging config
2021-11-12 17:35:30 +01:00
logging . basicConfig ( filename = " /tmp/dama.log " , level = logging . DEBUG )
2021-10-30 17:19:55 +02:00
2021-10-29 03:11:27 +02:00
def cleanHTML ( raw ) :
cleanText = re . sub ( CLEANR , ' ' , raw )
return cleanText
2022-05-01 04:06:09 +02:00
def lobby ( notification , content , account , extension ) :
2021-10-29 13:56:43 +02:00
try :
2022-05-01 04:06:09 +02:00
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 " )
2021-10-29 13:56:43 +02:00
return
2022-05-01 04:06:09 +02:00
if challenged == account : #The user tried to challenge him/herself
mastodon . status_post ( " Hello @ " + account + " \n You can ' t challenge yourself " , visibility = " direct " )
2021-10-29 03:11:27 +02:00
return
2022-05-01 04:06:09 +02:00
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 )
2022-05-02 02:41:46 +02:00
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 " )
2022-05-01 04:06:09 +02:00
return
elif content . split ( " " ) [ 1 ] . lower ( ) == " ok " and content . split ( " " ) [ 2 ] . lower ( ) == extension : #The opponent accepted the match
2021-10-29 16:31:33 +02:00
try :
2022-05-01 04:06:09 +02:00
challenger = notification [ " status " ] [ " mentions " ] [ 1 ] [ " acct " ]
2021-10-29 16:31:33 +02:00
except :
2022-05-01 04:06:09 +02:00
mastodon . status_post ( " Hello @ " + account + " \n your request is not valid " , visibility = " direct " ) #Somehow, the opponent answered with only one account
2021-10-29 16:31:33 +02:00
return
2022-05-01 04:06:09 +02:00
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 )
2021-10-29 03:11:27 +02:00
return
2022-05-01 04:06:09 +02:00
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
2021-10-29 03:11:27 +02:00
return
2022-05-01 04:06:09 +02:00
os . symlink ( save_position + challenger + " . " + extension , save_position + account + " . " + extension ) #Linked the two savegames together
if extension == " draughts " : #Draughts init
board = dama . init_board ( )
2022-05-02 02:51:28 +02:00
mastodon . status_post ( " ◾: @ " + account + " \n ◽: @ " + challenger + " \n turn ◽ \n " + dama . draw_checkerboard ( board , space , white_norm , white_knight , black_norm , black_knight , empty , column , frstrow ) , visibility = " direct " )
2022-05-01 04:06:09 +02:00
elif extension == " conn4 " : #Conn4 init
board = four_engine . initChequerboard ( )
2022-05-02 02:41:46 +02:00
mastodon . status_post ( " ⚪: @ " + account + " \n ⚫: @ " + challenger + " \n turn ⚪ \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
2022-05-01 04:06:09 +02:00
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
2021-10-29 03:11:27 +02:00
return
2022-05-01 04:06:09 +02:00
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 + " \n I can ' t understand your command or you ' re not in a match. \n Write HELP to see the list of available commands. " , visibility = " direct " ) #Every other command for the lobby ends here
return
2022-05-02 02:41:46 +02:00
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 ( " % f ile 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 + " \n the challenge has been withdrawn. " , visibility = " direct " )
else : #Lobby is disabled if a challenge request is active
mastodon . status_post ( " Hello @ " + account + " \n you 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
2022-05-01 04:06:09 +02:00
def check_message ( notification ) :
account = notification [ " account " ] [ " acct " ]
try :
content = cleanHTML ( notification [ " status " ] [ " content " ] )
except KeyError :
return
content = content [ len ( botname ) : ]
if " help " in content . lower ( ) : #Ask for help
mastodon . status_post ( " Hello @ " + account + " \n challenge an user in a game of draughts by writing to me \n DRAUGHTS <USERNAME> \n Ex. \" DRAUGHTS @someone@mastdn.inst.wxyz \" \n The challenger takes WHITE and begins the match. \n For movements and jumps, write the coords separated by spaces. \n Ex. \" A4 B5 \" (normal movement) or \" A4 C6 D8 \" (double jump) \n QUIT ends the match. \n Commands are NOT case sensitive. \n To challenge someone in a game of Connect 4 write CONN4 <USERNAME>. " , visibility = " direct " )
return
2022-05-02 02:41:46 +02:00
#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 ( 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 + " \n Invalid move. " , visibility = " direct " )
return
else :
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 + " \n turn " + 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 + " \n It ' s not your turn. " , visibility = " direct " )
return
2022-05-01 04:06:09 +02:00
#Draughts
if os . path . exists ( save_position + account + " .draughts " ) : #We are in a game, so movements are parsed and lobby commands are disabled
2022-05-02 02:51:28 +02:00
start , black , white , turn , board = load_status ( account , " draughts " , content )
2022-05-02 02:41:46 +02:00
if not ( start ) :
2021-10-29 03:11:27 +02:00
return
2022-05-02 02:41:46 +02:00
2021-11-03 03:34:24 +01:00
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
2021-10-29 15:17:58 +02:00
mastodon . status_post ( " @ " + account + " \n Invalid move. " , visibility = " direct " )
2021-10-29 03:11:27 +02:00
return
else :
2022-05-01 04:06:09 +02:00
with open ( save_position + account + " .draughts " , " wb " ) as f : #Save the updated board
2021-11-03 03:34:24 +01:00
pickle . dump ( " START " , f )
2021-10-29 03:11:27 +02:00
turn = not turn
pickle . dump ( black , f )
pickle . dump ( white , f )
pickle . dump ( turn , f )
pickle . dump ( board , f )
if turn == 0 :
colour = " ◽ "
else :
colour = " ◾ "
2021-11-03 03:34:24 +01:00
winner = dama . checkWin ( board ) #Check for winner
if winner == ( False , False ) : #No one is winning yet
2022-05-02 02:51:28 +02:00
mastodon . status_post ( " ◾: " + black + " \n ◽: " + white + " \n turn " + colour + " \n " + dama . draw_checkerboard ( board , space , white_norm , white_knight , black_norm , black_knight , empty , column , frstrow ) , visibility = " direct " )
2021-10-29 03:11:27 +02:00
return
2021-11-03 03:34:24 +01:00
else : #Someone won
2021-10-29 03:11:27 +02:00
if winner == ( True , False ) :
winner_t = " WHITE "
else :
winner_t = " BLACK "
2022-05-01 04:06:09 +02:00
os . remove ( save_position + black [ 1 : ] + " .draughts " )
os . remove ( save_position + white [ 1 : ] + " .draughts " )
2022-05-02 02:51:28 +02:00
mastodon . status_post ( " ◾: " + black + " \n ◽: " + white + " \n " + winner_t + " WINS! \n " + dama . draw_checkerboard ( board , space , white_norm , white_knight , black_norm , black_knight , empty , column , frstrow ) , visibility = " direct " )
2021-10-29 03:11:27 +02:00
return
2021-11-03 03:34:24 +01:00
else : #We moved in a wrong turn
2021-10-29 15:17:58 +02:00
mastodon . status_post ( " @ " + account + " \n It ' s not your turn. " , visibility = " direct " )
2022-05-01 04:06:09 +02:00
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 )
2021-10-29 03:11:27 +02:00
if __name__ == " __main__ " :
if api_url [ : 4 ] != " http " :
2021-11-03 13:25:44 +01:00
logging . error ( " Invalid address " )
2021-10-29 03:11:27 +02:00
quit ( )
mastodon = login . login ( api_url )
while True :
time . sleep ( 10 )
for x in mastodon . notifications ( ) :
check_message ( x )
mastodon . notifications_dismiss ( x [ " id " ] )