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實現點擊後往左快速位移一次的效果...

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]....

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....

September 21, 2023 · 1 min · 宗嘉

Day 11 拖曳紙牌的效果(三)如何一次拖曳多張卡牌

今日預計只實作如何一次拖曳 多張卡牌 多張卡牌拖曳的考察研究 這部分可能會讓很多人(我)失望,因為vue.draggable.next最近一次的合併更新在2021年8月, 所以目前Vue3無法像原本Vue2能使用vue.draggable的Multi-drag的擴充功能,所以我捨棄使用套件原生多筆拖曳的想法和 拖曳多張牌完美的畫面效果。 如果願意改變資料結構為巢狀Vue3版本還是有辦法對巢狀物件進行一次性的拖曳,但對我來說在未來資料處理的靈活性降低又提高判斷卡牌順序的複雜度因此不考慮。 想法邏輯 從來源牌堆先拖曳一張牌A移動到目標牌堆的指定位置 將來源牌堆中A牌後的剩餘卡片複製到目標牌堆的指定位置後方 刪除來源牌堆原A位置後的剩餘元素 實作後發現其實可以先複製一份來源牌堆、目標牌堆移動後的結果,後續處理會更為靈活。 實作邏輯 在:move對應的函數中判斷可拖曳時,產生『若拖曳成功後,來源/目標陣列的新狀態』並封裝成一個箭頭函數儲存至ref變數changeOption。 function limitLocalMove(evt) { // 限制同個牌堆無法拖曳 const result = evt.from !== evt.to; if (result) { // 取得牌堆的來源、目標名稱,對應reactive`cardStacks`內的名稱 const from = getDomName(evt.from); const to = getDomName(evt.to); const draggedContext = evt.draggedContext const { index, futureIndex } = draggedContext; // 產生多筆拖曳後,來源牌堆、目的牌堆的陣列變動後的結果 const newFromCards = cardStacks[from].slice(0, index); const newToCards = [ ...cardStacks[to].slice(0, futureIndex), ...cardStacks[from].slice(index), ...cardStacks[to].slice(futureIndex) ]; // 將變動牌堆的函數暫存,預計等到拖曳完成後執行 changeOption.value = () => { cardStacks[from] = newFromCards; cardStacks[to] = newToCards; changeOption....

September 20, 2023 · 2 min · 宗嘉