Β·
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.
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:
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
:
"π"
, and Santa can't walk on cells occupied by them" "
(pay attention that an alley is composed by TWO spaces π€£)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 = [
["πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ"],
["πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ"],
["πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ"],
["πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ"],
["πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ"],
["πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ"],
["πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ"],
["πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ"],
["πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ"],
["πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ", "πͺ"],
];
*/
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.