Tennis for Two video game on an oscilloscope
[Fall 2023] As previously mentioned, our project’s inspiration was drawn from the original “Tennis for Two” video game. We were able to find videos on YouTube of the original gameplay and it was helpful for us to see the pace the ball moved at and some of the underlying physics in the game. For example when we were setting parameters such as gravity and the velocity of the ball, we tried to match a similar ball flight as that to the original. We also drew inspiration from the original controller
- Suitable forB.Tech/B.E Embedded Systems students
- Build time4-6 weeks
- LevelIntermediate
- Available atAjvika Labs, Kakinada
About this project
Tennis for Two Implemented on the RP2040
by Gabe Taylor and Jack Strope
“A recreation of one of the earliest video games depicting tennis displayed on an oscilloscope.”
Project Introduction
For our final project, we decided to recreate one of the earliest video games, “Tennis for Two”, which was displayed on an oscilloscope. “Tennis for Two'' was created at Brookhaven National Laboratory in 1958 by William Higinbotham and Robert Dvorak. The game follows pretty similar rules to actual tennis with regards to how points are played out (e.g. each player can only hit the ball on their side, the ball can not bounce twice on either side during play, etc.). Our main inspiration for wanting to recreate this game was the challenge of developing a game that could be displayed on an oscilloscope. We both had ample experience with oscilloscopes through previous classes, but using the oscilloscope in xy mode was a new learning experience for us. We also had an interest in tennis which added to our eagerness to recreate this game specifically on the oscilloscope.
High Level Design
Rationale and Sources of Project Idea
As previously mentioned, our project’s inspiration was drawn from the original “Tennis for Two” video game. We were able to find videos on YouTube of the original gameplay and it was helpful for us to see the pace the ball moved at and some of the underlying physics in the game. For example when we were setting parameters such as gravity and the velocity of the ball, we tried to match a similar ball flight as that to the original. We also drew inspiration from the original controllers they designed for the game which consisted of a push button and a knob. When designing and building the controllers, we aimed for creating a very similar model.
Oscilloscope and Background Math
Before delving further into how we designed our “Tennis for Two” game, how we drew to the oscilloscope needs to be discussed. We were able to draw to the oscilloscope by operating it in xy mode. More commonly the oscilloscope is operated in the yt mode. By eliminating the time element and adding the extra x axis, we had much more flexibility to draw than yt mode. Now, the voltage signal that Channel 1 read corresponded to the x position. The voltage signal that Channel 2 read corresponded to the y position. For example, if Channel 1 and Channel 2 were both hooked up to a 1V signal, then we would see a small dot at (x, y) = (1V, 1V) on the oscilloscope.
Because we were able to write points to the oscilloscope, the mathematics we utilized consisted primarily of those related computing trajectories as well as computing angles of the ball after the ball had interacted with a surface. We used the standard trajectory equations that you would find in a first year physics course. The equations that governed the position of the ball are as follows:
x = x0 + t*v0*cos(theta)
y = y0 + t*v0*sin(theta) + 0.5*g*t^2
x0 and y0 represent the previous position of the ball, t represents the amount of time elapsed since the beginning of the trajectory, v0 is the initial velocity, g is gravity, and theta is the angle at the beginning of the trajectory. g and v0 are constants that we determined based on trial and error of gameplay and the speed of the game that we thought was best. The launch angle, theta , was determined by user input, specifically the knob on each player’s controller. Lastly we kept time t as a counter variable. In our case, we used an interrupt service routine (ISR) to configure a signal from the RP2040 that we sent to a DAC that was then sent to the oscilloscope. This will be discussed further when we talk more about the DAC, but time t was the number of interrupts since the beginning of the trajectory.
We implemented bouncing the ball off of surfaces by calculating the new launch angle theta when the ball hits the ground or net. When the ball hits a surface, the angle just before collision is found by measuring the difference between the x and y coordinates of the ball as it hits the surface and just before the collision, and using these to calculate the incident angle using arctangent. The reflected angle is the same as the incident angle, but reflected about the axis normal to the surface. This is used as the new launch angle for the trajectory equations above. We also implemented some kinetic energy loss on bounces by simply multiplying the initial velocity, v0, by a positive coefficient less than 1. For the net, we made this coefficient 0.5, since a real tennis ball loses a lot of speed when it hits the net. For bouncing off the ground, we found that the first bounce looked best with no loss of speed, which makes sense since real tennis balls lose very little speed when hitting the ground. However, if the ball bounces multiple times–after hitting the net, for example–some damping was necessary for the bouncing to look realistic. So, we set our damping coefficient to 0.9 when bouncing off the ground, but only if the ball has already bounced once, off the net or ground.
Logical Structure
The structure of our code consists of two interrupt service routines and two threads. Both of our interrupt service routines are dedicated to configuring a SPI transaction to a DAC to output what we want to draw to the oscilloscope. One of the service interrupt service routines is responsible for drawing the ball and the other is responsible for drawing the ground and net to the screen. It was crucial for us to use two separate ISRs because the frequency at which each triggers is significantly different. The ISR responsible for drawing the ground and the net fires at a frequency of 66.67 Hz or every 15000 microseconds. The ISR responsible for drawing the ball fires at a frequency of 142.86 Hz or every 7000 microseconds. The frequency that the ball was drawn was balanced mainly on how fast we wanted the game to play out. The noticed that the higher we made the frequency for the net and ground, the noiser the net and ground became. At the same time we needed to draw these components fast enough where you could not see gaps in them. As a result, we found the lowest frequency where we thought the net and ground looked the best.
We had one main thread in which all of our mathematical computations, game scoring logic, and game physics were implemented. This thread was set up in a careful way. Because we calculate the future position of the ball in the thread, we were concerned about how many times the ball’s position could be updated between ISRs. Ideally, we only wanted the ball position to be updated once between ISRs so we would get a smooth and reliable ball motion on the oscilloscope. To achieve this, we set up a semaphore that triggered the game logic thread to run at the end of each ISR that was responsible for drawing the ball. This ensured that the thread would only run once between each ISR.
Our second thread we left in our attached code, but is not visible or utilized by the user. We set up a PuTTY connection to help us debug, specifically when we were determining if our math for determining angles was working. It was really convenient being able to print out the current angle to the PuTTY terminal.
Program/Hardware Design
Program Details
As previously mentioned, our code had two ISRs and two threads. Most of the “meat” of our code is in our game logic thread called game_logic. This is because the two ISRs are really just preparing the values to be sent to the DAC. In the game_logic thread we are essentially calculating the new position of the ball, then we are checking to see if any conditions are met by the new position of the ball to either end the current point, or change the direction of the ball. There are multiple ways that a point can end. They are below:
1. A player hits the ball out of bounds without hitting the other player’s side
2. A player lets the ball bounce twice on their side
3. A player lets the ball bounce once on their side then go out of bounds
4. A player hits the ball into the ground on their own side or it hits the net then bounces onto the ground on their own side
Similarly, there are multiple ways that the ball trajectory can change. They are below:
1. The ball hits the ground
2. A player hits the ball
3. The ball hits the net
Because all of these sudden changes to the game depend on a player's action or where the ball is, we use a lot of conditional statements in this thread to check button presses and ball position. At the end of the thread, we update our score if either player has won a point. This is simply done by setting GPIO pins high to light up an LED on our score tracking device. Our heavily commented code implementation is below in Appendix E.
One more feature we added to the game that hasn’t been mentioned so far is our “loading/start” screen. This screen is displayed after a game is played and when first booting up the RP2040. On this screen, there is a ball traveling in a perfect circle resembling the loading symbol. The game is waiting for either player to hit their respective button. Whichever player hits their button will be the server for the upcoming game. We added this screen in because we wanted the option for either player to be the server and we believed that having a different screen would best indicate this. Specifically we chose to make the loading circle our screen because at the beginning of this project we experimented with drawing trajectories with the direct digital synthesis algorithm as in lab 1. As a result, some of the code we used is repurposed from lab 1 such as building the sin table in the main function. Although the game still looked pretty cool and arcade-like when we first started off using direct digital synthesis to draw circles for trajectories, it looked much more realistic after we implemented the parabolic trajectories instead.
Hardware Details
There were three components to our hardware. The first component was the DAC that we communicated with via a SPI channel to output ou
by Gabe Taylor and Jack Strope
“A recreation of one of the earliest video games depicting tennis displayed on an oscilloscope.”
Project Introduction
For our final project, we decided to recreate one of the earliest video games, “Tennis for Two”, which was displayed on an oscilloscope. “Tennis for Two'' was created at Brookhaven National Laboratory in 1958 by William Higinbotham and Robert Dvorak. The game follows pretty similar rules to actual tennis with regards to how points are played out (e.g. each player can only hit the ball on their side, the ball can not bounce twice on either side during play, etc.). Our main inspiration for wanting to recreate this game was the challenge of developing a game that could be displayed on an oscilloscope. We both had ample experience with oscilloscopes through previous classes, but using the oscilloscope in xy mode was a new learning experience for us. We also had an interest in tennis which added to our eagerness to recreate this game specifically on the oscilloscope.
High Level Design
Rationale and Sources of Project Idea
As previously mentioned, our project’s inspiration was drawn from the original “Tennis for Two” video game. We were able to find videos on YouTube of the original gameplay and it was helpful for us to see the pace the ball moved at and some of the underlying physics in the game. For example when we were setting parameters such as gravity and the velocity of the ball, we tried to match a similar ball flight as that to the original. We also drew inspiration from the original controllers they designed for the game which consisted of a push button and a knob. When designing and building the controllers, we aimed for creating a very similar model.
Oscilloscope and Background Math
Before delving further into how we designed our “Tennis for Two” game, how we drew to the oscilloscope needs to be discussed. We were able to draw to the oscilloscope by operating it in xy mode. More commonly the oscilloscope is operated in the yt mode. By eliminating the time element and adding the extra x axis, we had much more flexibility to draw than yt mode. Now, the voltage signal that Channel 1 read corresponded to the x position. The voltage signal that Channel 2 read corresponded to the y position. For example, if Channel 1 and Channel 2 were both hooked up to a 1V signal, then we would see a small dot at (x, y) = (1V, 1V) on the oscilloscope.
Because we were able to write points to the oscilloscope, the mathematics we utilized consisted primarily of those related computing trajectories as well as computing angles of the ball after the ball had interacted with a surface. We used the standard trajectory equations that you would find in a first year physics course. The equations that governed the position of the ball are as follows:
x = x0 + t*v0*cos(theta)
y = y0 + t*v0*sin(theta) + 0.5*g*t^2
x0 and y0 represent the previous position of the ball, t represents the amount of time elapsed since the beginning of the trajectory, v0 is the initial velocity, g is gravity, and theta is the angle at the beginning of the trajectory. g and v0 are constants that we determined based on trial and error of gameplay and the speed of the game that we thought was best. The launch angle, theta , was determined by user input, specifically the knob on each player’s controller. Lastly we kept time t as a counter variable. In our case, we used an interrupt service routine (ISR) to configure a signal from the RP2040 that we sent to a DAC that was then sent to the oscilloscope. This will be discussed further when we talk more about the DAC, but time t was the number of interrupts since the beginning of the trajectory.
We implemented bouncing the ball off of surfaces by calculating the new launch angle theta when the ball hits the ground or net. When the ball hits a surface, the angle just before collision is found by measuring the difference between the x and y coordinates of the ball as it hits the surface and just before the collision, and using these to calculate the incident angle using arctangent. The reflected angle is the same as the incident angle, but reflected about the axis normal to the surface. This is used as the new launch angle for the trajectory equations above. We also implemented some kinetic energy loss on bounces by simply multiplying the initial velocity, v0, by a positive coefficient less than 1. For the net, we made this coefficient 0.5, since a real tennis ball loses a lot of speed when it hits the net. For bouncing off the ground, we found that the first bounce looked best with no loss of speed, which makes sense since real tennis balls lose very little speed when hitting the ground. However, if the ball bounces multiple times–after hitting the net, for example–some damping was necessary for the bouncing to look realistic. So, we set our damping coefficient to 0.9 when bouncing off the ground, but only if the ball has already bounced once, off the net or ground.
Logical Structure
The structure of our code consists of two interrupt service routines and two threads. Both of our interrupt service routines are dedicated to configuring a SPI transaction to a DAC to output what we want to draw to the oscilloscope. One of the service interrupt service routines is responsible for drawing the ball and the other is responsible for drawing the ground and net to the screen. It was crucial for us to use two separate ISRs because the frequency at which each triggers is significantly different. The ISR responsible for drawing the ground and the net fires at a frequency of 66.67 Hz or every 15000 microseconds. The ISR responsible for drawing the ball fires at a frequency of 142.86 Hz or every 7000 microseconds. The frequency that the ball was drawn was balanced mainly on how fast we wanted the game to play out. The noticed that the higher we made the frequency for the net and ground, the noiser the net and ground became. At the same time we needed to draw these components fast enough where you could not see gaps in them. As a result, we found the lowest frequency where we thought the net and ground looked the best.
We had one main thread in which all of our mathematical computations, game scoring logic, and game physics were implemented. This thread was set up in a careful way. Because we calculate the future position of the ball in the thread, we were concerned about how many times the ball’s position could be updated between ISRs. Ideally, we only wanted the ball position to be updated once between ISRs so we would get a smooth and reliable ball motion on the oscilloscope. To achieve this, we set up a semaphore that triggered the game logic thread to run at the end of each ISR that was responsible for drawing the ball. This ensured that the thread would only run once between each ISR.
Our second thread we left in our attached code, but is not visible or utilized by the user. We set up a PuTTY connection to help us debug, specifically when we were determining if our math for determining angles was working. It was really convenient being able to print out the current angle to the PuTTY terminal.
Program/Hardware Design
Program Details
As previously mentioned, our code had two ISRs and two threads. Most of the “meat” of our code is in our game logic thread called game_logic. This is because the two ISRs are really just preparing the values to be sent to the DAC. In the game_logic thread we are essentially calculating the new position of the ball, then we are checking to see if any conditions are met by the new position of the ball to either end the current point, or change the direction of the ball. There are multiple ways that a point can end. They are below:
1. A player hits the ball out of bounds without hitting the other player’s side
2. A player lets the ball bounce twice on their side
3. A player lets the ball bounce once on their side then go out of bounds
4. A player hits the ball into the ground on their own side or it hits the net then bounces onto the ground on their own side
Similarly, there are multiple ways that the ball trajectory can change. They are below:
1. The ball hits the ground
2. A player hits the ball
3. The ball hits the net
Because all of these sudden changes to the game depend on a player's action or where the ball is, we use a lot of conditional statements in this thread to check button presses and ball position. At the end of the thread, we update our score if either player has won a point. This is simply done by setting GPIO pins high to light up an LED on our score tracking device. Our heavily commented code implementation is below in Appendix E.
One more feature we added to the game that hasn’t been mentioned so far is our “loading/start” screen. This screen is displayed after a game is played and when first booting up the RP2040. On this screen, there is a ball traveling in a perfect circle resembling the loading symbol. The game is waiting for either player to hit their respective button. Whichever player hits their button will be the server for the upcoming game. We added this screen in because we wanted the option for either player to be the server and we believed that having a different screen would best indicate this. Specifically we chose to make the loading circle our screen because at the beginning of this project we experimented with drawing trajectories with the direct digital synthesis algorithm as in lab 1. As a result, some of the code we used is repurposed from lab 1 such as building the sin table in the main function. Although the game still looked pretty cool and arcade-like when we first started off using direct digital synthesis to draw circles for trajectories, it looked much more realistic after we implemented the parabolic trajectories instead.
Hardware Details
There were three components to our hardware. The first component was the DAC that we communicated with via a SPI channel to output ou
Technologies used
Hardware components
- RP2040
- DAC
Software & tools
- C
- Arduino IDE
Project demo video
What you get
- Working project / hardware kit, tested before delivery
- Complete source code with explanation
- Project demo and guidance for your viva
- Project report, PPT and IEEE paper support (on request)
Delivery & Payment
Delivery takes a minimum of two weeks from the date your inquiry is confirmed.
Payment is in two parts:
• 50% advance to start the project
• Remaining 50% before delivery
Note:
• Documentation and PPT are not included by default
• If you need documentation/PPT, it can be arranged for extra time and cost
We'll confirm the exact timeline and cost with you after reviewing your requirements.