“The attempt to combine wisdom and power has only rarely been successful and then only for a short while.”
—Albert Einstein
Until now, most of the concepts discussed throughout the course of this book were either described abstractly or, for the sake of simplicity, demonstrated with small blocks of code or trivial programs. For this final chapter, we’ll apply what we’ve learned to create something with a bit more substance. Our sample application utilizes and combines many more of the techniques and mechanisms elaborated upon, more closely resembling how JavaFX might be employed in the real world.
The application we’ve chosen to implement is the game of Sudoku. As Sudoku has gained considerable worldwide popularity recently, it is likely to have been encountered by many reading this book. To the uninitiated, you’ll find that Sudoku is easy to learn, and furthermore easy to become addicted to playing. From a JavaFX perspective, Sudoku represents a reasonable example of how the logic required for the rules of the game and the presentation of that game can be nicely delineated.
For those unfamiliar with the game, a standard Sudoku board has nine rows and nine columns of spaces. The board is also grouped into nine boxes or regions. To solve a Sudoku puzzle, the numbers 1 through 9 must appear in each row, column, and box—but only once—and not in any particular order. New Sudoku puzzles start out with a certain number of spaces already filled in. In general, the fewer the number of pre-defined spaces, the more difficult the puzzle. The job of the person playing the game is to use logic to fill in the rest of the spaces.
The Sudoku application is available online and can be accessed by pointing your browser to the following URL:
Upon reaching this page, you have the option of running Java FX Sudoku in one of two ways:
• As a standalone application. This option utilizes Java Web Start technology to start the JavaFX Sudoku application as a separate process, independent from the browser.
• As an applet. This option allows you to run JavaFX Sudoku inside the browser as a traditional applet. Depending upon your system configuration (operating system, Java Runtime Environment, browser, etc.), you may be able to take advantage of the draggable applet feature, in effect giving you the ability to undock the applet from the browser.
Figure 13.1 depicts and describes the onscreen interface of the Sudoku program. Logically, the interface can be divided into three areas:
• The top section of the interface serves as a window frame. If you drag your mouse within this area, you’ll be able to reposition the Sudoku application on the screen. If you are running Sudoku as an applet within a browser, dragging your mouse in this area will enable you to undock the applet from the browser if your overall environment supports the draggable applet feature.
• The middle section, which encompasses a majority of the interface, contains the Sudoku board. By hovering your mouse over a space on the board, you can modify its contents by either clicking the mouse to enter in a new number or by typing a number from 1 to 9. If you want to clear a space, you can type either 0 or <space>. When a new puzzle is generated, a certain number of spaces will be filled in for you to aid in solving the puzzle. These spaces cannot be modified.
• The bottom section of the interface contains menu buttons needed to interact with the application. They include buttons to create a new game, instruct the user as to how to play, set the level of difficulty of the puzzle, reset the game to its original state, solve the puzzle, and quit the game.
Figure 13.1 The Sudoku Application Interface
The source code for JavaFX Sudoku can also be found at http://jfxbook.com/Sudoku/. Developed with the NetBeans Integrated Development Environment, the source comes bundled with project metadata to facilitate seamless integration into NetBeans.
Table 13.1 lists and briefly describes the packages that make up the JavaFX Sudoku application. It incorporates both JavaFX and Java source, plus images that are used as part of the overall presentation.
Table 13.1 Packages in the Sudoku Application
The JavaFX source files that comprise the sudoku
package (referenced in Table 13.1) are, in general, divided into two types of files. Those directly involved with the application interface have public classes that extend the CustomNode
class and are suffixed with Node
(e.g., Board
Node
.fx
, IconButton
Node
.fx)
. Source files without the Node
suffix do not extend the CustomNode
class and are involved more with the logic behind running the Sudoku application. Table 13.2 lists and describes the sudoku
package files.
Table 13.2 JavaFX Source Files in the sudoku Package
Briefly mentioned earlier, the application has been architected such that the overall game logic and the user interface have been cleanly separated in a relatively straightforward fashion. As a rule of thumb, source files suffixed with Node
are presentation or interface files, whereas those without the Node
moniker are dedicated to providing the logic necessary to play the Sudoku game.
The game logic for the Sudoku application is primarily supplied by two JavaFX classes: Board
and Space
. The Board
class represents a Sudoku board. It has code to interpret the rules of the game and is ultimately responsible for starting a new game, determining if an individual move is valid, maintaining the state of the game, and providing a solution to the puzzle.
The Board
class includes a sequence of 81 Space
s representing the 9×9 grid of spaces that make up a standard Sudoku puzzle. At initialization, the Board
class identifies which row, column, and region (or box) each of the 81 Space
s belong to and groups them accordingly. Each Space
has instance variables that identify its row
/column
/region
, and most importantly, a number
variable holding the value that is currently assigned to this space. The value of number
is used to interpret the current state of the Space
. Externally, Sudoku spaces can only have a numeric value ranging from 1–9, or be blank. Internally, the number
value for a Space
instance has a larger range. Table 13.3 explains the possible values that can be assigned to a Space
’s number
instance variable, and how they affect what is ultimately displayed to the user.
Table 13.3 Range of Internal Values a number Instance Variable Can Be Assigned and How They Are Ultimately Displayed Externally
The Board
and Space
classes have interface counterparts, named BoardNode
and SpaceNode
respectively, which handle the task of presenting the Sudoku game to the user. The BoardNode
class is responsible for the overall layout of the application; for each Space
, there is a corresponding SpaceNode
, which manages the input and display of that space on the puzzle. When the BoardNode
instance is initialized, it includes an instance variable called board
, which is a reference to the Board
instance. Likewise, each SpaceNode
instance contains an instance variable called space
, which points to its Space
counterpart. Figure 13.2 shows the relationship between Board
/Space
and BoardNode
/SpaceNode
.
Figure 13.2 Relationship Between Board/Spaces and BoardNode/SpaceNodes
With these classes in place, you might be asking the question, how is the internal state of the Space
s picked up by the corresponding SpaceNode
s and displayed on the interface? The short answer is through binding. Let’s run through how this takes place.
At startup, a series of URLs (filenames) are statically loaded into a String
sequence called imageFiles
. The initialization of imageFiles
looks like this:
As you may have surmised, the image files coincide with the layout of a Space
’s number
instance variable as described in Table 13.3. For example, imageFiles[0]
contains the URL "{__DIR__}images/blank.png"
—a blank space—whereas imageFiles[19]
points to "{__DIR__}images/9-bold.png"
—an image of the number 9 in bold, representing a non-editable space. The imageFiles
sequence, however, only contains strings that refer to URLs. What we actually need here is a sequence of type Image
to serve as an Image
cache. This is achieved by using imageFiles
with the following code:
Next, each SpaceNode
defines two instance variables, one called spaceImage
, which holds the current image displayed for each instance, and number
, which contains the value of the corresponding Space
’s number
instance variable. Using a combination of binding and triggers, we can achieve the effect of updating a SpaceNode
’s current image whenever its corresponding Space
has a change in its number
variable. This is accomplished with the following code:
The definition of the preceding number
variable performs two things:
1. It binds number
to space.number
. Whenever the value of space.number
changes, number
will automatically change accordingly.
2. The number
variable uses a trigger to force an update to the value of spaceImage
whenever number
changes too. In this case, spaceImage
points to a sequence (a cache) of Image
s indexed by the value of number
.
Finally, each SpaceNode
defines an instance of ImageView
, which is ultimately how the image is displayed. It looks something like this:
Now, the image for a SpaceNode
will dynamically change whenever space.number
changes. Here’s the chain of events:
• The user enters a number into an editable space.
• The space.number
instance variable is assigned a new value.
• The corresponding SpaceNode
has a number
instance variable that is bound to space.number
. Whenever space.number
changes, so does this number
variable.
• There is a trigger associated with the number
instance variable such that whenever it is modified, it causes the spaceImage
variable to be recalculated. In this case, spaceImage
uses number
as an index into a sequence of cached Image
s.
• Each SpaceNode
instantiates ImageView
. Its image
instance variable is bound to spaceImage
. Whenever spaceImage
changes, the image
will change too.
This all comes about because of the capabilities of binding and triggers.
So far, we’ve covered the logic and presentation of our Sudoku application, and how these two worlds relate to one another. The components comprising this portion of our application were written in JavaFX and represent the main effort in creating JavaFX Sudoku. There is a third element, however, which needs to be implemented to complete the Sudoku game. It revolves around the ability to randomly create new Sudoku puzzles and to provide solutions for them.
In conjunction with the popularity of Sudoku, many have put forth algorithms for creating and solving Sudoku puzzles. With this in mind, we had the option of either reinventing the wheel or leveraging the work that’s been done already in puzzle generation. In all honesty, there’s no real advantage to writing a Sudoku puzzle generator in JavaFX. It could have just as easily and effectively been written in any of the myriad of available high-level programming languages. So why not find what’s out there, and see how easily it could be integrated into our application? Code reuse and, in particular, integration with Java, arguably the largest development community on the planet, are key design characteristics of JavaFX.
Sourceforge.net is a well-known centralized repository for open source software projects. At that location, we found a Sudoku application located under the following URL:
http://sourceforge.net/projects/playsudoku/
This project is a complete program written in Java, including a Swing-based user interface. Our interest was solely in its capability to create new Sudoku puzzles, so we took a subset of the source code, specifically the classes found under the net.sourceforge.playsudoku
package, and integrated them into our application.
The integration effort was straightforward and involved two primary tasks:
1. Figuring out what methods need to be called to generate a new Sudoku puzzle.
2. Determining if the data structures used by the Java application can be directly used with JavaFX.
We’ll handle the second task first and use our solution there as part of the overall process needed to generate a new puzzle.
Delving into the net.sourceforge.playsudoku
package, you’ll find that the contents of the puzzle are maintained in a multi-dimensional array called grid
. The Java declaration for this array is found in the SudokuGrid.java
file and looks like this:
private int[][] grid;
Unfortunately, JavaFX currently has no support for multi-dimensional arrays. So, the first order of business is to convert grid
into something JavaFX understands. Listing 13.1 shows an additional Java method, called returnGridSequence()
, that was added to the SudokuGrid.java
source file. The purpose of this method is to convert the two-dimensional grid
array into a one-dimensional array of type int
. This then maps directly to a JavaFX Integer[]
sequence.
Listing 13.1 Java Method to Convert a Two-Dimensional Array into a One-Dimensional JavaFX Sequence
Using this method, we can now tackle the first task, constructing the necessary JavaFX code to generate a new JavaFX puzzle using the net.sourceforge.playsudoku
package. Listing 13.2 shows the complete newPuzzle()
function, which is contained within the Board.fx
source file. Let’s first take a look at a few of the parts of this function to see what’s going on. The first lines of newPuzzle()
call the necessary Java methods inside net.sourceforge.playsudoku
to generate a new puzzle:
Next comes the call to the returnGridSequence()
function, which converts the grid into a JavaFX Integer
sequence:
The rest of the function contains code necessary to translate the contents of each cell. Inside grid
, the way in which state is stored is fairly similar to the way it’s done with JavaFX Sudoku. Instead of using number values that are multiples of 10, the author(s) used bitmasks to store multiple values in each cell. Listing 13.2 shows the newPuzzle()
function.
Listing 13.2 The newPuzzle() Function
We’ve spent some time dissecting our sample Sudoku application including describing the user interface, the organization of the source, the overall architecture, and the interaction with components written elsewhere in Java. Feel free to take a look at the source and utilize it in any fashion you wish.