Skip to main content

Mahjong game simulator for evaluating submission files on mjai.app

Project description

mjai-simulator

mjai.app is a platform for mahjong AI competition. This repository contains the implementation of mahjong game simulator for evaluating submission files on mjai.app.

Usage

You can simulate a mahjong game by specifying submission files as shown in the code below. The Simulator runs Docker internally. The docker command must be installed and be available to run with user privileges.

import mjai

submissions = [
    "examples/shanten.zip",
    "examples/tsumogiri.zip",
    "examples/tsumogiri.zip",
    "examples/invalidbot2.zip",
]
mjai.Simulator(submissions, logs_dir="./logs").run()

This package also provides a base class for developing bots that communicate via the mjai protocol.

from mjai import Bot


class RulebaseBot(Bot):
    def think(self) -> str:
        if self.can_tsumo_agari:
            return self.action_tsumo_agari()
        elif self.can_ron_agari:
            return self.action_ron_agari()
        elif self.can_riichi:
            return self.action_riichi()

        ...

if __name__ == "__main__":
    RulebaseBot(player_id=int(sys.argv[1])).start()

Docker Image

Submission files are deployed in a Docker container and run as a Docker container.

This repository contains Dockerfile and other resources that will be used to create the Docker container. The docker image is pushed to Docker Hub (docker.io/smly/mjai-client:v3).

Submission format

Please prepare a program that outputs the appropriate mjai protocol message to the standard output when given input in the mjai protocol format from standard input. Name this program "bot.py" and pack it into a zip file. The zip file should contain bot.py directly under the root directory.

bot.py must be a Python script, but it is also possible to include precompiled libraries if they are executable. The program will be executed in a linux/amd64 environment. The submission file must be 1000 MB or less. bot.py takes a player ID as its first argument. Player ID must be 0, 1, 2, or 3. Player ID 0 represents the chicha 起家, and subsequent numbers represent the shimocha 下家, toimen 対面 or kamicha 上家 of the chicha 起家. See example code for details.

Timeout

When the mjai.Simulator instance creates an environment to run the submission file in docker, it specifies the --platform linux/amd64 option. If you want to run on a different architecture, you will have to emulate and run the container, which will be much slower. If you are debugging on an architecture other than linux/amd64, you can avoid timeout errors by relaxing the timeout limit. Specify the timeout argument as follows. The timeout is set to 2.0 by default.

Simulator(submissions, logs_dir="./logs", timeout=10.0).run()

If the simulation is interrupted, the docker container may not terminate successfully. If past docker containers remain, the new docker container may fail to launch due to duplicate HTTP ports. You can remove all docker containers that have the smly/mjai-client image as an ancestor in a batch as follows:

% for cid in `docker ps -a --filter ancestor=smly/mjai-client:v3 --format "{{.ID}}"`; do docker rm -f $cid; done

Mjai Protocol

Our Mjai protocol is basically based on the Gimite's original Mjai Protocol, but customized based on Mortal's Mjai Engine implementation. The following points are customized:

  • Messages are sent and received by standard input and standard output.
  • The game server sends messages collectively up to the event that the player can act on.
  • When the player does not send the appropriate event message, it is treated as a chombo and a mangan-sized penalty is paid.
  • If the player does not send a message within 2 seconds, it is treated as a chombo and a mangan-sized penalty is paid.
  • First round (kyoku) is not necessarily East 1.

First round is not necessarily East 1

If an error occurs in any one of the 4 players, the game is treated as a abortive draw (ryukyoku; 流局). At this time, all 4 players' containers are terminated. The players' containers are restarted and the game is resumed from the next round in which an error occurs. Therefore, the first round received by the player may not be East 1.

The following are messages sent and received as seen from player 0. <- denotes events for the player. -> denotes events from the player.

# Game resumed from S1-1
0 <- [{"type":"start_game","names":["0","1","2","3"],"id":0}]
0 -> {"type":"none"}

# NOTE: No ryukyoku events are sent from the game server.
0 <- [{"type":"end_kyoku"}]
0 -> {"type":"none"}

