Advertisement
nitrogenfingers

Rat Trap

Sep 19th, 2013
495
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 11.51 KB | None | 0 0
  1. --[[
  2.         Rat Trap
  3.         Inspired by the game by Christopher Lee Fraley
  4.        
  5.         Written by: Nitrogen Fingers
  6. ]]--
  7. local w,h = term.getSize()
  8. local running = true
  9.  
  10. local map = {}
  11. local objectMap = {}
  12.  
  13. local mapXSize = 35
  14. local mapYSize = 19
  15. local drawOffsetX = 0
  16. local drawOffsetY = 0
  17.  
  18. local enemies = {}
  19. local enemyTimer = 0
  20. local enemyInterval = 1
  21.  
  22. local spawnInterval = 60
  23. local spawnTimer = 0
  24. local levelEnemies = 2
  25. local levelName = "01_box.nfp"
  26.  
  27. local electricity = {}
  28.  
  29. local plX = 0
  30. local plY = 0
  31. local pltrapped = 0
  32. local pldead = 0
  33. local lives = 3
  34. local score = 0
  35.  
  36. --[[Section: Level Loading and Updating]]--
  37.  
  38. --Takes a file character and converts it to a map or game entity
  39. local function parseValue(x, y, lchar)
  40.   if tonumber(lchar, 16) then
  41.     lchar = math.pow(2, tonumber(lchar,16))
  42.    
  43.     if lchar == colours.lime then
  44.       map[y][x] = 0
  45.     elseif lchar == colours.grey then
  46.       map[y][x] = 1
  47.     elseif lchar == colours.red then
  48.       map[y][x] = "*"
  49.     elseif lchar == colours.lightGrey then
  50.       map[y][x] = "o"
  51.     elseif lchar == colours.white then
  52.       plX = x
  53.       plY = y
  54.     end
  55.   end
  56. end
  57.  
  58. --Loads a file and reads it in as a level
  59. local function loadMap(_sPath)
  60.   if not fs.exists(_sPath) then return false end
  61.   map = {}
  62.   objectMap = {}
  63.   enemies = {}
  64.    
  65.   local file = fs.open(_sPath, "r")
  66.   local line = file:readLine()
  67.   while line do
  68.     map[#map+1] = {}
  69.     for i=1,math.min(#line,mapXSize) do
  70.       local lchar = string.sub(line,i,i)
  71.       parseValue(i, #map, lchar)
  72.     end
  73.     if #map == mapYSize then break end
  74.     line = file:readLine()
  75.   end
  76.   file:close()
  77.   return true
  78. end
  79.  
  80. --When something moves or something needs to be drawn, we
  81. --just change the appropriate tile with this method.
  82. local function updateMap(x,y)
  83.     term.setCursorPos(x, y)
  84.     term.setBackgroundColour(colours.green)
  85.     if plX == x and plY == y then
  86.       if pltrapped > 0 then term.setTextColour(colours.grey)
  87.       else term.setTextColour(colours.white) end
  88.       term.write("&")
  89.     elseif map[y][x] == "o" then
  90.       term.setTextColour(colours.grey)
  91.       term.write("o")
  92.     elseif map[y][x] == "*" then
  93.       term.setTextColour(colours.red)
  94.       term.write("*")
  95.     elseif map[y][x] == "$" then
  96.       term.setTextColour(colours.orange)
  97.       term.write("$")
  98.     elseif map[y][x] == 0 then
  99.       term.setBackgroundColour(colours.lime)
  100.       term.write(" ")
  101.     elseif map[y][x] == 1 then
  102.       term.setBackgroundColour(colours.grey)
  103.       term.write(" ")
  104.     else
  105.       term.write(" ")
  106.     end
  107. end
  108.  
  109. --Draws some HUD information in the sidebar
  110. local function drawInterface()
  111.     term.setTextColour(colours.black)
  112.     term.setBackgroundColour(colours.lightGrey)
  113.    
  114.     local msg = "Rat Trap"
  115.     term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 2)
  116.     term.write(msg)
  117.     msg = "Lives"
  118.     term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 9)
  119.     term.write(msg)
  120.     msg = "Score"
  121.     term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 12)
  122.     term.write(msg)
  123.     msg = "Spawn Timer"
  124.     term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 15)
  125.     term.write(msg)
  126.    
  127.     term.setTextColour(colours.blue)
  128.     term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #levelName/2), 4)
  129.     term.write(levelName)
  130.    
  131.    
  132.     term.setTextColour(colours.white)
  133.     term.setBackgroundColour(colours.grey)
  134.     if lives > 3 then
  135.         msg = " & x 3 "
  136.     else
  137.         msg = " "..string.rep("& ", lives)
  138.     end
  139.     term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 10)
  140.     term.write(msg)
  141.     msg = " "..string.rep("0", 5 - math.log10(score+1))..score.." "
  142.     if score == 0 then msg = " 00000 " end
  143.     term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 13)
  144.     term.write(msg)
  145.     local mins = math.floor(spawnTimer / 60)
  146.     local secs = spawnTimer % 60
  147.     msg = " "
  148.     if mins < 10 then msg = msg.."0" end
  149.     msg = msg..mins..":"
  150.     if secs < 10 then msg = msg.."0" end
  151.     msg = msg..secs.." "
  152.     term.setCursorPos(math.ceil(mapXSize + (w-mapXSize)/2 - #msg/2), 16)
  153.     term.write(msg)
  154. end
  155.  
  156. local function drawEnemy(enemy)
  157.     term.setCursorPos(enemy.x, enemy.y)
  158.     term.setBackgroundColour(colours.green)
  159.     if enemy.trapped then term.setTextColour(colours.red)
  160.     else term.setTextColour(colours.orange) end
  161.     term.write("&")
  162. end
  163.  
  164. --Draws the entire map for the first time. This is only called once per level
  165. local function drawMap()
  166.   term.setBackgroundColour(colours.lightGrey)
  167.   term.clear()
  168.   for y=1,#map do
  169.     for x=1,mapXSize do
  170.       updateMap(x,y)
  171.     end
  172.   end
  173.   for _,enemy in pairs(enemies) do drawEnemy(enemy) end
  174.   for _,ball in pairs(electricity) do drawBall(ball) end
  175.   drawInterface()
  176. end
  177.  
  178. --[[Section: Enemy Logic]]--
  179.  
  180. local function killPlayer()
  181.     plX = -1
  182.     plY = -1
  183.     pltrapped = 0
  184.     lives = lives - 1
  185.     if lives == 0 then
  186.         running = false
  187.     else
  188.         pldead = 5
  189.     end
  190. end
  191.  
  192. --Takes in a XY position and generates a list of legal moves enemies can make.
  193. --Length of 0-8.
  194. local function getLegalMoves(x,y)
  195.     local legal = {}
  196.     for lx = math.max(1,x-1),math.min(mapXSize,x+1) do
  197.         for ly = math.max(1,y-1),math.min(mapYSize,y+1) do
  198.             if (lx ~= x or ly ~= y) and map[ly][lx] == nil then
  199.                 local enemyOccupy = false
  200.                 for _,enemy in pairs(enemies) do
  201.                     if enemy.x == lx and enemy.y == ly then
  202.                         enemyOccupy = true
  203.                         break
  204.                     end
  205.                 end
  206.            
  207.                 if not enemyOccupy then
  208.                     table.insert(legal, { x = lx, y = ly } )
  209.                 end
  210.             end
  211.         end
  212.     end
  213.    
  214.     return legal
  215. end
  216.  
  217. --Spawns the player, in a similar fashion to enemies
  218. local function spawnPlayer()
  219.     local moveLegal = false
  220.     local x,y = 0,0
  221.     repeat
  222.         x = math.random(1,mapXSize)
  223.         y = math.random(1,mapYSize)
  224.        
  225.         if map[y][x] == nil then
  226.             local mindist = math.huge
  227.             for _,enemy in pairs(enemies) do
  228.                 mindist = math.min(mindist, math.sqrt(math.pow(enemy.x - plX, 2) +
  229.                     math.pow(enemy.y - plY, 2)))
  230.             end
  231.             if mindist > 5 then moveLegal = true end
  232.         end
  233.     until moveLegal
  234.    
  235.     plX = x
  236.     plY = y
  237.     updateMap(x,y)
  238. end
  239.  
  240. --Creates a new enemy and places it in the world
  241. local function generateEnemy()
  242.     local moveLegal = false
  243.     local x,y = 0,0
  244.     repeat
  245.         --This approach... isn't good but it's good enough I expect
  246.         x = math.random(1,mapXSize)
  247.         y = math.random(1,mapYSize)
  248.         --The starting location HAS to be empty.
  249.         if map[y][x] == nil then
  250.             local moves = getLegalMoves(x,y)
  251.             local pdist = math.sqrt(math.pow(plX-x,2), math.pow(plY-y,2))
  252.            
  253.             --It can't be within 5 squares of the player or be immediately trapped.
  254.             if pdist > 5 and #moves > 0 then
  255.                 moveLegal = true
  256.             end
  257.         end
  258.     until moveLegal
  259.    
  260.     --Fortunately enemies are really easy; they're mostly stateless.
  261.     local enemy = {
  262.         --Enemy position
  263.         x = x; y = y;
  264.         --Whether or not it's trapped (for drawing). Always starts false.
  265.         trapped = false;
  266.     }
  267.     table.insert(enemies, enemy)
  268.     drawEnemy(enemy)
  269. end
  270.  
  271. --Moves the enemy. Each time this is called, unless an enemy is trapped, it MUST move.
  272. --This right now is a naive distance function. Later implementations will need to use
  273. --something more sophistocated.
  274. local function moveEnemy(enemy)
  275.     local moves = getLegalMoves(enemy.x, enemy.y)
  276.     if #moves == 0 then
  277.         --Score 5 points for trapping an enemy fresh
  278.         if not enemy.trapped then score = score + 5 end
  279.         enemy.trapped = true
  280.         drawEnemy(enemy)
  281.         return
  282.     end
  283.     enemy.trapped = false
  284.    
  285.     local ind = 0
  286.     if pldead > 0 then
  287.         ind = math.random(1,#moves)
  288.     else
  289.         local mindist = math.huge
  290.         for index,move in pairs(moves) do
  291.             local dist = math.sqrt(math.pow(move.x - plX, 2) + math.pow(move.y - plY, 2))
  292.             if dist < mindist then
  293.                 mindist = dist
  294.                 ind = index
  295.             end
  296.         end
  297.     end
  298.    
  299.     local ox,oy = enemy.x, enemy.y
  300.     enemy.x = moves[ind].x
  301.     enemy.y = moves[ind].y
  302.     if enemy.x == plX and enemy.y == plY then
  303.         killPlayer()
  304.     end
  305.    
  306.     updateMap(ox,oy)
  307.     drawEnemy(enemy)
  308. end
  309.  
  310. --[[Section: Movement and Input]]--
  311.  
  312. --When the player pushes a block, what happens to that block depends on whats
  313. --in front of it. This method takes the direction and determines if the column
  314. --moves based on its composition.
  315. local function pushBlock(ix,iy,dirX,dirY)
  316.     local ctx,cty = ix,iy
  317.     while ctx > 0 and ctx < w and cty > 0 and cty < h do
  318.         if map[cty][ctx] == 1 or map[cty][ctx] == "*" then
  319.             return false
  320.         elseif map[cty][ctx] == nil or map[cty][ctx] == "$" then
  321.             for _,enemy in pairs(enemies) do
  322.                 if enemy.x == ctx and enemy.y == cty then
  323.                     --You can't squash enemies. Trapped foes act as walls.
  324.                     if enemy.trapped then
  325.                         return false
  326.                     else
  327.                         moveEnemy(enemy)
  328.                     end
  329.                 end
  330.             end
  331.        
  332.             map[cty][ctx] = 0
  333.             map[iy][ix] = nil
  334.             updateMap(ctx,cty)
  335.             return true
  336.         elseif map[cty][ctx] == "o" then
  337.             map[iy][ix] = nil
  338.             return true
  339.         end
  340.         ctx = ctx + dirX
  341.         cty = cty + dirY
  342.     end
  343.     return false
  344. end
  345.  
  346. --Handles player movements. They attempt to move to a given square
  347. local function movePlayer(x,y)
  348.     if pltrapped > 0 or pldead > 0 then return end
  349.  
  350.     local ox,oy = plX, plY
  351.     local ptile = map[y][x]
  352.     if ptile == "o" then
  353.         map[y][x] = nil
  354.         pltrapped = 10
  355.         plX = x
  356.         plY = y
  357.         updateMap(ox, oy)
  358.         updateMap(plX, plY)
  359.     elseif ptile == "$" then
  360.         map[y][x] = nil
  361.         --20 points for getting a corpse
  362.         score = score + 20
  363.         plX = x
  364.         plY = y
  365.         updateMap(ox,oy)
  366.         updateMap(plX, plY)
  367.     elseif ptile == nil then
  368.         plX = x
  369.         plY = y
  370.         updateMap(ox, oy)
  371.         for _,enemy in pairs(enemies) do
  372.             if enemy.x == plX and enemy.y == plY then killPlayer() end
  373.         end
  374.         for _,ball in pairs(electricity) do
  375.             if ball.x == plX and ball.y == plY then killPlayer() end
  376.         end
  377.         updateMap(plX, plY)
  378.     else
  379.         local dirX, dirY = x - plX, y - plY
  380.         if pushBlock(x,y,dirX,dirY) then
  381.             plX = x
  382.             plY = y
  383.             updateMap(ox, oy)
  384.             updateMap(plX, plY)
  385.         end
  386.     end
  387.    
  388.     drawInterface()
  389. end
  390.  
  391. local function handleEvents()
  392.     local id,p1,p2,p3 = os.pullEvent()
  393.    
  394.     if id == "key" then
  395.         if p1 == numPad1 then
  396.             movePlayer(plX-1, plY+1)
  397.         elseif p1 == numPad2 or p1 == keys.down then
  398.             movePlayer(plX, plY+1)
  399.         elseif p1 == numPad3 then
  400.             movePlayer(plX+1, plY+1)
  401.         elseif p1 == numPad4 or p1 == keys.left then
  402.             movePlayer(plX-1, plY)
  403.         elseif p1 == numPad6 or p1 == keys.right then
  404.             movePlayer(plX+1, plY)
  405.         elseif p1 == numPad7 then
  406.             movePlayer(plX-1, plY-1)
  407.         elseif p1 == numPad8 or p1 == keys.up then
  408.             movePlayer(plX, plY-1)
  409.         elseif p1 == numPad9 then
  410.             movePlayer(plX+1, plY-1)
  411.         elseif p1 == keys.enter then
  412.             running = false
  413.         end
  414.     elseif id == "timer" then
  415.         --Right now it's just the one. This'll change with electricity balls.
  416.         local allEnemiesTrapped = true
  417.         for _,enemy in pairs(enemies) do
  418.             moveEnemy(enemy)
  419.             if not enemy.trapped then allEnemiesTrapped = false end
  420.         end
  421.        
  422.         if allEnemiesTrapped then
  423.             spawnTimer = spawnTimer + spawnInterval
  424.             for _,enemy in pairs(enemies) do
  425.                 map[enemy.y][enemy.x] = "$"
  426.                 updateMap(enemy.x, enemy.y)
  427.             end
  428.             enemies = {}
  429.             for i=1,levelEnemies do generateEnemy() end
  430.         end
  431.        
  432.         if spawnTimer == 0 then
  433.             spawnTimer = spawnInterval
  434.             for i=1,levelEnemies do generateEnemy() end
  435.         else spawnTimer = spawnTimer - 1 end
  436.        
  437.         if pltrapped > 0 then
  438.             pltrapped = pltrapped - 1
  439.             if pltrapped == 0 then updateMap(plX, plY) end
  440.         end
  441.         if pldead > 0 then
  442.             pldead = pldead - 1
  443.             if pldead == 0 then
  444.                 spawnPlayer()
  445.             end
  446.         end
  447.        
  448.         enemyTimer = os.startTimer(enemyInterval)
  449.         drawInterface()
  450.     end
  451. end
  452.  
  453. local fpath = shell.resolve(".").."/levels/"..levelName
  454. if not loadMap(fpath) then error("Couldn't load map!") end
  455. drawMap()
  456.  
  457. enemyTimer = os.startTimer(enemyInterval)
  458. while running do handleEvents() end
  459. term.setBackgroundColour(colours.black)
  460. shell.run("clear")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement