103 lines
3.2 KiB
TypeScript
103 lines
3.2 KiB
TypeScript
import { KeyEventType, Keyboard } from "./lib/controller";
|
|
import { TextureStore } from "./textureStore";
|
|
import { $, createCanvas } from "./lib/util";
|
|
import { expect, unwrap } from "./lib/option";
|
|
import { GameState } from "./gameState";
|
|
import { Sidebar } from "./sidebar";
|
|
import { KeyEvent } from "./keyEvent";
|
|
import {
|
|
PLAYFIELD_HEIGHT,
|
|
PLAYFIELD_WIDTH,
|
|
SIDEBAR_HEIGHT,
|
|
SIDEBAR_WIDTH,
|
|
} from "./constants";
|
|
|
|
// Throughout these source files, you will find methods like `expect` and `ifSome` as well as type aliases like `Option<T>`.
|
|
// These are a recreation of rust's (programming language) option type. They are found in lib/option.ts
|
|
|
|
// The game is made up of a few fundamental components
|
|
// - The TextureStore -- Loads textures over the network, holds the texture references, passed to all drawing methods
|
|
// - The Controller -- Listens to input events and allows for polling of events
|
|
// - The SideBar -- Contains the current score and the reference to the next Tetromino. Tetrominos are selected in the SideBar
|
|
// - The GameState -- Self explanatory; everything else
|
|
// The main function orchestrates all components
|
|
|
|
async function main(): Promise<void> {
|
|
const { canvas: playfieldCanvas, ctx: playfieldContext } = expect(
|
|
createCanvas(PLAYFIELD_WIDTH, PLAYFIELD_HEIGHT),
|
|
"Could not create playfield canvas"
|
|
);
|
|
expect(
|
|
$("pfcontainer"),
|
|
"Could not insert playfield canvas into DOM"
|
|
).appendChild(playfieldCanvas);
|
|
const { canvas: sidebarCanvas, ctx: sidebarContext } = expect(
|
|
createCanvas(SIDEBAR_WIDTH, SIDEBAR_HEIGHT),
|
|
"Could not create sidebar canvas"
|
|
);
|
|
expect(
|
|
$("sdcontainer"),
|
|
"Could not insert sidebar canvas into DOM"
|
|
).appendChild(sidebarCanvas);
|
|
|
|
const textures = await TextureStore.new();
|
|
const sidebar = new Sidebar();
|
|
const game = new GameState(sidebar);
|
|
const controller = new Keyboard<KeyEvent>();
|
|
controller.registerKeyEvent(["ArrowLeft", "a"], KeyEvent.Left);
|
|
controller.registerKeyEvent(["ArrowDown", "s"], KeyEvent.Down);
|
|
controller.registerKeyEvent(["ArrowRight", "d"], KeyEvent.Right);
|
|
controller.registerKeyEvent(
|
|
["ArrowUp", "w"],
|
|
KeyEvent.Up,
|
|
KeyEventType.OnlyOnce
|
|
);
|
|
|
|
// Main loop
|
|
function frame(): void {
|
|
const now = Date.now();
|
|
let gameReset = false;
|
|
|
|
controller.pollSpecificKey(KeyEvent.Up, () => game.rotate());
|
|
|
|
if (now - game.movementTimer > 80) {
|
|
game.movementTimer = now;
|
|
game.inputTick(controller.poll());
|
|
} else if (now - game.fallTimer > 500) {
|
|
game.fallTimer = now;
|
|
if (game.fallTick() === true) {
|
|
gameReset = true;
|
|
}
|
|
}
|
|
|
|
if (game.reDraw) {
|
|
game.drawPlayfield(playfieldContext, textures);
|
|
}
|
|
if (sidebar.reDraw) {
|
|
sidebar.draw(sidebarContext, textures);
|
|
}
|
|
if (gameReset) {
|
|
controller.clear();
|
|
const overlay = unwrap($("overlay"));
|
|
overlay.classList.remove("hidden");
|
|
const scoreCounter = unwrap($("scoreCounter"));
|
|
scoreCounter.textContent = `With a score of ${sidebar.score}`;
|
|
const tryAgain = unwrap($("tryAgain"));
|
|
tryAgain.addEventListener(
|
|
"click",
|
|
() => {
|
|
overlay.classList.add("hidden");
|
|
game.reset();
|
|
requestAnimationFrame(frame);
|
|
},
|
|
{ once: true }
|
|
);
|
|
} else {
|
|
requestAnimationFrame(frame);
|
|
}
|
|
}
|
|
requestAnimationFrame(frame);
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", main);
|