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