Learn what Felgo offers to help your business succeed. Start your free evaluation today! Felgo for Your Business

MultiplayerDemo

 import Felgo 3.0
 import QtQuick 2.3
 import "../common"
 import "../game"

 // scene with the actual game
 SceneBase {
   id: gameScene

   property alias clock: clock

   // these are sent via multiplayer
   // contains the turn value as value
   property int multiplayerCodeAddTime: 0
   // contains the absolute time value, sent initially when a new player joins to sync the time when a new player joins in the middle of a game
   property int multiplayerCodeSetTime: 1

   // gameLogic holds all functions for managing the actions of the different players
   Item {
     id: gameLogic

     // the active local player has clicked on one of the three numbers
     // continue by changing the time on the clock and signal a new turn
     function playNumber(number){
       if (multiplayer.myTurn) {
         gConsole.printLn('Playing Number ' + number + ' (as Player ' + multiplayer.localPlayer.name + ')');
         clock.addTime(number);
         multiplayer.sendMessage(multiplayerCodeAddTime, number);
         multiplayer.sendNextTurnMessage("Play Number " + number);
       } else {
         gConsole.printLn("Can't play Number, not my turn. Not sending anything.");
       }
     }

     // pick a random number for the disconnected/inactive players
     function getAIMove(){
       return Math.floor(Math.random() * 3) + 1;
     }

     // the leader plays an ai move vor the disconnected/inactive players
     // continue by changing the time on the clock and signal a new turn
     function executeAIMove() {
       var aiBone = getAIMove();
       var currentTurnPlayerId = multiplayer.activePlayer.userId;
       gConsole.printLn('>> Leader (' + multiplayer.leaderPlayer.name + ') playing Number on behalf of ' + multiplayer.activePlayer.name + ' using AI: Number '+ aiBone);
       clock.addTime(aiBone);
       multiplayer.sendMessage(multiplayerCodeAddTime, aiBone);
     }

     function startTurnTimer() {
       timer.stop();
       // give the player 10 seconds
       timerView.remainingTime = 10;
       timer.start();
     }

     function turnStarted(playerId) {
       gConsole.printLn("Turn started for " + multiplayer.activePlayer.name)

       // start the timer
       gameLogic.startTurnTimer();
       // schedule AI to take over in 3 seconds in case the player is gone
       multiplayer.leaderCode(function() {
         if (!multiplayer.activePlayer.connected) {
           aiTimeOut.start();
         }
       });
     }

     function turnTimedOut(){
       // clean up our UI
       timer.stop();

       // player timed out, so leader should take over
       multiplayer.leaderCode(function () {
         // play an AI bone if this player never played anything (this happens in the case where the player left some time during his turn, and so the early 3 second AI move didn't get scheduled
         executeAIMove();
         // trigger a new turn in case of timeout
         multiplayer.triggerNextTurn();
       });
     }

     function createGame(){
       timerView.textOverride = 'Waiting for players...';
       multiplayer.createGame();
     }

     function leaveGame(){
       timerView.textOverride = 'Leaving Game';
       multiplayer.leaveGame();
       aiTimeOut.stop();
       timer.stop();
     }

     function joinGame(room){
       timerView.textOverride = 'Waiting for players...';
       multiplayer.joinGame(room);
     }

     // initialize the game in the beginning
     // assign playerTags to the players in the game
     function initGame(){
       console.debug("initGame() called, multiplayer.initialized:", multiplayer.initialized)

       // only reset the clock if we are the leader, otherwise we get the initial value by the server
       // it is safe to reset the clock here also as non-leader and also if the player joins an already running game, because the correct initial value is then received shortly afterwards by the server
       //if(multiplayer.amLeader) {
         clock.reset()
       //}

       // we must not call this! forceStartGame() is called from the MatchMakingView, not from the GameScene!
       // if we would call forceStartGame or createGame here, and the player joins a late match, it would not work anymore
 //      if(multiplayer.initialized){
 //        multiplayer.forceStartGame();
 //      } else {
 //        createGame();
 //      }
     }
   }

   // trigger an auto turn for connected players after 10 seconds
   Timer {
     id: timer
     repeat: true
     interval: 1000;
     onTriggered: {
       timerView.remainingTime -= 1;
       if (timerView.remainingTime === 0) {
         gameLogic.turnTimedOut();
       }
     }
   }

   // trigger an auto turn for disconnected players after 3 seconds
   Timer {
     id: aiTimeOut
     interval: 3000;
     onTriggered: {
       gameLogic.executeAIMove();
       multiplayer.triggerNextTurn();
     }
   }

   // access the functions of the FelgoMultiplayer component
   Connections {
     // this is important! only handle the messages when we are currently in the game scene
     // otherwise, we would handle the playerJoined signal when the player is still in matchmaking view!
     // do not use the visible property here! as visible only gets triggered with the opacity animation in SceneBase
     target: multiplayer
     enabled: activeScene === gameScene

     onPlayerJoined: {
       console.debug("GameScene.onPlayerJoined:", player)

       // TODO: send a new message with the new sync value to the new player (or actually to all), as we now support late-joins of the game
       // what about the leaderCode, is this overwritten or just called if we are the leader?
       multiplayer.leaderCode(function() {
         console.debug("amLeader is true, sending the clock time:", clock.time)
         multiplayer.sendMessage(multiplayerCodeSetTime, clock.time)
       });
     }
     onPlayerLeft:{
       console.debug("GameScene.onPlayerLeft", player, JSON.stringify(player))
     }

     onPlayerChanged: {
       console.debug("GameScene.onPlayerChanged:", player, JSON.stringify(player))
     }

     onPlayersReady: {
       console.debug("GameScene.onPlayersReady")
     }

     // hide the start button and reset the clock in the beginning of the game
     // note: this is received too late, the gameWindow is not active at that time and thus the signal is not received
     // this is only received, if a game re-start would be triggered
     onGameStarted: {
       console.debug("GameScene.onGameStarted in game logic received, gameRestarted:", gameRestarted)
       // it is safe to reset the clock here, also if the player joins an already running game, because the correct initial value is then received shortly afterwards by the server
       clock.reset()
     }

     // this is currently never called in this demo, as we dont have a gamover state
     onGameEnded: {
       console.debug("GameScene.onGameEnded received")
     }

     // signal a leader change and allow the new leader to start the game
     onLeaderPlayerChanged:{
       gConsole.printLn("New Leader elected: " + multiplayer.leaderPlayer.name)
     }

     onActivePlayerChanged:{
       //console.debug("onActivePlayerChanged")
     }

     onTurnStarted:{
       gameLogic.turnStarted(playerId);
     }

     onGamePropertiesChanged: {
       console.debug("xxx-onGamePropertiesChanged, changedProps:", JSON.stringify(changedGameProperties), byClient)
     }

     // handle all received messages
     onMessageReceived: {
       console.debug("GameScene.game logic: received message with code", code, ", message:", message)
       // sync the time change of the clock
       if (code == multiplayerCodeAddTime) {
         clock.addTime(message);
       } else if (code == multiplayerCodeSetTime) {
         clock.setTime(message);
       }
     }
   }

   // background
   Rectangle {
     anchors.fill: gameWindowAnchorItem
     color: clock.sky

     // Behaviour animates the color property
     Behavior on color {
       ColorAnimation { easing.type: Easing.InOutQuad; duration: 400 }
     }
   }

   // displays the remaining time for the current turn
   TimerView {
     id: timerView
     remainingTime: 0
     anchors.right: gameWindowAnchorItem.right
     anchors.rightMargin: 10
     anchors.bottom: gConsole.top
     anchors.bottomMargin: 10
   }

   // game instructions
   Text {
     text: "Turn the time by 1, 2 or 3 hours.\nTry to land on midnight or noon."
     color: "black"
     font.pixelSize: 9
     anchors.left: gameWindowAnchorItem.left
     anchors.leftMargin: 10
     anchors.bottom: gConsole.top
     anchors.bottomMargin: 10
   }

  Column {
    anchors.right: gameWindowAnchorItem.right
    anchors.rightMargin: 10
    anchors.top: gameWindowAnchorItem.top
    anchors.topMargin: 10
    spacing: 10

   // back button to leave scene
   Button {
     id: closeButton
     text: "Close"
     radius: 4
     backgroundColor: Qt.lighter("#f05352", 1.2)
     anchors.right: parent.right
     onClicked: {
       gameLogic.leaveGame();
       backButtonPressed();
     }
   }

   Button {
     text: "Toggle GameOpen\nNow: " + multiplayer.gameOpenForLateJoin
     radius: 4
     backgroundColor: Qt.lighter("#f05352", 1.2)
     anchors.right: parent.right
     visible: multiplayer.amLeader
     onClicked: {
       console.debug("old value for isGameOpenForLateJoin:", multiplayer.gameOpenForLateJoin)
       // forcefully trigger the opening, also if no open slots are available
       multiplayer.setGameOpenForLateJoin(!multiplayer.gameOpenForLateJoin, true)
       console.debug("new value for isGameOpenForLateJoin:", multiplayer.gameOpenForLateJoin)
     }
   }

   Text {
     text: "Available Player Slots:" + multiplayer.availablePlayerSlots
     anchors.right: parent.right
   }
  }

  // overlay to make it better readable
  Rectangle {
    z:1
    anchors.fill: playerInfoText
    color: "white"
    visible: playerInfoText.visible
  }
  Text {
    id: playerInfoText
    z: 1
    anchors.bottom: parent.gameWindowAnchorItem.bottom
    text: getShortPlayerInfo()
    font.pixelSize: sp(9)
    width: parent.width
    wrapMode: Text.WrapAnywhere
    color: "black"
    visible: false// only make visible for verbose display of user infos for testing late joins, most interestingly the userIds and oldUserIds
  }

  function getShortPlayerInfo() {
    var text = ""
    for (var i=0; i<multiplayer.players.length; i++) {
      var p = multiplayer.players[i]
      text += "" + i + ": " + p.nickName + "(" + p.userId + "), oldUserId: " + p.oldUserId + ", activeTurn: " + p.activeTurn + ", leader: " + p.leader + "\n"
    }
    return text
  }

   // three buttons with the numbers 1, 2 and 3 increase the time of the clock
   Row{
     anchors.top: gameWindowAnchorItem.top
     anchors.topMargin: 10
     anchors.horizontalCenter: gameWindowAnchorItem.horizontalCenter
     spacing: 4
     z:1 // put on top of the Player names, if the names are longer it may overlap otherwise

     Repeater {
       id: actionButtons
       model: 3

       Button {
         width: 40
         height: 40
         radius: 5
         text: index + 1
         backgroundColor: multiplayer.myTurn ? "#90119851" : "#90FF032D"
         onClicked: {
           gameLogic.playNumber(index + 1);
         }
       }
     }
   }

   // the playerTags display the name of the players and mark the active one
   Column {
     anchors.left: gameWindowAnchorItem.left
     anchors.leftMargin: 10
     anchors.top: gameWindowAnchorItem.top
     anchors.topMargin: 10
     spacing: 4

     Repeater {
       id: players

       model: multiplayer.players

       PlayerTag {}
     }
   }

   // button to start the game
   // NOTE: this will never be visible in this game, because the game has no end condition
   Button {
     id: startButton
     visible: multiplayer.amLeader
     radius: 4
     text: "Restart Game"
     backgroundColor: "#747474"
     anchors.bottom: gConsole.top
     anchors.bottomMargin: 10

     onClicked: {
       multiplayer.restartGame()
     }
   }

   // console displaying the moves of the different players in text form
   GConsole {
     id: gConsole
     height: 100
     anchors.left: gameWindowAnchorItem.left
     anchors.right: gameWindowAnchorItem.right
     anchors.bottom: gameWindowAnchorItem.bottom
   }

   // actual gameplay
   // the time of the clock changes depending on the played numbers
   Clock {
     id: clock
     anchors.horizontalCenter: gameWindowAnchorItem.horizontalCenter
     anchors.top: gameWindowAnchorItem.top
     anchors.topMargin: 65
   }

   // init the game when entering the scene
   onVisibleChanged: {
     //console.debug("GameScene.visible changed to:", visible)
     if(visible) {
       gameLogic.initGame()
Qt_Technology_Partner_RGB_475 Qt_Service_Partner_RGB_475_padded