Auto Play Ping/Pong [UAlgo]Auto Play Ping/Pong is a fully self running arcade style mini game built entirely in Pine Script and rendered directly on the chart. Instead of analyzing price, this script turns the chart area into a compact game field where two AI controlled paddles rally a moving ball from one side to the other while the score updates in real time.
The script is designed as a visual and technical showcase of what Pine Script can do with custom objects such as boxes, lines, labels, arrays, and user defined types. It demonstrates persistent state handling, frame by frame physics updates, collision detection, automatic paddle control, scoring logic, and motion trail rendering, all inside a chart overlay.
The left and right paddles are both controlled by simple AI logic. Each paddle reacts to the vertical position of the ball and tries to align itself for the next return. The ball bounces off the top and bottom boundaries, changes direction when it touches a paddle, and resets to the center when one side misses. A trail effect is also added to make movement easier to follow and visually more dynamic.
What makes this script interesting is that it is not simply drawing static shapes. It maintains a persistent game state across updates, modifies that state on every bar, and redraws the field using live object coordinates. This makes it a playful but technically instructive example of animation and object control in Pine Script.
In practical terms, this script is a creative visual project rather than a trading tool. It is useful for demonstrating real time state management, chart object animation, and game style logic inside TradingView.
🔹 Features
🔸 Fully Automated Gameplay
Both paddles are controlled automatically. The script continuously tracks the ball position and moves each paddle vertically to intercept the ball without user input.
🔸 Persistent Game State
The script uses a dedicated game state object to store ball position, paddle positions, scores, trail points, and drawing references. This allows the whole game to evolve smoothly over time.
🔸 Ball Physics and Collision Logic
The ball moves with its own horizontal and vertical velocity, bounces off the top and bottom walls, reacts to paddle contact, and changes its vertical angle depending on where it hits the paddle.
🔸 Score Tracking
If one paddle misses the ball, the opposing side scores a point. The ball then resets to the center and starts a fresh rally with directional variation.
🔸 Paddle AI With Speed Limits
Each paddle follows the ball using its own maximum movement speed. This gives the game a natural chase behavior and prevents instant teleport style motion.
🔸 Motion Trail Effect
The ball leaves a fading trail behind it using a sequence of stored points and prebuilt lines. This improves visual clarity and gives the movement a smoother arcade feel.
🔸 Custom Game Field Rendering
The play area is drawn with a background box, two paddle lines, a circular ball label, a score label, and trail segments. Everything is positioned relative to the current bar index.
🔸 Chart Overlay Animation
The game is drawn directly over the chart with overlay=true , which turns the chart into a moving visual canvas.
🔹 Calculations
1) Defining the Game Geometry and Core Constants
var int GAME_WIDTH = 80
var float GAME_HEIGHT = 100.0
var float PADDLE_H = 20.0
var float BALL_SPD_X = 1.8
var float BALL_SPD_Y = 1.2
var int TRAIL_LEN = 10
var float AI_SPEED_1 = 1.1
var float AI_SPEED_2 = 1.2
This block defines the full physical layout and motion parameters of the game.
GAME_WIDTH sets the horizontal size of the play area.
GAME_HEIGHT sets the vertical size.
PADDLE_H defines paddle height.
BALL_SPD_X and BALL_SPD_Y define the initial ball speed.
TRAIL_LEN defines how many trail segments are stored.
AI_SPEED_1 and AI_SPEED_2 define how quickly each paddle can move.
So before any gameplay starts, the script already establishes the dimensions and motion rules of the whole arena.
2) Defining the Point and Game State Objects
type Point
float x
float y
type GameState
float ball_x
float ball_y
float ball_vx
float ball_vy
float p1_y
float p2_y
int p1_score
int p2_score
box bg_box
line p1_line
line p2_line
label ball_lbl
label score_lbl
array trail_pts
array trail_lines
This is the structural foundation of the script.
The Point type stores one coordinate pair. It is used for the trail system.
The GameState type stores the full live state of the game:
the ball position,
the ball velocity,
the vertical positions of both paddles,
both scores,
the main drawing objects,
and the trail arrays.
This design is important because the script is not just drawing shapes independently. It is managing a complete game world through one persistent object.
3) Creating the Visual Objects on the First Bar
method init_drawings(GameState state) =>
state.bg_box := box.new(na, na, na, na, border_color=color.new(color.gray, 60), border_width=1, bgcolor=C_BG)
state.p1_line := line.new(na, na, na, na, color=C_P1, width=4)
state.p2_line := line.new(na, na, na, na, color=C_P2, width=4)
state.ball_lbl := label.new(na, na, "", color=C_BALL, style=label.style_circle, size=size.small)
state.score_lbl := label.new(na, na, "0 - 0", color=color.new(color.white, 100), textcolor=color.silver, style=label.style_none, size=size.large)
This method creates the core objects that will later be updated every frame.
The script builds:
a background box for the game field,
a line for the left paddle,
a line for the right paddle,
a circular label for the ball,
and a score label.
These are created only once, then reused and repositioned as the game evolves. This is much more efficient than deleting and recreating everything on every update.
4) Preparing the Ball Trail System
for i = 0 to TRAIL_LEN - 1
color fade_color = color.new(C_BALL, 100 - int((TRAIL_LEN - i) * 100 / TRAIL_LEN))
state.trail_lines.push(line.new(na, na, na, na, color=fade_color, width=2))
state.trail_pts.push(Point.new(state.ball_x, state.ball_y))
This loop initializes the trail effect.
For each trail slot, the script creates:
a line object with progressively changing transparency,
and a point initialized at the current ball position.
The idea is simple. The newest trail segments remain more visible, while older trail segments fade away. This creates the illusion of motion persistence behind the ball.
So the trail is not a single effect. It is a chain of stored points and lines that move along with the ball.
5) Updating Ball Position Each Frame
method update_physics(GameState state) =>
state.ball_x += state.ball_vx
state.ball_y += state.ball_vy
This is the first step of the physics engine.
On every update, the ball position is advanced by its horizontal and vertical velocity values. This is the basic motion rule of the game.
If nothing else happened, the ball would keep moving in a straight line forever. The rest of the physics method exists to modify that path through AI movement, wall bounces, paddle collisions, and scoring resets.
6) Left Paddle AI Logic
if state.ball_vx < 0
if state.p1_y + PADDLE_H/2 < state.ball_y
state.p1_y += math.min(AI_SPEED_1, state.ball_y - (state.p1_y + PADDLE_H/2))
else if state.p1_y - PADDLE_H/2 > state.ball_y
state.p1_y -= math.min(AI_SPEED_1, (state.p1_y - PADDLE_H/2) - state.ball_y)
This block controls the left paddle.
The paddle only reacts when the ball is moving toward the left side, which is why the script first checks:
state.ball_vx < 0
Then it compares the ball’s vertical position to the top and bottom edges of the paddle. If the ball is above the paddle center zone, the paddle moves upward. If the ball is below it, the paddle moves downward.
The amount of movement is limited by AI_SPEED_1 , which prevents the paddle from moving instantly.
So the left paddle behaves like a simple tracking AI that tries to align itself with incoming ball position.
7) Right Paddle AI Logic
if state.ball_vx > 0
if state.p2_y + PADDLE_H/2 < state.ball_y
state.p2_y += math.min(AI_SPEED_2, state.ball_y - (state.p2_y + PADDLE_H/2))
else if state.p2_y - PADDLE_H/2 > state.ball_y
state.p2_y -= math.min(AI_SPEED_2, (state.p2_y - PADDLE_H/2) - state.ball_y)
This is the mirror logic for the right paddle.
It only moves when the ball is traveling toward the right side. It uses the same tracking idea as the left paddle, but its maximum speed is set independently by AI_SPEED_2 .
That means each side can have slightly different behavior and difficulty characteristics.
8) Keeping Paddles Inside the Arena
state.p1_y := math.max(PADDLE_H/2, math.min(GAME_HEIGHT - PADDLE_H/2, state.p1_y))
state.p2_y := math.max(PADDLE_H/2, math.min(GAME_HEIGHT - PADDLE_H/2, state.p2_y))
After paddle movement is updated, the script clamps both paddles so they cannot leave the top or bottom of the field.
The center of each paddle must remain between:
PADDLE_H/2
and
GAME_HEIGHT - PADDLE_H/2
This ensures that the visible paddle body never extends outside the game frame.
9) Ball Bounce on Top and Bottom Walls
if state.ball_y >= GAME_HEIGHT
state.ball_y := GAME_HEIGHT
state.ball_vy := -state.ball_vy
else if state.ball_y <= 0
state.ball_y := 0
state.ball_vy := -state.ball_vy
This block handles vertical wall collisions.
If the ball reaches or exceeds the top boundary, its vertical position is snapped to the top edge and its vertical velocity is reversed.
If the ball reaches or drops below the bottom boundary, the same thing happens at the lower edge.
This creates a classic arcade bounce effect where the ball reflects off the horizontal walls and stays inside the arena.
10) Left Side Paddle Collision and Right Side Scoring
if state.ball_x <= 0
if math.abs(state.ball_y - state.p1_y) <= PADDLE_H/2 + 3
state.ball_x := 0
state.ball_vx := -state.ball_vx
state.ball_vy += (state.ball_y - state.p1_y) * 0.15
state.ball_vy := math.max(-4.0, math.min(4.0, state.ball_vy))
else
state.p2_score += 1
state.ball_x := GAME_WIDTH / 2
state.ball_y := GAME_HEIGHT / 2
state.ball_vx := BALL_SPD_X
state.ball_vy := BALL_SPD_Y * (state.p2_score % 2 == 0 ? 1 : -1)
This is one of the main gameplay blocks.
When the ball reaches the left boundary, the script checks whether the ball is close enough to the left paddle vertically. If yes, it counts as a successful return.
On a successful return:
the ball is snapped to the left edge,
its horizontal velocity is reversed,
and its vertical velocity is modified based on where it hit the paddle.
This extra adjustment is important because it creates angled returns rather than perfectly repetitive motion. The farther from the paddle center the hit occurs, the more the vertical speed is changed.
The vertical speed is then clamped between negative four and positive four to keep the game stable.
If the left paddle misses, the right side scores a point. The ball resets to the center, moves back toward the right, and gets a vertical direction that alternates based on score parity.
11) Right Side Paddle Collision and Left Side Scoring
else if state.ball_x >= GAME_WIDTH
if math.abs(state.ball_y - state.p2_y) <= PADDLE_H/2 + 3
state.ball_x := GAME_WIDTH
state.ball_vx := -state.ball_vx
state.ball_vy += (state.ball_y - state.p2_y) * 0.15
state.ball_vy := math.max(-4.0, math.min(4.0, state.ball_vy))
else
state.p1_score += 1
state.ball_x := GAME_WIDTH / 2
state.ball_y := GAME_HEIGHT / 2
state.ball_vx := -BALL_SPD_X
state.ball_vy := BALL_SPD_Y * (state.p1_score % 2 == 0 ? 1 : -1)
This is the mirror version of the left side logic.
When the ball reaches the right boundary, the script tests whether the right paddle is in position. If it is, the ball bounces back left and its vertical speed changes according to impact location. If not, the left player scores and the ball resets to center.
Together, the left and right boundary blocks define the full rally and scoring logic of the game.
12) Updating the Trail Memory
state.trail_pts.unshift(Point.new(state.ball_x, state.ball_y))
state.trail_pts.pop()
After the new ball position is resolved, the script stores it at the front of the trail point array. Then it removes the oldest stored point from the end.
This gives the script a rolling history of recent ball positions. Those points are later used to position each trail segment.
So the trail always follows the newest motion path while keeping a fixed length.
13) Converting Game Coordinates Into Chart Coordinates
method draw_frame(GameState state, int base_x) =>
int right_x = base_x + 5
int left_x = right_x - GAME_WIDTH
This method begins the rendering step.
The game is not drawn in a separate graphics window. It is projected directly onto chart coordinates. The current bar index acts as the base anchor, and the script defines a right edge slightly ahead of it. From that right edge, it subtracts the game width to get the left edge.
So the whole game field is mapped into a section of chart space that moves with the current bar position.
14) Drawing the Background and Paddles
state.bg_box.set_lefttop(left_x, GAME_HEIGHT)
state.bg_box.set_rightbottom(right_x, 0)
state.p1_line.set_xy1(left_x, state.p1_y + PADDLE_H/2)
state.p1_line.set_xy2(left_x, state.p1_y - PADDLE_H/2)
state.p2_line.set_xy1(right_x, state.p2_y + PADDLE_H/2)
state.p2_line.set_xy2(right_x, state.p2_y - PADDLE_H/2)
This block updates the main field and the paddle drawings.
The background box spans from the left edge to the right edge and from zero to the full game height.
The left paddle is drawn as a vertical line on the left boundary.
The right paddle is drawn as a vertical line on the right boundary.
Each paddle extends above and below its center position by half the paddle height. That makes the paddle length consistent and easy to manage mathematically.
15) Drawing the Ball and the Score
state.ball_lbl.set_xy(left_x + int(math.round(state.ball_x)), state.ball_y)
state.score_lbl.set_xy(left_x + GAME_WIDTH/2, GAME_HEIGHT - 10)
state.score_lbl.set_text(str.tostring(state.p1_score) + " - " + str.tostring(state.p2_score))
This block positions the moving ball and updates the scoreboard.
The ball label is placed by adding the ball’s internal game x coordinate to the left boundary of the field. Its y coordinate is the current ball height.
The score label is placed near the top center of the arena and updated with the current left and right scores.
So every frame, the game communicates both live motion and match progress.
16) Drawing the Motion Trail
for i = 0 to TRAIL_LEN - 1
Point p1 = state.trail_pts.get(i)
Point p2 = i + 1 < TRAIL_LEN ? state.trail_pts.get(i + 1) : p1
line l = state.trail_lines.get(i)
l.set_xy1(left_x + int(math.round(p1.x)), p1.y)
l.set_xy2(left_x + int(math.round(p2.x)), p2.y)
This loop converts stored trail points into visible trail segments.
For each trail slot, the script reads one point and the next point after it. Then it updates the corresponding trail line so it connects those two positions.
Because the trail lines were created with different transparency levels earlier, the newest segments appear stronger and older segments fade out.
This gives the ball a continuous motion streak that makes gameplay easier to follow visually.
17) Persistent State Initialization
varip GameState state = GameState.new(
ball_x = GAME_WIDTH / 2,
ball_y = GAME_HEIGHT / 2,
ball_vx = BALL_SPD_X,
ball_vy = BALL_SPD_Y,
p1_y = GAME_HEIGHT / 2,
p2_y = GAME_HEIGHT / 2,
p1_score = 0,
p2_score = 0,
trail_pts = array.new(),
trail_lines = array.new()
)
This block creates the persistent live game state.
The ball starts in the center of the arena.
Both paddles start in the vertical center.
Both scores start at zero.
Empty arrays are prepared for the trail points and trail lines.
The use of varip is important here because it keeps the game state persistent as the script updates, allowing the game to evolve continuously rather than resetting each time.
18) First Bar Initialization and Main Update Loop
if barstate.isfirst
state.init_drawings()
state.update_physics()
state.draw_frame(bar_index)
This is the main execution flow.
On the very first bar, the script creates all required drawings through init_drawings() .
After that, every update performs two steps:
first the game physics are advanced,
then the new state is rendered onto the chart.
This is the standard game loop pattern:
update state,
then draw state.
19) Invisible Plot Anchors
plot(100, color=color.new(color.white, 100))
plot(0, color=color.new(color.white, 100))
These invisible plots help stabilize the vertical scale for the game area.
Because the whole arena is designed between zero and one hundred on the y axis, plotting hidden values at those levels ensures the script keeps a consistent vertical drawing space.
This is a subtle but important implementation detail. Without it, the game objects could be compressed or mispositioned by automatic scaling behavior.
20) Practical Interpretation
Auto Play Ping/Pong is best understood as a Pine Script animation and state management demo rather than as a market analysis indicator. Its real value comes from showing how chart objects, arrays, persistent state, and update logic can be combined to create a living visual system inside TradingView.
The script demonstrates:
state persistence,
object reuse,
basic game physics,
simple AI motion,
collision handling,
score management,
and visual effects such as motion trails.
That makes it a strong example for anyone exploring creative Pine development, chart animation, or non traditional overlay design.
Indicador Pine Script®






















