IZUPWMKDP7DYVZT4LI6WA4X2KGSLZ4GPVAYKDDNJUMRJ6AVZJLAAC
WBDAIDQZ3J7ON5DWIDA2L4OJEEZCUP2RLEWJSNR4AC3SDBHDBXIQC
3UFL673QX2JG7KHTAM7DFH4BBYVHDNMXBS2RW45G3JBNFZFLSRRQC
SNFPBDCSRTZ466KGMTCKMOSULFHXFNXN2IXT6O25RRXF6VLGEBZAC
OYNCMT7YDCHK7TFRZDVCMODYMYXRQKK3AU7EOZU7KRA76QGKI3EQC
5BA7VZ3D36S2TC7NZ64R3O364TGXPY5BJUJTGCFZHWZ6JWAXJMUQC
BHSJJQZKFNLL4KWBKDU5MUWK5HZ7HGNXUFC2UHAEGTIXN7ARY3UAC
B4JS4Z3VVAD5RJJ272S7GJM5BUNIHHMGNK2VSKVGZFPOVFP2YO4QC
GWNDFRV3SSE3VUV7AUE6KI7AXCEYYJDNRGRTJD2RNMSYG5OFPKIQC
QUT2VGNOS2XSDLSYCSMILCKF343M56MRPRSUWN4GXWPGCK6APK2QC
EMED7UWAGESRTWJJWVXRL27O4XMBGZDDAU4LNVF3MQAYKFKRDNXQC
OET27TXMNRKBX3HWDSLQWCWAJOWNY5NU4XDKLO5N7Z3EE23CSWOAC
6REHBZAOJN4CVZY4HFXVNEUMWGQUXFTXMLQPXEH7RTH5FBL2TXIAC
A75BWKGRLUDH3KF4M2HJYLNC6BSV5MAJFV2RJSF757F46HLRLCKQC
ZHOSSPNKGFIKSFPDXCGLMSYMMX2J433VU2BUUWBKUH7TOLQUBSPQC
DFFMZSJOCLTBA3IGRYS2ZIJNO2MC3VLBBU42QL5DFY2SMCNFXPIAC
ZGS4FTTFBXTF2SHYTPAJJBWEUVWVYXPSJVEFI5NYJWTW273B4NHAC
DLEEYV4V7X337ZJJM775DPARMCMXMLOBXGSBCWDMZBHYKSQTGZCQC
HUH4SI4HXIP72KQSJP2I4ELHX5KUQZM7FFGKZZGJ33DF7E3JHMYQC
OL5CSNTOYADQ6NOJS5YRWY4QIAWEQRGCJGBHNSRSMJD2I43COJ7AC
N2M7CWXHD722QWM6IIIEOD66YMR3SSZEJZLDCGEIPXRCVNF6YBSQC
AAKN4XJLZ2GARZMUFYX3CJZKYHTRRZDYNTFRLMY7VPAY7K6W4N3QC
YJ3YEICSBUHMDVJB6W3ZOOVWLTAXRDCIKOGNN6HRP6N3HWQTDGVAC
4UKXFJRZYO5WEPH5PKMYDZCWVWSEAXFSPL35G5NFIUBCMH3BCXEQC
const [application, setApplication] = useState<any>(initialApplication.get());
// const [application, setApplication] = useState<any>(() => new BaseApplication({
const [application, setApplication] = useState(initialApplication.get());
// const [application, setApplication] = useState(() => new BaseApplication({
const prevRef = useRef<DeepReadonly<GameState>>();
useEffect(() => {
prevRef.current = gameState;
});
const prevGameState = prevRef.current;
// const prevRef = useRef<DeepReadonly<GameState>>();
// useEffect(() => {
// prevRef.current = gameState;
// });
// const prevGameState = prevRef.current;
import * as Pixi from "pixi.js";
import { ChunkRef, PointNodeRef, ZLevelGen } from "../data/GameState";
import { KeyedHashMap, HashSet } from "../lib/util/data_structures/hash";
import { RenderedChunk, RenderedChunkConstants } from "./RenderedChunk";
export class RenderedZLevel {
public container: Pixi.Container;
public z: number;
public renderedChunks: KeyedHashMap<ChunkRef, RenderedChunk> = new KeyedHashMap();
constructor(args: {
pointNodeTexture: Pixi.Texture,
z: number,
zLevelGen: ZLevelGen, // what is in me
stateUpdaterQueue: [Function],
ticker: Pixi.Ticker
}) {
// constructor(zLevel: ZLevel, onNodeFocus: (selection: PointNodeRef) => void, texture?: Pixi.Texture) {
this.z = args.z;
this.container = new Pixi.Container();
for (let [chunkCoord, chunkGen] of args.zLevelGen.chunks.entries()) {
const chunkRef = new ChunkRef({
z: this.z,
chunkCoord,
chunkId: chunkGen.id,
});
const renderedChunk = new RenderedChunk({
selfChunkRef: chunkRef,
chunkGen,
...args
});
this.renderedChunks.put(chunkRef, renderedChunk);
this.container.addChild(renderedChunk.container);
renderedChunk.container.x = chunkCoord.x * RenderedChunkConstants.CHUNK_SPACING_PX;
renderedChunk.container.y = chunkCoord.y * RenderedChunkConstants.CHUNK_SPACING_PX;
}
}
public animate(delta: number): this {
return this;
}
public rerender(props: {
selectedPointNode: PointNodeRef | undefined,
allocatedPointNodeSubset: HashSet<PointNodeRef>,
}) {
for (let [chunkRef, child] of this.renderedChunks.entries()) {
let relevantToChunk = new HashSet(
props.allocatedPointNodeSubset.values()
.filter((pointNodeRef) => {
return pointNodeRef.chunkCoord.x === chunkRef.chunkCoord.x &&
pointNodeRef.chunkCoord.y === chunkRef.chunkCoord.y;
})
);
child.rerender({
selectedPointNode: props.selectedPointNode,
allocatedPointNodeSubset: relevantToChunk
})
}
}
}
import * as Pixi from "pixi.js";
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;
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();
// }
public rerender(props: {
selectedPointNode: PointNodeRef | undefined,
allocatedPointNodeSubset: HashSet<PointNodeRef>,
}) {
for (let child of this.renderedPointNodes.values()) {
child.rerender({
isAllocated: props.allocatedPointNodeSubset.contains(child.selfPointNodeRef),
})
}
isSelected: props.selectedPointNode?.pointNodeId === child.selfPointNodeRef.pointNodeId,
public animate(delta: number): this {
return this;
// this.container.x = this.chunk.location.x * RenderedChunk.CHUNK_SPACING_PX;
// this.container.y = this.chunk.location.y * RenderedChunk.CHUNK_SPACING_PX;
}
public setLocation(chunk: ChunkRef = this.selfChunkRef): this {
this.container.x = chunk.chunkCoord.x * RenderedChunkConstants.CHUNK_SPACING_PX;
this.container.y = chunk.chunkCoord.y * RenderedChunkConstants.CHUNK_SPACING_PX;
return this
}
let renderedPointNode = new RenderedPointNode({
selfPointNodeRef: pointNodeRef,
...args
})
this.renderedPointNodes.put(pointNodeRef, renderedPointNode);
this.container.addChild(renderedPointNode.sprite);
// renderedPointNode.setCoord(pointNodeRef); renderedPointNode.setCoord();
renderedPointNode.sprite.x = pointNodeCoord.x * RenderedChunkConstants.SPACING_PX;
renderedPointNode.sprite.y = pointNodeCoord.y * RenderedChunkConstants.SPACING_PX;
// args.chunkGen.pointNodes
for (let [pointNodeCoord, pointNodeGen] of args.chunkGen.pointNodes.entries()) {
const pointNodeRef = new PointNodeRef({
z: this.selfChunkRef.z,
chunkCoord: this.selfChunkRef.chunkCoord,
pointNodeCoord: pointNodeCoord,
pointNodeId: pointNodeGen.id
})
this.selfChunkRef = args.selfChunkRef;
this.renderedPointNodes = new KeyedHashMap();
// args here will never change, and changing this will NOT force a rerender
constructor(args: {
pointNodeTexture: Pixi.Texture,
selfChunkRef: ChunkRef, // where am i in parent
chunkGen: ChunkGen, // what is in me
stateUpdaterQueue: [Function],
ticker: Pixi.Ticker
}) {
public renderedPointNodes: KeyedHashMap<PointNodeRef, RenderedPointNode>;
// public renderedNodes: HashMap<Vector2, Pixi.Graphics | Pixi.Sprite> = new HashMap();
public selfChunkRef: ChunkRef;
import { ChunkGen, ChunkGenConstants, ChunkRef, PointNodeRef } from "../data/GameState";
import { RenderedPointNode } from "./RenderedPointNode";
import { HashSet, KeyedHashMap } from "../lib/util/data_structures/hash";
import * as Pixi from "pixi.js";
import { RenderedChunkConstants } from "./RenderedChunk";
/**
* Usage:
* class RenderedChunk {
* constructor(stateUpdaterQueue) {
* this.nodes = 0...10.map(i => new RenderedPointNode({texture, new NodeRef(i), stateUpdaterQueue}))
* // this.nodes[0].render({ some, stuff })
* this.nodes[0] should listen to gameState.playerUI.selectedPointNode and allocatedPointNodes, and
* updating gameState.playerUI.selectedPointNode or gameState.playerSave.allocatedPointNodes or their
* parents should trigger queueing of the rerender
* or rather, rerendering
* }
* }
*/
import { DeepReadonly, UpdaterGeneratorType } from "../lib/util/misc";
import { GameState, PointNodeRef } from "../data/GameState";
export class RenderedPointNode {
public sprite: Pixi.Sprite;
public selfPointNodeRef: PointNodeRef; // which node we are
// local state
this.selfPointNodeRef = args.selfPointNodeRef;
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", () => {
});
}
} else {
}
// 2. manually mark necessary changed components?? or autodeduce based on dependencies??
// otherwise, set selected node to us
args.gameStateUpdater.playerUI.selectedPointNode.set(this.selfPointNodeRef);
// TODO(bowei): make the (0,0) node in a chunk shimmer? or somehow visually distinguish it
return this;
}
public onClick(args: { gameState: GameState, gameStateUpdater: UpdaterGeneratorType<GameState>, entityUpdaterQueue: [any] }) {
// 1. update the state(s)
// if we were already selected, allocate us
// TODO(bowei): this code block should be somewhere else????
args.gameStateUpdater.playerSave.allocatedPointNodeSet.update(set => {
set.put(this.selfPointNodeRef);
return set;
})
args.gameStateUpdater.playerSave.allocatedPointNodeHistory.update(history => {
history.push(this.selfPointNodeRef);
return history;
})
if (args.gameState.playerUI.selectedPointNode?.pointNodeId === this.selfPointNodeRef.pointNodeId) {
public setLocation(node: PointNodeRef = this.selfPointNodeRef) : this {
this.sprite.x = node.pointNodeCoord.x * RenderedChunkConstants.SPACING_PX;
this.sprite.y = node.pointNodeCoord.y * RenderedChunkConstants.SPACING_PX;
return this;
}
public animate(delta: number) : this {
args.ticker.add(this.animate.bind(this));
args.stateUpdaterQueue.push(this.onClick.bind(this));
// this.justClicked = true;
// do 3 things:
// 1. queue up the necessary state changes, to be executed on tick
// 2. mark other components that need rerendering based on state change
// 3. do all the rerenders
this.sprite.buttonMode = true;
this.sprite = new Pixi.Sprite(args.pointNodeTexture);
// args here will never change, and changing this will NOT force a rerender
constructor(args: {
selfPointNodeRef: PointNodeRef,
stateUpdaterQueue: [Function],
ticker: Pixi.Ticker
}) {
pointNodeTexture: Pixi.Texture,
// public justClicked: boolean = false;
}
}
}
} else {
}
}
}
//
// 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);
//
// // 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;
// }
// } else if (gameState.playerUI.selectedPointNode.pointNodeId === this.selfPointNodeRef.pointNodeId) {
public isSelected(gameState: DeepReadonly<GameState>): boolean {
}
public isAllocated(gameState: DeepReadonly<GameState>): boolean {
return gameState.playerSave.allocatedPointNodeSet.get(this.selfPointNodeRef)
}
return gameState.playerUI.selectedPointNode?.pointNodeId === this.selfPointNodeRef.pointNodeId;
this.sprite.tint = 0xFFFFFF;
private setTint(args: { isSelected: boolean, isAllocated: boolean }) {
if (args.isAllocated) {
this.sprite.tint = 0x00AAFF;
} else {
if (args.isSelected) {
this.sprite.tint = 0xBBBBBB;
// public rerenderFromState(gameState: DeepReadonly<GameState>) {
// this.rerender({
// isSelected: this.isSelected(gameState),
// isAllocated: this.isAllocated(gameState)
// })
// }
this.setTint(props);
return this;
public rerender(props: { isSelected: boolean, isAllocated: boolean }) : this {
// 3. do the rerenders (someone else handles this...)
let zLevel = new RenderedZLevel({
pointNodeTexture,
z: 0,
zLevelGen: (new ZLevelGenFactory({}).create({ seed: this.randomSeed, z: 0})),
stateUpdaterQueue: [] as any,
ticker: this.app.ticker
})
this.actionStage.addChild(zLevel.container);
zLevel.container.x = this.app.screen.width / 2;
zLevel.container.y = this.app.screen.height / 2;
// let zLevel = new RenderedZLevel({
// pointNodeTexture,
// z: 0,
// zLevelGen: (new ZLevelGenFactory({}).create({ seed: this.randomSeed, z: 0})),
// stateUpdaterQueue: [] as any,
// ticker: this.app.ticker
// })
// this.actionStage.addChild(zLevel.container);
// zLevel.container.x = this.app.screen.width / 2;
// zLevel.container.y = this.app.screen.height / 2;