Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- Rat Trap
- Inspired by the game by Christopher Lee Fraley
- Written by: Nitrogen Fingers
- ]]--
- local w,h = term.getSize()
- local running = true
- local map = {}
- local objectMap = {}
- local mapXSize = 35
- local mapYSize = 19
- local drawOffsetX = 0
- local drawOffsetY = 0
- local enemies = {}
- local enemyTimer = 0
- local enemyInterval = 1
- local spawnInterval = 60
- local spawnTimer = 0
- local levelEnemies = 2
- local levelName = "01_box.nfp"
- local electricity = {}
- local plX = 0
- local plY = 0
- local pltrapped = 0
- local pldead = 0
- local lives = 3
- local score = 0
- --[[Section: Level Loading and Updating]]--
- --Takes a file character and converts it to a map or game entity
- local function parseValue(x, y, lchar)
- if tonumber(lchar, 16) then
- lchar = math.pow(2, tonumber(lchar,16))
- if lchar == colours.lime then
- map[y][x] = 0
- elseif lchar == colours.grey then
- map[y][x] = 1
- elseif lchar == colours.red then
- map[y][x] = "*"
- elseif lchar == colours.lightGrey then
- map[y][x] = "o"
- elseif lchar == colours.white then
- plX = x
- plY = y
- end
- end
- end
- --Loads a file and reads it in as a level
- local function loadMap(_sPath)
- if not fs.exists(_sPath) then return false end
- map = {}
- objectMap = {}
- enemies = {}
- local file = fs.open(_sPath, "r")
- local line = file:readLine()
- while line do
- map[#map+1] = {}
- for i=1,math.min(#line,mapXSize) do
- local lchar = string.sub(line,i,i)
- parseValue(i, #map, lchar)
- end
- if #map == mapYSize then break end
- line = file:readLine()
- end
- file:close()
- return true
- end
- --When something moves or something needs to be drawn, we
- --just change the appropriate tile with this method.
- local function updateMap(x,y)
- term.setCursorPos(x, y)
- term.setBackgroundColour(colours.green)
- if plX == x and plY == y then
- if pltrapped > 0 then term.setTextColour(colours.grey)
- else term.setTextColour(colours.white) end
- term.write("&")
- elseif map[y][x] == "o" then
- term.setTextColour(colours.grey)
- term.write("o")
- elseif map[y][x] == "*" then
- term.setTextColour(colours.red)
- term.write("*")
- elseif map[y][x] == "$" then
- term.setTextColour(colours.orange)
- term.write("$")
- elseif map[y][x] == 0 then
- term.setBackgroundColour(colours.lime)
- term.write(" ")
- elseif map[y][x] == 1 then
- term.setBackgroundColour(colours.grey)
- term.write(" ")
- else
- term.write(" ")
- end
- end
- --Draws some HUD information in the sidebar
- local function drawInterface()
- term.setTextColour(colours.black)
- term.setBackgroundColour(colours.lightGrey)
- local msg = "Rat Trap"
- term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 2)
- term.write(msg)
- msg = "Lives"
- term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 9)
- term.write(msg)
- msg = "Score"
- term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 12)
- term.write(msg)
- msg = "Spawn Timer"
- term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 15)
- term.write(msg)
- term.setTextColour(colours.blue)
- term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #levelName/2), 4)
- term.write(levelName)
- term.setTextColour(colours.white)
- term.setBackgroundColour(colours.grey)
- if lives > 3 then
- msg = " & x 3 "
- else
- msg = " "..string.rep("& ", lives)
- end
- term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 10)
- term.write(msg)
- msg = " "..string.rep("0", 5 - math.log10(score+1))..score.." "
- if score == 0 then msg = " 00000 " end
- term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 13)
- term.write(msg)
- local mins = math.floor(spawnTimer / 60)
- local secs = spawnTimer % 60
- msg = " "
- if mins < 10 then msg = msg.."0" end
- msg = msg..mins..":"
- if secs < 10 then msg = msg.."0" end
- msg = msg..secs.." "
- term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 16)
- term.write(msg)
- end
- local function drawEnemy(enemy)
- term.setCursorPos(enemy.x, enemy.y)
- term.setBackgroundColour(colours.green)
- if enemy.trapped then term.setTextColour(colours.red)
- else term.setTextColour(colours.orange) end
- term.write("&")
- end
- --Draws the entire map for the first time. This is only called once per level
- local function drawMap()
- term.setBackgroundColour(colours.lightGrey)
- term.clear()
- for y=1,#map do
- for x=1,mapXSize do
- updateMap(x,y)
- end
- end
- for _,enemy in pairs(enemies) do drawEnemy(enemy) end
- for _,ball in pairs(electricity) do drawBall(ball) end
- drawInterface()
- end
- --[[Section: Enemy Logic]]--
- local function killPlayer()
- plX = -1
- plY = -1
- pltrapped = 0
- lives = lives - 1
- if lives == 0 then
- running = false
- else
- pldead = 5
- end
- end
- --Takes in a XY position and generates a list of legal moves enemies can make.
- --Length of 0-8.
- local function getLegalMoves(x,y)
- local legal = {}
- for lx = math.max(1,x-1),math.min(mapXSize,x+1) do
- for ly = math.max(1,y-1),math.min(mapYSize,y+1) do
- if (lx ~= x or ly ~= y) and map[ly][lx] == nil then
- local enemyOccupy = false
- for _,enemy in pairs(enemies) do
- if enemy.x == lx and enemy.y == ly then
- enemyOccupy = true
- break
- end
- end
- if not enemyOccupy then
- table.insert(legal, { x = lx, y = ly } )
- end
- end
- end
- end
- return legal
- end
- --Spawns the player, in a similar fashion to enemies
- local function spawnPlayer()
- local moveLegal = false
- local x,y = 0,0
- repeat
- x = math.random(1,mapXSize)
- y = math.random(1,mapYSize)
- if map[y][x] == nil then
- local mindist = math.huge
- for _,enemy in pairs(enemies) do
- mindist = math.min(mindist, math.sqrt(math.pow(enemy.x - plX, 2) +
- math.pow(enemy.y - plY, 2)))
- end
- if mindist > 5 then moveLegal = true end
- end
- until moveLegal
- plX = x
- plY = y
- updateMap(x,y)
- end
- --Creates a new enemy and places it in the world
- local function generateEnemy()
- local moveLegal = false
- local x,y = 0,0
- repeat
- --This approach... isn't good but it's good enough I expect
- x = math.random(1,mapXSize)
- y = math.random(1,mapYSize)
- --The starting location HAS to be empty.
- if map[y][x] == nil then
- local moves = getLegalMoves(x,y)
- local pdist = math.sqrt(math.pow(plX-x,2), math.pow(plY-y,2))
- --It can't be within 5 squares of the player or be immediately trapped.
- if pdist > 5 and #moves > 0 then
- moveLegal = true
- end
- end
- until moveLegal
- --Fortunately enemies are really easy; they're mostly stateless.
- local enemy = {
- --Enemy position
- x = x; y = y;
- --Whether or not it's trapped (for drawing). Always starts false.
- trapped = false;
- }
- table.insert(enemies, enemy)
- drawEnemy(enemy)
- end
- --Moves the enemy. Each time this is called, unless an enemy is trapped, it MUST move.
- --This right now is a naive distance function. Later implementations will need to use
- --something more sophistocated.
- local function moveEnemy(enemy)
- local moves = getLegalMoves(enemy.x, enemy.y)
- if #moves == 0 then
- --Score 5 points for trapping an enemy fresh
- if not enemy.trapped then score = score + 5 end
- enemy.trapped = true
- drawEnemy(enemy)
- return
- end
- enemy.trapped = false
- local ind = 0
- if pldead > 0 then
- ind = math.random(1,#moves)
- else
- local mindist = math.huge
- for index,move in pairs(moves) do
- local dist = math.sqrt(math.pow(move.x - plX, 2) + math.pow(move.y - plY, 2))
- if dist < mindist then
- mindist = dist
- ind = index
- end
- end
- end
- local ox,oy = enemy.x, enemy.y
- enemy.x = moves[ind].x
- enemy.y = moves[ind].y
- if enemy.x == plX and enemy.y == plY then
- killPlayer()
- end
- updateMap(ox,oy)
- drawEnemy(enemy)
- end
- --[[Section: Movement and Input]]--
- --When the player pushes a block, what happens to that block depends on whats
- --in front of it. This method takes the direction and determines if the column
- --moves based on its composition.
- local function pushBlock(ix,iy,dirX,dirY)
- local ctx,cty = ix,iy
- while ctx > 0 and ctx < w and cty > 0 and cty < h do
- if map[cty][ctx] == 1 or map[cty][ctx] == "*" then
- return false
- elseif map[cty][ctx] == nil or map[cty][ctx] == "$" then
- for _,enemy in pairs(enemies) do
- if enemy.x == ctx and enemy.y == cty then
- --You can't squash enemies. Trapped foes act as walls.
- if enemy.trapped then
- return false
- else
- moveEnemy(enemy)
- end
- end
- end
- map[cty][ctx] = 0
- map[iy][ix] = nil
- updateMap(ctx,cty)
- return true
- elseif map[cty][ctx] == "o" then
- map[iy][ix] = nil
- return true
- end
- ctx = ctx + dirX
- cty = cty + dirY
- end
- return false
- end
- --Handles player movements. They attempt to move to a given square
- local function movePlayer(x,y)
- if pltrapped > 0 or pldead > 0 then return end
- local ox,oy = plX, plY
- local ptile = map[y][x]
- if ptile == "o" then
- map[y][x] = nil
- pltrapped = 10
- plX = x
- plY = y
- updateMap(ox, oy)
- updateMap(plX, plY)
- elseif ptile == "$" then
- map[y][x] = nil
- --20 points for getting a corpse
- score = score + 20
- plX = x
- plY = y
- updateMap(ox,oy)
- updateMap(plX, plY)
- elseif ptile == nil then
- plX = x
- plY = y
- updateMap(ox, oy)
- for _,enemy in pairs(enemies) do
- if enemy.x == plX and enemy.y == plY then killPlayer() end
- end
- for _,ball in pairs(electricity) do
- if ball.x == plX and ball.y == plY then killPlayer() end
- end
- updateMap(plX, plY)
- else
- local dirX, dirY = x - plX, y - plY
- if pushBlock(x,y,dirX,dirY) then
- plX = x
- plY = y
- updateMap(ox, oy)
- updateMap(plX, plY)
- end
- end
- drawInterface()
- end
- local function handleEvents()
- local id,p1,p2,p3 = os.pullEvent()
- if id == "key" then
- if p1 == numPad1 then
- movePlayer(plX-1, plY+1)
- elseif p1 == numPad2 or p1 == keys.down then
- movePlayer(plX, plY+1)
- elseif p1 == numPad3 then
- movePlayer(plX+1, plY+1)
- elseif p1 == numPad4 or p1 == keys.left then
- movePlayer(plX-1, plY)
- elseif p1 == numPad6 or p1 == keys.right then
- movePlayer(plX+1, plY)
- elseif p1 == numPad7 then
- movePlayer(plX-1, plY-1)
- elseif p1 == numPad8 or p1 == keys.up then
- movePlayer(plX, plY-1)
- elseif p1 == numPad9 then
- movePlayer(plX+1, plY-1)
- elseif p1 == keys.enter then
- running = false
- end
- elseif id == "timer" then
- --Right now it's just the one. This'll change with electricity balls.
- local allEnemiesTrapped = true
- for _,enemy in pairs(enemies) do
- moveEnemy(enemy)
- if not enemy.trapped then allEnemiesTrapped = false end
- end
- if allEnemiesTrapped then
- spawnTimer = spawnTimer + spawnInterval
- for _,enemy in pairs(enemies) do
- map[enemy.y][enemy.x] = "$"
- updateMap(enemy.x, enemy.y)
- end
- enemies = {}
- for i=1,levelEnemies do generateEnemy() end
- end
- if spawnTimer == 0 then
- spawnTimer = spawnInterval
- for i=1,levelEnemies do generateEnemy() end
- else spawnTimer = spawnTimer - 1 end
- if pltrapped > 0 then
- pltrapped = pltrapped - 1
- if pltrapped == 0 then updateMap(plX, plY) end
- end
- if pldead > 0 then
- pldead = pldead - 1
- if pldead == 0 then
- spawnPlayer()
- end
- end
- enemyTimer = os.startTimer(enemyInterval)
- drawInterface()
- end
- end
- local fpath = shell.resolve(".").."/levels/"..levelName
- if not loadMap(fpath) then error("Couldn't load map!") end
- drawMap()
- enemyTimer = os.startTimer(enemyInterval)
- while running do handleEvents() end
- term.setBackgroundColour(colours.black)
- shell.run("clear")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement