Santa is stuck! Let's see how I helped him to escape from the maze using the TypeScript type system.
This is the forth of the four challenges I liked the most from "Advent Of TypeScript 2023" by TypeHero. Check out the other challenges I liked here.
The problem
In this last exercise of "Advent Of TypeScript 2023", I had to help santa to escape from a maze .
Santa is craving cookies! But Alas, he's stuck in a dense North Polar forest. Implement Move so Santa ('๐ ') can find his way to the end of the maze. As a reward, if Santa escapes the maze, fill it with DELICIOUS_COOKIES ('๐ช'). Santa can only move through alleys (' ') and not through the trees ('๐').
The goal of the exercise was to implement a Move
type that, given the current maze state and the next Santa move,
it should return the updated maze:
- a new maze with santa updated state if he can move to the position requested
- a maze filled up of cookie if Santa escaped from the Maze
Implementation
The domain types provided by TypeHero to start to implement the game were quite simple.
The maze is described as a 10x10 matrix filled up by MazeItem
:
- Santa
- trees
"๐"
, and Santa can't walk on cells occupied by them - alley
" "
(pay attention that an alley is composed by TWO spaces :rofl:)
We also have a Directions
union type, that describe the next Santa move.
type Alley = " ";
type SantaClaus = "๐
";
type MazeItem = "๐" | SantaClaus | Alley;
type DELICIOUS_COOKIES = "๐ช";
type MazeMatrix = MazeItem[][];
type Directions = "up" | "down" | "left" | "right";
As it was for the previous exercises, a list of test cases was available for verifying that the implementation was correct.
// can't move up, return same maze!
type test_maze0_up_actual = Move<Maze0, 'up'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐
", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// but Santa can move down!
type test_maze0_down_actual = Move<Maze0, 'down'>;
/*
type Maze1 = [
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", "๐
", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// Santa can move down again!
type test_maze1_down_actual = Move<test_maze0_down_actual, 'down'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", "๐
", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// can't move left!
type test_maze2_left_actual = Move<test_maze1_down_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", "๐
", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// can't move right!
type test_maze2_right_actual = Move<test_maze2_left_actual, 'right'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", "๐
", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// we exhausted all other options! guess we gotta go down!
type test_maze2_down_actual = Move<test_maze2_right_actual, 'down'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", "๐
", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// maybe we just gotta go down all the time?
type test_maze3_down_actual = Move<test_maze2_down_actual, 'down'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", "๐
", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// let's go left this time just to change it up?
type test_maze4_left_actual = Move<test_maze3_down_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", "๐
", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// couldn't hurt to try left again?
type test_maze5_left_actual = Move<test_maze4_left_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", "๐
", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// three time's a charm?
type test_maze6_left_actual = Move<test_maze5_left_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", "๐
", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// we haven't tried up yet (?)
type test_maze6_up_actual = Move<test_maze6_left_actual, 'up'>;
/*
type Maze7 = [
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", "๐
", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// maybe another left??
type test_maze7_left_actual = Move<test_maze6_up_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", "๐
", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// haven't tried a right yet.. let's give it a go!
type test_maze7_right_actual = Move<test_maze7_left_actual, 'right'>;
/*
type Maze7 = [
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", "๐
", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// probably just need to stick with left then
type test_maze8_left_actual = Move<test_maze7_right_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", "๐
", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// why fix what's not broken?
type test_maze9_left_actual = Move<test_maze8_left_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", "๐
", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// do you smell cookies?? it's coming from down..
type test_maze10_down_actual = Move<test_maze9_left_actual, 'down'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", "๐
", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// the cookies must be freshly baked. I hope there's also the customary glass of milk!
type test_maze11_left_actual = Move<test_maze10_down_actual, 'left'>;
/*
type Maze12 = [
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
["๐
", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// COOKIES!!!!!
type test_maze12_left_actual = Move<test_maze11_left_actual, 'left'>;
/*
type MazeWin = [
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
];
*/
First, I needed to define a type that let me find the current Santa position. CurrentPosition
loops over rows and
columns of Maze
(helped by the other type I defined SearchOnRow
), and returns a tuple with two indexes that
describes Santa's position (x and y position starting from the top left corner).
With the new type above, I was ready to define UpdatedPositionFor
, a type that receive the current position and the
NextMove
as one of the Directions
available, and return a tuple with the new position for Santa.
This type uses two supports arrays, UpOrLeftDirectionsUpdatedIndexes
and DownOrRightDirectionsUpdatedIndexes
:
each element of them describes the next index where Santa should go starting from the current one.
They also contain the special element escape
, used when Santa has reached the boundaries of the maze (and later I
will show you what this value triggers).
So at this point, I composed the two types above to create the type NewPosition
, that is able to calculate and
return the new position for Santa.
type UpOrLeftDirectionsUpdatedIndexes = ['escape', 0, 1, 2, 3, 4, 5, 6, 7, 8];
type DownOrRightDirectionsUpdatedIndexes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'escape'];
type UpdatedPositionFor<NextMove extends Directions, RowPosition extends number, ColumnPosition extends number> =
{
"up": [UpOrLeftDirectionsUpdatedIndexes[RowPosition], ColumnPosition];
"down": [DownOrRightDirectionsUpdatedIndexes[RowPosition], ColumnPosition];
"left": [RowPosition, UpOrLeftDirectionsUpdatedIndexes[ColumnPosition]];
"right": [RowPosition, DownOrRightDirectionsUpdatedIndexes[ColumnPosition]];
}[NextMove];
type SearchOnRow<Row extends MazeItem[], PreviousElements extends MazeItem[] = []> =
Row extends [infer Current extends MazeItem, ...infer Others extends MazeItem[]]
? Current extends SantaClaus
? PreviousElements["length"]
: SearchOnRow<Others, [...PreviousElements, Current]>
: false
type CurrentPosition<Maze extends MazeItem[][], PreviousRows extends MazeItem[][] = []> =
Maze extends [infer CurrentRow extends MazeItem[], ...infer OtherRows extends MazeItem[][]]
? SearchOnRow<CurrentRow> extends false
? CurrentPosition<OtherRows, [...PreviousRows, CurrentRow]>
: [PreviousRows['length'], SearchOnRow<CurrentRow>]
: never;
type NewPosition<Maze extends MazeItem[][], NextMove extends Directions> =
CurrentPosition<Maze> extends [infer RowPosition extends number, infer ColumnPosition extends number]
? UpdatedPositionFor<NextMove, RowPosition, ColumnPosition>
: never;
Given the next position from the type above, I was ready to create the Update
type.
This is very similar to what I have already shown saw in the previous articles: I loop over the maze matrix and I place Santa in the cell
corresponding to NextMovePosition
.
The only new thing here is RemoveSantaFromRow
, a type that remove Santa from the cell if it doesn't match the one
contained in NextMovePosition
.
type RemoveSantaFromRow<Row extends MazeItem[]> = {
[Index in keyof Row]: Row[Index] extends SantaClaus ? Alley : Row[Index]
};
type UpdateColumn<Row extends MazeItem[], ColumnPosition extends number> = {
[Index in keyof Row]:
Index extends `${ColumnPosition}`
? SantaClaus
: Row[Index] extends SantaClaus
? Alley
: Row[Index]
};
type Update<Maze extends MazeItem[][], NextMovePosition extends [number, number]> = {
[Index in keyof Maze]:
Index extends `${NextMovePosition[0]}`
? UpdateColumn<Maze[Index], NextMovePosition[1]>
: RemoveSantaFromRow<Maze[Index]>
};
All the types above let me create the Move
type require by the exercise.
I get the NewPosition
result: if it is a valid position I update the maze (if the cell is an Alley
).
If the position is not a pair of Row, Column
numbers, it means that it contains escape
and so... the Cookies
type will fill the maze with cookies ๐ช.
type CookiesRow<Row extends MazeItem[]> = {
[Index in keyof Row]: DELICIOUS_COOKIES
};
type Cookies<Maze extends MazeItem[][], > = {
[Index in keyof Maze]: CookiesRow<Maze[Index]>
};
type Move<Maze extends MazeItem[][], NextMove extends Directions> =
NewPosition<Maze, NextMove> extends [infer Row extends number, infer Column extends number]
? Maze[Row][Column] extends Alley
? Update<Maze, [Row, Column]>
: Maze
: Cookies<Maze>;
Below, you can find the full solution and the test cases we saw before to verify its correctness.
type Alley = " ";
type SantaClaus = "๐
";
type MazeItem = "๐" | SantaClaus | Alley;
type DELICIOUS_COOKIES = "๐ช";
type MazeMatrix = MazeItem[][];
type Directions = "up" | "down" | "left" | "right";
type UpOrLeftDirectionsUpdatedIndexes = ['escape', 0, 1, 2, 3, 4, 5, 6, 7, 8];
type DownOrRightDirectionsUpdatedIndexes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'escape'];
type UpdatedPositionFor<NextMove extends Directions, RowPosition extends number, ColumnPosition extends number> =
{
"up": [UpOrLeftDirectionsUpdatedIndexes[RowPosition], ColumnPosition];
"down": [DownOrRightDirectionsUpdatedIndexes[RowPosition], ColumnPosition];
"left": [RowPosition, UpOrLeftDirectionsUpdatedIndexes[ColumnPosition]];
"right": [RowPosition, DownOrRightDirectionsUpdatedIndexes[ColumnPosition]];
}[NextMove];
type SearchOnRow<Row extends MazeItem[], PreviousElements extends MazeItem[] = []> =
Row extends [infer Current extends MazeItem, ...infer Others extends MazeItem[]]
? Current extends SantaClaus
? PreviousElements["length"]
: SearchOnRow<Others, [...PreviousElements, Current]>
: never
type CurrentPosition<Maze extends MazeItem[][], PreviousRows extends MazeItem[][] = []> =
Maze extends [infer CurrentRow extends MazeItem[], ...infer OtherRows extends MazeItem[][]]
? SearchOnRow<CurrentRow> extends false
? CurrentPosition<OtherRows, [...PreviousRows, CurrentRow]>
: [PreviousRows['length'], SearchOnRow<CurrentRow>]
: never;
type NewPosition<Maze extends MazeItem[][], NextMove extends Directions> =
CurrentPosition<Maze> extends [infer RowPosition extends number, infer ColumnPosition extends number]
? UpdatedPositionFor<NextMove, RowPosition, ColumnPosition>
: never;
type RemoveSantaFromRow<Row extends MazeItem[]> = {
[Index in keyof Row]: Row[Index] extends SantaClaus ? Alley : Row[Index]
};
type UpdateColumn<Row extends MazeItem[], ColumnPosition extends number> = {
[Index in keyof Row]:
Index extends `${ColumnPosition}`
? SantaClaus
: Row[Index] extends SantaClaus
? Alley
: Row[Index]
};
type Update<Maze extends MazeItem[][], NextMovePosition extends [number, number]> = {
[Index in keyof Maze]:
Index extends `${NextMovePosition[0]}`
? UpdateColumn<Maze[Index], NextMovePosition[1]>
: RemoveSantaFromRow<Maze[Index]>
};
type CookiesRow<Row extends MazeItem[]> = {
[Index in keyof Row]: DELICIOUS_COOKIES
};
type Cookies<Maze extends MazeItem[][], > = {
[Index in keyof Maze]: CookiesRow<Maze[Index]>
};
type Move<Maze extends MazeItem[][], NextMove extends Directions> =
NewPosition<Maze, NextMove> extends [infer Row extends number, infer Column extends number]
? Maze[Row][Column] extends Alley
? Update<Maze, [Row, Column]>
: Maze
: Cookies<Maze>;
// ---- TEST CASES -----
// can't move up, return same maze!
type test_maze0_up_actual = Move<Maze0, 'up'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐
", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// but Santa can move down!
type test_maze0_down_actual = Move<Maze0, 'down'>;
/*
type Maze1 = [
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", "๐
", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// Santa can move down again!
type test_maze1_down_actual = Move<test_maze0_down_actual, 'down'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", "๐
", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// can't move left!
type test_maze2_left_actual = Move<test_maze1_down_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", "๐
", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// can't move right!
type test_maze2_right_actual = Move<test_maze2_left_actual, 'right'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", "๐
", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// we exhausted all other options! guess we gotta go down!
type test_maze2_down_actual = Move<test_maze2_right_actual, 'down'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", "๐
", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// maybe we just gotta go down all the time?
type test_maze3_down_actual = Move<test_maze2_down_actual, 'down'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", "๐
", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// let's go left this time just to change it up?
type test_maze4_left_actual = Move<test_maze3_down_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", "๐
", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// couldn't hurt to try left again?
type test_maze5_left_actual = Move<test_maze4_left_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", "๐
", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// three time's a charm?
type test_maze6_left_actual = Move<test_maze5_left_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", "๐
", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// we haven't tried up yet (?)
type test_maze6_up_actual = Move<test_maze6_left_actual, 'up'>;
/*
type Maze7 = [
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", "๐
", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// maybe another left??
type test_maze7_left_actual = Move<test_maze6_up_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", "๐
", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// haven't tried a right yet.. let's give it a go!
type test_maze7_right_actual = Move<test_maze7_left_actual, 'right'>;
/*
type Maze7 = [
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", "๐
", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// probably just need to stick with left then
type test_maze8_left_actual = Move<test_maze7_right_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", "๐
", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// why fix what's not broken?
type test_maze9_left_actual = Move<test_maze8_left_actual, 'left'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", "๐
", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// do you smell cookies?? it's coming from down..
type test_maze10_down_actual = Move<test_maze9_left_actual, 'down'>;
/*
[
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
[" ", "๐
", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// the cookies must be freshly baked. I hope there's also the customary glass of milk!
type test_maze11_left_actual = Move<test_maze10_down_actual, 'left'>;
/*
type Maze12 = [
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", " ", " ", "๐"],
["๐", "๐", "๐", "๐", " ", "๐", " ", "๐", " ", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", " ", "๐"],
["๐
", " ", "๐", "๐", " ", " ", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", "๐", "๐", " ", "๐", " ", "๐", "๐", "๐"],
["๐", " ", " ", " ", " ", "๐", " ", "๐", "๐", "๐"],
["๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐", "๐"],
];
*/
// COOKIES!!!!!
type test_maze12_left_actual = Move<test_maze11_left_actual, 'left'>;
/*
type MazeWin = [
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
["๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช", "๐ช"],
];
*/
Conclusion
As I mentioned at the beginning, this is the forth of the four challenges I liked the most from "Advent Of TypeScript 2023" by TypeHero. Check out the other challenges I liked here.