Ping is a small, client-side Pong variant built with HTML, CSS and JavaScript. The original tutorial used Visual Studio 2013 and an empty ASP.NET Web Site as its project container, but it did not use C# or server-side game logic. You can recreate the exercise today with a static web project, a local development server and the same browser APIs.
This is a guided demonstration, not a guaranteed beginner completion time. An experienced web developer can assemble the core game in about an hour; debugging touch input, responsive layout and collision edge cases will take longer.
What you are building
Ping places a player paddle on the left and a scripted opponent on the right. The ball crosses the arena, can be caught and fired back at an angle, and awards a point when the opposing side misses. Keyboard input supports desktop play, while on-screen controls target phones and tablets.
The result is a single-page browser game. It is not multiplayer and is not server-authoritative. Visual Studio supplies editing, debugging and local execution; the ASP.NET project contributes a hosting shell rather than gameplay code. The original article appeared in MSDN Magazine in March 2015 and was republished by SitePoint on August 27, 2015: Microsoft’s archive and SitePoint’s version.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →#1 Best Overall
Why use browser technologies?
- HTML, CSS and JavaScript skills transfer directly to the game.
- A browser can run the same files on desktop, tablet and phone without a native installation.
- A handful of moving objects does not require Unity, Unreal, WebGL or a game engine.
- Developer tools make the DOM, styles and runtime state easy to inspect.
There are limits. Mobile viewport changes, orientation, touch gestures, accessibility and refresh-rate differences require deliberate testing. For many objects, particles or effects, canvas is usually a better renderer than repeatedly moving DOM nodes.
Choose a current setup
Historical project
The original instructions use Visual Studio 2013 Professional (the code was also updated with the 2013 Community edition), an ASP.NET Empty Web Site, jQuery 2.1.1, image assets and a “Press Start 2P” font. The old menu path was File → New → ASP.NET Empty Web Site, followed by an HTML page, style.css, ping.js and the assets.
Practical modern project
Current Visual Studio installations may not include that Web Site template, and ASP.NET is not technically required. Create a folder with this layout and serve it through a local web server rather than opening file:/// directly:
ping/
index.html
style.css
ping.js
assets/
arena.png
sprites.png
paddle.png
ball.png
press-start-2p.woff2
A minimal ASP.NET Core project, a static-file development server, or another local server can host the same files. Use the editor you already know; do not treat a particular Visual Studio release as a requirement.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteCreate the arena markup
Give each game entity a named element so the first version is easy to inspect:
<div id="arena">
<div id="score" aria-live="polite">
<h1>
<span id="playerScore">0</span>
<span id="opponentScore">0</span>
</h1>
</div>
<div id="player" aria-label="Player paddle"></div>
<div id="opponent" aria-label="Opponent paddle"></div>
<div id="ball" aria-label="Ball"></div>
<div id="controls-left">
<button id="up" type="button" aria-label="Move up">▲</button>
<button id="down" type="button" aria-label="Move down">▼</button>
</div>
<div id="controls-right">
<button id="left" type="button" aria-label="Aim left">◀</button>
<button id="right" type="button" aria-label="Aim right">▶</button>
</div>
</div>
The original used div elements for controls; buttons add keyboard focus and accessible names without changing the game model.
Rank #2
Style a responsive playfield
The tutorial uses a full-screen, relatively positioned arena and absolutely positioned entities:
html, body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
#arena {
position: relative;
width: 100%;
height: 100dvh;
min-height: 320px;
overflow: hidden;
background: url("assets/arena.png") center / cover no-repeat;
}
#score {
position: absolute;
z-index: 2;
left: 50%;
top: 5%;
transform: translateX(-50%);
}
#player, #opponent, #ball { position: absolute; }
#controls-left, #controls-right {
position: absolute;
bottom: max(1rem, env(safe-area-inset-bottom));
display: flex;
gap: .5rem;
touch-action: none;
user-select: none;
}
100dvh tracks the visible mobile viewport more reliably than a plain percentage height; include a fallback if supporting older browsers. The original stretched its background to the arena and warned that portrait mode could look awkward. Test landscape and portrait separately, account for safe-area insets, and decide whether cover cropping or a fixed logical game size better suits your art.
Load sprites and fonts carefully
The historical design uses a sprite sheet: one image containing several control or game graphics, revealed with CSS background-position. This keeps a pixel-art style consistent. Modern HTTP/2 and compression reduce the request-saving advantage, but sprites remain practical for small retro assets.
Plan for an arena background, paddle and ball artwork, control icons, the sprite sheet and the font file. Keep them local or serve them over HTTPS. The old jQuery reference was:
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.1.min.js"></script>
<script src="ping.js"></script>
<link rel="stylesheet" href="style.css">
jQuery 2.1.1 is a historical dependency, not a current recommendation. Either retain it explicitly for a line-by-line reproduction or use a clearly labeled vanilla-JavaScript rewrite. Do not mix assumptions from both versions.
Build a time-based animation loop
Use requestAnimationFrame and velocity in pixels per second, not a fixed movement per frame:
Free tools Windows power users keep installed
One-click scans. No signup required.
let previousTime = 0;
function update(time) {
const deltaTime = Math.min((time - previousTime) / 1000, 0.05);
previousTime = time;
updateGame(deltaTime);
render();
requestAnimationFrame(update);
}
requestAnimationFrame(update);
- Wait for the document and required assets to be ready.
- Create ball, paddle and score state.
- Compute elapsed time in seconds and clamp it after a backgrounded tab resumes.
- Advance the simulation, resolve collisions and update scores.
- Render positions, then schedule the next frame.
Keeping simulation and rendering as separate functions makes later migration to canvas easier. In a larger DOM game, batch style writes and avoid unnecessary layout reads.
Move the ball and resolve collisions
The original represents position and velocity as two-element arrays and applies elapsed time:
position[0] += velocity[0] * elapsed;
position[1] += velocity[1] * elapsed;
Use bounding boxes for the ball and paddles. Reverse vertical velocity at the top and bottom limits, and clamp paddle positions so they cannot leave the arena. At a paddle hit, resolve penetration before reversing direction; otherwise the ball can remain inside the paddle and trigger repeated collisions. Only reverse when the ball is travelling toward the paddle.
When the ball crosses the left or right scoring boundary, increment the opposite score, reset positions and establish the next owner. A more responsive game changes the outgoing angle according to the impact point: a hit near the paddle’s centre produces a shallow angle, while a hit near an edge produces a steeper one. At high speeds, discrete frames can tunnel through a paddle; continuous collision checks or a smaller time step may be necessary.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Add keyboard and pointer controls
Keyboard controls are useful on desktop, while pointer controls replace separate mouse and touch code on mobile:
const keys = new Set();
window.addEventListener("keydown", event => keys.add(event.key));
window.addEventListener("keyup", event => keys.delete(event.key));
const control = document.querySelector("#up");
control.addEventListener("pointerdown", event => {
event.preventDefault();
control.setPointerCapture(event.pointerId);
player.moveUp = true;
});
["pointerup", "pointercancel", "lostpointercapture"].forEach(type =>
control.addEventListener(type, () => { player.moveUp = false; }));
Release movement on pointerup, pointercancel and lost capture so a finger leaving the button cannot leave the paddle moving forever. Set touch-action: none on the controls to prevent scrolling, keep visible focus styles, offer keyboard alternatives and provide a pause mechanism. Do not communicate state through color alone.
Rank #4
Model catching and firing as explicit state
Ping’s distinctive mechanic is ball ownership. A small state machine is clearer than unrelated boolean flags:
free → moving toward player
moving → paddle collision
collided → owned by player
owned → aimed or positioned
owned → fired → moving toward opponent
Decide and document whether a player can hold the ball indefinitely, whether the opponent uses the same capture rule and what reset follows a score. Those are game-design choices, not consequences of ASP.NET. Store the owner, aim and fire transition together so a collision cannot both capture and score in the same update.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Implement the opponent as a simple state machine
The original opponent is scripted behavior, not machine learning. Its useful states are:
Following
Move toward the ball with a maximum tracking speed. Limiting speed prevents an unbeatable paddle.
Aiming and shooting
Choose a direction, apply an angle or controlled error, then fire. Separate the decision from movement so difficulty can be tuned.
Waiting
Pause between actions with a reaction delay. Randomized timing makes the opponent less mechanical without pretending it is intelligent.
For difficulty levels, vary reaction delay, tracking speed, prediction distance and aiming error. Test each level on both slow and high-refresh-rate displays.
Test the game instead of trusting the demo
- Play with a desktop keyboard and verify focus and restart behavior.
- Test touch controls in landscape and portrait, including pointer cancellation and page scrolling.
- Resize from narrow phones to wide monitors and check safe-area placement.
- Background the tab, return to it and confirm the clamped time step prevents a giant jump.
- Try high-DPI and high-refresh-rate screens and a slower device.
- Check reduced-motion preferences, readable score announcements and non-color feedback.
- Use browser developer tools’ Console and Network panels to find syntax errors and missing assets.
Blank page
Check filenames, stylesheet syntax, script order, missing assets and console errors. A blocked insecure http resource on an https page can also stop loading.
Ball does not move
Confirm that requestAnimationFrame starts and schedules itself again, elapsed time is initialized, the ball has position: absolute, and no exception has stopped the loop.
Ball speed is wrong
Check the milliseconds-to-seconds conversion and clamp unusually large deltas:
const deltaTime = Math.min((time - previousTime) / 1000, 0.05);
Collision glitches
Inspect actual element dimensions, resolve penetration, and reject a second collision while the ball is travelling away. Consider continuous collision detection when velocity rises.
When to choose another architecture
| Approach | Best fit | Trade-off |
|---|---|---|
| DOM and CSS | A few entities and a teaching project | Familiar and inspectable, but scales poorly as moving-object count grows |
| Canvas | Many sprites, particles and effects | Centralized 2D rendering, but you must implement text, scaling and accessibility |
| Static hosting | A one-player prototype | Simplest deployment; no server state |
| ASP.NET with server features | Accounts, persistence, telemetry or future matchmaking | Adds server architecture that Ping itself does not need |
Multiplayer is a different project: it needs authoritative state, real-time transport, reconnection, synchronization, latency handling and anti-cheat rules. Adding ASP.NET alone does not provide those systems.
What this tutorial teaches—and what is dated
The durable lessons are entity-based game state, time-based animation, collision geometry, explicit state machines, input cleanup and testing across viewports. The dated parts are the Visual Studio 2013 Web Site workflow, jQuery 2.1.1 and broad claims that the result automatically works on every PC, phone and tablet. Treat the 2015 tutorial as a clear browser-game exercise, then modernize the host, dependencies, viewport handling and accessibility before shipping.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




