using System.Collections.Generic; using StereoKit; namespace snake; static class Mono { public static double step_step = 60.0 / 80; // 80|100|120 bpm public static double step_time = 0.0; public static double step_t = 0.0; public static bool menu = true; public static Pose box_pose = new(0, -3 * U.cm, -10 * U.cm); public static float box_scale = 1.333f * U.cm; public const int SD_X = 2, SD_Y = 1, SD_Z = 2; public static SpatialArray s_array = new(SD_X, SD_Y, SD_Z, -1), head_fill = new(SD_X, SD_Y, SD_Z, -1), tail_fill = new(SD_X, SD_Y, SD_Z, -1); public static XYZi[] snake = new XYZi[ Maths.u_length(SD_X) * Maths.u_length(SD_Y) * Maths.u_length(SD_Z) ]; public static int snake_len = 4; public static int grow_buffer = 0; public static XYZi snake_dir = new(0, 0, 1); public static DeltaBool in_box = new(true); public static Dictionary holes = new(); public static XYZi food = new(2, 0, 0); public static double eat_timestamp = 0.0; public enum BoxMode { Float = -1, Hold = 0, Mount = 1, } // start mounted & in_cone public static BoxMode box_mode = BoxMode.Mount; public static DeltaBool in_cone = new(true); public static DeltaBool in_dist = new(false); public static void Init() { for (int i = 0; i < snake.Length; i++) { snake[i] = new XYZi(0, 0, 0); } } public static void Frame() { if (Rig.btn_trigger.delta == +1) { menu = !menu; SFX.click.PlayBox(new XYZi(0, 0, Mono.SD_Z + 1)); } // flatscreen dev controls if (Device.Name == "Simulator") { if (Input.Key(Key.MouseCenter).IsActive()) { float sx = Input.Mouse.posChange.x; float ssx = Maths.smooth_start(sx); box_pose.orientation *= Quat.FromAngles(0, sx * 180.0f * Time.Stepf, 0); } } else { in_dist.Step(Vec3.Distance(Rig.r_con_stick.position, box_pose.position) < 6 * U.cm); bool pickup = in_dist.state && Rig.btn_grip.delta == +1; in_cone.Step(Vec3.AngleBetween( Rig.head.orientation * Vec3.Forward, Vec3.Direction(box_pose.position, Rig.head.position) ) < 15.0f); switch (box_mode) { case BoxMode.Float: if (pickup) { box_mode = BoxMode.Hold; } break; case BoxMode.Hold: box_pose.position = Rig.r_con_stick.position; if (Rig.btn_grip.delta == -1) { box_mode = in_cone.state ? BoxMode.Mount : BoxMode.Float; } break; case BoxMode.Mount: // orbital_view box_pose.position = Rig.head.position + Rig.head.orientation * V.XYZ(0, -(SD_Y + 0.5f) * box_scale, -32 * U.cm); if (pickup) { box_mode = BoxMode.Hold; } break; } } XYZi next_pos = snake[0] + Rig.new_dir; bool neck_break = next_pos == snake[1]; if (!neck_break) { snake_dir = Rig.new_dir; } } public static void Step() { if (s_array[snake[0] + snake_dir] > -1) { // lose condition bool stuck = true; for (int i = 0; i < directions.Length; i++) { if (s_array[snake[0] + directions[i]] == -1) { stuck = false; } } if (stuck) { Log.Info("your stuck"); } return; } XYZi next_pos = snake[0] + snake_dir; XYZi inset_pos = new( next_pos.x - Maths.sign(next_pos.x), next_pos.y - Maths.sign(next_pos.y), next_pos.z - Maths.sign(next_pos.z) ); bool around_box = s_array.InRange(inset_pos); if (!around_box) { return; } if (snake_len == snake.Length) { // win condition Log.Info("full snake"); return; } else { if (grow_buffer > 0) { snake_len++; grow_buffer--; } } // slither if (!menu) { for (int i = snake.Length - 1; i > 0; i--) { snake[i] = snake[i - 1]; } snake[0] += snake_dir; } in_box.Step(s_array.InRange(snake[0])); if (in_box.delta != 0) // 1 just in -1 just out { holes.Add(snake[0], snake_dir); SFX.punch_through.PlayBox(snake[0]); } if (holes.ContainsKey(snake[snake_len - 1])) { holes.Remove(snake[snake_len - 1]); } s_array.Clear(-1); for (int i = 0; i < snake_len; i++) { s_array[snake[i]] = i; } // eat if (food == snake[0]) { eat_timestamp = Time.Total; grow_buffer += 3; Feed(); SFX.crisp_nom.PlayBox(snake[0]); } } static void Feed() { head_fill.Clear(-1); Gas(head_fill, snake[0]); // handle out of the box exception on tail tail_fill.Clear(-1); Gas(tail_fill, snake[snake_len - 1]); XYZi farthest_cell = new XYZi(0, 0, 0); int farthest_dist = 0; // [*] design configurable (we basically want it to be as far away as it can reasonably be) const int max_dist = 6; // [!] given how it loops over the space it directionally biases the results for (int sx = -s_array.Xslen; s_array.InX(sx); sx++) { for (int sy = -s_array.Yslen; s_array.InY(sy); sy++) { for (int sz = -s_array.Zslen; s_array.InZ(sz); sz++) { XYZi sv = new XYZi(sx, sy, sz); int dist = head_fill[sv]; bool good_dist = dist > farthest_dist && dist < max_dist; bool tail_access = tail_fill[sv] > 0; // help ensure completability bool snake_free = s_array[sv] == -1; if (good_dist && tail_access && snake_free) { farthest_dist = dist; farthest_cell = sv; } } } } food = farthest_cell; } // space fill algorithm static void Gas(SpatialArray fill_array, XYZi sv) { Queue queue = new Queue(); queue.Enqueue(sv); fill_array[sv] = 0; while (queue.Count > 0) { XYZi _sv = queue.Dequeue(); int currentDistance = fill_array[_sv]; // check all 4 directions foreach (XYZi dir in directions) { XYZi newV = _sv + dir; if (fill_array.InRange(newV) && fill_array[newV] == -1 && s_array[newV] == -1) { fill_array[newV] = currentDistance + 1; queue.Enqueue(newV); } } } } static int[] GetShuffledIndices(T[] array) { int[] indices = new int[array.Length]; for (int i = 0; i < array.Length; i++) { indices[i] = i; } for (int i = indices.Length - 1; i > 0; i--) { int j = System.Random.Shared.Next(i + 1); int temp = indices[i]; indices[i] = indices[j]; indices[j] = temp; } return indices; } // directions for moving in the grid static readonly XYZi[] directions = new XYZi[] { new XYZi(-1, 0, 0), // lft new XYZi(+1, 0, 0), // rht new XYZi(0, -1, 0), // dwn new XYZi(0, +1, 0), // up new XYZi(0, 0, -1), // fwd new XYZi(0, 0, +1), // back }; static readonly XYZi[] corners = new XYZi[] { new XYZi(-1, -1, -1), new XYZi(-1, -1, +1), new XYZi(-1, +1, -1), new XYZi(-1, +1, +1), new XYZi(+1, -1, -1), new XYZi(+1, -1, +1), new XYZi(+1, +1, -1), new XYZi(+1, +1, +1) }; }