Skip to content

Hello World C++

Andy edited this page May 26, 2019 · 8 revisions

Hello World C++

99% of everything you need to know about creating games on the XAYA blockchain gaming platform is covered in this tutorial. Only a few topics aren't covered, such as game logic.

While this is long, it is thorough and you will learn many core concepts:

  1. Starting xayad with the proper options
  2. Starting your game daemon with the proper options
  3. Working with daemons (xayad and libxayagame)
  4. Calling libxayagame
  5. Getting a game state
  6. Making a move

All of this will become clear as we build and run a concrete implementation.

In this tutorial we're going to build a Hello World console application in C++ from scratch. Once we're finished, we'll run it, check its game state, then make a move and say hello to everyone. This involves several major steps.

  1. Write our Hello World code
  2. Compile libxayagame so that we can include it in our executable
  3. Compile our Hello World game with libxayagame
  4. Sort out the extra dependency files that we need
  5. Start xayad and Hello World with the proper options
  6. Check the game state
  7. Make a move and say hello!

Our application will incorporate libxayagame and will run as a daemon. In another command prompt or terminal, we'll send moves to the game via JSON RPC and retrieve the results as serialised JSON. Our sending and receiving there mimics a "front end" as it were.

What Does Hello World Do?

The Hello World game that you're about to write does 1 simple thing: give people a way to say "Hello!" and then return a list of the last message that they sent. No more. No less.

Download the Code

All the code for Hello World is available for you as a finished product here. You can either write the code yourself as we progress, or you can open up the code in your editor and follow along.

All Hello World tutorial code (and precompiled dependencies for Windows) is available in the XAYA Tutorial Code repository. Please download it from there.

The repository contains some extra files/scripts to help with compilation and running.

Create helloworld.cpp and Add Includes

Open up your text editor and create a helloworld.cpp file. Next, add in these includes:

#include <xayagame/defaultmain.hpp>

#include <gflags/gflags.h>
#include <glog/logging.h>

#include <json/json.h>

#include <cstdlib>
#include <iostream>
#include <sstream>

"xayagame/defaultmain.hpp" adds in libxayagame. This is the core daemon that processes game states for you.

The "gflags/gflags.h" and "glog/logging.h" includes are for command line option processing and the Google logging module. You can find them here and here, respectively. Don't get hung up on these though. They're just libraries that we'll be using to make our lives easier.

We've chosen to use JSON RPC, and "json/json.h" gives us that.

The other includes are simply standard libraries.

Create an Anonymous Namespace

Our game logic and connection variables will be in an anonymous namespace, so go ahead and create that.

namespace { }

Now we can add code to our anonymous namespace.

Define Connection Variables

Hello World will run as a daemon, and in order for us to connect to our Hello World daemon, we need several options. We'll pass these options to the daemon as command line arguments when we run it.

  • xaya_rpc_url: URL at which Xaya Core's JSON-RPC interface is available
  • game_rpc_port: The port at which the game daemon's JSON-RPC server will be start (if non-zero).
  • enable_pruning: If non-negative (including zero), enable pruning of old undo data and keep as many blocks as specified by the value
  • storage_type: The type of storage to use for game data (memory, sqlite or lmdb)
  • datadir: The base data directory for game data (will be extended by the game ID and chain); must be set if --storage_type is not memory

We're using the Google Flags library (gflags) for these as it greatly simplifies our code. It provides various "DEFINE_xxx" methods. Each has 3 parameters:

  1. Variable name
  2. Variable value
  3. Comment about the variable, i.e. documentation

Go ahead and create the variables listed above as shown below. You can copy and paste that into your helloworld.cpp file under the includes.

DEFINE_string (xaya_rpc_url, "http://127.0.0.1:8396", "");
DEFINE_int32 (game_rpc_port, 29050, "");
DEFINE_int32 (enable_pruning, -1, "");
DEFINE_string (storage_type, "memory", "");
DEFINE_string (datadir, "", "");

Next, change xaya_rpc_url and game_rpc_port if required by your environment. Keep in mind that we'll be using those values throughout this tutorial, so if you change them, you must maintain consistency.

Leave the others as they are. We aren't using a data directory in this example, so blank is fine.

Create the HelloWorld Class

Our HelloWorld class contains all the game logic. In our main method, we'll create an instance of HelloWorld and pass that to libxayagame. libxayagame will understand everything in HelloWorld, process it, and return values for us, i.e. new game states.

The HelloWorld class inherits from xaya::CachingGame. CachingGame is an easy way to create simple games. Its main advantage is that we don't need to create any undo data, which we'll look at in a future tutorial. Let's add the HelloWorld class now.

class HelloWorld : public xaya::CachingGame
{
	protected:
}

We're going to add 3 methods to this class.

  1. GetInitialStateInternal: Sets the starting point on the blockchain
  2. UpdateState: Receives the previous game state and updates it
  3. GameStateToJson: A simple helper method

Write the GameStateToJson Method

GameStateToJson is the simplest method, so let's add it to our HelloWorld class first.

While we may not strictly need this method in our Hello World game, it's nice to have. Copy and paste it into our HelloWorld class.

GameStateToJson (const xaya::GameStateData& state) override
{
	std::istringstream in(state);
	Json::Value jsonState;
	in >> jsonState;
	return jsonState;
}

The GameStateToJson method overrides the GameStateToJson method in libxayagame's xaya::CachingGame class.

In there we get our GameStateData into a string buffer, then store it in a JSON value and return the JSON. This makes dealing with our GameStateData easier to handle because it's now just a big bunch of key/value pairs stored in JSON.

Write the GetInitialStateInternal Method

Next, our GetInitialStateInternal is similarly quite easy. It decides which chain to run on and which block to start at. It's only ever used once, but it's critical to get it right. We don't want to start at block 1 because then we need to look at every block before the game starts.

Start writing that method as shown below.

xaya::GameStateData
GetInitialStateInternal (unsigned& height, std::string& hashHex) override
{ }

Again, GetInitialStateInternal overrides the GetInitialStateInternal method in libxayagame's xaya::CachingGame similar to how GameStateToJson did above.

Choose Which Chain to Run On

In GetInitialStateInternal we'll decide which chain our Hello World game will run on, i.e. mainnet, testnet, or regtest. We'll use libxayagame's xaya::Chain enumeration to choose. Add in a switch case block as shown below.

switch (GetChain ())
{
	case xaya::Chain::MAIN:
	break;

	case xaya::Chain::TEST:
	break;

	case xaya::Chain::REGTEST:
	break;

	default:
	LOG (FATAL) << "Invalid chain: " << static_cast<int> (GetChain ());
}

The GetChain method detects which chain we're on, i.e. mainnet, testnet, or regtest. We'll actually decide what chain we want to run on at runtime when we pass in a command line argument for xaya_rpc_url. Here are some possibilities for mainnet, testnet, and regtest, respectively:

With that decided, we set the height and hashHex values.

The height is the block height that we want our game to start at. This should be the highest possible block height that is prior to any moves being made in our game. There's no sense in processing blocks with no game data in them.

The hashHex is the block hash for that block. You can look up the block hash at https://explorer.xaya.io/. We're going to start our game at block 555,555.

Go ahead and add that to the mainnet case as shown below.

case xaya::Chain::MAIN:
	height = 555555;
	hashHex
	  = "ce6a6ae43103db943a74294b90906de9bb873d602f2881ddb3eb7a9f0e626312";
	break;

You can fill in the values for testnet and regtest or leave them blank if you wish as we won't be using them. In a real game, you would have to fill in those values because you would need to use testnet and regtest during development. We'll look at testnet and regtest in other tutorials.

Since the initial state is only run once, and we need to have a valid value for our GameStateData, return a simple, valid JSON string after the switch case block as shown below.

return "{}";

That's the end of GetInitialStateInternal. We now turn our attention to the meaty goodness of processing "moves" in the game.

Write the UpdateState Method

The UpdateState method iterates over the various players in our game and creates a game state. Game states are representations of what the game world looks like at a particular "time" or block height. (See Time on the Blockchain for more information about that.)

Wire up your UpdateState method as shown below.

xaya::GameStateData
UpdateState (const xaya::GameStateData& oldState,
    const Json::Value& blockData) override
{ }

Notice again that UpdateState overrides the UpdateState method in libxayagame's xaya::CachingGame.

Get the Previous Game State

We need 1) the game state from the previous block, and 2) the new moves in order to process our game logic and create a new game state. We'll just use the new moves raw as they're already delivered to us as a Json::Value&.

Get the previous game state (oldState), store it in a string buffer, and then put it into a Json::Value so that we can use it easily.

Type or copy and paste the following into your UpdateState method.

std::istringstream in(oldState);
Json::Value state;
in >> state;

We now have our previous game state stored in state.

Iterate Over the Moves

We need to look at all the new moves in the new block data. Our blockData will contain JSON similar to the following.

{
  "block": {
	"hash": "dda7eccde4857742e5000bd66cf72154ce26c22876582654bc8b8d78dadbce8c",
	"height": 558369,
	"parent": "18f72c91c7b9223e9c7d0525216277e4016d748a2c81be4ba9d4a2b30eaed92d",
	"rngseed": "b36747498ce183b9da32b3ab6e0d72f2a17aa06859c08cf1d1e91907cb09dddc",
	"timestamp": 1549056526
  },
  "moves": [
	{
	  "move": {
		"m": "Hello world!"
	  },
	  "name": "ALICE",
	  "out": {
		"CMBPmRos5QADg2T8kvkQhMaMV5WzpzfedR": 3443.7832612
	  },
	  "txid": "edd0d7a7662a1b5f8ded16e333f114eb5bea343a432e6c72dfdbdcfef6bf4d44"
	}
  ],
  "reqtoken": "1fba0f4f9e76a65b1f09f3ea40a59af8"
}

The "moves" node is an array. We are only interested in the "move" and the "name".

Go ahead and wire up a for statement to iterate over all the "moves" in our blockData.

for (const auto& entry : blockData["moves"])
{

}

As mentioned above, we're really only interested in "name" and "move". Let's get them from our blockData into a string and an auto&.

const std::string name = entry["name"].asString ();
const auto& mvData = entry["move"];

Check for Errors

The "name" is simple enough, but we don't know anything about what the "move" is.

We know what a valid move should look like, but we don't know what THIS move is, so we must do some error checking.

Check to see if we have a valid object. If it's not valid, then we log ourselves a message and go back to the top of our for loop.

Go ahead and add some code to do that or copy and paste the following into your UpdateState method.

if (!mvData.isObject ())
{
	LOG (WARNING)
		<< "Move data for " << name << " is not an object: " << mvData;
	continue;
}

Get the Hello World Message

At last we've reached the point where we can get the actual messages that people are sending.

If you recall from the JSON above, our "move" data looks like this:

"move": { 
        "m": "Hello world!"
        }

So we can access the message through "m".

Go ahead and store it in a variable.

const auto& message = mvData["m"];

Check the Message for Errors

Anyone can submit a move into the XAYA blockchain, and there's no guarantee that a move will be valid. Let's check to make certain that we have a string and not something else.

Go ahead and write some error checking code, or copy and paste the following.

if (!message.isString ())
  {
	LOG (WARNING)
		<< "Message data for " << name << " is not a string: " << message;
	continue;
  }

Next, we'll update the game state.

Update the Game State

At this point, we know that we have a valid string. We now use the player's name as a key for our game state (state) and we assign its value as our message from above.

state[name] = message.asString ();

You've just updated the game state. Time to return it.

Return the Game State

With our game state completely updated, we can return it. Create an output string buffer to store our game state in, and then return it as a string.

Write that code on your own or copy and paste the following.

std::ostringstream out;
out << state;
return out.str ();

CONGRATULATIONS! We're finished our HelloWorld class and can move on to main method!

Write the main Method

Our main method will be written outside of the anonymous namespace. Wire it up as usual.

int main (int argc, char** argv)
{ }

Set Logging

