ZGS4FTTFBXTF2SHYTPAJJBWEUVWVYXPSJVEFI5NYJWTW273B4NHAC
MYAKTHOSHICOSCWJPA33Z4VLNB2G27GRO4RUS2764HHIXO5OFKJAC
HUH4SI4HXIP72KQSJP2I4ELHX5KUQZM7FFGKZZGJ33DF7E3JHMYQC
Z3E7XJOW6NSBDYRDKSGTOAEJSPATAUX4JUFCL4DIL3372GL4K52QC
TQ57VE45BHV7MOZ6GKTYZEAMAOTXLPQ3ROCWJ2FUCITQWOYVMUIAC
NJ3CLHJNB5S7K7XHXLMDGD3RDVIFIHYNDMFQJF3CUVMAK6E436YQC
ED3IWKTSVYYD52TWW5TOC4H63YZ7FTO3E53YO5NELPG5I3RLRAYAC
ZHOSSPNKGFIKSFPDXCGLMSYMMX2J433VU2BUUWBKUH7TOLQUBSPQC
DLEEYV4V7X337ZJJM775DPARMCMXMLOBXGSBCWDMZBHYKSQTGZCQC
}
export class ChunkGenConstants {
public static CHUNK_DIM = 9; // each chunk is a DIM x DIM grid of nodes, centered on a single node
public static CHUNK_HALF_DIM = (ChunkGenConstants.CHUNK_DIM - 1) / 2;
public static DROP_NODES_CHANCE = 0.3; // before generating edges, how many of the nodes to throw out
? (UpdaterGeneratorType<T[k]> & { getUpdater: () => UpdaterFn<T[k]> })
: { getUpdater: () => UpdaterFn<T[k]> }
} & { getUpdater: () => UpdaterFn<T> }
? (UpdaterGeneratorType<T[k]> & {
getUpdater: () => UpdaterFn<T[k]>,
set: UpdaterFn<T[k]>,
update: UpdaterFn<T[k]>,
})
: {
getUpdater: () => UpdaterFn<T[k]>,
set: UpdaterFn<T[k]>,
update: UpdaterFn<T[k]>,
}
} & {
getUpdater: () => UpdaterFn<T>,
set: UpdaterFn<T>,
update: UpdaterFn<T>,
}
export type DeepReadonly<T> = T extends Function ? T : {
readonly [P in keyof T]: T[P] extends { [k: string]: any } ? DeepReadonly<T[P]> : T[P];
}
import { Line } from "../lib/util/geometry/line";
import { Vector2 } from "../lib/util/geometry/vector2";
import { INTMAX32, squirrel3 } from "../lib/util/random";
import * as Pixi from "pixi.js";
import { HashMap, HashSet } from "../lib/util/data_structures/hash";
import { ChunkGenConstants, PointNodeRef } from "../data/GameState";
export class RenderedChunkConstants {
public static SPACING_PX: number = 24;
public static CHUNK_SPACING_PX: number = (ChunkGenConstants.CHUNK_DIM + 0.5) * RenderedChunkConstants.SPACING_PX;
public static NODE_SIZE_PX: number = 14;
public static NODE_HITAREA_PX: number = 18;
public static NODE_ROUNDED_PX: number = 4;
}
export class RenderedChunk {
// public chunk!: Chunk;
public container: Pixi.Container;
public renderedNodes: HashMap<Vector2, Pixi.Graphics | Pixi.Sprite> = new HashMap();
// constructor(chunk: Chunk, onNodeFocus: (selection: PointNodeRef) => void, texture?: Pixi.Texture) {
constructor(args: { onNodeFocus: (selection: PointNodeRef) => void, nodeTexture: Pixi.Texture }) {
// render the thing
this.container = new Pixi.Container();
for (let node of chunk.nodes) {
let g: Pixi.Sprite = new Pixi.Sprite(args.nodeTexture);
g.anchor.x = 0.5;
g.anchor.y = 0.5;
g.x = node.x * RenderedChunkConstants.SPACING_PX;
g.y = node.y * RenderedChunkConstants.SPACING_PX;
g.hitArea = new Pixi.Rectangle(
- RenderedChunkConstants.NODE_HITAREA_PX / 2,
- RenderedChunkConstants.NODE_HITAREA_PX / 2,
RenderedChunkConstants.NODE_HITAREA_PX,
RenderedChunkConstants.NODE_HITAREA_PX,
)
this.renderedNodes.put(node, g);
g.interactive = true;
if (this.chunk.allocatedNodes.get(node)) {
g.tint = 0x00aaff;
} else if (this.chunk.selectedNodes.get(node)) {
g.tint = 0xBBBBBB;
}
g.addListener("pointerdown", () => {
onNodeFocus(new PointNodeRef({
z: 0, // TODO(bowei): fix
chunkCoord: this.chunk.location,
pointNodeCoord: node,
pointNodeId: 0, // TODO(bowei): fix
}));
console.log(`clicked chunk ${this.chunk.location.x} ${this.chunk.location.y} node ${node.x}, ${node.y}`);
// if nothing is selected
if (this.chunk.selectedNodes.values().length == 0) {
// select it
this.chunk.selectedNodes.put(node);
g.tint = 0xBBBBBB;
// g.alpha = 0.5;
} else if (this.chunk.selectedNodes.get(node)) {
// i was already selected, let's allocate it
this.chunk.selectedNodes.remove(node);
// try to allocate, only allow if we are connected to something already allocated
let neighbors = [node.addX(1), node.addY(1), node.addY(-1), node.addX(-1)];
let allowed = false;
for (let neighbor of neighbors) {
if (this.chunk.allocatedNodes.get(neighbor)) {
allowed = true;
break;
}
}
if (allowed) {
this.chunk.allocatedNodes.put(node);
g.tint = 0x00aaff;
} else {
g.tint = 0xFFFFFF;
window.alert('not allowed to allocate that one!');
}
// g.alpha = 0.5;
} else {
// unselect what was previously selected
for (let selected of this.chunk.selectedNodes.values()) {
this.renderedNodes.get(selected).tint = 0xFFFFFF;
this.chunk.selectedNodes.remove(selected);
}
this.chunk.selectedNodes.put(node);
g.tint = 0xBBBBBB;
}
});
this.container.addChild(g);
}
this.container.x = this.chunk.location.x * RenderedChunk.CHUNK_SPACING_PX;
this.container.y = this.chunk.location.y * RenderedChunk.CHUNK_SPACING_PX;
}
public hash(): string {
return this.chunk.hash();
}
}
import { Line } from "../lib/util/geometry/line";
import { Vector2 } from "../lib/util/geometry/vector2";
import { INTMAX32, squirrel3 } from "../lib/util/random";
import * as Pixi from "pixi.js";
import { HashMap, HashSet } from "../lib/util/data_structures/hash";
import { ChunkGenConstants, GameState, PointNodeRef } from "../data/GameState";
import { RenderedChunkConstants } from "./RenderedChunk";
import { DeepReadonly, updaterGenerator, UpdaterGeneratorType } from "../lib/util/misc";
public sprite: Pixi.Sprite;
public selfPointNodeRef: PointNodeRef; // which node we are
// local state
public justClicked: boolean = false;
constructor(args: { texture: Pixi.Texture, selfPointNodeRef: PointNodeRef }) {
this.selfPointNodeRef = args.selfPointNodeRef;
this.sprite = new Pixi.Sprite(args.texture);
this.sprite.anchor.x = 0.5;
this.sprite.anchor.y = 0.5;
// this.sprite.x = node.x * RenderedChunkConstants.SPACING_PX;
// this.sprite.y = node.y * RenderedChunkConstants.SPACING_PX;
this.sprite.interactive = true;
this.sprite.hitArea = new Pixi.Rectangle(
- RenderedChunkConstants.NODE_HITAREA_PX / 2,
- RenderedChunkConstants.NODE_HITAREA_PX / 2,
RenderedChunkConstants.NODE_HITAREA_PX,
RenderedChunkConstants.NODE_HITAREA_PX,
);
this.sprite.addListener("pointerdown", () => {
this.justClicked = true;
});
}
private setTint(args: { isSelected: boolean, isAllocated: boolean }) {
if (args.isAllocated) {
this.sprite.tint = 0x00AAFF;
} else {
if (args.isSelected) {
this.sprite.tint = 0xBBBBBB;
} else {
this.sprite.tint = 0xFFFFFF;
}
}
}
private isSelected(gameState: DeepReadonly<GameState>): boolean {
return gameState.playerUI.selectedPointNode?.pointNodeId == this.selfPointNodeRef.pointNodeId;
}
private isAllocated(gameState: DeepReadonly<GameState>): boolean {
return gameState.playerSave.allocatedPointNodeSet.get(this.selfPointNodeRef)
}
public update(args: {
gameState: DeepReadonly<GameState>, gameStateUpdater: UpdaterGeneratorType<GameState>,
renderedNodeMap: DeepReadonly<HashMap<PointNodeRef, RenderedPointNode>>
}) {
let { gameState, gameStateUpdater } = args;
// sync ourselves with state
let isSelected = this.isSelected(gameState);
let isAllocated = this.isAllocated(gameState);
if (this.justClicked) {
if (!gameState.playerUI.selectedPointNode) {
// if nothing is is selected, select ourselves;
isSelected = true;
this.setTint({ isSelected, isAllocated });
gameStateUpdater.playerUI.selectedPointNode.set(this.selfPointNodeRef);
} else if (gameState.playerUI.selectedPointNode.pointNodeId == this.selfPointNodeRef.pointNodeId) {
// if we were already selected, try to allocate ourselves
if (!isAllocated) {
isAllocated = true;
// save our allocation to state
// TODO(bowei): this code block should be somewhere else????
gameStateUpdater.playerSave.allocatedPointNodeSet.update(set => {
set.put(this.selfPointNodeRef);
return set;
})
gameStateUpdater.playerSave.allocatedPointNodeHistory.update(history => {
history.push(this.selfPointNodeRef);
return history;
})
}
this.setTint({ isSelected, isAllocated });
} else {
// if something other than ourselves is selected, unselect it and select ourselves;
isSelected = true;
let otherNode = args.renderedNodeMap.get(gameState.playerUI.selectedPointNode);
gameStateUpdater.playerUI.selectedPointNode.set(this.selfPointNodeRef);
otherNode.setTint({
isSelected: false,
isAllocated: otherNode.isAllocated(gameState)
});
this.setTint({ isSelected: true, isAllocated})
}
}
// don't forget to reset state!
this.justClicked = false;
}