返回顶部
首页 > 资讯 > 前端开发 > JavaScript >教你用Js写一个简单的五子棋小游戏
  • 177
分享到

教你用Js写一个简单的五子棋小游戏

2024-04-02 19:04:59 177人浏览 安东尼
摘要

目录棋盘绘制棋子的绘制在点击 canvas 的时候获取相对于棋盘数据的坐标点是否结束悔棋功能总结 这里的五子棋只做一些基础的功能,对于相对专业的规则不做处理。 那

这里的五子棋只做一些基础的功能,对于相对专业的规则不做处理。

那么该五子棋实现的规则和功能如下:

  • 整体功能采用canvas实现
  • 行列都规定 20 个数量,那么棋子的行列数量是 20 + 1
  • 棋盘数据采用稀疏数组格式
  • 棋子:0 为黑色,1 为白色
  • 可以悔棋
  • 胜负结束判断

棋盘绘制

<template>
  <div class="Gobang">
    <canvas id="my-canvas" ref="canvasRef" width="640" height="640" @click="canvasClick">
    </canvas>
  </div>
</template>

<script lang="ts" setup>

type GobangData = (0 | 1 | undefined)[][]


// canvas dom 元素
const canvasRef = ref<InstanceType<typeof htmlCanvasElement>>()
// 行列数
const rcs = 20
// 行列的间隔距离
const gap = 30
// 棋子的半径
const radius = 12
// 棋盘的边距
const padding = 20
// 是否结束标记
const gameOver = ref(false)
// 当前下棋方
let current = ref<0 | 1>(1)
// canvas 的 2d 实例
let ctx: CanvasRenderinGContext2D

// 初始化棋盘数据
let data: GobangData = new Array(rcs + 1).fill(0).map(() => new Array(rcs + 1))

</script>

<style lang="sCSS" scope>
.gobang {
  width: 640px;
  margin: 0 auto;
}
.header {
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;

  .btns button {
    margin-left: 10px;
    padding: 0 5px;
  }
}
#my-canvas {
  background-color: #e6a23c;
  border-radius: 4px;
}
</style>

棋盘绘制


const drawChessboard = (
  ctx: CanvasRenderingContext2D, rcs: number, gap: number, padding: number
) => {
  ctx.beginPath()
  ctx.lineWidth = 1

  // 行
  for (let i = 0; i <= rcs; i++) {
    ctx.moveTo(padding + gap * i, padding)
    ctx.lineTo(padding + gap * i, padding + gap * rcs)
  }
  // 列
  for (let i = 0; i <= rcs; i++) {
    ctx.moveTo(padding, padding + gap * i)
    ctx.lineTo(padding + gap * rcs, padding + gap * i)
  }
  ctx.strokeStyle = '#000'
  ctx.stroke()
  ctx.closePath()

  // 绘制中心圆点  
  ctx.beginPath()
  ctx.arc(
    padding + gap * rcs / 2, padding + gap * rcs / 2, 5, 0, 2 * Math.PI
  )
  ctx.fillStyle = '#000'
  ctx.fill()
  ctx.closePath()
}

棋子的绘制

我们需要在行列线条交接的地方需要放置棋子,所以我们每次绘制需要循环棋盘的数据,根据棋盘数据在指定的地方绘制棋子


const drawPieces = (
  ctx: CanvasRenderingContext2D,
  data: GobangData,
  gap: number,
  padding: number,
  radius = 12
) => {
  const m = data.length, n = data[0].length
  for (let i = 0; i < m; i++) {
    const cj = i * gap + padding + 6 - padding
    const sj = padding + i * gap
    for (let j = 0; j < n; j++) {
      // 值为 undefined 时跳过
      if (data[i][j] === undefined) {
        continue
      }
      const ci = j * gap + padding + 6 - padding
      const si = padding + j * gap
      if (!data[i][j]) {
        // 值为 1 时,绘制黑棋
        drawBlackPieces(
          ctx, ci, cj, si, sj, radius
        )
      } else {
        // 值为 0 时,绘制黑棋
        drawWhitePieces(
          ctx, ci, cj, si, sj, radius
        )
      }
    }
  }
}

黑白子的绘制,只是颜色不一样

// 绘制白子
function drawWhitePieces(
  ctx: CanvasRenderingContext2D, ci: number, cj: number, si: number, sj: number, radius = 12
) {
  ctx.beginPath()
  const lg2 = ctx.createRadialGradient(
    ci, cj, 5, ci, cj, 20
  )
  // 向圆形渐变上添加颜色 
  lg2.addColorStop(0.1, '#fff')
  lg2.addColorStop(0.9, '#DDD')
  ctx.fillStyle = lg2
  ctx.arc(
    si, sj, radius, 0, 2 * Math.PI
  )
  ctx.fill()
  ctx.closePath()
}

