基於JavaScript編寫一個翻卡遊戲
前言
首先將這個遊戲需求拆分成三個部分:
- 翻卡動畫
- 生成隨機分佈數組
- 點擊事件
翻卡動畫
假如我們的盒子模型不是個二維的平面,而是有個三維的體積,讓它可以有正反兩面,那我們在做的時候是不是隻要將它真實的翻個面就可以瞭。讓我們來想想將它變成三維的方法。 之後發現瞭這個屬性:
transform: translateZ(1px);
使用瞭它,就可以把盒子內部的元素與盒子的底部撐出個高度。
<!-- html --> <div class="card"> <div class="top">我是正面哦~</div> </div>
隻用給叫做“top”的子盒子一個“距離父親的距離”,再將叫做“card”的父盒子預先翻轉180度rotateY(180deg)
,等到點擊的時候給它翻回來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); } }
生成隨機分佈數組
我們先來說下在理想環境中,每個元素都能勻均出現(次數相等)的情況。再來說下不能均勻出現的情況下,怎樣最大限度的均勻。
均勻元素下的隨機算法
此算法腦內模型由西塔(θ)先生友情提供
假設我們一共需要20個元素,有5個不同類型的格子,正好每個格子出現4次。我們就有瞭一個待分配元素的集合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']
混淆集合
有個指針p從下標0開始,在長度為20的數組格子裡面負責填圖案,填圖案的規律是從集合w中隨機取一個元素,取完後刪除該元素,p移動到下一個格子裡,迭代至完成。
function createRandomList(W: string[], total: number) { const list: any[] = [] function it(time: number): any { if (time === 0) return list // 隨機每次集合元素下標 const randomNum = Math.floor(Math.random() * (W.length)) list.push(W[randomNum]) // 新數組中加入隨機到的元素 W.splice(randomNum, 1) // 刪除集合中的元素 return it(--time) } return it(total) }
我們再讓這個方法靈活一點,使它的返回結果能夠隨便指定格式:
// fn非必傳項,默認返回原數據 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)) // 將元素和下標傳入fn中,將fn的計算結果加入到新數組中 W.splice(randomNum, 1) // 刪除集合中的元素 return it(--time) } return it(total) }
不均勻元素下的隨機算法
const W = []
不均勻元素,其實就是集合W裡的元素分佈規則改變瞭,混淆算法仍然不受影響。之後,讓我們來思考下怎麼定義一個“不均勻中的最大程度均勻”的集合。 將集合W分為兩個部分: 最大可均勻分佈部分 + 隨機部分
最大可均勻分佈的部分,它代表著icons中的每個元素都能出現相同的最多偶數次。可以這樣得到它:
- icons個數x2,得到完整一次配對需要多少格子
- 總格子數 / 一次完整格子數,得到可以完整配對的最大次數n
- 循環n次,每次循環往W裡添加icons x 2
// 得到最大重復次數 const times = Math.floor(total / (icons.length * 2)) for (let index = 0; index < times; index++) W.push(...icons, ...icons)
剩下的是需要隨機分佈的部分,它代表著,某幾個元素可以在這裡出現2次,剩下的則不會出現。
- 總格子數 % icons個數x2, 得到剩下未分配的格子
- 未分配格子 / 2, 就是需要隨機從icons中取出的元素個數n,這個n一定小於icons的個數
- 從icons中隨機取n個數,可以采用每取一個數,將該數從原集合刪除,重復n次的方法
- 將得到的n個數x2,往W裡添加
第(3)條是不是聽起來很耳熟,好像前面做過,沒錯就是前面寫的createRandomList
函數,W集合變成瞭icons,total變成瞭需要的個數n。
// 剩下未分配的格子個數 const lastCount = total % (icons.length * 2) // 從icons中隨機獲取n個數 const lastList = createRandomList(icons, lastCount / 2) W.push(...lastList, ...lastList)
合在一起就是就是創建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 }
生成最終數組
完整的生成隨機數組代碼:
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)
點擊事件
亂序的隨機數組有瞭,點一點還不簡單嗎! 先讓生成的數組屬性更豐富一些,來幫助我們展示內容。
type CardItem = { icon: string; isDel: boolean; isSelect: boolean, index: number } let list: CardItem[] = [] // isSelect屬性判斷是否翻轉,isDel屬性判斷是否已經消除,icon屬性標註元素屬性,index用來快速找到點擊元素位於數組中的位置 list = createRandomList(createW(icons, total), total, (icon: string, index) => ({ icon, isDel: false, isSelect: false, index }))
這下可以用生成的數組去展示瞭。接下來我們寫個點擊事件,接收參數是點擊的數組元素:
// isLock用來鎖定動畫完成前不能進行別的操作 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 // 翻轉動畫完成後進行操作 setTimeout(() => { // 如果選擇的元素相同,則消除屬性等於true 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) }
完整代碼
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編寫一個翻卡遊戲的詳細內容,更多關於JavaScript翻卡遊戲的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- vue編寫簡單的購物車功能
- 梳理總結25JavaScript數組操作方法實例
- JavaScript中reduce()的用法實例
- JavaScript中隨機數方法 Math.random()
- JavaScript前端分頁實現示例