KHAKSWQANUZL7B3EYF6GWTDGSUR2WVZGU5BPPI6OKYIPCD3QMWAQC
import { Entity } from "./entity";
import { Debug } from "./debug";
import { HashSet } from "./data_structures/hash";
import { TypesafeLoader, AllResourcesType } from "./typesafe_loader";
import { CreateGame as ReactMountGame } from "./react/react_root";
import { Camera } from "./camera";
import { DebugFlagsType } from "./react/debug_flag_buttons";
import { CollisionHandler } from "./collision_handler";
import { Rect } from "./geometry/rect";
import { CoroutineManager } from "./coroutine_manager";
import { IGameState } from 'Library';
import { BaseGameState } from "./base_state";
export let GameReference: BaseGame<any>;
export type GameArgs = {
scale: number;
canvasWidth: number;
canvasHeight: number;
tileHeight: number;
tileWidth: number;
debugFlags: DebugFlagsType;
state: Omit<IGameState, keyof BaseGameState>;
assets: TypesafeLoader<any>;
};
export const StageName = "Stage";
export const FixedStageName = "FixedStage";
export const ParallaxStageName = "ParallaxStage";
export class BaseGame<TResources extends AllResourcesType = {}> {
app: PIXI.Application;
state: IGameState;
/**
* The root of the display hierarchy for the game. Everything that exists in
* the game that isn't fixed as the camera moves should be under this.
*/
stage: Entity;
parallaxStage: Entity;
/**
* A stage for things in the game that don't move when the camera move and are
* instead fixed to the screen. For example, the HUD.
*/
fixedCameraStage: Entity;
private assets: TypesafeLoader<TResources>;
renderer: Renderer;
camera: Camera;
collisionHandler: CollisionHandler;
coroutineManager: CoroutineManager;
constructor(props: GameArgs) {
GameReference = this;
this.coroutineManager = new CoroutineManager(this);
this.state = {
...(new BaseGameState()),
...props.state,
}
const view = document.getElementById('canvas');
if (!view) {
throw new Error("I couldn't find an element named #canvas on initialization. Giving up!")
}
this.collisionHandler = new CollisionHandler({
canvasWidth: props.canvasWidth / props.scale,
canvasHeight: props.canvasHeight / props.scale,
tileHeight: props.tileHeight,
tileWidth: props.tileWidth,
});
this.app = new Application({
width: props.canvasWidth,
height: props.canvasHeight,
powerPreference: "low-power",
antialias: false,
transparent: false,
resolution: window.devicePixelRatio,
autoDensity: true,
backgroundColor: 0x4e5759,
view: view as HTMLCanvasElement,
});
this.app.stage.scale = new Point(props.scale, props.scale);
this.parallaxStage = new Entity({ name: ParallaxStageName });
this.stage = new Entity({ name: StageName });
this.fixedCameraStage = new Entity({ name: FixedStageName });
this.state.stage = this.stage;
this.app.stage.addChild(this.parallaxStage.sprite);
this.app.stage.addChild(this.stage.sprite);
this.app.stage.addChild(this.fixedCameraStage.sprite);
this.state.renderer = this.app.renderer;
this.state.stage = this.stage;
this.assets = props.assets;
this.assets.onLoadComplete(() => this.startGameLoop());
this.assets.onLoadComplete(() => this.initialize());
this.renderer = this.app.renderer;
this.camera = new Camera({
stage: this.stage,
state: this.state,
canvasWidth: props.canvasWidth,
canvasHeight: props.canvasHeight,
scale: props.scale,
bounds: new Rect({ x: -5000, y: -5000, width: 10000, height: 10000 }),
});
this.state.camera = this.camera;
ReactMountGame(this, props.debugFlags);
this.stage.sprite.sortableChildren = true;
this.fixedCameraStage.sprite.sortableChildren = true;
}
/**
* Called after resources are finished loading.
*/
initialize() {
}
startGameLoop = () => {
this.app.ticker.add(() => this.gameLoop());
};
gameLoop() {
Debug.Clear();
const { entities } = this.state;
if (!this.state.lastCollisionGrid) {
const grid = this.collisionHandler.buildCollisionGrid({
bounds: new Rect({ x: 0, y: 0, width: 5000, height: 5000 }),
entities: this.state.entities,
});
this.state.lastCollisionGrid = grid;
}
this.state.tick++;
this.state.keys.update();
for (const entity of entities.values()) {
entity.baseUpdate(this.state);
}
this.state.entities = new HashSet(entities.values().filter(ent => !this.state.toBeDestroyed.includes(ent)));
for (const entity of this.state.toBeDestroyed) {
if (entity.sprite.parent) {
entity.sprite.parent.removeChild(entity.sprite);
}
this.coroutineManager.stopCoroutinesOwnedBy(entity);
}
this.state.toBeDestroyed = [];
const activeEntities = new HashSet(this.state.entities.values().filter(e => e.activeModes.includes(this.state.mode)));
const grid = this.collisionHandler.buildCollisionGrid({
bounds: this.camera.getBounds(),
entities: activeEntities,
});
this.state.lastCollisionGrid = grid;
this.collisionHandler.resolveCollisions({
entities: activeEntities,
grid: grid,
});
this.camera.update(this.state);
this.coroutineManager.updateCoroutines(this.state);
// let foo = Debug.GetDrawnObjects();
// for (const f of Debug.GetDrawnObjects()) {
// if (f instanceof AugmentedSprite) {
// if (f.width > 1024) {
// f.visible = false;
// }
// }
// }
// let foo = Debug.GetDrawn();
Debug.ResetDrawCount();
};
}
// import { Entity } from "./entity";
// import { Debug } from "./debug";
// import { HashSet } from "./data_structures/hash";
// import { TypesafeLoader, AllResourcesType } from "./typesafe_loader";
// import { CreateGame as ReactMountGame } from "./react/react_root";
// import { Camera } from "./camera";
// import { DebugFlagsType } from "./react/debug_flag_buttons";
// import { CollisionHandler } from "./collision_handler";
// import { Rect } from "./geometry/rect";
// import { CoroutineManager } from "./coroutine_manager";
// import { IGameState } from 'Library';
// import { BaseGameState } from "./base_state";
//
// export let GameReference: BaseGame<any>;
//
// export type GameArgs = {
// scale: number;
// canvasWidth: number;
// canvasHeight: number;
// tileHeight: number;
// tileWidth: number;
// debugFlags: DebugFlagsType;
// state: Omit<IGameState, keyof BaseGameState>;
// assets: TypesafeLoader<any>;
// };
//
// export const StageName = "Stage";
// export const FixedStageName = "FixedStage";
// export const ParallaxStageName = "ParallaxStage";
//
// export class BaseGame<TResources extends AllResourcesType = {}> {
// app: PIXI.Application;
//
// state: IGameState;
//
// /**
// * The root of the display hierarchy for the game. Everything that exists in
// * the game that isn't fixed as the camera moves should be under this.
// */
// stage: Entity;
//
// parallaxStage: Entity;
//
// /**
// * A stage for things in the game that don't move when the camera move and are
// * instead fixed to the screen. For example, the HUD.
// */
// fixedCameraStage: Entity;
//
// private assets: TypesafeLoader<TResources>;
//
// renderer: Renderer;
//
// camera: Camera;
//
// collisionHandler: CollisionHandler;
//
// coroutineManager: CoroutineManager;
//
// constructor(props: GameArgs) {
// GameReference = this;
//
// this.coroutineManager = new CoroutineManager(this);
// this.state = {
// ...(new BaseGameState()),
// ...props.state,
// }
//
// const view = document.getElementById('canvas');
//
// if (!view) {
// throw new Error("I couldn't find an element named #canvas on initialization. Giving up!")
// }
//
// this.collisionHandler = new CollisionHandler({
// canvasWidth: props.canvasWidth / props.scale,
// canvasHeight: props.canvasHeight / props.scale,
// tileHeight: props.tileHeight,
// tileWidth: props.tileWidth,
// });
//
// this.app = new Application({
// width: props.canvasWidth,
// height: props.canvasHeight,
// powerPreference: "low-power",
// antialias: false,
// transparent: false,
// resolution: window.devicePixelRatio,
// autoDensity: true,
// backgroundColor: 0x4e5759,
// view: view as HTMLCanvasElement,
// });
//
// this.app.stage.scale = new Point(props.scale, props.scale);
//
// this.parallaxStage = new Entity({ name: ParallaxStageName });
// this.stage = new Entity({ name: StageName });
// this.fixedCameraStage = new Entity({ name: FixedStageName });
//
// this.state.stage = this.stage;
//
// this.app.stage.addChild(this.parallaxStage.sprite);
// this.app.stage.addChild(this.stage.sprite);
// this.app.stage.addChild(this.fixedCameraStage.sprite);
//
// this.state.renderer = this.app.renderer;
// this.state.stage = this.stage;
//
// this.assets = props.assets;
// this.assets.onLoadComplete(() => this.startGameLoop());
// this.assets.onLoadComplete(() => this.initialize());
//
// this.renderer = this.app.renderer;
//
// this.camera = new Camera({
// stage: this.stage,
// state: this.state,
// canvasWidth: props.canvasWidth,
// canvasHeight: props.canvasHeight,
// scale: props.scale,
// bounds: new Rect({ x: -5000, y: -5000, width: 10000, height: 10000 }),
// });
//
// this.state.camera = this.camera;
//
// ReactMountGame(this, props.debugFlags);
//
// this.stage.sprite.sortableChildren = true;
// this.fixedCameraStage.sprite.sortableChildren = true;
// }
//
// /**
// * Called after resources are finished loading.
// */
// initialize() {
//
// }
//
// startGameLoop = () => {
// this.app.ticker.add(() => this.gameLoop());
// };
//
// gameLoop() {
// Debug.Clear();
//
// const { entities } = this.state;
//
// if (!this.state.lastCollisionGrid) {
// const grid = this.collisionHandler.buildCollisionGrid({
// bounds: new Rect({ x: 0, y: 0, width: 5000, height: 5000 }),
// entities: this.state.entities,
// });
//
// this.state.lastCollisionGrid = grid;
// }
//
// this.state.tick++;
//
// this.state.keys.update();
//
// for (const entity of entities.values()) {
// entity.baseUpdate(this.state);
// }
//
// this.state.entities = new HashSet(entities.values().filter(ent => !this.state.toBeDestroyed.includes(ent)));
//
// for (const entity of this.state.toBeDestroyed) {
// if (entity.sprite.parent) {
// entity.sprite.parent.removeChild(entity.sprite);
// }
//
// this.coroutineManager.stopCoroutinesOwnedBy(entity);
// }
//
// this.state.toBeDestroyed = [];
//
// const activeEntities = new HashSet(this.state.entities.values().filter(e => e.activeModes.includes(this.state.mode)));
//
// const grid = this.collisionHandler.buildCollisionGrid({
// bounds: this.camera.getBounds(),
// entities: activeEntities,
// });
//
// this.state.lastCollisionGrid = grid;
//
// this.collisionHandler.resolveCollisions({
// entities: activeEntities,
// grid: grid,
// });
//
// this.camera.update(this.state);
//
// this.coroutineManager.updateCoroutines(this.state);
//
// // let foo = Debug.GetDrawnObjects();
//
// // for (const f of Debug.GetDrawnObjects()) {
// // if (f instanceof AugmentedSprite) {
// // if (f.width > 1024) {
// // f.visible = false;
// // }
// // }
// // }
//
// // let foo = Debug.GetDrawn();
//
// Debug.ResetDrawCount();
// };
// }
import { IGameState } from "Library";
import { Entity } from "./entity";
import { Game } from "../game/game";
import { BaseGame } from "./base_game";
import { IS_DEBUG } from "./environment";
/**
* const state: GameState = yield CoroutineResult;
*/
export type GameCoroutine = Generator<CoroutineResult, void, IGameState>
export type CoroutineResult = "next" | { frames: number } | { untilKeyPress: keyof KeyInfoType };
type ActiveCoroutine = {
fn : GameCoroutine;
status :
| { waiting: false }
| { waiting: true; type: "frames" ; frames: number }
| { waiting: true; type: "untilKey"; untilKey: keyof KeyInfoType }
name : string;
owner : Entity | Game;
};
export type CoroutineId = number;
export class CoroutineManager {
private _lastCoroutineId: CoroutineId = -1;
private _activeCoroutines: { [key: number]: ActiveCoroutine } = [];
private _game: BaseGame<any>;
constructor(game: BaseGame<any>) {
this._game = game;
}
startCoroutine(name: string, co: GameCoroutine, owner: Entity | Game): CoroutineId {
for (const activeCo of Object.values(this._activeCoroutines)) {
if (activeCo.name === name) {
if (IS_DEBUG) {
throw new Error(`Two coroutines with the name ${ name }. Tell grant about this!!!`);
} else {
return 0;
}
}
}
this._activeCoroutines[++this._lastCoroutineId] = {
fn : co,
status : { waiting: false },
name : name,
owner : owner,
};
return this._lastCoroutineId;
}
public stopCoroutine(id: CoroutineId): void {
delete this._activeCoroutines[id];
}
public updateCoroutines(state: IGameState): void {
for (const key of Object.keys(this._activeCoroutines)) {
const co = this._activeCoroutines[Number(key)];
if (co.status.waiting) {
if (co.status.type === "frames") {
if (co.status.frames-- < 0) {
co.status = { waiting: false };
} else {
continue;
}
} else if (co.status.type === "untilKey") {
if (state.keys.justDown[co.status.untilKey]) {
co.status = { waiting: false };
} else {
continue;
}
}
}
const { value, done } = co.fn.next(state);
if (done) {
this.stopCoroutine(Number(key));
continue;
}
if (value === "next") {
continue;
}
if (typeof value === "object") {
if ("frames" in value) {
co.status = { waiting: true, type: 'frames', frames: value.frames };
continue;
} else if ("untilKeyPress" in value) {
co.status = { waiting: true, type: 'untilKey', untilKey: value.untilKeyPress };
continue;
}
}
}
}
stopCoroutinesOwnedBy(entity: Entity) {
const ids = Object.keys(this._activeCoroutines).map(k => Number(k));
for (const id of ids) {
if (this._activeCoroutines[id].owner === entity) {
this.stopCoroutine(id);
}
}
}
}
// import { IGameState } from "Library";
// import { Entity } from "./entity";
// import { Game } from "../game/game";
// import { BaseGame } from "./base_game";
// import { IS_DEBUG } from "./environment";
//
// /**
// * const state: GameState = yield CoroutineResult;
// */
// export type GameCoroutine = Generator<CoroutineResult, void, IGameState>
//
// export type CoroutineResult = "next" | { frames: number } | { untilKeyPress: keyof KeyInfoType };
//
// type ActiveCoroutine = {
// fn : GameCoroutine;
// status :
// | { waiting: false }
// | { waiting: true; type: "frames" ; frames: number }
// | { waiting: true; type: "untilKey"; untilKey: keyof KeyInfoType }
// name : string;
// owner : Entity | Game;
// };
//
// export type CoroutineId = number;
//
// export class CoroutineManager {
// private _lastCoroutineId: CoroutineId = -1;
// private _activeCoroutines: { [key: number]: ActiveCoroutine } = [];
// private _game: BaseGame<any>;
//
// constructor(game: BaseGame<any>) {
// this._game = game;
// }
//
// startCoroutine(name: string, co: GameCoroutine, owner: Entity | Game): CoroutineId {
// for (const activeCo of Object.values(this._activeCoroutines)) {
// if (activeCo.name === name) {
// if (IS_DEBUG) {
// throw new Error(`Two coroutines with the name ${ name }. Tell grant about this!!!`);
// } else {
// return 0;
// }
// }
// }
//
// this._activeCoroutines[++this._lastCoroutineId] = {
// fn : co,
// status : { waiting: false },
// name : name,
// owner : owner,
// };
//
// return this._lastCoroutineId;
// }
//
// public stopCoroutine(id: CoroutineId): void {
// delete this._activeCoroutines[id];
// }
//
// public updateCoroutines(state: IGameState): void {
// for (const key of Object.keys(this._activeCoroutines)) {
// const co = this._activeCoroutines[Number(key)];
//
// if (co.status.waiting) {
// if (co.status.type === "frames") {
// if (co.status.frames-- < 0) {
// co.status = { waiting: false };
// } else {
// continue;
// }
// } else if (co.status.type === "untilKey") {
// if (state.keys.justDown[co.status.untilKey]) {
// co.status = { waiting: false };
// } else {
// continue;
// }
// }
// }
//
// const { value, done } = co.fn.next(state);
//
// if (done) {
// this.stopCoroutine(Number(key));
//
// continue;
// }
//
// if (value === "next") {
// continue;
// }
//
// if (typeof value === "object") {
// if ("frames" in value) {
// co.status = { waiting: true, type: 'frames', frames: value.frames };
//
// continue;
// } else if ("untilKeyPress" in value) {
// co.status = { waiting: true, type: 'untilKey', untilKey: value.untilKeyPress };
//
// continue;
// }
// }
// }
// }
//
// stopCoroutinesOwnedBy(entity: Entity) {
// const ids = Object.keys(this._activeCoroutines).map(k => Number(k));
//
// for (const id of ids) {
// if (this._activeCoroutines[id].owner === entity) {
// this.stopCoroutine(id);
// }
// }
// }
// }
import { Graphics, Sprite, Container } from "pixi.js";
import { Line } from "./geometry/line";
import { Entity } from "./entity";
import { Rect } from "./geometry/rect";
import { RectGroup } from "./geometry/rect_group";
import { GameReference } from "./base_game";
import { BaseGameState } from "./base_state";
import { IS_DEBUG, IS_PRODUCTION } from "./environment";
const MAX_DEBUGGING_GRAPHICS_COUNT = 500;
export class Debug {
public static stageReference: Entity;
public static DebugMode = false;
public static DebugGraphicStack: Graphics[] = [];
public static Clear(): void {
for (const debug of Debug.DebugGraphicStack) {
debug.parent.removeChild(debug);
debug.destroy();
}
Debug.DebugGraphicStack = [];
}
/**
* Draw a point on the canvas.
*
* We expect this function to be called every tick in an update() function.
* Debug graphics drawn in the previous tick are removed in the game loop.
* If that's not what you want, pass persistent = true.
*/
public static DrawPoint(point: IVector2, color = 0xff0000, persistent = false): Graphics {
if (IS_PRODUCTION) {
console.error("SHOULD NOT HAPPEN")
}
const graphics = new Graphics();
new Line({
x1: point.x - 40,
x2: point.x + 40,
y1: point.y - 40,
y2: point.y + 40,
}).drawOnto(graphics, color);
new Line({
x1: point.x + 40,
x2: point.x - 40,
y1: point.y - 40,
y2: point.y + 40,
}).drawOnto(graphics, color);
GameReference.stage.sprite.addChild(graphics);
if (!persistent) {
this.DebugGraphicStack.push(graphics);
if (this.DebugGraphicStack.length > MAX_DEBUGGING_GRAPHICS_COUNT) {
const toBeRemoved = this.DebugGraphicStack.shift()!;
toBeRemoved.parent.removeChild(toBeRemoved);
toBeRemoved.destroy();
}
}
return graphics;
}
/**
* Draw a line from start to end on the canvas, for debugging.
*
* We expect this function to be called every tick in an update() function.
* Debug graphics drawn in the previous tick are removed in the game loop.
*
* If that's not what you want, pass persistent = true.
*/
public static DrawLineV2(start: Vector2, end: Vector2, color = 0xff0000, persistent = false): Graphics {
if (IS_PRODUCTION) {
console.error("SHOULD NOT HAPPEN")
}
return Debug.DrawLine(new Line({ start, end }), color, persistent);
}
/**
* Draw a line on the canvas, for debugging.
*
* We expect this function to be called every tick in an update() function.
* Debug graphics drawn in the previous tick are removed in the game loop.
*
* If that's not what you want, pass persistent = true.
*/
public static DrawLine(line: Line, color = 0xff0000, persistent = false, target: "stage" | "fixed" = "fixed"): Graphics {
if (IS_PRODUCTION) {
console.error("SHOULD NOT HAPPEN")
}
const graphics = new Graphics();
line.drawOnto(graphics, color);
if (target === "fixed") {
GameReference.fixedCameraStage.sprite.addChild(graphics);
} else {
GameReference.stage.sprite.addChild(graphics);
}
if (!persistent) {
this.DebugGraphicStack.push(graphics);
if (this.DebugGraphicStack.length > MAX_DEBUGGING_GRAPHICS_COUNT) {
const toBeRemoved = this.DebugGraphicStack.shift()!;
toBeRemoved.parent.removeChild(toBeRemoved);
toBeRemoved.destroy();
}
}
return graphics;
}
/**
* Draw a rectangle from start to end on the canvas, for debugging.
*
* We expect this function to be called every tick in an update() function.
* Debug graphics drawn in the previous tick are removed in the game loop.
*
* If that's not what you want, pass persistent = true.
*/
public static DrawRect(rect: Rect, color = 0xff0000, persistent = false, target: "stage" | "fixed" = "fixed"): Graphics[] {
if (IS_PRODUCTION) {
console.error("SHOULD NOT HAPPEN")
}
const lines: Graphics[] = [];
for (const line of rect.getLinesFromRect()) {
lines.push(Debug.DrawLine(line, color, persistent, target));
}
return lines;
}
/**
* Draw the bounds of a game object on the canvas, for debugging.
*
* We expect this function to be called every tick in an update() function.
* Debug graphics drawn in the previous tick are removed in the game loop.
*
* If that's not what you want, pass persistent = true.
*/
public static DrawBounds(
entity: Entity | Sprite | Graphics | RectGroup | Container | Rect,
color = 0xff0000,
persistent = false,
target: "stage" | "fixed" = "stage"
): Graphics[] {
if (IS_PRODUCTION) {
console.error("SHOULD NOT HAPPEN")
}
if (entity instanceof Entity) {
entity = entity.collisionBounds()
.add(entity.positionAbsolute())
;
}
if (entity instanceof RectGroup) {
const results: Graphics[] = [];
for (const rect of entity.getRects()) {
const lines = Debug.DrawRect(rect, color, persistent, target);
for (const line of lines) {
results.push(line);
}
}
return results;
} else {
return Debug.DrawRect(new Rect({
x : entity.x,
y : entity.y,
width : entity.width,
height: entity.height,
}), color, persistent, target);
}
}
private static profiles: { [key: string]: number[] } = {};
/**
* Performance test a block of code.
*/
public static Profile(name: string, cb: () => void): void {
Debug.profiles[name] = Debug.profiles[name] || [];
const start = window.performance.now();
cb();
const end = window.performance.now();
Debug.profiles[name].push(end - start);
if (Debug.profiles[name].length === 60) {
const average = Debug.profiles[name].reduce((a, b) => a + b) / 60;
const rounded = Math.floor(average * 100) / 100;
Debug.profiles[name] = [];
console.log(`${ name }: ${ rounded }ms`);
}
}
static ResetDrawCount() {
(Sprite as any).drawCount = 0;
(Container as any).drawCount = 0;
drawn = [];
}
static GetDrawnObjects() {
return drawn;
}
static GetDrawCount() {
return (
(Sprite as any).drawCount +
(Container as any).drawCount
);
}
public static DebugStuff(state: BaseGameState) {
if (state.keys.justDown.Z) {
Debug.DebugMode = true;
state.stage.x = 0;
state.stage.y = 0;
if (state.stage.scale.x === 0.2) {
state.stage.scale = new Vector2({ x: 1, y: 1 });
} else {
state.stage.scale = new Vector2({ x: 0.2, y: 0.2 });
}
}
if (Debug.DebugMode) {
if (state.keys.down.W) {
state.stage.y += 20;
}
if (state.keys.down.S) {
state.stage.y -= 20;
}
if (state.keys.down.D) {
state.stage.x -= 20;
}
if (state.keys.down.A) {
state.stage.x += 20;
}
}
}
public static DebugShowRect(state: BaseGameState, rect: Rect) {
state.stage.scale = new Vector2({ x: 0.2, y: 0.2 });
state.stage.x = -rect.x * 0.2;
state.stage.y = -rect.y * 0.2;
}
}
let drawn: any[] = [];
if (IS_DEBUG) {
(Sprite as any).drawCount = 0;
(Sprite.prototype as any).__render = (Sprite.prototype as any)._render;
(Sprite.prototype as any)._render = function (renderer: any) {
(Sprite as any).drawCount++;
this.__render(renderer);
drawn.push(this);
};
(Sprite.prototype as any).__renderCanvas = (Sprite.prototype as any)._renderCanvas;
(Sprite.prototype as any)._renderCanvas = function (renderer: any) {
(Sprite as any).drawCount++;
this.__renderCanvas(renderer);
drawn.push(this);
};
// PIXI.Container
(Container as any).drawCount = 0;
(Container.prototype as any).__render = (Container.prototype as any)._render;
(Container.prototype as any)._render = function (renderer: any) {
(Container as any).drawCount++;
this.__render(renderer);
drawn.push(this);
};
(Container.prototype as any).__renderCanvas = (Container.prototype as any)._renderCanvas;
(Container.prototype as any)._renderCanvas = function (renderer: any) {
(Container as any).drawCount++;
this.__renderCanvas(renderer);
drawn.push(this);
};
}
// import { Graphics, Sprite, Container } from "pixi.js";
// import { Line } from "./geometry/line";
// import { Entity } from "./entity";
// import { Rect } from "./geometry/rect";
// import { RectGroup } from "./geometry/rect_group";
// import { GameReference } from "./base_game";
// import { BaseGameState } from "./base_state";
// import { IS_DEBUG, IS_PRODUCTION } from "./environment";
//
// const MAX_DEBUGGING_GRAPHICS_COUNT = 500;
//
// export class Debug {
// public static stageReference: Entity;
//
// public static DebugMode = false;
//
// public static DebugGraphicStack: Graphics[] = [];
//
// public static Clear(): void {
// for (const debug of Debug.DebugGraphicStack) {
// debug.parent.removeChild(debug);
// debug.destroy();
// }
//
// Debug.DebugGraphicStack = [];
// }
//
// /**
// * Draw a point on the canvas.
// *
// * We expect this function to be called every tick in an update() function.
// * Debug graphics drawn in the previous tick are removed in the game loop.
// * If that's not what you want, pass persistent = true.
// */
// public static DrawPoint(point: IVector2, color = 0xff0000, persistent = false): Graphics {
// if (IS_PRODUCTION) {
// console.error("SHOULD NOT HAPPEN")
// }
//
// const graphics = new Graphics();
//
// new Line({
// x1: point.x - 40,
// x2: point.x + 40,
//
// y1: point.y - 40,
// y2: point.y + 40,
// }).drawOnto(graphics, color);
//
// new Line({
// x1: point.x + 40,
// x2: point.x - 40,
//
// y1: point.y - 40,
// y2: point.y + 40,
// }).drawOnto(graphics, color);
//
// GameReference.stage.sprite.addChild(graphics);
//
// if (!persistent) {
// this.DebugGraphicStack.push(graphics);
//
// if (this.DebugGraphicStack.length > MAX_DEBUGGING_GRAPHICS_COUNT) {
// const toBeRemoved = this.DebugGraphicStack.shift()!;
//
// toBeRemoved.parent.removeChild(toBeRemoved);
// toBeRemoved.destroy();
// }
// }
//
// return graphics;
// }
//
// /**
// * Draw a line from start to end on the canvas, for debugging.
// *
// * We expect this function to be called every tick in an update() function.
// * Debug graphics drawn in the previous tick are removed in the game loop.
// *
// * If that's not what you want, pass persistent = true.
// */
// public static DrawLineV2(start: Vector2, end: Vector2, color = 0xff0000, persistent = false): Graphics {
// if (IS_PRODUCTION) {
// console.error("SHOULD NOT HAPPEN")
// }
//
// return Debug.DrawLine(new Line({ start, end }), color, persistent);
// }
//
// /**
// * Draw a line on the canvas, for debugging.
// *
// * We expect this function to be called every tick in an update() function.
// * Debug graphics drawn in the previous tick are removed in the game loop.
// *
// * If that's not what you want, pass persistent = true.
// */
// public static DrawLine(line: Line, color = 0xff0000, persistent = false, target: "stage" | "fixed" = "fixed"): Graphics {
// if (IS_PRODUCTION) {
// console.error("SHOULD NOT HAPPEN")
// }
//
// const graphics = new Graphics();
//
// line.drawOnto(graphics, color);
//
// if (target === "fixed") {
// GameReference.fixedCameraStage.sprite.addChild(graphics);
// } else {
// GameReference.stage.sprite.addChild(graphics);
// }
//
// if (!persistent) {
// this.DebugGraphicStack.push(graphics);
//
// if (this.DebugGraphicStack.length > MAX_DEBUGGING_GRAPHICS_COUNT) {
// const toBeRemoved = this.DebugGraphicStack.shift()!;
//
// toBeRemoved.parent.removeChild(toBeRemoved);
// toBeRemoved.destroy();
// }
// }
//
// return graphics;
// }
//
// /**
// * Draw a rectangle from start to end on the canvas, for debugging.
// *
// * We expect this function to be called every tick in an update() function.
// * Debug graphics drawn in the previous tick are removed in the game loop.
// *
// * If that's not what you want, pass persistent = true.
// */
// public static DrawRect(rect: Rect, color = 0xff0000, persistent = false, target: "stage" | "fixed" = "fixed"): Graphics[] {
// if (IS_PRODUCTION) {
// console.error("SHOULD NOT HAPPEN")
// }
//
// const lines: Graphics[] = [];
//
// for (const line of rect.getLinesFromRect()) {
// lines.push(Debug.DrawLine(line, color, persistent, target));
// }
//
// return lines;
// }
//
// /**
// * Draw the bounds of a game object on the canvas, for debugging.
// *
// * We expect this function to be called every tick in an update() function.
// * Debug graphics drawn in the previous tick are removed in the game loop.
// *
// * If that's not what you want, pass persistent = true.
// */
// public static DrawBounds(
// entity: Entity | Sprite | Graphics | RectGroup | Container | Rect,
// color = 0xff0000,
// persistent = false,
// target: "stage" | "fixed" = "stage"
// ): Graphics[] {
// if (IS_PRODUCTION) {
// console.error("SHOULD NOT HAPPEN")
// }
//
// if (entity instanceof Entity) {
// entity = entity.collisionBounds()
// .add(entity.positionAbsolute())
// ;
// }
//
// if (entity instanceof RectGroup) {
// const results: Graphics[] = [];
//
// for (const rect of entity.getRects()) {
// const lines = Debug.DrawRect(rect, color, persistent, target);
//
// for (const line of lines) {
// results.push(line);
// }
// }
//
// return results;
// } else {
// return Debug.DrawRect(new Rect({
// x : entity.x,
// y : entity.y,
// width : entity.width,
// height: entity.height,
// }), color, persistent, target);
// }
// }
//
// private static profiles: { [key: string]: number[] } = {};
//
// /**
// * Performance test a block of code.
// */
// public static Profile(name: string, cb: () => void): void {
// Debug.profiles[name] = Debug.profiles[name] || [];
//
// const start = window.performance.now();
//
// cb();
//
// const end = window.performance.now();
//
// Debug.profiles[name].push(end - start);
//
// if (Debug.profiles[name].length === 60) {
// const average = Debug.profiles[name].reduce((a, b) => a + b) / 60;
// const rounded = Math.floor(average * 100) / 100;
//
// Debug.profiles[name] = [];
//
// console.log(`${ name }: ${ rounded }ms`);
// }
// }
//
// static ResetDrawCount() {
// (Sprite as any).drawCount = 0;
// (Container as any).drawCount = 0;
// drawn = [];
// }
//
// static GetDrawnObjects() {
// return drawn;
// }
//
// static GetDrawCount() {
// return (
// (Sprite as any).drawCount +
// (Container as any).drawCount
// );
// }
//
// public static DebugStuff(state: BaseGameState) {
// if (state.keys.justDown.Z) {
// Debug.DebugMode = true;
//
// state.stage.x = 0;
// state.stage.y = 0;
//
// if (state.stage.scale.x === 0.2) {
// state.stage.scale = new Vector2({ x: 1, y: 1 });
// } else {
// state.stage.scale = new Vector2({ x: 0.2, y: 0.2 });
// }
// }
//
// if (Debug.DebugMode) {
// if (state.keys.down.W) {
// state.stage.y += 20;
// }
//
// if (state.keys.down.S) {
// state.stage.y -= 20;
// }
//
// if (state.keys.down.D) {
// state.stage.x -= 20;
// }
//
// if (state.keys.down.A) {
// state.stage.x += 20;
// }
// }
// }
//
// public static DebugShowRect(state: BaseGameState, rect: Rect) {
// state.stage.scale = new Vector2({ x: 0.2, y: 0.2 });
// state.stage.x = -rect.x * 0.2;
// state.stage.y = -rect.y * 0.2;
// }
// }
//
// let drawn: any[] = [];
//
// if (IS_DEBUG) {
// (Sprite as any).drawCount = 0;
//
// (Sprite.prototype as any).__render = (Sprite.prototype as any)._render;
// (Sprite.prototype as any)._render = function (renderer: any) {
// (Sprite as any).drawCount++;
// this.__render(renderer);
// drawn.push(this);
// };
//
//
// (Sprite.prototype as any).__renderCanvas = (Sprite.prototype as any)._renderCanvas;
// (Sprite.prototype as any)._renderCanvas = function (renderer: any) {
// (Sprite as any).drawCount++;
// this.__renderCanvas(renderer);
// drawn.push(this);
// };
//
//
// // PIXI.Container
//
// (Container as any).drawCount = 0;
//
// (Container.prototype as any).__render = (Container.prototype as any)._render;
// (Container.prototype as any)._render = function (renderer: any) {
// (Container as any).drawCount++;
// this.__render(renderer);
// drawn.push(this);
// };
//
//
// (Container.prototype as any).__renderCanvas = (Container.prototype as any)._renderCanvas;
// (Container.prototype as any)._renderCanvas = function (renderer: any) {
// (Container as any).drawCount++;
// this.__renderCanvas(renderer);
// drawn.push(this);
// };
// }
import crypto from "crypto";
/**
* Md5 is 16 bytes, or max int of 256 ** 16 = 2 ** 128
*/
export class HashState {
private seed!: Buffer;
/**
* HashState().step("foo") is equivalent to HashState("foo")
*/
constructor(seed?: string) {
const buffer = crypto
.createHash("md5")
.update((seed || "").toString())
.digest();
this.seed = buffer;
}
public peekRandom(): number {
const buffer = crypto.createHash("md5").update(this.seed).digest();
return Number(this.bufferToBigInt(buffer) % BigInt(2 ** 32)) % 2 ** 32;
}
// increment the seed linearly by 1
public step(numSteps: number = 1) {
this.seed = this.bigIntToBuffer(this.bufferToBigInt(this.seed) + BigInt(1));
}
public stepSeed(seed: string) {
const buffer = crypto.createHash("md5").update(seed.toString()).digest();
this.seed = this.bigIntToBuffer(
this.bufferToBigInt(this.seed) + this.bufferToBigInt(buffer)
);
}
private bigIntToBuffer(b: bigint): Buffer {
let buf = Buffer.alloc(16);
let val = b;
for (let i = 0; i < 16; i++) {
buf[i] = Number(val % BigInt(256));
val = val / BigInt(256);
}
return buf;
}
private bufferToBigInt(b: Buffer): bigint {
let val = BigInt(0);
for (let i = 0; i < 16; i++) {
val = val * BigInt(256) + BigInt(b[i]);
}
return val;
}
public random(): number {
this.step();
return this.peekRandom();
}
}
public drawAll() {}
public drawAll() {
// get the first 3 layers' configurations
// render the top layer points
// renderLayerPoints(layer[0], { rect: null })
// render the next layer
// render the intermediate connections
// render the final layer
// render the intermediate connections
}
import { connect } from "http2";
import { HashState } from "../library/random";
export type Topo3Frames = {
out: TopoFrame;
in0: TopoFrame;
in1: TopoFrame;
};
export class TopoTemplate {
public id!: string;
public orientation!: FrameOrientation;
public frames!: Topo3Frames;
public nodes: TopoNode[] = [];
constructor(id: string, frames: Topo3Frames, random: HashState) {
this.id = id;
this.frames = frames;
this.orientation = frames.out.orientation;
// start constructing the guy
connectCorner(this.frames.out.corners["00"], this.frames.in0.corners["00"]);
connectCorner(this.frames.out.corners["11"], this.frames.in1.corners["00"]);
if (this.orientation == "=") {
connectCorner(
this.frames.out.corners["01"],
this.frames.in0.corners["01"]
);
connectCorner(
this.frames.out.corners["10"],
this.frames.in1.corners["10"]
);
connectMiddleCorner(
this.frames.in0.corners["10"],
this.frames.in1.corners["00"]
);
connectMiddleCorner(
this.frames.in0.corners["11"],
this.frames.in1.corners["01"]
);
} else if (this.orientation == "0") {
connectCorner(
this.frames.out.corners["01"],
this.frames.in1.corners["01"]
);
connectCorner(
this.frames.out.corners["10"],
this.frames.in0.corners["10"]
);
connectMiddleCorner(
this.frames.in0.corners["01"],
this.frames.in1.corners["00"]
);
connectMiddleCorner(
this.frames.in0.corners["11"],
this.frames.in1.corners["10"]
);
}
}
}
export type FrameOrientation = "=" | "0";
export class TopoFrame {
public id!: string;
public orientation!: FrameOrientation;
public corners!: {
"00": TopoNode;
"01": TopoNode;
"10": TopoNode;
"11": TopoNode;
};
/**
* 00 ----- 10
* | |
* 01 ----- 11
*/
constructor(id: string, orientation: FrameOrientation) {
this.id = id;
this.corners = {
"00": new TopoNode(id + "-N00"),
"01": new TopoNode(id + "-N01"),
"10": new TopoNode(id + "-N10"),
"11": new TopoNode(id + "-N11"),
};
this.orientation = orientation;
}
}
export class TopoNode {
public id!: string;
constructor(id: string) {
this.id = id;
}
}