小屋創作

日誌2021-09-17 15:37

JS網頁遊戲-客製化Nonogram

作者:熾炎之翼



遊戲連結:https://skylining823.github.io/nonogram/index.html


前言:

度度度 一直說要做的Nonogram
最初會想做這個遊戲
不只是當作練習HTML+CSS+Javascript
其實主要是因為我自己在玩其他Nonogram的時候被各種廣告煩到很不爽
而且不能自訂行列 生命值也沒辦法自己決定
再加上我其實想要玩看看隨機版面
所以就乾脆自己動手做了
過程其實很辛苦 畢竟JS跟CSS學不到幾個禮拜
而且開學以後比較忙 所以就被我擱置了
直到昨天12點不知道發生什麼事直接肝到3點
今天早上沒啥課就直接調整得差不多了

實際做好真的很有成就感


規則:

首先介紹一下Nonogram規則
https://zh.wikipedia.org/wiki/數織

就是利用行列的提示來找到黑塊
4代表有一組4格黑塊 33代表有兩組3格黑塊 1111就是有四組1格黑塊
而數字順序即代表出現的順序
比如10格中出現54即代表
■■■■■X■■■■
以此類推 算是不難上手


遊戲介面:

一開始遊戲介面是這樣

版面配置:決定行列 不過請注意數字不要寫太大一來是版面容易跑掉(可以靠縮放解決),二來是你瀏覽器可能會跑到當掉XD
難度:分為1~3(填小於1會歸類在1,填大於3會歸類在3),數字越大離散度越高,總體難度越難
生命值:預設3,無上限(至少1),點錯到紅塊會扣1,剩餘0則遊戲結束

以下以5x5開局來介紹:

設定好後 按「確認/重置」按鈕可以開始/換新一局遊戲

點擊灰塊提示按鈕可以得到完整提示
(在過大的行列容易出現數字溢出框的情況,這功能就很重要了)


所有黑塊找出來即可獲勝

反之要是一直點錯 生命值歸0就會重置一局遊戲

最後 如果版面亂掉調整視窗即可


遊戲主軸目前差不多就這樣
簡單又有可玩性 很需要動腦來解題

技術方面:

下面來講解技術細節
一樣先只講解JS的核心概念
有空再補剩下的
畢竟感覺要打好多好多...QQ

HTML
<!DOCTYPE html>
<html>

<head>
    <link rel="stylesheet" type="text/css" href="style.css">
    <title>Nonogram</title>
</head>

<body>
    <div id="settingBoard">
        <b>版面配置:</b>
        <input type="text" id="row" class="settingItem" placeholder="Rows">
        <b>×</b>
        <input type="text" id="col" class="settingItem" placeholder="Cols">
    </div>
    <div id="difficultBoard">
        <b>難度:</b>
        <input type="text" id="difficult" placeholder="1~3">
        <b>生命值:</b>
        <input type="text" id="life" value="3">
    </div>
    <div id="confirmBtnBoard">
        <button id="confirmBtn">確定 / 重置</button>
    </div>
    <div id='warningBoard'>
        <b>提示: 如果版面亂掉請重新縮放頁面大小,點擊灰框可以得到完整提示</b>
    </div>
    <div id="nowLifeBoard">
        <b id="nowLifeText">當前生命值:</b>
        <div id="nowLife">0</div>
    </div>
    </div>
    <div id="container">
        <div id="Board">
        </div>
    </div>
    <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous">
    </script>
    <script src="script.js"></script>
</body>

</html>


CSS
#settingBoard,
#difficultBoard,
#confirmBtnBoard,
#warningBoard,
#nowLifeBoard {
    display: flex;
    justify-content: center;
    position: relative;
    flex-direction: row;
    margin: 10px;
}

#nowLifeText {
    font-size: 20px;
    margin-top: 5px;
}

#nowLife {
    font-size: 30px;
    color: red;
    margin-left: 10px;
}

.settingItem,
#difficult,
#life {
    width: 35px;
    margin-left: 10px;
    margin-right: 10px;
}

#container {
    position: relative;
    margin: auto;
    margin-right: 50px;
    display: flex;
    justify-content: center;
}

#Board {
    position: relative;
    width: 500px;
    height: 500px;
}

.hiddenBtn,
.pressBtn,
.hintBtn {
    -webkit-appearance: none;
    width: 50px;
    height: 50px;
    margin: 15px;
    float: left;
    border-width: 1px;
    border-radius: 0%;
    margin: 0px;
}

.hintBtn {
    background-color: gray;
    text-align: left;
}

