add replay functionality
This commit is contained in:
parent
4158aa0854
commit
083fc0463c
13 changed files with 452 additions and 125 deletions
|
|
@ -1,8 +1,9 @@
|
|||
import fs from 'fs'
|
||||
import { createPuzzle } from './Puzzle.js'
|
||||
import GameCommon from './../common/GameCommon.js'
|
||||
import Util from './../common/Util.js'
|
||||
import { Rng } from '../common/Rng.js'
|
||||
import GameLog from './GameLog.js'
|
||||
import { createPuzzle } from './Puzzle.js'
|
||||
|
||||
const DATA_DIR = './../data'
|
||||
|
||||
|
|
@ -42,24 +43,44 @@ function loadAllGames() {
|
|||
}
|
||||
|
||||
const changedGames = {}
|
||||
async function createGame(gameId, targetTiles, image) {
|
||||
const rng = new Rng(gameId);
|
||||
async function createGameObject(gameId, targetTiles, image, ts) {
|
||||
const seed = Util.hash(gameId + ' ' + ts)
|
||||
const rng = new Rng(seed)
|
||||
return GameCommon.__createGameObject(
|
||||
gameId,
|
||||
{
|
||||
type: 'Rng',
|
||||
obj: rng,
|
||||
},
|
||||
await createPuzzle(rng, targetTiles, image, ts),
|
||||
{},
|
||||
[],
|
||||
{}
|
||||
)
|
||||
}
|
||||
async function createGame(gameId, targetTiles, image, ts) {
|
||||
GameLog.log(gameId, 'createGame', targetTiles, image, ts)
|
||||
|
||||
const seed = Util.hash(gameId + ' ' + ts)
|
||||
const rng = new Rng(seed)
|
||||
GameCommon.newGame({
|
||||
id: gameId,
|
||||
rng: {
|
||||
type: 'Rng',
|
||||
obj: rng,
|
||||
},
|
||||
puzzle: await createPuzzle(rng, targetTiles, image),
|
||||
puzzle: await createPuzzle(rng, targetTiles, image, ts),
|
||||
players: {},
|
||||
sockets: [],
|
||||
evtInfos: {},
|
||||
})
|
||||
|
||||
changedGames[gameId] = true
|
||||
}
|
||||
|
||||
function addPlayer(gameId, playerId) {
|
||||
GameCommon.addPlayer(gameId, playerId)
|
||||
function addPlayer(gameId, playerId, ts) {
|
||||
GameLog.log(gameId, 'addPlayer', playerId, ts)
|
||||
GameCommon.addPlayer(gameId, playerId, ts)
|
||||
changedGames[gameId] = true
|
||||
}
|
||||
|
||||
|
|
@ -68,8 +89,10 @@ function addSocket(gameId, socket) {
|
|||
changedGames[gameId] = true
|
||||
}
|
||||
|
||||
function handleInput(gameId, playerId, input) {
|
||||
const ret = GameCommon.handleInput(gameId, playerId, input)
|
||||
function handleInput(gameId, playerId, input, ts) {
|
||||
GameLog.log(gameId, 'handleInput', playerId, input, ts)
|
||||
|
||||
const ret = GameCommon.handleInput(gameId, playerId, input, ts)
|
||||
changedGames[gameId] = true
|
||||
return ret
|
||||
}
|
||||
|
|
@ -93,6 +116,7 @@ function persistChangedGames() {
|
|||
}
|
||||
|
||||
export default {
|
||||
createGameObject,
|
||||
loadAllGames,
|
||||
persistChangedGames,
|
||||
createGame,
|
||||
|
|
|
|||
25
server/GameLog.js
Normal file
25
server/GameLog.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import fs from 'fs'
|
||||
|
||||
const DATA_DIR = './../data'
|
||||
|
||||
const log = (gameId, ...args) => {
|
||||
const str = JSON.stringify(args)
|
||||
fs.appendFileSync(`${DATA_DIR}/log_${gameId}.log`, str + "\n")
|
||||
}
|
||||
|
||||
const get = (gameId) => {
|
||||
const all = fs.readFileSync(`${DATA_DIR}/log_${gameId}.log`, 'utf-8')
|
||||
return all.split("\n").filter(line => !!line).map((line) => {
|
||||
try {
|
||||
return JSON.parse(line)
|
||||
} catch (e) {
|
||||
console.log(line)
|
||||
console.log(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
log,
|
||||
get,
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import sizeOf from 'image-size'
|
||||
import Util from './../common/Util.js'
|
||||
import Util from '../common/Util.js'
|
||||
import exif from 'exif'
|
||||
import { Rng } from '../common/Rng.js'
|
||||
|
||||
|
|
@ -38,7 +38,8 @@ async function getExifOrientation(imagePath) {
|
|||
async function createPuzzle(
|
||||
/** @type Rng */ rng,
|
||||
targetTiles,
|
||||
image
|
||||
image,
|
||||
ts
|
||||
) {
|
||||
const imagePath = image.file
|
||||
const imageUrl = image.url
|
||||
|
|
@ -135,7 +136,7 @@ async function createPuzzle(
|
|||
// 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
|
||||
started: ts, // start timestamp
|
||||
finished: 0, // finish timestamp
|
||||
},
|
||||
// static puzzle information. stays same for complete duration of
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import twing from 'twing'
|
|||
import bodyParser from 'body-parser'
|
||||
import v8 from 'v8'
|
||||
import { Rng } from '../common/Rng.js'
|
||||
import GameLog from './GameLog.js'
|
||||
|
||||
const allImages = () => [
|
||||
...fs.readdirSync('./../data/uploads/').map(f => ({
|
||||
|
|
@ -50,6 +51,14 @@ app.use('/g/:gid', async (req, res, next) => {
|
|||
WS_ADDRESS: config.ws.connectstring,
|
||||
}))
|
||||
})
|
||||
|
||||
app.use('/replay/:gid', async (req, res, next) => {
|
||||
res.send(await render('replay.html.twig', {
|
||||
GAME_ID: req.params.gid,
|
||||
WS_ADDRESS: config.ws.connectstring,
|
||||
}))
|
||||
})
|
||||
|
||||
app.post('/upload', (req, res) => {
|
||||
upload(req, res, (err) => {
|
||||
if (err) {
|
||||
|
|
@ -68,7 +77,8 @@ app.post('/newgame', bodyParser.json(), async (req, res) => {
|
|||
console.log(req.body.tiles, req.body.image)
|
||||
const gameId = Util.uniqId()
|
||||
if (!Game.exists(gameId)) {
|
||||
await Game.createGame(gameId, req.body.tiles, req.body.image)
|
||||
const ts = Util.timestamp()
|
||||
await Game.createGame(gameId, req.body.tiles, req.body.image, ts)
|
||||
}
|
||||
res.send({ url: `/g/${gameId}` })
|
||||
})
|
||||
|
|
@ -77,6 +87,7 @@ app.use('/common/', express.static('./../common/'))
|
|||
app.use('/uploads/', express.static('./../data/uploads/'))
|
||||
app.use('/', async (req, res, next) => {
|
||||
if (req.path === '/') {
|
||||
const ts = Util.timestamp()
|
||||
const games = [
|
||||
...Game.getAllGames().map(game => ({
|
||||
id: game.id,
|
||||
|
|
@ -84,7 +95,7 @@ app.use('/', async (req, res, next) => {
|
|||
finished: Game.getFinishTs(game.id),
|
||||
tilesFinished: Game.getFinishedTileCount(game.id),
|
||||
tilesTotal: Game.getTileCount(game.id),
|
||||
players: Game.getActivePlayers(game.id).length,
|
||||
players: Game.getActivePlayers(game.id, ts).length,
|
||||
imageUrl: Game.getImageUrl(game.id),
|
||||
})),
|
||||
]
|
||||
|
|
@ -124,11 +135,36 @@ wss.on('message', async ({socket, data}) => {
|
|||
const msg = JSON.parse(data)
|
||||
const msgType = msg[0]
|
||||
switch (msgType) {
|
||||
case Protocol.EV_CLIENT_INIT_REPLAY: {
|
||||
const log = GameLog.get(gameId)
|
||||
let game = await Game.createGameObject(
|
||||
gameId,
|
||||
log[0][1],
|
||||
log[0][2],
|
||||
log[0][3]
|
||||
)
|
||||
notify(
|
||||
[Protocol.EV_SERVER_INIT_REPLAY, {
|
||||
id: game.id,
|
||||
rng: {
|
||||
type: game.rng.type,
|
||||
obj: Rng.serialize(game.rng.obj),
|
||||
},
|
||||
puzzle: game.puzzle,
|
||||
players: game.players,
|
||||
sockets: [],
|
||||
evtInfos: game.evtInfos,
|
||||
}, log],
|
||||
[socket]
|
||||
)
|
||||
} break;
|
||||
|
||||
case Protocol.EV_CLIENT_INIT: {
|
||||
if (!Game.exists(gameId)) {
|
||||
throw `[game ${gameId} does not exist... ]`
|
||||
}
|
||||
Game.addPlayer(gameId, clientId)
|
||||
const ts = Util.timestamp()
|
||||
Game.addPlayer(gameId, clientId, ts)
|
||||
Game.addSocket(gameId, socket)
|
||||
const game = Game.get(gameId)
|
||||
notify(
|
||||
|
|
@ -150,7 +186,9 @@ wss.on('message', async ({socket, data}) => {
|
|||
case Protocol.EV_CLIENT_EVENT: {
|
||||
const clientSeq = msg[1]
|
||||
const clientEvtData = msg[2]
|
||||
Game.addPlayer(gameId, clientId)
|
||||
const ts = Util.timestamp()
|
||||
|
||||
Game.addPlayer(gameId, clientId, ts)
|
||||
Game.addSocket(gameId, socket)
|
||||
|
||||
const game = Game.get(gameId)
|
||||
|
|
@ -164,7 +202,7 @@ wss.on('message', async ({socket, data}) => {
|
|||
}],
|
||||
[socket]
|
||||
)
|
||||
const changes = Game.handleInput(gameId, clientId, clientEvtData)
|
||||
const changes = Game.handleInput(gameId, clientId, clientEvtData, ts)
|
||||
notify(
|
||||
[Protocol.EV_SERVER_EVENT, clientId, clientSeq, changes],
|
||||
Game.getSockets(gameId)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue