Developing Games or Applications with the NEM Blockchain and Node.jssteemCreated with Sketch.

in #blockchain7 years ago (edited)

Developing Games or Applications with the NEM Blockchain and Node.js

PacNEM Logo

Introduction

Dear readers,

today I am going to share a piece of my development experience on the PacNEM  game developed using Node.js and the nem blockchain (Feb. 2017 to Jul. 2017). Details of the implementation will be posted along with this article in a more in-depth article.

This beginner’s guide aims to be simple and should only introduce you to a few basic features of blockchain related application and game development.

The nem blockchain provides a very intuitive HTTP/JSON API, also pretty easy to use. This post will describe an example of an easy first implementations you can do with javascript and a blockchain in a game or application development project.

This article uses the NEM-sdk package, which can be installed through npm as follows: npm install nem-sdk . You can also download the minified SDK javascript from Github and link it in your HTML like this: <script type="text/javascript" src="/js/nem-sdk.min.js"></script>.

Link to the SDK: github.com/QuantumMechanics/NEM-sdk.
Link to PacNEM: pacnem.com.

Brainstorming and Documentation

Linking a blockchain to a game or application development project is a little more complicated than what you might think as it requires a good understanding of the Peer to Peer networks brought back to life by blockchains.

Yes, blockchains use Peer to Peer and so —  each cryptocurrency’s blockchain is distributed across a network of nodes. These nodes also validate transactions for a protocol defined by the given cryptocurrency — this validation process is usually referred to as Consensus Rules.

This combination of Peer to Peer networks and Consensus Rules is a game changer in the software development industry. It gives us a totally new —  trustless —  software environment. We can now build apps whose outputs will be validated not by one server but by thousands of blockchain nodes — replicating our data — and making sure it is censorship resistant through distribution.

I have looked around for quite some time before I found an easy to use blockchain with genuine features and growth — Then I discovered nem.

In this article I will describe my first idea of saving PacNEM high scores to the NEM blockchain. I won’t cover all the details about Multi Signature Accounts, Namespaces & Mosaics, etc. in this article - I will only give you an idea of how easy it is to interact with the NEM blockchain and integrate NEM into your business layer implementations.

The source code for the complete game is the result of months of documentation about blockchains, NEM, Trustware, Multi Signature Accounts, Mosaics etc. and a good chunk of hobby programming time and pure developing envy. The full source code for PacNEM can be found at Github.

The nem blockchain has a way of making things easy for developers. Using the NEM-sdk is quite trivial and requires only a little knowledge of Javascript because the SDK can be integrated into Browser Javascript or into your backend Node.js application source code.

Now, let's do a quick Brainstorming session with following keywords on the Board :

Those are the main areas we will be trespassing for this article. Feel free to document and come back later if anything is too complicated and I’ll try to link this article with good sources for your reading too. The blockchain features, implemented with nem, are more complicated to explain than they are to implement!

Work the [ne]magic

So, the needs for our Game are pretty simple: We defined that we want to save High Scores on the nem blockchain.

First key aspect of blockchain application development is that saving data to a blockchain will always happen in a process of sending a transaction which gets included in a block of the given blockchain. nem provides with different transactions types but we will need only one: Mosaic Transfer Transactions.

A transfer transaction on the nem blockchain can contain an amount, a message, a fee and attached mosaics. This already gives us multiple implementation choices for the high scores implementation:

  • We could save high scores in transactions with messages (the message is the score).
  • We could save high scores in transactions with mosaics (amount of mosaic is the score).
  • We could save high scores in transactions with both messages (for authenticity) and mosaics (for the score).

In this article, I will illustrate an example of the second implementation choice. This keeps it quite simple.

We now know that our high scores will be represented as an amount of a given mosaic published on the nem blockchain. We don’t want people to be able to forward or transfer these high scores to other accounts so we will create a non-transferable mosaic. If you don’t know about NEM Namespaces & Mosaics yet, I recommend reading this article.

Another constraint of blockchain application development is that we will need a XEM account that we will create for our game or application. Using the NanoWallet is quite feasible for everyone, right? Once you have created the account, copy your account address and private key somewhere safe. In this article, we will refer to the created Application Wallet as pacnem-business. This wallet should contain your business funds (mosaics).

You are now all set to start developing games or applications with the NEM blockchain. As a little test experiment, following snippet will retrieve the last block of the NEM Testnet blockchain:

// include the NEM-sdk package in your package.json first.
// nem-sdk: npmjs.com/package/nem-sdk

let nem_ = require("nem-sdk").default;
let connectedNode = nem_.model.objects.create("endpoint")("http://bigalice2.nem.ninja", "7890");

nem_.com.requests.chain.lastBlock(connectedNode)
    .then(function(err, block)
    {
        if (! err && block) {
            console.log("NEM Testnet blockchain last block: " + JSON.stringify(block));
        }
    });

Easy, wasn’t it? OK, this was only a test experiment that will let you test your NEM-sdk package installation.

As you can see, the SDK lets you query the blockchain for data out-of-the-box. Now lets get back to our horses and create an easy function to query a given account's High Scores entries.

Blockchain is distributed, but NEM is an account-based Ledger. This means that you can query blockchain nodes through HTTP APIs to get current Account State Data. With this knowledge, it is now possible to determine which account has received our Mosaic pacnem:cheese.

This process will be quite resource expensive as you will need to query each account who received the Mosaic to retrieve their current Balances. What would be more optimized is to determine a specific paying account for this Mosaic pacnem:cheese and query only this account’s outgoing transactions to retrieve Player High Scores. Again, we will call it the pacnem-business account —  which, in fact, is just your NEM wallet address.

Also worth noting, the NEM blockchain nodes will only allow you to retrieve data about 25 transactions per request. Some nodes accept a hash parameter and all nodes accept the id parameter. We will need to query the blockchain multiple times in case there is more than 25 transactions, this is why the following example uses recursion to wrap around the SDK Promises calls and make sure we retrieve all available transactions.

Ok, now let’s build the code snippet to read High Scores from the Blockchain using our wallet pacnem-business and the SDK. For the sake of simplicity I will store the data in a global object that we will re-use in later examples. To the source code:

/**
 * Class OutgoingTransactionStream can be used to read 
 * outgoing transactions of a given NEM Account.
 *
 * This class is kept quite simple and only defines a
 * function to read *all* outgoing transactions of an
 * account.
 *
 * @param   {Object}    sdk     use `require('nem-sdk').default`
 * @param   {Object}    endpoint
 **/
let OutgoingTransactionStream = function(sdk, endpoint) {
    this.sdk = sdk;
    this.node = endpoint;

    let trxesByAddr = {};

    /**
     * The read() method can be used to *recursively* read all outgoing
     * transactions of a given `address` XEM address.
     *
     * When reading is done, the `successCallback` callable will be executed
     * and in case any error happens you can plug to those with the 
     * `failureCallback` parameter.
     *
     * Pass `null` to the `startTrxId` in order to start fetching the last 
     * recent outgoing transaction of the given `address`.
     *
     * @param   {String}    address     XEM wallet address
     * @param   {Integer}   startTrxId  Set to `null` the first time
     * @param   {Function}  successCallback
     * @param   {Function}  failureCallback
     **/
    this.read = function(address, startTrxId, successCallback, failureCallback) {
        let self = this;

        if (!trxesByAddr.hasOwnProperty(address) || startTrxId === null) {
            trxesByAddr[address] = {};
        }

        // 3rd argument is the transaction hash (always empty)
        // 4th argument is the transaction ID to begin with 
        self.sdk.com.requests.account.transactions
            .outgoing(self.node, address, null, startTrxId)
            .then(function(response)
        {
            let transactions = response.data;
            let isDone = false;
            let lastId = startTrxId;
            for (let i = 0; i < transactions.length; i++) {
                let trx = transactions[i];
                let tid = trx.meta.id;

                // if the transaction has been reported before (transaction ID already read)
                // we should stop requesting for new transactions.
                if (trxesByAddr[address].hasOwnProperty(tid)) {
                    isDone = true;
                    break;
                }

                // transaction not reported before, save the transaction for later usage.
                trxesByAddr[address][tid] = trx;
                lastId = tid; // parameter used for NIS request
            }

            if (transactions.length < 25 || isDone) {
                // done reading all outgoing transactions for this account
                if (typeof successCallback == "function") {
                    return successCallback(trxesByAddr[address]);
                }
            }

            // recursion until we read all outgoing transactions for this account.
            return self.read(address, lastId, successCallback, failureCallback);

        }, function(error) {
            console.log("NIS Error: " + JSON.stringify(error));
            if (typeof failureCallback == "function") {
                return failureCallback(error);
            }
        });
    };
};

(function() {

    let sdk = require("nem-sdk").default;
    let connectedNode = sdk.model.objects.create("endpoint")("http://bigalice2.nem.ninja", "7890");
    let applicationWallet = "TCTIMURL5LPKNJYF3OB3ACQVAXO3GK5IU2BJMPSU";

    let stream = new OutgoingTransactionStream(sdk, endpoint);

    // provide `null` as the last transaction id to get the most recent transactions
    stream.read(applicationWallet, null, function(transactions) {
        let cnt = Object.getOwnPropertyNames(allTrx).length;
        console.log("Total of " + cnt + " outgoing transactions read for " + applicationWallet);
    });
}());

Executing this snippet will give you a list of outgoing transactions. This was pretty straight forward, as I explained earlier the only little hick-up is about the 25 transactions limit for which we end up querying a HTTP API several times. Nevertheless I think this example keeps it simple enough.

This code snippet will be used later in other gists. The outgoing transactions list is what we will be using to retrieve our Hall of Fame data in the form of NEM blockchain Mosaics.

NEM Mosaics are Tokens published on the NEM blockchain. Using our outgoing transactions list, we will be able to determine the amounts of Mosaics. This was a first step-in to reading blockchain transactions with the NEM-sdk.

Use NEM blockchain transactions

Now we have all outgoing transactions for the given pacnem-business account and all we need to do is check in these transactions to find all transactions containing pacnem:cheese mosaics.

NEM makes it really easy to read Mosaic data in blockchain transactions. Mosaic Transfers Transaction can be differentiated from others through their mosaics field containing an array of attached Mosaics. This is very simple too, so let's tackle the snippet to retrieve exact latest scores on the NEM Blockchain:

// nem-sdk: npmjs.com/package/nem-sdk
// class OutgoingTransactionStream from gist: https://gist.github.com/evias/aea14ff6bea618239ca21a7f3802773e.js

let sdk = require("nem-sdk").default;

/**
 * The Transaction class defines some utilities methods to keep
 * reading data from NEM Transactions organized a little bit.
 *
 * @param   {Object}    transactionMetaDataPair     Transaction object received in NIS response
 **/
let Transaction = function(transactionMetaDataPair) {
    this.data = transactionMetaDataPair;

    this.isMultisig = function() {
        return this.data.transaction.type == sdk.model.transactionTypes.multisigTransaction;
    };

    this.isType = function(sdkTransactionType) {
        // return INNER transaction type
        return this.getContent().type == sdkTransactionType;
    };

    this.getContent = function() {
        // in case of multisig, get `real content`
        if (this.isMultisig())
            return this.data.transaction.otherTrans;

        return this.data.transaction;
    };

    this.extractMosaic = function(mosaicFQN) {
        // extract namespace and mosaic name from fully qualified name
        var lookupNS = mosaicFQN.replace(/:[^:]+$/, "");
        var lookupMos = mosaicFQN.replace(/^[^:]+:/, "");

        // now read mosaic data from transaction content
        var mosData = this.getContent().mosaics;
        if (!mosData.length) return 0;

        for (let i in mosData) {
            let mosaic = mosData[i];
            let isLookupMosaic = mosaic.mosaicId.namespaceId == lookupNS && mosaic.mosaicId.name == lookupMos;
            if (!isLookupMosaic)
                continue; // we are interested only in our Mosaic

            // mosaic `mosaicFQN` found in transaction
            let quantity = mosaic.quantity;
            return quantity;
        }
    };
};

