国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

前端每日實(shí)戰(zhàn):164# 視頻演示如何用原生 JS 創(chuàng)作一個(gè)數(shù)獨(dú)訓(xùn)練小游戲(內(nèi)含 4 個(gè)視頻)

OBKoro1 / 1055人閱讀

摘要:第部分第部分第部分第部分源代碼下載每日前端實(shí)戰(zhàn)系列的全部源代碼請(qǐng)從下載代碼解讀解數(shù)獨(dú)的一項(xiàng)基本功是能迅速判斷一行一列或一個(gè)九宮格中缺少哪幾個(gè)數(shù)字,本項(xiàng)目就是一個(gè)訓(xùn)練判斷九宮格中缺少哪個(gè)數(shù)字的小游戲。

效果預(yù)覽

按下右側(cè)的“點(diǎn)擊預(yù)覽”按鈕可以在當(dāng)前頁(yè)面預(yù)覽,點(diǎn)擊鏈接可以全屏預(yù)覽。

https://codepen.io/comehope/pen/mQYobz

可交互視頻

此視頻是可以交互的,你可以隨時(shí)暫停視頻,編輯視頻中的代碼。

請(qǐng)用 chrome, safari, edge 打開(kāi)觀看。

第 1 部分:
https://scrimba.com/p/pEgDAM/c7Q86ug

第 2 部分:
https://scrimba.com/p/pEgDAM/ckgBNAD

第 3 部分:
https://scrimba.com/p/pEgDAM/cG7bWc8

第 4 部分:
https://scrimba.com/p/pEgDAM/cez34fp

源代碼下載

每日前端實(shí)戰(zhàn)系列的全部源代碼請(qǐng)從 github 下載:

https://github.com/comehope/front-end-daily-challenges

代碼解讀

解數(shù)獨(dú)的一項(xiàng)基本功是能迅速判斷一行、一列或一個(gè)九宮格中缺少哪幾個(gè)數(shù)字,本項(xiàng)目就是一個(gè)訓(xùn)練判斷九宮格中缺少哪個(gè)數(shù)字的小游戲。游戲的流程是:先選擇游戲難度,有 Easy、Normal、Hard 三檔,分別對(duì)應(yīng)著九宮格中缺少 1 個(gè)、2 個(gè)、3 個(gè)數(shù)字。開(kāi)始游戲后,用鍵盤輸入九宮格中缺少的數(shù)字,如果全答出來(lái)了,就會(huì)進(jìn)入下一局,一共 5 局,5 局結(jié)束之后這一次游戲就結(jié)束了。在游戲過(guò)程中,九宮格的左上角會(huì)計(jì)時(shí),右上角會(huì)計(jì)分。

整個(gè)游戲分成 4 個(gè)步驟開(kāi)發(fā):靜態(tài)頁(yè)面布局、程序邏輯、計(jì)分計(jì)時(shí)和動(dòng)畫效果。

一、頁(yè)面布局

定義 dom 結(jié)構(gòu),.app 是整個(gè)應(yīng)用的容器,h1 是游戲標(biāo)題,.game 是游戲的主界面。.game 中的子元素包括 .message.digits.message 用來(lái)提示游戲時(shí)間 .time、游戲的局?jǐn)?shù) .round、得分 .score.digits 里是 9 個(gè)數(shù)字:

Sudoku Training

Time: 00:00

1/5

Score: 100

1 2 3 4 5 6 7 8 9

居中顯示:

body {
    margin: 0;
    height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: silver;
    overflow: hidden;
}

定義應(yīng)用的寬度,子元素縱向布局:

.app {
    width: 300px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    user-select: none;
}

標(biāo)題為棕色字:

h1 {
    margin: 0;
    color: sienna;
}

提示信息是橫向布局,重點(diǎn)內(nèi)容加粗:

.game .message {
    width: inherit;
    display: flex;
    justify-content: space-between;
    font-size: 1.2em;
    font-family: sans-serif;
}

.game .message span {
    font-weight: bold;
}

九宮格用 grid 布局,外框棕色,格子用杏白色背景:

.game .digits {
    box-sizing: border-box;
    width: 300px;
    height: 300px;
    padding: 10px;
    border: 10px solid sienna;
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-gap: 10px;
}

.game .digits span {
    width: 80px;
    height: 80px;
    background-color: blanchedalmond;
    font-size: 30px;
    font-family: sans-serif;
    text-align: center;
    line-height: 2.5em;
    color: sienna;
    position: relative;
}

至此,游戲區(qū)域布局完成,接下來(lái)布局選擇游戲難度的界面。
在 html 文件中增加 .select-level dom 結(jié)構(gòu),它包含一個(gè)難度列表 levels 和一個(gè)開(kāi)始游戲的按鈕 .play,游戲難度分為 .easy.normal.hard 三個(gè)級(jí)別:

Sudoku Training

Play

為選擇游戲難度容器畫一個(gè)圓形的外框,子元素縱向布局:

.select-level {
    z-index: 2;
    box-sizing: border-box;
    width: 240px;
    height: 240px;
    border: 10px solid rgba(160, 82, 45, 0.8);
    border-radius: 50%;
    box-shadow: 
        0 0 0 0.3em rgba(255, 235, 205, 0.8),
        0 0 1em 0.5em rgba(160, 82, 45, 0.8);
    display: flex;
    flex-direction: column;
    align-items: center;
    font-family: sans-serif;
}

布局 3 個(gè)難度選項(xiàng),橫向排列:

.select-level .levels {
    margin-top: 60px;
    width: 190px;
    display: flex;
    justify-content: space-between;
}

input 控件隱藏起來(lái),只顯示它們對(duì)應(yīng)的 label

.select-level .levels {
    position: relative;
}

.select-level input[type=radio] {
    visibility: hidden;
    position: absolute;
    left: 0;
}

設(shè)置 label 的樣式,為圓形按鈕:

.select-level label {
    width: 56px;
    height: 56px;
    background-color: rgba(160, 82, 45, 0.8);
    border-radius: 50%;
    text-align: center;
    line-height: 56px;
    color: blanchedalmond;
    cursor: pointer;
}

當(dāng)某個(gè) label 對(duì)應(yīng)的 input 被選中時(shí),令 label 背景色加深,以示區(qū)別:

.select-level input[type=radio]:checked + label {
    background-color: sienna;
}

設(shè)置開(kāi)始游戲按鈕 .play 的樣式,以及交互效果:

