Крошечная змейка на JavaScript (30 строк кода)

    Прочитав статью про excel в 30 строк, я загорелся глупой идеей — написать что-нибудь на 30 строк. Не долго думая остановился на змейке.

    Особенности:
    • 30 строк необычного JavaScript (задача была уместить в 30 строк, так что код похлеще чем на ночных хакатонах)
    • Использованные библиотеки: отсутствуют




    Вот фиддл.

    И сам код:

    Эстетам и перфекционистам лучше не смотреть
    (function(width, height, length, current, dx, dy, x, y, hasFood, newEl){     
        
    document.body.onkeydown = function(e){
        dx = (e.keyCode - 38) % 2, dy = (e.keyCode - 39) % 2;
    };
        
    var timer = setInterval(function () {
        x = (x + dx) < 0 ? width - 1 : (x + dx) % width; 
        y = (y + dy) < 0 ? height - 1 : (y + dy) % height;
        newEl = document.getElementsByClassName(y + '_' + x)[0]
        if(newEl.className.indexOf('s') > 0) {
        	clearInterval(timer), alert('Game Over! Score: ' + length)
        };
        if(newEl.className.indexOf('f') > 0) {
        	newEl.className = newEl.className.replace(' f', ''), length++, hasFood = false;
        }
        newEl.className += ' s', newEl.setAttribute('data-n', current++);
    
        for(var i = 0, min = Infinity, item, items = document.getElementsByClassName('s'), len = items.length; i < len && len > length; i++)
        	if(+items[i].getAttribute('data-n') < min)
        		min = +items[i].getAttribute('data-n'), item = items[i];
    
        if(!!item) item.className = item.className.replace(' s', '');
    
        for(var fItem, fX, fY; !hasFood; fX = Math.round(Math.random() * 10 % width), fY = Math.round(Math.random() * 10 % height))
        	if(!!fX && !!fY && document.getElementsByClassName(fY + '_' + fX)[0].className.indexOf('s') < 0)
        		hasFood = true, document.getElementsByClassName(fY + '_' + fX)[0].className += ' f';
    }, 1000);
    
    })(10, 10, 5, 1, 1, 0, 0, 0, false, null);
    



    Признаюсь, я немного сэкономил, опустив код, которым я сгенерировал разметку. Да и сам подход, весьма и весьма плох, но все-таки в 30 строк я уложился.
    Пару слов про «алгоритм» (понимаю, что читать этот «код» в Воскресение никому не захочется):
    • решил сэкономить на том, чтобы не делать массив и не мапить его на UI, а сделать «Dom addictive» алгоритм, который работает сразу с DOM'ом (клеточки змейки помечаются классом «s» а еда классом «f», по наличию этих классов и определяется коллизия со змеей или с едой).
    • каждая новая клетка змеи помечается атрибутом «data-n» который равен number++ (постоянно увеличивается)
    • ищем div'ы с классом «s» (клетки змеи) и находим клетку с минимальным значением data-n и делаем ее обычной (удаляем/перемещаем хвост змеи)


    Не судите код строго и хороших Вам выходных!
    Поделиться публикацией
    Комментарии 47
      +2
      Впечатляет!
      Можно узнать, примерна за какой срок написали? Интересует примерная оценка чистого времени и протяженность, в течении какого срока садились и писали? :)
        +20
        Часа два, наверное (с минимальными перерывами на ужин и чай)
          +36
          Мужык
        +1
        Отличная работа! Но есть замечание: в привычной змейке если одновременно нажать Вниз-Вправо-Вверх то змейка выполнит эти команды по очереди. А у вас она выполняет последнюю команду. И ещё, например, если змейка движется Вправо, а вы нажмёте Влево, то игра заканчивается. А так всё великолепно.
          +8
          Не могу понять: если нажать «одновременно» — откуда порядок браться будет?
            0
            Из логики движения змейки. Она не может поворачивать назад и вперед, только влево и вправо.
              0
              Все равно не сходится. Например, если змейка движется влево, то одновременное нажатие клавиш остальных направлений может трактоваться как вверх-вправо-вниз, или как вниз-вправо-вверх.
                +1
                Dinir102, наверное, имел ввиду «одновременно для змейки, но не одновременно в жизни», т.е. между тактами змейки человек может успеть натыркать кнопки управления, порядок которых запоминается и исполняется в следующих шагах.
          +12
          }, 1000);
          Что-то детская у вас змейка. Даешь 100!
            0
            У меня на 200 уже начинаются проблемы с управлением. Не могу ровно ввернуть)
            +6
            Скорость бы еще при увеличении длины увеличивать:)
              +4
              Отлично. Спасибо за то, что дважды напомнили молодость — во-первых, змея была первой компьютерной игрой в моей жизни (1987 год, машина Robotron 1715), и во-вторых, в 1989 году я написал на Турбо-Паскале собственный вариант, который занимал в памяти 5.5 килобайта. Я тогда тоже решил сэкономить на том, чтобы не делать массив, и хранил состояние змеи и еды прямо в видеопамяти, в той части, которая не отображалась на экран.
              P. S. Скажите, а в современном программировании использование лямбда-выражений такого размера — это тренд, или таки грязный хак, чтобы уложиться в 30 строк?
                0
                Кхм… ну раз уж лямбда выражения это грязный хак, тогда уж использование классов или там транслируемого языка программирования вообще пошлость…
                Даешь змейку на асме за 30 операторов :)
                  0
                  4 года назад писал змейку на асме, получилось хоть и не 30 операторов, а 79, но без ужималок в одну строку нескольких строк
                    0
                    Респект. А под какой камень писали ежель не секрет?
                      +1
                      для x86 16-битный проц. А вот и код
                      Скрытый текст
                      .model small
                      .data
                         snake dw ?,3,offset snake+18,-160,40,0,2000, 2160, 2320, 2480, 100 dup(0),0, -160,0,0,-2,0,2,0,0,160,25173,80*24*2
                      .stack 10h
                      .code      
                      draw_elem:  mov cx, 4
                                  mov bp, 2403h
                      while:      mov ax, snake
                                  imul snake[240]
                                  add ax, 13849
                                  mov snake, ax
                                  xor dx, dx
                                  div snake[242]
                                  mov si, dx
                                  cmp es:[si], word ptr 2500h
                                  jne while
                                  mov es:[si], bp
                                  mov bp, 200fh
                                  loop while
                                  ret
                      update:     mov si, snake[4]
                                  mov di, [si]
                                  mov es:[di], 2500h
                                  mov cx, snake[2]
                      for1:       mov ax, [si-2]
                                  mov [si], ax
                                  sub si, 2
                                  loop for1
                                  mov di, [si]
                                  add di, snake[6]
                                  mov [si], di
                                  cmp es:[di], 2403h
                                  jne endif3
                                  mov si, snake[4]
                                  mov bx, [si]
                                  mov [si+2], bx
                                  add snake[4], 2
                                  inc snake[2]
                                  call draw_elem
                      endif3:     cmp es:[di], byte ptr 0eh
                                  ja game_over
                                  cmp di, 0
                                  jl game_over
                                  cmp di, 80*25*2
                                  jg game_over
                                  mov es:[di], word ptr 06dbh
                                  ret
                      main:       cld
                                  mov ax, @data
                                  mov ds, ax
                                  mov ax, 3
                                  int 10h
                                  int 1ah
                                  mov snake, dx
                                  mov ax, 0b800h
                                  mov es, ax
                                  mov ax, 2500h
                                  xor di, di
                                  mov cx, 80*25
                                  rep stosw
                                  call draw_elem
                      begin:      xor ah, ah
                                  int 1ah
                                  cmp dx, snake[220]
                                  jb endif1
                                  add dx, 2
                                  mov snake[220], dx
                                  call update
                      endif1:     mov ah, 6
                                  mov dl, 0ffh
                                  int 21h
                                  mov bl, al
                                  cmp bl, 48h
                                  jb endif2
                                  cmp bl, 50h
                                  ja endif2
                                  xor bh, bh
                                  shl bx, 1
                                  mov ax, [bx+offset snake-2*48h+222]
                                  mov snake[6], ax
                      endif2:     cmp al, 1bh
                                  jne begin
                      game_over:  mov ax, 4c00h
                                  int 21h
                                  end main
                      

                +24
                Стоит упомянуть www.140byt.es — коллекцию скриптов умещающихся в 1 сообщение Twiter'a. Там и змейка есть.
                  0
                  Чёрт, он по-моему умер
                  +1
                  Чую на Хабре серию постов с очень крутыми решениями на JS в 30 строк.
                  Вот как так-то?
                    +2
                    Да, но только это минифицированный вариант. Оригинальный близок к 30 строкам. Правда у DjComandos проигрыш с минификацией, из-за работы напрямую с DOM.
                      0
                      А я почти написал уже, но JS fiddle начал писать мне ERROR 500 и потому думается Хабр не увидит мои вариант змейки ))
                      +1
                      Боковые стенки проходит «на сквозь» и появляется с другой стороны и игра не прекращается. Так и должно быть?
                        +3
                        Так и должно быть.
                        Вы разве не играли в «Snake» на старых ч/б-телефонах?
                          +6
                          Вы разве не играли в «Snake» на старых ч/б-телефонах?

                          Нет, не довелось, к сожалению. Все время меня эти стенки предательски подводили. Теперь я смогу им отомстить :).
                            +1
                            Аккуратнее со стенами, все-таки.
                            От виртуальности до реальности один шаг, особенно к концу рабочего дня.
                        0
                        DjComandos, нашел интересную штуку, попробуйте направить змейку в противоположную сторону от движения :)
                          +1
                          Насколько я помню, в стандартной змейке было так же и если подумать, то это правильно.
                          0
                          Проход через стены есть, а вот если нажать кнопочку в противоположную сторону, чтобы поползти обратно, то будет «маленькая неприятность».
                          чОрт, опередили, пока играл )
                            0
                            Ждем комментарий про астрологов и JS.
                            По сабжу — это прекрасно.
                              +3
                              Имхо 30 строк натянуто. Вы переусердствовали оператором запятая.
                                +1
                                Ждем Пэкмена и спейс-инвайдерс в 30 строк.
                                  +4
                                  Мой вариант с автоматической генерацией поля:

                                  var h_pos=f_pos=score=0, len=5, move=1, snake=[0], dx=15, dy=10, dxy=150;
                                  
                                  function _$ (body) { return eval("(function (a, b) { return "+body+"; })"); }
                                  elem = _$("document.getElementById (a)");
                                  set_class = _$("elem('cell_'+a).className = 'cell'+ (b ? ' '+b : '')");
                                  is_snake = _$("elem('cell_'+a).className.indexOf(' s') >= 0");
                                  new_food = _$("is_snake (f_pos = Math.floor(Math.random() * a) + b) \
                                                          ? new_food(a,b) : f_pos");
                                  new_cell = _$("'<div id=cell_'+a+' class=\"cell'+(a ? '': ' s')+'\"></div>'");
                                  new_line = _$("a ? '<div class=line></div>' : ''");
                                  line_change = _$("dx * (parseInt ((a + b%dx)/dx) - parseInt (a/dx))");
                                  
                                  for (var i=0; i<dxy; ++i) 
                                      elem ("main").innerHTML += new_cell (i) +new_line(i%dx == dx-1);
                                  
                                  document.body.onkeydown = function(e) { 
                                      move = ([-1, -dx, 1, dx])[e.keyCode-37]; 
                                  };
                                  
                                  set_class (new_food(dxy-2, 1), 'f');
                                  var interval = setInterval (function () {
                                      h_pos = (h_pos +dxy + move - line_change (h_pos, move)) %dxy;
                                      if ( is_snake(h_pos) && !clearInterval(interval) )  
                                          alert('Game Over! Your score: ' + score);
                                      set_class (h_pos, 's');
                                      f_pos == h_pos && new_food(dxy-1, 0)>=0 
                                                     && ++len && ++score && set_class (f_pos, 'f');
                                      snake.push (h_pos);
                                      snake.length>=len && set_class (snake.shift());
                                  }, 300);
                                  
                                    0
                                    Что-то не сохранился нормально предыдущий вариант :(
                                    Теперь, вроде, все ок и работает.
                                    +17
                                    Тут не 30 строк, а примерно 60.
                                    С таким же успехом можно было и в одну строку сжать.
                                    Зачем этот маленький обман?
                                      +1
                                      Зато у меня честные 30 ^_^
                                      Даже в 80 символов по ширине умещаются))
                                      +4
                                      Я 10 лет назад о_О написал на VB6 подобное, уложившись в 838 байт кода (15 строк):

                                      Private Type xy:x As Long:y As Long:End Type
                                      Dim Z(99)As xy,k,L,r,p As xy
                                      Sub Form_KeyDown(c As Integer,s As Integer):k=c:End Sub
                                      Sub Form_Unload(c As Integer):End:End Sub
                                      Sub Form_Load():m=10:v=5:ScaleMode=3:AutoRedraw=-1:Show
                                      Line(v,v)-(m*v,m*v),,B:DrawWidth=v:Picture=Image
                                      0:c=0:L=1:Do:t=Timer:Do While Timer-t<1/15:DoEvents:Loop
                                      r=r-1/20:If r<0 Then r=Rnd*9:p.x=Rnd*m-1:p.y=Rnd*m-1:q=q+1:q=q Mod 3
                                      h=Mid(652800025565535,q*5+1,5)
                                      With Z(0):Select Case k:Case 37:.x=.x-1:Case 38:.y=.y-1:Case 39:.x=.x+1:Case 40:.y=.y+1
                                      End Select
                                      If p.x=.x And p.y=.y Then:L=L-(q=0):L=L+(q=1):c=c-(q=2):p.x=-9
                                      If L<=0 Or .x<0 Or .x>m-1 Or .y<0 Or .y>m-1 Then MsgBox c:.x=0:.y=0:k=0:GoTo 0
                                      Caption=«L=»&L &" P="& c:Cls:End With:PSet(p.x*v+v,p.y*v+v),h
                                      For i=0 To L-1:Z(L-i)=Z(L-i-1):PSet(Z(L-i).x*v+v,Z(L-i).y*v+v),vbBlue:Next:Loop:End Sub

                                      Ссылка на топик: forum.sources.ru/index.php?showtopic=15756&st=0&#entry131215
                                        0
                                        У меня наверное самый пошлый код из всех представленных вышел, особо ужимать не пытался, но вышло до 60 строк ))
                                        html
                                        <html>
                                            <head>
                                                <meta charset="utf-8">
                                                <title>SNAKE</title>
                                                <script src="snake.js"></script>
                                            </head>
                                            <body onload='main()'>
                                                <canvas id="snake">mf</canvas>
                                            </body>
                                        </html>
                                        

                                        javascript
                                        function update (direction, snake, apple) {
                                        	var eaten = false,
                                                gameover = false,
                                                prev = snake[0],
                                                current  = {'x': snake[0].x + direction.x, 'y': snake[0].y + direction.y};
                                        	if (current.x === 15) current.x =  0;
                                            if (current.x === -1) current.x = 14;
                                        	if (current.y === 10) current.y =  0;
                                        	if (current.y === -1) current.y =  9;
                                        	snake[0] = current;
                                        	if (snake[0].x === apple.x && snake[0].y === apple.y) eaten = true;
                                        	for (var i = 1, len = snake.length; i < len; i++)
                                        		if (snake[0].x === snake[i].x && snake[0].y === snake[i].y) gameover = true;
                                        	for (var i = 1, len = snake.length; i < len; i++) {
                                        		current	= snake[i];
                                        		snake[i] = prev;
                                        		prev = current; }
                                        	if (eaten) {
                                        		snake.push(current);
                                        		apple.x = Math.random() * 15 | 0;
                                        		apple.y = Math.random() * 9 | 0; }
                                        	if (gameover) {
                                        		alert('GAME OVER!');
                                        		location.reload(); }
                                        };
                                        function draw (context, snake, apple) {
                                        	for (var i = 0, len = snake.length; i < len; i++) {
                                        		context.fillStyle = (i === 0) ? '#000' : '#666';
                                                context.fillRect(snake[i].x * 32, snake[i].y * 32, 32, 32); }
                                        	context.fillStyle = '#933';
                                        	context.fillRect(apple.x * 32, apple.y * 32, 32, 32);
                                        };
                                        function main () {
                                            var canvas = document.getElementById('snake'),
                                                context = canvas.getContext('2d'),
                                        		snake = [{'x': 5, 'y': 3}, {'x': 4, 'y': 3}, {'x': 3, 'y': 3}],
                                        		direction = {'x': 1, 'y': 0},
                                                apple = {'x': Math.random() * 15 | 0, 'y': Math.random() * 9 | 0};
                                            canvas.width  = 480; canvas.height = 320;
                                        	alert('GET READY!');
                                        	setInterval(function () {
                                        		context.clearRect(0, 0, canvas.width, canvas.height);
                                                update(direction, snake, apple);
                                                draw(context, snake, apple);
                                        		}, 1000 / 4);
                                        	document.onkeydown = function (event) {
                                                if (event.keyCode === 37)
                                        		if (direction.x !==  1) direction = {'x': -1, 'y': 0};
                                                if (event.keyCode === 38)
                                        		if (direction.y !==  1) direction = {'x': 0, 'y': -1};
                                                if (event.keyCode === 39)
                                        		if (direction.x !== -1) direction = {'x': 1, 'y': 0};
                                                if (event.keyCode === 40)
                                        		if (direction.y !== -1) direction = {'x': 0, 'y': 1};
                                        	};
                                        }
                                        main();

                                        Код тоже уложился в пару часов, потому порнография страшная и глюканов много ))
                                        Фидл
                                        image
                                          0
                                          Всякими читерскими ужимками довёл до 45 строк, но удовольствия никакого от этого самообмана)
                                            0
                                            Ох хабр, хабр и почему же у тебя нельзя редактировать комменты чуть больше чем две минутки ))
                                            Конечно ссылка на фидл правильней такая
                                              0
                                              Странно, у меня красный квадратик прям на змейке появлялся. Так и должно быть?
                                                0
                                                Ничего странного, рабочий день заканчивался и я не написал чтоб генерируя координаты для яблока исключались ячейки со змейкой ( Завтра поправлю
                                            0
                                            Кстати, я в своем варианте отказался от двумерного позиционирования (x:y) — и получилось заметно короче, потому что работаю только с одной координатой практически.
                                              0
                                              Да для экономии места самое оптимальное юзать координаты вида xy думается мне, хотя я может и не прав. Мой вариант кода нервно курит возле вашего, я уж молчу про длины строк и злоупотребление запятыми
                                            +10
                                            Планы на жизнь:

                                            ✗ ... 
                                            ✔ пройти змейку до конца;
                                            ✗ ... 
                                            

                                              0
                                              Неделя проектов на Хабре — JavaScript (30 строк кода)
                                                0
                                                Самая засада, когда надо, чтобы это были честные 30 строк, а не
                                                a(); b(); c(); d(); if(){}; for(); function() {};

                                                ;)
                                                  0

                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                  Самое читаемое