Pongs is a 2 player game in which 2 players move their paddles simultaneously to hit a ball back and forth. Player 1 uses keys 'W', 'A', 'S', and 'D' for up, down, left and right respectively and Player 2 uses arrow keys. Both players attempt to hit the ball to opponents side. If the ball goes past a player's side of the screen, their opponent gains a point.
Quick Demo: click here
Source Code: click here
Description
Pongs is a game where you can move your paddle using the keys W for up movement, A, S, D (likewise) and arrow keys to hit the ball to the other side.
Figure 1 - Gif of game being played
If the ball goes past your side of the screen, the other person gets a point. The game goes on forever and keeps track of your score.
Figure 2 - Directions page from game
Settings
In this game, there are also customizable settings such as…
Figure 3 - Settings page from game
Ball Speed - Changes the speed at which the ball moves
Ball Size - Changes the size of the ball
Paddle Speed - Changes the speed at which the paddles move
Paddle Size - Changes the size of the paddles
Rounds to Win - The amount of wins a player needs to win the game
There are also pause and restart buttons which either pause the game until clicked again or restart the game respectively.
Figure 4 - Pause and restart button from game
Background
This game was made using WPF and C# on Visual Studio in the span of one week. It was made to obtain the basics of WPF and how to use C# code behind to create dynamic objects in the code.
Code Explanation
Drawing the board
One key part of my code is drawing everything on the board, such as the paddle and the ball. This is done through 3 different functions.
public void DrawPaddle(Rectangle Paddle, double x, double y)
{
log.Info("DrawPaddle Start");
Paddle.Width = 1.5 * SliderInfo.PaddleSize;
Paddle.Height = 7.8 * SliderInfo.PaddleSize;
Paddle.Fill = Brushes.Black;
Paddle.Stroke = Brushes.White;
Paddle.StrokeThickness = 4;
Canvas.SetTop(Paddle, y);
Canvas.SetLeft(Paddle, x);
log.Info("DrawPaddle End");
}
The above code draws the paddle onto the board. It is drawn separate from the other shapes since it is the only shape that moves via player input, and also requires inputs, such as which paddle and the x and y coordinates of it.
public void ReDraw()
{
log.Info("ReDraw Start");
if (WindowState == WindowState.Maximized)
{
Window.Height = (int)System.Windows.SystemParameters.PrimaryScreenHeight;
Window.Width = (int)System.Windows.SystemParameters.PrimaryScreenWidth;
}
Board.Width = Window.Width;
Board.Height = Window.Height;
sbkGameEngine.y1 = Board.Height / 2 - (paddle1.Height / 2);
sbkGameEngine.y2 = sbkGameEngine.y1;
sbkGameEngine.x2 = Board.Width - 32;
sbkGameEngine.x1 = 0;
Ball.Width = 1.5 * SliderInfo.BallSize;
Ball.Height = 1.5 * SliderInfo.BallSize;
Ball.Fill = Brushes.White;
Ball.Stroke = Brushes.White;
Ball.StrokeThickness = 4;
Canvas.SetTop(Ball, Board.Height / 2 - Ball.Height / 2);
Canvas.SetLeft(Ball, Board.Width / 2 - Ball.Width / 2);
Menu.Width = Board.Width;
if (Board.Width - (SettingsMenu.Width + About.Width + Help.Width) - (1.5 * RestartButton.Width + PauseButton.Width) - 2 > 0)
{
Spacer.Width = Board.Width - (SettingsMenu.Width + About.Width + Help.Width) - (1.5 * RestartButton.Width + PauseButton.Width) - 2;
}
P1Scoreboard.Text = "" + sbkGameEngine.P1Score;
P2Scoreboard.Text = "" + sbkGameEngine.P2Score;
sbkGameEngine.CanBallMove = false;
ReDrawUnmoving();
log.Info("ReDraw End");
}
This function draws all of the unmoving shapes, as well as the menu and board. This is done to initially set up the board and to reset the board when necessary.
private void ReDrawUnmoving()
{
log.Info("ReDrawUnmoving Start");
Boundary.Stroke = Brushes.Gray;
Boundary.StrokeThickness = 3;
Boundary.X1 = Board.Width / 2;
Boundary.X2 = Board.Width / 2;
Boundary.Y1 = 0;
Boundary.Y2 = Board.Height;
BottomWall.Width = Board.Width;
BottomWall.Height = 24;
BottomWall.Fill = Brushes.White;
BottomWall.Stroke = Brushes.White;
BottomWall.StrokeThickness = 4;
Canvas.SetTop(BottomWall, Board.Height - 63);
Canvas.SetLeft(BottomWall, 0);
log.Info("ReDrawUnmoving End");
}
This function does the same thing as ReDraw(), just this time drawing the unmoving parts.
These functions allow for the board to be drawn, which a is key element of my game. This is because knowing where everything is on the board is necessary for the player to react in the right way.
Moving the Ball
Another key part of my game is the movement of the ball. This is because the main point of the game is to try and block the ball from moving into your side of the field.
public void BallMovement()
{
log.Info("BallMovement Start");
if (sbkGameEngine.CanBallMove && sbkGameEngine.GamePlayable)
{
Canvas.SetTop(Ball, Canvas.GetTop(Ball) + sbkGameEngine.VMovement);
Canvas.SetLeft(Ball, Canvas.GetLeft(Ball) + sbkGameEngine.HMovement);
}
if (sbkGameEngine.P1Wins)
{
WhoWon_.Text = "Player 1 Wins!";
WhoWon_.Visibility = Visibility.Visible;
RestartText.Visibility = Visibility.Visible;
OnPause(Ball, a);
sbkGameEngine.i = 2;
}
if (sbkGameEngine.P2Wins)
{
WhoWon_.Text = "Player 2 Wins!";
WhoWon_.Visibility = Visibility.Visible;
RestartText.Visibility = Visibility.Visible;
OnPause(Ball, a);
sbkGameEngine.i = 2;
}
log.Info("BallMovement End");
}
This function only takes the variables from the sbkGameEngine class to move the ball. It doesn't do any calculations itself and just does what the engine tells it to do via the variables that are changed by the sbkGameEngine.
public void BallMovement()
{
log.Info("BallMovement Start");
if (GamePlayable)
{
int P1Top = (int)Canvas.GetTop(mGuiReference.paddle1);
int P1Bottom = (int)(Canvas.GetTop(mGuiReference.paddle1) + mGuiReference.paddle1.Height);
int P1Left = (int)Canvas.GetLeft(mGuiReference.paddle1);
int P1Right = (int)(Canvas.GetLeft(mGuiReference.paddle1) + mGuiReference.paddle1.Width);
int P2Top = (int)Canvas.GetTop(mGuiReference.paddle2);
int P2Bottom = (int)(Canvas.GetTop(mGuiReference.paddle2) + mGuiReference.paddle2.Height);
int P2Left = (int)Canvas.GetLeft(mGuiReference.paddle2);
int P2Right = (int)(Canvas.GetLeft(mGuiReference.paddle2) + mGuiReference.paddle2.Width);
int BallTop = (int)Canvas.GetTop(mGuiReference.Ball);
int BallBottom = (int)(Canvas.GetTop(mGuiReference.Ball) + mGuiReference.Ball.Height);
int BallLeft = (int)Canvas.GetLeft(mGuiReference.Ball);
int BallRight = (int)(Canvas.GetLeft(mGuiReference.Ball) + mGuiReference.Ball.Width);
if ((P2Bottom > BallTop && P2Top < BallBottom && BallLeft < P2Right && BallRight > P2Left && HMovement == 1) || (P1Bottom > BallTop && P1Top < BallBottom && BallLeft < P1Right && BallRight > P1Left && HMovement == -1))
{
HMovement *= -1;
Console.Beep(37, 10);
}
if (BoundaryCheck(mGuiReference.Ball, 25, (int)(mGuiReference.Board.Height - (mGuiReference.BottomWall.Height * 2) - 5), 0, 0, true, true, false, false) == false)
{
VMovement *= -1;
Console.Beep(70, 5);
}
if (Canvas.GetLeft(mGuiReference.Ball) >= mGuiReference.Board.Width)
{
P1Score++;
mGuiReference.ReDraw();
log.Info("Player 1 scored!");
if (P1Score == SliderInfo.RoundsToWin)
{
P1Wins = true;
}
}
if (Canvas.GetLeft(mGuiReference.Ball) + 15 <= 0)
{
P2Score++;
mGuiReference.ReDraw();
log.Info("Player 2 scored!");
if (P2Score == SliderInfo.RoundsToWin)
{
P1Wins = true;
}
}
}
log.Info("BallMovement End");
}
This function is the one actually doing the calculations. It changes the variables controlling which direction the ball is moving when certain conditions are met, like swapping directions after hitting a paddle.
Both these functions combine to move the ball around and allow for the ball to interact with it's environment.
Paddle Movement
Another element necessary to the creation of the game is the movement of the paddles, since it is the only player controlled shape.
public void OnKeyDown(object sender, KeyEventArgs e)
{
log.Info("OnKeyDown Start");
if (AllowedKeys.Contains(e.Key))
{
if (i == 0)
{
KeysPressed.Add(e.Key);
CanBallMove = true;
}
}
log.Info("OnKeyDown End");
}
This function, when a key is pressed, adds that key to a hashset. The hashset will be later used to identify which keys are being pressed and which are not.
public void OnKeyUp(object sender, KeyEventArgs e)
{
log.Info("OnKeyUp Start");
if (KeysPressed.Contains(e.Key))
{
KeysPressed.Remove(e.Key);
}
log.Info("OnKeyUp End");
}
This function removes the let go key and runs when a key is pressed.
public void PressedKeys()
{
log.Info("PressedKeys Start");
if (GamePlayable)
{
if (KeysPressed.Contains(Key.Up) && BoundaryCheck(mGuiReference.paddle2, 25, (int)mGuiReference.Board.Height - 78, (int)mGuiReference.Board.Width / 2, (int)mGuiReference.Board.Width, true, false, false, false) == true)
{
y2 -= 2;
}
if (KeysPressed.Contains(Key.W) && BoundaryCheck(mGuiReference.paddle1, 25, (int)mGuiReference.Board.Height - 78, (int)mGuiReference.Board.Width / 2, (int)mGuiReference.Board.Width, true, false, false, false) == true)
{
y1 -= 2;
}
if (KeysPressed.Contains(Key.Down) && BoundaryCheck(mGuiReference.paddle2, 25, (int)mGuiReference.Board.Height - 78, (int)mGuiReference.Board.Width / 2, (int)mGuiReference.Board.Width, false, true, false, false) == true)
{
y2 += 2;
}
if (KeysPressed.Contains(Key.S) && BoundaryCheck(mGuiReference.paddle1, 25, (int)mGuiReference.Board.Height - 78, (int)mGuiReference.Board.Width / 2, (int)mGuiReference.Board.Width, false, true, false, false) == true)
{
y1 += 2;
}
if (KeysPressed.Contains(Key.Left) && BoundaryCheck(mGuiReference.paddle2, 25, (int)mGuiReference.Board.Height - 78, (int)mGuiReference.Board.Width / 2, (int)mGuiReference.Board.Width / 2, false, false, true, false) == true)
{
x2 -= 2;
}
if (KeysPressed.Contains(Key.A) && BoundaryCheck(mGuiReference.paddle1, 25, (int)mGuiReference.Board.Height - 78, 0, (int)mGuiReference.Board.Width / 2, false, false, true, false) == true)
{
x1 -= 2;
}
if (KeysPressed.Contains(Key.Right) && BoundaryCheck(mGuiReference.paddle2, 25, (int)mGuiReference.Board.Height - 78, 0, (int)mGuiReference.Board.Width - 20, false, false, false, true) == true)
{
x2 += 2;
}
if (KeysPressed.Contains(Key.D) && BoundaryCheck(mGuiReference.paddle1, 25, (int)mGuiReference.Board.Height - 78, 0, (int)mGuiReference.Board.Width / 2, false, false, false, true) == true)
{
x1 += 2;
}
}
log.Info("PressedKeys End");
}
The above function is run repeatedly by a different function and checks if any keys are in the hashset. If a key is in the hashset, it will do the corresponding move, such as moving the player 2 paddle up when the up key is in the hashset.
These all combine to allow player key inputs to be able to correspond to actions taken by the paddles in the game.
Running the Application
- Go to the GitHub link: click here
- Go to the folder labeled "Quick Demo"
- Download the "ZippedDemo.zip"
- Unzip the file
- Run Pongs.exe
History
V1.0 -- May 5th 2024 - First version
v1.1 -- May 22nd 2024 - Revised abstract
v1.2 -- May 23rd 2024 - Updated steps to run the application
v1.3 -- May 24th 2024 - Updated code
v1.4 -- May 27 2024 - Added settings header
v1.5 -- May 29 2024 - Added figure numbers and descriptions
v1.6 -- May 31 2024 - Fixed gif that was previously not loading
References
1. https://stackoverflow.com/
2. https://learn.microsoft.com/en-us/dotnet/
If you found this article helpful / interesting, please don't forget to Vote!
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.