Skip to content

Commit 41a7712

Browse files
authored
Merge pull request #5633 from hozlucas28/Solution-33-TypeScript
#33 - TypeScript
2 parents 962f148 + ba3bfe8 commit 41a7712

File tree

1 file changed

+300
-0
lines changed

1 file changed

+300
-0
lines changed
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
import readline from 'node:readline/promises'
2+
3+
/* -------------------------------------------------------------------------- */
4+
/* UTILITY TYPES */
5+
/* -------------------------------------------------------------------------- */
6+
7+
type ArrayOfN<
8+
T extends any,
9+
N extends number,
10+
Acc extends T[] = []
11+
> = Acc['length'] extends N ? Acc : ArrayOfN<T, N, [T, ...Acc]>
12+
13+
type NumberBetween<
14+
Min extends number,
15+
Max extends number,
16+
Counter extends 0[] = ArrayOfN<0, Min>,
17+
Acc extends number[] = []
18+
> = Counter['length'] extends Max
19+
? Acc[number]
20+
: NumberBetween<Min, Max, [0, ...Counter], [Counter['length'], ...Acc]>
21+
22+
/* -------------------------------------------------------------------------- */
23+
/* TYPES */
24+
/* -------------------------------------------------------------------------- */
25+
26+
type Cell = '⬜️' | '⬛️' | '🐭' | '🚪'
27+
28+
type Dashboard = ArrayOfN<ArrayOfN<Cell, 6>, 6>
29+
30+
interface Position {
31+
x: NumberBetween<0, 6> | (number & {})
32+
y: NumberBetween<0, 6> | (number & {})
33+
}
34+
35+
/* -------------------------------------------------------------------------- */
36+
/* ERRORS */
37+
/* -------------------------------------------------------------------------- */
38+
39+
class ExitNotFoundError extends Error {
40+
public constructor() {
41+
super('Exit not found')
42+
this.name = 'ExitNotFoundError'
43+
}
44+
}
45+
46+
class GameOverError extends Error {
47+
public constructor() {
48+
super('Game over')
49+
this.name = 'GameOverError'
50+
}
51+
}
52+
53+
class InvalidPlayerPositionError extends Error {
54+
public constructor(invalidPos: Position) {
55+
super(
56+
`Position (${invalidPos.x}, ${invalidPos.y}) is invalid for a player`
57+
)
58+
this.name = 'InvalidPlayerPositionError'
59+
}
60+
}
61+
62+
class PlayerNotFoundError extends Error {
63+
public constructor() {
64+
super('Player not found')
65+
this.name = 'PlayerNotFoundError'
66+
}
67+
}
68+
69+
/* -------------------------------------------------------------------------- */
70+
/* CLASSES */
71+
/* -------------------------------------------------------------------------- */
72+
73+
/* ---------------------------------- Maze ---------------------------------- */
74+
75+
interface InitialPositions {
76+
exitPos: Position
77+
playerPos: Position
78+
}
79+
80+
interface IMaze {
81+
getDashboard: () => Dashboard
82+
getExitPos: () => Position
83+
getPlayerPos: () => Position
84+
setPlayerPos: (newPos: Position) => this | never
85+
}
86+
87+
interface MazeConstructor {
88+
dashboard: Dashboard
89+
}
90+
91+
class Maze implements IMaze {
92+
private dashboard: Dashboard
93+
private exitPos: Position
94+
private playerPos: Position
95+
private originalCellAtPlayerPos: Cell
96+
97+
public constructor({dashboard}: MazeConstructor) {
98+
const {exitPos, playerPos} = this.getInitialPositions(dashboard)
99+
this.dashboard = dashboard
100+
this.exitPos = exitPos
101+
this.playerPos = playerPos
102+
this.originalCellAtPlayerPos = '⬜️'
103+
}
104+
105+
private getInitialPositions(
106+
dashboard: Dashboard
107+
): InitialPositions | never {
108+
let exitPos: Position = {x: -1, y: -1}
109+
let playerPos: Position = {x: -1, y: -1}
110+
111+
for (let y = 0; y < dashboard.length; y++) {
112+
const row = dashboard[y]
113+
for (let x = 0; x < row.length; x++) {
114+
const col = row[x]
115+
if (col === '🚪') exitPos = {x, y}
116+
else if (col === '🐭') playerPos = {x, y}
117+
}
118+
}
119+
120+
if (exitPos.x < 0) throw new ExitNotFoundError()
121+
if (playerPos.x < 0) throw new PlayerNotFoundError()
122+
123+
return {exitPos, playerPos}
124+
}
125+
126+
public getDashboard(): Dashboard {
127+
return this.dashboard
128+
}
129+
130+
public getExitPos(): Position {
131+
return this.exitPos
132+
}
133+
134+
public getPlayerPos(): Position {
135+
return this.playerPos
136+
}
137+
138+
public setPlayerPos(pos: Position): this | never {
139+
if (!this.isValidPosForPlayer(pos))
140+
throw new InvalidPlayerPositionError(pos)
141+
142+
const prevPlayerPos = this.playerPos
143+
const prevOriginalCellAtPlayerPos = this.originalCellAtPlayerPos
144+
145+
this.originalCellAtPlayerPos = this.dashboard[pos.y][pos.x]
146+
this.dashboard[pos.y][pos.x] = '🐭'
147+
this.dashboard[prevPlayerPos.y][prevPlayerPos.x] =
148+
prevOriginalCellAtPlayerPos
149+
this.playerPos = pos
150+
return this
151+
}
152+
153+
private isValidPos(pos: Position): boolean {
154+
const {x, y}: Position = pos
155+
const dashboard: Dashboard = this.dashboard
156+
157+
const dashboardMaxY: number = dashboard.length - 1
158+
const outOfYRange: boolean = y < 0 || y > dashboardMaxY
159+
if (outOfYRange) return false
160+
161+
const dashboardMaxX: number = dashboard[0].length - 1
162+
const outOfXRange: boolean = x < 0 || x > dashboardMaxX
163+
if (outOfXRange) return false
164+
165+
return true
166+
}
167+
168+
private isValidPosForPlayer(pos: Position): boolean {
169+
if (!this.isValidPos(pos)) return false
170+
171+
const {x, y}: Position = pos
172+
const posAtDashboard: Cell = this.dashboard[y][x]
173+
174+
const obstacleAtPos: boolean = posAtDashboard === '⬛️'
175+
if (obstacleAtPos) return false
176+
177+
return true
178+
}
179+
}
180+
181+
/* ---------------------------------- Game ---------------------------------- */
182+
183+
type Move = 'down' | 'left' | 'right' | 'up'
184+
185+
interface IGame {
186+
getMaze: () => IMaze
187+
movePlayer: (move: Move) => this | never
188+
isGameOver: () => boolean
189+
}
190+
191+
interface GameConstructor {
192+
maze: IMaze
193+
}
194+
195+
class Game implements IGame {
196+
private maze: IMaze
197+
198+
public constructor({maze}: GameConstructor) {
199+
this.maze = maze
200+
}
201+
202+
public getMaze(): IMaze {
203+
return this.maze
204+
}
205+
206+
public movePlayer(move: Move): this | never {
207+
if (this.isGameOver()) throw new GameOverError()
208+
209+
const maze: IMaze = this.getMaze()
210+
const {x, y}: Position = maze.getPlayerPos()
211+
212+
const moveActions: Record<Move, () => void> = {
213+
down: () => maze.setPlayerPos({x, y: y + 1}),
214+
left: () => maze.setPlayerPos({x: x - 1, y}),
215+
right: () => maze.setPlayerPos({x: x + 1, y}),
216+
up: () => maze.setPlayerPos({x, y: y - 1}),
217+
}
218+
219+
moveActions[move]()
220+
221+
return this
222+
}
223+
224+
public isGameOver(): boolean {
225+
const maze: IMaze = this.getMaze()
226+
const exitPos = maze.getExitPos()
227+
const playerPos = maze.getPlayerPos()
228+
229+
const playerAtExitPos: boolean =
230+
playerPos.x === exitPos.x && playerPos.y === exitPos.y
231+
232+
return playerAtExitPos
233+
}
234+
}
235+
236+
/* -------------------------------------------------------------------------- */
237+
/* MAIN */
238+
/* -------------------------------------------------------------------------- */
239+
240+
;(async () => {
241+
const maze: Maze = new Maze({
242+
dashboard: [
243+
['🚪', '⬛️', '⬛️', '⬛️', '⬛️', '⬛️'],
244+
['⬜️', '⬛️', '⬜️', '⬜️', '⬜️', '⬛️'],
245+
['⬜️', '⬛️', '⬜️', '⬛️', '⬜️', '⬜️'],
246+
['⬜️', '⬜️', '⬜️', '⬛️', '⬜️', '🐭'],
247+
['⬛️', '⬛️', '⬜️', '⬛️', '⬜️', '⬛️'],
248+
['⬛️', '⬛️', '⬜️', '⬜️', '⬜️', '⬛️'],
249+
],
250+
})
251+
252+
const game: Game = new Game({maze})
253+
console.dir(game.getMaze().getDashboard())
254+
255+
const rl = readline.createInterface({
256+
input: process.stdin,
257+
output: process.stdout,
258+
})
259+
260+
while (!game.isGameOver()) {
261+
const move = await rl.question(
262+
"\n> Enter a move ('up', 'right', 'left', or 'down'): "
263+
)
264+
265+
try {
266+
console.log()
267+
268+
switch (move.trim().toUpperCase() as Uppercase<Move>) {
269+
case 'DOWN':
270+
game.movePlayer('down')
271+
console.dir(game.getMaze().getDashboard())
272+
break
273+
274+
case 'LEFT':
275+
game.movePlayer('left')
276+
console.dir(game.getMaze().getDashboard())
277+
break
278+
279+
case 'RIGHT':
280+
game.movePlayer('right')
281+
console.dir(game.getMaze().getDashboard())
282+
break
283+
284+
case 'UP':
285+
game.movePlayer('up')
286+
console.dir(game.getMaze().getDashboard())
287+
break
288+
289+
default:
290+
console.log('> Invalid move! Try again...')
291+
}
292+
} catch (error) {
293+
if (error instanceof InvalidPlayerPositionError)
294+
console.log('> Player can not move down!')
295+
}
296+
}
297+
rl.close()
298+
299+
console.log('\n> Game over!')
300+
})()

0 commit comments

Comments
 (0)