better game overview, start/finish time
This commit is contained in:
parent
af5364155f
commit
69ab049f50
7 changed files with 142 additions and 27 deletions
|
|
@ -98,7 +98,14 @@ function removeSocket(gameId, socket) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllGames() {
|
function getAllGames() {
|
||||||
return Object.values(GAMES)
|
return Object.values(GAMES).sort((a, b) => {
|
||||||
|
// when both have same finished state, sort by started
|
||||||
|
if (isFinished(a.id) === isFinished(b.id)) {
|
||||||
|
return b.puzzle.data.started - a.puzzle.data.started
|
||||||
|
}
|
||||||
|
// otherwise, sort: unfinished, finished
|
||||||
|
return isFinished(a.id) ? 1 : -1
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllPlayers(gameId) {
|
function getAllPlayers(gameId) {
|
||||||
|
|
@ -119,6 +126,10 @@ function getImageUrl(gameId) {
|
||||||
return GAMES[gameId].puzzle.info.imageUrl
|
return GAMES[gameId].puzzle.info.imageUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isFinished(gameId) {
|
||||||
|
return getFinishedTileCount(gameId) === getTileCount(gameId)
|
||||||
|
}
|
||||||
|
|
||||||
function getFinishedTileCount(gameId) {
|
function getFinishedTileCount(gameId) {
|
||||||
let count = 0
|
let count = 0
|
||||||
for (let t of GAMES[gameId].puzzle.tiles) {
|
for (let t of GAMES[gameId].puzzle.tiles) {
|
||||||
|
|
@ -527,6 +538,11 @@ function handleInput(gameId, playerId, input) {
|
||||||
finishTiles(gameId, tileIdxs)
|
finishTiles(gameId, tileIdxs)
|
||||||
changePlayer(gameId, playerId, { points: getPlayerPoints(gameId, playerId) + tileIdxs.length })
|
changePlayer(gameId, playerId, { points: getPlayerPoints(gameId, playerId) + tileIdxs.length })
|
||||||
_tileChanges(tileIdxs)
|
_tileChanges(tileIdxs)
|
||||||
|
// check if the puzzle is finished
|
||||||
|
if (getFinishedTileCount(gameId) === getTileCount(gameId)) {
|
||||||
|
changeData(gameId, { finished: Util.timestamp() })
|
||||||
|
_dataChange()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Snap to other tiles
|
// Snap to other tiles
|
||||||
const check = (gameId, tileIdx, otherTileIdx, off) => {
|
const check = (gameId, tileIdx, otherTileIdx, off) => {
|
||||||
|
|
|
||||||
18
game/game.js
18
game/game.js
|
|
@ -76,10 +76,20 @@ function addMenuToDom(previewImageUrl) {
|
||||||
)
|
)
|
||||||
|
|
||||||
const settingsEl = document.createElement('table')
|
const settingsEl = document.createElement('table')
|
||||||
settingsEl.classList.add('layer', 'settings', 'closed')
|
settingsEl.classList.add('settings')
|
||||||
settingsEl.appendChild(bgColorPickerRow)
|
settingsEl.appendChild(bgColorPickerRow)
|
||||||
settingsEl.appendChild(playerColorPickerRow)
|
settingsEl.appendChild(playerColorPickerRow)
|
||||||
settingsEl.appendChild(nameChangeRow)
|
settingsEl.appendChild(nameChangeRow)
|
||||||
|
settingsEl.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
})
|
||||||
|
|
||||||
|
const settingsOverlay = document.createElement('div')
|
||||||
|
settingsOverlay.classList.add('overlay', 'transparent', 'closed')
|
||||||
|
settingsOverlay.appendChild(settingsEl)
|
||||||
|
settingsOverlay.addEventListener('click', () => {
|
||||||
|
settingsOverlay.classList.toggle('closed')
|
||||||
|
})
|
||||||
|
|
||||||
const previewEl = document.createElement('div')
|
const previewEl = document.createElement('div')
|
||||||
previewEl.classList.add('preview')
|
previewEl.classList.add('preview')
|
||||||
|
|
@ -100,7 +110,7 @@ function addMenuToDom(previewImageUrl) {
|
||||||
settingsOpenerEl.classList.add('opener')
|
settingsOpenerEl.classList.add('opener')
|
||||||
settingsOpenerEl.appendChild(document.createTextNode('🛠️ Settings'))
|
settingsOpenerEl.appendChild(document.createTextNode('🛠️ Settings'))
|
||||||
settingsOpenerEl.addEventListener('click', () => {
|
settingsOpenerEl.addEventListener('click', () => {
|
||||||
settingsEl.classList.toggle('closed')
|
settingsOverlay.classList.toggle('closed')
|
||||||
})
|
})
|
||||||
|
|
||||||
const homeEl = document.createElement('a')
|
const homeEl = document.createElement('a')
|
||||||
|
|
@ -153,7 +163,7 @@ function addMenuToDom(previewImageUrl) {
|
||||||
scoresEl.appendChild(scoresTitleEl)
|
scoresEl.appendChild(scoresTitleEl)
|
||||||
scoresEl.appendChild(scoresListEl)
|
scoresEl.appendChild(scoresListEl)
|
||||||
|
|
||||||
document.body.appendChild(settingsEl)
|
document.body.appendChild(settingsOverlay)
|
||||||
document.body.appendChild(previewOverlay)
|
document.body.appendChild(previewOverlay)
|
||||||
document.body.appendChild(menuEl)
|
document.body.appendChild(menuEl)
|
||||||
document.body.appendChild(scoresEl)
|
document.body.appendChild(scoresEl)
|
||||||
|
|
@ -284,7 +294,7 @@ async function main() {
|
||||||
bgColorPickerEl.addEventListener('change', () => {
|
bgColorPickerEl.addEventListener('change', () => {
|
||||||
evts.addEvent(['bg_color', bgColorPickerEl.value])
|
evts.addEvent(['bg_color', bgColorPickerEl.value])
|
||||||
})
|
})
|
||||||
playerColorPickerEl.value = Game.getPlayerBgColor(gameId, CLIENT_ID)
|
playerColorPickerEl.value = Game.getPlayerColor(gameId, CLIENT_ID)
|
||||||
playerColorPickerEl.addEventListener('change', () => {
|
playerColorPickerEl.addEventListener('change', () => {
|
||||||
evts.addEvent(['player_color', playerColorPickerEl.value])
|
evts.addEvent(['player_color', playerColorPickerEl.value])
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,20 @@ const Upload = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const timestamp = () => {
|
||||||
|
const d = new Date();
|
||||||
|
return Date.UTC(
|
||||||
|
d.getUTCFullYear(),
|
||||||
|
d.getUTCMonth(),
|
||||||
|
d.getUTCDate(),
|
||||||
|
d.getUTCHours(),
|
||||||
|
d.getUTCMinutes(),
|
||||||
|
d.getUTCSeconds(),
|
||||||
|
d.getUTCMilliseconds(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Upload,
|
Upload,
|
||||||
|
|
@ -37,13 +51,17 @@ export default {
|
||||||
template: `
|
template: `
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<h1>Running games</h1>
|
<h1>Running games</h1>
|
||||||
<div v-for="g in games">
|
<div class="game-teaser-wrap" v-for="g in games">
|
||||||
<a :href="'/g/' + g.id">
|
<div class="game-teaser" :style="'background-image: url('+g.imageUrl+')'">
|
||||||
<img :src="g.imageUrl" style="width: 150px; display: inline-block; margin: 5px;" />
|
<a class="game-info" :href="'/g/' + g.id">
|
||||||
<br />
|
<span class="game-info-text">
|
||||||
{{g.tilesFinished}}/{{g.tilesTotal}} pieces, {{g.players}} players
|
🧩 {{g.tilesFinished}}/{{g.tilesTotal}}<br />
|
||||||
|
👥 {{g.players}}<br />
|
||||||
|
{{time(g.started, g.finished)}}<br />
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h1>New game</h1>
|
<h1>New game</h1>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -81,6 +99,32 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
time(start, end) {
|
||||||
|
const icon = end ? '🏁' : '⏳'
|
||||||
|
|
||||||
|
const from = start;
|
||||||
|
const to = end || timestamp()
|
||||||
|
|
||||||
|
const MS = 1
|
||||||
|
const SEC = MS * 1000
|
||||||
|
const MIN = SEC * 60
|
||||||
|
const HOUR = MIN * 60
|
||||||
|
const DAY = HOUR * 24
|
||||||
|
|
||||||
|
let diff = to - from
|
||||||
|
const d = Math.floor(diff / DAY)
|
||||||
|
diff = diff % DAY
|
||||||
|
|
||||||
|
const h = Math.floor(diff / HOUR)
|
||||||
|
diff = diff % HOUR
|
||||||
|
|
||||||
|
const m = Math.floor(diff / MIN)
|
||||||
|
diff = diff % MIN
|
||||||
|
|
||||||
|
const s = Math.floor(diff / SEC)
|
||||||
|
|
||||||
|
return `${icon} ${d}d ${h}h ${m}m ${s}s`
|
||||||
|
},
|
||||||
mediaImgUploaded(j) {
|
mediaImgUploaded(j) {
|
||||||
this.image = j.image
|
this.image = j.image
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,21 @@ a:hover { color: var(--link-hover-color); }
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layer {
|
.overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
background: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay.transparent {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
|
|
@ -56,16 +70,6 @@ a:hover { color: var(--link-hover-color); }
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 10;
|
|
||||||
background: var(--bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
|
|
@ -143,3 +147,38 @@ input:focus {
|
||||||
box-shadow: 0 0 1px rgba(150,150,150,.4) inset;
|
box-shadow: 0 0 1px rgba(150,150,150,.4) inset;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.game-teaser-wrap {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20%;
|
||||||
|
padding: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-teaser {
|
||||||
|
display: block;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
background-size: contain;
|
||||||
|
position: relative;
|
||||||
|
padding-top: 56.25%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #222222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-info {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-info-text {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
background: var(--bg-color);
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,19 @@ async function createGame(gameId, targetTiles, image) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DATA_DIR = './../data'
|
||||||
|
|
||||||
function loadAllGames() {
|
function loadAllGames() {
|
||||||
const files = fs.readdirSync('./../data/')
|
const files = fs.readdirSync(DATA_DIR)
|
||||||
for (const f of files) {
|
for (const f of files) {
|
||||||
if (!f.match(/\.json$/)) {
|
if (!f.match(/\.json$/)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const gameId = f.replace(/\.json$/, '')
|
const file = `${DATA_DIR}/${f}`
|
||||||
const contents = fs.readFileSync(`./../data/${f}`, 'utf-8')
|
const contents = fs.readFileSync(file, 'utf-8')
|
||||||
const game = JSON.parse(contents)
|
const game = JSON.parse(contents)
|
||||||
GameCommon.newGame({
|
GameCommon.newGame({
|
||||||
id: gameId,
|
id: game.id,
|
||||||
puzzle: game.puzzle,
|
puzzle: game.puzzle,
|
||||||
players: game.players,
|
players: game.players,
|
||||||
sockets: [],
|
sockets: [],
|
||||||
|
|
@ -33,7 +35,7 @@ function loadAllGames() {
|
||||||
|
|
||||||
function persistAll() {
|
function persistAll() {
|
||||||
for (const game of GameCommon.getAllGames()) {
|
for (const game of GameCommon.getAllGames()) {
|
||||||
fs.writeFileSync('./../data/' + game.id + '.json', JSON.stringify({
|
fs.writeFileSync(`${DATA_DIR}/${game.id}.json`, JSON.stringify({
|
||||||
id: game.id,
|
id: game.id,
|
||||||
puzzle: game.puzzle,
|
puzzle: game.puzzle,
|
||||||
players: game.players,
|
players: game.players,
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,8 @@ async function createPuzzle(targetTiles, image) {
|
||||||
// TODO: maybe calculate this each time?
|
// TODO: maybe calculate this each time?
|
||||||
maxZ: 0, // max z of all pieces
|
maxZ: 0, // max z of all pieces
|
||||||
maxGroup: 0, // max group of all pieces
|
maxGroup: 0, // max group of all pieces
|
||||||
|
started: Util.timestamp(), // start timestamp
|
||||||
|
finished: 0, // finish timestamp
|
||||||
},
|
},
|
||||||
// static puzzle information. stays same for complete duration of
|
// static puzzle information. stays same for complete duration of
|
||||||
// the game
|
// the game
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,8 @@ app.use('/', async (req, res, next) => {
|
||||||
const games = [
|
const games = [
|
||||||
...Game.getAllGames().map(game => ({
|
...Game.getAllGames().map(game => ({
|
||||||
id: game.id,
|
id: game.id,
|
||||||
|
started: game.puzzle.data.started,
|
||||||
|
finished: game.puzzle.data.finished,
|
||||||
tilesFinished: Game.getFinishedTileCount(game.id),
|
tilesFinished: Game.getFinishedTileCount(game.id),
|
||||||
tilesTotal: Game.getTileCount(game.id),
|
tilesTotal: Game.getTileCount(game.id),
|
||||||
players: Game.getActivePlayers(game.id).length,
|
players: Game.getActivePlayers(game.id).length,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue