Exploring Randomness in Mobile Gaming

I know we all expect mobile games to be rigged. Anything goes in the name of engagement and retention numbers, but it’s always hard to prove. Hidden odds, complicated algorithms and “power ups”, and incomprehensible stat sheets all obfuscate the actual pRNG (psuedo Random Number Generator). But recently, a mobile game I’d been playing held a special event that let you roll dice. This should be a clear look straight at the results of the RNG, but is it?

To find out, I rolled a pair of electronic dice 200 times, meticulously noting the values in a spreadsheet. The things we do for science. At first glance, intuitively, the data looks off:

randomSingles

That doesn’t look right at all. These should be even (aside from random noise), and instead we have a evenly sloped line – showing that higher numbers are encouraged on the dice (and in-game, higher values are rewarded). Now that we’ve got that down, lets look at doubles (also rewarded in-game):

randomDoubles

We’d expect a 16% chance (first dice comes up X. Second dice has a 1:6 chance of matching X), but we’re at 23%. More damningly, Doubles occurred at a 30% rate in the first third of the data (not pictured, see spreadsheet at end). It seems the odds change as you play, starting strong to hook you before tapering towards (but never getting to) a natural rate.

The final aspect that I chose to examine was the rate of 7s. 7s are also rewarded, but rewarded so heavily it would make the game too easy if they came up too often.

randomSum

But seven should be the most commonly rolled sum. If we put together a table we can calculate the exact chances:

naturalTwoDice

There are 6 out of 36 possible results that are 7, more than any other number. However in our gameplay 7 came up less often than 3, which only has 2 possible creations! (1+2 or 2+1).

So here we have several indicators that the RNG is not truly random, nor even psuedorandom, but instead a weighted number generator that is watching the combined totals in order to manipulate the player into playing more, striving to get that 7 that should be just around the corner. But do we have proof? Perhaps I just got particularly unlucky several hundred times in a row. So how unlikely is it for this to occur?

For the two-dice example, we only had 6^2 options, so we could build a table and calculate the odds by hand. However, in order to build a table to match all 200 runs, we’d have (6^2)^200 options! Even google gives up at that point.

googleiswrong

This is where we get to the hard math. Binomial distributions – essentially, how likely is a combined result of X, given N trials and a probability per-trial of p. This requires a lot of summations, factorials, and integrations… I don’t have time for that, I have fake dice to roll! Luckily excel will do it for us: BINOM.DIST([occurrences], [trials],[probability per trial], [cumulative or single])

So, for example, the odds of flipping 5 or more heads in 10 tosses is BINOM.DIST(5,10,.5, TRUE [meaning x or more, not exactly X]), and excel tells us this is likely 62% of the time. So lets look at our two biggest red-flags:

The chance that we got only 16 (or less) sevens, given 6/36 odds (6 of the 36 possible combinations result in 7), out of 200 rolls:
BINOM.DIST(16,200,6/36,TRUE) = .03%, about 1:3000 odds.

The chance that we got 46 (or more) pairs, in 199 trials, given 1:6 odds:
BINOM.DIST(46,199,1/6,TRUE) = .7%, about 1:120 odds.

And we can combine these and look for the chances that these both occur together, 1:(3000*120) = 1:360k. I didn’t adjust for the lack of 7s making doubles more likely, but these rough numbers are close enough to prove the point. I’m sure if we added the chances for the single dice rolls as well we’d be in 1:1M odds that these results are natural.

Potential mechanism of action: There may be a simple algorithm that would result in all of these red flags: If a 7 is rolled, flip a coin. If heads, return 7. If tails, increment one dice. This rule change would explain the slope of the singles, the lack of 7s, and the increase in doubles – but this increase would be located only on 4:4. So to test this hypothesis, lets create a graph showing the frequency of each pair:

frequencyOfEachPair

We’d expect a flat chart, so this is ridiculously unnatural. However, it’s not unnatural in the way we hypothesized above, so the proposed algorithm is clearly not the one they are using. Fewer ones were rolled than any other number, and yet they are most likely to be in a pair. Clearly something else is going on.

