gÀÌÀÀÌÀÌ ÌÌÌÀÌ ÀÌ ÀÌ ÌÌÌÌÀÌ ÌÌÌÀÌ ÀÌ ÀÌ ÀÌ ÌÌÌÀÌÌÌ ÌÌ ÌÀÌÀÀÀÀÀÌÌ ÌÌÌ Ì ÌÌÌÌ À À À ÀÌ ÀÌÌ ÌÌÌÌÌÀÌÌ ÌÌÌÌÀÌÌÀ ÀÀ ÀÌÌÌÌÌÌÌÀÌÌÌÌÌÌ ÌÌÀÌÌ À ÀÌÀÌÌÌÀÌ ÀÀÌÌÌ ÀÌÌÌÀÌÀÌÀÌÀÌÀÀÌÀÀÀÌÀÀÌÀÀÀÌÀÀÀÌÌÌÐÌÌÌÝÝÝÌÌÌÀÌ ÌÌÀÜ Ì Ì Ì À À ÀÀ ÀÌÌÌÌÌÀÌÌÀÀÀÀ ÀÌ ÀÌ À À À ÌÌÌ ÌÌÌÌÌ ÌÌ ÀÌÌÌÌÌÀÌÌÌÌÌÌÌÌÌÌÀ ÀÀ ÀÀÌÌÌÌÌÌ ÀÌÌÌ ÌÌ ÀÀ ÀÀÌÀÌÀÌÀÌÌÌÌÀÌ ÌÌÀÌÌÀÌÀÌÌÌÀÌ ÀÌÀÀÌÀÌÌÌ ÌÌÌÀÌÀÌÀÌÀÌÀÌÌÌÀÌÌÌÀÌÀÌÀÌÌÌÀÌÌÌ ÌÌÌÀÀÌÀÀÌ ÌÌÀÀÌÀÀÌÌÌÌÌÌÌÀÌ ÀÌ ÀÌ ÌÌÌÀÌ ÌÀÌ ÀÌ ÀÌ ÀÌ ÌÌÌÀÌ ÌÐÌÌÝÝÌÜÐÌÝ ÝÌÌÜÝÝ ÀÌÀÌÌÌÌÌÀÌÌ Ì Ì ÌÌÌ ÌÀÌ ÀÀ À ÀÌ ÀÌ À ÀÌ ÀÌ ÀÌÌÐÌÌÝÝÌÌÀÌÌÌÌÌÌÌÌÌÜÝ ÌÌÌÀÌÌÌÀÌ ÌÀÌÌÀ ÀÌÌÀÌ ÌÀÌÌÌ ÌÌÌÀÌÌÌÀÌ ÌÀÌ ÌÀ ÌÀÌÌÀÌ ÌÀÌÌÌ ÌÌÌÀÌÌÌÀÌÌÌÀÌÌÀ ÀÌÀÌÌÌÀÌÌÌÀÌÌÌ ÌÌÌ ÌÌ ÀÌÌ ÀÌ ÌÌÌÌÌÐÌÌÌÝÝÝÜÐÌÌ ÝÝÌÌÀÌÌÀÌÜÐÌÝ Ý Ì ÌÌÜÝÝ   `` `À?îîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîààÀÌ àÀÌ àÀÌ ààÀÌ ààÀÌ ààÀÌ ààÀÌ ÌÌààÀÌ ÌÌààÀÌ ÌÌàÀÌ ÌÌÀàÀÌ ÀàÀÌ ÀÌàÀÌ àÀÌ àÀÌ ààÀÌ ààààààààààààààààààààààààààààààààààààààÀÌÌÌÌàÀÌÌÌÌàÀÌÌÌÌàÀÌÌÌÌàÀÌÌÌÌàÀÌÌÌÌàÌÌÌÀÌÌ àÌÌÌÀÌÌ àÌÌÌÀÌÌ ÌÌÌÀÌÌ àÌÌÌÀÌÌ ÌÌÌÀÌÌ àÌÌÌÌ Ì ÌÌÌÌ Ì àÌÌÌÌ Ì ÌÌÌÌ Ì àÌÌÌÌ Ì ÌÌÌÌ Ì àÌÌÌÌ ÌÌÌÌ àÌÌÌÌ àÌÌÌÌ àÌÌÌÌ àÌÌÌÌ àÀÌÌÌÌàÀÌÌÌÌàÀÌÌÌÌàÀÌÌÌÌàÀÌÌÌÌàÀÌÌÌÌàààààààààààààààààààààààààààààààààÀÌÌÌ ÀÌÌÌÌ ÀÌÌ ÀÌÌÌ ÀÌ À ÀàÀÌÌÌ ÀÌÌÌÌ ÀÌÌ ÀÌÌÌ ÀÌ À Ì àÀÌ À ÀÌ ÀÌìÀ ÀÌ À ÀÌ À ÀÌÌàÀÌ À ÀÌ ÀÌ À ÀÌ À ÀÌ À Ì àÀÌ À ÀÌÌÌ ÀÌ À ÀÌ À ÀÌÌÌ Ì àÀÌ À ÀÌÌÌ ÀÌ À ÀÌ À ÀÌÌÌ àÀÌÌÌÀÌÌÌ ÀÌ ÀÌÌÌÌ ÀÌ À ÀÌ ÌÌÌ àÀÌÌÌÌ ÀÌÌÌ ÀÌ ÀÌÌÌÌ ÀÌ À ÀÌ ÀÌÌÌÌàÌ Ì Ì ÀÌÀÌ À ÀÌÌÌÌ ÀÌ À ÀÌÌÌ ÀÌ Ì ÀÌ ÌÌàÀÌÌÌ Ì ÀÀÌÀÌ À ÀÌÌÌÌ ÀÌìÀ ÀÌÌÌ ÀÌ Ì ÀÌÀÌàÌÌÌ Ì Ì ÀÌàÀÌ À ÌàÌ Ì Ì Àà ÀÌ ÌÌàÌ Ì Ì À à ÀÀÌ ÌÌàÌ ÌÌÌÌ ÀàÀ ÀÌÌÌÌàÍÌÌÌ À à ÀÐÌÌÌÜàÐÝÝÝÀÌ ÌÀ ÝÝÝ àÀÌÌÌÀÌÌÌÀ ÀÌÌÌÀÌÌ ÀÌ Ì ÌÌÌ ÌÌÌ ÌÌÌ àÌ ÌÌÌÌ ÌÌÌÌ À ÀÌÀÌÀÌÀ ÌÀ ÀÀ ÀÌÌÌÌÀÌÌÌÌÀÌÌÌÌàÌ Ì Ì Ì Ì ÀÌ ÌÀÌÌÌÀÌÀ À ÀÌ ÌÌÀÌ ÌÌÀÌ ÌÌàÌ ÌÌ Ì Ì Ì À ÀÌÌÌÌÌ À ÌÌÀÌÌÌÀÌ ÌÌÀÌ ÀÌàÌÌÌ ÌÌÀÌ ÌÌÀÌ Ì À ÌÀ ÌÀ ÌàÀÌÌÌ ÌÌ Ì Ì àÀÌÌÌÀÌÀÌÀÌ ÀÌàÌ Ì Ì Ì Ì àÀÌ ÌÌÀÌ ÌÌÀÌ ÌÌàÀÌÌÌÌ ÌÌÌÌ àÀÌÌÌÌÀÌÌÌÌÀÌÌÌÌàÍÌÌÌ ÍÌÌÌ àÐÌÌÌÜÐÌÌÌÜÐÌÌÌÜàÐÝÝÝÐÝÝÝàÝÝÝ ÝÝÝ ÝÝÝ àààÌ àÌ àÀÌÌàÌ àÀàààààààààààÀÌ ààÀÌ àÀÌ àÀÌ àÀÌ àÀÌ àÀÌ àÀÌ ààÀÌ ààÀÌ ààÀÌ ààÀÌ ààÀÌ ààÀÌ àÀÌ àÀÌ àààîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîî3-- title: Pong -- author: walrusbane -- desc: A PONG implementation following a tutorial by Zach Attakk Codes (https://www.youtube.com/@ZachAttakkCodes), with a few modifications -- license : CC Attribution-NonCommercial (BY-NC 4.0) -- script: lua -- menu: KEYBOARD CONTROLS SHARED CONTROLLER STATES = { GAMEPLAY = 0, WAIT_START = 1, START_MENU = 2 } -- GAME GLOBALS p1_score = 0 p2_score = 0 game_speed = 1.0 STATE = STATES.WAIT_START active_player = 1 keyboard = true menu_item = 0 -- RENDER GLOBALS padding = 3 paddle_w = 4 paddle_h = 16 ball_size = 4 game_col = 12 weak_col = 14 net_height = 6 -- SOUND GLOBALS border_bounce_sfx = 0 spaddle_bounce_sfx = 1 -- PLAYER OBJECT function newPlayer( id, x, y, btn_up, btn_dn, btn_ok, key_up, key_dn, key_ok, speed ) paddle = { id = id, x = x, y = y, btn_up = btn_up, btn_dn = btn_dn, btn_ok = btn_ok, key_up = key_up, key_dn = key_dn, key_ok = key_ok, prev_collide = false, prev_move = 1, speed = speed, dy = 0 } function paddle:handle_input() self.dy = 0 -- up and down if (not keyboard and btn(self.btn_up)) or (keyboard and key(self.key_up)) then self.dy = -1 self.prev_move = -1 end if (not keyboard and btn(self.btn_dn)) or (keyboard and key(self.key_dn)) then self.dy = 1 self.prev_move = 1 end self:update() end -- paddle:handle_input function paddle:handle_inputs_start() if (not keyboard and btn(self.btn_ok)) or (keyboard and key(self.key_ok)) then STATE = STATES.GAMEPLAY sfx(3) end end function paddle:update() self.y = self.y + self.dy * self.speed * game_speed if self.y <= padding then self.y = padding end if self.y + paddle_h >= 136 - padding then self.y = 136 - padding - paddle_h end end -- paddle:update return paddle end -- newPlayer -- PLAYER INSTANCES PADDLES = { P1 = newPlayer( 1, padding, padding, 0, -- D-pad up 1, -- D-pad down 2, -- D-pad left 1, -- A 26,-- Z 24,-- X 1.2 ), P2 = newPlayer( 2, 240 - padding - paddle_w, 136 - padding - paddle_h, 7, -- Controller Y 4, -- Controller A 5, -- Controller B 58,-- Arrow up 59,-- Arrow down 60,-- Arrow left 1.2 ) } -- BALL OBJECT BALL = { x = 120 - ball_size, y = 66, dx = 1, dy = -1 } function BALL:check_paddle_collision(paddle) if (self.x < paddle.x + paddle_w) and (self.x + ball_size > paddle.x) and (self.y < paddle.y + paddle_h) and (self.y + ball_size > paddle.y) then return true end end function BALL:handle_vborder_collision() if (self.y < padding) or (self.y + ball_size > 135 - padding) then self.dy = -self.dy sfx(border_bounce_sfx) end end -- BALL:handle_vborder_collisison function BALL:handle_paddle_collision(paddle) local pad_hit = self:check_paddle_collision(paddle) if pad_hit and not paddle.prev_collide then sfx(paddle_bounce_sfx) game_speed = math.min(game_speed + 0.2, 3.9) self.dx = -self.dx self.dy = self.dy + paddle.dy * 0.5 self.dy = clamp(self.dy, -2, 2) end paddle.prev_collide = pad_hit end -- BALL:handle_paddle_collision function BALL:move() -- move the ball self.x = self.x + self.dx * game_speed self.y = self.y + self.dy * game_speed end -- check_paddle_collision function RESET() if active_player == 1 then BALL.x = PADDLES.P1.x + paddle_w + 3 BALL.y = PADDLES.P1.y + paddle_h/2 - ball_size/2 else BALL.x = PADDLES.P2.x - paddle_w - 3 BALL.y = PADDLES.P2.y + paddle_h/2 - ball_size/2 end game_speed = 1 STATE = STATES.WAIT_START end -- RESET function BOOT() RESET() STATE = STATES.START_MENU end -- BOOT function TIC() if STATE == STATES.WAIT_START then INPUTS_START() INPUTS_GAME() UPDATES_START() elseif STATE == STATES.GAMEPLAY then INPUTS_GAME() UPDATES_GAME() end if (STATE == STATES.GAMEPLAY) or (STATE == STATES.WAIT_START) then DRAW_GAME() if STATE == STATES.WAIT_START then DRAW_START() end elseif STATE == STATES.START_MENU then INPUTS_MENU() DRAW_MENU() end end -- TIC function INPUTS_GAME() -- handle paddle inputs for _, pad in pairs(PADDLES) do pad:handle_input() end end -- INPUTS_GAME function INPUTS_START() for _, pad in pairs(PADDLES) do if pad.id == active_player then pad:handle_inputs_start() end end end function INPUTS_MENU() if btnp(0) then menu_item = menu_item - 1 sfx(1) elseif btnp(1) then menu_item = menu_item + 1 sfx(1) end clamp(menu_item, 0, 1) local confirm_buttons = {4, 5, 6, 7} local confirm_keys = {48, 50} for b = 1, #confirm_buttons do if btnp(confirm_buttons[b]) then MENU(menu_item) STATE = STATES.WAIT_START sfx(3) end end for k = 1, #confirm_keys do if keyp(confirm_keys[k]) then MENU(menu_item) STATE = STATES.WAIT_START sfx(3) end end end function UPDATES_GAME() for _, paddle in pairs(PADDLES) do -- check whether paddles have left the screen BALL:handle_paddle_collision(paddle) end BALL:handle_vborder_collision() BALL:move() HANDLE_SCORING() end function UPDATES_START() for _, paddle in pairs(PADDLES) do if STATE == STATES.WAIT_START and paddle.id==active_player then BALL.y = paddle.y + paddle_h/2 - ball_size/2 BALL.dy = paddle.prev_move end end end function DRAW_GAME() cls() -- border rectb(0, 0, 240, 136, weak_col) -- net for _i = 5, 130, net_height*2 do rect(119, _i, 2, net_height, weak_col) end -- paddles for _, paddle in pairs(PADDLES) do rect(paddle.x, paddle.y, paddle_w, paddle_h, game_col) end -- ball rect(BALL.x, BALL.y, ball_size, ball_size, game_col) end -- DRAW_GAME function DRAW_START() -- Scores print_center(p1_score, 43, 3, 61) print_center(p2_score, 43, 3, 181) print_center("READY", 80, 2) print_center("Player "..active_player, 95, 1) -- Ball direction indicator local v_invert = 0 local h_invert = 0 if BALL.dy == 1 then v_invert = 2 end if BALL.dx == -1 then h_invert = 1 end spr(256, BALL.x-2, BALL.y-2, 0, 1, h_invert+v_invert) -- Controls if keyboard then spr(265, 41, 80, 0, 1, 0, 0, 4, 4) spr(320, 164, 80, 0, 1, 0, 0, 4, 4) else spr(257, 41, 80, 0, 1, 0, 0, 4, 4) spr(261, 167, 80, 0, 1, 0, 0, 4, 4) end end -- DRAW_START function DRAW_MENU() cls(0) print_center("PONG", 30, 5) print_center("Select Input:", 70) print_center("Keyboard Controls", 80) print_center("Shared Controller", 90) rectb(67, 77 + menu_item*10, 107, 11, 12) end function HANDLE_SCORING() if BALL.x > 240 then p1_score = p1_score + 1 active_player = 1 RESET() sfx(2) elseif BALL.x + ball_size < 1 then p2_score = p2_score + 1 active_player = 2 RESET() sfx(2) end end -- HELPERS function print_center(text, height, scale, v_axis) local scale = scale or 1 local v_axis = v_axis or 121 local length = print(text, 0, -200, 12, true, scale) print(text, v_axis - length/2, height, 12, true, scale) end function MENU(id) if id == 0 then keyboard = true elseif id == 1 then keyboard = false end end function clamp(val, min_val, max_val) if val > max_val then return max_val elseif val < min_val then return min_val end return val end