Skip to content

Instantly share code, notes, and snippets.

@d-imal
Created March 17, 2024 21:47
Show Gist options
  • Save d-imal/d789c9d6153b20791a2172df3771bb30 to your computer and use it in GitHub Desktop.
Save d-imal/d789c9d6153b20791a2172df3771bb30 to your computer and use it in GitHub Desktop.
Space Invaders
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