CCCPaste
-- 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