Neitralov Neitralov

Привет, я Максим Осин

Backend • Frontend • DevOps

Россия, г. Казань

Нашел свой код 10-летней давности

12 июня 2026 г.


Предисловие

“Любой школьник, увидев компьютер, мечтает начать делать игры.”

(Александр Ермолаев, Исполнительный директор Red Spell)

И я не стал исключением. Мне было 13 лет и я очень любил компьютерные игры. Правда, в тот жизненный период, когда я захотел начать программировать, у меня не было компьютера. Зато был планшет HP Slate 7, на тогда еще свежем Android 4.1.

Сначала я начал что-то читать по этой теме, изучать гайды на YouTube. Очень быстро понял, что если смогу найти какой-нибудь компилятор кода под Android, то я могу начать писать простенькие алгоритмы, изучать синтаксис какого-нибудь языка программирования и т.д.

Компилятор под Android, без требования постоянного доступа к интернету, в те мохнатые годы я не нашел, но зато наткнулся на приложение Sololearn.

Sololearn представлял собой приложение, которое позволяло изучать различные языки программирования. Кусочки теоретического материала совмещали с небольшими практическими заданиями. Также присутствовал “режим песочницы”, где просто можно пописать код вне контекста заданий.

Благодаря Sololearn мне удалось попробовать много различных языков: PHP, C++, Python, C#, JavaScript, Ruby… Я тогда еще ничего не смыслил в том, что языки программирования различаются по множеству критериев, поэтому выбирал те, у которых мне больше нравился синтаксис.

По итогу остановился на JavaScript. На то было 2 причины:

  1. Мне, в целом, понравились языки с си-подобным синтаксисом (зачатки ненависти к Python начали формироваться еще тогда).
  2. JavaScript версия приложения Sololearn (раньше у них было по приложению на каждый язык) была единственной, у которой режим песочницы работал офлайн.

А чуть позже я выяснил, что достаточно в текстовом файле оформить основную структуру HTML документа, где в тегах <script></script> можно писать на JS все что угодно. Останется только сменить расширение файла на .html и его можно будет открыть в любом браузере.

Именно так началась моя эра программирования на JavaScript с планшета. Я даже не разбирался в HTML или CSS. Весь мой ввод-вывод держался на трех всадниках апокалипсиса функциях: alert, prompt, confirm.

Я начал делать кликеры на JavaScript

”Открытие” игрового жанра

Пока баловался с JavaScript, очень быстро нащупал незатейливую игровую механику. В бесконечном цикле можно спрашивать у пользователя подтверждение следующего хода через confirm и начислять ему за это очки.

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

Код лучшей своей игры я отправил одному своему другу, который держал в ВК паблик для программистов. Он опубликовал ее код. Только благодаря этому эта реликвия дожила до наших дней.

Revolution Clicker Game

Разумеется ничего революционного в игре не было (но хайпа нагнать же надо?). Игрушка крайне простая. Игрока приветствует alert с описанием игры, а после prompt любезно просит ввести никнейм. Затем начинается игровой цикл. Каждый ход игрок видит свое здоровье, опыт, деньги и информацию по врагу.

Game UI

Интерфейс игры

Если нажать OK, то пользователь совершит удар. В результате игрок с врагом обменяются ударами. Если нажать CANCEL, то можно войти в магазин, в котором можно приобретать улучшения урона, брони, здоровья, а также пропускать ходы, чтобы регенерировать по 1 HP (что полностью делит на ноль необходимость качать здоровье и покупать аптечки, но про геймдизайн мы еще поговорим).

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

Оригинальный код игры содержит всего ~220 строк, так что приложу его прямо в эту статью.

Код игры

Пускай мне и было 13 лет. Я программировал как мог (с планшета!), но сейчас начинается самое “вкусное”. Ради чего эта статья вообще и затевалась.

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

Однако перегибать не стану, так что обойдемся без того, что “а вот, если бы ООП” или “надо переписать на консольное приложение при помощи NodeJS/Bun”. Помним, да? Это фрагмент содержимого из тегов <script> в пустом HTML файле.

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

var startHealth = 4
var enemyHealth = 4
var myHealth = 25
var overHealth = 25
var money = 0
var kill = 0
var att = 1
var enemyAtt = 1
var enemAttM = 0
var enemyLvl = 1
var names = ""
var enemyName = "Троль"
var dropMoney = 2
var buyMoney1 = 19
var buyMoney2 = 20
var buyMoney3 = 19
var buyMoney4 = 20
var buyMoney5 = 9
var buyMoney6 = 10
var buyMoney7 = 14
var buyMoney8 = 15
var armor = 0
var score = 0  

function BlockateOverHealth() {
	if (myHealth > overHealth) {
		myHealth = overHealth
		alert("Достигнуто максимальное здоровье")
	} else { }
}

function EnemyCheckNames() {
	if (enemyName == "Троль") {
		if (enemyLvl == 15) {
			enemyName = "Плоть"
			enemyLvl = 0
			enemyHealth = enemyHealth + 3
			dropMoney = dropMoney + 0.5
			enemyAtt = ++enemyAtt
		} else { }
	} else if (enemyName == "Плоть") {
		if (enemyLvl == 15) {
			enemyName = "Мимик"
			enemyLvl = 0;
			enemyHealth = enemyHealth + 3
			dropMoney = dropMoney + 0.5
			enemyAtt = ++enemyAtt
		} else { }
	} else if (enemyName == "Мимик") {
		if (enemyLvl == 15) {
			enemyName = "Рыцарь"
			enemyLvl = 0
			enemyHealth = enemyHealth + 3
			dropMoney = dropMoney + 0.5
			enemyAtt = ++enemyAtt
		}
	} else if (enemyName == "Рыцарь") {
		if (enemyLvl == 15) {
			enemyName = "Дракон"
			enemyLvl = 0
			enemyHealth = enemyHealth + 300
			dropMoney = dropMoney + 1
			enemyAtt = ++enemyAtt
			enemyAtt = ++enemyAtt
			alert("БОСС");
		}
	} else if (enemyName == "Дракон") {
		if (enemyLvl == 2) {
			enemyName = "Троль"
			enemyLvl = 0
			enemyHealth = enemyHealth - 299
			dropMoney = dropMoney + 0.5
			enemyAtt = ++enemyAtt
		}
	} else { }
}

function MainMenu() {
	alert("Приветствую тебя в игре Revolution Clicker Game\nАвтор : Максим Осин ")
	var name = prompt("Введи свое имя [длиннее 4 символов не рекомендуется] ", "")

	if (name == null || name == "" || name == " ") {
		alert("Вы обязаны выбрать имя")
		MainMenu()
	} else {
		names = name
		ClickerGameStart()
	}
}

function MonsterHealthChecked() {
	if (enemyHealth < 1) {
		alert("Противник убит")
		EnemyCheckNames()
		enemyHealth = startHealth
		startHealth = ++startHealth
		enemyHealth = ++enemyHealth
		kill = ++kill
		enemyLvl = ++enemyLvl

	} else { }
}

