Skip to content

Commit

Permalink
feat: create puzzle game with 3 levels
Browse files Browse the repository at this point in the history
  • Loading branch information
remarkablemark committed May 18, 2024
1 parent 332affa commit e6d16bb
Show file tree
Hide file tree
Showing 66 changed files with 298 additions and 83 deletions.
4 changes: 4 additions & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './pipes'
export * from './scenes'
export * from './tags'
export * from './tiles'
36 changes: 36 additions & 0 deletions src/constants/pipes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export enum EmptyPipe {
'═' = '═',
'║' = '║',
'╔' = '╔',
'╗' = '╗',
'╚' = '╚',
'╝' = '╝',
'╠' = '╠',
'╣' = '╣',
'╦' = '╦',
'╩' = '╩',
'╬' = '╬',
}

export enum FilledPipe {
'━' = '━',
'┃' = '┃',
'┏' = '┏',
'┓' = '┓',
'┗' = '┗',
'┛' = '┛',
'┣' = '┣',
'┫' = '┫',
'┳' = '┳',
'┻' = '┻',
'╋' = '╋',
}

export enum DirectionPipe {
'△' = '△',
'▷' = '▷',
'▽' = '▽',
'◁' = '◁',
}

export const pipes = [...Object.values(EmptyPipe), ...Object.values(FilledPipe)]
1 change: 1 addition & 0 deletions src/types/scene.ts → src/constants/scenes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export enum Scene {
game = 'game',
preload = 'preload',
}
3 changes: 3 additions & 0 deletions src/constants/tags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum Tag {
pipe = 'pipe',
}
1 change: 1 addition & 0 deletions src/constants/tiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const TILE_SIZE = 234
22 changes: 0 additions & 22 deletions src/events/cursors.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/events/index.ts

This file was deleted.

5 changes: 0 additions & 5 deletions src/gameobjects/enemy.ts

This file was deleted.

2 changes: 0 additions & 2 deletions src/gameobjects/index.ts

This file was deleted.

5 changes: 0 additions & 5 deletions src/gameobjects/player.ts

This file was deleted.

2 changes: 2 additions & 0 deletions src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './pipes'
export * from './solution'
59 changes: 59 additions & 0 deletions src/helpers/pipes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { GameObj } from 'kaboom'

import { EmptyPipe } from '../constants'

/**
* Rotates pipe 90 degrees.
*
* @param pipe - Pipe sprite.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function rotatePipe(pipe: GameObj<any>) {
switch (pipe.type) {
case EmptyPipe['═']:
pipe.type = EmptyPipe['║']
break

case EmptyPipe['║']:
pipe.type = EmptyPipe['═']
break

case EmptyPipe['╔']:
pipe.type = EmptyPipe['╗']
break

case EmptyPipe['╗']:
pipe.type = EmptyPipe['╝']
break

case EmptyPipe['╚']:
pipe.type = EmptyPipe['╔']
break

case EmptyPipe['╝']:
pipe.type = EmptyPipe['╚']
break

case EmptyPipe['╠']:
pipe.type = EmptyPipe['╦']
break

case EmptyPipe['╣']:
pipe.type = EmptyPipe['╩']
break

case EmptyPipe['╦']:
pipe.type = EmptyPipe['╣']
break

case EmptyPipe['╩']:
pipe.type = EmptyPipe['╠']
break
}

pipe.angle += 90

if (pipe.angle >= 360) {
pipe.angle -= 360
}
}
14 changes: 14 additions & 0 deletions src/helpers/solution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { GameObj } from 'kaboom'

import { Tag } from '../constants'

/**
* Checks if level has been solved.
*
* @returns - If all the pipes are in the correct position.
*/
export function checkSolution(level: GameObj<unknown>): boolean {
return level.get(Tag.pipe).reduce((accumulator, pipe) => {
return accumulator && pipe.type === pipe.solution
}, true)
}
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { start } from './scenes'

start()

// press F1
// debug.inspect = true;
42 changes: 42 additions & 0 deletions src/levels/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { TILE_SIZE } from '../constants'
import { levels } from './levels'
import { getTiles } from './tiles'

/**
* Gets level data.
*
* @param level - Level number.
* @returns - Level data.
*/
export function getLevel(level: number) {
const { map, scale } = levels[level]

const tileWidth = TILE_SIZE * scale
const tileHeight = TILE_SIZE * scale

const width = tileWidth * map[0].length
const height = tileHeight * map.length

const options = {
tileWidth,
tileHeight,
tiles: getTiles(scale),

pos: vec2(
center().x + tileWidth / 2 - width / 2,
center().y + tileHeight / 2 - height / 2,
),
}

return [map, options] as const
}

/**
* Checks if level exists.
*
* @param level - Level number.
* @returns - Whether level exists.
*/
export function hasLevel(level: number): boolean {
return Boolean(levels[level])
}
42 changes: 42 additions & 0 deletions src/levels/levels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// https://wikipedia.org/wiki/Box-drawing_characters
// ▲ ▼ ► ◄ ↑ ↓ → ← ⇦ ⇧ ⇨ ⇩
// △ ▷ ▽ ◁ ⬤ ◉ ◎
// ┓ ┗ ┏ ┛ ┣ ┫ ┳ ┻ ╋ ━ ┃
// ╔ ╗ ╚ ╝ ╠ ╣ ╦ ╩ ╬ ═ ║

