游戏:贪吃蛇

创建时间 2020-06-06
更新时间 2022-05-31

附上游戏的代码。

/* jshint esversion: 6 */

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");

var width = 32;
var height = 18;
var size = 0;
var length = 2; // 初始化长度
var interval = 300;

var fullscreen = false;

var grey = 'grey';
var green = 'green';
var red = 'red';
var orange = 'orange';
var pink = 'pink';
var transparent = 'transparent';

var line_color = grey;
var body_color = green;
var head_color = red;
var food_color = orange;
var next_color = pink;

// 37 左,38 上,39 右,40 下
var code_left = 37;
var code_up = 38;
var code_right = 39;
var code_down = 40;
var code_restart = 13;
var code_accelerate = 65;

var snake = new Snake();

function Box(x, y, color) {
    this.x = x;
    this.y = y;
    this.color = color;
}

Box.prototype.draw = function () {
    context.beginPath();
    context.fillStyle = this.color;
    context.rect(this.x * size, this.y * size, size, size);
    context.fill();
    if (this.color !== transparent)
        context.stroke();
};

function Snake() {
    this.canvas = canvas;
    this.context = context;
    this.map = {};
    for (i = 0; i < width; i++) {
        for (var j = 0; j < height; j++) {
            key = i + '-' + j;
            this.map[key] = {};
            this.map[key].x = i;
            this.map[key].y = j;
        }
    }
}

Snake.prototype.new_game = function () {
    this.body = [];
    for (var i = 0; i < length; i++) {
        this.body[i] = new Box(i, 0, body_color);
    }

    this.head = new Box(i, 0, head_color);
    this.next = null;
    this.food = this.get_random_food();

    // 37 左,38 上,39 右,40 下
    this.direction = 39;
    this.directions = [39];
    this.running = true;
    this.accelerate = false;
    this.last_direct_time = new Date().getTime();
    this.draw();
};

Snake.prototype.get_interval = function () {
    var result = interval;
    if (this.accelerate) {
        result /= 4;
    }
    if (this.eaten) {
        this.eaten = false;
        result += 50;
    }
    return result;
};

Snake.prototype.clear = function () {
    context.clearRect(0, 0, canvas.width, canvas.height);
};

Snake.prototype.draw = function () {
    if (snake.death()) return;
    this.clear();
    for (var i = 0; i < this.body.length; i++) {
        this.body[i].draw();
    }
    this.head.draw();
    this.food.draw();
    if (this.next != null) this.next.draw();
    this.draw_line();
};

Snake.prototype.draw_line = function () {
    for (i = 0; i < height + 1; i++) {
        context.strokeStyle = line_color;
        context.beginPath();
        context.moveTo(0, i * size);
        context.lineTo(canvas.width, i * size);
        context.closePath();
        context.stroke();
    }

    for (i = 0; i < width + 1; i++) {
        context.strokeStyle = line_color;
        context.beginPath();
        context.moveTo(i * size, 0);
        context.lineTo(i * size, canvas.height);
        context.closePath();
        context.stroke();
    }
};

Snake.prototype.get_random_food = function () {
    var body = {};
    var remain = [];

    var key = this.head.x + '-' + this.head.y;
    body[key] = true;

    this.body.forEach(function (box) {
        key = box.x + '-' + box.y;
        body[key] = true;
    });

    for (var name in this.map) {
        if (name in body) continue;
        remain.push(this.map[name]);
    }

    if (remain.length == 0) {
        alert("You win!!!");
        this.new_game();
        return this.get_random_food();
    }

    var value = remain[Math.round(Math.random() * (remain.length - 1))];
    var food = new Box(value.x, value.y, orange);
    return food;
};

Snake.prototype.check = function () {
    if (this.death()) {
        alert('Game Over');
        this.running = false;
        return true;
    }
    return false;
};

Snake.prototype.death = function () {
    // 撞墙
    if (this.head.x >= width) return true;
    if (this.head.y >= height) return true;
    if (this.head.x < 0) return true;
    if (this.head.y < 0) return true;

    // 撞身体
    for (var i = 0; i < this.body.length; i++) {
        var box = this.body[i];
        if (box.x != this.head.x) continue;
        if (box.y != this.head.y) continue;
        return true;
    }
    return false;
};

Snake.prototype.eat = function () {
    if (this.next.x == this.food.x && this.next.y == this.food.y) {
        this.food = this.get_random_food();
        this.eaten = true;
        return true;
    }
    this.eaten = false;
    return false;
};

Snake.prototype.get_next_box = function () {
    // 37 左,38 上,39 右,40 下

    if (this.check()) return this.next;

    deltas = {
        37: [-1, 0],
        38: [0, -1],
        39: [1, 0],
        40: [0, 1],
    };

    delta = deltas[this.direction];

    var x = this.head.x + delta[0];
    var y = this.head.y + delta[1];
    return new Box(x, y, next_color);
};

Snake.prototype.get_direction = function () {
    changes = {
        37: [38, 40],
        39: [38, 40],
        38: [37, 39],
        40: [37, 39],
    };
    change = changes[this.direction];
    for (var i = 0; i < change.length; i++) {
        var code = change[i];
        if (keymap[code]) {
            this.direction = code;
            this.directions[0] = code;
            break;
        }
    }
    var direction = this.directions[0];
    if (this.directions.length > 1) {
        this.directions.shift();
    }
    return direction;
};

Snake.prototype.move = function () {
    this.direction = this.get_direction();
    if (this.check()) return;

    if (this.next == null)
        this.next = this.get_next_box();

    if (!this.eat()) {
        this.body.shift();
    }

    this.next.color = head_color;
    this.body.push(this.head);
    this.head.color = body_color;
    this.head = this.next;
    this.next = null;
};

Snake.prototype.start = function () {
    if (!snake.running) return;
    snake.move();
    if (snake.check()) return;
    snake.draw();
    setTimeout(snake.start, snake.get_interval());
};

Snake.prototype.restart = function () {
    var running = this.running;
    this.new_game();
    if (!running) {
        this.start();
    }
    return;
};



Snake.prototype.key_down_event = function (code) {
    console.debug(code);
    keymap[code] = true;
    if (code == code_restart) { // 如果是Enter,重新开始
        this.restart();
    }
    if (code == code_accelerate){
        this.accelerate = true;
    }

    changes = {
        37: [38, 40],
        39: [38, 40],
        38: [37, 39],
        40: [37, 39],
    };

    var direction = this.directions.slice(-1)[0];
    change = changes[direction];
    if (!change.includes(code)) return;

    if (snake.last_direct_time + interval <= new Date().getTime()) {
        snake.directions[0] = code;
    } else {
        snake.directions.push(code);
        while (snake.directions.length > 2) {
            snake.directions.shift();
        }
    }
    snake.last_direct_time = new Date().getTime();
};

Snake.prototype.key_up_event = function (code) {
    keymap[code] = false;
    if (code == code_accelerate){
        this.accelerate = false;
    }
};

function resize() {
    size = $('.segment.canvas').width() / width;
    canvas.width = $('.segment.canvas').width();
    canvas.height = $('.segment.canvas').width() * height / width;
}

$(window).resize(function () {
    resize();
    snake.draw();
});

$(document).ready(function () {
    resize();
    snake.new_game();
    setTimeout(snake.start, snake.get_interval());
});

var keymap = {};

$(document).keydown(function (e) {
    // 37 左,38 上,39 右,40 下
    var event = e || window.event;
    snake.key_down_event(event.keyCode);
    event.preventDefault();
});

$(document).keyup(function (e) {
    var event = e || window.event;
    snake.key_up_event(event.keyCode);
    event.preventDefault();
});

// button down

$('.button.up').mousedown(function (e) {
    snake.key_down_event(code_up);
});

$('.button.down').mousedown(function (e) {
    snake.key_down_event(code_down);
});

$('.button.left').mousedown(function (e) {
    snake.key_down_event(code_left);
});

$('.button.right').mousedown(function (e) {
    snake.key_down_event(code_right);
});

// button up

$('.button.up').mouseup(function (e) {
    snake.key_up_event(code_up);
});

$('.button.down').mouseup(function (e) {
    snake.key_up_event(code_down);
});

$('.button.left').mouseup(function (e) {
    snake.key_up_event(code_left);
});

$('.button.right').mouseup(function (e) {
    snake.key_up_event(code_right);
});

// restart down

$('.button.restart').click(function (e) {
    snake.restart();
});

$('.button.pause').click(function (e) {
    $(this).toggleClass('yellow');
    $(this).toggleClass('blue');
    if (snake.running) {
        snake.running = false;
        $(this).html('继续游戏');
    } else {
        snake.running = true;
        snake.start();
        $(this).html('暂停游戏');
    }
});

$('.button.accelerate').mousedown(function (e) {
    snake.accelerate = true;
});

$('.button.accelerate').mouseup(function (e) {
    snake.accelerate = false;
});

$(canvas).dblclick(function () {
    if (!fullscreen) {
        var element = canvas || document.body;
        if (element.requestFullscreen) {
            element.requestFullscreen();
        } else if (element.mozRequestFullScreen) {
            element.mozRequestFullScreen();
        } else if (element.webkitRequestFullscreen) {
            element.webkitRequestFullscreen();
        } else if (element.msRequestFullscreen) {
            element.msRequestFullscreen();
        }
        fullscreen = true;
    } else {
        if (document.exitFullscreen) {
            document.exitFullscreen();
        } else if (document.mozExitFullScreen) {
            document.mozExitFullScreen();
        } else if (document.webkitExitFullscreen) {
            document.webkitExitFullscreen();
        }
        fullscreen = false;
    }
});