Making a Twitter Bot that lets you play chess with other humans

Have you thought about making a Twitter bot to beef up the “Projects section” of your resume?

Guess what, it isn’t as difficult as it sounds!

I recently made a Twitter bot that allows people to play chess on Twitter with their followers.

Here’s the story of why and how I built my first ever Twitter bot. I hope it can help you in building yours!

The WHY?

I came across this tweet last year, that talks about playing chess on Twitter via screenshots and replies and thought that this could be a fun thing to automate. The idea was to create a bot that can read tweets that contain chess moves, update the chess board accordingly and tweet a screenshot of the updated chess board.

Suppose Tom and Jerry decided to play a chess match one day, this is how it would work:

image


image

image

image

image

Isn’t this cool? Keep reading to know how you can also create the greatest Twitter bot ever! :smirk:

The HOW?

Making a Twitter bot might seem like a complex thing but all I needed for making this bot was:

  • To know a little bit about chess
  • A Twitter account
  • Basic knowledge of Python

Initially, I had no idea how to begin working on this project. Naturally, I went to Google and typed “how to make a twitter bot”. I came across this amazing article on realpython.com that talks about making a bot in Python with a package called Tweepy, that provides a convenient way for you to access the Twitter API. The Twitter API can be used to tweet, retweet, like, DM and comment on Twitter.

Installing Packages

I decided to use repl.it for working on my project and followed these steps to get Tweepy up and running and get familiar with using it.

The next step was to figure out how to create a chess board and update the board after parsing the players’ replies. For this, I found a library called python-chess.

I also installed a few other packages that were required later on in the program.

    import tweepy
    import chess
    import chess.svg
    import json
    import os
    from cairosvg import svg2png

Responding to new players

Now that the required packages had been installed, the first step was to check for new mentions of the chessbot’s Twitter handle and reply to them by printing the initial position of the chess board.

GAME_TWEETS_LIST = []
GAME_REPLIES_DICT = {} # dictionary with keys as individual games;
# values as lists of all replies to that game from least recent to most recent
GAME_BOARD_DICT = {} # dictionary with keys as individual games; 
# values as FEN representation of the latest chess board
GAME_PLAYER = {}  # dictionary of dictionary with outer keys as individual games;
# values as dictionary storing player's Twitter handles


def invite_new_players(api):
  """
  Checks for new mentions of the chessbot's Twitter handle.
  Invites them to play by printing the initial position of the board.
  """
  timeline = api.mentions_timeline()
  for tweet in timeline:
    if tweet.id not in GAME_TWEETS_LIST and not tweet.in_reply_to_status_id_str:
      GAME_TWEETS_LIST.append(tweet.id)
      GAME_REPLIES_DICT[int(tweet.id)] = []
      GAME_PLAYER[int(tweet.id)] = {0: None, 1: None}
      print(f"{tweet.user.name} said {tweet.text}")
      m = "Hello! Let's play chess. Here's the board: \n"
      board = create_chess_board(int(tweet.id))
      s = print_board(api, tweet, board, m)
      GAME_REPLIES_DICT[int(tweet.id)].append(s.id)

Initializing a new chess board

The create_chess_board() method initializes a new chess board for the game.

def create_chess_board(game):
"""
Initialises a new chess board for the game
:param game: Tweet id of the starter tweet for the game
"""

board = chess.Board()
GAME_BOARD_DICT[game] = board.fen()
return board

Parsing the latest replies

Next, I created a method that would parse the games’ tweet threads to get the latest moves, check the validity of the moves and print the updated chess board.

def parse_latest_replies(api):
  """
  Parses the games' tweet threads to get the latest move. Checks validity of each move. Prints the updated board.
  """

  get_latest_replies(api)
  for game in GAME_REPLIES_DICT:
    if len(GAME_REPLIES_DICT[game]) > 1:
      if is_valid_turn(game, api):
        latest_reply = api.get_status(GAME_REPLIES_DICT[game][-1]).text.split()[-1]
        update_board(game, latest_reply)
        result = check_results(game)
        print_board(api=api, 
                         tweet=api.get_status(int(GAME_REPLIES_DICT[game][-1])),
                         board=chess.Board(GAME_BOARD_DICT[game]),
                         optional_msg=result)
      else:
        GAME_REPLIES_DICT[game].pop()

Getting the latest replies

The get_latest_replies() method stores the latest tweets for each ongoing game and sets games’ players.

def get_latest_replies(api):
  """
  Stores the latest tweets for each ongoing game and sets games' players
  """
  timeline = api.mentions_timeline()
  for tweet in timeline:
    if hasattr(tweet, 'in_reply_to_status_id_str') and tweet.in_reply_to_status_id_str: #if the tweet is a reply
      parent_tweet = int(tweet.in_reply_to_status_id_str)
      grandparent_tweet = int(api.get_status(parent_tweet).in_reply_to_status_id_str)
      for game in GAME_REPLIES_DICT:
        if grandparent_tweet == game:
          if tweet.id not in GAME_REPLIES_DICT[game]:
            GAME_REPLIES_DICT[game].append(tweet.id)
            GAME_PLAYER[game][1] = tweet.user.screen_name
        elif grandparent_tweet == GAME_REPLIES_DICT[game][-1]:
          if tweet.id not in GAME_REPLIES_DICT[game]:
            GAME_REPLIES_DICT[game].extend([parent_tweet, tweet.id])
            if not GAME_PLAYER[game][0]:
              GAME_PLAYER[game][0] = tweet.user.screen_name

