Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

14. March 2012

 

 

In part 1 we look at a very basic example of sending information across the wire from SFML to NodeJS.  In part 2 we looked at JSON encoding high score data, that is then sent via UDP socket to a Node server.  Now we complete the process, by having Node return high score data back to your C++ SFML app.  By the end of this section, you will have all the code you need for a functioning ( if somewhat fragile ) high score client and server.

 

 

First we look at the C++ code.  Things are very similar to part 2, except code has been refactored a bit for re-use.  Lets look now.

 

 

#include "SFML/Network.hpp" #include "JSON.h" #include <iostream> void MakePacket(const wchar_t* action, const wchar_t * name, const float score, sf::Packet &packet) { JSONObject data; data[L"action"] = new JSONValue(action); data[L"name"] = new JSONValue(name); data[L"score"] = new JSONValue(score); JSONValue * val = new JSONValue(data); data.clear(); std::wstring dataString = val->Stringify(); delete val; std::string notSoWide; notSoWide.assign(dataString.begin(),dataString.end()); packet.Append(notSoWide.c_str(),notSoWide.length()); } int main(int argc, char* argv[]) { sf::IPAddress ip("127.0.0.1"); sf::SocketUDP socket; sf::Packet packet; unsigned short port = 1000; unsigned short respPort = 1001; if(argc == 1) { //No arguments means program should retrieve scores and print them MakePacket(L"GetScores",L"",0.0f,packet); socket.Bind(respPort); socket.Send(packet,ip,port); char buffer[512]; // The buffer to store raw response data in sf::IPAddress respIP; // The ip address where the response came from size_t respSize; // The amount of data actually written to buffer // Now receive a response. This is a blocking call, meaning your program // will hang until a response is received. socket.Receive(buffer,512,respSize,respIP,port); socket.Close(); std::string respString(buffer,respSize); // Now lets turn the string back into JSON JSONValue * jsonHighScores = JSON::Parse(respString.c_str()); if(!jsonHighScores->IsObject()) { std::cout << "Something went wrong, not good."; return -1; } JSONObject root = jsonHighScores->AsObject(); if(root.find(L"Scores") != root.end() && root[L"Scores"]->IsArray()) { JSONArray scores = root[L"Scores"]->AsArray(); std::cout << "Current high scores:" << std::endl; for(int i = 0; i < scores.size();i++) { JSONObject curObj = scores[i]->AsObject(); std::wcout << "Name:" << curObj[L"Name"]->AsString(); std::cout << " High Score:" << curObj[L"Score"]->AsNumber() << std::endl; } } delete jsonHighScores; } else if(argc == 3) { MakePacket(L"AddScore", std::wstring(argv[1],argv[1] + strlen(argv[1])).c_str(), atof(argv[2]), packet); if(socket.Send(packet,ip,port) != sf::Socket::Done) { std::cout << "An error ocurred sending packet" << std::endl; } socket.Close(); } else { std::cout << "Invalid usage, proper format is player name then score, for example:" << std::endl; std::cout << "Or run with no arguments to get a list of scores returned" << std::endl; std::cout << "Scoreboard \"Player Name\" 42" << std::endl; return -1; } return 0; }

Click here to download Scoreboard.cpp

 

 

We have reorganized slightly to use a set of if/else’s based on the number of parameters passed in.  The common code between the two handled conditions has been moved to the method MakePacket(), which contains nothing new from last part.  It’s the section where argc == 1 ( which means there were no parameters specified, as argc at 1 represents the executables name ) that we are interested in.  If the user runs the application from the command line with no parameters, we want to fetch the high scores from the server.

 

 

The request process is the same, although we are passing the action GetScores instead.  One key difference is we Bind our port.  Think of this action as say “Yo!  This port is ours!”.  Only one application per computer can have access to a given port.  This is why we run our server on 1000, but then bind our response port on 1001, since client and server on running on the same machine.  Unlike when we add a score, for GetScores we want to listen for a response, which is what we do with socket.Receive().  Keep in mind, this action blocks, so your program wont be able to continue until this is done.  There are alternatives ( like Selector ) if this behavior is undesirable.

 

 