// function used to sort an array of scores
var sortScores = function(scores) {
    var sorted = scores.getOwnPropertyNames();
    sorted.sort(function(a, b) {
        if (parseInt(a) < parseInt(b)) return -1;
        if (parseInt(a) > parseInt(b)) return 1;
        return 0;
    }).reverse();

    return sorted;
};

// display scores entries
var printScores = function(sortedKeys, scores) {

    console.log("Latest Blockchain High Score:" );
    console.log("-----------------------------" );

    for (let s = 0; s < sortedKeys.length; s++) {
        let currentScores = scores[s];

        for (let i = 0; i < currentScores.length; i++) {
            // each transaction per Score is a separate High Score on the Blockchain
            let score = currentScores[i].score;
            let trx   = new Transaction(currentScores[i].trx);
            let player = trx.getContent().recipient;

            console.log("Score: " + score + " for Player: " + player);
        }
    }

    console.log("-----------------------------" );
};

(function() {

    let connectedNode = sdk.model.objects.create("endpoint")("http://bigalice2.nem.ninja", "7890");
    let applicationWallet = "TCTIMURL5LPKNJYF3OB3ACQVAXO3GK5IU2BJMPSU";
    let lastTrxId = null;
    let scores = {};

    let stream = new OutgoingTransactionStream(sdk, connectedNode);

    stream.read(applicationWallet, null, function(allTrx) {
        for (let trxId in allTrx) {
            let trx = new Transaction(allTrx[trxId]);

            if (!trx.isType(sdk.model.transactionTypes.transfer)) continue;
            if (!trx.isMosaic()) continue;

            // relevant transaction: save this transaction content
            let quantity = trx.extractMosaic("pacnem:cheese");
            if (!scores.hasOwnProperty(quantity))
                scores[quantity] = [];

            // scores are indexed by Score to facilitate sorting
            scores[quantity].push({score: quantity, trx: trx});
        }

        // sort our high scores keys and print
        let sorted = sortScores(scores);
        printScores(sorted, scores);
    });
}());

With this executed, you have successfully read your first High Score list ever from a blockchain.
Those code snippets are relatively long but they are also kept very simple and I think with this you should be able to interpret and use your first NEM blockchain transactions without problems.

The NEM-sdk for Node.js offers a lot more features. I recommend you have a read at: https://github.com/QuantumMechanics/NEM-sdk. This SDK implements pretty much anything needed to interact with the NEM blockchain and even allows connecting to nodes with Websockets!

Lots of projects have been started using the NEM blockchain and we can only be happier to see even more coming!

The NEM blockchain offers advanced blockchain features out-of-the-box and provides a great ground layer for any blockchain related Game or Application development project.

Remember, if you are new to the blockchain industry — this requires some thinking ahead — plan those extra hours of studying blockchain data if you don’t know about it yet!

Conclusion

I hope everyone could follow along with this article. I will be posting more articles about NEM and blockchain integrations in the Software Industry in the coming weeks and months. I hope you enjoyed it!

I would love some inputs/feedback on my latest projects:

This article and the PacNEM project are published Open Source. Feel free to send donations to one of the following addresses:

  • NEM/XEM: NCK34K5LIXL4OMPDLVGPTWPZMGFTDRZQEBRS5Q2S
  • Bitcoin: 1Js4Tz3KSC6PAbvzy4xHga5caQLtKLysye

Credits to Telegram user @spizzerb (Patrick) for the PacNEM logos !!! Thank you a lot Patrick!

See you soonish!

Greg
Github: https://github.com/evias
Twitter: https://twitter.com/eVias

--

NEM Projects / Links

Other Resources / References

Sort:  

Great work! This is what we need to get things started.....

Great post! Definitely going to look into NEM next :) Currently, I'm seeing where ION goes and how their integration works as far as future game development goes.

Congratulations @gevs! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Do not miss the last post from @steemitboard:

Carnival Challenge - Here are the winners
Vote for @Steemitboard as a witness to get one more award and increased upvotes!