Compare commits
4 Commits
5401694e7b
...
53c8aa5012
Author | SHA1 | Date | |
---|---|---|---|
53c8aa5012 | |||
a7be3e1cd2 | |||
e02d1b855b | |||
fa71caa020 |
@ -47,15 +47,25 @@
|
|||||||
#made {
|
#made {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mode {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
list-style-type: none;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
#instr {
|
#instr {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding-left: 20px;
|
}
|
||||||
|
|
||||||
|
#threshold {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#made a {
|
#made a {
|
||||||
@ -69,7 +79,7 @@
|
|||||||
<canvas id="pongCanvas" width="1200" height="800"></canvas>
|
<canvas id="pongCanvas" width="1200" height="800"></canvas>
|
||||||
<div id="score"></div>
|
<div id="score"></div>
|
||||||
<p id="instr">
|
<p id="instr">
|
||||||
balls can be controlled with number keys
|
balls can be controlled with keys shown to the left of their names
|
||||||
</p>
|
</p>
|
||||||
<p id="made">
|
<p id="made">
|
||||||
made by <a href="https://koenvangilst.nl">Koen van Gilst</a> | source on
|
made by <a href="https://koenvangilst.nl">Koen van Gilst</a> | source on
|
||||||
@ -78,6 +88,13 @@
|
|||||||
patches from dogeystamp | patched source on
|
patches from dogeystamp | patched source on
|
||||||
<a href="https://github.com/dogeystamp/garbage-monorepo/tree/main/pongwars">github</a>
|
<a href="https://github.com/dogeystamp/garbage-monorepo/tree/main/pongwars">github</a>
|
||||||
</p>
|
</p>
|
||||||
|
<p id="threshold"></p>
|
||||||
|
<div id="mode">
|
||||||
|
change mode:
|
||||||
|
<a class="modeLink" data-mode="normal">normal</a>
|
||||||
|
<a class="modeLink" data-mode="big">big</a>
|
||||||
|
<a class="modeLink" data-mode="small">small</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
@ -91,6 +108,7 @@
|
|||||||
const canvas = document.getElementById("pongCanvas");
|
const canvas = document.getElementById("pongCanvas");
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
const scoreElement = document.getElementById("score");
|
const scoreElement = document.getElementById("score");
|
||||||
|
const thresholdElement = document.getElementById("threshold");
|
||||||
|
|
||||||
var suddenDeathStart = null;
|
var suddenDeathStart = null;
|
||||||
var suddenDeathCoeff = 0;
|
var suddenDeathCoeff = 0;
|
||||||
@ -114,7 +132,7 @@
|
|||||||
{
|
{
|
||||||
name: "orange",
|
name: "orange",
|
||||||
color: "coral",
|
color: "coral",
|
||||||
backgroundColor: "chocolate",
|
backgroundColor: "#DD4500",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "white",
|
name: "white",
|
||||||
@ -136,33 +154,91 @@
|
|||||||
color: "gray",
|
color: "gray",
|
||||||
backgroundColor: "#333333",
|
backgroundColor: "#333333",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "yellow",
|
||||||
|
color: "yellow",
|
||||||
|
backgroundColor: "goldenrod",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pink",
|
||||||
|
color: "darksalmon",
|
||||||
|
backgroundColor: "mediumvioletred",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "turquoise",
|
||||||
|
color: "turquoise",
|
||||||
|
backgroundColor: "royalblue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "salmon",
|
||||||
|
color: "peachpuff",
|
||||||
|
backgroundColor: "salmon",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
var state = {}
|
var state = {}
|
||||||
|
|
||||||
const squareSize = 16;
|
|
||||||
const numSquaresX = canvas.width / squareSize;
|
var squareSize = 16;
|
||||||
const numSquaresY = canvas.height / squareSize;
|
var numSquaresX = canvas.width / squareSize;
|
||||||
|
var numSquaresY = canvas.height / squareSize;
|
||||||
let squares = [];
|
let squares = [];
|
||||||
|
|
||||||
// matrix of "timestamps" where each square was claimed
|
// matrix of "timestamps" where each square was claimed
|
||||||
let squareTaken = [];
|
let squareTaken = [];
|
||||||
var squareTime = 0;
|
var squareTime = 0;
|
||||||
|
|
||||||
|
// threshold for territory under which a color starts dying
|
||||||
|
let threshold = 0;
|
||||||
|
|
||||||
|
var nTeams = teams.length;
|
||||||
|
// NTeams but with ease in to avoid instant deaths
|
||||||
|
var smoothNTeams = nTeams;
|
||||||
|
|
||||||
|
// do not edit without editing the code below for key to idx
|
||||||
|
const idxKeyMap = [..."0123456789abcdefghijklmnopqrstuvwxyz"];
|
||||||
|
function keyToIdx(key) {
|
||||||
|
const keyInt = parseInt(key, 36);
|
||||||
|
if (!isNaN(keyInt) && keyInt < teams.length) return keyInt;
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("keydown", (event) => {
|
document.addEventListener("keydown", (event) => {
|
||||||
const key = parseInt(event.key)
|
const idx = keyToIdx(event.key)
|
||||||
if (isNaN(key)) return
|
if (idx === null) return
|
||||||
state[key].boostEnabled = true;
|
state[idx].boostEnabled = true;
|
||||||
})
|
})
|
||||||
document.addEventListener("keyup", (event) => {
|
document.addEventListener("keyup", (event) => {
|
||||||
const key = parseInt(event.key)
|
const idx = keyToIdx(event.key)
|
||||||
if (isNaN(key)) return
|
if (idx === null) return
|
||||||
state[key].boostEnabled = false;
|
state[idx].boostEnabled = false;
|
||||||
})
|
})
|
||||||
|
|
||||||
function initialState() {
|
const params = new URLSearchParams(window.location.search);
|
||||||
state = teams.map((team) => ({
|
|
||||||
elim: false,
|
const modeLinks = document.getElementsByClassName("modeLink");
|
||||||
|
for (let i = 0; i < modeLinks.length; i++) {
|
||||||
|
let loc = new URL(window.location);
|
||||||
|
loc.searchParams.set("size", modeLinks[i].getAttribute("data-mode"));
|
||||||
|
modeLinks[i].href = loc.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialState({gridW = 4, gridH = 2, cols = teams.length, canvasW=1200, canvasH=800, squareSize=16} = {}) {
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
nTeams = cols;
|
||||||
|
smoothNTeams = cols;
|
||||||
|
|
||||||
|
canvas.width = canvasW;
|
||||||
|
canvas.height = canvasH;
|
||||||
|
window.squareSize = squareSize;
|
||||||
|
|
||||||
|
numSquaresX = canvas.width / squareSize;
|
||||||
|
numSquaresY = canvas.height / squareSize;
|
||||||
|
|
||||||
|
state = {};
|
||||||
|
state = teams.map((team, idx) => ({
|
||||||
|
elim: idx >= cols,
|
||||||
boostEnabled: false,
|
boostEnabled: false,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
@ -171,7 +247,7 @@
|
|||||||
score: 0,
|
score: 0,
|
||||||
// how many consecutive contested tiles it has hit
|
// how many consecutive contested tiles it has hit
|
||||||
conflict: 0,
|
conflict: 0,
|
||||||
}))
|
}));
|
||||||
|
|
||||||
// base noise pattern (failsafe in case the grid doesn't cover some part)
|
// base noise pattern (failsafe in case the grid doesn't cover some part)
|
||||||
for (let i = 0; i < numSquaresX; i++) {
|
for (let i = 0; i < numSquaresX; i++) {
|
||||||
@ -186,15 +262,13 @@
|
|||||||
|
|
||||||
const enableGrid = true;
|
const enableGrid = true;
|
||||||
if (enableGrid) {
|
if (enableGrid) {
|
||||||
const gridW = 4;
|
|
||||||
const gridH = 2;
|
|
||||||
for (let i = 0; i < gridW; i++) {
|
for (let i = 0; i < gridW; i++) {
|
||||||
for (let j = 0; j < gridH; j++) {
|
for (let j = 0; j < gridH; j++) {
|
||||||
const gridIdx = j * gridW + i;
|
const gridIdx = j * gridW + i;
|
||||||
const t = (gridIdx < teams.length) ? gridIdx : null;
|
const t = (gridIdx < teams.length) ? gridIdx : null;
|
||||||
|
|
||||||
for (let x = Math.floor(i/gridW*numSquaresX); x < Math.floor((i+1)/gridW*numSquaresX); x++) {
|
for (let x = Math.floor(i/gridW*numSquaresX); x < Math.ceil((i+1)/gridW*numSquaresX); x++) {
|
||||||
for (let y = Math.floor(j/gridH*numSquaresY); y < Math.floor((j+1)/gridH*numSquaresY); y++) {
|
for (let y = Math.floor(j/gridH*numSquaresY); y < Math.ceil((j+1)/gridH*numSquaresY); y++) {
|
||||||
if (t !== null) {
|
if (t !== null) {
|
||||||
squares[x][y] = t;
|
squares[x][y] = t;
|
||||||
}
|
}
|
||||||
@ -214,8 +288,33 @@
|
|||||||
state[i].dy = 8 * Math.sin(angle);
|
state[i].dy = 8 * Math.sin(angle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.forEach((t) => (t.score = 0));
|
||||||
|
for (let i = 0; i < numSquaresX; i++) {
|
||||||
|
for (let j = 0; j < numSquaresY; j++) {
|
||||||
|
state[squares[i][j]].score++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < numSquaresX; i++) {
|
||||||
|
for (let j = 0; j < numSquaresY; j++) {
|
||||||
|
drawSquare(i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (params.get("size")) {
|
||||||
|
case "big":
|
||||||
|
initialState({gridW: 4, gridH: 3, cols: 12, canvasW: 1600, canvasH: 1000});
|
||||||
|
break;
|
||||||
|
case "small":
|
||||||
|
initialState({gridW: 2, gridH: 1, cols: 2, canvasW: 600, canvasH: 600, squareSize: 25});
|
||||||
|
break;
|
||||||
|
case "normal":
|
||||||
|
default:
|
||||||
|
initialState({gridW: 4, gridH: 2, cols: 8});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
initialState();
|
|
||||||
|
|
||||||
function randomNum(min, max) {
|
function randomNum(min, max) {
|
||||||
return Math.random() * (max - min) + min;
|
return Math.random() * (max - min) + min;
|
||||||
@ -231,6 +330,19 @@
|
|||||||
|
|
||||||
updateScoreElement();
|
updateScoreElement();
|
||||||
|
|
||||||
|
function coverBall(x, y) {
|
||||||
|
// draw over the last ball
|
||||||
|
let i = Math.floor(x / squareSize);
|
||||||
|
let j = Math.floor(y / squareSize);
|
||||||
|
for (let checkI = i-1; checkI <= i+1; checkI++) {
|
||||||
|
for (let checkJ = j-1; checkJ <= j+1; checkJ++) {
|
||||||
|
if (checkI >= 0 && checkI < numSquaresX && checkJ >= 0 && checkJ < numSquaresY) {
|
||||||
|
drawSquare(checkI, checkJ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function drawBall(x, y, color) {
|
function drawBall(x, y, color) {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(x, y, squareSize / 2, 0, Math.PI * 2, false);
|
ctx.arc(x, y, squareSize / 2, 0, Math.PI * 2, false);
|
||||||
@ -239,13 +351,17 @@
|
|||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawSquares() {
|
function drawSquare(i, j) {
|
||||||
for (let i = 0; i < numSquaresX; i++) {
|
ctx.fillStyle = teams[squares[i][j]].backgroundColor;
|
||||||
for (let j = 0; j < numSquaresY; j++) {
|
ctx.fillRect(i * squareSize, j * squareSize, squareSize, squareSize);
|
||||||
ctx.fillStyle = teams[squares[i][j]].backgroundColor;
|
}
|
||||||
ctx.fillRect(i * squareSize, j * squareSize, squareSize, squareSize);
|
|
||||||
}
|
function takeSquare(i, j, team) {
|
||||||
}
|
state[team].score++;
|
||||||
|
state[squares[i][j]].score--;
|
||||||
|
squares[i][j] = team;
|
||||||
|
squareTaken[i][j] = squareTime;
|
||||||
|
drawSquare(i, j);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clamp(min, max, num) {
|
function clamp(min, max, num) {
|
||||||
@ -256,14 +372,10 @@
|
|||||||
return v*x + (1-v)*y;
|
return v*x + (1-v)*y;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nTeams = teams.length;
|
|
||||||
|
|
||||||
function updateSquareAndBounce(x, y, dx, dy, color) {
|
function updateSquareAndBounce(x, y, dx, dy, color) {
|
||||||
if (state[color].elim) return
|
if (state[color].elim) return
|
||||||
if (Math.max(Math.abs(dx), Math.abs(dy)) < 0.02) state[color].elim = true
|
if (Math.max(Math.abs(dx), Math.abs(dy)) < 0.02) state[color].elim = true
|
||||||
|
|
||||||
nTeams = state.map(t => !t.elim).filter(Boolean).length
|
|
||||||
|
|
||||||
if (Math.min(x, y) < 0 || isNaN(x) || isNaN(y)) {
|
if (Math.min(x, y) < 0 || isNaN(x) || isNaN(y)) {
|
||||||
console.warn(`warped ${teams[color].name}`)
|
console.warn(`warped ${teams[color].name}`)
|
||||||
state[color].x = canvas.width * 0.1;
|
state[color].x = canvas.width * 0.1;
|
||||||
@ -281,7 +393,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// death coefficient (if not enough territory, slow down)
|
// death coefficient (if not enough territory, slow down)
|
||||||
const coeff = Math.min((state[color].score/(numSquaresX*numSquaresY/nTeams/mix(1.2, 4, suddenDeathCoeff))), 1.00);
|
var coeff = Math.min((state[color].score/threshold), 1.00);
|
||||||
|
|
||||||
|
// winners don't slow down
|
||||||
|
if (nTeams === 1) coeff = 1;
|
||||||
|
|
||||||
// death coefficient when no collision
|
// death coefficient when no collision
|
||||||
const vacuumCoeff = coeff**(0.01);
|
const vacuumCoeff = coeff**(0.01);
|
||||||
@ -329,13 +444,12 @@
|
|||||||
console.log(`${teams[color].name} claims the remaining territory of ${teams[foreign].name}`)
|
console.log(`${teams[color].name} claims the remaining territory of ${teams[foreign].name}`)
|
||||||
for (let ai = 0; ai < numSquaresX; ai++) {
|
for (let ai = 0; ai < numSquaresX; ai++) {
|
||||||
for (let aj = 0; aj < numSquaresY; aj++) {
|
for (let aj = 0; aj < numSquaresY; aj++) {
|
||||||
if (squares[ai][aj] == foreign) squares[ai][aj] = color;
|
if (squares[ai][aj] == foreign) takeSquare(ai, aj, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
squareTaken[i][j] = squareTime;
|
takeSquare(i, j, color);
|
||||||
squares[i][j] = color;
|
|
||||||
|
|
||||||
// Determine bounce direction based on the angle
|
// Determine bounce direction based on the angle
|
||||||
if (Math.abs(Math.cos(angle)) > Math.abs(Math.sin(angle))) {
|
if (Math.abs(Math.cos(angle)) > Math.abs(Math.sin(angle))) {
|
||||||
@ -375,21 +489,10 @@
|
|||||||
if (!squares) {
|
if (!squares) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.forEach((t) => (t.score = 0));
|
|
||||||
for (let i = 0; i < numSquaresX; i++) {
|
|
||||||
for (let j = 0; j < numSquaresY; j++) {
|
|
||||||
state[squares[i][j]].score++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0; i < teams.length; i++) {
|
|
||||||
if (state[i].score <= 4) {
|
|
||||||
state[i].elim = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nTeams > 1) {
|
if (nTeams > 1) {
|
||||||
scoreElement.textContent = teams
|
scoreElement.textContent = teams
|
||||||
.map((t, idx) => `(${idx}) ${t.name} ${state[idx].score}`)
|
.map((t, idx) => `(${idxKeyMap[idx]}) ${t.name} ${state[idx].score}`)
|
||||||
.filter((t, idx) => !state[idx].elim)
|
.filter((t, idx) => !state[idx].elim)
|
||||||
.join(" | ") + (suddenDeathCoeff > 0.1 ? " | sudden death!" : "");
|
.join(" | ") + (suddenDeathCoeff > 0.1 ? " | sudden death!" : "");
|
||||||
} else {
|
} else {
|
||||||
@ -397,11 +500,26 @@
|
|||||||
.filter((t, idx) => !state[idx].elim)
|
.filter((t, idx) => !state[idx].elim)
|
||||||
.map((t) => `${t.name} 👑 wins`)
|
.map((t) => `${t.name} 👑 wins`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thresholdElement.textContent = `threshold: ${Math.round(threshold)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function draw() {
|
function draw() {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
for (let i = 0; i < teams.length; i++) {
|
||||||
drawSquares();
|
if (state[i].score <= 4) {
|
||||||
|
state[i].elim = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nTeams = state.map(t => !t.elim).filter(Boolean).length;
|
||||||
|
|
||||||
|
// practically inertia (0-1)
|
||||||
|
const smoothCoeff = 0.999;
|
||||||
|
|
||||||
|
smoothNTeams = smoothNTeams * (smoothCoeff) + nTeams * (1 - smoothCoeff);
|
||||||
|
if (isNaN(smoothNTeams)) smoothNTeams = nTeams;
|
||||||
|
|
||||||
|
threshold = numSquaresX*numSquaresY/smoothNTeams/mix(1.2, 4, suddenDeathCoeff);
|
||||||
|
|
||||||
squareTime++;
|
squareTime++;
|
||||||
squareTime %= 100000;
|
squareTime %= 100000;
|
||||||
@ -409,7 +527,6 @@
|
|||||||
for (let i = 0; i < teams.length; i++) {
|
for (let i = 0; i < teams.length; i++) {
|
||||||
if (state[i].elim) continue
|
if (state[i].elim) continue
|
||||||
const t = state[i];
|
const t = state[i];
|
||||||
drawBall(t.x, t.y, teams[i].color);
|
|
||||||
let bounce = updateSquareAndBounce(t.x, t.y, t.dx, t.dy, i);
|
let bounce = updateSquareAndBounce(t.x, t.y, t.dx, t.dy, i);
|
||||||
t.dx = bounce.dx;
|
t.dx = bounce.dx;
|
||||||
t.dy = bounce.dy;
|
t.dy = bounce.dy;
|
||||||
@ -427,8 +544,10 @@
|
|||||||
t.dy = -t.dy;
|
t.dy = -t.dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
coverBall(t.x, t.y);
|
||||||
t.x += t.dx;
|
t.x += t.dx;
|
||||||
t.y += t.dy;
|
t.y += t.dy;
|
||||||
|
drawBall(t.x, t.y, teams[i].color);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateScoreElement();
|
updateScoreElement();
|
||||||
@ -440,6 +559,6 @@
|
|||||||
requestAnimationFrame(draw);
|
requestAnimationFrame(draw);
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(draw);
|
requestAnimationFrame(draw);
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user