Skip to main content

Omok (오목)

Omok is an open-source Python library for developing AI for Omok (Gomoku) and Connect6.

Install

$ pip install omok
$ pip install "omok[fast]"   # optional: numba-compiled Renju rule, ~30x faster

With the fast extra, Renju forbidden move detection is compiled with numba (the first run takes a few seconds to compile, later runs use the on-disk cache). Without it, a pure Python implementation is used. Set OMOK_DISABLE_NUMBA=1 to force the pure Python version.

Development

This project uses uv.

$ uv sync            # create a virtual environment and install dependencies
$ uv run pytest      # run tests
$ uv run python -m omok
$ uv build           # build distribution packages

Usage

Play

$ python -m omok

A web server starts at http://127.0.0.1:8000 and a browser opens automatically. Click the board to place a stone. Keyboard shortcuts: A (AI move), B (undo), Space (reset), S (save game record). Turn on Show AI probabilities (shortcut P) to show the AI's move probabilities (%) for the current turn as a heatmap on the board.

$ python -m omok --port 8080 --no-browser   # set the port, don't open a browser
$ python -m omok --no-agent                 # run without the AI
$ python -m omok --game connect6            # Connect6 (no AI)

Rule

The default rule is Renju. Black may not play a double-three (3-3), a double-four (4-4), or an overline (six or more in a row), and wins only with exactly five. If a move makes a five and a forbidden shape at the same time, the five takes priority. White has no restrictions, and an overline also counts as a win for White. In the web UI, forbidden points are marked with a red × on Black's turn.

$ python -m omok --rule freestyle           # freestyle, no forbidden moves
env = omok.Omok(rule='freestyle')  # rule='renju' by default
env.get_forbidden()   # Black's current forbidden points (Renju only, on Black's turn; otherwise [])

Environment

import omok

env = omok.Omok()
for move in [112, 111, 96, 97, 128, 113, 80, 127, 144]:
    env(move)
print(env)

"""
Result
+-------------------------------+
| - - - - - - - - - - - - - - - |
| - - - - - - - - - - - - - - - |
| - - - - - - - - - - - - - - - |
| - - - - - - - - - - - - - - - |
| - - - - - - - - - - - - - - - |
| - - - - - O - - - - - - - - - |
| - - - - - - O X - - - - - - - |
| - - - - - - X O X - - - - - - |
| - - - - - - - X O - - - - - - |
| - - - - - - - - - O - - - - - |
| - - - - - - - - - - - - - - - |
| - - - - - - - - - - - - - - - |
| - - - - - - - - - - - - - - - |
| - - - - - - - - - - - - - - - |
| - - - - - - - - - - - - - - - |
+-------------------------------+
| Player 2  Winner 1  Moves   9 |
+-------------------------------+
"""

Agent

import omok

agent = omok.OmokAgent(model_index=1)
env = omok.Omok()
while True:
    state = env.get_state()
    player = env.get_player()
    action = agent(state, player)
    env(action)
    print(env)
    if env.is_done():
        break

Reinforcement Learning

import numpy as np
import omok

env = omok.Omok()
while not env.is_done():
    obs = env.get_observation()   # (5, 15, 15) float32, see below
    mask = env.get_legal_mask()   # (225,) bool: legal moves (excludes Renju forbidden points)
    action = np.random.choice(np.flatnonzero(mask))
    env(action)                   # 0: continue, 1: game over, -1: illegal move
print(env.get_winner())           # 0: draw, 1: black wins, 2: white wins

Gymnasium-style step()

env = omok.Omok()
obs, info = env.reset()
terminated = False
while not terminated:
    action = np.random.choice(np.flatnonzero(info['action_mask']))
    obs, reward, terminated, truncated, info = env.step(action)
print(reward, info['winner'])  # reward is for the player who just moved (1 for a win, else 0)
  • obs and info['action_mask'] are for the player to move next; reward is for the player who just moved.
  • step() raises ValueError on an illegal move or when the game is already over.
  • The Renju forbidden points are cached until the board changes, so repeated get_legal_mask() calls are cheap.
  • Renju env steps are about 2x slower than freestyle with numba, and about 50-70x slower without it.
  • get_state() and get_move_history() return copies, so they are safe to store as-is.
  • A position out of range (outside 0 <= pos < 225) raises ValueError.
  • Undo moves with move_back().

Observation planes of get_observation(), all from the current player's view:

Plane Omok (5, 15, 15) Connect6 (5, 19, 19)
0 own stones own stones
1 opponent stones opponent stones
2 1 if black to play 1 if black to play
3 last stone placed last stone placed
4 forbidden points (Renju, black to play) 1 if the next stone is the last one of the turn

Search (e.g. MCTS)

env = omok.Omok.from_moves([112, 111, 96])   # replay moves; kwargs go to the constructor (rule=...)
child = env.clone()                          # independent copy, ~6x faster than copy.deepcopy
child.move(97)

Symmetry augmentation

from omok import transforms

obs = env.get_observation()
policy = np.zeros(225, np.float32)           # e.g. MCTS visit distribution
for obs_t, policy_t in transforms.symmetries(obs, policy):   # 8 rotations/reflections
    ...

transforms.transform_board, transform_action and transform_policy apply a single symmetry code (0-7) and its inverse, and work for any board size.

Connect6

import omok

env = omok.Connect6()   # 19x19, six or more in a row wins
for move in [180, 179, 161, 160, 200, 181, 199, 140, 220, 198, 162, 120]:
    env(move)           # black opens with 1 stone, then each side places 2 per turn
print(env.get_winner())  # 1: black completes six with the last move

Connect6 provides the same interface as Omok (step, get_legal_mask, get_observation, from_moves, clone, move_back, etc.).

License

MIT

Metadata

Release files for omok 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for omok 0.1.0
File Interpreter ABI Platform
omok-0.1.0-py3-none-any.whl Python 3 none any Details

Release files / omok-0.1.0-py3-none-any.whl

Download URL omok-0.1.0-py3-none-any.whl
Size 19.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f86568f1e227d56e22b1b6caab8ff7bc88b642f6c825f2de8a7ddebc7ed95d66
BLAKE2b-256 checksum
How to use checksums
4f0ca3fe9aff6936637186928d3c60b625fc9b8401293659dc9331fb589ee448
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Release history Release notifications | RSS feed

0.2.0

1 release file

This release

0.1.0 This release

1 release file

0.0.11

1 release file

0.0.10

1 release file

0.0.9

1 release file

0.0.8

1 release file

0.0.7

1 release file

0.0.6

1 release file

0.0.5

1 release file

0.0.4

1 release file

0.0.3

1 release file

0.0.2

1 release file

0.0.1

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page