.select-level .play {
    width: 120px;
    height: 30px;
    background-color: sienna;
    color: blanchedalmond;
    text-align: center;
    line-height: 30px;
    border-radius: 30px;
    text-transform: uppercase;
    cursor: pointer;
    margin-top: 30px;
    font-size: 20px;
    letter-spacing: 2px;
}

.select-level .play:hover {
    background-color: saddlebrown;
}

.select-level .play:active {
    transform: translate(2px, 2px);
}

至此,選擇游戲難度的界面布局完成,接下來(lái)布局游戲結(jié)束界面。
游戲結(jié)束區(qū) .game-over 包含一個(gè) h2 標(biāo)題,二行顯示最終結(jié)果的段落 p 和一個(gè)再玩一次的按鈕 .again。最終結(jié)果包括最終耗時(shí) .final-time 和最終得分 .final-score

Sudoku Training

Game Over

Time: 00:00

Score: 3000

Play Again

因?yàn)橛螒蚪Y(jié)束界面和選擇游戲難度界面的布局相似,所以借用 .select-level 的代碼:

.select-level,
.game-over {
    z-index: 2;
    box-sizing: border-box;
    width: 240px;
    height: 240px;
    border: 10px solid rgba(160, 82, 45, 0.8);
    border-radius: 50%;
    box-shadow: 
        0 0 0 0.3em rgba(255, 235, 205, 0.8),
        0 0 1em 0.5em rgba(160, 82, 45, 0.8);
    display: flex;
    flex-direction: column;
    align-items: center;
    font-family: sans-serif;
}

標(biāo)題和最終結(jié)果都用棕色字:

.game-over h2 {
    margin-top: 40px;
    color: sienna;
}

.game-over p {
    margin: 3px;
    font-size: 20px;
    color: sienna;
}

“再玩一次”按鈕 .again 的樣式與開(kāi)始游戲 .play 的樣式相似,所以也借用 .play 的代碼:

.select-level .play,
.game-over .again {
    width: 120px;
    height: 30px;
    background-color: sienna;
    color: blanchedalmond;
    text-align: center;
    line-height: 30px;
    border-radius: 30px;
    text-transform: uppercase;
    cursor: pointer;
}

.select-level .play {
    margin-top: 30px;
    font-size: 20px;
    letter-spacing: 2px;
}

.select-level .play:hover,
.game-over .again:hover {
    background-color: saddlebrown;
}

.select-level .play:active,
.game-over .again:active {
    transform: translate(2px, 2px);
}

.game-over .again {
    margin-top: 10px;
}

把選擇游戲難度界面 .select-level 和游戲結(jié)束界面 .game-over 定位到游戲容器的中間位置:

.app {
    position: relative;
}

.select-level,
.game-over {
    position: absolute;
    bottom: 40px;
}

至此,游戲界面 .game、選擇游戲難度界面 .select-level 和游戲結(jié)束界面 .game-over 均已布局完成。接下來(lái)為動(dòng)態(tài)程序做些準(zhǔn)備工作。
把選擇游戲難度界面 .select-level 和游戲結(jié)束界面 .game-over 隱藏起來(lái),當(dāng)需要它們呈現(xiàn)時(shí),會(huì)在腳本中設(shè)置它們的 visibility 屬性:

.select-level,
.game-over {
    visibility: hidden;
}

游戲中,當(dāng)選擇游戲難度界面 .select-level 和游戲結(jié)束界面 .game-over 出現(xiàn)時(shí),應(yīng)該令游戲界面 .game 變模糊,并且加一個(gè)緩動(dòng)時(shí)間,.game.stop 會(huì)在腳本中調(diào)用:

.game {
    transition: 0.3s;
}

.game.stop {
    filter: blur(10px);
}

游戲中,當(dāng)填錯(cuò)了數(shù)字時(shí),要把錯(cuò)誤的數(shù)字描一個(gè)紅邊;當(dāng)填對(duì)了數(shù)字時(shí),把數(shù)字的背景色改為巧克力色。.game .digits span.wrong.game .digits span.correct 會(huì)在腳本中調(diào)用:

.game .digits span.wrong {
    border: 2px solid crimson;
}

.game .digits span.correct {
    background-color: chocolate;
    color: gold;
}

至此,完成全部布局和樣式設(shè)計(jì)。

二、程序邏輯

引入 lodash 工具庫(kù),后面會(huì)用到 lodash 提供的一些數(shù)組函數(shù):

在寫程序邏輯之前,先定義幾個(gè)存儲(chǔ)業(yè)務(wù)數(shù)據(jù)的常量。ALL_DIGITS 存儲(chǔ)了全部備選的數(shù)字,也就是從 1 到 9;ANSWER_COUNT 存儲(chǔ)的是不同難度要回答的數(shù)字個(gè)數(shù),easy 難度要回答 1 個(gè)數(shù)字,normal 難度要回答 2 個(gè)數(shù)字,hard 難度要回答 3 個(gè)數(shù)字;ROUND_COUNT 存儲(chǔ)的是每次游戲的局?jǐn)?shù),默認(rèn)是 5 局;SCORE_RULE 存儲(chǔ)的是答對(duì)和答錯(cuò)時(shí)分?jǐn)?shù)的變化,答對(duì)加 100 分,答錯(cuò)扣 10 分。定義這些常量的好處是避免在程序中出現(xiàn)魔法數(shù)字,提高程序可讀性:

const ALL_DIGITS = ["1","2","3","4","5","6","7","8","9"]
const ANSWER_COUNT = {EASY: 1, NORMAL: 2, HARD: 3}
const ROUND_COUNT = 5
const SCORE_RULE = {CORRECT: 100, WRONG: -10}

再定義一個(gè) dom 對(duì)象,用于引用 dom 元素,它的每個(gè)屬性是一個(gè) dom 元素,key 值與 class 類名保持一致。其中大部分 dom 元素是一個(gè) element 對(duì)象,只有 dom.digitsdom.levels 是包含多個(gè) element 對(duì)象的數(shù)組;另外 dom.level 用于獲取被選中的難度,因?yàn)樗闹惦S用戶選擇而變化,所以用函數(shù)來(lái)返回實(shí)時(shí)結(jié)果:

const $ = (selector) => document.querySelectorAll(selector)
const dom = {
    game: $(".game")[0],
    digits: Array.from($(".game .digits span")),
    time: $(".game .time")[0],
    round: $(".game .round")[0],
    score: $(".game .score")[0],
    selectLevel: $(".select-level")[0],
    level: () => {return $("input[type=radio]:checked")[0]},
    play: $(".select-level .play")[0],
    gameOver: $(".game-over")[0],
    again: $(".game-over .again")[0],
    finalTime: $(".game-over .final-time")[0],
    finalScore: $(".game-over .final-score")[0],
}

