基于JavaScript編寫一個(gè)翻卡游戲
前言
首先將這個(gè)游戲需求拆分成三個(gè)部分:
- 翻卡動(dòng)畫
- 生成隨機(jī)分布數(shù)組
- 點(diǎn)擊事件
翻卡動(dòng)畫
假如我們的盒子模型不是個(gè)二維的平面,而是有個(gè)三維的體積,讓它可以有正反兩面,那我們?cè)谧龅臅r(shí)候是不是只要將它真實(shí)的翻個(gè)面就可以了。讓我們來想想將它變成三維的方法。 之后發(fā)現(xiàn)了這個(gè)屬性:
transform: translateZ(1px);
使用了它,就可以把盒子內(nèi)部的元素與盒子的底部撐出個(gè)高度。
<!-- html --> <div class="card"> <div class="top">我是正面哦~</div> </div>
只用給叫做“top”的子盒子一個(gè)“距離父親的距離”,再將叫做“card”的父盒子預(yù)先翻轉(zhuǎn)180度rotateY(180deg)
,等到點(diǎn)擊的時(shí)候給它翻回來transform: rotateY(0)
就可以了。
.card{ ... height: 100%; width: 100%; position: relative; transform-style: preserve-3d; transform: rotateY(180deg); transition: all 600ms; background: pink; &.select { transform: rotateY(0); } .top{ ... height: 100%; width: 100%; position: absolute; top: 0; left: 0; box-sizing: border-box; background: white; border: 2px solid #b6a6dc; transform: translateZ(1px); } }
生成隨機(jī)分布數(shù)組
我們先來說下在理想環(huán)境中,每個(gè)元素都能勻均出現(xiàn)(次數(shù)相等)的情況。再來說下不能均勻出現(xiàn)的情況下,怎樣最大限度的均勻。
均勻元素下的隨機(jī)算法
此算法腦內(nèi)模型由西塔(θ)先生友情提供
假設(shè)我們一共需要20個(gè)元素,有5個(gè)不同類型的格子,正好每個(gè)格子出現(xiàn)4次。我們就有了一個(gè)待分配元素的集合W:
const total = 20 const icons = ['a', 'b', 'c', 'd', 'e'] // => 得到集合W const W = ['a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'd', 'e', 'e', 'e', 'e']
混淆集合
有個(gè)指針p從下標(biāo)0開始,在長(zhǎng)度為20的數(shù)組格子里面負(fù)責(zé)填圖案,填圖案的規(guī)律是從集合w中隨機(jī)取一個(gè)元素,取完后刪除該元素,p移動(dòng)到下一個(gè)格子里,迭代至完成。
function createRandomList(W: string[], total: number) { const list: any[] = [] function it(time: number): any { if (time === 0) return list // 隨機(jī)每次集合元素下標(biāo) const randomNum = Math.floor(Math.random() * (W.length)) list.push(W[randomNum]) // 新數(shù)組中加入隨機(jī)到的元素 W.splice(randomNum, 1) // 刪除集合中的元素 return it(--time) } return it(total) }
我們?cè)僮屵@個(gè)方法靈活一點(diǎn),使它的返回結(jié)果能夠隨便指定格式:
// fn非必傳項(xiàng),默認(rèn)返回原數(shù)據(jù) function createRandomList(W: string[], total: number, fn: (<T>(icon: string, index?: number) => T) = icon => icon) { const list: any[] = [] // 迭代器 function it(time: number): any { if (time === 0) return list // 隨機(jī)每次集合元素下標(biāo) const randomNum = Math.floor(Math.random() * (W.length)) list.push(fn(W[randomNum], total-time)) // 將元素和下標(biāo)傳入fn中,將fn的計(jì)算結(jié)果加入到新數(shù)組中 W.splice(randomNum, 1) // 刪除集合中的元素 return it(--time) } return it(total) }
不均勻元素下的隨機(jī)算法
const W = []
不均勻元素,其實(shí)就是集合W里的元素分布規(guī)則改變了,混淆算法仍然不受影響。之后,讓我們來思考下怎么定義一個(gè)“不均勻中的最大程度均勻”的集合。 將集合W分為兩個(gè)部分: 最大可均勻分布部分 + 隨機(jī)部分
最大可均勻分布的部分,它代表著icons中的每個(gè)元素都能出現(xiàn)相同的最多偶數(shù)次??梢赃@樣得到它:
- icons個(gè)數(shù)x2,得到完整一次配對(duì)需要多少格子
- 總格子數(shù) / 一次完整格子數(shù),得到可以完整配對(duì)的最大次數(shù)n
- 循環(huán)n次,每次循環(huán)往W里添加icons x 2
// 得到最大重復(fù)次數(shù) const times = Math.floor(total / (icons.length * 2)) for (let index = 0; index < times; index++) W.push(...icons, ...icons)
剩下的是需要隨機(jī)分布的部分,它代表著,某幾個(gè)元素可以在這里出現(xiàn)2次,剩下的則不會(huì)出現(xiàn)。
- 總格子數(shù) % icons個(gè)數(shù)x2, 得到剩下未分配的格子
- 未分配格子 / 2, 就是需要隨機(jī)從icons中取出的元素個(gè)數(shù)n,這個(gè)n一定小于icons的個(gè)數(shù)
- 從icons中隨機(jī)取n個(gè)數(shù),可以采用每取一個(gè)數(shù),將該數(shù)從原集合刪除,重復(fù)n次的方法
- 將得到的n個(gè)數(shù)x2,往W里添加
第(3)條是不是聽起來很耳熟,好像前面做過,沒錯(cuò)就是前面寫的createRandomList
函數(shù),W集合變成了icons,total變成了需要的個(gè)數(shù)n。
// 剩下未分配的格子個(gè)數(shù) const lastCount = total % (icons.length * 2) // 從icons中隨機(jī)獲取n個(gè)數(shù) const lastList = createRandomList(icons, lastCount / 2) W.push(...lastList, ...lastList)
合在一起就是就是創(chuàng)建W的方法:
function createW(icons: string[], total: number) { const times = Math.floor(total / (icons.length * 2)) const lastCount = total % (icons.length * 2) const W = [] for (let index = 0; index < times; index++) W.push(...icons, ...icons) const lastList = createRandomList(icons, lastCount / 2) W.push(...lastList, ...lastList) return W }
生成最終數(shù)組
完整的生成隨機(jī)數(shù)組代碼:
function createW(icons: string[], total: number) { const times = Math.floor(total / (icons.length * 2)) const lastCount = total % (icons.length * 2) const W = [] for (let index = 0; index < times; index++) W.push(...icons, ...icons) const lastList = createRandomList(icons, lastCount / 2) W.push(...lastList, ...lastList) return W } function createRandomList(W: string[], total: number, fn: (<T>(icon: string, index?: number) => T) = icon => icon) { const list: any[] = [] function it(time: number): any { if (time === 0) return list const randomNum = Math.floor(Math.random() * (W.length)) list.push(fn(W[randomNum], total-time)) W.splice(randomNum, 1) return it(--time) } return it(total) } // ['a', 'b', 'c', "d"] => ['c', 'd'...x15...'b', 'c', 'a'] createRandomList(createW(icons, total), total)
點(diǎn)擊事件
亂序的隨機(jī)數(shù)組有了,點(diǎn)一點(diǎn)還不簡(jiǎn)單嗎! 先讓生成的數(shù)組屬性更豐富一些,來幫助我們展示內(nèi)容。
type CardItem = { icon: string; isDel: boolean; isSelect: boolean, index: number } let list: CardItem[] = [] // isSelect屬性判斷是否翻轉(zhuǎn),isDel屬性判斷是否已經(jīng)消除,icon屬性標(biāo)注元素屬性,index用來快速找到點(diǎn)擊元素位于數(shù)組中的位置 list = createRandomList(createW(icons, total), total, (icon: string, index) => ({ icon, isDel: false, isSelect: false, index }))
這下可以用生成的數(shù)組去展示了。接下來我們寫個(gè)點(diǎn)擊事件,接收參數(shù)是點(diǎn)擊的數(shù)組元素:
// isLock用來鎖定動(dòng)畫完成前不能進(jìn)行別的操作 function handlerTap(card: CardItem) { if (isLock) return list[card.index].isSelect = true const selectors = list.filter(item => item.isSelect && !item.isDel) // 假如選擇元素<2,直接返回,不走之后流程 if (selectors.length <= 1) return isLock = true const [item1, item2] = selectors // 翻轉(zhuǎn)動(dòng)畫完成后進(jìn)行操作 setTimeout(() => { // 如果選擇的元素相同,則消除屬性等于true if (item1.icon === item2.icon) { list[item1.index].isDel = true list[item2.index].isDel = true } //將所有卡牌翻轉(zhuǎn)過背面 list = list.map(item => ({...item, isSelect: false})) isLock = false // 判斷是否所有卡牌都已經(jīng)翻轉(zhuǎn)完成 if (list.every(item => item.isDel)) console.log( "your win!") }, 800) }
完整代碼
100行整)。
<script lang="ts"> type CardItem = { icon: string; isDel: boolean; isSelect: boolean, index: number } const icons = ['a', 'b', 'c', "d"] const total = 20 let list: CardItem[] = [] let isLock = false function handlerTap(card: CardItem) { if (isLock) return list[card.index].isSelect = true const selectors = list.filter(item => item.isSelect && !item.isDel) if (selectors.length <= 1) return isLock = true const [item1, item2] = selectors setTimeout(() => { if (item1.icon === item2.icon) { list[item1.index].isDel = true list[item2.index].isDel = true } list = list.map(item => ({...item, isSelect: false})) isLock = false if (list.every(item => item.isDel)) console.log( "your win!") }, 800) } function createW(icons: string[], total: number) { const times = Math.floor(total / (icons.length * 2)) const lastCount = total % (icons.length * 2) const W = [] for (let index = 0; index < times; index++) W.push(...icons, ...icons) const lastList = createRandomList(icons, lastCount / 2) W.push(...lastList, ...lastList) return W } function createRandomList(W: string[], total: number, fn: (<T>(icon: string, index?: number) => T) = icon => icon) { const list: any[] = [] function it(time: number): any { if (time === 0) return list const randomNum = Math.floor(Math.random() * (W.length)) list.push(fn(W[randomNum], total-time)) W.splice(randomNum, 1) return it(--time) } return it(total) } list = createRandomList(createW(icons, total), total, (icon: string, index) => ({ icon, isDel: false, isSelect: false, index })) </script> <div class="game-box"> {#each list as item} <div class="grid"> {#if !item.isDel} <div class="card {item.isSelect && 'select'}" on:click="{() => handlerTap(item)}"> <div class="top">{item.icon}</div> </div> {/if} </div> {/each} </div> <style lang="less"> .game-box{ margin: 10px auto 0; width: 90vw; height: 80vh; display: grid; grid-template-columns: repeat(4, calc(100% / 4 - 3px)); grid-template-rows: repeat(5, calc(100% / 5 - 3px)); grid-row-gap:3px; grid-column-gap: 3px; .card{ height: 100%; width: 100%; box-sizing: border-box; position: relative; transform-style: preserve-3d; transform: rotateY(180deg); transition: all 600ms; background: pink; &.select { transform: rotateY(0); } .top{ height: 100%; width: 100%; position: absolute; top: 0; left: 0; box-sizing: border-box; display: flex; justify-content: center; align-items: center; background: white; border: 2px solid #b6a6dc; transform: translateZ(1px); } } } </style>
以上就是基于JavaScript編寫一個(gè)翻卡游戲的詳細(xì)內(nèi)容,更多關(guān)于JavaScript翻卡游戲的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
需靈活掌握的Bootstrap預(yù)定義排版類 你精通嗎?
Bootstrap預(yù)定義排版類,做web前端開發(fā)的你精通嗎?bootstrap前端框架到底為我們預(yù)定義了那些排版的類呢?感興趣的小伙伴們可以參考一下2016-06-06javascript動(dòng)態(tài)添加樣式(行內(nèi)式/嵌入式/外鏈?zhǔn)降纫?guī)則)
添加CSS的方式有行內(nèi)式、嵌入式、外鏈?zhǔn)?、?dǎo)入式,下面為大家詳細(xì)介紹下javascript動(dòng)態(tài)添加以上樣式規(guī)則的方法,感興趣的朋友可以參考下哈2013-06-06easyUI實(shí)現(xiàn)(alert)提示框自動(dòng)關(guān)閉的實(shí)例代碼
下面小編就為大家?guī)硪黄猠asyUI實(shí)現(xiàn)(alert)提示框自動(dòng)關(guān)閉的實(shí)例代碼。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11uniapp小程序項(xiàng)目獲取位置經(jīng)緯度信息
在實(shí)際項(xiàng)目中很多時(shí)候我們需要獲取設(shè)備的位置信息,去展示給客戶,或者以位置信息為參數(shù),繼續(xù)向服務(wù)器獲取一些數(shù)據(jù),這篇文章主要介紹了uni-app如何獲取位置信息(經(jīng)緯度),需要的朋友可以參考下2022-11-11一個(gè)簡(jiǎn)單的Node.js異步操作管理器分享
這篇文章主要介紹了一個(gè)簡(jiǎn)單的Node.js異步操作管理器分享,需要的朋友可以參考下2014-04-04js和jquery判斷數(shù)據(jù)類型的4種方法總結(jié)
這篇文章主要給大家介紹了關(guān)于js和jquery判斷數(shù)據(jù)類型的4種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08