# S2-2 first tsumo tile
0 <- [{"type":"start_kyoku","bakaze":"S","dora_marker":"1p","kyoku":2,"honba":2,"kyotaku":0,"oya":1,"scores":[800,61100,11300,26800],"tehais":[["4p","4s","P","3p","1p","5s","2m","F","1m","7s","9m","6m","9s"],["?","?","?","?","?","?","?","?","?","?","?","?","?"],["?","?","?","?","?","?","?","?","?","?","?","?","?"],["?","?","?","?","?","?","?","?","?","?","?","?","?"]]},{"type":"tsumo","actor":1,"pai":"?"},{"type":"dahai","actor":1,"pai":"F","tsumogiri":false},{"type":"tsumo","actor":2,"pai":"?"},{"type":"dahai","actor":2,"pai":"3m","tsumogiri":true},{"type":"tsumo","actor":3,"pai":"?"},{"type":"dahai","actor":3,"pai":"1m","tsumogiri":true},{"type":"tsumo","actor":0,"pai":"3s"}]
0 -> {"type":"dahai","pai":"3s","actor":0,"tsumogiri":true}

Case Study: Furiten (振聴)

When the player has already made a tenpai but the hand is furiten. Since the player cannot Ron, even if the waiting tile is discarded by the opponent, no action is possible. For example, let's say you have 2333678m 678s 678p. The waiting tile is 14m and 1m has already been discarded. Since the hand is a Furiten, even if the other player discards 1m, the player cannot Ron.

3 <- [{"type":"dahai","actor":3,"pai":"P","tsumogiri":true},{"type":"tsumo","actor":0,"pai":"?"},{"type":"dahai","actor":0,"pai":"2p","tsumogiri":true},{"type":"tsumo","actor":1,"pai":"?"},{"type":"dahai","actor":1,"pai":"4m","tsumogiri":true},{"type":"tsumo","actor":2,"pai":"?"},{"type":"dahai","actor":2,"pai":"6m","tsumogiri":true}]

In this case, immediately after actor 2 discards 6m, input is given to actor 3. actor 3 needs to decide whether to call chi on 6m.

Case Study: Ankan (暗槓)

In the case of an ankan, the dora event comes first, followed by the tsumo event.

3 -> {"type": "ankan", "actor": 3, "consumed": ["6s", "6s", "6s", "6s"]}
3 <- [{"type":"ankan","actor":3,"consumed":["6s","6s","6s","6s"]},{"type":"dora","dora_marker":"6p"},{"type":"tsumo","actor":3,"pai":"7p"}]
3 -> {"type":dahai","actor":3,"pai":"7p","tsumogiri":true}

For Developers

Debug with interactive shell

The procedures executed by Simulator can be checked and debugged one by one as follows:

# pull latest docker image
% docker pull docker.io/smly/mjai-client:v3

# launch
% CONTAINER_ID=`docker run -d --rm -p 28080:3000 --mount "type=bind,src=/Users/smly/gitws/mjai.app/examples/rulebase.zip,dst=/bot.zip,readonly" smly/mjai-client:v3 sleep infinity`

# install bot program
% docker exec ${CONTAINER_ID} unzip -q /bot.zip

# debug
% docker exec -it ${CONTAINER_ID} /workspace/.pyenv/shims/python -u bot.py 0
[{"type":"start_game","id":0}]  <-- Input
{"type":"none"}  <-- Output
[{"type":"start_kyoku","bakaze":"E","dora_marker":"2s","kyoku":1,"honba":0,"kyotaku":0,"oya":0,"scores":[25000,25000,25000,25000],"tehais":[["E","6p","9m","8m","C","2s","7m","S","6m","1m","S","3s","8m"],["?","?","?","?","?","?","?","?","?","?","?","?","?"],["?","?","?","?","?","?","?","?","?","?","?","?","?"],["?","?","?","?","?","?","?","?","?","?","?","?","?"]]},{"type":"tsumo","actor":0,"pai":"1m"}]  <-- Input
{"type": "dahai", "actor": 0, "pai": "C", "tsumogiri": false}  <-- Output
[{"type":"dahai","actor":0,"pai":"C","tsumogiri":false},{"type":"tsumo","actor":1,"pai":"?"},{"type":"dahai","actor":1,"pai":"3m","tsumogiri":false},{"type":"tsumo","actor":2,"pai":"?"},{"type":"dahai","actor":2,"pai":"1m","tsumogiri":false}]  <-- Input
{"type": "none"}  <-- Output
[{"type":"tsumo","actor":3,"pai":"?"},{"type":"dahai","actor":3,"pai":"1m","tsumogiri":false}]  <-- Input
{"type": "none"}  <-- Output
[{"type":"tsumo","actor":0,"pai":"C"}]  <-- Input
{"type": "dahai", "actor": 0, "pai": "C", "tsumogiri": true}  <-- Output

Debug with http sever

# install http server interface of mjai protocol
% docker cp python/mjai/http_server/server.py ${CONTAINER_ID}:/workspace/00__server__.py

# http server mode. `0` is the player index.
% docker exec -it ${CONTAINER_ID} /workspace/.pyenv/shims/python 00__server__.py 0
% curl http://localhost:28080/
OK

% curl -X POST -d '[{"type":"start_game","id":0}]' http://localhost:28080/
{"type":"none"}

% cat > request.json
[{"type":"start_kyoku","bakaze":"E","dora_marker":"2s","kyoku":1,"honba":0,"kyotaku":0,"oya":0,"scores":[25000,25000,25000,25000],"tehais":[["E","6p","9m","8m","C","2s","7m","S","6m","1m","S","3s","8m"],["?","?","?","?","?","?","?","?","?","?","?","?","?"],["?","?","?","?","?","?","?","?","?","?","?","?","?"],["?","?","?","?","?","?","?","?","?","?","?","?","?"]]},{"type":"tsumo","actor":0,"pai":"1m"}]

% curl -X POST -d '@request.json' http://localhost:28080/
{"type":"dahai","actor":0,"pai":"6p","tsumogiri":false}

Development

Confirmed working with rustc 1.75.0 (82e1608df 2023-12-21).

$ pip install maturin  # install build tool
$ maturin build --release --locked --target aarch64-apple-darwin --out dist

Special Thanks

The code in ./src directory is Mortal's libriichi with minor updates. Mortal is distributed under the AGPL-3.0 and is copyrighted by Equim.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mjai-0.2.0.tar.gz (373.6 kB view details)

Uploaded Source

Built Distributions

mjai-0.2.0-cp312-none-win_amd64.whl (1.4 MB view details)

Uploaded CPython 3.12 Windows x86-64

mjai-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.5 MB view details)

