Day 06 實現自訂義牌堆

今日目標 研究如何製作牌堆 製作牌堆 製作CardBox元件負責用來擺放卡片的容器,所以結構上就只是比原本卡片元件的稍寬,所以設計成可放子元件進去的樣板,然後當沒放牌時會產生跟牌一個寬高的隱藏區塊到slot裡面。 // CardBox.vue <template > <div class="card-box"> <slot> <div class="card" style="visibility: hidden;"></div> </slot> </div> </template> 所以目前首頁是長這樣,放了兩個空的CardBox佔位置。 // HomeView.vue <div style="display: grid; grid-template-columns: 1fr 1fr;"> <CardBox></CardBox> <CardBox></CardBox> </div> 接著思考如何實作卡牌從A點發到B點,看著畫面思考發現, 不如今天就來實作兩邊卡堆點擊後會移動到對方卡堆的功能。 這邊為了簡單驗證想法,定義函數geneateDeck(5, true)生成五張卡牌依序是梅花A~5(設為開牌), 我預計實驗兩個卡堆,所以函數也就設計以下兩個非常相似的函數, 功用正是將卡堆的最後一張卡彈出,並推到另一個牌堆。 // HomeView.vue const fstCards = ref(geneateDeck(5, true)); const secondCards = ref([]); const moveCardFromAToB = () => { const card = fstCards.value.pop(); if (card === undefined) return; secondCards.value.push(card); }; const moveCardFromBToA = () => { const card = secondCards.value.pop(); if (card === undefined) return; fstCards.value.push(card); }; 在經過一番折騰,發現CardBox.vue應該要內部自帶Card元件, 由外面用slot的方式注入進去畫面反而不好寫版面,當然也有可能是我排版功力不夠,改由props傳入cards陣列,由外部HomeView.vue處理資料傳遞,但由CardBox.vue負責卡片的顯示。 所以可以看到以下CardBox.vue會判斷當前有牌沒牌渲染不同的樣式。 // CardBox.vue <script setup> import { computed } from 'vue'; import Card from '../components/Card.vue'; const { cards } = defineProps(['cards']); const isEmpty = computed(() => { return cards.length == 0; }); </script> <template > <div class="card card-box" :class="{ 'empty-card-box': isEmpty }"> <div v-if="isEmpty">沒牌啦</div> <div v-else class="card"> <Card v-for="(card, index) in cards" :key="card.value" :value="card.value" :isOpen="card.isOpen" /> </div> </div> </template> 遊戲大廳HomeView.vue負責處理資料的部分依舊如上,但樣板改成傳簡單的卡片陣列進到<CardBox /> ...

September 15, 2023 · 1 min · 宗嘉

Day 05 引入Vue Router切換頁面

因為想留著昨天完成的撲克牌連連看,預計將不同遊戲的頁面可以做保留並切換, 所以我打算在做牌堆之前,首先應該要了解Vue3在路由頁面的實作是如何切換頁面。 引入 Vue Router 接下來步驟我是參考『直接使用npm create vue@latest指令產生預設攜帶有用Vue Router專案的檔案架構』 下去調整的,所以步驟草率請敬請見諒。 安裝依賴Vue Router npm install vue-router@4 在src底下新增兩個資料夾views和router 在views資料夾底下新增兩個頁面檔Game1View.vue和HomeView.vue src ├─App.vue ├─main.js ├─views | ├─Game1View.vue # 撲克連連看 | └─HomeView.vue # 首頁 ├─utils | ├─constants.js | └─poker-helper.js ├─router | └─index.js ├─components | ├─Card.vue | ├─FoxyHeader.vue | ├─GameBoard.vue | 略... 如果想知道是怎麼產生樹狀目錄,我是在src目錄下執行npx treer -e ./result.txt 便會 將樹狀結構寫到當前的result.txt文件中 定義路由應對應的元件,routes的部分可以有預先載入的功能 // router/index.js import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', name: 'home', component: HomeView }, { path: '/game1', name: 'game1', // Lazy Loading component: () => import('../views/Game1View.vue') } ] }) export default router 接著App.vue修改成以下結構,RouterView就是渲染對應路由的元件,RouterLink則是像<a>的連結會修改網址的對應路由,只是不會重新整理整個網頁,而是動態載入去切換部分畫面、網址。 // App.vue <script setup> import { RouterView, RouterLink } from 'vue-router'; </script> <template> <header> <nav> <RouterLink to="/">Home</RouterLink> <RouterLink to="/game1">撲克牌連連看</RouterLink> </nav> </header> <RouterView /> </template> 如果需要當前路由對應到對應連結時自動套用特定class的話,不一定要在每個<RouterLink>寫:class=""去做判斷,只需要在<style>...</style>中定義class .router-link-active即可 ...

