280 lines
8.9 KiB
C#
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
|
|
};
|
|
}
|