Let's take a look at the final effect of the game at first:
The video of the game
Now let's talk about how to achieve it with Javascript.
The game is made using HT for Web, with about 100 lines of code.
Initialize the scene
First, let's do some initialization work, including initializing the 3D scene, setting the ground grid, and enabling event monitoring, etc. The main code and comments are as follows:
w = 40; // the grid gap
m = 20; // the count of rows/columns of grid
d = w * m / 2;
food = null;
dm = new ht.DataModel(); // create one data model, which saves all the grid, snake and food
g3d = new ht.graph3d.Graph3dView(dm); // initialize one 3d scene
// config the grid on ground
g3d.setGridVisible(true);
g3d.setGridColor('#29B098');
g3d.setGridSize(m);
g3d.setGridGap(w);
// add the 3d scene to body
view = g3d.getView();
view.className = 'main';
document.body.appendChild(view);
// monitor the resize event and mouse click event
window.addEventListener('resize', function (e) { g3d.invalidate(); }, false);
g3d.sm().setSelectionMode('none');
view.addEventListener(ht.Default.isTouchable ? 'touchstart' : 'mousedown', function(e){
if(isRunning){
var p = g3d.getHitPosition(e); // get the position in scene when mousedown
// calculate the move direction with the click position
if(Math.abs(p[0]) < d && Math.abs(p[2]) < d){
if(direction === 'up' || direction === 'down'){
direction = p[0] > snake[0].p3()[0] ? 'right' : 'left';
}
else if(direction === 'left' || direction === 'right'){
direction = p[2] > snake[0].p3()[2] ? 'down' : 'up';
}
}
}else if(ht.Default.isDoubleClick(e)){
start(); // double click the scene to start the game
}
}, false);
start();
// the snake move forward every 200ms
setInterval(function(){ if(isRunning){ isRunning = next(); } }, 200);
Create the food
Every time the gluttonous snake eats a piece of food, its body will grow longer. At this point, new food needs to be created and randomly and placed in a new location. When creating food, its position should not coincide with the previous position, nor repeated with the current snake body.
/**
* Create the food and place it in a random position
* The food should not dup with snake or last food
*
*/
function createFood(){
var x = getRandom(), y = getRandom();
// skip the snake body and last food
while(touchFood(x, y) || touchSnake(x, y)){ x = getRandom(); y = getRandom(); }
if(food) dm.remove(food);
food = createNode(x, y);
food.s({'shape3d': 'sphere', 'shape3d.color': 'red'});
}
/**
* whether the given position (x, y) is dup with the snake body
*
* @param {*} x
* @param {*} y
* @return {*}
*/
function touchSnake(x, y){
for(var i=0; i<snake.length; i++){
if(snake[i].a('x') === x && snake[i].a('y') === y){ return true; }
}
return false;
}
/**
* whether the given position (x, y) is dup with the food
*
* @param {*} x
* @param {*} y
* @return {*}
*/
function touchFood(x, y){
return food && food.a('x') === x && food.a('y') === y;
}
Create the snake
In the first step, we set the grid size and gap. In this way, the length and width of the entire grid and the size of each block of the snake are determined. In this step, we add boundaries to the mesh and then generate the snake.
/**
* clear the scene, create the snake and wall
*
*/
function start(){
dm.clear(); snake = []; score = 0; direction = 'up'; isRunning = true;
// create wall
shape = new ht.Shape();
shape.setPoints(new ht.List([
{x: -d, y: d},
{x: d, y: d},
{x: d, y: -d},
{x: -d, y: -d},
{x: -d, y: d}
]));
shape.setThickness(4);
shape.setTall(w);
shape.setElevation(w/2);
shape.s({'all.color': 'rgba(20, 120, 120, 0.5)', 'all.transparent': true, 'all.reverse.cull': true});
dm.add(shape);
// create the snake
for(var i=0; i<m/2; i++) { snake.push(createNode(m/2 + i, m/2)); }
createFood();
}
Make the snake move forward
After having the snake and the food, the next step is to deal with the logic of the snake walking. include:
- whether the snake has reached the border or touched its body
- when the snake touched the food, its body gets longer
- in other cases, move forward
/**
* calculate nest position based on direction. and check:
* 1. whether the snake has reached the border or touched its body
* 2. when the snake touched the food, its body gets longer
* 3. in other cases, move forward
*
* @return {*}
*/
function next(){
var node = snake[0], x = node.a('x'), y = node.a('y');
if(direction === 'up') y--;
if(direction === 'down') y++;
if(direction === 'left') x--;
if(direction === 'right') x++;
if(x < 0 || x >= m || y < 0 || y >= m || touchSnake(x, y)){ return false; }
if(touchFood(x, y)){
score++;
snake.splice(0, 0, createNode(x, y));
createFood();
}else{
snake.splice(0, 0, createNode(x, y));
dm.remove(snake.pop());
}
return true;
}
At this point, the whole snake game is complete. Quite simple, right? Double click on the scene to start the game or click on the scene to change the direction of the snake movement.
You can also directly click the link below to try the game:
3D Gluttonous Snake
To get the source code, just open the game link and press F12.
Top comments (1)
This is a nice take at the game.
Just today I published my take of the Worm/Snake game in 2D:
dev.to/htho/wibbles-web-nibbles-re...