mirror of
https://github.com/simon987/bingo.git
synced 2025-04-10 13:56:42 +00:00
484 lines
12 KiB
HTML
484 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Test</title>
|
|
<style>
|
|
* {
|
|
padding: 0;
|
|
margin: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
z-index: 1;
|
|
padding-top: 100px;
|
|
left: 0;
|
|
top: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0, 0, 0, 0.6);
|
|
}
|
|
|
|
.modal-content {
|
|
background-color: #fefefe;
|
|
margin: auto;
|
|
padding: 20px;
|
|
border: 1px solid #888;
|
|
width: 80%;
|
|
max-width: 1200px;
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div id="game"></div>
|
|
|
|
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
|
|
<script src="https://pixijs.download/release/pixi.min.js"></script>
|
|
|
|
<div id="create-game" class="modal">
|
|
<div class="modal-content">
|
|
|
|
<h3>Create game</h3>
|
|
<form onsubmit="return onCreateGameSubmit()">
|
|
<label>
|
|
Game mode
|
|
<select id="game-mode">
|
|
<option value="FREE">Free</option>
|
|
<option value="ADMIN" disabled>Admin</option>
|
|
<option value="CPU" disabled>CPU</option>
|
|
</select>
|
|
</label>
|
|
<br>
|
|
|
|
<label>
|
|
Maximum size
|
|
|
|
<select id="maximum-size">
|
|
<option selected value="0">Automatic</option>
|
|
<option value="2">2</option>
|
|
<option value="3">3</option>
|
|
<option value="4">4</option>
|
|
<option value="5">5</option>
|
|
<option value="6">6</option>
|
|
</select>
|
|
</label>
|
|
<br>
|
|
|
|
<label>
|
|
Word/Phrases (one per line)
|
|
<br>
|
|
<textarea id="pool" cols="70" rows="10" placeholder="Words/Phrases (one per line)"></textarea>
|
|
</label>
|
|
<br>
|
|
<label>
|
|
Middle cell is free
|
|
<input id="middle-free" type="checkbox">
|
|
</label>
|
|
<br>
|
|
|
|
<input type="submit" value="Create game">
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="create-user" class="modal">
|
|
<div class="modal-content">
|
|
|
|
<h3>New player</h3>
|
|
<form onsubmit="return onCreateUserSubmit()">
|
|
<label>
|
|
Name
|
|
<input type="text" id="name">
|
|
</label>
|
|
<br>
|
|
<input type="submit" value="Join room">
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const socket = io("/socket");
|
|
|
|
const ROOM = window.location.pathname.slice(1);
|
|
|
|
const WIDTH = window.innerWidth;
|
|
const HEIGHT = window.innerHeight;
|
|
|
|
const PORTRAIT = window.innerWidth < window.innerHeight;
|
|
|
|
let CARDS = {};
|
|
|
|
let COLS, ROWS;
|
|
if (PORTRAIT) {
|
|
COLS = 3;
|
|
ROWS = 2;
|
|
} else {
|
|
COLS = 2;
|
|
ROWS = 3;
|
|
}
|
|
|
|
let NEXT_ROW = [0, 0];
|
|
let NEXT_COL = [0, 0];
|
|
let NEXT_SIDE = 0;
|
|
|
|
//TODO: refact/extract to maskInputAlpha() or someth
|
|
const input = document.getElementById("name");
|
|
input.addEventListener("keydown", e => {
|
|
if (!isAlphanumeric(e.key) && e.key !== "Backspace" && e.key !== "Enter") {
|
|
e.preventDefault();
|
|
}
|
|
})
|
|
|
|
function createGameModal() {
|
|
document.getElementById("create-game").style.display = "block";
|
|
}
|
|
|
|
function onCreateGameSubmit() {
|
|
const gameMode = document.getElementById("game-mode").value;
|
|
const pool = document.getElementById("pool").value;
|
|
const maximumSize = document.getElementById("maximum-size").value;
|
|
const middleFree = document.getElementById("middle-free").value;
|
|
|
|
socket.emit("create_game", {
|
|
"oid": selfOid(),
|
|
"room": ROOM,
|
|
"mode": gameMode,
|
|
"maximum_size": maximumSize,
|
|
"middle_free": middleFree,
|
|
"pool": pool.split(/\s+/).map(w => w.trim())
|
|
})
|
|
return false;
|
|
}
|
|
|
|
function selfOid() {
|
|
return localStorage.getItem("oid")
|
|
}
|
|
|
|
function selfName() {
|
|
return localStorage.getItem("name")
|
|
}
|
|
|
|
socket.on("message", msg => {
|
|
TEXT._display(msg.text, msg.timeout)
|
|
})
|
|
|
|
socket.on("end_message", msg => {
|
|
alert(msg.text)
|
|
})
|
|
|
|
socket.on("game_state", msg => {
|
|
if (msg.state === "PLAYING") {
|
|
document.getElementById("create-game").style.display = "none";
|
|
|
|
socket.emit("get_card", {
|
|
"oid": selfOid(),
|
|
"room": ROOM,
|
|
})
|
|
} else if (msg.state === "ENDED") {
|
|
socket.emit("get_end_message")
|
|
}
|
|
})
|
|
|
|
socket.on("card_state", msg => {
|
|
if (CARDS.hasOwnProperty("SELF")) {
|
|
if (CARDS.hasOwnProperty(msg.card.oid)) {
|
|
CARDS[msg.card.oid]._update(msg.card.cells)
|
|
} else {
|
|
// Add other card
|
|
let card = new BingoCard(msg.card.oid, msg.parent, XSCALE, YSCALE);
|
|
card._update(msg.card.cells)
|
|
app.stage.addChild(card);
|
|
CARDS[msg.card.oid] = card;
|
|
|
|
NEXT_SIDE = (Object.keys(CARDS).length - 1) % 2
|
|
card.x = (CARD_WIDTH * XSCALE + CARD_PAD) * NEXT_COL[NEXT_SIDE] + CARD_PAD;
|
|
card.y = (CARD_HEIGHT * YSCALE + CARD_PAD) * NEXT_ROW[NEXT_SIDE] + CARD_PAD;
|
|
|
|
if (NEXT_SIDE === 1) {
|
|
card.x += WIDTH * (2 / 3) - CARD_PAD / 2
|
|
}
|
|
if (NEXT_COL[NEXT_SIDE] === COLS - 1) {
|
|
NEXT_COL[NEXT_SIDE] = 0;
|
|
NEXT_ROW[NEXT_SIDE] += 1;
|
|
} else {
|
|
NEXT_COL[NEXT_SIDE] += 1;
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
socket.on("get_card_rsp", msg => {
|
|
// Add self card
|
|
let card = new BingoCard(msg.card.oid, msg.parent, 1.0);
|
|
card._update(msg.card.cells)
|
|
card.x = WIDTH / 2 - (CARD_WIDTH / 2);
|
|
card.y = HEIGHT / 2 - (CARD_HEIGHT / 2);
|
|
app.stage.addChild(card);
|
|
|
|
CARDS[msg.card.oid] = card;
|
|
CARDS["SELF"] = card;
|
|
})
|
|
|
|
function createUser() {
|
|
document.getElementById("create-user").style.display = "block";
|
|
}
|
|
|
|
function isAlphanumeric(c) {
|
|
return "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_".indexOf(c) > -1;
|
|
}
|
|
|
|
function onCreateUserSubmit() {
|
|
const name = document.getElementById("name").value;
|
|
localStorage.setItem("name", name)
|
|
|
|
socket.emit("join", {
|
|
room: ROOM,
|
|
name: name
|
|
});
|
|
return false;
|
|
}
|
|
|
|
socket.on("join_rsp", msg => {
|
|
|
|
if (msg.ok === false) {
|
|
createUser();
|
|
return;
|
|
}
|
|
|
|
document.getElementById("create-user").style.display = "none";
|
|
|
|
localStorage.setItem("oid", msg.oid)
|
|
document.title = msg.oid
|
|
|
|
if (msg.state === "CREATING") {
|
|
createGameModal();
|
|
|
|
} else if (msg.state === "PLAYING") {
|
|
socket.emit("get_card", {
|
|
"oid": selfOid(),
|
|
"room": ROOM,
|
|
})
|
|
} else if (msg.state === "ENDED") {
|
|
socket.emit("get_end_message")
|
|
}
|
|
});
|
|
|
|
socket.on("connect", () => {
|
|
let oid = selfOid();
|
|
if (oid) {
|
|
socket.emit("join", {
|
|
room: ROOM,
|
|
name: selfName(),
|
|
oid: oid,
|
|
});
|
|
} else {
|
|
createUser();
|
|
}
|
|
});
|
|
|
|
socket.on("room_join", msg => {
|
|
console.log(msg);
|
|
})
|
|
|
|
const app = new PIXI.Application(
|
|
{antialias: false, width: WIDTH, height: HEIGHT, resolution: 2, resizeTo: window}
|
|
);
|
|
document.body.appendChild(app.view);
|
|
app.renderer.autoDensity = true;
|
|
app.renderer.resize(window.innerWidth, window.innerHeight);
|
|
|
|
let CARD_WIDTH, CARD_HEIGHT;
|
|
|
|
if (PORTRAIT) {
|
|
CARD_WIDTH = 0.65 * WIDTH;
|
|
CARD_HEIGHT = (1 / 3) * HEIGHT;
|
|
} else {
|
|
CARD_WIDTH = (1 / 3) * WIDTH;
|
|
CARD_HEIGHT = 0.70 * HEIGHT;
|
|
}
|
|
|
|
const CELL_COLOR = 0x00FF00;
|
|
const CELL_COLOR_CHECKED = 0xFF0000;
|
|
const CELL_COLOR_HOVER = 0x00FFFF;
|
|
|
|
const CELL_PAD = 4;
|
|
const CARD_PAD = 20;
|
|
|
|
function makeCell(cell, size, card_oid, xscale, yscale) {
|
|
|
|
const g = new PIXI.Graphics();
|
|
|
|
if (xscale === 1) {
|
|
g.interactive = true;
|
|
g.buttonMode = true;
|
|
|
|
g.on("mouseover", () => {
|
|
g._color = CELL_COLOR_HOVER;
|
|
g._update();
|
|
})
|
|
|
|
g.on("mouseout", () => {
|
|
g._color = g._baseColor;
|
|
g._update();
|
|
})
|
|
|
|
g.on("click", () => {
|
|
socket.emit("cell_click", {
|
|
"oid": selfOid(),
|
|
"cidx": cell.cidx,
|
|
"card": card_oid,
|
|
"room": ROOM
|
|
})
|
|
})
|
|
}
|
|
|
|
g._baseColor = cell.checked ? CELL_COLOR_CHECKED : CELL_COLOR;
|
|
g._color = g._baseColor;
|
|
|
|
g._update = function () {
|
|
g.clear();
|
|
g.beginFill(this._color);
|
|
g.drawRect(0, 0,
|
|
((xscale * CARD_WIDTH - CELL_PAD) / size - CELL_PAD),
|
|
((yscale * CARD_HEIGHT - CELL_PAD) / size - CELL_PAD)
|
|
);
|
|
g.endFill()
|
|
|
|
if (g.children.length === 0) {
|
|
const maxWidth = g.width - 4;
|
|
const maxHeight = g.height - 4;
|
|
|
|
const text = new PIXI.Text(cell.text,
|
|
{
|
|
fontFamily: 'Hack',
|
|
fontSize: xscale === 1 ? 16 : 8,
|
|
fill: 0x000000,
|
|
align: "center",
|
|
breakWords: true,
|
|
wordWrap: true,
|
|
wordWrapWidth: maxWidth,
|
|
}
|
|
);
|
|
text.anchor.set(0.5, 0.5)
|
|
text.x = g.width / 2;
|
|
text.y = g.height / 2;
|
|
|
|
//TODO: Adjust text size
|
|
if (text.height < maxHeight) {
|
|
g.addChild(text);
|
|
}
|
|
}
|
|
}
|
|
|
|
g._update()
|
|
return g
|
|
}
|
|
|
|
function BingoCard(oid, parent, xscale = 1, yscale = 1) {
|
|
|
|
const COLOR = 0x5cafe2;
|
|
|
|
let g = new PIXI.Graphics();
|
|
|
|
g.setMatrix(new PIXI.Matrix().scale(xscale, yscale));
|
|
g.lineStyle(3, COLOR);
|
|
g.drawRect(0, 0, CARD_WIDTH, CARD_HEIGHT);
|
|
|
|
const text = new PIXI.Text(parent, {
|
|
fontFamily: 'Hack',
|
|
fontSize: 16,
|
|
fill: 0xFFFFFF,
|
|
align: "center",
|
|
strokeThickness: 3
|
|
}
|
|
);
|
|
text.anchor.set(0.5, 0.35)
|
|
text.x = g.width / 2;
|
|
text.y = g.height;
|
|
g.addChild(text);
|
|
|
|
g._update = function (cells) {
|
|
|
|
g.children.forEach(child => {
|
|
if (child !== text) {
|
|
child.destroy();
|
|
}
|
|
})
|
|
|
|
let size = Math.floor(Math.sqrt(cells.length))
|
|
|
|
for (let col = 0; col < size; col++) {
|
|
for (let row = 0; row < size; row++) {
|
|
|
|
let cidx = col * size + row;
|
|
let cell = cells[cidx];
|
|
cell.cidx = cidx;
|
|
|
|
let c = makeCell(cell, size, oid, xscale, yscale)
|
|
c.x = (c.width + CELL_PAD) * row + CELL_PAD;
|
|
c.y = (c.height + CELL_PAD) * col + CELL_PAD;
|
|
g.addChild(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
return g;
|
|
}
|
|
|
|
function makeText() {
|
|
|
|
const PAD = 5;
|
|
|
|
const t = new PIXI.Text("", {
|
|
fontFamily: 'Hack',
|
|
fontSize: 38,
|
|
fill: 0xFFFFFF,
|
|
strokeThickness: 2,
|
|
align: "left",
|
|
breakWords: true,
|
|
wordWrap: true,
|
|
wordWrapWidth: WIDTH / 3 - PAD * 2,
|
|
});
|
|
|
|
t.x = WIDTH / 2;
|
|
t.y = PORTRAIT ? HEIGHT / 2 : HEIGHT / 12;
|
|
t.anchor.set(0.5, 0.5)
|
|
|
|
t._display = function(text, timeout) {
|
|
app.stage.children.sort((a,b) => {
|
|
return a === t ? 1 : 0;
|
|
})
|
|
t.text = text
|
|
|
|
if (t._to) {
|
|
window.clearTimeout(t._to);
|
|
}
|
|
t._to = window.setTimeout(() => {
|
|
t.text = ""
|
|
}, timeout)
|
|
}
|
|
|
|
return t;
|
|
}
|
|
|
|
let TEXT = makeText();
|
|
app.stage.addChild(TEXT);
|
|
|
|
let XSCALE, YSCALE;
|
|
if (PORTRAIT) {
|
|
XSCALE = (WIDTH) / ((CARD_WIDTH + CARD_PAD * 2) * COLS + CARD_PAD * 2)
|
|
YSCALE = (HEIGHT / 3) / ((CARD_HEIGHT + CARD_PAD * 4) * ROWS + CARD_PAD * 4)
|
|
} else {
|
|
XSCALE = (WIDTH / 3) / ((CARD_WIDTH + CARD_PAD * 2) * COLS + CARD_PAD * 2)
|
|
YSCALE = (HEIGHT) / ((CARD_HEIGHT + CARD_PAD * 4) * ROWS + CARD_PAD * 4)
|
|
}
|
|
|
|
</script>
|
|
</body>
|
|
</html> |