Now assuming Receive() worked correctly ( which in the Real World™ you shouldn’t!), buffer will be full of our JSON encoded string, while respSize will represent the amount of buffer that was actually used.  Using these two pieces of information, lets create a string from only the meaningful bits ( the rest of the buffer will be full of gibberish ).  We now turn our string back into JSON ( in production code, I would extend the JSON library to work with standard strings ), and check to see if it is a valid JSON object, error out if it’s not.  Now it’s a matter of parsing out the JSON into meaningful form.  Remember that a JSONObject is actually just a STL map of key/value pairs, so we find our array of type JSONArray named Scores.  Each item in that array is in turn another map, so we loop through them all find the value for “Name” and “Score”, turn them back into their native type and display them out to the console.  And that’s about it.

 

 

Now lets take a look at the server side of things.  Here we made much less invasive changes, so lets just look at what has changed.  To see the fully modified server.js click here.  I no doubt forgot a small change here or there, I always do!

 

 

First we add another condition to the switch statement as follows:

 

case "GetScores": console.log("Get Scores called"); // Turn highscores back into a JSON encoded string var highScoreAsString = JSON.stringify(highScores); // Create a buffer to hold that string var responseBuffer = new Buffer(highScoreAsString.length); // Write the string to the buffer responseBuffer.write(highScoreAsString); // Send it back to the client, using the addressing information // passed in via rinfo server.send(responseBuffer,0,responseBuffer.length, rinfo.port,rinfo.address, function(err, sent){ if(err) console.log("Error sending response"); else console.log("Responded to client at " + rinfo.address + ":" + rinfo.port ); }); break;

 

 

The code is pretty much documented, the nutshell version is, we turn our high score information into a JSON encoded string, make a buffer large enough for the string, copy the string to the buffer, then call send to the address and port the request came from, as defined in rinfo.

 

 

Then, mostly because we can, we use Node to create an extremely simple high score web server, so if you visit this site with your browser you can get a current list of high scores.  Look how laughably easy that task is!

 

 

// Now, lets show off a bit, with the worlds simplest web server. // Dynamically creates webpage showing current high scores. var webServer = require('http'); webServer.createServer(function(req,res){ res.writeHead(200, {'Content-Type': 'text/html'}); res.write("<html><body><h1>High Scores</h1><ul>"); for(i=0;i < highScores.Scores.length;++i) { res.write(highScores.Scores[i].Name + "&nbsp;&nbsp;" + highScores.Scores[i].Score + "<br />"); } res.write("</ul></body></html>"); res.end(); }).listen(80);

 

 

 

And… that’s it!  So now, lets take a look at things in action.  Open a command prompt and launch your server.js in node.  Then in another command prompt run scoreboard.exe, like this:

 

 

Here is using Scoreboard from the command line:

image

 

 

And here is the server handling these requests:

 

image

 

 

Finally, fire up a web browser and hit 127.0.0.1 ( assuming you don’t have another webserver running on your machine ):

 

image

 

 

It ain’t pretty, but it is a fully functioning high score server.  All you need to do is add a layer of security, harden things a bit with an iota of fault tolerance, pretty things up a bit and you are set.

 

 

As always, you can download the complete project zip here.

Programming ,

9. March 2012

 EDIT: DO not use the included binaries, instead look at the link from the Pang tutorial series to your right!

 

I have gotten a few emails or messages on this topic, enough so that I figure I will put together a post on the subject.  In my PANG! SFML C++ game tutorial, I show how to configure for debug builds but not release.

 

 

So, first and foremost, you are going to need to compile SFML 1.6 for release mode if you don’t already have the release libraries.  You can either download the SFML 1.6  source code here and compile them for release, in either DLL or static mode.  To do so, in Visual Studio 2010 after importing the project ( from SFML-1.6\build\vc2008 ) go ahead and delete the samples folder in the Solution Explorer. Then select the Build Menu, go to the Configuration Manager, then select the build you want to make:

 

Untitled

 

 

You either want to build Release DLL or Release Static, depending on if you are compiling static or dynamic.  If that is completely alien to you what the difference is, dynamic means you will ship the SFML Dll’s with your application, which will contain the SFML related code.  Static on the other hand, will compile all of the SFML related code into your EXE file, making it quite large but easier to distribute.  Either works, but if you ran into the dreaded ATI bug, static fixes it.  On the other hand, if you don’t use all parts of the SFML library, dynamic can be a whole lot smaller.

 

 

If you can’t be bothered building the DLLs, I have compiled them for you.  This archive contains the .lib/dll files for both static and dll versions of SFML compiled for Visual Studio 2010.

 

 

Now that you have the proper lib and dll files, the instructions are basically identical to this part of the tutorial.   The only thing you have to do different is, when in your projects properties before adding libraries, you need to select Release Build.

 

image

 

 

Also, when you specify the library names, you do not include –d at the end.  So for example, instead of adding sfml-system-d.lib, you add sfml-system.lib.  Repeat this for each library.

 

Now you should be able to build the release version of your game without problem.  This again is accomplished using Build->Configuration Manager, like so:

 

image

 

 

Simply switch to release rebuild and done.

Programming

28. February 2012

 

 

Picking up where our last section left off, by the end of this chapter you will be able to send a high score across the wire and process it in node.  In order to do this, we need to create a common format both our client and server understand.  In this particular case we are going to use JSON.  If you have done any recent web program, you are probably familiar with JSON already.  Basically JSON is a micro-format designed for transferring data on the web with a lighter footprint than XML.  All told it is a pretty simple format, here is the JSON we are going to use for storing high scores:

 

 

{ "Scores" : [ {"Name" : "Mike", "Score" : 2}, {"Name" :"Bob", "Score" : 14}, {"Name" :"Steve", "Score" : 12}, {"Name" :"John", "Score" : 10}, {"Name" :"Henry", "Score" : 8} ] }

Click here to download Highscores.txt

 

This JSON represents an object named “Scores” composed of an array of 5 objects that in turn are made up of a string field “Name” and a integer field “Score”.  Javascript and JSON go together like peanut butter and jam, but what about C++?  Well, you could encode your data into a string with very little effort ( one of the big advantageous of JSON ), but “little effort” is still effort, and I’m a lazy guy!  Therefore we are going to use an existing library.  I wanted a light weight and extremely simple JSON library, so I went with the aptly named SimpleJSON.  Installation really couldn’t be simple, just add the 4 cpp files ( 2 headers, 2 source ) to your project and you are done.

 

 

Now lets take a look at our SFML client.  It is going to be a simple command line utility for now, from a dos prompt simply pass in the name and high score as parameters, and it will send them across to the node server.  Lets take a look at Scoreboard.cpp:

 

#include "SFML/Network.hpp" #include "JSON.h" #include <iostream> int main(int argc, char* argv[]) { if(argc != 3) { std::cout << "Invalid usage, proper format is player name then score, for example:" << std::endl; std::cout << "Scoreboard \"Player Name\" 42" << std::endl; return -1; } sf::IPAddress ip("127.0.0.1"); sf::SocketUDP socket; sf::Packet packet; JSONObject data; data[L"action"] = new JSONValue(L"AddScore"); data[L"name"] = new JSONValue(std::wstring(argv[1],argv[1] + strlen(argv[1]))); data[L"score"] = new JSONValue(atof(argv[2])); JSONValue * val = new JSONValue(data); data.clear(); std::wstring dataString = val->Stringify(); delete val; std::string notSoWide; notSoWide.assign(dataString.begin(),dataString.end()); packet.Append(notSoWide.c_str(),notSoWide.length()); unsigned short port = 1000; if(socket.Send(packet,ip,port) != sf::Socket::Done) { std::cout << "An error ocurred sending packet" << std::endl; } socket.Close(); return 0; }

Click here to download scoreboard.cpp

 

 

