GamesΒΆ
Some two-player games implemented using scene click events.
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 """Play a game of Connect 4."""
25 server.scene.reset()
26
27 num_rows = 6
28 num_cols = 7
29
30 whose_turn: Literal["red", "yellow"] = "red"
31 pieces_in_col = [0] * num_cols
32
33 # Create the board frame.
34 for col in range(num_cols):
35 for row in range(num_rows):
36 server.scene.add_mesh_trimesh(
37 f"/structure/{row}_{col}",
38 trimesh.creation.annulus(0.45, 0.55, 0.125),
39 position=(0.0, col, row),
40 wxyz=tf.SO3.from_y_radians(np.pi / 2.0).wxyz,
41 )
42
43 # Create a sphere to click on for each column.
44 def setup_column(col: int) -> None:
45 sphere = server.scene.add_icosphere(
46 f"/spheres/{col}",
47 radius=0.25,
48 position=(0, col, num_rows - 0.25),
49 color=(255, 255, 255),
50 )
51
52 # Drop piece into the column.
53 @sphere.on_click
54 def _(_) -> None:
55 nonlocal whose_turn
56 whose_turn = "red" if whose_turn != "red" else "yellow"
57
58 row = pieces_in_col[col]
59 if row == num_rows - 1:
60 sphere.remove()
61
62 pieces_in_col[col] += 1
63 cylinder = trimesh.creation.cylinder(radius=0.4, height=0.125)
64 piece = server.scene.add_mesh_simple(
65 f"/game_pieces/{row}_{col}",
66 cylinder.vertices,
67 cylinder.faces,
68 wxyz=tf.SO3.from_y_radians(np.pi / 2.0).wxyz,
69 color={"red": (255, 0, 0), "yellow": (255, 255, 0)}[whose_turn],
70 )
71 for row_anim in np.linspace(num_rows - 1, row, num_rows - row + 1):
72 piece.position = (
73 0,
74 col,
75 row_anim,
76 )
77 time.sleep(1.0 / 30.0)
78
79 for col in range(num_cols):
80 setup_column(col)
81
82
83def play_tic_tac_toe(server: viser.ViserServer) -> None:
84 """Play a game of tic-tac-toe."""
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 """Draw an X or O in the given cell."""
100 for scale in np.linspace(0.01, 1.0, 5):
101 if symbol == "x":
102 for k in range(2):
103 server.scene.add_box(
104 f"/symbols/{i}_{j}/{k}",
105 dimensions=(0.7 * scale, 0.125 * scale, 0.125),
106 position=(i, j, 0),
107 color=(0, 0, 255),
108 wxyz=tf.SO3.from_z_radians(np.pi / 2.0 * k + np.pi / 4.0).wxyz,
109 )
110 elif symbol == "o":
111 mesh = trimesh.creation.annulus(0.25 * scale, 0.35 * scale, 0.125)
112 server.scene.add_mesh_simple(
113 f"/symbols/{i}_{j}",
114 mesh.vertices,
115 mesh.faces,
116 position=(i, j, 0),
117 color=(255, 0, 0),
118 )
119 else:
120 assert_never(symbol)
121 server.flush()
122 time.sleep(1.0 / 30.0)
123
124 def setup_cell(i: int, j: int) -> None:
125 """Create a clickable sphere in a given cell."""
126 sphere = server.scene.add_icosphere(
127 f"/spheres/{i}_{j}",
128 radius=0.25,
129 position=(i, j, 0),
130 color=(255, 255, 255),
131 )
132
133 @sphere.on_click
134 def _(_) -> None:
135 nonlocal whose_turn
136 whose_turn = "x" if whose_turn != "x" else "o"
137 sphere.remove()
138 draw_symbol(whose_turn, i, j)
139
140 for i in range(3):
141 for j in range(3):
142 setup_cell(i, j)
143
144
145if __name__ == "__main__":
146 main()