iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 30
1

難度控制

Day22 我們有提到,隨著過了越多關,關卡會越來越難。我們這邊要讓題目變難的方法有兩個方向,第一個是題目會越來越長,光是這樣還不夠,而且我還希望畫面上的方塊能夠越來越多。

然後,不能只有題目變難而已,我們要讓犯錯的次數是有限制的,如果讓玩家不限次數的犯錯,這樣就沒有遊戲性,因為就算隨便亂按,總有一天還是會過關,這樣就不好玩了,所以我們也要來限制一下犯錯次數。

增加題目的長度

首先,我分來增加題目的長度。在 Day28 的時候我們由實作當過關的時候,level 會加一。由於題目的長度跟關卡 level 的數字有正相關,所以我們在設計遊戲長度的時候,會把 level 當作參數加進去。
src/containers/MemoryBlocks/utils.js

export const generateLevelData = (level, sideLength) => {
    const maxNote = sideLength * sideLength;
    const numOfNote = level + sideLength; // level 數大小會決定題目長度
    const levelData = Array.from(Array(numOfNote), (value, index) => Math.floor(Math.random() * maxNote));
    return levelData;
};

所以,當我們的題目是由上面這個函數所產生的時候,題目長度會隨著關卡增加而變長。

note-number

增加方塊數目

再來我們隨著關卡增加來增加方塊數目。因為我們目前方塊排列的上限是 5x5 ,所以如果我們過一關就把方塊增加的話,這樣一下子就到 5x5 了,覺得這樣很沒有挑戰性,所以我希望過了幾關之後,再來增加方塊數目。我自己有試玩過幾組數字,如果5關當作一個單位,每過5關增加一次方塊數目的話,覺得大概玩到 3x3 就會玩不下去了,因為光是 3x3 的方塊,就需要記住十幾個音符,所以我覺得難度太難。

那如果3關當作一個單位,每過3關就增加一次方塊數目,其實又覺得太簡單,所以我這邊就折衷,以4關為一個單位,來做為我們調整難度的間隔
src/containers/MemoryBlocks/constants.js

export const LEVEL_SET = 4;

// 這邊是限制最高上限為 5x5 的方格,MAX_SIDE_LENGTH是 5
const updatedSideLength = (sideLength + 1) > MAX_SIDE_LENGTH ? MAX_SIDE_LENGTH : (sideLength + 1);

src/containers/MemoryBlocks/reducer.js
.updateIn(['sideLength'], (sideLength) => {
    if (updatedLevel % LEVEL_SET === 0) { // 關卡是4的倍數,就增加方塊數目
        return updatedSideLength; 
    }
    return sideLength; // 否則就是維持原狀
})

block-number

限制犯錯次數 - 命的機制

最後,我們要來限制犯錯次數以及重播次數。這個靈感是想到以前在玩瑪利歐或是玩洛克人的時候,都會有命的限制。有了這個之後,對玩家而言,就不能隨便玩,對待遊戲的時候會比較謹慎,不會隨便亂按,然後因為機會有限,所以會開始想各種辦法希望能夠破到最後一關,當玩家開始產生這種心理的時候,過關就會變得有成就感,所以遊戲會變得有趣

TnTmMbE

所以首先,我們要先給個起始值,預設有幾命,如果這個值給太小,玩家就不容易玩到後面的關卡,所以就會白費我們把 5x5 的方塊設計得那麼漂亮的苦心,因為太難了,玩家永遠都看不到。但是如果值給很大,那玩家就會覺得多死幾次沒關係,就會失去我們設計這個命的機制的初衷,所以我把遊戲拿給朋友試玩之後,自己也玩玩看,覺得大概一開始給 5 命比較剛好。
src/containers/MemoryBlocks/constants.js

export const DEFAULT_CHANCE = 5;

然後這個命的機制,如同我們一開始設計的時候,在 Day22 所說的,過一關就可以增加一命,其實這個也是一種獎勵機制,讓玩家過關之後能夠得到回饋,且有成就感,這個機制也能夠幫助玩家玩到比較後面的關卡,讓玩家有種努力就能夠得到收穫的感覺。

所以過一關增加一命的部分作法如下
src/containers/MemoryBlocks/reducer.js

if (isCorrect && (updatedAnswer.size === levelData.size)) {
    // if correct and complete
    const updatedLevel = level + 1;
    const updatedSideLength = (sideLength + 1) > MAX_SIDE_LENGTH ? MAX_SIDE_LENGTH : (sideLength + 1);
    return state
        ...
        ...
        .updateIn(['chance'], (chance) => {
            return chance + 1;
        });
}

接著,我們有兩種狀況需要扣一命,第一個是答錯的時候

src/containers/MemoryBlocks/reducer.js

return state
    .set('isCorrect', false)
    .set('answer', List())
    .updateIn(['chance'], (chance) => chance - 1);

第二個是我們重播音樂的時候,我希望也能扣一命,因為我不希望玩家能夠無限重播
src/containers/MemoryBlocks/reducer.js

case SET_REPLAY_SOUND: {
    return state
        .updateIn(['chance'], (chance) => chance - 1);
}

Day29 的時候我們有實作重播音樂按鈕,按下去的時候,就會執行上面這一段程式。最後,我們需要在畫面告訴使用者目前還有幾命,這邊我想要把這個還有幾命的資訊跟重播音樂按鈕結合,也就是重播音樂按鈕上面不要寫重播,而是要寫還有幾命。這樣的好處是我們不用再多留一個地方來放這個資訊,這樣玩家有太多地方要看,會眼花撩亂。第二個考量是因為我希望讓玩家注意到重播的時候命是會減少的。

