Pong Wars is a mesmerizing game concept where two balls represent day and night, each painting the canvas with their respective colors as they bounce around. In this tutorial, we'll implement this captivating game in LiveCode, creating a visual battle between light and dark.
The game mechanics are straightforward:
Let's build this game from scratch using LiveCode's powerful event-driven programming environment.
We'll start by defining our game structure and global variables. This setup will establish the foundation for our game, including constants, colors, and the grid system.
-- Global variables global gCanvasWidth, gCanvasHeight global gSquareSize, gNumSquaresX, gNumSquaresY global gDayColor, gNightColor, gDayBallColor, gNightBallColor global gSquares, gBalls global gDayScore, gNightScore global gMinSpeed, gMaxSpeed global gIteration -- Set up constants and initial variables on preOpenStack -- Set canvas dimensions put 600 into gCanvasWidth put 600 into gCanvasHeight -- Set colors put "224,232,227" into gDayColor -- MysticMint put "17,76,90" into gNightColor -- NocturnalExpedition put "17,43,54" into gDayBallColor -- OceanicNoir put "224,232,227" into gNightBallColor -- MysticMint -- Set game parameters put 25 into gSquareSize put 5 into gMinSpeed put 10 into gMaxSpeed -- Calculate grid dimensions put gCanvasWidth div gSquareSize into gNumSquaresX put gCanvasHeight div gSquareSize into gNumSquaresY -- Initialize scores put 0 into gDayScore put 0 into gNightScore -- Initialize iteration counter put 0 into gIteration end preOpenStack
Next, we'll set up our game board with a grid of squares and initialize the two balls - one for day and one for night. This is a continuation of our preOpenStack
handler.
-- Continue the preOpenStack handler -- Set up the squares array (one half day, one half night) repeat with i = 1 to gNumSquaresX repeat with j = 1 to gNumSquaresY if i < (gNumSquaresX / 2) then put gDayColor into gSquares[i,j] else put gNightColor into gSquares[i,j] end if end repeat end repeat -- Set up the balls -- Ball 1 (Day ball) put gCanvasWidth / 4 into tX1 put gCanvasHeight / 2 into tY1 put 8 into tDx1 put -8 into tDy1 -- Ball 2 (Night ball) put (gCanvasWidth / 4) * 3 into tX2 put gCanvasHeight / 2 into tY2 put -8 into tDx2 put 8 into tDy2 -- Create balls array put empty into gBalls -- Ball 1 put tX1 into gBalls[1]["x"] put tY1 into gBalls[1]["y"] put tDx1 into gBalls[1]["dx"] put tDy1 into gBalls[1]["dy"] put gDayColor into gBalls[1]["reverseColor"] put gDayBallColor into gBalls[1]["ballColor"] -- Ball 2 put tX2 into gBalls[2]["x"] put tY2 into gBalls[2]["y"] put tDx2 into gBalls[2]["dx"] put tDy2 into gBalls[2]["dy"] put gNightColor into gBalls[2]["reverseColor"] put gNightBallColor into gBalls[2]["ballColor"] -- Start the game loop send "gameLoop" to me in 10 milliseconds end preOpenStack
The heart of any game is its game loop. In LiveCode, we implement this using message passing to create a recursive loop that updates and renders the game state.
-- Main game loop on gameLoop updateGame drawGame add 1 to gIteration if gIteration mod 1000 = 0 then put "Iteration:" && gIteration end if send "gameLoop" to me in 10 milliseconds end gameLoop -- Update game state on updateGame -- Reset scores put 0 into gDayScore put 0 into gNightScore -- Update scores based on squares repeat with i = 1 to gNumSquaresX repeat with j = 1 to gNumSquaresY if gSquares[i,j] = gDayColor then add 1 to gDayScore else if gSquares[i,j] = gNightColor then add 1 to gNightScore end if end repeat end repeat -- Update ball positions and check for collisions repeat with tBallIndex = 1 to 2 checkSquareCollision tBallIndex checkBoundaryCollision tBallIndex -- Update ball position add gBalls[tBallIndex]["dx"] to gBalls[tBallIndex]["x"] add gBalls[tBallIndex]["dy"] to gBalls[tBallIndex]["y"] -- Add randomness to ball movement addRandomness tBallIndex end repeat -- Update score display put "Day" && gDayScore && "| Night" && gNightScore into field "scoreField" end updateGame
Now, let's implement the drawing functions that will create and update our visual elements on the screen.
-- Draw the game on drawGame -- Clear the canvas set the rect of graphic "canvas" to 0,0,gCanvasWidth,gCanvasHeight -- Draw squares drawSquares -- Draw balls repeat with tBallIndex = 1 to 2 drawBall tBallIndex end repeat end drawGame -- Draw the grid of squares on drawSquares lock screen repeat with i = 1 to gNumSquaresX repeat with j = 1 to gNumSquaresY -- Calculate position put (i - 1) * gSquareSize into tX put (j - 1) * gSquareSize into tY -- Create or update square graphic put "square_" & i & "_" & j into tSquareName if there is a graphic tSquareName then set the rect of graphic tSquareName to tX,tY,tX+gSquareSize,tY+gSquareSize set the backgroundColor of graphic tSquareName to gSquares[i,j] else create graphic tSquareName set the style of graphic tSquareName to "rectangle" set the rect of graphic tSquareName to tX,tY,tX+gSquareSize,tY+gSquareSize set the backgroundColor of graphic tSquareName to gSquares[i,j] set the borderWidth of graphic tSquareName to 0 end if end repeat end repeat unlock screen end drawSquares -- Draw a ball on drawBall pBallIndex put "ball_" & pBallIndex into tBallName -- Get ball properties put gBalls[pBallIndex]["x"] into tX put gBalls[pBallIndex]["y"] into tY put gBalls[pBallIndex]["ballColor"] into tColor put gSquareSize / 2 into tRadius -- Create or update ball graphic if there is a graphic tBallName then set the loc of graphic tBallName to tX,tY else create graphic tBallName set the style of graphic tBallName to "oval" set the backgroundColor of graphic tBallName to tColor set the borderWidth of graphic tBallName to 0 end if -- Set ball size set the width of graphic tBallName to tRadius * 2 set the height of graphic tBallName to tRadius * 2 set the loc of graphic tBallName to tX,tY end drawBall
lock screen
and unlock screen
commands prevent flickering during updates.
A key aspect of our game is collision detection - both with squares and boundaries. Let's implement these vital functions.
-- Check if ball collides with squares and change their color on checkSquareCollision pBallIndex put gBalls[pBallIndex]["x"] into tBallX put gBalls[pBallIndex]["y"] into tBallY put gBalls[pBallIndex]["reverseColor"] into tReverseColor put gSquareSize / 2 into tRadius -- Check multiple points around the ball's circumference repeat with tAngle = 0 to 359 step 45 put tBallX + cos(tAngle * pi / 180) * tRadius into tCheckX put tBallY + sin(tAngle * pi / 180) * tRadius into tCheckY -- Convert to grid coordinates put floor(tCheckX / gSquareSize) + 1 into tI put floor(tCheckY / gSquareSize) + 1 into tJ -- Check if within bounds and not already the reverse color if tI >= 1 and tI <= gNumSquaresX and tJ >= 1 and tJ <= gNumSquaresY then if gSquares[tI,tJ] <> tReverseColor then -- Square hit! Update square color put tReverseColor into gSquares[tI,tJ] -- Determine bounce direction based on the angle if abs(cos(tAngle * pi / 180)) > abs(sin(tAngle * pi / 180)) then put -gBalls[pBallIndex]["dx"] into gBalls[pBallIndex]["dx"] else put -gBalls[pBallIndex]["dy"] into gBalls[pBallIndex]["dy"] end if -- Exit after handling collision exit repeat end if end if end repeat end checkSquareCollision -- Check if ball hits canvas boundaries on checkBoundaryCollision pBallIndex put gBalls[pBallIndex]["x"] into tBallX put gBalls[pBallIndex]["y"] into tBallY put gBalls[pBallIndex]["dx"] into tBallDx put gBalls[pBallIndex]["dy"] into tBallDy put gSquareSize / 2 into tRadius -- Check x boundaries if (tBallX + tBallDx > gCanvasWidth - tRadius) or (tBallX + tBallDx < tRadius) then put -tBallDx into gBalls[pBallIndex]["dx"] end if -- Check y boundaries if (tBallY + tBallDy > gCanvasHeight - tRadius) or (tBallY + tBallDy < tRadius) then put -tBallDy into gBalls[pBallIndex]["dy"] end if end checkBoundaryCollision
To make our game more interesting, we'll add slight randomness to the ball movement. We'll also create the user interface to complete our game.
-- Add slight randomness to ball movement on addRandomness pBallIndex -- Get current speeds put gBalls[pBallIndex]["dx"] into tDx put gBalls[pBallIndex]["dy"] into tDy -- Add small random changes add (random(100) / 5000 - 0.01) to tDx add (random(100) / 5000 - 0.01) to tDy -- Limit maximum speed if tDx > gMaxSpeed then put gMaxSpeed into tDx if tDx < -gMaxSpeed then put -gMaxSpeed into tDx if tDy > gMaxSpeed then put gMaxSpeed into tDy if tDy < -gMaxSpeed then put -gMaxSpeed into tDy -- Ensure minimum speed if abs(tDx) < gMinSpeed then if tDx > 0 then put gMinSpeed into tDx else put -gMinSpeed into tDx end if end if if abs(tDy) < gMinSpeed then if tDy > 0 then put gMinSpeed into tDy else put -gMinSpeed into tDy end if end if -- Update ball speeds put tDx into gBalls[pBallIndex]["dx"] put tDy into gBalls[pBallIndex]["dy"] end addRandomness -- Setup the UI on createInterface -- Create main canvas if there is not a graphic "canvas" then create graphic "canvas" set the style of graphic "canvas" to "rectangle" set the rect of graphic "canvas" to 0,0,gCanvasWidth,gCanvasHeight set the backgroundColor of graphic "canvas" to "white" set the borderWidth of graphic "canvas" to 0 end if -- Create score display if there is not a field "scoreField" then create field "scoreField" set the rect of field "scoreField" to 0,gCanvasHeight+10,gCanvasWidth,gCanvasHeight+40 set the textAlign of field "scoreField" to "center" set the textFont of field "scoreField" to "courier" set the textSize of field "scoreField" to 14 end if -- Create credits if there is not a field "creditsField" then create field "creditsField" set the rect of field "creditsField" to 0,gCanvasHeight+50,gCanvasWidth,gCanvasHeight+80 set the textAlign of field "creditsField" to "center" set the textSize of field "creditsField" to 10 put "Pong Wars - Day vs Night" into field "creditsField" end if end createInterface -- Stack initialization on openStack createInterface end openStack
We've built a complete Pong Wars game in LiveCode from scratch! The game showcases many of LiveCode's strengths:
The result is a visually captivating game where day and night balls compete to claim territory on the game board. The collision physics, the color changes, and the slight randomness in ball movement all combine to create an engaging and somewhat hypnotic experience.
This project demonstrates how LiveCode can be used to create interactive visualizations and games with relatively little code. The readability of LiveCode's syntax and its object-oriented approach make it an excellent choice for projects like this.
Feel free to experiment by changing colors, adjusting speeds, or adding new features to make the game your own!
Here are some ideas for extending the game: