Чтобы хоть как-то разбавить тенденцию к 30строчникам решил написать достаточно завершенную и, в сравнении с 30lines, объемную реализацию классической игры Asteroids.
screenshot
Я не буду меряться числом строк или символов кода, т.к. в нем есть и достаточно нормальное оформление и даже комментарии.
Мир игры отрисовывается на canvas, все объекты мира унифицированы, а детектор коллизий использует попиксельный тест. Есть простая озвучка, жизни, godmod на первые секунды после появления, очки, растущая вместе с очками сложность и, конечно, разваливающиеся на куски астероиды.
Попробовать можно тут. Очень советую Chrome или хотя бы FF.


Для уменьшения объема логики все объекты мира сделаны универсальными, с минимальным разделением в зависимости от конкретного типа. Геометрия объектов хранится в списках вершин, прямо как раньше делали через glBegin(GL_LINES). Для корабля отводится две геометрии (обычная и с пламенем при разгоне) ради все той же унификации логики. Если поломать код, то можно сделать астероиды или пули в форме корабля, ну и т.п. Все точки в геометрии задаются в координатах [-1..1], а конкретные размеры получаются масштабированием на уровне отрисовки в canvas. И для любого размера астероида можно применить любую геометрию (вариантов астероидов несколько).

Каждый объект обладает группой характеристик:
  • Тип объекта: корабль, астероид, пуля. НЛО пока нет;
  • Текущей геометрией;
  • Координатами центра геометрии в СК экрана;
  • Размер объекта, как множитель координат в геометрии;
  • Проекции вектора скорости на координатные оси;
  • Коэффициент торможения (>1 для корабля, что приводит к торможению после ускорения);
  • Угол поворота и «угловая скорость», применяемые для астероидов и пуль;
  • Время жизни объекта, если это время ограничено (применяется для пуль).

Унификация значительно упростила обработку особых ситуаций, вроде заворачивания объекта при его уходе за границы экрана.
Вся логика заворачивания для обеих о��ей
S — данные объекта, W — данные мира
[{x:'x', W:W.w}, {x:'y', W:W.h}].forEach(function(_) {
    var limit = (_.W>>1)+S.r, diff = _.W+2*S.r;
    if      (S[_.x] < -limit) {S[_.x] += diff}
    else if (S[_.x] >  limit) {S[_.x] -= diff}
});
Код можно еще сократить. Но имхо от станет менее понятным.

В одной из первых версий пробовал вариант с отображением объектов в две части, при частичном заходе за края (этакий Portal). Выглядело забавно, но мне показалось совсем не каноничным. Может стоит вернуть?

Дифференциация по типам выполняется лишь в логике обработки коллизий, чтобы понимать, что вообще с чем столкнулось. Сейчас учитываются столкновения астероидов в кораблем и пулями. Есть мысли о столкновении астероидов между собой, ну и, конечно, введение в игру НЛО.

imageПервоначальный вариант коллизий по оболочкам (bounding box через описанные окружности) показал несостоятельность, когда в случае, как на картинке справа, срабатывала коллизия. Это было недопустимо, так как лишала всего «драйва». Придумал два варианта решения: математический обсчет пересечения геометрии (любой объект представляется замкнутым набором отрезков) и попиксельное тестирование именно так, как отрисовывает движок браузера. Первый вариант, теоретически, менее ресурсоемок, но выбрал я второй.
Создается вторая canvas, на которую (только если тест оболочек подтвердил возможное пересечение) отрисовываются два проверяемых объекта, но в красно-зеленой цветовой гамме с активным блендингом на 50%. В итоге, в месте реального пересечения у нас появятся пиксели, имеющие и красную и зеленую компоненты. Очень просто, хоть и не эффективно.
Тест на второй canvas выглядит примерно так
image
Скриншот немного кривой из-за отсутствия синхронизации отрисовки со скриншотилкой


Ну и некоторое уже более-менее «мясцо» напоследок:
image
Видно, что астероиды выводятся wireframe. Опять же ради толики каноничности.