function ClickerGameStart() {
	var player = confirm("1| Враг: " + enemyName + " [" + "Hp " + enemyHealth + "]" + "|[LvL " + enemyLvl + "]" + "\n2| " + names + " :[" + "Kill " + kill + "]|[$ " + money + "]" + "|[Hp " + myHealth + "] " + "\n3| Нанести удар [ok]" + " " + "\n4| Открыть магазин [cancel]")

	if (myHealth < 0.01) {
		alert("Ты умер ")
		score = score * 0.2
		alert("Ваши очки " + score)
	} else if (player == true) {
		enemAttM = enemyAtt - armor
		enemyHealth = enemyHealth - att
		myHealth = myHealth - enemyAtt + armor
		money = money + dropMoney
		score = score + 2
		alert("Вы ударили врага [-" + att + " HP]" + " " + " Вас ударил враг [-" + enemAttM + " HP]");
		MonsterHealthChecked()
		ClickerGameStart()
	} else if (player == false) {
		var jojo = prompt("Выберите соотвецтвующию цифру для улучшения или для выхода из игры\n(*1* улучшить урон)\n(*2* улучшить защиту)\n(*3* выйти из игры)\n(*4* купить аптечку)\n(*5* улучшить максимальное здоровье)\n(*cancel* регенерация здоровья )", "")

		if (jojo == 1) {
			var jojo2 = confirm("Купить улучшение ?" + "\nСтоимость " + buyMoney2 + "$")
			
			if (jojo2 == true) {
				if (money > buyMoney1) {
					money = money - buyMoney2
					att = ++att
					alert("Урон увеличен " + "" + " -" + buyMoney2 + " $")
					buyMoney1 = buyMoney1 + 5
					buyMoney2 = buyMoney2 + 5
					ClickerGameStart()
					
				} else if (money < buyMoney2) {
					alert("Нехватает денег")
					ClickerGameStart()
					
				} else { alert("g") }
			} else if (jojo2 == false) {
				alert("Выход из магазина")
				ClickerGameStart()
				
			} else { alert("g") }
		} else if (jojo == 3) {
			alert("Выход из игры")
			score = score * 1.5
			alert("Ваши очки " + score)
			
		} else if (jojo == 2) {
			var jojo2 = confirm("Купить улучшение ?" + "\nСтоимость " + buyMoney4 + "$")
			
			if (jojo2 == true) {
				if (money > buyMoney3) {
					money = money - buyMoney4
					armor = armor + 0.25
					alert("Защита увеличина " + "" + " -" + buyMoney4 + " $")
					buyMoney3 = buyMoney3 + 25
					buyMoney4 = buyMoney4 + 25
					ClickerGameStart()
					
				} else if (money < buyMoney4) {
					alert("Нехватает денег")
					ClickerGameStart()
					
				} else { alert("g") }
			}
		} else if (jojo == 4) {
			var jojo2 = confirm("Купить аптечку ?" + "\nСтоимость " + buyMoney6 + "$")
			
			if (jojo2 == true) {
				if (money > buyMoney5) {
					money = money - buyMoney6
					myHealth = myHealth + 999
					alert("Аптечка куплена " + "" + " -" + buyMoney6 + " $")
					BlockateOverHealth()
					ClickerGameStart()
					
				} else if (money < buyMoney6) {
					alert("Нехватает денег")
					ClickerGameStart()
					
				} else { alert("g") }
			} else if (jojo2 == false) {
				alert("Выход из магазина")
				ClickerGameStart()
				
			} else { alert("g") }
		} else if (jojo == 5) {
			var jojo2 = confirm("Улучшить максимальное здоровье ?" + "\nСтоимость " + buyMoney8 + "$")

			if (jojo2 == true) {
				if (money > buyMoney7) {
					money = money - buyMoney8
					overHealth = overHealth + 5
					alert("Максимальное здоровье увеличено " + "" + " -" + buyMoney8 + " $")
					buyMoney7 = buyMoney7 + 15
					buyMoney8 = buyMoney8 + 15
					ClickerGameStart()

				} else if (money < buyMoney8) {
					alert("Нехватает денег")
					ClickerGameStart()
					
				} else { alert("g") }
			} else if (jojo2 == false) {
				alert("Выход из магазина");
				ClickerGameStart()

			} else { alert("g") }
		} else if (jojo == null) {
			alert("Ход пропущен [+ 1 HP]");
			myHealth = ++myHealth
			BlockateOverHealth()
			ClickerGameStart()

		} else {
			ClickerGameStart()
		}
	} else { alert("g") }
}

MainMenu()

Проблема №1 “Глобальные переменные”

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

Проблема №2 “Некорректная реализация паттерна Game Loop”

Можно наблюдать, что разные ветки условия в функции ClickerGameStart ведут к ее рекурсивному вызову, чтобы игра могла продолжаться после хода.

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

// НЕправильно
function gameLoop() {
	if (health <= 0) {
		// ...заканчиваем игру
	}

	var input = // Получение ввода от пользователя
	
	if (input == "Выполнить удар") {
		// ...выполняем удар
		gameLoop()
	} else if (input == "Зайти в магазин") {
		// ...заходим в магазин улучшений
		gameLoop()
	}
}

// Уже лучше
function gameLoop() {
	do {
		var input = // Получение ввода от пользователя
	
		if (input == "Выполнить удар") {
			// ...выполняем удар
		} else if (input == "Зайти в магазин") {
			// ...заходим в магазин улучшений
		}
	} while (health > 0)

	// ...заканчиваем игру
}

Проблема №3 “Проверка здоровья после получения ввода”

Я серьезно проверял здоровье после того, как давал игроку сделать ход???

Наверное, было очень приятно удивиться, когда персонаж не погибал после смертельного удара. Потом, конечно, приходилось сильно расстроиться, ведь игра все равно заканчивалась после хода, но все же…

Проблема №4 “Пустые блоки else”

Они буквально не нужны, но я зачем-то их писал. Некоторые из таких блоков содержат console.log("g"), но это какие-то архаичные способы дебага.

Проблема №5 “Страдающий нейминг переменных/функций”

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

Таким образом случился внезапный “JoJo референс” в недрах ClickerGameStart. Хотя я тогда даже не знал про существование этого аниме.

Проблема №6 “Счетчик убитых врагов ни на что не влияет”

Переменная kill не участвует в подсчете очков, которые будут отображены при смерти главного героя.

Проблема №7 “Дублирование ценника на товар в магазине”

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

Надо ли говорить, что при сравнении цен достаточно было использовать >=, а не >?

Поэтому кол-во переменных можно (нужно) сократить. При желании им даже можно было дать нормальные названия:

  1. buyMoney1 и buyMoney2 -> damageUpgradeCost.
  2. buyMoney3 и buyMoney4 -> armorUpgradeCost.
  3. buyMoney5 и buyMoney6 -> medkitBuyCost.
  4. buyMoney7 и buyMoney8 -> maxHealthUpgradeCost.

Проблема №8 “Инвертирование If вышло из чата”

Нередки в коде куски, когда можно сократить вложенность условий элементарнейшим трюком.

// Было
function CheckHeroHealth() {
	if (heroHealth > maxHeroHealth) {
		// Какая-то логика...
	}
}

// Стало
function CheckHeroHealth() {
	if (heroHealth <= maxHeroHealth)
		return
		
	// Какая-то логика...
}

Проблема №9 “Второй и третий пункт выбора в магазине улучшений перепутаны местами в коде”

Какая-то мистика 🤔

Проблема №10 “Не используется интерполяция строк”

Читать бесконечные сшивания переменных с текстом через ”+” очень тяжело.

// Было
var actionType = confirm("1| Враг: " + enemyName + " [" + "Hp " + enemyHealth + "]" + "|[LvL " + enemyLvl + "]" + "\n2| " + heroName + " :[$ " + money + "]" + "|[Hp " + heroHealth + "] " + "\n3| Нанести удар [ok]" + " " + "\n4| Открыть магазин [cancel]")

// Стало
var actionType = confirm(`1| Враг: ${enemyName} [Hp ${enemyHealth}]|[LvL ${enemyLvl}]\n2| ${heroName}: [$ ${money}]|[Hp ${heroHealth}]\n3| Нанести удар [y]\n4| Открыть магазин [n]`)

Проблема №11 “Не используются сокращения для присваивания”

// Зачем так
armor = armor + 0.25

// Если можно вот так?
armor += 0.25

Проблема №12 “Неадекватная проверка имени игрока”

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

// Бред
var name = prompt("Введи свое имя [длиннее 4 символов не рекомендуется] ")
	
if (name == null || name == "" || name == " ") {
	alert("Вы обязаны выбрать имя")
}

// Пойдет
var name = prompt("Введи свое имя [длиннее 4 символов не рекомендуется] ")?.trim() ?? ""

if (name.length == 0) {
	alert("Вы обязаны выбрать имя")
}

Проблема №13 “Умереть в игре менее позорно, чем ливнуть самостоятельно”

Гений на геймдизайнере решил, что расчет очков должен происходить по-разному в зависимости от того каким образом заканчивается игра. Если мы выходим сами, то очки умножаются на 1.5 перед тем как отобразиться игроку, а если нас победил противник, то коэффициент не утешающий. Всего 0.2.

Проблема №14 “Странный порядок функций в коде”

Функция BlockateOverHealth находятся над всеми другими, но впервые вызвана она будет в игровом цикле после покупки аптечки.

Проблема №15 “Неадекватный код лечения героя”

При покупке лечения прибавляю 999 HP и проверяю превышения порога, чтобы потом срезать до overHealth.

Проблема №16 “Смешивание логики в коде”

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

Заключение

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

Есть ли столь древние артефакты лично у тебя?