dofdemo/src/Mono.cs
2024-11-25 13:37:23 -05:00

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)
);
}
}