基础HTML页面
< ! DOCTYPE html>
< html lang= "en" >
< head>
< meta charset= "UTF-8" >
< meta name= "viewport" content= "width=device-width, initial-scale=1.0" >
< title> Document< / title>
< style>
* {
padding: 0 px;
margin: 0 px;
}
#map {
width: 400 px;
height: 400 px;
background- color: #000 ;
position: relative;
}
< / style>
< / head>
< body>
< h2 id= "grade" > 0 < / h2>
< div id= "map" > < / div>
< script type= "module" >
import Game from "./js/game.js" ;
{
let map = document. getElementById ( "map" ) ;
let game = new Game ( map, 20 ) ;
let gradeEl = document. getElementById ( "grade" ) ;
game. on ( "changegrade" , ( grade) => {
console. log ( grade) ;
gradeEl. innerHTML = parseInt ( grade) ;
} ) ;
document. onclick = function ( ) {
game. start ( ) ;
}
}
< / script>
< / body>
< / html>
地图类
export default class Map {
constructor ( el, rect = 10 ) {
this . el = el;
this . rect = rect;
this . data = [ ] ;
this . rows = Math. ceil ( Map. getStyle ( el, "height" ) / rect) ;
this . cells = Math. ceil ( Map. getStyle ( el, "width" ) / rect) ;
Map. setStyle ( el, "height" , this . rows * rect) ;
Map. setStyle ( el, "width" , this . cells * rect) ;
}
static getStyle ( el, attr) {
return parseFloat ( getComputedStyle ( el) [ attr] )
}
static setStyle ( el, attr, val) {
el. style[ attr] = val + "px" ;
}
setData ( newData) {
this . data = this . data. concat ( newData) ;
}
clearData ( ) {
this . data. length = 0 ;
}
include ( { x, y } ) {
return this . data. some ( item => ( item. x == x && item. y == y) )
}
render ( ) {
this . el. innerHTML = this . data. map ( item => {
return `<span style="position:absolute; left: ${ item. x * this . rect} px; top: ${ item. y * this . rect} px;
width: ${ this . rect} px; height: ${ this . rect} px; background: ${ item. color} ;"></span>`
} ) . join ( "" ) ;
}
}
食物类
export default class Food {
constructor ( cells, rows, colors = [ "yellow" , "orange" , "blue" , "pink" ] ) {
this . cells = cells;
this . rows = rows;
this . data = null ;
this . colors = colors;
this . create ( ) ;
}
create ( ) {
let x = Math. floor ( Math. random ( ) * this . cells) ;
let y = Math. floor ( Math. random ( ) * this . rows) ;
let color = this . colors[ parseInt ( Math. random ( ) * this . colors. length) ] ;
this . data = { x, y, color } ;
}
}
蛇类
export default class Snake {
constructor ( ) {
this . data = [
{ x: 6 , y: 4 , color: "red" } ,
{ x: 6 , y: 3 , color: "blue" } ,
{ x: 6 , y: 2 , color: "blue" } ,
{ x: 6 , y: 1 , color: "blue" }
] ;
this . direction = "right" ;
this . lastData = { } ;
}
move ( ) {
let i = this . data. length - 1 ;
this . lastData = {
x: this . data[ i] . x,
y: this . data[ i] . y,
color: this . data[ i] . color
}
for ( i; i > 0 ; i-- ) {
this . data[ i] . x = this . data[ i - 1 ] . x;
this . data[ i] . y = this . data[ i - 1 ] . y;
}
switch ( this . direction) {
case "left" :
this . data[ 0 ] . x-- ;
break ;
case "right" :
this . data[ 0 ] . x++ ;
break ;
case "top" :
this . data[ 0 ] . y-- ;
break ;
case "bottom" :
this . data[ 0 ] . y++ ;
break ;
}
}
changeDir ( dir) {
if ( this . direction === "left" || this . direction === "right" ) {
if ( dir === "left" || dir === "right" ) {
return false ;
}
}
if ( this . direction === "top" || this . direction === "bottom" ) {
if ( dir === "top" || dir === "bottom" ) {
return false ;
}
}
this . direction = dir;
return true ;
}
eatFood ( ) {
this . data. push ( this . lastData)
}
}
Evnet事件池
export default class Event {
events = { } ;
on ( eventName, fn) {
if ( ! this . events[ eventName] ) {
this . events[ eventName] = [ ] ;
}
if ( ! this . events[ eventName] . includes ( fn) ) {
this . events[ eventName] . push ( fn) ;
}
}
off ( eventName, fn) {
if ( ! this . events[ eventName] ) {
return
}
this . events[ eventName] = this . events[ eventName] . filter ( item => item != fn) ;
}
dispatch ( eventName, ... arg) {
if ( ! this . events[ eventName] ) {
return ;
}
this . events[ eventName] . forEach ( item => {
item. call ( this , ... arg)
} ) ;
}
}
游戏类
import Map from "./map.js" ;
import Event from "./event.js" ;
import Snake from "./snake.js" ;
import Food from "./food.js" ;
export default class Game extends Event {
constructor ( el, rect) {
super ( ) ;
this . map = new Map ( el, rect) ;
this . food = new Food ( this . map. cells, this . map. rows) ;
this . snake = new Snake ( this . map) ;
this . map. setData ( this . snake. data) ;
this . createFood ( ) ;
this . render ( ) ;
this . timer = null ;
this . interval = 200 ;
this . keyDown = this . keyDown. bind ( this ) ;
this . grade = 0 ;
this . control ( ) ;
}
createFood ( ) {
this . food. create ( ) ;
if ( this . map. include ( this . food. data) ) {
this . createFood ( ) ;
}
this . map. setData ( this . food. data) ;
}
start ( ) {
this . move ( ) ;
}
stop ( ) {
clearInterval ( this . timer) ;
}
render ( ) {
this . map. clearData ( ) ;
this . map. setData ( this . snake. data) ;
this . map. setData ( this . food. data) ;
this . map. render ( ) ;
}
move ( ) {
this . timer = setInterval ( ( ) => {
this . snake. move ( ) ;
if ( this . isEat ( ) ) {
this . grade++ ;
this . snake. eatFood ( ) ;
this . createFood ( ) ;
this . changeGrade ( this . grade) ;
this . interval *= 0.9999 ;
this . stop ( ) ;
this . start ( ) ;
if ( this . grade >= 20 ) {
this . over ( 1 ) ;
}
}
if ( this . isOver ( ) ) {
this . over ( ) ;
return ;
}
this . render ( ) ;
} , this . interval) ;
}
isEat ( ) {
return ( this . snake. data[ 0 ] . x === this . food. data. x) && ( this . snake. data[ 0 ] . y === this . food. data. y) ;
}
changeGrade ( grade) {
this . dispatch ( "changegrade" , grade) ;
}
isOver ( ) {
if ( this . snake. data[ 0 ] . x < 0 || this . snake. data[ 0 ] . x >= this . map. cells || this . snake. data[ 0 ] . y < 0 || this . snake. data[ 0 ] . y >= this . map. rows) {
return true ;
}
for ( let i = 1 ; i < this . snake. data. length; i++ ) {
if ( this . snake. data[ 0 ] . x == this . snake. data[ i] . x && this . snake. data[ 0 ] . y == this . snake. data[ i] . y) {
return true ;
}
}
return false ;
}
over ( overState = 0 ) {
if ( overState) {
this . dispatch ( "toWin" )
} else {
this . dispatch ( "toOver" )
}
this . stop ( ) ;
}
keyDown ( { keyCode } ) {
switch ( keyCode) {
case 37 :
this . snake. changeDir ( "left" ) ;
break ;
case 38 :
this . snake. changeDir ( "top" ) ;
break ;
case 39 :
this . snake. changeDir ( "right" ) ;
break ;
case 40 :
this . snake. changeDir ( "bottom" ) ;
break ;
}
}
control ( ) {
if ( this . toControl) {
this . toControl ( ) ;
return ;
}
window. addEventListener ( "keydown" , this . keyDown) ;
}
addControl ( fn) {
fn. call ( this ) ;
window. removeEventListener ( "keydown" , this . keyDown) ;
}
}