// 绘制黑子
function drawBlackPieces(
  ctx: CanvasRenderingContext2D, ci: number, cj: number, si: number, sj: number, radius = 12
) {
  ctx.beginPath()
  const lg2 = ctx.createRadialGradient(
    ci, cj, 5, ci, cj, 20
  )
  // 向圆形渐变上添加颜色 
  lg2.addColorStop(0.1, '#666')
  lg2.addColorStop(0.9, '#000')
  ctx.fillStyle = lg2
  ctx.arc(
    si, sj, radius, 0, 2 * Math.PI
  )
  ctx.fill()
  ctx.closePath()
}

其中 ci 和 cj 是用于棋子上渐变的坐标,si 和 sj 是用于棋子绘制的圆心坐标。

在点击 canvas 的时候获取相对于棋盘数据的坐标点

const canvasClick = (e: MouseEvent) => {
  if (gameOver.value) {
    return
  }
  const { offsetX, offsetY } = e
  const posi = getPostions(
    offsetX, offsetY, gap, padding, radius
  )
  // 当前位置在放置棋子范围内且没有放置棋子
  if (posi && !data[posi[0]][posi[1]]) {
    data[posi[0]][posi[1]] = current.value
    init()
    pushStack(data)
    const res = isOver(data)
    if (res) {
      gameOver.value = true
      setTimeout(() => {
        const msg = (Array.isArray(res) ? `${data[res[0]][res[1]] ? '白' : '黑'}方获胜!` : '平局!')
        alert('游戏结束,' + msg)
      }, 50)
    }
  }
}


const getPostions = (
  offsetX: number, offsetY: number, gap: number, padding: number, r = 12
): [number, number] | false => {
  const x = Math.round((offsetY - padding) / gap)
  const y = Math.round((offsetX - padding) / gap)
  // x1, y1 为圆心坐标
  const x1 = x * gap + padding, y1 = y * gap + padding
  const nr = Math.pow(Math.pow(x1 - offsetY, 2) + Math.pow(y1 - offsetX, 2), 0.5)
  if (nr <= r) {
    return [x, y]
  }
  return false
}

这里来判断点击的当前位置是否是有效的,并且具体坐标的规则是:

  • 首先需要获取当前点最靠近哪一个棋子的圆心坐标
  • 然后因为棋子的半径是 12,所以点击的位置距离棋子圆心的距离不能超过 12
  • 满足则返回具体坐标,不满足则返回 false

是否结束

游戏结束分为两种情况:

  • 所有格子全部填满,平局
  • 已有相同的 5 颗棋子连成一条线,判胜负

在每一次棋子放下之后,就需要判断一次是否结束,我们每次需要判断一个坐标点的八个方向是否有相同的 4 颗棋子连成一条线。但是我们是依照从左至右,从上往下的顺序来检查的,所以具体检查只需要四个方向即可。


const isOver = (data: GobangData) => {
  const m = data.length, n = data[0].length
  let nullCnt = m * n

  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (data[i][j] !== undefined) {
        nullCnt--
        if (getPostionResult(data, i, j, m, n)) {
          return [i, j]
        }
      }
    }
  }
  // 是否所有格子都已已有棋子
  return !nullCnt
}


function getPostionResult(
  data: GobangData, x: number, y: number, m: number, n: number
) {
  //          右上      右      右下    下
  const ds = [[-1, 1], [0, 1], [1, 1], [1, 0]]
  const val = data[x][y]

  for (let i = 0; i < ds.length; i++) {
    const [dx, dy] = ds[i]
    let nx = x, ny = y, flag = true
    for (let i = 0; i < 4; i++) {
      nx += dx
      ny += dy
      // 是否是有效坐标,且值是否一样
      if (!(nx >= 0 && nx < m && ny >= 0 && ny < n) || data[nx][ny] !== val) {
        flag = false
        break
      }
    }
    // 已有 5 颗连成一条线
    if (flag) {
      return true
    }
  }
  return false
}

关于是否结束的优化

是否结束还有一个优化的点,就是我们不需要判断所有坐标点是否满足,我们只需要判断最后一个放置棋子的点是否满足结束条件,但是如果只判断单个点的话,我们需要判断这个点的八个方向,所以可以优化下:

//           右上      左下       右      左          右下    左上        下      上
const ds = [[[-1, 1], [1, -1]], [[0, 1], [0, -1]], [[1, 1], [-1, -1]], [[1, 0], [-1, 0]]]


