/// /// /// // For Photon Cloud Application access create cloud-app-info.js file in the root directory (next to default.html) and place next lines in it: //var AppInfo = { // AppId: "your app id", // AppVersion: "your app version", //} // fetching app info global variable while in global context var DemoWss = this["AppInfo"] && this["AppInfo"]["Wss"]; var DemoAppId = this["AppInfo"] && this["AppInfo"]["AppId"] ? this["AppInfo"]["AppId"] : ""; var DemoAppVersion = this["AppInfo"] && this["AppInfo"]["AppVersion"] ? this["AppInfo"]["AppVersion"] : "1.0"; var DemoFbAppId = this["AppInfo"] && this["AppInfo"]["FbAppId"]; var DemoConstants = { EvClick: 1, // EvGetMap: 2, // for debug only // EvGetMapProgress: 3, EvNewGame: 4, MasterEventMax: 100, // separate events handled by master client from client events EvGameStateUpdate: 101, EvPlayersUpdate: 102, EvGameMap: 103, EvClickDebug: 104, EvShowCards: 105, EvHideCards: 106, // EvGameMapProgress: 107, EvMoveTimer: 108, EvDisconnectOnAlreadyConnected: 151, GameStateProp: "gameState", MoveCountProp: "moveCount", LogLevel: Exitgames.Common.Logger.Level.DEBUG, } class Demo extends Photon.LoadBalancing.LoadBalancingClient { masterClient: MasterClient; public useGroups: boolean = false; public automove: boolean = false; constructor(private canvas: HTMLCanvasElement) { super(DemoWss ? Photon.ConnectionProtocol.Wss : Photon.ConnectionProtocol.Ws, DemoAppId, DemoAppVersion); this.masterClient = new MasterClient(this); // uncomment to use Custom Authentication // this.setCustomAuthentication("username=" + "yes" + "&token=" + "yes"); Output.log("Init", DemoAppId, DemoAppVersion); this.logger.info("Init", DemoAppId, DemoAppVersion); this.setLogLevel(DemoConstants.LogLevel); } // sends to all including itself raiseEventAll(eventCode: number, data?: any, options?: { interestGroup?: number; cache?: number; receivers?: number; targetActors?: number[]; }) { options = options || {}; options.receivers = Photon.LoadBalancing.Constants.ReceiverGroup.All; this.raiseEvent(eventCode, data, options); } logger = new Exitgames.Common.Logger("Demo:", DemoConstants.LogLevel); // overrides roomFactory(name: string) { return new DemoRoom(this, name); } actorFactory(name: string, actorNr: number, isLocal: boolean) { return new DemoPlayer(this, name, actorNr, isLocal); } myRoom() { return super.myRoom(); } myActor() { return super.myActor(); } myRoomActors() { return <{ [index: number]: DemoPlayer }>super.myRoomActors(); } start() { this.stage = new createjs.Stage(this.canvas); this.setupUI(); this.myRoom().loadResources(this.stage); // this.connectToRegionMaster("EU"); } // overrides onError(errorCode: number, errorMsg: string) { Output.log("Error", errorCode, errorMsg); // optional super call super.onError(errorCode, errorMsg); } onOperationResponse(errorCode: number, errorMsg: string, code: number, content: any) { this.masterClient.onOperationResponse(errorCode, errorMsg, code, content); if (errorCode) { switch (code) { case Photon.LoadBalancing.Constants.OperationCode.JoinRandomGame: switch (errorCode) { case Photon.LoadBalancing.Constants.ErrorCode.NoRandomMatchFound: Output.log("Join Random:", errorMsg); this.createDemoRoom(); break default: Output.log("Join Random:", errorMsg); break; } break; case Photon.LoadBalancing.Constants.OperationCode.CreateGame: if (errorCode != 0) { Output.log("CreateGame:", errorMsg); this.disconnect(); } break; case Photon.LoadBalancing.Constants.OperationCode.JoinGame: if (errorCode != 0) { Output.log("CreateGame:", errorMsg); this.disconnect(); } break; default: Output.log("Operation Response error:", errorCode, errorMsg, code, content); break; } } } onEvent(code: number, content: any, actorNr: number) { this.masterClient.onEvent(code, content, actorNr); switch (code) { case DemoConstants.EvDisconnectOnAlreadyConnected: Output.log("Disconnected by Master Client as already connected player"); this.disconnect(); break; case DemoConstants.EvMoveTimer: var t = document.getElementById("info"); t.textContent = "Your turn now! (" + content.timeout + " sec.)"; break case DemoConstants.EvClickDebug: Output.log(content.msg); break case DemoConstants.EvShowCards: Output.log("show ", content.cards); for (var c in content.cards) { this.showCard(parseInt(c), content.cards[c]); } if (content.resetShown) { var demo = this; setTimeout(function () { for (var c in content.resetShown.cards) { demo.hideCard(parseInt(content.resetShown.cards[c]), true); } demo.stage.update(); }, GameProperties.cardShowTimeout); } this.stage.update(); break; case DemoConstants.EvHideCards: Output.log("hide ", content.cards); if (content.all) { for (var i = 1; i <= this.myRoom().variety * 2; ++i) { this.hideCard(i); } } for (var k in content.cards) { this.hideCard(content.cards[k]); } this.stage.update(); break; default: } this.logger.info("Demo: onEvent", code, "content:", content, "actor:", actorNr); } onStateChange(state: number) { this.masterClient.onStateChange(state); // "namespace" import for static members shorter acceess var LBC = Photon.LoadBalancing.LoadBalancingClient; var stateText = document.getElementById("statetxt"); stateText.textContent = LBC.StateToName(state); switch (state) { case LBC.State.JoinedLobby: this.joinRandomRoom(); break; default: break; } this.updateRoomButtons(); var t = document.getElementById("info"); t.textContent = "Not in Game"; this.updateAutoplay(this); } private autoClickTimer: number = 0; updateAutoplay(client: Demo) { clearInterval(this.autoClickTimer); var t = document.getElementById("autoplay"); if (this.isConnectedToGame() && t.checked) { this.autoClickTimer = setInterval( function () { var hidden = []; var j = 0; for (var i = 1; i <= client.myRoom().variety * 2; ++i) { if (!client.shownCards[i]) { hidden[j] = i; ++j; } } if (hidden.length > 0) { var card = hidden[Math.floor(Math.random() * hidden.length)]; client.raiseEventAll(DemoConstants.EvClick, { "card": card }); } }, 750) } } private updateMasterClientMark() { var el = document.getElementById("masterclientmark"); el.textContent = this.masterClient.isMaster() ? "!" : ""; } onRoomListUpdate(rooms: Photon.LoadBalancing.RoomInfo[], roomsUpdated: Photon.LoadBalancing.RoomInfo[], roomsAdded: Photon.LoadBalancing.RoomInfo[], roomsRemoved: Photon.LoadBalancing.RoomInfo[]) { // Output.log("onRoomListUpdate", rooms, roomsUpdated, roomsAdded, roomsRemoved); this.updateRoomButtons(); // join btn state can be changed } onRoomList(rooms: Photon.LoadBalancing.RoomInfo[]) { this.updateRoomButtons(); } onJoinRoom() { this.updateMasterClientMark(); this.masterClient.onJoinRoom(); this.logger.info("onJoinRoom myRoom", this.myRoom()); this.logger.info("onJoinRoom myActor", this.myActor()); this.logger.info("onJoinRoom myRoomActors", this.myRoomActors()); this.updatePlayerOnlineList(); this.setupScene(); var game = this.myRoom().getCustomProperty("game"); for (var card = 1; card <= this.myRoom().variety * 2; ++card) { // TODO: remove game.mapProgress check after empty object send bug fix var icon = game.mapProgress && game.mapProgress[card]; if (icon) { this.showCard(card, icon); } } this.stage.update(); } onActorJoin(actor: Photon.LoadBalancing.Actor) { this.updateMasterClientMark(); this.masterClient.onActorJoin(actor); Output.log("actor " + actor.actorNr + " joined"); this.updatePlayerOnlineList(); } onActorLeave(actor: Photon.LoadBalancing.Actor) { this.updateMasterClientMark(); this.masterClient.onActorLeave(actor); Output.log("actor " + actor.actorNr + " left"); this.updatePlayerOnlineList(); } // tools private createDemoRoom() { Output.log("New Game"); this.myRoom().setEmptyRoomLiveTime(10000); this.createRoomFromMy("DemoPairsGame (Master Client)"); } //scene private stage: createjs.Stage; private shape: createjs.Shape; private cellWidth = 96; private cellHeight = 96; private bgColor = 'rgba(100,100,100,255)'; private gridColor = 'rgba(180,180,180,255)'; private setupScene() { this.shownCards = []; this.stage.removeAllChildren(); this.canvas.width = this.cellWidth * this.myRoom().columnCount this.canvas.height = this.cellHeight * this.myRoom().rowCount(); this.drawBg(); this.drawGrid(); this.stage.update(); } private shownCards: createjs.Bitmap[] = []; private hideCard(card: number, checkMap?: boolean) { var game = this.myRoom().getCustomProperty("game"); // TODO: remove game.mapProgress check after empty object send bug fix if (checkMap && game.mapProgress && game.mapProgress[card]) { // leave it open } else { if (this.shownCards[card]) { this.stage.removeChild(this.shownCards[card]); this.shownCards[card] = null; } } } private showCard(card: number, icon: number) { if (!this.shownCards[card]) { var img = this.myRoom().icon(icon - 1); var bitmap = new createjs.Bitmap(img); var col = this.myRoom().columnCount; bitmap.x = ((card - 1) % col) * this.cellWidth; bitmap.y = Math.floor((card - 1) / col) * this.cellHeight; this.stage.addChild(bitmap); this.shownCards[card] = bitmap; } } private drawBg() { var bg = new createjs.Shape(); bg.graphics.beginFill(this.bgColor).drawRect(0, 0, this.canvas.width, this.canvas.height); this.stage.addChild(bg); } private drawGrid() { var grid = new createjs.Shape(); var w = this.canvas.width; var h = this.canvas.height for (var i = 0; i < this.myRoom().columnCount + 1; ++i) { var x = i * this.cellWidth; grid.graphics.setStrokeStyle(1); grid.graphics.beginStroke(this.gridColor).moveTo(x, 0).lineTo(x, h); } for (var i = 0; i < this.myRoom().rowCount() + 1; ++i) { var y = i * this.cellHeight; grid.graphics.setStrokeStyle(1); grid.graphics.beginStroke(this.gridColor).moveTo(0, y).lineTo(w, y); } this.stage.addChild(grid); } // ui private setupUI() { this.stage.addEventListener("stagemousedown", (ev) => { var x = Math.floor(this.stage.mouseX / this.cellWidth); var y = Math.floor(this.stage.mouseY / this.cellHeight); this.raiseEventAll(DemoConstants.EvClick, { "card": x + y * this.myRoom().columnCount + 1 }); this.stage.update(); }) var cb = document.getElementById("autoplay"); cb.onchange = () => this.updateAutoplay(this); var btn = document.getElementById("connectbtn"); btn.onclick = (ev) => { var n = document.getElementById("playername"); // this.myActor().setName(n.value); var id = "n:" + n.value; // clients set actors's id this.myActor().setInfo(id, n.value); this.myActor().setCustomProperty("auth", { name: n.value }); this.connectToRegionMaster("EU"); } btn = document.getElementById("disconnectbtn"); btn.onclick = (ev) => { this.disconnect(); return false; } btn = document.getElementById("newgame"); btn.onclick = (ev) => { this.raiseEventAll(DemoConstants.EvNewGame, null); return false; } btn = document.getElementById("newtrivial"); btn.onclick = (ev) => { this.raiseEventAll(DemoConstants.EvNewGame, { trivial: true }); return false; } this.updateRoomButtons(); } public updatePlayerOnlineList() { var list = document.getElementById("playeronlinelist"); while (list.firstChild) { list.removeChild(list.firstChild); } for (var i in this.myRoomActors()) { var a = this.myRoomActors()[i]; var item = document.createElement("li"); item.attributes["value"] = a.getName() + " /" + a.getId(); item.textContent = a.getName() + " / " + a.getId() + " / " + a.actorNr; if (a.isLocal) { item.textContent = "-> " + item.textContent; } list.appendChild(item); this.logger.info("actor:", a); } } private updateRoomButtons() { var btn; var connected = this.state != Photon.LoadBalancing.LoadBalancingClient.State.Uninitialized && this.state != Photon.LoadBalancing.LoadBalancingClient.State.Disconnected btn = document.getElementById("connectbtn"); btn.disabled = connected; btn = document.getElementById("fblogin"); btn.disabled = connected; btn.hidden = !DemoFbAppId; btn = document.getElementById("disconnectbtn"); btn.disabled = !connected; btn = document.getElementById("newgame"); btn.disabled = !this.isJoinedToRoom() btn = document.getElementById("newtrivial"); btn.disabled = !this.isJoinedToRoom() } } class Output { public static logger = new Exitgames.Common.Logger(); static log(str: string, ...op: any[]) { var log = document.getElementById("log"); var formatted = this.logger.formatArr(str, op); var newLine = document.createElement('div'); newLine.textContent = formatted; log.appendChild(newLine); log.scrollTop = log.scrollHeight; } } class DemoRoom extends Photon.LoadBalancing.Room { constructor(private demo: Demo, name: string) { super(name); this.variety = GameProperties.variety; this.columnCount = GameProperties.columnCount; this.iconUrls = GameProperties.icons; } // acceess properties every time public variety = 0; public columnCount = 0; public rowCount() { return Math.ceil(2 * this.variety / this.columnCount) } public iconUrls = {}; public icons = {}; public iconUrl(i: number) { return this.iconUrls[i]; } public icon(i: number) { return this.icons[i]; } public onPropertiesChange(changedCustomProps: any, byClient?: boolean) { //case DemoConstants.EvGameStateUpdate: if (changedCustomProps.game) { var game = this.getCustomProperty("game"); var t = document.getElementById("gamestate"); t.textContent = JSON.stringify(game); t = document.getElementById("nextplayer"); t.textContent = ""; var turnsLeft = 0; for (var i = 0; i < game.nextPlayerList.length; i++) { if (turnsLeft == 0 && game.nextPlayerList[i] == this.demo.myActor().getId()) { turnsLeft = i; } t.textContent += " " + game.nextPlayerList[i]; } var t = document.getElementById("info"); t.textContent = turnsLeft == 0 ? "Your turn now!" : "Wait " + turnsLeft + " turn(s)"; if (game.nextPlayer == this.demo.myActor().getId()) { this.demo.updateAutoplay(this.demo); } } // case DemoConstants.EvPlayersUpdate: if (changedCustomProps.game || changedCustomProps.playersStats) { var game = this.getCustomProperty("game"); var playersStats = this.getCustomProperty("playersStats") || {}; var list = document.getElementById("players"); while (list.firstChild) { list.removeChild(list.firstChild); } for (let i in game.players) { var id = game.players[i]; var item = document.createElement("li"); item.attributes["value"] = id; var d = game.playersData[id]; var s = playersStats && playersStats[id]; item.textContent = d.name + " / " + id + ": " + d.hitCount + " / " + (d.hitCount + d.missCount) + (s ? " [" + s.hitCount + " / " + (s.hitCount + s.missCount) + " / " + s.gamesPlayed + "]" : ""); item.title = "Player id: " + id + ", name: " + d.name + "\nCurrent game: hits = " + d.hitCount + ", clicks = " + (d.hitCount + d.missCount) + (s ? "\n Totals: games played = " + s.gamesPlayed + ", hits = " + s.hitCount + ", clicks = " + (s.hitCount + s.missCount) : ""); list.appendChild(item); } } } public loadResources(stage: createjs.Stage) { for (var i = 0; i < this.variety; ++i) { var img = new Image(); this.icons[i] = img; img.onload = function () { Output.log("Image " + img.src + " loaded"); stage.update(); }; img.src = this.iconUrl(i); } } } class DemoPlayer extends Photon.LoadBalancing.Actor { constructor(private demo: Demo, name: string, actorNr: number, isLocal: boolean) { super(name, actorNr, isLocal); } public getId() { return this.getCustomProperty("id"); } public getName() { return this.getCustomProperty("name"); } public onPropertiesChange(changedCustomProps: any) { if (this.isLocal) { document.title = this.getName() + " / " + this.getId() + " Pairs Game (Master Client)"; } this.demo.updatePlayerOnlineList(); } public setInfo(id: string, name: string) { this.demo.setUserId(id); this.setCustomProperty("id", id); this.setCustomProperty("name", name); } } var loadBalancingClient; window.onload = () => { loadBalancingClient = new Demo(document.getElementById("canvas")); loadBalancingClient.start(); };