refactor: update to new version

This commit is contained in:
hypercross 2026-04-17 17:29:13 +08:00
parent 706de4e33a
commit f7a6154c68
5 changed files with 49 additions and 76 deletions

View File

@ -4,14 +4,15 @@ import {
placeItem,
getAdjacentItems,
parseShapeString,
heroItemFighter1Data,
data,
type GridInventory,
type InventoryItem,
type GameItemMeta,
} from 'boardgame-core/samples/slay-the-spire-like';
import { ReactiveScene } from 'boardgame-phaser';
export class GridViewerScene extends ReactiveScene {
private inventory: GridInventory;
private inventory: GridInventory<GameItemMeta>;
private readonly CELL_SIZE = 60;
private readonly GRID_WIDTH = 6;
private readonly GRID_HEIGHT = 4;
@ -20,7 +21,7 @@ export class GridViewerScene extends ReactiveScene {
constructor() {
super('GridViewerScene');
this.inventory = createGridInventory(this.GRID_WIDTH, this.GRID_HEIGHT);
this.inventory = createGridInventory<GameItemMeta>(this.GRID_WIDTH, this.GRID_HEIGHT);
}
create(): void {
@ -29,26 +30,18 @@ export class GridViewerScene extends ReactiveScene {
this.gridOffsetX = (width - this.GRID_WIDTH * this.CELL_SIZE) / 2;
this.gridOffsetY = (height - this.GRID_HEIGHT * this.CELL_SIZE) / 2 + 20;
// Place some sample items
this.placeSampleItems();
// Draw grid
this.drawGrid();
// Draw items
this.drawItems();
// Title
this.add.text(width / 2, 30, 'Grid Inventory Viewer (4x6)', {
fontSize: '24px',
color: '#ffffff',
fontStyle: 'bold',
}).setOrigin(0.5);
// Controls
this.createControls();
// Info text
this.add.text(width / 2, height - 40, 'Hover over cells to see item details', {
fontSize: '14px',
color: '#aaaaaa',
@ -56,22 +49,23 @@ export class GridViewerScene extends ReactiveScene {
}
private placeSampleItems(): void {
// Place a few items from heroItemFighter1Data
const items = data.desert.items;
const sampleItems = [
{ index: 0, x: 0, y: 0 }, // 剑
{ index: 3, x: 3, y: 0 }, // 短刀
{ index: 6, x: 0, y: 2 }, // 盾
{ index: 12, x: 3, y: 2 }, // 披风
{ index: 0, x: 0, y: 0 },
{ index: 3, x: 3, y: 0 },
{ index: 6, x: 0, y: 2 },
{ index: 12, x: 3, y: 2 },
];
for (const { index, x, y } of sampleItems) {
const data = heroItemFighter1Data[index];
const shape = parseShapeString(data.shape);
const item: InventoryItem = {
if (index >= items.length) continue;
const itemData = items[index];
const shape = parseShapeString(itemData.shape);
const item: InventoryItem<GameItemMeta> = {
id: `item-${index}`,
shape,
transform: { offset: { x, y }, rotation: 0, flipX: false, flipY: false },
meta: { name: data.name, data },
meta: { itemData, shape },
};
placeItem(this.inventory, item);
}
@ -80,7 +74,6 @@ export class GridViewerScene extends ReactiveScene {
private drawGrid(): void {
const graphics = this.add.graphics();
// Draw cells
for (let y = 0; y < this.GRID_HEIGHT; y++) {
for (let x = 0; x < this.GRID_WIDTH; x++) {
const px = this.gridOffsetX + x * this.CELL_SIZE;
@ -102,7 +95,6 @@ export class GridViewerScene extends ReactiveScene {
const shape = item.shape;
const transform = item.transform;
// Get occupied cells
const cells: { x: number; y: number }[] = [];
for (let y = 0; y < shape.height; y++) {
for (let x = 0; x < shape.width; x++) {
@ -114,7 +106,6 @@ export class GridViewerScene extends ReactiveScene {
}
}
// Draw item cells with a unique color per item
const itemColor = this.getItemColor(itemId);
const graphics = this.add.graphics();
@ -126,12 +117,11 @@ export class GridViewerScene extends ReactiveScene {
graphics.fillRect(px + 2, py + 2, this.CELL_SIZE - 4, this.CELL_SIZE - 4);
}
// Item name label (at first cell)
if (cells.length > 0) {
const firstCell = cells[0];
const px = this.gridOffsetX + firstCell.x * this.CELL_SIZE;
const py = this.gridOffsetY + firstCell.y * this.CELL_SIZE;
const itemName = (item.meta?.name as string) ?? item.id;
const itemName = item.meta?.itemData.name ?? item.id;
this.add.text(px + this.CELL_SIZE / 2, py + this.CELL_SIZE / 2, itemName, {
fontSize: '11px',
@ -140,10 +130,9 @@ export class GridViewerScene extends ReactiveScene {
}).setOrigin(0.5);
}
// Adjacent items info
const adjacent = getAdjacentItems(this.inventory, itemId);
if (adjacent.size > 0) {
const adjacentNames = Array.from(adjacent.values()).map(i => (i.meta?.name as string) ?? i.id).join(', ');
const adjacentNames = Array.from(adjacent.values()).map(i => i.meta?.itemData.name ?? i.id).join(', ');
const firstCell = cells[0];
const px = this.gridOffsetX + firstCell.x * this.CELL_SIZE;
const py = this.gridOffsetY + firstCell.y * this.CELL_SIZE - 20;
@ -157,7 +146,6 @@ export class GridViewerScene extends ReactiveScene {
}
private getItemColor(itemId: string): number {
// Generate a consistent color based on item ID
const hash = itemId.split('').reduce((acc, c) => acc + c.charCodeAt(0), 0);
const colors = [0x4488cc, 0xcc8844, 0x44cc88, 0xcc4488, 0x8844cc, 0x44cccc];
return colors[hash % colors.length];
@ -166,18 +154,15 @@ export class GridViewerScene extends ReactiveScene {
private createControls(): void {
const { width, height } = this.scale;
// Back button
this.createButton('返回菜单', 100, 40, async () => {
await this.sceneController.launch('IndexScene');
});
// Clear button
this.createButton('清空', width - 260, 40, async () => {
this.inventory = createGridInventory(this.GRID_WIDTH, this.GRID_HEIGHT);
this.inventory = createGridInventory<GameItemMeta>(this.GRID_WIDTH, this.GRID_HEIGHT);
await this.sceneController.restart();
});
// Random fill button
this.createButton('随机填充', width - 130, 40, async () => {
this.randomFill();
await this.sceneController.restart();
@ -185,16 +170,15 @@ export class GridViewerScene extends ReactiveScene {
}
private randomFill(): void {
this.inventory = createGridInventory(this.GRID_WIDTH, this.GRID_HEIGHT);
this.inventory = createGridInventory<GameItemMeta>(this.GRID_WIDTH, this.GRID_HEIGHT);
const items = data.desert.items;
// Try to place random items
let itemIndex = 0;
for (let y = 0; y < this.GRID_HEIGHT && itemIndex < heroItemFighter1Data.length; y++) {
for (let x = 0; x < this.GRID_WIDTH && itemIndex < heroItemFighter1Data.length; x++) {
const data = heroItemFighter1Data[itemIndex];
const shape = parseShapeString(data.shape);
for (let y = 0; y < this.GRID_HEIGHT && itemIndex < items.length; y++) {
for (let x = 0; x < this.GRID_WIDTH && itemIndex < items.length; x++) {
const itemData = items[itemIndex];
const shape = parseShapeString(itemData.shape);
// Check if placement is valid
const occupiedCells = new Set<string>();
for (let sy = 0; sy < shape.height; sy++) {
for (let sx = 0; sx < shape.width; sx++) {
@ -204,22 +188,21 @@ export class GridViewerScene extends ReactiveScene {
}
}
// Simple check if any cell is out of bounds or occupied
let valid = true;
for (const cell of occupiedCells) {
const [cx, cy] = cell.split(',').map(Number);
if (cx >= this.GRID_WIDTH || cy >= this.GRID_HEIGHT || this.inventory.occupiedCells.has(cell)) {
if (cx >= this.GRID_WIDTH || cy >= this.GRID_HEIGHT || this.inventory.occupiedCells.has(cell as `${number},${number}`)) {
valid = false;
break;
}
}
if (valid) {
const item: InventoryItem = {
const item: InventoryItem<GameItemMeta> = {
id: `item-${itemIndex}`,
shape,
transform: { offset: { x, y }, rotation: 0, flipX: false, flipY: false },
meta: { name: data.name, data },
meta: { itemData, shape },
};
placeItem(this.inventory, item);
itemIndex++;

View File

@ -1,6 +1,7 @@
import Phaser from 'phaser';
import { ReactiveScene } from 'boardgame-phaser';
import { generatePointCrawlMap, type PointCrawlMap, type MapNode, MapNodeType } from 'boardgame-core/samples/slay-the-spire-like';
import { createRNG } from 'boardgame-core';
import { generatePointCrawlMap, data, type PointCrawlMap, type MapNode, MapNodeType } from 'boardgame-core/samples/slay-the-spire-like';
const NODE_COLORS: Record<MapNodeType, number> = {
[MapNodeType.Start]: 0x44aa44,
@ -143,7 +144,8 @@ export class MapViewerScene extends ReactiveScene {
}
private drawMap(): void {
this.map = generatePointCrawlMap(this.seed);
const rng = createRNG(this.seed);
this.map = generatePointCrawlMap(rng, data.desert.encounters);
const { width, height } = this.scale;

View File

@ -127,7 +127,7 @@ export class PlaceholderEncounterScene extends ReactiveScene {
// Item name
const first = cells[0];
const name = item.meta?.itemData?.name ?? item.id;
const name = item.meta?.itemData.name ?? item.id;
this.add.text(
this.gridX + first.x * this.CELL_SIZE + this.CELL_SIZE / 2,
this.gridY + first.y * this.CELL_SIZE + this.CELL_SIZE / 2,

View File

@ -1,14 +1,10 @@
import Phaser from 'phaser';
import { ReactiveScene } from 'boardgame-phaser';
import { parseShapeString, heroItemFighter1Data, type ParsedShape } from 'boardgame-core/samples/slay-the-spire-like';
import { parseShapeString, data, type ParsedShape } from 'boardgame-core/samples/slay-the-spire-like';
export class ShapeViewerScene extends ReactiveScene {
private readonly CELL_SIZE = 40;
private readonly ITEMS_PER_ROW = 4;
private currentIndex = 0;
private currentRotation = 0;
private currentFlipX = false;
private currentFlipY = false;
constructor() {
super('ShapeViewerScene');
@ -25,14 +21,12 @@ export class ShapeViewerScene extends ReactiveScene {
const { width, height } = this.scale;
// Title
this.add.text(width / 2, 30, 'Shape Viewer - Item Shapes', {
fontSize: '24px',
color: '#ffffff',
fontStyle: 'bold',
}).setOrigin(0.5);
// Draw all shapes in a grid
this.drawAllShapes();
}
@ -42,12 +36,11 @@ export class ShapeViewerScene extends ReactiveScene {
const spacingX = 220;
const spacingY = 140;
// Show first 12 items for clarity
const itemsToShow = heroItemFighter1Data.slice(0, 12);
const itemsToShow = data.desert.items.slice(0, 12);
for (let i = 0; i < itemsToShow.length; i++) {
const data = itemsToShow[i];
const shape = parseShapeString(data.shape);
const itemData = itemsToShow[i];
const shape = parseShapeString(itemData.shape);
const col = i % this.ITEMS_PER_ROW;
const row = Math.floor(i / this.ITEMS_PER_ROW);
@ -55,32 +48,28 @@ export class ShapeViewerScene extends ReactiveScene {
const x = 60 + col * spacingX;
const y = startY + row * spacingY;
this.drawSingleShape(x, y, data, shape);
this.drawSingleShape(x, y, itemData, shape);
}
}
private drawSingleShape(startX: number, startY: number, data: any, shape: ParsedShape): void {
private drawSingleShape(startX: number, startY: number, itemData: any, shape: ParsedShape): void {
const graphics = this.add.graphics();
// Draw shape background
const shapeWidth = shape.width * this.CELL_SIZE;
const shapeHeight = shape.height * this.CELL_SIZE;
// Title - item name
this.add.text(startX + shapeWidth / 2, startY - 20, data.name, {
this.add.text(startX + shapeWidth / 2, startY - 20, itemData.name, {
fontSize: '14px',
color: '#ffffff',
fontStyle: 'bold',
}).setOrigin(0.5);
// Draw shape cells
for (let y = 0; y < shape.height; y++) {
for (let x = 0; x < shape.width; x++) {
if (shape.grid[y]?.[x]) {
const px = startX + x * this.CELL_SIZE;
const py = startY + y * this.CELL_SIZE;
// Determine if this is the origin cell
const isOrigin = x === shape.originX && y === shape.originY;
const color = isOrigin ? 0x88cc44 : 0x4488cc;
@ -89,7 +78,6 @@ export class ShapeViewerScene extends ReactiveScene {
graphics.lineStyle(2, 0xffffff);
graphics.strokeRect(px, py, this.CELL_SIZE, this.CELL_SIZE);
// Mark origin with 'O'
if (isOrigin) {
this.add.text(px + this.CELL_SIZE / 2, py + this.CELL_SIZE / 2, 'O', {
fontSize: '16px',
@ -101,21 +89,18 @@ export class ShapeViewerScene extends ReactiveScene {
}
}
// Shape string
this.add.text(startX + shapeWidth / 2, startY + shapeHeight + 10, `形状: ${data.shape}`, {
this.add.text(startX + shapeWidth / 2, startY + shapeHeight + 10, `形状: ${itemData.shape}`, {
fontSize: '11px',
color: '#aaaaaa',
}).setOrigin(0.5);
// Type and cost
this.add.text(startX + shapeWidth / 2, startY + shapeHeight + 28,
`类型: ${data.type} | 费用: ${data.costCount} ${data.costType}`, {
`类型: ${itemData.type} | 费用: ${itemData.costCount} ${itemData.costType}`, {
fontSize: '11px',
color: '#cccccc',
}).setOrigin(0.5);
// Description
this.add.text(startX + shapeWidth / 2, startY + shapeHeight + 46, data.desc, {
this.add.text(startX + shapeWidth / 2, startY + shapeHeight + 46, itemData.desc, {
fontSize: '10px',
color: '#888888',
wordWrap: { width: shapeWidth },
@ -125,12 +110,10 @@ export class ShapeViewerScene extends ReactiveScene {
private createControls(): void {
const { width, height } = this.scale;
// Back button
this.createButton('返回菜单', 100, height - 40, async () => {
await this.sceneController.launch('IndexScene');
});
// Info text
this.add.text(width / 2, height - 40,
`Showing first 12 items | Green = Origin | Blue = Normal`, {
fontSize: '14px',

View File

@ -1,5 +1,5 @@
import { MutableSignal, mutableSignal } from 'boardgame-core';
import { createRunState, type RunState } from 'boardgame-core/samples/slay-the-spire-like';
import { MutableSignal, mutableSignal, createRNG } from 'boardgame-core';
import { createRunState, generatePointCrawlMap, data, type RunState, type ItemData } from 'boardgame-core/samples/slay-the-spire-like';
/**
* Signal
@ -8,7 +8,12 @@ import { createRunState, type RunState } from 'boardgame-core/samples/slay-the-s
* signal UI
*/
export function createGameState(seed?: number): MutableSignal<RunState> {
return mutableSignal<RunState>(createRunState(seed));
const actualSeed = seed ?? Date.now();
const rng = createRNG(actualSeed);
const encounters = data.desert.encounters;
const map = generatePointCrawlMap(rng, encounters);
const starterItems: ItemData[] = data.desert.items.slice(0, 3);
return mutableSignal<RunState>(createRunState(map, starterItems));
}
/** 获取当前遭遇数据computed getter */