function getPostionResult(
  data: GobangData, x: number, y: number, m: number, n: number
) {
  const val = data[x][y]

  for (let i = 0; i < ds.length; i++) {
    const [[lx, ly], [rx, ry]] = ds[i]
    let nx = x, ny = y, cnt = 1
    for (let j = 0; j < 4; j++) {
      nx += lx
      ny += ly
      if (!(nx >= 0 && nx < m && ny >= 0 && ny < n) || data[nx][ny] !== val) {
        break
      }
      cnt++
    }

    nx = x
    ny = y
    for (let j = 0; j < 4; j++) {
      nx += rx
      ny += ry
      if (!(nx >= 0 && nx < m && ny >= 0 && ny < n) || data[nx][ny] !== val) {
        break
      }
      cnt++
    }
    if (cnt >= 5) {
      return true
    }
  }
  return false
}


export const isOver = (data: GobangData, posi: [number, number]) => {
  const m = data.length, n = data[0].length
  let nullCnt = m * n

  // 先判断最后一个点是否满足结束
  if (getPostionResult(data, posi[0], posi[1], m, n)) {
    return posi
  }
  
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (data[i][j] !== undefined) {
        nullCnt--
      }
    }
  }

  return !nullCnt
}

悔棋功能

悔棋,也就是撤销功能,在放子的时候,保存当前的棋盘数据的快照,在悔棋的时候,拿到前一个快照的数据渲染出来。在做数据深拷贝的时候,用 JSON字符串解析方法,和 lodash 的深拷贝方法,都会讲原稀疏数组的空值都会填满,会破坏稀疏数组的结构定义,所以就自己根据场景写了一个拷贝方法:

// 深拷贝稀疏数组
function cloneDeep<T extends GobangData>(data: T):T {
  const m = data.length, n = data[0].length
  const res = new Array(m).fill(0).map(() => new Array(n)) as T

  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (data[i][j] !== undefined) {
        res[i][j] = data[i][j]
      }
    }
  }

  return res
}

// 缓存
const cacheData: GobangData[] = [cloneDeep<GobangData>(data)]
const cacheIndex = ref(0)

const pushStack = (data: GobangData) => {
  cacheData.push(cloneDeep<GobangData>(data))
  cacheIndex.value++
}
const popStack = () => {
  if (cacheIndex.value && !gameOver.value) {
    data = cloneDeep(cacheData[--cacheIndex.value])
    cacheData.length = cacheIndex.value + 1
    init()
  }
}

到这里,一个简单的五子棋就完成了。

GitHub:五子棋

总结

到此这篇关于教你用js写一个简单的五子棋小游戏的文章就介绍到这了,更多相关Js写五子棋内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 教你用Js写一个简单的五子棋小游戏

