Schlangenspiel mit Canvas-Elementcode

75241
Vignesh

Ich habe ein klassisches Schlangenspiel im Canvas-Element erstellt. Ich habe dabei keine Best Practices in Betracht gezogen, ich wollte es erst beenden. Jetzt ist es an der Zeit, die Kodierungspraxis zu verbessern. Sie können mir dabei helfen, indem Sie schlechte Praktiken erwähnen, Code-Verbesserungen vornehmen und alles andere vorschlagen.

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Feed the Snake v 1.1 beta</title>
<style>
body
{
    background:#000;
    color:#FFF;
}
canvas
{
    background:#FFF;
}
#controls
{
    position:absolute;
    top:0;
    right:0;
    margin:10px;
}
</style>
<script type="text/javascript">
var snake = window.snake || {};
function launchFullscreen(element) {
  if(element.requestFullscreen) {
    element.requestFullscreen();
  } else if(element.mozRequestFullScreen) {
    element.mozRequestFullScreen();
  } else if(element.webkitRequestFullscreen) {
    element.webkitRequestFullscreen();
  } else if(element.msRequestFullscreen) {
    element.msRequestFullscreen();
  }
}
window.onload = function(){
    document.addEventListener("fullscreenchange", function(){snake.game.adjust();});
    document.addEventListener("webkitfullscreenchange", function(){snake.game.adjust();});
    document.addEventListener("mozfullscreenchange", function(){snake.game.adjust();});
    document.addEventListener("MSFullscreenChange", function(){snake.game.adjust();});

    snake.game = (function()
    {
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');
        var status=false;
        var score = 0;
        var old_direction = 'right';
        var direction = 'right';
        var block = 10;
        var score = 0;
        var refresh_rate = 250;
        var pos = [[5,1],[4,1],[3,1],[2,1],[1,1]];
        var scoreboard = document.getElementById('scoreboard');
        var control = document.getElementById('controls');
        var keys = {
            37 : 'left',
            38 : 'up',
            39 : 'right',
            40 : 'down'
            };
        function adjust()
        {
            if (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement )
            {
                canvas.width=window.innerWidth;
                canvas.height=window.innerHeight;
                control.style.display='none';
            }
            else
            {
                canvas.width=850;
                canvas.height=600;
                control.style.display='inline';
            }
        }
        var food = [Math.round(Math.random(4)*(canvas.width - 10)), Math.round(Math.random(4)*(canvas.height - 10)),];
        function todraw()
        {
            for(var i = 0; i < pos.length; i++)
            {
                draw(pos[i]);
            }
        }
        function giveLife()
        {
            var nextPosition = pos[0].slice();
            switch(old_direction)
            {
                case 'right':
                    nextPosition[0] += 1;
                    break;
                case 'left':
                    nextPosition[0] -= 1;
                    break;
                case 'up':
                    nextPosition[1] -= 1;
                    break;
                case 'down':
                    nextPosition[1] += 1;
                    break;    
            }
            pos.unshift(nextPosition);
            pos.pop();
        }
        function grow()
        {
            var nextPosition = pos[0].slice();
            switch(old_direction)
            {
                case 'right':
                    nextPosition[0] += 1;
                    break;
                case 'left':
                    nextPosition[0] -= 1;
                    break;
                case 'up':
                    nextPosition[1] -= 1;
                    break;
                case 'down':
                    nextPosition[1] += 1;
                    break;    
            }
            pos.unshift(nextPosition);
        }
        function loop()
        {
            ctx.clearRect(0,0,canvas.width,canvas.height);
            todraw();
            giveLife();
            feed();
            if(is_catched(pos[0][0]*block,pos[0][1]*block,block,block,food[0],food[1],10,10))
            {
                score += 10;
                createfood();
                scoreboard.innerHTML = score;
                grow();
                if(refresh_rate > 100)
                {
                    refresh_rate -=5;
                }
            }
            snake.game.status = setTimeout(function() { loop(); },refresh_rate);
        }
        window.onkeydown = function(event){
             direction = keys[event.keyCode];
                if(direction)
                {
                    setWay(direction);
                    event.preventDefault();
                }
            };
        function setWay(direction)
        {
            switch(direction)
            {
                case 'left':
                    if(old_direction!='right')
                    {
                        old_direction = direction;
                    }
                    break;
                case 'right':
                    if(old_direction!='left')
                    {
                        old_direction = direction;
                    }
                    break;
                case 'up':
                    if(old_direction!='down')
                    {
                        old_direction = direction;
                    }
                    break;
                case 'down':
                    if(old_direction!='up')
                    {
                        old_direction = direction;
                    }
                    break;
            }

        }
        function feed()
        {
            ctx.beginPath();
            ctx.fillStyle = "#ff0000";
            ctx.fillRect(food[0],food[1],10,10);
            ctx.fill();
            ctx.closePath();
        }
        function createfood()
        {
            food = [Math.round(Math.random(4)*850), Math.round(Math.random(4)*600)];
        }
        function is_catched(ax,ay,awidth,aheight,bx,by,bwidth,bheight) {
            return !(
            ((ay + aheight) < (by)) ||
            (ay > (by + bheight)) ||
            ((ax + awidth) < bx) ||
            (ax > (bx + bwidth))
            );
        }
        function draw(pos)
        {
            var x = pos[0] * block;
            var y = pos[1] * block;
            if(x >= canvas.width || x <= 0 || y >= canvas.height || y<= 0)
            {
                    document.getElementById('pause').disabled='true';
                    snake.game.status=false;
                    ctx.clearRect(0,0,canvas.width,canvas.height);
                    ctx.font='40px san-serif';
                    ctx.fillText('Game Over',300,250);
                    ctx.font = '20px san-serif';
                    ctx.fillStyle='#000000';
                    ctx.fillText('To Play again Refresh the page or click the Restarts button',200,300);
                    throw ('Game Over');
            }
            else
            {
                ctx.beginPath();
                ctx.fillStyle='#000000';
                ctx.fillRect(x,y,block,block);
                ctx.closePath();
            }
        }
        function pause(elem)
        {
            if(snake.game.status)
            {
                clearTimeout(snake.game.status);
                snake.game.status=false;
                elem.value='Play'
            }
            else
            {
                loop();
                elem.value='Pause';
            }
        }
        function begin()
        {
            loop();
        }
        function restart()
        {
            location.reload();
        }
        function start()
        {
            ctx.fillStyle='#000000';
            ctx.fillRect(0,0,canvas.width,canvas.height);
            ctx.fillStyle='#ffffff';
            ctx.font='40px helvatica';
            ctx.fillText('Vignesh',370,140);
            ctx.font='20px san-serif';
            ctx.fillText('presents',395,190);
            ctx.font='italic 60px san-serif';
            ctx.fillText('Feed The Snake',240,280);
            var img = new Image();
            img.onload = function()
            {
                ctx.drawImage(img,300,300,200,200);
                ctx.fillRect(410,330,10,10);
            }
            img.src ='snake.png';
        }
        function fullscreen()
        {
            launchFullscreen(canvas);
        }
        return {
            pause: pause,
            restart : restart,
            start : start,
            begin: begin,
            fullscreen : fullscreen,
            adjust : adjust,
        };
    })();
    snake.game.start();
}
</script>
</head>
<body>
<canvas width="850" height="600" id="canvas" style="border:1px solid #333;" onclick="snake.game.begin();">
</canvas>
<div id="controls" style="float:right; text-align:center;">
    <input type="button" id="pause" value="Play" onClick="snake.game.pause(this);" accesskey="p">
    <input type="button" id="restart" value="Restart" onClick="snake.game.restart();">
    <br/><br/>
    <input type="button" id="fullscreen" value="Play Fullscreen" onClick="snake.game.fullscreen();">
    <br/><br/>
    <div style="font-size:24px;">
    Score : 
    <span id="scoreboard">0</span>
    </div>
</div>
</body>
</html>

Sie können eine Live-Version des Spiels hier sehen .

Antworten
16
Ich bekomme kein Spiel zu Ende, wenn die Schlange sich selbst trifft; nur wenn es die Wände trifft. Macht es etwas zu einfach :) Flambino vor 6 Jahren 3
Sie sollten den "Apfel" und die Schlange auf derselben Linie richtig anordnen, damit Sie tatsächlich sehen können, ob Sie ihn essen möchten oder nicht. Chrillewoodz vor 5 Jahren 0
@Chrillewoodz Ich verstehe es nicht, kannst du mehr erklären? Vignesh vor 5 Jahren 0
Dies ist der Apfel: - und wenn die Schlange dafür kommt, richten sie sich wie folgt aus: -_, statt: -. Kapiert? Chrillewoodz vor 5 Jahren 1
Ja, ich habe es verstanden. Vignesh vor 5 Jahren 0
Der Demo-Link ist jetzt defekt. Grant Miller vor 2 Jahren 1
@ GrantMiller Ich werde es mit einer Geige aktualisieren, Danke für das Melden. Vignesh vor 2 Jahren 0

4 Antworten auf die Frage

14
Syjin

Einige Gedanken zu Ihrem allgemeinen Codestil (einige Punkte können von Ihren persönlichen Vorlieben abhängen):

  1. Ich empfehle, HTML / CSS / JS in verschiedene Dateien aufzuteilen
  2. Ihre Verwendung von Einzug und Leerzeichen ist inkonsistent

    function launchFullscreen(element) {
      if(element.requestFullscreen) {
        element.requestFullscreen();
    

    hat einen Einzug von zwei Leerzeichen

        snake.game = (function()
        {
            var canvas = document.getElementById('canvas');
    

    hat einen Einzug von vier Räumen

            if(x >= canvas.width || x <= 0 || y >= canvas.height || y<= 0)
            {
                    document.getElementById('pause').disabled='true';
                    snake.game.status=false;
    

    hat einen Einzug von acht Leerzeichen

    var status=false; // no spaces before/after '='
    var block = 10; // space before/after '='
    
  3. Sie haben zwei Zeiten:

    var score = 0;
    
  4. Methodennamen sind inkonsistent is_catched, setWay, todraw.

  5. Schreiben Sie Konstanten in Großbuchstaben, um sie von Variablen zu unterscheiden, die Sie ändern möchten : BLOCKAnstatt blockoder in diesem Fall ist etwas ähnlicherBLOCK_SIZE

  6. Sie deklarieren Ihre foodVariable beim ersten Mal irgendwo zwischen allen Funktionen, obwohl Sie eine creatfoodMethode haben

  7. Es gibt mehrere magische Zahlen, die Sie in Variablen umwandeln können / sollen

  8. Einige Parameternamen sind ziemlich kryptisch: is_catched(ax,ay,awidth,aheight,bx,by,bwidth,bheight)

  9. Sie können für einige Variablen Objekte anstelle von Arrays verwenden. ZB: foodist ein Array mit 2 Elementen (wahrscheinlich x / y pos). Sie könnten daraus ein Objekt machen { x: XXX, y: XXX }. Dies kann an einigen Stellen die Lesbarkeit verbessern.

  10. Derzeit scheint Ihre Aktualisierungslogik mit Ihrer Zeichnungslogik gemischt zu sein. Es ist wahrscheinlich besser (und einfacher zu pflegen), wenn Sie diese trennen. Sie überprüfen auch, ob das Spiel in Ihrem Draw-Call vorbei ist ...

"Cached" in "is_catched" sollte wahrscheinlich auch "erwischt" werden. Stephan vor 6 Jahren 3
9
konijn

Von einem einmal vorbei:

Gut

  • Ich mag es, wie Sie eine IIFE verwenden
  • Ich mag es wirklich, wie du verwendest direction = keys[event.keyCode];

Nicht so gut

  • Sie wenden die zweite gute Technik nicht konsequent an, zum Beispiel diese:

    function setWay(direction)
    {
        switch(direction)
        {
            case 'left':
                if(old_direction!='right')
                {
                    old_direction = direction;
                }
                break;
            case 'right':
                if(old_direction!='left')
                {
                    old_direction = direction;
                }
                break;
            case 'up':
                if(old_direction!='down')
                {
                    old_direction = direction;
                }
                break;
            case 'down':
                if(old_direction!='up')
                {
                    old_direction = direction;
                }
                break;
        }
    
    }
    

    hätte einfach sein können

    function setWay(direction)
    {
      var oppositeDirection = {
        left : 'right',
        right: 'left',
        up: 'down',
        down:'up'
      }
    
      if( direction != oppositeDirection[old_direction] ){
        old_direction = direction;
      }
    }
    

    Ich werde tiefe Gedanken darüber lassen, ob

    • Sie möchten angeben, dass 'left'das Gegenteil von ist 'right', da Sie 'right'das Gegenteil von angegeben haben'left'
    • Ob Sie zusammenführen möchten oppositeDirectionundkeys
  • Sie haben etwas Code eingefügt giveLifeund growdas könnte auch von dem oben genannten Ansatz profitieren. Ich hätte das geschrieben:

        switch(old_direction)
        {
            case 'right':
                nextPosition[0] += 1;
                break;
            case 'left':
                nextPosition[0] -= 1;
                break;
            case 'up':
                nextPosition[1] -= 1;
                break;
            case 'down':
                nextPosition[1] += 1;
                break;    
        }
    

    wie

    //2 properly named array indexes for x and y
    var X = 0; 
    var Y = 1;
    //vectors for each direction
    var vectors = {
      right : { x : 1, y : 0 },
      left  : { x : -1, y : 0 },
      up    : { x : 0, y : -1 },
      down  : { x : 0, y : 1 }
    }
    
    function updatePosition( direction ){
    
      var vector = vectors( direction );
      if( vector ){
        nextPosition[X] += vector.x;
        nextPosition[Y] += vector.y;
      }
      else{
        throw "Invalid direction: " + direction
      }
    }
    

    Die Vorteile hier sind:

    • Wenn Sie eine Schlange mit 8 Richtungen spielen wollten, könnten Sie
    • Kein stiller Fehler, wenn eine ungültige Richtung weitergegeben wird
  • Der folgende Code gibt mir die Gänsehaut:

    function launchFullscreen(element) {
      if(element.requestFullscreen) {
        element.requestFullscreen();
      } else if(element.mozRequestFullScreen) {
        element.mozRequestFullScreen();
      } else if(element.webkitRequestFullscreen) {
        element.webkitRequestFullscreen();
      } else if(element.msRequestFullscreen) {
        element.msRequestFullscreen();
      }
    }
    

    hast du überlegt, so etwas zu benutzen?

    function launchFullscreen(e) {
      var request = e.requestFullscreen || 
                    e.mozRequestFullScreen || 
                    e.webkitRequestFullscreen || 
                    e.msRequestFullscreen;
      request();
    }
    
  • Dies ist auch kein schöner Anblick:

    document.addEventListener("fullscreenchange", function(){snake.game.adjust();});
    document.addEventListener("webkitfullscreenchange", function(){snake.game.adjust();});
    document.addEventListener("mozfullscreenchange", function(){snake.game.adjust();});
    document.addEventListener("MSFullscreenChange", function(){snake.game.adjust();});
    

    Das sollte zumindest sein

    document.addEventListener("fullscreenchange",       snake.game.adjust );
    document.addEventListener("webkitfullscreenchange", snake.game.adjust );
    document.addEventListener("mozfullscreenchange",    snake.game.adjust );
    document.addEventListener("MSFullscreenChange",     snake.game.adjust );
    

    und wirklich muss es einen besseren Weg geben, als jedes Browserereignis zu abonnieren;) Ich gehe davon aus, dass Sie nicht einfach angeben, snake.game.adjustda es zu diesem Zeitpunkt noch nicht initialisiert ist. Ich würde dieses Problem lieber lösen und dann Funktionen erstellen, um dieses Problem zu lösen.

4
Danziger

Ich würde vorschlagen, die Schlange als einzelne Polylinie anstatt als eine Reihe von Blöcken zu zeichnen, so dass Sie nur strokeeinmal aufrufen, anstatt fillRectso oft zu rufen, wie Blöcke die Schlange haben.

Im Allgemeinen wäre es effizienter, eine komplexe Form zu streicheln, anstatt nur ein paar einfache. Sie können das in diesem Test sehen .

Eine andere Option, die ich in Betracht ziehen würde, ist clearRectnur den letzten Block der Schlange zu ziehen und dann fillRectnur den neuen zu zeichnen ( ), so dass Sie nicht die gesamte Szene neu zeichnen müssen, sondern nur die Teile, die sich geändert haben. Noch ein Test hier .

Sie müssen beide Optionen testen und sehen, welche für Sie besser ist, aber ich würde die zweite Option wählen.

Ich würde auch requestAnimationFrameinsted of verwenden setTimeout. Aus dem MDN-Dokument:

Die window.requestAnimationFrame()Methode teilt dem Browser mit, dass Sie eine Animation ausführen möchten, und fordert den Browser auf, eine angegebene Funktion aufzurufen, um eine Animation vor der nächsten Aktualisierung zu aktualisieren. Die Methode nimmt als Argument einen Rückruf an, der vor dem Repaint aufgerufen werden soll.

Lesen Sie dazu auch diesen Beitrag . Es erläutert die Grundlagen und auch das Festlegen einer benutzerdefinierten Framerate, damit Sie sie weiterhin refresh_ratein Ihrem Code verwenden können.

Es gibt zwei Möglichkeiten:

Einwickeln requestAnimationFramein ein setTimeout:

function draw() {
    setTimeout(function() {
        requestAnimationFrame(draw);
        // Drawing code goes here
    }, customTime);
}

Oder mit Deltas:

var time;
function draw() {
    requestAnimationFrame(draw);
    var now = new Date().getTime(),
        dt = now - (time || now);

    time = now;

    // Drawing code goes here... for example updating an 'x' position:
    this.x += 10 * dt; // Increase 'x' by 10 units per millisecond
}

In Ihrem Fall denke ich, dass es die erste Option ist, da die Schlange nur in bestimmten Blöcken gezeichnet werden kann. Daher macht es mir nicht viel Sinn, hier Deltas zu verwenden.

2
ANeves

Zu @ Sykins Antwort würde ich hinzufügen:

Fehler

Wenn Sie drücken downund dann leftschneller als die Schlange braucht, um "einen Schritt" zu rutschen, dreht sich die Schlange um und schlängelt sich über sich.

Einzug, Code-Stil und Code-Konsistenz

Ich finde, dass JSLint mir wirklich hilft, einen einheitlichen Code-Stil zu erhalten.

Strikter Modus

Ich schlage vor, Sie verwenden den strikten Modus in Ihrem JS, indem Sie mit einer Zeile beginnen"use strict";

Der "use strict" Vorschlag muss klargestellt werden. "Beginnen mit" ist für mein pedantisches Ohr vage. "Strict" hat Geltungsbereich, also kommt es darauf an, wo Sie es platzieren. radarbob vor 2 Jahren 0