Mafia Go, Vanila JS and WebSocket

    It's about the web-implementation of the popular card game " Mafia ". It was written for fun and experience in game development. The first version was written in two weeks of free time from work and for the same time was rewritten before the second version. The advantage of such a game is the absence of the leader.

    Based on the development goals, I made decisions on the implementation / non-implementation of features.
    What exactly needed to be done:

    • Minimally working game, repeating the rules of the classic game
    • Voice command of the host on client devices
    • Continue the game even after reloading the browser tab

    What was not planned or could be postponed:

    • Registration in the game
    • Administration Interface
    • Permanent game data storage in the database
    • Time synchronization between devices

    Written in Go. Keeps the state of the game and is responsible for its logic.

    During the game, you can contact the server to find out the full information:

    curl '' | python -m json.tool

    Display information about the game
    "event": "greet_mafia",
    "event_status": 2,
    "id": 23,
    "is_over": false,
    "iter": 1,
    "players": [
    "addr": "",
    "createdAt": "2018-09-23T14:39:29.631475779Z",
    "id": 33309,
    "name": "Anton",
    "role": 4
    "addr": "",
    "createdAt": "2018-09-23T14:39:32.867080927Z",
    "id": 5457,
    "name": "username:0",
    "role": 2
    "addr": "",
    "createdAt": "2018-09-23T14:39:32.882463945Z",
    "id": 14214,
    "name": "username:2",
    "role": 1
    "addr": "",
    "createdAt": "2018-09-23T14:39:32.895209072Z",
    "id": 63759,
    "name": "username:1",
    "role": 3
    "win": 0

    Or find out the status of the server:

    curl '' | python -m json.tool

    Displays server status information
    "runtime.MemStats.Alloc": 764752,
    "runtime.MemStats.NumGC": 0,
    "runtime.MemStats.Sys": 4165632,
    "runtime.MemStats.TotalAlloc": 764752,
    "runtime.NumGoroutine": 14

    To determine if the player is still active, the backend sends a heartbeat. If the player does not answer after a certain interval, then he leaves the game. At the same time, if a player reconnects before the end of the interval (the network is gone), then he can continue the game.

    For stable operation, the backend was covered by Unit tests with the standard Go library , where the main work scenarios are checked.

    go test mafia-backend/src -cover
    ok      mafia-backend/src       1.315s  coverage: 70.7% of statements

    Written in pure JS and compiled using Grunt .
    Does not carry any logic.

    When an event occurs with a backend, it renders the desired page, displays the data that was sent to it, and plays the sound of a new event.

    Frontend stores the game ID and player ID in LocalStorage or the browser's request bar (if you need to run multiple tabs for different players in one browser). The complete lack of logic, as well as the storage of the main parameters of the game make it possible, even after reloading the page, to restore the state of the game.

    Browser prohibitsAutoplay sounds without user interaction (for example, pressing a button). In order to play sounds for each event, only 1 JavaScript Audio object was made with the backend. Each player must press the button to start the game and at this point the Audio object becomes active (available for playback), and subsequently it can change the src parameter to play different sounds without user intervention.

    Also to test the work of the game was written "bot", which can play with itself.
    Just open the browser tab, where the parameters indicate that you need to run the test

    and allow opening new tabs from javascript for this domain.
    After the start of the game, 5 more tabs with players will open and they will start playing among themselves.

    Interaction protocol

    The WebSocket protocol was chosen because of the need for constant two-way data exchange between the backend and frontend and its support in both languages.

    Game events

    The whole game is divided into events:

    • game
      • create
      • join
      • start
      • over
      • reconnect

    • day
      • start

    • night
      • start

    • citizens-greeting
      • start
      • role
      • end

    • mafia-greeting
      • start
      • players
      • end

    • court
      • start
      • players
      • end

    • mafia
      • start
      • players
      • end

    • doctor
      • start
      • players
      • end

    • girl
      • start
      • players
      • end

    • sherif
      • start
      • players
      • end

    Events have a beginning, an end and a meaningful part.
    At the beginning and end of the event, a notification is sent to all active players that needs to be confirmed. The game continues only after confirmation of this event by all active players (for example, only after the sound file is played).


    The whole game can be raised using Docker :

    version: '3'
            image: mrsuh/mafia-frontend:latest
            container_name: mafia_frontend
                - 9080:80
            image: mrsuh/mafia-backend:latest
            container_name: mafia_backend
                - 8000:8000

    It is enough to install Docker (if you have not already done this), copy the docker-compose.yml text to yourself and execute the command:

    docker-compose up

    After that you can open the tab with the game in the browser:


    Here you can see what happened (playback speed increased by 1.5 times).

    After nearly a month of development in my spare time, I got a fairly stable game that you can play with friends. The game can withstand page reload or temporary network loss. Voice events on the devices works, albeit without time synchronization. Further development of the game is not planned.

    PS: Thanks Lera for the voice acting of the game.

    Also popular now: