Con rắn trong Javascript

2
L292092 2020-07-11 08:57.

Tôi cảm thấy có điều gì đó không ổn với cách tạo kiểu mã của mình nhưng tôi không thể xác định được điều gì sai với nó. Có lẽ cách tôi thể hiện con rắn hơi phức tạp?

const HEIGHT = 20;
const WIDTH = 20;

const snake = function() {
    const snakeCoordinates = [[0, 0], [1, 0]];
    const moveDirections = [[-1, 0], [-1, 0]];

    function shiftDirections() {
        for (let i = snakeCoordinates.length; i > 0; i--) 
            moveDirections[i] = moveDirections[i - 1];
    }

    return {
        hasSnake: function(row, col) {
            return snakeCoordinates.reduce((accum, [snakeRow, snakeCol]) => accum || (row === snakeRow && col === snakeCol), false);
        },

        expandSnake: function() {       
            const head = snakeCoordinates[0];
            const headDirection = moveDirections[0];

            snakeCoordinates.unshift(wrapAround([head[0] + headDirection[0], head[1] + headDirection[1]])); 
            moveDirections.unshift(headDirection);
        },

        changeDirection: function(x, y) {
            const isAboutTurn = x === -moveDirections[0][0] || y === -moveDirections[0][1];

            if (!isAboutTurn)
                moveDirections[0] = [x, y];
        },

        getSnakeHead: function() {
            return snakeCoordinates[0];
        },

        isSnakeDead: function() {
            const tail = snakeCoordinates.slice(1);
            const head = snakeCoordinates[0];

            return tail.reduce((accum, [row, col]) => accum || (row === head[0] && col === head[1]), false);
        },

        moveSnake: function() {         
            for (let i = 0; i < snakeCoordinates.length; i++) {
                snakeCoordinates[i][0] += moveDirections[i][0];
                snakeCoordinates[i][1] += moveDirections[i][1];

                snakeCoordinates[i] = wrapAround(snakeCoordinates[i]);
            }                   

            shiftDirections();  
        }
    };
}();

const goal = function() {
    var goal = [HEIGHT - 1, WIDTH - 1];

    return {
        newGoal: function() {
            goal = [rng(0, HEIGHT), rng(0, WIDTH)];
        },

        isGoal: function(row, col) {
            return goal[0] === row && goal[1] === col;
        }
    };
}();

initialiseGame();

function initialiseGame() {
    initialiseGridInDOM();
    addEventListeners();

    runGame();
}

function addEventListeners() {
    document.addEventListener("keydown", dealWithKeyPress);
}

// Map key press onto action
function dealWithKeyPress(keyPress) {
    const leftArrow = 37;
    const upArrow = 38;
    const rightArrow = 39;
    const downArrow = 40;

    switch (keyPress.keyCode) {
        case upArrow:
            snake.changeDirection(-1, 0);
            break;
        case leftArrow:
            snake.changeDirection(0, -1);
            break;
        case rightArrow:
            snake.changeDirection(0, 1);
            break;
        case downArrow:
            snake.changeDirection(1, 0);
            break;
    }
}

async function runGame() {
    var running = true;

    while (running) {       
        snake.moveSnake();  
        checkForSnakeDeath(gameOver);
        checkForGoalCapture();
        updateDOM();
        await delay();
    }

    function gameOver() {
        alert("Game over!");
        running = false;
    }
}

function checkForSnakeDeath(gameOver) {
    if (snake.isSnakeDead())
        gameOver();
}

function checkForGoalCapture() {
    const head = snake.getSnakeHead();

    if (goal.isGoal(head[0], head[1])) {
        snake.expandSnake();
        goal.newGoal();
    }
}

function delay() {
    return new Promise(resolve => {
        setTimeout(resolve, 60);
    });
}

function updateDOM() {
    for (let row = 0; row < HEIGHT; row++) 
        for (let col = 0; col < WIDTH; col++) 
            if (snake.hasSnake(row, col)) 
                colorSnakeInDOM(row, col);
            else if (goal.isGoal(row, col))
                colorGoalInDOM(row, col);
            else
                colorWallInDOM(row, col);
}