So remember: When you’re not paying for the product, you may be the product.

*Update!*
More data more better, right? I gathered another 280 pairs and reran the analysis.

diceRound2

The rate of doubles is identical at 23%. The individual numbers were a bit more mixed, but still trended towards the most 6s, and not nearly the correct number of sevens.

Rerunning the binomial distribution on the combined data set leads to:
23% rate of doubles in 475 throws: .017% = 1:6000
(39)7s in 475: 1:17M
Both: 1:100T

So yeah.

Postscript: All the raw numbers and calculations are available here: https://docs.google.com/spreadsheets/d/1h1KqF-IuFLbCTrlajyu_8774xoV1nsafQXCPMrw_5mU/edit?usp=sharing . If you’d like to do further analysis or correct any mistakes I may have made, please reach out, we’d love to get even deeper into these numbers.

The Game of Life Coffee Table

About a year ago, we were smashing up a TV with Mammoth (video) and I fell in love with the look of the broken LCD, and decided to make it into a table-top. Despite veering wildly offcourse throughout this build - and using almost none of the original parts – that carnage set this project underway.

First step was to CNC the base, an array of hexagons to serve as cells beneath the surface. You might think the first step would be to figure out what the end product would look like… but nah. I knew I wanted some multicolored lighting, and hexagons make a cool pattern, might as well start. Plus, it was a fun excuse to play with the CNC.

20210912_214848

Then I painted this array, and added WS2811 LED modules (link, but you can find them cheaper elsewhere – search for  “36mm ws2811″) to each cell:

Screenshot_20220606-120735_Gallery

 

After that, it was time to program. This was definitely the most fun portion of the project.  I started with Conway’s Game of Life - but Conway’s Game of Life is only played on a square grid, and I chose to use hexagons.  Additionally, CGOL uses binary states – a cell is either on or off. Purely because it looks cool, I wanted to use an analog state so I could blend between colors. So I designed a new version, aiming for a system that wouldn’t reach any steady-states, and the rules are as follows:

Initial Setup:
            A random number of cells, chosen at random,  start with 25% heat, every other cell starts with no heat.
Gameplay - for each cell, repeating indefinitely:
            1) If your neighbors are hot, get a portion of their heat and add it to your own.
            2) If you get too hot, you explode, die, and lose all your heat.
            3) If your neighbor explodes, you die from blast damage and lose all your heat, but you do not explode (no cascading explosions).

That’s it. Very simple concept, but the implementation is a little bit trickier, especially on an Arduino.  The small micro size means we need to optimize for both speed and memory. The first issue I found is there wasn’t enough room in RAM to hold the addresses of all the neighbors. The massive 6×150 byte array – essential so cells know who their neighbors are – has to be stored in flash memory. Once you figure out the keywords that the Arduino IDE wants, this is pretty simple:

const static PROGMEM byte neighborArray[][6]

And then to access those bytes:

memcpy_P(localNeighbors, neighborArray[i], 6);

There should be shortcuts to access those values directly… but there aren’t, at least not for bytes. Just copy them into a temporary array whenever you need to read them and save yourself a lot of headaches.

Memory issues solved! Onto the speed problem. There are two main portions of the program: Display and Calculation.  During the display phase, the speed was fine, I even had to add delays to get the right fade between the previous state and the current state. But during the calculation phase, the program would visibly lag and interrupt the smooth flow of the display phase.  To start with the obvious optimizations, I used simple byte math. Addition, subtraction, and bit-shifts. Very little multiplication and never any division. But optimizing the math wasn’t enough, and we had to get tricky. Instead of calculating the next state for the whole array at once, I only calculated 5 new cells at a time. By increasing the steps in the display-phase fade to 30, I was able to replace the display-phase delays with these calculations. This meant that I could continuously calculate the next array while animating the previous change, eliminating the stutter from my program. This made it a little harder to track the current state of anything, but nothing a few extra arrays couldn’t fix.

After that, it was onto aesthetics. How do we translate a byte into a 3-byte color in a way that looks good. The simple version is: 1-125 directly increased the blue value of the color, and then 125-255 decreased the blue value while increasing the red value.

The complicated version involves an analog input to choose a starting color between blue and green, and then a tweaking the brightnesses because LEDs get brighter linearly as the PWM increases, but your eye will perceive brightness logarithmically (an LED with twice the current only appears 150% brighter). Luckily, we don’t have to actually do that math, we can just fake it by getting brighter slowly for the low values and quickly for the high values. To say this in a nerdy way with lots of math:

//create a color between oldstate and newStateTemp, stepped by phase
//30 phases per change
uint32_t StateToColor(float oldstate, float newState, float phase)
{

   byte interstate=oldstate+phase*((newState-oldstate)/30);

   //if are <75, fade up blue/green slowly (at half rate)
   if (interstate < 75) {

   //colorfade(colorcomponent,brightness) is %colorcomponent * state, with speed optimizations, and overflow protection to make sure it’s a byte.
         return Color(0,colorfade(color1g,interstate>>1),colorfade(color1b,interstate>>1));
   }
   //75-125, fade up blue/green faster (at full rate, but account for the earlier slowness so there’s not a visible step)
   if (interstate < 125) {
      return Color(0,colorfade(color1g,interstate-37),colorfade(color1b,interstate-37));
   }

   //125+, fade out blue/green and fade in red.
   //fade in red doubly fast because it has 125-255 to go from 0-255
   //fade in red slightly faster than that to max out red early
   //so it doesn’t immediately disappear when it dies next round.
   int adjColor=(250-interstate-45);
   if (adjColor<0){ adjColor=0; }
   int adjColor2=2.2*(interstate-125);
   if (adjColor2>255){ adjColor2=255; }
   return Color(adjColor2, colorfade(color1g,adjColor), colorfade(color1b,adjColor));
}

After that, the electronics were done and all that was left was turning it into a table! I sketched a few wildly varied designs for legs and settled on hexagons to match the display. Then I tried a few layouts for the hexagons in CAD to settle on a final design. Despite being far more comfortable in wood, I decided to use Aluminum and practice my TIG skills. I CNC’d a hexagonal jig in some scrap MDF to hold the pieces in the right spot, and blew through an entire tank of Argon doing a few hundred welds. And it worked! A few things clicked during the process and finally feel like understand tigging now.  (Side note: My motorcycle buddy asked “So, can I just come to the hackerspace and practice welding for free?” “Yeah, totally.” “Wait really?” “Yeah, we’re an educational nonprofit. Our Mission statement is basically ‘teach anybody to build cool stuff’, so come by whenever”)
279753349_986385208571745_9136975729903789763_n

The surface is made of a sheet of plexi to hold down all the wires, a layer of diffraction grating (from the murdered TV that started all this) to spread the light in an interesting manner, and then a layer of fancy semi-transparent plexi to hide the guts but allow the light to pass through.

286362142_10101278382119334_2995923001429898190_n

 

VCR-Head Scrollwheel

I’ve always wanted to build a nice weighted scrollwheel, and after finding a VCR head in my stash of parts hoarded during college, I decided there was no time like the present!  Researching online shows several preexisting builds, but they always seemed too complicated. Quadrature encoders, opamps, voltage comparators…. I thought there had to be an easier way.  And there is. The secret is: don’t reinvent the wheel. There’s a preexisting scroll wheel and chip in the mouse, and by reusing them we can entirely avoid the electronics-side of the equation. I started by drilling a hole in the shaft of the VCR head. This was easy with a lathe, but you could 3d print an adapter, turn one on a lathe, or even fab one out of a small piece of plastic with a few holes drilled in it. We just need some way of keeping the axles concentric.
20220209_204330

