Tic-Tac-Toe with JavaScript: Creating the Board Class | Ali Alaa - Front-end Web Developerhttps://alialaa.com/blog/tic-tac-toe-js

本文由 3 个部分组成。在第一部分中,我们将开始构建井字棋游戏棋盘背后的逻辑。我们将学习如何创建一个代表棋盘的 Javascript 类。除了一些有助于我们获取有关棋盘信息的方法外,该类将保存棋盘的当前状态。

查看演示或访问项目的 github 页面。
  • 第 1 部分:构建井字棋游戏棋盘
  • 第 2 部分:使用 Minimax 算法的 AI 玩家
  • 第 3 部分:构建用户界面

文件夹结构

让我们从创建项目的文件夹开始。文件夹的结构会很简单;一个index.html文件和一个script.js文件。除此之外,我们将有一个名为classes的文件夹,我们将把我们的 JS 类放入其中。所以让我们从Board类开始,在classes文件夹中创建一个board.js文件。这将是我们此时的文件夹结构:

project
│   index.html
│   script.js
│
|───classes│   classes.js

index.html中,我们将有一个基本的 html 文档。在文档的底部,我们将使用脚本标签导入我们的script.js文件。由于我们要使用modules,我们必须添加type="module"到 script 标签:

index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Tic Tac Toe</title></head><body><script type="module" src="script.js"></script></body>
</html>

设置服务器以避免浏览器中出现 CORS 错误

现在让我们尝试在classes/board.js文件中添加一些代码,然后将该文件导入到我们的script.js文件中,并确保我们的代码将在浏览器中运行。所以在classes/board.js中添加一个简单的类:

board.js
export default class Board {constructor() {console.log("Hello Board");}
}

现在在我们的script.js中,让我们尝试导入并初始化我们的 Board 类:

script.js
import Board from "./classes/board.js";
const board = new Board();

如果您现在在浏览器中打开index.html,您应该会在控制台中看到“Hello Board”,因为我们在类构造函数中记录了这个字符串。但是,在某些浏览器中,您可能会收到如下所示的CORS 错误:

发生这种情况是因为某些浏览器阻止了从file://协议获取的资源。因此,为了使用http,我们需要将文件夹放在服务器上。在本地服务器上运行我们的项目的一种快速方法是使用名为serve的 NPM 包。使用它所要做的就是打开你的 CMD/Terminal 并切换到你的文件夹目录:

cd path/to/your/folder

确保您的机器上安装了npm,然后运行以下命令:

npm serve

您将获得一个类似http://localhost:5000的 localhost url ,您可以在浏览器中打开它,现在 CORS 错误应该消失了,您应该在控制台中看到“Hello Board”

棋盘结构

现在让我们开始构建我们的棋盘。首先,我们将为我们的棋盘类提供一个参数。该参数将是一个长度为 9 的数组。该数组将保存棋盘的状态。状态是指棋盘的当前配置或X 和O 的位置​​。数组中的每个索引都将引用棋盘的某个单元格。如果我们将状态定义为["x","","o","o","","","x","",""]它将映射到:

左侧的棋盘显示每个单元格的指定数组索引。右边是一个具有数组配置的棋盘:["x","","o","o","","","x","",""]。

现在让我们转到board.js并为类的构造函数添加我们的参数,即棋盘的状态。并且默认将是一个空棋盘;因此,一个由 9 个空单元组成的数组:

board.js
export default class Board {constructor(state = ["", "", "", "", "", "", "", "", ""]) {this.state = state;}
}

打印格式化棋盘

我们要创建的第一个方法对于游戏逻辑来说不是必需的;但是,它将帮助我们在开发过程中在浏览器控制台中可视化棋盘。这个方法将被称为printFormattedBoard

board.js
printFormattedBoard() {let formattedString = '';this.state.forEach((cell, index) => {formattedString += cell ? ` ${cell} |` : '   |';if((index + 1) % 3 === 0)  {formattedString = formattedString.slice(0,-1);if(index < 8) formattedString += '\n\u2015\u2015\u2015 \u2015\u2015\u2015 \u2015\u2015\u2015\n';}});console.log('%c' + formattedString, 'color: #c11dd4;font-size:16px');
}

此方法使用 forEach 迭代状态数组,并打印每个单元格内容 + 旁边的垂直线。每 3 个单元格,我们在新行中使用 \u2015 unicode 字符打印 3 条水平线。我们还确保在最后 3 个单元格之后不打印 3 条水平线。为了测试这一点,我们在script.js中输入:

