Games#
Some two-player games implemented using scene click events.
1import time
2from typing import Literal
3
4import numpy as onp
5import trimesh.creation
6import viser
7import viser.transforms as tf
8from typing_extensions import assert_never
9
10
11def main() -> None:
12 server = viser.ViserServer()
13 server.configure_theme(dark_mode=True)
14 play_connect_4(server)
15
16 server.add_gui_button("Tic-Tac-Toe").on_click(lambda _: play_tic_tac_toe(server))
17 server.add_gui_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.reset_scene()
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.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(onp.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.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.add_mesh_simple(
65 f"/game_pieces/{row}_{col}",
66 cylinder.vertices,
67 cylinder.faces,
68 wxyz=tf.SO3.from_y_radians(onp.pi / 2.0).wxyz,
69 color={"red": (255, 0, 0), "yellow": (255, 255, 0)}[whose_turn],
70 )
71 for row_anim in onp.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.reset_scene()
86
87 whose_turn: Literal["x", "o"] = "x"
88
89 for i in range(4):
90 server.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(onp.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 onp.linspace(0.01, 1.0, 5):
101 if symbol == "x":
102 for k in range(2):
103 server.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(
109 onp.pi / 2.0 * k + onp.pi / 4.0
110 ).wxyz,
111 )
112 elif symbol == "o":
113 mesh = trimesh.creation.annulus(0.25 * scale, 0.35 * scale, 0.125)
114 server.add_mesh_simple(
115 f"/symbols/{i}_{j}",
116 mesh.vertices,
117 mesh.faces,
118 position=(i, j, 0),
119 color=(255, 0, 0),
120 )
121 else:
122 assert_never(symbol)
123 server.flush()
124 time.sleep(1.0 / 30.0)
125
126 def setup_cell(i: int, j: int) -> None:
127 """Create a clickable sphere in a given cell."""
128 sphere = server.add_icosphere(
129 f"/spheres/{i}_{j}",
130 radius=0.25,
131 position=(i, j, 0),
132 color=(255, 255, 255),
133 )
134
135 @sphere.on_click
136 def _(_) -> None:
137 nonlocal whose_turn
138 whose_turn = "x" if whose_turn != "x" else "o"
139 sphere.remove()
140 draw_symbol(whose_turn, i, j)
141
142 for i in range(3):
143 for j in range(3):
144 setup_cell(i, j)
145
146
147if __name__ == "__main__":
148 main()