Self Promotion the Internet Way

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.

Air Safety The Israeli Way

Completely off-topic for NYE:

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.

It was an experience.

Can Poker Copilot read Tournament Summary Emails Directly?

Yes. Soon. In the next update.

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:

Screen shot 2009-12-30 at 2.04.36 PM.png

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.

Legacy Code Already

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:

  1. Locate the problem
  2. Add a unit test that fails because of the problem
  3. Clean up the existing code to make it more understandable
  4. Make sure the full set of unit tests – excepting the one added in step 2 – still pass. If not, I return to step 3
  5. Fix the problem identified in step 2
  6. Make sure the full set of unit tests – including the one added in step 2 – pass. If not, I return to step 5

Poker Copilot Christmas Special: $10 Off

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.

Buy Poker Copilot 2 now for $49.95.

Upgrade to version 2 now for $19.95.

There’s no catches, no tricks, no hidden clauses. You’ll get the full Poker Copilot 2, including all version 2 updates.

Remember, this offer is valid only until 31st December 2009.

BlazingStars: Mac Poker Auto HotKeys Open Sourced

BlazingStars is now open sourced on Google Code.

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.

Screen shot 2009-12-22 at 11.24.00 AM.png

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.

Java and AppleScript: Speech Part 3

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.



package com.barbarysoftware.pokercopilot.speech;

import org.rococoa.cocoa.foundation.NSAutoreleasePool;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.util.ArrayList;

public class SpeechDemo {

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);
}
}));

frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}

private void setUpSpeechRecognition() {
SpeechRecognizer speechRecognizer = new SpeechRecognizer();
speechRecognizer.setSpeechRecognizerListener(new SpeechRecognizerListener() {
public void didRecognizeCommand(String command) {
if (command.equals("Fold")) {
clickButton1();

} else if (command.equals("Check")) {
clickButton1();

} else if (command.equals("Call")) {
clickButton2();

} else if (command.equals("Raise")) {
clickButton3();

}
}
});

speechRecognizer.setCommands("Fold", "Check", "Raise", "Call", "Grab");
speechRecognizer.setListensInForegroundOnly(false);
speechRecognizer.setDisplayedCommandsTitle("Poker Copilot");
speechRecognizer.startListening();
}


private void clickButton1() {
clickAtPoint(getPointInWindow(0.689, 0.946));
}

private void clickButton2() {
clickAtPoint(getPointInWindow(0.820, 0.946));
}

private void clickButton3() {
clickAtPoint(getPointInWindow(0.951, 0.946));
}

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);
}
}

private void clickAtPoint(Point point) {
try {
Robot robot = new Robot();
robot.mouseMove(point.x, point.y);
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseRelease(InputEvent.BUTTON1_MASK);

} catch (AWTException 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.

Speech, Java, Poker, Mac OS X

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?

That was the idea. Surprisingly it works. I found a tiny Mac program that simulates mouse clicks, even in non-scriptable applications.

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.

Preview of Poker Copilot 2.22 Ready to Download

Poker Copilot 2.22 is now available to download.

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.

Update Instructions:

  1. Download version 2.22 here.
  2. Open the downloaded file.
  3. Drag the Poker Copilot icon to the Applications icon.
  4. If prompted to replace an existing version, confirm that you do want to replace.

Now you’re done and ready to hit the tables.

Speech Recognition on Mac OS X in Java

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:

   
private static void setUpSpeechRecognition() {
NSSpeechRecognizer recog = NSSpeechRecognizer.CLASS.alloc().init();
recog.setDelegate(new NSSpeechRecognizer.NSSpeechRecognizerDelegate() {
public void speechRecognizer_didRecognizeCommand(
NSSpeechRecognizer sender, NSString command) {
System.out.println("You said " + command);
}
});
NSArray commands = NSArray.CLASS.arrayWithObjects(
NSString.stringWithString("Check"),
NSString.stringWithString("Raise"),
NSString.stringWithString("Call"));
recog.setCommands(commands);
recog.setListensInForegroundOnly(false);
recog.setDisplayedCommandsTitle("Poker Copilot");
recog.startListening();
}

Now I too can have an app that responds haphazardly to voice control. Here’s some evidence:

Screen shot 2009-12-17 at 9.46.17 PM.png