336 lines
9.7 KiB
C#
336 lines
9.7 KiB
C#
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<int>
|
|
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);
|
|
|
|
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<XYZi, XYZi> holes = new();
|
|
public static XYZi food = new(0, -1, 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);
|
|
}
|
|
}
|
|
|
|
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]);
|
|
}
|
|
|
|
s_array.Clear(-1);
|
|
for (int i = 0; i < snake_len; i++)
|
|
{
|
|
s_array[snake[i]] = i;
|
|
}
|
|
|
|
// 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<int> fill_array, XYZi sv)
|
|
{
|
|
Queue<XYZi> queue = new Queue<XYZi>();
|
|
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>(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)
|
|
);
|
|
}
|
|
}
|