在游戲過(guò)程中需要根據(jù)游戲進(jìn)展隨時(shí)修改 dom 元素的內(nèi)容,這些修改過(guò)程我們也把它們先定義在 render 對(duì)象中,這樣程序主邏輯就不用關(guān)心具體的 dom 操作了。render 對(duì)象的每個(gè)屬性是一個(gè) dom 操作,結(jié)構(gòu)如下:

const render = {
    initDigits: () => {},
    updateDigitStatus: () => {},
    updateTime: () => {},
    updateScore: () => {},
    updateRound: () => {},
    updateFinal: () => {},
}

下面我們把這些 dom 操作逐個(gè)寫下來(lái)。
render.initDigits 用來(lái)初始化九宮格。它接收一個(gè)文本數(shù)組,根據(jù)不同的難度級(jí)別,數(shù)組的長(zhǎng)度可能是 8 個(gè)(easy 難度)、7 個(gè)(normal 難度)或 6 個(gè)(hard 難度),先把它補(bǔ)全為長(zhǎng)度為 9 個(gè)數(shù)組,數(shù)量不足的元素補(bǔ)空字符,然后把它們隨機(jī)分配到九宮格中:

const render = {
    initDigits: (texts) => {
        allTexts = texts.concat(_.fill(Array(ALL_DIGITS.length - texts.length), ""))
        _.shuffle(dom.digits).forEach((digit, i) => {
            digit.innerText = allTexts[i]
            digit.className = ""
        })
    },
    //...
}

render.updateDigitStatus 用來(lái)更新九宮格中單個(gè)格子的狀態(tài)。它接收 2 個(gè)參數(shù),text
是格子里的數(shù)字,isAnswer 指明這個(gè)數(shù)字是不是答案。格子的默認(rèn)樣式是淺色背景深色文字,如果傳入的數(shù)字不是答案,也就是答錯(cuò)了,會(huì)為格子加上 wrong 樣式,格子被描紅邊;如果傳入的數(shù)字是答案,也就是答對(duì)了,會(huì)在一個(gè)空格子里展示這個(gè)數(shù)字,并為格子加上 correct 樣式,格子的樣式會(huì)改為深色背景淺色文字:

const render = {
    //...
    updateDigitStatus: (text, isAnswer) => {
        if (isAnswer) {
            let digit = _.find(dom.digits, x => (x.innerText == ""))
            digit.innerText = text
            digit.className = "correct"
        }
        else {
            _.find(dom.digits, x => (x.innerText == text)).className = "wrong"
        }
    },
    //...
}

render.updateTime 用來(lái)更新時(shí)間,render.updateScore 用來(lái)更新得分:

const render = {
    //...
    updateTime: (value) => {
        dom.time.innerText = value.toString()
    },
    updateScore: (value) => {
        dom.score.innerText = value.toString()
    },
    //...
}

render.updateRound 用來(lái)更新當(dāng)前局?jǐn)?shù),顯示為 “n/m” 的格式:

const render = {
    //...
    updateRound: (currentRound) => {
        dom.round.innerText = [
            currentRound.toString(),
            "/",
            ROUND_COUNT.toString(),
        ].join("")
    },
    //...
}

render.updateFinal 用來(lái)更新游戲結(jié)束界面里的最終成績(jī):

const render = {
    //...
    updateFinal: () => {
        dom.finalTime.innerText = dom.time.innerText
        dom.finalScore.innerText = dom.score.innerText
    },
}

接下來(lái)定義程序整體的邏輯結(jié)構(gòu)。當(dāng)頁(yè)面加載完成之后執(zhí)行 init() 函數(shù),init() 函數(shù)會(huì)對(duì)整個(gè)游戲做些初始化的工作 ———— 令開(kāi)始游戲按鈕 dom.play 被點(diǎn)擊時(shí)調(diào)用 startGame() 函數(shù),令再玩一次按鈕 dom.again 被點(diǎn)擊時(shí)調(diào)用 playAgain() 函數(shù),令按下鍵盤時(shí)觸發(fā)事件處理程序 pressKey() ———— 最后調(diào)用 newGame() 函數(shù)開(kāi)始新游戲:

window.onload = init

function init() {
    dom.play.addEventListener("click", startGame)
    dom.again.addEventListener("click", playAgain)
    window.addEventListener("keyup", pressKey)

    newGame()
}

function newGame() {
    //...
}

function startGame() {
    //...
}

function playAgain() {
    //...
}

function pressKey() {
    //...
}

當(dāng)游戲開(kāi)始時(shí),令游戲界面變模糊,呼出選擇游戲難度的界面:

function newGame() {
    dom.game.classList.add("stop")
    dom.selectLevel.style.visibility = "visible"
}

當(dāng)選擇了游戲難度,點(diǎn)擊開(kāi)始游戲按鈕 dom.play 時(shí),隱藏掉選擇游戲難度的界面,游戲界面恢復(fù)正常,然后把根據(jù)用戶選擇的游戲難度計(jì)算出的答案數(shù)字個(gè)數(shù)存儲(chǔ)到全局變量 answerCount 中,調(diào)用 newRound() 開(kāi)始一局游戲:

let answerCount

function startGame() {
    dom.game.classList.remove("stop")
    dom.selectLevel.style.visibility = "hidden"

    answerCount = ANSWER_COUNT[dom.level().value.toUpperCase()]
    newRound()
}

當(dāng)一局游戲開(kāi)始時(shí),打亂所有候選數(shù)字,生成一個(gè)全局?jǐn)?shù)組變量 digitsdigits 的每個(gè)元素包含 3 個(gè)屬性,text 屬性表示數(shù)字文本,isAnswer 屬性表示該數(shù)字是否為答案,isPressed 表示該數(shù)字是否被按下過(guò),isPressed 的初始值均為 false,緊接著把 digits 渲染到九宮格中:

let digits

function newRound() {
    digits = _.shuffle(ALL_DIGITS).map((x, i) => {
        return {
            text: x,
            isAnwser: (i < answerCount),
            isPressed: false
        }
    })
    render.initDigits(_.filter(digits, x => !x.isAnwser).map(x => x.text))
}

