Day 17 實作結算牌堆元件(一)樣板&資料結構

前言 今日要製作結算牌堆,跟中間的7疊牌不同,結算牌堆只有4堆且只能允許同花色疊在上面,必須由小到大(A->K)放上去,放上去的牌會擋住底下的牌。 修正發牌區拖曳Bug 在開發今日功能前,測試遊玩發牌區到中間七牌堆發現能任意插到七牌堆的中間, 在函數dealerMove中補上evt.draggedContext.futureIndex == cardStacks[to].length的判斷後才正常,以下是修正後的程式碼: /** 發牌區移動 */ function dealerMove(evt) { const to = getDomName(evt.to); const dealerCard = evt.draggedContext.element; // 只能移動至目標牌堆的最後一張牌 let result = futureIndex == cardStacks[to].length; // 檢查疊牌順序、花色是否正確 result = result && checkNextOk(cardStacks[to], dealerCard); if (result) { changeOption.value = () => { cardStacks.dealerStacks = cardStacks.dealerStacks.filter(card => card.value !== dealerCard.value); changeOption.value = null; }; } return result; } 製作結算牌堆樣板 看一下草稿圖,接著想像畫面應該會有四個長方塊並排,四個長方都有個底圖對應花色, 底圖上方都可以擺一張牌。 跟製作發牌區<DealerArea>相同,打算先做出元件<FinishedArea>在整合進原本的接龍區塊, 以下是目前的樣板程式碼: // FinishedArea.vue <template> <div style="display: flex;"> <div class="card club"></div> <div class="card diamond"></div> <div class="card heart"></div> <div class="card spade"></div> </div> </template> 補上拖曳的樣板前,這部分會需要考慮四個牌堆的資料, 所以資料結構就設計成四個花色各自對應1個Card陣列的狀態 為了之後測試方便,僅先將各花色A放入對應陣列。 // FinishedArea.vue const fourCards = reactive({ club: [], // 梅花 diamond: [],// 方塊 heart: [], // 紅心 spade: [], // 黑桃 }); onMounted(() => { const cards = geneateDeck(52, true); fourCards.club = cards.slice(0, 1); fourCards.diamond = cards.slice(13, 14); fourCards.heart = cards.slice(26, 27); fourCards.spade = cards.slice(39, 40); }) 接著將樣板調整成有添加draggable元件,因為沒有添加:move屬性所以預設同個group的<draggable>內的元素是可以互相堆疊。 ...

September 26, 2023 · 2 min · 宗嘉

Day16 接龍發牌區功能實作(三)拖曳與疊牌區整合

前言 今天要來處理<DealerArea>內的元素如何整合拖曳移動到7個牌堆, 這部分會拆成2個部分來看: 可將<DealerArea>卡牌移入至牌堆上方,且遵守移動遊戲規則的條件 其他牌堆的牌無法移入<DealerArea>牌堆中 實作將<DealerArea>卡牌移入牌堆 這邊在DragDemo.vue中設定提供給發牌區<DealerArea>判斷用的:move函數,因為這個函數不像函數limitLocalMove函數是同時給7個牌堆各自使用,所以可以看到沒有特別提到from的部分,因為不用特別想就可以知道一定是來自(from)發牌區。 如果不清楚limitLocalMove是做什麼的,這部分從 第10天~第12天 的文章都有提到,主要是對應<draggable>元件的:move函數判斷使否可拖曳成功。 檢查疊牌遊戲規則的部分就由函數checkNextOk判斷,幾天前寫好的函數重新複用了👍 這邊只要result回傳true就會讓卡牌移動自動產生變化。 // DragDemo.vue /** 發牌區移動 */ function dealerMove(evt) { const to = getDomName(evt.to); const dealerCard = evt.draggedContext.element; // 檢查疊牌順序、花色是否正確 const result = checkNextOk(cardStacks[to], dealerCard); return result; } 樣板的部分就是小調整而已: // DragDemo.vue <DealerArea :deck="cardStacks.dealerStacks" :moveCard="dealerMove" /> 結果看起來拖曳過去是沒問題,但本該移動過去的元素也仍留在原地, 如下圖GIF出現了2個梅花9,此為禁忌的二重身問題💀必須修正。 修正陰魂不散的元素 因為還是有拖曳成功,只是舊資料殘留在發牌區的陣列cardStacks.dealerStacks,所以只要在拖曳完成時,將發牌區的陣列去除已經發出去的那一張牌即可,以下是修正後的程式碼: // DragDemo.vue function dealerMove(evt) { const to = getDomName(evt.to); const dealerCard = evt.draggedContext.element; // 檢查疊牌順序、花色是否正確 const result = checkNextOk(cardStacks[to], dealerCard); if (result) { changeOption.value = () => { cardStacks.dealerStacks = cardStacks.dealerStacks.filter(card => card.value !== dealerCard.value); changeOption.value = null; }; } return result; } changeOption的使用在第11天有提到的。 小結 今天主要就是完成單方面的拖曳和修Bug,本來還想說要如何防止其他卡堆的牌移入<DealerArea>的牌堆中, 後來發現這件事不會發生。 反而是實現<DealerArea>專屬的函數dealerMove後意外發現的Bug-二重身, 這可能是因為<DealerArea>內拖曳的撲克牌是使用computed計算出來的陣列,被移除了元素也不會影響原本上游的陣列。 明日預計實作 結算牌堆,敬請期待! ...

September 25, 2023 · 1 min · 宗嘉

Day15 接龍發牌區功能實作(二)拖曳功能及CSS發牌動畫

前言 今天要來補強昨日缺少的拖曳功能和將整個頁面封裝成提供整合至接龍遊戲的元件發牌區。 移牌區改為可拖曳 首先將移牌區的部分調整成從原生HTML元素div替換成這幾天都在使用draggable元件,程式碼如下: <!-- 移牌區 - 左邊水平疊牌最多三張 --> <draggable :list="canTakeCards" group="pokers" itemKey="value" class="list-group" > <template #item="{ element, index }"> <Card :value="element.value" :isOpen="element.isOpen" /> </template> </draggable> 發牌區調整動畫 雖然移牌區感覺在加牌的時候應該要套用動畫,但無奈vue.draggable.next的transition-group有Bug存在且已被官方棄養,在issue也有許多類似問題issue with transition-group in composition API就不細講。 不論是Vue2使用的vue.draggable或Vue3使用的vue.draggable.next都是對SortableJS進行包裝的容器,基本上不滿意的話只能自己重封裝實現只是CP值不高,所以我不考慮這麼做。 這不代表選擇放棄動畫,打算以CSS來實現以下動畫效果。 實際調整 首先調整樣板程式碼,主要是針對有牌時添加對應的class"card-back animtion",也順便將點擊事件@click移到外層div: <!-- 發牌堆 --> <div class="card-box"> <div class="card " style="visibility: hidden;"> <div style="visibility: visible; width: 100%;height: 100%;" @click="clickCard"> <Transition name="slide-left"> <div v-if="deckState == 'empty'">無牌可用</div> <div v-else-if="deckState == 'full'" class="card">重新循環</div> <div v-else-if="deckState == 'normal'" class="card-back animation"></div> </Transition> </div> </div> </div> 添加對應CSS實現點擊後往左快速位移一次的效果 @keyframes move-left { from { transform: translateX(0rem); } to { transform: translateX(-100rem); } } .card-back.animation:active { animation: move-left 0.55s ease; animation-iteration-count: 1; } 添加對應CSS當滑鼠移到發牌堆上方使其左右搖晃的效果 ...

September 23, 2023 · 2 min · 宗嘉

Day14 接龍發牌區功能實作(一)發牌循環

為了實現接龍發牌區功能,必須先思考如何讓撲克牌循環利用,這部分程式碼我是先拆一個頁面來練習實作,避免單一頁面的程式碼邏輯太過混亂。 發牌區的樣板 今日實作的目標會是一個左邊移牌區右邊發牌堆結合在一起的, 點擊發牌堆會將牌發到移牌區顯示,移牌區最多同時顯示三張牌。 實作的樣板程式碼 // DealerAreaView.vue <GameBoard> <div class="text">發牌區</div> <div style="display: grid; grid-template-columns: 1.5fr 1fr; gap:3rem; width: fit-content;"> <!-- 移牌區 - 左邊水平疊牌最多三張 --> <div style="display: grid; grid-template-columns: repeat(3, 3rem);"> <div v-for="card in canTakeCards" :key="card.value"> <Card :value="card.value" :isOpen="true" /> </div> </div> <!-- 發牌堆 --> <div class="card-box"> <div class="card" style="visibility: hidden;"> <div style="visibility: visible; width: 100%;height: 100%; "> <Transition name="slide-left"> <div v-if="deckState == 'empty'">無牌可用</div> <div v-else-if="deckState == 'full'" class="card" @click="clickCard">重新循環</div> <div v-else-if="deckState == 'normal'" @click="clickCard" class="card-back"></div> </Transition> </div> </div> </div> </div> </GameBoard> 可以注意到發牌堆使用<Transition>包裹,裡面的元件使用v-if和v-else-if去判斷三種情況顯示元件, 這邊使用到的deckState是一個計算結果。 ...

September 23, 2023 · 1 min · 宗嘉

Day13 牌尾自動翻牌、限制疊牌順序

今日預計實作項目: ☑️ 只能對牌尾進行翻牌 ☑️ 牌尾自動翻牌 ☑️ 限制疊牌順序 只能對牌尾進行翻牌 新增函數openCard(cards, element)因為需要確認點擊卡牌確實是在牌尾, 所以需要參數cards對應原本的卡牌陣列、參數element對應點擊的卡牌元素。 檢查牌尾就單純檢查element的數字(value)是否跟陣列cards的最後一筆數字相同, 在這邊的情況不用擔心cards的長度為0,因為所在牌堆沒有卡牌自然就不會渲染使用openCard函數的<Card>元件。 /** 開牌函數 * @param {Card[]} cards * @param {Card} element */ function openCard(cards, element) { let same = cards[cards.length - 1].value == element.value; if (same) { element.isOpen = true; } } 調整每一個<Card>元件的@click監聽,以下以第1牌堆的樣板舉例: <Card :value="element.value" :isOpen="element.isOpen" @click="openCard(cardStacks.first, element)" /> 第2、3牌堆只需將cardStacks.first中的first替換成對應陣列second和third, 雖然下一步牌尾會自動翻牌不需要人去手動翻開、但還是要避免有人去對非牌尾的開牌。 牌尾自動翻牌 這部分我是依賴Vue本身的watch去監聽牌堆的變化, 跟上一個步驟很像的取得最後一張牌lastCard並打開isOpen = true,不一樣的是需要真的檢查牌堆是否為空的情況,避免出現undefined的Error watch(cardStacks, (stacks) => { // 檢查每組牌堆最後一張 ['first', 'second', 'third'].forEach(cardName => { if (stacks[cardName].length > 0) { const lastCard = stacks[cardName][stacks[cardName].length - 1]; lastCard.isOpen = true; } }); }); 限制疊牌順序 這邊先解釋以下在紙牌接龍的中心會有七個疊牌區, 疊牌的規局會是不同花色以黑紅交錯,黑牌後面只能接紅牌,紅牌後面只能接黑牌, 且數字必須是前面的減一,不能多也不能少,例如梅花K後面只能接方塊Q或紅心Q ...

September 22, 2023 · 1 min · 宗嘉