Serpente in Javascript

2
L292092 2020-07-11 08:57.

Mi sento come se ci fosse qualcosa di sbagliato nello stile del mio codice, ma non riesco a individuare cosa c'è di sbagliato in esso. Forse il modo in cui ho rappresentato il serpente è un po 'contorto?

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.

Innanzitutto, sii indulgente, l'inglese non è la mia lingua madre :).

Ecco la mia opinione sul tuo copione. Commenterò le modifiche che ho apportato.

Ho separato il tuo script in capitoli (questo può portare a file separati se vuoi):

  • Logica di gioco (serpente e obiettivo)
  • Flusso di gioco (inizio, ciclo, fine del gioco)
  • Controlli
  • Rendering

Ho eseguito l'allineamento delle variabili e ho utilizzato la definizione del metodo abbreviato ES6 (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions)


LOGICA DI GIOCO

Per l'oggetto serpente ho cambiato la funzione di autoesecuzione in una funzione con nome per una migliore leggibilità.

Ho anche accorciato i nomi dei metodi perché sappiamo già di essere sull'oggetto serpente.

Ad esempio, ho cambiato snake.expandSnake () in snake.expand ().

Gli onliner di riduzione erano un po 'difficili da leggere, quindi li ho cambiati in multi riga.

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
        }
    }

}

FLUSSO DI GIOCO

Puoi vedere gli oggetti serpente e obiettivo ora creati in questo capitolo.

Ho rinominato checkForSnakeDeath e checkForGoalCapture in handleSnakeDeath e handleGoal, perché quello che stai facendo in queste funzioni è più di un semplice controllo. A mio parere, un controllo dovrebbe restituire solo vero o falso, o forse un oggetto di errore ma non eseguire azioni.

// 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)
    })
}

CONTROLLI

Per i controlli ho cambiato il "passaggio" alla mappatura dei tasti e ai metodi degli oggetti per una migliore leggibilità e modularità (ora puoi eseguire move.left () a livello di codice se vuoi).

// 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

Per il rendering ho messo insieme le variabili di colore per facilitare le modifiche. Ho diviso updateDOM (che ora si chiama paintGrid) in due metodi: forEachCell e 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
}

UTILI

Solo alcune piccole modifiche alla leggibilità qui

// 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
}

Ovviamente questa è solo la mia opinione, quindi scegli quello che ti piace nelle mie modifiche :-)

Related questions

MORE COOL STUFF

Jana Duggar: tutto ciò che ha detto sull'amore e sulla sua finestra di 5 anni per trovare "l'unico"

Jana Duggar: tutto ciò che ha detto sull'amore e sulla sua finestra di 5 anni per trovare "l'unico"

Jana Duggar è stata aperta sulla sua ricerca dell'amore. Ecco tutto ciò che ha detto sull'argomento e sulla sua finestra di cinque anni per il matrimonio.

Sam Heughan sa esattamente cosa prenderà dal set di 'Outlander': 'Mi sento come se fossi Jamie quando li indosso'

Sam Heughan sa esattamente cosa prenderà dal set di 'Outlander': 'Mi sento come se fossi Jamie quando li indosso'

La star di 'Outlander' Sam Heughan ha recentemente rivelato cosa prenderà dal set per ricordare Jamie Fraser, il ruolo che lo ha reso una star.

La canzone dei primi Beatles che è stata una delle "esibizioni più entusiasmanti" della band, secondo un membro dei Fab Four

La canzone dei primi Beatles che è stata una delle "esibizioni più entusiasmanti" della band, secondo un membro dei Fab Four

"She Loves You" è stata scritta in un'ora, registrata in un giorno ed è stata la canzone dei Beatles con una delle loro migliori performance della loro carriera.

Dolly Parton ha aiutato sua nonna "invalida" quando nessuno degli altri nipoti l'avrebbe fatto: le faceva anche uno scherzo

Dolly Parton ha aiutato sua nonna "invalida" quando nessuno degli altri nipoti l'avrebbe fatto: le faceva anche uno scherzo

Dolly Parton e sua nonna Bessie avevano un rapporto speciale. Nonna Parton era molto malata, ma questo non ha impedito a Dolly di farle uno scherzo.

Qual è il legno più duro del mondo?

Qual è il legno più duro del mondo?

Si potrebbe pensare che la quercia o il noce americano siano resistenti, ma quando si tratta del legno più duro del mondo, non si avvicinano nemmeno.

10 delle creature marine più spaventose che si nascondono nelle profondità dell'oceano

10 delle creature marine più spaventose che si nascondono nelle profondità dell'oceano

L'oceano è pieno di bellezza, ma nasconde anche alcune delle creature marine più spaventose del pianeta. Molti di questi animali si nascondono molto al di sotto della superficie dell'oceano, nel mondo oscuro e ad alta pressione delle profondità marine.

I 10 migliori incantesimi per le spade in Minecraft

I 10 migliori incantesimi per le spade in Minecraft

Se stai combattendo contro nemici ostili o ti stai preparando per scenari PvP, conoscere i migliori incantesimi per le spade offerti da Minecraft può darti un vantaggio notevole. Incantare le spade ti permette di infliggere più danni, aumentare il numero di nemici che cadono e aumentare la durata della tua spada.

13 Paesi socialisti molto diversi tra loro

13 Paesi socialisti molto diversi tra loro

Quando si parla di paesi socialisti, spesso si immagina un sistema con il pieno controllo del governo e nessuna proprietà privata. Ma nella pratica, le economie socialiste variano notevolmente.

Il riepilogo di Bear: Marcus è l'uomo più gentile, tenero e sincero di Chicago?

Il riepilogo di Bear: Marcus è l'uomo più gentile, tenero e sincero di Chicago?

“Children” presenta un cameo di un grande nome e alcune notizie devastanti

A $ 9.800, li metteresti dentro in questa Toyota RAV4 del 2008 a sette posti?

A $ 9.800, li metteresti dentro in questa Toyota RAV4 del 2008 a sette posti?

Si dice che questo RAV4 sia in condizioni eccellenti ed è pronto per il gioco delle sedie musicali.

Il furgone delle consegne di Amazon prende fuoco in un'esplosione di fuoco durante la calura estiva di Houston

Il furgone delle consegne di Amazon prende fuoco in un'esplosione di fuoco durante la calura estiva di Houston

Le riprese dei droni hanno ripreso i vigili del fuoco che sembravano lottare per spegnere l'incendio.

Lo spettacolo Black Panther della Marvel sarà lo spettacolo animato più cruciale di sempre

Lo spettacolo Black Panther della Marvel sarà lo spettacolo animato più cruciale di sempre

Eyes of Wakanda si collega direttamente al MCU, oltre ad aggiornamenti su X-Men '97, What If..., Daredevil e altro ancora.

La storia sentimentale di Ava Gardner: uno sguardo ai matrimoni e alle storie d'amore dell'icona di Hollywood

La storia sentimentale di Ava Gardner: uno sguardo ai matrimoni e alle storie d'amore dell'icona di Hollywood

Ava Gardner avrebbe dovuto sposarsi con Mickey Rooney e Frank Sintra, e frequentare diverse altre star di Hollywood. Ecco uno sguardo alla storia sentimentale di Ava Gardner.

Chi è la moglie di John Cleese? Tutto su Jennifer Wade

Chi è la moglie di John Cleese? Tutto su Jennifer Wade

John Cleese ha sposato sua moglie, Jennifer Wade, nel 2012. Ecco tutto quello che c'è da sapere sulla moglie di John Cleese, Jennifer Wade.

Chi è la ragazza di Dev Patel? Tutto su Tilda Cobham-Hervey

Chi è la ragazza di Dev Patel? Tutto su Tilda Cobham-Hervey

Dev Patel ha incontrato la sua ragazza nel 2016 sul set di "Hotel Mumbai". Ecco tutto quello che c'è da sapere su Tilda Cobham-Hervey.

Cronologia della relazione tra Garth Brooks e Trisha Yearwood

Cronologia della relazione tra Garth Brooks e Trisha Yearwood

Garth Brooks e Trisha Yearwood sono sposati dal 2005. Ecco una cronologia completa della loro relazione.

i pronomi sono super spaventosi

Mi colpisce Improvvisamente come spesso accade È di nuovo la via di mezzo <No, non quello nel pacifico tu marrone, quello al carnevale> Ogni titolo a voce alta…..

3 modi comprovati per migliorare la tua abilità di scrittura

I vantaggi della scrittura e le strategie per aiutarti a farlo

3 modi comprovati per migliorare la tua abilità di scrittura

Nel 2022, con il rilascio di GPT-3, l'abilità di scrittura sembra diventare meno importante. Con l'intelligenza artificiale, anche le persone con una scrittura scadente possono generare personale di qualità.

La lenta erosione: svelare i modi in cui le persone deperiscono nella loro carriera

La lenta erosione: svelare i modi in cui le persone deperiscono nella loro carriera

Nel mondo frenetico e competitivo di oggi, la propria carriera gioca un ruolo significativo nella crescita personale, nella stabilità finanziaria e nella soddisfazione generale della vita. Tuttavia, molte persone si trovano intrappolate in un ciclo inesorabile di stagnazione e insoddisfazione, che si esauriscono gradualmente nella loro vita professionale.

Cosa rende influente un podcast?

Cosa rende influente un podcast?

La scorsa settimana ho notato un comunicato stampa, inviato tramite PressGazette (un sito Web di notizie sui media britannici). L'articolo annunciava che Acast, la società di hosting e pubblicità di podcast di Scandi, avrebbe guidato un consorzio di editori "probabilmente i più influenti" nel podcasting.

Language