當(dāng)用戶按下鍵盤時(shí),若按的鍵不是候選文本,就忽略這次按鍵事件。通過(guò)按鍵的文本在 digits 數(shù)組中找到對(duì)應(yīng)的元素 digit,判斷該鍵是否被按過(guò),若被按過(guò),也退出事件處理。接下來(lái),就是針對(duì)沒(méi)按過(guò)的鍵,在對(duì)應(yīng)的 digit 對(duì)象上標(biāo)明該鍵已按過(guò),并且更新這個(gè)鍵的顯示狀態(tài),如果用戶按下的不是答案數(shù)字,就把該數(shù)字所在的格子描紅,如果用戶按下的是答案數(shù)字,就突出顯示這個(gè)數(shù)字:

function pressKey(e) {
    if (!ALL_DIGITS.includes(e.key)) return;

    let digit = _.find(digits, x => (x.text == e.key))
    if (digit.isPressed) return;

    digit.isPressed = true
    render.updateDigitStatus(digit.text, digit.isAnwser)
}

當(dāng)用戶已經(jīng)按下了所有的答案數(shù)字,這一局就結(jié)束了,開(kāi)始新一局:

function pressKey(e) {
    if (!ALL_DIGITS.includes(e.key)) return;

    let digit = _.find(digits, x => (x.text == e.key))
    if (digit.isPressed) return;

    digit.isPressed = true
    render.updateDigitStatus(digit.text, digit.isAnwser)

    //判斷用戶是否已經(jīng)按下所有的答案數(shù)字
    let hasPressedAllAnswerDigits = (_.filter(digits, (x) => (x.isAnwser && x.isPressed)).length == answerCount)
    if (!hasPressedAllAnswerDigits) return;

    newRound()
}

增加一個(gè)記錄當(dāng)前局?jǐn)?shù)的全局變量 round,在游戲開(kāi)始時(shí)它的初始值為 0,每局游戲開(kāi)始時(shí),它的值就加1,并更新游戲界面中的局?jǐn)?shù) dom.round

let round

function newGame() {
    round = 0 //初始化局?jǐn)?shù)

    dom.game.classList.add("stop")
    dom.selectLevel.style.visibility = "visible"
}

function startGame() {
    render.updateRound(1) //初始化頁(yè)面中的局?jǐn)?shù)
    
    dom.game.classList.remove("stop")
    dom.selectLevel.style.visibility = "hidden"

    answerCount = ANSWER_COUNT[dom.level().value.toUpperCase()]
    newRound()
}

function newRound() {
    digits = _.shuffle(ALL_DIGITS).map((x, i) => {
        return {
            text: x,
            isAnwser: (i < answerCount),
            isPressed: false
        }
    })
    render.initDigits(_.filter(digits, x => !x.isAnwser).map(x => x.text))

    //每局開(kāi)始時(shí)為局?jǐn)?shù)加 1
    round++
    render.updateRound(round)
}

當(dāng)前局?jǐn)?shù) round 增加到常量 ROUND_COUNT 定義的游戲總局?jǐn)?shù),本次游戲結(jié)束,調(diào)用 gameOver() 函數(shù),否則調(diào)用 newRound() 函數(shù)開(kāi)始新一局:

function pressKey(e) {
    if (!ALL_DIGITS.includes(e.key)) return;

    let digit = _.find(digits, x => (x.text == e.key))
    if (digit.isPressed) return;

    digit.isPressed = true
    render.updateDigitStatus(digit.text, digit.isAnwser)

    let hasPressedAllAnswerDigits = (_.filter(digits, (x) => (x.isAnwser && x.isPressed)).length == answerCount)
    if (!hasPressedAllAnswerDigits) return;
    
    //判斷是否玩夠了總局?jǐn)?shù)
    let hasPlayedAllRounds = (round == ROUND_COUNT)
    if (hasPlayedAllRounds) {
        gameOver()
    } else {
        newRound()
    }
}

游戲結(jié)束時(shí),令游戲界面變模糊,調(diào)出游戲結(jié)束界面,顯示最終成績(jī):

function gameOver() {
    render.updateFinal()
    
    dom.game.classList.add("stop")
    dom.gameOver.style.visibility = "visible"
}

在游戲結(jié)束界面,用戶可以點(diǎn)擊再玩一次按鈕 dom.again,若點(diǎn)擊了此按鈕,就把游戲結(jié)束界面隱藏起來(lái),開(kāi)始一局新游戲,這就回到 newGame() 的流程了:

function playAgain() {
    dom.game.classList.remove("stop")
    dom.gameOver.style.visibility = "hidden"

    newGame()
}

至此,整個(gè)游戲的流程已經(jīng)跑通了,此時(shí)的腳本如下:

const ALL_DIGITS = ["1","2","3","4","5","6","7","8","9"]
const ANSWER_COUNT = {EASY: 1, NORMAL: 2, HARD: 3}
const ROUND_COUNT = 3
const SCORE_RULE = {CORRECT: 100, WRONG: -10}

const $ = (selector) => document.querySelectorAll(selector)
const dom = {
    game: $(".game")[0],
    digits: Array.from($(".game .digits span")),
    time: $(".game .time")[0],
    round: $(".game .round")[0],
    score: $(".game .score")[0],
    selectLevel: $(".select-level")[0],
    level: () => {return $("input[type=radio]:checked")[0]},
    play: $(".select-level .play")[0],
    gameOver: $(".game-over")[0],
    again: $(".game-over .again")[0],
    finalTime: $(".game-over .final-time")[0],
    finalScore: $(".game-over .final-score")[0],
}

const render = {
    initDigits: (texts) => {
        allTexts = texts.concat(_.fill(Array(ALL_DIGITS.length - texts.length), ""))
        _.shuffle(dom.digits).forEach((digit, i) => {
            digit.innerText = allTexts[i]
            digit.className = ""
        })
    },
    updateDigitStatus: (text, isAnswer) => {
        if (isAnswer) {
            let digit = _.find(dom.digits, x => (x.innerText == ""))
            digit.innerText = text
            digit.className = "correct"
        }
        else {
            _.find(dom.digits, x => (x.innerText == text)).className = "wrong"
        }
    },
    updateTime: (value) => {
        dom.time.innerText = value.toString()
    },
    updateScore: (value) => {
        dom.score.innerText = value.toString()
    },
    updateRound: (currentRound) => {
        dom.round.innerText = [
            currentRound.toString(),
            "/",
            ROUND_COUNT.toString(),
        ].join("")
    },
    updateFinal: () => {
        dom.finalTime.innerText = dom.time.innerText
        dom.finalScore.innerText = dom.score.innerText
    },
}

let answerCount, digits, round

