- Remove redundant `GameFlowScene` and `PlaceholderEncounterScene` - Introduce `SceneKey` enum for type-safe scene management - Clean up unused imports and configuration references in existing scenes - Standardize scene navigation using `SceneKey`
294 lines
7.9 KiB
TypeScript
294 lines
7.9 KiB
TypeScript
import { ReactiveScene } from "boardgame-phaser";
|
|
import { createButton } from "@/utils/createButton";
|
|
import { GRID_CONFIG, ITEM_COLORS } from "@/config";
|
|
import {
|
|
createGridInventory,
|
|
placeItem,
|
|
getAdjacentItems,
|
|
parseShapeString,
|
|
data,
|
|
type GridInventory,
|
|
type InventoryItem,
|
|
type GameItemMeta,
|
|
} from "boardgame-core/samples/slay-the-spire-like";
|
|
import { SceneKey } from "./types";
|
|
|
|
export class GridViewerScene extends ReactiveScene {
|
|
private inventory: GridInventory<GameItemMeta>;
|
|
private gridOffsetX = 0;
|
|
private gridOffsetY = 0;
|
|
|
|
constructor() {
|
|
super("GridViewerScene");
|
|
this.inventory = createGridInventory<GameItemMeta>(
|
|
GRID_CONFIG.WIDTH,
|
|
GRID_CONFIG.HEIGHT,
|
|
);
|
|
}
|
|
|
|
create(): void {
|
|
super.create();
|
|
const { width, height } = this.scale;
|
|
this.gridOffsetX =
|
|
(width - GRID_CONFIG.WIDTH * GRID_CONFIG.VIEWER_CELL_SIZE) / 2;
|
|
this.gridOffsetY =
|
|
(height - GRID_CONFIG.HEIGHT * GRID_CONFIG.VIEWER_CELL_SIZE) / 2 + 20;
|
|
|
|
this.placeSampleItems();
|
|
this.drawGrid();
|
|
this.drawItems();
|
|
|
|
this.add
|
|
.text(width / 2, 30, "Grid Inventory Viewer (4x6)", {
|
|
fontSize: "24px",
|
|
color: "#ffffff",
|
|
fontStyle: "bold",
|
|
})
|
|
.setOrigin(0.5);
|
|
|
|
this.createControls();
|
|
|
|
this.add
|
|
.text(width / 2, height - 40, "Hover over cells to see item details", {
|
|
fontSize: "14px",
|
|
color: "#aaaaaa",
|
|
})
|
|
.setOrigin(0.5);
|
|
}
|
|
|
|
private placeSampleItems(): void {
|
|
const items = data.desert.getItems();
|
|
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 },
|
|
];
|
|
|
|
for (const { index, x, y } of sampleItems) {
|
|
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: { itemData, shape },
|
|
};
|
|
placeItem(this.inventory, item);
|
|
}
|
|
}
|
|
|
|
private drawGrid(): void {
|
|
const graphics = this.add.graphics();
|
|
|
|
for (let y = 0; y < GRID_CONFIG.HEIGHT; y++) {
|
|
for (let x = 0; x < GRID_CONFIG.WIDTH; x++) {
|
|
const px = this.gridOffsetX + x * GRID_CONFIG.VIEWER_CELL_SIZE;
|
|
const py = this.gridOffsetY + y * GRID_CONFIG.VIEWER_CELL_SIZE;
|
|
|
|
const isOccupied = this.inventory.occupiedCells.has(`${x},${y}`);
|
|
const color = isOccupied ? 0x334455 : 0x222233;
|
|
|
|
graphics.fillStyle(color);
|
|
graphics.fillRect(
|
|
px + 1,
|
|
py + 1,
|
|
GRID_CONFIG.VIEWER_CELL_SIZE - 2,
|
|
GRID_CONFIG.VIEWER_CELL_SIZE - 2,
|
|
);
|
|
graphics.lineStyle(1, 0x555577);
|
|
graphics.strokeRect(
|
|
px,
|
|
py,
|
|
GRID_CONFIG.VIEWER_CELL_SIZE,
|
|
GRID_CONFIG.VIEWER_CELL_SIZE,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private drawItems(): void {
|
|
for (const [itemId, item] of this.inventory.items) {
|
|
const shape = item.shape;
|
|
const transform = item.transform;
|
|
|
|
const cells: { x: number; y: number }[] = [];
|
|
for (let y = 0; y < shape.height; y++) {
|
|
for (let x = 0; x < shape.width; x++) {
|
|
if (shape.grid[y]?.[x]) {
|
|
const finalX = x + transform.offset.x;
|
|
const finalY = y + transform.offset.y;
|
|
cells.push({ x: finalX, y: finalY });
|
|
}
|
|
}
|
|
}
|
|
|
|
const itemColor = this.getItemColor(itemId);
|
|
const graphics = this.add.graphics();
|
|
|
|
for (const cell of cells) {
|
|
const px = this.gridOffsetX + cell.x * GRID_CONFIG.VIEWER_CELL_SIZE;
|
|
const py = this.gridOffsetY + cell.y * GRID_CONFIG.VIEWER_CELL_SIZE;
|
|
|
|
graphics.fillStyle(itemColor);
|
|
graphics.fillRect(
|
|
px + 2,
|
|
py + 2,
|
|
GRID_CONFIG.VIEWER_CELL_SIZE - 4,
|
|
GRID_CONFIG.VIEWER_CELL_SIZE - 4,
|
|
);
|
|
}
|
|
|
|
if (cells.length > 0) {
|
|
const firstCell = cells[0];
|
|
const px =
|
|
this.gridOffsetX + firstCell.x * GRID_CONFIG.VIEWER_CELL_SIZE;
|
|
const py =
|
|
this.gridOffsetY + firstCell.y * GRID_CONFIG.VIEWER_CELL_SIZE;
|
|
const itemName = item.meta?.itemData.name ?? item.id;
|
|
|
|
this.add
|
|
.text(
|
|
px + GRID_CONFIG.VIEWER_CELL_SIZE / 2,
|
|
py + GRID_CONFIG.VIEWER_CELL_SIZE / 2,
|
|
itemName,
|
|
{
|
|
fontSize: "11px",
|
|
color: "#ffffff",
|
|
fontStyle: "bold",
|
|
},
|
|
)
|
|
.setOrigin(0.5);
|
|
}
|
|
|
|
const adjacent = getAdjacentItems(this.inventory, itemId);
|
|
if (adjacent.size > 0) {
|
|
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 * GRID_CONFIG.VIEWER_CELL_SIZE;
|
|
const py =
|
|
this.gridOffsetY + firstCell.y * GRID_CONFIG.VIEWER_CELL_SIZE - 20;
|
|
|
|
this.add
|
|
.text(
|
|
px + GRID_CONFIG.VIEWER_CELL_SIZE / 2,
|
|
py,
|
|
`邻接: ${adjacentNames}`,
|
|
{
|
|
fontSize: "10px",
|
|
color: "#ffff88",
|
|
},
|
|
)
|
|
.setOrigin(0.5);
|
|
}
|
|
}
|
|
}
|
|
|
|
private getItemColor(itemId: string): number {
|
|
const hash = itemId.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0);
|
|
return ITEM_COLORS[hash % ITEM_COLORS.length];
|
|
}
|
|
|
|
private createControls(): void {
|
|
const { width, height } = this.scale;
|
|
|
|
createButton({
|
|
scene: this,
|
|
label: "返回菜单",
|
|
x: 100,
|
|
y: 40,
|
|
onClick: async () => {
|
|
await this.sceneController.launch(SceneKey.IndexScene);
|
|
},
|
|
});
|
|
|
|
createButton({
|
|
scene: this,
|
|
label: "清空",
|
|
x: width - 260,
|
|
y: 40,
|
|
onClick: async () => {
|
|
this.inventory = createGridInventory<GameItemMeta>(
|
|
GRID_CONFIG.WIDTH,
|
|
GRID_CONFIG.HEIGHT,
|
|
);
|
|
await this.sceneController.restart();
|
|
},
|
|
});
|
|
|
|
createButton({
|
|
scene: this,
|
|
label: "随机填充",
|
|
x: width - 130,
|
|
y: 40,
|
|
onClick: async () => {
|
|
this.randomFill();
|
|
await this.sceneController.restart();
|
|
},
|
|
});
|
|
}
|
|
|
|
private randomFill(): void {
|
|
this.inventory = createGridInventory<GameItemMeta>(
|
|
GRID_CONFIG.WIDTH,
|
|
GRID_CONFIG.HEIGHT,
|
|
);
|
|
const items = data.desert.getItems();
|
|
|
|
let itemIndex = 0;
|
|
for (let y = 0; y < GRID_CONFIG.HEIGHT && itemIndex < items.length; y++) {
|
|
for (let x = 0; x < GRID_CONFIG.WIDTH && itemIndex < items.length; x++) {
|
|
const itemData = items[itemIndex];
|
|
const shape = parseShapeString(itemData.shape);
|
|
|
|
const occupiedCells = new Set<string>();
|
|
for (let sy = 0; sy < shape.height; sy++) {
|
|
for (let sx = 0; sx < shape.width; sx++) {
|
|
if (shape.grid[sy]?.[sx]) {
|
|
occupiedCells.add(`${sx + x},${sy + y}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
let valid = true;
|
|
for (const cell of occupiedCells) {
|
|
const [cx, cy] = cell.split(",").map(Number);
|
|
if (
|
|
cx >= GRID_CONFIG.WIDTH ||
|
|
cy >= GRID_CONFIG.HEIGHT ||
|
|
this.inventory.occupiedCells.has(cell as `${number},${number}`)
|
|
) {
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (valid) {
|
|
const item: InventoryItem<GameItemMeta> = {
|
|
id: `item-${itemIndex}`,
|
|
shape,
|
|
transform: {
|
|
offset: { x, y },
|
|
rotation: 0,
|
|
flipX: false,
|
|
flipY: false,
|
|
},
|
|
meta: { itemData, shape },
|
|
};
|
|
placeItem(this.inventory, item);
|
|
itemIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|