🎮 快来翻翻看你的记忆怎么样
大约 4 分钟
我正在参加 码上掘金体验活动,详情:show出你的创意代码块
我正在参加掘金社区游戏创意投稿大赛个人赛,详情请看:游戏创意投稿大赛
前言
- 实在没怎么写过文章,上次的游戏阳光都没普照到,希望这次能有点参与感,哈哈
简介
游戏开发契机,网上冲浪时发现一个小游戏 https://jaimegonzalezjr.com/games/memory/#/ 玩了几盘后发现跟我以前做的游戏流程几乎一致 https://juejin.cn/post/7055982382268022821 所以在很短的时间内实现出来了,技术有限代码不涉及到啥算法😓
玩法介绍
- 🎮 经典游戏应该不用过多介绍吧
- 👆 开始游戏尽可能多的记住棋子互相对应的位置,点击某个棋子,其余棋子将关闭显示
- 😈 石乐志模式:降低翻开错误显示时间
在线试玩
https://code.juejin.cn/pen/7085889586542411810
代码实现
游戏涉及到的图标
https://www.iconfont.cn/collections/detail?spm=a313x.7781069.1998910419.d9df05512&cid=35711 从iconfont挑选图标添加到项目后,导入图标在线包,作为棋子图案
<script src="//at.alicdn.com/t/font_3322468_le15gvj35l.js?spm=a313x.7781069.1998910419.52&file=font_3322468_le15gvj35l.js" type="text/javascript" charset="utf-8"></script>
棋子打乱
把所有棋子图案的名称复制出来后随机打乱,注意:棋盘生成大小不能超过棋子图案*2
听说Array.sort(()=>0.5 - Math.random())
这样洗牌是不严谨的,我也不知道对不对,能跑就行
// 打乱棋子图案,不打乱其实也行
const ICONS = ['xiangsu_aixin', 'xiangsu_xiaoya', 'xiangsu_biaoqing', 'xiangsu_meiguihua',
'xiangsu_tangguo','xiangsu_xianrenzhang', 'xiangsu_caomei', 'xiangsu_tuzi', 'xiangsu_qizhi',
'xiangsu_wuqi','xiangsu_xigua', 'xiangsu_jiangbei', 'xiangsu_yaoshui', 'xiangsu_fangzi',
'xiangsu_youxiji','xiangsu_jinbi', 'xiangsu_shu', 'xiangsu_mao', 'xiangsu_zuanshi',
'xiangsu_pijiu'].sort(() => Math.random() - 0.5)
生成棋盘
- 棋子图标的获取与放置位置困扰了我很久,渲染棋子的时候如何获取、另一个相同的棋子放在哪
- 后面另辟蹊径的不如根据棋盘大小先把图标出来,再渲染的时候按顺序取不就行了
const 难度 = [[3, 2], [3, 4], [5, 4], [6, 6]]
// 生成棋子图案可用区间
const max = 难度[0] * 难度[1] / 2
// 从可用棋子图案中随机抽取不重复的棋子
let iconArr = getRandomArrayValue(ICONS, max)
// 复制一份用于游戏规则并继续打乱
iconArr = [...iconArr, ...iconArr].sort(() => Math.random() - 0.5)
// 根据难度生成棋盘,渲染行
Array.from({length: 难度[0]}).map((row, index) => {
const rowEl = doc.createElement('div')
rowEl.className = 'game-row'
this.el.appendChild(rowEl)
// 渲染列
return Array.from({length: 难度[1]}).map((child, cindex) => {
const colEl = doc.createElement('div')
colEl.className = 'game-col'
rowEl.appendChild(colEl)
const chess = doc.createElement('div')
const id = `game-chess-${index}-${cindex+1}`
chess.id = id
chess.className = 'game-chess'
// 按顺序从棋子图案可用区间抽取棋子放置
let icon = iconArr[cindex]
if (index) {
icon = iconArr[index * chessboard[1] + cindex]
}
chess.innerHTML = `<svg class="icon" aria-hidden="true">
<use xlink:href="#icon-${icon}"></use>
</svg>`
colEl.appendChild(chess)
const obj = {
position: [index, cindex + 1], // 记录棋子位置,用于记录翻棋记录
el: chess, // 棋子dom对象
status: false, // 是否正确翻开
icon // 棋子图标,用于判断两次翻开是否正确
}
// 给棋子添加点击事件
chess.addEventListener('click', () => {
if (!this.__noClick) { // 翻棋错误的时候不允许继续点击
this.step(obj, chess)
}
})
return obj
})
})
翻开棋子
step(chess, el) { // 棋子添加点击事件 step 函数,参数:棋子对象,点击的dom节点
if (this.__paintInit) { // 开始游戏
this.time()
this.__paintInit = false
this.el.classList.remove('loading-start')
// TODO: 开始游戏清空图标(防止作弊功能,因动画问题未解决暂未开放)
// const svgNodes = this.el.querySelectorAll('svg')
// svgNodes.forEach(_ => _.remove())
}
}
if (!chess.status) { // 落点不能是有棋子的位置
// 翻开棋子加个动画
el.classList.add('game-chess__focus')
if (this.__chessA) { // 翻开第二个
this.__chessZ = chess
// 计算是否符合游戏规则
const toStep = this.toStep(chess)
if (toStep) {
// 翻开成功就清理掉翻开的变量,把成功的棋子记录下来
this.__chessZ = chess
this.__chessA.status = true
this.__chessA.el.classList.add('game-chess__close')
this.__chessA = undefined
this.__chessZ.status = true
this.__chessZ.el.classList.add('game-chess__close')
this.__chessZ = undefined
// 翻开成功就判断是否成功完成游戏
if (this.isSucessGame()) {
if (this.__clearTime) {
clearTimeout(this.__clearTime)
}
Object.prototype.toString.call(this.opt.success) === '[object Function]' && this.opt.success({
ms: this.__ms,
steps: this.__recordSteps
})
}
} else {
// 翻棋错误时清理掉翻开的棋子
this.clearFoucs()
}
} else {
this.__chessA = chess
}
// 记录翻开的步骤
this.recordStep(this.__chessA, this.__chessZ)
清理翻开的棋子
clearFoucs() { // 清理翻开的棋子
return new Promise(res => {
// 清理时不允许点击棋子
this.__noClick = true
window.setTimeout(() => {
this.__chessA?.el.classList.remove('game-chess__focus')
this.__chessA = undefined
this.__chessZ?.el.classList.remove('game-chess__focus')
this.__chessZ = undefined
this.__noClick = false
res()
}, this.__levelHard ? 100 : 500) // __levelHard 石乐志模式减少显示时间
})
}
判断是否成功完成游戏
isSucessGame() { // 是否成功完成游戏
// 验证棋子是否全部被翻开
let num = this.opt.chessboard[0] * this.opt.chessboard[1]
isSucess:
for (let i = 0; i < this.chessboard.length; i++) {
const child = this.chessboard[i]
for (let j = 0; j < child.length; j++) {
if (!child[j].status) { // 有一个棋子不成功的直接判定为未完成游戏,退出多重循环
break isSucess
} else {
num--
}
}
}
if (num == 0) { // 剩余未翻开棋子为0则游戏成功
return true
}
return false
}