├── index.html
├── style.css
└── script.js
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Table-Tennis AI
11 |
12 |
13 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::after,
3 | *::before {
4 | box-sizing: border-box;
5 | }
6 |
7 | :root {
8 | --hue: 200;
9 | --saturation: 50%;
10 | --foreground-color: hsl(var(--hue), var(--saturation), 75%);
11 | --background-color: hsl(var(--hue), var(--saturation), 20%);
12 | }
13 |
14 | body {
15 | margin: 0;
16 | background-color: var(--background-color);
17 | overflow: hidden;
18 | }
19 |
20 | .paddle {
21 | --position: 50;
22 |
23 | position: absolute;
24 | background-color: var(--foreground-color);
25 | top: calc(var(--position) * 1vh);
26 | transform: translateY(-50%);
27 | width: 1vh;
28 | height: 10vh;
29 | }
30 |
31 | .paddle.left {
32 | left: 1vw;
33 | }
34 |
35 | .paddle.right {
36 | right: 1vw;
37 | }
38 |
39 | .ball {
40 | --x: 50;
41 | --y: 50;
42 |
43 | position: absolute;
44 | background-color: var(--foreground-color);
45 | left: calc(var(--x) * 1vw);
46 | top: calc(var(--y) * 1vh);
47 | border-radius: 50%;
48 | transform: translate(-50%, -50%);
49 | width: 2.5vh;
50 | height: 2.5vh;
51 | }
52 |
53 | .score {
54 | display: flex;
55 | justify-content: center;
56 | font-weight: bold;
57 | font-size: 7vh;
58 | color: var(--foreground-color);
59 | }
60 |
61 | .score>* {
62 | flex-grow: 1;
63 | flex-basis: 0;
64 | padding: 0 2vh;
65 | margin: 1vh 0;
66 | opacity: .5;
67 | }
68 |
69 | .score> :first-child {
70 | text-align: right;
71 | border-right: .5vh solid var(--foreground-color);
72 | }
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | import Ball from "./Ball.js"
2 | import Paddle from "./Paddle.js"
3 |
4 | const ball = new Ball(document.getElementById("ball"))
5 | const playerPaddle = new Paddle(document.getElementById("player-paddle"))
6 | const computerPaddle = new Paddle(document.getElementById("computer-paddle"))
7 | const playerScoreElem = document.getElementById("player-score")
8 | const computerScoreElem = document.getElementById("computer-score")
9 |
10 | let lastTime
11 | function update(time) {
12 | if (lastTime != null) {
13 | const delta = time - lastTime
14 | ball.update(delta, [playerPaddle.rect(), computerPaddle.rect()])
15 | computerPaddle.update(delta, ball.y)
16 | const hue = parseFloat(
17 | getComputedStyle(document.documentElement).getPropertyValue("--hue")
18 | )
19 |
20 | document.documentElement.style.setProperty("--hue", hue + delta * 0.01)
21 |
22 | if (isLose()) handleLose()
23 | }
24 |
25 | lastTime = time
26 | window.requestAnimationFrame(update)
27 | }
28 |
29 | function isLose() {
30 | const rect = ball.rect()
31 | return rect.right >= window.innerWidth || rect.left <= 0
32 | }
33 |
34 | function handleLose() {
35 | const rect = ball.rect()
36 | if (rect.right >= window.innerWidth) {
37 | playerScoreElem.textContent = parseInt(playerScoreElem.textContent) + 1
38 | } else {
39 | computerScoreElem.textContent = parseInt(computerScoreElem.textContent) + 1
40 | }
41 | ball.reset()
42 | computerPaddle.reset()
43 | }
44 |
45 | document.addEventListener("mousemove", e => {
46 | playerPaddle.position = (e.y / window.innerHeight) * 100
47 | })
48 |
49 | window.requestAnimationFrame(update)
--------------------------------------------------------------------------------