Статьи
Статьи

Как я решил вспоминать программирование

  • DEV notes

В эпоху вайбкодинга, я решил пойти против течения, и вспомнить как это программировать по настоящему. Речь идет о программировании на компилируемых языках программирования, потому что последние 20 лет я писал код исключительно для веб разработки на PHP и JS.

Почему я принял это решение? Главное, чтобы не забывать, кроме того, в мире, где код создается агентами, и все больше людей вообще не понимают, что происходит с их кодом в момент работы, становиться крайне важным понимать, как работает компьютер и для чего нужен код. По этому я решил начать, так сказать с азов, и повторить какую-нибудь классическую игру на языке СИ для моего macbook.

Вайбкодинг и программирование

Перед тем как начнем, я хочу напомнить, что агенты не создают новый код, они решают задачу по стандартным шаблонам, на которых они обучены. Фактически это выглядит примерно так: агент просто находит в некотором виртуальном репозитарии код, которые решает вашу задачу и адаптирует его под вашу реализацию. По этому крайне важно правильно выбирать стек технологий (и хорошо бы вообще знать, что это такое), потому что LLM учат на том, чего много, а много кода на JS, на пример, по этому агенты хорошо умеют в задачи фронтенда. Но вот мои эксперименты создать нормальный работающий продукт на Swift показали, что этот язык агент (в моем случае Claude Code) знает сильно хуже, чем JS или СИ.

С формальной точки зрения, агенты выступают в роли компилятора, когда давно, люди писали код на языках низкого уровня, таких как ассемблер или вообще в машинных кодах. 

WOZ MONITOR — APPLE I ROM   $FF00–$FFFF   (256 bytes, 1976)
FF00: D8 58 A0 7F 8C 12 D0 A9
FF08: A7 8D 11 D0 8C 13 D0 C9
FF10: DF F0 13 C9 9B F0 03 C8
FF18: 10 0F A9 DC 20 EF FF A9
FF20: 8D 20 EF FF A0 01 B9 00
FF28: 02 09 80 20 EF FF C9 8D
FF30: D0 D4 8A 48 A9 AC 8D 11
FF38: D0 A9 A7 8D 11 D0 99 00
FF40: 02 0A 85 2B C8 B9 00 02
FF48: C9 8D F0 D4 C9 A3 90 F4
FF50: F0 F0 C9 BA F0 EB C9 D2
FF58: F0 3B 86 28 86 29 84 2A
FF60: B9 00 02 49 B0 C9 0A 90
FF68: 06 69 88 C9 FA 90 11 0A
FF70: 0A 0A 0A A2 04 0A 26 28
FF78: 26 29 CA D0 F8 C8 D0 E0
FF80: C4 2A F0 97 24 2B 50 10
FF88: A5 28 81 26 E6 26 D0 B5
FF90: E6 27 4C 44 FF 6C 24 00
FF98: 30 2B A2 02 B5 27 95 25
FFA0: 95 23 CA D0 F7 D0 14 A9
FFA8: 8D 20 EF FF A5 25 20 DC
FFB0: FF A5 24 20 DC FF A9 BA
FFB8: 20 EF FF A0 00 B1 24 20
FFC0: DC FF A5 24 29 07 10 02
FFC8: A9 8D 20 EF FF E5 24 A5
FFD0: 24 C5 28 A5 25 E5 29 B0
FFD8: 2B E6 24 D0 CE E6 25 4C
FFE0: B4 FF 48 4A 4A 4A 4A 20
FFE8: E5 FF 68 29 0F 09 B0 C9
FFF0: BA 90 02 69 06 2C 12 D0
FFF8: 30 FB 8D 12 D0 60 00 FF
FFFA: 00 00
FFFC: 00 FF
FFFE: 00 00

Это между прочим, вам надо было набрать руками, что бы вы могли начать использовать самый первый компьютер Apple, надеюсь вы теперь понимаете, почему Стив Джобс был гением, и вовсе не потому что создал iPhone.

Такой способ взаимодействия с компьютером был крайне неудобым и появились языки программирования высокого уровня, такие как СИ, теперь чтобы написать программу, вместо 100 строчек кода, надо было писать почти человеческую строку: 

printf("Hello World!");

И это был прогресс. Мы вместо 100 строк, стали писать человеческим языком. А потом появились еще более продвинутые языки, типа Python, где одна строчка заменяет сотни строк на языке СИ.

Так, что программирование агентами это просто новый тип компилятора, который понимает несвязный человеческий текст и превращает его в код, причем работает он именно как компилятор, то есть он понятия не имеет, что он делает, вся ответственность на вас. 

Пока ни один агент не смог придумать вот такой код, а между прочим этот код был не менее революционным и перевернул весь мир игровой индустрии в 90-х годах.

float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;

x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

return y;
}

И так, чтобы уметь читать код, который создает мне агент, я решил потренироваться. 

Пишем pac-man

Когда я был подростком, я программировал классические игры, такие как змейка, арканойд, тетрис, но вот pac-man не довелось, а оказалось, что это весьма интересная играм, именно из-за игровых механик.

Во-первых, я не знал, что 4 привидения они разные по поведению, например, они по разному ведут себя относительно игрока, в разное время нападают и есть даже одно, которое боится игрока и убегает от него и погибнуть от него можно только если вы будет у него на пути, потому что привидения не умеют разворачиваться.

Кроме того в оригинальной игре очень интересно реализовали поведение атаки, есть противник, который атакует напрямую, есть который старается угадать куда вы пойдете и атакует это место. В общем весьма интересно.

Но механики описаны в разных статьях, и следовательно надо взять и сделать. И вот тут стало интересно, так как 20 лет назад, когда я еще писал код на СИ — я это делал на компьютерах с операционной системой Windows и местами даже DOS. И это было несколько просто для меня, я понимал железо и понимал операционную систему, и просто мог вывести графику, или текст.

Но сейчас у меня macbook и как в нем делать простой код я не знал, но современные агенты прививают пользователям работу в терминале, и соответственно напомнили мне, что терминал это почти как DOS. 

И я начал писать.

#include <ncurses.h>
#include <locale.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <math.h>
#include <string.h>

#define HEIGHT 36
#define WIDTH 28
#define SLEEP 60000
#define ENEMY_SPEED 2
#define ENEMY_PANIC 150
#define FRUIT_TIMER 150

Создаем файл pacman.c и начинаем. В целом самым сложным оказалось реализовать карту и алгоритм движения противника. С картой проблема в том, что оригинальная игра была графическая и карта была можно сказать квадратная, состояла она из 28 на 36 квадратиков 8 на 8 точек каждый. А в терминале все выводиться символами, каждый из которых занимает 8 на 16 точек, и если я беру 1 квадрат и заменяю его символом то карта будет прямоугольной и вытянутой по вертикали. А это не красиво.

По этому я пошел другим путем, каждый квадрат карты я заменил 3 символами, это позволяло мне использовать 1 символ на каждого персонажа и перемещать их по виртуальной карте 28 на 36 квадратиков. При этом карта не вытягивается по вертикали, но немного растягивается по ширине. 

Выглядит похоже, но далеко от оригинала. Можно было заменить символы квадратиков на специальные закрашенные символы, но карта становиться крайне широкой, обратите внимание, в оригинальной игре многие стены занимают 2 квадрата в ширину, а у меня это значит 6, и это не вариант. Нужно было искать другой путь. И он собственно лежал на поверхности, во-первых, рисовать каждый объект не как три рядом стоящих блока,  а как один блок окруженный пробелами. Стены становятся уже. но там где 2 блока рядом появляются дыры. И тогда я решил, а что если рисовать стены специальными символами, линиями, углами и тп, чтобы было похоже на оригинальную игру, но для этого надо было придумать алгоритм, как в начале статьи алгоритм для быстрого квадратного корня.

