A blog for eternal code 💻

Creating a player bot to automate Mankala

Hello everyone! Once again, I am here to share my fun experiences incorporating new features for Mankala.

Problem

This time I have built a bot server for Mankala where you can automate the entire game process of playing the game moves by using the server endpoints present in the bot. This Mankala bot can be run in various ways, and for the easiest to understand, can be an example in Python, which I have used in our process.

Approach

To build this bot, I built a minimal HTTP server using Qt’s own QTcpServer. The BotApiServer class listens on port 8080 and handles raw HTTP requests by parsing the method and path manually, and when these requests are synced from the terminal, the changes can be observed in the GUI, showing a change in the number of shells.

The API exposes two endpoints:

A list of documentation with all the endpoints present has also been added as part of the Help section present in the game, which also contains predefined scripts to run 1 bot or even 2 bots simultaneously, automating the entire game.

OpenAPI documentation:

openapi: 3.1.0
info:
  title: Mankala Bot API
  description: API to programmatically control and play Mankala.
  version: 1.0.0
paths:
  /api/status:
    get:
      summary: Get the current game status
      description: Retrieves the full state of the board, scores, and turn information.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [in_progress, game_over, winner, turn, variant, pit_count, player1_pits, player2_pits, player1_store, player2_store, board]
                properties:
                  in_progress: { type: boolean }
                  game_over: { type: boolean }
                  winner: { type: string, enum: ["", "player1", "player2"] }
                  turn: { type: string, enum: ["player1", "player2"] }
                  variant: { type: string, enum: ["Bohnenspiel", "Oware", "Pallanguli"] }
                  pit_count: { type: integer, minimum: 1 }
                  player1_pits: { type: array, items: { type: integer, minimum: 0 } }
                  player2_pits: { type: array, items: { type: integer, minimum: 0 } }
                  player1_store: { type: integer, minimum: 0 }
                  player2_store: { type: integer, minimum: 0 }
                  board: { type: array, items: { type: integer, minimum: 0 } }
  /api/move:
    post:
      summary: Make a move
      description: Executes a move for the specified player at the given pit index.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [player, pit_index]
              properties:
                player: { type: string, enum: ["player1", "player2"] }
                pit_index: { type: integer, minimum: 0 }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                required: [status, turn, game_over]
                properties:
                  status: { type: string, enum: ["ok"] }
                  turn: { type: string, enum: ["player1", "player2"] }
                  game_over: { type: boolean }
                  winner: { type: string, enum: ["", "player1", "player2"] }
        "400":
          description: Bad Request (Invalid Move, Not Your Turn, Invalid JSON)
          content:
            application/json:
              schema:
                type: object
                properties:
                  error: { type: string }

Here we can observe a Python script for a player vs bot game:

import urllib.request, json, time

API = "http://localhost:8080/api"

def status():
    return json.loads(urllib.request.urlopen(f"{API}/status").read())

def move(player, pit):
    req = urllib.request.Request(f"{API}/move",
        data=json.dumps({"player": player, "pit_index": pit}).encode(), method="POST")
    req.add_header("Content-Type", "application/json")
    return json.loads(urllib.request.urlopen(req).read())

# Bot plays as player2, waits for human player1
while True:
    st = status()
    if st["game_over"] or not st["in_progress"]:
        print(f"Game Over! Winner: {st['winner']}")
        break
        
    if st["turn"] != "player2":
        time.sleep(0.5)  # wait for human
        continue
        
    pits = st["player2_pits"]
    # pick first non-empty pit
    pit = next((i for i, v in enumerate(pits) if v > 0), -1)
    
    if pit == -1: 
        break
        
    print(f"Bot moves pit {pit}")
    move("player2", pit)
    time.sleep(1.5)  # animation delay

For the above code, we can adjust the localhost port and the animation speed based on our needs and save it.

Challenges I faced:

I would say the synchronization of the GUI with the server was hard. So to make the GUI animations correctly incorporated, I made the apiMoveMade signal using which the change in game state was listened in GameWindowLandscape.qml and GameWindowPortrait.qml files. This was something really new that I have done till now and was a fun experience for me.

Thanks for reading 🚀....