September 14, 2023 · 2 min · 宗嘉

Day 04 調整翻牌效果&實作洗牌&外加撲克牌連連看

改變翻牌效果 研究Transition的文件後發現跟我想像的動畫變換不同,官方用法<Transition></Transition> 包裹內的新舊元件其中一個在動畫過程會先被移除掉或新增,但我先前設計好的CSS會是兩個元素都存在只是一個會被轉到CSS效果轉到背後,因此我的水平翻轉動畫需要兩個元素都存在HTML上。 為了在時間內完成,我打消原本水平翻轉的作法,我改採Transition結合v-if的方式去顯示卡牌正反兩面, 發現用漸進式出現消失也是不錯的效果,以下是樣板的改變: // Card.vue <template> <div class="card " @click="emit('poker-flip', value)"> <Transition name="card-flip"> <div v-if="isOpen" class="card-front" :class="numberClass">{{ content }}</div> <div v-else class="card-back"></div> </Transition> </div> </template> 參考Vue官方的範例, 在style添加以下後綴是-enter-active/leave-active/-enter-from/-leave-to的class,是為了讓Transition知道動畫要如何改變。 // Card.vue <style scoped> .card-flip-enter-active, .card-flip-leave-active { transition: all 0.5s ease-out; } .card-flip-enter-from, .card-flip-leave-to { opacity: 0; } {/* 略 */} 實作洗牌 實作兩個函數存於utils/poker-helper.js 洗牌的函數shuffle,洗牌採用的演算法參考看別人文章實作 Fisher-Yates 演算法,雖然洗牌用其他方法也可以,但看參考的文章說這個時間複雜度最低。 function shuffle(deck) { let length = deck.length; for (let i = 0; i < length; i++) { let rand_to_swap = Math.floor(Math.random()*(length-i)); let tmp = deck[length-1-i]; deck[length-1-i] = deck[rand_to_swap]; deck[rand_to_swap] = tmp; } return deck; } 產生52張洗亂的牌geneateShuffleDeck,這邊的isDone是為了儲存比對成功, 原本在實作上是會消失,但後來發現連連看的版面會跑掉,所以設計isDone為true時卡片會變成顯示不可點擊的小綠卡。 function geneateShuffleDeck() { let deck = []; for (let i = 0; i < 52; i++) { deck.push({ value: i, isOpen: false, isDone: false }); } return shuffle(deck); } 製作撲克牌連連看 簡單就是52張牌洗亂擺在桌上,翻出兩張牌打開有相同的就等於連上, 因為我記憶力沒多好,所以遊戲規則調整成一次最多可以翻六張,超過張數的話牌就會蓋起來! ...

September 13, 2023 · 1 min · 宗嘉

Day 03 完成卡牌自動翻面的效果,但還不完整