window.onload = init

function init() {
    dom.play.addEventListener("click", startGame)
    dom.again.addEventListener("click", playAgain)
    window.addEventListener("keyup", pressKey)

    newGame()
}

function newGame() {
    round = 0

    dom.game.classList.add("stop")
    dom.selectLevel.style.visibility = "visible"
}

function startGame() {
    render.updateRound(1)
    
    dom.game.classList.remove("stop")
    dom.selectLevel.style.visibility = "hidden"

    answerCount = ANSWER_COUNT[dom.level().value.toUpperCase()]
    newRound()
}

function newRound() {
    digits = _.shuffle(ALL_DIGITS).map((x, i) => {
        return {
            text: x,
            isAnwser: (i < answerCount),
            isPressed: false
        }
    })
    render.initDigits(_.filter(digits, x => !x.isAnwser).map(x => x.text))

    round++
    render.updateRound(round)
}

function gameOver() {
    render.updateFinal()
    
    dom.game.classList.add("stop")
    dom.gameOver.style.visibility = "visible"
}

function playAgain() {
    dom.game.classList.remove("stop")
    dom.gameOver.style.visibility = "hidden"

    newGame()
}

function pressKey(e) {
    if (!ALL_DIGITS.includes(e.key)) return;

    let digit = _.find(digits, x => (x.text == e.key))
    if (digit.isPressed) return;

    digit.isPressed = true
    render.updateDigitStatus(digit.text, digit.isAnwser)

    let hasPressedAllAnswerDigits = (_.filter(digits, (x) => (x.isAnwser && x.isPressed)).length == answerCount)
    if (!hasPressedAllAnswerDigits) return;
    
    let hasPlayedAllRounds = (round == ROUND_COUNT)
    if (hasPlayedAllRounds) {
        gameOver()
    } else {
        newRound()
    }
}
三、計(jì)分和計(jì)時(shí)

接下來(lái)處理得分和時(shí)間,先處理得分。
首先聲明一個(gè)用于存儲(chǔ)得分的全局變量 score,在新游戲開(kāi)始之前設(shè)置它的初始值為 0,在游戲開(kāi)始時(shí)初始化頁(yè)面中的得分:

let score

function newGame() {
    round = 0
    score = 0 //初始化得分

    dom.game.classList.add("stop")
    dom.selectLevel.style.visibility = "visible"
}

function startGame() {
    render.updateRound(1)
    render.updateScore(0) //初始化頁(yè)面中的得分

    dom.game.classList.remove("stop")
    dom.selectLevel.style.visibility = "hidden"

    answerCount = ANSWER_COUNT[dom.level().value.toUpperCase()]
    newRound()
}

在用戶按鍵事件中根據(jù)按下的鍵是否為答案記錄不同的分值:

function pressKey(e) {
    if (!ALL_DIGITS.includes(e.key)) return;

    let digit = _.find(digits, x => (x.text == e.key))
    if (digit.isPressed) return;

    digit.isPressed = true
    render.updateDigitStatus(digit.text, digit.isAnwser)

    //累積得分
    score += digit.isAnwser ? SCORE_RULE.CORRECT : SCORE_RULE.WRONG
    render.updateScore(score)

    let hasPressedAllAnswerDigits = (_.filter(digits, (x) => (x.isAnwser && x.isPressed)).length == answerCount)
    if (!hasPressedAllAnswerDigits) return;
    
    let hasPlayedAllRounds = (round == ROUND_COUNT)
    if (hasPlayedAllRounds) {
        gameOver()
    } else {
        newRound()
    }
}

接下來(lái)處理時(shí)間。先創(chuàng)建一個(gè)計(jì)時(shí)器類 Timer,它的參數(shù)是一個(gè)用于把時(shí)間渲染到頁(yè)面上的函數(shù),另外 Timerstart()stop() 2 個(gè)方法用于開(kāi)啟和停止計(jì)時(shí)器,計(jì)時(shí)器每秒會(huì)執(zhí)行一次 tickTock() 函數(shù):

function Timer(render) {
    this.render = render
    this.t = {},
    this.start = () => {
        this.t = setInterval(this.tickTock, 1000);
    }
    this.stop = () => {
        clearInterval(this.t)
    }
}

定義一個(gè)記錄時(shí)間的變量 time,它的初始值為 00 秒,在 tickTock() 函數(shù)中把秒數(shù)加1,并調(diào)用渲染函數(shù)把當(dāng)前時(shí)間寫到頁(yè)面中:

function Timer(render) {
    this.render = render
    this.t = {}
    this.time = {
        minute: 0,
        second: 0,
    }
    this.tickTock = () => {
        this.time.second ++;
        if (this.time.second == 60) {
            this.time.minute ++
            this.time.second = 0
        }

        render([
            this.time.minute.toString().padStart(2, "0"),
            ":",
            this.time.second.toString().padStart(2, "0"),
        ].join(""))
    }
    this.start = () => {
        this.t = setInterval(this.tickTock, 1000)
    }
    this.stop = () => {
        clearInterval(this.t)
    }
}

在開(kāi)始游戲時(shí)初始化頁(yè)面中的時(shí)間:

function startGame() {
    render.updateRound(1)
    render.updateScore(0)
    render.updateTime("00:00") //初始化頁(yè)面中的時(shí)間

    dom.game.classList.remove("stop")
    dom.selectLevel.style.visibility = "hidden"

    answerCount = ANSWER_COUNT[dom.level().value.toUpperCase()]
    newRound()
}

定義一個(gè)存儲(chǔ)定時(shí)器的全局變量 timer,在創(chuàng)建游戲時(shí)初始化定時(shí)器,在游戲開(kāi)始時(shí)啟動(dòng)計(jì)時(shí)器,在游戲結(jié)束時(shí)停止計(jì)時(shí)器:

let timer

function newGame() {
    round = 0
    score = 0
    timer = new Timer(render.updateTime) //創(chuàng)建定時(shí)器

    dom.game.classList.add("stop")
    dom.selectLevel.style.visibility = "visible"
}

function startGame() {
    render.updateRound(1)
    render.updateScore(0)
    render.updateTime("00:00")

    dom.game.classList.remove("stop")
    dom.selectLevel.style.visibility = "hidden"

    answerCount = ANSWER_COUNT[dom.level().value.toUpperCase()]
    newRound()
    timer.start()  //開(kāi)始計(jì)時(shí)
}

function gameOver() {
    timer.stop()  //停止計(jì)時(shí)
    render.updateFinal()
    
    dom.game.classList.add("stop")
    dom.gameOver.style.visibility = "visible"
}

