Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Flappy Bird for ComputerCraft
- -- Designed for a 5x2 monitor setup
- -- Find peripherals
- local monitor = peripheral.find("monitor")
- local speaker = peripheral.find("speaker")
- if not monitor then
- print("No monitor found! Please attach a monitor.")
- return
- end
- -- Set up the display
- monitor.setTextScale(0.5)
- local width, height = monitor.getSize()
- print("Monitor size: " .. width .. "x" .. height)
- -- Game constants
- local GRAVITY = 0.25
- local FLAP_POWER = -0.8
- local BIRD_X = 20
- local PIPE_SPEED = 0.7
- local PIPE_WIDTH = 6
- local PIPE_GAP = 10
- local PIPE_SPACING = 35
- local GROUND_HEIGHT = 4
- local CEILING_HEIGHT = 1
- -- Sound effects
- local function playSound(name)
- if not speaker then return end
- if name == "flap" then
- speaker.playNote("harp", 1.0, 12)
- elseif name == "score" then
- speaker.playNote("bell", 1.0, 18)
- os.sleep(0.05)
- speaker.playNote("bell", 1.0, 20)
- elseif name == "hit" then
- speaker.playNote("bass", 1.0, 8)
- os.sleep(0.05)
- speaker.playNote("bass", 1.0, 6)
- elseif name == "die" then
- speaker.playNote("bass", 1.0, 10)
- os.sleep(0.1)
- speaker.playNote("bass", 1.0, 8)
- os.sleep(0.1)
- speaker.playNote("bass", 1.0, 6)
- os.sleep(0.1)
- speaker.playNote("bass", 1.0, 4)
- elseif name == "start" then
- speaker.playNote("harp", 1.0, 14)
- os.sleep(0.1)
- speaker.playNote("harp", 1.0, 16)
- os.sleep(0.1)
- speaker.playNote("harp", 1.0, 18)
- elseif name == "button" then
- speaker.playNote("harp", 1.0, 16)
- end
- end
- -- Helper function to draw a button
- local function drawButton(x, y, w, h, text, bgColor, textColor)
- monitor.setBackgroundColor(bgColor)
- for i = 0, h - 1 do
- monitor.setCursorPos(x, y + i)
- monitor.write(string.rep(" ", w))
- end
- monitor.setTextColor(textColor)
- local textX = x + math.floor((w - #text) / 2)
- local textY = y + math.floor(h / 2)
- monitor.setCursorPos(textX, textY)
- monitor.write(text)
- end
- -- Helper function to check if a point is inside a rectangle
- local function isInside(px, py, x, y, w, h)
- return px >= x and px < x + w and py >= y and py < y + h
- end
- -- Draw the start screen
- local function drawStartScreen()
- monitor.setBackgroundColor(colors.cyan)
- monitor.clear()
- -- Draw ground
- monitor.setBackgroundColor(colors.green)
- for y = height - GROUND_HEIGHT + 1, height do
- monitor.setCursorPos(1, y)
- monitor.write(string.rep(" ", width))
- end
- -- Draw title
- local title = "FLAPPY BIRD"
- monitor.setBackgroundColor(colors.cyan)
- monitor.setTextColor(colors.yellow)
- monitor.setCursorPos(math.floor(width/2) - math.floor(#title/2), math.floor(height/3))
- monitor.write(title)
- -- Draw bird
- local birdY = math.floor(height/2)
- monitor.setBackgroundColor(colors.yellow)
- monitor.setCursorPos(BIRD_X, birdY)
- monitor.write(" ")
- monitor.setCursorPos(BIRD_X, birdY + 1)
- monitor.write(" ")
- -- Draw wing
- monitor.setBackgroundColor(colors.orange)
- monitor.setCursorPos(BIRD_X - 1, birdY)
- monitor.write(" ")
- -- Draw eye
- monitor.setBackgroundColor(colors.black)
- monitor.setCursorPos(BIRD_X + 1, birdY)
- monitor.write(" ")
- -- Start button
- local buttonWidth = 16
- local buttonHeight = 3
- local buttonX = math.floor(width/2) - math.floor(buttonWidth/2)
- local buttonY = math.floor(height*2/3)
- drawButton(buttonX, buttonY, buttonWidth, buttonHeight, "Start Game", colors.lime, colors.white)
- return {
- startButton = {x = buttonX, y = buttonY, width = buttonWidth, height = buttonHeight}
- }
- end
- -- Draw game over screen
- local function drawGameOverScreen(score, highScore)
- monitor.setBackgroundColor(colors.black)
- monitor.clear()
- -- Game over text
- local title = "GAME OVER"
- monitor.setTextColor(colors.red)
- monitor.setCursorPos(math.floor(width/2) - math.floor(#title/2), math.floor(height/3))
- monitor.write(title)
- -- Score
- local scoreText = "Score: " .. score
- monitor.setTextColor(colors.yellow)
- monitor.setCursorPos(math.floor(width/2) - math.floor(#scoreText/2), math.floor(height/3) + 2)
- monitor.write(scoreText)
- -- High score
- local highScoreText = "High Score: " .. highScore
- monitor.setTextColor(colors.yellow)
- monitor.setCursorPos(math.floor(width/2) - math.floor(#highScoreText/2), math.floor(height/3) + 4)
- monitor.write(highScoreText)
- -- Play again button
- local buttonWidth = 16
- local buttonHeight = 3
- local buttonX = math.floor(width/2) - math.floor(buttonWidth/2)
- local buttonY = math.floor(height*2/3)
- drawButton(buttonX, buttonY, buttonWidth, buttonHeight, "Play Again", colors.lime, colors.white)
- return {
- playAgainButton = {x = buttonX, y = buttonY, width = buttonWidth, height = buttonHeight}
- }
- end
- -- Load high score
- local function loadHighScore()
- if fs.exists("flappy_highscore") then
- local file = fs.open("flappy_highscore", "r")
- local highScore = tonumber(file.readAll()) or 0
- file.close()
- return highScore
- end
- return 0
- end
- -- Save high score
- local function saveHighScore(newScore)
- local highScore = loadHighScore()
- if newScore > highScore then
- local file = fs.open("flappy_highscore", "w")
- file.write(tostring(newScore))
- file.close()
- return newScore
- end
- return highScore
- end
- -- Main game function
- local function playGame()
- -- Game state
- local bird = {
- y = math.floor(height/2),
- velocity = 0,
- frame = 0
- }
- local pipes = {}
- local score = 0
- local gameOver = false
- local scrollOffset = 0
- -- Initialize pipes
- for i = 1, 5 do
- local pipeX = width + (i - 1) * PIPE_SPACING
- -- Ensure valid gap position
- local minGap = CEILING_HEIGHT + PIPE_GAP/2 + 2
- local maxGap = height - GROUND_HEIGHT - PIPE_GAP/2 - 2
- -- If screen is too small, just put gap in middle
- local gapY
- if minGap >= maxGap then
- gapY = math.floor(height / 2)
- else
- gapY = math.random(minGap, maxGap)
- end
- table.insert(pipes, {
- x = pipeX,
- gapY = gapY,
- passed = false
- })
- end
- -- Draw the game (optimized for performance)
- local function drawGame()
- -- Clear screen with sky color
- monitor.setBackgroundColor(colors.cyan)
- monitor.clear()
- -- Draw pipes
- for _, pipe in ipairs(pipes) do
- if pipe.x + PIPE_WIDTH >= 1 and pipe.x <= width then
- -- Draw top pipe
- monitor.setBackgroundColor(colors.green)
- for y = CEILING_HEIGHT, pipe.gapY - PIPE_GAP/2 - 1 do
- monitor.setCursorPos(pipe.x, y)
- monitor.write(string.rep(" ", PIPE_WIDTH))
- end
- -- Draw bottom pipe
- for y = pipe.gapY + PIPE_GAP/2 + 1, height - GROUND_HEIGHT do
- monitor.setCursorPos(pipe.x, y)
- monitor.write(string.rep(" ", PIPE_WIDTH))
- end
- end
- end
- -- Draw ground
- monitor.setBackgroundColor(colors.green)
- for y = height - GROUND_HEIGHT + 1, height do
- monitor.setCursorPos(1, y)
- monitor.write(string.rep(" ", width))
- end
- -- Draw bird
- local birdColor = colors.yellow
- if gameOver then
- birdColor = colors.red
- end
- monitor.setBackgroundColor(birdColor)
- monitor.setCursorPos(BIRD_X, math.floor(bird.y))
- monitor.write(" ")
- monitor.setCursorPos(BIRD_X, math.floor(bird.y) + 1)
- monitor.write(" ")
- -- Draw wing (flapping animation)
- monitor.setBackgroundColor(colors.orange)
- if bird.frame % 6 < 3 then
- monitor.setCursorPos(BIRD_X - 1, math.floor(bird.y))
- else
- monitor.setCursorPos(BIRD_X - 1, math.floor(bird.y) + 1)
- end
- monitor.write(" ")
- -- Draw eye
- monitor.setBackgroundColor(colors.black)
- monitor.setCursorPos(BIRD_X + 1, math.floor(bird.y))
- monitor.write(" ")
- -- Draw score
- monitor.setBackgroundColor(colors.black)
- monitor.setTextColor(colors.white)
- local scoreText = "Score: " .. score
- monitor.setCursorPos(5, 2)
- monitor.write(scoreText)
- end
- -- Check collisions
- local function checkCollisions()
- local birdTop = math.floor(bird.y)
- local birdBottom = birdTop + 1
- local birdLeft = BIRD_X
- local birdRight = BIRD_X + 1
- -- Check ceiling collision
- if birdTop <= CEILING_HEIGHT then
- return true
- end
- -- Check ground collision
- if birdBottom >= height - GROUND_HEIGHT then
- return true
- end
- -- Check pipe collisions
- for _, pipe in ipairs(pipes) do
- if pipe.x <= birdRight and pipe.x + PIPE_WIDTH > birdLeft then
- -- Bird is horizontally within pipe
- if birdTop <= pipe.gapY - PIPE_GAP/2 or birdBottom >= pipe.gapY + PIPE_GAP/2 then
- -- Bird is vertically outside gap
- return true
- end
- end
- end
- return false
- end
- -- Update game state
- local function updateGame()
- -- Update bird
- bird.velocity = bird.velocity + GRAVITY
- bird.y = bird.y + bird.velocity
- bird.frame = bird.frame + 1
- -- Update scroll offset (for parallax)
- scrollOffset = scrollOffset + PIPE_SPEED
- -- Update pipes
- for i, pipe in ipairs(pipes) do
- pipe.x = pipe.x - PIPE_SPEED
- -- Check if bird passed pipe
- if not pipe.passed and pipe.x + PIPE_WIDTH < BIRD_X then
- pipe.passed = true
- score = score + 1
- playSound("score")
- end
- -- If pipe is off screen, recycle it
- if pipe.x + PIPE_WIDTH < 0 then
- -- Find the rightmost pipe
- local rightmostX = 0
- for _, p in ipairs(pipes) do
- rightmostX = math.max(rightmostX, p.x)
- end
- -- Recycle this pipe
- pipe.x = rightmostX + PIPE_SPACING
- -- Ensure valid gap position
- local minGap = CEILING_HEIGHT + PIPE_GAP/2 + 2
- local maxGap = height - GROUND_HEIGHT - PIPE_GAP/2 - 2
- -- If screen is too small, just put gap in middle
- if minGap >= maxGap then
- pipe.gapY = math.floor(height / 2)
- else
- pipe.gapY = math.random(minGap, maxGap)
- end
- pipe.passed = false
- end
- end
- -- Check for collisions
- if checkCollisions() then
- gameOver = true
- playSound("hit")
- os.sleep(0.3)
- playSound("die")
- end
- end
- -- Handle bird flap
- local function flapBird()
- if not gameOver then
- bird.velocity = FLAP_POWER
- playSound("flap")
- end
- end
- -- THIS IS THE KEY CHANGE FOR SMOOTHNESS:
- -- Use a timer-based approach instead of parallel API
- local function runGame()
- local lastTime = os.clock()
- local updateInterval = 0.01
- -- Initial draw
- drawGame()
- -- Set up a timer for regular updates
- local updateTimer = os.startTimer(updateInterval)
- while not gameOver do
- local event, p1, p2, p3 = os.pullEvent()
- if event == "timer" and p1 == updateTimer then
- -- It's time to update the game
- updateGame()
- drawGame()
- -- Set the next timer
- updateTimer = os.startTimer(updateInterval)
- elseif event == "monitor_touch" then
- -- Handle touch input
- flapBird()
- end
- end
- -- One final draw to show the crashed bird
- drawGame()
- end
- -- Start the game
- playSound("start")
- runGame()
- -- Game over handling
- os.sleep(1)
- local highScore = saveHighScore(score)
- local buttons = drawGameOverScreen(score, highScore)
- -- Wait for touch on play again button
- while true do
- local event, side, touchX, touchY = os.pullEvent("monitor_touch")
- if isInside(touchX, touchY,
- buttons.playAgainButton.x,
- buttons.playAgainButton.y,
- buttons.playAgainButton.width,
- buttons.playAgainButton.height) then
- playSound("button")
- return -- Exit to main function which will restart the game
- end
- end
- end
- -- Main program loop
- while true do
- local buttons = drawStartScreen()
- -- Wait for touch on start button
- while true do
- local event, side, touchX, touchY = os.pullEvent("monitor_touch")
- if isInside(touchX, touchY,
- buttons.startButton.x,
- buttons.startButton.y,
- buttons.startButton.width,
- buttons.startButton.height) then
- playSound("button")
- break
- end
- end
- -- Start the game
- playGame()
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement