A key component to online business is self-promotion. Link bait is popular: a blog article or web page that gets passed around because it is informative or so damn funny is a good way of spreading the word that your business exists.
Which gives me a good excuse to link to a prime example: Weird Books. This chuckle fest includes “Jewish Chess Masters on Stamps” and “Tattooed Mountain Women and Spoonboxes of Daghestan”.
To all my loyal customers and blog readers, I wish you a happy New Year.
Regarding the latest attempt to blow up a US airliner, there’s been a lot of words spoken on news channels and written in mainstream media and on blogs about the TSA, air safety, and whether terrorism on board passenger airplanes can be stopped. There’s complaints about the way security inspectors at airport do (or don’t do) their jobs. Low-paid, bored workers, despised simply for doing their tedious jobs, make people take off shoes but inadvertently let cigarette lighters and pocket knifes onboard.
If you want to see air safety done properly, travel on El Al to Tel Aviv. Then travel back again. El Al jets have been prominent targets for attacks for decades. The Israelis have learnt how to keep their jets in the air. From Wikipedia:
El Al is widely acknowledged as the world’s most secure airline, after foiling many attempted hijackings and terror attacks through its security protocols.
When my girlfriend and I travelled on El Al from Frankfurt to Tel Aviv a couple of years ago, we were questioned in detail before we were allowed to check in by a polite woman about how we met, how long we had known each other, why we travelling, etc. We went through normal Frankfurt airport security measures, then a thorough double pat-down from Israeli guards at the boarding lounge. The pat-down include private areas. Our plane, while on the ground, was surrounded by German police. An APC with a machine-gun turret was parked nearby. As the plane taxied and accelerated for take-off, a German police car drove alongside for as long as possible.
On the way back from Tel Aviv, all our baggage was x-rayed. In her backpack my girlfriend had some souvenir salt and sand she scraped up from the Dead Sea and put in plastic bags. This looked suspicious in the x-ray, so her bags were searched thoroughly.
Something that stood out was that every person involved took the job seriously.
The method to get PokerStars tournament summaries into Poker Copilot is subpar. I’ve improved it over time, but the basic problem is this: you have to manually copy and paste from an email to a Poker Copilot dialog. The email can contain up to 200 tournament summaries. Each of these may be 10,000 lines or more – a recent PokerStars tournament had 100,000 players – depending on what tournaments you play. That’s potentially two million lines of text you need to copy via the clipboard from your email to Poker Copilot. Pasting two million lines of text will crash many an app – including Poker Copilot.
I did some experimental work today in accessing my Gmail account directly from Poker Copilot. It seems to work satisfactorily, although some heavy testing is in order.
Here’s a mockup of the user interface:
Why only Gmail? Making sure Poker Copilot works well with a range of email services is a ton of testing work. I can foresee a world of user support pain if I support all email services. So I figured it was better to only support a few very popular services. Gmail is a good starting point. It’s free. It’s reliable. I use it myself. Once I get the concept running well, I’ll probably add support for me.com as well.
Today I took some time off from Poker Copilot and contributed to BlazingStars instead. My task: add auto-detection of the current PokerStars theme to BlazingStars.
I have already done this in Poker Copilot. Therefore I took the existing Poker Copilot Java code and converted it to Objective-C, so that BlazingStars could also use it.
While doing this I noticed that the code seemed more complex than I would have expected. There were some strange clauses. One block checked two different locations for the PokerStars preferences. Another code block accommodates unusual theme names that don’t follow the typical pattern. I added each of these clauses after chasing down problems that users reported. Each problem only arose after thousands of people had downloaded and used Poker Copilot.
After less than two years, Poker Copilot has legacy code. Legacy code is a pejorative term in the software industry that originally applied to code from older, no longer supported systems, languages, or operating systems. Over the years, the meaning has expanded to refer any existing code that doesn’t use the latest, greatest technologies, or seems more difficult to understand than to rewrite.
In my opinion, legacy code is good code. It is code that has survived because it works. It is code that has had bugs removed and work-arounds added. It is code that can cope with unusual situations. It is code that stands behind many days, months, and years of application use. Rewriting legacy code usually leads in the short term to a less stable, less reliable system.
Having legacy code in the system also means one has to work somewhat slower. Fixing a problem means first understanding why the existing code has various clauses. It also means care must be taken when adding new features.
I try to reduce the negative impact of legacy code in my own projects by dividing coding time between cleaning up existing code and adding new features. When solving an existing problem I go through the following steps:
Locate the problem
Add a unit test that fails because of the problem
Clean up the existing code to make it more understandable
Make sure the full set of unit tests – excepting the one added in step 2 – still pass. If not, I return to step 3
Fix the problem identified in step 2
Make sure the full set of unit tests – including the one added in step 2 – pass. If not, I return to step 5
Thinking of buying Poker Copilot? Got a relative or friend who still doesn’t know what to get you for Christmas?
After a quick chat with a business advisor (also known as my girlfriend), I made an impromptu decision. Between now and the end of December, Poker Copilot is $10 cheaper. Instead of $59.95, it costs $49.95. Recent sales of Poker Copilot have been strong. What better time to encourage even more people to buy?
Upgrades from version 1 to version 2 are also $10 cheaper. So if you still haven’t upgraded, upgrade now for $19.95.
Steven Hamblin created BlazingStars, an AutoHotKeys-like app for Mac OS X. It currently works with PokerStars. For various reasons Steven is not able to work on BlazingStars as much as he would like.
My recent blog articles on speech controlled poker prompted Steven to contact me. After some e-mail discussion, he decided to make his work open source. This is good news for the Mac poker community.
If you are a Mac poker player and a programmer then please join in. It’s all in Objective-C. It seems to be in a stable condition and working solidly. Download the app. Use it. Report bugs. Grab the source.
Here’s a video of me using speech recognition on a Full Tilt play money table:
As related here and here, I recently hooked Java into Mac OS X’s Speech Recognition API and made the mouse click on buttons in response to spoken commands.
Today I investigated ways to find the poker room table buttons. I decided to limit what I’m doing for now to Full Tilt Poker “classic” theme.
It seems that in a Full Tilt window, the buttons are always at fixed proportions. If I resize the window, the buttons resize too, and remain always at the same location in proportion to the window size. Good.
Now I have to work out the location and size of the current Full Tilt window. I discovered yesterday that as of Java 6, you can run AppleScript directly within Java. This makes the task extremely easy. It’s not particularly efficient but it works. The following Java method uses AppleScript to fetch the bounds of the main window of the front most application:
public Rectangle getMainWindowBounds() { try { String script = "tell application \"System Events\"\n" + " set theprocess to the first process whose frontmost is true\n" + " set thewindow to the value of attribute \"AXMainWindow\" of theprocess\n" + " set thelocation to the position of thewindow\n" + " set thesize to the size of thewindow\n" + " return thelocation & thesize\n" + "end tell";
ScriptEngine engine = new ScriptEngineManager().getEngineByName("AppleScript");
ArrayList list = (ArrayList) engine.eval(script); int x = ((Long) list.get(0)).intValue(); int y = ((Long) list.get(1)).intValue(); int width = ((Long) list.get(2)).intValue(); int height = ((Long) list.get(3)).intValue(); return new Rectangle(x, y, width, height);
} catch (ScriptException e) { throw new RuntimeException(e); } }
This was the missing piece of the puzzle. The centre of button 1 – either Fold or Check – is (window.x + window.width * 0.689, window.y + window.height * 0.946)
Now it is possible for me to open a Full Tilt poker table, move and resize the window, then speak my commands. As a test I played on two tables simultaneously and had it working, although I had to manually set the focus to the window I wanted to speak to.
Finally here is the entire app. It’s low on error-checking, and assumes Full Tilt is the current front-most application. It’s a good starting point for someone who wants to take the idea further.
public static void main(String[] args) { Runnable runnable = new Runnable() { public void run() { final NSAutoreleasePool pool = NSAutoreleasePool.new_(); try { new SpeechDemo().setUpSpeechRecognition(); } finally { pool.release(); } } }; new Thread(runnable).start();
JFrame frame = new JFrame("Speech Demo"); frame.getContentPane().add(new JButton(new AbstractAction("Quit") { public void actionPerformed(ActionEvent e) { System.exit(0); } }));
private Point getPointInWindow(double xfactor, double yfactor) { final Rectangle bounds = getMainWindowBounds(); final int x = (int) (bounds.x + bounds.width * xfactor); final int y = (int) (bounds.y + bounds.height * yfactor); return new Point(x, y); }
public Rectangle getMainWindowBounds() { try { String script = "tell application \"System Events\"\n" + " set theprocess to the first process whose frontmost is true\n" + " set thewindow to the value of attribute \"AXMainWindow\" of theprocess\n" + " set thelocation to the position of thewindow\n" + " set thesize to the size of thewindow\n" + " return thelocation & thesize\n" + "end tell";
ScriptEngine engine = new ScriptEngineManager().getEngineByName("AppleScript");
ArrayList list = (ArrayList) engine.eval(script); int x = ((Long) list.get(0)).intValue(); int y = ((Long) list.get(1)).intValue(); int width = ((Long) list.get(2)).intValue(); int height = ((Long) list.get(3)).intValue(); return new Rectangle(x, y, width, height);
} catch (ScriptException e) { throw new RuntimeException(e); } }
What’s next? I think I’ve done as much with this as I intend to. It’s been a diversion for me during some particularly cold winter days. I’m considering putting together everything I’ve done into Google Code as an open source project as the basis of an auto-hot key app. If you’re interested in seeing this become an open source project, let me know in the comments.
Last night I got my Mac to understand me when I said basic poker terms like “Fold”, “Call”, and “Raise”. This evening I took the idea a bit further. What if, when I said “Fold”, my Java program would fold my hand for me?
This should be a insanely easy task using AppleScript. Unfortunately none of the poker room clients seem to be scriptable. But if I know where the “Fold” button is, perhaps I could tell my Mac to simulate a mouse click on that spot?
I opened a poker table at Full Tilt and used Pixie to measure the locations of the Fold, Check, Raise, and Call buttons. Then I got my Java program to click at the right spot for each word.
Did it work? You betcha! It was cool saying “Fold” and watching it work.
Here’s the code that does most of the work:
private static void setUpSpeechRecognition() { SpeechRecognizer speechRecognizer = new SpeechRecognizer(); speechRecognizer.setSpeechRecognizerListener( new SpeechRecognizerListener() { public void didRecognizeCommand(String command) { if (command.equals("Fold")) { click(627, 704); } else if (command.equals("Check")) { click(627, 704); } else if (command.equals("Raise")) { click(864, 704); } else if (command.equals("Call")) { click(750, 704); } } }); speechRecognizer.setCommands("Fold", "Check", "Raise", "Call"); speechRecognizer.setListensInForegroundOnly(false); speechRecognizer.setDisplayedCommandsTitle("Poker Copilot"); speechRecognizer.startListening(); }
private static void click(final int x, final int y) { try { Robot robot = new Robot(); robot.mouseMove(x, y); robot.mousePress(InputEvent.BUTTON1_MASK); robot.mouseRelease(InputEvent.BUTTON1_MASK);
} catch (AWTException e) { throw new RuntimeException(e); } }
The next task: determining programmatically the location of the command buttons at a poker table.
This release contains nought but a couple of bug fixes.
What’s fixed:
The “More…” toolbar button should now work for all people. Some people were having trouble. I switched it from a “click and hold” model to a “click and release” model. It was surprisingly difficult to do: a day’s work to add what I assumed to be a common component.
Mucked cards were not always been shown after a showdown hand. Thanks to loyal Poker Copilot customer Dave for creating a number of videos showing this problem and pinpointing when the problem occurred.
When I got my first Mac, I discovered the voice-controlled Chess app. Well, sort of voice-controlled. “Pawn e2 to e4”, I would say, and my bishop would move. I don’t even enjoy chess but I was in Mac-awe, and showed anyone who would look how amazing it was that I could play voice-controlled chess.
Due to a recent e-mail conversation I got curious as to what it takes to add voice control to a Mac OS X app. I played around this evening for fun and was surprised how simple it is. My aim was to create an app that listens to basic poker commands, such as “Call”, “Fold”, and “Raise”.
With help from Google, I managed to do it in Java, via the Rococoa bridge between Cocoa and Java. Here’s pretty much the entire program, once the Rococoa boiler-plate code is removed: