基于cocos3.x复刻《猪了个猪》挪了个船:位置生成实现

(1)复刻《猪了个猪》挪了个船:位置生成实现cocos3.x

一、整体生成思路

生成一个可解的初始布局,再随机合法移动打乱。

流程:

  1. 清空网格与所有小船
  2. 按规则一艘一艘放满 16 艘小船(不会无解)
  3. 对这个合规布局做 80 步随机逆向移动

条件:

  1. 小船长款比为2:1

二、第一步:尝试放满 16 艘小船

函数:tryPlaceAllBoats()

1. 每艘小船生成

  • 随机方向:水平 / 垂直
  • 随机朝向:左/右 或 上/下
  • 遍历整个棋盘,寻找所有能放的位置

2. 位置必须满足 4 条规则

函数:isPositionValid()

保证关卡有解的关键,任何位置不满足就直接跳过:

  1. 格子为空、不越界 小船占 2 格,两格都必须是空的、在棋盘内。

  2. 不能和其他船面对面 同一条直线上,反向船不能相对。

  3. 不能出现 3 艘连续同向 水平/垂直方向,不能出现 3 艘方向一样、连在一起的船。

  4. 不能形成循环依赖 比如 A 堵 B、B 堵 C、C 堵 A,形成死锁。


三、第二步:位置打分

候选位置打分,让布局更紧凑。

函数:getPlacementScore()

三个维度:

  1. 中心优先 越靠近棋盘中心,分数越高。
  2. 紧凑优先 周围船越多,分数越高。
  3. 方向多样性优先 水平、垂直穿插摆放,分数更高(避免全横或全竖)。

前 5 高分位置里随机选一个


四、第三步:逆向打乱

函数:shuffleByReverseMoves(80)

打乱规则:

  1. 找出当前所有能移动的船
  2. 随机选一艘,让它移动一步
  3. 移动前依然检查:
    • 不能越界
    • 不能撞船
    • 不能违反面对面、连续三个方向一致、循环依赖
  4. 执行 80 步

五、小船生成完整流程

复制代码
开始生成
  ↓
清空网格、删除所有小船
  ↓
循环 16 次:
   随机方向+朝向
   遍历棋盘找所有合规位置
   位置打分(中心+紧凑+方向多样)
   选高分位置放置
   标记格子占用
  ↓
16 艘船全部放完(合规布局)
  ↓
执行 80 步随机合法移动(打乱)
  ↓
生成完成 → 进入游戏

六、核心代码

typescript 复制代码
import { _decorator, Component, Node, Prefab, instantiate } from "cc";
const { ccclass, property } = _decorator;

// 小船数据结构
interface Boat {
  id: number;
  node: Node;
  row: number;
  col: number;
  horizontal: boolean;
  dir: number; // 1右/下  -1左/上
}

// 网格数据系统
export class GridSystem {
  grid: (number | null)[][];
  rows: number;
  cols: number;

  constructor(rows: number, cols: number) {
    this.rows = rows;
    this.cols = cols;
    this.grid = Array.from({ length: rows }, () =>
      Array.from({ length: cols }, () => null),
    );
  }

  inBound(r: number, c: number) {
    return r >= 0 && c >= 0 && r < this.rows && c < this.cols;
  }

  isEmpty(r: number, c: number) {
    return this.inBound(r, c) && this.grid[r][c] === null;
  }

  place(id: number, cells: [number, number][]) {
    for (const [r, c] of cells) this.grid[r][c] = id;
  }

  clear(cells: [number, number][]) {
    for (const [r, c] of cells) this.grid[r][c] = null;
  }
}
typescript 复制代码
@ccclass("BoatController")
export class BoatController extends Component {
  @property(Prefab) boat_h_r: Prefab | null = null;
  @property(Prefab) boat_h_l: Prefab | null = null;
  @property(Prefab) boat_v_t: Prefab | null = null;
  @property(Prefab) boat_v_b: Prefab | null = null;

  grid!: GridSystem;
  boats: Boat[] = [];

  readonly CELL = 75;
  readonly rows = 10;
  readonly cols = 10;
  readonly maxBoats = 16; // 一共生成16艘船

  startX = 0;
  startY = 0;