export const levels = [
// 0
{
// prettier-ignore
map: [
'▷═╗ ',
' ║ ',
' ╚═◁',
],
scale: 1,
},

// 1
{
// prettier-ignore
map: [
'╔═╦═◁',
'╚═╬═╗',
'▷═╩═╝',
],
scale: 1,
},

// 2
{
// prettier-ignore
map: [
' ╔══╦══╗ ',
' ║╔═╬═╗║ ',
'▷╣╠═╬═╣╠◁',
' ║╚═╬═╝║ ',
' ╚══╩══╝ ',
],
scale: 0.8,
},
]
53 changes: 53 additions & 0 deletions src/levels/tiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { DirectionPipe, EmptyPipe, pipes, Tag } from '../constants'

/**
* Gets level map tile object.
*
* @param tileScale - Tile scale.
* @returns - Tile object.
*/
export function getTiles(tileScale: number) {
const tileComps = [anchor('center'), scale(tileScale)]

const staticComps = [...tileComps, color(135, 206, 235)]

return pipes.reduce(
(accumulator: Record<string, () => unknown[]>, value) => {
accumulator[value] = () => [
...tileComps,
sprite(value),
area(),
rotate(0),
{ type: value, solution: value },
Tag.pipe,
]

return accumulator
},
{
[DirectionPipe['△']]: () => [
...staticComps,
sprite(EmptyPipe['║']),
{ type: DirectionPipe['△'] },
],

[DirectionPipe['▷']]: () => [
...staticComps,
sprite(EmptyPipe['═']),
{ type: DirectionPipe['▷'] },
],

[DirectionPipe['▽']]: () => [
...staticComps,
sprite(EmptyPipe['║']),
{ type: DirectionPipe['▽'] },
],

[DirectionPipe['◁']]: () => [
...staticComps,
sprite(EmptyPipe['═']),
{ type: DirectionPipe['◁'] },
],
},
)
}
35 changes: 19 additions & 16 deletions src/scenes/game.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { addCursorKeys } from '../events'
import { addEnemy, addPlayer } from '../gameobjects'
import { Scene, Tag } from '../constants'
import { checkSolution, rotatePipe } from '../helpers'
import { getLevel, hasLevel } from '../levels'

scene('game', () => {
const player = addPlayer()

player.onUpdate(() => {
player.angle += 120 * dt()
})
scene(Scene.game, (levelNumber: number) => {
if (!hasLevel(levelNumber)) {
levelNumber = 0
}

addCursorKeys(player)
const level = addLevel(...getLevel(levelNumber))

onClick(() => addKaboom(mousePos()))
level.get(Tag.pipe).forEach((pipe) => {
Array(randi(4))
.fill(undefined)
.forEach(() => rotatePipe(pipe))
})

add([text('Press arrow keys', { width: width() / 2 }), pos(12, 12)])
onClick(Tag.pipe, (pipe) => {
rotatePipe(pipe)

for (let i = 0; i < 3; i++) {
const x = rand(0, width())
const y = rand(0, height())
addEnemy(x, y)
}
if (checkSolution(level)) {
go(Scene.game, levelNumber + 1)
}
})
})
10 changes: 8 additions & 2 deletions src/scenes/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
export * from './game'
export * from './start'
import './game'
import './preload'

import { Scene } from '../constants'

export function start() {
go(Scene.preload)
}
11 changes: 11 additions & 0 deletions src/scenes/preload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { pipes, Scene } from '../constants'

scene(Scene.preload, async () => {
await Promise.all(
pipes.map((value) => {
loadSprite(value, `sprites/pipes/${value}.png`)
}),
)

go(Scene.game, new URLSearchParams(location.search).get('level') || 0)
})
5 changes: 0 additions & 5 deletions src/scenes/start.ts

This file was deleted.

Binary file removed src/sounds/score.mp3
Binary file not shown.
Binary file removed src/sprites/apple.png
Binary file not shown.
Binary file removed src/sprites/bag.png
Binary file not shown.
Binary file removed src/sprites/bean.png
Binary file not shown.
6 changes: 0 additions & 6 deletions src/sprites/bean.ts

This file was deleted.

Binary file removed src/sprites/bobo.png
Binary file not shown.
Binary file removed src/sprites/boom.png
Binary file not shown.
Binary file removed src/sprites/btfly.png
Binary file not shown.
Binary file removed src/sprites/cloud.png
Binary file not shown.
Binary file removed src/sprites/coin.png
Binary file not shown.
Binary file removed src/sprites/cursor_default.png
Binary file not shown.
Binary file removed src/sprites/cursor_pointer.png
Binary file not shown.
Binary file removed src/sprites/dino.png
Binary file not shown.
Binary file removed src/sprites/door.png
Binary file not shown.
Binary file removed src/sprites/egg.png
Binary file not shown.
Binary file removed src/sprites/egg_crack.png
Binary file not shown.
Binary file removed src/sprites/ghosty.png
Binary file not shown.
Loading

0 comments on commit e6d16bb

Please sign in to comment.