Created
March 17, 2024 21:47
-
-
Save dim882/d789c9d6153b20791a2172df3771bb30 to your computer and use it in GitHub Desktop.
Space Invaders
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
interface IPoint { | |
x: number; | |
y: number; | |
} | |
interface ISprite { | |
position: IPoint; | |
dimensions: { | |
width: number; | |
height: number; | |
}; | |
} | |
interface IMonsterBlockEdges { | |
left: number; | |
right: number; | |
} | |
type IMonsterDirection = 'left' | 'right'; | |
const MARGIN = 10; | |
const BaseMonster = { | |
width: 75, | |
height: 75, | |
}; | |
runGame(); | |
// Support Functions | |
function runGame() { | |
const canvas = document.getElementById('gameCanvas') as HTMLCanvasElement; | |
const context = canvas.getContext('2d'); | |
let monsters = createMonsterRow(canvas.width, MARGIN); | |
const player = createPlayer({ | |
x: canvas.width / 2, | |
y: canvas.height, | |
}); | |
setUpEventHandlers(player); | |
if (context) { | |
// Game loop values | |
let lastTime = 0; | |
const fps = 60; | |
const frameDuration = 1000 / fps; | |
let monsterDirection: IMonsterDirection = 'right'; | |
const gameLoop = (time: number) => { | |
const deltaTime = time - lastTime; | |
if (deltaTime >= frameDuration) { | |
lastTime = time; | |
const monsterBlockEdges = findMonsterBlockEdges(monsters); | |
monsterDirection = getMonsterDirection(monsterDirection, monsterBlockEdges, canvas.width, MARGIN); | |
monsters = moveMonsters(monsters, monsterDirection); | |
renderFrame(context, player, monsters); | |
} | |
requestAnimationFrame(gameLoop); | |
}; | |
requestAnimationFrame(gameLoop); | |
} | |
} | |
function renderFrame(context: CanvasRenderingContext2D, player: ISprite, monsters: ISprite[]) { | |
context.clearRect(0, 0, context.canvas.width, context.canvas.height); | |
drawPlayer(context, player); | |
monsters.forEach((monster) => { | |
drawMonster(context, monster); | |
}); | |
} | |
function setUpEventHandlers(player: ISprite) { | |
document.addEventListener('keydown', function (event) { | |
switch (event.key) { | |
case 'ArrowLeft': | |
player.position = { | |
...player.position, | |
x: player.position.x - 20, | |
}; | |
break; | |
case 'ArrowRight': | |
player.position = { | |
...player.position, | |
x: player.position.x + 20, | |
}; | |
break; | |
} | |
}); | |
} | |
function createPlayer(position: IPoint) { | |
return { | |
position, | |
dimensions: { | |
width: 100, | |
height: 100, | |
}, | |
}; | |
} | |
function createMonster(position: IPoint) { | |
return { | |
position, | |
dimensions: BaseMonster, | |
}; | |
} | |
function createMonsterRow(widthSpace: number, margin: number) { | |
let monsters = []; | |
let totalMonsters = 8; | |
for (let i = 1; monsters.length < totalMonsters; i++) { | |
const x = (BaseMonster.width + 10) * i; | |
monsters.push( | |
createMonster({ | |
x, | |
y: BaseMonster.height, | |
}) | |
); | |
} | |
return monsters; | |
} | |
function findMonstersLeftPosition(monsters: ISprite[]) { | |
return monsters.reduce((acc, monster) => { | |
return monster.position.x < acc ? monster.position.x : acc; | |
}, Infinity); | |
} | |
function findMonsterBlockEdges(monsters: ISprite[]): IMonsterBlockEdges { | |
const leftPosition = findMonstersLeftPosition(monsters); | |
return { | |
left: leftPosition, | |
right: leftPosition + findMonstersTotalWidth(monsters), | |
}; | |
} | |
function getMonsterDirection( | |
currentDirection: IMonsterDirection, | |
{ left, right }: IMonsterBlockEdges, | |
width: number, | |
margin: number | |
): IMonsterDirection { | |
if (left <= margin) { | |
return 'right'; | |
} | |
if (right >= width) { | |
return 'left'; | |
} | |
return currentDirection; | |
} | |
function findMonstersTotalWidth(monsters: ISprite[]) { | |
return monsters.reduce((acc, monster) => acc + monster.dimensions.width + 10, 0); | |
} | |
function moveMonsters(monsters: ISprite[], direction: IMonsterDirection) { | |
const offset = direction === 'right' ? 1 : -1; | |
return monsters.map((monster) => { | |
return { | |
...monster, | |
position: { | |
y: monster.position.y, | |
x: monster.position.x + offset, | |
}, | |
}; | |
}); | |
} | |
function drawPlayer(context: CanvasRenderingContext2D, sprite: ISprite) { | |
context.beginPath(); | |
context.arc(sprite.position.x, sprite.position.y, sprite.dimensions.width / 2, 0, Math.PI * 2, false); | |
context.fill(); | |
} | |
function drawMonster(context: CanvasRenderingContext2D, sprite: ISprite) { | |
const { position, dimensions } = sprite; | |
context.beginPath(); | |
context.rect(position.x, position.y, dimensions.width, dimensions.height); | |
context.fill(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment