0%

The F2E Freecell Part 1

文章目的

有鑑於本周 THE F2E 的新接龍有許多邏輯方面的設計,想藉由本篇將開發邏輯記錄下來,另外也想嘗試發布部落格,因此將本篇當作第一篇文章。

本次使用技術

  1. Bootsrap 4
  2. Scss
  3. JavaScript 原生語法
    原本是想用 Vue 開發,後來覺得應該用原生語法也不致於太難,但做到一半就開始後悔怎麼沒有 Vue (因為方便好多XD)。
    這次使用原生語法就當作加強自己的觀念,也很感謝有懷恩老師的直播開發流程(文章最後會附上連結),沒有老師的直播我應該會卡關到懷疑人生。

新接龍初步分析

新接龍規則部分我們這邊就不多加解釋,附上 遊戲規則
新接龍總共分為3塊遊戲區,分別是:

  1. 完成區(左上角,共4格)
  • 只能同花色堆疊
  • 數字要由小到大按順序堆疊
  • 堆疊完成的牌不需要再被拖曳
  1. 暫存區(右上角,共4格)
  • 每個空格只能存放一張卡片
  • 空格裡的卡片可以再被拖曳至完成區或未整理區
  1. 未整理區(下方,共7排,左4排初始7張,右4排初始6張)
  • 只能異色堆疊
  • 數字要由小到大按順序堆疊
  • 空排要能放牌且不限數字
  • 此區卡片可以被拖曳至暫存區與完成區

有了這樣初步的了解,我們可以開始來了解 JS 的語法。
切版部分本文章不會提到,我是利用 Bootstrap 4 和一些 SCSS 來完成切版。

變數宣告與各區域陣列分配

首先,宣告一個變數來決定遊戲是否暫停,後續的一些功能處裡,暫停遊戲都會影響到,以下是程式碼:

1
let isgamePause = false;

我們可以將完成區看成一個區域,暫存區看成一個區域,未整理區看成一個區域,因此這裡變數可以這樣設計:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let finishArea = [[], [], [], []]; //完成區域左上角

let temporaryArea = [[], [], [], []]; //卡片暫放區(每一格只能放一張)右上角

let maingameArea = [ //未整理區共8排
[], //7張
[], //7張
[], //7張
[], //7張
[], //6張
[], //6張
[], //6張
[] //6張
];

每個大區域就是一個陣列,每個大區域裡的每一個空格就是一個陣列。
另外因為我是用 Bootstrap 4 的格線系統做排版,我為了方便設計,將未整理區又分為左右7張與6張兩區,變數宣告如下:

1
let cardbigGroup = [[], []];

隨機發牌

