slash/Mono.cs

280 lines
8.9 KiB
C#

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<int>
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<XYZi, XYZi> 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<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 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
};
}