function colorSnakeInDOM(row, col) {
    const SNAKE_COLOR = "#00BFFF";

    colorTileInDOM(SNAKE_COLOR, row, col);
}

function colorWallInDOM(row, col) {
    const WALL_COLOR = selectBackgroundColor(row, col);

    colorTileInDOM(WALL_COLOR, row, col);
}

function colorGoalInDOM(row, col) {
    const GOAL_COLOR = "yellow";
        
    colorTileInDOM(GOAL_COLOR, row, col);
}

function colorTileInDOM(color, row, col) {
    const tileDOM = getTileInDOM(row, col);

    tileDOM.style.backgroundColor = color;
}

function getTileInDOM(row, col) {
    const gridDOM = document.querySelector("#grid");
    const rowDOM = gridDOM.rows[row];
    const tileDOM = rowDOM.cells[col];

    return tileDOM;
}

// Dynamically generate HTML for a plain grid
function initialiseGridInDOM() {
    const gridDOM = document.querySelector("#grid");

    for (let row = 0; row < HEIGHT; row++) {
        let newRow = createEmptyRowInDOM(row);
        gridDOM.append(newRow);
    }

    updateDOM();
}   

function createEmptyRowInDOM(row) {
    const newRow = document.createElement("tr");
    newRow.className = "row";

    for (let col = 0; col < WIDTH; col++)  {
        let newTile = createEmptyTileInDOM(row, col);
        newRow.append(newTile);
    }

    return newRow;
}

function createEmptyTileInDOM(row, col) {
    const newTile = document.createElement("td");

    newTile.className = "tile";

    return newTile;
}

function selectBackgroundColor(row, col) {
    const BACKGROUND_COLOR1 = "#3dfc03";
    const BACKGROUND_COLOR2 = "#03fc03";

    if ((row + col) % 2 === 0)
        return BACKGROUND_COLOR1;
    else
        return BACKGROUND_COLOR2;
}

function wrapAround([row, col]) {
    if (row === HEIGHT) 
        row = 0;
    if (row < 0)
        row = HEIGHT - 1;    
    if (col === WIDTH)
        col = 0;
    if (col < 0)
        col = WIDTH - 1;

    return [row, col];
}

// Generates a random number whose value lies between lower and upper
function rng(lower, upper) {
    return Math.floor(Math.random() * (upper - lower)) + lower;
}

```

1 answers

3
Hugeen 2020-07-11 22:21.

Đầu tiên, xin hãy khoan dung, tiếng Anh không phải là ngôn ngữ mẹ đẻ của tôi :).

Đây là phần của tôi về kịch bản của bạn. Tôi sẽ nhận xét về những sửa đổi mà tôi đã thực hiện.

Tôi đã tách tập lệnh của bạn thành các chương (có thể dẫn đến các tệp được tách biệt nếu bạn muốn):

  • Logic trò chơi (con rắn và mục tiêu)
  • Luồng trò chơi (bắt đầu, lặp lại, kết thúc trò chơi)
  • Kiểm soát
  • Kết xuất

Tôi đã thực hiện một số căn chỉnh biến và sử dụng định nghĩa phương pháp rút gọn ES6 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions)


LOGIC TRÒ CHƠI

Đối với đối tượng solid, tôi đã thay đổi chức năng tự thực thi thành một chức năng được đặt tên để dễ đọc hơn.

Tôi cũng đã rút ngắn tên phương thức vì chúng ta đã biết chúng ta đang ở trên đối tượng rắn.

Ví dụ: tôi đã thay đổi solid.expandSnake () thành solid.expand ().

Các trình giảm trực tuyến hơi khó đọc, vì vậy tôi đã thay đổi chúng thành nhiều dòng.

const HEIGHT = 20
const WIDTH  = 20



// GAME LOGIC

function createSnake () {

    const coordinates    = [[0, 0], [1, 0]]
    const moveDirections = [[-1, 0], [-1, 0]]

    function shiftDirections () {
        for (let i = coordinates.length; i > 0; i--) {
            moveDirections[i] = moveDirections[i - 1]
        }
    }

    return {

        hasSnake (row, col) {
            return coordinates.reduce(function (accum, [snakeRow, snakeCol]) {
                return accum || (row === snakeRow && col === snakeCol)
            }, false)
        },

        expand () {
            const head = coordinates[0]
            const headDirection = moveDirections[0]

            coordinates.unshift(
                wrapAround([
                    head[0] + headDirection[0],
                    head[1] + headDirection[1]
                ])
            )

            moveDirections.unshift(headDirection)
        },

        changeDirection (x, y) {
            const isAboutTurn = (
                x === -moveDirections[0][0] ||
                y === -moveDirections[0][1]
            )

            if (!isAboutTurn) {
                moveDirections[0] = [x, y]
            }
        },

        getHead () {
            return coordinates[0]
        },

        isDead () {
            const tail = coordinates.slice(1)
            const head = coordinates[0]

            return tail.reduce(function (accum, [row, col]) {
                return accum || (row === head[0] && col === head[1])
            }, false)
        },

        move () {
            for (let i = 0; i < coordinates.length; i++) {
                coordinates[i][0] += moveDirections[i][0]
                coordinates[i][1] += moveDirections[i][1]

                coordinates[i] = wrapAround(coordinates[i])
            }                   

            shiftDirections()
        }

    }

}



function createGoal () {

    let coords = [HEIGHT - 1, WIDTH - 1]

    return {
        newGoal: function () {
            coords = [randomIntBetween(0, HEIGHT), randomIntBetween(0, WIDTH)]
        },

        isGoal: function (row, col) {
            return coords[0] === row && coords[1] === col
        }
    }

}

DÒNG TRÒ CHƠI

Bạn có thể thấy các đối tượng rắn và mục tiêu hiện đã được tạo trong chương này.

Tôi đã đổi tên checkForSnakeDeath và checkForGoalCapture thành handleSnakeDeath và handleGoal, bởi vì những gì bạn đang làm trong các hàm thoses không chỉ là một kiểm tra đơn giản. Theo tôi, một séc chỉ nên trả về true hoặc false, hoặc có thể là một đối tượng lỗi nhưng không thực hiện các hành động.

// GAME FLOW

let snake = createSnake()
let goal  = createGoal()



initialiseGame()

function initialiseGame () {
    initialiseDOMGrid()
    addEventListeners()

    runGame()
}



function addEventListeners () {
    document.addEventListener('keydown', moveSnakeFromInput)
}


async function runGame () {

    var running = true

    while (running) {
        snake.move()
        handleSnakeDeath(gameOver)
        handleGoal()

        paintGrid()

        await delay()
    }

    function gameOver () {
        alert('Game over!')
        running = false
    }

}


function handleSnakeDeath (callback) {
    if (snake.isDead()) {
        callback()
    }
}


function handleGoal () {
    const head = snake.getHead()

    if (goal.isGoal(head[0], head[1])) {
        snake.expand()
        goal.newGoal()
    }
}


function delay () {
    return new Promise(resolve => {
        setTimeout(resolve, 60)
    })
}

ĐIỀU KHIỂN

Đối với các điều khiển, tôi đã thay đổi "chuyển đổi" sang phương thức ánh xạ khóa và đối tượng để có thể đọc và mô-đun tốt hơn (bây giờ bạn có thể thực hiện move.left () theo chương trình nếu muốn).

// CONTROLS

const moves = {
    left () {
        snake.changeDirection(-1, 0)
    },
    up () {
        snake.changeDirection(0, -1)
    },
    right () {
        snake.changeDirection(0, 1)
    },
    down () {
        snake.changeDirection(1, 0)
    }
}

const keysMap = {
    37: 'left',
    38: 'up',
    39: 'right',
    40: 'down'
}


function moveSnakeFromInput (keyPress) {
    if (keyPress in keysMap) {
        moves[keysMap[keyPress]]()
    }
}

RENDERING

Đối với kết xuất, tôi đã đặt các biến màu lại với nhau để thay đổi dễ dàng hơn. Tôi đã chia updateDOM (hiện được gọi là paintGrid) thành hai phương thức: forEachCell và paintCell.

// RENDERING


const SNAKE_COLOR  = '#00BFFF'
const GOAL_COLOR   = 'yellow'
const BACKGROUND_1 = '#3dfc03'
const BACKGROUND_2 = '#03fc03'


function paintGrid () {
    forEachCell(paintCell)
}


function forEachCell (iterator) {
    for (let row = 0; row < HEIGHT; row++) {
        for (let col = 0; col < WIDTH; col++) {
            iterator(row, col)
        }
    }
}


function paintCell (row, col) {
    if (snake.hasSnake(row, col)) {
        paintSnake(row, col)
    } else if (goal.isGoal(row, col)) {
        paintGoal(row, col)
    } else {
        paintWall(row, col)
    }
}


function paintSnake (row, col) {
    paintTile(SNAKE_COLOR, row, col)
}


function paintWall (row, col) {
    const WALL_COLOR = getBackgroundColor(row, col)

    paintTile(WALL_COLOR, row, col)
}


function paintGoal (row, col) {
    paintTile(GOAL_COLOR, row, col)
}


function paintTile (color, row, col) {
    const tileDOM = getTile(row, col)

    tileDOM.style.backgroundColor = color
}


function getTile (row, col) {
    const gridDOM = document.querySelector('#grid')
    const rowDOM = gridDOM.rows[row]
    const tileDOM = rowDOM.cells[col]

    return tileDOM
}


function initialiseDOMGrid () {
    const gridDOM = document.querySelector('#grid')

    for (let row = 0; row < HEIGHT; row++) {
        let newRow = createDOMRow(row)
        gridDOM.append(newRow)
    }

    paintGrid()
}   


function createDOMRow () {
    const newRow = document.createElement('tr')
    newRow.className = 'row'

    for (let col = 0; col < WIDTH; col++)  {
        let newTile = createDOMTile()
        newRow.append(newTile)
    }

    return newRow
}


function createDOMTile () {
    const newTile = document.createElement('td')
    newTile.className = 'tile'

    return newTile
}


function getBackgroundColor (row, col) {
    return (row + col) % 2 === 0 ? BACKGROUND_1 : BACKGROUND_2
}

ĐỒ DÙNG

Chỉ một số sửa đổi nhỏ về khả năng đọc ở đây

// UTILS


function wrapAround ([row, col]) {
    if (row === HEIGHT) {
        row = 0
    }

    if (row < 0) {
        row = HEIGHT - 1
    }

    if (col === WIDTH) {
        col = 0
    }

    if (col < 0) {
        col = WIDTH - 1
    }

    return [row, col]
}


function randomIntBetween (lower, upper) {
    return Math.floor(Math.random() * (upper - lower)) + lower
}

Rõ ràng đây chỉ là ý kiến ​​của tôi, vì vậy chỉ cần chọn những gì bạn thích trong các sửa đổi của tôi :-)

Related questions

MORE COOL STUFF

Jana Duggar: Tất cả những gì cô ấy nói về tình yêu và khoảng thời gian 5 năm để tìm 'một nửa' của cô ấy

Jana Duggar: Tất cả những gì cô ấy nói về tình yêu và khoảng thời gian 5 năm để tìm 'một nửa' của cô ấy

Jana Duggar đã cởi mở về việc tìm kiếm tình yêu của mình. Đây là tất cả những gì cô ấy nói về chủ đề này và khoảng thời gian 5 năm kết hôn của cô ấy.

Sam Heughan biết chính xác những gì anh ấy sẽ lấy từ bộ 'Outlander' - 'Tôi cảm thấy như mình là Jamie khi tôi mặc chúng'

Sam Heughan biết chính xác những gì anh ấy sẽ lấy từ bộ 'Outlander' - 'Tôi cảm thấy như mình là Jamie khi tôi mặc chúng'

Ngôi sao của 'Outlander' Sam Heu Afghanistan gần đây đã tiết lộ những gì anh ấy sẽ làm từ phim trường để tưởng nhớ Jamie Fraser, vai diễn đã đưa anh ấy trở thành một ngôi sao.

Bài hát đầu tiên của The Beatles là một trong những 'Màn trình diễn thú vị nhất' của ban nhạc, theo Fab Four Insider

Bài hát đầu tiên của The Beatles là một trong những 'Màn trình diễn thú vị nhất' của ban nhạc, theo Fab Four Insider

"She Loves You" được viết trong một giờ, thu âm trong một ngày và là bài hát của The Beatles với một trong những màn trình diễn xuất sắc nhất trong sự nghiệp của họ.

Dolly Parton đã giúp đỡ bà nội 'không hợp lệ' của cô ấy khi không có đứa cháu nào khác muốn—bà ấy cũng chơi khăm bà ấy

Dolly Parton đã giúp đỡ bà nội 'không hợp lệ' của cô ấy khi không có đứa cháu nào khác muốn—bà ấy cũng chơi khăm bà ấy

Dolly Parton và bà Bessie của cô ấy có một mối quan hệ đặc biệt. Bà Parton ốm nặng, nhưng điều đó không ngăn được Dolly chơi khăm bà.

Loại gỗ cứng nhất thế giới là gì?

Loại gỗ cứng nhất thế giới là gì?

Bạn có thể nghĩ rằng gỗ sồi hoặc gỗ hồ đào rất cứng, nhưng khi nói đến loại gỗ cứng nhất thế giới thì chúng thậm chí còn không có loại nào sánh bằng.

10 sinh vật biển đáng sợ nhất ẩn núp dưới đáy đại dương

10 sinh vật biển đáng sợ nhất ẩn núp dưới đáy đại dương

Đại dương tràn ngập vẻ đẹp, nhưng cũng ẩn chứa một số sinh vật biển đáng sợ nhất hành tinh. Nhiều loài trong số này ẩn núp sâu dưới bề mặt đại dương, trong thế giới tối tăm, áp suất cao của biển sâu.

10 Phép thuật Kiếm tốt nhất trong Minecraft

10 Phép thuật Kiếm tốt nhất trong Minecraft

Nếu bạn đang chiến đấu với quái vật hung hãn hoặc chuẩn bị cho các tình huống PvP, việc nắm rõ những phép thuật kiếm tốt nhất trong Minecraft có thể mang lại cho bạn lợi thế lớn. Phép thuật kiếm cho phép bạn gây nhiều sát thương hơn, tăng lượng quái vật rơi ra và kéo dài độ bền của kiếm.

13 quốc gia xã hội chủ nghĩa có vẻ ngoài khác biệt nhau

13 quốc gia xã hội chủ nghĩa có vẻ ngoài khác biệt nhau

Khi nói đến các nước xã hội chủ nghĩa, người ta thường hình dung ra sự kiểm soát hoàn toàn của chính phủ và không có sở hữu tư nhân. Nhưng trên thực tế, các nền kinh tế xã hội chủ nghĩa rất khác nhau.

Tóm tắt về Gấu: Marcus có phải là người đàn ông tốt bụng nhất, dịu dàng nhất, chân thành nhất ở Chicago không?

Tóm tắt về Gấu: Marcus có phải là người đàn ông tốt bụng nhất, dịu dàng nhất, chân thành nhất ở Chicago không?

“Những đứa trẻ” tung dàn khách mời tên tuổi và nhiều tin tức chấn động

Với mức giá 9.800 USD, bạn có muốn 'dọn đồ' trong chiếc Toyota RAV4 2008 bảy chỗ này không?

Với mức giá 9.800 USD, bạn có muốn 'dọn đồ' trong chiếc Toyota RAV4 2008 bảy chỗ này không?

Chiếc RAV4 này được khẳng định là ở tình trạng tuyệt vời và được chuẩn bị cho một trò chơi ghế âm nhạc.

Xe tải giao hàng Amazon bốc cháy trong vụ nổ rực lửa trong cái nóng mùa hè ở Houston

Xe tải giao hàng Amazon bốc cháy trong vụ nổ rực lửa trong cái nóng mùa hè ở Houston

Cảnh quay từ máy bay không người lái đã ghi lại cảnh lính cứu hỏa dường như đang cố gắng dập tắt ngọn lửa.

Black Panther Show của Marvel sẽ là chương trình hoạt hình quan trọng nhất

Black Panther Show của Marvel sẽ là chương trình hoạt hình quan trọng nhất

Eyes of Wakanda liên kết trực tiếp với MCU, cùng với các cập nhật về X-Men '97, What If..., Daredevil, v.v.

Tất tần tật về chiếc váy trắng biểu tượng của Marilyn Monroe (và bi kịch hậu trường mà nó gây ra)

Tất tần tật về chiếc váy trắng biểu tượng của Marilyn Monroe (và bi kịch hậu trường mà nó gây ra)

Marilyn Monroe đã mặc một chiếc váy trắng nổi tiếng trong bộ phim 'The Seven Year Itch'. Sau đây là mọi thông tin cần biết về chiếc váy và khoảnh khắc mang tính biểu tượng của Monroe trên song sắt tàu điện ngầm.

Vợ của John Cleese là ai? Tất cả về Jennifer Wade

Vợ của John Cleese là ai? Tất cả về Jennifer Wade

John Cleese kết hôn với vợ mình, Jennifer Wade, vào năm 2012. Sau đây là mọi thông tin cần biết về vợ của John Cleese, Jennifer Wade.

Vợ của Patton Oswalt là ai? Tất cả về Meredith Salenger

Vợ của Patton Oswalt là ai? Tất cả về Meredith Salenger

Patton Oswalt kết hôn với vợ mình, nữ diễn viên Meredith Salenger, vào năm 2017. Sau đây là mọi thông tin cần biết về vợ của Patton Oswalt, Meredith Salenger.

Vợ của Michael C. Hall là ai? Tất cả về Morgan Macgregor

Vợ của Michael C. Hall là ai? Tất cả về Morgan Macgregor

Michael C. Hall đã kết hôn với vợ Morgan Macgregor từ năm 2016. Dưới đây là tất cả những điều cần biết về vợ của Michael C. Hall.

đại từ là siêu đáng sợ

Nó đập vào mắt tôi Đột ngột như nó thường xảy ra Nó lại ở giữa chừng <Không, không phải cái ở Thái Bình Dương đâu bạn màu hạt dẻ, cái ở lễ hội hóa trang> Mọi tiêu đề đều ầm ĩ…..

3 cách đã được chứng minh để cải thiện kỹ năng viết của bạn

Lợi ích của việc viết và các chiến lược giúp bạn làm điều đó

3 cách đã được chứng minh để cải thiện kỹ năng viết của bạn

Vào năm 2022, với việc phát hành GPT-3, kỹ năng viết dường như trở nên ít quan trọng hơn. Với AI, những người viết kém cũng có thể tạo ra đội ngũ nhân viên chất lượng.

Sự xói mòn chậm: Hé lộ những cách mọi người lãng phí trong sự nghiệp của họ

Sự xói mòn chậm: Hé lộ những cách mọi người lãng phí trong sự nghiệp của họ

Trong thế giới có nhịp độ nhanh và cạnh tranh ngày nay, sự nghiệp của một người đóng một vai trò quan trọng trong sự phát triển cá nhân, ổn định tài chính và sự hài lòng trong cuộc sống nói chung. Tuy nhiên, nhiều cá nhân thấy mình bị mắc kẹt trong một chu kỳ trì trệ và không hài lòng không ngừng, dần dần lãng phí trong cuộc sống nghề nghiệp của họ.

Điều gì làm cho một podcast có ảnh hưởng?

Điều gì làm cho một podcast có ảnh hưởng?

Tuần trước, tôi nhận thấy một thông cáo báo chí, được gửi qua PressGazette (một trang web tin tức truyền thông của Anh). Bài báo thông báo rằng Acast, công ty quảng cáo và lưu trữ podcast của Scandi, sẽ lãnh đạo một tập đoàn gồm các nhà xuất bản “có lẽ là có ảnh hưởng nhất” trong lĩnh vực podcasting.

Language