Day21 顯示接龍分數、遊戲時間

前言 今天預計實作的項目顯示分數、顯示遊玩時間, 但實作顯示分數必須配合實作累計分數的功能,不然分數都不會變動也是尷尬😂。 儲存分數、遊玩時間的變數宣告: const gameScore = ref(0); const gameTime = ref(0); const gameTimer = ref(null); 實作遊戲分數 實作分數累計規則 先整理出接龍的分數在什麼情況會增加? 從發牌區移出則加10分,因為發牌區的牌不會被重新移入所以不用擔心重複加分。 // DragDemo.vue /** 發牌區移動 */ function dealerMove(evt) { // 略 if(result){ changeOption.value = () => { // 略 gameScore.value += 10; } } } 7牌堆的牌被打開則加5分,因為被打開的牌不會被蓋回去。 原本程式就會將7牌堆最一張牌設為打開,改判斷最後一張原本是蓋牌才開牌、加5分避免分數重複累加。 // DragDemo.vue watch(cardStacks, (stacks) => { // 檢查每組牌堆最後一張 validNames.forEach(cardName => { if (stacks[cardName].length > 0) { const lastCard = stacks[cardName][stacks[cardName].length - 1]; if (!lastCard.isOpen) { lastCard.isOpen = true; gameScore.value += 5; } } }); }); 卡牌移入結算牌堆加15分,移出則扣15分。 移出的部分直接調整結算牌堆移動進行扣分 /** 結算牌堆移動 */ function finishedCardMove(evt) { // 略 if (result) { changeOption.value = () => { gameScore.value -= 15; changeOption.value = null; }; } return result; } 移入結算牌堆則會需要調整發牌區和7牌堆的移動,一樣是調整changeOption.value的程式碼要加15分。 因為第1點的規則(10分),如果直接從發牌區拖曳至結算牌堆會加25分(15+10) ...

September 29, 2023 · 2 min · 宗嘉

Day20 實作結算牌堆元件(四) 實作移入7牌堆的拖曳&遊戲重置

前言 今天會先實作結算牌堆的牌要可以拖曳至7牌堆, 另外因為每次要重新開始都要切換頁面或按F5重新整理也會實作一個重置遊戲的按鈕 拖曳結算牌堆的牌 至 7牌堆 原本以為會花點時間想怎麼做,但實際上思考、實作都沒花多少時間就完成。 因為結算牌堆拖曳的牌一次只會拖曳一張,且拖曳到7牌堆的過程判斷基本上抄之前7牌堆自己的拖曳判斷方法就差不多完成,只多了一個先判斷拖曳的目標牌堆是否為七牌堆,甚至不用像其他牌堆拖曳:move還要額外去寫『拖曳成功後要觸發修改陣列』的函數changeOption.vlaue。 以下是對應結算牌堆<FinishedArea >內元件的屬性:move實作程式碼: // DragDemo.vue /** 結算牌堆移動 */ function finishedCardMove(evt) { const to = getDomName(evt.to); const { futureIndex, element } = evt.draggedContext; let result = validNames.includes(to); // 只能移動至目標牌堆的最後一張牌 result = result && futureIndex == cardStacks[to].length; // 檢查疊牌順序、花色是否正確 result = result && checkNextOk(cardStacks[to], element); return result; } 調整樣板<FinishedArea >的屬性:moveCard="finishedCardMove"即可套用上方的函數。 <!-- DragDemo.vue --> <div class="text">結算牌堆</div> <FinishedArea :fourCards="cardStacks" :moveCard="finishedCardMove" @doms="setFourCardDoms" :change="cardChange" /> 實作重置遊戲的按鈕 因為遊戲初始化就是寫在onMounted但重設的部分不夠完整,至少並沒有考慮到結算牌堆的部分,舊版初始化程式碼如下: onMounted(() => { const data = geneateShuffleDeck(52); const everyIndex = [0, 1, 3, 6, 10, 15, 21, 28]; validNames.forEach((name, idx) => { cardStacks[name] = data.slice(everyIndex[idx], everyIndex[idx + 1]); }); cardStacks.dealerStacks = data.slice(28).map(card => ({ ...card, isOpen: true })); }); 所以乾脆將初始化遊戲寫成函數resetGame(),這樣點擊重置按鈕、渲染元件onMounted時都可以呼叫同個函數。 // DragDemo.vue onMounted(() => { resetGame(); }); 意外發現發牌區<DealerArea>開牌到第幾張的狀態是包在元件內不利於初始化,決定先將狀態提升父元件的程式碼中DragDemo.vue,將開牌到第幾張的索引改用props方式傳入通知元件<DealerArea>要更新索引。 // DragDemo.vue <script setup> let dealer = reactive({ index: 0 }); </script> <template> <div class="text">發牌區</div> <DealerArea :dealer="dealer" :deck="cardStacks.dealerStacks" :moveCard="dealerMove" /> </template> 為了讓<DealerArea>取得發牌索引的變化,在definProps新增屬性dealer對應一個reactive物件,當物件dealer參考變動時則發牌區的索引index會被設值 【變動後dealer的index】,參考下方程式碼: ...