.hiddenBtn {
    background-color: rgba(0, 0, 0, 0);
    border-width: 0px;
    margin: 0px;
}


Javascript
const row = document.getElementById("row")
const col = document.getElementById("col")
const difficult = document.getElementById("difficult")
const life = document.getElementById("life")
const confirmBtn = document.getElementById("confirmBtn")
const Board = document.getElementById('Board')


function colCount(rowNum, colNum, ansArray) {
    for (let i = 0; i < colNum; i++) {
        let numArray = new Array();
        let colArray = new Array();
        count = 0;
        t = 0;
        for (let j = 0; j < rowNum; j++)
            colArray.push(ansArray[j][i])
        for (let j = 0; j < colArray.length; j++) {
            if (colArray[j] == 1) {
                count += 1;
            } else {
                if (count == 0)
                    continue;
                else {
                    numArray.push(count);
                    count = 0;
                    t++;
                }
            }
        }
        if (count != 0) {
            numArray.push(count);
            t++;
        }
        if (t == 0) {
            numArray.push(0);
        }
        for (let k = 0; k < numArray.length; k++) {
            document.getElementById(`Col${i+1}`).innerText += String(numArray[k]) + ".";
        }
    }
}

function rowCount(rowNum, colNum, ansArray) {
    for (let i = 0; i < rowNum; i++) {
        let numArray = new Array();
        count = 0;
        t = 0;
        for (let j = 0; j < colNum; j++) {
            if (ansArray[i][j] == 1) {
                count += 1;
            } else {
                if (count == 0)
                    continue;
                else {
                    numArray.push(count);
                    count = 0;
                    t++;
                }
            }
        }
        if (count != 0) {
            numArray.push(count);
            t++;
        }
        if (t == 0) {
            numArray.push(0);
        }
        for (let k = 0; k < numArray.length; k++) {
            document.getElementById(`Row${i+1}`).innerText += String(numArray[k]) + ".";
        }
    }

}

function check(rowNum, colNum, ansArray) {
    let s = 0;
    for (let i = 0; i < rowNum; i++) {
        for (let j = 0; j < colNum; j++) {
            if (ansArray[i][j] == 1) {
                s = 1;
                break;
            }
        }
    }
    if (s == 0) {
        alert("恭喜你找出所有黑塊!!!");
    }
}


confirmBtn.addEventListener("click", function() {
    rowNum = parseInt(row.value);
    colNum = parseInt(col.value);
    difficultNum = parseInt(difficult.value);
    lifeNum = parseInt(life.value);

    if (lifeNum < 1)
        lifeNum = 1;
    if (difficultNum < 1)
        difficultNum = 1;
    else if (difficultNum > 3)
        difficultNum = 3;

    document.getElementById("nowLife").innerText = lifeNum;
    let nowlife = document.getElementById("nowLife").innerText

    Board.innerHTML = ''
    for (let i = 0; i < rowNum + 1; i++) {
        for (let j = 0; j < colNum + 1; j++) {
            if (i == 0 && j == 0) {
                Board.innerHTML += `<button class="hiddenBtn"></button>`;
            } else if (i == 0) {
                Board.innerHTML += `<button class="hintBtn" id="Col${j}"></button>`;
            } else if (j == 0) {
                Board.innerHTML += `<button class="hintBtn" id="Row${i}"></button>`;
            } else {
                Board.innerHTML += `<button class="pressBtn" id="${i}x${j}"></button>`;
            }
        }
    }

    let ansArray = new Array(); //先宣告一維
    for (let i = 0; i < rowNum; i++) {
        ansArray[i] = new Array(); //宣告二維
        for (let j = 0; j < colNum; j++) {
            let randNum = Math.floor(Math.random() * 10);
            if (randNum <=
8 - difficultNum)
                ansArray[i][j] = 1;
            else
                ansArray[i][j] = 0;
        }
    }

    rowCount(rowNum, colNum, ansArray);
    colCount(rowNum, colNum, ansArray);

    let pressBtn = document.getElementsByClassName("pressBtn");
    let hintBtn = document.getElementsByClassName("hintBtn");

    for (let i = 0; i < pressBtn.length; i++) {
        pressBtn[i].addEventListener("click", function() {
            const btnRow = Math.floor(i / colNum);
            const btnCol = i % colNum;
            if (ansArray[btnRow][btnCol] == 1) {
                this.style.backgroundColor = "black";
                ansArray[btnRow][btnCol] = 0;
                check(rowNum, colNum, ansArray);
            } else {
                if (this.style.backgroundColor != "black") {
                    this.style.backgroundColor = "red";
                    this.innerHTML = "X"
                    nowlife -= 1;
                    document.getElementById("nowLife").innerText = nowlife;
                    if (nowlife == 0) {
                        alert("您的生命值已歸0!\n遊戲將重置");
                        confirmBtn.click();
                    }


                }
            }
        })
    }

    for (let i = 0; i < hintBtn.length; i++) {
        hintBtn[i].addEventListener("click", function() {
            alert(hintBtn[i].innerText);
        })
    }

    Board.style.cssText = `width: ${(colNum+1)*50}px; height: ${(rowNum+1)*50}px;`;
})

