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

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()