damastodon.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. #!/usr/bin/python3
  2. from mastodon import Mastodon
  3. import draughts_engine as dama
  4. import four_engine
  5. import login
  6. import pickle
  7. import random
  8. import os
  9. import time
  10. import sys
  11. import re
  12. import logging
  13. #configuration server
  14. api_url = sys.argv[1]
  15. save_position = "/tmp/"
  16. CLEANR = re.compile('<.*?>')
  17. #botname = "@damastodon "
  18. botname = "@dama "
  19. available_games = ("draughts","conn4")
  20. #board appearence
  21. frstrow = "🇻1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣\n"
  22. column = "🇦🇧🇨🇩🇪🇫🇬🇭"
  23. space="🟥 "
  24. white_norm="◽ "
  25. white_knight="⚪ "
  26. black_norm="◾ "
  27. black_knight="⚫ "
  28. empty="🟦 "
  29. #conn4
  30. conn4row = "1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣\n"
  31. #logging config
  32. logging.basicConfig(filename="/tmp/dama.log",level=logging.DEBUG)
  33. def cleanHTML(raw):
  34. cleanText = re.sub(CLEANR, '',raw)
  35. return cleanText
  36. def lobby(notification,content,account,extension):
  37. try:
  38. challenged = notification["status"]["mentions"][1]["acct"] #If there isn't another account cited, then the request is malformed
  39. except:
  40. mastodon.status_post("Hello @"+account+" \n your request is not valid",visibility="direct")
  41. return
  42. if challenged == account: #The user tried to challenge him/herself
  43. mastodon.status_post("Hello @"+account+"\n You can't challenge yourself",visibility="direct")
  44. return
  45. if content.split(" ")[0].lower() == extension: #Challenge someone, check if a savegame is already existing
  46. if os.path.exists(save_position+challenged+"."+extension):
  47. mastodon.status_post("Hello @"+account+" \n the user you challenged is already playing a match",visibility="direct")
  48. return
  49. else:
  50. 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
  51. pickle.dump("WAIT",f)
  52. 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")
  53. return
  54. elif content.split(" ")[1].lower() == "ok" and content.split(" ")[2].lower() == extension: #The opponent accepted the match
  55. try:
  56. challenger = notification["status"]["mentions"][1]["acct"]
  57. except:
  58. mastodon.status_post("Hello @"+account+" \n your request is not valid",visibility="direct") #Somehow, the opponent answered with only one account
  59. return
  60. try:
  61. with open(save_position+challenger+"."+extension,"rb") as f:
  62. start = pickle.load(f)
  63. except FileNotFoundError:
  64. mastodon.status_post("Hello @"+account+" \n unfortunately, your savegame is corrupted or missing",visibility="direct") #The file has moved or corrupted
  65. logging.warning("%s file not found",account)
  66. return
  67. if start != "WAIT":
  68. 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
  69. return
  70. os.symlink(save_position+challenger+"."+extension,save_position+account+"."+extension) #Linked the two savegames together
  71. if extension == "draughts": #Draughts init
  72. board = dama.init_board()
  73. mastodon.status_post("◾: @"+account+" \n◽: @"+challenger+" \nturn ◽\n"+dama.draw_checkerboard(board,space,white_norm,white_knight,black_norm,black_knight,empty,column,frstrow),visibility="direct")
  74. elif extension == "conn4": #Conn4 init
  75. board = four_engine.initChequerboard()
  76. mastodon.status_post("⚪: @"+account+" \n⚫: @"+challenger+" \nturn ⚪\n"+four_engine.drawChequerboard(board,players=[white_knight,black_knight],space=empty,toprow=conn4row),visibility="direct")
  77. with open(save_position+account+"."+extension,"wb") as f: #Writing the file with the status of the game
  78. pickle.dump("START",f) #Now the game has started
  79. pickle.dump("@"+account,f)
  80. pickle.dump("@"+challenger,f)
  81. pickle.dump(False,f)
  82. pickle.dump(board,f)
  83. return
  84. elif content.split(" ")[1].lower() == "no" and content.split(" ")[2].lower == extension: #The opponent refused the game
  85. try:
  86. challenger = notification["status"]["mentions"][1]["acct"]
  87. except:
  88. mastodon.status_post("Hello @"+account+" \n your request is not valid",visibility="direct") #Only one user in the message
  89. return
  90. os.remove(save_position+challenger+"."+extension)
  91. mastodon.status_post("@"+account+" you cancelled the challenge from @"+challenger,visibility="direct") #Game is cancelled, free to challenge someone else
  92. return
  93. else:
  94. 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
  95. return
  96. def load_status(account,extension,content):
  97. with open(save_position+account+"."+extension,"rb") as f: #Open the status file - extension is the type of the game
  98. try:
  99. start = pickle.load(f)
  100. except EOFError: #Something wrong happened
  101. mastodon.status_post("Hello @"+account+" \n unfortunately your savegame is corrupted or missing. The game is cancelled.",visibility="direct")
  102. os.remove(save_position+account+"."+extension)
  103. logging.warning("% file corrupted or missing",account+"."+extension)
  104. return False
  105. player_1 = pickle.load(f) #Read status from file
  106. player_2 = pickle.load(f)
  107. turn = pickle.load(f)
  108. board = pickle.load(f)
  109. if (start == "WAIT"): #The game is not started yet
  110. if "quit" in content.lower(): #Game withdrawn
  111. os.remove(save_position+account+"."+extension)
  112. mastodon.status_post("Hello @"+account+" \nthe challenge has been withdrawn.",visibility="direct")
  113. else: #Lobby is disabled if a challenge request is active
  114. mastodon.status_post("Hello @"+account+" \nyou have already challenged someone, type QUIT to withdraw,",visibility="direct")
  115. return False
  116. if "quit" in content.lower(): #The game is quitted
  117. os.remove(save_position+player_1[1:]+"."+extension)
  118. os.remove(save_position+player_2[1:]+"."+extension)
  119. mastodon.status_post(player_2+" "+player_1+" the match was cancelled.",visibility="direct")
  120. return False
  121. return True,player_1,player_2,turn,board
  122. def check_message(notification):
  123. account = notification["account"]["acct"]
  124. try:
  125. content = cleanHTML(notification["status"]["content"])
  126. except KeyError:
  127. return
  128. content = content[len(botname):]
  129. if "help" in content.lower(): #Ask for help
  130. 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")
  131. return
  132. #Conn4
  133. if os.path.exists(save_position+account+".conn4"):
  134. start,player_1,player_2,turn,board = load_status(account,"conn4",content)
  135. if not(start):
  136. return
  137. if (player_2 == "@"+account and turn == 1) or (player_1 == "@"+account and turn == 0):
  138. board,win = four_engine.dropChip(board,content.lower()[-1],turn+1)
  139. if not(board):
  140. mastodon.status_post("@"+account+" \nInvalid move.",visibility="direct")
  141. return
  142. else:
  143. with open(save_position+account+".conn4","wb") as f:
  144. pickle.dump("START",f)
  145. turn = not turn
  146. pickle.dump(player_1,f)
  147. pickle.dump(player_2,f)
  148. pickle.dump(turn,f)
  149. pickle.dump(board,f)
  150. if turn == 0: #the first is the current turn, the second is the last turn
  151. colour = (white_knight,black_knight)
  152. else:
  153. colour = (black_knight,white_knight)
  154. if win == 0:
  155. 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")
  156. return
  157. else: #Someone won!
  158. 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")
  159. os.remove(save_position+player_1[1:]+".conn4")
  160. os.remove(save_position+player_2[1:]+".conn4")
  161. return
  162. else: #We moved in a wrong turn
  163. mastodon.status_post("@"+account+" \nIt's not your turn.",visibility="direct")
  164. return
  165. #Draughts
  166. if os.path.exists(save_position+account+".draughts"): #We are in a game, so movements are parsed and lobby commands are disabled
  167. start,black,white,turn,board = load_status(account,"draughts",content)
  168. if not(start):
  169. return
  170. if (black == "@"+account and turn == 1) or (white == "@"+account and turn == 0): #Check if the turn is right
  171. board = dama.valid_move(content.lower(),turn,board,inversion=True) #Function dama.valid_move parses the input for couples of letter and number
  172. if board == -1: #We made an invalid move
  173. mastodon.status_post("@"+account+" \nInvalid move.",visibility="direct")
  174. return
  175. else:
  176. with open(save_position+account+".draughts","wb") as f: #Save the updated board
  177. pickle.dump("START",f)
  178. turn = not turn
  179. pickle.dump(black,f)
  180. pickle.dump(white,f)
  181. pickle.dump(turn,f)
  182. pickle.dump(board,f)
  183. if turn == 0:
  184. colour = "◽"
  185. else:
  186. colour = "◾"
  187. winner = dama.checkWin(board) #Check for winner
  188. if winner == (False,False): #No one is winning yet
  189. mastodon.status_post("◾: "+black+" \n◽: "+white+" \nturn "+colour+"\n"+dama.draw_checkerboard(board,space,white_norm,white_knight,black_norm,black_knight,empty,column,frstrow),visibility="direct")
  190. return
  191. else: #Someone won
  192. if winner == (True,False):
  193. winner_t = "WHITE"
  194. else:
  195. winner_t = "BLACK"
  196. os.remove(save_position+black[1:]+".draughts")
  197. os.remove(save_position+white[1:]+".draughts")
  198. 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")
  199. return
  200. else: #We moved in a wrong turn
  201. mastodon.status_post("@"+account+" \nIt's not your turn.",visibility="direct")
  202. return
  203. #Lobby
  204. for game in available_games:
  205. if game in content.lower():
  206. if not os.path.exists(save_position+account+"."+game): #If there is no a savegame file, then lobby mode is activated
  207. lobby(notification,content,account,game)
  208. if __name__ == "__main__":
  209. if api_url[:4] != "http":
  210. logging.error("Invalid address")
  211. quit()
  212. mastodon = login.login(api_url)
  213. while True:
  214. time.sleep(10)
  215. for x in mastodon.notifications():
  216. check_message(x)
  217. mastodon.notifications_dismiss(x["id"])