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 · 宗嘉

Day 12 實作拖曳卡牌只能置放到目標牌堆的牌尾、蓋牌無法拖曳

今天預計實作接龍紙牌的拖曳規則 ☑️ 拖曳卡牌只能置放到目標牌堆的牌尾 -> 只能置放至目標最後 ☑️ 牌堆只有打開的牌才能進行拖曳 -> 蓋牌無法拖曳 拖曳卡牌只能置放到目標牌堆的牌尾 檢查方式很簡單,首先將昨日的:move函數內的變數result判斷先改成用let宣告, 因為不能任意插入前面的順序,所以增加判斷只有新目標位置futureIndex等於目標牌堆陣列的長度cardStacks[to].length時才可以進行移動。 如果要移動到的目標牌堆沒有牌,futureIndex就需要等於0 如果要移動到目標牌堆有1張牌,futureIndex就需要等於1 總之目標牌堆有N張牌,futureIndex就要為N,以此類推… 實際程式碼 function limitLocalMove(evt) { // 限制同個牌堆無法拖曳 let result = evt.from !== evt.to; // 取得牌堆的來源、目標名稱,對應reactive`cardStacks`內的名稱 const from = getDomName(evt.from); const to = getDomName(evt.to); const { index, futureIndex } = evt.draggedContext; // 只能移動目標牌堆的最後一張牌 result = result && futureIndex == cardStacks[to].length; if (result) { // 中間略 } return result; } 牌堆只有打開的牌才能進行拖曳 先初始化改為39張牌均分給3個牌堆且將所有牌都闔上, 這樣接下來才能測試闔牌狀態不能拖曳。 const cardStacks = reactive({ first: [], second: [], third: [] }); onMounted(() => { const data = geneateDeck(39, false); cardStacks.first = data.slice(0, 13);// 梅花A~梅花K cardStacks.second = data.slice(13, 26);// 方塊A~~方塊K cardStacks.third = data.slice(26); // 紅心A~~紅心K }); 因為多一個牌堆除了調整樣板,但同樣也在新牌堆樣板添加ref="third",方便接下來在:move中判斷拖曳來源的DOM是對應cardStacks內的哪個陣列。 看到這邊聰明的讀者大概想到又要調整:move函數內的變數result,Bingo! 只需要在函數內if (result) {的上方添加底下程式碼: ...

September 21, 2023 · 1 min · 宗嘉