If you remember the glog include way up above, we're going to start using that now. Add logging as shown below.

google::InitGoogleLogging (argv[0]);

argv[0] is the FLAGS_xaya_rpc_url URL, so glog will send output to libxayagame. (Remember from above that we're going to compile libxayagame directly into our Hello World game.)

Set Flags

We must also set our flags. If you remember from above, we included gflags. We'll use that to parse command line arguments into our flags, i.e.:

  • FLAGS_xaya_rpc_url
  • FLAGS_game_rpc_port
  • FLAGS_enable_pruning
  • FLAGS_storage_type
  • FLAGS_datadir

You can do that manually, or you can use gflags. Go ahead and write code to parse command line arguments for our flags, or copy and paste in the following code.

  gflags::SetUsageMessage ("Run HelloWorld game daemon");
  gflags::SetVersionString ("1.0");
  gflags::ParseCommandLineFlags (&argc, &argv, true);

The flags we listed above are now populated with the appropriate values.

Check for Errors

We must check for errors in our flags.

We must have a correct RPC URL. You can write code to do thorough error checking or copy and paste the following check into your main method.

if (FLAGS_xaya_rpc_url.empty ())
{
  std::cerr << "Error: --xaya_rpc_url must be set" << std::endl;
  return EXIT_FAILURE;
}

libxayagame can use 3 different types of storage:

  • Memory
  • SQLite
  • lmdb

Memory doesn't require a data directory, but the other 2 do. Let's check for an error there. The strings for each are as above, but lower case.

Write some code to check or copy and paste the following code into your main method.

if (FLAGS_datadir.empty () && FLAGS_storage_type != "memory")
{
  std::cerr << "Error: --datadir must be specified for non-memory storage"
			<< std::endl;
  return EXIT_FAILURE;
}

Wire Up and Set a Daemon Configuration

libxayagame expects a daemon configuration. Copy and paste the following into your main method.

xaya::GameDaemonConfiguration config;

We'll fill the configuration with data from our flags. Write code to fill the flags or copy and paste the following into your main method.

config.XayaRpcUrl = FLAGS_xaya_rpc_url;
if (FLAGS_game_rpc_port != 0)
{
  config.GameRpcServer = xaya::RpcServerType::HTTP;
  config.GameRpcPort = FLAGS_game_rpc_port;
}
config.EnablePruning = FLAGS_enable_pruning;
config.StorageType = FLAGS_storage_type;
config.DataDirectory = FLAGS_datadir;

RPC Server Type

NOTE: The configuration requires an RPC server type but we didn't have an option set for it. It is set as follows.

config.GameRpcServer = xaya::RpcServerType::HTTP;

In your own game you can use TCP, i.e. xaya::RpcServerType::TCP. However, in Hello World we use HTTP.

Instantiate Your HelloWorld Class

The HelloWorld class is all the game logic. It's time to put it to work. Create an instance of it now.

HelloWorld logic;

libxayagame Startup Checklist

We need 3 things to start libxayagame:

  1. A daemon configuration
  2. A game name
  3. Game logic

We created the daemon configuration through the command line options that we parsed into flags above in Wire Up and Set a Daemon Configuration.

Our game name is "helloworld".

Our game logic is our HelloWorld class that we named logic.

Start libxayagame

Copy and paste the following at the end of your main method.

const int res = xaya::DefaultMain (config, "helloworld", logic);
return res;

Connecting to libxayagame is a blocking operation, so return res; will never be reached.

CONGRATULATIONS! You can now compile and run your Hello World daemon.

Compile Hello World

Compiling Hello World has quite a few requirements, and for some people this is probably the most difficult part.

Our Hello World needs libxayagame to work. For that, we must compile libxayagame.

Once that's done we can compile Hello World.

Compiling libxayagame

There are separate tutorials to show you how to compile libxayagame. They have all the scripts and instructions needed.

ON WINDOWS: Finish the How to Compile libxayagame tutorial then return back here. If all goes well, you can complete that tutorial in a few minutes as it is very short.

ON LINUX OR MAC OS X: Consult the How to Compile libxayagame in Ubuntu document for how to build libxayagame.

Compiling Hello World

Now that you have libxayagame built and available, let's compile Hello World.

Compiling Hello World on Windows

  1. Open up an MSYS2 MingGW 64-bit terminal

  2. Create a new folder for Hello World:

    C:\msys64\home<username>\hello world\

    In MSYS2 that is:

    \home<username>\hello world\

  3. Copy in all the files for it, i.e.:

    • helloworld.cpp <⸺ mandatory
    • build.sh <⸺ mandatory
    • hellotest.py <⸺ optional
    • run-regtest.sh <⸺ optional
    • run-mainnet.sh <⸺ optional
  4. Run build.sh as follows:

    ./build.sh

  5. Done.

Our build.sh script compiled Hello World as the "hello" executable file.

Compiling Hello World on Linux or Mac OS X

  1. Open up a terminal

  2. Navigate to your Hello World folder

  3. Run build.sh as follows:

    ./build.sh

  4. Done.

Trouble Shooting

If you get an error running build.sh, in your terminal run the following 2 commands, i.e. the contents of the file.

packages="libxayagame jsoncpp libglog gflags libzmq openssl"

g++ hello.cpp -o hello -Wall -Werror -pedantic -std=c++14 -DGLOG_NO_ABBREVIATED_SEVERITIES `pkg-config --cflags ${packages}` `pkg-config --libs ${packages}` -pthread -lstdc++fs

That will build the hello executable file for you.

Copy Dependencies

If you're on Windows, our Hello World executable file, hello, won't run quite yet. libxayagame is built into it, but libxayagame has many dependencies.

If you're on Linux or Mac OS X, you already installed all the dependencies when you built libxayagame above in Compiling libxayagame, so no further action is required from you at this point. You can skip down to Run Hello World and continue.

Copy Dependencies on Windows

libxayagame has a long list of dependencies. See List of Dependencies for libxayagame in Windows for those.

You can also find them in the "Required dependencies.txt" file. For the sake of expediency, you can run the "copy-dependencies.bat" batch file from a Windows command prompt in the Hello World build folder to copy them quickly.

With the dependencies copied you can now run Hello World.

Run Hello World

We're going to need 3 command prompts or terminals.

  1. To run xayad
  2. To run hello
  3. To execute RPC commands with xaya-cli and curl

hello and daemons

Open them up when they are required and navigate to the appropriate paths.

Run xayad with Proper Options

To run xayad properly configured for games like our Hello World game, run it with the following options.

  • -rpcuser=user
  • -rpcpassword=password
  • -wallet=game.dat
  • -server=1
  • -rpcallowip=127.0.0.1
  • -zmqpubhashtx=tcp://127.0.0.1:28332
  • -zmqpubhashblock=tcp://127.0.0.1:28332
  • -zmqpubrawblock=tcp://127.0.0.1:28332
  • -zmqpubrawtx=tcp://127.0.0.1:28332
  • -zmqpubgameblocks=tcp://127.0.0.1:28332

Or, all on 1 line:

-rpcuser=user -rpcpassword=password -wallet=game.dat -server=1 -rpcallowip=127.0.0.1 -zmqpubhashtx=tcp://127.0.0.1:28332 -zmqpubhashblock=tcp://127.0.0.1:28332 -zmqpubrawblock=tcp://127.0.0.1:28332 -zmqpubrawtx=tcp://127.0.0.1:28332 -zmqpubgameblocks=tcp://127.0.0.1:28332

NOTE: You can change rpcuser name and the rpcpassword. Also, in this example we're using the game wallet. It resides in the "game.dat" folder in the data directory. If you wish to use the main wallet in the data directory folder, you can simply delete "-wallet=game.dat" and xayad will default to it.

In a command prompt, navigate to the folder where you have xayad (it's included with the XAYA QT wallet download and in the same folder as xaya-cli). Start xayad as shown below. (Remember to add "./" at the beginning in Linux or OS X.)

xayad -rpcuser=user -rpcpassword=password -wallet=game.dat -server=1 -rpcallowip=127.0.0.1 -zmqpubhashtx=tcp://127.0.0.1:28332 -zmqpubhashblock=tcp://127.0.0.1:28332 -zmqpubrawblock=tcp://127.0.0.1:28332 -zmqpubrawtx=tcp://127.0.0.1:28332 -zmqpubgameblocks=tcp://127.0.0.1:28332

xayad will immediately begin scrolling information and adding more as blocks come in.

xayad

We'll see other output from our Hello World game (technically, its from libxayagame) below when we get the current game state with curl.

Hello World Needs Proper Options/Flags

Our Hello World daemon needs several options/flags to be set. Recall from Set Flags above that we have 5 flags that we set:

  • xaya_rpc_url
  • game_rpc_port
  • enable_pruning
  • storage_type
  • datadir

We set the enable_pruning option independently of any command line options. So, we must pass in the other 4 when we run Hello World.

The value for our xaya_rpc_url depends upon how we started xayad. It takes this form:

http://user:password@host:port

The user and password must be set or we won't be able to do many things. Above we ran xayad with "user" and "password", so those are fine in the URL above.

The host should be the local host or 127.0.0.1.

http://user:password@127.0.0.1:port

The port depends upon which chain we want to run. Recall from Choose Which Chain to Run On that the port number for mainnet, testnet and regtest are 8396, 18396 and 18493, respectively. We're going to run on mainnet.

http://user:password@127.0.0.1:8396

That completes our RPC URL.

So far to run hello, we have:

hello --xaya_rpc_url="http://user:password@127.0.0.1:8396"

We set our game RPC port to 29050. This could be any free port and is completely arbitrary. This is our command so far.

hello --xaya_rpc_url="http://user:password@127.0.0.1:8396" --game_rpc_port=29050 

Hello World is very simple and doesn't require a database, so we set storage to "memory".

hello --xaya_rpc_url="http://user:password@127.0.0.1:8396" --game_rpc_port=29050 --storage_type=memory 

We don't need to set a data directory if we're not using SQLite or lmdb, but let's just set one anyways.

hello --xaya_rpc_url="http://user:password@127.0.0.1:8396" --game_rpc_port=29050 --storage_type=memory --datadir=/tmp/xayagame

Our options/flags to run hello are complete.

Running Hello World

Navigate in a command prompt to the hello executable.

Regtest

This is the network that you would use for development. It is here for informational purposes. You should likely run on mainnet.

Running on the regtest chain will look like this:

./hello --xaya_rpc_url="http://user:password@localhost:18493" --game_rpc_port=29050  --storage_type=memory --datadir=/tmp/xayagame

Or on Windows like this:

hello.exe --xaya_rpc_url="http://user:password@localhost:18493" --game_rpc_port=29050 --storage_type=memory --datadir=/tmp/xayagame

Mainnet

Real games run on mainnet. You should use this to see actual, real-world results.

Running on mainnet will look like this (these are the options we created above):

./hello --xaya_rpc_url="http://user:password@localhost:8396" --game_rpc_port=29050  --storage_type=memory --datadir=/tmp/xayagame

Or on Windows like this:

hello.exe --xaya_rpc_url="http://user:password@localhost:8396" --game_rpc_port=29050 --storage_type=memory --datadir=/tmp/xayagame

Real-world Hello World

Run one of those now.

Hello World on Windows:

Screenshot of Hello World

Hello World on Linux:

Screenshot of Hello World

The output you can see in the screenshot above happens whenever a new move is made. We didn't add this to our code, but you can add that hello daemon output as we've done in the screenshot like this:

// Some output is nice.
std::cout << name << " said " << message << "\r\n";

Put that just before you store the player's message in the game state.

CONGRATULATIONS! Hello World is running!

Our next tasks are to check the state of the game with curl and make a move with xaya-cli.

Trouble Shooting

If you're not getting any results, it is likely that you do not have xayad running.

See the First Steps tutorial and Running xayad for Games for information on how to run xayad properly.

Alternatively, you can run the XAYA Electron wallet as it is preconfigured to run with all the proper options set for games, such as our Hello World example.

A Quick Note About Front Ends

Our Hello World game is running as a daemon, and we don't have a front end or GUI.

Below we'll use curl and xaya-cli to mimic a front end.

We'll use curl to display results, and we'll use xaya-cli to make moves.

With what we have so far, you can easily create a real GUI for yourself using any technology or language or toolkit that you prefer. You could do it with Unreal Engine, Unity, Windows Forms, WPF, or anything. You only need to be able to send RPC commands to the hello daemon (for game states to update your GUI) and xayad (to make moves).

See What People Are Saying with GetCurrentState

To get the game state and see what people are saying, we must issue an RPC command to the Hello World daemon (technically, it's libxayagame that will return our results, but we built libxayagame into the hello daemon). We can use curl for that. Note that we must use JSON 2.0.

curl --data-binary '{"jsonrpc": "2.0", "id":"curltest", "method": "getcurrentstate"}" -H content-type: text/plain;' http://127.0.0.1:29050/

Or on Windows:

curl --data-binary "{\"jsonrpc\": \"2.0\", \"id\":\"curltest\", \"method\": \"getcurrentstate\"}" -H "content-type: text/plain;" http://127.0.0.1:29050/

Open up a new command prompt or terminal and try that now.

Our result is JSON and will be similar to the following.

{"id":"curltest","jsonrpc":"2.0","result":{"blockhash":"cd8235ac63697d20732d65bd9d49bcf8107f24d4a4bc32f83e93786dd8276c6a","chain":"main","gameid":"helloworld","gamestate":{"ALICE":"","BOB":"HELLO WORLD!","Crawling Chaos":"...Hello, ALICE!","Wile E. Coyote":"Hello Road Runner!"},"height":610662,"state":"up-to-date"}}

Or prettified:

{
  "id" : "curltest",
  "jsonrpc" : "2.0",
  "result" : {
    "blockhash" : "cd8235ac63697d20732d65bd9d49bcf8107f24d4a4bc32f83e93786dd8276c6a",
    "chain" : "main",
    "gameid" : "helloworld",
    "gamestate" : {
      "ALICE" : "",
      "BOB" : "HELLO WORLD!",
      "Crawling Chaos" : "...Hello, ALICE!",
      "Wile E. Coyote" : "Hello Road Runner!"
      },
    "height" : 610662,
    "state" : "up-to-date"
    }
}

The chain is "main", as discussed in Choose Which Chain to Run On.

You can also see our gameid is "helloworld", just as we set it above in Start libxayagame.

const int res = xaya::DefaultMain (config, "helloworld", logic);

However, of most interest is our game state.

"gamestate" : {
      "ALICE" : "",
      "BOB" : "HELLO WORLD!",
      "Crawling Chaos" : "...Hello, ALICE!",
      "Wile E. Coyote" : "Hello Road Runner!"
      }

If you recall from above in Update the Game State, we added players to our game state with their name as the key and their message as the value.

state[name] = message.asString ();

Now we have those back in a nice, neat key/value pair array.

If we were to have a front end for our Hello World game, we would take that game state and process it. That might mean displaying all players in a list with their messages like this:

<name> said "<message>"

Now that we know how to get the game state, let's make a move!

Moves in Hello World

With our Hello World game now running as a daemon and libxayagame embedded inside it, we must use another command line or terminal to send moves to the XAYA blockchain. Go ahead and open one now.

Moves are made through JSON RPC to the XAYA daemon, i.e. to xayad. There are many ways to do that, but we'll limit our methods to xaya-cli and curl.

If you're not already familiar with xaya-cli, see the Getting Started with xaya-cli tutorial.

Each game has it's own format for sending moves. For Hello World, it's trivial. ("m" stands for "message".)

"m":"<hello message>"

However, there's a general format for all games. Here you can see the above embedded in it:

{"g":{"helloworld":{"m":"<hello message>"}}}

The "g" means the game namespace and "helloworld" is the name of the game.

Do You Have a Name?

Let's find out if you have a name in your XAYA wallet. In your command prompt/terminal, navigate to the folder with the XAYA QT wallet because xaya-cli is in there. We're going to issue a name_list command.

Type or copy and paste this command (we must remember that we ran xayad with a username and password):

xaya-cli -rpcuser=user -rpcpassword=password name_list

name_list

If you have any names, they'll be listed. If you don't have any, go back and complete that portion of the First Steps tutorial.

Do keep in mind that we can have multiple XAYA wallets, and when we run xayad, we must specify which wallet we want to use if we don't want to use the default wallet. In this tutorial we're using the game wallet. For more information about wallets, consult the Wallet topic in the XAYA Electron wallet help and the data directory document.

Time to Make Your Move!

Moves are made through the name_update RPC method. The following is an example that updates your name to have no value.

xaya-cli -rpcuser=user -rpcpassword=password name_update "p/<my name>" "{}"

Combining that with our move structure from above yields the following.

xaya-cli -rpcuser=user -rpcpassword=password name_update "p/<my name>" "{\"g\":{\"helloworld\":{\"m\":\"Hello World!\"}}}"

Replace your name in that command and run it to make a move in our Hello World game.

You will receive an error code if you made a mistake, such as using a name that you don't own in your wallet.

If all went well you'll receive a transaction ID (or txid as is more commonly used).

name_update

BASH ERRORS: On Linux, when running the above xaya-cli command to update a name value, you may run into a bash: !\: event not found error. This is because ! has a special meaning for bash.

You can turn of the bash history by running set +H and then run the name_update operation normally.

If you watch your xayad command prompt, you'll see that xayad saw the name_update transaction and is processing it.

xayad sees all

CONGRATULATIONS!

You made a move in Hello World!

Now, check the game state as you did above. You may need to wait a minute until it's mined into the blockchain. (Miners provide an invaluable service.)

And we getcurrentstate from our hello daemon with curl just as we did above under See What People Are Saying with GetCurrentState.

Alice getcurrentstate

{"id":"curltest","jsonrpc":"2.0","result":{"blockhash":"8ebedb732eb33f97e49553db1fbfcddbaeedae9ecaaf41e80de66281ef0a79b9","chain":"main","gameid":"helloworld","gamestate":{"ALICE":"","Alice in Wonderland":"Hello World!","BOB":"HELLO WORLD!","Crawling Chaos":"...Hello, ALICE!","Wile E. Coyote":"Hello Road Runner!"},"height":613228,"state":"up-to-date"}}

Or prettified:

{
  "id": "curltest",
  "jsonrpc": "2.0",
  "result": {
    "blockhash": "8ebedb732eb33f97e49553db1fbfcddbaeedae9ecaaf41e80de66281ef0a79b9",
    "chain": "main",
    "gameid": "helloworld",
    "gamestate": {
      "ALICE": "",
      "Alice in Wonderland": "Hello World!",
      "BOB": "HELLO WORLD!",
      "Crawling Chaos": "...Hello, ALICE!",
      "Wile E. Coyote": "Hello Road Runner!"
    },
    "height": 613228,
    "state": "up-to-date"
  }
}

Looking in our hello daemon, we can see our move output.

In Windows:

name_update in hello

In Linux:

name_update in hello

Summary

We wrote a Hello World game on the XAYA platform and learned about a lot of different moving parts. In particular, we overrode 3 libxayagame methods:

  1. GetInitialStateInternal
  2. UpdateState
  3. GameStateToJson

We then created a main method where we:

  1. Processed command line options into flags (gflags)
  2. Did some error checking
  3. Started libxayagame with const int res = xaya::DefaultMain (config, "helloworld", logic);

We compiled libxayagame in such a way that we can add it to any game we choose, just as we did with Hello World.

We compiled Hello World with libxayagame embedded in our executable file.

We copied (or installed) all the dependencies for our hello daemon and ran it.

We used a JSON RPC to get the game state from the hello daemon with curl.

We made a move through JSON RPC and the name_update method with xaya-cli.

We checked our move once it was mined into the blockchain, again with curl.

We did a lot! Congratulations! You're well on your way to becoming a XAYA gaming maven!

Clone this wiki locally