  onLoad() {
    this.grid = new GridSystem(this.rows, this.cols);
    // 网格居中计算
    this.startX = -((this.cols - 1) * this.CELL) / 2;
    this.startY = ((this.rows - 1) * this.CELL) / 2;

    // 生成必解关卡
    this.generateSolvableLevel();
  }

  // =========================================================
  // 【主生成函数】
  // =========================================================
  generateSolvableLevel() {
    let attempts = 0;
    const maxAttempts = 20;

    while (attempts < maxAttempts) {
      attempts++;
      this.node.destroyAllChildren();
      this.boats = [];
      this.grid = new GridSystem(this.rows, this.cols);

      // 成功放满16艘船 → 打乱
      if (this.tryPlaceAllBoats()) {
        this.shuffleByReverseMoves(80);
        return;
      }
    }
  }

  // =========================================================
  // 【规则1】:放满16艘船
  // =========================================================
  tryPlaceAllBoats(): boolean {
    let id = 1;
    for (let i = 0; i < this.maxBoats; i++) {
      const horizontal = Math.random() > 0.5;
      const dir = Math.random() > 0.5 ? 1 : -1;
      const candidates: { r: number; c: number; score: number }[] = [];

      // 遍历所有位置找可放置点
      for (let r = 0; r < this.rows; r++) {
        for (let c = 0; c < this.cols; c++) {
          if (!this.isPositionValid(r, c, horizontal, dir)) continue;
          const score = this.getPlacementScore(r, c, horizontal);
          candidates.push({ r, c, score });
        }
      }

      if (candidates.length === 0) return false;

      // 最优位置随机选择
      candidates.sort((a, b) => b.score - a.score);
      const topCount = Math.min(5, candidates.length);
      const pick = candidates[Math.floor(Math.random() * topCount)];

      // 放置船
      const cells = this.getBoatCells(pick.r, pick.c, horizontal);
      this.grid.place(id, cells);
      const boat = this.createBoat(id, pick.r, pick.c, horizontal, dir);
      this.boats.push(boat);
      id++;
    }
    return true;
  }

  // =========================================================
  // 【规则2】:位置合法性检查
  // =========================================================
  isPositionValid(
    r: number,
    c: number,
    horizontal: boolean,
    dir: number,
  ): boolean {
    const cells = this.getBoatCells(r, c, horizontal);
    // 1. 格子为空 + 不越界
    for (const [cr, cc] of cells) {
      if (!this.grid.inBound(cr, cc) || !this.grid.isEmpty(cr, cc))
        return false;
    }
    // 2. 禁止面对面
    if (this.hasOppositeOnSameLine(r, c, horizontal, dir)) return false;

    // 临时放置,检查3连+循环依赖
    this.grid.place(-1, cells);
    const tempBoat: Boat = {
      id: -1,
      node: null!,
      row: r,
      col: c,
      horizontal,
      dir,
    };
    this.boats.push(tempBoat);

    // 3. 禁止连续三搜船方向一致同向  4. 禁止循环依赖
    const violates =
      this.hasConsecutiveSameDirection() || this.hasCyclicDependency();

    this.boats.pop();
    this.grid.clear(cells);
    return !violates;
  }

  // =========================================================
  // 【规则3】:逆向打乱80步(保持可解)
  // =========================================================
  shuffleByReverseMoves(steps: number) {
    for (let i = 0; i < steps; i++) {
      const movable: { boat: Boat; dr: number; dc: number }[] = [];

      for (const b of this.boats) {
        const dr = b.horizontal ? 0 : b.dir;
        const dc = b.horizontal ? b.dir : 0;
        if (
          this.canMoveBoat(b, dr, dc) &&
          !this.wouldCreateOpposite(b, b.row + dr, b.col + dc)
        ) {
          if (!this.wouldViolateOnMove(b, b.row + dr, b.col + dc)) {
            movable.push({ boat: b, dr, dc });
          }
        }
      }

      if (movable.length === 0) continue;
      const pick = movable[Math.floor(Math.random() * movable.length)];
      this.applyMove(pick.boat, pick.dr, pick.dc);
    }
  }

  // =========================================================
  // 工具函数:船占2格
  // =========================================================
  getBoatCells(r: number, c: number, horizontal: boolean): [number, number][] {
    return horizontal
      ? [
          [r, c],
          [r, c + 1],
        ]
      : [
          [r, c],
          [r + 1, c],
        ];
  }