Uploaded CPython 3.12 manylinux: glibc 2.17+ x86-64

mjai-0.2.0-cp312-cp312-macosx_11_0_arm64.whl (1.5 MB view details)

Uploaded CPython 3.12 macOS 11.0+ ARM64

mjai-0.2.0-cp311-none-win_amd64.whl (1.4 MB view details)

Uploaded CPython 3.11 Windows x86-64

mjai-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.5 MB view details)

Uploaded CPython 3.11 manylinux: glibc 2.17+ x86-64

mjai-0.2.0-cp311-cp311-macosx_11_0_arm64.whl (1.5 MB view details)

Uploaded CPython 3.11 macOS 11.0+ ARM64

mjai-0.2.0-cp310-none-win_amd64.whl (1.4 MB view details)

Uploaded CPython 3.10 Windows x86-64

mjai-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.5 MB view details)

Uploaded CPython 3.10 manylinux: glibc 2.17+ x86-64

mjai-0.2.0-cp310-cp310-macosx_11_0_arm64.whl (1.5 MB view details)

Uploaded CPython 3.10 macOS 11.0+ ARM64

File details

Details for the file mjai-0.2.0.tar.gz.

File metadata

  • Download URL: mjai-0.2.0.tar.gz
  • Upload date:
  • Size: 373.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.12.2

File hashes

Hashes for mjai-0.2.0.tar.gz
Algorithm Hash digest
SHA256 c7e994ecae24a3dd01263a5d1266ccd16d305ac6d3081db9c50680b7451c3b2c
MD5 f6119e37acc9536186d359f15db08d1a
BLAKE2b-256 d6a1ecc639900721a463b0608af16741549ea2dfe8a35b4ad8c65cbd75eb7654

See more details on using hashes here.

File details

Details for the file mjai-0.2.0-cp312-none-win_amd64.whl.

File metadata

  • Download URL: mjai-0.2.0-cp312-none-win_amd64.whl
  • Upload date:
  • Size: 1.4 MB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.12.2

File hashes

