Bring Your Own Code: The End of the Lucky Deuce
What feels like forever ago, we introduced the Lucky Deuce casino contest. This is a series of challenges, brought to you by our pals over at Infragistics, where we call on you to help us build a "scoundrel's casino".
Last week, we introduced our final challenge: take one of our casino games, add a betting system, and then build a strategy that lets you keep playing (by distributing the load across multiple accounts).
Once again, we had a nice pile of great submissions, and there are two who are going to get to take home a nice TDWTF hoodie for their troubles. As always, check out the full projects on GitHub.
For all of our winners (from this week or any previous)- or anyone who entered using Infragistics controls- expect an email in the next week or two to follow up about how best to get you prizes.
WinnersFirst, let's talk about Bruno's solution. For bonus points, he decided that his system should be able to detect a user's gender by their name:
public String getSex() { return (username.matches(".*[aeiou]+$")) ? "female" : "male"; }
Now, that has nothing to do with the requirements, but I always get a chuckle out of how poorly these sorts of regex-based gender detectors work. How did Bruno implement the requirements?
Like an onion, there are layers. First, he introduces a subtle bug in the User.equals method:
@Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final User other = (User) obj; if (!Validator.validateTrue(this.profile = other.profile)) { return false; } if (Validator.isNull(this.username) ? !Validator.isNull(other.username) : !Validator.validateEquals(this.username, other.username)) { return false; } return this.username.equals(other.username); }
Note on the validateTrue line, he's misusing the assignment operator- something Java tries to prevent. This means that, when comparing two User accounts, they end up using the same profile.
That profile property is important- there's one profile called CHEATER which is applied to anyone that's caught cheating. With a method included that allows two players to send money to each other, you're basically set up to do a comps hustle, but with a twist- the CHEATER tag is only set at login. If two players login, they can play the comps hustle until one of them gets caught and kicked out- if the "uncaught" player sends money to the "caught" player, they also get marked as a cheater- but since the flag is only checked at login, they can sit there and keep playing. And, if they want to keep making money, Bruno included a "catcha[sic]" on login that uses the same random number generator as the games being played- which means that by checking the captchas you can deduce what was used to seed the RNG:
for (int i = 1; i < 14; i++) { source.add("catcha/" + i + ".jpg"); } for (int j = 13; j > 1; j--) { catcha.add(source.get(random % j)); source.remove(random % j); }
It's not exactly our requirements, but it's a clever way to leak state about your RNG, and since a clever cheater can avoid getting kicked out of this casino, it does mean you can play indefinitely.
Our other winner this week is also the only player who has entered every week, and already has collected a nice pile of swag: Jonathan is back, with yet another great iteration of the same RNG with a solid strategy for confounding the casino.
Using our "biased" roulette wheel from the first week, Jonathan decided to build four classes of account- Winners, Losers, High Rollers and the Cage.
Winners use their knowledge of the wheel to keep earning money, Losers still make smart bets, but still lose to the house edge. The high rollers are the risky players- they make big bets, they're volatile players, but because of their knowledge of the wheel's behavior, they may have seemingly big losses, but on average, never actually lose that much. The Cage is just a player that holds the money generated by the winners to hand off to the losers, if needed.
It's a little long, but here's the complete implementation of Jonathan's strategy for all of our different players:
void operator() () { // If increment is positive, we're a "winner" and use inside bets. // If increment is negative, we're a "loser" and use outside bets. // If increment is zero, we're a "high roller": // use inside bets until RNG state is known and float is adequate, // then use "full complete" bets for maximum returns. balance = FREE_BALANCE; compsEarned = 0; uncertainty = 64; std::unique_lock<std::mutex> lock(cageMutex, std::defer_lock); // simulated connection to Lucky Deuce casino char* buffer = (char*) malloc(257); FILE* server = popen("yes \"\" | ./roulette", "r"); assert(server); fgets(buffer, 256, server); // initial prompt // rigged RNG RoulettePlayerHistory wheel; // proper RNG FILE* rngSrc = fopen("/dev/random", "rb"); printf("%s: joining the table.\n", name); while(1) { while(balance < bet) { // replenish float from the cage uint32_t wanted = bet; if(FREE_BALANCE > wanted) wanted = FREE_BALANCE; if(!increment && bet * 40 > wanted) wanted = bet * 40; wanted -= balance; lock.lock(); if(cageBank > wanted) { printf("%s: taking $%u from the cage.\n", name, wanted); balance += wanted; target += wanted; cageBank -= wanted; } else { printf("%s: taking $%u from the cage.\n", name, cageBank); balance += cageBank; target += cageBank; cageBank = 0; cageQueue.wait(lock); } lock.unlock(); } if(increment > 0 && balance > target && target > FREE_BALANCE) { uint32_t surplus = target - FREE_BALANCE; lock.lock(); printf("%s: donating $%u to the cage.\n", name, surplus); cageBank += surplus; balance -= surplus; target -= surplus; lock.unlock(); cageQueue.notify_all(); } if(increment > 0 || (-increment) > target) target += increment; uint32_t threshold = bet; uint32_t effectiveUncertainty = uncertainty; if(balance > target) effectiveUncertainty += balance - target; if(increment < 0) effectiveUncertainty = 16; uint32_t stats[38] = {0}; wheel.Predict(effectiveUncertainty, stats); // choose a bet to place if(increment > 0) { uint32_t betIndex = cr.BestOutsideBet(stats); printf("%s: betting $%u on %s.\n", name, bet, cr.betTypes[betIndex].name); cr.PlaceBet(betIndex, bet); balance -= bet; } else if(increment < 0) { uint32_t betIndex = cr.BestInsideBet(stats); printf("%s: betting $%u on %s.\n", name, bet, cr.betTypes[betIndex].name); cr.PlaceBet(betIndex, bet); balance -= bet; } else { if(!uncertainty && balance > 40*bet) { uint8_t betNumber = cr.BestFullCompleteBet(stats); printf("%s: betting %u to the *MAX*!\n", name, betNumber); balance -= cr.PlaceFullCompleteBet(betNumber, bet); } else { uint32_t betIndex = cr.BestInsideBet(stats); printf("%s: betting $%u on %s.\n", name, bet, cr.betTypes[betIndex].name); cr.PlaceBet(betIndex, bet); balance -= bet; } } again: // spin the wheel usleep(SPIN_TIME); fgets(buffer, 256, server); uint8_t spin = 0; if(!strcmp(buffer, "00\n")) { spin = 37; } else { char* e; spin = strtoul(buffer, &e, 10); if(spin > 36 || !e || *e != '\n' || !*buffer) { printf("%s: Miti vittu?!? '%s'\n", name, buffer); goto again; } } uint32_t payout = cr.PayBets(spin); if(payout) printf("%s: Spun %u, won $%u! :D\n", name, spin, payout); else printf("%s: Spun %u, didn't win. :(\n", name, spin); balance += payout; wheel.Update(spin); uncertainty = (uncertainty ? uncertainty-1 : 0); } };
Jonathan's solution does involve two different programs, so you'll need to compile roulette.cpp to provide the wheel, and roulette-hustle.cpp to simulate players. Run roulette-hustle, and it worries about launching roulette.
Congratulations to all of our winners and participants in every week of the contest. Hopefully everyone had as much fun reading through the entries as I did.
The ConclusionIt's six months later. You haven't heard anything from Paula since your rushed-hack job behind the coffee shop dumpster, but you haven't gone looking, either. With the profits you got flipping comps, and dipping into rigged gambling games, you were able to clean up your reputation.
No more run-down hotels for you, no more fly-by-night coding gigs, no more running from the FBI. You're staying in the Hyatt Regency right on the Embarcado in San Francisco. You check your reflection in the mirror on your way out into the hallway- you're dressed to the nines in the most professional fashion imaginable, because you're in town to speak at a security conference, of all things.
That's just one of the perks of giving up your life of crime and turning into a consultant. Honestly, it's even more of a con than the stuff you did for the Lucky Deuce: you give bad advice that will either be ignored or horribly misimplemented, and charge thousands of dollars an hour for it. Every time your conscience starts to nag at you, you think back to what it was like staring down the barrel of Paula's gun. Besides, with the rates you charge, only big companies who can soak the costs hire you anyway.
You step inside the elevator, beside another guest who looks like a gorilla wearing a cheap suit. You press the button for the atrium, and turn around to look out the glass-walled elevator as the atrium the size of an airplane hanger rushes up at you.
The gorilla pushes the emergency stop. He smiles at you like he's about to eat you for lunch. You realize something must be very wrong, and your mind latches onto the fact that you can't be in that much danger- everyone in the hotel can see what happens in this elevator.
"My employer used to own the Lucky Deuce," he says. His accent is cultured- English is obviously not his native tongue, but the sharp edges of his accent have been rubbed down so much that you'd never be able to tell where in the world he came from. The clipped and clear diction contrasts with everything else about his appearance. "My employer is displeased by the disruptions in its productivity- and would like to discuss your future."
The ape hands you a thumb drive, then releases the elevator, getting off at the next floor. That's when you realize" this might not be over"
To Be Continued"?Thanks to Infragistics for making this possible.
A worldwide leader in user experience, Infragistics helps developers build amazing applications. More than a million developers trust Infragistics for enterprise-ready user interface toolsets that deliver high-performance applications for Web, Windows and mobile applications. Their Indigo Studio is a design tool for rapid, interactive prototyping.