  // =========================================================
  // 创建船节点
  // =========================================================
  createBoat(
    id: number,
    r: number,
    c: number,
    horizontal: boolean,
    dir: number,
  ): Boat {
    let prefab: Prefab | null = null;
    if (horizontal) prefab = dir > 0 ? this.boat_h_r : this.boat_h_l;
    else prefab = dir > 0 ? this.boat_v_b : this.boat_v_t;

    const node = instantiate(prefab!);
    this.node.addChild(node);

    const x = horizontal
      ? this.startX + (c + 0.5) * this.CELL
      : this.startX + c * this.CELL;
    const y = horizontal
      ? this.startY - r * this.CELL
      : this.startY - (r + 0.5) * this.CELL;
    node.setPosition(x, y);

    return { id, node, row: r, col: c, horizontal, dir };
  }

  // =========================================================
  // 移动相关(生成时需要)
  // =========================================================
  canMoveBoat(boat: Boat, dr: number, dc: number): boolean {
    const newCells = this.getBoatCells(
      boat.row + dr,
      boat.col + dc,
      boat.horizontal,
    );
    for (const [r, c] of newCells) {
      if (!this.grid.inBound(r, c)) continue;
      const id = this.grid.grid[r][c];
      if (id !== null && id !== boat.id) return false;
    }
    return true;
  }

  applyMove(boat: Boat, dr: number, dc: number) {
    const oldCells = this.getBoatCells(boat.row, boat.col, boat.horizontal);
    this.grid.clear(oldCells.filter(([r, c]) => this.grid.inBound(r, c)));

    boat.row += dr;
    boat.col += dc;
    const newCells = this.getBoatCells(boat.row, boat.col, boat.horizontal);
    const inBoundNew = newCells.filter(([r, c]) => this.grid.inBound(r, c));
    if (inBoundNew.length > 0) this.grid.place(boat.id, inBoundNew);

    const x = boat.horizontal
      ? this.startX + (boat.col + 0.5) * this.CELL
      : this.startX + boat.col * this.CELL;
    const y = boat.horizontal
      ? this.startY - boat.row * this.CELL
      : this.startY - (boat.row + 0.5) * this.CELL;
    boat.node.setPosition(x, y);
  }

  wouldViolateOnMove(boat: Boat, nr: number, nc: number): boolean {
    // 省略...
    return false;
  }

  // =========================================================
  // 规则检查:面对面 / 连续三搜船方向一致 / 循环依赖
  // =========================================================
  hasOppositeOnSameLine(
    r: number,
    c: number,
    h: boolean,
    dir: number,
  ): boolean {
    // 省略...
    // 同线反向相对检查
    return false;
  }
  // 省略...
  wouldCreateOpposite(boat: Boat, r: number, c: number): boolean {
    return false;
  }
  // 省略...
  hasConsecutiveSameDirection(): boolean {
    return false;
  }
  // 省略...
  hasCyclicDependency(): boolean {
    return false;
  }

  // =========================================================
  // 位置评分:中心 + 紧凑 + 多样
  // =========================================================
  getPlacementScore(r: number, c: number, horizontal: boolean): number {
    // 省略...
    return 10; // 简化评分,不影响生成
  }
}
相关推荐
青木_JS1 小时前
qiankun 子应用重开后仍显示旧数据?问题出在模块顶层的 useStore()
前端
货拉拉技术1 小时前
面向 Agent Skill 的 CLI/SSO 鉴权体系:安全、无感、可追溯
前端·agent
ssshooter2 小时前
为什么父元素的高度不会包含子元素的 margin?
前端·javascript·面试
静Yu2 小时前
从一个九宫格素材小程序,看轻量工具产品该如何优化体验
前端·微信小程序
程序员黑豆2 小时前
AI全栈开发之Java:第一个Java程序
前端·后端·ai编程
小Q的编程笔记2 小时前
Pump.fun 的核心是什么?用 300 行 Solidity 实现 Bonding Curve 与自动 LP 销毁
前端·后端·智能合约
卷帘依旧2 小时前
React Fiber机制
前端
卷帘依旧2 小时前
JavaScript 判断页面加载完成的多种场景
前端
光影少年3 小时前
React 项目常见优化方案
前端·react.js·前端框架