Hashes for mjai-0.2.0-cp312-none-win_amd64.whl
Algorithm Hash digest
SHA256 5233f6fbec381a20f9ea0f0097e548ea8ed43eff2d84febb87eb0506d0d87d8c
MD5 720a68e96a73981485327147aa141e8c
BLAKE2b-256 b2055ae90928d4e5ec7684e1f69dc48105792282ac7cc203054e8fb70bc2acbf

See more details on using hashes here.

File details

Details for the file mjai-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for mjai-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 8a0b1c08d7a3c2b8e884e381f123bca7d6ccba60e18d666d0e6364a8a1c059c5
MD5 ae4f3ef49729981645059be662edb8b5
BLAKE2b-256 3cee8407dd7da8ee06cf81cd8542315f507fc88ffeda2dd6a5b5c4bc58f5935b

See more details on using hashes here.

File details

Details for the file mjai-0.2.0-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for mjai-0.2.0-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 ac5c6f8565cb62c560d28628538f3363345225e3e260430cfd4ddef8a13471d5
MD5 9747c4fb906e6529bc4464d7b5bd46d2
BLAKE2b-256 59a34806e32cca334ae7692abfe8b030e147b0749b61e7cc8e92639132ae6597

See more details on using hashes here.

File details

Details for the file mjai-0.2.0-cp311-none-win_amd64.whl.

File metadata

  • Download URL: mjai-0.2.0-cp311-none-win_amd64.whl
  • Upload date:
  • Size: 1.4 MB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.12.2

File hashes

Hashes for mjai-0.2.0-cp311-none-win_amd64.whl
Algorithm Hash digest
SHA256 466b35423c379f872b5bfa9c72b397f51bcf9914ef8e56a7275e620b17c9607e
MD5 090d1454ae602d71a8334150d9d167c9
BLAKE2b-256 3cd4813f6f3e87ddc87715c0442a490c92dcfb298219f246502e10c951a4320a

See more details on using hashes here.

File details

Details for the file mjai-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for mjai-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 83fdb20820e5a99791cf9391390059da21704a76a17cdd04aba15a1e45ceed7a
MD5 2c9cba72a1cc131f6d41000fd5ad2462
BLAKE2b-256 280b0e40a837078bf29aee07fe7972449ef39973750620a7f1ecd4d1fd7b5aee

See more details on using hashes here.

File details

Details for the file mjai-0.2.0-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for mjai-0.2.0-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 ddd2927cdd092b2d0642f5698c0c3609c92983cc51972778f5b9cbef30fbb111
MD5 061b08871bac13ea6fb3ef6e2ac07cb0
BLAKE2b-256 73f1bb00de74c6810dba1b7dc055e50597637a50f61ff734c488fb68a446c364

See more details on using hashes here.

File details

Details for the file mjai-0.2.0-cp310-none-win_amd64.whl.

File metadata

  • Download URL: mjai-0.2.0-cp310-none-win_amd64.whl
  • Upload date:
  • Size: 1.4 MB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.12.2

File hashes

Hashes for mjai-0.2.0-cp310-none-win_amd64.whl
Algorithm Hash digest
SHA256 30664603a3ce75ebbac4a1327a6fbe600d73058fc42e0b0306ec60f10f1b91c2
MD5 5724953ba4db74b22cb6bc0ed1b23de5
BLAKE2b-256 d873a480c976c14a74b87cdc655053ef8c87a88548d96da524700f59f15353bc

See more details on using hashes here.

File details

Details for the file mjai-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for mjai-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 e31a25a4bcd3f540f13211b9bb79f63701941cd65bc0eacf426ef8c17e1f318d
MD5 e3491bab1eb2f9df2209f44eed7482cd
BLAKE2b-256 2d736fa60e71f6ddbf0e7c6b51c59fb3db1bcb2eab0d5867dfcfc5f3893e79fd

See more details on using hashes here.

File details

Details for the file mjai-0.2.0-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for mjai-0.2.0-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 4b2e0b124a1ec36cbe1daaf5c458bcd42e3d59f6d8b8ca6e621c424a406dd0a7
MD5 69bae1dacac25fe96dff9b0a6c47b3c3
BLAKE2b-256 cbbceafdae4f97dcc2d19c6f5909f039e4bbf092df6ac4d8c4c553bfbb0b08c3

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page