Games

Two-player games demonstrating click interactions and game state management.

Features:

  • Click interactions on game board objects

  • Multi-client game state synchronization

  • Turn-based gameplay mechanics

  • Dynamic scene updates for game boards

Source: examples/04_demos/05_games.py

Games

Code

  1import time
  2from typing import Literal
  3
  4import numpy as np
  5import trimesh.creation
  6from typing_extensions import assert_never
  7
  8import viser
  9import viser.transforms as tf
 10
 11
 12def main() -> None:
 13    server = viser.ViserServer()
 14    play_connect_4(server)
 15
 16    server.gui.add_button("Tic-Tac-Toe").on_click(lambda _: play_tic_tac_toe(server))
 17    server.gui.add_button("Connect 4").on_click(lambda _: play_connect_4(server))
 18
 19    while True:
 20        time.sleep(10.0)
 21
 22
 23def play_connect_4(server: viser.ViserServer) -> None:
 24    server.scene.reset()
 25
 26    num_rows = 6
 27    num_cols = 7
 28
 29    whose_turn: Literal["red", "yellow"] = "red"
 30    pieces_in_col = [0] * num_cols
 31
 32    # Create the board frame.
 33    for col in range(num_cols):
 34        for row in range(num_rows):
 35            server.scene.add_mesh_trimesh(
 36                f"/structure/{row}_{col}",
 37                trimesh.creation.annulus(0.45, 0.55, 0.125),
 38                position=(0.0, col, row),
 39                wxyz=tf.SO3.from_y_radians(np.pi / 2.0).wxyz,
 40            )
 41
 42    # Create a sphere to click on for each column.
 43    def setup_column(col: int) -> None:
 44        sphere = server.scene.add_icosphere(
 45            f"/spheres/{col}",
 46            radius=0.25,
 47            position=(0, col, num_rows - 0.25),
 48            color=(255, 255, 255),
 49        )
 50
 51        # Drop piece into the column.
 52        @sphere.on_click
 53        def _(_) -> None:
 54            nonlocal whose_turn
 55            whose_turn = "red" if whose_turn != "red" else "yellow"
 56
 57            row = pieces_in_col[col]
 58            if row == num_rows - 1:
 59                sphere.remove()
 60
 61            pieces_in_col[col] += 1
 62            cylinder = trimesh.creation.cylinder(radius=0.4, height=0.125)
 63            piece = server.scene.add_mesh_simple(
 64                f"/game_pieces/{row}_{col}",
 65                cylinder.vertices,
 66                cylinder.faces,
 67                wxyz=tf.SO3.from_y_radians(np.pi / 2.0).wxyz,
 68                color={"red": (255, 0, 0), "yellow": (255, 255, 0)}[whose_turn],
 69            )
 70            for row_anim in np.linspace(num_rows - 1, row, num_rows - row + 1):
 71                piece.position = (
 72                    0,
 73                    col,
 74                    row_anim,
 75                )
 76                time.sleep(1.0 / 30.0)
 77
 78    for col in range(num_cols):
 79        setup_column(col)
 80
 81
 82def play_tic_tac_toe(server: viser.ViserServer) -> None:
 83    server.scene.reset()
 84
 85    whose_turn: Literal["x", "o"] = "x"
 86
 87    for i in range(4):
 88        server.scene.add_spline_catmull_rom(
 89            f"/gridlines/{i}",
 90            ((-0.5, -1.5, 0), (-0.5, 1.5, 0)),
 91            color=(127, 127, 127),
 92            position=(1, 1, 0),
 93            wxyz=tf.SO3.from_z_radians(np.pi / 2 * i).wxyz,
 94        )
 95
 96    def draw_symbol(symbol: Literal["x", "o"], i: int, j: int) -> None:
 97        for scale in np.linspace(0.01, 1.0, 5):
 98            if symbol == "x":
 99                for k in range(2):
100                    server.scene.add_box(
101                        f"/symbols/{i}_{j}/{k}",
102                        dimensions=(0.7 * scale, 0.125 * scale, 0.125),
103                        position=(i, j, 0),
104                        color=(0, 0, 255),
105                        wxyz=tf.SO3.from_z_radians(np.pi / 2.0 * k + np.pi / 4.0).wxyz,
106                    )
107            elif symbol == "o":
108                mesh = trimesh.creation.annulus(0.25 * scale, 0.35 * scale, 0.125)
109                server.scene.add_mesh_simple(
110                    f"/symbols/{i}_{j}",
111                    mesh.vertices,
112                    mesh.faces,
113                    position=(i, j, 0),
114                    color=(255, 0, 0),
115                )
116            else:
117                assert_never(symbol)
118            server.flush()
119            time.sleep(1.0 / 30.0)
120
121    def setup_cell(i: int, j: int) -> None:
122        sphere = server.scene.add_icosphere(
123            f"/spheres/{i}_{j}",
124            radius=0.25,
125            position=(i, j, 0),
126            color=(255, 255, 255),
127        )
128
129        @sphere.on_click
130        def _(_) -> None:
131            nonlocal whose_turn
132            whose_turn = "x" if whose_turn != "x" else "o"
133            sphere.remove()
134            draw_symbol(whose_turn, i, j)
135
136    for i in range(3):
137        for j in range(3):
138            setup_cell(i, j)
139
140
141if __name__ == "__main__":
142    main()