mirror of
https://github.com/simon987/bingo.git
synced 2025-04-10 13:56:42 +00:00
style, resize, animations
This commit is contained in:
parent
a16f3533b1
commit
ec706ab7ab
21
app.py
21
app.py
@ -17,10 +17,6 @@ socketio = SocketIO(app, async_mode="eventlet")
|
|||||||
logger = logging.getLogger("default")
|
logger = logging.getLogger("default")
|
||||||
|
|
||||||
|
|
||||||
# TODO: alphanum room
|
|
||||||
# TODO: alphanum name w/max len
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def page_index():
|
def page_index():
|
||||||
return send_file("web/index.html")
|
return send_file("web/index.html")
|
||||||
@ -58,13 +54,11 @@ class BingoNamespace(Namespace):
|
|||||||
cell = card.cells[message["cidx"]]
|
cell = card.cells[message["cidx"]]
|
||||||
|
|
||||||
if not cell.checked or card.last_cell == message["cidx"]:
|
if not cell.checked or card.last_cell == message["cidx"]:
|
||||||
cell.checked = not cell.checked
|
card.check_cell(message["cidx"])
|
||||||
card.last_cell = message["cidx"]
|
card.last_cell = message["cidx"]
|
||||||
db.save_card(card)
|
db.save_card(card)
|
||||||
|
|
||||||
emit("card_state", {
|
emit("card_state", {"card": card.serialize()}, room=room)
|
||||||
"card": card.serialize()
|
|
||||||
}, room=room)
|
|
||||||
|
|
||||||
if card.moves_until_win() == 0:
|
if card.moves_until_win() == 0:
|
||||||
game = db.get_game(room)
|
game = db.get_game(room)
|
||||||
@ -72,7 +66,7 @@ class BingoNamespace(Namespace):
|
|||||||
|
|
||||||
if game.should_end():
|
if game.should_end():
|
||||||
game.state = GameState.ENDED
|
game.state = GameState.ENDED
|
||||||
emit("game_state", {"state": game.state.name})
|
emit("game_state", {"state": game.state.name}, room=room)
|
||||||
db.save_game(game)
|
db.save_game(game)
|
||||||
|
|
||||||
def on_get_card(self, message):
|
def on_get_card(self, message):
|
||||||
@ -125,10 +119,8 @@ class BingoNamespace(Namespace):
|
|||||||
game.pool = message["pool"]
|
game.pool = message["pool"]
|
||||||
db.save_game(game)
|
db.save_game(game)
|
||||||
|
|
||||||
emit("game_state", {
|
emit("game_state", {"state": game.state.name, }, room=room)
|
||||||
"state": game.state.name,
|
emit("style_state", {"style": game.style}, room=room)
|
||||||
}, room=room)
|
|
||||||
|
|
||||||
emit("create_game_rsp", {
|
emit("create_game_rsp", {
|
||||||
"created": True,
|
"created": True,
|
||||||
})
|
})
|
||||||
@ -162,6 +154,9 @@ class BingoNamespace(Namespace):
|
|||||||
game = db.get_game(message["room"])
|
game = db.get_game(message["room"])
|
||||||
if not game:
|
if not game:
|
||||||
game = BingoGame(room, user.oid)
|
game = BingoGame(room, user.oid)
|
||||||
|
emit("style_state", {
|
||||||
|
"style": game.style
|
||||||
|
})
|
||||||
|
|
||||||
join_room(room)
|
join_room(room)
|
||||||
game.players.add(user.oid)
|
game.players.add(user.oid)
|
||||||
|
61
models.py
61
models.py
@ -10,10 +10,11 @@ import common
|
|||||||
|
|
||||||
class BingoCell:
|
class BingoCell:
|
||||||
|
|
||||||
def __init__(self, text, checked=False, free=False):
|
def __init__(self, text, checked=False, free=False, shake=False):
|
||||||
self.text = text
|
self.text = text
|
||||||
self.free = free
|
self.free = free
|
||||||
self.checked = checked
|
self.checked = checked
|
||||||
|
self.shake = shake
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self):
|
||||||
return self.__dict__
|
return self.__dict__
|
||||||
@ -26,7 +27,8 @@ class BingoCell:
|
|||||||
return BingoCell(
|
return BingoCell(
|
||||||
text=j["text"],
|
text=j["text"],
|
||||||
free=bool(j["free"]),
|
free=bool(j["free"]),
|
||||||
checked=bool(j["checked"])
|
checked=bool(j["checked"]),
|
||||||
|
shake=bool(j["shake"])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -51,12 +53,30 @@ class BingoCard:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def moves_until_win(self):
|
def moves_until_win(self):
|
||||||
return min(
|
return min(sum(1 for c in line if not c.checked) for line in self._lines())
|
||||||
*(sum(1 for c in self._row(row) if not c.checked) for row in range(0, self.size)),
|
|
||||||
*(sum(1 for c in self._col(col) if not c.checked) for col in range(0, self.size)),
|
def _lines(self):
|
||||||
sum(1 for c in self._diag_left() if not c.checked),
|
return [
|
||||||
sum(1 for c in self._diag_right() if not c.checked),
|
*(self._row(row) for row in range(0, self.size)),
|
||||||
)
|
*(self._col(col) for col in range(0, self.size)),
|
||||||
|
self._diag_left(),
|
||||||
|
self._diag_right(),
|
||||||
|
]
|
||||||
|
|
||||||
|
def check_cell(self, cell_idx):
|
||||||
|
cell = self.cells[cell_idx]
|
||||||
|
cell.checked = not cell.checked
|
||||||
|
self._update_shaking(cell)
|
||||||
|
|
||||||
|
def _update_shaking(self, cell):
|
||||||
|
for c in self.cells:
|
||||||
|
c.shake = False
|
||||||
|
if cell.checked:
|
||||||
|
for line in self._lines():
|
||||||
|
moves_remaining = sum(1 for c in line if not c.checked)
|
||||||
|
if cell in line and moves_remaining <= self.size / 2 and moves_remaining <= 3:
|
||||||
|
for c in line:
|
||||||
|
c.shake = not c.checked
|
||||||
|
|
||||||
def _row(self, idx):
|
def _row(self, idx):
|
||||||
return self.cells[idx * self.size:idx * self.size + self.size]
|
return self.cells[idx * self.size:idx * self.size + self.size]
|
||||||
@ -91,8 +111,24 @@ class GameState(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class BingoGame:
|
class BingoGame:
|
||||||
|
_default_style = {
|
||||||
|
"cell": {
|
||||||
|
"base": 0xBD93F9,
|
||||||
|
"checked": 0xFF5555,
|
||||||
|
"hover": 0xFF79C6,
|
||||||
|
"text": 0x111111
|
||||||
|
},
|
||||||
|
"card": {
|
||||||
|
"base": 0xFF5555,
|
||||||
|
"text": 0x50FA7B
|
||||||
|
},
|
||||||
|
"background": 0x282A36,
|
||||||
|
"message": 0xF8F8F2,
|
||||||
|
"font": "'Roboto Mono', 'Lucida Console'"
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, room, admin, mode=GameMode.FREE, pool=None, state=GameState.CREATING,
|
def __init__(self, room, admin, mode=GameMode.FREE, pool=None, state=GameState.CREATING,
|
||||||
players=None, winners=None):
|
players=None, winners=None, style=None):
|
||||||
self.room = room
|
self.room = room
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.admin = admin
|
self.admin = admin
|
||||||
@ -106,6 +142,9 @@ class BingoGame:
|
|||||||
if winners is None:
|
if winners is None:
|
||||||
winners = []
|
winners = []
|
||||||
self.winners = winners
|
self.winners = winners
|
||||||
|
if style is None:
|
||||||
|
style = BingoGame._default_style
|
||||||
|
self.style = style
|
||||||
|
|
||||||
def should_end(self):
|
def should_end(self):
|
||||||
# TODO: add winner count
|
# TODO: add winner count
|
||||||
@ -126,6 +165,7 @@ class BingoGame:
|
|||||||
"pool": self.pool,
|
"pool": self.pool,
|
||||||
"players": list(self.players),
|
"players": list(self.players),
|
||||||
"winners": self.winners,
|
"winners": self.winners,
|
||||||
|
"style": self.style,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -137,7 +177,8 @@ class BingoGame:
|
|||||||
admin=j["admin"],
|
admin=j["admin"],
|
||||||
state=GameState[j["state"]],
|
state=GameState[j["state"]],
|
||||||
players=set(j["players"]),
|
players=set(j["players"]),
|
||||||
winners=j["winners"]
|
winners=j["winners"],
|
||||||
|
style=j["style"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
101
static/net.js
Normal file
101
static/net.js
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
let SOCKET;
|
||||||
|
|
||||||
|
function initNet() {
|
||||||
|
SOCKET = io("/socket");
|
||||||
|
|
||||||
|
SOCKET.on("connect", () => {
|
||||||
|
let oid = selfOid();
|
||||||
|
if (oid) {
|
||||||
|
SOCKET.emit("join", {
|
||||||
|
room: ROOM,
|
||||||
|
name: selfName(),
|
||||||
|
oid: oid,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
openCreateUserModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
const card = new BingoCard(msg.card.oid, msg.parent, XSCALE, YSCALE);
|
||||||
|
card._self = msg.card
|
||||||
|
CARDS[msg.card.oid] = card;
|
||||||
|
updateCards();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
SOCKET.on("get_card_rsp", msg => {
|
||||||
|
// Add self card
|
||||||
|
let card = new BingoCard(msg.card.oid, msg.parent, 1.0);
|
||||||
|
|
||||||
|
card._self = msg.card;
|
||||||
|
CARDS[msg.card.oid] = card;
|
||||||
|
CARDS["SELF"] = card;
|
||||||
|
updateCards();
|
||||||
|
})
|
||||||
|
|
||||||
|
SOCKET.on("join_rsp", msg => {
|
||||||
|
|
||||||
|
if (msg.ok === false) {
|
||||||
|
openCreateUserModal();
|
||||||
|
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("room_join", msg => {
|
||||||
|
// console.log(msg);
|
||||||
|
})
|
||||||
|
|
||||||
|
SOCKET.on("style_state", msg => {
|
||||||
|
STYLE = msg.style
|
||||||
|
APP.renderer.backgroundColor = STYLE.background;
|
||||||
|
|
||||||
|
if (TEXT === undefined) {
|
||||||
|
TEXT = makeText();
|
||||||
|
APP.stage.addChild(TEXT);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
1
static/pixi-tween.js
Normal file
1
static/pixi-tween.js
Normal file
File diff suppressed because one or more lines are too long
8
static/pixi.min.js
vendored
Normal file
8
static/pixi.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
354
static/room.js
Normal file
354
static/room.js
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
const ROOM = window.location.pathname.slice(1);
|
||||||
|
|
||||||
|
let APP;
|
||||||
|
|
||||||
|
let STYLE;
|
||||||
|
|
||||||
|
let CARDS = {};
|
||||||
|
let TEXT;
|
||||||
|
|
||||||
|
let COLS, ROWS;
|
||||||
|
|
||||||
|
let XSCALE, YSCALE;
|
||||||
|
|
||||||
|
const CELL_PAD = 4;
|
||||||
|
const CARD_PAD = 20;
|
||||||
|
|
||||||
|
let WIDTH;
|
||||||
|
let HEIGHT;
|
||||||
|
let PORTRAIT;
|
||||||
|
|
||||||
|
let CARD_WIDTH, CARD_HEIGHT;
|
||||||
|
|
||||||
|
function calculateDimensions() {
|
||||||
|
|
||||||
|
WIDTH = window.innerWidth;
|
||||||
|
HEIGHT = window.innerHeight;
|
||||||
|
PORTRAIT = WIDTH < HEIGHT;
|
||||||
|
|
||||||
|
if (PORTRAIT) {
|
||||||
|
COLS = 3;
|
||||||
|
ROWS = 2;
|
||||||
|
|
||||||
|
CARD_WIDTH = 0.65 * WIDTH;
|
||||||
|
CARD_HEIGHT = (1 / 3) * HEIGHT;
|
||||||
|
|
||||||
|
XSCALE = (WIDTH) / ((CARD_WIDTH + CARD_PAD * 2) * COLS + CARD_PAD * 2)
|
||||||
|
YSCALE = (HEIGHT / 3) / ((CARD_HEIGHT + CARD_PAD * 4) * ROWS + CARD_PAD * 4)
|
||||||
|
} else {
|
||||||
|
COLS = 2;
|
||||||
|
ROWS = 3;
|
||||||
|
|
||||||
|
CARD_WIDTH = (1 / 3) * WIDTH;
|
||||||
|
CARD_HEIGHT = 0.70 * HEIGHT;
|
||||||
|
|
||||||
|
XSCALE = (WIDTH / 3) / ((CARD_WIDTH + CARD_PAD * 2) * COLS + CARD_PAD * 2)
|
||||||
|
YSCALE = (HEIGHT) / ((CARD_HEIGHT + CARD_PAD * 4) * ROWS + CARD_PAD * 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maskInputAlphaNum(document.getElementById("name"));
|
||||||
|
|
||||||
|
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 openCreateUserModal() {
|
||||||
|
document.getElementById("create-user").style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCreateUserSubmit() {
|
||||||
|
const name = document.getElementById("name").value;
|
||||||
|
localStorage.setItem("name", name)
|
||||||
|
|
||||||
|
SOCKET.emit("join", {
|
||||||
|
room: ROOM,
|
||||||
|
name: name
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function initApp() {
|
||||||
|
APP = new PIXI.Application({
|
||||||
|
antialias: false,
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
resolution: 2,
|
||||||
|
resizeTo: window
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(APP.view);
|
||||||
|
APP.renderer.autoDensity = true;
|
||||||
|
APP.renderer.resize(window.innerWidth, window.innerHeight);
|
||||||
|
|
||||||
|
// pixi-tween init
|
||||||
|
function animate() {
|
||||||
|
window.requestAnimationFrame(animate);
|
||||||
|
APP.renderer.render(APP.stage);
|
||||||
|
PIXI.tweenManager.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
animate();
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeCell(cell, size, card_oid, xscale, yscale) {
|
||||||
|
|
||||||
|
const g = new PIXI.Graphics();
|
||||||
|
const sprite = new PIXI.Sprite();
|
||||||
|
|
||||||
|
sprite._baseColor = cell.checked ? STYLE.cell.checked : STYLE.cell.base;
|
||||||
|
sprite._color = sprite._baseColor;
|
||||||
|
|
||||||
|
sprite._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: STYLE.font,
|
||||||
|
fontSize: xscale === 1 ? 16 : 10,
|
||||||
|
fill: STYLE.cell.text,
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (text.height < maxHeight) {
|
||||||
|
g.addChild(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.texture = APP.renderer.generateTexture(g, PIXI.SCALE_MODES.LINEAR, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
sprite._destroy = function () {
|
||||||
|
if (this._tw) {
|
||||||
|
this._tw.stop();
|
||||||
|
this._tw.remove();
|
||||||
|
}
|
||||||
|
this.destroy({texture: true, baseTexture: true, children: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
sprite._update()
|
||||||
|
|
||||||
|
if (xscale === 1) {
|
||||||
|
sprite.interactive = true;
|
||||||
|
sprite.buttonMode = true;
|
||||||
|
|
||||||
|
sprite.on("mouseover", () => {
|
||||||
|
sprite._color = STYLE.cell.hover;
|
||||||
|
sprite._update();
|
||||||
|
})
|
||||||
|
|
||||||
|
sprite.on("mouseout", () => {
|
||||||
|
sprite._color = sprite._baseColor;
|
||||||
|
sprite._update();
|
||||||
|
})
|
||||||
|
|
||||||
|
sprite.on("click", () => {
|
||||||
|
SOCKET.emit("cell_click", {
|
||||||
|
"oid": selfOid(),
|
||||||
|
"cidx": cell.cidx,
|
||||||
|
"card": card_oid,
|
||||||
|
"room": ROOM
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprite
|
||||||
|
}
|
||||||
|
|
||||||
|
function BingoCard(oid, parent, xscale = 1, yscale = 1) {
|
||||||
|
|
||||||
|
let g = new PIXI.Graphics();
|
||||||
|
|
||||||
|
|
||||||
|
g._update = function (card) {
|
||||||
|
|
||||||
|
g.clear();
|
||||||
|
g.setMatrix(new PIXI.Matrix().scale(xscale, yscale));
|
||||||
|
g.lineStyle(3, STYLE.card.base);
|
||||||
|
g.drawRect(0, 0, CARD_WIDTH, CARD_HEIGHT);
|
||||||
|
|
||||||
|
if (!this._text) {
|
||||||
|
this._text = new PIXI.Text(parent, {
|
||||||
|
fontFamily: STYLE.font,
|
||||||
|
fontSize: 16,
|
||||||
|
fill: STYLE.card.text,
|
||||||
|
align: "center",
|
||||||
|
strokeThickness: 3
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this._text.anchor.set(0.5, 0.35)
|
||||||
|
} else {
|
||||||
|
g.removeChild(this._text);
|
||||||
|
}
|
||||||
|
this._text.x = g.width / 2;
|
||||||
|
this._text.y = g.height;
|
||||||
|
g.addChild(this._text);
|
||||||
|
|
||||||
|
this._self = card;
|
||||||
|
let toDestroy = [];
|
||||||
|
g.children.forEach(child => {
|
||||||
|
if (child !== this._text) {
|
||||||
|
toDestroy.push(child);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
toDestroy.forEach(x => {
|
||||||
|
x._destroy();
|
||||||
|
})
|
||||||
|
|
||||||
|
let size = Math.floor(Math.sqrt(this._self.cells.length))
|
||||||
|
|
||||||
|
for (let col = 0; col < size; col++) {
|
||||||
|
for (let row = 0; row < size; row++) {
|
||||||
|
|
||||||
|
let cidx = col * size + row;
|
||||||
|
let cell = this._self.cells[cidx];
|
||||||
|
cell.cidx = cidx;
|
||||||
|
|
||||||
|
let c = makeCell(cell, size, oid, xscale, yscale)
|
||||||
|
c.x = (c.width + CELL_PAD) * row + CELL_PAD + 1;
|
||||||
|
c.y = (c.height + CELL_PAD) * col + CELL_PAD + 1;
|
||||||
|
|
||||||
|
if (cell.shake) {
|
||||||
|
cell.shake = false;
|
||||||
|
shake(c, "x", 16 * xscale)
|
||||||
|
shake(c, "y", 16 * xscale)
|
||||||
|
shake(g, "x", 3 * xscale)
|
||||||
|
shake(g, "y", 3 * xscale)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.addChild(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeText() {
|
||||||
|
|
||||||
|
const PAD = 5;
|
||||||
|
|
||||||
|
const t = new PIXI.Text("", {
|
||||||
|
fontFamily: STYLE.font,
|
||||||
|
fontSize: 38,
|
||||||
|
fill: STYLE.cell.text,
|
||||||
|
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, _) => {
|
||||||
|
return a === t ? 1 : 0;
|
||||||
|
})
|
||||||
|
t.text = text
|
||||||
|
|
||||||
|
if (t._to) {
|
||||||
|
window.clearTimeout(t._to);
|
||||||
|
}
|
||||||
|
t._to = window.setTimeout(() => {
|
||||||
|
t.text = ""
|
||||||
|
}, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCards() {
|
||||||
|
|
||||||
|
let nextRow = [0, 0];
|
||||||
|
let nextCol = [0, 0];
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
Object.keys(CARDS).forEach(key => {
|
||||||
|
|
||||||
|
if (key === "SELF") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
counter += 1;
|
||||||
|
|
||||||
|
let card = CARDS[key];
|
||||||
|
|
||||||
|
if (CARDS["SELF"] === card) {
|
||||||
|
//Self
|
||||||
|
card.x = WIDTH / 2 - (CARD_WIDTH / 2);
|
||||||
|
card.y = HEIGHT / 2 - (CARD_HEIGHT / 2);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Other
|
||||||
|
let nextSide = counter % 2;
|
||||||
|
card.x = (CARD_WIDTH * XSCALE + CARD_PAD) * nextCol[nextSide] + CARD_PAD;
|
||||||
|
card.y = (CARD_HEIGHT * YSCALE + CARD_PAD) * nextRow[nextSide] + CARD_PAD;
|
||||||
|
|
||||||
|
if (nextSide === 1) {
|
||||||
|
if (PORTRAIT) {
|
||||||
|
card.y += HEIGHT * (2 / 3);
|
||||||
|
} else {
|
||||||
|
card.x += WIDTH * (2 / 3) - CARD_PAD / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextCol[nextSide] === COLS - 1) {
|
||||||
|
nextCol[nextSide] = 0;
|
||||||
|
nextRow[nextSide] += 1;
|
||||||
|
} else {
|
||||||
|
nextCol[nextSide] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: if count > row*cols, increase rows
|
||||||
|
}
|
||||||
|
|
||||||
|
APP.stage.removeChild(card);
|
||||||
|
APP.stage.addChild(card);
|
||||||
|
card._update(card._self);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onresize = function () {
|
||||||
|
calculateDimensions();
|
||||||
|
updateCards();
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateDimensions();
|
||||||
|
initApp();
|
||||||
|
initNet();
|
8
static/socket.io.js
Normal file
8
static/socket.io.js
Normal file
File diff suppressed because one or more lines are too long
59
static/util.js
Normal file
59
static/util.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
function maskInputAlphaNum(input) {
|
||||||
|
input.addEventListener("keydown", e => {
|
||||||
|
if (!isAlphanumeric(e.key) && e.key !== "Backspace" && e.key !== "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAlphanumeric(c) {
|
||||||
|
return "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_".indexOf(c) > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// PIXI-tween
|
||||||
|
function shake(sprite, axis, amplitude) {
|
||||||
|
sprite._tw = PIXI.tweenManager.createTween(sprite);
|
||||||
|
|
||||||
|
let tw2 = PIXI.tweenManager.createTween(sprite);
|
||||||
|
tw2.time = 1;
|
||||||
|
tw2.from({[axis]: sprite[axis] + 1});
|
||||||
|
tw2.to({[axis]: sprite[axis]});
|
||||||
|
tw2.easing = constant();
|
||||||
|
tw2.expire = true;
|
||||||
|
|
||||||
|
let tw = sprite._tw;
|
||||||
|
tw.time = 400;
|
||||||
|
tw.expire = true;
|
||||||
|
tw.easing = shakeFn(12, amplitude);
|
||||||
|
tw.from({[axis]: sprite[axis]});
|
||||||
|
tw.to({[axis]: sprite[axis] + 1});
|
||||||
|
tw.chain(tw2);
|
||||||
|
tw.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
let shakeFn = function (duration, amplitude) {
|
||||||
|
let counter = duration;
|
||||||
|
|
||||||
|
return function (t) {
|
||||||
|
if (counter <= 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
counter--;
|
||||||
|
return (Math.random() - 0.5) * amplitude * (counter / duration);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let constant = function () {
|
||||||
|
return function (t) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalStorage stuff
|
||||||
|
function selfOid() {
|
||||||
|
return localStorage.getItem("oid")
|
||||||
|
}
|
||||||
|
|
||||||
|
function selfName() {
|
||||||
|
return localStorage.getItem("name")
|
||||||
|
}
|
383
web/room.html
383
web/room.html
@ -30,7 +30,6 @@
|
|||||||
width: 80%;
|
width: 80%;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@ -38,9 +37,6 @@
|
|||||||
|
|
||||||
<div id="game"></div>
|
<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 id="create-game" class="modal">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
|
|
||||||
@ -102,378 +98,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script src="/static/socket.io.js"></script>
|
||||||
const socket = io("/socket");
|
<script src="/static/pixi.min.js"></script>
|
||||||
|
<script src="/static/pixi-tween.js"></script>
|
||||||
const ROOM = window.location.pathname.slice(1);
|
<script src="/static/util.js"></script>
|
||||||
|
<script src="/static/net.js"></script>
|
||||||
const WIDTH = window.innerWidth;
|
<script src="/static/room.js"></script>
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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, _) => {
|
|
||||||
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>
|
</body>
|
||||||
</html>
|
</html>
|
Loading…
x
Reference in New Issue
Block a user