/// // Game logic runs on one of the conected clients - master client~ // Server Lua Extension Scripts port (see demo-pairs) // map and mapProgress arrays start for 1 because of lua origial source (and may be compatibility) var GameProperties = { variety: 8, columnCount: 4, cardShowTimeout: 1000, moveTimeoutSec: 10, icons: [ "resources/game-icons/angler-fish.png", "resources/game-icons/crown.png", "resources/game-icons/drink-me.png", "resources/game-icons/fishbone.png", "resources/game-icons/glass-heart.png", "resources/game-icons/horse-head.png", "resources/game-icons/robe.png", "resources/game-icons/rocket.png", ] } class MasterClient { private logger = new Exitgames.Common.Logger("MC:", Exitgames.Common.Logger.Level.DEBUG); private flushPropsFlag = false; // set this flag to send props update private game: any; // room property copy for modification and update (persistent game state) private state: any; // room property copy for modification and update (state to reset after game load) constructor(private client: Demo) { this.logger.info("Created"); } // Master is client with smallest actorNr // TODO: cache it and update only on changes isMaster(): boolean { var myActorNr = this.client.myActor().actorNr; for (var i in this.client.myRoomActors()) { if (parseInt(i) < myActorNr) { return false; } } return true; } // sends to all including itself broadcast(eventCode: number, data?: any, options?: { interestGroup?: number; cache?: number; receivers?: number; targetActors?: number[]; }) { this.logger.debug("broadcast", eventCode); this.client.raiseEventAll(eventCode, data, options); } private cacheProps() { var room = this.client.myRoom(); this.flushPropsFlag = false; this.game = room.getCustomProperty("game"); this.state = room.getCustomProperty("state") || {}; // websocket empty object send bug workaround // TODO: remove after fix this.state = this.state || {}; if (this.game) { this.game.players = this.game.players || new Array(); this.game.playersData = this.game.playersData || {}; this.game.mapProgress = this.game.mapProgress || new Array(); } // } private flushProps() { // websocket empty object send bug workaround // TODO: remove after fix if (this.state && Object.keys(this.state).length == 0) this.state = null; if (this.game) { if (Object.keys(this.game).length == 0) this.game = null; else { if (this.game.players && Object.keys(this.game.players).length == 0) this.game.players = null; if (this.game.playersData && Object.keys(this.game.playersData).length == 0) this.game.playersData = null; if (this.game.mapProgress && Object.keys(this.game.mapProgress).length == 0) this.game.mapProgress = null; } } // if (this.flushPropsFlag) { var room = this.client.myRoom(); room.setCustomProperty("game", this.game); room.setCustomProperty("state", this.state); } } onJoinRoom() { if (!this.isMaster()) return; this.logger.debug("onJoinRoom"); this.cacheProps(); if (!this.game) { this.logger.info("Creating new game..."); this.InitGame(); } // handle client's actor join same way as others this.onPlayerJoin(this.client.myActor()); this.flushProps(); } onActorJoin(actor: Photon.LoadBalancing.Actor) { if (!this.isMaster()) return; this.logger.debug("onActorJoin", actor.actorNr); this.cacheProps(); this.onPlayerJoin(actor); this.flushProps(); } onActorLeave(actor: Photon.LoadBalancing.Actor) { if (!this.isMaster()) return; this.logger.debug("onActorLeave", actor.actorNr); this.cacheProps(); var room = this.client.myRoom(); this.flushPropsFlag = false; var id = actor.getCustomProperty("id"); this.logger.info("Next:", id, this.game.nextPlayer) if (id == this.game.nextPlayer) { this.ResetShownCards(); } this.NextPlayer(0); this.flushProps(); } private onPlayerJoin(actor: Photon.LoadBalancing.Actor) { if (!this.isMaster()) return; this.logger.debug("onPlayerJoin", actor.actorNr); var actorInfo = { name: actor.getCustomProperty("name") }; var id = actor.getCustomProperty("id"); var actors = this.client.myRoomActors(); for (var n in actors) { var a = actors[n]; if (a.actorNr !== actor.actorNr && a.getCustomProperty("id") === id) { var msg = "Player " + id + " already connected"; this.broadcast(DemoConstants.EvDisconnectOnAlreadyConnected, null, { targetActors: [actor.actorNr] }); this.logger.info("onPlayerJoin", msg); return; } } // TODO: player load var returning = false; for (var i in this.game.players) { var iid = this.game.players[i]; if (id == iid) { returning = true; break; } } if (!returning) { this.logger.info("Player ", id, " added to game") this.game.players.push(id); this.GamePlayerDataInit(id, actorInfo); } else { this.logger.info("Player ", id, " returned to game"); this.GamePlayerDataUpdate(id, actorInfo); } this.NextPlayer(0); this.flushPropsFlag = true; } onEvent(code: number, content: any, actorNr: number) { if (!this.isMaster()) return; this.cacheProps(); switch (code) { case DemoConstants.EvClick: this.OnMoveEvent(actorNr, code, content); break; case DemoConstants.EvNewGame: this.OnNewGameEvent(actorNr, code, content); break; } this.flushProps(); } onStateChange(state: number) { if (!this.isMaster()) return; } onOperationResponse(errorCode: number, errorMsg: string, code: number, content: any) { if (!this.isMaster()) return; } private OnMoveEvent(actorNr: number, evCode: number, inData: any) { this.logger.debug("OnMoveEvent"); var id = this.client.myRoomActors()[actorNr].getCustomProperty("id"); if (this.game.nextPlayer == id) { var card = inData["card"]; if (card >= 1 && card <= GameProperties.variety * 2) { if (!this.game.mapProgress[card]) { if (this.state.click1 != card) { this.game.moveCount = this.game.moveCount + 1; var icon = this.game.map[card]; var tmp = {}; tmp[card] = icon; var data = new Object; data["cards"] = tmp; data["match"] = false; if (this.state.click1 && this.state.click2) { this.ResetShownCards(card); } else if (this.state.click1 && this.game.map[this.state.click1] == this.game.map[card]) { data["match"] = true this.logger.info("Match"); this.game.playersData[id].hitCount = this.game.playersData[id].hitCount + 1 this.game.mapProgress[card] = icon this.game.mapProgress[this.state.click1] = icon // timer.stop(user.clickTimer) this.state.clickTimer = null this.state.click1 = null var tot = 0 for (var i = 1; i <= GameProperties.variety * 2; ++i) { if (this.game.mapProgress[i]) { tot = tot + 1; } } if (tot == GameProperties.variety * 2) { this.logger.info("Game complete", tot, this.game.mapProgress); for (var ii in this.game.mapProgress) { this.logger.info("[" + ii + "]=" + this.game.mapProgress[ii]); } this.OnGameComplete(); } else { this.logger.info("Game to go", tot, GameProperties.variety * 2); } this.ResetMoveTimer(id); } else if (this.state.click1) { this.state.click2 = card; this.game.playersData[id].missCount = this.game.playersData[id].missCount + 1; data["resetShown"] = { cards: [this.state.click1, this.state.click2] } this.NextPlayer(1); // this.state.clickTimer = timer.create( // this.ResetShownCards, // GameProperties.cardShowTimeout // ) } else { this.state.click1 = card; } this.broadcast(DemoConstants.EvShowCards, data); this.flushPropsFlag = true; } else { this.broadcast(DemoConstants.EvClickDebug, { msg: "Card " + card + " is shown currently" }, { targetActors: [actorNr] }); } } else { this.broadcast(DemoConstants.EvClickDebug, { msg: "Card " + card + " already opened" }, { targetActors: [actorNr] }); } } else { this.broadcast(DemoConstants.EvClickDebug, { msg: "Card " + card + " is out of range" }, { targetActors: [actorNr] }); } } else { this.broadcast(DemoConstants.EvClickDebug, { msg: "Not your turn" }, { targetActors: [actorNr] }); } } private ResetShownCards(click1?: number) { //timer.stop(user.clickTimer) //this.state.clickTimer = nil this.broadcast(DemoConstants.EvHideCards, { cards: [this.state.click1, this.state.click2] }) this.state.click1 = click1; this.state.click2 = null; } private OnGameComplete() { var room = this.client.myRoom(); var playersStats = room.getCustomProperty("playersStats") || {}; var playersData = this.game.playersData; for (var i in this.game.players) { var id = this.game.players[i]; this.logger.info("Updating player", id, "stats"); this.UpdatePlayerStats(id, playersStats, playersData); } room.setCustomProperty("playersStats", playersStats); } private UpdatePlayerStats(id: number, playersStats: any, playersData: any) { if (!playersStats[id]) { playersStats[id] = {}; } var stats = playersStats[id]; stats.gamesPlayed = (stats.gamesPlayed || 0) + 1; stats.hitCount = (stats.hitCount || 0) + playersData[id].hitCount; stats.missCount = (stats.missCount || 0) + playersData[id].missCount; var data = {}; data["id"] = id; data["gamesPlayed"] = stats.gamesPlayed; data["hitCount"] = stats.hitCount; data["missCount"] = stats.missCount; // SavePlayer } private ResetMoveTimer(nextPlayer: string) { //TODO } private NextPlayer(turns: number) { var onlineIds = {}; for (var actorNr in this.client.myRoomActors()) { var actor = this.client.myRoomActors()[actorNr]; var id = actor.getCustomProperty("id"); onlineIds[id] = true; } var players = this.game.players; var count = players.length; var current = 0; for (var i = 0; i < players.length; ++i) { if (this.game.nextPlayer == players[i]) { current = i; this.logger.debug("NextPlayer", "current=", current) break; } } this.game.nextPlayer = null; this.game.nextPlayerList = new Array(); var first = true; for (var i = 0; i < players.length; ++i) { var ii = (current + turns + i) % count; if (onlineIds[players[ii]]) { if (first) { first = false; this.game.nextPlayer = players[ii]; } this.game.nextPlayerList.push(players[ii]); } this.logger.info("Next Player:", this.game.nextPlayer, ", next player list:", this.game.nextPlayerList.join(",")); this.ResetMoveTimer(this.game.nextPlayer); } } private OnNewGameEvent(actorNr: number, evCode: number, data: any) { this.logger.debug("OnNewGameEvent"); var actors = this.client.myRoomActors(); var id = actors[actorNr].getCustomProperty("id"); this.logger.info("New Game request from " + id); var trivial = data && data.trivial; this.broadcast(DemoConstants.EvHideCards, {all: true }); // hide cards // put all online players in game var players = new Array(); for (var a in actors) { players.push(actors[a].getId()); } this.InitGame(trivial, players); this.ResetShownCards(); } private InitGame(trivial?: boolean, players?: Array) { this.logger.info("Game Init") var prevPlayersData = this.game && this.game.playersData || {}; this.game = {}; this.state = {}; this.game.id = "TheOnlyGame"; this.game.players = players || new Array(); this.game.playersData = {}; for (var i in this.game.players) { var id = this.game.players[i]; this.GamePlayerDataInit(id, prevPlayersData[id]); } this.game.moveCount = 0; this.game.map = this.GenerateMap(trivial); this.game.mapProgress = new Array(); this.NextPlayer(0); // wait until animation ends // this.state.ignoreClientEvents = true //timer.create( // function () user.ignoreClientEvents = nil end, // 3000 // ) //// show and hide all cards at once //room.broadcast(105, { cards = user.this.game.map }) //timer.create( // function () room.broadcast(106, {all = true }) end, // 1500 // ) //// show and hide all cards one by one //for i = 1, user.GameProperties().variety * 2 do // timer.create( // function () room.broadcast(105, {cards = {[i] = user.this.game.map[i] }}) end, // 500 + i*50 // ) // timer.create( // function() room.broadcast(106, { cards = {i }}) end, //500 + i * 50 + 1500 // ) //end this.flushPropsFlag = true; } private GenerateMap(trivial?: boolean) { this.logger.info("Generating map...", trivial); var m = {}; var put = {}; var nCards = GameProperties.variety * 2; var todo = new Array(); for (var i = 1; i <= nCards; ++i) { todo[i] = i; } for (var i = 1; i <= nCards; ++i) { var j; if (trivial) { j = 1; } else { j = Math.floor(Math.random() * (nCards - i + 1)) + 1; } m[i] = Math.floor((todo[j] + 1) / 2) // project to pairs todo.splice(j, 1); } this.logger.info("Map generated:"); for (var k in m) { this.logger.info(k, m[k]); } return m } private GamePlayerDataInit(id: string, data: any) { this.game.playersData[id] = { id: id, name: data && data.name, hitCount: 0, missCount: 0 } } private GamePlayerDataUpdate(id: string, data: any) { this.game.playersData[id].name = data && data.name; } }