前端人工智能:canvas 绘制棋盘及落子逻辑

快捷目录

前端人工智能:来谈谈前端人工智能

前端人工智能:前端AI研发之算法

前端人工智能:canvas 绘制棋盘线条

前端人工智能:canvas 绘制黑白棋子


前两节中,我们利用 canvas 绘制出了线条和棋子。那么在这一节当中,我们将利用前两节所学的知识把棋盘构建出来。把布局全部搭建完毕后,用于去承载我们后续用于人工智能的逻辑性代码。

使用 canvas 绘制棋盘

我们在前两节中知道应该怎么去画线了,而一个棋盘是由 15 * 15 的盘面组成的。那按照正常逻辑来说,我们肯定是通过循环去绘制这个盘面,而不是一根根去画对吧。

那接下来我们就开始正式绘制棋盘,而为了跟棋子区分开来,我就不使用黑色去绘制了,改用灰色(context.strokeStyle),并且要跟它说一声,我们要开始画了(context.beginPath()):

xml 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title></title>
</head>

<body>
  <canvas id="chess" width=800 height=800></canvas>
</body>
<script type="text/javascript">
  var chess = document.getElementById("chess");
  var context = canvas.getContext("2d");
  context.strokeStyle = "#ccc";
  context.beginPath()
</script>
</html>

我们需要绘制 15 条线,也就是说我们需要去循环 15 次:

scss 复制代码
for (var i = 0; i < 15; i++) {
  // 起点
  context.moveTo(15, 15 + i * 30)
  // 终点
  context.lineTo(435, 15 + i * 30)
  // 结束绘画
  context.stroke()
}

然后我们再看看效果:

我们可以清楚地看到横线都出来了,那接下来我们来解释一下代码为什么要这么去写。我们需要绘制15条横线,那我们就从15开始( context.moveTo(15, 15 + i * 30)),这里的15是x轴横坐标。那么纵坐标呢,我们就希望它慢慢地往下涨,每次涨 30,所以是15 + i * 30

那么我们终点的context.lineTo(435, 15 + i * 30)为什么是 435 呢?我们每一行往下涨 30,然后一共涨 14 行,也就是 30 乘以 14 ,再加上我们第一行原本的 15,是不是就得到 435 了。不管是起点还是终点,我们绘制的都是横线,所以y轴都是不变的,就保持15 + i * 30

那我们的横线就解决了,接下来我们就得画竖线。那竖线其实跟我们的横线是差不多的,横线有了,那竖线是不是把x轴跟y轴反过来就可以了:

ini 复制代码
var chess = document.getElementById("chess");
var context = chess.getContext("2d");
context.strokeStyle = "#ccc";
context.beginPath()
for (var i = 0; i < 15; i++) {
  // 横线起点
  context.moveTo(15, 15 + i * 30)
  // 横线终点
  context.lineTo(435, 15 + i * 30)
  // 竖线起点
  context.moveTo(15 + i * 30, 15)
  // 竖线终点
  context.lineTo(15 + i * 30, 435)
  // 结束绘画
  context.stroke()
}

我们来看看效果:

那我们棋盘就完成了,颜色有点淡的我们再调整一下。那我们棋盘做好了,接下来是不是就得落子了呢?哎,这个就稍微有点难度了。为什么麻烦呢?因为这个就需要涉及到计算了。在我们落子的时候,你肯定没办法点得那么准对吧,很难去做到刚好就落到我们期望的位置。

基本落子逻辑处理

我们可以弄一个 3030 的范围,只要你落在这个范围上,我都会认为你就是落到这个点上了。也就是说每个点都会有自己 3030 的范围,即中心点上下左右 15 的范围,这就是我们解决落子点的一个判断方法。

那接下来我们就开始写落子点了,首先这个肯定会有一个 onclick 点击事件,这个没问题吧。但是我们使用的是canvas,它不像 div,没办法单独给每个点去添加一个点击事件。所以我们只能给 canvas 去添加:

ini 复制代码
chess.onclick = function (e) {
    console.log(e.offsetX, e.offsetY);
}

上面的代码中,我们打印了一个offsetXoffsetYoffsetXoffsetY是相对于点击元素的左上坐标。然后我们定义变量把点击的坐标值除以我们格子的大小 30,再通过向下取整(Math.floor):

ini 复制代码
chess.onclick = function (e) {
  // console.log(e.offsetX, e.offsetY);
  var i = Math.floor(e.offsetX / 30)
  var j = Math.floor(e.offsetY / 30)
  console.log(i, j);
}

接下来我们来看看这个得到的坐标值是不是我们想要的效果:

可以看到当我们点击左上角的时候,我们得到的坐标值为(0,0);点击第二行第二列时,我们可以得到坐标值(2,2)。那我们得到坐标值x和y后,我们现在就可以去进行绘画了。

那我们画这个圆的时候,首先得留意一下,我们这个棋盘距离左边是不是有 15 的距离。因为每个格子的中心点上下左右都是 15,那么我们画圆的时候就得把这个 15 给算进去。结合下图看一下:

比如我们点击红框的范围,此时我们要框选出这个盒子,是不是就得拿i乘以盒子的大小 30。但是我们还得加上棋盘距离左边那 15 的距离,所以我们这个圆的起始点x就得等于15+i*30,同理起始点y就得等于15+j*30

半径的话我们使用 13,因为 15 刚好就是盒子的半径,我们尽量取比半径小点的值。

scss 复制代码
// 复习一下
context.arc(圆心x轴的位置,圆心y轴的位置,半径,起始角度,终点角度,是否逆时针)

context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);

然后我们再按照黑棋的做法给它加反光的效果:

ini 复制代码
chess.onclick = function (e) {
 
  var i = Math.floor(e.offsetX / 30)
  var j = Math.floor(e.offsetY / 30)

  var context = chess.getContext("2d");
  context.beginPath();
  // 画圆
  context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);
  // 增加反光效果
  var grd = context.createRadialGradient(15 + i * 30, 15 + j * 30, 13, 15 + i * 30 + 2, 15 + j * 30 - 2, 0)
  // 给反光效果加颜色
  grd.addColorStop(0, 'black')
  grd.addColorStop(1, 'white')
  context.fillStyle = grd
  context.fill()
  context.stroke()
}

保存代码运行一下,我们来看看有没有落子成功。

我们可以看到棋子已经完美落地,而截至目前为止呢,我们把基本的布局基本都做完了。基本的落子逻辑也处理完了,但是这个逻辑目前还是存在问题的,我们只是简单地把落子的位置处理好了。

在本节中,我们完善了整个布局,并且已经开始运用简单的算法去处理落子的位置。那么在下一节中,我们将完善我们的落子逻辑,并且要对游戏的胜利规则进行算法的计算。


本节代码

xml 复制代码
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title></title>
</head>

<body>
  <canvas id="chess" width=800 height=800></canvas>
</body>
<script type="text/javascript">
  var chess = document.getElementById("chess");
  var context = chess.getContext("2d");
  context.strokeStyle = "#666";
  context.beginPath()

  // 绘制棋盘
  for (var i = 0; i < 15; i++) {
    // 横线起点
    context.moveTo(15, 15 + i * 30)
    // 横线终点
    context.lineTo(435, 15 + i * 30)
    // 竖线起点
    context.moveTo(15 + i * 30, 15)
    // 竖线终点
    context.lineTo(15 + i * 30, 435)
    // 结束绘画
    context.stroke()
  }


  // 落子
  chess.onclick = function (e) {
    // 横坐标
    var i = Math.floor(e.offsetX / 30)
    // 纵坐标
    var j = Math.floor(e.offsetY / 30)
    var context = chess.getContext("2d");
    context.beginPath();
    context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);
    var grd = context.createRadialGradient(15 + i * 30, 15 + j * 30, 13, 15 + i * 30 + 2, 15 + j * 30 - 2, 0)
    grd.addColorStop(0, 'black')
    grd.addColorStop(1, 'white')
    context.fillStyle = grd
    context.fill()
    context.stroke()
  }

  

</script>

</html>
相关推荐
Elastic 中国社区官方博客几秒前
使用 Elasticsearch Agent Builder 构建对话式费用助手,结合 Telegram, n8n 和 AWS Bedrock
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·aws
OnlyEasyCode4 分钟前
Linux部署Nginx前后端web教程
linux·前端·nginx
山海青风4 分钟前
藏文TTS介绍:4 神经网络 TTS 的随机性与自然度
人工智能·python·神经网络·音视频
梵得儿SHI5 分钟前
Vue Router 路由管理从入门到精通:基础、导航与参数传递实战(含避坑指南)
前端·javascript·vue.js·路由基础配置·版本适配·路由实例创建·路由规则定义
Deepoch6 分钟前
从“单体智能”到“群体协同”:机器狗集群的分布式智能演进之路
人工智能·科技·开发板·具身模型·deepoc·机械狗
人工智能技术咨询.6 分钟前
【无标题】基于Tensorflow库的RNN模型预测实战
人工智能
yumgpkpm7 分钟前
Cloudera CDH5|CDH6|CDP7.1.7|CDP7.3|CMP 7.3的产品优势分析(在华为鲲鹏 ARM 麒麟KylinOS、统信UOS)
大数据·人工智能·hadoop·深度学习·spark·transformer·cloudera
IT_陈寒8 分钟前
JavaScript 性能优化实战:7 个让你的应用提速 50%+ 的 V8 引擎技巧
前端·人工智能·后端
十三画者8 分钟前
【文献分享】vConTACT3机器学习能够实现可扩展且系统的病毒分类体系的构建
人工智能·算法·机器学习·数据挖掘·数据分析
Watermelo6179 分钟前
【前端实战】Axios 错误处理的设计与进阶封装,实现网络层面的数据与状态解耦
前端·javascript·网络·vue.js·网络协议·性能优化·用户体验