diff --git a/packages/sts-like-viewer/src/scenes/GridViewerScene.ts b/packages/sts-like-viewer/src/scenes/GridViewerScene.ts index 30b580a..0e3e906 100644 --- a/packages/sts-like-viewer/src/scenes/GridViewerScene.ts +++ b/packages/sts-like-viewer/src/scenes/GridViewerScene.ts @@ -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; 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(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 = { 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(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(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(); 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 = { 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++; diff --git a/packages/sts-like-viewer/src/scenes/MapViewerScene.ts b/packages/sts-like-viewer/src/scenes/MapViewerScene.ts index e50b37b..d7113fc 100644 --- a/packages/sts-like-viewer/src/scenes/MapViewerScene.ts +++ b/packages/sts-like-viewer/src/scenes/MapViewerScene.ts @@ -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.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; diff --git a/packages/sts-like-viewer/src/scenes/PlaceholderEncounterScene.ts b/packages/sts-like-viewer/src/scenes/PlaceholderEncounterScene.ts index e2ed79e..12ada5a 100644 --- a/packages/sts-like-viewer/src/scenes/PlaceholderEncounterScene.ts +++ b/packages/sts-like-viewer/src/scenes/PlaceholderEncounterScene.ts @@ -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, diff --git a/packages/sts-like-viewer/src/scenes/ShapeViewerScene.ts b/packages/sts-like-viewer/src/scenes/ShapeViewerScene.ts index 21616a4..2ac9b72 100644 --- a/packages/sts-like-viewer/src/scenes/ShapeViewerScene.ts +++ b/packages/sts-like-viewer/src/scenes/ShapeViewerScene.ts @@ -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', diff --git a/packages/sts-like-viewer/src/state/gameState.ts b/packages/sts-like-viewer/src/state/gameState.ts index 135ee34..674a0e1 100644 --- a/packages/sts-like-viewer/src/state/gameState.ts +++ b/packages/sts-like-viewer/src/state/gameState.ts @@ -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 { - return mutableSignal(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(createRunState(map, starterItems)); } /** 获取当前遭遇数据(computed getter) */