至此,時(shí)鐘已經(jīng)可以運(yùn)行了,在游戲開(kāi)始時(shí)從 0 分 0 秒開(kāi)始計(jì)時(shí),在游戲結(jié)束時(shí)停止計(jì)時(shí)。
最后一個(gè)環(huán)節(jié),當(dāng)游戲結(jié)束之后,不應(yīng)再響應(yīng)用戶的按鍵事件。為此,我們定義一個(gè)標(biāo)明是否可按鍵的變量 canPress,在創(chuàng)建新游戲時(shí)它的狀態(tài)是不可按,游戲開(kāi)始之后變?yōu)榭砂矗螒蚪Y(jié)束之后再變?yōu)椴豢砂矗?/p>

let canPress

function newGame() {
    round = 0
    score = 0
    time = {
        minute: 0,
        second: 0
    }
    timer = new Timer()
    canPress = false  //初始化是否可按鍵的標(biāo)志

    dom.game.classList.add("stop")
    dom.selectLevel.style.visibility = "visible"
}

function startGame() {
    render.updateRound(1)
    render.updateScore(0)
    render.updateTime(0, 0)

    dom.game.classList.remove("stop")
    dom.selectLevel.style.visibility = "hidden"

    answerCount = ANSWER_COUNT[dom.level().value.toUpperCase()]
    newRound()
    timer.start(tickTock)
    canPress = true //游戲開(kāi)始后,可以按鍵
}

function gameOver() {
    canPress = false //游戲結(jié)束后,不可以再按鍵
    timer.stop()
    render.updateFinal()
    
    dom.game.classList.add("stop")
    dom.gameOver.style.visibility = "visible"
}

在按鍵事件處理程序中,首先判斷是否允許按鍵,若不允許,就退出事件處理程序:

function pressKey(e) {
    if (!canPress) return; //判斷是否允許按鍵
    if (!ALL_DIGITS.includes(e.key)) return;

    let digit = _.find(digits, x => (x.text == e.key))
    if (digit.isPressed) return;

    digit.isPressed = true
    render.updateDigitStatus(digit.text, digit.isAnwser)

    score += digit.isAnwser ? SCORE_RULE.CORRECT : SCORE_RULE.WRONG
    render.updateScore(score)

    let hasPressedAllAnswerDigits = (_.filter(digits, (x) => (x.isAnwser && x.isPressed)).length == answerCount)
    if (hasPressedAllAnswerDigits) {
        newRound()
    }
}

至此,計(jì)分計(jì)時(shí)設(shè)計(jì)完畢,此時(shí)的腳本如下:

const ALL_DIGITS = ["1","2","3","4","5","6","7","8","9"]
const ANSWER_COUNT = {EASY: 1, NORMAL: 2, HARD: 3}
const ROUND_COUNT = 3
const SCORE_RULE = {CORRECT: 100, WRONG: -10}

const $ = (selector) => document.querySelectorAll(selector)
const dom = {
    //略,與此前代碼相同
}

const render = {
    //略,與此前代碼相同
}

let answerCount, digits, round, score, timer, canPress

window.onload = init

function init() {
    //略,與此前代碼相同
}

function newGame() {
    round = 0
    score = 0
    timer = new Timer(render.updateTime)
    canPress = false

    dom.game.classList.add("stop")
    dom.selectLevel.style.visibility = "visible"
}

function startGame() {
    render.updateRound(1)
    render.updateScore(0)
    render.updateTime(0, 0)

    dom.game.classList.remove("stop")
    dom.selectLevel.style.visibility = "hidden"

    answerCount = ANSWER_COUNT[dom.level().value.toUpperCase()]
    newRound()
    timer.start()
    canPress = true
}

function newRound() {
    //略,與此前代碼相同
}

function gameOver() {
    canPress = false
    timer.stop()
    render.updateFinal()
    
    dom.game.classList.add("stop")
    dom.gameOver.style.visibility = "visible"
}

function playAgain() {
    //略,與此前代碼相同
}

function pressKey(e) {
    if (!canPress) return;
    if (!ALL_DIGITS.includes(e.key)) return;

    let digit = _.find(digits, x => (x.text == e.key))
    if (digit.isPressed) return;

    digit.isPressed = true
    render.updateDigitStatus(digit.text, digit.isAnwser)

    score += digit.isAnwser ? SCORE_RULE.CORRECT : SCORE_RULE.WRONG
    render.updateScore(score)

    let hasPressedAllAnswerDigits = (_.filter(digits, (x) => (x.isAnwser && x.isPressed)).length == answerCount)
    if (!hasPressedAllAnswerDigits) return;
    
    let hasPlayedAllRounds = (round == ROUND_COUNT)
    if (hasPlayedAllRounds) {
        gameOver()
    } else {
        newRound()
    }
}
四、動(dòng)畫效果

引入 gsap 動(dòng)畫庫(kù):

游戲中一共有 6 個(gè)動(dòng)畫效果,分別是九宮格的出場(chǎng)與入場(chǎng)、選擇游戲難度界面的顯示與隱藏、游戲結(jié)束界面的顯示與隱藏。為了集中管理動(dòng)畫效果,我們定義一個(gè)全局常量 animation,它的每個(gè)屬性是一個(gè)函數(shù),實(shí)現(xiàn)一個(gè)動(dòng)畫效果,結(jié)構(gòu)如下,注意因?yàn)檫x擇游戲難度界面和游戲結(jié)束界面的樣式相似,所以它們共享了相同的動(dòng)畫效果,在調(diào)用函數(shù)時(shí)要傳入一個(gè)參數(shù) element 指定動(dòng)畫的 dom 對(duì)象:

const animation = {
    digitsFrameOut: () => {
        //九宮格出場(chǎng)
    },
    digitsFrameIn: () => {
        //九宮格入場(chǎng)
    },
    showUI: (element) => {
        //顯示選擇游戲難度界面和游戲結(jié)束界面
    },
    frameOut: (element) => {
        //隱藏選擇游戲難度界面和游戲結(jié)束界面
    },
}

確定下這幾個(gè)動(dòng)畫的時(shí)機(jī):

function newGame() {
    round = 0
    score = 0
    timer = new Timer(render.updateTime)
    canPress = false

    //選擇游戲難度界面 - 顯示
    dom.game.classList.add("stop")
    dom.selectLevel.style.visibility = "visible"
}