One annoyance of the library I chose for JSON is it works with UTF-8 wide strings, but the string that we send we want encoded as standard ascii, so there is a bit of gunk as we create the JSON object using wide character strings, then after turning it into a JSON string, we encode it back to ascii.  Otherwise the code is quite straight forward.

 

 

First we verify we got the proper number of command line arguments, declare our various SFML and JSON related variables.  We are setting the ip address to 127.0.0.1, which is the loopback address, or the equivalent of saying “this machine”.  Next we build up our JSON string.  If you have worked with XML before, the process will be very familiar. We create a JSONObject named data, which is essentially a map of key value pairs of other JSONValues.  When then populate it with our data, then in turn use it as the parameter in creating a new JSONValue.  All the heavy lifting is done in JSONValue’s constructor.  Stringify() is the method that does the actual re-encoding returning a std::wstring.  Of course, we actually want a std:string, so we create one.  Obviously in time sensitive code, we would alter SimpleJSON to use std::string instead.  Our end result is a JSON string that looks like this:

 

{"action":"AddScore","name":"Bob Dole","score":23}

 

 

Now that we have our data in JSON encoded string format, it’s time to send it.  We simply append our string data to our packet and send it using our Socket.  If an error occurs, report it.  Otherwise, we are done, close our Socket and exit.  If you strip away all the wide character string annoyances, the process is actually quite straight forward.

 

 

Now lets take a look at the Node side of things.  The code is fairly long, so instead of walking through it I have simply commented it.  If you have any questions not covered by the comments, fire away.  So here is the contents of Server.js

 

var dgram = require('dgram'), fileSystem = require('fs'), highScores, server; //Load high scores from file fileSystem.readFile("HighScores.txt", 'utf8', function(err,data){ if(err){ //Error occurred loading file, spit out error message then die console.log("Error occurred loading file"); process.exit(); } console.log("Loading high scores from file"); try{ // use JSON to turn file contents back into a Javascript array object highScores = JSON.parse(data); }catch(e) { // Exception occurred parsing file contents as JSON, error out and die. console.log("Exception occured parsing data"); process.exit(); } // Now sort the high scores by score, high to low highScores.Scores.sort(function(a,b){ return b.Score - a.Score; }); // Display the sorted high scores to the console console.log(highScores); }); //Alternative way to read file in NodeJS //file.on("error",function(exception){ // process.exit(); // } //); //file.on("data",function(data){ // fileData = data; // } //); //file.on("close",function(){ // highScores = JSON.parse(fileData); //}); //Create a UDP socket server = dgram.createSocket('udp4'); console.log("Socket created"); // Add a handler for incoming traffic on the socket. This will be called each time something connects to the socket server.on("message",function (msg,rinfo) { //console.log(parseInt(msg).toString()); console.log(rinfo); // SFML sends two packets, one with the size of the following packet ( as a 4 byte number ) // We don't need it, so as a crude-hack, we ignore any 4 byte packets if(rinfo.size != 4) { console.log("Received message:" + msg.toString()); // Socket data comes in as a JSON encoded array of objects, turn back into a JS object var jsonData,i; try{ jsonData = JSON.parse(msg); } catch( exception ) { console.log("Invalid JSON request received"); return; // Non lethal error, just stop processing packet } // The action parameter determines what you should do with this packet switch(jsonData.action) { // action==AddScore, add the score to the highscore array if it's higher than an existing score case "AddScore": console.log("AddScore called\n"); // Make sure highscore has been initialize... order can be a weird thing in node if(highScores != undefined){ // Loop through current highScores ( which should be sorted ) // and insert score if a lower match found for(i=0;i < highScores.Scores.length;++i) { if(highScores.Scores[i].Score < jsonData.score){ highScores.Scores.splice(i,0,{"Name" : jsonData.name, "Score" : jsonData.score}); console.log("Inserted highscore by: " + jsonData.name); break; // match found, stop looping } } } // Display newly created highscore array console.log(highScores.Scores); break; } } // // }); // Called when socket starts listening for packets. besides logging, currently serves no purpose server.on("listening", function () { var address = server.address(); console.log("server listening " + address.address + ":" + address.port); }); // Finally, bind the server to port 1000. 1000 was randomly chosen. Think of this as saying GO! // Now we are listening for UDP connections on port 1000 server.bind(1000);

Click here to download server.js

 

 

Now start the server at the command line ( node server.js ) and run the client from a different command line.  It should look like this:

 

image

 

 

As you can see, we are successfully sending data from SFML over a socket to our node based server.  In the next part, we will look at sending data the other way.

 

 

You can download the complete project right here.  The scripts are in a sub-folder named NodeJS.

 

 

Click for part 3

Programming ,

13. February 2012

 

On the PlayN discussion forum PlayN 1.1 was just announced.  Complete release notes are available here.

 

 

Key new features include:

  • add HTML Canvas backend ( in addition to the WebGL and deprecated DOM HTML5 back ends ) as a fallback for browsers that don’t support WebGL *cough* Internet Explorer *cough*
  • iOS support… probably the biggie of this release
  • removed GAE dependencies ( this was a pain in the butt previously )
  • Android properly supporting mp3
  • various other bug fixes

 

As was mentioned in this earlier post iOS support isn’t complete, yet.

 

 

Good job PlayN team!

News, Programming

8. February 2012

 

The last week or so I’ve been looking at using Node for server side game programming and I have been quite impressed.  I have found a few things quite frustrating ( the documentation is extremely sparse and tooling support is… iffy ) but code wise everything has worked about exactly how I expect it.

 

That said, I ran into my first glaring bug and it is an annoying one.  In my current code, I want to write out some memory to file when my code exits, I imagine a fairly common event.  At first it seems like a remarkably simple thing to do.  Every node application has a process object, and process has an exit event.  Therefore handling on exit should be as simple as:

 

process.on('exit', function () { //handle your on exit code console.log("Exiting, have a nice day"); });

 

*Should* being the operative word.  Reality is, this doesn’t work.  The event simply does not fire on CTRL+C.   Hmmm.  So I decide to throw process.exit() at the end of my script and BAM, the on exit event is fired.  So it appears that contrary to the documentation, the CTRL+C event doesn’t raise an exit event, or so I thought.

 

Alright, no big deal, I’ll just trap the SIGINT event raised from CTRL+C and handle exit then.  Ohh… that didn’t work out well.

 



C:\Users\Mike\workspace\NodeDev\src>node server.js

node.js:218
        throw e; // process.nextTick error, or 'error' event on first tick
              ^
Error: No such module
    at EventEmitter.<anonymous> (node.js:403:27)
    at Object.<anonymous> (C:\Users\Mike\workspace\NodeDev\src\server.js:5:9)
    at Module._compile (module.js:434:26)
    at Object..js (module.js:452:10)
    at Module.load (module.js:353:32)
    at Function._load (module.js:310:12)
    at Array.0 (module.js:472:10)
    at EventEmitter._tickCallback (node.js:209:41)



Hmmm…

 

So off to Google I go!  After a few minutes I discover this tidbit.  Basically it’s a known issue, SIGINT isn’t raised on Windows and they claim it isn’t possible, which considering Python handles it just fine, I find a bit difficult to believe.  So, it is a currently unsolved issue, but hopefully it will be resolved in the future.

 

Considering that CTRL+C is the defacto way to shut down Node, I really hope this is resolved soon.  In the meanwhile, does anyone out there know an effective workaround?

 

 

Update

 

Stack Overflow came to my rescue on this issue.  User pimvdb posted this work around:

 

var tty = require("tty"); process.openStdin().on("keypress", function(chunk, key) { if(key && key.name === "c" && key.ctrl) { console.log("bye bye"); process.exit(); } }); tty.setRawMode(true);

 

It feels like a bit of a hack, but then, I suppose it is!  Hopefully this issue gets fixed in node eventually, but for now, here is an effective workaround.

Programming

Month List

Popular Comments