SET — tenis dwóch kresek
GryPodgląd
Ładowanie podglądu…
Pong przeciw komputerowi. Pierwszy do siedmiu punktów wygrywa, a kierunek odbicia zależy od miejsca trafienia.
Kod źródłowy
Licencja MIT — także do użytku komercyjnego. Zachowaj informacje o autorze i licencji.
HTML
<main><header><span class="eyebrow">Pong</span><h1>SET.</h1><p>Ty, komputer i piłka. Do siedmiu.</p></header><div class="desktop-split"><section aria-label="Plansza"><canvas id="screen" width="600" height="400" tabindex="0" aria-label="SET.. Steruj lewą paletką: góra / dół lub dotyk na boisku. Wygrywa pierwszy gracz z 7 punktami.">Gra wymaga obsługi canvas. Wynik i przyciski znajdują się pod planszą.</canvas><div class="controls" aria-label="Sterowanie"><button type="button" data-key="ArrowUp" aria-label="Ruch: góra">↑</button><button type="button" data-key="ArrowDown" aria-label="Ruch: dół">↓</button></div></section><aside><div class="eyebrow">Ty : komputer</div><div id="score" class="metric">0</div><p id="status" class="status" role="status">Naciśnij Graj.</p><div class="actions"><button id="play" type="button">Graj</button><button id="reset" type="button">Od nowa</button></div><p>Steruj lewą paletką: góra / dół lub dotyk na boisku. Wygrywa pierwszy gracz z 7 punktami.</p><small>Esc — pauza. Gra zatrzymuje się po opuszczeniu okna.</small></aside></div></main>
CSS
*{box-sizing:border-box}body{margin:0;font-family:system-ui,sans-serif;background:var(--bg);color:var(--ink)}main{width:min(100% - 32px,980px);margin:32px auto}h1{font-size:clamp(32px,7vw,64px);line-height:1;margin:12px 0 20px;letter-spacing:-.045em}p{line-height:1.55}button{font:inherit;min-height:44px;padding:10px 16px;border:1px solid currentColor;border-radius:0;background:transparent;color:inherit;cursor:pointer}button:hover{background:var(--ink);color:var(--bg)}button:disabled{cursor:default;opacity:.55}button:focus-visible,canvas:focus-visible{outline:3px solid var(--focus);outline-offset:3px}button[aria-pressed="true"]{background:var(--ink);color:var(--bg)}.eyebrow{font:12px ui-monospace,monospace;letter-spacing:.16em;text-transform:uppercase}.status{min-height:48px;font-weight:600}.actions{display:flex;flex-wrap:wrap;gap:8px}.metric{font:700 28px ui-monospace,monospace}small{line-height:1.5}canvas{display:block;width:100%;height:auto;border:1px solid currentColor;touch-action:none}.controls{display:flex;flex-wrap:wrap;gap:8px;margin:14px 0}.controls button{min-width:54px;touch-action:none;user-select:none}.board{display:grid;gap:5px}.board button{padding:0;aspect-ratio:1;min-width:0;font-size:clamp(18px,5vw,30px)}@media(max-width:600px){main{width:calc(100% - 24px);margin:20px auto}h1{margin-bottom:16px}.desktop-split{display:block!important}}@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}
:root{--bg:#e9edcd;--ink:#153e35;--focus:#aa2b1b}h1{font-family:Georgia,serif;font-style:italic;font-size:100px;font-weight:400}header{display:flex;align-items:baseline;gap:28px;flex-wrap:wrap}header p{max-width:220px}.desktop-split{display:grid;grid-template-columns:minmax(0,700px) 1fr;gap:28px}.metric{font:64px Georgia,serif}aside{border-top:6px double #153e35;padding-top:16px}canvas{border:8px solid #f9f7e4}
@media(max-width:600px){aside .metric{font-size:36px}aside .status{min-height:0;margin:8px 0}aside .actions{margin:12px 0}}
@media(max-width:600px){header{display:block}header h1{font-size:70px;margin:12px 0}header p{max-width:none}}
JavaScript
function pongBounce(offset, speed) { const vy=Math.max(-1,Math.min(1,offset))*speed*.75; return {vx:Math.sqrt(speed*speed-vy*vy),vy}; }
$(function () {
const canvas=document.getElementById('screen'),ctx=canvas.getContext('2d');
let player,ai,ball,you,them;
function serve(sign=1){ball={x:300,y:200,vx:sign*220,vy:95};}
function init(){player=160;ai=160;you=0;them=0;serve();$('#score').text('0 : 0');}
function direction(key){if(key==='ArrowUp')player=Math.max(0,player-18);if(key==='ArrowDown')player=Math.min(320,player+18);}
$(canvas).on('pointermove pointerdown',function(event){if(!playing)return;const box=canvas.getBoundingClientRect();player=Math.max(0,Math.min(320,(event.originalEvent.clientY-box.top)*400/box.height-40));});
function update(dt){
if(held.has('ArrowUp'))player-=dt*320;if(held.has('ArrowDown'))player+=dt*320;player=Math.max(0,Math.min(320,player));
ai+=Math.max(-dt*150,Math.min(dt*150,ball.y-ai-40));ai=Math.max(0,Math.min(320,ai));
ball.x+=ball.vx*dt;ball.y+=ball.vy*dt;
if(ball.y<7||ball.y>393){ball.y=Math.max(7,Math.min(393,ball.y));ball.vy*=-1;}
const left=ball.vx<0&&ball.x<=34&&ball.x>=20&&ball.y>=player-7&&ball.y<=player+87;
const right=ball.vx>0&&ball.x>=566&&ball.x<=580&&ball.y>=ai-7&&ball.y<=ai+87;
if(left||right){const hit=pongBounce((ball.y-(left?player:ai)-40)/40,Math.min(400,Math.hypot(ball.vx,ball.vy)+15));ball.vx=hit.vx*(left?1:-1);ball.vy=hit.vy;ball.x=left?34:566;}
if(ball.x<0||ball.x>600){if(ball.x<0)them++;else you++;$('#score').text(you+' : '+them);if(you===7||them===7){finish(you===7?'Wygrywasz seta!':'Set dla komputera. Rewanż?');return;}serve(you>=them?-1:1);}
}
function draw(){ctx.fillStyle='#24604d';ctx.fillRect(0,0,600,400);ctx.strokeStyle='#dce8b4';ctx.lineWidth=2;ctx.strokeRect(12,12,576,376);ctx.setLineDash([5,8]);ctx.beginPath();ctx.moveTo(300,12);ctx.lineTo(300,388);ctx.stroke();ctx.setLineDash([]);ctx.fillStyle='#f7f4d9';ctx.fillRect(20,player,7,80);ctx.fillRect(573,ai,7,80);ctx.fillStyle='#efda64';ctx.beginPath();ctx.arc(ball.x,ball.y,7,0,Math.PI*2);ctx.fill();}
let playing = false, ended = false, frameId = null, previous = 0;
const held = new Set();
function pause() {
if (!playing) return;
playing = false; held.clear(); cancelAnimationFrame(frameId);
$('#play').text('Wznów'); $('#status').text('Pauza.');
}
function finish(message) {
playing = false; ended = true; held.clear(); cancelAnimationFrame(frameId);
$('#play').text('Zagraj ponownie'); $('#status').text(message);
}
function reset() {
playing = false; ended = false; held.clear(); cancelAnimationFrame(frameId);
init(); draw(); $('#play').text('Graj'); $('#status').text('Naciśnij Graj.');
}
function frame(now) {
if (!playing) return;
const dt = Math.min((now - previous) / 1000, .035); previous = now;
update(dt); draw();
if (playing) frameId = requestAnimationFrame(frame);
}
$('#play').on('click', function () {
if (playing) { pause(); return; }
if (ended) reset();
playing = true; previous = performance.now();
$(this).text('Pauza'); $('#status').text('Gra trwa.'); canvas.focus();
frameId = requestAnimationFrame(frame);
});
$('#reset').on('click', reset);
$(document).on('keydown', function (event) {
if (event.key === 'Escape') { pause(); return; }
if (playing && ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(event.key)) {
event.preventDefault(); held.add(event.key); direction(event.key);
}
}).on('keyup', event => held.delete(event.key));
$('[data-key]').on('pointerdown', function (event) {
if (!playing) return;
event.preventDefault(); this.setPointerCapture(event.originalEvent.pointerId);
const key = $(this).attr('data-key'); held.add(key); direction(key);
}).on('pointerup pointercancel lostpointercapture', function () { held.delete($(this).attr('data-key')); })
.on('click', function (event) { if (playing && event.originalEvent.detail === 0) direction($(this).attr('data-key')); });
$(window).on('blur', pause);
$(document).on('visibilitychange', function () { if (document.hidden) pause(); });
reset();
});
Edytuj swój pen
Podaj adres e-mail użyty przy zapisie. Wyślemy link do edycji.