fledgling/src/game.ts
2025-02-01 20:30:03 -08:00

219 lines
5.4 KiB
TypeScript

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";
import {D, I} from "./engine/public.ts";
import {IGame, Point, Size} from "./engine/datatypes.ts";
class MenuCamera {
// measured in whole screens
position: Point;
target: Point
constructor({position, target}: {position: Point, target: Point}) {
this.position = position;
this.target = target;
}
update() {
let adjust = (x0: number, x1: number) => {
if (Math.abs(x1 - x0) < 0.01) { return x1; }
return (x0 * 8 + x1 * 2) / 10;
}
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): Point {
if (state === "Gameplay") {
return new Point(0, 0);
}
if (state === "Thralls") {
return new Point(0, 1);
}
throw `invalid state: ${state}`
}
export class Game implements IGame {
camera: MenuCamera;
state: GameState;
huntMode: HuntMode;
constructor() {
this.camera = new MenuCamera({
position: new Point(0, 0),
target: new Point(0, 0),
});
this.state = "Gameplay";
this.huntMode = HuntMode.generate({depth: 1});
}
update() {
if (I.isKeyPressed("w")) {
this.state = "Gameplay"
}
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
this.updateGameplay();
}
draw() {
// draw screen background
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 mouse = I.mousePosition?.offset(D.camera);
if (mouse != null) {
D.invertRect(mouse.offset(new Point(-1, -1)), new Size(3, 3))
}
}
getPaneRegionForGameState(gameState: GameState) {
let screen = getScreen();
let {w, h} = screen.size;
let overallScreenLocation = getScreenLocation(gameState);
let bigPaneX = overallScreenLocation.x * w;
let bigPaneY = overallScreenLocation.y * h;
let bigPaneW = w;
let bigPaneH = h;
let smallPaneW = desiredWidth;
let smallPaneH = desiredHeight;
let smallPaneX = Math.floor(bigPaneX + (bigPaneW - smallPaneW) / 2)
let smallPaneY = Math.floor(bigPaneY + (bigPaneH - smallPaneH) / 2)
return {
big: {
position: new Point(bigPaneX, bigPaneY),
size: new Size(bigPaneW, bigPaneH),
},
small: {
position: new Point(smallPaneX, smallPaneY),
size: new Size(smallPaneW, smallPaneH),
}
}
}
updateGameplay() {
}
drawGameplay() {
let region = this.getPaneRegionForGameState("Gameplay")
// TODO: Draw
D.drawText("hello", region.small.position, FG_TEXT);
}
}
type Stat = "AGI" | "INT" | "CHA" | "PSI";
const ALL_STATS: Array<Stat> = ["AGI", "INT", "CHA", "PSI"];
type MapCellContent =
{type: "statPickup", stat: Stat} |
{type: "stairs"} |
{type: "block"}
type MapCell = {
content: MapCellContent,
revealed: boolean
}
class HuntMode {
depth: number
cells: Array<Array<MapCell>>
player: Point | null
constructor({depth, cells, player}: {depth: number, cells: Array<Array<MapCell>>, player: Point | null}) {
this.depth = depth;
this.cells = cells;
this.player = player;
checkGrid(this.cells);
}
static generate({depth}: {depth: number}) {
let mapNames: Array<string> = Object.keys(maps);
let mapName = mapNames[Math.floor(Math.random() * mapNames.length)];
let map = maps[mapName];
let rows = [];
for (let y = 0; y < mapSzY; y++) {
let row = [];
for (let x = 0; x < mapSzX; x++) {
let src = map[y][x];
row.push(HuntMode.#generateCell(src))
}
rows.push(row);
}
if (Math.random() < 0.75) {
while (true) {
let x = Math.floor(Math.random() * mapSzX);
let y = Math.floor(Math.random() * mapSzY);
let item = rows[y][x];
if (item.content.type != "block") {
item.content = {type: "stairs"}
break;
}
}
}
return new HuntMode({depth, cells: rows, player: null})
}
static #generateCell(conceptual: ConceptualCell): MapCell {
switch (conceptual) {
case "X":
return { content: {type: "block"}, revealed: true};
case " ":
return { content: HuntMode.#generateContent(true), revealed: true };
case ".":
return { content: HuntMode.#generateContent(false), revealed: true };
}
}
static #generateContent(_revealed: boolean): MapCellContent {
// stat pickup
let gsp = (): MapCellContent => {
return {type: "statPickup", stat: choose(ALL_STATS)}
};
// TODO: Other objects?
return choose([
gsp, gsp, gsp, gsp
])();
}
}
function choose<T>(array: Array<T>): T {
if (array.length == 0) {
throw `array cannot have length 0 for choose`
}
return array[Math.floor(Math.random() * array.length)]
}
export let game = new Game();