Welcome

Welcome to my blog. I mainly blog about the basics of running Flash games over the internet, but sometimes I get distracted. Here you will find my efforts at programming turn based Flash games, plus links to all the games I have written. You are free (as in beer) to get all the code, just email me - nicolas_evans at yahoo.com (sorry you cant click on it - I dont want more spam).

Building Multiplayer Games in Flash.

The Posts

Tuesday, March 21, 2006

Hi. I have made some changes to Scramble recently, mainly to add more error checking on server failures.
I also changed some methods to static. This makes it obvious those methods do not access instance variables. And it makes them easier to reference in the code, eg. MyCommunications.rTrim() instead of _parent.myCommunications.rTrim().

# posted by nicolas_evans @ 11:00 AM  

The Posts

Friday, March 10, 2006

Hi. I promised to explain how everything fits together in my Scramble game. By the way, it has been improved thanks to a good friend of mine, Charlie, who tests my games and points out that instructions are usually a good thing.
What the Flash does for each player is send the move to the server like this (this is for player 1):
myCommunications.fnMessageSuccess = function ()
    {gotoAndPlay("Player1TestMessageID");}
myCommunications.fnMessageFail = function ()
    {trace ("fail activated");gotoAndPlay("Player1End") }
myCommunications.fnSendToServer (1) ;
Then it tests the message returned is correct.
if (parseInt(myCommunications.gmyLoadVars.messageID) != 1 + 
myCommunications.gnMessageID) {
    trace(["MessageID Error",
    myCommunications.gmyLoadVars.messageID,
    myCommunications.gnMessageID])
    _level0.gotoAndStop("ConnectionError")   
}
Lastly it repeatedly asks the server (every 3 seconds) if the opponent has made his move.
if (parseInt(myCommunications.gmyLoadVars.messageID) != 1 + 
myCommunications.gnMessageID) {
    trace(["MessageID Error",
    myCommunications.gmyLoadVars.messageID,
    myCommunications.gnMessageID])
    _level0.gotoAndStop("ConnectionError")   
}
myCommunications.fnMessageSuccess = function () {
    if (parseInt(myCommunications.gmyLoadVars.pno) == 2) {
        gotoAndPlay("Player1LoopEnd");
    }
}
myCommunications.fnMessageFail = function ()
    {trace ("fail activated");gotoAndPlay("Player1Loop") }
myCommunications.fnLoadVariables("?action=VIEW&filename=" + myScrambleGame.gnGameNumber);

This concludes the explanation, but if you feel I missed a bit (or a lot) out, please comment or email me. My next task is to convert 3 more games to running over the net, and I will blog if I have any improvements.

# posted by nicolas_evans @ 12:57 PM  

The Posts

Thursday, March 09, 2006

Hi. This is where I explain how to connect a flash game to the internet. Well start to explain, as its a long process. First a warning, I spent much of the development time trying to get Loadvars.sendAndLoad() working. I never did and I suggest you avoid it too and use Loadvars.load() instead.
First a note about errors. Sometimes Flash will send a message, and fail. It just happens. So everytime I send a message I include what to do if there is a failure. (I also keep a count of errors and give up completely when I reach 5.) This code I put into fnMessageFail(). This method is overwritten by each send. I have never seen other programmers do this, I expect they use inheritance in a proper OOP way, but my way works and seems easier.
Once again I put everything in a class, you can read my previous blogs why I like this so much. Here it is, I try to explain it in my next blog.
class MyCommunications {

  private var gsURL:String;
  var gmyLoadVars:LoadVars;
  var gnMessageID:Number;
  private var gnErrorCount:Number;

  function MyCommunications (pURL:String) {
      gsURL = pURL;
      gnErrorCount = 0;
      gmyLoadVars = new LoadVars();
      gmyLoadVars.onLoad = function(success:Boolean) {
          if (success) {
              if (gnErrorCount > 0)
                  gnErrorCount--;
              _level0.myCommunications.fnMessageSuccess();
          }
          else if (gnErrorCount < 5) {
              gnErrorCount++;
              _level0.myCommunications.fnMessageFail();         
          }
          else {
              trace("Error loading/parsing LoadVars.");
              _level0.gotoAndStop("FatalError");
          }
      }
  }
  function fnLoadVariables(pString:String) {
      trace("fnLoadVariables")
      gmyLoadVars.load(gsURL + "scramble.jsp" + pString +
"&nocache=" + Math.random(), "GET");
  }

  function fnSendToServer (pAsPlayer) {
      var i:Number,j:Number;
      var arTemp:Array = new Array();
   
      j = 0;
      arTemp[j++] = gsURL + "scramble.jsp" +
          "?action=INSERT" +
          "&filename=" + _level0.myScrambleGame.gnGameNumber +
          "&pno=" + pAsPlayer +
          "&nTurn=" + _level0.myScrambleGame.gnTurn +
          "&rnd=" + _level0.myScrambleGame.gnRound +
          "&p2score=" + _level0.myScrambleGame.player2Hand.nScore +
          "&pwd=" + _level0.myScrambleGame.gsPassword +
          "&p1hand=" ;
      for (i=0;i<_level0.myScrambleGame.player1Hand.arPlayerTiles.length;i++) {
          arTemp[j++] = _level0.myScrambleGame.player1Hand.arPlayerTiles[i]._name + ",";
      }         
      arTemp[j++] = "&p2hand=";
      for (i=0;i<_level0.myScrambleGame.player2Hand.arPlayerTiles.length;i++) {
          arTemp[j++] = _level0.myScrambleGame.player2Hand.arPlayerTiles[i]._name + ",";
      }         
      arTemp[j++] = "&board=";
      for (i=0;i<_level0.myScrambleGame.garBoard.length;i++) {
          if (_level0.myScrambleGame.garBoard[i] != "Empty")
              arTemp[j++] = _level0.myScrambleGame.garBoard[i]._name;
          arTemp[j++] = ",";
      }
      arTemp[j++] = "&order=" ;
      for (i=0;i<_level0.myScrambleGame.garTileOrder.length;i++) {
          arTemp[j++] = _level0.myScrambleGame.garTileOrder[i] + ",";
      }
      arTemp[j++] = "&p1toopen=" + _level0.myScrambleGame.player1Hand.bToOpen +
          "&p2toopen=" + _level0.myScrambleGame.player2Hand.bToOpen +
          "&p1score=" + _level0.myScrambleGame.player1Hand.nScore +
          "&messageID=";
      gnMessageID = Math.floor(Math.random() * 1000);
      arTemp[j++] = "" + gnMessageID;
      gmyLoadVars.load(arTemp.join(""),gmyLoadVars,"POST");

  }
  function fnParseVariablesFromServer() {

      if (parseInt(gmyLoadVars.nErrorNumber) != 0) {
          trace("Error received from server " + gmyLoadVars.nErrorNumber)
          _level0.gotoAndStop("ConnectionError")
      }
      _level0.myScrambleGame.gnPlayerNumber = parseInt(gmyLoadVars.pno); 
      _level0.myScrambleGame.gnTurn = parseInt(gmyLoadVars.nTurn);
      _level0.myScrambleGame.gnRound = parseInt(gmyLoadVars.rnd) ;

      _level0.myScrambleGame.gsPassword = rTrim(gmyLoadVars.pwd);
      _level0.myScrambleGame.player1Hand.arPlayerTiles =
      fnConvertStringToObjects(gmyLoadVars.p1hand);
      _level0.myScrambleGame.player2Hand.arPlayerTiles =
          fnConvertStringToObjects(gmyLoadVars.p2hand);
      _level0.myScrambleGame.garBoard = fnConvertStringToObjects(gmyLoadVars.board);
      _level0.myScrambleGame.garTileOrder = gmyLoadVars.order.split(",");
      _level0.myScrambleGame.garTileOrder.pop(); // remove empty space at end
      _level0.myScrambleGame.player1Hand.bToOpen =
          fnConvertStringToBoolean(gmyLoadVars.p1toopen);
      _level0.myScrambleGame.player2Hand.bToOpen =
          fnConvertStringToBoolean(gmyLoadVars.p2toopen);
      _level0.myScrambleGame.player1Hand.nScore = parseInt(gmyLoadVars.p1score);
      _level0.myScrambleGame.player2Hand.nScore = parseInt(gmyLoadVars.p2score);
  }
  function fnConvertStringToObjects (pString:String):Array {
      trace("fnConvertStringToObjects")
      var i:Number;
      var arTemp =new Array();
      var arTempOut = new Array();

      arTemp = pString.split(",");     
      for (i =0;i<arTemp.length;i++) {
          if (arTemp[i] == "undefined" || arTemp[i] == "")
              arTempOut.push("Empty");     
          else if (_level0[arTemp[i]])
              arTempOut.push(_level0[arTemp[i]] );
      }
      return arTempOut;
  }
  function fnConvertStringToBoolean(pString:String):Boolean {

      if (pString.substr(0,4) == "true")
          return true;
      else
          return false;
      }
  function rTrim(s) {
      var i:Number;
      var sTemp = s;
   
      i = sTemp.length;
      while (i > 0) {
          if (sTemp.charAt(i-1) == " " ||
              sTemp.charAt(i-1) == "\n" ||
              sTemp.charAt(i-1) == "\r") {
              i--;
          }
          else {
              break;
          }
      }
      return sTemp.substr(0,i);
  }
  function fnMessageSuccess() {
      trace ("fnMessageSuccess");
  // overwritten by each message
  }
  function fnMessageFail() {
      trace ("fnMessageFail");
  // overwritten by each message
  }
}