Checking for validity of the turn

The is_valid_turn() method checks whether the player is playing out of turn.

def is_valid_turn(game, api):
  """
  Checks if the player is not playing out of turn

  Returns True if the move is valid; False otherwise
  """
  latest_reply_uname = api.get_status(int(GAME_REPLIES_DICT[game][-1])).user.screen_name
  
  #parses the string representation of the chess board
  board = chess.Board(GAME_BOARD_DICT[game])
  
  if not GAME_PLAYER[game][int(board.turn)] == latest_reply_uname:
    return False

  return True

Updating the board

The update_board() mehod makes a move on the board and updates it if the move is legal according to chess rules.

def update_board(game, move):
  """
  Makes a move on the board and updates it if the move is legal according to chess rules.
  """
  try:
    board = chess.Board(GAME_BOARD_DICT[game])
    board.push_san(move)
    GAME_BOARD_DICT[game] = board.fen()
    
    print(str(board))

    # check for mates
  except ValueError:
    popped = GAME_REPLIES_DICT[game].pop()
    print("Invalid move" + str(popped))

Checking the results

The check_results() method checks the results of the game and returns the appropriate message.

def check_results(game):
  """
  Checks the results of the game and returns the appropriate message.
  """
  board = chess.Board(GAME_BOARD_DICT[game])

  if board.is_checkmate():
    msg = "CHECKMATE: " + GAME_PLAYER[game][int(not board.turn)] + " WINS!"
  elif board.is_check():
    msg = "CHECK"
  elif board.is_stalemate():
    msg = "draw: stalemate"
  elif board.is_fivefold_repetition():
    msg = "draw: 5-fold repetition"
  elif board.is_insufficient_material():
    msg = "draw: insufficient material"
  elif board.can_claim_draw():
    msg = "draw: claim"
  else:
    msg = ""
  return msg + "\n"

Printing the chess board

Finally, the print_board() prints the current status of the chess board.

def print_board(api, tweet, board, optional_msg="", text_only=False):
  """
  Prints the current status of the chess board in text format in a new reply

  :param api: API object
  :param tweet: Status object for the tweet to reply to
  :param board: chess.Board object
  :param optional_msg: optional message to be printed with the board
  """
  sn = tweet.user.screen_name
  msg = "@%s " % (sn)
  optional_msg = msg + optional_msg + "\n"
  message = optional_msg
  if text_only:
    message += str(board)
  message += '\n\n'+"Play the next move..."

  if text_only:
    try:
      s = api.update_status(message, tweet.id)
      return s
    except tweepy.error.TweepError:
      print("Already replied to this tweet")
  else:
    img = get_board_png(board)
    try:
      s = api.update_with_media(filename=img, 
        status=message,
        in_reply_to_status_id=tweet.id)
      return s
    except tweepy.error.TweepError:
      print("Already replied to this tweet")
    os.remove(img)

Generating a png file of the board

This method calls the get_board_png() method that generates a png file from the board.

def get_board_png(board):
  """
  Generates a png file from the board and returns the png filename

  :param board: chess.Board object
  :return: String containing the png filename
  """
  svg_xml = chess.svg.board(board=board)
  png_filename = "board.png"
  svg2png(bytestring=svg_xml, write_to=png_filename)

  return png_filename

And that’s it! If you’re reading this and followed the steps correctly your very own chess bot is now ready to host a few chess matches. Of course, you would have to host it on a server to make it go live.

Next steps

  • Make the program more elegant.
  • Host the bot on a server.
  • Add functionalities to the code so that the bot never replies to it’s own tweets.

Thank you so much for reading. If you have any questions/feedback or would just like to chat, you can reach out to me on Twitter or LinkedIn.

16 Likes

Welcome to the Community @theparidhi0 and congratulations on writing a great article with a fresh and original idea! :heart_eyes: Once I won a female championship at my university, but only because we were only 2 girls participating, and the second one was playing for the first time in her life :joy:

5 Likes

Thank you so much Elena! I’d love to play chess with you some day although I’m really bad at it too. :stuck_out_tongue:

2 Likes

Well, after that my “triumph” I didn’t play much as well, so I’m probably not that good anymore too :sweat_smile:

2 Likes

@theparidhi0 Thanks for introducing us to Twitter bots with this great article! As @Elena_Kosourova said, it’s very fresh to read! Can’t wait to build myself one of those little bots. :smile:

5 Likes

Bots are really fun to make. Excited to read your article after you finish making your bot. :stuck_out_tongue: Thank you so much! :sunflower:

3 Likes

It is a very ingenious idea to use twitter as the medium to make a chess game, it is a very clever way to understand how the platform can work with its particularities, not to mention how to make use of python.

Congratulations, it is an elegant, discreet, original and with a lot of potential in my opinion.

Thanks for sharing it.

1 Like

Thank you so much @Edelberth ! Really appreciate your kind words. :smiley:

1 Like

Thanks to you, if you hadn’t told me, I probably wouldn’t have seen it.

A.

1 Like

That’s a neat way to brush up your Python skills :+1:t2: I don’t play chess myself but your article made it easy to follow what is involved in making the bots work. Thanks for sharing!

1 Like

Thank you so much Atikah! :grinning:

Excellent, interesting and fresh-idea project! Congrats @theparidhi0!

2 Likes

Thanks a ton @makis_plegas !

Thank you so much. :smiley:

1 Like

Great article @theparidhi0 ! :smiley:

2 Likes