Advertisement
alexnolan

flappy

Mar 26th, 2025 (edited)
304
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 12.53 KB | None | 0 0
  1. -- Flappy Bird for ComputerCraft
  2. -- Designed for a 5x2 monitor setup
  3.  
  4. -- Find peripherals
  5. local monitor = peripheral.find("monitor")
  6. local speaker = peripheral.find("speaker")
  7.  
  8. if not monitor then
  9.   print("No monitor found! Please attach a monitor.")
  10.   return
  11. end
  12.  
  13. -- Set up the display
  14. monitor.setTextScale(0.5)
  15. local width, height = monitor.getSize()
  16. print("Monitor size: " .. width .. "x" .. height)
  17.  
  18. -- Game constants
  19. local GRAVITY = 0.25
  20. local FLAP_POWER = -0.8
  21. local BIRD_X = 20
  22. local PIPE_SPEED = 0.7
  23. local PIPE_WIDTH = 6
  24. local PIPE_GAP = 10
  25. local PIPE_SPACING = 35
  26. local GROUND_HEIGHT = 4
  27. local CEILING_HEIGHT = 1
  28.  
  29. -- Sound effects
  30. local function playSound(name)
  31.   if not speaker then return end
  32.  
  33.   if name == "flap" then
  34.     speaker.playNote("harp", 1.0, 12)
  35.   elseif name == "score" then
  36.     speaker.playNote("bell", 1.0, 18)
  37.     os.sleep(0.05)
  38.     speaker.playNote("bell", 1.0, 20)
  39.   elseif name == "hit" then
  40.     speaker.playNote("bass", 1.0, 8)
  41.     os.sleep(0.05)
  42.     speaker.playNote("bass", 1.0, 6)
  43.   elseif name == "die" then
  44.     speaker.playNote("bass", 1.0, 10)
  45.     os.sleep(0.1)
  46.     speaker.playNote("bass", 1.0, 8)
  47.     os.sleep(0.1)
  48.     speaker.playNote("bass", 1.0, 6)
  49.     os.sleep(0.1)
  50.     speaker.playNote("bass", 1.0, 4)
  51.   elseif name == "start" then
  52.     speaker.playNote("harp", 1.0, 14)
  53.     os.sleep(0.1)
  54.     speaker.playNote("harp", 1.0, 16)
  55.     os.sleep(0.1)
  56.     speaker.playNote("harp", 1.0, 18)
  57.   elseif name == "button" then
  58.     speaker.playNote("harp", 1.0, 16)
  59.   end
  60. end
  61.  
  62. -- Helper function to draw a button
  63. local function drawButton(x, y, w, h, text, bgColor, textColor)
  64.   monitor.setBackgroundColor(bgColor)
  65.   for i = 0, h - 1 do
  66.     monitor.setCursorPos(x, y + i)
  67.     monitor.write(string.rep(" ", w))
  68.   end
  69.  
  70.   monitor.setTextColor(textColor)
  71.   local textX = x + math.floor((w - #text) / 2)
  72.   local textY = y + math.floor(h / 2)
  73.   monitor.setCursorPos(textX, textY)
  74.   monitor.write(text)
  75. end
  76.  
  77. -- Helper function to check if a point is inside a rectangle
  78. local function isInside(px, py, x, y, w, h)
  79.   return px >= x and px < x + w and py >= y and py < y + h
  80. end
  81.  
  82. -- Draw the start screen
  83. local function drawStartScreen()
  84.   monitor.setBackgroundColor(colors.cyan)
  85.   monitor.clear()
  86.  
  87.   -- Draw ground
  88.   monitor.setBackgroundColor(colors.green)
  89.   for y = height - GROUND_HEIGHT + 1, height do
  90.     monitor.setCursorPos(1, y)
  91.     monitor.write(string.rep(" ", width))
  92.   end
  93.  
  94.   -- Draw title
  95.   local title = "FLAPPY BIRD"
  96.   monitor.setBackgroundColor(colors.cyan)
  97.   monitor.setTextColor(colors.yellow)
  98.   monitor.setCursorPos(math.floor(width/2) - math.floor(#title/2), math.floor(height/3))
  99.   monitor.write(title)
  100.  
  101.   -- Draw bird
  102.   local birdY = math.floor(height/2)
  103.   monitor.setBackgroundColor(colors.yellow)
  104.   monitor.setCursorPos(BIRD_X, birdY)
  105.   monitor.write("  ")
  106.   monitor.setCursorPos(BIRD_X, birdY + 1)
  107.   monitor.write("  ")
  108.  
  109.   -- Draw wing
  110.   monitor.setBackgroundColor(colors.orange)
  111.   monitor.setCursorPos(BIRD_X - 1, birdY)
  112.   monitor.write(" ")
  113.  
  114.   -- Draw eye
  115.   monitor.setBackgroundColor(colors.black)
  116.   monitor.setCursorPos(BIRD_X + 1, birdY)
  117.   monitor.write(" ")
  118.  
  119.   -- Start button
  120.   local buttonWidth = 16
  121.   local buttonHeight = 3
  122.   local buttonX = math.floor(width/2) - math.floor(buttonWidth/2)
  123.   local buttonY = math.floor(height*2/3)
  124.  
  125.   drawButton(buttonX, buttonY, buttonWidth, buttonHeight, "Start Game", colors.lime, colors.white)
  126.  
  127.   return {
  128.     startButton = {x = buttonX, y = buttonY, width = buttonWidth, height = buttonHeight}
  129.   }
  130. end
  131.  
  132. -- Draw game over screen
  133. local function drawGameOverScreen(score, highScore)
  134.   monitor.setBackgroundColor(colors.black)
  135.   monitor.clear()
  136.  
  137.   -- Game over text
  138.   local title = "GAME OVER"
  139.   monitor.setTextColor(colors.red)
  140.   monitor.setCursorPos(math.floor(width/2) - math.floor(#title/2), math.floor(height/3))
  141.   monitor.write(title)
  142.  
  143.   -- Score
  144.   local scoreText = "Score: " .. score
  145.   monitor.setTextColor(colors.yellow)
  146.   monitor.setCursorPos(math.floor(width/2) - math.floor(#scoreText/2), math.floor(height/3) + 2)
  147.   monitor.write(scoreText)
  148.  
  149.   -- High score
  150.   local highScoreText = "High Score: " .. highScore
  151.   monitor.setTextColor(colors.yellow)
  152.   monitor.setCursorPos(math.floor(width/2) - math.floor(#highScoreText/2), math.floor(height/3) + 4)
  153.   monitor.write(highScoreText)
  154.  
  155.   -- Play again button
  156.   local buttonWidth = 16
  157.   local buttonHeight = 3
  158.   local buttonX = math.floor(width/2) - math.floor(buttonWidth/2)
  159.   local buttonY = math.floor(height*2/3)
  160.  
  161.   drawButton(buttonX, buttonY, buttonWidth, buttonHeight, "Play Again", colors.lime, colors.white)
  162.  
  163.   return {
  164.     playAgainButton = {x = buttonX, y = buttonY, width = buttonWidth, height = buttonHeight}
  165.   }
  166. end
  167.  
  168. -- Load high score
  169. local function loadHighScore()
  170.   if fs.exists("flappy_highscore") then
  171.     local file = fs.open("flappy_highscore", "r")
  172.     local highScore = tonumber(file.readAll()) or 0
  173.     file.close()
  174.     return highScore
  175.   end
  176.   return 0
  177. end
  178.  
  179. -- Save high score
  180. local function saveHighScore(newScore)
  181.   local highScore = loadHighScore()
  182.   if newScore > highScore then
  183.     local file = fs.open("flappy_highscore", "w")
  184.     file.write(tostring(newScore))
  185.     file.close()
  186.     return newScore
  187.   end
  188.   return highScore
  189. end
  190.  
  191. -- Main game function
  192. local function playGame()
  193.   -- Game state
  194.   local bird = {
  195.     y = math.floor(height/2),
  196.     velocity = 0,
  197.     frame = 0
  198.   }
  199.  
  200.   local pipes = {}
  201.   local score = 0
  202.   local gameOver = false
  203.   local scrollOffset = 0
  204.  
  205.   -- Initialize pipes
  206.   for i = 1, 5 do
  207.     local pipeX = width + (i - 1) * PIPE_SPACING
  208.     -- Ensure valid gap position
  209.     local minGap = CEILING_HEIGHT + PIPE_GAP/2 + 2
  210.     local maxGap = height - GROUND_HEIGHT - PIPE_GAP/2 - 2
  211.    
  212.     -- If screen is too small, just put gap in middle
  213.     local gapY
  214.     if minGap >= maxGap then
  215.       gapY = math.floor(height / 2)
  216.     else
  217.       gapY = math.random(minGap, maxGap)
  218.     end
  219.    
  220.     table.insert(pipes, {
  221.       x = pipeX,
  222.       gapY = gapY,
  223.       passed = false
  224.     })
  225.   end
  226.  
  227.   -- Draw the game (optimized for performance)
  228.   local function drawGame()
  229.     -- Clear screen with sky color
  230.     monitor.setBackgroundColor(colors.cyan)
  231.     monitor.clear()
  232.    
  233.     -- Draw pipes
  234.     for _, pipe in ipairs(pipes) do
  235.       if pipe.x + PIPE_WIDTH >= 1 and pipe.x <= width then
  236.         -- Draw top pipe
  237.         monitor.setBackgroundColor(colors.green)
  238.         for y = CEILING_HEIGHT, pipe.gapY - PIPE_GAP/2 - 1 do
  239.           monitor.setCursorPos(pipe.x, y)
  240.           monitor.write(string.rep(" ", PIPE_WIDTH))
  241.         end
  242.        
  243.         -- Draw bottom pipe
  244.         for y = pipe.gapY + PIPE_GAP/2 + 1, height - GROUND_HEIGHT do
  245.           monitor.setCursorPos(pipe.x, y)
  246.           monitor.write(string.rep(" ", PIPE_WIDTH))
  247.         end
  248.       end
  249.     end
  250.    
  251.     -- Draw ground
  252.     monitor.setBackgroundColor(colors.green)
  253.     for y = height - GROUND_HEIGHT + 1, height do
  254.       monitor.setCursorPos(1, y)
  255.       monitor.write(string.rep(" ", width))
  256.     end
  257.    
  258.     -- Draw bird
  259.     local birdColor = colors.yellow
  260.     if gameOver then
  261.       birdColor = colors.red
  262.     end
  263.    
  264.     monitor.setBackgroundColor(birdColor)
  265.     monitor.setCursorPos(BIRD_X, math.floor(bird.y))
  266.     monitor.write("  ")
  267.     monitor.setCursorPos(BIRD_X, math.floor(bird.y) + 1)
  268.     monitor.write("  ")
  269.    
  270.     -- Draw wing (flapping animation)
  271.     monitor.setBackgroundColor(colors.orange)
  272.     if bird.frame % 6 < 3 then
  273.       monitor.setCursorPos(BIRD_X - 1, math.floor(bird.y))
  274.     else
  275.       monitor.setCursorPos(BIRD_X - 1, math.floor(bird.y) + 1)
  276.     end
  277.     monitor.write(" ")
  278.    
  279.     -- Draw eye
  280.     monitor.setBackgroundColor(colors.black)
  281.     monitor.setCursorPos(BIRD_X + 1, math.floor(bird.y))
  282.     monitor.write(" ")
  283.    
  284.     -- Draw score
  285.     monitor.setBackgroundColor(colors.black)
  286.     monitor.setTextColor(colors.white)
  287.     local scoreText = "Score: " .. score
  288.     monitor.setCursorPos(5, 2)
  289.     monitor.write(scoreText)
  290.   end
  291.  
  292.   -- Check collisions
  293.   local function checkCollisions()
  294.     local birdTop = math.floor(bird.y)
  295.     local birdBottom = birdTop + 1
  296.     local birdLeft = BIRD_X
  297.     local birdRight = BIRD_X + 1
  298.    
  299.     -- Check ceiling collision
  300.     if birdTop <= CEILING_HEIGHT then
  301.       return true
  302.     end
  303.    
  304.     -- Check ground collision
  305.     if birdBottom >= height - GROUND_HEIGHT then
  306.       return true
  307.     end
  308.    
  309.     -- Check pipe collisions
  310.     for _, pipe in ipairs(pipes) do
  311.       if pipe.x <= birdRight and pipe.x + PIPE_WIDTH > birdLeft then
  312.         -- Bird is horizontally within pipe
  313.         if birdTop <= pipe.gapY - PIPE_GAP/2 or birdBottom >= pipe.gapY + PIPE_GAP/2 then
  314.           -- Bird is vertically outside gap
  315.           return true
  316.         end
  317.       end
  318.     end
  319.    
  320.     return false
  321.   end
  322.  
  323.   -- Update game state
  324.   local function updateGame()
  325.     -- Update bird
  326.     bird.velocity = bird.velocity + GRAVITY
  327.     bird.y = bird.y + bird.velocity
  328.     bird.frame = bird.frame + 1
  329.    
  330.     -- Update scroll offset (for parallax)
  331.     scrollOffset = scrollOffset + PIPE_SPEED
  332.    
  333.     -- Update pipes
  334.     for i, pipe in ipairs(pipes) do
  335.       pipe.x = pipe.x - PIPE_SPEED
  336.      
  337.       -- Check if bird passed pipe
  338.       if not pipe.passed and pipe.x + PIPE_WIDTH < BIRD_X then
  339.         pipe.passed = true
  340.         score = score + 1
  341.         playSound("score")
  342.       end
  343.      
  344.       -- If pipe is off screen, recycle it
  345.       if pipe.x + PIPE_WIDTH < 0 then
  346.         -- Find the rightmost pipe
  347.         local rightmostX = 0
  348.         for _, p in ipairs(pipes) do
  349.           rightmostX = math.max(rightmostX, p.x)
  350.         end
  351.        
  352.         -- Recycle this pipe
  353.         pipe.x = rightmostX + PIPE_SPACING
  354.        
  355.         -- Ensure valid gap position
  356.         local minGap = CEILING_HEIGHT + PIPE_GAP/2 + 2
  357.         local maxGap = height - GROUND_HEIGHT - PIPE_GAP/2 - 2
  358.        
  359.         -- If screen is too small, just put gap in middle
  360.         if minGap >= maxGap then
  361.           pipe.gapY = math.floor(height / 2)
  362.         else
  363.           pipe.gapY = math.random(minGap, maxGap)
  364.         end
  365.        
  366.         pipe.passed = false
  367.       end
  368.     end
  369.    
  370.     -- Check for collisions
  371.     if checkCollisions() then
  372.       gameOver = true
  373.       playSound("hit")
  374.       os.sleep(0.3)
  375.       playSound("die")
  376.     end
  377.   end
  378.  
  379.   -- Handle bird flap
  380.   local function flapBird()
  381.     if not gameOver then
  382.       bird.velocity = FLAP_POWER
  383.       playSound("flap")
  384.     end
  385.   end
  386.  
  387.   -- THIS IS THE KEY CHANGE FOR SMOOTHNESS:
  388.   -- Use a timer-based approach instead of parallel API
  389.  
  390.   local function runGame()
  391.     local lastTime = os.clock()
  392.     local updateInterval = 0.01  
  393.    
  394.     -- Initial draw
  395.     drawGame()
  396.    
  397.     -- Set up a timer for regular updates
  398.     local updateTimer = os.startTimer(updateInterval)
  399.    
  400.     while not gameOver do
  401.       local event, p1, p2, p3 = os.pullEvent()
  402.      
  403.       if event == "timer" and p1 == updateTimer then
  404.         -- It's time to update the game
  405.         updateGame()
  406.         drawGame()
  407.        
  408.         -- Set the next timer
  409.         updateTimer = os.startTimer(updateInterval)
  410.       elseif event == "monitor_touch" then
  411.         -- Handle touch input
  412.         flapBird()
  413.       end
  414.     end
  415.    
  416.     -- One final draw to show the crashed bird
  417.     drawGame()
  418.   end
  419.  
  420.   -- Start the game
  421.   playSound("start")
  422.   runGame()
  423.  
  424.   -- Game over handling
  425.   os.sleep(1)
  426.   local highScore = saveHighScore(score)
  427.   local buttons = drawGameOverScreen(score, highScore)
  428.  
  429.   -- Wait for touch on play again button
  430.   while true do
  431.     local event, side, touchX, touchY = os.pullEvent("monitor_touch")
  432.     if isInside(touchX, touchY,
  433.                buttons.playAgainButton.x,
  434.                buttons.playAgainButton.y,
  435.                buttons.playAgainButton.width,
  436.                buttons.playAgainButton.height) then
  437.       playSound("button")
  438.       return -- Exit to main function which will restart the game
  439.     end
  440.   end
  441. end
  442.  
  443. -- Main program loop
  444. while true do
  445.   local buttons = drawStartScreen()
  446.  
  447.   -- Wait for touch on start button
  448.   while true do
  449.     local event, side, touchX, touchY = os.pullEvent("monitor_touch")
  450.     if isInside(touchX, touchY,
  451.                buttons.startButton.x,
  452.                buttons.startButton.y,
  453.                buttons.startButton.width,
  454.                buttons.startButton.height) then
  455.       playSound("button")
  456.       break
  457.     end
  458.   end
  459.  
  460.   -- Start the game
  461.   playGame()
  462. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement