Programming against the Splinterlands API with .NET Core

splinterlands_logo_fx_1200.png

Intro

Hey everyone! Hope your season has gone well making the league or rank you want to be at. I wanted to share something different than the typical daily or end of season rewards as well as forgo this weeks battle challenge and decided to I should share some Splinterlands development using .NET 6 and C#.

Game Background

For those of you that don't know about Splinterlands let me give you a quick intro. #Splinterlands is a highly addictive and fun block chain based #play2earn card battle game, originally launched as Steem Monsters running on the Steem blockchain back in 2018. Due to issues with the Steem blockchain they rebranded as Splinterlands and moved to the Hive blockchain.

In Splinterlands, you battle as one of 6 splinters, Fire, Water, Earth, Life, Death, and Dragons to win Dec, the in game currency. Dec can be used to purchase or rent cards allowing you to grow more powerful and achieve higher ranking and League entry or it can be traded for other currencies on Tribaldex. In addition to earning currency you can win reward cards helping you grow your power.

My Background

This is going to date me but I myself have been programming for about 17 years now. I became interested in computers and programming at a very young age just like a lot of other developers. My first computer experience was my dad bringing home an Apple II GS with 5k of extended memory with a big ole dot matrix printer that I can still hear line feed in my head to this day. I remember playing all the games my dad would bring home from his friends at work and quickly being enthralled with just watching my sisters play wondering how everything worked. It wasn't until a few years later when my dad upgraded us to a 386 based Intel system that I got my first taste for programming in basic. From there my curiosity and love of computers and programming blossomed.

In high school I took every cs class available and did so well at absorbing everything that my teacher Ms Brown gave me a book on C++ programming to read over the summer. To this day I still have that book and a few others she gave me the next year that helped me and fueled my passion. In college I pursued a degree in computer science as well as a minor in math. This was in the late 90s early 2000s and the focus was still on C/C++. By the time I was in higher level courses learning about os theory, algorithms and data structures the school shifted to Java the new latest and greatest thing as well as some .NET courses. It was during this time I made a mistake and dropped out for my first tech job, not the smartest idea but hey I was young. This quickly backfired as the company got bought out and then laid off the entire engineering and it departments.

After figuring out what I wanted to do I eventually went back to school but this time I pursued a degree in Game and Simulation programming. Heck games had been my passion since I could remember so this only made sense. It wasn't long after that I graduated and found myself in a real developer role for a small casino company. I started with slot games, video poker and all the other gambling games you can think of in C++ but quickly was put to learning .NET and helping rewrite the companies online poker portal from classic asp to asp.net web forms as well as SOAP services.

After quite a few years I ended up moving on and did work for a government contractor using cry engine 3 as well as a custom graphics engine they had developed. Here I also did lots of random work in WPF, embedded C/C++ as well as C++ CLR (yuck!). I really liked this role but got engaged and my fiancee wanted to move closer to her family so we moved out west and I left this dream role and went to the corporate world, landing at a mortgage company. Here I got my first major introduction to MVC, jQuery, knockout.js and some various orms like Nhibernate.

This role was very short lived. It was kind of funny, the company was in a rush for us to move out where they were and then we get there and they have little to no work for me. Other than learning I pretty much did nothing for the entire 6 months I was there and got bored and moved on. I ended up at a very large corporation doing some mvc and random .net work and while it was decent work it wasn't challenging. Not long after getting this role my now wife decided she wanted to go back to school full time and after lots of discussion we decided that I would need to find a better paying role to be able to support us through this so once again I got out into the job market.

It didn't take long to find another role and I found myself at a healthcare robotics company. From the outside the company seemed really great and the products seemed amazing but this was one of the worst code bases I had ever worked in, almost 100% rewritten by my then boss who was a complete egotistical maniac. Their code base was very old, it was C with C++ slapped on for their server and embeded devices with a Java UI and then some .NET services and utilities they had been adding. I was there a little over a year when they announced they were moving locations and this was going to add an hour commute to my day in the morning and evening, so once again I entered the job market.

I think I was looking maybe 15 days before I found my next role, at yet another mortgage company. However this time the role was a lead and I would get a lot of say in things. During this time I learned Vue.js, becoming the tech lead for it at the company as well as finally going down the .NET Core path. Being a mortgage company there were lots of regulations and things moved slow but the team I was on accomplished a lot and brought a lot of new technical innovations to the company. However after 3 years of 1% raises and not getting a promotion that I had been trying for I decided it was time to move on. I ended up taking a lead Vue.js developer role with a very very small company and only stayed 6 months due to being approached by my current employer with a role and offer I couldn't say no to.

My current role is also a lead Vue.js developer but also a guild leader and mentor to other developers. As a guild leader I evangelize Vue.js development and share code and go over various front end topics with other developers trying to build excitement for development and the directions were moving with things as well as introducing others to the technical workings of things.

So there you have it. I have tons of experience across a diverse range of businesses and industries. I love writing code and teaching others and wouldn't trade it for the world and that is why I'm writing this. Anyways whew that was a lot, so enough about me, lets get onto why were here.

What were doing

Well this is the fun part. Were going to go over how to see what API calls are made while browsing around the Splinterlands website, looking at their the requests and responses as well as going over how we can call these API's ourselves.

We're going to use the chrome dev tools to watch what calls the game makes to various API's and were going to try to call them ourselves first in the browser and then through .NET. To make rest requests in C# were going to use RestSharp, a very widely used REST client for .NET programming. To handle JSon data and de-serialization were going to use JSon.net which is pretty much the gold standard for handling JSon in .NET.

Tools we will use

You can really use any browser, they all have developer tools however it will be easier following along while using a chromium based browser.

You can also also use VSCode if you setup C#, however I have Visual Studio installed and prefer it to VSCode when doing C# development so it is what will be in my screen shots.

Getting Started

So go ahead and open up Chrome and the dev tools, F12 on windows, then head on over to Splinterlands. Open up your network tab and you should see a bunch of traffic. If you type splinterlands.com into the filter box this will help some but you will still see calls to google analytics and hive, so to get down to just Splinterlands calls, type api2 into the filter box.

devtoolsfilter.PNG


Perfect, now you should see a pretty good list of calls similar to the above, except the calls should involve your user name if you have an account and are logged in/log in. So in this view we can see all the calls that went out to the Splinterlands API just for logging in and being on the home battle page. By default you should see the Name, Status, Type, Initiator, Size, Time and Waterfall, if you right click and select header options you can turn on other columns like I have the URL column enabled. In this view you should also see preflight requests which we don't care about so in the upper toolbar select Fetch/XHR instead of all we can get down to actual API calls with responses.


devtools_fetch.PNG

If we select an API call in this view we can see the response and thanks to the JSON formatter extension we can select Preview tab and see a well formatted JSON response object we can inspect to see what kind of data Splinterlands sends to the UI.

devtools_payload.PNG



In addition you can right click on one of these calls and select open in new tab. This allows you to execute the call and view the payload, again thanks to the JSON formatter extension we installed we get a nicely formatted preview of the response payload.


newtab.PNG


Perfect! So now we know how to view Splinterlands API calls as well as view their response payloads in both the chrome dev tools network tab as well as in a new browser tab. We can now go over the responses and see all the fields that make up the Splinterlands DTOs (Data transfer objects) and form a plan to create our corresponding C# DTO types.

Cards List

So looking at the calls the second call made is to get_details?v={number} (which I believe is my user id). This call if you right click and open in a new tab we can see returns a list of every card available in the game, and you can safely remove the query string and still get the same data. If you inspect the json you can see that there is quite a lot of data available, including stats for each possible level for the card as well as set distributions.

cards_details.PNG

I've collapsed several fields but you can see there is a lot of data. So how do we consume the API call in C#? Well this will involve creating DTO (Data transfer object) types and using JSON deserialization to process responses in C#.

Rest Sharp

So to make our calls to Splinterlands were going to use a library called RestSharp. RestSharp is a Rest client library that makes working with REST apis easy and quick. For us there are three main types we are going to be working with. The RestClient class, the RestRequest class and the IRestResponse interface.

To create a RestClient object we need to provide it with a string URL, in our case we want to use "https://api2.splinterlands.com". To create the RestRequest we need to provide the end point, in this case "cards/get_details", in all our examples we will be sending GET requests and expect responses in the Json data format.

restrequest.PNG

In the attached image from Visual Studio you can see the basics of creating a request. From playing around with the game and other resources available you can see that there is actually two api end points even though you will only see the api2 calls in our example. While this code is a good start and will do what we want, executing a call to the Splinterlands API and returning the response it leaves us having to handle repetitive status code checks.

To improve this we can use generics, constrained with a new clause, and ensure that when we extend our code later we don't have to copy paste or duplicate code around handling the response again. This is a big improvement as it helps ensure that later on we ere less likely to forget checking a response type or handling an invalid responses.

restrequest_generics.PNG

In the attached image you can see we have updated the method to use C# generics. We use the where T: new() clause to enforce that any type passed to the method must support being constructed with new, so any kind of object or collection while excluding basic types like integers. Here, instead of returning the response we check the status and ensure we got a response that has content and attempt to use JSON deserialization to convert to a C# type that we will define, if the deserialization returns null we will return a new object. If the entire process fails we throw an exception, log it and throw it on to the calling code.

Creating the Card DTO

If we look at the parsed data in the browser we can see that cards are basically a bunch of string and integer fields with stats being an object and distribution being a collection of objects. So looking at this we can easily create a few different classes, Card, Stats, and Distribution, as well as an Enum for rarity. So lets start with the simplest types first and create the rarity Enum.

Sub Types used by Card

Rarity Enum

If we scroll through and look at cards, or you use your browsers find you can search "rarity": and quickly go through. We can easily see that the values for rarity are 1 - 4 for Common - Legendary. So that allows us to define the following enumeration

