import { Node, Prefab, instantiate, Sprite, SpriteFrame, UITransform, Vec3, } from "cc"; import { BoardShapes, BoardShapeName } from "./BoardShape"; export interface LevelConfig { rows?: number; cols?: number; blockSize?: number; tileTypes?: number; currentShape?: BoardShapeName; isTutorial?: boolean; fixedBoard?: number[][]; } export class LevelBuilder { public board: number[][] = []; public blockNodes: (Node | null)[][] = []; public rows: number = 0; public cols: number = 0; private node: Node; private blockPrefab: Prefab; private spriteFrames: SpriteFrame[] = []; private blockSize: number = 100; private tileTypes: number = 5; public currentShape: BoardShapeName = "rectangle"; private fixedBoard: number[][] | null = null; constructor( node: Node, blockPrefab: Prefab, spriteFrames: SpriteFrame[], config?: LevelConfig, ) { this.node = node; this.blockPrefab = blockPrefab; this.spriteFrames = spriteFrames; if (config) { this.blockSize = config.blockSize ?? this.blockSize; this.tileTypes = config.tileTypes ?? this.tileTypes; if (config.currentShape) this.currentShape = config.currentShape; if (config.fixedBoard) this.fixedBoard = config.fixedBoard; } } public buildBoard(): void { if (this.fixedBoard) { this.buildFixedBoard(); return; } const shapeTemplate = BoardShapes[this.currentShape]; if (!shapeTemplate) throw new Error(`Shape "${this.currentShape}" 未定义`); const maxCols = Math.max(...shapeTemplate.map((row) => row.length)); const rows = shapeTemplate.length; // 自动补齐矩形 const fixedTemplate: number[][] = []; for (let r = 0; r < rows; r++) { fixedTemplate[r] = []; for (let c = 0; c < maxCols; c++) { fixedTemplate[r][c] = shapeTemplate[r][c] ?? 0; } } // 收集可用格子 const validCells = []; for (let r = 0; r < rows; r++) { for (let c = 0; c < maxCols; c++) { if (fixedTemplate[r][c] === 1) validCells.push({ r, c }); } } if (validCells.length % 2 !== 0) throw new Error("有效格子数量必须为偶数"); // 随机生成配对 let tiles: number[] = []; for (let i = 0; i < validCells.length / 2; i++) { const id = i % this.tileTypes; tiles.push(id, id); } for (let i = tiles.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [tiles[i], tiles[j]] = [tiles[j], tiles[i]]; } let tileIndex = 0; this.board = []; this.blockNodes = []; this.rows = rows; this.cols = maxCols; for (let r = 0; r < rows; r++) { this.board[r] = []; this.blockNodes[r] = []; for (let c = 0; c < maxCols; c++) { if (fixedTemplate[r][c] === 1) { const tileId = tiles[tileIndex++]; this.board[r][c] = tileId; const block = this.createNormalBlock(tileId); block.setPosition(this.getPos(r, c)); this.node.addChild(block); this.blockNodes[r][c] = block; } else if (fixedTemplate[r][c] === -1) { this.board[r][c] = -1; this.blockNodes[r][c] = null; } else { this.board[r][c] = -2; const stone = this.createStoneBlock(); stone.setPosition(this.getPos(r, c)); this.node.addChild(stone); this.blockNodes[r][c] = stone; } } } } private buildFixedBoard() { this.rows = this.fixedBoard.length; this.cols = this.fixedBoard[0].length; this.board = []; this.blockNodes = []; for (let r = 0; r < this.rows; r++) { this.board[r] = []; this.blockNodes[r] = []; for (let c = 0; c < this.cols; c++) { const val = this.fixedBoard[r][c]; this.board[r][c] = val; if (val >= 0) { const block = this.createNormalBlock(val); block.setPosition(this.getPos(r, c)); this.node.addChild(block); this.blockNodes[r][c] = block; } else if (val === -1) { this.blockNodes[r][c] = null; } else if (val === -2) { const stone = this.createStoneBlock(); stone.setPosition(this.getPos(r, c)); this.node.addChild(stone); this.blockNodes[r][c] = stone; } } } } private createNormalBlock(tileId: number) { const block = instantiate(this.blockPrefab); const bgNode = block.getChildByName("Bg"); const bgUI = bgNode.getComponent(UITransform) ?? bgNode.addComponent(UITransform); bgUI.setContentSize(this.blockSize, this.blockSize); const iconNode = block.getChildByName("Icon"); const iconUI = iconNode.getComponent(UITransform) ?? iconNode.addComponent(UITransform); const iconUI_sprite = iconNode.getComponent(Sprite)!; iconUI_sprite.spriteFrame = this.spriteFrames[tileId]; iconUI.setContentSize(this.blockSize * 0.7, this.blockSize * 0.7); iconNode.setPosition(0, 0, 0); return block; } private createStoneBlock() { const stone = instantiate(this.blockPrefab); const bgNode = stone.getChildByName("Bg"); const bgUI = bgNode.getComponent(UITransform) ?? bgNode.addComponent(UITransform); bgUI.setContentSize(this.blockSize, this.blockSize); const iconNode = stone.getChildByName("Icon"); iconNode.active = false; return stone; } private getPos(row: number, col: number): Vec3 { const x = col * this.blockSize - (this.cols * this.blockSize) / 2 + this.blockSize / 2; const y = (this.rows * this.blockSize) / 2 - row * this.blockSize - this.blockSize / 2; return new Vec3(x, y, 0); } /** * 新建 UI 节点,并设置类型和位置 */ public createBlockNode(type: number, r: number, c: number): Node { let block: Node; if (type >= 0) { // 普通方块 block = this.createNormalBlock(type); } else if (type === -2) { // 石头障碍 block = this.createStoneBlock(); } else { // 空格不需要节点 return null!; } block.setPosition(this.getPos(r, c)); this.node.addChild(block); return block; } /** * 更新节点的贴图/颜色/数字等显示 */ public setBlockType(node: Node, type: number): void { if (!node) return; const iconNode = node.getChildByName("Icon"); if (!iconNode) return; if (type >= 0) { // 普通方块 iconNode.active = true; const iconUI = iconNode.getComponent(UITransform) ?? iconNode.addComponent(UITransform); const iconUI_sprite = iconNode.getComponent(Sprite)!; iconUI_sprite.spriteFrame = this.spriteFrames[type]; iconUI.setContentSize(this.blockSize * 0.7, this.blockSize * 0.7); iconNode.setPosition(0, 0, 0); } else if (type === -2) { // 石头 iconNode.active = false; } else { // 空格 iconNode.active = false; } } /** * 更新节点的坐标位置(UI 上) */ public setBlockPosition(node: Node, r: number, c: number): void { if (!node) return; node.setPosition(this.getPos(r, c)); } /** * 销毁节点或隐藏 */ public destroyBlockNode(node: Node): void { if (!node) return; node.destroy(); // 如果想复用可改为 node.active = false } }