script.js
import Board from "./classes/board.js";const board = new Board(["x", "o", "", "", "o", "", "", "", "o"]);
board.printFormattedBoard();

现在在控制台中,我们应该看到我们的棋盘格式如下:

检查棋盘的状态

接下来的 3 种方法将用于检查棋盘的当前状态。我们需要检查三件事;棋盘是空的吗?棋盘满了吗?棋盘是否处于最终状态?最终状态是其中一个玩家获胜或游戏为平局。

要检查棋盘是否为空,我们将使用数组助手every。

board.js
isEmpty() {return this.state.every(function(cell) {return cell === "";});
}

如果每次迭代都返回 true,则 each 助手将返回 true;即如果所有单元格cell === ""都为真。cell === ""可以重构为!cell,因为空字符串是错误的陈述。此外,我们可以使用箭头函数代替普通函数。因此,isEmpty 和 isFull 可以这样写:

board.js
isEmpty() {return this.state.every(cell => !cell);
}
isFull() {return this.state.every(cell => cell);
}

我们需要检查的最后一件事是最终状态棋盘。这种方法会很长但非常重复。首先,我们将使用 isEmpty 并在棋盘为空时返回 false。然后使用 if 条件,我们将检查水平、垂直和对角线获胜。如果不满足任何条件,我们将检查棋盘是否已满。如果棋盘已满且不满足任何获胜条件,则必须是平局。

如果发生获胜或平局,将返回一个对象,其中包含获胜者、获胜方向(垂直、水平或对角线)以及获胜者获胜的行/列数或对角线获胜的情况;将返回对角线的名称(从左上角到右下角的对角线为main ,从右上角到左下角的对角线为counter)。当我们为游戏构建 UI 时,这个对象将非常有用。

board.js
isTerminal() {//Return False if board in emptyif(this.isEmpty()) return false;//Checking Horizontal Winsif(this.state[0] === this.state[1] && this.state[0] === this.state[2] && this.state[0]) {return {'winner': this.state[0], 'direction': 'H', 'row': 1};}if(this.state[3] === this.state[4] && this.state[3] === this.state[5] && this.state[3]) {return {'winner': this.state[3], 'direction': 'H', 'row': 2};}if(this.state[6] === this.state[7] && this.state[6] === this.state[8] && this.state[6]) {return {'winner': this.state[6], 'direction': 'H', 'row': 3};}//Checking Vertical Winsif(this.state[0] === this.state[3] && this.state[0] === this.state[6] && this.state[0]) {return {'winner': this.state[0], 'direction': 'V', 'column': 1};}if(this.state[1] === this.state[4] && this.state[1] === this.state[7] && this.state[1]) {return {'winner': this.state[1], 'direction': 'V', 'column': 2};}if(this.state[2] === this.state[5] && this.state[2] === this.state[8] && this.state[2]) {return {'winner': this.state[2], 'direction': 'V', 'column': 3};}//Checking Diagonal Winsif(this.state[0] === this.state[4] && this.state[0] === this.state[8] && this.state[0]) {return {'winner': this.state[0], 'direction': 'D', 'diagonal': 'main'};}if(this.state[2] === this.state[4] && this.state[2] === this.state[6] && this.state[2]) {return {'winner': this.state[2], 'direction': 'D', 'diagonal': 'counter'};}//If no winner but the board is full, then it's a drawif(this.isFull()) {return {'winner': 'draw'};}//return false otherwisereturn false;
}

现在让我们通过尝试一些棋盘配置并记录我们方法的值来测试此代码。例如,通过在 script.js 中包含此代码:

script.js
import Board from "./classes/board.js";const board = new Board(["x", "o", "x", "x", "o", "o", "o", "o", "x"]);
board.printFormattedBoard();
console.log(board.isEmpty());
console.log(board.isFull());
console.log(board.isTerminal());

您的控制台应如下所示:

尝试其他一些电路板状态并确保一切都按预期工作!

插入符号并获得可能的移动

insert方法将简单地在某个单元格处插入一个符号。该方法将接收符号(x 或 o)和位置(单元格索引)。首先,如果单元格不存在或符号无效,我们将返回错误,以确保我们不会意外滥用此方法。然后,如果单元格已被占用,我们将返回 false。否则,我们将简单地更新状态数组并返回 true:

board.js
insert(symbol, position) {if(![0,1,2,3,4,5,6,7,8].includes(position)) {throw new Error('Cell index does not exist!')}if(!['x','o'].includes(symbol)) {throw new Error('The symbol can only be x or o!')}if(this.state[position]) {return false;}this.state[position] = symbol;return true;
}

最后,我们将创建一个方法,该方法返回一个包含所有可用移动的数组。这将简单地迭代状态数组并仅在单元格为空时将单元格的索引推送到返回的数组:

board.js
getAvailableMoves() {const moves = [];this.state.forEach((cell, index) => {if(!cell) moves.push(index);});return moves;
}

现在让我们做一些测试。假设我们有这个棋盘配置,让我们测试一下我们的一些方法:

script.js
import Board from "./classes/board.js";const board = new Board(["x", "o", "", "x", "o", "", "o", "", "x"]);
board.printFormattedBoard();
console.log(board.isTerminal());
board.insert("o", 7);
board.printFormattedBoard();
console.log(board.getAvailableMoves());
console.log(board.isTerminal());

这应该是我们的结果:

这就是我们完成的 Board 类的样子:

board.js
/*** @desc This class represents the board, contains methods that checks board state, insert a symbol, etc..* @param {Array} state - an array representing the state of the board*/
class Board {//Initializing the boardconstructor(state = ["", "", "", "", "", "", "", "", ""]) {this.state = state;}//Logs a visualized board with the current state to the consoleprintFormattedBoard() {let formattedString = "";this.state.forEach((cell, index) => {formattedString += cell ? ` ${cell} |` : "   |";if ((index + 1) % 3 === 0) {formattedString = formattedString.slice(0, -1);if (index < 8)formattedString +="\n\u2015\u2015\u2015 \u2015\u2015\u2015 \u2015\u2015\u2015\n";}});console.log("%c" + formattedString, "color: #c11dd4;font-size:16px");}//Checks if board has no symbols yetisEmpty() {return this.state.every(cell => !cell);}//Check if board has no spaces availableisFull() {return this.state.every(cell => cell);}/*** Inserts a new symbol(x,o) into a cell* @param {String} symbol* @param {Number} position* @return {Boolean} boolean represent success of the operation*/insert(symbol, position) {if (![0, 1, 2, 3, 4, 5, 6, 7, 8].includes(position)) {throw new Error("Cell index does not exist!");}if (!["x", "o"].includes(symbol)) {throw new Error("The symbol can only be x or o!");}if (this.state[position]) {return false;}this.state[position] = symbol;return true;}//Returns an array containing available moves for the current stategetAvailableMoves() {const moves = [];this.state.forEach((cell, index) => {if (!cell) moves.push(index);});return moves;}/*** Checks if the board has a terminal state ie. a player wins or the board is full with no winner* @return {Object} an object containing the winner, direction of winning and row/column/diagonal number/name*/isTerminal() {//Return False if board in emptyif (this.isEmpty()) return false;//Checking Horizontal Winsif (this.state[0] === this.state[1] && this.state[0] === this.state[2] && this.state[0]) {return { winner: this.state[0], direction: "H", row: 1 };}if (this.state[3] === this.state[4] && this.state[3] === this.state[5] && this.state[3]) {return { winner: this.state[3], direction: "H", row: 2 };}if (this.state[6] === this.state[7] && this.state[6] === this.state[8] && this.state[6]) {return { winner: this.state[6], direction: "H", row: 3 };}//Checking Vertical Winsif (this.state[0] === this.state[3] && this.state[0] === this.state[6] && this.state[0]) {return { winner: this.state[0], direction: "V", column: 1 };}if (this.state[1] === this.state[4] && this.state[1] === this.state[7] && this.state[1]) {return { winner: this.state[1], direction: "V", column: 2 };}if (this.state[2] === this.state[5] && this.state[2] === this.state[8] && this.state[2]) {return { winner: this.state[2], direction: "V", column: 3 };}//Checking Diagonal Winsif (this.state[0] === this.state[4] && this.state[0] === this.state[8] && this.state[0]) {return { winner: this.state[0], direction: "D", diagonal: "main" };}if (this.state[2] === this.state[4] && this.state[2] === this.state[6] && this.state[2]) {return { winner: this.state[2], direction: "D", diagonal: "counter" };}//If no winner but the board is full, then it's a drawif (this.isFull()) {return { winner: "draw" };}//return false otherwisereturn false;}
}
export default Board;

在下一部分中,我们将开始创建一个Player类。这个类将使用一种算法来获得最好的移动。我们还将为此玩家添加不同的难度级别。

使用 JavaScript 进行井字游戏:创建棋盘类相关推荐

  1. Java程序员从笨鸟到菜鸟之(二十九)javascript对象的创建和继承实现

    JavaScript对象的创建 JavaScript中定义对象的几种方式(JavaScript中没有类的概念,只有对象): 1) 基于已有对象扩充其属性和方法:  [html] view plainc ...

  2. JavaScript面向对象——理解构造函数继承(类继承)

    JavaScript面向对象--理解构造函数继承(类继承) 构造函数式继承(类继承) function SuperClass(id) {// 引用类型公有属性this.books = ['JavaSc ...

  3. php创建一个类,JavaScript_创建一个类Person的简单实例,创建一个类Person,包含以下属 - phpStudy...

    创建一个类Person的简单实例 创建一个类Person,包含以下属性:姓名(name).年龄(age).朋友(friends数组).问候(sayhi方法,输出问候语,例如:"你好!&quo ...

  4. javascript 总结(常用工具类的封装)(转)

    转载地址:http://dzblog.cn/article/5a6f48afad4db304be1e7a5f javascript 总结(常用工具类的封装) JavaScript 1. type 类型 ...

  5. JavaScript设计模式之创建型设计模式

    此系列总结与<JavaScript设计模式>,总共分为创建型设计模式.结构型设计模式.行为型设计模式.技巧型设计模式和架构性设计模式五大类. github原文地址:YOU-SHOULD-K ...

  6. JavaScript面向对象—— 动态创建tab标签页

    昨天呢,介绍了js中类的概念,以及使用类创建对象的过程.今天就用js中的类实现一个小的功能,动态添加.删除标签页.emmmmm,也有点像tab栏切换,不过比tab栏切换多了添加和删除的功能. 案例说明 ...

  7. SpringBoot 框架中 使用Spring Aop 、创建注解、创建枚举类 使用过程记录

    1.开始 在Springboot框架中引入AOP <dependency><groupId>org.springframework.boot</groupId>&l ...

  8. 使用tolua++编译pkg,从而创建自定义类让Lua脚本使用

    2019独角兽企业重金招聘Python工程师标准>>> 在Lua第三篇中介绍了,如何在cocos2dx中使用Lua创建自定义类供Lua脚本调用使用,当时出于Himi对Lua研究不够深 ...

  9. python动态创建类_Python中通过参数动态创建扩展类(class)

    class Bar: def super_cool_function(self): print("Cool") 1.利用Python闭包动态扩展类 通过在内部创建并从函数返回它来动 ...

最新文章

  1. 最精简写法→去掉任意多个空行
  2. 导航能力堪比GPS!动物们是这样做到的
  3. 忘记Windows系统密码不用急 这个办法轻松帮你破解
  4. c++构造函数以及类中变量初始化顺序
  5. tensorflow从入门到精通100讲(五)-知识图谱( Knowledge Graph)关系抽取之PCNN
  6. 怎样学java软件编程6_月光软件站 - 编程文档 - Java - 我学习使用java的一点体会(6)...
  7. php取月份函数,分享3个php获取日历的函数
  8. BugkuCTF-MISC题旋转跳跃
  9. cmd运行python脚本处理其他文件_如何在cmd命令行里运行python脚本
  10. word敲空格文字不后退_你还在敲“空格”对齐Word?快瞧瞧效率达人是怎么做的吧!...
  11. 大量删除MySQL中的数据
  12. Pentium奔腾架构/流水线及其优化
  13. 【优化算法】改进定步长与变步长LMS算法【含Matlab源码 629期】
  14. 软件测试AI语音智能音响,什么是智能音箱_ai音箱都有什么功能 - 全文
  15. 分享【珠海】联想 IBM X3850 X6服务器维修真实案例
  16. 我的世界java版注册账号教程_我的世界java版官方购买教程
  17. 网贷风控体系之-系统架构
  18. Ubuntu Intel显卡驱动安装 (Ubuntu 14.04--Ubuntu 16.10 + Intel® Graphics Update Tool)
  19. 超级详细的计数问题的解法
  20. 中国饲料矿物质添加剂市场趋势报告、技术动态创新及市场预测

热门文章

  1. 腾讯云-云服务器介绍售前常见问题
  2. Silence影视2.0.2 清爽版
  3. if条件判断中的-z到-d的意思
  4. android点击右上角图标调转,Android 图标右上角添加数字提醒
  5. input输入框实时监测
  6. 万一被烫伤/烧伤怎么办?
  7. matlab在DSP中的应用(六)---离散傅里叶变换的性质
  8. mybatisplus通用批量修改
  9. Hadoop2.9.2 WordCount单词计数
  10. html5的大型游戏开发,大型HTML5游戏之父--Layabox