然後這邊重播按鈕我給他一個音符的 icon ,這是取自 Font awesome
src/containers/MemoryBlocks/index.js

{
    isGameStart &&
    <div className="memory-blocks__group-btn-wrapper">
        <button className="memory-blocks__hint-btn memory-blocks__font-music" onClick={this.handleOnReplaySound}>
            <i className="fas fa-music memory-blocks__font-music" />
            <span> x {chance}</span>
        </button>
        <button className="memory-blocks__restart-btn" onClick={this.handleOnGameRestart}>Restart</button>
    </div>
}

到目前為止,我們記憶方塊的所有功能就大功告成啦!
chance

參考程式碼 & 遊戲展示

Memory Blocks - Github


完賽感言 & 準備過程細節大公開

這是我第一次參加鐵人賽,今年也是剛接觸 ReactJS 的一年。感謝跟我一起參賽的團隊和鼓勵我參加的朋友,讓我能夠透過這個有意義的活動來檢視自己。

透過鐵人賽我覺得很有收穫的部分是,程式雖然會寫是會寫,但是很多東西沒有整理的話真的是講不出來,所以在過程當中很多東西也是迫使我再次去找資料和整理,因此也幫助我釐清一些沒有注意到的小細節,覺得參與鐵人賽對自己來說真的很有收穫。

過去不知道為什麼,雖然看過很多 iT邦幫忙 的文章,但是卻沒有聽過鐵人賽,只是很好奇為什麼那麼多人寫 30天系列,只是很單純的覺得是跟風而已,或是 30 可能是一個比較吉利的數字。我是今年 9 月才知道有鐵人賽這個活動,覺得自己真的很孤陋寡聞。

等到決定要參加活動,到活動開始已經只剩下兩個禮拜,所以在準備上真的很匆促,這次的主題是 以經典小遊戲為主題之ReactJS應用練習。所以我要做兩件事情,一個是寫出遊戲,一個是寫文章,每一個遊戲包含做出功能和 refactor 我平均花的時間是一個禮拜,所以真的很趕。因為文章大家都會看到,程式碼也全部都公開,所以沒有 refactor 我真的不太好意思拿出來秀。

寫完程式之後要開始寫文章,文章是從設計和構想開始逐步建構一個完整的遊戲,但是我的遊戲是已經寫完整了才開始寫文章,所以迫使我需要再另開一個新的專案重新再寫一次遊戲,這樣才有辦法一步一步的擷取程式碼和 demo 未完成的功能,所以實際上,每個遊戲我都寫兩次,甚至寫第二次的時候又會發現哪裡寫不好,需要重構什麼的,又會再花一些時間來修正。我的步驟是先想辦法構想出一個遊戲並做出來,然後把這個遊戲切割成 10 個主題,之後撰寫文章再邊重寫程式以便截圖和展示。

我是拖到最後一天才開賽,開賽那一天,我手邊實際上只有圈圈叉叉遊戲以及 8 篇文章,至於後面還要做幾個遊戲,要寫什麼遊戲,我都還沒拿定主意,真的蠻緊張和刺激的,所以我不敢在第一篇就列出完整大綱,誇口說我未來三十天打算要做什麼。然後第二個遊戲貪吃蛇程式寫完之後,其實我也還沒拿定第三個遊戲要寫什麼。

另一方面,貪吃蛇記憶方塊在寫的時候,其實我真的是沒有把握可以在時間內寫完,因為過程當中真的有卡關,眼看著前一系列就要寫完了,結果下一系列的程式還沒寫完,真的超擔心會拖累隊友的。因為上班族的時間也只有下班可以用,還有就是早一點起床來打拼,另外我最多的衝刺時間就是週末,可以的話,六日每天就都至少拼個三篇來當作我的目標,在這不確定性以及時間不足的壓力之下完成了這次鐵人。

這次的鐵人賽,雖然自己的文章可能也不算是什麼好文,但是想到自己上班之後還能夠利用零碎時間做這麼多事情,想到在寫每一篇文章的時候那個過程和心情,覺得對自己而言很有紀念性也很有意義。只有不肯,沒有不能,這是我在過程當中很有感觸的一句話,成功的人找方法,失敗的人找藉口。另外,雖然這次真的很驚險的完賽了,但是我覺得這是一個不太好的方式,比起臨陣磨槍,更好的是平常就要累積,平常就要準備,能夠準時達標的方法,就是要提早去做

以此完賽感言自勉並作為警惕,並謝謝過程當中支持我的文章的大大們。


上一篇
Day29 - 記憶方塊篇:遊戲關卡控制
系列文
以經典小遊戲為主題之ReactJS應用練習30

2 則留言

0
Homura
iT邦研究生 1 級 ‧ 2018-11-14 09:13:18

恭喜完賽/images/emoticon/emoticon42.gif
大大寫得很精彩唷!/images/emoticon/emoticon12.gif

Taiming iT邦新手 5 級‧ 2018-11-14 13:48:32 檢舉

感謝大大一直以來的支持!
/images/emoticon/emoticon25.gif

0
huli
iT邦新手 5 級 ‧ 2018-11-14 16:58:06

很不錯的一個系列,恭喜完賽!

我要留言

立即登入留言