After removing all the existing electronics and wiring inside the VCR head, a dab of superglue was all it took and I now had an encoder wheel hard-mounted to the VCR head.  Next step was figuring out how to hold the rest of the mouse in position to read the encoder wheel. I desoldered the buttons from the mouse to shrink the footprint, and then carved a block of foam to a shape that would fit the mouse board and support the VCR head.

20220213_124325

I traced the foam in cad, and then CNC’d it into a chunk of Iroko wood from our scrap hardwood pile. Again, you could do this with a router, a drillpress and a bandsaw, or even with handtools – but I took the shortcuts I had on hand.  In retrospect, I should have left both ends of the pocket capped to avoid having to glue in a plate beneath the wheel, but I was making this up as I went.  Then was a bunch of assembly – gluing the blocks together, a holesaw to create a recess for the wheel, routing the corners for a round-over, sanding everything, and epoxying the base of the wheel in place.

20220213_180937

Now, all that was left was to attach the board so that the IR beam passed through the scrollwheel. I used a screw to temporarily mount the board while I tested it (and it worked great! I can scroll 7500 lines in excel with a hard flick!!). Once I established the location was good and there was no rubbing, I epoxied the board permanently in place.  To finish, I added a small escape channel for the mouse wire, knotted the wire for strain relief, and finished with wax for a nice shine. And just like that, a project I’d had on my list for literally a decade was done in a weekend, and I couldn’t be more pleased with it.

20220213_182137

Art Deco Lamp

20220213_185950

 

Another project inspired by Cowboy Bebop, this one an art-deco lamp glimpsed in the background.  This time, I didn’t try to replicate the original, I just took inspiration from that glimpse and started brainstorming.  Brainstorming turned into cad, and turned into an excuse to practice my cad. Which turned into an excuse to do trig, build spreadsheets of pipe lengths, and completely overthink the whole design. That’s going to be a recurring theme here – excuses to practice skills, try new techniques, and vastly try-hard the whole build. Because it’s fun.

render50

CAD completed, I ordered some brass pipe from Online metals, and chopped it to size.  I used the CAD to CNC some internal and external spacers (thank goodness the shopbot will let you scale files, I ended up needing to run at 102.5% for a snug but not impossible fit), and then used those spacers to hold the pipes in place while I affixed them. The first attempt was soldering, but that was a complete failure. So I TIG’d the whole thing, mostly because I needed practice tigging. It turned out needing a lot of cleanup, so if I were to do it again, I’d build it in two halves, left and right. I’d tig the tops and bottoms which  are hidden in the bases, but I’d rivet the centers in from the inside. TBH, it could be done entirely with rivets, and you’d maintain the polished surface finish.

20220213_190139

 

For the top and the bottom I used walnut, CNC’d the pockets in place to hold the ends of the tubes, and then turned them on the lathe. I could have done the shaping on the CNC, but (as you probably guessed) I wanted practice turning on the lathe. Once I had them shaped, I set about adding detail. I love the marbled look, but didn’t want to actually do marble, so I set about recreating the look with Lichtenburg burning. All the references recommend using Baking Soda dissolved in water to add conductivity to the surface, but looking around the shop we didn’t have any. Second choice was salt, but again absent in the shop. Looking around, desperate for ions, I found some borax – which actually worked beautifully.  Using a 10kv high-voltage tester, I painted the dissolved borax solution onto the wood, then burned paths while blowing air with the compressor. The airflow deepened the burn patterns, which gave me more room to add bronze.  Instead of telling you about inlaying bronze, I will instead link to Blanch Woodworking who has done all the experimenting, and who’s techniques I followed.  TLDR: Superglue, brass powder, sand, repeat, repeat.

 

20220202_202345
20220129_175328
20220202_202338

 

After some stain and a lot of polyurethane, it was on to the feet.  Again, this was just an excuse to play on the lathe, this time with machine tools instead of chisels. Knurling, pocketing, drilling, and parting.

 

After that, it was a pretty simple assembly. A top plate from SCS, a bunch of lamp parts (quite reasonably priced from Grand Brass Lamp Parts),  an expensive shade from wayfair, and it’s done!