function startGame() {
    render.updateRound(1)
    render.updateScore(0)
    render.updateTime("00:00")

    //選擇游戲難度界面 - 隱藏
    dom.game.classList.remove("stop")
    dom.selectLevel.style.visibility = "hidden"

    answerCount = ANSWER_COUNT[dom.level().value.toUpperCase()]
    newRound()
    timer.start()
    canPress = true
}

function newRound() {
    //九宮格 - 出場(chǎng)

    digits = _.shuffle(ALL_DIGITS).map((x, i) => {
        return {
            text: x,
            isAnwser: (i < answerCount),
            isPressed: false
        }
    })
    render.initDigits(_.filter(digits, x => !x.isAnwser).map(x => x.text))

    //九宮格 - 入場(chǎng)

    round++
    render.updateRound(round)
}

function gameOver() {
    canPress = false
    timer.stop()
    render.updateFinal()
    
    //游戲結(jié)束界面 - 顯示
    dom.game.classList.add("stop")
    dom.gameOver.style.visibility = "visible"
}

function playAgain() {
    //游戲結(jié)束界面 - 隱藏
    dom.game.classList.remove("stop")
    dom.gameOver.style.visibility = "hidden"

    newGame()
}

把目前動(dòng)畫時(shí)機(jī)所在位置的代碼移到 animation 對(duì)象中,九宮格出場(chǎng)和入場(chǎng)的動(dòng)畫目前是空的:

const animation = {
    digitsFrameOut: () => {
        //九宮格出場(chǎng)
    },
    digitsFrameIn: () => {
        //九宮格入場(chǎng)
    },
    showUI: (element) => {
        //顯示選擇游戲難度界面和游戲結(jié)束界面
        dom.game.classList.add("stop")
        element.style.visibility = "visible"
    },
    hideUI: (element) => {
        //隱藏選擇游戲難度界面和游戲結(jié)束界面
        dom.game.classList.remove("stop")
        element.style.visibility = "hidden"
    },
}

在動(dòng)畫時(shí)機(jī)的位置調(diào)用 animation 對(duì)應(yīng)的動(dòng)畫函數(shù),因?yàn)閯?dòng)畫是有執(zhí)行時(shí)長(zhǎng)的,下一個(gè)動(dòng)畫要等到上一個(gè)動(dòng)畫結(jié)束之后再開(kāi)始,所以我們采用了 async/await 的語(yǔ)法,讓相鄰的動(dòng)畫順序執(zhí)行:

async function newGame() {
    round = 0
    score = 0
    timer = new Timer(render.updateTime)
    canPress = false

    // 選擇游戲難度界面 - 顯示
    await animation.showUI(dom.selectLevel)
}

async function startGame() {
    render.updateRound(1)
    render.updateScore(0)
    render.updateTime("00:00")

    // 選擇游戲難度界面 - 隱藏
    await animation.hideUI(dom.selectLevel)

    answerCount = ANSWER_COUNT[dom.level().value.toUpperCase()]
    newRound()
    timer.start()
    canPress = true
}

async function newRound() {
    //九宮格 - 出場(chǎng)
    await animation.digitsFrameOut()

    digits = _.shuffle(ALL_DIGITS).map((x, i) => {
        return {
            text: x,
            isAnwser: (i < answerCount),
            isPressed: false
        }
    })
    render.initDigits(_.filter(digits, x => !x.isAnwser).map(x => x.text))

    //九宮格 - 入場(chǎng)
    await animation.digitsFrameIn()

    round++
    render.updateRound(round)
}

async function gameOver() {
    canPress = false
    timer.stop()
    render.updateFinal()
    
    // 游戲結(jié)束界面 - 顯示
    await animation.showUI(dom.gameOver)
}

async function playAgain() {
    // 游戲結(jié)束界面 - 隱藏
    await animation.hideUI(dom.gameOver)

    newGame()
}

接下來(lái)就開(kāi)始設(shè)計(jì)動(dòng)畫效果。
animation.digitsFrameOut 是九宮格的出場(chǎng)動(dòng)畫,各格子分別旋轉(zhuǎn)著消失。注意,為了與 async/await 語(yǔ)法配合,我們讓函數(shù)返回了一個(gè) Promise 對(duì)象:

const animation = {
    digitsFrameOut: () => {
        return new Promise(resolve => {
            new TimelineMax()
                .staggerTo(dom.digits, 0, {rotation: 0})
                .staggerTo(dom.digits, 1, {rotation: 360, scale: 0, delay: 0.5})
                .timeScale(2)
                .eventCallback("onComplete", resolve)
        })
    },
    //...
}

animation.digitsFrameIn 是九宮格的入場(chǎng)動(dòng)畫,它的動(dòng)畫效果是各格子旋轉(zhuǎn)著出現(xiàn),而且各格子的出現(xiàn)時(shí)間稍有延遲:

const animation = {
    //...
    digitsFrameIn: () => {
        return new Promise(resolve => {
            new TimelineMax()
                .staggerTo(dom.digits, 0, {rotation: 0})
                .staggerTo(dom.digits, 1, {rotation: 360, scale: 1}, 0.1)
                .timeScale(2)
                .eventCallback("onComplete", resolve)
        })
    },
    //...
}

animation.showUI 是顯示擇游戲難度界面和游戲結(jié)束界面的動(dòng)畫,它的效果是從高處落下,并在底部小幅反彈,模擬物體跌落的效果:

const animation = {
    //...
    showUI: (element) => {
        dom.game.classList.add("stop")
        return new Promise(resolve => {
            new TimelineMax()
                .to(element, 0, {visibility: "visible", x: 0})
                .from(element, 1, {y: "-300px", ease: Elastic.easeOut.config(1, 0.3)})
                .timeScale(1)
                .eventCallback("onComplete", resolve)
        })
    },
    //...
}

animation.hideUI 是隱藏選擇游戲難度界面和游戲結(jié)束界面的動(dòng)畫,它從正常位置向右移出畫面:

const animation = {
    //...
    hideUI: (element) => {
        dom.game.classList.remove("stop")
        return new Promise(resolve => {
            new TimelineMax()
                .to(element, 1, {x: "300px", ease: Power4.easeIn})
                .to(element, 0, {visibility: "hidden"})
                .timeScale(2)
                .eventCallback("onComplete", resolve)
        })
    },
}

至此,整個(gè)游戲的動(dòng)畫效果就完成了,全部代碼如下:

const ALL_DIGITS = ["1","2","3","4","5","6","7","8","9"]
const ANSWER_COUNT = {EASY: 1, NORMAL: 2, HARD: 3}
const ROUND_COUNT = 3
const SCORE_RULE = {CORRECT: 100, WRONG: -10}

