// =============================================================================
// > CLASSES
// =============================================================================
/**
* Shortcute to create a new Set
*
* @param array $array
* @return Set
*/
function set($array = []) {
return new Set($array);
}
/**
* Shortcut to create a new Scalar object
*
* @param mixed $scalar_value
* @return Scalar
*/
function scalar($scalar_value) {
return new Scalar($scalar_value);
}
/**
* Shortcut to create a grid pointer
* @param array $xy [x, y]
* @return GridPointer
*/
function xy($xy = [0, 0])
{
if ($xy instanceof GridPointer) return $xy;
return new GridPointer($xy);
}
/**
* Shortcut to create a new grid
* @param mixed $from Either a string, scalar, array or set
* @param mixed $x_delimiter For strings only : Number of characters per cell, or separation character to use for cells
* @param string $y_delimiter For strings only : Separation character to use for rows
* @return Grid
*/
function grid($from = [], $x_delimiter = 1, $y_delimiter = "\n")
{
return new Grid($from, $x_delimiter, $y_delimiter);
}
/**
* Shortcut to create a new tree
*
* @param array $relations Relations of [parent=>children]
* @return Tree
*/
function tree($relations = [])
{
return new Tree($relations);
}
// =============================================================================
// > TYPES
// =============================================================================
/**
* Force int type, regardless of current type
* Works with objects that implement __toString().
*
* @param mixed $value
* @return Int
*/
function int($value)
{
return Scalar::int($value);
}
// =============================================================================
// > SEARCHES
// =============================================================================
/**
* Binary search : Find a result between two bounds
* Move bounds until the comparaison function returns 0.
* @param int $lb
* @param int $ub
* @param callable $fn
* @return int
*/
function binary_search($lb, $ub, $fn, $round = true) {
$value = $lb + (($ub - $lb) / 2);
switch ($fn($value, $lb, $ub)) {
case 0: return $round ? round($value) : $value;
case -1: $ub = $round ? ceil($value) : $value; break;
case 1: $lb = $round ? floor($value) : $value; break;
}
return binary_search($lb, $ub, $fn, $round);
}
// =============================================================================
// > CACHE
// =============================================================================
/**
* Get or set a value in the cache
*
* @param string $key
* @param mixed|callback $value
* @return mixed
*/
function cache($key, $value = null)
{
// If the value is null, return the cached value
if (is_null($value)) {
return Cache::get($key);
}
return Cache::set($key, $value);
}
/**
* Cahce a value only if it's not already cached
*
* @param string $key
* @param mixed|callback $value
* @return mixed
*/
function cache_once($key, $value)
{
if (Cache::has($key)) {
return Cache::get($key);
}
return Cache::set($key, $value);
}
/**
* Memoize a function call
*
* @param array ...$args
* @return mixed
*/
function memoized($function, $args, $key)
{
return Cache::memoized($function, $args, $key);
}
// =============================================================================
// > 2D
// =============================================================================
/**
* Normalize a direction (l -> left, ^ -> up, r -> right, v -> down, ...)
*
* @param string $direction
* @return string
*/
function direction($direction)
{
// Create a cached list of aliases for all direcitons
$aliases = cache_once("direction_aliases", function () {
$directions = [
"up" => ["up", "north", "above", "jump", "^", "u", "n"],
"right" => ["right", "east", "forward", "advance", ">", "r", "e"],
"down" => ["down", "south", "below", "dig", "v", "d", "s"],
"left" => ["left", "west", "backward", "back", "<", "l", "w"],
"up-right" => ["up-right", "north-east", "above-forard", "jump-forward", "^<", "ur", "ne"],
"up-left" => ["up-left", "north-west", "above-backward", "jump-backward", "^>", "ul", "nw"],
"down-right" => ["down-right", "south-east", "below-forward", "dig-forward", "v<", "dr", "se"],
"down-left" => ["down-left", "south-west", "below-backward", "dig-backward", "v>", "dl", "sw"],
];
$aliases = [];
grid(set($directions)->instanciate("Set"))->columns()->callEach()->values()->merge()->each(function ($alias, $i) use ($directions, &$aliases) {
$aliases[$alias] = array_keys($directions)[$i % 8];
});
return $aliases;
});
return $aliases[strtolower($direction)] ?? die("No alias found for direction : {$direction}");
}
/**
* Convert a direction based on the facing direction
*
* @param string $facing
* @param string $going
* @return string
*/
function facing($facing, $going)
{
return [
"up" => ["up" => "up", "left" => "left", "right" => "right", "down" => "down"],
"left" => ["up" => "left", "left" => "down", "right" => "up", "down" => "right"],
"right" => ["up" => "right", "left" => "up", "right" => "down", "down" => "left"],
"down" => ["up" => "down", "left" => "right", "right" => "left", "down" => "up"],
][$facing][$going];
}
/**
* Shortcut for neighbors points
*/
function neighbors($xy, $diagonals = false, $minX = -INF, $maxX = INF, $minY = -INF, $maxY = INF)
{
$neighbors = [
"up" => [$xy[0], $xy[1] - 1],
"right" => [$xy[0] + 1, $xy[1]],
"down" => [$xy[0], $xy[1] + 1],
"left" => [$xy[0] - 1, $xy[1]],
];
if ($diagonals) $neighbors = array_merge($neighbors, [
"up-right" => [$xy[0] + 1, $xy[1] - 1],
"up-left" => [$xy[0] - 1, $xy[1] - 1],
"down-right" => [$xy[0] + 1, $xy[1] + 1],
"down-left" => [$xy[0] - 1, $xy[1] + 1],
]);
return array_filter($neighbors, fn ($xy) =>
$xy[0] >= $minX && $xy[0] <= $maxX && $xy[1] >= $minY && $xy[1] <= $maxY
);
}
/**
* Manhattan distance between two points
*
* @param array $a
* @param array $b
* @return int
*/
function manhattan($a, $b)
{
return array_reduce(array_keys((array) $a), fn ($sum, $i) => $sum + abs($a[$i] - $b[$i]), 0);
}
// =============================================================================
// > 3D
// =============================================================================
/**
* Get the size of a box
*
* @param array $box
* @return array
*/
function get_box_size($box) {
return [
abs($box[0][0] - $box[0][1]),
abs($box[1][0] - $box[1][1]),
abs($box[2][0] - $box[2][1]),
];
}
/**
* Get a box center
*
* @return array $box
* @return array
*/
function get_box_center($box)
{
$size = get_box_size($box);
return [
$box[0][0] + $size[0] / 2,
$box[1][0] + $size[1] / 2,
$box[2][0] + $size[2] / 2
];
}
/**
* Divide a box in 8 sub-boxes
*
* @param array $box [-x, +x] [-y, +y] [-z, +z]
* @return array
*/
function divide_box($box) {
$boxes = [];
$size = get_box_size($box);
foreach ([0, 1] as $a[0]) foreach ([0, 1] as $a[1]) foreach ([0, 1] as $a[2]) {
$subox = [];
foreach ($a as $i=>$k) $subox[$i] = !$k
? [$box[$i][0], floor($box[$i][0] + $size[$i] / 2)]
: [ceil($box[$i][0] + $size[$i] / 2), $box[$i][1]];
$boxes[] = $subox;
}
return $boxes;
}
/**
* Rotate a 3D point on an axis
*
* @param array $point [x, y, z]
* @param string $axis x, y or z
* @param int $angle Angle in degree
* @return array The resulting point
*/
function rotate_3d_point($point, $axis, $angle) {
[$x, $y, $z] = $point;
[$nx, $ny, $nz] = $point;
$angle = pi() * $angle / 180;
switch ($axis) {
case "x":
$ny = $y * cos($angle) - $z * sin($angle);
$nz = $y * sin($angle) + $z * cos($angle);
break;
case "y":
$nx = $x * cos($angle) + $z * sin($angle);
$nz = -$x * sin($angle) + $z * cos($angle);
break;
case "z":
$nx = $x * cos($angle) - $y * sin($angle);
$ny = $x * sin($angle) + $y * cos($angle);
break;
default:
return $point;
}
return array_map(fn ($v) => $v == -0 ? 0 : $v, [round($nx), round($ny), round($nz)]);
}
/**
* Rotate several points at a time
*
* @see rotate_3d_point
* @return array
*/
function rotate_3d_points($points, $axis, $angle) {
return array_map(fn ($p) => rotate_3d_point($p, $axis, $angle), $points);
}
/**
* Apply a 3D rotation function 24 times on an item, one for each possible orientation
*
* @param mixed
* @return Iterator Each 24 rotations of the item
*/
function get_3d_rotations($item, $rotation_callback) {
$top = [["x", 0], ["x", 90], ["x", -90], ["x", 180], ["z", -90], ["z", 90]];
foreach ($top as [$axis, $angle]) {
$rot = $rotation_callback($item, $axis, $angle);
for ($i = 0; $i < 4; $i++) {
yield $rotation_callback($rot, "y", 90 * $i);
}
}
}
/**
* The 24 possible rotations of a 3D point
*/
function get_3d_point_rotations($point) {
return get_3d_rotations($point, "rotate_3d_point");
}
/**
* The 24 possible rotations of an array of 3D points
*/
function get_3d_points_rotations($points) {
return get_3d_rotations($points, "rotate_3d_points");
}