add replay functionality

This commit is contained in:
Zutatensuppe 2020-12-22 22:35:09 +01:00
parent 4158aa0854
commit 083fc0463c
13 changed files with 452 additions and 125 deletions

View file

@ -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
View 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,
}

View file

@ -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

View file

@ -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)