Tableau Proba Blackjack
Joshua Milligan is a five-time Tableau Zen Master. His passion is training, mentoring, and helping people gain insights and make decisions based on their data through data visualization using Tableau. He is a principal consultant at Teknion Data Solutions, where he has served clients in numerous industries since 2004. If (on average) each has 10 simultaneous games of blackjack going on, and if each game takes 5 minutes, and the casinos are open for 5 hours on each of say 350 days per year, that means that each.
Tableau helps people see and understand data. Our analytics platform fuels exploration, allowing you to quickly answer questions with data and share insights. I wanted to add to this, glad you got it figured out below, but that didn't work for me in Tableau 10. I had the same issue where the columns option was grayed out. Select 'Show Field Labels for Rows' then swap columns and rows and then swap back, This added field labels back to columns for me. I decided to put everything tableau related to the tableau namespace on client side (=clojurescript, running in browser), blackjack game related functions went to blackjack namespace on server side while client-server communications are in client and server on their respective sides.
This year I had the honor to present at Tableau Customer Conference in Las Vegas together two hyper-blackbelt-jedi masters: Michael Kovner and Russell Christopher. The track’s name was “You did WHAT with the Javascript API?!” and as you can imagine we had some heavyweight stuff to throw. One of my API example was a complete multiplayer blackjack game using Tableau Public as gfx engine and clojure/script as backend and frontend programming language.
Let’s see how I built it from Tableau and programming perspective. But first… the game.
The Game
It’s a classic black jack game: all players (well, maximum two in our case) plays against the dealer. First the players hit until they’re fine with their cards, then stand. Dealer must always hit if his score is less than 17, otherwise stand. Now imagine this in Tableau. (But unlike vizpainter’s black jack game the logic here is outside of tableau).
Since the presentation was recorded the easiest way is to watch this video:
You can try it here as well: http://blackjack.starschema.biz/
Now a couple of things (disclaimer or user’s guide):
- When the page loads you have to add your name and chose your role (player 1 or player 2)
- For new game click “New”
- Hit is hit, stand is stand. If two of you are playing then both of you need to stand before the dealer reveals his cards.
- If anyone logs in with the same role in the meantime fun things will happen. You might be kicked off or other unexpected things might arise (like switch to cooperative mode)
GL/HF.
Details, Tableau
How does this work? Since I built the whole game in less than a day you can imagine: it’s not too complex. The game area is a dashboard. On the top everything is parameter driven: the opponent’s name, score and bar chart with score all rely on parameters.
The cards are coming from two separate sheets. Those sheets are using the same data set: numbers from zero to 851. The first digit (after left padding with zero) shows the location in hand while the second and third describes the card itself (it can be empty, a standard card from 52 cards deck or a face down card).
Every card has its location and card identifier. This used to position it to a scatter plot using custom shapes
The viz is shape based with the card position on the columns shelf. Shapes are custom, each one for a card type.
You can have a look on the raw tableu workbook here: https://public.tableau.com/views/Blackjack/BlackjackTableau . Feel free to download and have look in it.
Even more Details, Clojure, ClojureScript
Okay, let’s switch to hardcore mode. First of all all sources are on github, but this is not everything. Since I used clojure docstrings and comments I was able to generate developer docs using Marginalia (not the original, but Michael Blume‘s fork). Marginalia produces similar output as Docco for CoffeScript: on the left side you can see your docstrings while the right side contains the code. In my opinion this is the best approach: usually I start reading the source codes before any developer documentation but here I can read both in the same time.:
This is how the generated documentation looks: code and explanation in the same line.
You can read the generated documentation here: http://tfoldi.github.io/data15-blackjack/.
Architecture
My main goal was to show how multiple people can see the same dataset from different angles from multiple locations. Thus, I needed a server that receives interactions from users, interprets them and sends back the shared state. And this needs to be real time: if one player hits a card then the other should see it immediately (like a push notification based tableau data change). This just cries for web sockets that allow to send and receive asynchronous messages between browsers and server.
I decided to put everything tableau related to the tableau namespace on client side (=clojurescript, running in browser), blackjack game related functions went to blackjack namespace on server side while client-server communications are in client and server on their respective sides. The client does nothing just sends the click events to server (logon, hit, stand, new) and renders the tableau report when the server asks for it.
The server sends the full state information always in the same format. It has the player names and scores, their cards, status messages. When it arrives to client I just use the Tableau JS API to set the correct filters and parameters. Tableau JS API is smart enough to not update the dashboard if I set the same filters again and again.
2 4 6 8 10 12 14 16 18 | [uidstate] my-role(if(=uidplayer1-name):player1:player2) [other-statusyour-statusfeedbackother-nameyour-hand] [(keywordizeother-user'status') (keywordizemy-role'feedback') (keywordizemy-role'hand')])] (update-cards'Dealer'(get-cardsdealer-hand)) (parameter-update(workbook)'other-score'(firstother-status)) (parameter-update(workbook)'your-score'(firstyour-status)) (parameter-update(workbook)'other-name'(orother-name'Player2')))) |
For managing websocket I have used sente which provided a core.async compatible interface to send and receive messages. It was cool.
Avoiding “callback hell”
Last time when I built my snake game I used asynchronous messaging to notify the game engine when the tableau visualization loaded. This time I used a different method to avoid nesting callbacks and have dependency between modules (by design your tableau workbook should not know about your game logic and your game logic should not know about your tableau workbook). I end up with using atom . When the workbook has loaded it changes its state to loaded. Then, other modules can sign up to this atom’s changes.
2 4 6 8 10 12 14 16 18 20 22 24 26 28 | ;; (defviz 'Map (atom) to store tableau viz load status & object reference ' 'Javascript object to interface with `Viz` constructor. Hide tabs & toolbars. After initalizing the Viz change `:status` to `:viz-ready` in our `viz` atom. The Client UI watches this atom: when the status changes the dom node will be visible (js-obj 'hideToolbar'true 'onFirstInteractive'#(swap!vizassoc:status:viz-ready))) ;; In client.clj ;; Watch for `:viz-ready` status in `tableau/viz` atom. If the `viz` ;; status have changed and the new status is :viz-ready, then send (add-watchtableau/viz:ui (when(=:viz-ready(new-state:status)) |
When the viz is ready just send a “load” message to the server and start the fun!
The WHY
There is always a purpose. With this mini-project my goal was to showcase that with Tableau JS API your only limitation is your imagination. It was a few hours of work to build an application where different users were able to see and understand play with the same set of data, together but from different perspectives. Or if you logon with the same username from multiple devices and change something it will visible on the other device immediately. Again, only matters of few hours of work.
It’s fun and easy.
- Tableau Extensions Addons Introduction: Synchronized Scrollbars - December 2, 2019
- Tableau External Services API: Adding Haskell Expressions as Calculations - November 20, 2019
- Scaling out Tableau Extracts – Building a distributed, multi-node MPP Hyper Cluster - August 11, 2019
Random Adventures in Tableau
Before we get into the meat of the blog I wanted to give you a short test: see if you can guess where the data used in the below vizusalisation came from. I removed the axis labels to make it harder. I’ve also highlighted one series, but at random, highlight another and see if you can work out the dataset…
[tableau server=”public.tableausoftware.com” workbook=”RandomAdventures-Part1″ view=”Dashboard” tabs=”no” toolbar=”yes” revert=”all” refresh=”no” linktarget=”” width=”600px” height=”620px”][/tableau]
Stocks and Shares, right? You know what that share who’s dropping is? That high flyer? Now hit F5 to refresh your internet page, watch the data….
I’m sorry to say that this was all randomly generated data, not stocks and shares, I made it all up. Each line started off at the same value and I gave it 100 random movements (1 point up, 1 point down or no movement – all equally likely) before I showed you on the chart. Want to check? Here’s the axes and full chart (the above chart starts at x = 100)
[tableau server=”public.tableausoftware.com” workbook=”RandomAdventures-Part1b” view=”Dashboard” tabs=”no” toolbar=”yes” revert=”all” refresh=”no” linktarget=”” width=”600px” height=”620px”][/tableau]
So next time you’re telling yourself you’re onto a sure fire stock market winner, or a “can’t lose” streak at the roulette / blackjack table (Vegas TCC 15 anyone?) then just double check that you’re not looking at random increase. I find it amazing how different each of these lines of data is after just 100 generations of randomness.
It’s randomness in Tableau, and specifically generating random numbers that I want to explore in this post.
Why Generate Random Numbers in Tableau?
Before we get into the HOW, lets explore the WHY. The main reason for introducing randomness into the dataset might be to “jitter” data-points in the view. Steve Wexler of Data Revelations has already written on this subject, and I recommend his excellent article to see details of one approach. However another approach, where using INDEX() isn’t appropriate might be to use a random number. We’ll visit one particular use case later in this article.
Secondly you may wish to model processes that include a random probability or chance, if you do then obviously random numbers offer an approach.
Thirdly, you may just want to have fun, and say…build a blackjack game in Tableau.
How do you generate Random Numbers in Tableau?
Aside from methods using RAWSQL functions or SCRIPT functions to call out to SQL/JET and R respectively then there is no function that will allow you to bring a random number into your Tableau workbook. Instead you’re going to have to use a random number generator such as a Linear congruential generator (LCG) – these are pseudo-random number generators that are incredibly simple as they have linear algorithms.
You can read about LCG’s here, and I will also show you an implementation stolen from the Tableau genius that is Joshua Milligan (author of the Blackjack game referenced above – I advise you check out his Tableau Public visualisations).
The actual random number calculation is recursive – so the calculation takes its previous value as one of the inputs – using Previous_Value – a table calculation:
Random Number (one method – many variants involving different values exist)
To create a Random Integer
[Random Upper Limit] is a parametrized upper limit for the calculation.
Tableau Proba Blackjack Game
Seed
In the calculations above, [Seed] could be anything to start of the series but I have chosen a completely random seed based on the date and time, this ensures a different random number series each time. I could have used a fixed number or a parameter to control the series, or give the user control, if we follow this route the same [Seed] will generate the same series of random numbers, allowing repeatability.
Implementing a Random Number for “Jittering”
To show you how to implement jittering I want to return to an old post of mine, Health Check your Data using Alteryx and Tableau. In that post I showed a “DNA” profile of data, I want to now show you an alternative method using Jittering. in this case using INDEX() wasn’t appropriate as the Row Number was quite possibly related to the data type. So I use a random number.
Tableau Proba Blackjack Games
Though here I used another version of the LCG formula (just for fun):
I also created a seed and integer version as I detailed above, then I added the integer version as a continuous row after my existing Column names. Hiding the axis of this “jitter” row then left me with what I needed (after some formatting) – click below to see the jittered result. Backwards engineer the viz to see the exact details (this is a highly recommended way of learning).
More Random Adventures
Doing random stuff in Tableau is how I get my kicks, and so also to add a bit more randomness to this post here’s a video of a vizualisation I built I call “Tableau Life” – I’d like to think this is how new features get propagated in Tableau 🙂
Quiz Question
As a bit of fun while you’re watching this try and guess how many rows of data were used in making this visualisation – answer at the bottom of this blog.
Building this visualisation was a challenge and fun but perhaps a little complicated to explain as part of this post, it’s probably enough to say I took inspiration from the amazing and inspirational Noah Salvaterra and his amazing Fractal images in Tableau. Take a look at his blog post to explore his methods, mine are fairly similar (if less advanced).
My approach was to create two sets of random number to check for + and – movement (or none) on each axis (equal chance of each one). Then to iterate across an X value – for the path – and a Y value – for each “node”. The result was a set of random walks – which you can play with and recreate here:
[tableau server=”public.tableausoftware.com” workbook=”RandomAdventures-Part2″ view=”Dashboard” tabs=”no” toolbar=”yes” revert=”all” refresh=”no” linktarget=”” width=”600px” height=”820px”][/tableau]
Quiz Answer
How many rows of data? The answer is only 2! Here’s the dataset I used to create both random datasets in this post – the random “stocks and shares” and the “random walk”.
If I’ve just blown your mind then I suggest your read Noah’s post and download my workbooks and backwards engineer them. Welcome to the world of Tableau – the rabbit hole just got deeper 🙂 Any questions on this – I have left it unexplained as it is a little off the beaten track for most Tableau users then please tweet me @ChrisLuv or comment below.