SetTitle("Snakes In A Game!") -- set the screen buffer and window size local width, height = 80, 50 SetScreenBufferSize(hOut, width, height) SetWindowInfo(hOut, false, 0, 0, width - 1, height - 1) -- hide the cursor SetCursorVisible(hOut, false) -- movement directions local dir_dx = { 0, 1, 0, -1 } local dir_dy = { -1, 0, 1, 0 } -- player head local player_head = { string.char(0x1E, 0x0A), string.char(0x10, 0x0A), string.char(0x1F, 0x0A), string.char(0x11, 0x0A), } -- player body local player_body = { { string.char(0xBA, 0x2A), -- U -> U string.char(0xC9, 0x2A), -- U -> R string.char(0xBA, 0x2A), -- U -> D string.char(0xBB, 0x2A), -- U -> L }, { string.char(0xBC, 0x2A), -- R -> U string.char(0xCD, 0x2A), -- R -> R string.char(0xBB, 0x2A), -- R -> D string.char(0xCD, 0x2A), -- R -> L }, { string.char(0xBA, 0x2A), -- D -> U string.char(0xC8, 0x2A), -- D -> R string.char(0xBA, 0x2A), -- D -> D string.char(0xBC, 0x2A), -- D -> L }, { string.char(0xC8, 0x2A), -- L -> U string.char(0xCD, 0x2A), -- L -> R string.char(0xC9, 0x2A), -- L -> D string.char(0xCD, 0x2A), -- L -> L }, } local body_length = 20 -- player tail local player_tail = { string.char(0x1F, 0x02), string.char(0x11, 0x02), string.char(0x1E, 0x02), string.char(0x10, 0x02), } -- player explosion local player_explode = { string.char(0x0F, 0x6F), string.char(0x0F, 0x4E), string.char(0x0F, 0x0E), string.char(0x0F, 0x0C), string.char(0x2A, 0x04), string.char(0x2A, 0x08), string.char(0xFA, 0x08), } -- target item local target_item = string.char(0x04, 0x0E) -- player state local x, y, dir, prev_dir, body local hit = 0 -- most recent key code local key_code -- pause state local paused = false -- create background buffer local background = NewCharInfoBuffer(width, height) -- create the display buffer local display = NewCharInfoBuffer(width, height) -- last time value local lastTime = GetMillis() -- delay function Delay(duration) local delayTime = duration - GetMillis() + lastTime Sleep(delayTime) lastTime = GetMillis() end -- add a target function AddTarget() local tx, ty repeat tx = math.random(8, width - 9) ty = math.random(8, height - 9) until display:Get(tx, ty) == background:Get(tx, ty) display:Set(tx, ty, target_item) end -- initialize the background function InitBackground() -- fill the with random characters local background_attrib = 1 for j = 0, height - 1 do for i = 0, width - 1 do background:Set(i, j, math.random(1, 255), background_attrib) end end end -- initialize the level function InitLevel() -- initialize the background InitBackground() -- duplicate the background into the display buffer -- (this is lame but works) WriteOutput(hOut, background, 0, 0, 0, 0, width - 1, height - 1) ReadOutput(hOut, display, 0, 0, 0, 0, width - 1, height - 1) -- draw the perimeter local perimeter_attrib = 0x4D display:Set(0, 0, 0xC9, perimeter_attrib) for x = 1, width - 2 do display:Set(x, 0, 0xCD, perimeter_attrib) end display:Set(width-1, 0, 0xBB, perimeter_attrib) for y = 1, height - 2 do display:Set(0, y, 0xBA, perimeter_attrib) display:Set(width - 1, y, 0xBA, perimeter_attrib) end display:Set(0, height - 1, 0xC8, perimeter_attrib) for x = 1, width - 2 do display:Set(x, height - 1, 0xCD, perimeter_attrib) end display:Set(width - 1, height - 1, string.char(0xBC), perimeter_attrib) -- set player starting position and direction repeat x = math.random(16, width - 17) y = math.random(16, height - 17) until display:Get(x, y) == background:Get(x, y) dir = math.random(4) prev_dir = dir -- start the body segment array body = { y * width + x } -- add targets for i = 1, 10 do AddTarget() end -- present the display buffer WriteOutput(hOut, display, 0, 0, 0, 0, width - 1, height - 1) -- initialize wait state InitWait() end function InitWait() -- set update to wait key_code = nil hit = 0 Update = UpdateWait end function UpdateWait() if key_code then -- enter live state Update = UpdateLive else -- draw initial player position display:Set(x, y, string.char(hit == 0 and 0x09 or 0x07, 0x2A)) hit = 1 - hit -- present the display buffer WriteOutput(hOut, display, 0, 0, 0, 0, width - 1, height - 1) -- waiting for input Delay(100) end end function UpdateLive() -- if the body is maximum length... if #body >= body_length then -- clear player tail local t = table.remove(body, 1) local ty = math.floor(t / width) local tx = t - ty * width display:Set(tx, ty, background:Get(tx, ty)) end -- draw player tail if #body > 1 then local tail_pos = body[1] local tail_y = math.floor(tail_pos / width) local tail_x = tail_pos - tail_y * width local next_pos = body[2] local next_y = math.floor(next_pos / width) local next_x = next_pos - next_y * width local tail_dir = 2 + (next_y - tail_y) + bit.band(tail_x - next_x + 1, 2) display:Set(tail_x, tail_y, player_tail[tail_dir]) end -- draw player body display:Set(x, y, player_body[prev_dir][dir], body_attrib) -- move the player head x = x + dir_dx[dir] y = y + dir_dy[dir] -- wrap around if x < 0 then x = width - 1 elseif x > width - 1 then x = 0 end if y < 0 then y = height - 1 elseif y > height - 1 then y = 0 end -- check the location under the head local loc = display:Get(x, y) if loc == target_item then -- target item body_length = body_length + 10 AddTarget() elseif loc ~= background:Get(x, y) then -- solid obstacle x = x - dir_dx[dir] y = y - dir_dy[dir] InitHit() end -- add body segment table.insert(body, y * width + x) -- draw player head display:Set(x, y, player_head[dir], player_attrib) -- save as previous direction prev_dir = dir -- present the display buffer WriteOutput(hOut, display, 0, 0, 0, 0, width - 1, height - 1) -- try to run at 20 FPS Delay(50) end function InitHit() hit = 0 Update = UpdateHit end function UpdateHit() -- play impact animation local frame = bit.band(hit, 3) + 1 display:Set(x, y, player_explode[frame]) hit = hit + 1 if hit >= 20 then InitExplode() end -- present the display buffer WriteOutput(hOut, display, 0, 0, 0, 0, width - 1, height - 1) -- try to run at 60 FPS Delay(17) end function InitExplode() hit = 0 Update = UpdateExplode end function UpdateExplode() -- play explosions along the body local i = #body for s = hit, 0,- 1 do local t = body[i] local ty = math.floor(t / width) local tx = t - ty * width display:Set(tx, ty, player_explode[s]) i = i - 1 if i < 1 then break end end if hit < #player_explode then hit = hit + 1 else table.remove(body) if #body < 1 then key_code = nil Update = UpdateDead end end -- present the display buffer WriteOutput(hOut, display, 0, 0, 0, 0, width - 1, height - 1) -- try to run at 60 FPS Delay(17) end function UpdateDead() if key_code then -- enter init level Update = InitLevel else -- wait for input Sleep(100) end end function TogglePause() paused = not paused Pause(paused) end local key_handler = { [37] = --left arrow function() dir = 4 end, [38] = -- up arrow function() dir = 1 end, [39] = -- right arrow function() dir = 2 end, [40] = -- down arrow function() dir = 3 end, [27] = -- escape Quit, [19] = -- pause TogglePause, [80] = TogglePause, } function KeyPressed(repeatcount, keycode, scancode, asciichar, modifiers) --debugprint("KeyPressedRun", repeatcount, keycode, scancode, asciichar, modifiers) key_code = keycode local h = key_handler[keycode] if h then h() end end -- do setup InitLevel() --[[ function KeyReleased(repeatcount, keycode, scancode, asciichar, modifiers) debugprint("key up", repeatcount, keycode, scancode, asciichar, modifiers) end function MouseMoved(x, y) debugprint("mouse", x, y) end function MousePressed(button) debugprint("button down", button) end function MouseReleased(button) debugprint("button up", button) end --]]