本人網頁開發菜逼八 請鞭小力

程式主要圍繞於confirmBtn.addEventListener()
講白話就是點擊確認按鈕後即開始動作
程式會按照玩家輸入的Row數Col數開始生成按鈕
這邊其實生成的行列數都會各多一
因為要預留給提示方塊按鈕
為了維持版面 其實左上角的缺口也有個隱藏按鈕
只是被我從CSS用background-color: rgba(0, 0, 0, 0);調成透明的而已

接著宣告一個二維陣列ansArray來生成對應整個局面的地圖
我的邏輯就是1代表黑塊 0代表紅塊
程式會照版面亂數生成0,1
其實一開始0,1的生成機率是1:1
但是經過我本人和朋友們測試 這樣會讓離散度太高
實際玩起來會難到哭
而先前輸入的難度就用在這裡了
我事後運用了難度difficultNum來調節生成機率
黑紅生成機率 難度1是7:3 難度2是6:4 難度3是5:5
這樣就很大程度解決了這個問題
也進一步增加客製程度

然後用rowCount()colCount()兩個函式
根據ansArray()來偵測版面
並且把提示數字印到提示按鈕上

之後就用迴圈幫每個按鈕都掛上addEventListener()
也就是裝上各自該有的功能與邏輯判斷

每個隱藏的黑塊被點擊後除了現形之外
同時在ansArray上的值都會從1改成0
再去跑前面寫的check()函式做檢測
一旦整個數值地圖上都沒有1
則判斷玩家勝利

每個隱藏紅塊被點擊除了現形之外
會讓生命值扣1再判斷剩下的生命值
如果生命值歸0則判斷玩家敗北
再彈出提示以後即重新一局遊戲

順帶一提 紅塊的判斷有加上條件 this.style.backgroundColor != "black"
來避免點到已經找到的黑塊
因為數值地圖上該座標變成0
而被系統誤判斷採到紅塊的窘境

每個提示灰塊的功能就很簡單
點擊以後用彈出alert的方式告訴玩家提示數字

最後的Board.style.cssText = `width: ${(colNum+1)*50}px; height: ${(rowNum+1)*50}px;`;
就是在動態調整版面容器大小
讓版面可以維持成Row+1 x Col+1的格式

技術就差不多到這邊
剩下之後有空補

檢討要點:

這次總體來說完整度偏高

不過對 我到打這篇文的時候
才發現我忘記做手動打X功能 = =
這之後要補上

然後其實也能加上紀錄勝敗場的功能等等
現在只是基本功能齊全(除了手動打X = =)而已

還有一個問題雖然發生機率不太大但是確實存在
就是亂數的不可控 就算可以用難度來限制大致趨勢
但還是有機會出現難度1出現超難版面 難度3出現超智障版面的狀況
這部分目前也只能重置局面來解決了
還要再想想有啥限制的方式



度度度 打好多字
差不多就這樣 歡迎大家遊玩
有BUG或是建議請務必在留言告訴我

最後祝大家玩得開心~
感謝觀看


24

14

LINE 分享

相關創作

造型練習。 0501⋯⋯臨摹日系可愛的畫風

【生活相關】2024/04/30拜二、四月份營運報表結算及整理&甜點

【五月挑戰任務LV10】蚩尤無腦通關

留言

開啟 APP

face基於日前微軟官方表示 Internet Explorer 不再支援新的網路標準,可能無法使用新的應用程式來呈現網站內容,在瀏覽器支援度及網站安全性的雙重考量下,為了讓巴友們有更好的使用體驗,巴哈姆特即將於 2019年9月2日 停止支援 Internet Explorer 瀏覽器的頁面呈現和功能。
屆時建議您使用下述瀏覽器來瀏覽巴哈姆特:
。Google Chrome(推薦)
。Mozilla Firefox
。Microsoft Edge(Windows10以上的作業系統版本才可使用)

face我們了解您不想看到廣告的心情⋯ 若您願意支持巴哈姆特永續經營,請將 gamer.com.tw 加入廣告阻擋工具的白名單中,謝謝 !【教學】