本文链接: https://lsjlt.com/news/153432.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 教你用Js写一个简单的五子棋小游戏
    目录棋盘绘制棋子的绘制在点击 canvas 的时候获取相对于棋盘数据的坐标点是否结束悔棋功能总结 这里的五子棋只做一些基础的功能,对于相对专业的规则不做处理。 那...
    99+
    2024-04-02
  • 怎么用Js写一个简单的五子棋小游戏
    这篇文章主要讲解了“怎么用Js写一个简单的五子棋小游戏”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么用Js写一个简单的五子棋小游戏”吧!这里的五子棋只做一些基础的功能,对于相对专业的规则...
    99+
    2023-07-02
  • 教你用Vue基础语法来写一个五子棋小游戏
    目录前言1.绘制游戏区域和游戏元素2.点击事件3.悔棋功能4.判断胜负写在最后前言 在之前的文章中,用JS的基础语法写了一个五子棋小游戏# 如何使用原生JS,快速写出一个五子棋小游戏...
    99+
    2024-04-02
  • 使用原生JS快速写出一个五子棋小游戏
    目录1.棋盘和棋子的绘制。2.轮流下棋的点击事件3.获胜条件判断3.1横轴获胜3.2数轴获胜3.3正斜轴获胜3.4反斜轴获胜4.悔棋功能总结1.棋盘和棋子的绘制。 let arr ...
    99+
    2024-04-02
  • python实现简单五子棋小游戏
    用python实现五子棋简单人机模式的练习过程,供大家参考,具体内容如下 最近在初学python,今天就用自己的一些粗浅理解,来记录一下这几天的python简单人机五子棋游戏的练习,...
    99+
    2024-04-02
  • java实现简单五子棋小游戏(1)
    本文实例为大家分享了java实现简单五子棋小游戏的具体代码,供大家参考,具体内容如下 讲解 五子棋,实际上就是用一个数组来实现的。没有其他很复杂的结构。首先我们制作五子棋,先要有一个...
    99+
    2024-04-02
  • java实现简单五子棋小游戏(2)
    本文实例为大家分享了java实现简单五子棋小游戏游戏的具体代码,供大家参考,具体内容如下 讲解 在第一步实现的基础上,添加游戏结束条件。五子棋游戏中的相同棋子如果同时有五个连接成一条...
    99+
    2024-04-02
  • Python写一个创意五子棋游戏
    前言 在本教程中,我们将使用Python写一个创意五子棋游戏     📝个人主页→数据挖掘博主ZTLJQ的主页 个人推荐python学习系列: ☄️爬虫JS逆向系列专栏 - 爬虫逆向教学 ☄️pytho...
    99+
    2023-08-31
    python 算法 windows pycharm 爬虫
  • 用C语言实现简单五子棋小游戏
    本文实例为大家分享了C语言实现简单五子棋小游戏的具体代码,供大家参考,具体内容如下 在vs2019创建新项目,然后添加两个源文件test.c和game.c,接着创建一个头文件game...
    99+
    2024-04-02
  • java怎么实现简单五子棋小游戏
    本篇文章为大家展示了java怎么实现简单五子棋小游戏,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。讲解五子棋,实际上就是用一个数组来实现的。没有其他很复杂的结构。首先我们制作五子棋,先要有一个棋盘。...
    99+
    2023-06-26
  • C语言实现简单的五子棋小游戏
    本文实例为大家分享了C语言实现五子棋小游戏的具体代码,供大家参考,具体内容如下 我们需要一个二维数组去储存当前的棋盘状态,然后打印出来。 我们游戏的逻辑是初始化棋盘,打印棋盘,人下棋...
    99+
    2024-04-02
  • java实现简单的五子棋游戏
    目录一、主要界面二、功能概况三、代码部分四、部分效果展示本文实例为大家分享了java实现简单五子棋游戏的具体代码,供大家参考,具体内容如下 一、主要界面 1、登录界面;2、游戏选择界...
    99+
    2024-04-02
  • C++实现简易的五子棋小游戏
    本文实例为大家分享了C++实现简易五子棋小游戏的具体代码,供大家参考,具体内容如下 游戏界面: 这是一个简易的五子棋游戏,由c语言编写,每次循环输入两个位置的坐标,通过其奇偶性判...
    99+
    2024-04-02
  • 怎么用C语言实现简单五子棋小游戏
    这篇文章主要讲解了“怎么用C语言实现简单五子棋小游戏”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么用C语言实现简单五子棋小游戏”吧!本文实例为大家分享了C语言实现简单五子棋小游戏的具体代...
    99+
    2023-06-20
  • C语言如何实现简单五子棋小游戏
    这篇文章主要介绍C语言如何实现简单五子棋小游戏,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!本文实例为大家分享了C语言实现简单五子棋小游戏的具体代码,供大家参考,具体内容如下效果图如下:设计思路:棋盘设计为15×15...
    99+
    2023-06-15
  • 利用c++写一个简单的推箱子小游戏
    效果图 相信各位都肯定完整这种推箱子的小游戏。游戏玩法很简单,那就是一个人把所有的箱子推动到对应的位置那就可以赢了。 那么我们接下来看看这个推箱子的游戏改怎么写 char ma...
    99+
    2024-04-02
  • 怎么使用Vue开发一个五子棋小游戏
    这篇文章主要讲解了“怎么使用Vue开发一个五子棋小游戏”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么使用Vue开发一个五子棋小游戏”吧!1.绘制游戏区域和游戏元素开始写代码之前,一定要记...
    99+
    2023-07-02
  • JavaScript实现简单五子棋游戏的方法
    本篇内容介绍了“JavaScript实现简单五子棋游戏的方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!本文实例为大家分享了JavaScr...
    99+
    2023-06-20
  • Java怎么实现简单的五子棋游戏
    本文小编为大家详细介绍“Java怎么实现简单的五子棋游戏”,内容详细,步骤清晰,细节处理妥当,希望这篇“Java怎么实现简单的五子棋游戏”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。项目结构这个是在网上找的资源,...
    99+
    2023-06-30
  • C语言实现简单的五子棋游戏
    本文实例为大家分享了c语言实现简单五子棋游戏的具体代码,供大家参考,具体内容如下 环境vs2017 一、游戏设计思想 1.该代码设置为 玩家1(*) vs 玩家2(O) 2.选择玩游...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作