# posted by nicolas_evans @ 6:37 AM  

The Posts

Wednesday, March 08, 2006

Hi. How does one player's game communicate with the other? Mine uses the internet, but an intranet or home network would work as well.
Flash has two ways to do this, by http, or by use of a socket. I never found a cheap socket server host, but I already had a free server called http://www.myjavaserver.com/ so I used that.
The program you place on your server handles messages from the game. As mine was a java server, I wrote it in JSP. However you could use ASP, PHP , Perl, Cold Fusion or others, whichever is allowed on your server. It stores the game variables in a file.
My game only uses three types of message, one to empty the file (CLEAR), one to read the file (VIEW), and one to update the file (INSERT). Here is my program:
<%@ page import = "java.io.*" %>

<%
synchronized(page)     {
    int intVariableCount = 12;

    String filename = request.getParameter("filename");
    String action = request.getParameter("action");
       
    int intErrorNumber;
    int intTemp;

    try {
        intTemp = Integer.parseInt(filename);
    }
    catch (NumberFormatException nfe) {
        intErrorNumber = 7;
        out.println ("v=v&nErrorNumber=" + intErrorNumber);
        return;
    }

       
    // Create a Blank File if it doesn't already exist
    File fileObject = new File("/users/nicolasevans/" + filename + ".txt");
    if (!fileObject.exists() ) {
        fileObject.createNewFile();
    }

    // Read the file in
    String[]  arVariables;
    arVariables = new String[intVariableCount];

    FileReader fileRead = new FileReader(fileObject);
    BufferedReader buffFileIn = new BufferedReader(fileRead);
    String strLine  = "";
    int intCount = 0;
    while ((strLine=buffFileIn.readLine()) != null)
        if (intCount < intVariableCount) arVariables[intCount++] = strLine;
    buffFileIn.close();
    fileRead.close();

   
    // Fill in any missing data with none/0
    for (int i = intCount; i < intVariableCount ; i++)     {
        arVariables[i] = " ";
    }

    // Process the actions   

    // Insert a score/name
    if (action.equals("INSERT"))     {
       
        intErrorNumber = 0;
        // Abandon message if not later than the file

        arVariables[0] = request.getParameter("pno");
        if (Integer.parseInt(arVariables[0]) != 1
            && Integer.parseInt(arVariables[0]) != 2) {
            intErrorNumber = 1;
        }
       
        if (!arVariables[1].equals(" ") && Integer.parseInt(arVariables[1])
            >= Integer.parseInt(request.getParameter("nTurn") )) {
            intErrorNumber = 2;
        }
        arVariables[1] = request.getParameter("nTurn");

        try {
            intTemp = Integer.parseInt(request.getParameter("rnd"));
        }
        catch (NumberFormatException nfe) {
            intErrorNumber = 3;
        }
        arVariables[2] = request.getParameter("rnd");
               
        try {
            intTemp = Integer.parseInt(request.getParameter("p2score"));
        }
        catch (NumberFormatException nfe) {
            intErrorNumber = 6;
        }
        arVariables[3]= request.getParameter("p2score");
               
        arVariables[4] = request.getParameter("pwd");
       
        arVariables[5] = request.getParameter("p1hand");
       
        arVariables[6] = request.getParameter("p2hand");
       
        arVariables[7] = request.getParameter("board");
       
        arVariables[8] = request.getParameter("order");
       
        arVariables[9] = request.getParameter("p1toopen");
       
        arVariables[10]= request.getParameter("p2toopen");
       
        try {
            intTemp = Integer.parseInt(request.getParameter("p1score"));
        }
        catch (NumberFormatException nfe) {
            intErrorNumber = 5;
        }           
        arVariables[11]= request.getParameter("p1score");
       
        try {
            intTemp = Integer.parseInt(request.getParameter("messageID"));
        }
        catch (NumberFormatException nfe) {
            intErrorNumber = 8;
        }
               
        if (intErrorNumber == 0) {
        // create a new file with the variables passed in                                   
            fileObject.createNewFile();
            FileOutputStream fileStream = new FileOutputStream(fileObject);
            DataOutputStream dataStream = new DataOutputStream(fileStream);
   
            for (int i=0;i < intVariableCount;i++) {
                dataStream.writeBytes(arVariables[i] + "\n");
            }
            dataStream.close();
            fileStream.close();
            intTemp = Integer.parseInt(request.getParameter("messageID")) + 1;
            out.println ("v=v&messageID=" + intTemp);
        }
        else {
            out.println ("v=v&nErrorNumber=" + intErrorNumber);
            return;
        }
    }
    // Clear the list   
    else if (action.equals("CLEAR"))     {
        out.println("deleting");
        fileObject.delete();
    }
    else if (action.equals("VIEW"))    {
    // return the variables from the file
        out.println ("v=v&nErrorNumber=0");
   
        out.println ("&pno=" + arVariables[0] );
       
        out.println ("&nTurn=" + arVariables[1] );
       
        out.println ("&rnd=" + arVariables[2] );
       
        out.println ("&p2score=" + arVariables[3] );
       
        out.println ("&pwd=" + arVariables[4] );
       
        out.println ("&p1hand=" + arVariables[5] );
       
        out.println ("&p2hand=" + arVariables[6] );
       
        out.println ("&board=" + arVariables[7] );
       
        out.println ("&order=" + arVariables[8] );
   
        out.println ("&p1toopen=" + arVariables[9] );
   
        out.println ("&p2toopen=" + arVariables[10] );
       
        out.println ("&p1score=" + arVariables[11] );
    }
}
%>

If you understand Java you can see its very simple. If not, I hope the comments help.
I will deal with how Flash sends messages in the next blog.

# posted by nicolas_evans @ 8:42 AM  

The Posts

Tuesday, March 07, 2006

Hi. How did I build Scramble, a multiplayer game played over the net? Well I started by building the game for two people sitting at one PC. I dont think I could have built it to play over the net at first. Flash games are fairly complicated, and start by building a net version would have been very hard. What I should have done, was build a save/restore function. This would have been really useful, because the variables you need to save are also the ones you need to send over the net. I also put as much code as I could in a class file. This avoids all the problems that dog actionscript with its weak type checking, variables with the same name, etc. Here is the framework I have used for a couple of games:
class GENERIC {
    public var clip:MovieClip;
    function GENERIC (ca:MovieClip) {
        clip = ca;
    }
}

import GENERIC;

class MyScrambleGame extends GENERIC {       
    function MyScrambleGame (ca:MovieClip,pTarget,pAsPlayer:Number) {
        super(ca);
        mcCa = ca;   
    }
    function fnStartOfRound() {
    }
    function fnStartOfTurn() {
    }
    function fnEndOfTurn() {
    }
    function fnEndOfRound() {
    }   
    function fnEndOfGame() {
    }
    function fnShuffleTiles() {
    }
    function fnDisplayBoard() {
    }       
    function fnHideBoard() {
    }           
    function fnEnableTableDrag() {
    }
    function fnDisableTableDrag() {
    }   
    function fnStartDrag (mc:MovieClip) {
    }
    function fnStopDrag (mc:MovieClip) {
    }
}
There are other methods specific to this game. My next post should deal with a net version of Scramble.

# posted by nicolas_evans @ 2:30 PM  

Hi. I made a few changes to the blogger template. I cant find a good guide to using blogger, so if you know of one, please contact me. The theme I am using is from my current project, which I will blog when it goes on to the web.

# posted by nicolas_evans @ 8:29 AM  

The Posts

Monday, March 06, 2006

Hi. This is my first post in my first blog. Well my blog is supposed to be about building multiplayer games in Flash. But I am only just starting out, so it will rather be learning to build multiplayer games in Flash. My first attempt can be found at http://www.myjavaserver.com/~nicolasevans/scramble.swf This link may change, but I will try to keep the blog up to date. My games are open source, so contact me if you would like to see how this is built. All my flash games (not multiplayer) can be found at http://www.geocities.com/nicolas_evans/

# posted by nicolas_evans @ 1:58 PM  

This page is powered by Blogger. Isn't yours?