I have published some download statistics of The Little Crane That Could. Like 2012, the year 2013 was another blast.
This weblog documents Bram Stolk's life as an indie game developer who immigrated from Holland to Vancouver, British Columbia.
Tuesday, December 31, 2013
Wednesday, December 11, 2013
Bitcoin, logarithmically
Some time ago, I purchased a bitcoin for a little under $300,- CAD. My reasoning went like this:
- There is a non-zero chance of bitcoin replacing USD as default currency. Let's put it at 1 in 1000.
- There is a hard limit of 21M bitcoins.
- If in 2030, the world uses bitcoins, then those 21M coins will represent the entire money supply of the world's default currency.
- My 1 bitcoin will represent 1/21Mth of the world's money in 2030, with a 1:1000 chance.
- The world money supply divided by 21M and then divided by 1000, is still worth a lot more than the $300,- I paid for it.
Friday, August 2, 2013
A spin-off.
Please let me introduce to you, a new blog thelittleengineerthatcould.blogspot.ca. It is a spin off from this blog. I decided to do this, so that game development blogs and personal items would not be mixed together anymore. If you like following my indie game development efforts, hop on over! I look forward to reading your comments.
Friday, July 12, 2013
Irrational Investor
Because I've been fortunate with my app store ventures, I have some money to invest. I want to share with you the story behind my worst investment, so that others may learn from my mistakes. When it comes to investing, it is hard to remain rational. One of the psychological hurdles to take, is to let go of buy-price. You should be able to reassess your holding at any time, and sell below cost if necessary. If you can't you are 'anchored' and this will hurt you. Here is what happened to me when I first started investing on the Toronto Stock Exchange:
My instinct was telling me that solar energy would be a growth opportunity, so I sought out solar panel producers. I found these two on the Toronto Stock Exchange:
- Day4 Energy Inc. (DFE)
- ARISE Technologies Corporation. (APV)
So I studied them carefully: the earnings, the price, their debt, the news items on them. After this study I decided to go with Day4, as it had just got certified in an important market. So I made the online trade, worth $9000, using the online investment website of my broker (RBC). The thing is: I had typed in the wrong symbol. So there I was, holding $9000,- APV instead of $9000,- DFE.
If I had been rational, I would have sold the APV and bought the DFE. However, I really did not like to spend two more transaction fees of $9.95 each. Psychologically, I was not prepared to take my $20,- loss on a stupid mistake, so I let it be. Hopefully, APV would see some nice yield. In reality, APV was bankrupt within a year, and I lost my entire $9000,- investment in 'green technology.'
I can laugh about it now, but darn, did I feel like a fool. Trying to penny pinch and save $20,- transaction costs, ending up losing my $9000,- principle. Talk about being anchored... Anyway, I still hope that for the rest of my holdings, all I have to do is wait long enough and everything will be in the plus. I'm a long term investor, and see much wisdom in the ideas of Warren Buffet. Currently, my largest holding is AAPL, which currently yielded -24% for me (not counting dividend.) I'm pretty confident that within a 5 years it will be the shooting star it once was, so I don't plan on selling before that one hits the cool $1000,- milestone (or $100,- when the stock finally splits 1:10 or so.)
Thursday, June 6, 2013
Quadratic Equations, solving 8 in one go.
Today I found out that modern intel CPUs can do 256 bit wide SIMD. This is possible with the Advanced Vector Extensions, or AVX. Operating on 8 floats at the same time is even better than what the PS3 Cell processor was capable of. So I wrote this neat little function that can solve 8 quadratic equations in a single go, without branching. It should come in handy when implementing a Fast Marching Method to implement Continuum Crowds. For SIGGRAPH 2008, some people at ATI implemented this on the GPU for their amazing Froblins Demo.
/* * solve aX^2 + bX + c = 0 * solves 8 instances at the same time, using AVX SIMD without any branching to avoid stalls. * returns two solutions per equation in root0 and root1. * returns FLT_UNDF if there is no solution due to discriminant being negative. */ static const __m256 zero8 = _mm256_set_ps( 0, 0, 0, 0, 0, 0, 0, 0 ); static const __m256 undf8 = _mm256_set_ps( FLT_UNDF, FLT_UNDF, FLT_UNDF, FLT_UNDF, FLT_UNDF, FLT_UNDF, FLT_UNDF, FLT_UNDF ); static const __m256 four8 = _mm256_set_ps( 4, 4, 4, 4, 4, 4, 4, 4 ); inline void evaluate_quadratic8( __m256 a, __m256 b, __m256 c, __m256& root0, __m256& root1 ) { __m256 minb = _mm256_sub_ps( zero8, b ); // -b __m256 bb = _mm256_mul_ps( b, b ); // b*b __m256 foura = _mm256_mul_ps( four8, a ); // 4*a __m256 fourac = _mm256_mul_ps( foura, c ); // 4*a*c __m256 det = _mm256_sub_ps( bb, fourac ); // b*b - 4*a*c __m256 twoa = _mm256_add_ps( a, a ); // 2*a __m256 dvalid = _mm256_cmp_ps( fourac, bb, _CMP_LE_OS ); __m256 sr = _mm256_sqrt_ps( det ); root0 = _mm256_add_ps( minb, sr ); root1 = _mm256_sub_ps( minb, sr ); root0 = _mm256_div_ps( root0, twoa ); root1 = _mm256_div_ps( root1, twoa ); root0 = _mm256_blendv_ps( undf8, root0, dvalid ); root1 = _mm256_blendv_ps( undf8, root1, dvalid ); }
Wednesday, May 22, 2013
2012 and 2013 goals.
So, this post is verbatim from an old indiegamer.com forum posting. On this old forum, it was tradition to set goals for the year, share them with the board community, and evaluate afterwards. indiegamer.com is down today, and I suspect it may be gone forever now. Readership was dwindling, spam was a plague, so I think it will be dead, if not now, soon. So let's preserve my goals here on my own blog.
The goals I set for 2012:-
Add support for this awesome input device to the little crane that could for MacOSX.
SUCCESS
-
Maintain my 2011 income (had a very good year).
BIGGER SUCCESS
-
Release my first Android Game.
SUCCESS
-
Release an update for Hover Biker.
SUCCESS
-
New level for the little crane that could iOS version.
SUCCESS
-
Port the little crane that could to Android.
SUCCESS
-
Attend GDC.
SUCCESS
It is amazing, but I hit all my goals. Maybe it is true what they say about: writing down goals make them come true. Although I did cheat a tiny bit: 'Port Little Crane to Android' and 'Publish Android Game' were originally intended to be separate games, but meh.
For 2013 I will:-
Publish an OUYA game.
-
Hit 30K OUYA downloads.
-
Hit 10M downloads (cumulative over all platforms, all years) for Little Crane That Could.
-
Maintain 2012 income.
-
Reach 750 followers on Twitter.
-
Publish my first multiplayer game.
Monday, May 6, 2013
Ethics, not octane
I wonder what would happen if we stopped selecting Octane Numbers at the gas pump. Instead, we pay a premium for fuel that is guaranteed not to fund oppressive regimes. Maybe I am naive, but I think it could solve so many problems. That is why I built the ethics-not-octane website. This could solve so many problems.
Saturday, May 4, 2013
Mad Carpentry Skillz
I inherited my grandfather's carpentry skills, it seems. With a little coaching from my father I managed to build this mean looking stainless steel kitchen in my basement. The parts come mainly from IKEA supplemented with a few Home Depot parts. Water, drainage and vents are self installed as well. If you want to build one like this yourself, look into IKEA's AKURUM line with RUBRIK stainless steel finishing. It is quite time consuming, and I now know why kitchen installs are so expensive. It is simply a whole lot of work. The IKEA parts are quite clever though, and make such a project doable for a handy amateur. A big thanks to my father for all the coaching.
Friday, April 19, 2013
25 releases in 26 months.
I think I can rightfully call myself a game developer dedicated to his creation. The Little Crane That Could saw 25 releases in just 26 months. Each release making it a better game. (Except for the 4.4.1 version which made the game crash upon launch. Argh!) I can only put the blame on myself for that and a little bit on Apple, who just for that particular release, decided not to test my update. (It got approved immediately after going into state 'in review'). And I did not even count the Android, Raspberry Pi and OUYA releases here.
I clearly remember the first release when frankly, it was an unfinished product. I launched with too few levels, without sound, with penetrating music that could not be switched off and other shortcomings. Still it managed to be a hit from week 1. Not day 1, as I also clearly remember my first sales report. I was horrified to find that the game had managed to sell one single copy. At that point I had given up hope, and decided to do contracting jobs from now on, for other people's apps. However, the next days, sales increased 10 fold every day, hitting a high of 6000 sales in a single day. The game still brings in good money, so that is why the updates keep coming. Today I submitted version 5.07 for review by Apple. I hope they test launch it this time before approving.
Wednesday, April 17, 2013
Trailer for Buggy Bang! Bang!
My upcoming title 'Buggy Bang! Bang!' is nearing completion. Therefore, I have created this promotional video with a great soundtrack to give it a nice epic feeling.
EDIT - Now available on iTunes.Thursday, April 11, 2013
Googling "Little Crane"
Wednesday, April 10, 2013
False sense of security
So my new house came with Weiser Smart Key locks for the back, garage and basement doors. One morning, I could no longer open my garage. There was no way to get to my car to pickup my little monkey from day care. My builder is a great guy, so was on site with his installer in 15 minutes. They helped me get in by drilling out the lock. The next day, another guy came in and put in a replacement Weiser deadbolt lock. And what do you know, 10 minutes after he left, I discovered the same thing had happened, with a different lock, different set of keys.
At this time, I had lost all confidence in Weiser locks, and decided to replace them all. I got ANSI Grade 1 fixed key deadbolts from Schlage, and installed those on all doors that previously had that Weiser crap on them. They are robust, super smooth, and rated top grade for commercial use. My advice: never go with those 'programmable' locks known as Smart Key or SecureKey. See the photo above of all the useless crappy locks I removed from my house. Professionals rate the Weiser Smart Key of the same caliber as that lock that could be opened with a Bic pen. See this video on how to open any Weiser Smart Key lock in a few seconds.
So to sum up: Never get a programmable key, especially not the crap from Weiser that will lock you out at random. Instead get a lock that is not programmable, and rated ANSI Grade 1, for instance, this one from Schlage.
Wednesday, March 27, 2013
Ackermann
When I created Little Crane 2½ years ago, I naively thought that when a car turns left, both front wheels will turn in at the same angle. This is not the case in real cars, as this would cause scrubbing of the tyre. Instead, the inner wheel in steered in more than the outer wheel.
When creating my next games, A Blocky Kind of Love and Buggy Bang! Bang! I knew better, so implemented proper Ackermann steering. Today I ported this code back into the Little Crane code base, so the next version will feature a smoother ride. I also implemented a drive differential, so that the outer rear wheel is driven to spin faster than the inner rear wheel in turns.
It was invented in 1816 by Georg Lankensperger, whose agent was Rudolf Ackermann. This Rudolf Ackermann is not to be confused by Prof. Dr. Ir. Akkermans.
Friday, March 1, 2013
Proportional Integral Differential
I am a game programmer that spends the bulk of his time on writing physics simulations of virtual worlds. In these virtual worlds, there are typically people or vehicles. When such a person or vehicle is not under player control, but controlled by the computer, we often call this AI (Artificial Intelligence) or NPC (Non player character.) A common problem is to realistically move these entities around in the world.
A naive way to move these virtual actors would be to write code that sets the new position during each frame of the simulation. This is typically how a ghost in pacman or a space invader is moved on the screen. However, for a proper simulation this is the wrong way to do it. Modern complex games use physics simulations that calculates forces and accelerations.
This means that steering an object in the world becomes a very indirect way of steering. The algorithm sets a force (linear, or torque) which results in acceleration, or change in velocity. The velocity, lastly, will change the position and orientation of the object. This makes steering a hard problem: how do you move an object to position X? If X is far away, you would apply force towards X, but if X is near, and we are quickly approaching X, we need to apply force away from X so that we come to a halt on X without overshooting.
Luckily, this problem has been solved for us by the engineers in process control. When heating a building for instance, knowing when and how much to run the heater depends not only on the current and desired temperature, but also how the difference between the two have been changing in the past. If the temperature is rising quickly (because the heater was running full blast) it is time to stop the heater, as the the lag in the system will cause the temperature to continue rising. If a lot of cold air is entering the building causing the temperature not to rise much in the near past, heating needs to be increased. For this, the engineers use so called PID Controllers.
PID control splits the steering into three different components:
- Proportional (what is the error right now?)
- Integral (what is the historic error?)
- Differential (how is the error changing?)
Humans actually steer the same way naturally. For instance, when we need to open a door of unknown weight. First we push a little if it is closed. The longer the door remains closed, the harder we push (this is the integral part). And the faster it swings open (error decreases quickly), the less we push it, or even start pulling it. The PID system is nicely adaptive. If a hard wind is blowing the door shut, the integral steering will make that we compensate by pushing harder. This is what is called steady-state-error. The entities in our simulation will seem smarter for it, if they can adapt to changing conditions.
The PID controller calculates the required force for us, each time step in our simulation. The error is calculated from desired and actual values. Note that the desired value does not have to remain fixed, it can be a moving target. PID will cope with this automagically, e.g. when trying to aim a rifle at a chaotically moving target.
So, are there no downsides to this miracle technique? Well, not really, as long as the controller is tuned roughly correctly. We need to determine with what weights we mix the P, I and D control. We need to select three P, I, D coefficients, and the optimal values are typically different in each application. Personally, I find that selecting P roughly 10 times larger than D, and I somewhere in between always makes my steering converging nicely without much overshoot, and reasonably quickly. Note that all three coefficients need to be negative. (We need to steer against current error, against historic error and against rate of change of error. Just start with some default values (-10, -2, -1) and tweak them with the following guide:
- When system explodes due to excessive force, lower all coefficients.
- When overshoot is large, increase D, lower I.
- When convergence is slow, increase P and I, decrease D.
- When observed value jitters a lot, decrease P and increase I.
It's high time for some code now. The code is really concise, if it carries a little bit of state (previous error, historic error) along with the P,I,D coefficients.
//! Scalar PID controller typedef struct { float P; float I; float D; float previousError; // Last error float integralError; // Historic error bool fresh; // If set, we have no 'last error' yet. bool angular; // Angular PIDs have errors wrap around at -pi and +pi. } pid1_t; //! Reset a PID controller. Clears historial error. void pid1_reset( pid1_t &p ) { p.previousError = p.integralError = 0.0f; p.fresh = true; } //! Calculate the steering force based on current value (ist) and desired value (soll). float pid1_update( pid1_t &p, float dt, float ist, float soll ) { if ( dt <= 0.0f ) return 0.0f; float error = ist - soll; if ( p.angular ) { // normalize angular error error = ( error < -M_PI ) ? error + 2 * M_PI : error; error = ( error > M_PI ) ? error - 2 * M_PI : error; } p.integralError = ( p.fresh ) ? error : p.integralError; p.previousError = ( p.fresh ) ? error : p.previousError; p.integralError = ( 1.0f - dt ) * p.integralError + dt * error; float derivativeError = ( error - p.previousError ) / dt; p.previousError = error; p.fresh = false; return p.P * error + p.I * p.integralError + p.D * derivativeError; }
And to use this PID controller to aim a turret in a tower-defence game:
pid1_t pid; pid.reset(); pid.angular = true; pid.P = -10.0f; pid.I = -2.0f; pid.D = -1.0f; while ( simulating ) { ... float desired = angleTowardsTarget( turret, enemy ); float actual = angleOfGun( turret ); float steer = pid1_update( &pid, dt, actual, desired ); applyTorque( turret, steer ); ... }
And that is pretty much it. The only thing to watch out for, is that if the turret suddenly selects a different target, the PID controller needs to be reset, so that the historic error based on previous target does not influence the steering for the new target. This causes a quicker convergence. To do this, just clear the historic error. And there you have it, a smoothly targeting turret that does not need kludges for smooth-in/smooth-out parts of a synthetic animation. Animation is bad, simulation is good.
I use this PID code to:
- Smoothly move my camera.
- Smoothly reorient my camera.
- Smoothly have a tank aim at a moving enemy.
- Smoothly balance a helicopter at a desired attitude.
- Smoothly push, pull and twist oars of a rowing boat.
- Smoothly hover a bike over undulating terrain.
- Smoothly steer a missile towards a fast moving target.
Thursday, February 21, 2013
Matching up players in action games.
I am currently reading up on cartography. I need to visualize the earth in such a way that distances are accurately depicted. The way to do this is to map the globe using Interrupted Sinusoidal Mapping. Why am I so interested in distances at the moment? It is because of multiplayer gaming.
One of my goals for 2013 is to release a multiplayer game. And writing multi player action games is hard. Especially if you consider that my games always have a strong theme in physics simulation. Distributing a physics simulation is so hard, I consider it unsolved and probably intractable as well. Games typically use hacks, tricks and fakery to give the illusion of a shared experience in a virtual world with simulated physics.
What makes multi player game development hard is not bandwidth restriction, but the network latencies. In action based games (not turn based) you want immediate responses to player input. This player input needs to be communicated over the network to other parties. Even at speeds that are near the speed of light, this communication takes a very long time from the computer's point of view.
The speed of light in vacuum is roughly 300,000km/sec. This seems really fast, but if a UDP packet needs to take a one-way trip to a player 15,000km away, it would take 0.05 seconds. And guess what, in 0.05 seconds our game will have rendered three frames at 60fps. Now this is under ideal circumstances. What if the information needs to make a round trip? Then it is already 6 frames. It gets worse with the latencies introduced by the network equipment. And what if the packet makes a detour to another hub, or worse a satellite hop? And worse of all, a UDP packet can get lost altogether.
As a game programmer, I have no control over the network equipment at a player's home and at his provider. I do have control on how I match up my players though. I cannot afford to have an online match where one player is in Europe and another is in Australia. The limited speed of the signal and the long distance will create large delays between when information is sent and when it is received. I need to do the match-making in my game lobby based on geography. And while doing that, I might just as well communicate this local nature of opponent selection to the players. So I am going to visualize the pool of potential opponents for the player using a mapped globe that accurately depicts distances.
To divide the globe into player pools, I was considering using time zones. This would give 24 partitions. But in a single timezone, there is a long north-south distance. So I will split those at the equator. As there are no gamers on the North and South Pole, the resulting 48 zones should be relatively compact.
The multi player gaming API in Apple's iOS is part of GameKit. It is hard to determine how Apple servers work when matching up players. I hope they take locality into account when matching players. But I have not been able to determine whether they actually do this. So I think I have to do the region encoding myself. There is a hook in iOS for this in the GKMatchRequest class. This class lets you divide players with the playerGroup property. And using the queryPlayerGroupActivity:withCompletionHandler: method of GKMatchMaker I could even visualize player activity on the globe map.
Keep an eye out on my blog, to follow my progress in this venture. And wish me luck, I need it. There is very little documentation on distributed simulations. The main resource on this subject is seven years old and available at Gaffer on Games. I will leave you with a sneak peek at my new game.
Tuesday, February 19, 2013
Computer History
I thought I would document my computer history here. It all started with a Sinclair ZX Spectrum 48K (1982), which much later got replaced by a Sinclair Spectrum Plus (the latter I did not use very much.)
The next computer was a PC-XT clone, with Intel 8088 microprocessor running at 8MHz, equipped with a rare HEGA (Hercules/EGA combo) graphics card.
While studying computer science at the University of Amsterdam, I bought the first computer with my own (not my father's) money, which was a SOYO 386DX at 33MHz. I later equipped this machine with a 387 coprocessor.
The next computer was again a PC, and again self-built from components. The processor was an Intel Pentium at 66MHz, and I remember FedEX stopping by to swap my processor with FDIV bug for a new one.
Next up, again a PC, and it was a really good deal at the bargain bin of a dealer at the HCC dagen. I bought a real multiprocessor motherboard equipped with two Pentium PRO at 200MHz. I was working at ElectroGIG at the time, and I chose it on the floating point performance which is paramount in ray tracing technology.
After ElectroGIG, I purchased a powerhouse. Pretty much the fastest processor money could buy, thanks to some funding from the Silicon Polder Fund. I bought it during a road trip to the US, from Aspen Systems in Colorado. It was a Dec Alpha based, 533MHz ev56 164LX system called Durango II.
While I was working at SARA super computer centre as a Virtual Reality specialist, one of the supers got dismantled. It was a Parsytec CC with 56 PowerPC processors. Each node was a PReP Blackhawk 603 board. Because it was capable of running Linux, the scrapped nodes were distributed to personel, and I received one. This computer was my home server for a long time.
SARA also encouraged and subsidized private computers for employees. Under the PC-Prive regeling I bought a mini laptop years ahead of its time. The Sony Vaio C1VE is more a piece of jewelry than a plain laptop, and it featured a novel CPU from Transmeta. I was following Transmeta actively at the time, as it employed my idol Linus Torvalds at the time.
After exotic architectures like DecAlpha and PowerPC, I bought another x86 PC. Shopping around I decided to get the very average-performance but attractively priced AMD Duron.
As I had my computer switched on day and night, performance was becoming less important to me, and I wanted something silent. My first attempt at HTPC was based on Via C3.
This got replaced with my second attempt at HTPC based on Mobile Pentium technology and an AOpen 915Gmm motherboard.
Tinkering with a Sony PS3 running linux got me interested in CELL SPU programming. This in turn landed me a job with SlantSix games in Vancouver. While in Vancouver I started exploring iOS and for this purchased a Mac Mini and a Macbook Air.
Friday, January 25, 2013
The Little Computer That Could
There was once a Little Computer called Pi. He was smaller than all the big computers around him. Pi wanted to do the work that the big computers were doing. He would love to run high resolution 3D games for all the gamers in the village. But the big computers made fun of him. "Oh, you are too small for HDTV gaming." they would scorn. But the Little Computer would respond "I think I can! I Think I can!" But the big computers would not let him run the complex games. "Too slow for rigid body physics simulations" they would say. Well, "Running physics simulations, I think I can, I think I can!" responded Little Pi.
On one day, when all the expensive computers were busy charging micro-transactions to their users, Little Pi was given the task to run a crane simulator game with complex rigid body simulation and render it at a 1920x1080 resolution. The programmer demanded a fluid 30 frames per second. The Little Pi was straining, but thought "I think I can! I think I can!" It began calculating the constraints and solving the matrices, it would spew out 2 MPixel framebuffers, and there it was. "I knew I could! I knew I could!"
If you want to play The Little Crane That Could on the big screen, now you can conveniently do so with a Raspberry Pi. This little $35,- computer packs quite a punch, and manages to run the game at 1920x1080 at 30 frames per second, albeit without the dynamic shadows. So why don't you head over to the Pi Store and get yourself a free copy of The Little Crane That Could.
Friday, January 18, 2013
Raspberry Pi Explorations
Since I got challenged to porting my game to the Raspberry Pi, I got myself one of these devices. Here I will document my explorations of this device.
- I installed an OS image using RPi sd card builder tool.
- Upon booting, you get access to a tool called 'raspi-config' which lets you resize the root filesystem to take up the full size of the SD card.
- raspi-config also lets you change the keyboard mapping. This worked for the text mode keyboard, but in X I still have a UK keyboard mapping. Editing /etc/default/keyboard did not help. Typing 'setxkbmap us' in a xterm does fix it though. Why is this still a problem? It got reported a long time ago.
- I am not impressed with the case I bought for it. It is very fragile and hard to open and close. My advice is to shop for something better.
- Hooking it up to the DVI input of my Samsung SyncMaster 172w did not work. My Dell Ultrasharp works superbly at 1920x1200 though.
- You can power the device with a microUSB cable that is pligged into an iPhone charger.
- The graphics chip is the Broadcom VideoCoreIV and there is a github for it. Note that a misspelling as VideCoreIV is often repeated.
- Apparently you can do OpenGLES2 without X.
- A guide on EGL on the Pi.
- The VideoCoreIV driver lacks both GL_OES_texture_half_float and GL_OES_depth_texture which would make shadow mapping a very expensive operation. I think I will have to leave out shadows from my Little Crane port.
- I have not been able to find support for OpenSLES on the Raspberry Pi.
- To get XWindow use the full screen on TVs, you need to disable overscan in /boot/config.txt
- To get natural scrolling, like OSX does, you need to: apt-get install x11-xserver-utils followed by echo "pointer = 1 2 3 5 4 7 6 8 9 10 11 12" > ~/.Xmodmap && xmodmap ~/.Xmodmap