This is blinking an LED with an Arduino - advanced edition.

The details are for the Attiny2313, but you can use whatever you want, you can replace the display with whatever you want or just remove it altogether

(note that I do not run the Attiny at CLK/8 - uncheck that box or it'll be slow)

I know the Atmel 8 bit family has hardware PWM, but I wanted to keep it simple and not read the friendly manual again.

So I set up a loop that counts from 0 to 255 in an endless loop.
If the loop counter exceeds a brightness value (which also goes from 0 to 255, but in a programmed, exciting fashion), it switches one LED off and the other on.
This way you get two cross-fading channels of PWM with 8 bits of granularity and full control.
The LEDs are connected to PortA0 and PortA1 (pins 4 and 5).
Now you need a button for each player to press. These go to INT0 and INT1 (pins 6 and 7). These are also set up as ISRs.

And that's where I hit the first snag - My Hello World just added the direction variable to the brightness variable once every 16 passes of the loop mentioned above. But pushing the buttons did not change the direction of the ball, ahem, the way the colors faded. Long story short, I never write the direction variable in the main() and I never read it in the ISRs. Compiler optimization got the better of me and took several hours (and a night's sleep) to figure out. The solution was to declare all variables volatile. (and take a look at the generated assembly listing from time to time)

Next I added the score display. Young me, interested in electronics especially if it contained LEDs and/or looked unusual, but with no knowledge past relays, bought it a quarter century ago. It's 3½ digits and I haven't found a use for it. Until now.
Remember PONG (the real one)? The score only goes to 15, because it's a 4 bit machine. And since I wanted several difficulty settings, I use the tens digit for that so each and every LED in this display gets a chance to light up.
The score display is BUSsed internally, so all segments are connected together, very disappointing for young me as I couldn't make it spell anything back then.
Since we have enough I/O this time, no need for a support chip (but hint hint, if you need to strobe up to 10 digits, consider using the CD4017, it only needs two inputs)
So I set up TIMER0 to do the strobing. I implemented it as a state machine. Depending on the internal count, it enables PortD0, PortD1, PortD4, PortD5 (since D2 and D3 are hardware interrupts). And depending on which port it enables, a different digit lights up and different actions have to be taken.
There is a sevensegment[] Look Up Table that encodes the outputs for PortB.  Note that PortB3 can't be used as this is going to be set up as a buzzer output to give you those beeps and boops of yore. The LUT can be fed a number from 0 to 9 and it translates it to the outputs needed to make a 7-digit display light up that number. Also, it adds a 'd' to announce difficulty setting. You can add many more letters and symbols as you please.

So next I set up sound. I had that in TinyPONG from I think 2009... I'm collecting vintage video game consoles, now one that I made can almost be considered vintage... so I just stole the routine. timer1_init() sets it up and

    OCR1A= 600; //frequency
    TCCR1A |= (1 << COM1A0); //sound on
    _delay_ms(50); //how long you want to beep
    TCCR1A=0; //sound off
makes a Beep. Advantage - no CPU time (I eventually replaced delays with a countdown that triggers in the main loop and turns sound off when it reaches 0). Disadvantage: You're tied to a certain pin and can't use that for other stuff.

Another gotcha that bit me is that for some reasons, interrupts trigger twice most of the time. Even if you do a cli(); as the first command in an ISR. So on the set difficulty screen, it would almost always add two. Eventually I gave up and just rightshifted difficulty. (note that my tapedeck has the same problem on the tape counter for the same reason)

So the only thing left was to program the game!

How does it even work?

Let's start simple. The idea is that a player hits the ball when the LED is all his color.
Let's say the LED is red when brightness=0 and the LED is green when brightness=255.
So when a player pushes the button, you just need to check the brightness to determine if he's hit the ball.
Now with 256 granularities, the time window to really hit the ball is minimal. Also, does real PONG have 1 pixel high paddles? No. And you can set their height.

So instead we check against a threshold. On easy, if the brightness is less than 32 steps away from the target brightness when the button was pushed, it counts as a hit. If the counter overflows. we check direction to see which way it was going and give the other player a point. On higher levels, we decrease the window to 20 steps all the way down to 4.

Now that would work, but players will soon learn that they could spam the button and hit the ball every time.
For that, I declared the area between 64 steps away from goal and whatever the difficulty margin (32..4) is as a cheat detection zone. You missed the ball because you hit early. A point for the opponent.
And to fix a bug when the ball is within that 64 step range, you could press the button rapidly to give both players points and break the game, I set up a flag that is set when a button is pressed (ingame) and reset when the ball hits the middle of the playfield.

The game also has two modes of play, denoted by odd and even levels of difficulty.
On even levels, the speed of the ball increases every 6 successful hits (and resets on a goal which I almost forgot), on odd levels, the speed of the ball depends on how near to the base you hit it - like real PONG where you could angle the ball by hitting it with the edge of the paddle. The closer to home, the faster the ball will go. Also the harder it will be to hit and the easier to miss. So this is really plain old two-dimensional PONG collapsed into a single pixel. Who knew that a single pixel would offer such deep a gameplay? I know. Shallower than a puppy dog's [urine] puddle (C) AVGN.

Since I built it on Monday, I managed to find another person to play it against and it turned out to be more playable than expected (especially if you watch the video of me playing against myself). We managed to keep the ball in play for more than 6 successive hits and it was a somewhat balanced duel until my opponent lost interest at around 5 points.

By the way - on Game Over, hitting both buttons at the same time in a certain fashion (I think the blue one first) makes the game go back to the difficulty setting screen so you can start a new game without having to unplug the console.

So is it fun? Well, erm... with a lot of good intentions and goodwill, let's say maybe.