Project 1: Chess Agent

During this project, you will exercise what you have learned about representation and search by creating a simple Chess-playing program. This project provides a model of the state space of Chess, and it is your job to write a program that explores that space efficiently and decides what moves to make to defeat other programs.

Before you start, you may want to get familiar with the basics of Chess. You do not need to know specific details or become a skilled Chess player to complete this project, but a high-level understanding of the basic rules of the game may be helpful. You should also review the readings and lecture notes on Adversarial Search (AIMA sections 5.1, 5.2, and 5.3).

Contents

Chess Framework

To use the Chess framework, you either need to download the source and executables or use the pre-configured IDE provided by your instructor.

Source and Executables

If you are not using the pre-configured IDE, you will need to download the components of the Chess framework:

Extract agents.zip into the same directory as chess.jar. It contains 6 .jar files which are each Chess-playing programs, or agents. To see a sample tournament between some of these agents, open a terminal window and execute the following command:

java -jar chess.jar -g 1 -a agents/random.jar agents/greedy.jar agents/novice.jar

This tells the Chess program to run a tournament between three agents: Random, Greedy, and Novice. Each pair of agents will play 2 games against one another, one as white and one as black.

A transcript of each game is shown to the right of the board (in Portable Game Notation) and the final results of the tournament are given once all games have been played. If you run the command above, Novice should come in first with 4 points, Greedy second with 1.5 points, and Random last with 0.5 points. 1 point is awarded for a win, and 0.5 points is awarded for a draw or stalemate.

Include human.jar in the list of agents to add a human player. For example, to play 2 games between two human players:

java -jar chess.jar -g 1 -a agents/human.jar agents/human.jar

To play against the Novice agent:

java -jar chess.jar -g 1 -a agents/human.jar agents/novice.jar

If the agents are taking a long time to decide on moves, you can try increasing the amount of memory that the Java virtual machine uses. The -Xms command line argument specifies the minimum amount of memory to use, and the -Xmx command line argument specifies the maximum amount to use. You can also allocate more space to young generation objects using -XX:NewSize command line argument to save on garbage collection time. To run the above example with exactly 4 gigabytes of memory, 3 of which are used for new objects:

java -Xms4g -Xmx4g -XX:NewSize=3g -jar chess.jar -g 2 -a agents/random.jar agents/greedy.jar agents/novice.jar

Pre-Configured IDE

The Chess framework is included in the course programming projects. The relevant projects are:

Note that you can comment out lines in Test.java to modify which agents participate in the tournament. While you are testing your agent, you might only want to compete against one agent at a time. Generally, if you can beat Greedy, you can also beat Random, and if you can beat Novice you can also be Greedy, etc., though it is good idea to run a full tournament before submitting your project.

It is recommended that you rename the Example Chess Agent project to reflect your name:

Documentation

Regardless of how you use the framework, the documentation will be helpful. Some of the more important classes are:

Assignment

Your assignment is to write your own Chess agent. Do this by creating a .jar file which contains (at the top level) any class which is a subclass of edu.uky.ai.chess.Agent. It must override the chooseMove method.

You must name your agent according to your LinkBlue ID. In the Example Chess Agent project, this means changing the name of the agent from "Example" to your LinkBlue ID in the constructor. My LinkBlue ID is swa378, so the constructor for my agent would look like this:

public ExampleAgent() {
    super("swa378");
}

Your agent should explore the state space of Chess and make intelligent decisions about which move to make next. You should start in the chooseMove method. This methods takes a State as input and should return one of its child states (thus indicating which move to make next).

The number of states you may explore is limited to 500,000, and you may not take more than 5 minutes to decide on a move. This means you need to explore the space as efficiently as possible. If you exceed the search limit or time limit, your agent will throw an exception and forfeit the current game. Note that these limitations apply per move, not per game (i.e. you may explore 500k states every time you decide on a move).

When you have finished your agent, you can export it from the pre-configured IDE as a .jar file like this:

Since my LinkBlue ID is swa378, the name of my agent's executable will be swa378.jar.

You must also submit your source code. From the pre-configured IDE:

Since my LinkBlue ID is swa378, my project source archive name will be swa378.zip.

You will need to submit both the agent jar file and the source zip file on Canvas. If you submit only one, your project will not be graded.

Grading

This is how your project will be graded: First, I will download your jar file and place it in a directory that matches your LinkBlue ID. Then I will run the following command, substituting your ID for mine:

java -Xms4g -Xmx4g -XX:NewSize=3g -jar chess.jar -a swa378/swa378.jar agents/random.jar agents/greedy.jar agents/novice.jar agents/beginner.jar agents/intermediate.jar -g 1 -nl 500000 -tl 300000 -o results.html -b

This means that your agent will play in a tournament against the five AI agents I have provided. Your agent will play 2 games against each opponent, one as white and one as black.

Agents are ranked according to these criteria:

Your grade will be determined by your score in the tournament:

Your agent...Undergraduate GradeHonors/Graduate Grade
Beats Intermediate in the tournamentA (105%)A (100%)
Beats Beginner in the tournamentA (100%)B (80%)
Beats Novice in the tournamentB (80%)C (70%)
Beats Greedy in the tournamentC (70%)D (60%)
Beats Random in the tournamentD (60%)E (50%)
Compiles and runs without crashingE (50%)E (0%)

Please observe the following requirements when submitting your project:

About the Agents

Here is some information about your opponents which you may find helpful when designing your agent:

Class Tournament

Every agent which runs without throwing an exception will be entered into a class-wide Chess tournament (2 games between each pair of agents). The first, second, and third place winners of this tournament will receive bonus points on this project.

Help and Hints

Help! I don't know where to start!

I recommend that you tackle this project in parts rather than trying to start by writing an agent that beats Intermediate.

Running the tournament takes forever!

Intermediate Bot takes the longest to decide on its moves.

You can run the tournament from Test.java in the "Test Chess Agents" project. In Test.java, you can comment out Intermediate Bot to prevent it from playing in the tournament. Be sure to put it back in when you run your final tests, but while you are working you can play a much faster tournament by temporarily removing that agent.

How can I find out how many pieces of what type are on the board?

The board object implements Iterable<Piece>. This means you can treat a board like a list of piece objects. You can use the Java instanceof keyword to find out if each piece is a King, Queen, Rook, etc.

Here are two functions that might be helpful if you want to calculate material score:

private static int material(Board board, Player player) {
	int material = 0;
	for(Piece piece : board)
		if(piece.player == player)
			material += value(piece);
	return material;
}
private static int value(Piece piece) {
	if(piece instanceof Pawn)
		return 1;
	else if(piece instanceof Knight)
		return 3;
	else if(piece instanceof Bishop)
		return 3;
	else if(piece instanceof Rook)
		return 5;
	else if(piece instanceof Queen)
		return 9;
	// The piece must be a King.
	else
		return 100;
}

Don't compare strings in Java using the double equal sign.

In Java, the == operator (double equal sign) compares whether two things are exactly the same. It is possible for two strings to be different objects even when they have the same letters. If that happens, == will return false.

// This is incorrect.
if(board.player.toString() == "WHITE")
	return "I am playing as white!";

The above code actually does work in some cases because of string internament in the Java compiler, but it's a bad habbit to get into, especially when dealing with non-string objects.

To compare two objects, you need to use Object#equals(Object).

// This is correct.
if(board.player.toString().equals("WHITE"))
	return "I am playing as white!";

In this particular case, there is an even better way to make the comparison. The Player enum defines two constants, Player.WHITE and Player.BLACK. You can always safely compare these objects using the double equal sign.

// This is best.
if(board.player == Player.WHITE)
	return "I am playing as white!";

How can I return multiple things from a method?

Methods in Java can only return one thing, but that one thing can be an object with multiple fields. For example, you might find a class like this helpful. The code needs to be in a new Result.java file in your project:

import edu.uky.ai.chess.state.State;

public class Result {

	public State state;
	public double value;

	public Result(State state, double value) {
		this.state = state;
		this.value = value;
	}
}

You can use the new class like this:

// A dummy method to demonstrate how to use the Result object.
public Result returnTwoThings(State current) {
	return new Result(current, 0.0);
}

I'm getting an error that says "Invalid move: XXX (not a child of the current state)."

Your chooseMove method needs to return a child of the state that it was given, which represents the move the agent is making.

The most common cause of this problem is that you are doing some form of MinMax search and returning a leaf node at the bottom of the tree, rather than the move that will (hopefully) lead to that leaf. You might be looking 4 moves ahead, but you can't return a state that is 4 moves ahead; you need to return a state that is 1 move ahead. If this if your problem, you can simply walk back up the tree using State.previous until you find a state whose previous state is current state.

My agent won't end the game, even when there's a clear opportunity to checkmate!

Assuming you are using some variation of MinMax search, you need to make sure that your utility function accounts for games that are over. If your utility function is, for example, just material score, then your agent won't want to end the game because it might have a chance to get more material! Why checkmate when you have the chance to promote a pawn to a Queen?

There are 4 possible cases that your utility function needs to handle. The first 3 are cases where the game is over (i.e. State.outcome is not null).

If your utility function is working properly, but your agents still take a long time to win, the problem could be that your agent does not pay attention to how far away the end of the game is. Assuming you are using material score as your utility function, MinMax thinks that a checkmate that is 3 moves away is equally as good as a checkmate that is 1 move away, but clearly we should prefer to checkmate sooner rather than later. When deciding which move is "best" you might want to add a tie breaker: when two moves have the same utility, prefer the one that is fewer moves away. You can do this easily by looking at the State.turn counter.

My agent is making really bad moves!

There are many reasons why this could be happening, but here are two that I see frequently: