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 Pose box_pose = new(0, 0, -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 Vec3 fullstick = Vec3.Up; public static Pose r_con_stick = Pose.Identity; public static XYZi snake_dir = new(0, 0, 1), last_snake_dir = new(0, 0, 1); public static DeltaBool in_box = new(true); public static DeltaBool snake_fall = new(false); public static Dictionary holes = new(); public static XYZi food = new(2, 0, 0); public static DeltaBool food_fall = new(false); public static double eat_timestamp = 0.0; static void PlayBox(this Sound sound, XYZi pos) { sound.Play(box_pose.ToMatrix(box_scale) * pos.ToVec3); } public static Sound sfx_crisp_nom = Sound.FromFile("sfx/crisp_nom.mp3"); public static Sound sfx_punch_through = Sound.FromFile("sfx/punch_through.mp3"); public static void Init() { for (int i = 0; i < snake.Length; i++) { snake[i] = new XYZi(0, 1, 0); } } public static void Frame() { // flatscreen dev controls if (Device.Name == "Simulator") { if (Input.Key(Key.MouseLeft).IsActive()) { float sx = Maths.s_scalar(Input.Mouse.pos.x / 640); float ssx = Maths.smooth_start(sx); box_pose.orientation *= Quat.FromAngles(0, sx * 180.0f * Time.Stepf, 0); } if (Input.Key(Key.A).IsJustActive()) snake_dir = new(-1, 0, 0); if (Input.Key(Key.S).IsJustActive()) snake_dir = new(+1, 0, 0); if (Input.Key(Key.W).IsJustActive()) snake_dir = new(0, 0, -1); if (Input.Key(Key.R).IsJustActive()) snake_dir = new(0, 0, +1); if (Input.Key(Key.Shift).IsJustActive()) snake_dir = new(0, -1, 0); if (Input.Key(Key.Space).IsJustActive()) snake_dir = new(0, +1, 0); fullstick = snake_dir.ToVec3; } else { Pose head = Input.Head; box_pose.position = head.position + head.orientation * V.XYZ(0, -(SD_Y + 0.5f) * box_scale, -32 * U.cm); Hand r_hand = Input.Hand(Handed.Right); Controller r_con = Input.Controller(Handed.Right); bool con_tracked = r_con.trackedPos > TrackState.Lost; Input.HandVisible(Handed.Max, !con_tracked); if (con_tracked) { Vec2 stick = r_con.stick; Quat stick_rot = Quat.FromAngles(stick.y * -90, 0, stick.x * +90); float stick_sign = r_con.IsStickClicked ? -1 : +1; r_con_stick = r_con.pose; r_con_stick.position += r_con_stick.orientation * V.XYZ(0.0065f, -0.012f, -0.05f); r_con_stick.orientation *= Quat.FromAngles(-50, 0, 0); fullstick = r_con_stick.orientation * stick_rot * Vec3.Up * stick_sign; // Vec3 fullstick = r_hand.palm.orientation * Vec3.Up; float ax = Maths.abs(fullstick.x); float ay = Maths.abs(fullstick.y); float az = Maths.abs(fullstick.z); if (ax > ay && ax > az) snake_dir = new(Maths.sign(fullstick.x), 0, 0); if (ay > ax && ay > az) snake_dir = new(0, Maths.sign(fullstick.y), 0); if (az > ax && az > ay) snake_dir = new(0, 0, Maths.sign(fullstick.z)); } } // catch invalid direction and revert to last_dir if (snake[0] + snake_dir == snake[1]) { snake_dir = last_snake_dir; } last_snake_dir = snake_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; } 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; // gravity bool grounded = false; for (int i = 0; i < snake_len; i++) { if (snake[i].y == -SD_Y) { grounded = true; break; } } snake_fall.Step(!grounded); if (snake_fall.state) { for (int i = 0; i < snake_len; i++) { snake[i] -= new XYZi(0, 1, 0); } } bool food_grounded = food.y == -SD_Y; XYZi below_food = new XYZi(food.x, food.y - 1, food.z); bool on_snake = s_array.InRange(below_food) && s_array[below_food] > -1; food_fall.Step(!food_grounded && !on_snake); if (food_fall.state) { food -= new XYZi(0, 1, 0); } 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); } } } } // 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 }; }