In this project, we were to create an air hockey video game that could be played by either one or two players to illustrate the skills we learned throughout the semester. We must define the rink, goals, blockers, and puck, and the physics behind the active components were to move as close to what true physics would indicate. One of the most challenging aspects of this project was incorporating the physics behind puck and blocker contact. With both objects being circular, the tangential point of contact was difficult to locate, resulting in some counterintuitive movement at times. One aspect I truly enjoyed about this project was the ability to highlight the development in structured programming skills, the use of advanced plotting, and ability of the program to constantly respond to user input.
With the program being tested by professors, I also had a bit of fun putting in the challenge of playing against the computer, as I could have a little fun with it and make the program impossible to beat, while still letting the user score. When the computer was at least one point ahead, the computer blocker would be offset enough that the puck would be allowed through, however if the user was tied or in the lead, the blocker was moved to follow the puck, making a near perfect game always impossible.
If you are interested in trying to play the hardest but most enjoyable game of air hockey you've ever played, feel free to copy the script below into MatLab. Enjoy!!
If you are interested in trying to play the hardest but most enjoyable game of air hockey you've ever played, feel free to copy the script below into MatLab. Enjoy!!
% MECE-117 Intro Comp Prog for Eng
% Final Project
% Air Hockey
% April 1st, 2021 -- Olivia Meyers
% Close all open figure windows
close all
%Clears all variables from workspace
clear
%Clears global variables
clear global
% Erases all text from command window
clc
%%
%Establishes global variables for functions
global puck_x puck_y r_blocker_x r_blocker_y ...
puck_vel_x puck_vel_y computer_score player_score b_blocker_x b_blocker_y ...
b_blocker_vel_x b_blocker_vel_y distance_blue distance_red figure_1
%Gets the gameboard setup and gets initial board information
StartAndSetup ()
%%
%Blocker Codes
%Creates the blocker for the red side
theta = linspace(0, 360, 36);
%Creates the matrix for the outside of blocker
big_red_blocker_x = (8 * cosd(theta));
big_red_blocker_y = (8 * sind(theta) + 50);
big_red_blocker = [big_red_blocker_x; big_red_blocker_y];
%Creates the matrix for the inside of the blocker
small_red_blocker_x = (3.5 * cosd(theta));
small_red_blocker_y = (3.5 * sind(theta) + 50);
small_red_blocker = [small_red_blocker_x; small_red_blocker_y];
%Patches the blocker together and creates moveable patch
red_blocker_patch_outside = patch(big_red_blocker_x(1,:), big_red_blocker_y(1,:), 'r');
red_blocker_patch_inside = patch(small_red_blocker_x(1,:), small_red_blocker_y(1,:), 'r');
%Creates the blue blocker
theta = linspace(0, 360, 36);
%Creates the outside matrix for the blue blocker
big_blue_blocker_x = (8 * cosd(theta));
big_blue_blocker_y = (8 * sind(theta) - 50);
big_blue_blocker = [big_blue_blocker_x; big_blue_blocker_y];
%Creates the inside matrix for the blue blocker
small_blue_blocker_x = (3.5 * cosd(theta));
small_blue_blocker_y = (3.5 * sind(theta) - 50);
small_blue_blocker = [small_blue_blocker_x; small_blue_blocker_y];
%Pathces the blocker together and creates moveable patch
blue_blocker_patch_outside = patch(big_blue_blocker_x(1,:), big_blue_blocker_y(1,:), [0, 0.4470, 0.7410]);
blue_blocker_patch_inside = patch(small_blue_blocker_x(1,:), small_blue_blocker_y(1,:), [0, 0.4470, 0.7410]);
%Blocker Start Codes
%initializes variables
b_blocker_vel_x = 3;
b_blocker_vel_y = 3;
b_blocker_x = 0;
b_blocker_y = 0;
%%
%Puck Start Codes
puck_x = 0;
puck_y = 0;
%Creates the initial puck figure
theta = linspace(0, 360, 36);
x_puck = 5 * cosd(theta);
y_puck = 5 * sind(theta);
puck = [x_puck; y_puck];
%Create patch & assign handle
puck_patch = patch(x_puck, y_puck, 'k');
%Creates a starting velocity for the puck
starting_velocity = 1;
puck_vel_x = -starting_velocity;
puck_vel_y = -starting_velocity;
%%
%Movement
%Creates a push button to start the function
start = uicontrol("Style", "pushbutton", "String", "Start Game!", "Position", [550, 540, 100, 30], "Callback", "uiresume");
%Has program listen to key presses
set(figure_1, 'WindowKeyPressFcn', @KeyDownListener)
%Waits until the player presses the start button before running the game
uiwait()
%Creates the movement of the puck and blockers together
while (player_score < 7) && (computer_score < 7)
%Calculates the distance between the puck and blockers
distance_blue = sqrt((puck_x - b_blocker_x)^2 + (puck_y - (b_blocker_y - 50))^2);
distance_red = sqrt((puck_x - r_blocker_x)^2 + (puck_y - (r_blocker_y + 50))^2);
%-------------------------------------------------------------------------------
%This section moves the blue blocker
%Creates the matrix for movement
b_movement_mtrx1 = big_blue_blocker;
b_movement_mtrx2 = small_blue_blocker;
%Moves both the inside and outside of the blocker x and y distance over
set(blue_blocker_patch_outside, 'Xdata', b_movement_mtrx1(1,:) + b_blocker_x, 'Ydata', b_movement_mtrx1(2,:) + b_blocker_y)
set(blue_blocker_patch_inside, 'Xdata', b_movement_mtrx2(1,:) + b_blocker_x, 'Ydata', b_movement_mtrx2(2,:) + b_blocker_y)
pause(0.005)
%-----------------------------------------------------------------------------
%This section creates an automated movement for the red blocker
ComputerBlocker()
%Creates the matrix for movement
r_movement_mtrx1 = big_red_blocker;
r_movement_mtrx2 = small_red_blocker;
%Moves both the inside and the outside of the blocker x and y distance
%over
set(red_blocker_patch_outside, 'Xdata', r_movement_mtrx1(1,:) + r_blocker_x, 'Ydata', r_movement_mtrx1(2,:) + r_blocker_y)
set(red_blocker_patch_inside, 'Xdata', r_movement_mtrx2(1,:) + r_blocker_x, 'Ydata', r_movement_mtrx2(2,:) + r_blocker_y)
pause(0.005)
%------------------------------------------------------------------------------
%This section moves the puck
puck_movement()
%Creates a matrix for movement
mvmnt_mtrx = puck;
%Moves the puck x and y distance over
set(puck_patch, 'Xdata', mvmnt_mtrx(1,:) + puck_x, 'Ydata', mvmnt_mtrx(2,:) + puck_y)
pause(0.005)
end
%Tells the players that the game is over
uicontrol("Style", "Text", "String", "Congratulations! The game has finished. Thank you for playing Olivia Meyers' final.", "Position", [200, 500, 300, 50]);
pause(10)
%Game closes after someone wins
close all
%%
%Functions
%Creates the rink, the score board, the title, and the instructions
function StartAndSetup (~,~)
%Creates the game board
%Establishes global variables for function
global PlayerScore ComputerScore computer_score player_score figure_1
%Checks screen size
screen_size = get(0, "ScreenSize");
%Creates figure window
figpos = [0, 0, 800, 995];
figure_1 = figure("Position", figpos);
%Defines axis limits
x_max = 120;
x_min = -80;
y_max = -x_min;
y_min = x_min;
%Creates axis with equal aspect ration and prevents rescaling
axis([x_min, x_max, y_min, y_max])
axis equal
axis manual
axis off
%Creates a title at the top of the page
title("Olivia Meyers Air Hockey Final Project")
%Initializes the computer score and the player's score
computer_score = 0;
player_score = 0;
%Creates the instructions
uicontrol("Style", "Text", "String", 'To start the game, press the "Start Game!" button. To move your blocker, use the arrow keys. After the puck scores, it will reset in the center of the board. To end the game early, press the escape key. The first player to 7 points will win and the game will end. Enjoy!', "Position", [150, 800, 500, 50])
%Creates the scoreboard
uicontrol("Style", "Text", "String", "Computer Score", "Position", [550, 700, 100, 20]); %Creates the title for the computer score
ComputerScore = uicontrol("Style", "Edit", "String", num2str(computer_score) , "Position", [550, 680, 100, 20]); %Creates a box for the computer's score to be input
uicontrol("Style", "Text", "String", "Score of Player", "Position", [550, 630, 100, 20]); %Creates the title for the player's scores
PlayerScore = uicontrol("Style", "Edit", "String", num2str(player_score, "%1.f") , "Position", [550, 610, 100, 20]); %Creates a box for the player's score to be input
%Creates figure for border
red_boundary = [-50, 50, 50, -50;... %Creates top half of board for red team
80, 80, 0, 0];
blue_boundary = [-50, 50, 50, -50;... %Creates bottom half of board for blue team
0, 0, -80, -80];
playing_field = [-40, 40, 40, -40;... %Creates white space for the playing field
70, 70, -70, -70];
red_goal = [-20, 20, 20, -20;... %Creates goal on red side of board
70, 70, 75, 75];
blue_goal = [-20, 20, 20, -20;... %Creates goal on blue side of board
-70, -70, -75, -75];
%Patches game boundaries
red_patch = patch(red_boundary(1,:), red_boundary(2,:), 'r', "EdgeColor", 'r');
blue_patch = patch(blue_boundary(1,:), blue_boundary(2,:), [0, 0.4470, 0.7410], "EdgeColor", [0, 0.4470, 0.7410]);
play_patch = patch(playing_field(1,:), playing_field(2,:), 'w', "EdgeColor", 'w');
red_goal_patch = patch(red_goal(1,:), red_goal(2,:), 'w', "EdgeColor", 'w');
blue_goal_patch = patch(blue_goal(1,:), blue_goal(2,:), 'w', "EdgeColor", 'w');
end
%-----------------------------------------------------------------------
% Key-press 'listener' function to move the player blokcer
function KeyDownListener(src,event)
global KeyID up_movement down_movement right_movement left_movement ...
b_blocker_x b_blocker_y b_blocker_vel_x b_blocker_vel_y figure_1
KeyID = event.Key;
%Has the game stop if the escape key is pressed
finish_game = strcmp(KeyID,"escape");
%Lets player move as long as the game has not ended
if finish_game ~= 1
%Sets up movement variables
up_movement = strcmp(KeyID,"uparrow");
down_movement = strcmp(KeyID,"downarrow"); %These create the variables for
right_movement = strcmp(KeyID,"rightarrow"); %movement acording to the arrow keys
left_movement = strcmp(KeyID,"leftarrow");
if up_movement == 1
b_blocker_y = b_blocker_y + (b_blocker_vel_y * 1);
if b_blocker_y > 50
b_blocker_y = 50;
elseif b_blocker_y <-12
b_blocker_y = -12; %Moves the blocker up until it reaches the edge of the playing space
elseif b_blocker_x > 32
b_blocker_x = 32;
elseif b_blocker_x < -32
b_blocker_x = -32;
end
elseif down_movement == 1
b_blocker_y = b_blocker_y + (b_blocker_vel_y * -1);
if b_blocker_y > 50
b_blocker_y = 50;
elseif b_blocker_y <-12
b_blocker_y = -12; %Moves the blocker down until it reaches the edge of the playing space
elseif b_blocker_x > 32
b_blocker_x = 32;
elseif b_blocker_x < -32
b_blocker_x = -32;
end
elseif right_movement == 1
b_blocker_x = b_blocker_x + (b_blocker_vel_x * 1);
if b_blocker_y > 50
b_blocker_y = 50;
elseif b_blocker_y <-12
b_blocker_y = -12; %Moves the blocker right until it reaches the edge of the playing space
elseif b_blocker_x > 32
b_blocker_x = 32;
elseif b_blocker_x < -32
b_blocker_x = -32;
end
elseif left_movement == 1
b_blocker_x = b_blocker_x + (b_blocker_vel_x * -1);
if b_blocker_y > 50
b_blocker_y = 50;
elseif b_blocker_y < -12
b_blocker_y = -12;
elseif b_blocker_x > 32 %Moves the blocker left until it reaches the edge of the playing space
b_blocker_x = 32;
elseif b_blocker_x < -32
b_blocker_x = -32;
end
end
else
close all
end
end
%-----------------------------------------------------------------------
%Computer blocker movement
function ComputerBlocker(~,~)
%Gets and gives global variables
global computer_score player_score r_blocker_x r_blocker_y puck_x ...
r_blocker_vel_x puck_vel_x r_blocker_vel_y
%This function creates the automated movement for the computer blocker
if computer_score <= player_score
r_blocker_vel_y = 0;
if puck_x < 32 && puck_x >= 0
r_blocker_x = puck_x-2;
r_blocker_y = 0;
r_blocker_vel_x = puck_vel_x;
elseif puck_x >= 32 && puck_x >= 0
r_blocker_x = 32; %As long as the computer is loosing, its
r_blocker_y = 0; % x position will be equal to the puck's,
r_blocker_vel_x = puck_vel_x; % and the velocity will be the same as well
elseif puck_x > -30 && puck_x < 0
r_blocker_x = puck_x-2;
r_blocker_y = 0;
r_blocker_vel_x = puck_vel_x;
elseif puck_x <= -32 && puck_x < 0
r_blocker_x = -32;
r_blocker_y = 0;
r_blocker_vel_x = puck_vel_x;
end
elseif computer_score > player_score
r_blocker_vel_y = 0;
if puck_x < 30 && puck_x >= -10
r_blocker_x = puck_x -10;
r_blocker_y = 0;
elseif puck_x >= 22 && puck_x >= -10 %When the computer is winning, the computer
r_blocker_x = 20; % blocker is ofset so the puck can get past it
r_blocker_y = 0; % and score
elseif puck_x > -9 && puck_x < -10
r_blocker_x = puck_x -10;
r_blocker_y = 0;
elseif puck_x <= -22 && puck_x < -10
r_blocker_x = -20;
r_blocker_y = 0;
end
end
end
%-----------------------------------------------------------------------
%Puck movement off of walls and off of blockers
function puck_movement(~,~)
%Creates global variables for the puck
global puck_x puck_y puck_vel_x puck_vel_y computer_score player_score ...
ComputerScore PlayerScore r_blocker_x r_blocker_y r_blocker_vel_x r_blocker_vel_y...
b_blocker_x b_blocker_y distance_blue distance_red
%Progams puck and blocker collision information
%Starts with initial variable information and simple movement info
puck_x = puck_x + puck_vel_x;
puck_y = puck_y + puck_vel_y;
%Gives the masses of the puck and blocker
mass_puck = 1;
mass_blocker = 5;
r_puck = 5;
r_blocker = 10;
%Gives the vector of the initial velocities
r_blocker_vel_initial = sqrt((r_blocker_vel_x ^2) + (r_blocker_vel_y ^2));
puck_vel_initial = sqrt((puck_vel_x ^2) + (puck_vel_y ^2));
%-----------------------------------------------------------------------
%Codes computer blocker and puck collision
if abs(distance_red) <= 13
theta2 = -atan2(r_blocker_y+50 - puck_y, r_blocker_x - puck_x);
alpha = atan2(r_blocker_vel_y, r_blocker_vel_x);
beta = atan2(puck_vel_y, puck_vel_x);
puck_x = r_blocker_x - (r_puck + r_blocker) * cos(theta2);
puck_y = r_blocker_y+50 + (r_puck + r_blocker) * sin(theta2);
%Gives the initial blocker tangential and normal velocities
r_blocker_vel_normal_initial = r_blocker_vel_initial * cos(theta2 + alpha);
r_blocker_vel_tangential_initial = r_blocker_vel_initial * sin(theta2 + alpha);
%Gives the initial puck tangential and normal velocities
puck_vel_normal_initial = puck_vel_initial * cos(theta2 + beta);
puck_vel_tangential_initial = puck_vel_initial * sin(theta2 + beta);
%Gives the after-collision tangential velocities
puck_vel_tangential = puck_vel_tangential_initial;
%Calclustes the momentum and kenetic energy before the collision
momentum_initial = (mass_puck * puck_vel_normal_initial) + (5 * r_blocker_vel_normal_initial);
kenetic_initial = ((1/2) * mass_puck * puck_vel_initial^2) + ((1/2) * 5 * r_blocker_vel_initial^2);
%Calculates coefficients for the quadraic formula
a = ((mass_puck^2) + mass_puck * mass_blocker)/ mass_blocker;
b = (2 * momentum_initial * mass_puck)/ mass_blocker;
c = (momentum_initial^2/mass_blocker) + mass_puck * puck_vel_tangential_initial^2 + ...
mass_blocker * r_blocker_vel_tangential_initial^2 - 2 * kenetic_initial;
%Gives the normal velocity of the puck after the collision
puck_vel_normal = (-b - sqrt(b^2 - (4 * a * c)))/ (2 * a);
%Calculates the final speeds of the puck and it's new angle
puck_vel_total = sqrt(puck_vel_tangential^2 + puck_vel_normal^2);
beta_after = atan2(puck_vel_tangential , puck_vel_normal) - theta2;
%Gives the velocity in x and y coordinates
if puck_vel_normal < 0
puck_vel_y = puck_vel_total * sin(beta_after);
puck_vel_x = -1 *(puck_vel_total * cos(beta_after));
elseif puck_vel_x
else
puck_vel_y = puck_vel_total * sin(beta_after);
puck_vel_x = puck_vel_total * cos(beta_after);
end
%-----------------------------------------------------------------------
%Codes player blocker and puck collision
elseif abs(distance_blue) <= 13
theta3 = -atan2d(b_blocker_y-50 - puck_y, b_blocker_x - puck_x);
alpha = pi - theta3;
beta = atan2d(puck_vel_y, puck_vel_x);
puck_x = b_blocker_x - (r_puck + r_blocker) * cosd(theta3);
puck_y = (b_blocker_y - 50) + (r_puck + r_blocker) * sind(theta3);
%Gives the initial blocker tangential and normal velocities
%Gives the initial blocker tangential and normal velocities
b_blocker_vel_normal_initial = -3;
b_blocker_vel_tangential_initial = 0;
%Gives the initial puck tangential and normal velocities
puck_vel_normal_initial = puck_vel_initial * cosd(theta3 + beta);
puck_vel_tangential_initial = puck_vel_initial * sind(theta3 + beta);
%Gives the after-collision tangential velocities
puck_vel_tangential = puck_vel_tangential_initial;
%Calclustes the momentum and kenetic energy before the collision
momentum_initial = (mass_puck * puck_vel_normal_initial) + (mass_blocker * b_blocker_vel_normal_initial);
kenetic_initial = ((1/2) * mass_puck * puck_vel_initial^2) + ((1/2) * mass_blocker * ((-3)^2));
%Calculates coefficients for the quadraic formula
a = ((mass_puck^2) + mass_puck * mass_blocker)/ mass_blocker;
b = (2 * momentum_initial * mass_puck)/ mass_blocker;
c = (momentum_initial^2/mass_blocker) + mass_puck * puck_vel_tangential_initial^2 + ...
mass_blocker * b_blocker_vel_tangential_initial^2 - 2 * kenetic_initial;
%Gives the normal velocity of the puck after the collision
puck_vel_normal = (-b - sqrt(b^2 - (4 * a * c)))/ (2 * a);
%Calculates the final speeds of the puck and it's new angle
puck_vel_total = sqrt(puck_vel_tangential^2 + puck_vel_normal^2);
beta_after = atan2d(puck_vel_tangential , puck_vel_normal) - theta3;
%Gives the velocity in x and y coordinates
if puck_vel_normal < 0
puck_vel_y = puck_vel_total * sin(beta_after);
puck_vel_x = -1 *(puck_vel_total * cos(beta_after));
else
puck_vel_y = puck_vel_total * sin(beta_after);
puck_vel_x = puck_vel_total * cos(beta_after);
end
%-----------------------------------------------------------------------
%Checks if the puck hits the right wall
elseif puck_x >= 35
puck_vel_x = -puck_vel_x;
if puck_vel_x > 2.5
puck_vel_x = 2.5;
elseif puck_vel_x < -2.5 %Limits the maximum speed of the puck
puck_vel_x = -2.5;
elseif puck_vel_y > 2.5
puck_vel_y = 2.5;
elseif puck_vel_y < -2.5
puck_vel_y = -2.5;
end
%-----------------------------------------------------------------------
%Checks if the puck hits the left wall
elseif puck_x <= -35
puck_vel_x = -puck_vel_x;
if puck_vel_x > 2.5
puck_vel_x = 2.5;
elseif puck_vel_x < -2.5 %Limits the maximum speed of the puck
puck_vel_x = -2.5;
elseif puck_vel_y > 2.5
puck_vel_y = 2.5;
elseif puck_vel_y < -2.5
puck_vel_y = -2.5;
end
%-----------------------------------------------------------------------
%Checkes if the puck hits the bottom wall without hitting the goal
elseif puck_y <= -65 && (puck_x <=-20 || puck_x >=20)
puck_vel_y = -puck_vel_y;
if puck_vel_x > 2.5
puck_vel_x = 2.5;
elseif puck_vel_x < -2.5 %Limits the maximum speed of the puck
puck_vel_x = -2.5;
elseif puck_vel_y > 2.5
puck_vel_y = 2.5;
elseif puck_vel_y < -2.5
puck_vel_y = -2.5;
end
%-----------------------------------------------------------------------
%Checks if the puck hits the top wall without hitting the goal
elseif puck_y >= 65 && (puck_x <=-20 || puck_x >=20)
puck_vel_y = -puck_vel_y;
if puck_vel_x > 2.5
puck_vel_x = 2.5;
elseif puck_vel_x < -2.5 %Limits the maximum speed of the puck
puck_vel_x = -2.5;
elseif puck_vel_y > 2.5
puck_vel_y = 2.5;
elseif puck_vel_y < -2.5
puck_vel_y = -2.5;
end
%-----------------------------------------------------------------------
%Checks if the puck hits the bottom goal
elseif (puck_x >= -20 || puck_x <= 20) && (puck_y < -70)
%Reverses the direction so the oposite player will get the puck
%next
puck_vel_y = -puck_vel_y;
%Resets the puck to the center of the board
puck_x = 0;
puck_y = 0;
%Adds a point to the computer score
computer_score = computer_score + 1;
%Displays the added point in the scoreboard as a natrual number
ComputerScore.String = num2str(computer_score,'%1.0f');
%Gives the player time to reset
pause(1.5)
%-----------------------------------------------------------------------
%Checks if the puck hits the top goal
elseif (puck_x >= -20 || puck_x <= 20) && (puck_y >70)
%Reverses the direction so the oposite player will get the puck
%next
puck_vel_y = -puck_vel_y;
%Resets the puck to the center of the board
puck_x = 0;
puck_y = 0;
%Adds a point to the player score
player_score = player_score + 1;
%Displays the added point in the scoreboard as a natrual number
PlayerScore.String = num2str(player_score,'%1.0f');
%Gives the player time to reset
pause(1.5)
end
%----------------------------------------------------------------------
%Checks if the puck is inside the wall, and makes it not
if puck_x > 36.9
puck_x = 34;
puck_vel_x = - puck_vel_x;
elseif puck_x < -36.9
puck_x = -34;
puck_vel_x = - puck_vel_x;
elseif puck_y < -68 && (puck_x > 20 || puck_x < -20) %If the puck is inside the wall, it will push it too the outside to
puck_y = -67; % attempt to reduce the vibrating/glitching in special instances
puck_vel_y = - puck_vel_y;
elseif puck_y > 68 && (puck_x > 20 || puck_x < -20)
puck_y = 67;
puck_vel_y = - puck_vel_y;
end
end