diff --git a/src/colors.ts b/src/colors.ts index 3a58941..27452e8 100644 --- a/src/colors.ts +++ b/src/colors.ts @@ -1 +1,5 @@ -export const BG_OUTER = "#000"; \ No newline at end of file +import {Color} from "./engine/datatypes.ts"; + +export const BG_OUTER = Color.parseHexCode("#200500"); +export const FG_TEXT = Color.parseHexCode("#c0c0c0") +export const FG_BOLD = Color.parseHexCode("#ffffff") diff --git a/src/counter.ts b/src/counter.ts deleted file mode 100644 index 09e5afd..0000000 --- a/src/counter.ts +++ /dev/null @@ -1,9 +0,0 @@ -export function setupCounter(element: HTMLButtonElement) { - let counter = 0 - const setCounter = (count: number) => { - counter = count - element.innerHTML = `count is ${counter}` - } - element.addEventListener('click', () => setCounter(counter + 1)) - setCounter(0) -} diff --git a/src/engine/datatypes.ts b/src/engine/datatypes.ts new file mode 100644 index 0000000..d138db5 --- /dev/null +++ b/src/engine/datatypes.ts @@ -0,0 +1,147 @@ +export interface IGame { + update(): void; + draw(): void; +} + +export class Color { + readonly r: number; + readonly g: number; + readonly b: number; + readonly a: number; + + constructor(r: number, g: number, b: number, a?: number) { + this.r = r; + this.g = g; + this.b = b; + this.a = a ?? 255; + } + + static parseHexCode(hexCode: string) { + const regex1 = /#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})?/; + const regex2 = /#([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})?/; + let result = regex1.exec(hexCode) ?? regex2.exec(hexCode); + if (result == null) { + throw `could not parse color: ${hexCode}` + } + + let parseGroup = (s: string | undefined): number => { + if (s === undefined) { + return 255; + } + if (s.length == 1) { + return 17 * parseInt(s, 16); + } + return parseInt(s, 16); + } + return new Color( + parseGroup(result[1]), + parseGroup(result[2]), + parseGroup(result[3]), + parseGroup(result[4]), + ); + } + + toStyle(): string { + return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a / 255.0})` + } +} + +export class Point { + readonly x: number; + readonly y: number; + + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } + + offset(other: Point | Size): Point { + if (other instanceof Point) { + return new Point(this.x + other.x, this.y + other.y); + } + + return new Point(this.x + other.w, this.y + other.h); + } + + negate() { + return new Point(-this.x, -this.y); + } +} + +export class Size { + readonly w: number; + readonly h: number; + + constructor(w: number, h: number) { + this.w = w; + this.h = h; + } +} + +export class Grid { + data: T[][]; + + constructor({size, cbDefault}: {size: Size, cbDefault: (xy: Point) => T}) { + this.data = []; + + for (let y = 0; y < size.h; y++) { + let row = []; + for (let x = 0; x < size.w; x++) { + row.push(cbDefault(new Point(x, y))) + } + this.data.push(row); + } + } + + static createGridFromStringArray(ary: Array): Grid { + let w = 0; + let h = ary.length; + for (let i = 0; i < h - 1; i++) { + let w1 = ary[i].length; + let w2 = ary[i + 1].length; + if (w1 != w2) { + throw `createGridFromStringArray: must be grid-shaped, got ${ary}` + } + w = w1; + } + + return new Grid({ + size: new Size(w, h), + cbDefault: (xy) => { + return ary[xy.y].charAt(xy.x); + } + }) + } + + static createGridFromJaggedArray(ary: Array>): Grid { + let w = 0; + let h = ary.length; + for (let i = 0; i < h - 1; i++) { + let w1 = ary[i].length; + let w2 = ary[i + 1].length; + if (w1 != w2) { + throw `createGridFromJaggedArray: must be grid-shaped, got ${ary}` + } + w = w1; + } + + return new Grid({ + size: new Size(w, h), + cbDefault: (xy) => { + return ary[xy.y][xy.x]; + } + }) + } +} + +export enum AlignX { + Left = 0, + Center = 1, + Right = 2 +} + +export enum AlignY { + Top = 0, + Middle = 1, + Right = 2, +} diff --git a/src/engine/internal/abstract.ts b/src/engine/internal/abstract.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/assets.ts b/src/engine/internal/assets.ts similarity index 100% rename from src/assets.ts rename to src/engine/internal/assets.ts diff --git a/src/clock.ts b/src/engine/internal/clock.ts similarity index 100% rename from src/clock.ts rename to src/engine/internal/clock.ts diff --git a/src/engine/internal/drawing.ts b/src/engine/internal/drawing.ts new file mode 100644 index 0000000..cd0b769 --- /dev/null +++ b/src/engine/internal/drawing.ts @@ -0,0 +1,88 @@ +import {getScreen} from "./screen.ts"; +import {AlignX, AlignY, Color, Point, Size} from "../datatypes.ts"; +import {mainFont} from "./font.ts"; +import {Sprite} from "./sprite.ts"; + +class Drawing { + camera: Point; + + constructor() { + this.camera = new Point(0, 0); + } + + get size() { return getScreen().size; } + + invertRect(position: Point, size: Size) { + position = this.camera.negate().offset(position); + + let ctx = getScreen().unsafeMakeContext(); + ctx.globalCompositeOperation = "difference"; + ctx.fillStyle = "#fff"; + ctx.fillRect( + Math.floor(position.x), + Math.floor(position.y), + Math.floor(size.w), + Math.floor(size.h) + ) + } + + fillRect(position: Point, size: Size, color: Color) { + position = this.camera.negate().offset(position); + + let ctx = getScreen().unsafeMakeContext(); + ctx.fillStyle = color.toStyle(); + ctx.fillRect( + Math.floor(position.x), + Math.floor(position.y), + Math.floor(size.w), + Math.floor(size.h) + ); + } + + drawRect(position: Point, size: Size, color: Color) { + position = this.camera.negate().offset(position); + + let ctx = getScreen().unsafeMakeContext(); + ctx.strokeStyle = color.toStyle(); + ctx.strokeRect( + Math.floor(position.x) + 0.5, + Math.floor(position.y) + 0.5, + Math.floor(size.w), + Math.floor(size.h) + ) + } + + drawText(text: string, position: Point, color: Color, options?: {alignX?: AlignX, alignY?: AlignY, forceWidth?: number}) { + position = this.camera.negate().offset(position); + + let ctx = getScreen().unsafeMakeContext(); + mainFont.internalDrawText({ + ctx, + text, + position, + alignX: options?.alignX, + alignY: options?.alignY, + forceWidth: options?.forceWidth, + color + }) + } + + measureText(text: string, forceWidth?: number): Size { + return mainFont.measureText({text, forceWidth}) + } + + drawSprite(sprite: Sprite, position: Point, ix?: number, options?: {xScale?: number, yScale: number, angle: number}) { + position = this.camera.negate().offset(position); + + let ctx = getScreen().unsafeMakeContext(); + sprite.internalDraw(ctx, {position, ix, xScale: options?.xScale, yScale: options?.yScale, angle: options?.angle}) + } +} + +let active: Drawing = new Drawing(); + +export function getDrawing(): Drawing { + return active; +} + + diff --git a/src/font.ts b/src/engine/internal/font.ts similarity index 78% rename from src/font.ts rename to src/engine/internal/font.ts index fc9ca87..190207b 100644 --- a/src/font.ts +++ b/src/engine/internal/font.ts @@ -1,37 +1,27 @@ import {getAssets} from "./assets.ts"; -import fontSheet from './art/fonts/vga_8x16.png'; - -export enum AlignX { - Left = 0, - Center = 1, - Right = 2 -} - -export enum AlignY { - Top = 0, - Middle = 1, - Right = 2, -} +import fontSheet from '../../art/fonts/vga_8x16.png'; +import {AlignX, AlignY, Color, Point, Size} from "../datatypes.ts"; class Font { #filename: string; - #cx: number; - #cy: number; - #px: number; - #py: number; + #cellsPerSheet: Size; + #pixelsPerCell: Size; #tintingCanvas: HTMLCanvasElement; #tintedVersions: Record; - constructor(filename: string, cx: number, cy: number, px: number, py: number) { + constructor(filename: string, cellsPerSheet: Size, pixelsPerCell: Size) { this.#filename = filename; - this.#cx = cx; - this.#cy = cy; - this.#px = px; - this.#py = py; + this.#cellsPerSheet = cellsPerSheet; + this.#pixelsPerCell = pixelsPerCell; this.#tintingCanvas = document.createElement("canvas"); this.#tintedVersions = {} } + get #cx(): number { return this.#cellsPerSheet.w } + get #cy(): number { return this.#cellsPerSheet.h } + get #px(): number { return this.#pixelsPerCell.w } + get #py(): number { return this.#pixelsPerCell.h } + #getTintedImage(color: string): HTMLImageElement | null { let image = getAssets().getImage(this.#filename); @@ -65,25 +55,24 @@ class Font { return result; } - drawText({ctx, text, x, y, alignX, alignY, forceWidth, color}: { + internalDrawText({ctx, text, position, alignX, alignY, forceWidth, color}: { ctx: CanvasRenderingContext2D, text: string, - x: number, y: number, alignX?: AlignX, alignY?: AlignY, - forceWidth?: number, color?: string + position: Point, alignX?: AlignX, alignY?: AlignY, + forceWidth?: number, color: Color }) { alignX = alignX == undefined ? AlignX.Left : alignX; alignY = alignY == undefined ? AlignY.Top : alignY; forceWidth = forceWidth == undefined ? 65535 : forceWidth; - color = color == undefined ? "#ffffff" : color; - let image = this.#getTintedImage(color) + let image = this.#getTintedImage(color.toStyle()) if (image == null) { return; } let sz = this.#glyphwise(text, forceWidth, () => {}); - let offsetX = x; - let offsetY = y; + let offsetX = position.x; + let offsetY = position.y; offsetX += (alignX == AlignX.Left ? 0 : alignX == AlignX.Center ? -sz.w / 2 : - sz.w) offsetY += (alignY == AlignY.Top ? 0 : alignY == AlignY.Middle ? -sz.h / 2 : - sz.h) @@ -105,7 +94,7 @@ class Font { ); } - measureText({text, forceWidth}: {text: string, forceWidth?: number}): {w: number, h: number} { + measureText({text, forceWidth}: {text: string, forceWidth?: number}): Size { return this.#glyphwise(text, forceWidth, () => {}); } @@ -141,4 +130,4 @@ class Font { } } -export let mainFont = new Font(fontSheet, 32, 8, 8, 16); \ No newline at end of file +export let mainFont = new Font(fontSheet, new Size(32, 8), new Size(8, 16)); \ No newline at end of file diff --git a/src/engine/internal/host.ts b/src/engine/internal/host.ts new file mode 100644 index 0000000..ba2b241 --- /dev/null +++ b/src/engine/internal/host.ts @@ -0,0 +1,34 @@ +import './style.css' + +import {pollAndTouch} from "./screen.ts"; +import {getClock} from "./clock.ts"; +import {getInput, setupInput} from "./input.ts"; +import {IGame} from "../datatypes.ts"; + +export function hostGame(game: IGame) { + let gameCanvas = document.getElementById("game") as HTMLCanvasElement; + setupInput(gameCanvas); + onFrame(game, undefined); // start on-frame draw loop, set up screen +} + +function onFrame(game: IGame, timestamp: number | undefined) { + let gameCanvas = document.getElementById("game") as HTMLCanvasElement; + requestAnimationFrame((timestamp: number) => onFrame(game, timestamp)); + + if (timestamp) { + getClock().recordTimestamp(timestamp); + } + onFrameFixScreen(gameCanvas); + + while (getClock().popUpdate()) { + game.update(); + getInput().update(); + } + + game.draw(); +} + +function onFrameFixScreen(canvas: HTMLCanvasElement) { + pollAndTouch(canvas); +} + diff --git a/src/engine/internal/input.ts b/src/engine/internal/input.ts new file mode 100644 index 0000000..22de0c5 --- /dev/null +++ b/src/engine/internal/input.ts @@ -0,0 +1,120 @@ +import {getScreen} from "./screen.ts"; +import {Point} from "../datatypes.ts"; + +function handleKey(e: KeyboardEvent, down: boolean) { + active.handleKeyDown(e.key, down); +} +function handleMouseOut() { + active.handleMouseMove(-1, -1); +} + +function handleMouseMove(canvas: HTMLCanvasElement, m: MouseEvent) { + if (canvas.offsetWidth == 0 || canvas.offsetHeight == 0) { + return; + } + active.handleMouseMove(m.offsetX / canvas.offsetWidth, m.offsetY / canvas.offsetHeight); +} + +function handleMouseButton(canvas: HTMLCanvasElement, m: MouseEvent, down: boolean) { + if (canvas.offsetWidth == 0 || canvas.offsetHeight == 0) { + return; + } + active.handleMouseMove(m.offsetX / canvas.offsetWidth, m.offsetY / canvas.offsetHeight); + let button: MouseButton | null = ( + m.button == 0 ? "leftMouse" : + m.button == 1 ? "rightMouse" : + null + ) + if (button != null) { + active.handleMouseDown(button, down); + } +} + + +export function setupInput(canvas: HTMLCanvasElement) { + canvas.addEventListener("keyup", (k) => handleKey(k, false)); + document.addEventListener("keyup", (k) => handleKey(k, false)); + canvas.addEventListener("keydown", (k) => handleKey(k, true)); + document.addEventListener("keydown", (k) => handleKey(k, true)); + canvas.addEventListener("mouseout", (_) => handleMouseOut()); + canvas.addEventListener("mousemove", (m) => handleMouseMove(canvas, m)); + canvas.addEventListener("mousedown", (m) => handleMouseButton(canvas, m, true)); + canvas.addEventListener("mouseup", (m) => handleMouseButton(canvas, m, false)); +} + +export type MouseButton = "leftMouse" | "rightMouse"; + +class Input { + #keyDown: Record; + #previousKeyDown: Record; + #mouseDown: Record; + #previousMouseDown: Record; + #mousePosition: Point | null; + + constructor() { + this.#keyDown = {}; + this.#previousKeyDown = {}; + this.#mouseDown = {}; + this.#previousMouseDown = {}; + this.#mousePosition = null; + } + + update() { + this.#previousKeyDown = {...this.#keyDown}; + this.#previousMouseDown = {...this.#mouseDown}; + } + + handleMouseDown(name: string, down: boolean) { + this.#mouseDown[name] = down; + } + handleKeyDown(name: string, down: boolean) { + this.#keyDown[name] = down; + } + + handleMouseMove(x: number, y: number) { + let screen = getScreen(); + if (x < 0.0 || x >= 1.0) { this.#mousePosition = null; } + if (y < 0.0 || y >= 1.0) { this.#mousePosition = null; } + + let w = screen.size.w; + let h = screen.size.h; + this.#mousePosition = new Point( + Math.floor(x * w), + Math.floor(y * h), + ) + } + + isMouseDown(btn: MouseButton) : boolean { + return this.#mouseDown[btn]; + } + + isMouseClicked(btn: MouseButton) : boolean { + return this.#mouseDown[btn] && !this.#previousMouseDown[btn]; + } + + isMouseReleased(btn: MouseButton) : boolean { + return !this.#mouseDown[btn] && this.#previousMouseDown[btn]; + } + + get mousePosition(): Point | null { + return this.#mousePosition + } + + isKeyDown(key: string) : boolean { + return this.#keyDown[key]; + } + + isKeyPressed(key: string) : boolean { + return this.#keyDown[key] && !this.#previousKeyDown[key]; + } + + isKeyReleased(key: string) : boolean { + return !this.#keyDown[key] && this.#previousKeyDown[key]; + } +} + +let active = new Input(); + +export function getInput(): Input { + return active; +} \ No newline at end of file diff --git a/src/screen.ts b/src/engine/internal/screen.ts similarity index 79% rename from src/screen.ts rename to src/engine/internal/screen.ts index 936cdd3..8fd340a 100644 --- a/src/screen.ts +++ b/src/engine/internal/screen.ts @@ -1,15 +1,17 @@ +import {Size} from "../datatypes.ts"; + +// TODO: Just switch to the same pattern as everywhere else +// (without repeatedly reassigning the variable) class Screen { #canvas: HTMLCanvasElement - w: number - h: number + size: Size - constructor(canvas: HTMLCanvasElement, w: number, h: number) { + constructor(canvas: HTMLCanvasElement, size: Size) { this.#canvas = canvas; - this.w = w; - this.h = h; + this.size = size } - makeContext(): CanvasRenderingContext2D { + unsafeMakeContext(): CanvasRenderingContext2D { let ctx = this.#canvas.getContext("2d")!; // TODO: Other stuff to do here? @@ -51,7 +53,7 @@ export function pollAndTouch(canvas: HTMLCanvasElement) { realHeight = Math.floor(canvas.offsetHeight / divisors[div]); canvas.width = realWidth; canvas.height = realHeight; - active = new Screen(canvas, realWidth, realHeight); + active = new Screen(canvas, new Size(realWidth, realHeight)); } export function getScreen(): Screen { diff --git a/src/engine/internal/sprite.ts b/src/engine/internal/sprite.ts new file mode 100644 index 0000000..1f72672 --- /dev/null +++ b/src/engine/internal/sprite.ts @@ -0,0 +1,47 @@ +import {getAssets} from "./assets.ts"; +import {Point, Size} from "../datatypes.ts"; + + +export class Sprite { + readonly imageSet: string; + // spritesheet params + readonly pixelsPerSubimage: Size; + readonly origin: Point; + readonly cellsPerSheet: Size; + // number of frames + readonly nFrames: number; + + constructor(imageSet: string, pixelsPerSubimage: Size, origin: Point, cellsPerSheet: Size, nFrames: number) { + this.imageSet = imageSet; + this.pixelsPerSubimage = pixelsPerSubimage; + this.origin = origin; + this.cellsPerSheet = cellsPerSheet; + this.nFrames = nFrames; + + let nPossibleFrames = this.cellsPerSheet.w * this.cellsPerSheet.h; + if (this.nFrames > nPossibleFrames) { + throw `can't have ${this.nFrames} with a spritesheet dimension of ${nPossibleFrames}`; + } + } + + internalDraw(ctx: CanvasRenderingContext2D, {position, ix, xScale, yScale, angle}: {position: Point, ix?: number, xScale?: number, yScale?: number, angle?: number}) { + ix = ix == undefined ? 0 : ix; + xScale = xScale == undefined ? 1.0 : xScale; + yScale = yScale == undefined ? 1.0 : yScale; + angle = angle == undefined ? 0.0 : angle; + + // ctx.translate(Math.floor(x), Math.floor(y)); + ctx.translate(position.x, position.y); + ctx.rotate(angle * Math.PI / 180); + ctx.scale(xScale, yScale); + ctx.translate(-this.origin.x, -this.origin.y); + + let me = getAssets().getImage(this.imageSet); + let srcCx = ix % this.cellsPerSheet.w; + let srcCy = Math.floor(ix / this.cellsPerSheet.w); + let srcPx = srcCx * this.pixelsPerSubimage.w; + let srcPy = srcCy * this.pixelsPerSubimage.h; + console.log(`src px and py ${srcPx} ${srcPy}`) + ctx.drawImage(me, srcPx, srcPy, this.pixelsPerSubimage.w, this.pixelsPerSubimage.h, 0, 0, this.pixelsPerSubimage.w, this.pixelsPerSubimage.h); + } +} diff --git a/src/style.css b/src/engine/internal/style.css similarity index 100% rename from src/style.css rename to src/engine/internal/style.css diff --git a/src/engine/public.ts b/src/engine/public.ts new file mode 100644 index 0000000..94f412e --- /dev/null +++ b/src/engine/public.ts @@ -0,0 +1,8 @@ +import {getInput} from "./internal/input.ts"; +import {getDrawing} from "./internal/drawing.ts"; + +// input reexports +export let I = getInput(); + +// drawing reexports +export let D = getDrawing(); diff --git a/src/game.ts b/src/game.ts index df6815e..ed0e850 100644 --- a/src/game.ts +++ b/src/game.ts @@ -1,9 +1,8 @@ -import {desiredHeight, desiredWidth, getScreen} from "./screen.ts"; -import {BG_OUTER} from "./colors.ts"; -import {getInput} from "./input.ts"; +import {desiredHeight, desiredWidth, getScreen} from "./engine/internal/screen.ts"; +import {BG_OUTER, FG_TEXT} from "./colors.ts"; import {checkGrid, ConceptualCell, maps, mapSzX, mapSzY} from "./maps.ts"; - -type Point = {x: number, y: number} +import {D, I} from "./engine/public.ts"; +import {IGame, Point, Size} from "./engine/datatypes.ts"; class MenuCamera { // measured in whole screens @@ -20,33 +19,35 @@ class MenuCamera { if (Math.abs(x1 - x0) < 0.01) { return x1; } return (x0 * 8 + x1 * 2) / 10; } - this.position.x = adjust(this.position.x, this.target.x); - this.position.y = adjust(this.position.y, this.target.y); + this.position = new Point( + adjust(this.position.x, this.target.x), + adjust(this.position.y, this.target.y), + ); } } type GameState = "Gameplay" | "Thralls"; -function getScreenLocation(state: GameState): {x: number, y: number} { +function getScreenLocation(state: GameState): Point { if (state === "Gameplay") { - return {x: 0.0, y: 0.0} + return new Point(0, 0); } if (state === "Thralls") { - return {x: 0.0, y: 1.0} + return new Point(0, 1); } throw `invalid state: ${state}` } -export class Game { +export class Game implements IGame { camera: MenuCamera; state: GameState; huntMode: HuntMode; constructor() { this.camera = new MenuCamera({ - position: {x: 0.0, y: 0.0}, - target: {x: 0.0, y: 0.0} + position: new Point(0, 0), + target: new Point(0, 0), }); this.state = "Gameplay"; @@ -54,14 +55,18 @@ export class Game { } update() { - if (getInput().isPressed("w")) { + if (I.isKeyPressed("w")) { this.state = "Gameplay" } - if (getInput().isPressed("s")) { + if (I.isKeyPressed("s")) { this.state = "Thralls" } this.camera.target = getScreenLocation(this.state); + D.camera = new Point( + D.size.w * this.camera.position.x, + D.size.h * this.camera.position.y, + ) this.camera.update(); // state-specific updates @@ -69,65 +74,25 @@ export class Game { } draw() { - let screen = getScreen(); - let ctx = screen.makeContext(); - // draw screen background - ctx.fillStyle = BG_OUTER; - ctx.fillRect(0, 0, screen.w, screen.h); + let oldCamera = D.camera; + D.camera = new Point(0, 0); + D.fillRect(new Point(0, 0), D.size, BG_OUTER); + D.camera = oldCamera; this.drawGameplay(); // we draw all states at once and pan between them // mainFont.drawText({ctx: ctx, text: "You have been given a gift.", x: 0, y: 0}) - let xy = this.getCameraMouseXy(); - if (xy != null) { - ctx = this.makeCameraContext(); - ctx.globalCompositeOperation = "difference"; - if (getInput().isDown("leftMouse")) { - ctx.fillStyle = "#ff0"; - } else { - ctx.fillStyle = "#fff"; - } - ctx.fillRect(xy.x - 1, xy.y - 1, 3, 3); - ctx.fillRect(xy.x, xy.y - 1, 1, 3); - ctx.fillRect(xy.x - 1, xy.y, 3, 1); + let mouse = I.mousePosition?.offset(D.camera); + if (mouse != null) { + D.invertRect(mouse.offset(new Point(-1, -1)), new Size(3, 3)) } } - getCameraOffset(): Point { - let screen = getScreen(); - let {w, h} = screen; - return { - x: Math.floor(w * this.camera.position.x), - y: Math.floor(h * this.camera.position.y) - }; - } - - getCameraMouseXy(): Point | null { - let xy = getInput().mouseXy(); - if (xy == null) { - return null; - } - let {x: dx, y: dy} = this.getCameraOffset(); - return { - x: xy.x + dx, - y: xy.y + dy, - }; - } - - makeCameraContext() { - let screen = getScreen(); - let ctx = screen.makeContext(); - let {x, y} = this.getCameraOffset(); - - ctx.translate(-x, -y); - return ctx; - } - getPaneRegionForGameState(gameState: GameState) { let screen = getScreen(); - let {w, h} = screen; + let {w, h} = screen.size; let overallScreenLocation = getScreenLocation(gameState); let bigPaneX = overallScreenLocation.x * w; @@ -142,12 +107,12 @@ export class Game { return { big: { - position: {x: bigPaneX, y: bigPaneY}, - size: {x: bigPaneW, y: bigPaneH} + position: new Point(bigPaneX, bigPaneY), + size: new Size(bigPaneW, bigPaneH), }, small: { - position: {x: smallPaneX, y: smallPaneY}, - size: {x: smallPaneW, y: smallPaneH} + position: new Point(smallPaneX, smallPaneY), + size: new Size(smallPaneW, smallPaneH), } } @@ -159,10 +124,9 @@ export class Game { drawGameplay() { let region = this.getPaneRegionForGameState("Gameplay") - let ctx = this.makeCameraContext() - ctx.translate(region.small.position.x, region.small.position.y) - + // TODO: Draw + D.drawText("hello", region.small.position, FG_TEXT); } } @@ -243,9 +207,6 @@ class HuntMode { gsp, gsp, gsp, gsp ])(); } - - get(at: Point) { - } } function choose(array: Array): T { diff --git a/src/input.ts b/src/input.ts deleted file mode 100644 index bacf65b..0000000 --- a/src/input.ts +++ /dev/null @@ -1,108 +0,0 @@ -import {getScreen} from "./screen.ts"; - -function handleKey(e: KeyboardEvent, down: boolean) { - active.handleDown(e.key, down); -} -function handleMouseOut() { - active.handleMouseMove(-1, -1); -} - -function handleMouseMove(canvas: HTMLCanvasElement, m: MouseEvent) { - if (canvas.offsetWidth == 0 || canvas.offsetHeight == 0) { - return; - } - active.handleMouseMove(m.offsetX / canvas.offsetWidth, m.offsetY / canvas.offsetHeight); -} - -function handleMouseButton(canvas: HTMLCanvasElement, m: MouseEvent, down: boolean) { - if (canvas.offsetWidth == 0 || canvas.offsetHeight == 0) { - return; - } - active.handleMouseMove(m.offsetX / canvas.offsetWidth, m.offsetY / canvas.offsetHeight); - let button: KnownButton | null = ( - m.button == 0 ? "leftMouse" : - m.button == 1 ? "rightMouse" : - null - ) - if (button != null) { - active.handleDown(button, down); - } -} - - -export function setupInput(canvas: HTMLCanvasElement) { - canvas.addEventListener("keyup", (k) => handleKey(k, false)); - document.addEventListener("keyup", (k) => handleKey(k, false)); - canvas.addEventListener("keydown", (k) => handleKey(k, true)); - document.addEventListener("keydown", (k) => handleKey(k, true)); - canvas.addEventListener("mouseout", (_) => handleMouseOut()); - canvas.addEventListener("mousemove", (m) => handleMouseMove(canvas, m)); - canvas.addEventListener("mousedown", (m) => handleMouseButton(canvas, m, true)); - canvas.addEventListener("mouseup", (m) => handleMouseButton(canvas, m, false)); -} - -type KnownKey = "w" | "a" | "s" | "d"; -type KnownButton = "leftMouse" | "rightMouse"; - -class Input { - #down: Record; - #previousDown: Record; - #mouseXy: {x: number, y: number} | null; - - constructor() { - this.#down = {}; - this.#previousDown = {}; - this.#mouseXy = null; - } - - update() { - this.#previousDown = {...this.#down}; - } - - handleDown(name: string, down: boolean) { - if (down) { - this.#down[name] = down; - } - else { - delete this.#down[name]; - } - } - - handleMouseMove(x: number, y: number) { - let screen = getScreen(); - if (x < 0.0 || x >= 1.0) { this.#mouseXy = null; } - if (y < 0.0 || y >= 1.0) { this.#mouseXy = null; } - - let w = screen.w; - let h = screen.h; - this.#mouseXy = { - x: Math.floor(x * w), - y: Math.floor(y * h), - } - } - - isDown(key: KnownKey | KnownButton) : boolean { - return this.#down[key]; - } - - isPressed(key: KnownKey | KnownButton) : boolean { - return this.#down[key] && !this.#previousDown[key]; - } - - isReleased(key: KnownKey | KnownButton) : boolean { - return !this.#down[key] && this.#previousDown[key]; - } - - mouseXy(): {x: number, y: number} | null { - if (this.#mouseXy == null) { - return null; - } - return {...this.#mouseXy} - } -} - -let active = new Input(); - -export function getInput(): Input { - return active; -} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 9139762..2b03db2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,87 +1,4 @@ -import './style.css' -import {pollAndTouch} from "./screen.ts"; -import {getClock} from "./clock.ts"; +import {hostGame} from "./engine/internal/host.ts"; import {game} from "./game.ts"; -import {getInput, setupInput} from "./input.ts"; -// import typescriptLogo from './typescript.svg' -// import viteLogo from '/vite.svg' -// import { setupCounter } from './counter.ts' -// import {AlignX, mainFont} from "./font.ts"; - - -function setupGame() { - let gameCanvas = document.getElementById("game") as HTMLCanvasElement; - setupInput(gameCanvas); - onFrame(undefined); // start on-frame draw loop, set up screen -} - -function onFrame(timestamp: number | undefined) { - let gameCanvas = document.getElementById("game") as HTMLCanvasElement; - requestAnimationFrame(onFrame); - - if (timestamp) { - getClock().recordTimestamp(timestamp); - } - onFrameFixScreen(gameCanvas); - - while (getClock().popUpdate()) { - game.update(); - getInput().update(); - } - - game.draw(); - - /* - let ctx = getScreen().canvas.getContext("2d")!; - ctx.fillStyle = "#000"; - ctx.fillRect(0, 0, gameCanvas.width, gameCanvas.height); - - // ctx.drawImage(getAssets().getImage(font), 0, frame % (getScreen().h + 256) - 128); - // console.log(mainFont.measureText({text: "a!\nb\n"})); - mainFont.drawText({ - ctx: ctx, - text: "Hello, world!\nI wish you luck!", - x: gameCanvas.width, - y: 0, - color: "#f00", - alignX: AlignX.Right, - }); - mainFont.drawText({ - ctx: ctx, - text: "^._.^", - x: gameCanvas.width/2, - y: 32, - color: "#0ff", - alignX: AlignX.Center, - }); - */ -} - -function onFrameFixScreen(canvas: HTMLCanvasElement) { - pollAndTouch(canvas); -} - -setupGame(); - -/* -document.querySelector('#app')!.innerHTML = ` -
- - - - - - -

Vite + TypeScript

-
- -
-

- Click on the Vite and TypeScript logos to learn more -

-
-` - -setupCounter(document.querySelector('#counter')!) - */ \ No newline at end of file +hostGame(game); \ No newline at end of file diff --git a/src/sprite.ts b/src/sprite.ts deleted file mode 100644 index 2b84c5f..0000000 --- a/src/sprite.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {getAssets} from "./assets.ts"; - - -export class Sprite { - readonly imageSet: string; - // spritesheet params - // image size (px, py) - readonly px: number; readonly py: number; - // origin (ox, oy) - readonly ox: number; readonly oy: number; - // dimension in cells (cx, cy) - readonly cx: number; readonly cy: number; - // number of frames - readonly nFrames: number; - - constructor(imageSet: string, px: number, py: number, ox: number, oy: number, cx: number, cy: number, nFrames: number) { - this.imageSet = imageSet; - this.px = px; this.py = py; - this.ox = ox; this.oy = oy; - this.cx = cx; this.cy = cy; - this.nFrames = nFrames; - - if (this.nFrames < this.cx * this.cy) { - throw `can't have ${this.nFrames} with a spritesheet dimension of ${this.cx * this.cy}`; - } - } - - draw(ctx: CanvasRenderingContext2D, {x, y, ix, xScale, yScale, angle}: {x: number, y: number, ix?: number, xScale?: number, yScale?: number, angle?: number}) { - ix = ix == undefined ? 0 : ix; - xScale = xScale == undefined ? 1.0 : xScale; - yScale = yScale == undefined ? 1.0 : yScale; - angle = angle == undefined ? 0.0 : angle; - - // ctx.translate(Math.floor(x), Math.floor(y)); - ctx.translate(x, y); - ctx.rotate(angle * Math.PI / 180); - ctx.scale(xScale, yScale); - ctx.translate(-this.ox, -this.oy); - - let me = getAssets().getImage(this.imageSet); - let srcCx = ix % this.cx; - let srcCy = Math.floor(ix / this.cx); - let srcPx = srcCx * this.px; - let srcPy = srcCy * this.py; - console.log(`src px and py ${srcPx} ${srcPy}`) - ctx.drawImage(me, srcPx, srcPy, this.px, this.py, 0, 0, this.px, this.py); - } -} diff --git a/src/sprites.ts b/src/sprites.ts index fea8d68..52b115f 100644 --- a/src/sprites.ts +++ b/src/sprites.ts @@ -1,4 +1,4 @@ -import {Sprite} from "./sprite.ts"; +import {Sprite} from "./engine/internal/sprite.ts"; import imgBat from "./art/characters/bat.png"; import imgKobold from "./art/characters/kobold.png"; import imgRaccoon from "./art/characters/raccoon.png";