A couple of weeks ago, I experimented with using bluetooth to communicate hidden information from a game to a phone held by the players. While the prototype worked, there were issues with client side logic and lack of support for iOS.
Another way to achive the goal of displaying hidden information on the phone would be to create a web page that the phone could access. In the past, we have done this by having the game state in a MySQL database and php scripts to access the data. The game server and clients would poll the data watching for updates and sending their moves. This system was cumbersome, laggy and prone to error.
However, the web page is a nice way to provide the data to the clients. It is easy to layout a good looking interface, you can use javascript to allow the player to interact with the game and all phones have a web browser. The problem lies in the communication back to the C++ game.
One solution to this problem would be to use web sockets to send data back and forth between the clients and the game. Ideally, the C++ game would serve out the web pages that the clients display too so that people buying the game wouldn’t have to know how to setup a web server.
Mongoose is a simple web server with web socket support that can be compiled into a C++ application. With a couple of minor tweaks I got the mongoose code integrated into Torque.
Adding web sockets was a bit more problematic. The support for raw web socket connections comes with Mongoose, but I needed to write the code to send and receive the messages in the format expected by the chrome WebSocket class.
This code is surprisingly messy. The first byte is always 0x81. The second byte is a length, but the most significant bit is an indicator of whether there is a mask. If that second byte is less than 126 it is just the length of the data. If it is equal to 126 then the next two bytes are the length if it is equal to 127, then the next four bytes are the length. What is even stranger is that if the mask is set, then all the data sent is XORed with the a prior byte. Here is the code I ended up with to read a message:
// Read the message from the client // For now we will assume that this is a string of text // Read in the header and size unsigned char header[10]; int totalRead = 0; while ( totalRead < 2 ) { int bytesRead = mg_read(conn, header+totalRead, 2-totalRead); if ( bytesRead <=0 ) return nullptr; totalRead += bytesRead; } // Check the data received if ( header[0] != 0x81 ) return nullptr; long long messageLength = header[1] & 127; int maskLength = (header[1] & 128) ? 4 : 0; if ( messageLength == 126 ) // Large message { totalRead = 0; while ( totalRead < 2 ) { int bytesRead = mg_read(conn, header+totalRead, 2-totalRead); if ( bytesRead <=0 ) return nullptr; totalRead += bytesRead; } messageLength = (((int) header[0]) << 8) + header[1]; } else if ( messageLength > 126 ) // Very large message { totalRead = 0; while ( totalRead < 8 ) { int bytesRead = mg_read(conn, header+totalRead, 8-totalRead); if ( bytesRead <=0 ) return nullptr; totalRead += bytesRead; } messageLength = (((long long) htonl(*(int*)&header[0])) << 32) | htonl(*(int*)&header[4]); } // Now read the data unsigned char* buf = new unsigned char[messageLength+maskLength]; totalRead = 0; while ( totalRead < messageLength+maskLength ) { int bytesRead = mg_read(conn, buf+totalRead, messageLength+maskLength-totalRead); if ( bytesRead <=0 ) { delete[] buf; return nullptr; } totalRead += bytesRead; } char* message = new char[messageLength+1]; for ( int i=0; i<messageLength; ++i ) { int xor = (maskLength==0 ? 0 : buf[i%4]); message[i] = buf[i+maskLength] ^ xor; } message[messageLength] = '\0'; delete[] buf; return message;
With Mongoose integrated and web sockets working, our Torque games can communicate in real-time with web based clients. And since Awesomium is also integrated into our engine, the same web page can be viewed by the players sitting at the touch table. We needed to make a couple improvements to Awesomium so that people could actually interact with the web page:
- To get TUIO and mouse events to the web page, Awesomium requires that touch/mouse events be injected into its system. The mouse events were straight-forward, but the touch event requires a list of new, changed and unchanged touch points every time a touch is added, removed or moved.
- To allow a user to enter data into a web page, we need to provide a virtual keyboard and inject keyboard events into Awesomium
Cool stuff! Now we just need to setup a VPN or port forwarding so I can play from my computer at home! 🙂