̬̬̬̈̬̬̬̬̈̬̬̬̬̬̪쬪̬̬̬̪쬪`ff``fffffmm```fffݐ ݝݝ "" """""--  """OHAffffffff``````` """""""" ````````     ffff```````ffff """" """" >= """" --    """"""""""""""                      """"""""ffffffff```````H```````````````ffffffffffffff```` mm``ffff           ݝݝ  -- title: Pong -- author: frantisek -- desc: Classic Pong with cars - player vs AI, with lane traffic obstacles -- script: lua local W,H=240,136 local paddleW,paddleH=4,24 local p1y=H/2-paddleH/2 local p2y=H/2-paddleH/2 local p1score,p2score=0,0 local speed=2 local minVY=0.6 -- minimum vertical ball speed after a top/bottom wall bounce local ballX,ballY,ballDX,ballDY -- vehicle obstacles: fixed lanes, one driving down, one driving up. -- two vehicle types share the same lanes and keep a minimum gap so they -- never drive through each other. local vehicles={} local carW=16 local vehicleTypes={ {tiles={32,34,36}, h=32, shape="box"}, -- lorries (2x4 tiles) - rectangular hitbox {tiles={40,42,44}, h=24, shape="ellipse"}, -- passenger cars (2x3 tiles) - ellipse hitbox } local laneDownX=W/2-24 -- lane 1: vehicles travel top -> bottom local laneUpX=W/2+8 -- lane 2: vehicles travel bottom -> top local spawnTimerDown,spawnTimerUp=0,0 local spawnDelay=90 local minGap=10 -- minimum bumper-to-bumper gap kept between vehicles in a lane -- simple procedural 8-bit background loop (uses the default sfx sample at varying pitch) local melody={"C-3","E-3","G-3","C-4","G-3","E-3","D-3","G-3"} local melodyIndex=1 local musicTimer=0 local musicDelay=20 local function resetBall(dir) ballX,ballY=W/2,H/2 ballDX=speed*dir ballDY=(math.random(0,1)==0) and 1 or -1 end local function spawnVehicle(lane,dir) local t=vehicleTypes[math.random(1,#vehicleTypes)] local v={ x=lane, y=(dir==1) and -t.h or H, w=carW, h=t.h, dir=dir, speed=0.7+math.random(0,10)/10, tile=t.tiles[math.random(1,#t.tiles)], shape=t.shape } table.insert(vehicles,v) end -- resolves ball vs a single vehicle's rectangle, returns true if a bounce happened local function bounceOffVehicle(v) local bs=4 if ballX+bs>v.x and ballXv.y and ballY0 and val or 0.0001) ballX=ex+dx*t-bs/2 ballY=ey+dy*t-bs/2 end local dot=ballDX*nx+ballDY*ny if dot<0 then ballDX=ballDX-2*dot*nx ballDY=ballDY-2*dot*ny end sfx(0,"C-2",10,2,14,-3) -- low thud for hitting a vehicle return true end return false end -- ellipsoid-style paddle deflection: hit position along the paddle bends the angle local function paddleBounce(paddleY,dir) local ballCenterY=ballY+2 local paddleCenterY=paddleY+paddleH/2 local relative=(ballCenterY-paddleCenterY)/(paddleH/2) if relative>1 then relative=1 elseif relative<-1 then relative=-1 end local maxAngle=math.rad(60) local angle=relative*maxAngle local totalSpeed=math.sqrt(ballDX*ballDX+ballDY*ballDY) if totalSpeedb.y end) -- most-advanced (largest y) first else table.sort(list,function(a,b) return a.y1 then local leader=list[i-1] if dir==1 then local maxY=leader.y-v.h-minGap if newY>maxY then newY=maxY end else local minY=leader.y+leader.h+minGap if newYballY+4 then p2y=p2y-1.5 end -- clamp paddles to screen if p1y<0 then p1y=0 end if p1y>H-paddleH then p1y=H-paddleH end if p2y<0 then p2y=0 end if p2y>H-paddleH then p2y=H-paddleH end -- spawn vehicles into each fixed lane on its own timer spawnTimerDown=spawnTimerDown+1 if spawnTimerDown>=spawnDelay then spawnTimerDown=0 spawnVehicle(laneDownX,1) end spawnTimerUp=spawnTimerUp+1 if spawnTimerUp>=spawnDelay then spawnTimerUp=0 spawnVehicle(laneUpX,-1) end -- split vehicles by lane/direction, advance each lane keeping spacing, -- then drop anything that has fully left the screen local downList,upList={},{} for _,v in ipairs(vehicles) do if v.dir==1 then table.insert(downList,v) else table.insert(upList,v) end end advanceLane(downList,1) advanceLane(upList,-1) for i=#vehicles,1,-1 do local v=vehicles[i] if (v.dir==1 and v.y>H) or (v.dir==-1 and v.y<-v.h) then table.remove(vehicles,i) end end -- background music: cycle through a short arpeggio loop musicTimer=musicTimer+1 if musicTimer>=musicDelay then musicTimer=0 sfx(0,melody[melodyIndex],musicDelay-2,1,6,0) melodyIndex=melodyIndex%#melody+1 end -- move ball ballX=ballX+ballDX ballY=ballY+ballDY -- bounce off top/bottom walls; always leave with enough vertical speed -- so the ball can't get stuck skimming back and forth along an edge if ballY<=0 then ballY=0 ballDY=math.abs(ballDY) if ballDY=H-4 then ballY=H-4 ballDY=-math.abs(ballDY) if ballDY>-minVY then ballDY=-minVY end end -- left paddle collision (ellipsoid deflection) if ballX<=8+paddleW and ballX>=8 and ballY+4>=p1y and ballY<=p1y+paddleH and ballDX<0 then paddleBounce(p1y,1) ballX=8+paddleW end -- right paddle collision (ellipsoid deflection) if ballX>=W-8-paddleW-4 and ballX<=W-8 and ballY+4>=p2y and ballY<=p2y+paddleH and ballDX>0 then paddleBounce(p2y,-1) ballX=W-8-paddleW-4 end -- ball vs vehicles (only resolve one hit per frame to keep it simple) for _,v in ipairs(vehicles) do local hit if v.shape=="ellipse" then hit=ellipseBounce(v) else hit=bounceOffVehicle(v) end if hit then break end end -- scoring if ballX<0 then p2score=p2score+1 resetBall(1) elseif ballX>W then p1score=p1score+1 resetBall(-1) end cls(0) -- dashed center line for i=0,H,8 do rect(W/2-1,i,2,4,15) end -- vehicles: composite sprite, 2 tiles wide, height depends on vehicle type -- (4 tiles tall for lorries, 3 for cars). Flipped vertically for the -- downward lane so the nose faces the direction of travel. for _,v in ipairs(vehicles) do local flip=(v.dir==1) and 2 or 0 spr(v.tile,v.x,v.y,0,1,flip,0,2,v.h/8) end -- paddles and ball rect(8,p1y,paddleW,paddleH,12) rect(W-8-paddleW,p2y,paddleW,paddleH,12) rect(ballX,ballY,4,4,15) -- score print(tostring(p1score),W/2-20,10,15) print(tostring(p2score),W/2+14,10,15) end