Skip to content

Instantly share code, notes, and snippets.

@EasonChang0115
Created April 17, 2018 09:36
Show Gist options
  • Save EasonChang0115/7c225f7cae64d592ef2731137d768bf0 to your computer and use it in GitHub Desktop.
Save EasonChang0115/7c225f7cae64d592ef2731137d768bf0 to your computer and use it in GitHub Desktop.
貪吃蛇
canvas#mycanvas
.panel
h2 Score: 0
button(onclick="game.startGame()") Start
// 遊戲中用到的向量物件
var Vector = function(x,y){
this.x = x || 0;
this.y = y || 0;
}
Vector.prototype.add = function(v) {
return new Vector(this.x + v.x, this.y + v.y);
}
Vector.prototype.sub = function(v) {
return new Vector(this.x - v.x, this.y - v.y);
}
Vector.prototype.length = function() {
return Math.aqrt(this.x * this.x + this.y * this.y);
}
Vector.prototype.set = function(x,y) {
this.x = x;
this.y = y;
}
Vector.prototype.equal = function(v) {
return this.x === v.x && this.y === v.y;
}
Vector.prototype.mul = function(s) {
return new Vector(this.x * s, this.y * s);
}
Vector.prototype.clone = function() {
return new Vector(this.x , this.y);
}
// 蛇物件
var Snake = function(){
this.body = [];
this.maxLength = 5;
this.head = new Vector();
this.speed = new Vector(1,0);
this.direction = 'Right';
}
Snake.prototype.update = function(){
let newHead = this.head.add(this.speed);
this.body.push(this.head);
this.head = newHead;
while(this.body.length > this.maxLength){
this.body.shift(1);
}
}
Snake.prototype.setDirection = function(dir){
var target = {};
if(dir === 'Up'){
target = new Vector(0,-1);
}
if(dir === 'Down'){
target = new Vector(0,1);
}
if(dir === 'Right'){
target = new Vector(1,0);
}
if(dir === 'Left'){
target = new Vector(-1,0);
}
if(target.equal(this.speed.mul(-1)) === false){
this.speed = target;
}
}
Snake.prototype.checkBoundary = function(gameWidth){
let xInRange = 0 <= this.head.x && this.head.x <= gameWidth;
let yInRange = 0 <= this.head.y && this.head.y <= gameWidth;
return xInRange && yInRange;
}
// 遊戲中用到的遊戲物件
var Game = function(){
this.bw = 12;//格子寬度
this.bs = 2;//格子間距
this.gameWidth = 40;
this.speed = 30;//速度
this.snake = new Snake();
this.foods = [];
this.start = false;
}
Game.prototype.startGame = function(){
this.start = true;
this.snake = new Snake();
$('.panel').hide();
$('h2').text(`Score: 0`);
this.playSound("C#5",-10,0);
this.playSound("E5",-10,200);
}
Game.prototype.endGame = function(){
this.playSound("A3",-10,100);
this.playSound("E2",-10,200);
this.playSound("A2",-10,300);
this.start = false;
$('h2').text(`Score: ${(this.snake.maxLength - 5) * 10 }`);
$('.panel').show();
}
Game.prototype.init = function(){
this.canvas = document.getElementById('mycanvas');
this.canvas.width = this.bw * this.gameWidth + this.bs * (this.gameWidth - 1);
this.canvas.height = this.bw * this.gameWidth + this.bs * (this.gameWidth - 1);
this.ctx = this.canvas.getContext('2d');
this.render();
this.update();
this.generateFood();
}
Game.prototype.getPosition = function(x,y) {
return new Vector(
x * this.bw + (x - 1) * this.bs,
y * this.bw + (y - 1) * this.bs
);
}
Game.prototype.drawBlock = function(v,color) {
this.ctx.fillStyle = color;
var pos = this.getPosition(v.x, v.y);
this.ctx.fillRect(pos.x,pos.y,this.bw,this.bw);
}
Game.prototype.drawEffect = function(x,y){
var r = 2;
var pos = this.getPosition(x,y);
var _this = this;
var effect = function(){
r++;
_this.ctx.strokeStyle = `rgba(255,0,0,${(100 - r) / 100})`;
_this.ctx.beginPath();
_this.ctx.arc(pos.x + _this.bw/2, pos.y + _this.bw/2, r, 0, Math.PI*2);
_this.ctx.stroke();
if(r<100){
requestAnimationFrame(effect);
}
}
requestAnimationFrame(effect);
}
Game.prototype.generateFood = function(){
var x = parseInt(Math.random() * this.gameWidth);
var y = parseInt(Math.random() * this.gameWidth);
this.foods.push(new Vector(x,y));
this.drawEffect(x,y);
this.playSound("E5",-15);
this.playSound("A5",-15,50);
}
Game.prototype.render = function() {
this.ctx.fillStyle = "rgba(0,0,0,0.3)";
this.ctx.fillRect(0,0,this.canvas.width,this.canvas.height);
for( var x = 0; x < this.gameWidth; x++){
for( var y = 0; y < this.gameWidth; y++){
this.drawBlock(new Vector(x,y),"rgba(255,255,255,0.1)");
}
}
// 畫出蛇
this.snake.body.forEach( (sp,i) => {
this.drawBlock(sp,'white');
});
// 畫出食物
this.foods.forEach( (food,i) => {
this.drawBlock(food,'red');
});
requestAnimationFrame(()=>{this.render()});
}
Game.prototype.update = function() {
if(this.start){
this.playSound("A2",-20);
this.snake.update();
this.foods.forEach((food,i)=>{
if(this.snake.head.equal(food)){
this.snake.maxLength++;
this.foods.splice(i,1);
this.generateFood();
}
});
this.snake.body.forEach(bp => {
if(this.snake.head.equal(bp)){
console.log('撞到身體');
this.endGame();
}
});
if(this.snake.checkBoundary(this.gameWidth) === false){
console.log('撞到邊界');
this.endGame();
}
}
this.speed = Math.sqrt(this.snake.body.length) + 5;
setTimeout(()=>{
this.update();
},parseInt(1000/this.speed));
}
Game.prototype.playSound = function(note,vol,when) {
setTimeout(function(){
var synth = new Tone.Synth().toMaster();
synth.volumne = vol || -12;
synth.triggerAttackRelease(note,'8n')
},when || 0);
}
var game = new Game();
game.init();
$(window).keydown(function(evt){
game.snake.setDirection(evt.key.replace('Arrow',''));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/13.0.1/Tone.js"></script>
html,body
width: 100%
height: 100%
margin: 0
display: flex
justify-content: center
align-items: center
background-color: #000
canvas
max-width: 100%
.panel
position: absolute
color: #fff
display: flex
justify-content: center
align-items: center
flex-direction: column
button
background-color: transparent
color: #fff
border-radius: 20px
padding: 5px 20px
cursor: pointer
outline: none
transition: all .3s
&:hover
background-color: #fff
color: #000
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment