
Sports
You play a sport or compete in races: controlling an athlete, a team or a vehicle, or running a club from the manager's office. The challenge combines the sport's rules and tactics with your reflexes or your management.
Also known as: sports game, sports video game
Defining mechanics
- The rules of a real sport, simplified and enforced by the game, which acts as referee with the score, serves, fouls, outs and the clock.
- Direct control of an athlete or, in team sports, of the player nearest the ball, while the AI moves your teammates and the opponent.
- Shots, passes and throws whose outcome depends on when and in which direction you make them, not just on pressing a button.
- Player and team attributes (speed, power, accuracy), often based on real, licensed athletes and leagues.
- Variants with layers of their own, such as running a club, extreme sports built on tricks and combos, and racing, which forms a subgenre of its own.
Gameplay loop
Read the play, get into position, hit or pass at the right moment, watch the score change and adjust your tactics to the opponent until the end of the match.
Our demo
Controls. Up and down arrows or W/S to move your racket (the yellow one). With the mouse or a finger, the racket moves towards the pointer. When it is your serve, press space, Enter or click.
Open full screenA top-down tennis game against an AI, with simplified real rules. There is a cross-court serve with a change of side every point, a change of server every game, the 15-30-40 count with deuce and advantage, and the ball is out if it leaves over the sidelines. Where the ball touches the racket decides where it goes. The first to win three games wins.
Made with Kaplay, in about 175 lines of JavaScript.
How it's made
Setup and constants
The game is created with virtual buttons (up, down, serve), and the court is defined along with the numbers that decide how it plays, such as the speed of the players, the speed of the ball and how much it speeds up with each hit, and AIM_SPREAD, which says how much the angle opens up when you hit with the racket's edge.
const k = kaplay({ width: 640, height: 360, letterbox: true, pixelDensity, background: [46, 110, 70], buttons: { up: { keyboard: ["up", "w"] }, down: { keyboard: ["down", "s"] }, serve: { keyboard: ["space", "enter"] }, }, }); // The court, seen from above: baselines left and right, sidelines top and bottom. const COURT = { left: 40, right: 600, top: 50, bottom: 330 }; const MID_Y = (COURT.top + COURT.bottom) / 2; const HALF_H = (COURT.bottom - COURT.top) / 2; const RACKET_H = 56; const PLAYER_SPEED = 330; // px/s, the same for you and the AI const BALL_SPEED = 320; // first shot of a rally const MAX_SPEED = 560; // every hit is 5% faster, up to this const AIM_SPREAD = 1.25; // hitting with the racket's edge aims beyond the sideline const GAMES_TO_WIN = 3;The court
The court is drawn with lines, which are the baselines, sidelines, service lines, centre line and net. The lines are not decoration, because the rules rely on them to decide whether the ball is in or out.
onDraw(() => { const { left, right, top, bottom } = COURT; drawRect({ pos: vec2(left, top), width: right - left, height: bottom - top, color: rgb(60, 120, 190) }); const line = (a, b) => drawLine({ p1: vec2(...a), p2: vec2(...b), width: 2, color: rgb(255, 255, 255) }); line([left, top], [right, top]); line([left, bottom], [right, bottom]); line([left, top], [left, bottom]); line([right, top], [right, bottom]); line([170, top], [170, bottom]); // service lines line([470, top], [470, bottom]); line([170, MID_Y], [470, MID_Y]); // centre service line drawLine({ p1: vec2(320, top - 10), p2: vec2(320, bottom + 10), width: 4, color: rgb(230, 230, 230) }); // net });The players
Each player is a racket that moves vertically, with the same top speed for you and the AI, so the match is fair. With the keyboard you move directly. With the mouse or a finger, the racket heads towards the pointer at that same speed. You can step slightly outside the court to reach wide balls.
const racket = (x, col) => add([rect(10, RACKET_H), pos(x, MID_Y), anchor("center"), color(col), { goal: MID_Y }]); const players = [racket(COURT.left + 12, rgb(255, 220, 90)), racket(COURT.right - 12, rgb(255, 120, 120))]; const [me, ai] = players; const moveTowards = (p, goal) => { p.pos.y += clamp(goal - p.pos.y, -PLAYER_SPEED * dt(), PLAYER_SPEED * dt()); p.pos.y = clamp(p.pos.y, 20, 340); // you may step out of the court to reach wide balls }; // Keyboard moves the racket; the mouse or a finger sets where it should go. let pointerY = null; onMouseMove((p) => (pointerY = p.y)); me.onUpdate(() => { if (state === "serve" && server === 0) return; // the server stands still to serve if (isButtonDown("up") || isButtonDown("down")) { pointerY = null; me.pos.y += (isButtonDown("down") ? 1 : -1) * PLAYER_SPEED * dt(); me.pos.y = clamp(me.pos.y, 20, 340); } else if (pointerY !== null) moveTowards(me, pointerY); });The rival AI
The AI works out where the ball will cross its line and places the racket so the ball hits the part it wants, and that is how it aims. It reacts with a delay, like a person, and picks a safe angle at random. It also knows the rules, because it lets go balls that are flying out, since that wins it the point.
// The AI predicts where the ball will cross its baseline and places the racket // so the ball hits the part it wants (that is how it aims). It reacts late, // like a person, and it lets go balls that are flying out: they lose the point. let aiDelay = 0; let aiAim = 0; ai.onUpdate(() => { if (state === "serve" && server === 1) return; aiDelay -= dt(); let goal = MID_Y; if (state === "rally" && ball.vel.x > 0 && aiDelay <= 0) { const y = ball.pos.y + (ball.vel.y * (ai.pos.x - ball.pos.x)) / ball.vel.x; const goingOut = y < COURT.top - 6 || y > COURT.bottom + 6; if (!goingOut) goal = y - aiAim * (RACKET_H / 2); } moveTowards(ai, goal); });The ball and the shot
The ball travels in a straight line towards a point on the rival's baseline. A hit happens when it crosses the racket's line within its height. The crossing is checked rather than the overlap, so a fast ball cannot pass through the racket between two frames. Where it touches decides the target, as the centre sends it down the middle while the edge opens it towards the lines. Each hit makes it 5% faster.
const ball = add([circle(6), pos(0, 0), anchor("center"), color(230, 255, 120), { vel: vec2(0, 0), speed: BALL_SPEED }]); let lastHitter = 0; // Shoot the ball from where it is towards a point on the other baseline. function shoot(from, targetY, speed) { const targetX = from === 0 ? COURT.right : COURT.left; ball.speed = speed; ball.vel = vec2(targetX, targetY).sub(ball.pos).unit().scale(speed); lastHitter = from; if (from === 0) { aiDelay = 0.22; // reaction time aiAim = rand(-0.75, 0.75); } } // A hit happens when the ball crosses the racket's line within its height. // Where it touches the racket (centre or edge) decides the aim, as in Pong. function checkHit(p, who, prevX) { const crossed = who === 0 ? prevX >= p.pos.x && ball.pos.x < p.pos.x : prevX <= p.pos.x && ball.pos.x > p.pos.x; const offset = (ball.pos.y - p.pos.y) / (RACKET_H / 2 + 6); if (!crossed || Math.abs(offset) > 1) return; ball.pos.x = p.pos.x; shoot(who, MID_Y + offset * HALF_H * AIM_SPREAD, Math.min(MAX_SPEED, ball.speed * 1.05)); } ball.onUpdate(() => { if (state === "serve") { const s = players[server]; // the ball waits in front of the server ball.pos = s.pos.add(server === 0 ? 16 : -16, 0); return; } if (state !== "rally") return; const prevX = ball.pos.x; ball.pos = ball.pos.add(ball.vel.scale(dt())); if (ball.vel.x < 0) checkHit(me, 0, prevX); else checkHit(ai, 1, prevX);Rules, out and serve
This is the genre's mechanic, the game acting as referee. If the ball leaves over a sideline before it is returned, it is out and the point goes to the hitter's rival. If it crosses a baseline inside the court, whoever had to return it missed. The serve goes cross-court and the server switches sides every point, right on even points and left on odd ones, as in real tennis. Aiming at the lines is a risk the rules punish or reward.
// Simplified tennis rules. If the ball leaves the court over a sideline // before anyone returns it, it is OUT: the point goes to the other player. // If it passes a baseline inside the court, the receiver missed it. const out = ball.pos.y < COURT.top || ball.pos.y > COURT.bottom; if (out) return endPoint(1 - lastHitter, t({ es: "¡Fuera!", en: "Out!" })); if (ball.pos.x < COURT.left - 10) return endPoint(1, t({ es: "Punto del rival", en: "Rival's point" })); if (ball.pos.x > COURT.right + 10) return endPoint(0, t({ es: "¡Punto para ti!", en: "Your point!" })); }); // The serve goes cross-court, and the server switches side every point // (right side on even points, left on odd ones), as in real tennis. let state = "serve"; let server = 0; function placeServer() { const played = score.points[0] + score.points[1]; const side = played % 2 === 0 ? 1 : -1; // 1 = server's right-hand side players[server].pos.y = MID_Y + side * (server === 0 ? 1 : -1) * HALF_H * 0.5; if (server === 1) wait(1.2, () => state === "serve" && serve()); } function serve() { state = "rally"; const s = players[server]; shoot(server, MID_Y - (s.pos.y - MID_Y), BALL_SPEED * 0.9); // aim at the opposite diagonal } onButtonPress("serve", () => state === "serve" && server === 0 && serve()); onMousePress(() => state === "serve" && server === 0 && serve());Tennis scoring
Points are counted 0, 15, 30, 40 and game. At 40 all it is «deuce», and two points in a row are needed, advantage and then game. Every game changes the server. Turning an odd count like this into a few conditions is a good example of how a video game encodes a rulebook.
// Tennis counts 0, 15, 30, 40, game. At 40–40 it is «deuce»: you need two // points in a row (advantage, then game). The server changes every game. const score = { points: [0, 0], games: [0, 0] }; const CALLS = ["0", "15", "30", "40"]; function callScore() { const [a, b] = score.points; if (a >= 3 && b >= 3) { if (a === b) return t({ es: "Iguales", en: "Deuce" }); return a > b ? t({ es: "Ventaja tuya", en: "Advantage you" }) : t({ es: "Ventaja rival", en: "Advantage rival" }); } return `${CALLS[a]} – ${CALLS[b]}`; } function endPoint(winner, message) { state = "pause"; const p = score.points; p[winner]++; if (p[winner] >= 4 && p[winner] - p[1 - winner] >= 2) { score.games[winner]++; score.points = [0, 0]; server = 1 - server; message += "\n" + t({ es: "Juego", en: "Game" }); } banner.text = message; wait(1.4, () => { banner.text = ""; if (score.games[winner] === GAMES_TO_WIN) return go("end", winner === 0, score.games); state = "serve"; placeServer(); }); }Scoreboard
The scoreboard shows the games and the current game's count as it is called in tennis, and tells you when it is your serve. After each point a banner explains what happened (out, point, game), so the player learns the rules while playing.
const hud = add([text("", { size: 16, font: FONT, align: "center" }), pos(320, 22), anchor("center")]); const banner = add([text("", { size: 26, font: FONT, align: "center" }), pos(320, 160), anchor("center"), z(10)]); onUpdate(() => { const serving = state === "serve" && server === 0 ? t({ es: " · Sacas tú: espacio o clic", en: " · Your serve: space or click" }) : ""; const games = t({ es: `Tú ${score.games[0]} – ${score.games[1]} Rival`, en: `You ${score.games[0]} – ${score.games[1]} Rival` }); hud.text = `${games} · ${callScore()}${serving}`; }); placeServer();Scenes
The game has three scenes, title, game and end. The title explains the controls and the key idea of aiming with the racket's edge, and the end scene shows the result of the set.
function screen(title, subtitle) { add([text(title, { size: 34, font: FONT, align: "center" }), pos(center().sub(0, 50)), anchor("center")]); add([text(subtitle, { size: 16, font: FONT, align: "center", width: 560 }), pos(center().add(0, 30)), anchor("center"), color(220, 240, 220)]); onButtonPress("serve", () => go("game")); onClick(() => go("game")); } scene("title", () => screen( t({ es: "Tenis", en: "Tennis" }), t({ es: `Mueve tu raqueta (amarilla) con las flechas o W/S, o con el ratón o el dedo. Donde toca la bola decide hacia dónde sale: con el borde apuntas a las líneas, pero puede irse fuera. Gana ${GAMES_TO_WIN} juegos.\nEspacio o clic para empezar.`, en: `Move your racket (yellow) with the arrows or W/S, or with the mouse or a finger. Where the ball touches it decides where it goes: the edge aims at the lines, but it may go out. Win ${GAMES_TO_WIN} games.\nSpace or click to start.`, }), ), ); scene("end", (won, games) => screen( won ? t({ es: "¡Ganaste el set!", en: "You won the set!" }) : t({ es: "El rival gana el set", en: "The rival wins the set" }), t({ es: `Juegos ${games[0]} – ${games[1]}. Espacio o clic para jugar otra vez.`, en: `Games ${games[0]} – ${games[1]}. Space or click to play again.` }), ), ); go("title");
What we simplified
- The ball does not bounce or have height, and the net does not stop it. A shot is «in» if the ball crosses the rival's baseline inside the court, and «out» if it leaves over a sideline first.
- The players only move vertically along their baseline, and there are no double faults, no lets on the serve, no tie-break and no change of ends.
- The set is won with three games and without needing a two-game lead (six in real tennis), and the score always shows your points first even when the rival serves.
To modify it
- Require a two-game lead to win the set, and add a 7-point tie-break when it is tied.
- Add a second serve. If the first one goes out it is a fault, and two faults in a row give the point to the rival.
- Let the players also move horizontally, to come up to the net or drop back, and think about what changes in the AI.
- Change the sport to volleyball or badminton, deciding which lines, serves and scoring are needed.
Full code
// Sports demo (tennis)
// Engine: Kaplay 3001 (https://kaplayjs.com). MIT licence.
import kaplay from "../../vendor/kaplay.mjs";
import { t, FONT, pixelDensity } from "../common.js";
// #region setup
const k = kaplay({
width: 640,
height: 360,
letterbox: true, pixelDensity,
background: [46, 110, 70],
buttons: {
up: { keyboard: ["up", "w"] },
down: { keyboard: ["down", "s"] },
serve: { keyboard: ["space", "enter"] },
},
});
// The court, seen from above: baselines left and right, sidelines top and bottom.
const COURT = { left: 40, right: 600, top: 50, bottom: 330 };
const MID_Y = (COURT.top + COURT.bottom) / 2;
const HALF_H = (COURT.bottom - COURT.top) / 2;
const RACKET_H = 56;
const PLAYER_SPEED = 330; // px/s, the same for you and the AI
const BALL_SPEED = 320; // first shot of a rally
const MAX_SPEED = 560; // every hit is 5% faster, up to this
const AIM_SPREAD = 1.25; // hitting with the racket's edge aims beyond the sideline
const GAMES_TO_WIN = 3;
// #endregion
scene("game", () => {
// #region court
onDraw(() => {
const { left, right, top, bottom } = COURT;
drawRect({ pos: vec2(left, top), width: right - left, height: bottom - top, color: rgb(60, 120, 190) });
const line = (a, b) => drawLine({ p1: vec2(...a), p2: vec2(...b), width: 2, color: rgb(255, 255, 255) });
line([left, top], [right, top]);
line([left, bottom], [right, bottom]);
line([left, top], [left, bottom]);
line([right, top], [right, bottom]);
line([170, top], [170, bottom]); // service lines
line([470, top], [470, bottom]);
line([170, MID_Y], [470, MID_Y]); // centre service line
drawLine({ p1: vec2(320, top - 10), p2: vec2(320, bottom + 10), width: 4, color: rgb(230, 230, 230) }); // net
});
// #endregion
// #region players
const racket = (x, col) => add([rect(10, RACKET_H), pos(x, MID_Y), anchor("center"), color(col), { goal: MID_Y }]);
const players = [racket(COURT.left + 12, rgb(255, 220, 90)), racket(COURT.right - 12, rgb(255, 120, 120))];
const [me, ai] = players;
const moveTowards = (p, goal) => {
p.pos.y += clamp(goal - p.pos.y, -PLAYER_SPEED * dt(), PLAYER_SPEED * dt());
p.pos.y = clamp(p.pos.y, 20, 340); // you may step out of the court to reach wide balls
};
// Keyboard moves the racket; the mouse or a finger sets where it should go.
let pointerY = null;
onMouseMove((p) => (pointerY = p.y));
me.onUpdate(() => {
if (state === "serve" && server === 0) return; // the server stands still to serve
if (isButtonDown("up") || isButtonDown("down")) {
pointerY = null;
me.pos.y += (isButtonDown("down") ? 1 : -1) * PLAYER_SPEED * dt();
me.pos.y = clamp(me.pos.y, 20, 340);
} else if (pointerY !== null) moveTowards(me, pointerY);
});
// #endregion
// #region ai
// The AI predicts where the ball will cross its baseline and places the racket
// so the ball hits the part it wants (that is how it aims). It reacts late,
// like a person, and it lets go balls that are flying out: they lose the point.
let aiDelay = 0;
let aiAim = 0;
ai.onUpdate(() => {
if (state === "serve" && server === 1) return;
aiDelay -= dt();
let goal = MID_Y;
if (state === "rally" && ball.vel.x > 0 && aiDelay <= 0) {
const y = ball.pos.y + (ball.vel.y * (ai.pos.x - ball.pos.x)) / ball.vel.x;
const goingOut = y < COURT.top - 6 || y > COURT.bottom + 6;
if (!goingOut) goal = y - aiAim * (RACKET_H / 2);
}
moveTowards(ai, goal);
});
// #endregion
// #region ball
const ball = add([circle(6), pos(0, 0), anchor("center"), color(230, 255, 120), { vel: vec2(0, 0), speed: BALL_SPEED }]);
let lastHitter = 0;
// Shoot the ball from where it is towards a point on the other baseline.
function shoot(from, targetY, speed) {
const targetX = from === 0 ? COURT.right : COURT.left;
ball.speed = speed;
ball.vel = vec2(targetX, targetY).sub(ball.pos).unit().scale(speed);
lastHitter = from;
if (from === 0) {
aiDelay = 0.22; // reaction time
aiAim = rand(-0.75, 0.75);
}
}
// A hit happens when the ball crosses the racket's line within its height.
// Where it touches the racket (centre or edge) decides the aim, as in Pong.
function checkHit(p, who, prevX) {
const crossed = who === 0 ? prevX >= p.pos.x && ball.pos.x < p.pos.x : prevX <= p.pos.x && ball.pos.x > p.pos.x;
const offset = (ball.pos.y - p.pos.y) / (RACKET_H / 2 + 6);
if (!crossed || Math.abs(offset) > 1) return;
ball.pos.x = p.pos.x;
shoot(who, MID_Y + offset * HALF_H * AIM_SPREAD, Math.min(MAX_SPEED, ball.speed * 1.05));
}
ball.onUpdate(() => {
if (state === "serve") {
const s = players[server]; // the ball waits in front of the server
ball.pos = s.pos.add(server === 0 ? 16 : -16, 0);
return;
}
if (state !== "rally") return;
const prevX = ball.pos.x;
ball.pos = ball.pos.add(ball.vel.scale(dt()));
if (ball.vel.x < 0) checkHit(me, 0, prevX);
else checkHit(ai, 1, prevX);
// #endregion
// #region rules
// Simplified tennis rules. If the ball leaves the court over a sideline
// before anyone returns it, it is OUT: the point goes to the other player.
// If it passes a baseline inside the court, the receiver missed it.
const out = ball.pos.y < COURT.top || ball.pos.y > COURT.bottom;
if (out) return endPoint(1 - lastHitter, t({ es: "¡Fuera!", en: "Out!" }));
if (ball.pos.x < COURT.left - 10) return endPoint(1, t({ es: "Punto del rival", en: "Rival's point" }));
if (ball.pos.x > COURT.right + 10) return endPoint(0, t({ es: "¡Punto para ti!", en: "Your point!" }));
});
// The serve goes cross-court, and the server switches side every point
// (right side on even points, left on odd ones), as in real tennis.
let state = "serve";
let server = 0;
function placeServer() {
const played = score.points[0] + score.points[1];
const side = played % 2 === 0 ? 1 : -1; // 1 = server's right-hand side
players[server].pos.y = MID_Y + side * (server === 0 ? 1 : -1) * HALF_H * 0.5;
if (server === 1) wait(1.2, () => state === "serve" && serve());
}
function serve() {
state = "rally";
const s = players[server];
shoot(server, MID_Y - (s.pos.y - MID_Y), BALL_SPEED * 0.9); // aim at the opposite diagonal
}
onButtonPress("serve", () => state === "serve" && server === 0 && serve());
onMousePress(() => state === "serve" && server === 0 && serve());
// #endregion
// #region scoring
// Tennis counts 0, 15, 30, 40, game. At 40–40 it is «deuce»: you need two
// points in a row (advantage, then game). The server changes every game.
const score = { points: [0, 0], games: [0, 0] };
const CALLS = ["0", "15", "30", "40"];
function callScore() {
const [a, b] = score.points;
if (a >= 3 && b >= 3) {
if (a === b) return t({ es: "Iguales", en: "Deuce" });
return a > b ? t({ es: "Ventaja tuya", en: "Advantage you" }) : t({ es: "Ventaja rival", en: "Advantage rival" });
}
return `${CALLS[a]} – ${CALLS[b]}`;
}
function endPoint(winner, message) {
state = "pause";
const p = score.points;
p[winner]++;
if (p[winner] >= 4 && p[winner] - p[1 - winner] >= 2) {
score.games[winner]++;
score.points = [0, 0];
server = 1 - server;
message += "\n" + t({ es: "Juego", en: "Game" });
}
banner.text = message;
wait(1.4, () => {
banner.text = "";
if (score.games[winner] === GAMES_TO_WIN) return go("end", winner === 0, score.games);
state = "serve";
placeServer();
});
}
// #endregion
// #region hud
const hud = add([text("", { size: 16, font: FONT, align: "center" }), pos(320, 22), anchor("center")]);
const banner = add([text("", { size: 26, font: FONT, align: "center" }), pos(320, 160), anchor("center"), z(10)]);
onUpdate(() => {
const serving = state === "serve" && server === 0 ? t({ es: " · Sacas tú: espacio o clic", en: " · Your serve: space or click" }) : "";
const games = t({ es: `Tú ${score.games[0]} – ${score.games[1]} Rival`, en: `You ${score.games[0]} – ${score.games[1]} Rival` });
hud.text = `${games} · ${callScore()}${serving}`;
});
placeServer();
// #endregion
});
// #region scenes
function screen(title, subtitle) {
add([text(title, { size: 34, font: FONT, align: "center" }), pos(center().sub(0, 50)), anchor("center")]);
add([text(subtitle, { size: 16, font: FONT, align: "center", width: 560 }), pos(center().add(0, 30)), anchor("center"), color(220, 240, 220)]);
onButtonPress("serve", () => go("game"));
onClick(() => go("game"));
}
scene("title", () =>
screen(
t({ es: "Tenis", en: "Tennis" }),
t({
es: `Mueve tu raqueta (amarilla) con las flechas o W/S, o con el ratón o el dedo. Donde toca la bola decide hacia dónde sale: con el borde apuntas a las líneas, pero puede irse fuera. Gana ${GAMES_TO_WIN} juegos.\nEspacio o clic para empezar.`,
en: `Move your racket (yellow) with the arrows or W/S, or with the mouse or a finger. Where the ball touches it decides where it goes: the edge aims at the lines, but it may go out. Win ${GAMES_TO_WIN} games.\nSpace or click to start.`,
}),
),
);
scene("end", (won, games) =>
screen(
won ? t({ es: "¡Ganaste el set!", en: "You won the set!" }) : t({ es: "El rival gana el set", en: "The rival wins the set" }),
t({ es: `Juegos ${games[0]} – ${games[1]}. Espacio o clic para jugar otra vez.`, en: `Games ${games[0]} – ${games[1]}. Space or click to play again.` }),
),
);
go("title");
// #endregionDesign challenges
- Choosing which rules to simulate and which to simplify, so the sport is recognisable and the match stays quick and readable.
- A rival AI that plays like a person, with reaction time and mistakes, neither perfect nor clumsy.
- Controlling a whole team with a single controller, which demands good player switching and teammates who position themselves sensibly.
- Balancing realism and fun, and choosing a camera (top-down, side-on or TV-style) that lets you see the play.
History
- 1958OriginTennis for Two
Tennis for Two (1958), by William Higinbotham, a two-player tennis game on an oscilloscope, is the earliest sports video game.
Sports video game (Wikipedia) - 1972Pong
Atari's Pong, the first commercially successful video game, is a simplified table tennis.
Sports video game (Wikipedia) - 1982Football Manager
Football Manager, for the ZX Spectrum, is the first association football management simulation.
Sports video game (Wikipedia) - 1983Track & Field
Konami's Track & Field turns athletics into alternating two buttons as fast as possible and is one of the highest-grossing arcade games of 1984.
Track & Field (Wikipedia) - 1993FIFA International Soccer
FIFA International Soccer brings the isometric perspective to football and is the best-selling home game of 1993 in the United Kingdom.
Sports video game (Wikipedia) - 1997Gran Turismo
Gran Turismo, a racing simulator for the PlayStation, ships more than ten million copies.
Gran Turismo (1997 video game) (Wikipedia) - 1999Tony Hawk's Pro Skater
Tony Hawk's Pro Skater popularises extreme sports, with tricks chained into combos, and its series becomes one of the most popular in sports games.
Sports video game (Wikipedia) - 2006Wii Sports
Wii Sports has you play by copying the real movement with a motion controller and becomes Nintendo's best-selling game ever.
Wii Sports (Wikipedia) - 2021Doodle Champion Island Games
Google celebrates the Tokyo Olympics with Doodle Champion Island Games, seven sports mini-games played free in the browser.
Doodle Champion Island Games (Wikipedia)
Reference games
1958
Tennis for Two
A two-player tennis game seen from the side on an oscilloscope screen; each controller had a knob to set the angle and a button to hit. It was shown to the public at a Brookhaven open day.
1972
Pong
Table tennis stripped to its essentials; each player moves a paddle to return the ball and the first to reach eleven points wins. It is the first commercially successful video game.
1982
Football Manager
You do not play the matches, you run the club. You start in the fourth division, pick the line-up, buy and sell players and look after the finances to get promoted, almost all with text and the keyboard.
1983
Track & Field
Six athletics events, from the 100 metres to the high jump. You run by alternating two buttons as fast as you can and use a third one to time the jump or the throw; if you miss the qualifying mark, you lose a life.
1993
FIFA International Soccer
Football with national teams seen in an isometric perspective, when other football games used a top-down view. It is the first in EA Sports' FIFA series.
1997
Gran Turismo
A driving simulator with 140 real cars. In its career mode you earn driving licences, win money in races and spend it buying and tuning cars.
1999
Tony Hawk's Pro Skater
You skate through open levels and chain tricks into combos to score points; if you bail, you lose the combo. In career mode you have two minutes per session to complete goals.
2006
Wii Sports
Five sports (tennis, baseball, bowling, golf and boxing) that you play by copying the real movement with the Wii's motion controller, as if it were a racket, a club or a ball.
2012
Basketball 2012 (Google Doodle)
A Google Doodle for the 2012 Olympic Games in which you have 24 seconds to score as many baskets as you can; you hold and release at the right moment, and the shooter moves further from the hoop as you score.
2021
Doodle Champion Island Games
A Google Doodle with seven sports mini-games (table tennis, skateboarding, archery, rugby, artistic swimming, climbing and marathon) linked by an island to explore. Each one simplifies its sport's rules down to one or two buttons.
Exercise
Play a set of the demo and make two lists, one with the real tennis rules that are there (cross-court serve, switching sides every point, 15-30-40, deuce and advantage, out) and another with the ones that were simplified. In the code, change the shot spread and the AI speed and explain how the difficulty changes. Finally, pick another net sport (volleyball, badminton) and write in half a page the minimum rules its video game version would have.
Subgenres
- Cue sportsYou play billiards, pool, snooker or carom: aiming, choosing the strength and spin of each shot and working out how the balls will rebound. The challenge is precision and planning the next shots.
- FishingYou fish for sport: choosing the spot, the bait and the tackle, casting the line, reacting in time when a fish bites and reeling it in without snapping the line while it pulls. You win on the weight or size of your catch.
- Football (soccer)You control a football team during the match, switching to whichever player is nearest the ball: passing, dribbling, shooting and defending in real time. The challenge is combining with your teammates to create chances and stop the other side.
- GolfYou play a golf course stroke by stroke: choosing a club, aiming with the wind and the terrain in mind and setting the power of the shot, often with a meter you stop at just the right moment. You win by sinking the ball in each hole in as few strokes as possible.
- RacingA game in which you compete to finish first or in the shortest time on a circuit or a route, almost always at the wheel of a vehicle. The challenge is mastering speed and trajectory without going off the track or losing time in the corners.