September 29, 2023 · 2 min · 宗嘉

Day 19 實作結算牌堆元件(三)整合至接龍頁面

前言 今天會實作發牌區、7牌堆的牌可以拖曳到結算牌堆,且拖曳的過程需遵守結算牌堆的同色疊牌由A至K的規則。 整理重複的函數 先將昨天在DealerAreaView.vue撰寫的程式碼移動到拖曳練習的頁面DragDemo.vue。 樣板的部分是沒什麼問題,只是發現有太多函數宣告出現在DragDemo.vue,所以將常數FOUR_SUITS和判斷結算牌堆規則的checkNextOk2函數先移入工具目錄utils/內的程式碼,DragDemo.vue改用import的方式載入通用的常數、函數。 // DragDemo.vue import { FOUR_SUITS } from '../utils/constants'; import { geneateShuffleDeck, checkNextOk, checkNextOk2 } from "../utils/poker-helper"; 設定結算牌堆用的資料 在原本的cardStack中添加針對結算牌堆四花色的撲克牌陣列 // DragDemo.vue const cardStacks = reactive({ // 略... /** @type {Card[]} */ club: [], /** @type {Card[]} */ diamond: [], /** @type {Card[]} */ heart: [], /** @type {Card[]} */ spade: [] }); 因為<FinishedArea />的屬性:fourCard只有要求傳入的物件需要有對應花色名稱的KEY就可以,沒有硬性要求不能有其他屬性, 所以樣板的傳值我就簡單設定cardStacks傳入,如下程式碼: <!-- DragDemo.vue --> <div> <div class="text">發牌區</div> <DealerArea :deck="cardStacks.dealerStacks" :moveCard="dealerMove" /> <div class="text">結算牌堆</div> <FinishedArea :fourCards="cardStacks" @doms="setFourCardDoms" :change="cardChange" /> </div> 在DragDemo.vue中對變數cardStacks新增四個key調整,而原本的函數getDomName(dom)也要跟著調整, 主要除了上層對應7個牌堆、也需要對應到結算牌堆的HTML元素。 底下程式碼中使用花色列表FOUR_SUITS一個個檢查, 若有相同的元素則回傳結算牌堆的花色名稱 (club/diamond/heart/spade),否則依然回傳none。 // DragDemo.vue function getDomName(dom) { if (dom == first.value.targetDomElement) { return 'first'; } else if (dom == second.value.targetDomElement) { return 'second'; } else if (dom == third.value.targetDomElement) { return 'third'; } else if (dom == fourth.value.targetDomElement) { return 'fourth'; } else if (dom == fifth.value.targetDomElement) { return 'fifth'; } else if (dom == sixth.value.targetDomElement) { return 'sixth'; } else if (dom == seventh.value.targetDomElement) { return 'seventh'; } else { for (let i = 0; i < FOUR_SUITS.length; i++) { const name = FOUR_SUITS[i]; if (dom == fourCardsDom[name]) { return name; } } return 'none'; } } 上方程式碼寫到的變數fourCardsDom是一個字典,KEY會是花色(字串),VALUE對應畫色的結算牌堆(HTML元素)。 ...

September 28, 2023 · 2 min · 宗嘉

Day 18 實作結算牌堆元件(二) 整合拖曳相容不同規則

前言 昨天完成結算牌堆樣版的部分,今天接著處理整合拖曳到接龍之前的步驟, 先調整結算牌堆的內部結構、方法。 調整結算牌堆 FinishedArea 先調整結算牌堆<FinishedArea />內部需要的props結構,就底下這三種: props.fourCards對應四個牌堆的陣列 props.moveCard對應<draggable>元件的屬性:move判斷是否可以拖曳成功的函數 props.change對應<draggable>內的列表更新時觸發的函數(這部分等等再說明) 程式碼如下: // FinishedArea.vue const props = defineProps({ fourCards: { type: Object, required: true, validator: (value) => { return ( value.hasOwnProperty('club') && value.hasOwnProperty('diamond') && value.hasOwnProperty('heart') && value.hasOwnProperty('spade') ); }, }, moveCard: { type: Function, default: () => { return false; } }, change: { type: Function, default: () => { return false; } } }) 然後結算牌堆就像發牌區一樣,卡牌陣列都是由外部props傳入且props的陣列內容都會變動, 所以也需要watch監控props.fourCards變化調整元件內的fourCards內的值。 // FinishedArea.vue const fourCards = ref({ club: [], diamond: [], heart: [], spade: [], }); watch(props.fourCards, (newVal) => { fourCards.value = newVal; }); 另外設計一個用來將對應HTML元素(HTML Element)傳回父元件的emit事件,此事件只會在元件渲染成功時觸發一次,發送doms主要是讓父元件可以判斷是被拖曳至哪一個牌堆。 ...

September 27, 2023 · 2 min · 宗嘉

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