diff --git a/common/GameCommon.js b/common/GameCommon.js
index 7b055a3..109f170 100644
--- a/common/GameCommon.js
+++ b/common/GameCommon.js
@@ -98,7 +98,14 @@ function removeSocket(gameId, socket) {
}
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) {
@@ -119,6 +126,10 @@ function getImageUrl(gameId) {
return GAMES[gameId].puzzle.info.imageUrl
}
+function isFinished(gameId) {
+ return getFinishedTileCount(gameId) === getTileCount(gameId)
+}
+
function getFinishedTileCount(gameId) {
let count = 0
for (let t of GAMES[gameId].puzzle.tiles) {
@@ -527,6 +538,11 @@ function handleInput(gameId, playerId, input) {
finishTiles(gameId, tileIdxs)
changePlayer(gameId, playerId, { points: getPlayerPoints(gameId, playerId) + tileIdxs.length })
_tileChanges(tileIdxs)
+ // check if the puzzle is finished
+ if (getFinishedTileCount(gameId) === getTileCount(gameId)) {
+ changeData(gameId, { finished: Util.timestamp() })
+ _dataChange()
+ }
} else {
// Snap to other tiles
const check = (gameId, tileIdx, otherTileIdx, off) => {
diff --git a/game/game.js b/game/game.js
index 9a05d2c..ca851b4 100644
--- a/game/game.js
+++ b/game/game.js
@@ -76,10 +76,20 @@ function addMenuToDom(previewImageUrl) {
)
const settingsEl = document.createElement('table')
- settingsEl.classList.add('layer', 'settings', 'closed')
+ settingsEl.classList.add('settings')
settingsEl.appendChild(bgColorPickerRow)
settingsEl.appendChild(playerColorPickerRow)
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')
previewEl.classList.add('preview')
@@ -100,7 +110,7 @@ function addMenuToDom(previewImageUrl) {
settingsOpenerEl.classList.add('opener')
settingsOpenerEl.appendChild(document.createTextNode('🛠️ Settings'))
settingsOpenerEl.addEventListener('click', () => {
- settingsEl.classList.toggle('closed')
+ settingsOverlay.classList.toggle('closed')
})
const homeEl = document.createElement('a')
@@ -153,7 +163,7 @@ function addMenuToDom(previewImageUrl) {
scoresEl.appendChild(scoresTitleEl)
scoresEl.appendChild(scoresListEl)
- document.body.appendChild(settingsEl)
+ document.body.appendChild(settingsOverlay)
document.body.appendChild(previewOverlay)
document.body.appendChild(menuEl)
document.body.appendChild(scoresEl)
@@ -284,7 +294,7 @@ async function main() {
bgColorPickerEl.addEventListener('change', () => {
evts.addEvent(['bg_color', bgColorPickerEl.value])
})
- playerColorPickerEl.value = Game.getPlayerBgColor(gameId, CLIENT_ID)
+ playerColorPickerEl.value = Game.getPlayerColor(gameId, CLIENT_ID)
playerColorPickerEl.addEventListener('change', () => {
evts.addEvent(['player_color', playerColorPickerEl.value])
})
diff --git a/game/index.js b/game/index.js
index ec42324..f9ef63e 100644
--- a/game/index.js
+++ b/game/index.js
@@ -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 {
components: {
Upload,
@@ -37,12 +51,16 @@ export default {
template: `
Running games
-
-
-
-
- {{g.tilesFinished}}/{{g.tilesTotal}} pieces, {{g.players}} players
-
+
New game
@@ -81,6 +99,32 @@ export default {
}
},
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) {
this.image = j.image
},
diff --git a/game/style.css b/game/style.css
index 3a72a81..5cab9d8 100644
--- a/game/style.css
+++ b/game/style.css
@@ -44,7 +44,21 @@ a:hover { color: var(--link-hover-color); }
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;
left: 50%;
top: 50%;
@@ -56,16 +70,6 @@ a:hover { color: var(--link-hover-color); }
z-index: 1;
}
-.overlay {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 10;
- background: var(--bg-color);
-}
-
.preview {
position: absolute;
top: 20px;
@@ -143,3 +147,38 @@ input:focus {
box-shadow: 0 0 1px rgba(150,150,150,.4) inset;
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;
+}
diff --git a/server/Game.js b/server/Game.js
index 1368036..6e0ea21 100644
--- a/server/Game.js
+++ b/server/Game.js
@@ -12,17 +12,19 @@ async function createGame(gameId, targetTiles, image) {
})
}
+const DATA_DIR = './../data'
+
function loadAllGames() {
- const files = fs.readdirSync('./../data/')
+ const files = fs.readdirSync(DATA_DIR)
for (const f of files) {
if (!f.match(/\.json$/)) {
continue
}
- const gameId = f.replace(/\.json$/, '')
- const contents = fs.readFileSync(`./../data/${f}`, 'utf-8')
+ const file = `${DATA_DIR}/${f}`
+ const contents = fs.readFileSync(file, 'utf-8')
const game = JSON.parse(contents)
GameCommon.newGame({
- id: gameId,
+ id: game.id,
puzzle: game.puzzle,
players: game.players,
sockets: [],
@@ -33,7 +35,7 @@ function loadAllGames() {
function persistAll() {
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,
puzzle: game.puzzle,
players: game.players,
diff --git a/server/Puzzle.js b/server/Puzzle.js
index c671069..ad89014 100644
--- a/server/Puzzle.js
+++ b/server/Puzzle.js
@@ -101,6 +101,8 @@ async function createPuzzle(targetTiles, image) {
// TODO: maybe calculate this each time?
maxZ: 0, // max z 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
// the game
diff --git a/server/index.js b/server/index.js
index 73ba731..c93f6ac 100644
--- a/server/index.js
+++ b/server/index.js
@@ -78,6 +78,8 @@ app.use('/', async (req, res, next) => {
const games = [
...Game.getAllGames().map(game => ({
id: game.id,
+ started: game.puzzle.data.started,
+ finished: game.puzzle.data.finished,
tilesFinished: Game.getFinishedTileCount(game.id),
tilesTotal: Game.getTileCount(game.id),
players: Game.getActivePlayers(game.id).length,