I Love Automating Stuff
I have always liked automating things. Not necessarily in a grand “future of work” kind of way — more in the very practical sense of: if I have to click the same button 200 times, something has gone wrong, and I would rather spend a weekend writing a script than spend 20 minutes doing it manually.
In early 2019, my friend Frederik Ledermüller introduced me to a game called Airlines Manager Tycoon. I have always been fascinated by airports, airlines, logistics, route networks, and anything involving capacity planning. I am not a gamer by any means, but this seemed like a fun little game that Fred and I could play during breaks while publishing a paper together with CERN at university.
The CERN connection
At first, we both played it casually, each running our own separate airline — bought hubs, opened routes, purchased aircraft, compared progress over lunch, and enjoyed the usual tycoon-game loop: number goes up, buy thing, number goes up faster.
The Joke That Became a Weekend Project
After a while, I started noticing patterns on my own airline. Some routes were obviously better than others. Long-haul flights were more profitable. Certain flight durations were much easier to schedule around. Route audits revealed exact demand and pricing numbers — this wasn’t a game you had to guess your way through. Other players showed up on the leaderboard, but they never competed with you for the same passengers.
At some point this became funny to Fred and me. The game looked like an airline simulation, but underneath, it behaved much more like a deterministic optimization problem. No dynamic fuel prices, no weather, no regulators, no competitor pricing pressure — just demand numbers, fixed formulas, and an old browser-based UI sitting on top of them.
For anyone who knows Selenium or any kind of browser automation, an old, predictable, server-rendered UI sitting on top of a deterministic game is basically striking gold.
So I took a weekend and tried to automate my own airline.
Programming in 2019
The Basics of the Game
The core mechanics of Airlines Manager Tycoon are fairly simple. You create an airline, buy a hub, open routes from that hub, buy or lease aircraft, configure the seats inside those aircraft, set ticket prices, and schedule flights. Over time, your airline earns money, which you reinvest into more routes, more hubs, and more aircraft.
The interesting part is that almost every decision has a numerical tradeoff:
- A hub determines which routes are available to you.
- A route has a flight duration, a demand profile, and an expected revenue.
- An aircraft has a range, a capacity, a cost, and operating characteristics.
- A cabin configuration decides how much Economy, Business, and First Class demand you can actually serve.
- A schedule determines how much of an aircraft’s day is spent earning money.
Played manually, all of this is fun for a while and tedious very quickly. Played as an optimization problem, it’s exactly the kind of thing I can’t leave alone.
Turning the Game Into Math
Speed didn't matter
1. Route duration: the 24-hour sweet spot
Short routes are fine, but long-haul flights are far more profitable per aircraft, because the plane spends less of its day idle on the ground. The real sweet spot is a route whose round trip fits cleanly into a 24-hour schedule — the aircraft flies out and back in a single day with almost no dead time.
The game itself exposes flight duration as a function of distance and aircraft speed. Here’s the actual calculation straight out of the bot, run for two reference cruise speeds (850 km/h and 911 km/h) and rounded to the nearest game-allowed quarter hour:
1duration_850_dec = float(this_distance)/850*2+2-math.floor(float(this_distance)/850*2+2)2duration_911_dec = float(this_distance)/911*2+2-math.floor(float(this_distance)/911*2+2)34if duration_850_dec > 0 and duration_850_dec <= 0.25:5 duration_850_dec = 0.256if duration_850_dec > 0.25 and duration_850_dec <= 0.5:7 duration_850_dec = 0.508if duration_850_dec > 0.5 and duration_850_dec <= 0.75:9 duration_850_dec = 0.7510if duration_850_dec > 0.75 and duration_850_dec <= 1:11 duration_850_dec = 1.001213duration_850 = math.floor(float(this_distance)/850*2+2)+duration_850_dec14duration_911 = math.floor(float(this_distance)/911*2+2)+duration_911_decThose two numbers aren’t arbitrary — they’re the two cruise-speed classes the bot cared about. Everything from the 787 family up through the A350 and 747-8I cruises at 911 km/h; the cheaper, shorter-range A310-300 (the workhorse for early, low-category hubs) cruises at 850 km/h. The A380 actually cruises at 903 km/h, but we are just going to lump it in with the 911 km/h bucket — close enough that a third duration column wasn’t worth the trouble. When the bot later picks which routes to buy, it literally branches on this: routes reachable by 911 km/h-class aircraft are ranked by 911_duration, while routes only reachable by the slower A310 are filtered by raw distance instead.
I kicked off a manual script to purchase, audit and store information about every route based from a specific hub. That evaluation took about 30 minutes. Once every route in a hub had a known distance, demand, and duration, ranking them becomes a straightforward scoring problem: maximize daily profit, reward schedules that fit neatly into 24 hours, and penalize everything else. Some hubs are simply better than others under this model — Rio de Janeiro, for instance, unlocked an unusually large number of long-haul European routes with very clean schedule timings.
2. Aircraft mix: bin-packing your passengers
The bigger problem was choosing aircraft. Buying the largest plane available for every route is wasteful — you pay for hundreds of empty seats you’ll never sell. The real question is: which combination of aircraft covers this route’s demand with the least wasted capacity?
Every aircraft is a configurable container of seats. Economy counts as 1 unit of capacity, Business as 2, First Class as 4. Total seat demand for a route comes straight from the in-game audit:
1total_seat_demand = (economy_demand+business_demand*2+first_demand*4)/2+10 # airline needs to provide this many seatsThe shortlist itself came straight from the game’s aircraft catalog — here’s the actual roster of aircraft that I decided I wanted to fly (mainly because of their range and speed):
Aircraft the passenger calculation could choose from
- A380-800853903 km/h15,000 km
- 747-8I730911 km/h14,800 km
- A350-1000522911 km/h14,700 km
- 787-10440911 km/h10,400 km
- 787-9420911 km/h15,000 km
- 787-8381911 km/h15,000 km
- A310-300275850 km/h9,600 km
From there it’s a bin-packing problem: given a route’s demand and a shortlist of aircraft sizes available on that route (capped by hub and route category), find the combination of planes that covers demand with the fewest aircraft and the least leftover capacity. The bot brute-forces this with itertools.product, widening its tolerance for waste in stages if nothing fits at first:
1smallest_plane = min(int(s) for s in planes)2max_planes = 253for x in range(1, max_planes):4 if (total_seat_demand - smallest_plane*x) <= 0:5 max_planes = x6 break78planes.append(0)9all_combinations = [p for p in itertools.product(planes, repeat=max_planes)]1011final_combinations = []12leeway = [-150, -500, -1000]13for lee in leeway:14 for x in all_combinations:15 remaining_demand = (total_seat_demand - sum(x))16 if remaining_demand <= 0 and remaining_demand > lee:17 x = [d for d in x if d != 0]18 if max_planes == len(x):19 final_combinations.append(x)20 if max_planes > len(x):21 final_combinations = [x]22 max_planes = len(x)23 if len(final_combinations) > 0:24 break25 # last leeway was too low, widen the search and try againThe smaller, standalone version of this algorithm — the one I actually prototyped first, before wiring it into the bot — looked like this. Same idea, generic box sizes instead of aircraft:
1demand = 10002boxes = [700, 400, 150, 100]3leeway = 1004# PROBLEM: combinations tested = len(boxes) ** (demand / smallest_box)5# On a MacBook Air i7 this already takes ~50 seconds for 10 million combinations.67smallest_box = min(int(s) for s in boxes)8max_boxes = int(math.ceil(demand / smallest_box))9boxes.append(0)1011all_combinations = [p for p in itertools.product(boxes, repeat=max_boxes)]12# ...filter down to the combination with the fewest boxes and least wasteIt’s a brute force, and it knows it — the comment in the original file is basically a confession. But the search space for a single route is small enough (a handful of aircraft types, capped at 7 planes) that it resolves in well under a second. The only reason this works is because I gave up on trying to optimize for the best optimal solution. As you can see, the code tries the tightest tolerance first (waste under 150 seats) and only widens it to 500, then 1000, if nothing fits. If a combination below 150 cannot be found, I was happier to just widen the leeway and be slightly less profitable than trying to include all possible planes and combinations, or spending more time trying to optimize the code.
The original python code has been rewritten to work on web. Give it a try:
Aircraft mix solver
brute-forcing 125 combinationsTotal seat demand = 1000/2 + 150 + 40×2 + 10 = 740
Passenger demand
Aircraft configured
The Browser UI Was Basically an Accidental API
Airlines Manager Tycoon’s entire interface is server-rendered, predictable HTML — exactly the kind of thing Selenium eats for breakfast. Once I had the math, the rest was “just” teaching a browser to click in the right order, several thousand times a day.
Logging in, for instance, is three form fields and a click:
1def login(uname, upass):2 driver.get('https://tycoon.airlines-manager.com')3 time.sleep(5)4 driver.find_element_by_xpath('//*[@id="username"]').send_keys(str(uname))5 time.sleep(1)6 driver.find_element_by_xpath('//*[@id="password"]').send_keys(str(upass))7 time.sleep(1)8 driver.find_element_by_xpath('//*[@id="loginSubmit"]').click()9 time.sleep(15)(As mentioned before, I didn't need this to be super stable and optimized, I just needed it to work). The rest of the bot is built from the same handful of moves, repeated relentlessly:
- Audit every route in a hub. Open each route, scrape distance, demand, and gross price, write it to MySQL.
- Rank routes by the duration and profit model above, and decide what to buy next.
- Solve the aircraft mix for the chosen route using the bin-packing logic.
- Buy the aircraft, configure economy/business/first/cargo seat splits, and assign them to the route.
- Schedule flights for every aircraft, a week at a time, staggering departures so the fleet isn’t all landing at once. (I randomly assigned when a plane would start during the day)
- Run maintenance on whatever needs a check, on a timer.
- Reinvest the profits into the next hub, and start again.
None of this is individually clever. What made it interesting is that it removed the one constraint that actually limits how big you can grow in a game like this: how many hours a day you’re willing to spend clicking. Buying one aircraft is fine. Buying a hundred is annoying. Managing a fleet that needs maintenance, scheduling, and rebalancing across thousands of aircraft a day stops being a game and starts being operations work — which is exactly the kind of work a script doesn’t mind doing at 4am.
Growth stopped being a strategy problem and became a question of time and how much in-game capital the bot could keep reinvesting.
Results
Left running, the bot kept auditing, buying, configuring, and scheduling — hub after hub, route after route — until “Broski Air” looked like this:
Broski Air — final standing
Fleet composition
At its peak, that was enough to land Broski Air inside the top 20 airlines worldwide by structural profit — among airlines run by people who, presumably, were not delegating the clicking to Python. ALthough I have absolutely no idea how anyone could do this manually.
The App Eventually Gave Up
The best part wasn’t the ranking. It was the moment the game’s own UI decided it had had enough. Broski Air’s route network had grown so dense that the in-game 3D globe — the one that draws every route as an arc between two airports — simply refused to render it:
“The display of your aerial network is too large, for performance reasons it will not be mapped.” I didn’t break any rules. I just generated a route network the rendering engine wasn’t built to draw.
By now (2026), Airline Manager Tycoon actually revamped their mobile app to now work much smoother. These kinds of messages didn't exist back in 2019, so the app regularly just crashed completely. Their webapp, the thing that the automation was built on, did not get that same revamp, so automation was basically out of the window by the end of 2020 :( I stopped playing since then.
There was nothing sophisticated about any of this — no polished software product, no clever infrastructure. Just Python, Selenium, a couple of route tables, a server.
A weekend joke between two friends, scaled into 45,000 aircraft, trillions in in-game value, and a route network so large the map itself gave up trying to draw it. Still my favorite error message of any piece of software I’ve shipped.
6 years later. Check-in in 2026
Broski Air is still sitting there, untouched since the bot last ran. While writing this post, out of curiosity, I logged back in to see what happened to it. Here’s the ranking screen as of today, in June 2026 — after running a maintenance check on my aircraft:
Rank 97. Still proud of that one.