Алгоритм который будет понимать, что если у нас стена начинается то перед ней рисуем пробел, далее рисуем линии, там где поворот ставим правильный уголок. И вот этот алгоритм я думал пару дней, но не смог решить, попросил совета у Claude Code и получил его.

const char* get_wall_char(int y, int x) {
int idx = get_idx(y, x);

switch (idx) {
case 0: return " ";
case 1: return "─";
case 2: return "─";
case 3: return "─";
case 4: return "│";
case 5: return "╭";
case 6: return "╮";
case 7: {
int b = get_idx(y+1, x);
if (b == 11 || b == 15) return "─";
int ri = get_idx(y, x+1);
int li = get_idx(y, x-1);
if (li == 7 && ri == 7) return "─";
if (ri == 7) return "╮";
if (li == 7) return "╭";
return "┬";
}
case 8: return "│";
case 9: return "╰";
case 10: return "╯";
case 11: {
int a = get_idx(y-1, x);
if (a == 7 || a == 15) return "─";
int ri = get_idx(y, x+1);
int li = get_idx(y, x-1);
if (li == 11 && ri == 11) return "─";
if (ri == 11) return "╯";
if (li == 11) return "╰";
return "┴";
}
case 12: return "│";
case 13: {
int r = get_idx(y, x+1);
if (r == 14 || r == 15) return "│";
int bi = get_idx(y+1, x);
int ai = get_idx(y-1, x);
if (ai == 13 && bi == 13) return "│";
if (bi == 13) return "╰";
if (ai == 13) return "╭";
return "├";
}
case 14: {
int l = get_idx(y, x-1);
if (l == 13 || l == 15) return "│";
int bi = get_idx(y+1, x);
int ai = get_idx(y-1, x);
if (ai == 14 && bi == 14) return "│";
if (bi == 14) return "╯";
if (ai == 14) return "╮";
return "┤";
}
case 15: {
int ul = is_wall(y-1,x-1), ur = is_wall(y-1,x+1);
int dl = is_wall(y+1,x-1), dr = is_wall(y+1,x+1);
if (ul && ur && dl && dr) return " ";
if (ul && ur && !dl && dr) return "╮";
if (ul && ur && dl && !dr) return "╭";
if (dl && dr && !ul && ur) return "╯";
if (dl && dr && ul && !ur) return "╰";
return "─";
}
}
return " ";
}

И это позволило превратить игру уже в играбельный вариант.

Хорошо видно, что теперь игра похожа на ту, которая была в оригинале, при этом она полностью сделана в тексте и работает в терминале. Единственно, что у меня нет символов для pac-man и привидений, но это уже и не важно.

Я смог написать игру после того, как 20 лет не брал в руки СИ код. При этом было много разных проблем, ошибки компилирования, алгоритма, и тп. Например, была ошибка, окончания раунда. Как она работала, чтобы перейти в новый уровень надо собрать все точки на карте, в игре есть счетчик, который их считает. И вот если вы умирали (все три жизни закончились), и вы решили начать заново, то игра переходила на новый уровень, даже если вы собрали условно 5 точек. Проблема оказалась простой, я не сбросил счетчик точек в новой игре, и он помнил, сколько точек вы собрали до того как проиграли. 

Ну а закончить я хочу тем, что, вайбкодеры, изучайте программирование, это поможет вам экономить токены, и понимать, что вам пишет ваш агент, и не подложил ли он вам свинью, то есть жука, то есть bug. 

Сказать спасибо и оказать поддержку будущему контенту.

50/год

  • Доступ к закрытым материалам
  • Возможность комментирования материалов
  • Доступ к Реально Заданным Вопросам
  • Помочь мне донести идею Data Driven до большего числа людей

Для доступа к содержанию материалов и комментариям, оформите подписку.
Если вы уже клиент, то просто .

  

* – оплата через Boosty позволяет оплачивать картами Мир, оплата через Stripe для международных карт Visa, Mastercard и т.д.