Project 2: Design Patterns in Java

COMP 121, Spring 2021

Test case due: Tue, Mar 2 @ 11:59pm

Main project due: Mon, Mar 8 @ 11:59pm

In this project, you will implement a chess game simulator. Your simulator will take as input a file that describes the initial locations of pieces on the board and a series of moves of those pieces. Your simulator will then set up the board and execute those moves, checking that all the moves are legal and, at the end, printing the state of the game board.

The real goal of implementing this project, however, is to gain experience with design patterns. More specifically, as part of this project you will implement the following patterns: Singleton, Factory, External Dependency Injection, Observer, and (External) Iterator. Here is a code skeleton to get you started.

This project will also give you a little practice with the essential software engineering skill of solving programming problems by finding library code to do what you want. We'll give you some hints, but fewer than in the last project. So you'll need to spend some time searching on Google and poking around the JDK 15 API. Most of the methods you want are probably in the following:

But, if you want to get fancy, you can use other parts of the API, e.g., java.nio or java.util.regex. In this and all other projects, you are allowed to use any part of the JDK 15 API you like.

This project doesn't break down into separate steps quite the way the previous one does, but it does break down into modules. So this writeup is organized as a description of the modules you'll need to write. The description is written in an order that's sensible for presentation, but you can implement the different modules in whatever order you want.

Hint: The hardest part of the project, in a technical sense, is writing the code to model the moves of all the chess pieces. The rest of the project has much less code, but you will need some time to understand all the design patterns we've crammed into the project. And yes, there are too many design patterns here, but hopefully not as many as this factorial implementation that's been design patterned to death.

Note: You are free to add any helper methods, additional classes, etc, that you'd like to for this project. Just be sure to upload a complete, working program to Gradescope.

Recall that the game of chess is played on and eight-by-eight board with the following initial layout:
abcdefgh
8
br

bn

bb

bq

bk

bb

bn

br
7
bp

bp

bp

bp

bp

bp

bp

bp
6
5
4
3
2
wp

wp

wp

wp

wp

wp

wp

wp
1
wr

wn

wb

wq

wk

wb

wn

wr
Using standard notation, we've labeled the chess board's rows 1-8 and the columns a-h, and using slightly non-standard notation we're describing each piece with two characters: the color (black (b) or white (w)) and the kind (king (k), queen (q), knight (n), bishop (b), rook (r), and pawn (p)).

Your simulator will be run via the command

    java Chess layout moves 
where layout is the name of a file describing the initial locations of pieces on the chessboard, and moves is the name of a file describing a sequence of moves. We've given you one example each of the layout and move files, layout1 and moves1, respectively, where layout1 contains the standard setup of chess pieces on the board. For ideas of other ways you could lay out chess pieces initially, search the web for chess puzzles or chess problems.

You will need to write code in Chess.java that (a) reads data from the layout file and sets up the board and then (b) plays the sequence of moves in the moves files. Of course, you can create whatever additional methods and classes you need.

If you look in Chess.java, you'll see a main method of the usual type. You'll want to add your code toward the bottom of this method. Notice that the names of the files you need to read are given on the command line, which means they will be stored in the args parameter of main. The layout file name will be in args[0], and the moves file name will be in args[1].

To write this code, you'll need to figure out how to open and read files in Java. We won't tell you how to do this, but if you search the web, look in the JDK documentation, and/or look through the Java textbooks linked from the class web page (see the resources at the bottom of the page), you can figure it out.

There are actually a few different ways to access files in Java. You just have to find one that works. Please don't ask your fellow students for help with this. Pretend that you're working at a company, you need to work with files in Java, and none of your colleagues knows how to do it. This kind of situation happens often, so now is a good time to practice finding the information on your own!

As you process the layout and moves files, you must enforce the following rules about the file. If any of the rules is violated, throw an exception (any exception will do):

There are six classes representing different kinds of chess pieces: King, Queen, Knight, Bishop, Rook, and Pawn. Each of these classes is a subclass of Piece, which is a class rather than an interface because it has some code in it. You need to add code to all seven of these classes (the six chess piece classes and their superclass) to match the following design:

Aside: Notice that Piece is an abstract class, with two abstract methods. This means that Piece is somewhere between an interface an a class: It has some methods (and possibly fields) that are inherited by subclasses, and it has some methods that must be implemented by subclasses.

You will need to implement the factory pattern to create chess pieces. Here's how the design should work.

Whew, that's pretty complicated! And probably unnecessary for chess. But, it does have the nice (?) feature that it would be easy to create new kinds of chess pieces and add them to the board without having to change too much code.

Next up, you need to write code for class Board, which stores the locations of the pieces, among other things. To give you practice with another pattern, we've decided that the Board should be a singleton class. Hence you need to implement a method theBoard that returns the singleton Board instance.

The board itself stores the piece locations in field pieces, which is a two-dimensional array of Piece. Notice that, very often, you will need to convert coordinates like "a3" into an access into this array. It's up to you how you do this. You could implement a full-scale adapter pattern. You could write a utility method or two to convert. Or you could duplicate code all over the place. Only the last choice is not recommended!

You should implement four mutators for Board:

The Board class also supports the observer pattern, so that listeners can be called back when key events happen. More precisely, a BoardListener is an observer that implements two methods: void onMove(String from, String to, Piece p), which is called whenever a move is made on a board; and void onCapture(Piece attacker, Piece captured), which is called whenever a piece is captured. If a piece is captured, there should be a call to onMove first and then a call to onCapture.

You must implement the observer pattern by modifying Board.java as follows:

In Chess.java, the main method registers a simple observer that listens for updates to the board and prints out what happened. This could be helpful for debugging.

But wait, there's more! (We promise this is the last part...) The Board class also supports external iteration. The BoardExternalIterator interface (sorry for the terribly long name) defines a method void visit(String loc, Piece p). You must implement the iterate method of Board so that it takes a BoardExternalIterator and, for every square on the board, calls the external iterator's visit method, passing that square's location and the piece at the location, or null if the location is vacant. The iteration order is up to you, but it must visit every square of the board. (So, visit will be called 64 times.)

To help you test your code, we've given you a file Test.java with another main method that, like the previous project, runs a test case. You can invoke the tests with

    java -ea Test

We've included one sample test case. (Note this test case is a little advanced; you'll have to do a fair amount of work before it passes.)

As part of this project, you must write a test case for one Piece's moves method and share it with the class on campuswire. More specifically, your test case should set up a board by calling addPiece some number of times and then call one appropriate moves method, testing the result. Be sure your test case ignores the order of moves in the returned list because different implementations might return moves in different orders.

Of course, you will want to write many other test cases for different parts of the project!

Put all your code in the code skeleton we have supplied, and upload your solution using Gradescope.