接下來我們來設計隨機發牌,讓系統可以在遊戲開始時隨機發牌到未整理區的陣列裡,而且按照7張、6張的規則排列。

  • 撲克牌總共有52張,因此我們就用1~52的數字當作我們的牌,這裡我們宣告一個陣列將1~52的數字儲存進去。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //利用 for 迴圈
    let pokerArr = [];
    for (let i = 0; i < 52; i++) {
    pokerArr.push(i + 1)
    }

    //利用 Array.from 和 Array.map
    let pokerArr =Array.from(new Array(52)).map(function(item,index){
    return index+1
    })
  • 接著要將陣列的52個數字打亂,這邊我用的方法是 shuffle,有關 shuffle 相關介紹可參考這篇,以下是程式碼。

    1
    2
    3
    4
    5
    6
    7
    8
    9
     function shuffle(array) {
    for (let i = array.length - 1; i > 0; i--) {
    let j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
    }
    return array;
    }

    let pokerRandom = shuffle(pokerArr);
  • 我們已成功打亂數字,接著就是要將數字按照規則排列,我們一開始宣告的maingameArea就派上用場了,我們可以按照規則將數字一一塞入maingameArea的陣列裡,以下是程式碼。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function mainpokerArr() {
    let randomarrNum = Math.floor(Math.random() * 8);
    if (randomarrNum <= 3) {
    if (maingameArea[randomarrNum].length >= 7) {
    return mainpokerArr()
    }
    } else {
    if (maingameArea[randomarrNum].length >= 6) {
    return mainpokerArr()
    }
    }
    return randomarrNum;
    }

    這段程式碼的想法是因為maingameArea總共有8個陣列,從左到右的索引號是0~7,因此我們利用randomarrNum來隨機選出一個索引號。
    透過判斷式讓索引號為0~3時陣列長度維持為7張,索引號為4~7時長度維持為6張。
    現在我們有了符合規則的發牌方法,接下來就要將牌一一放進陣列裡,以下是程式碼。

    1
    2
    3
    4
    pokerRandom.map(function (item) {
    let runmainpokerArr = mainpokerArr();
    maingameArea[runmainpokerArr].push(item);
    });

    這裡讓未整理區maingameArea透過我們剛剛設計的發牌函式mainpokerArr()將我們剛打亂的陣列pokerRandom裡的數字放入未整理區的8個陣列裡。
    這邊因為我自己的設計關係,我再把maingameArea裡的8個陣列,分為4個一組放入更大的陣列cardbigGroup,以下是程式碼。

    1
    2
    3
    4
    5
    6
    7
    maingameArea.forEach(function (item, index) {
    if (index <= 3) {
    cardbigGroup[0].push(item);
    } else {
    cardbigGroup[1].push(item)
    };
    });
  • 隨機發牌的資料都按照規則整理完畢了,我們接下來要做的就是讓牌可以渲染到畫面上
    首先,因為我們的數字是1~52,但系統並不知道花色,因此我們要先來定義花色,以下是程式碼。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function judgeColor(cardNum) {
    if (cardNum >= 1 && cardNum <= 13) { //1~13是黑桃
    return 'spade'
    } else if (cardNum >= 14 && cardNum <= 26) { //14~26是紅心
    return 'heart'
    } else if (cardNum >= 27 && cardNum <= 39) { //27~39是方塊
    return 'diamond'
    } else if (cardNum >= 40 && cardNum <= 52) { //40~52是梅花
    return 'club'
    }
    };

    花色也定義完成後,我們就要正式選渲染畫面,因為是原生語法關係,我們利用 createElementappendChild 來做渲染,因為程式碼較多,先來看看完整程式碼,我再根據每個部份做解釋。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
     let gamingArea = document.getElementById('gamingArea');
    function putCard() {
    cardbigGroup.forEach(function (section, sectionNum) {
    let cardbiggroupPart = document.createElement('div');
    cardbiggroupPart.className = 'col-6 d-flex w-100';
    gamingArea.appendChild(cardbiggroupPart);
    section.forEach(function (item, index) {
    let cardGroup = document.createElement('div');
    cardGroup.className = 'relative w-100';
    cardGroup.style.height = '600px'
    cardGroup.group = index;
    cardGroup.section = sectionNum;
    cardbiggroupPart.appendChild(cardGroup)
    item.forEach(function (el, num) {
    let oneCard = document.createElement('div');
    oneCard.className = 'cardArea absolute';
    if (!isRefresh) {
    oneCard.style.transition = 'all .3s'
    oneCard.style.top = '-1000px';
    oneCard.style.left = '-2000px';
    setTimeout(function () {
    oneCard.style.top = num * 30 + 'px';
    oneCard.style.left = '0px'
    }, index * num * 30)
    } else {
    oneCard.style.top = num * 30 + 'px';
    oneCard.style.left = '0px'
    }
    let cardImg = document.createElement('img');
    cardImg.draggable = false
    cardImg.card = el;
    cardImg.section = sectionNum;
    cardImg.group = index;
    cardImg.color = judgeColor(el)
    cardImg.src = `pokerimg/card-${judgeColor(el)}-${el % 13}.svg`;
    if (!isgamePause && num + 1 == item.length) {
    oneCard.draggable = true;
    cardImg.draggable = true
    }
    oneCard.appendChild(cardImg)
    cardGroup.appendChild(oneCard);
    })

    })
    });

    };
    1
    2
    3
    4
    5
    6
    let gamingArea = document.getElementById('gamingArea');
    function putCard() {
    cardbigGroup.forEach(function (section, sectionNum) {
    let cardbiggroupPart = document.createElement('div');
    cardbiggroupPart.className = 'col-6 d-flex w-100';
    gamingArea.appendChild(cardbiggroupPart);

    我利用 getElementById選取要做為未整理區最外層的 HTML 元素。
    宣告一個函式負責執行隨機發牌,接著我要一層一層的剝開我們的陣列往內進逼針對陣列裡的元素做設定。
    第一個陣列就是分為左右兩大區塊的cardbigGroup,讓兩塊區域都createElement區塊元素(div)出來,那因為我有使用格線來排版,所以為他們加了一些排版相關的className
    接著就可以利用appendChild將我們創造出來的兩個區塊元素加入到gamingArea下方。

    1
    2
    3
    4
    5
    6
    7
    section.forEach(function (item, index) {
    let cardGroup = document.createElement('div');
    cardGroup.className = 'relative w-100';
    cardGroup.style.height = '600px'
    cardGroup.group = index;
    cardGroup.section = sectionNum;
    cardbiggroupPart.appendChild(cardGroup)

    第二個碰到的陣列是各區底下的4塊小區域(7張牌與6張牌),原理一樣我們為每塊區域創造區塊元素,並將入一些自定義的classNamestyle
    這邊比較重要的一點是**為每個區塊元素增加一些屬性來記錄它所在的位置(group,section)**,這些屬性將會在後面我們要製作拖曳效果時會用到。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    item.forEach(function (el, num) {
    let oneCard = document.createElement('div');
    oneCard.className = 'cardArea absolute';
    if (!isRefresh) {
    oneCard.style.transition = 'all .3s'
    oneCard.style.top = '-1000px';
    oneCard.style.left = '-2000px';
    setTimeout(function () {
    oneCard.style.top = num * 30 + 'px';
    oneCard.style.left = '0px'
    }, index * num * 30)
    } else {
    oneCard.style.top = num * 30 + 'px';
    oneCard.style.left = '0px'
    }

    最後我們碰到的就是我們的主角–撲克牌,這邊一樣是為每張牌創建區塊元素,那因為我採用的設計稿是 吳俊儀設計師的設計稿,俊儀設計師很佛心的把每張牌做成svg,所以我待會只要把每張svg appendChild到每張牌的div底下就好。
    至於判斷式的部分,是設計遊戲開始時的卡片出現動畫,這邊我就不多加敘述,卡片排版方式是利用絕對定位,那position:relative的部分我是設定給上一層的8個小區塊。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let cardImg = document.createElement('img');
    cardImg.draggable = false
    cardImg.card = el;
    cardImg.section = sectionNum;
    cardImg.group = index;
    cardImg.color = judgeColor(el)
    cardImg.src = `pokerimg/card-${judgeColor(el)}-${el % 13}.svg`;
    if (!isgamePause && num + 1 == item.length) {
    oneCard.draggable = true;
    cardImg.draggable = true
    }
    oneCard.appendChild(cardImg)
    cardGroup.appendChild(oneCard);
    })

    這邊我們就來處理圖片的部分,為每個數字創造img標籤,src的部分我們可以透過命名與判斷顏色的函式(judgeColor),來取得符合該數字的圖片。
    一樣的,我們玩遊戲時都會拖曳這些卡片,因此也在這為它新增一些屬性(card,section,group,color),分別是數字、所在的大區塊、所在的小區塊、花色
    另外一樣重要的就是**draggable,設定它trueorfalse會決定該元素是否能拖曳,那因為遊戲規則只有最後一張牌可以拖曳**,因此額外增加判斷式來判斷該張卡片是否為最後一張牌。
    最後一樣appendChild至上一層元素中。

結語

恭喜你!! 做到這裡,你的新接龍已經可以隨機發牌並且渲染到畫面上了。
也謝謝你,看完我的文章!! 這是我第一次用 hexo 建立部落格與寫文章,排版可能還有點拙劣,傷眼抱歉。
有關後續的完成區、暫存區與拖曳效果,我會再利用時間補上。

附上我的 DEMO程式碼


參考資料

懷恩老師的開發直播

shuffle 文章

Ray大的新接龍文章

吳俊儀設計師的設計稿