add replay functionality
This commit is contained in:
parent
4158aa0854
commit
083fc0463c
13 changed files with 452 additions and 125 deletions
|
|
@ -7,7 +7,7 @@ function exists(gameId) {
|
||||||
return (!!GAMES[gameId]) || false
|
return (!!GAMES[gameId]) || false
|
||||||
}
|
}
|
||||||
|
|
||||||
function createGame(id, rng, puzzle, players, sockets, evtInfos) {
|
function __createGameObject(id, rng, puzzle, players, sockets, evtInfos) {
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
rng: rng,
|
rng: rng,
|
||||||
|
|
@ -18,7 +18,7 @@ function createGame(id, rng, puzzle, players, sockets, evtInfos) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPlayer(id, ts) {
|
function __createPlayerObject(id, ts) {
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
x: 0,
|
x: 0,
|
||||||
|
|
@ -33,7 +33,7 @@ function createPlayer(id, ts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function newGame({id, rng, puzzle, players, sockets, evtInfos}) {
|
function newGame({id, rng, puzzle, players, sockets, evtInfos}) {
|
||||||
const game = createGame(id, rng, puzzle, players, sockets, evtInfos)
|
const game = __createGameObject(id, rng, puzzle, players, sockets, evtInfos)
|
||||||
setGame(id, game)
|
setGame(id, game)
|
||||||
return game
|
return game
|
||||||
}
|
}
|
||||||
|
|
@ -62,26 +62,23 @@ function playerExists(gameId, playerId) {
|
||||||
return !!GAMES[gameId].players[playerId]
|
return !!GAMES[gameId].players[playerId]
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRelevantPlayers(gameId) {
|
function getRelevantPlayers(gameId, ts) {
|
||||||
const ts = Util.timestamp()
|
|
||||||
const minTs = ts - 30000
|
const minTs = ts - 30000
|
||||||
return getAllPlayers(gameId).filter(player => {
|
return getAllPlayers(gameId).filter(player => {
|
||||||
return player.ts >= minTs || player.points > 0
|
return player.ts >= minTs || player.points > 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActivePlayers(gameId) {
|
function getActivePlayers(gameId, ts) {
|
||||||
const ts = Util.timestamp()
|
|
||||||
const minTs = ts - 30000
|
const minTs = ts - 30000
|
||||||
return getAllPlayers(gameId).filter(player => {
|
return getAllPlayers(gameId).filter(player => {
|
||||||
return player.ts >= minTs
|
return player.ts >= minTs
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPlayer(gameId, playerId) {
|
function addPlayer(gameId, playerId, ts) {
|
||||||
const ts = Util.timestamp()
|
|
||||||
if (!GAMES[gameId].players[playerId]) {
|
if (!GAMES[gameId].players[playerId]) {
|
||||||
setPlayer(gameId, playerId, createPlayer(playerId, ts))
|
setPlayer(gameId, playerId, __createPlayerObject(playerId, ts))
|
||||||
} else {
|
} else {
|
||||||
changePlayer(gameId, playerId, { ts })
|
changePlayer(gameId, playerId, { ts })
|
||||||
}
|
}
|
||||||
|
|
@ -368,19 +365,23 @@ const freeTileIdxByPos = (gameId, pos) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPlayerBgColor = (gameId, playerId) => {
|
const getPlayerBgColor = (gameId, playerId) => {
|
||||||
return getPlayer(gameId, playerId).bgcolor
|
const p = getPlayer(gameId, playerId)
|
||||||
|
return p ? p.bgcolor : null
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPlayerColor = (gameId, playerId) => {
|
const getPlayerColor = (gameId, playerId) => {
|
||||||
return getPlayer(gameId, playerId).color
|
const p = getPlayer(gameId, playerId)
|
||||||
|
return p ? p.color : null
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPlayerName = (gameId, playerId) => {
|
const getPlayerName = (gameId, playerId) => {
|
||||||
return getPlayer(gameId, playerId).name
|
const p = getPlayer(gameId, playerId)
|
||||||
|
return p ? p.name : null
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPlayerPoints = (gameId, playerId) => {
|
const getPlayerPoints = (gameId, playerId) => {
|
||||||
return getPlayer(gameId, playerId).points
|
const p = getPlayer(gameId, playerId)
|
||||||
|
return p ? p.points : null
|
||||||
}
|
}
|
||||||
|
|
||||||
// determine if two tiles are grouped together
|
// determine if two tiles are grouped together
|
||||||
|
|
@ -398,6 +399,14 @@ const getTableHeight = (gameId) => {
|
||||||
return GAMES[gameId].puzzle.info.table.height
|
return GAMES[gameId].puzzle.info.table.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPuzzle = (gameId) => {
|
||||||
|
return GAMES[gameId].puzzle
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRng = (gameId) => {
|
||||||
|
return GAMES[gameId].rng.obj
|
||||||
|
}
|
||||||
|
|
||||||
const getPuzzleWidth = (gameId) => {
|
const getPuzzleWidth = (gameId) => {
|
||||||
return GAMES[gameId].puzzle.info.width
|
return GAMES[gameId].puzzle.info.width
|
||||||
}
|
}
|
||||||
|
|
@ -406,7 +415,7 @@ const getPuzzleHeight = (gameId) => {
|
||||||
return GAMES[gameId].puzzle.info.height
|
return GAMES[gameId].puzzle.info.height
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInput(gameId, playerId, input) {
|
function handleInput(gameId, playerId, input, ts) {
|
||||||
const puzzle = GAMES[gameId].puzzle
|
const puzzle = GAMES[gameId].puzzle
|
||||||
let evtInfo = GAMES[gameId].evtInfos[playerId]
|
let evtInfo = GAMES[gameId].evtInfos[playerId]
|
||||||
|
|
||||||
|
|
@ -472,8 +481,6 @@ function handleInput(gameId, playerId, input) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ts = Util.timestamp()
|
|
||||||
|
|
||||||
const type = input[0]
|
const type = input[0]
|
||||||
if (type === 'bg_color') {
|
if (type === 'bg_color') {
|
||||||
const bgcolor = input[1]
|
const bgcolor = input[1]
|
||||||
|
|
@ -559,7 +566,7 @@ function handleInput(gameId, playerId, input) {
|
||||||
_tileChanges(tileIdxs)
|
_tileChanges(tileIdxs)
|
||||||
// check if the puzzle is finished
|
// check if the puzzle is finished
|
||||||
if (getFinishedTileCount(gameId) === getTileCount(gameId)) {
|
if (getFinishedTileCount(gameId) === getTileCount(gameId)) {
|
||||||
changeData(gameId, { finished: Util.timestamp() })
|
changeData(gameId, { finished: ts })
|
||||||
_dataChange()
|
_dataChange()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -614,6 +621,8 @@ function handleInput(gameId, playerId, input) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
__createGameObject,
|
||||||
|
__createPlayerObject,
|
||||||
newGame,
|
newGame,
|
||||||
exists,
|
exists,
|
||||||
playerExists,
|
playerExists,
|
||||||
|
|
@ -638,6 +647,8 @@ export default {
|
||||||
setPuzzleData,
|
setPuzzleData,
|
||||||
getTableWidth,
|
getTableWidth,
|
||||||
getTableHeight,
|
getTableHeight,
|
||||||
|
getPuzzle,
|
||||||
|
getRng,
|
||||||
getPuzzleWidth,
|
getPuzzleWidth,
|
||||||
getPuzzleHeight,
|
getPuzzleHeight,
|
||||||
getTilesSortedByZIndex,
|
getTilesSortedByZIndex,
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,16 @@ EV_SERVER_INIT: event sent to one client after that client
|
||||||
*/
|
*/
|
||||||
const EV_SERVER_EVENT = 1
|
const EV_SERVER_EVENT = 1
|
||||||
const EV_SERVER_INIT = 4
|
const EV_SERVER_INIT = 4
|
||||||
|
const EV_SERVER_INIT_REPLAY = 5
|
||||||
const EV_CLIENT_EVENT = 2
|
const EV_CLIENT_EVENT = 2
|
||||||
const EV_CLIENT_INIT = 3
|
const EV_CLIENT_INIT = 3
|
||||||
|
const EV_CLIENT_INIT_REPLAY = 6
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
EV_SERVER_EVENT,
|
EV_SERVER_EVENT,
|
||||||
EV_SERVER_INIT,
|
EV_SERVER_INIT,
|
||||||
|
EV_SERVER_INIT_REPLAY,
|
||||||
EV_CLIENT_EVENT,
|
EV_CLIENT_EVENT,
|
||||||
EV_CLIENT_INIT,
|
EV_CLIENT_INIT,
|
||||||
|
EV_CLIENT_INIT_REPLAY,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,19 @@ function coordByTileIdx(info, tileIdx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hash = (str) => {
|
||||||
|
let hash = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
let chr = str.charCodeAt(i);
|
||||||
|
hash = ((hash << 5) - hash) + chr;
|
||||||
|
hash |= 0; // Convert to 32bit integer
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
hash,
|
||||||
uniqId,
|
uniqId,
|
||||||
randomInt,
|
randomInt,
|
||||||
choice,
|
choice,
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,25 @@ function connect(gameId, clientId) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function connectReplay(gameId, clientId) {
|
||||||
|
clientSeq = 0
|
||||||
|
events = {}
|
||||||
|
conn = new WsClient(WS_ADDRESS, clientId + '|' + gameId)
|
||||||
|
return new Promise(r => {
|
||||||
|
conn.connect()
|
||||||
|
send([Protocol.EV_CLIENT_INIT_REPLAY])
|
||||||
|
conn.onSocket('message', async ({ data }) => {
|
||||||
|
const msg = JSON.parse(data)
|
||||||
|
const msgType = msg[0]
|
||||||
|
if (msgType === Protocol.EV_SERVER_INIT_REPLAY) {
|
||||||
|
const game = msg[1]
|
||||||
|
const log = msg[2]
|
||||||
|
r({game, log})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function sendClientEvent(mouse) {
|
function sendClientEvent(mouse) {
|
||||||
// when sending event, increase number of sent events
|
// when sending event, increase number of sent events
|
||||||
// and add the event locally
|
// and add the event locally
|
||||||
|
|
@ -52,6 +71,7 @@ function sendClientEvent(mouse) {
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
connect,
|
connect,
|
||||||
|
connectReplay,
|
||||||
onServerChange,
|
onServerChange,
|
||||||
sendClientEvent,
|
sendClientEvent,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ export default {
|
||||||
newGame: GameCommon.newGame,
|
newGame: GameCommon.newGame,
|
||||||
getRelevantPlayers: GameCommon.getRelevantPlayers,
|
getRelevantPlayers: GameCommon.getRelevantPlayers,
|
||||||
getActivePlayers: GameCommon.getActivePlayers,
|
getActivePlayers: GameCommon.getActivePlayers,
|
||||||
|
addPlayer: GameCommon.addPlayer,
|
||||||
handleInput: GameCommon.handleInput,
|
handleInput: GameCommon.handleInput,
|
||||||
getPlayerBgColor: GameCommon.getPlayerBgColor,
|
getPlayerBgColor: GameCommon.getPlayerBgColor,
|
||||||
getPlayerColor: GameCommon.getPlayerColor,
|
getPlayerColor: GameCommon.getPlayerColor,
|
||||||
|
|
@ -15,6 +16,8 @@ export default {
|
||||||
setPuzzleData: GameCommon.setPuzzleData,
|
setPuzzleData: GameCommon.setPuzzleData,
|
||||||
getTableWidth: GameCommon.getTableWidth,
|
getTableWidth: GameCommon.getTableWidth,
|
||||||
getTableHeight: GameCommon.getTableHeight,
|
getTableHeight: GameCommon.getTableHeight,
|
||||||
|
getPuzzle: GameCommon.getPuzzle,
|
||||||
|
getRng: GameCommon.getRng,
|
||||||
getPuzzleWidth: GameCommon.getPuzzleWidth,
|
getPuzzleWidth: GameCommon.getPuzzleWidth,
|
||||||
getPuzzleHeight: GameCommon.getPuzzleHeight,
|
getPuzzleHeight: GameCommon.getPuzzleHeight,
|
||||||
getTilesSortedByZIndex: GameCommon.getTilesSortedByZIndex,
|
getTilesSortedByZIndex: GameCommon.getTilesSortedByZIndex,
|
||||||
|
|
|
||||||
183
game/game.js
183
game/game.js
|
|
@ -12,11 +12,14 @@ import { Rng } from '../common/Rng.js'
|
||||||
|
|
||||||
if (typeof GAME_ID === 'undefined') throw '[ GAME_ID not set ]'
|
if (typeof GAME_ID === 'undefined') throw '[ GAME_ID not set ]'
|
||||||
if (typeof WS_ADDRESS === 'undefined') throw '[ WS_ADDRESS not set ]'
|
if (typeof WS_ADDRESS === 'undefined') throw '[ WS_ADDRESS not set ]'
|
||||||
|
if (typeof MODE === 'undefined') throw '[ MODE not set ]'
|
||||||
|
|
||||||
if (typeof DEBUG === 'undefined') window.DEBUG = false
|
if (typeof DEBUG === 'undefined') window.DEBUG = false
|
||||||
|
|
||||||
let RERENDER = true
|
let RERENDER = true
|
||||||
|
|
||||||
|
let TIME = () => Util.timestamp()
|
||||||
|
|
||||||
function addCanvasToDom(canvas) {
|
function addCanvasToDom(canvas) {
|
||||||
canvas.width = window.innerWidth
|
canvas.width = window.innerWidth
|
||||||
canvas.height = window.innerHeight
|
canvas.height = window.innerHeight
|
||||||
|
|
@ -41,6 +44,13 @@ function addMenuToDom(gameId) {
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function btn(txt) {
|
||||||
|
const btn = document.createElement('button')
|
||||||
|
btn.classList.add('btn')
|
||||||
|
btn.innerText = txt
|
||||||
|
return btn
|
||||||
|
}
|
||||||
|
|
||||||
function colorinput() {
|
function colorinput() {
|
||||||
const input = document.createElement('input')
|
const input = document.createElement('input')
|
||||||
input.type = 'color'
|
input.type = 'color'
|
||||||
|
|
@ -143,10 +153,10 @@ function addMenuToDom(gameId) {
|
||||||
|
|
||||||
const scoresListEl = document.createElement('table')
|
const scoresListEl = document.createElement('table')
|
||||||
const updateScores = () => {
|
const updateScores = () => {
|
||||||
const ts = Util.timestamp()
|
const ts = TIME()
|
||||||
const minTs = ts - 30000
|
const minTs = ts - 30000
|
||||||
|
|
||||||
const players = Game.getRelevantPlayers(gameId)
|
const players = Game.getRelevantPlayers(gameId, ts)
|
||||||
const actives = players.filter(player => player.ts >= minTs)
|
const actives = players.filter(player => player.ts >= minTs)
|
||||||
const nonActives = players.filter(player => player.ts < minTs)
|
const nonActives = players.filter(player => player.ts < minTs)
|
||||||
|
|
||||||
|
|
@ -181,7 +191,7 @@ function addMenuToDom(gameId) {
|
||||||
const icon = ended ? '🏁' : '⏳'
|
const icon = ended ? '🏁' : '⏳'
|
||||||
|
|
||||||
const from = started;
|
const from = started;
|
||||||
const to = ended || Util.timestamp()
|
const to = ended || TIME()
|
||||||
|
|
||||||
const MS = 1
|
const MS = 1
|
||||||
const SEC = MS * 1000
|
const SEC = MS * 1000
|
||||||
|
|
@ -208,12 +218,27 @@ function addMenuToDom(gameId) {
|
||||||
timerCountdownEl.innerText = timerStr()
|
timerCountdownEl.innerText = timerStr()
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
timerCountdownEl.innerText = timerStr()
|
timerCountdownEl.innerText = timerStr()
|
||||||
}, 1000)
|
}, 50) // needs to be small, so that it updates quick enough in replay
|
||||||
|
|
||||||
const timerEl = document.createElement('div')
|
const timerEl = document.createElement('div')
|
||||||
timerEl.classList.add('timer')
|
timerEl.classList.add('timer')
|
||||||
timerEl.appendChild(timerCountdownEl)
|
timerEl.appendChild(timerCountdownEl)
|
||||||
|
|
||||||
|
let replayControl = null
|
||||||
|
if (MODE === 'replay') {
|
||||||
|
const replayControlEl = document.createElement('div')
|
||||||
|
const speedUp = btn('⏫')
|
||||||
|
const speedDown = btn('⏬')
|
||||||
|
const pause = btn('⏸️')
|
||||||
|
const speed = document.createElement('div')
|
||||||
|
replayControlEl.appendChild(speed)
|
||||||
|
replayControlEl.appendChild(speedUp)
|
||||||
|
replayControlEl.appendChild(speedDown)
|
||||||
|
replayControlEl.appendChild(pause)
|
||||||
|
timerEl.appendChild(replayControlEl)
|
||||||
|
replayControl = { speedUp, speedDown, pause, speed }
|
||||||
|
}
|
||||||
|
|
||||||
const scoresEl = document.createElement('div')
|
const scoresEl = document.createElement('div')
|
||||||
scoresEl.classList.add('scores')
|
scoresEl.classList.add('scores')
|
||||||
scoresEl.appendChild(scoresTitleEl)
|
scoresEl.appendChild(scoresTitleEl)
|
||||||
|
|
@ -230,6 +255,7 @@ function addMenuToDom(gameId) {
|
||||||
playerColorPickerEl,
|
playerColorPickerEl,
|
||||||
nameChangeEl,
|
nameChangeEl,
|
||||||
updateScores,
|
updateScores,
|
||||||
|
replayControl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -324,23 +350,46 @@ async function main() {
|
||||||
return cursors[key]
|
return cursors[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a canvas and attach adapters to it so we can work with it
|
||||||
|
const canvas = addCanvasToDom(Graphics.createCanvas())
|
||||||
|
|
||||||
|
|
||||||
|
// stuff only available in replay mode...
|
||||||
|
// TODO: refactor
|
||||||
|
let GAME_LOG
|
||||||
|
let GAME_LOG_IDX = 0
|
||||||
|
let REPLAY_SPEEDS = [0.5, 1, 2, 5, 10, 20, 50]
|
||||||
|
let REPLAY_SPEED_IDX = 1
|
||||||
|
let REPLAY_PAUSED = false
|
||||||
|
let lastRealTime = null
|
||||||
|
let lastGameTime = null
|
||||||
|
|
||||||
|
if (MODE === 'play') {
|
||||||
const game = await Communication.connect(gameId, CLIENT_ID)
|
const game = await Communication.connect(gameId, CLIENT_ID)
|
||||||
game.rng.obj = Rng.unserialize(game.rng.obj)
|
game.rng.obj = Rng.unserialize(game.rng.obj)
|
||||||
Game.newGame(game)
|
Game.newGame(game)
|
||||||
|
} else if (MODE === 'replay') {
|
||||||
|
const {game, log} = await Communication.connectReplay(gameId, CLIENT_ID)
|
||||||
|
game.rng.obj = Rng.unserialize(game.rng.obj)
|
||||||
|
Game.newGame(game)
|
||||||
|
GAME_LOG = log
|
||||||
|
lastRealTime = Util.timestamp()
|
||||||
|
lastGameTime = GAME_LOG[0][GAME_LOG[0].length - 1]
|
||||||
|
TIME = () => lastGameTime
|
||||||
|
} else {
|
||||||
|
throw '[ 2020-12-22 MODE invalid, must be play|replay ]'
|
||||||
|
}
|
||||||
|
|
||||||
const bitmaps = await PuzzleGraphics.loadPuzzleBitmaps(game.puzzle)
|
const bitmaps = await PuzzleGraphics.loadPuzzleBitmaps(Game.getPuzzle(gameId))
|
||||||
|
|
||||||
const {bgColorPickerEl, playerColorPickerEl, nameChangeEl, updateScores} = addMenuToDom(gameId)
|
const {bgColorPickerEl, playerColorPickerEl, nameChangeEl, updateScores, replayControl} = addMenuToDom(gameId)
|
||||||
updateScores()
|
updateScores()
|
||||||
|
|
||||||
// Create a dom and attach adapters to it so we can work with it
|
|
||||||
const canvas = addCanvasToDom(Graphics.createCanvas())
|
|
||||||
|
|
||||||
const longFinished = Game.getFinishTs(gameId)
|
const longFinished = Game.getFinishTs(gameId)
|
||||||
let finished = longFinished ? true : false
|
let finished = longFinished ? true : false
|
||||||
const justFinished = () => !!(finished && !longFinished)
|
const justFinished = () => !!(finished && !longFinished)
|
||||||
|
|
||||||
const fireworks = new fireworksController(canvas, game.rng.obj)
|
const fireworks = new fireworksController(canvas, Game.getRng(gameId))
|
||||||
fireworks.init(canvas)
|
fireworks.init(canvas)
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
|
|
@ -371,6 +420,7 @@ async function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const evts = new EventAdapter(canvas, viewport)
|
const evts = new EventAdapter(canvas, viewport)
|
||||||
|
if (MODE === 'play') {
|
||||||
bgColorPickerEl.value = playerBgColor()
|
bgColorPickerEl.value = playerBgColor()
|
||||||
evts.addEvent(['bg_color', bgColorPickerEl.value])
|
evts.addEvent(['bg_color', bgColorPickerEl.value])
|
||||||
bgColorPickerEl.addEventListener('change', () => {
|
bgColorPickerEl.addEventListener('change', () => {
|
||||||
|
|
@ -389,7 +439,30 @@ async function main() {
|
||||||
localStorage.setItem('player_name', nameChangeEl.value)
|
localStorage.setItem('player_name', nameChangeEl.value)
|
||||||
evts.addEvent(['player_name', nameChangeEl.value])
|
evts.addEvent(['player_name', nameChangeEl.value])
|
||||||
})
|
})
|
||||||
|
} else if (MODE === 'replay') {
|
||||||
|
let setSpeedStatus = () => {
|
||||||
|
replayControl.speed.innerText = 'Replay-Speed: ' + (REPLAY_SPEEDS[REPLAY_SPEED_IDX] + 'x') + (REPLAY_PAUSED ? ' Paused' : '')
|
||||||
|
}
|
||||||
|
setSpeedStatus()
|
||||||
|
replayControl.speedUp.addEventListener('click', () => {
|
||||||
|
if (REPLAY_SPEED_IDX + 1 < REPLAY_SPEEDS.length) {
|
||||||
|
REPLAY_SPEED_IDX++
|
||||||
|
setSpeedStatus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
replayControl.speedDown.addEventListener('click', () => {
|
||||||
|
if (REPLAY_SPEED_IDX >= 1) {
|
||||||
|
REPLAY_SPEED_IDX--
|
||||||
|
setSpeedStatus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
replayControl.pause.addEventListener('click', () => {
|
||||||
|
REPLAY_PAUSED = !REPLAY_PAUSED
|
||||||
|
setSpeedStatus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MODE === 'play') {
|
||||||
Communication.onServerChange((msg) => {
|
Communication.onServerChange((msg) => {
|
||||||
const msgType = msg[0]
|
const msgType = msg[0]
|
||||||
const evClientId = msg[1]
|
const evClientId = msg[1]
|
||||||
|
|
@ -417,11 +490,53 @@ async function main() {
|
||||||
}
|
}
|
||||||
finished = Game.getFinishTs(gameId)
|
finished = Game.getFinishTs(gameId)
|
||||||
})
|
})
|
||||||
|
} else if (MODE === 'replay') {
|
||||||
|
// no external communication for replay mode,
|
||||||
|
// only the GAME_LOG is relevant
|
||||||
|
let inter = setInterval(() => {
|
||||||
|
let realTime = Util.timestamp()
|
||||||
|
if (REPLAY_PAUSED) {
|
||||||
|
lastRealTime = realTime
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let timePassedReal = realTime - lastRealTime
|
||||||
|
|
||||||
|
let timePassedGame = timePassedReal * REPLAY_SPEEDS[REPLAY_SPEED_IDX]
|
||||||
|
let maxGameTs = lastGameTime + timePassedGame
|
||||||
|
do {
|
||||||
|
if (REPLAY_PAUSED) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let nextIdx = GAME_LOG_IDX + 1
|
||||||
|
if (nextIdx >= GAME_LOG.length) {
|
||||||
|
clearInterval(inter)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let logEntry = GAME_LOG[nextIdx]
|
||||||
|
let nextTs = logEntry[logEntry.length - 1]
|
||||||
|
if (nextTs > maxGameTs) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logEntry[0] === 'addPlayer') {
|
||||||
|
Game.addPlayer(gameId, ...logEntry.slice(1))
|
||||||
|
RERENDER = true
|
||||||
|
} else if (logEntry[0] === 'handleInput') {
|
||||||
|
Game.handleInput(gameId, ...logEntry.slice(1))
|
||||||
|
RERENDER = true
|
||||||
|
}
|
||||||
|
GAME_LOG_IDX = nextIdx
|
||||||
|
} while (true)
|
||||||
|
lastRealTime = realTime
|
||||||
|
lastGameTime = maxGameTs
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
|
||||||
let _last_mouse_down = null
|
let _last_mouse_down = null
|
||||||
const onUpdate = () => {
|
const onUpdate = () => {
|
||||||
for (let evt of evts.consumeAll()) {
|
for (let evt of evts.consumeAll()) {
|
||||||
|
if (MODE === 'play') {
|
||||||
// LOCAL ONLY CHANGES
|
// LOCAL ONLY CHANGES
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
const type = evt[0]
|
const type = evt[0]
|
||||||
|
|
@ -432,6 +547,7 @@ async function main() {
|
||||||
const mouse = viewport.worldToViewport(pos)
|
const mouse = viewport.worldToViewport(pos)
|
||||||
const diffX = Math.round(mouse.x - _last_mouse_down.x)
|
const diffX = Math.round(mouse.x - _last_mouse_down.x)
|
||||||
const diffY = Math.round(mouse.y - _last_mouse_down.y)
|
const diffY = Math.round(mouse.y - _last_mouse_down.y)
|
||||||
|
RERENDER = true
|
||||||
viewport.move(diffX, diffY)
|
viewport.move(diffX, diffY)
|
||||||
|
|
||||||
_last_mouse_down = mouse
|
_last_mouse_down = mouse
|
||||||
|
|
@ -457,11 +573,41 @@ async function main() {
|
||||||
|
|
||||||
// LOCAL + SERVER CHANGES
|
// LOCAL + SERVER CHANGES
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
const changes = Game.handleInput(GAME_ID, CLIENT_ID, evt)
|
const ts = TIME()
|
||||||
|
const changes = Game.handleInput(GAME_ID, CLIENT_ID, evt, ts)
|
||||||
if (changes.length > 0) {
|
if (changes.length > 0) {
|
||||||
RERENDER = true
|
RERENDER = true
|
||||||
}
|
}
|
||||||
Communication.sendClientEvent(evt)
|
Communication.sendClientEvent(evt)
|
||||||
|
} else if (MODE === 'replay') {
|
||||||
|
// LOCAL ONLY CHANGES
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
const type = evt[0]
|
||||||
|
if (type === 'move') {
|
||||||
|
if (_last_mouse_down) {
|
||||||
|
// move the cam
|
||||||
|
const pos = { x: evt[1], y: evt[2] }
|
||||||
|
const mouse = viewport.worldToViewport(pos)
|
||||||
|
const diffX = Math.round(mouse.x - _last_mouse_down.x)
|
||||||
|
const diffY = Math.round(mouse.y - _last_mouse_down.y)
|
||||||
|
RERENDER = true
|
||||||
|
viewport.move(diffX, diffY)
|
||||||
|
|
||||||
|
_last_mouse_down = mouse
|
||||||
|
}
|
||||||
|
} else if (type === 'down') {
|
||||||
|
const pos = { x: evt[1], y: evt[2] }
|
||||||
|
_last_mouse_down = viewport.worldToViewport(pos)
|
||||||
|
} else if (type === 'up') {
|
||||||
|
_last_mouse_down = null
|
||||||
|
} else if (type === 'zoomin') {
|
||||||
|
viewport.zoomIn()
|
||||||
|
RERENDER = true
|
||||||
|
} else if (type === 'zoomout') {
|
||||||
|
viewport.zoomOut()
|
||||||
|
RERENDER = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finished = Game.getFinishTs(gameId)
|
finished = Game.getFinishTs(gameId)
|
||||||
|
|
@ -528,13 +674,15 @@ async function main() {
|
||||||
|
|
||||||
// DRAW PLAYERS
|
// DRAW PLAYERS
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
for (let player of Game.getActivePlayers(gameId)) {
|
const ts = TIME()
|
||||||
|
for (let player of Game.getActivePlayers(gameId, ts)) {
|
||||||
const cursor = await getPlayerCursor(player)
|
const cursor = await getPlayerCursor(player)
|
||||||
const pos = viewport.worldToViewport(player)
|
const pos = viewport.worldToViewport(player)
|
||||||
ctx.drawImage(cursor,
|
ctx.drawImage(cursor,
|
||||||
Math.round(pos.x - cursor.width/2),
|
Math.round(pos.x - cursor.width/2),
|
||||||
Math.round(pos.y - cursor.height/2)
|
Math.round(pos.y - cursor.height/2)
|
||||||
)
|
)
|
||||||
|
if (MODE === 'play') {
|
||||||
if (player.id !== CLIENT_ID) {
|
if (player.id !== CLIENT_ID) {
|
||||||
ctx.fillStyle = 'white'
|
ctx.fillStyle = 'white'
|
||||||
ctx.font = '10px sans-serif'
|
ctx.font = '10px sans-serif'
|
||||||
|
|
@ -544,6 +692,15 @@ async function main() {
|
||||||
Math.round(pos.y) + cursor.height
|
Math.round(pos.y) + cursor.height
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (MODE === 'replay') {
|
||||||
|
ctx.fillStyle = 'white'
|
||||||
|
ctx.font = '10px sans-serif'
|
||||||
|
ctx.textAlign = 'center'
|
||||||
|
ctx.fillText(player.name + ' (' + player.points + ')',
|
||||||
|
Math.round(pos.x),
|
||||||
|
Math.round(pos.y) + cursor.height
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (DEBUG) Debug.checkpoint('players done')
|
if (DEBUG) Debug.checkpoint('players done')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ a:hover { color: var(--link-hover-color); }
|
||||||
.scores {
|
.scores {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
background: var(--bg-color);
|
background: var(--bg-color);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
@ -32,6 +33,7 @@ a:hover { color: var(--link-hover-color); }
|
||||||
.timer {
|
.timer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
background: var(--bg-color);
|
background: var(--bg-color);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
@ -41,6 +43,8 @@ a:hover { color: var(--link-hover-color); }
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
background: var(--bg-color);
|
background: var(--bg-color);
|
||||||
|
|
@ -192,3 +196,9 @@ input:focus {
|
||||||
background: var(--bg-color);
|
background: var(--bg-color);
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.game-replay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
<body>
|
<body>
|
||||||
<script>window.GAME_ID = '{{GAME_ID}}'</script>
|
<script>window.GAME_ID = '{{GAME_ID}}'</script>
|
||||||
<script>window.WS_ADDRESS = '{{WS_ADDRESS}}'</script>
|
<script>window.WS_ADDRESS = '{{WS_ADDRESS}}'</script>
|
||||||
|
<script>window.MODE = 'play'</script>
|
||||||
<script src="/game.js" type="module"></script>
|
<script src="/game.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
21
game/templates/replay.html.twig
Normal file
21
game/templates/replay.html.twig
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="/style.css" />
|
||||||
|
<style type="text/css">
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>🧩 jigsaw.hyottoko.club</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>window.GAME_ID = '{{GAME_ID}}'</script>
|
||||||
|
<script>window.WS_ADDRESS = '{{WS_ADDRESS}}'</script>
|
||||||
|
<script>window.MODE = 'replay'</script>
|
||||||
|
<script src="/game.js" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { createPuzzle } from './Puzzle.js'
|
|
||||||
import GameCommon from './../common/GameCommon.js'
|
import GameCommon from './../common/GameCommon.js'
|
||||||
import Util from './../common/Util.js'
|
import Util from './../common/Util.js'
|
||||||
import { Rng } from '../common/Rng.js'
|
import { Rng } from '../common/Rng.js'
|
||||||
|
import GameLog from './GameLog.js'
|
||||||
|
import { createPuzzle } from './Puzzle.js'
|
||||||
|
|
||||||
const DATA_DIR = './../data'
|
const DATA_DIR = './../data'
|
||||||
|
|
||||||
|
|
@ -42,24 +43,44 @@ function loadAllGames() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const changedGames = {}
|
const changedGames = {}
|
||||||
async function createGame(gameId, targetTiles, image) {
|
async function createGameObject(gameId, targetTiles, image, ts) {
|
||||||
const rng = new Rng(gameId);
|
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({
|
GameCommon.newGame({
|
||||||
id: gameId,
|
id: gameId,
|
||||||
rng: {
|
rng: {
|
||||||
type: 'Rng',
|
type: 'Rng',
|
||||||
obj: rng,
|
obj: rng,
|
||||||
},
|
},
|
||||||
puzzle: await createPuzzle(rng, targetTiles, image),
|
puzzle: await createPuzzle(rng, targetTiles, image, ts),
|
||||||
players: {},
|
players: {},
|
||||||
sockets: [],
|
sockets: [],
|
||||||
evtInfos: {},
|
evtInfos: {},
|
||||||
})
|
})
|
||||||
|
|
||||||
changedGames[gameId] = true
|
changedGames[gameId] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function addPlayer(gameId, playerId) {
|
function addPlayer(gameId, playerId, ts) {
|
||||||
GameCommon.addPlayer(gameId, playerId)
|
GameLog.log(gameId, 'addPlayer', playerId, ts)
|
||||||
|
GameCommon.addPlayer(gameId, playerId, ts)
|
||||||
changedGames[gameId] = true
|
changedGames[gameId] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,8 +89,10 @@ function addSocket(gameId, socket) {
|
||||||
changedGames[gameId] = true
|
changedGames[gameId] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInput(gameId, playerId, input) {
|
function handleInput(gameId, playerId, input, ts) {
|
||||||
const ret = GameCommon.handleInput(gameId, playerId, input)
|
GameLog.log(gameId, 'handleInput', playerId, input, ts)
|
||||||
|
|
||||||
|
const ret = GameCommon.handleInput(gameId, playerId, input, ts)
|
||||||
changedGames[gameId] = true
|
changedGames[gameId] = true
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
@ -93,6 +116,7 @@ function persistChangedGames() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
createGameObject,
|
||||||
loadAllGames,
|
loadAllGames,
|
||||||
persistChangedGames,
|
persistChangedGames,
|
||||||
createGame,
|
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 sizeOf from 'image-size'
|
||||||
import Util from './../common/Util.js'
|
import Util from '../common/Util.js'
|
||||||
import exif from 'exif'
|
import exif from 'exif'
|
||||||
import { Rng } from '../common/Rng.js'
|
import { Rng } from '../common/Rng.js'
|
||||||
|
|
||||||
|
|
@ -38,7 +38,8 @@ async function getExifOrientation(imagePath) {
|
||||||
async function createPuzzle(
|
async function createPuzzle(
|
||||||
/** @type Rng */ rng,
|
/** @type Rng */ rng,
|
||||||
targetTiles,
|
targetTiles,
|
||||||
image
|
image,
|
||||||
|
ts
|
||||||
) {
|
) {
|
||||||
const imagePath = image.file
|
const imagePath = image.file
|
||||||
const imageUrl = image.url
|
const imageUrl = image.url
|
||||||
|
|
@ -135,7 +136,7 @@ async function createPuzzle(
|
||||||
// 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
|
started: ts, // start timestamp
|
||||||
finished: 0, // finish timestamp
|
finished: 0, // finish timestamp
|
||||||
},
|
},
|
||||||
// static puzzle information. stays same for complete duration of
|
// static puzzle information. stays same for complete duration of
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import twing from 'twing'
|
||||||
import bodyParser from 'body-parser'
|
import bodyParser from 'body-parser'
|
||||||
import v8 from 'v8'
|
import v8 from 'v8'
|
||||||
import { Rng } from '../common/Rng.js'
|
import { Rng } from '../common/Rng.js'
|
||||||
|
import GameLog from './GameLog.js'
|
||||||
|
|
||||||
const allImages = () => [
|
const allImages = () => [
|
||||||
...fs.readdirSync('./../data/uploads/').map(f => ({
|
...fs.readdirSync('./../data/uploads/').map(f => ({
|
||||||
|
|
@ -50,6 +51,14 @@ app.use('/g/:gid', async (req, res, next) => {
|
||||||
WS_ADDRESS: config.ws.connectstring,
|
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) => {
|
app.post('/upload', (req, res) => {
|
||||||
upload(req, res, (err) => {
|
upload(req, res, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
@ -68,7 +77,8 @@ app.post('/newgame', bodyParser.json(), async (req, res) => {
|
||||||
console.log(req.body.tiles, req.body.image)
|
console.log(req.body.tiles, req.body.image)
|
||||||
const gameId = Util.uniqId()
|
const gameId = Util.uniqId()
|
||||||
if (!Game.exists(gameId)) {
|
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}` })
|
res.send({ url: `/g/${gameId}` })
|
||||||
})
|
})
|
||||||
|
|
@ -77,6 +87,7 @@ app.use('/common/', express.static('./../common/'))
|
||||||
app.use('/uploads/', express.static('./../data/uploads/'))
|
app.use('/uploads/', express.static('./../data/uploads/'))
|
||||||
app.use('/', async (req, res, next) => {
|
app.use('/', async (req, res, next) => {
|
||||||
if (req.path === '/') {
|
if (req.path === '/') {
|
||||||
|
const ts = Util.timestamp()
|
||||||
const games = [
|
const games = [
|
||||||
...Game.getAllGames().map(game => ({
|
...Game.getAllGames().map(game => ({
|
||||||
id: game.id,
|
id: game.id,
|
||||||
|
|
@ -84,7 +95,7 @@ app.use('/', async (req, res, next) => {
|
||||||
finished: Game.getFinishTs(game.id),
|
finished: Game.getFinishTs(game.id),
|
||||||
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, ts).length,
|
||||||
imageUrl: Game.getImageUrl(game.id),
|
imageUrl: Game.getImageUrl(game.id),
|
||||||
})),
|
})),
|
||||||
]
|
]
|
||||||
|
|
@ -124,11 +135,36 @@ wss.on('message', async ({socket, data}) => {
|
||||||
const msg = JSON.parse(data)
|
const msg = JSON.parse(data)
|
||||||
const msgType = msg[0]
|
const msgType = msg[0]
|
||||||
switch (msgType) {
|
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: {
|
case Protocol.EV_CLIENT_INIT: {
|
||||||
if (!Game.exists(gameId)) {
|
if (!Game.exists(gameId)) {
|
||||||
throw `[game ${gameId} does not exist... ]`
|
throw `[game ${gameId} does not exist... ]`
|
||||||
}
|
}
|
||||||
Game.addPlayer(gameId, clientId)
|
const ts = Util.timestamp()
|
||||||
|
Game.addPlayer(gameId, clientId, ts)
|
||||||
Game.addSocket(gameId, socket)
|
Game.addSocket(gameId, socket)
|
||||||
const game = Game.get(gameId)
|
const game = Game.get(gameId)
|
||||||
notify(
|
notify(
|
||||||
|
|
@ -150,7 +186,9 @@ wss.on('message', async ({socket, data}) => {
|
||||||
case Protocol.EV_CLIENT_EVENT: {
|
case Protocol.EV_CLIENT_EVENT: {
|
||||||
const clientSeq = msg[1]
|
const clientSeq = msg[1]
|
||||||
const clientEvtData = msg[2]
|
const clientEvtData = msg[2]
|
||||||
Game.addPlayer(gameId, clientId)
|
const ts = Util.timestamp()
|
||||||
|
|
||||||
|
Game.addPlayer(gameId, clientId, ts)
|
||||||
Game.addSocket(gameId, socket)
|
Game.addSocket(gameId, socket)
|
||||||
|
|
||||||
const game = Game.get(gameId)
|
const game = Game.get(gameId)
|
||||||
|
|
@ -164,7 +202,7 @@ wss.on('message', async ({socket, data}) => {
|
||||||
}],
|
}],
|
||||||
[socket]
|
[socket]
|
||||||
)
|
)
|
||||||
const changes = Game.handleInput(gameId, clientId, clientEvtData)
|
const changes = Game.handleInput(gameId, clientId, clientEvtData, ts)
|
||||||
notify(
|
notify(
|
||||||
[Protocol.EV_SERVER_EVENT, clientId, clientSeq, changes],
|
[Protocol.EV_SERVER_EVENT, clientId, clientSeq, changes],
|
||||||
Game.getSockets(gameId)
|
Game.getSockets(gameId)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue