- 全部代码重写,改为拖拽合成(drag-and-drop merge) - 拖拽方块到相邻同等级方块上完成合成 - 拖到空格移动,拖到无效位置弹回 - Cocos Creator 3.8.8 全面适配 - 移除 GameManager.ts → 改为 GameController.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
209 lines
5.9 KiB
TypeScript
209 lines
5.9 KiB
TypeScript
/**
|
||
* 棋盘 — 8×12 网格管理、生成、移动、合并
|
||
*/
|
||
import { _decorator, Component, Node, UITransform, Color, Sprite, Vec3, v3 } from 'cc';
|
||
import { ROWS, COLS, CELL, PAD, MAX_LEVEL, MERGE_BASE_SCORE } from './BlockConfig';
|
||
import { Block } from './Block';
|
||
|
||
const { ccclass } = _decorator;
|
||
|
||
@ccclass('GameBoard')
|
||
export class GameBoard extends Component {
|
||
private grid: (Block | null)[][] = [];
|
||
private originX = 0;
|
||
private originY = 0;
|
||
|
||
/** 合并得分回调 */
|
||
onScore: ((pts: number) => void) | null = null;
|
||
|
||
// ============================================================
|
||
// 初始化
|
||
// ============================================================
|
||
|
||
onLoad() {
|
||
const w = COLS * (CELL + PAD) + PAD;
|
||
const h = ROWS * (CELL + PAD) + PAD;
|
||
this.originX = -w / 2 + PAD;
|
||
this.originY = -h / 2 + PAD;
|
||
|
||
this.initGrid();
|
||
this.drawBg(w, h);
|
||
}
|
||
|
||
private initGrid() {
|
||
this.grid = [];
|
||
for (let r = 0; r < ROWS; r++) {
|
||
this.grid[r] = [];
|
||
for (let c = 0; c < COLS; c++) {
|
||
this.grid[r][c] = null;
|
||
this.drawCell(r, c);
|
||
}
|
||
}
|
||
}
|
||
|
||
/** 每个格子的半透明底纹 */
|
||
private drawCell(r: number, c: number) {
|
||
const n = new Node('cell');
|
||
const s = n.addComponent(Sprite);
|
||
s.type = Sprite.Type.SIMPLE;
|
||
s.color = new Color(60, 60, 60, 80);
|
||
n.addComponent(UITransform).setContentSize(CELL, CELL);
|
||
n.setPosition(this.toWorld(r, c));
|
||
this.node.addChild(n);
|
||
}
|
||
|
||
/** 棋盘背景面板 */
|
||
private drawBg(w: number, h: number) {
|
||
const bg = new Node('bg');
|
||
const s = bg.addComponent(Sprite);
|
||
s.type = Sprite.Type.SIMPLE;
|
||
s.color = new Color(28, 28, 40, 220);
|
||
bg.addComponent(UITransform).setContentSize(w + 16, h + 16);
|
||
bg.setPosition(0, 0, -1);
|
||
this.node.addChild(bg);
|
||
}
|
||
|
||
// ============================================================
|
||
// 坐标
|
||
// ============================================================
|
||
|
||
toWorld(r: number, c: number): Vec3 {
|
||
return v3(
|
||
this.originX + c * (CELL + PAD) + CELL / 2,
|
||
this.originY + r * (CELL + PAD) + CELL / 2,
|
||
0,
|
||
);
|
||
}
|
||
|
||
/** 世界坐标 → 网格坐标,超出范围返回 null */
|
||
toGrid(x: number, y: number): { r: number; c: number } | null {
|
||
const c = Math.round((x - this.originX) / (CELL + PAD));
|
||
const r = Math.round((y - this.originY) / (CELL + PAD));
|
||
if (r < 0 || r >= ROWS || c < 0 || c >= COLS) return null;
|
||
return { r, c };
|
||
}
|
||
|
||
// ============================================================
|
||
// 查询
|
||
// ============================================================
|
||
|
||
isEmpty(r: number, c: number) {
|
||
return r >= 0 && r < ROWS && c >= 0 && c < COLS && !this.grid[r][c];
|
||
}
|
||
|
||
get(r: number, c: number): Block | null {
|
||
if (r < 0 || r >= ROWS || c < 0 || c >= COLS) return null;
|
||
return this.grid[r][c];
|
||
}
|
||
|
||
/** 四方向邻居 */
|
||
neighbors(r: number, c: number): Block[] {
|
||
const out: Block[] = [];
|
||
for (const [dr, dc] of [[-1, 0], [1, 0], [0, -1], [0, 1]]) {
|
||
const b = this.get(r + dr, c + dc);
|
||
if (b) out.push(b);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
// ============================================================
|
||
// 方块操作
|
||
// ============================================================
|
||
|
||
/** 在随机空位生成新方块 */
|
||
spawn(level: number): Block | null {
|
||
const empty: { r: number; c: number }[] = [];
|
||
for (let r = 0; r < ROWS; r++)
|
||
for (let c = 0; c < COLS; c++)
|
||
if (this.isEmpty(r, c)) empty.push({ r, c });
|
||
|
||
if (!empty.length) return null;
|
||
const g = empty[Math.floor(Math.random() * empty.length)];
|
||
return this._create(g.r, g.c, level);
|
||
}
|
||
|
||
/** 在指定位置创建方块 */
|
||
_create(r: number, c: number, level: number): Block {
|
||
const n = new Node('block');
|
||
const b = n.addComponent(Block);
|
||
b.init(r, c, level);
|
||
n.setPosition(this.toWorld(r, c));
|
||
this.node.addChild(n);
|
||
this.grid[r][c] = b;
|
||
return b;
|
||
}
|
||
|
||
/** 移动方块到新位置(目标必须为空) */
|
||
move(block: Block, nr: number, nc: number): boolean {
|
||
if (!this.isEmpty(nr, nc)) return false;
|
||
this.grid[block.row][block.col] = null;
|
||
block.row = nr;
|
||
block.col = nc;
|
||
this.grid[nr][nc] = block;
|
||
block.node.setPosition(this.toWorld(nr, nc));
|
||
return true;
|
||
}
|
||
|
||
/** 移除方块 */
|
||
remove(block: Block) {
|
||
this.grid[block.row][block.col] = null;
|
||
block.node.destroy();
|
||
}
|
||
|
||
// ============================================================
|
||
// 合并
|
||
// ============================================================
|
||
|
||
/** 尝试合并 a 和 b(必须相邻同等级) */
|
||
tryMerge(a: Block, b: Block): boolean {
|
||
if (a.level !== b.level || a.isMerging || b.isMerging) return false;
|
||
if (Math.abs(a.row - b.row) + Math.abs(a.col - b.col) !== 1) return false;
|
||
this._doMerge(a, b);
|
||
return true;
|
||
}
|
||
|
||
/** 触发某个方块周围的合并检测 */
|
||
checkMerge(block: Block) {
|
||
if (block.isMerging) return;
|
||
for (const nb of this.neighbors(block.row, block.col)) {
|
||
if (nb.level === block.level && !nb.isMerging) {
|
||
this._doMerge(block, nb);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
private _doMerge(a: Block, b: Block) {
|
||
a.isMerging = true;
|
||
b.isMerging = true;
|
||
|
||
const lv = Math.min(a.level + 1, MAX_LEVEL);
|
||
this.remove(b);
|
||
a.upgradeTo(lv);
|
||
|
||
if (this.onScore) this.onScore(lv * MERGE_BASE_SCORE);
|
||
|
||
// 链式合并
|
||
this.scheduleOnce(() => this.checkMerge(a), 0.25);
|
||
}
|
||
|
||
// ============================================================
|
||
// 全局
|
||
// ============================================================
|
||
|
||
isFull(): boolean {
|
||
for (let r = 0; r < ROWS; r++)
|
||
for (let c = 0; c < COLS; c++)
|
||
if (!this.grid[r][c]) return false;
|
||
return true;
|
||
}
|
||
|
||
clearAll() {
|
||
for (let r = 0; r < ROWS; r++)
|
||
for (let c = 0; c < COLS; c++) {
|
||
const b = this.grid[r][c];
|
||
if (b) { b.node.destroy(); this.grid[r][c] = null; }
|
||
}
|
||
}
|
||
}
|