const $ = (selector) => document.querySelectorAll(selector)
const dom = {
    //略,與增加動(dòng)畫前相同
}

const render = {
    //略,與增加動(dòng)畫前相同
}

const animation = {
    digitsFrameOut: () => {
        return new Promise(resolve => {
            new TimelineMax()
                .staggerTo(dom.digits, 0, {rotation: 0})
                .staggerTo(dom.digits, 1, {rotation: 360, scale: 0, delay: 0.5})
                .timeScale(2)
                .eventCallback("onComplete", resolve)
        })
    },
    digitsFrameIn: () => {
        return new Promise(resolve => {
            new TimelineMax()
                .staggerTo(dom.digits, 0, {rotation: 0})
                .staggerTo(dom.digits, 1, {rotation: 360, scale: 1}, 0.1)
                .timeScale(2)
                .eventCallback("onComplete", resolve)
        })
    },
    showUI: (element) => {
        dom.game.classList.add("stop")
        return new Promise(resolve => {
            new TimelineMax()
                .to(element, 0, {visibility: "visible", x: 0})
                .from(element, 1, {y: "-300px", ease: Elastic.easeOut.config(1, 0.3)})
                .timeScale(1)
                .eventCallback("onComplete", resolve)
        })
    },
    hideUI: (element) => {
        dom.game.classList.remove("stop")
        return new Promise(resolve => {
            new TimelineMax()
                .to(element, 1, {x: "300px", ease: Power4.easeIn})
                .to(element, 0, {visibility: "hidden"})
                .timeScale(2)
                .eventCallback("onComplete", resolve)
        })
    },
}

let answerCount, digits, round, score, timer, canPress

window.onload = init

function init() {
    //略,與增加動(dòng)畫前相同
}

async function newGame() {
    round = 0
    score = 0
    timer = new Timer(render.updateTime)
    canPress = false

    await animation.showUI(dom.selectLevel)
}

async function startGame() {
    render.updateRound(1)
    render.updateScore(0)
    render.updateTime("00:00")

    await animation.hideUI(dom.selectLevel)

    answerCount = ANSWER_COUNT[dom.level().value.toUpperCase()]
    newRound()
    timer.start()
    canPress = true
}

async function newRound() {
    await animation.digitsFrameOut()

    digits = _.shuffle(ALL_DIGITS).map((x, i) => {
        return {
            text: x,
            isAnwser: (i < answerCount),
            isPressed: false
        }
    })
    render.initDigits(_.filter(digits, x => !x.isAnwser).map(x => x.text))

    await animation.digitsFrameIn()

    round++
    render.updateRound(round)
}

async function gameOver() {
    canPress = false
    timer.stop()
    render.updateFinal()
    
    await animation.showUI(dom.gameOver)
}

async function playAgain() {
    await animation.hideUI(dom.gameOver)

    newGame()
}

function pressKey(e) {
    //略,與增加動(dòng)畫前相同
}

function tickTock() {
    //略,與增加動(dòng)畫前相同
}

大功告成!

最后,附上交互流程圖,方便大家理解。其中藍(lán)色條帶表示動(dòng)畫,粉色橢圓表示用戶操作,綠色矩形和菱形表示主要的程序邏輯:

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/117030.html

相關(guān)文章

  • 前端每日實(shí)戰(zhàn) 2018年10月至2019年6月項(xiàng)目匯總(共 20 個(gè)項(xiàng)目)

    摘要:過(guò)往項(xiàng)目年月份項(xiàng)目匯總共個(gè)項(xiàng)目年月份項(xiàng)目匯總共個(gè)項(xiàng)目年月份項(xiàng)目匯總共個(gè)項(xiàng)目年月份項(xiàng)目匯總共個(gè)項(xiàng)目年月份項(xiàng)目匯總共個(gè)項(xiàng)目年月份項(xiàng)目匯總共個(gè)項(xiàng)目年月至年月發(fā)布的項(xiàng)目前端每日實(shí)戰(zhàn)專欄每天分解一個(gè)前端項(xiàng)目,用視頻記錄編碼過(guò)程,再配合詳細(xì)的代碼解讀, 過(guò)往項(xiàng)目 2018 年 9 月份項(xiàng)目匯總(共 26 個(gè)項(xiàng)目) 2018 年 8 月份項(xiàng)目匯總(共 29 個(gè)項(xiàng)目) 2018 年 7 月份項(xiàng)目匯總(...

    muddyway 評(píng)論0 收藏0
  • 前端每日實(shí)戰(zhàn)164# 視頻演示何用原生 JS 創(chuàng)作個(gè)數(shù)獨(dú)訓(xùn)練游戲內(nèi)含 4 個(gè)視頻

    摘要:第部分第部分第部分第部分源代碼下載每日前端實(shí)戰(zhàn)系列的全部源代碼請(qǐng)從下載代碼解讀解數(shù)獨(dú)的一項(xiàng)基本功是能迅速判斷一行一列或一個(gè)九宮格中缺少哪幾個(gè)數(shù)字,本項(xiàng)目就是一個(gè)訓(xùn)練判斷九宮格中缺少哪個(gè)數(shù)字的小游戲。 showImg(https://segmentfault.com/img/bVbkNGa?w=400&h=300); 效果預(yù)覽 按下右側(cè)的點(diǎn)擊預(yù)覽按鈕可以在當(dāng)前頁(yè)面預(yù)覽,點(diǎn)擊鏈接可以全屏預(yù)...

    Heier 評(píng)論0 收藏0
  • 前端每日實(shí)戰(zhàn):163# 視頻演示何用原生 JS 創(chuàng)作個(gè)多選場(chǎng)景的交互游戲內(nèi)含 3 個(gè)視頻

    摘要:本項(xiàng)目將設(shè)計(jì)一個(gè)多選一的交互場(chǎng)景,用進(jìn)行頁(yè)面布局用制作動(dòng)畫效果用原生編寫程序邏輯。中包含個(gè)展示頭像的和個(gè)標(biāo)明當(dāng)前被選中頭像的。 showImg(https://segmentfault.com/img/bVbknOW?w=400&h=302); 效果預(yù)覽 按下右側(cè)的點(diǎn)擊預(yù)覽按鈕可以在當(dāng)前頁(yè)面預(yù)覽,點(diǎn)擊鏈接可以全屏預(yù)覽。 https://codepen.io/comehope/pen/L...

    pakolagij 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<