хуйу нас не матерятся
В очередной раз проснулся во мне старый игрофил. И решил я запилить змейку на чистейшем Javascript'е. Времени это заняло не долго, примерно 2-2.5 часа, включая перекуры.
Вот сама игра на отдельное странице, а ниже немного подробностей как и что устроено.
В этот раз я решил не страдать хернёй с рендерингом в чистый html, а заюзал canvas. Весь канвас делится на кватраты, которые могут принимать несколько фиксированных значений: ничего, голова змейки, тушка змейки, еда.
const rectStyles = [
{
id: 0,
type: 'Background',
stroke: 'rgb(25, 25, 25)',
fill: 'rgb(15, 15, 15)'
},
{
id: 1,
type: 'Snake head',
stroke: 'rgb(250, 250, 250)',
fill: 'rgb(220, 220, 220)'
},
{
id: 2,
type: 'Snake body',
stroke: 'rgb(200, 200, 200)',
fill: 'rgb(180, 180, 180)'
},
{
id: 3,
type: 'Food',
stroke: 'rgb(250, 250, 0)',
fill: 'rgb(230, 230, 0)'
}
]
function createDisplay(width=15, height=15) {
for(let w=0; w<width; w++) {
for(let h=0; h<height; h++) {
if( !display[ h ] )
display[ h ] = [];
display[ h ][ w ] = {
h: h,
w: w,
id: 0,
type: 0
};
}
}
}
function clearDisplay() {
for(let h=0; h<display.length; h++)
for(let w=0; w<display[ 0 ].length; w++)
display[ h ][ w ].type = 0;
}
function render(element) {
let ctx = element.getContext('2d');
ctx.canvas.width = ctx.canvas.clientWidth;
ctx.canvas.height = ctx.canvas.clientHeight;
let rectWidth = parseInt(ctx.canvas.width / display[ 0 ].length - 1);
let rectHeight = parseInt(ctx.canvas.height / display.length - 1);
for(let h=0; h<display.length; h++)
for(let w=0; w<display[ h ].length; w++) {
let left = w * rectWidth + w;
let top = h * rectHeight + h;
ctx.strokeStyle = rectStyles[ display[ h ][ w ].type ].fill;
ctx.fillStyle = rectStyles[ display[ h ][ w ].type ].stroke;
ctx.fillRect(left, top, rectWidth, rectHeight);
ctx.strokeRect(left, top, rectWidth, rectHeight);
}
}
Интересно получилось с генератором еды. Так как диспей у нас- это двумерный массив, то мы не можем просто взять случайное число и по нему поместить еду на дисплей, клетки могут быть заняты.
По этому я сперва создаю одномерный массив со ссылками на свободные клетки дисплея, уже потом беру случайное число, узнаю координаты клетки, и создаю объект "еда" с новыми координатами.
function createFood() {
let emptyRects = [];
for(let h=0; h<display.length; h++)
for(let w=0; w<display[ 0 ].length; w++)
if( display[ h ][ w ].type == 0 )
emptyRects.push(display[ h ][ w ]);
let foodRect = emptyRects[ getRandomIntInclusive(0, emptyRects.length-1) ];
return {
x: foodRect.w,
y: foodRect.h,
type: 1
}
}
Перерисовка сцены и пересчёт змейки происходит по таймеру, и в таймере же задаётся скорость игры, которая возрастает каждые 25 ходов.
function tick() {
if( gameStatus !== 'game' ) return; // game paused
renderIndex ++;
if( renderIndex > speedUpLimit ) {
renderIndex =0;
gameSpeed ++;
clearInterval(gameTimer);
gameTimer = setInterval(tick, 1000 - (gameSpeed * 50));
}
clearDisplay();
renderFood();
renderSnake();
render(viewPort);
if( onTick ) onTick();
}
...
function startGame(onTickHandler) {
snake = createSnake();
food = createFood();
gameStatus = 'game';
onTick = onTickHandler;
gameTimer = setInterval(tick, 1000 - (gameSpeed * 50));
}
В остальном код очень простой. Полностью в функциональном стиле. Открывайте исходник страницы, смотрите что там и как.
Ну и конечно играйте с удовольствием!