-
Notifications
You must be signed in to change notification settings - Fork 10
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:
- Starting xayad with the proper options
- Starting your game daemon with the proper options
- Working with daemons (xayad and libxayagame)
- Calling libxayagame
- Getting a game state
- 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.
- Write our Hello World code
- Compile libxayagame so that we can include it in our executable
- Compile our Hello World game with libxayagame
- Sort out the extra dependency files that we need
- Start xayad and Hello World with the proper options
- Check the game state
- 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.
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.
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.
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.
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.
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:
- Variable name
- Variable value
- 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.
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.
- GetInitialStateInternal: Sets the starting point on the blockchain
- UpdateState: Receives the previous game state and updates it
- GameStateToJson: A simple helper 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.
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.
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:
- --xaya_rpc_url="http://user:password@localhost:8396"
- --xaya_rpc_url="http://user:password@localhost:18396"
- --xaya_rpc_url="http://user:password@localhost:18493"
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.
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
.
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
.
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"];
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;
}
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"];
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.
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.
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!
Our main
method will be written outside of the anonymous namespace. Wire it up as usual.
int main (int argc, char** argv)
{ }
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.)
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.
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;
}
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;
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.
The HelloWorld
class is all the game logic. It's time to put it to work. Create an instance of it now.
HelloWorld logic;
We need 3 things to start libxayagame:
- A daemon configuration
- A game name
- 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
.
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.
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.
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.
Now that you have libxayagame built and available, let's compile Hello World.
-
Open up an MSYS2 MingGW 64-bit terminal
-
Create a new folder for Hello World:
C:\msys64\home<username>\hello world\
In MSYS2 that is:
\home<username>\hello world\
-
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
-
Run build.sh as follows:
./build.sh
-
Done.
Our build.sh script compiled Hello World as the "hello" executable file.
-
Open up a terminal
-
Navigate to your Hello World folder
-
Run build.sh as follows:
./build.sh
-
Done.
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.
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.
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.
We're going to need 3 command prompts or terminals.
- To run xayad
- To run hello
- To execute RPC commands with xaya-cli and curl
Open them up when they are required and navigate to the appropriate paths.
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.
We'll see other output from our Hello World game (technically, its from libxayagame) below when we get the current game state with curl.
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.
Navigate in a command prompt to the hello executable.
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
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
Run one of those now.
Hello World on Windows:
Hello World on Linux:
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.
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.
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).
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!
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.
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
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.
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).
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.
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.
{"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:
In Linux:
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:
- GetInitialStateInternal
- UpdateState
- GameStateToJson
We then created a main method where we:
- Processed command line options into flags (gflags)
- Did some error checking
- 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!
- Step 0: Blockchain Basics
- Step 1: xayad <⸺ start here
- Step 2: The Game State Processor
- Step 3a: libxayagame Component Relationships
- Step 3b: Compile libxayagame in Windows
- Step 3b: Compile libxayagame in Ubuntu
- Step 4: Run xayad for Games
- Step 5: Hello World! in C++
- Step 5: Hello World! in C#
- Step 5: Hello World! with SQLite
- Step 6a: Mover Overview
- Step 6b: Mover Console
- Step 6c: Mover Unity
- libxayagame Component Relationships
- How to Compile libxayagame in Ubuntu 20.04.03
- How to Compile libxayagame in Ubuntu 22.04
- How to Compile libxayagame in Windows
- Xayaships (How to get started playing)