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