[Hero Of UnderGround 地下城] — 8F Tic-Tac-Toe 井字遊戲

成果 & 程式碼

Demo:點我

Code Source:點我

Introduction & 前言

時間一晃又過了一個禮拜,這次晃到8樓一看嚇了一下,好像剩沒幾個人,不管了還是抱持著自己的原則,硬幹就對了。另外這次算滿大的挑戰也收穫滿滿,因為首次嘗試用 Vue 挑戰(所以有寫不好的地方請見諒…

8F大魔王 — 井字遊戲

Summary & 摘要

由於JS地下城每個BOSS的弱點都不一樣,每一層都要由弱點去進行攻略,本次BOSS的弱點有五項。

  1. 【特定技術】先手為 O,後手為 X,某方獲勝時,上方會紀錄各方的獲勝戰績

  2. 【特定技術】每回合結束後,會判定結果頁(平手、O 獲勝、X 獲勝)

  3. 【特定技術】需符合 RWD,能在低螢幕解析度也能遊玩,介面不能超出 x 軸,至少在以下解析度能夠遊玩。

iPhone SE 320px

iPhone 8 375px

iPhone PLUS 414px

4.【特定技術】請使用瀏覽器離線儲存技術,將戰績保留起來,重新打開遊戲也仍可觀看到歷史戰績。技術請任選以下幾種

Cookie

localStorage

5.【書寫能力】請寫 BLOG,描述你在開發「滿足獲勝條件」解題思維來進行加強描述

上面三個 O 符合獲勝條件

斜線三個 X 符合獲勝條件

以往的額外條件書寫BLOG這次是其中一個 BOSS 弱點了(笑


START加上hover效果之截圖滑鼠消失了

畫面製作

第一次嘗試使用 Vue 切畫面,就可以直接把 display:none 扔了(誤,這邊我用了兩個 Views,一個是遊戲開始前畫面,一個是遊戲中加上結束畫面,其實看見其他大神似乎還能把畫面在拉出去個別當 Componemt 不過還不熟的 Vue 菜雞我就先能走再說跑了。

切版第一個挑戰點

CSS 之 before & after 之有更好得做法?

剛開始看見有框的叉叉後直覺是使用 after 跟 before 來做,在上面使用兩塊黑色的偽元素去蓋住它,但是我發現其他大神有更容易的做法。

第二個頁面立馬碰上

在第二個頁面又碰到了需要白邊的字,爬了一下文後發現了救星,那就是:

1
text-stroke: .3rem #fff;

加上去後就不需要再用一個 beforeafter 去覆蓋他這麼麻煩了。


串起你的世界 Vue-router

想起上一層還在 display:nonenone 去,不僅程式碼露露長也容易造成使用者直接 F12 查看你隱藏的東西,自從接觸 Vue-router 之後考試都考一百分(誤。

關於如何從零到 Vue-cli UI 介面可以看我的 Vue系列文章Vue-Cli #1 初次見面(後續加載中),或是直接把 Vue-cli 升級到 3x 版本後直接在終端機上輸入 vue ui

Vue-cli 3x 版本後 UI 介面超方便啊

點選添加 Vue-router 之後你的src資料夾內會多出 View 資料夾及 router.js,可以把 View 資料夾內的檔案 About.vue Home.vue 當作 Componemt 的概念。

主要在 router 內 import 你需要導的頁面

然後新增或修改你要的頁面就好了,因為這篇主要不是要介紹 Vue-cli 之後會放到 Vue系列文章去做介紹。

成功畫面


開始攻略

對於資料的處理 Vue 一直很拿手,直接把需要用到的資料丟進 data 內,我們需要的有局數、玩家、九個格子的陣列、判斷贏的陣列、分數、四個畫面的變數。

徹底愛上 Vue

其實還能再縮減,這完全看寫程式的人邏輯,菜雞如我邏輯超級爛,連出門都會迷路…

1
2
3
4
5
6
7
8
9
10
11
<div class="play-area d-flex flex-wrap" v-if="play_area">

<div class="grids row justify-content-center" v-for="(grid,id) in grids" :key="id" @click="set(id)">

<transition name="text-show"><font-awesome-icon v-if="grid === 1" :icon="CrossIcon" style="color:white;"/></transition>

<transition name="text-show"><font-awesome-icon v-if="grid === -1" :icon="CircleIcon"/></transition>

</div>

</div> // transition的部分下面會介紹

利用 v-for 造出九宮格,v-for=”(grid,id) in grids” 第一個 grid 代表用於迭代的元素,第二個 id 代表 **索引值(optional)**。後面使用 v-bind 加上 :key=”id”,目的是確保每個元素的唯一性,當元素更新,例如改變順序時,有可識別唯一性的 key 來確保重新渲染。

讓你的畫面活起來

Vue 可以在新增、更新或移除 DOM 時使用 CSS 顯示動畫,讓你的畫面更精彩,使用方式也很簡單,先來了解一下 Vue 提供的特殊 Class名稱。

流程圖

  1. v-enter:元素一開始的狀態。在元素被新增時觸發,在下一個影格立即移除。

  2. v-enter-active:元素被新增時的狀態。在元素被新增前加入,然後在整個動畫中使用它,最後在動畫結時被移除。

  3. v-enter-to:元素新增狀態的結束。在元素被新增後觸發,在 v-enter-active 動畫結束後被移除。這是 v2.1.8 新增的狀態,原有的 v-enter 被它取代。

  4. v-leave:元素被刪除前的初始狀態。在刪除時立即觸發,在下一個影格立即移除。

  5. v-leave-active:元素被刪除時的狀態。在元素被移除前加入,然後在整個動畫中使用它,最後在動畫結束時被移除。

  6. v-leave-to:元素刪除狀態的結束。在元素被刪除後觸發,在 v-leave-active 動畫結束後移除。這是 v2.1.8 新增的狀態,原有的 v-leave 被它取代。

最前面的 v 替換成自己命名的 class 名稱,舉個例子,寫好**** 後,到 css 去。

1
2
3
4
5
6
7
8
9
10
11
12
13
.test-enter-active {

transition: opacity .8s;

} // 元素新增前加入動畫並在整個動畫內使用它


.test-enter
.test-leave-to {

opacity: 0;

}

可以搭上 Animation 讓畫面更靈活

關於 Animation 的導入也會在 Vue 系列文章中提到,之後會再更新。


各種methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
set(id){

// 判斷如果格子是否被點過 & 判斷局數是否在九局內

if(this.grids[id] !== 0 || this.step > 9)return;

// 局數增加

this.step ++;

// 如果是除2是於0代表X下手,反之O下手

this.step % 2 !== 0 ? (this.player = 1) : (this.player = -1);

// 替換掉grids內的數值

this.$set(this.grids,id,this.player);

},

使用 @click=”set(id)” 讓每一個格子的 id 傳回來,並且判斷這個格子有沒有被按過,以及步數是否在九步內,如果不符合就不進判斷式,如果符合的話就讓步數增加,並且利用三元運算,去判斷這次是誰下手的,我們一開始的 player 預設是叉叉先,所以除 20 的都是叉叉下手,反之圈圈下手。

在我們的 template 裡我們使用 v-if 去判斷現在是誰下手,然後該出現圈圈還是叉叉,這邊剛開始有使用 1 & 2 , 但是發生悲劇,因為後面判斷輸贏是用格子的 id 加總去判斷,如果一個圈跟叉就會發生 1+2=3,但是使用 1 & -1 的話加總只有 -3 -1 1 3 這四種可能,其中前後兩個就可以拿去判斷贏或輸。

1
2
3
4
5
v-if="grid === 1"

or

v-if="grid === -1"

最後用 $set() 去替換掉 grids 內的值,用剛剛判斷的現在是哪一個 player 去替換

關於三元運算

三元運算其實很簡單,並且有效減少你的程式碼,也是這次的收穫之一。看看下方例子就知道超簡單啦!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let a = 100;
let b = 10;

// 三元運算

a > b ? do something : do something2;

// 翻譯蒟蒻

if( a > b ){

do something

} else {

do something2

}

輸或贏?

剛開始的想法是放在 computed 內,因為在學習 Vue 時查看了一下文件,這是一個專門做運算的計算屬性,但是 computed 是根據相依的資料改變時才做計算,不像 method 是不管有無相依都會計算,而且在我打在 computed 的期間,只要 run 專案,就會有警告出現,而且資料並不會即時更新,獲勝後必須去使用 Vue devtolls 才會更新資料,我想應該是渲染問題,這部分會在研究更新。

到這算是踩了一個坑,不過想了一下改了一個作法,既然 computed 不用呼叫就會進去跑,那我也可以退而求其次使用 watch 監聽我的局數,從第二步開始就去看有沒有贏家。

1
2
3
4
5
6
7
watch:{
step(){
if(this.step >= 2 && this.step < 9){
this.checkwinner();
}
}
}

檢查的部分使用迴圈去跑,檢查八條線的贏法,在上面我們已經先把贏法存在陣列裡。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
checkwinner(){
// 檢查八種贏法
for(let i = 0; i < 8 ; i++){
const [a, b, c] = this.Lines[i];
const sum = this.grids[a] + this.grids[b] + this.grids[c];
if(sum === 3){
this.Xscore ++; // 叉叉贏加分
localStorage.setItem("Xwin", this.Xscore); // localStorage下方說明
this.play_area = false; // 隱藏遊戲區
this.Xwin = true; // 顯示叉叉贏的畫面
}
if(sum === -3){
this.Oscore ++;
localStorage.setItem("Owin", this.Oscore); // localStorage下方說明
this.play_area = false; // 隱藏遊戲區
this.Owin = true; // 顯示圈圈贏的畫面
}
}
},

平手怎麼辦?

因為沒有使用 computed 所以沒有 return 的值讓你去判斷,但我們可以自己寫一個 checkdraw() ,並在 watch 判斷內直接寫上,並在 methods 內加上式子,顯示平手畫面等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
watch(){
step(){
if(this.step >= 2 && this.step < 9){
this.checkwinner();
}
// 第九步直接判斷為平手
if(this.step === 9){
this.checkdraw(); // 直接在watch內新增上判斷
}
}

////////////////////////////////////////

methods:{
checkdraw(){
// 第九步直接判斷為平手
localStorage.setItem("DRAW!!", this.drawtimes);
this.drawtimes ++;
this.play_area = false;
this.draw = true;
},
}

重新開始遊戲

1
2
3
4
restart(){
this.player = 1;
this.grids = [0, 0, 0, 0, 0, 0, 0, 0, 0];
}

在按鈕上加上 @click=”restart” 後,這邊很簡單的只做初始化遊戲。

RESTART Btn


打造新手遊

遊戲帶出門

現在越來越多版需要符合手機大小,這邊使用 @Media 的方式,另外也可以搭上 Boostrapcol 去做排版,可以省的手機版還要調大小呢。

學會善用咪低壓

因為 BOSS 的弱點必須至少在 iPhone SE iPhone 8 iPhone PLUS 可以遊玩,這幾個此吋都介於差不多之間,就把他們一起編輯了,當然詳細的手機畫面還是要放到手機看比較清楚,因為網頁的跟手機的畫面高度差一小截呢,菜雞可是踩過幾次坑。

1
2
3
4
5
6
@media screen and (min-width: 320px) and (max-width: 576px) {
.yourClassName or #id{
width:100px;
height:100px;
}
}

名留青史善用 LocalStorage

LocalStorage

這是一個 HTML5 提供的 web storage,擁有 5MB 大小,但是無法跨域使用,但是你可以存一些資訊在上面,好比我們的戰績,或是購物網站常用的,短時間內你關掉網頁不用再重新登入。

localStorage 是以 key-value 儲存資料,所以我們可以給他一個 item name 還有一個 value,使用方式也很簡單,始用 setItem 給他一個 ( name , value ) 就存進去啦,之後除非你去手動清除或者程式設計者執行 clear ,否則會一直存在,甚至重開機。

1
localStorage.setItem("Xwin", this.Xscore);

在我們每次載入遊戲頁面的時候,使用 created 去取得 localStorage 的資料,說到 created 就會提到 Vue 的生命週期,之後也會在 Vue系列介紹,這邊先讓我們取出你的戰績吧。

1
2
3
4
created() {
this.Xscore = localStorage.getItem("Xwin") ? localStorage.getItem("Xwin") : 0;
this.Oscore = localStorage.getItem("Owin") ? localStorage.getItem("Owin") : 0;
},

關於 GitHub 的那點小事

由於以往都是單純 HTML 的檔案上傳,這次第一次使用專案方式整包上傳,所以造成網址讀不到,可以收尋 webpack baseUrl 爬文看看。
現在這情況下可以先到根目錄下創建一個 vue.config.js 檔案,並且在內打上:

1
2
3
4
5
module.exports = {

baseUrl: ""

};

之後重新 Build 一次專案,然後在網址後面加上 **dist/index.html#/**,就能跑出來了。

終於見到作品啦


Conclusion & 結論

這次花了幾天的時間在看 Vue 文件,最後抱著跟當初勇闖地下城的心態一樣,先做了再說,許多時候都是這樣跌跌撞撞再成長,對菜雞我這種人來說只是一昧的看文件是看不懂的,加上現在地下城有許多 Vue 大神在勇闖,我想這不失為一個學習的好機會。

一直想加強自己的邏輯概念,這次 Vue 正好是一個磨練的好時機,雖然越高樓層越來越難,不過抱著就算是成長 0.1 也要往上衝的態度,我想幾年後回頭看看自己現在,一定有所收穫,最後除了繼續努力之外還是要列出檢討的地方,然後朝下一關繼續加油,完畢。

  • JS

  • 邏輯

  • 文筆

  • 體重


我的其他範例

  • Hero Of UnderGround — 1F Multiplicatio 九九乘法表

    Demo:點我

    Code Source:點我

  • Hero Of UnderGround — 2F Clock 時鐘

    Demo:點我

    Code Source:點我

  • Hero Of UnderGround — 3F Calculator 計算機

    Demo:點我

    Code Source:點我

  • Hero Of UnderGround — 4F World Clock 各國時區

    Demo:點我

    Code Source:點我

    Blog:點我

  • Hero Of UnderGround — 5F AQI 全台空氣指標儀表板

    Demo:點我

    Code Source:點我

    Blog:點我

  • Hero Of UnderGround — 6F SECONDS CHALLENGE 倒數遊戲

    Demo:點我

    Code Source:點我

    Blog:點我

  • Hero Of UnderGround — 7F Canvas 畫板

    Demo:點我

    Code Source:點我

    Blog:點我

參考網站