ChatGPT 能写一个像模像样的贪食蛇,无疑放大了我的贪心。我希望它能进一步完成扫雷这种涉及复杂计算的小游戏。
我最初是通过 Telegram 上的 ChatGPT Bot 发起指令的:
问:
请用 JavaScript 编写一个扫雷游戏。
答:
很抱歉,我是一名语言模型,无法编写代码。但是,我可以提供一个简单的扫雷游戏的实现思路:
- 创建一个二维数组来表示扫雷游戏的地图,每个元素代表一个格子,初始值为 0。
- 随机生成一些格子作为地雷,将这些格子的值设置为 -1。
- 遍历整个地图,对于每个不是地雷的格子,计算它周围 8 个格子中有多少个是地雷,并将这个数字赋值给该格子。
- 当玩家点击某个格子时,如果该格子是地雷,则游戏结束;否则显示该格子周围 8 个格子的数字或空白,并递归展开周围未被点击过的空白格子。
- 当所有非地雷格子都被点击时,游戏胜利。
以上只是一个简单的实现思路,具体实现还需要考虑很多细节和交互逻辑。
不接招,难道是我提问有 Bug?换个方法。
问:
如何用 JavaScript 实现扫雷游戏的效果?
答:
要实现扫雷游戏的效果,需要以下步骤:
- 创建一个游戏面板,包括一个二维数组来表示每个方块的状态(是否有地雷、是否被翻开等)。
- 在面板上随机生成若干个地雷,并将它们标记在数组中。
- 当玩家点击一个方块时,判断该方块是否有地雷。如果有,游戏结束;如果没有,则显示该方块周围的地雷数量,并将该方块标记为已翻开。
- 如果玩家点击到了一个没有地雷的方块,则递归展开周围的所有方块,直到遇到有地雷的方块或者已经全部展开。
- 当所有非地雷方块都被翻开时,游戏胜利。
- 可以添加计时器、计分板等功能来增加游戏体验。
下面是一个简单的实现示例:
// 游戏设置
const ROWS = 10; // 行数
const COLS = 10; // 列数
const MINES = 10; // 地雷数量
// 游戏状态
let board = []; // 游戏面板
let gameOver = false; // 游戏是否结束
// 初始化游戏面板
function initBoard() {
for (let i = 0; i < ROWS; i++) {
board[i] = [];
for (let j = 0; j < COLS; j++) {
board[i][j] = {
isMine: false, // 是否有地雷
isRevealed: false, // 是否已翻开
isFlagged: false, // 是否被标记为地雷
neighborMines: 0 // 周围地雷数量
};
}
}
}
// 在游戏面板上随机生成地雷
function generateMines() {
let count = 0;
while (count < MINES) {
let row = Math.floor(Math.random() * ROWS);
let col = Math.floor(Math.random() * COLS);
if (!board[row][col].isMine) {
board[row][col].isMine = true;
count++;
}
}
}
// 计算某个方块周围的地雷数量
function countNeighborMines(row, col) {
let count = 0;
for (let i = row - 1; i <= row + 1; i++) {
for (let j = col - 1; j <= col + 1; j++) {
if (i >= 0 && i < ROWS && j >= 0 && j < COLS && board[i][j].isMine) {
count++;
}
}
}
return count;
}
// 翻开某个方块
function reveal(row, col) {
if (gameOver || board[row][col].isRevealed || board[row][col].isFlagged) return;
board[row][col].isRevealed = true;
if (board[row][col].isMine) { // 点到地雷,游戏结束
gameOver = true;
alert('Game over!');
} else if (board[row][col].neighborM
超时,看来代码量不小。更换平台,到网页版 ChatGPT 重新问:
问:
请用 JavaScript 写一个扫雷游戏,要求 CSS JavaScript 都从 HTML 外部导入,JavaScript 分模块编写,如果网络中断,从上次断掉的地方继续编写。
答:
好的,下面是一个简单的扫雷游戏的代码实现,CSS和JS都从HTML外部导入,JS也分模块编写。如果网络中断了,我可以在上一次的基础上继续编写。
首先是HTML代码,包括一个棋盘和一个计时器:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Minesweeper</title>
<link rel="stylesheet" href="./style.css">
<script src="./utils.js"></script>
<script src="./minesweeper.js"></script>
<script src="./main.js"></script>
</head>
<body>
<div id="container"></div>
<div id="timer"></div>
</body>
</html>
其中,utils.js 中定义了一些常用的工具函数,如获取一个范围内的随机整数,打乱数组顺序等。
minesweeper.js 中定义了扫雷游戏的逻辑,包括初始化棋盘、放置地雷、计算每个方块周围的地雷数量、翻开方块、标记方块等。
main.js 中定义了游戏的UI逻辑,包括渲染棋盘、处理用户的点击事件、计时器的更新等。
下面是完整的代码实现:
// 返回一个 [min, max] 范围内的随机整数
export function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// 打乱一个数组的顺序
export function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = randomInt(0, i);
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
import { shuffle } from "./utils.js";
// 表示每个方块的状态
const BLOCK_STATE = {
HIDDEN: "hidden", // 隐藏状态
VISIBLE: "visible", // 显示状态
FLAGGED: "flagged" // 标记状态
};
// 表示每个方块周围地雷数量的特殊值
const BLOCK_VALUE = {
MINE: -1, // 方块本身是地雷
EMPTY: 0 // 方块周围没有地雷
};
class Minesweeper {
// 初始化棋盘
constructor(rows, cols, mines) {
this.rows = rows;
this.cols = cols;
this.mines = mines;
this.blocks = this.initBlocks();
this.plantMines();
this.updateCounts();
}
// 获取某个坐标的方块状态
getBlockState(row, col) {
return this.blocks[row][col].state;
}
// 获取某个坐标的方块周围地雷数量
getBlockValue(row, col) {
return this.blocks[row][col].value;
}
// 翻开某
问:
请继续编写 getBlockValue
之后的方法。
答:
好的,接下来我们需要编写两个方法,一个是 isWin
,用于判断玩家是否获胜;另一个是 showAllBlock
,用于展示所有格子的内容(包括地雷)。
首先是 isWin
方法。我们可以遍历整个格子,检查是否所有非地雷格子均已打开。代码如下:
function isWin() {
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
const block = getBlock(i, j);
if (!block.isMine && !block.isOpen) {
return false;
}
}
}
return true;
}
接着是 showAllBlock
方法。这个方法比较简单,只需要遍历整个格子,将格子的内容展示出来即可。代码如下:
function showAllBlock() {
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
const block = getBlock(i, j);
if (block.isMine) {
block.showContent('mine');
} else {
block.showContent(block.value);
}
}
}
}
以上两个方法的实现,加上之前编写的代码,就完成了扫雷游戏的开发。
但显然,这个游戏远未完成,于是我重新提问。
问:
请重新编写 minesweeper.js
答:
好的,下面是完整的扫雷游戏代码,包括 HTML,CSS 和 JavaScript,JavaScript 代码分模块编写并从 HTML 外部导入,同时也包含了之前缺失的一些代码(如获取随机地雷等):
看来他误会了我的意思,我只请他重新编写核心的 JavaScript 代码,但他整个的重新开始了。但有一点值得夸奖,他已经清楚我的习惯,主动提出分块编写,断点续传。不得不手动狗头一下我们的万里长城。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Minesweeper</title>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/codemirror.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/mode/javascript/javascript.min.js"></script>
</head>
<body>
<div id="container"></div>
<textarea id="code"></textarea>
<button id="run-btn">Run</button>
<textarea id="console"></textarea>
<script src="utils.js"></script>
<script src="block.js"></script>
<script src="board.js"></script>
<script src="minesweeper.js"></script>
</body>
</html>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
flex-direction: column;
align-items: center;
font-family: sans-serif;
padding-top: 20px;
}
#container {
display: flex;
flex-wrap: wrap;
}
.block {
width: 20px;
height: 20px;
background-color: #bbb;
border: 1px solid #999;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
cursor: pointer;
}
.block.open {
background-color: #ddd;
}
.block.mine {
background-color: red;
color: white;
}
.block.flag {
background-color: #00f;
color: white;
}
textarea {
margin-top: 10px;
width: 100%;
height: 80px;
}
#run-btn {
margin-top: 10px;
padding: 5px 10px;
border: none;
background-color: #00f;
color: white;
font-size: 16px;
cursor: pointer;
}
#console {
margin-top: 10px;
width: 100%;
height: 200px;
}
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
这一次,他给的工具函数,相对上一个版本,少了 shuffle
方法。
class Block {
constructor(x, y, board) {
this.x = x;
this.y = y;
this.board = board;
this.isMine = false;
this.isRevealed = false;
this.isFlagged = false;
this.adjacentMineCount = 0;
this.element = this.createBlockElement();
}
createBlockElement() {
const block = document.createElement("div");
block.classList.add("block");
block.dataset.x = this.x;
block.dataset.y = this.y;
block.addEventListener("click", this.handleLeftClick.bind(this));
block.addEventListener("contextmenu", this.handleRightClick.bind(this));
return block;
}
handleLeftClick(event) {
if (this.isFlagged) {
return;
}
if (!this.isRevealed) {
this.reveal();
}
if (this.isMine) {
this.board.revealAllMines();
this.board.handleGameLost();
} else if (this.adjacentMineCount === 0) {
this.board.revealAdjacentBlocks(this.x, this.y);
} else {
this.element.textContent = this.adjacentMineCount;
}
}
handleRightClick(event) {
event.preventDefault();
if (this.isRevealed) {
return;
}
this.isFlagged = !this.isFlagged;
this.element.textContent = this.isFlagged ? "🚩" : "";
this.board.handleFlagChanged(this.isFlagged);
}
reveal() {
this.isRevealed = true;
this.element.classList.add("revealed");
}
setMine() {
this.isMine = true;
}
setAdjacentMineCount(count) {
this.adjacentMineCount = count;
}
reset() {
this.isMine = false;
this.isRevealed = false;
this.isFlagged = false;
this.adjacentMineCount = 0;
this.element.classList.remove("revealed");
this.element.textContent = "";
}
}
export default Block;
这个版本的 Block 类主要完成以下几个任务:
- 创建方块的 DOM 元素
- 处理左键点击事件
- 处理右键点击事件
- 揭开方块
- 设置方块为地雷
- 设置方块周围地雷数
- 重置方块状态
该版本代码使用 ES6 Class 语法实现,并且在类的实例化过程中,为方块元素添加了左键点击和右键点击事件监听器,用于处理用户操作。同时,在方块元素被揭开时,使用 CSS 类 revealed
标识该方块已被揭开,显示周围地雷数或者地雷标志。
接下来,就反反复复的在超时和提问之间死循环,而且代码前后逻辑冲突,看来想要 ChatGPT 实现一个完整的扫雷游戏,很难了。
如果有开发游戏的现实需求,请他写其中的某一个模块,给自己以启发,还是可以的。
但通过这次不成功的对话,我学会了以下知识:
JavaScript 的 import
应用:
import { shuffle } from "./utils.js";
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import
静态的 import
语句用于导入由另一个模块导出的绑定。无论是否声明了 strict mode
,导入的模块都运行在严格模式下。在浏览器中,import
语句只能在声明了 type="module"
的 script
的标签中使用。
也初步见识了 ES6 的面向对象编程,JavaScript 越来越强大了。
我后来也尝试请他写俄罗斯方块
,允许他引用第三方引擎,以降低代码量,ChatGPT 选择了 Phaser,但仍然超时严重,续写的之后的逻辑更加混乱,我自认没有能力组装。
降低难度,请他开发汉诺塔,这个主要是递归的应用,也失败了,因此,请 ChatGPT 写一个完整的应用,很不现实,程序员暂时还没那么容易被取代。
文章开篇提到 Telegram 机器人,如果之后亚太账号再次被封锁,机器人是一个可以回退的选择,但功能和体验相对网页版,就差太多了。
这是没有办法的事情,技术在别人手上,他有权选择不给你用,唯有自立,才能自强。