
Deportes
Practicas un deporte o compites en carreras: controlas a un atleta, a un equipo o un vehículo, o diriges un club desde la oficina. El reto combina las reglas y la táctica del deporte con tus reflejos o con tu gestión.
También se llama: videojuego deportivo, juego de deportes, videojuego de deportes
Mecánicas que lo definen
- Las reglas de un deporte real, simplificadas y aplicadas por el juego, que hace de árbitro con el marcador, los saques, las faltas, los fueras y el reloj.
- Control directo de un atleta o, en los deportes de equipo, del jugador más cercano a la pelota, mientras la IA mueve a tus compañeros y al rival.
- Golpes, pases y tiros cuyo resultado depende del momento y de la dirección en que los haces, no solo de pulsar un botón.
- Atributos de jugadores y equipos (velocidad, potencia, precisión), a menudo basados en deportistas y ligas reales con licencia.
- Variantes con capas propias, como la gestión de un club, los deportes extremos de trucos y combos, y las carreras, que forman un subgénero aparte.
Bucle de juego
Leer la jugada, colocarte, golpear o pasar en el momento justo, ver cómo cambia el marcador y ajustar tu táctica al rival hasta el final del partido.
Demo propia
Controles. Flechas arriba y abajo o W/S para mover tu raqueta (la amarilla). Con el ratón o el dedo, la raqueta va hacia el puntero. Cuando sacas tú, pulsa espacio, Enter o haz clic.
Abrir en pantalla completaUn tenis visto desde arriba contra una IA, con reglas reales simplificadas. Hay saque cruzado y cambio de lado en cada punto, el cambio de sacador en cada juego, el conteo 15-30-40 con iguales y ventaja, y la bola fuera si sale por los laterales. Donde la bola toca la raqueta decide hacia dónde sale. Gana el primero en llevarse tres juegos.
Hecha con Kaplay, en unas 175 líneas de JavaScript.
Cómo está hecha
Configuración y constantes
Se crea el juego con botones virtuales (up, down, serve) y se definen la pista y los números que deciden cómo se juega, como la velocidad de los jugadores, la de la bola y cuánto acelera en cada golpe, y AIM_SPREAD, que dice cuánto se abre el ángulo al golpear con el borde de la raqueta.
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;La pista
La pista se dibuja con líneas, que son las de fondo, las laterales, las de saque, la central y la red. Las líneas no son decoración, porque el reglamento se apoya en ellas para decidir si la bola es buena o fuera.
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 });Los jugadores
Cada jugador es una raqueta que se mueve en vertical, con la misma velocidad máxima para ti y para la IA, así la partida es justa. Con teclado te mueves directamente. Con el ratón o el dedo, la raqueta va hacia el puntero a esa misma velocidad. Puedes salir un poco de la pista para alcanzar bolas abiertas.
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); });La IA rival
La IA calcula por dónde cruzará la bola su línea y coloca la raqueta para que la bola le dé en la parte que quiere, y así apunta. Reacciona con un retraso, como una persona, y elige al azar un ángulo seguro. Además, conoce el reglamento, porque deja pasar las bolas que van fuera, ya que así gana el punto.
// 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); });La bola y el golpe
La bola va en línea recta hacia un punto de la línea de fondo rival. Un golpe ocurre cuando cruza la línea de la raqueta dentro de su altura. Se mira el cruce y no el solapamiento, para que una bola rápida no atraviese la raqueta entre dos fotogramas. Donde toca decide el destino, y el centro la manda al medio mientras el borde la abre hacia las líneas. Cada golpe la acelera un 5 %.
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);Reglas, fuera y saque
Aquí está la mecánica del género, que es el juego haciendo de árbitro. Si la bola sale por un lateral antes de que la devuelvan, es fuera y el punto es del rival de quien golpeó. Si cruza una línea de fondo dentro de la pista, el que debía devolverla falló. El saque va cruzado y el sacador cambia de lado en cada punto, a la derecha en los pares y a la izquierda en los impares, como en el tenis real. Apuntar a las líneas es un riesgo que el reglamento castiga o premia.
// 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());El tanteo del tenis
Los puntos se cuentan 0, 15, 30, 40 y juego. Con 40 iguales se dice «iguales» y hacen falta dos puntos seguidos, la ventaja y luego el juego. Cada juego cambia el sacador. Traducir un tanteo raro como este a unas pocas condiciones es un buen ejemplo de cómo un videojuego codifica un reglamento.
// 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(); }); }Marcador
El marcador muestra los juegos y el tanteo del juego en curso tal como se canta en el tenis, y avisa cuando te toca sacar. Tras cada punto un cartel explica qué pasó (fuera, punto, juego), así el jugador aprende el reglamento mientras juega.
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();Escenas
El juego tiene tres escenas (title, game y end). El título explica los controles y la idea clave de apuntar con el borde de la raqueta, y la escena final muestra el resultado del 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");
Qué simplificamos
- La bola no bota ni tiene altura, y la red no la detiene. Un golpe es «bueno» si la bola cruza la línea de fondo rival dentro de la pista, y «fuera» si sale antes por un lateral.
- Los jugadores solo se mueven en vertical sobre su línea de fondo, y no hay doble falta, red en el saque, tie-break ni cambio de campo.
- El set se gana con tres juegos y sin necesidad de dos de ventaja (en el tenis real son seis), y el marcador pone siempre tus puntos primero aunque saque el rival.
Para modificarla
- Exige dos juegos de ventaja para ganar el set y añade un tie-break a 7 puntos cuando se empata.
- Añade el segundo saque. Si el primero va fuera es falta, y dos faltas seguidas dan el punto al rival.
- Deja que los jugadores se muevan también en horizontal, para subir a la red o retroceder, y piensa qué cambia en la IA.
- Cambia el deporte a vóley o bádminton, decidiendo qué líneas, saques y tanteo hacen falta.
Código completo
// 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");
// #endregionRetos de diseño
- Elegir qué reglas simular y cuáles simplificar para que el deporte se reconozca y el partido siga siendo ágil y legible.
- Una IA rival que juegue como una persona, con tiempo de reacción y errores, y que no sea ni perfecta ni torpe.
- Controlar a todo un equipo con un solo mando, lo que exige un buen cambio de jugador y compañeros que se coloquen solos con sentido.
- Equilibrar realismo y diversión, y elegir una cámara (cenital, lateral o de televisión) que deje ver la jugada.
Historia
- 1958OrigenTennis for Two
Tennis for Two (1958), de William Higinbotham, un tenis para dos jugadores en un osciloscopio, es el videojuego de deportes más antiguo.
Sports video game (Wikipedia) - 1972Pong
Pong, de Atari, primer videojuego de éxito comercial, es un tenis de mesa simplificado.
Sports video game (Wikipedia) - 1982Football Manager
Football Manager, para ZX Spectrum, es la primera simulación de gestión de un club de fútbol.
Sports video game (Wikipedia) - 1983Track & Field
Track & Field, de Konami, convierte el atletismo en alternar dos botones lo más rápido posible y es uno de los arcades más taquilleros de 1984.
Track & Field (Wikipedia) - 1993FIFA International Soccer
FIFA International Soccer lleva la perspectiva isométrica al fútbol y es el juego doméstico más vendido de 1993 en el Reino Unido.
Sports video game (Wikipedia) - 1997Gran Turismo
Gran Turismo, simulador de carreras para PlayStation, distribuye más de diez millones de copias.
Gran Turismo (1997 video game) (Wikipedia) - 1999Tony Hawk's Pro Skater
Tony Hawk's Pro Skater populariza los deportes extremos, con trucos encadenados en combos, y su serie se vuelve una de las deportivas más populares.
Sports video game (Wikipedia) - 2006Wii Sports
Wii Sports hace que juegues imitando el gesto real con un mando de movimiento y se vuelve el juego de Nintendo más vendido de la historia.
Wii Sports (Wikipedia) - 2021Doodle Champion Island Games
Google celebra los Juegos Olímpicos de Tokio con Doodle Champion Island Games, siete minijuegos deportivos que se juegan gratis en el navegador.
Doodle Champion Island Games (Wikipedia)
Juegos de referencia
1958
Tennis for Two
Un tenis para dos personas que se veía de lado en la pantalla de un osciloscopio; cada mando tenía una perilla para elegir el ángulo y un botón para golpear. Se mostró al público en una jornada de puertas abiertas de Brookhaven.
1972
Pong
Un tenis de mesa reducido a lo esencial; cada jugador mueve una paleta para devolver la pelota y gana quien llega primero a once puntos. Es el primer videojuego con éxito comercial.
1982
Football Manager
No juegas los partidos, diriges el club. Empiezas en cuarta división, eliges la alineación, compras y vendes jugadores y cuidas las finanzas para ascender, casi todo con texto y teclado.
1983
Track & Field
Seis pruebas de atletismo, de los 100 metros al salto de altura. Corres alternando dos botones lo más rápido que puedas y usas un tercero para elegir el momento del salto o del lanzamiento; si no alcanzas la marca, pierdes una vida.
1993
FIFA International Soccer
Fútbol con selecciones nacionales visto en perspectiva isométrica, cuando otros juegos del deporte usaban la vista cenital. Es el primero de la serie FIFA de EA Sports.
1997
Gran Turismo
Simulador de conducción con 140 coches reales. En su modo carrera sacas licencias de conducir, ganas dinero en carreras y lo gastas en comprar y ajustar coches.
1999
Tony Hawk's Pro Skater
Patinas por niveles abiertos y encadenas trucos en combos para sumar puntos; si te caes, pierdes el combo. En el modo carrera tienes dos minutos por sesión para cumplir objetivos.
2006
Wii Sports
Cinco deportes (tenis, béisbol, bolos, golf y boxeo) que juegas imitando el gesto real con el mando de movimiento de Wii, como si fuera una raqueta, un palo o una bola.
2012
Basketball 2012 (Google Doodle)
Un Doodle de Google para los Juegos Olímpicos de 2012 en el que tienes 24 segundos para encestar todo lo que puedas; mantienes pulsado y sueltas en el momento justo, y el tirador se aleja del aro a medida que aciertas.
2021
Doodle Champion Island Games
Un Doodle de Google con siete minijuegos deportivos (tenis de mesa, monopatín, tiro con arco, rugby, natación artística, escalada y maratón) unidos por una isla que se explora. Cada uno simplifica las reglas de su deporte a uno o dos botones.
Ejercicio
Juega un set de la demo y haz dos listas, una con las reglas del tenis real que sí están (saque cruzado, cambio de lado en cada punto, 15-30-40, iguales y ventaja, fuera) y otra con las que se simplificaron. Cambia en el código la dispersión del golpe y la velocidad de la IA y explica cómo cambia la dificultad. Por último, elige otro deporte de red (vóley, bádminton) y escribe en media página las reglas mínimas que tendría su versión en videojuego.
Subgéneros
- BillarJuegas billar, pool, snooker o carambola: apuntas, eliges la fuerza y el efecto del tiro y calculas cómo rebotarán las bolas. El reto es la precisión y planear las jugadas siguientes.
- CarrerasJuego en el que compites por llegar primero o en el menor tiempo por un circuito o un recorrido, casi siempre al volante de un vehículo. El reto es dominar la velocidad y la trayectoria sin salirte ni perder tiempo en las curvas.
- FútbolControlas a un equipo de fútbol en el partido, pasando el control al jugador más cercano al balón: pasas, regateas, disparas y defiendes en tiempo real. El reto es combinar con tus compañeros para crear ocasiones y frenar al rival.
- GolfJuegas un recorrido de golf golpe a golpe: eliges el palo, apuntas teniendo en cuenta el viento y el terreno y marcas la fuerza del golpe, a menudo con una barra que detienes en el momento justo. Ganas metiendo la bola en cada hoyo en el menor número de golpes.
- PescaPescas por deporte: eliges el lugar, el cebo y el equipo, lanzas la línea, reaccionas a tiempo cuando pica y recoges el sedal sin que se rompa mientras el pez tira. Ganas por el peso o el tamaño de lo que capturas.