Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- Set up local variables
- local args = {...}
- local sub = string.sub
- local width, height = term.getSize()
- local bricks, balls, paddle
- local messages = {
- "Use the keyboard or mouse to move the paddle.",
- "To start your turn click or press enter.",
- "To pause the game press 'p'.",
- "The goal is to break all the bricks using the ball.",
- "The ball will bounce off of bricks and the paddle.",
- "You lose a turn if the balls fall below the paddle.",
- "After the last turn ends the game is over.",
- }
- local damage = {
- ".",
- ":",
- "|",
- "&",
- }
- local game = {}
- local state = {
- gameRunning = true,
- gamePaused = false,
- turnStarted = false,
- turnsLeft = 4,
- currentTurn = 1,
- level = args[1] or 1,
- score = 0,
- wantedPaddlePositionX = math.floor(width*.5),
- }
- local powerUps
- -- Vector2 class --
- local Vector2 = {}
- do
- Vector2._index = function(self, key)
- if key == "x" or key == "y" then
- return self[key]
- end
- return Vector2[key]
- end
- local pcall, type, tonumber, setmetatable = pcall, type, tonumber, setmetatable
- local sin, cos, rad, max = math.sin, math.cos, math.rad, math.max
- local new = function(X, Y)
- if not pcall(tonumber, X) then
- error("bad argument #1 to 'new' (number expected, got "..type(X)..")", 2)
- end
- if not pcall(tonumber, Y) then
- error("bad argument #2 to 'new' (number expected, got "..type(Y)..")", 2)
- end
- return setmetatable({X = tonumber(X), Y = tonumber(Y)}, Vector2)
- end
- local newFromAngleInDegrees = function(Degrees, Scale)
- local radians = rad(Degrees)
- return setmetatable({X = cos(radians)*Scale, Y = sin(radians)*Scale}, Vector2)
- end
- local newFromAngleInRadians = function(Radians, Scale)
- return setmetatable({X = cos(Radians)*Scale, Y = sin(Radians)*Scale}, Vector2)
- end
- local linearInpoleration = function(obj1, obj2, percent)
- return obj1 + (obj2 - obj1)*percent
- end
- local unit = function(self)
- local maximum = max(self.X, self.Y)
- return setmetatable({X = self.X/maximum, X = self.Y/maximum}, Vector2)
- end
- Vector2.new = new
- Vector2.Lerp = linearInpoleration
- Vector2.Unit = unit
- Vector2.__unm = function(self) return setmetatable({X = -self.X, Y = -self.Y}, Vector2) end
- Vector2.__mod = function(obj1, obj2)
- if type(obj1) == "table" and type(obj2) == "table" then
- return setmetatable({X = obj1.X % obj2.X, Y = obj1.Y % obj2.Y}, Vector2)
- end
- if type(obj1) == "table" then
- return setmetatable({X = obj1.X % obj2, Y = obj1.Y % obj2}, Vector2)
- elseif type(obj2) == "table" then
- return setmetatable({X = obj1 % obj2.X, Y = obj1 % obj2.Y}, Vector2)
- end
- end
- Vector2.__div = function(obj1, obj2)
- if type(obj1) == "table" and type(obj2) == "table" then
- return setmetatable({X = obj1.X / obj2.X, Y = obj1.Y / obj2.Y}, Vector2)
- end
- if type(obj1) == "table" then
- return setmetatable({X = obj1.X / obj2, Y = obj1.Y / obj2}, Vector2)
- elseif type(obj2) == "table" then
- return setmetatable({X = obj1 / obj2.X, Y = obj1 / obj2.Y}, Vector2)
- end
- end
- Vector2.__mul = function(obj1, obj2)
- if type(obj1) == "table" and type(obj2) == "table" then
- return setmetatable({X = obj1.X * obj2.X, Y = obj1.Y * obj2.Y}, Vector2)
- end
- if type(obj1) == "table" then
- return setmetatable({X = obj1.X * obj2, Y = obj1.Y * obj2}, Vector2)
- elseif type(obj2) == "table" then
- return setmetatable({X = obj1 * obj2.X, Y = obj1 * obj2.Y}, Vector2)
- end
- end
- Vector2.__sub = function(obj1, obj2)
- if type(obj1) == "table" and type(obj2) == "table" then
- return setmetatable({X = obj1.X - obj2.X, Y = obj1.Y - obj2.Y}, Vector2)
- end
- if type(obj1) == "table" then
- return setmetatable({X = obj1.X - obj2, Y = obj1.Y - obj2}, Vector2)
- elseif type(obj2) == "table" then
- return setmetatable({X = obj1 - obj2.X, Y = obj1 - obj2.Y}, Vector2)
- end
- end
- Vector2.__add = function(obj1, obj2)
- if type(obj1) == "table" and type(obj2) == "table" then
- return setmetatable({X = obj1.X + obj2.X, Y = obj1.Y + obj2.Y}, Vector2)
- end
- if type(obj1) == "table" then
- return setmetatable({X = obj1.X + obj2, Y = obj1.Y + obj2}, Vector2)
- elseif type(obj2) == "table" then
- return setmetatable({X = obj1 + obj2.X, Y = obj1 + obj2.Y}, Vector2)
- end
- end
- local epsilon = 0.0005
- Vector2.__eq = function(obj1, obj2)
- if type(obj1) == "table" and type(obj2) == "table" then
- return math.abs((obj1.X or obj1[1]) - (obj2.X or obj2[1])) < epsilon
- and math.abs((obj1.Y or obj1[2]) - (obj2.Y or obj2[2])) < epsilon
- end
- return false
- end
- Vector2.__lt = function(obj1, obj2)
- if type(obj1) == "table" and type(obj2) == "table" then
- return (obj1.X or obj1[1] < obj2.X or obj2[1]) and (obj1.Y or obj1[2] < obj2.Y or obj2[2])
- end
- return false
- end
- Vector2.__le = function(obj1, obj2)
- if type(obj1) == "table" and type(obj2) == "table" then
- return (obj1.X or obj1[1] <= obj2.X or obj2[1]) and (obj1.Y or obj1[2] <= obj2.Y or obj2[2])
- end
- return false
- end
- Vector2.__tostring = function(self)
- return (self.X or 0)..", "..(self.Y or 0)
- end
- end
- -- Set up colors --
- local tColorLookup = {}
- for n=1, 16 do
- tColorLookup[ string.byte( "0123456789abcdef",n,n ) ] = 2^(n-1)
- end
- local tColorToString = {}
- for n=1, 16 do
- tColorToString[ 2^(n-1) ] = string.sub( "0123456789abcdef",n,n )
- end
- local bgColor = colors.black
- local ballColor = colors.blue
- local paddleColor = colors.white
- local brickColors = {}
- -- Set the brick colors
- if term.isColor() then
- brickColors = {
- colors.red,
- colors.brown,
- colors.gray,
- }
- elseif term.blit then
- brickColors = {
- colors.gray,
- colors.lightGray,
- }
- ballColor = colors.white
- else
- brickColors = {colors.white}
- ballColor = colors.white
- end
- -- Brick class --
- local Brick = {}
- Brick.__index = Brick
- -- Creates a new brick.
- function Brick.new(Position, Size, Color, BrickID)
- local self = setmetatable({}, Brick)
- self.Position = Position
- self.Size = Size
- self.Color = Color
- --self.Text = " "
- self.IsBrick = true
- self.BrickID = BrickID
- -- Brick health
- if state.level >= 4 then
- self.Health = 1 + math.random(0, math.floor((state.level - 1)/4))
- self.MaxHealth = self.Health
- end
- -- Select a random power up.
- for key, powerUp in pairs(powerUps) do
- local selectPowerUp = math.random()
- if state.level >= (powerUp.LevelToStartSpawning or 1) then
- if selectPowerUp <= (powerUp.Chance or .025) then
- self.PowerUp = powerUp
- self.Color = powerUp.BrickColor
- self.Text = powerUp.BrickText
- break
- end
- end
- end
- return self
- end
- -- Draws the brick on to the screen.
- function Brick:Draw()
- local str
- if self.Health then
- str = string.rep(self.Text or (damage[math.floor(((self.MaxHealth - self.Health)/self.MaxHealth)*#damage)] or " "), self.Size.X)
- else
- str = string.rep(self.Text or " ", self.Size.X)
- end
- term.setBackgroundColor(self.Color)
- for y = self.Position.Y, self.Position.Y + self.Size.Y - 1 do
- term.setCursorPos(self.Position.X - math.floor(self.Size.X*.5), y)
- term.write(str)
- end
- end
- -- Damages the brick.
- function Brick:Damage(ball)
- state.score = state.score + 1
- if self.Health then
- self.Health = self.Health - ball.Strength
- if self.Health <= 0 then
- self:Destroy(ball)
- end
- else
- self:Destroy(ball)
- end
- end
- -- Destroys the brick.
- function Brick:Destroy(ball)
- if self.PowerUp then
- self.PowerUp.OnDestroy(self, ball)
- end
- bricks[self.BrickID] = nil
- state.score = state.score + 1
- -- Check if there are any bricks left.
- local bricksLeft = 0
- for key, value in pairs(bricks) do
- bricksLeft = bricksLeft + 1
- break
- end
- if bricksLeft <= 0 then
- game.GoToNextLevel()
- end
- end
- -- Ball class --
- local Ball = {}
- Ball.__index = Ball
- -- Creates a new ball.
- function Ball.new(Position, BallID)
- local self = setmetatable({}, Ball)
- self.Position = Position
- self.Velocity = Vector2.new(0, -1)
- self.Strength = 1
- self.BallID = BallID
- return self
- end
- -- Handles collision with other entities.
- function Ball:DoCollision(otherItem)
- local objCenter, objWidth = otherItem.Position.X, otherItem.Size.X
- local offset = (self.Position.X - objCenter)/objWidth
- self.Velocity = Vector2.new(offset, -self.Velocity.Y)
- -- If it is a brick then destroy it.
- if otherItem.IsBrick then
- otherItem:Damage(self)
- end
- end
- -- Updates the ball.
- function Ball:Update()
- -- Try to collide with the bricks.
- for key, brick in pairs(bricks) do
- if self.Position.X >= math.ceil(brick.Position.X - brick.Size.X*.5) and self.Position.X <= math.floor(brick.Position.X + (brick.Size.X - 1)*.5) then
- if self.Position.Y >= brick.Position.Y and self.Position.Y <= brick.Position.Y + brick.Size.Y then
- self:DoCollision(brick)
- break
- end
- end
- end
- -- Try to collide with the paddle.
- if self.Position.X >= paddle.Position.X - paddle.Size.X*.5 and self.Position.X <= paddle.Position.X + paddle.Size.X*.5 then
- if self.Position.Y >= paddle.Position.Y and self.Position.Y <= paddle.Position.Y + paddle.Size.Y then
- self:DoCollision(paddle)
- end
- end
- -- Try to collide with the wall.
- if self.Position.X <= 1 then
- self.Velocity.X = math.abs(self.Velocity.X)
- elseif self.Position.X >= width then
- self.Velocity.X = -math.abs(self.Velocity.X)
- elseif self.Position.Y <= 1 then
- self.Velocity.Y = math.abs(self.Velocity.Y)
- elseif self.Position.Y > height then
- -- The player lost the ball by letting it touch the bottom of the screen.
- self:Destroy()
- return
- end
- -- Shift the ball.
- self.Position = self.Position + self.Velocity
- end
- -- Draws the ball to the screen.
- function Ball:Draw()
- term.setCursorPos(self.Position.X, self.Position.Y)
- term.setBackgroundColor(ballColor)
- term.write("#")
- end
- -- Destroys the ball.
- function Ball:Destroy()
- table.remove(balls, self.BallID)
- for key, ball in ipairs(balls) do
- ball.BallID = key
- end
- -- Check if there are any balls left, and if no balls are left then make the player lose a turn.
- local ballsLeft = #balls
- if ballsLeft <= 0 then
- game.ReloadLevel(false, true)
- end
- end
- -- Paddle class --
- local Paddle = {}
- Paddle.__index = Paddle
- -- Creates a new paddle.
- function Paddle.new(Position)
- local self = setmetatable({}, Paddle)
- self.Position = Position
- self.Size = Vector2.new(7, 1)
- return self
- end
- -- Updates the paddle.
- function Paddle:Update()
- -- Move the paddle closer to where the player wants it.
- local offset = (state.wantedPaddlePositionX - self.Position.X)
- if math.abs(offset) > 0.5 then
- self.Position = self.Position + Vector2.new((offset/math.abs(offset)), 0)
- end
- end
- -- Draws the paddle to the screen.
- function Paddle:Draw()
- term.setCursorPos(self.Position.X - math.floor(self.Size.X*.5), self.Position.Y)
- term.setBackgroundColor(paddleColor)
- term.write(string.rep(" ", self.Size.X))
- end
- -- Makes the game progress to the next level.
- function game.GoToNextLevel()
- sleep(.5)
- state.level = state.level + 1
- game.ReloadLevel(true)
- end
- -- Reloads the level, by resetting the ball and paddle, and also the bricks if requested.
- function game.ReloadLevel(ReloadBricks, TurnLost)
- -- Check if we lost a turn.
- if TurnLost then
- state.turnsLeft = state.turnsLeft - 1
- if state.turnsLeft <= 0 then
- state.turnsLeft = 0
- state.gameRunning = false -- End the game.
- return false
- end
- state.currentTurn = state.currentTurn + 1
- end
- -- Reload the level.
- state.turnStarted = false
- state.wantedPaddlePositionX = math.floor(width*.5)
- -- Reset the paddle.
- paddle = Paddle.new(Vector2.new(math.floor(width*.5), height - 1))
- -- Reset the ball.
- balls = {Ball.new(Vector2.new(width*.5, height - 2), 1)}
- -- If we need to then reload the bricks as well.
- if ReloadBricks then
- math.randomseed(state.level)
- bricks = {}
- local startPos = width%4
- for y = 1, math.min(math.floor((state.level - 1)*.4) + 4, height - 5) do
- for x = startPos, -startPos + width + 4, 4 do
- bricks[#bricks + 1] = Brick.new(Vector2.new(x, y), Vector2.new(4, 1), brickColors[(#bricks%#brickColors) + 1], #bricks + 1)
- end
- end
- end
- end
- -- Draws on to the screen.
- function game.Draw()
- term.setBackgroundColor(bgColor)
- term.clear()
- local topLineBgColors
- if term.blit then
- -- Set up the topLineBgColors for the term.blit operation we will do later.
- topLineBgColors = {}
- for x = 1, width do
- topLineBgColors[x] = tColorToString[bgColor]
- end
- -- Draw the bricks.
- for key, brick in pairs(bricks) do
- -- If the brick is on the top line, add it to the topLineBgColors table.
- if brick.Position.Y == 1 then
- local left = brick.Position.X - math.floor(brick.Size.X*.5)
- local right = brick.Position.X + math.floor(brick.Size.X*.5) - 1
- for x = left, math.floor(right) do
- if x >= 1 and x <= width then
- topLineBgColors[x] = tColorToString[brick.Color]
- end
- end
- if brick.Size.Y > 1 then
- brick:Draw()
- end
- else
- brick:Draw()
- end
- end
- else
- -- Since blit is not available, we simply draw the bricks.
- for key, brick in pairs(bricks) do
- brick:Draw()
- end
- end
- -- Draw text on the top line of the terminal if term.blit exists, or just below the paddle if not.
- if term.blit then
- -- Draw the text at the top of the screen.
- local topLineText = string.rep(" ", width)
- local topLineTxtColorString = string.rep("0", width)
- local topLineBGString = table.concat(topLineBgColors, "")
- local scoreText = "Score "..state.score
- local levelText = "Level "..state.level
- local turnText = "Turn "..(state.currentTurn).." of "..(state.turnsLeft - 1 + state.currentTurn)
- topLineText = sub(scoreText, 1, 12) .. sub(topLineText, #scoreText + 1, 12) .. sub(levelText, 1, 12) .. sub(topLineText, #levelText + 13, 24) .. sub(turnText, 1, width - 24) .. sub(topLineText, #turnText + 25)
- term.setCursorPos(1, 1)
- term.blit(topLineText, topLineTxtColorString, topLineBGString)
- elseif (state.currentTurn > 1 or state.level > 1) then
- -- Draw the text at the bottom of the screen.
- term.setBackgroundColor(bgColor)
- term.setCursorPos(1, height)
- term.write("Score "..state.score)
- term.setCursorPos(13, height)
- term.write("Level "..state.level)
- term.setCursorPos(25, height)
- term.write("Turn "..(state.currentTurn).." of " .. (state.turnsLeft - 1 + state.currentTurn))
- end
- -- Draw the paddle.
- paddle:Draw()
- -- Draw the balls.
- for key, ball in pairs(balls) do
- ball:Draw()
- end
- -- Draw help at the bottom of the screen.
- if #messages > 0 then
- if not state.turnStarted and (term.blit or state.currentTurn <= 1 or state.level <= 1) then
- term.setCursorPos(1, height)
- term.write(messages[math.floor(((os.clock()/5)%#messages) + 1)])
- end
- end
- end
- -- Finish the powerUps
- powerUps = {
- ["ExtraTurn"] = {
- Name = "Extra Turn",
- --LevelToStartSpawning = 1,
- OnDestroy = function(self, ball)
- state.turnsLeft = state.turnsLeft + 1
- end,
- BrickText = "#",
- BrickColor = colors.blue,
- },
- ["DoubleBall"] = {
- Name = "Double Ball",
- --LevelToStartSpawning = 2,
- OnDestroy = function(self, ball)
- balls[#balls + 1] = Ball.new(self.Position, #balls + 1)
- end,
- BrickText = "2",
- BrickColor = colors.blue,
- },
- ["SuperBall"] = {
- Name = "Super Ball",
- --LevelToStartSpawning = 5,
- OnDestroy = function(self, ball)
- ball.Strength = ball.Strength + 1
- end,
- BrickText = "S",
- BrickColor = colors.red,
- },
- ["WiderPaddle"] = {
- Name = "Wide Paddle",
- --LevelToStartSpawning = 13,
- OnDestroy = function(self, ball)
- paddle.Size.X = paddle.Size.X + 6
- end,
- BrickText = "W",
- BrickColor = colors.green,
- },
- }
- -- Load the level.
- game.ReloadLevel(true)
- game.Draw()
- -- Game loop --
- local timerUsed = os.startTimer(.05) -- Start a timer to make the game continue on its own
- while state.gameRunning do
- -- Handle events.
- local event, arg1, arg2, arg3, arg4, arg5 = os.pullEventRaw()
- if event == "key" then
- if arg1 == keys.left or arg1 == keys.a then
- state.wantedPaddlePositionX = math.max(state.wantedPaddlePositionX - 1, math.floor(paddle.Size.X*.5))
- elseif arg1 == keys.right or arg1 == keys.d then
- state.wantedPaddlePositionX = math.min(state.wantedPaddlePositionX + 1, math.ceil(width - paddle.Size.X*.5))
- elseif arg1 == keys.space or arg1 == keys.enter then
- state.turnStarted = true
- elseif arg1 == keys.p or arg1 == keys.esc then
- state.gamePaused = not state.gamePaused
- end
- if not state.turnStarted then
- for key, ball in pairs(balls) do
- ball.Position = paddle.Position + Vector2.new(0, -1)
- end
- end
- elseif event == "mouse_click" and arg1 == 1 then
- state.turnStarted = true
- state.wantedPaddlePositionX = math.max(math.min(arg2, math.ceil(width - paddle.Size.X*.5)), math.floor(paddle.Size.X*.5))
- elseif event == "timer" and arg1 == timerUsed then
- -- Start updating.
- if not state.gamePaused then
- paddle:Update()
- if state.turnStarted then
- for key = 1, #balls do
- if balls[key] then
- balls[key]:Update()
- end
- end
- else
- for key, ball in pairs(balls) do
- ball.Position = paddle.Position + Vector2.new(0, -1)
- end
- end
- end
- -- Draw to the screen.
- game.Draw()
- timerUsed = os.startTimer(.05) -- Start a timer to make the game continue on its own again
- elseif event == "terminate" then
- state.gameRunning = false
- end
- end
- -- The game has ended.
- term.setBackgroundColor(colors.black)
- term.setTextColor(colors.white)
- term.setCursorPos(1, 1)
- term.clear()
- if state.turnsLeft <= 0 then
- print("Game over! No more turns left.")
- end
- print("You obtained a score of "..state.score.." points.")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement