Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """
- Simplified Pong!
- Attributions under CC:
- http://creativecommons.org/licenses/by/3.0/
- Sounds:
- PongBlipF4.wav
- http://www.freesound.org/people/NoiseCollector/sounds/4359/
- Error.wav
- http://www.freesound.org/people/Autistic%20Lucario/sounds/142608/
- """
- import math
- import simplegui
- import random
- #Globals
- # Room dimensions
- room_width = 600
- room_height = 400
- top_margin = 80
- room_center = ( room_width/2, (room_height + top_margin)/2 )
- # Ball parameters
- ball_radius = 8
- # Paddle parameters
- player1_speed = 8
- player2_speed = player1_speed
- paddle_length = 80
- paddle_width = 8
- # Paddle Movement sensitivity
- UP = (0, -1) # move paddle in up/negative Y direction
- DOWN = (0, 1) # move paddle in down/positive Y direction
- # Single or Two Player
- player2_auto = False
- """
- Classes
- """
- ## Geometric Primitives
- # Rectangle
- class Rect(object):
- def __init__(self, center, width, height, color="White"):
- self.width = width
- self.height = height
- self.center = center
- self.color = color
- def getWidth(self):
- return self.width
- def getHeight(self):
- return self.height
- def getCenter(self):
- return self.center
- def getTopLeft(self):
- """
- Calculate topLeft based on center coordinates.
- Useful when moving the rect by changing center coordinates
- """
- center = self.center
- width = self.width
- height = self.height
- return ((center[0] - width/2), (center[1] - height/2))
- def pointList(self):
- topLeft = self.getTopLeft()
- return ([topLeft, \
- (topLeft[0] + self.width, topLeft[1]), \
- (topLeft[0] + self.width, topLeft[1] + self.height), \
- (topLeft[0], topLeft[1] + self.height)])
- def intersection(self, point):
- """
- Determine if a given point is inside or outside the rectangle
- point: tuple
- returns True if point is ON the edges or INSIDE the shape
- else False
- """
- topLeft = self.getTopLeft()
- xIntersect = point[0] >= topLeft[0] \
- and point[0] <= topLeft[0] + self.width
- yIntersect = point[1] >= topLeft[1] \
- and point[1] <= topLeft[1] + self.height
- return xIntersect and yIntersect
- def draw(self, canvas):
- canvas.draw_polygon(self.pointList(), 1, self.color, self.color)
- # Circle
- class Circle(object):
- def __init__(self, center, radius, color="White"):
- self.center = center
- self.radius = radius
- self.color = color
- def getCenter(self):
- return self.center
- def getRadius(self):
- return self.radius
- def distanceToCenter(self, point):
- """ Distance between a point somewhere in space, and the center of this circle """
- return math.sqrt( (point[0] - self.center[0])**2 + (point[1] - self.center[1])**2 )
- def getBoundingBox(self):
- return Rect(self.getCenter(), self.getRadius()*2, self.getRadius()*2)
- def intersection(self, point):
- """
- Determine if a given point is inside or outside the circle
- point: tuple
- returns True if point is ON the edges or INSIDE the shape
- else False
- """
- distance = self.distanceToCenter(point)
- return self.radius >= distance
- def draw(self, canvas):
- canvas.draw_circle(self.center, self.radius, 1, self.color, self.color)
- # Classes: Vectors (cartesian)
- class Vector(object):
- def __init__(self, coordinates):
- self.coordinates = coordinates
- def getCoordinates(self):
- # Returns Cartesian coordinates of this vector
- return self.coordinates
- def getMagnitude(self):
- # Returns magnitude of this vector
- x = self.coordinates[0]
- y = self.coordinates[1]
- return math.sqrt(x**2 + y**2)
- def dot(self, v2):
- """
- Dot product of this and v2
- Returns a scalar value
- """
- x = self.coordinates[0]
- y = self.coordinates[1]
- v2_coord = v2.getCoordinates()
- result = (x * v2_coord[0]) + (y * v2_coord[1])
- return result
- def __add__(self, v2):
- """
- Add this vector to another vector v2
- Returns tuple of resultant vector
- """
- x = self.coordinates[0]
- y = self.coordinates[1]
- v2_coord = v2.getCoordinates()
- result = (x + v2_coord[0], y + v2_coord[1])
- return Vector(result)
- def __sub__(self, v2):
- """
- Subtract v2 from this vector
- Returns tuple of resultant vector
- """
- x = self.coordinates[0]
- y = self.coordinates[1]
- v2_coord = v2.getCoordinates()
- result = (x - v2_coord[0], y - v2_coord[1])
- return Vector(result)
- def normalize(self):
- """
- Returns the normalized coordinates of this vector
- """
- magnitude = self.getMagnitude()
- x = self.coordinates[0]
- y = self.coordinates[1]
- result = (x/magnitude, y/magnitude)
- return Vector(result)
- def scalarMul(self, C):
- """
- Scalar multiplication of a vector
- Returns tuple containing coordinates multiplied by the scalar
- """
- x = self.coordinates[0]
- y = self.coordinates[1]
- result = (C*x, C*y)
- return Vector(result)
- def proj(self, v2):
- """
- Project this onto v2. v2 should be the surface normal of a wall or other object
- """
- n = v2.normalize()
- return n.scalarMul(self.dot(n))
- def reflectionVector(self, v2):
- """
- http://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector
- Coupe de grace; Reflects this according to the law of reflection
- v2 is the surface normal of reflecting surface
- Returns a tuple containing coordinates of reflected vector
- """
- v1_projected_v2 = (self.proj(v2)).scalarMul(2).getCoordinates()
- x = self.coordinates[0] - v1_projected_v2[0]
- y = self.coordinates[1] - v1_projected_v2[1]
- return Vector((x, y))
- ## Classes: Game Elements
- # Ball
- class Ball(Circle):
- """
- Define a ball object which is a circle with additional properties
- speed: position increment in pixels. Position will change with every
- call to move()
- direction: angle measured from positive x axis
- """
- def __init__(self, center, radius, velocity):
- Circle.__init__(self, center, radius, "Yellow")
- self.velocity = velocity
- def getSpeed(self):
- return self.velocity.getMagnitude()
- def getDirection(self):
- return self.velocity.normalize().getCoordinates()
- def setPosition(self, center):
- self.center = center
- def getVelocity(self):
- return self.velocity
- def setVelocity(self, direction):
- self.velocity = Vector(direction)
- def move(self):
- c = self.getCenter()
- speed = self.getSpeed()
- d = self.getDirection()
- new_pos = [(c[0] + speed * d[0]), (c[1] + speed * d[1])]
- if new_pos[0] < 0:
- new_pos[0] = 0
- elif new_pos[0] > room_width - paddle_width - 1:
- new_pos[0] = room_width - paddle_width - 1
- if new_pos[1] < (top_margin + paddle_width)/2:
- new_pos[1] = (top_margin + paddle_width)/2
- elif new_pos[1] > (top_margin + paddle_width)/2 + room_height:
- new_pos[1] = room_height
- self.setPosition( tuple(new_pos) )
- def reflect(self, normal):
- """
- The velocity vector is reflected according to the law of reflection,
- upon collision with a surface
- """
- self.velocity = self.velocity.reflectionVector(normal)
- # Paddle
- class Paddle(Rect):
- """
- Define a paddle object which is a Rect with additional properties
- speed: position increment in pixels. Position will change with every
- call to move()
- direction: either down or up
- """
- def __init__(self, center, width, height, color, velocity, surfaceNormal):
- Rect.__init__(self, center, width, height, color)
- self.normal = surfaceNormal
- self.velocity = velocity
- def getSpeed(self):
- return self.velocity.getMagnitude()
- def getDirection(self):
- return self.velocity.normalize().getCoordinates()
- def getSurfaceNormal(self):
- return self.normal
- def setPosition(self, center):
- self.center = center
- def setVelocity(self, vel):
- self.velocity = Vector(vel)
- def move(self):
- speed = self.getSpeed()
- d = self.getDirection()
- newy = self.center[1] + speed * d[1]
- self.center = (self.center[0], newy)
- # Wall/Boundaries
- class Wall(Rect):
- """
- Define a paddle object which is a Rect with additional properties
- speed: position increment in pixels. Position will change with every
- call to move()
- direction: either down or up
- """
- def __init__(self, center, width, height, surfaceNormal):
- Rect.__init__(self, center, width, height)
- self.surfaceNormal = surfaceNormal
- def getSurfaceNormal(self):
- # Return the Surface Normal Vector
- return self.surfaceNormal
- """
- Helper functions
- """
- # Random Velocity
- def genRandomVelocity(sgn=0):
- if sgn != 0:
- # If sign is specified, only generate Y randomly
- x = sgn * random.randrange(120, 240)
- y = random.randrange(-1,2,2) * random.randrange(60, 180)
- return ((x/60, y/60))
- else:
- # Sign is not specified, both components are random
- x = random.randrange(-1,2,2) * random.randrange(120, 240)
- y = random.randrange(-1,2,2) * random.randrange(60, 180)
- return ((x/60, y/60))
- # Determine collisions
- def intersectingShapes(shape1, shape2):
- """
- Takes 2 shapes and determines if there is any overlap
- returns: True if shape1, and shape2 overlap
- """
- # Rect and Rect
- def overlappingRectangles(shape1, shape2):
- """
- If rectangles are of different sizes, need to check if vertices
- of smaller rectangle are inside the larger one.
- """
- vertices_shape1 = shape1.pointList()
- vertices_shape2 = shape2.pointList()
- for v1 in vertices_shape1:
- if shape2.intersection(v1):
- return True
- else:
- for v2 in vertices_shape2:
- if shape1.intersection(v2):
- return True
- else:
- return False
- # Circle and Rectangle
- def overlappingCircleandRect(shape1, shape2):
- if isinstance(shape1, Circle):
- c = shape1
- r = shape2
- else:
- c = shape2
- r = shape1
- boundingBox = c.getBoundingBox()
- vertices = r.pointList()
- for vertex in vertices:
- if c.intersection(vertex):
- return True
- else:
- return overlappingRectangles(r, boundingBox)
- if isinstance(shape1, Rect) and isinstance(shape2, Rect):
- return overlappingRectangles(shape1, shape2)
- elif isinstance(shape1, Circle) and isinstance(shape2, Rect) \
- or isinstance(shape2, Circle) and isinstance(shape1, Rect):
- return overlappingCircleandRect(shape1, shape2)
- """
- Initialize and Load Game
- """
- def game_init():
- """ Loads the game environment """
- # Game elements
- global pong_ball, top_wall, bottom_wall, player1_paddle, player2_paddle, roomRect
- # Controls, scoring
- global keyboard, player1_score, player2_score, match_point
- # Flags
- global start_game, player1_out, player2_out
- # Game Assets
- global ball_hit_sound, ball_out_sound
- # Game Elements
- # Ball
- pong_ball = Ball( room_center, ball_radius, Vector(genRandomVelocity()) )
- # Walls
- top_wall_y = top_margin/2
- bottom_wall_y = top_wall_y + room_height + paddle_width
- top_wall = Wall( (room_width/2, top_wall_y), room_width, paddle_width, Vector((0, 1)) )
- bottom_wall = Wall( (room_width/2, bottom_wall_y), room_width, paddle_width, Vector((0, -1)) )
- # print (bottom_wall.getCenter()[1] - paddle_width/2) - (top_wall.getCenter()[1] + paddle_width/2) # should be == room_width
- # Paddles
- paddles_y = room_center[1]
- player1_vel = Vector(DOWN).scalarMul(player1_speed)
- player2_vel = Vector(DOWN).scalarMul(player2_speed)
- player1_paddle = Paddle( (paddle_width/2, paddles_y), paddle_width, paddle_length, "Red", player1_vel, Vector((1, 0)) )
- player2_paddle = Paddle( (room_width - paddle_width/2 - 1, paddles_y), paddle_width, paddle_length, "Blue", player2_vel, Vector((-1, 0)) )
- # Canvas (Game map/area)
- roomRect = Rect(room_center, room_width, room_height + top_margin)
- # User Control
- keyboard = {'up' : False,
- 'down' : False,
- 'w' : False,
- 's' : False,
- }
- # Scoring
- player1_score = 0
- player2_score = 0
- match_point = 7
- # Flags
- start_game = False
- player1_out = False
- player2_out = False
- # Game Assets
- # Sounds
- ball_hit_sound = simplegui.load_sound("http://www.freesound.org/people/NoiseCollector/sounds/4359/download/4359__noisecollector__pongblipf4.wav")
- ball_out_sound = simplegui.load_sound("http://www.freesound.org/people/Autistic%20Lucario/sounds/142608/download/142608__autistic-lucario__error.wav")
- game_init()
- """
- Movement and animation functions
- """
- """
- Ball Movements
- """
- # Ball out of bounds
- def ball_outOfBounds(ball):
- global player1_out, player2_out
- c = ball.getCenter()
- r = ball.getRadius()
- d = ball.getDirection()
- p1 = player1_paddle.getCenter()
- p2 = player2_paddle.getCenter()
- if d[0] < 0:
- # Ball out of bounds on left side of screen
- if (c[1] + r <= p1[1] - player1_paddle.getHeight()/2)\
- or (c[1] - r >= p1[1] + player1_paddle.getHeight()/2):
- ball_out = (c[0] - r <= paddle_width)\
- or (c[0] + r >= room_width - paddle_width)
- if ball_out:
- player1_out = True
- return ball_out
- elif d[0] > 0:
- # Ball out of bounds on right side of screen
- if (c[1] + r <= p2[1] - player1_paddle.getHeight()/2)\
- or (c[1] - r >= p2[1] + player1_paddle.getHeight()/2):
- ball_out = (c[0] - r <= paddle_width)\
- or (c[0] + r >= room_width - paddle_width)
- if ball_out:
- player2_out = True
- return ball_out
- # Reset Ball Position
- def ball_reset():
- """ Reset the ball, when it goes out of bounds """
- global start_game, player1_out, player2_out
- start_game = player1_out = player2_out = False
- ball_out_sound.play()
- reset_timer.start()
- # Ball Reset timer
- def reset_pause():
- global start_game
- start_game = True
- pong_ball.setPosition(room_center)
- reset_timer.stop()
- # Buffer zone around surfaces before smartly testing for collisions
- def ball_buffer():
- """ Returns the distance between center of ball and center of paddle
- one timestep before imminent collision.
- """
- return (paddle_width/2 + pong_ball.getSpeed() + ball_radius)
- # Smartly determine if ball is in dangerzone (dz) and
- # collision is imminent (after one additional timestep)
- dz_left = player1_paddle.getCenter()[0] + ball_buffer()
- dz_right = player2_paddle.getCenter()[0] - ball_buffer()
- dz_top = top_wall.getCenter()[1] + ball_buffer()
- dz_bottom = bottom_wall.getCenter()[1] - ball_buffer()
- def ball_collisions():
- """
- Since the game has only one moving object, with 4 fixed surfaces, and a room of known size
- significant efficiency gains can be achieved by not checking for collisions
- on every single draw callback.
- This function will smartly check for collisions based on distance between surfaces and ball
- """
- pong_ball_center = pong_ball.getCenter()
- pong_ball_dir = pong_ball.getDirection()
- # Left paddle (player 1)
- if pong_ball_center[0] <= dz_left and pong_ball_dir[0] < 0:
- if intersectingShapes(player1_paddle, pong_ball):
- ball_hit_sound.play()
- pong_ball.reflect(player1_paddle.getSurfaceNormal())
- pong_ball.setVelocity(pong_ball.getVelocity().scalarMul(1.1).getCoordinates())
- # Right paddle (player 2)
- elif pong_ball_center[0] >= dz_right and pong_ball_dir[0] > 0:
- if intersectingShapes(player2_paddle, pong_ball):
- ball_hit_sound.play()
- pong_ball.reflect(player2_paddle.getSurfaceNormal())
- pong_ball.setVelocity(pong_ball.getVelocity().scalarMul(1.1).getCoordinates())
- # Top wall
- if pong_ball_center[1] <= dz_top and pong_ball_dir[1] < 0:
- if intersectingShapes(top_wall, pong_ball):
- ball_hit_sound.play()
- pong_ball.reflect(top_wall.getSurfaceNormal())
- # Bottom wall
- elif pong_ball_center[1] >= dz_bottom and pong_ball_dir[1] > 0:
- if intersectingShapes(bottom_wall, pong_ball):
- ball_hit_sound.play()
- pong_ball.reflect(bottom_wall.getSurfaceNormal())
- """
- Scoring
- """
- def calculateScores():
- global player1_score, player2_score
- # Updates scores.
- if player2_out:
- player1_score += 1
- elif player1_out:
- player2_score += 1
- def isGameOver():
- return player1_score == match_point\
- or player2_score == match_point
- """
- User Controls: Paddle Movements
- """
- # Determine if paddle is able to move
- def paddle_validMove(paddle):
- """ Move the paddle if it won't collide with the walls """
- speed = paddle.getSpeed()
- d = paddle.getDirection()
- newy = paddle.getCenter()[1] + speed * d[1] # predicted position of paddle center after one tick
- # Paddle moving UP
- if d[1] < 0 :
- newy -= paddle_length/2
- boundary = top_wall.getCenter()[1] + paddle_width/2
- if newy <= boundary:
- paddle.setPosition( (paddle.getCenter()[0], boundary + paddle_length/2) )
- else:
- paddle.move()
- # Paddle moving DOWN
- elif d[1] > 0 :
- newy += paddle_length/2
- boundary = bottom_wall.getCenter()[1] - paddle_width/2
- if newy >= boundary:
- paddle.setPosition( (paddle.getCenter()[0], boundary - paddle_length/2) )
- else:
- paddle.move()
- # Player1 movement
- def player1_move(paddle):
- if keyboard['s']:
- paddle.setVelocity(Vector(DOWN).scalarMul(player1_speed).getCoordinates())
- paddle_validMove(paddle)
- elif keyboard['w']:
- paddle.setVelocity(Vector(UP).scalarMul(player1_speed).getCoordinates())
- paddle_validMove(paddle)
- # Player2 Movement
- def player2_move(paddle):
- if player2_auto:
- # Computer controlled second paddle
- target = pong_ball.getCenter()
- current = paddle.getCenter()
- # move computer paddle only after player has struck the ball and pong_ball.getCenter()[0] > room_width/2
- if pong_ball.getDirection()[0] > 0:
- if target[1] < current[1]:
- paddle.setVelocity(Vector(UP).scalarMul(player2_speed).getCoordinates())
- if target[1] > current[1]:
- paddle.setVelocity(Vector(DOWN).scalarMul(player2_speed).getCoordinates())
- paddle_validMove(paddle)
- else:
- # User controlled second paddle
- if keyboard['down']:
- paddle.setVelocity(Vector(DOWN).scalarMul(player2_speed).getCoordinates())
- paddle_validMove(paddle)
- elif keyboard['up']:
- paddle.setVelocity(Vector(UP).scalarMul(player2_speed).getCoordinates())
- paddle_validMove(paddle)
- """
- Game Animations
- """
- # Movement of all objects in game
- def game_movement():
- """ Movement and collision detection for all game objects """
- # Paddles should move regardless of what the ball is doing
- player1_move(player1_paddle)
- player2_move(player2_paddle)
- # Ball: Check for boundary violations/collisions
- if not ball_outOfBounds(pong_ball):
- ball_collisions()
- pong_ball.move()
- elif ball_outOfBounds(pong_ball) and not reset_timer.is_running():
- p1Score = player1_score
- p2Score = player2_score
- calculateScores()
- ball_reset() # start reset timer
- if player1_score > p1Score:
- pong_ball.setVelocity(genRandomVelocity(-1))
- elif player2_score > p2Score:
- pong_ball.setVelocity(genRandomVelocity(1))
- """
- Game Draw functions
- """
- def draw_scores(canvas):
- canvas.draw_text(str(player1_score), (room_width/4, (top_margin - paddle_width)/3), 24, "White")
- canvas.draw_text(str(player2_score), (room_width/4 + room_width/2, (top_margin - paddle_width)/3), 24, "White")
- def game_over(canvas):
- def gameover_msg_coordinates(message, text_size):
- message_width = pong_frame.get_canvas_textwidth(message, text_size)
- x = (room_center[0] - message_width)/2
- y = room_center[1]
- return (x, y)
- if player1_score > player2_score:
- message = "P1 WIN!"
- coord = gameover_msg_coordinates(message, 24)
- canvas.draw_text(message, coord, 24, "Red")
- else:
- message = "P2 WIN!"
- coord = gameover_msg_coordinates(message, 24)
- canvas.draw_text(message, coord, 24, "Blue")
- def draw_game_static_env(canvas):
- """ Draw all the static objects in the game environment """
- # centerline
- canvas.draw_line( (room_width/2, top_wall.getCenter()[1]), (room_width/2, bottom_wall.getCenter()[1]), 2, "White")
- canvas.draw_line([paddle_width, top_wall.getCenter()[1]],[paddle_width, bottom_wall.getCenter()[1]], 1, "White")
- canvas.draw_line([room_width - paddle_width - 1, top_wall.getCenter()[1]],[room_width - paddle_width, bottom_wall.getCenter()[1]], 1, "White")
- # walls
- top_wall.draw(canvas)
- bottom_wall.draw(canvas)
- # scores
- draw_scores(canvas)
- def draw_ball_paddles(canvas):
- # Draw Ball
- pong_ball.draw(canvas)
- # Draw Paddles
- player1_paddle.draw(canvas)
- player2_paddle.draw(canvas)
- # Main game callback (auto-loop)
- def draw(canvas):
- global start_game
- # draw static elements
- draw_game_static_env(canvas)
- # Check if current game is over
- if not isGameOver():
- draw_ball_paddles(canvas)
- # Check that the game is not blocked,
- # or new game has not yet been started
- if start_game:
- game_movement()
- else:
- """ Game has ended, print game results, and freeze canvas
- until user creates a new game
- """
- draw_ball_paddles(canvas)
- start_game = False
- game_over(canvas)
- """
- Event Handlers and Game Loop
- """
- # New Game Button Handler
- # Mouseclick- Game start
- def mouseclick_startGame(position):
- global start_game
- if not start_game:
- start_game = True
- # Enable single player mode, player2 paddle will be controlled by computer
- def singlePlayer():
- global player2_auto
- game_init()
- player2_auto = True
- # 2Player Button Handler
- def twoPlayer():
- global player2_auto
- game_init()
- player2_auto = False
- # Key is pushed and held down
- def keydown(key):
- if key == simplegui.KEY_MAP['s']:
- keyboard['s'] = True
- elif key == simplegui.KEY_MAP['w']:
- keyboard['w'] = True
- if key == simplegui.KEY_MAP['down']:
- keyboard['down'] = True
- elif key == simplegui.KEY_MAP['up']:
- keyboard['up'] = True
- # Key is released
- def keyup(key):
- if key == simplegui.KEY_MAP['s']:
- keyboard['s'] = False
- elif key == simplegui.KEY_MAP['w']:
- keyboard['w'] = False
- if key == simplegui.KEY_MAP['down']:
- keyboard['down'] = False
- elif key == simplegui.KEY_MAP['up']:
- keyboard['up'] = False
- """
- Game Window Settings
- """
- def gameWindow_init():
- global pong_frame, reset_timer
- # Create Game Window
- pong_frame = simplegui.create_frame("Pong", roomRect.getWidth(), roomRect.getHeight(), 300)
- game_instructions = []
- game_instructions.append(pong_frame.add_label("Welcome to Pong!"))
- game_instructions.append(pong_frame.add_label(""))
- game_instructions.append(pong_frame.add_label("Up and Down arrow keys control the Blue paddle"))
- game_instructions.append(pong_frame.add_label(""))
- game_instructions.append(pong_frame.add_label("W and S keys control the Red paddle"))
- game_instructions.append(pong_frame.add_label(""))
- game_instructions.append(pong_frame.add_label("Click anywhere in the game window to start"))
- game_instructions.append(pong_frame.add_label(""))
- game_instructions.append(pong_frame.add_label("May the force be with you."))
- game_instructions.append(pong_frame.add_label(""))
- # Game Window Buttons and Controls
- # resetButton = pong_frame.add_button("New Game", new_game, 150)
- pong_frame.add_button("Single Player (vs Computer)", singlePlayer, 250)
- pong_frame.add_button("2 Player (Restart)", twoPlayer, 250)
- # Timers
- reset_timer = simplegui.create_timer(1000, reset_pause)
- # Register event handlers
- pong_frame.set_keydown_handler(keydown)
- pong_frame.set_keyup_handler(keyup)
- pong_frame.set_draw_handler(draw)
- pong_frame.set_mouseclick_handler(mouseclick_startGame)
- # Start the frame animation
- gameWindow_init()
- pong_frame.start()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement