我正在参加掘金社区游戏创意投稿大赛团队赛,详情请看:游戏创意投稿大赛
我正在参加 码上掘金体验活动,详情:show出你的创意代码块
先上一下效果
在线预览地址:code.juejin.cn/pen/7087442…
前言
最近在使用【码上掘金】的时候发现能引入第三方js库,在开发的区域中也有切换React开发语言的选项。我就突发奇想的试了一下使用cdn引入React和AntDesign,没想到真的可以使用。这篇文章记录一下使用【React】+【Ant Design】在【码上掘金】上开发一个井字棋小游戏。
在【码上掘金】中引入React和Ant Design
我们通过cdn引入这两个库
先引入antd的css样式
步骤如下所示
- 点击设置
- 切换到Style Tab
- 点击添加新资源,输入以下地址
https://unpkg.com/antd@4.19.5/dist/antd.min.css
然后引入antd和react的js库
我们切换到Script Tab,依次添加如下三个资源
https://unpkg.com/react@17/umd/react.production.min.js
https://unpkg.com/react-dom@17/umd/react-dom.production.min.js
https://unpkg.com/antd@4.19.5/dist/antd.min.js
前边两个是react和react dom的源,后边是 antd的源
先试一下是否正常引入
我们先实验一下是否正常引入了AntDesign,只要在我们的预览区中可以正常显示按钮样式,就代表这我们已经完成了所有我们需要的源引入
我们在JS开发区域中输入代码如下
class ChirldComponent extends React.Component {
render() {
return (<antd.Button type="primary" >这是一个按钮</antd.Button>
)
}
}
ReactDOM.render(<ChirldComponent />, document.getElementById("app"));
最终效果如下
到这里我们已经成功引入了需要的开发依赖,只想要了解如何引入React和AntDesign到码上掘金的读者可以先撤了,下面我们开始介绍井字棋的实现过程
shou you the code
我先把代码贴出来,大家可以先试一下运行一下
Script
class ChirldComponent extends React.Component {
constructor(props) {
super(props);
let nowKey = Date.now();
this.state = {
showWelcomeDiv: true,
userFirst: true,
currentStepIsUser: true,
checkerboardArr: [
[
{ id: 1, clickFlag: false, belone: "", key: `${nowKey}_1` },
{ id: 2, clickFlag: false, belone: "", key: `${nowKey}_2` },
{ id: 3, clickFlag: false, belone: "", key: `${nowKey}_3` },
],
[
{ id: 4, clickFlag: false, belone: "", key: `${nowKey}_4` },
{ id: 5, clickFlag: false, belone: "", key: `${nowKey}_5` },
{ id: 6, clickFlag: false, belone: "", key: `${nowKey}_6` },
],
[
{ id: 7, clickFlag: false, belone: "", key: `${nowKey}_7` },
{ id: 8, clickFlag: false, belone: "", key: `${nowKey}_8` },
{ id: 9, clickFlag: false, belone: "", key: `${nowKey}_9` },
],
],
result: {
finish: false,
winOwener: "",
},
};
}
render() {
function randomNum(minNum, maxNum) {
switch (arguments.length) {
case 1:
return parseInt(Math.random() * minNum + 1, 10);
break;
case 2:
return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
break;
default:
return 0;
break;
}
}
const autoPlay = (tdItemId) => {
let { checkerboardArr } = this.state;
let autoAr = [];
checkerboardArr.map((trItem) => {
trItem.map((tdItem) => {
if (tdItem.id === tdItemId) {
tdItem.clickFlag = true;
tdItem.belone = "user";
}
if (!tdItem.clickFlag) {
autoAr.push(tdItem);
}
});
});
this.setState({
checkerboardArr: checkerboardArr,
});
let finishFlag = calculate(checkerboardArr);
if (finishFlag) {
return;
}
const { currentStepIsUser } = this.state;
this.setState({
currentStepIsUser: !currentStepIsUser,
});
let autoDot = autoAr[randomNum(0, autoAr.length - 1)];
if (typeof autoDot === "undefined") {
return this.setState({
result: {
finish: true,
winOwener: "",
},
});
}
setTimeout(() => {
checkerboardArr.map((trItem) => {
trItem.map((tdItem) => {
if (tdItem.id === autoDot.id) {
tdItem.clickFlag = true;
tdItem.belone = "pc";
}
});
});
this.setState({
checkerboardArr: checkerboardArr,
});
this.setState({
currentStepIsUser: currentStepIsUser,
});
calculate(checkerboardArr);
}, 1000);
};
const calculate = () => {
let { checkerboardArr } = this.state;
let userDot = [];
let pcDot = [];
checkerboardArr.map((trItem) => {
trItem.map((tdItem) => {
if (tdItem.belone === "user") {
userDot.push(tdItem.id);
}
if (tdItem.belone === "pc") {
pcDot.push(tdItem.id);
}
});
});
// 判断用户是否胜利
if (isCurrerntWin(userDot)) {
checkerboardArr.map((trItem) => {
trItem.map((tdItem) => {
tdItem.clickFlag = true;
});
});
this.setState({
result: {
finish: true,
winOwener: "user",
},
checkerboardArr,
});
return true;
}
// 判断电脑是否胜利
if (isCurrerntWin(pcDot)) {
checkerboardArr.map((trItem) => {
trItem.map((tdItem) => {
tdItem.clickFlag = true;
});
});
this.setState({
result: {
finish: true,
winOwener: "pc",
},
checkerboardArr,
});
return true;
}
};
const isCurrerntWin = (arr) => {
return (
(arr.includes(1) && arr.includes(2) && arr.includes(3)) ||
(arr.includes(4) && arr.includes(5) && arr.includes(6)) ||
(arr.includes(7) && arr.includes(8) && arr.includes(9)) ||
(arr.includes(1) && arr.includes(4) && arr.includes(7)) ||
(arr.includes(2) && arr.includes(5) && arr.includes(8)) ||
(arr.includes(3) && arr.includes(6) && arr.includes(9)) ||
(arr.includes(1) && arr.includes(5) && arr.includes(9)) ||
(arr.includes(3) && arr.includes(5) && arr.includes(7))
);
};
return (
<div>
{this.state.showWelcomeDiv ? (
<div id="welcomeDiv">
<h1>欢迎体验【井字棋】小游戏</h1>
<h3>本小游戏基于React和Antd开发,使用的开发工具为【码上掘金】</h3>
<antd.Button
type="primary"
id="toGame"
onClick={() => {
let random = Math.random();
let userFirst = random > 0.5;
this.setState({
showWelcomeDiv: false,
userFirst: userFirst,
currentStepIsUser: userFirst,
});
if (!userFirst) {
autoPlay(null);
}
}}
>
点我开始游戏
</antd.Button>
</div>
) : null}
{!this.state.showWelcomeDiv ? (
<div>
<h2> {this.state.userFirst ? "您为先手" : "您为后手"}</h2>
<h2> {this.state.currentStepIsUser ? "请您落子" : "等待落子"}</h2>
<table id="checkerboard">
{this.state.checkerboardArr.map((trItem) => (
<tr>
{trItem.map((tdItem) => (
<td key={tdItem.key}>
<antd.Button
id="checkerboard-btn"
class={
tdItem.clickFlag
? tdItem.belone === "pc"
? "primary"
: "danger"
: ""
}
disabled={
!this.state.currentStepIsUser || tdItem.clickFlag
}
onClick={() => {
autoPlay(tdItem.id);
}}
>
<b>
{tdItem.clickFlag && tdItem.belone !== ""
? tdItem.belone === "pc"
? "X"
: "O"
: " "}
</b>
</antd.Button>
</td>
))}
</tr>
))}
</table>
{this.state.result.finish ? (
<div>
<h2>
{this.state.result.winOwener &&
this.state.result.winOwener !== ""
? this.state.result.winOwener === "pc"
? "失败乃成功之母,电脑获胜"
: " 恭喜您,您获胜了"
: "棋逢对手,和棋"}
</h2>
<antd.Button
type="primary"
onClick={() => {
let nowKey = Date.now();
const checkerboardArr = [...this.state.checkerboardArr];
checkerboardArr.map((trItem) => {
trItem.map((tdItem, index) => {
tdItem.clickFlag = false;
tdItem.belone = "";
tdItem.key = nowKey + "_" + index;
});
});
let random = Math.random();
let userFirst = random > 0.5;
this.setState({
checkerboardArr,
userFirst: userFirst,
currentStepIsUser: userFirst,
result: {
finish: false,
winOwener: "",
},
currentStepIsUser: true,
});
if (!userFirst) {
autoPlay(null);
}
}}
>
再来一局
</antd.Button>
</div>
) : null}
</div>
) : null}
</div>
);
}
}
ReactDOM.render(<ChirldComponent />, document.getElementById("app"));
Style(scss)
#app {
width: 100%;
height: 100%;
text-align: center;
}
#checkerboard {
margin: auto;
font-size: 30px;
}
#checkerboard .primary {
color: blue;
}
#checkerboard .danger {
color: red;
}
#checkerboard-btn {
width: 50px;
height: 50px;
}
MarkUp
<div id="app"></div>
实现逻辑
我们的实现逻辑其实比较简单,就是定义9个td,当用户点击td的时候,自动设置当前td的点击人和自动计算是否赢得比赛,然后存储在state中,我对比代码给大家介绍一下实现逻辑
初始化数据
我们在类的构造器中初始化开始的数据
- showWelcomeDiv: 是否展示欢迎页。默认展示
- userFirst:是否为用户先手,此处只是定义值,点击进入游戏和再来一局的时候会算这个值
- currentStepIsUser:当前人是否是用户。这里为了防止在计算电脑下棋时用户点击棋盘,当前人不是用户的时候。所有的td都不可以点击
- checkerboardArr:棋盘的td初始化数据
- result.finish:是否结束当前棋局。平局和胜、负都会结束
- result.winOwener:赢家。如果没有值则为平局
随机数算法
这个算法是cv的网上的,我们用随机数来随机当前没有下过的格子给pc使用,作为它的落子点
自动下棋的方法
\
- 我们先获取当前用户点击的棋子,将它设置为用户已经点击的数据,然后更新状态。\
- 设置完之后计算实时的胜负情况,如果有人胜出则结束该方法
3 .然后设置所有的当前步为pc执子:currentStepIsUser: !currentStepIsUser\ - 之后我们将获取到的所有未下的格子随机出一个格子来,如果随机不出来则表示平局(因为所有人都下完棋了而且在上一步计算的时候没有人胜出)\
- 如果不是平局,设置一个定时器。1S后落子计算输赢的情况
计算电脑或者用户输赢的方法
这个方法只是用来收集用户和pc下的棋和设置胜利之后的状态值,下面是真正计算的方法
算是否赢得比赛的方法很简单,就是穷举,这里的所有可能赢得情况是copilot生成的。不得不说,ai生代码真香
欢迎页和开始页
这里我们在按钮点击的时候设置用户是否先手,如果用户后手需要pc先下一格棋
棋盘页
棋盘页面负责的大部分就是渲染棋子的功能。在状态更新之后及时渲染
结果页
结果页没有什么好说的,唯一需要注意的是checkerboardArr数组类的元素的key值一定要实时生成,否则状态会继续根据默认的index进行渲染,这个时候key===index没有发生变化。我们的棋盘不会重新渲染!
结语
今天使用【码上掘金】开发了一个井字棋小游戏,总体感觉就是十分强大。试想一下,一会如果出了Vue4.0,React20.x我们直接引入cdn就可以学习新特性了,真的是前端卷王必备。
如果您觉得对您有用的换,欢迎大家点赞交流!