使用Vue3實現(xiàn)羊了個羊的算法
前言
這兩天社區(qū)很多羊了個羊的web實現(xiàn),雖然各種實現(xiàn)花里花哨,然而,并沒有一個一個jy
能給他說清楚到底怎么實現(xiàn)的,由于可怕的求知欲,自己來吧!
大綱
羊了個羊這個現(xiàn)象級游戲之所以能成功,不是因為他像原神
一樣,靠著質(zhì)量、體驗、劇情你愛不釋手
他靠的是爛
,讓你愛不釋手,人家玩的是營銷,玩的是人性,也許你壓根就過不了關(guān)!
他的技術(shù)實現(xiàn),其實相當(dāng)簡單,在技術(shù)上從來沒有什么高深的東西,
果然,高深的技術(shù)總是顯得這么樸實無華!
最難的部分也就是算法
了,我也大致的鉆研了一下,但是這個算法坦率的講不是我發(fā)明的, 我只是站在巨人的肩膀上
他的算法實現(xiàn)的難點(diǎn)我以為有四方面
- 1、 初始化的隨機(jī)位置算法
- 2、 檢查是否被覆算法
- 3、 三連匹配算法
- 4、隊列區(qū)排序算法
在線演示
https://code.juejin.cn/pen/7144922644788297735
初始化的隨機(jī)位置算法
在理解算法之前,我們先大致看元數(shù)據(jù)
他需要包含 一些必備的屬性, 默認(rèn)的覆蓋情況,是否被選中的狀態(tài),icon 圖標(biāo),icon 的唯一id x 坐標(biāo) y坐標(biāo)
const scene=({ isCover: false, // 默認(rèn)都是沒有被覆蓋的 status: 0,// 是否被選中的狀態(tài) icon,// 圖標(biāo) id: randomString(4), // 生成隨機(jī)id x: column * 100 + offset, //x 坐標(biāo) y: row * 100 + offset,// y坐標(biāo) }
然后再來說算法,他的算法,本質(zhì)上其實就是限定的畫布內(nèi),隨機(jī)生成位置
在當(dāng)前這個算法中他使用一個8x8的網(wǎng)格中,生成方塊,然后利用隨機(jī)偏移量,來造成隨機(jī)堆疊的樣子
// 以下感謝大佬們提供的算法 const makeScene = (level) => { // 獲取當(dāng)前關(guān)卡 const curLevel = Math.min(maxLevel, level); // 獲取當(dāng)前關(guān)卡應(yīng)該擁有的icon數(shù)量 const iconPool = icons.slice(0, 2 * curLevel); // 算出偏移量范圍具體細(xì)節(jié)范圍 const offsetPool = [0, 25, -25, 50, -50].slice(0, 1 + curLevel); // 最終的元數(shù)據(jù)數(shù)組 const scene = []; // 確定范圍 //在一般情下 translate 的偏移量,如果是百分比的話,是按照自身的寬度或者高度去計算的,所以最大的偏移范圍是百分800% // 然后通過Math.random 會小于百分之八百 // 所以就會形成當(dāng)前區(qū)間的隨機(jī)數(shù) const range = [ [2, 6], [1, 6], [1, 7], [0, 7], [0, 8], ][Math.min(4, curLevel - 1)]; const randomSet = (icon: string) => { // 求偏移量 const offset = offsetPool[Math.floor(offsetPool.length * Math.random())]; // 偏移求列數(shù) const row = range[0] + Math.floor((range[1] - range[0]) * Math.random()); // 求偏移行數(shù) const column = range[0] + Math.floor((range[1] - range[0]) * Math.random()); console.log(offset, row, column); // 生成元數(shù)據(jù)對象 scene.push({ isCover: false, // 默認(rèn)都是沒有被覆蓋的 status: 0,// 是否被選中的狀態(tài) icon,// 圖標(biāo) id: randomString(4), // 生成隨機(jī)id x: column * 100 + offset, //x 坐標(biāo) y: row * 100 + offset,// y坐標(biāo) }); }; // 如果級別高了就加點(diǎn)icon 花哨一點(diǎn) let compareLevel = curLevel; while (compareLevel > 0) { iconPool.push(...iconPool.slice(0, Math.min(10, 2 * (compareLevel - 5)))); compareLevel -= 5; } // 生成元數(shù)據(jù),初始狀態(tài)下 iconPool的內(nèi)容少生 隨著增加,就會越來越難 for (const icon of iconPool) { for (let i = 0; i < 6; i++) { randomSet(icon); } } // 返回元數(shù)據(jù) return scene; };
解釋一下, 我們在初始化的時候, 會生成一個范圍,來初始化 他的預(yù)計位置
const range = [ [2, 6], [1, 6], [1, 7], [0, 7], [0, 8], ][Math.min(4, curLevel - 1)];
range 最后的結(jié)果,就表示格子范圍,這里是為了跟關(guān)卡結(jié)合,在初始化的時候 由于圖標(biāo)少, 所以就會在 在8x8之內(nèi)的更小的格子
例如這樣:
當(dāng)關(guān)卡越來越多的時候就會如下圖:
以為在后面關(guān)卡的時候?qū)⑺械母褡訐螡M了為8x8
那么如何計算偏移量呢?
const randomSet = (icon: string) => { // 求偏移量 const offset = offsetPool[Math.floor(offsetPool.length * Math.random())]; // 偏移求列數(shù) const row = range[0] + Math.floor((range[1] - range[0]) * Math.random()); // 求偏移行數(shù) const column = range[0] + Math.floor((range[1] - range[0]) * Math.random()); console.log(offset, row, column); // 生成元數(shù)據(jù)對象 scene.push({ isCover: false, // 默認(rèn)都是沒有被覆蓋的 status: 0,// 是否被選中的狀態(tài) icon,// 圖標(biāo) id: randomString(4), // 生成隨機(jī)id x: column * 100 + offset, //x 坐標(biāo) y: row * 100 + offset,// y坐標(biāo) }); };
其實偏移量的核心就是 Math.random
這個函數(shù),來生成0-1
的隨機(jī)數(shù),我們需要求 offset
基礎(chǔ)偏移量 row
列的偏移量 column
行的偏移量
由于為了導(dǎo)致位置的總體差異,和細(xì)節(jié)差異,來達(dá)到符合預(yù)期的
亂序效果,所以最終他生成的坐標(biāo)需要 基礎(chǔ)偏移和行列偏移來結(jié)合
檢查是否被覆算法
檢查是否被覆蓋算法其實本質(zhì)上來說 ,就是祖?zhèn)鞯?code>碰撞檢測算法
根據(jù)是否碰撞,來計算覆蓋情況
代碼如下:
// 檢查是否被覆蓋 const checkCover = (value) => { // 深拷貝一份 const updateScene = value.slice(); // 是否覆蓋算法 // 遍歷所有的元數(shù)據(jù) // 雙重for循環(huán)來找到每個元素的覆蓋情況 for (let i = 0; i < updateScene.length; i++) { // 當(dāng)前item對角坐標(biāo) const cur = updateScene[i]; // 先假設(shè)他都不是覆蓋的 cur.isCover = false; // 如果status 不為0 說明已經(jīng)被選中了,不用再判斷了 if (cur.status !== 0) continue; // 拿到坐標(biāo) const { x: x1, y: y1 } = cur; // 為了拿到他們的對角坐標(biāo),所以要加上100 //之所以要加上100 是由于 他的總體是800% 也就是一個格子的換算寬度是100 const x2 = x1 + 100, y2 = y1 + 100; // 第二個來循環(huán)來判斷他的覆蓋情況 for (let j = i + 1; j < updateScene.length; j++) { const compare = updateScene[j]; if (compare.status !== 0) continue; const { x, y } = compare; // 處理交集也就是選中情況 // 兩區(qū)域有交集視為選中 // 兩區(qū)域不重疊情況取反即為交集 if (!(y + 100 <= y1 || y >= y2 || x + 100 <= x1 || x >= x2)) { // 由于后方出現(xiàn)的元素會覆蓋前方的元素,所以只要后方的元素被選中了,前方的元素就不用再判斷了 // 又由于雙層循環(huán)第二層從j 開始,所以不用擔(dān)心會重復(fù)判斷 cur.isCover = true; break; } } } scene.value = updateScene; };
碰撞檢測
所謂碰撞檢測,就是計算兩個東西的坐標(biāo)有沒有重疊,也就是求交集
主要算法如下,就是比較他們的各個方向的位置
function isButt(obj1,obj2){ var l1=obj1.offsetLeft; var t1=obj1.offsetTop; var r1=l1+obj1.offsetWidth; var b1=t1+obj1.offsetHeight; var l2=obj2.offsetLeft; var t2=obj2.offsetTop; var r2=l2+obj2.offsetWidth; var b2=t2+obj2.offsetHeight; return!(r1<l2||b1<t2||r2<l1||b2<t1) }
覆蓋算法實現(xiàn)
覆蓋算法其實實現(xiàn)也非常簡單,就是一個雙重for循環(huán)
來將每個方塊的位置做比較,做一個碰撞檢測,從而能篩選出來被遮擋的方塊
值得注意的是
- 1、j的值需要從i+1開始,為了防止已經(jīng)比較過的
方塊
再次比較 - 2、由于元數(shù)據(jù)的渲染,的后方物體天然的會遮擋前方物體,所以當(dāng)碰撞檢測成功之后是只需要遮擋前方
方塊
即可
for (let i = 0; i < updateScene.length; i++) { // 第二個來循環(huán)來判斷他的覆蓋情況 for (let j = i + 1; j < updateScene.length; j++) { // 執(zhí)行碰撞檢測 } }
三連匹配算法
三連匹配其實相比于前兩點(diǎn),就非常簡單了
我們只需要拿到相同的方塊的icon名, 湊夠三個直接改變方塊
樣式即可
// 點(diǎn)擊item const clickSymbol = async (idx: number) => { // 如果已經(jīng)完成了,就不處理 if (finished.value || animating.value) return; // 拷貝一份Scene const symbol = scene.value[idx]; // 覆蓋了和已經(jīng)在隊列里的也不處理 if (symbol.isCover || symbol.status !== 0) return; //置為可以選中狀態(tài) symbol.status = 1; queue.value.push(symbol); // 制造動畫效果中防止點(diǎn)擊 animating.value = true; //三百毫秒的延遲 await waitTimeout(300); // 拿到與他匹配的所有icon const filterSame = queue.value.filter((sb) => sb.icon === symbol.icon); // 選中的三個配對成功表示已經(jīng)是三連了 if (filterSame.length === 3) { // 由于icon的類型一樣,留下隊列中的不一樣的剩余內(nèi)容重新賦值 queue.value = queue.value.filter((sb) => sb.icon !== symbol.icon); // 隱藏iocn,dom for (const sb of filterSame) { const find = scene.value.find((i) => i.id === sb.id); // 將他們的狀態(tài)變?yōu)? 通過opacity 屬性 來隱藏icon if (find) find.status = 2; } } // 當(dāng)格子沾滿了,那么久表示已經(jīng)失敗了 if (queue.value.length === 7) { tipText.value = '失敗了' finished.value = true; } if (!scene.value.find((s) => s.status !== 2)) { // 如果完成所有關(guān)卡,那就過了所有關(guān)了 if (level.value === maxLevel) { tipText.value = '完成挑戰(zhàn)'; finished.value = true return; } //否則加一關(guān) level.value = level.value + 1; queue.value = [] // 重新初始化 checkCover(makeScene(level.value + 1)); } else { // 處理覆蓋情況 checkCover(scene.value); } // 動畫結(jié)束 animating.value = false; };
以上代碼中,我們只需要 改變元數(shù)據(jù)的status
的狀態(tài)值即可 ,然后再配合css的視覺效果,來達(dá)到消失的效果,其實dom 還是在頁面中,并沒有消失移除,因為元數(shù)據(jù)沒變
隊列區(qū)排序算法
在隊列中我們發(fā)現(xiàn)如果湊夠三個他需要排序,
比如說在有一個叉子,就會排在米飯的前面然后消失
實現(xiàn)如下:
// 隊列區(qū)排序 watchEffect(() => { const cache = {}; // 通過當(dāng)前的icon的標(biāo)識,將相同的icon歸納到一塊 // 方便后續(xù)排序 for (const symbol of queue.value) { if (cache[symbol.icon]) { cache[symbol.icon].push(symbol); } else { cache[symbol.icon] = [symbol]; } } const temp = []; for (const symbols of Object.values(cache)) { temp.push(...(symbols as any)); } const updateSortedQueue = {}; let x = 50; // 拿到更新后的隊列區(qū)數(shù)據(jù),計算權(quán)重 for (const symbol of temp) { updateSortedQueue[symbol.id] = x; x += 100; } //賦值 ,這個是為了將選中的排序后的內(nèi)容移動到隊列區(qū) sortedQueue.value = updateSortedQueue // 檢查覆蓋情況 checkCover(scene.value); })
他的實現(xiàn)原理其實就是利用緩存對隊列計算先后權(quán)重,從而計算他排序的位置,其實他的元數(shù)據(jù)或者選中順序并沒有變
只是在視覺上更改了css 的樣式
總結(jié)
到此這篇關(guān)于使用Vue3實現(xiàn)羊了個羊的算法的文章就介紹到這了,更多相關(guān)vue羊了個羊算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決vuex數(shù)據(jù)異步造成初始化的時候沒值報錯問題
今天小編大家分享一篇解決vuex數(shù)據(jù)異步造成初始化的時候沒值報錯問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11vue3+element-plus props中的變量使用 v-model 報錯及解決方案
這篇文章主要介紹了vue3+element-plus props中的變量使用 v-model 報錯及解決方案,prop 是單向數(shù)據(jù)流,這里只能用:model-value,不能用v-model,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10Vue Element Sortablejs實現(xiàn)表格列的拖拽案例詳解
這篇文章主要介紹了Vue Element Sortablejs實現(xiàn)表格列的拖拽案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09elementplus實現(xiàn)多級表格(最后一級展示圖片)
本文主要介紹了elementplus實現(xiàn)多級表格(最后一級展示圖片),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05關(guān)于vue的element-ui web端引入高德地圖并獲取經(jīng)緯度
這篇文章主要介紹了關(guān)于vue的element-ui web端引入高德地圖并獲取經(jīng)緯度,高德地圖首先要去申請key和密鑰,文中提供了部分實現(xiàn)代碼和解決思路,感興趣的朋友可以學(xué)習(xí)一下2023-04-04