rarity.PNG. When working with enums I like to include an invalid/unknown state so that when initializing fields I can set them to something that shouldn't occur, hence the inclusion of the Unkown = -1. Since the values for rarity are 1 - 4 we also set Common to 1 so that the Enum will start its mapping from there instead of 0.

Distribution Class

Next we have the Distribution class. Looking at the parsed data from the call we can see this is a simple mapping of mainly strings and some integers a boolean and another Enum candidate Edition. Again using the search for "edition:" we can figure out the mappings and come up with the following.

edition.PNG

Now we can see there are editions 0,1,2,4,5 and 7. I was able to map these to their sets other than 2 so if someone knows what set that is please let me know or make a PR and update the Enum.

So now that we have the types needed we can go ahead and create the Distribution class and we come up with the following

distribution2.PNG

Stats Class

Looking at a monster we can see that stats are each an array indicating at the power of that stat at a certain level and in the case of abilities what level they are granted, however if we look at a summoner we see that stats are a single value indicating how much of a stat a summoner grants to a monster.

monster statssummoner stats
monsterstats.PNGsummonerstats.PNG

So how do we handle this? There are a few ways we can do this, one using the dynamic type but that is a horrible use of dynamic especially if you are using it to shut up warnings about casing operations, the other and simpler way is using object, the base type of all object types in C#. Using object means that we will need to ensure we know what data type were working with and cast to that type when appropriate when we parse the type. This also means that our CardStats class is going to be a little more complex than defining the properties we need and will contain backing fields for each property as well as two helper methods for parsing the values in each setter of abilities and stats.

cardstats.PNG

So lets look at how we need to handle parsing the stats. Again in the case of monsters each stat is an array that contains x number of entries where x corresponds to the rarity of the card and the max level and in the case of summoners this is a single value indicating how much of that stat the summoner grants monsters, mana excluded. For example a common can be up to level 10 so the stats for a common contain 10 entries.

To handle parsing monster stats we first need to check the JSon objects type, if its a JArray then we will try to cast it to one and then attempt to use a helper method and cast all the entries in the array to a list of strings, if that fails we will just return an object. In the case of summoners we see the type is not a JArray and instead just convert it to a string.

parsestats.PNG

Awesome so now each Property when parsed will execute this code and either be a List or string under the hood. To parse abilities were going to do something similar except Abilities should be an array no matter monster or summoner, however this array can be of strings or contain sub arrays so we need to handle that as well. So to parse things we first need to check the type of the JSon object if its a JArray we can try to cast it, if that is successful we then check if the the array is an array of values otherwise its an array of arrays. In the first case we can easily use a linq expression selecting each child, converting it to string and returning the results as a List. In the case of arrays of arrays we again can use linq but we first need to select each child and start a linq operation on it. This second linq operation converts the sub array into a comma separated list which we then return to the outer linq operation and return a List. So as an example imagine the input of

json

[
  ['ability1, 'ability2'], // granted at lvl1
  [],
  [],
  ['abilitiy3'],
  ...
]

This results in us having a list of strings

json
[
  'ability1, ability2', 
   '', 
   '', 
   'ability3', 
    ....
]

parseabilities.PNG

Card Class

So now that we have all the supporting types we need we can define the actual Card class. We can start by maping the basic integer and string types as well as using the classes and enums we defined to map the complex fields. In addition in the data there are two fields that are always null Subtype and Tier. For these we can use Object? to handle reading them, you could also leave them off the data type and the deserialization will ignore them.

cardclass.PNG

Here you can see I included the Subtype and tier. I also added a helper for computing the max level of the card based on the rarity, so common 10, rare 8 and so on.

Putting it all together

So we should now be able to actually call the Splinterlands API and look at some cards in .NET! The quickest way is to add a unit test project and write a test so that is what I did.

unittestmock.PNG

So now all you have to do is set a break point and start debugging and then you can inspect the data we pulled down.

debug.PNG

Here you can see that the call was successful and retrieved 448 cards. I expanded the stats to show that they were parsed as well.

Wraping up

So now you should be able to make calls to Splinterlands through .NET and are ready to put that data to use. I really hope you all enjoyed the read and learned something either about .NET or Splinterlands. This code is a constant work in progress but you can check out the Github repo and make contributions or submit feedback. The repo is farther ahead than our lessons and includes calls to a few different APIs as well as supporting both Synchronous and Asynchronous methods.

If you're not yet playing please use my referral link and join Splinterlands



Disclaimer

Splinterlands logo taken from the Splinterlands media kit. All screenshots taken in Visual Studio community edition or Chrome. Visual Studio is copyright Microsoft, Chrome copyright Google

Sort:  

Congratulations @farpetrad! You have completed the following achievement on the Hive blockchain and have been rewarded with new badge(s):

You distributed more than 700 upvotes.
Your next target is to reach 800 upvotes.

You can view your badges on your board and compare yourself to others in the Ranking
If you no longer want to receive notifications, reply to this comment with the word STOP

To support your work, I also upvoted your post!

Check out the last post from @hivebuzz:

Hive Power Up Month - Feedback from February day 11
Valentine's day challenge - Give a badge to your beloved!