原本今天預計製作發牌和卡牌自動翻面的效果,希望兩個都能有動畫效果, 但現實的能力讓我想先說說今天的開發方式 實際思考/開發的過程 卡牌翻面 想像的實作過程 沿用原本的Card.vue內的樣板,在裡面分成兩個div區塊, 一個div區塊放正面有花色數字的牌面,另一個則放一張背圖。 在Card.vue內撰寫 @click 去改變卡片的狀態 撰寫CSS的動畫去實現翻面的過程 現實開發 先調整撲克牌背面的樣板,花了些時間讓圖片不會超出卡牌寬高 background-size: cover; 上網查詢是怎麼做翻轉撲克的效果,甚至查到有人做出6個div欄位做出3D方塊,但嘗試改成只有前後的div欄位但發現景深會產生兩張卡片交疊的一點位移就放棄這個寫法 意外查到IT邦上有個純CSS挑戰撲克翻轉,所以就先照著原作的CSS翻轉寫法調整Card.vue樣板 並且添加@click 會觸發emit將點到的卡牌數字回傳至上層進行更新 // Card.vue <template> <div class="card " @click="emit('poker-flip', value)" :class="backCardClass"> <div class="card-front" :class="numberClass">{{ content }}</div> <div class="card-back"></div> </div> </template> 因為我Vue3是採用SFC(Single-File Components)的寫法,所以定義emit的方式如下所示: // Card.vue const emit = defineEmits(["poker-flip"]); 在上層的元件要接會使用剛剛定義在defineEmits的名稱 @poker-flip // GameBoard.vue 其他都略 <Card v-for="card in boardCards" key="card.value" :value="card.value" :isOpen="card.isOpen" @poker-flip="toggleFlip" /> 可能因為練習過ReactJS官網的的關係,知道狀態提升(Lifting State Up)的概念,所以目前所有卡牌資料依然是先放在上層的GameBoard.vue裡面,toggleFlip是我撰寫 // GameBoard.vue function toggleFlip(num) { // 找到對應的開牌狀態且翻轉 const targetIdx = boardCards.value.findIndex((item) => item.value === num) boardCards.value[targetIdx].isOpen = !boardCards.value[targetIdx].isOpen; } 今天也有犯一點蠢,發現開牌的狀態一直傳不下去,但toggleFlip函數又有拿到對應的數字和改變的狀態, 原來是GameBoard.vue中寫的 <Card /> 元件沒寫屬性 :isOpen 導致程式沒報錯,但有執行異常的情況… ...

September 12, 2023 · 1 min · 宗嘉

Day 02 調整css調整桌面&產生52張紙牌

動工前的準備 因為昨天標題在畫面縮放的情況下會擋住放卡牌的地方,所以早上就先看CSS相關的網站學習並且如何在Vue專案中使用。 意外發現原來App.vue檔(放置卡牌區的元件)在<style scoped></style>定義的class雖然會套用到App.vue的<template>的元件中,但卻不影響App.vue內引入的GameBoard.vue<template>內使用相同class名稱的元素 修正重疊問題 關於排版的部分身為前端菜雞,就現學現賣使用將元件內部flex-wrap: wrap將包裹標題和卡牌區的main元素進行調整,如下所示: main { display: flex; flex-wrap: wrap; } 另外並且避免標題的狐狸圖太大,直接對包裹狐狸圖的header元素進行以下的配置, 採用 overflow: hidden; 這可以避免header內的元素超出限制範圍的部分進行隱藏。 header { padding: 1rem; display: flex; align-items: center; width: fit-content; max-height: 100px; text-align: center; overflow: hidden; } 顯示卡牌在牌桌 處理完之後想到今天好像約定要怎麼將卡牌一張張顯示在畫面,但腦袋不知道為什麼想到製作卡牌花色, 所以就使用線上工具vectr製作卡牌花色分別製作成svg檔案 https://vectr.com/design/editor/1dd3ff02-4dc1-4de6-84bb-17b9446450b8 因為我昨天刻的桌子似乎不太適合顯示一堆卡牌, 馬上詢問ChatGPT『能用HTML和CSS做出撲克牌桌的樣子嗎?』 複製得到的回應(HTML和CSS)渲染在瀏覽器上看看,發現它的作法花色其實是用字元表示。 我就放棄顯示svg在div元素上的想法,還有渲染出來的牌桌顏色、卡牌的外框也都採用ChatGPT的作法。 卡牌元件 每一張卡元件都會攜帶的資訊包含一個數字,使用0~51依序去表示梅花A至黑桃K。 另外使用布林值表示當前狀態是開牌/蓋牌,如下所示: // Card.vue const props = defineProps({ value: Number, isOpen: Boolean }); 另外也學到如果要在<script setup>中拿取屬性資料 defineProps() 需要先存在自訂義的變數,不能像 <template> 那麼自由直接取值 ,原本還想說怎麼白畫面且開發人員工具一直報Error跟我說『value is not defined』 // Card.vue const pokerValue = props.value; // 對應撲克花色符號,Ex: ♣A const content = PokerValuesMap[pokerValue].content; // 對應撲克顏色class const numberClass = PokerValuesMap[pokerValue].isRed ? 'card card-red' : 'card'; 卡片也實際使用到 :class 去決定要顯示指定的class,畢竟紅心/梅花是套用紅色。 Card.vue的樣板很簡單是因為把一些轉換顏色物件的部分移到我自訂義的uitls/constants.js進行map對照產生Card實際顯示的花色符號。 ...

September 11, 2023 · 1 min · 宗嘉