Ùzje mahjong engine
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
isoraqathedh d2ed6b06eb Tiles can be discarded in tsumo. 3 weeks ago
test Implement pung, chow and minkan. 3 weeks ago
README.org Minor spacing stuff. 3 weeks ago
errors.lisp Add some new error conditions. 2 months ago
game-actions.lisp Refactor stuff and add prospective chii. 3 months ago
game-records.lisp Juggling around the serialisation stuff. 3 weeks ago
hands.lisp Tiles can be discarded in tsumo. 3 weeks ago
package.lisp Add for to the dependencies. 2 months ago
rounds.lisp Implement pung, chow and minkan. 3 weeks ago
tiles.lisp Allow tilesets to be specified by what tiles they have. 2 months ago
ujmj.asd Force package. 2 months ago
ujmj.lisp Reorganise. 3 months ago



This is an engine that will support Ùzje mahjong games. It is also able to run a replay file. It is meant to be bottable and usable for developing AI for the game. There is no GUI for this library.



  (ql:quickload :ujmj)
  (in-package #:info.isoraqathedh.ujmj)

Then create a game and place it somewhere:

  (defparameter *match* (make-instance 'match))

The match class can take some options, but only some of these are implemented:


Choose what tiles to use. A list of three symbols:

  1. The first symbol decides whether or not to use flowers. Use either t to include flowers, or nil to not.
  2. The second symbol decides whether to use the full complement of winds, N E S W, or used a compressed version, H V. Use :full for the former and :abbreviated for the latter.
  3. The third symbol decides whether to use all the number tiles, or only retain the terminals for the tongs. For the former, use :full, and for the latter, use :abbreviated.

Some of these options are redundant to the number of players, and this may be changed later.

Currently unused. Will eventually be used to include some gameplay variations, such as the behaviour of kongs, whether or not head-bump is used or the admittance or denial of certain winning patterns.

After this, you start a game with:

  (defparameter *game* (deal *match*))

And then you issue commands:

  (command *game* "Ed1m") ;; e.g.

The command is a string of characters:

  1. The first character is the player making the move, which is one of E, S, W, or N. It is currently not optional but it will eventually be able to be omitted if the moves are in sequence.
  2. The second character is the action. It can be one of the following:

    Char Meaning Arguments
    - Draw 0
    d Discard 1
    c Chii 2
    p Pon 1
    k Kan 1
    s Tsumo 0
    r Ron 0
  3. Zero or more arguments, which are the tiles included in the action above.

Multiple actions can be provided, separated by spaces.

Replay file

An important part of the engine is to be able to replay games written in a specific file format. This will represent a full game of mahjong, in the same way that algebraic notation would do for a chess game.

Existing notation (e.g. paifu) is too contextual to be used here, so an alternate notation has to be created. It is done in a "god's-eye view", allowing a full replay of a game to be used.

General characteristics

The replay file is in plain text, and uses only ASCII characters. It represents a single game, but will be extended later to allow any number of games (and so sets) to be included.

Generally speaking, the file is paragraph based. A paragraph is a collection of lines that are surrounded either by file boundaries, or by empty lines on both sides. That means that single newlines on their own are understood to be just ordinary white space and carry no meaning by themselves. The exception are comments, which are line-based.

There are three types of paragraphs:

  • Game paragraph
  • Data paragraph
  • Comment paragraph

We'll describe them as below.

Game paragraph

The game paragraph is the default type of paragraph. If a paragraph doesn't meet any other type of paragraph, then it is a game paragraph.

The paragraph contains words separated by spaces or newlines.

General structure

The general structure of a game paragraph goes like this:

  set round repeat players tiles... turns... [end indicator]

The first four words are together called the header. Then come the tile list, then the turn list. The following sections will explain each of these sections.


The first four words give some context as to what rounds one is playing. They consist of:

  1. A single letter, representing the set orientation:

  2. An integer, representing the round orientation.
  3. An integer, representing the round repeat.
  4. An integer, representing the number of players: 3, 4, 6 or 0. If 0, that means that it is inherited using a property elsewhere in the file.

Tile list

From the fifth word onwards up to but not including the first word with a . in it is the tile list.

It represents the layout of the wall just after it was first broken by dice roll, i.e. the first four tiles mentioned will be the first four tiles taken by the dealer, and as players draw tiles from the wall during ordinary play, they will be drawn in this order (save the draws from the back of the wall, where they will be drawn from the end of tile list backward). Each tile takes up one word, and can contain any subset of the following tiles:

  • Circles, up to four copies, 1 to 16;
  • Bamboos, up to four copies, 1 to 16;
  • Characters, up to four copies, 1 to 16;
  • Honours, up to four copies, Zero, East, South, West, North, Red, Green, White;
  • Flowers, up to one copy, the eight standard flowers.

Turn list

The move list is a list of moves, consisting of words that all contain the full stop. These words have further substructure in them. We can describe the substructure as such:

  turn = turn-number "." [player] move [("," move)...]
  move = action [tile...] | [action] tile...
This is a collection of digits. The exact digits don't matter, and are discarded on read. However, when written, they should start at 1 and count up by 1 per turn. Alternatively, they can be all numbered 0.
The player that is playing at this turn. Can be one of E, H, S, V, W, N.

This is any one of these characters:

specifies a "pung". Does not admit tile arguments.
specifies a "chow". Admits exactly two tile arguments.

specifies a "kong". Admits zero or one tile arguments, depending on its position:

  • At the start of a list of moves, it does not admit tile arguments.
  • At the middle of a list of moves, it requires exactly one tile argument.
specifies a successful declaration of a "ron", that is, the winning tile is discarded from the previous player. This move must be the final move of the game. It admits no tile arguments.
specifies the successful declaration of a "tsumo", that is, the winning tile is drawn from the wall. This move must be the final move of the game. It admits no tile arguments.
specifies discarding a tile from the wall. Requires exactly one tile argument.
A list of tiles in the alphabetic notation. One can also abbreviate tiles, e.g. 2t3t becomes 23t. Only exactly as many tiles as specified by the action can be provided.

It is possible to omit certain parts of a move. See section /isoraqathedh/ujmj-engine/src/branch/master/*Abbreviation for more.


To ease typing in by humans and removing redundant information, writers can choose to omit writing parts of a turn. Here are the omission rules:

  1. The player can be omitted in a turn if it can be predicted from the player of the previous turn:

    • The first turn is always East's, so it can be omitted.
    • If the current turn is in order to the previous one, e.g. in a four player game the previous player is E and this turn is S, then it can be omitted too. In practise, this means that the player letter can be omitted if the first move in the turn is not P, K or R.
  2. A turn consisting of a single tile means that the implicit next player in line has drawn from the wall, and then discarded the tile named. That is, a move like 0.P can be expanded to 0.?,P.
  3. If a turn consists of two moves, neither with actions, one specifying two tiles and one specifying one tile, then this means that the player has called chow on the two tiles, and then discarded the one tile.

Data paragraph

A data paragraph is a regular paragraph whose first word ends in a colon.

Two data paragraphs can be separated by a single newline.

Data paragraphs are intended for storage of some metadata that is relevant to the game, in a similar vein to the tag pairs of Portable Game Notation.

Valid tags are those that end in a colon, and contain only letters, digits and hyphens.

The meaning of these tags are not set and will be decided later.


A line starting with a colon is a comment line. It is ignored.

A paragraph that isn't a game or a data paragraph is a comment paragraph. It is also ignored.

A word that is starts and ends with matching brackets of any kind within a game paragraph is also a comment. It is ignored in the sense of parsing a game, but it is intended that it corresponds to a matching comment paragraph whose first word is the same, allowing for a game to be annotated.