using System.Collections.Generic; using StereoKit; namespace snake; static class Mono { public static double game_time = 0.0; 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 double intro_skip = 0.0; public static bool intro = true; // press to move until x condition or hold to skip 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 = 3, SD_Y = 2, SD_Z = 3; public static SpatialArray box_space = new(SD_X - 1, SD_Y - 1, SD_Z - 1, -1), s_array = new(SD_X, SD_Y, SD_Z, -1), tail_fill = new(SD_X, SD_Y, SD_Z, -1); static void update_s_array() { s_array.Clear(-1); for (int i = 0; i < snake_len; i++) { s_array[snake[i]] = i; } } 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 = 1; public static int grow_buffer = 3; 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(0, 0, 0); // [!] start random to keep new game fresh? public static int eaten = 0; public static DeltaBool eaten_latch = new(false); 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); } update_s_array(); } public static void Frame() { if (Rig.btn_back.delta == +1) { menu = !menu; SFX.click.PlayBox(new XYZi(0, 0, Mono.SD_Z + 1)); } if (menu) { // just one btn(resume) in menu rn if (Rig.btn_select.delta == +1) { Rig.btn_select.delta = 0; // [!] hacky capture menu = false; 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; Renderer.CameraRoot *= Matrix.R( Quat.FromAngles(0, -sx * 180.0f * Time.Stepf, 0) ); // orbital_view box_pose.position = Input.Head.position + Input.Head.orientation * V.XYZ(0, -0 * U.cm, -10 * U.cm); } } 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() { // eat tail if (snake[0] + snake_dir == snake[snake_len - 1]) { snake_len--; grow_buffer = 0; } else 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; } bool in_or_around_box = s_array.InRange(snake[0] + snake_dir); if (!in_or_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 for (int i = snake.Length - 1; i > 0; i--) { snake[i] = snake[i - 1]; } snake[0] += snake_dir; in_box.Step(box_space.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]); Arts.box_shake += snake_dir.ToVec3; } if (holes.ContainsKey(snake[snake_len - 1])) { holes.Remove(snake[snake_len - 1]); } update_s_array(); // eat if (!eaten_latch.state) { eaten_latch.Step(food == snake[0]); if (eaten_latch.delta == +1) { eat_timestamp = Time.Total; grow_buffer += 3; eaten++; VFX.Play(snake[0]); SFX.crisp_nom.PlayBox(snake[0]); } } else { (bool viable, XYZi cell) = Feed(); eaten_latch.Step(!viable); if (eaten_latch.delta == -1) { food = cell; } } } static (bool, XYZi) Feed() { // [!] handle out of the box exception on tail // by making the spatial arrays encapsulate the layer outside of the box tail_fill.Clear(-1); Gas(tail_fill, snake[snake_len - 1]); // step from head using directions towards the tail // and stop at either 1 cell away from the tail or 5 spaces away from the head bool viable = false; bool viable_step = false; XYZi cell = snake[0]; for (int step = 0; step < 5; step++) { viable_step = false; int min_dist = 100; XYZi min_cell = new(); int[] dir_indices = GetShuffledIndices(directions); for (int i = 0; i < directions.Length; i++) { XYZi dir = directions[dir_indices[i]]; XYZi dir_cell = cell + dir; int tail_dist = tail_fill[dir_cell]; if (tail_dist > 1 && tail_dist < min_dist && box_space.InRange(dir_cell)) { min_dist = tail_dist; min_cell = dir_cell; viable = true; viable_step = true; } } if (viable_step) { tail_fill[min_cell] = -1; // prevent backtracking cell = min_cell; } // if (min_dist <= 1) // { // break; // } } return (viable, 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 6 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); (indices[j], indices[i]) = (indices[i], indices[j]); } return indices; } // directions for moving in the grid static readonly XYZi[] directions = new XYZi[] { new(-1, 0, 0), // lft new(+1, 0, 0), // rht new(0, -1, 0), // dwn new(0, +1, 0), // up new(0, 0, -1), // fwd new(0, 0, +1), // back }; static readonly XYZi[] corners = new XYZi[] { new(-1, -1, -1), new(-1, -1, +1), new(-1, +1, -1), new(-1, +1, +1), new(+1, -1, -1), new(+1, -1, +1), new(+1, +1, -1), new(+1, +1, +1) }; static XYZi InsetCell(XYZi cell) { // [!] don't inset past 0 return new( cell.x - Maths.sign(cell.x), cell.y - Maths.sign(cell.y), cell.z - Maths.sign(cell.z) ); } }