-- snake.lua — классическая "Змейка" для OpenComputers
-- Управление: стрелки ← ↑ → ↓, Пауза: P, Выход: Q
-- Адаптируется к текущему разрешению; безопасно возвращает настройки при выходе.
local comp = require("component")
local event = require("event")
local keyboard = require("keyboard")
local term = require("term")
assert(comp.isAvailable("gpu"), "Нужна видеокарта (gpu).")
local gpu = comp.gpu
-- Палитра
local COL_BG = 0x202020 -- тёмно-серый фон
local COL_BORDER = 0x404040
local COL_TEXT = 0xFFFFFF
local COL_SNAKE = 0x00FF66 -- зелёная змейка
local COL_FOOD = 0x33CCFF -- голубая еда
-- Сохранение текущих настроек, чтобы красиво выйти
local oldFg, oldBg = gpu.getForeground(), gpu.getBackground()
local oldW, oldH = gpu.getResolution()
local function cleanup(msg)
gpu.setForeground(oldFg); gpu.setBackground(oldBg)
gpu.setResolution(oldW, oldH)
term.clear()
if msg then print(msg) end
end
-- Настройка разумного резолюшена под игру (не больше максимума GPU)
do
local maxW, maxH = gpu.maxResolution()
local targetW = math.min(100, maxW)
local targetH = math.min(35, maxH)
pcall(gpu.setResolution, targetW, targetH)
end
local W, H = gpu.getResolution()
-- Поле: оставим рамку и статус-строку
local LEFT, TOP = 2, 3
local RIGHT, BOTTOM = W - 1, H - 1
local FIELD_W = RIGHT - LEFT + 1
local FIELD_H = BOTTOM - TOP + 1
-- Защита: поле должно быть достаточно большим
if FIELD_W < 10 or FIELD_H < 8 then
cleanup("Слишком маленькое разрешение экрана для игры.")
return
end
-- Утилиты отрисовки
local function cls()
gpu.setBackground(COL_BG); gpu.setForeground(COL_TEXT)
term.clear()
end
local function drawFrame()
gpu.setForeground(COL_BORDER)
-- Верхняя панель
gpu.fill(1, 1, W, 1, " ")
gpu.set(2, 1, "S N A K E (P - пауза, Q - выход)")
-- Рамка
for x = LEFT-1, RIGHT+1 do gpu.set(x, TOP-1, "─"); gpu.set(x, BOTTOM+1, "─") end
for y = TOP-1, BOTTOM+1 do gpu.set(LEFT-1, y, "│"); gpu.set(RIGHT+1, y, "│") end
gpu.set(LEFT-1, TOP-1, "┌"); gpu.set(RIGHT+1, TOP-1, "┐")
gpu.set(LEFT-1, BOTTOM+1, "└"); gpu.set(RIGHT+1, BOTTOM+1, "┘")
end
local function status(score, speed)
gpu.setForeground(COL_TEXT)
local line = string.format(" Score: %d Speed: %.1f fps ", score, speed)
gpu.fill(1, 2, W, 1, " ")
gpu.set(math.max(2, math.floor((W - #line)/2)), 2, line)
end
local function fillField()
gpu.setBackground(COL_BG)
gpu.fill(LEFT, TOP, FIELD_W, FIELD_H, " ")
end
-- Игровые координаты
local function rnd(a, b) return math.random(a, b) end
local function posEq(a, b) return a.x == b.x and a.y == b.y end
-- Генерация еды на свободной клетке
local function spawnFood(snake)
while true do
local fx = rnd(LEFT, RIGHT)
local fy = rnd(TOP, BOTTOM)
local clash = false
for i=1,#snake do if snake[i].x == fx and snake[i].y == fy then clash = true; break end end
if not clash then return {x=fx, y=fy} end
end
end
-- Отрисовка змейки/еды
local function drawCell(x, y, color, ch)
gpu.setBackground(COL_BG) -- подложка
gpu.setForeground(color)
gpu.set(x, y, ch or "█")
end
-- Инициализация змейки
local snake = {}
local startLen = 5
local sx = math.floor((LEFT + RIGHT) / 2)
local sy = math.floor((TOP + BOTTOM) / 2)
for i=0,startLen-1 do
table.insert(snake, 1, {x = sx - i, y = sy})
end
local dir = {x = 1, y = 0} -- движемся вправо
local pendingDir = dir
local food = spawnFood(snake)
local score = 0
local paused = false
local alive = true
-- Скорость (кадры/сек). Повышаем по мере роста змейки.
local fps = 8
local function stepDelay() return 1.0 / fps end
-- Ограничение: нельзя разворачиваться на 180°
local function setDirection(nx, ny)
-- если новая ось противоположна текущей — игнор
if (#snake >= 2) then
local head, neck = snake[1], snake[2]
-- Проверим, не ведёт ли новый вектор в шею (разворот)
if head.x + nx == neck.x and head.y + ny == neck.y then return end
end
pendingDir = {x = nx, y = ny}
end
-- Логика шага
local function tick()
dir = pendingDir
local head = snake[1]
local nx, ny = head.x + dir.x, head.y + dir.y
-- Столкновение со стеной
if nx < LEFT or nx > RIGHT or ny < TOP or ny > BOTTOM then
alive = false; return
end
-- Столкновение с собой
for i=1,#snake do
if snake[i].x == nx and snake[i].y == ny then alive = false; return end
end
-- Добавляем новую голову
table.insert(snake, 1, {x=nx, y=ny})
drawCell(nx, ny, COL_SNAKE, "█")
-- Проверка еды
if nx == food.x and ny == food.y then
score = score + 1
-- немного ускоряемся, но с ограничением
fps = math.min(20, 8 + score * 0.3)
food = spawnFood(snake)
drawCell(food.x, food.y, COL_FOOD, "●")
else
-- убираем хвост (перерисовываем ячейку фоном)
local tail = table.remove(snake)
gpu.setForeground(COL_BG)
gpu.set(tail.x, tail.y, " ")
end
end
-- Рендер стартового состояния
math.randomseed(os.time())
cls()
drawFrame()
fillField()
for i=1,#snake do drawCell(snake[i].x, snake[i].y, COL_SNAKE, "█") end
drawCell(food.x, food.y, COL_FOOD, "●")
status(score, fps)
-- Главный цикл
local lastTime = computer.uptime and computer.uptime() or os.clock()
local function now() return (computer.uptime and computer.uptime()) or os.clock() end
while alive do
-- обработка ввода (не блокируя логику шага)
local timeout = stepDelay()
local ev = {event.pull(timeout, "key_down")}
if #ev > 0 then
local _, _, _, code = table.unpack(ev)
if code == keyboard.keys.q then break end
if code == keyboard.keys.p then paused = not paused end
if code == keyboard.keys.up then setDirection(0, -1) end
if code == keyboard.keys.down then setDirection(0, 1) end
if code == keyboard.keys.left then setDirection(-1, 0) end
if code == keyboard.keys.right then setDirection(1, 0) end
end
if paused then
gpu.setForeground(COL_TEXT)
gpu.set(math.floor(W/2)-3, 2, "[PAUSE]")
else
-- тикаем по времени (вдруг event.pull вернулось раньше)
local t = now()
if t - lastTime >= stepDelay() then
tick()
status(score, fps)
lastTime = t
end
end
end
if alive then
cleanup("Вы вышли из игры. Счёт: "..tostring(score))
else
cleanup("Игра окончена. Счёт: "..tostring(score))
end