245 lines
7 KiB
C#
245 lines
7 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 XYZi 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;
|
|
|
|
// put in SFX static class
|
|
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);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
box_pose.position = Rig.head.position + Rig.head.orientation * V.XYZ(0, -(SD_Y + 0.5f) * box_scale, -32 * U.cm);
|
|
}
|
|
|
|
// filter out neck breaking from snake_dir value set
|
|
if (snake[0] + Rig.new_dir != snake[1])
|
|
{
|
|
snake_dir = Rig.new_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
|
|
};
|
|
}
|