Recently, I have been experimenting with JavaFX in Clojure. Initially, in one of my experiments, I wanted to learn how to re-size a game-board interface as it’s containing window was re-sized. In the past I’ve had medical device interfaces that draw a representation of a physical device and these drawings must re-size as their window is re-sized. The initial experiment was with a simple interface for Tic-Tac-Toe. Since I had such a nice interface, I thought, why not program the complete game.

Well, it’s done. The code is here. It’s unbeatable. If you happen to win, it’s a bug.

An In-Progress Tic-Tac-Toe Game
An in-progress Tic-Tac-Toe game

The key to re-sizing is to watch the width and height properties of the window containing the board and attach change listeners to the properties. The relevant code from the program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
(doto (.widthProperty canvas)
(.bind (.widthProperty center))
(.addListener
(proxy [ChangeListener] []
(changed [ov old-state new-state]
(redraw-board canvas)))))
(doto (.heightProperty canvas)
(.bind (.heightProperty center))
(.addListener
(proxy [ChangeListener] []
(changed [ov old-state new-state]
(redraw-board canvas)))))
...

Every time the canvas is re-sized, the redraw-board function is called. That function takes care of calculating the new size of the board and symbols, then draws them.

Another feature of JavaFX is the ability to style the interface elements. Since flat interfaces are all the rage now, I thought I would play with that too. This aspect of JavaFX still feels a little unfinished in that some styling can be accomplished programmatically while other styles can only be done with CSS. For example, note the “Reset” button in the screen capture above. You can set some of the style directly in the program. To get all of the behavior you want for the button, such as mouse-over handling, you have to use a CSS file. Here’s the file I used for the program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/********************************************************************************
*                                                                               *
* Push Button - Styles stolen from Pedro Duque Vieira a.k.a. "Pixel Duke".      *
* See his post at                                                               *
* http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ *
*                                                                              *
********************************************************************************/
.root {
-fx-background-color: #fafad2;
}
.button {
-fx-padding: 5 22 5 22;
-fx-border-style: null;
-fx-background-radius: 0;
-fx-background-color: #cccccc;
-fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif;
-fx-font-size: 11pt;
-fx-text-fill: black;
}
.button:hover {
-fx-background-color: #d8d8d8;
}
.button:pressed, .button:default:hover:pressed {
-fx-background-color: black;
-fx-text-fill: white;
}
.button:focused {
-fx-border-color: black;
-fx-border-width: 1;
-fx-border-style: segments(1, 1);
-fx-background-insets: 0 0 0 0, 0, 1, 2;
}
.button:disabled, .button:default:disabled {
-fx-opacity: 0.4;
-fx-background-color: #cccccc;
-fx-text-fill: #212121;
}
.button:default {
-fx-background-color: #40e0d0;
-fx-text-fill: #ffffff;
}
.button:default:hover {
-fx-background-color: #48d1cc;
}

I’ve already written about achieving similarly styled dialogs in MonologFX.

One of the nice things about writing a program like this in Clojure is the way listener-type methods can be written. This program has a few. Some ChangeListeners were shown above. There are also a couple of EventListeners for button clicks. In fact, most of the game play occurs in the method that listens for player clicks on the game board.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
(defn canvas-click-handler
"Handle a click on the board canvas. If the click is within the bounds of the
board, interpret it as an attempt by the human player to place a symbol on
the board. If the click is on an empty position, place the symbol and get
the computer's response. If the game is over after either the human or
computer plays, declare the winner or draw and offer to start a new game."
[canvas]
(reify EventHandler
(handle [this event]
(let [x (.getSceneX event)
y (.getSceneY event)]
(if (click-is-in-bounds? x y)
(let [pos (click-pos->board-pos x y)]
(when (square-is-empty? @position pos)
(fill-square pos @me)
(redraw-board canvas)
(if (logic/game-is-over? @position)
(declare-winner canvas)
(let [square (get-opponent-move @position @me)]
(fill-square square (logic/opponent @me))
(redraw-board canvas)
(if (logic/game-is-over? @position)
(declare-winner canvas)))))))))))
...

This method still feels like there is some room for improvement, but it works for now.

The computer player is closely derived from the one described in Chapter 10 of “Simply Scheme” by Brian Harvey and Matthew Wright. The unit tests in the project consist mostly of the example function usage shown in that chapter. The book is available in several forms on-line. It is unbeatable. If you happen to beat the game, it is undoubtedly a program bug introduced in my translation.

If you are interested in the complete code for the project, it is on BitBucket here.