Lab: Catan

Download the latest Catan code: catan0227.zip (Feb 27)

Submit my latest bot

How is my bot doing?

Table of Contents

Introduction
Rules
Getting Started
The Assignment
Grading
Frequently Asked Questions
Tips for Winning
Reference
Known Bugs
Updates
Board

Introduction

In this lab, you will implement a bot to play a simplified version of Catan--a board game for up to 4 players (designed by Klaus Teuber).

The game takes place on a board consisting of hexagonal tiles called hexes. Each hex produces one of five resources: lumber, brick, grain, wool, or ore. You build roads along the edges of the hexes, and you build settlements at the corners. You begin the game with 2 settlements and 2 roads. Because each settlement is worth 1 point, you begin the game with 2 points. The first player to reach 10 points wins the game.

Each hex is marked with a number. At the start of each turn, 2 dice are rolled to determine which hexes produce resources. For example, if a 3 is rolled in the game shown below, the red and yellow players will earn 1 ore, and the white player will earn 1 lumber. (Note that some rolls are more probable than others.)

On your turn, you can use the resources you've accumulated to make purchases. For example, you can buy a road for 1 lumber and 1 brick, as shown below.

All HCS2 and HCS3 students will program a bot. These will compete in two kinds of tournaments: public tournaments (for fun and bragging rights) and massive overnight tournaments consisting of thousands of games (to minimize the role of luck). Your grade will be determined by your bot's performance against benchmark bots and against all other student bots in overnight tournaments. All that matters is how your bot plays. If a simple strategy consistently beats a more complicated one, then the simple strategy will earn a better grade. Thorough testing is the best way to ensure that your bot performs well.

Completed Catan code has been provided to you. This code will be updated over time. The initial code provided almost certainly contains critical bugs and lacks essential functionality. (You can potentially improve your grade by being the first to identify such a bug or functionality.)


I already know how to play Catan! How and why have you changed the rules?!!

In order to make the assignment more accessible, rules were changed to reduce the kinds of decisions a bot must make. Here are the biggest changes.


Rules

Building

On your turn, you may build as many times as you can afford, or none at all.

Road = 1 lumber + 1 brick
A new road must connect to your existing roads. You are limited to 15 roads. The player with the longest road (with at least 5 segments) earns 2 points. (In the case of a tie, the points go to the first player to reach that length.)

Settlement = 1 lumber + 1 brick + 1 grain + 1 wool
A new settlement must be placed on one of your roads, and cannot be immediately adjacent to any settlement/city. Each settlement is worth 1 point. You are limited to 5 settlements at one time.

City = 2 grain + 3 ore
A new city must replace one of your settlements. Each city is worth 2 points. A city earns 2 of each resource produced by a neighboring hex. You are limited to 4 cities.


The Robber

When you roll a 7, no resources are produced, and you must move the robber to a different hex. You also take one resource from an opponent with a settlement/city on that hex (randomly selected). The robber stops that hex from producing resources until the next time someone moves it.

Also, when a 7 is rolled, any player with more than 7 resources loses one-third of them (rounded down, and randomly selected).


Cards

= 1 grain + 1 wool + 1 ore

When you buy a card, you will randomly draw one of 25 cards and immediately earn the benefits. There are 5 types of cards.


Points

You need 10 points to win the game, earned as follows.

1 point - each settlement (limited to 5 settlements at one time)

2 points - each city (limited to 4 cities)

1 point - each victory point card drawn (5 of these exist)

2 points - longest road (with at least 5 segments; in the case of a tie, the points go to the first player to reach that length)

2 points - most knights (with at least 3 knights; in the case of a tie, the points go to the first player to collect that many)


Trade

You are never required to trade. But it is often helpful to trade resources to give you the right combinations for buying roads, settlements, cities, and cards. On your turn, you may trade as many times as you can afford, or none at all. There are two kinds of trade.

Trade with the Market - Trade any 1 of your resources for 1 resource available at the market. (Any resource you trade to the market will be available for your opponents to trade for.)

Trade with the Bank - Trade at a 4:1 exchange rate, trading 4 identical resources for any 1 resource. If you have a settlement/city on a harbor marked "3:1", you may trade at a 3:1 rate. If you have a settlement/city on a harbor marked "brick" (for example), you may trade at a 2:1 rate: 2 bricks for any 1 resource.


Getting Started

Open Main.java. Look at the following code in the main method:

Human human = new Human("You");

Game game = new Game(new JonCatan(), new JonCatan(), human);

Display display = new Display(game, 500);

game.setDisplay(display);

human.setDisplay(display);

System.out.println(game.play());

The Display class lets you watch or play the game in a graphical window.

To help you get started, two strategies have been provided to you:

Both JonCatan and Human implement the Strategy interface. The bot you develop will implement the Strategy interface, too.

The Game class's play method plays a single game of Catan with 1 - 4 players using the given Strategys. (You can also have bots play against each other without a Display.) The play method returns the winner's seat number. For the code above, there is a JonCatan in seats 0 and 1, and a human named "You" in seat 2. When the human wins, play will return 2.

Go ahead and run the main method to try your skills at Catan. (You are encouraged to modify the main method as you test your work.)

Now open JonCatan.java. What do you notice about how this code is directing Jon Catan to play?

Tired of playing against Jon Catan? Then it's time to code a better opponent ...


The Assignment

Implement the Strategy interface in a file using your full name (for example, DaveFeinberg.java). This is the only file you will submit. You can define other classes but they must be declared inside the same file. (Feel free to ask for help doing this.)

You cannot share your Java code with anyone--not even to try playing a game against another student's bot. Your code cannot access the file system or network for any reason. Any violation of these rules would be a serious case of academic dishonesty to be reported to the dean.

Fully test your code. You will receive no credit for a submission that crashes or freezes--even in rare circumstances. Your submitted code cannot print to the console, pop up a dialog, play a song, sleep, etc.

You may submit your work multiple times, but any newer version you submit will replace your older submission(s).

(Your class may have multiple constructors for testing purposes, but it must have a constructor that takes in no parameters. You will only be graded on the performance of this parameter-less constructor.)


Grading

Your grade will depend on how well your bot performs in massive overnight tournaments.

To earn a grade of 90%, your bot must consistently beat simple benchmark bots.

For example, suppose there are 5 bots. Bot A places first in massive overnight tournmants, followed by B, then C, then D, and then E. Only bots A, B, and C consistently beat the benchmark bots.  As a result, bot A earns 100%, bot B earns 95%, and bot C earns 90%. Bots D and E would earn scores below 90%, subject to the teacher's discretion.


Frequently Asked Questions

How do I start making my own bot?
Make a copy of the JonCatan.java file. Name the copy after your full name (e.g. DaveFeinberg.java). Change the Main.java file to test a game that includes your bot.


Which files should I change?
You should only change the file with your bot (e.g. DaveFeinberg.java) and the Main.java file used for testing.


What should my bot's getName method return?
This method returns the name to show in the Display window. This is your chance to name your bot anything you like (as long as it's school-appropriate). Or you can simply use your real name.


What is a Table object?
At the start of the game, a Table object is passed to your gameStarted method. This Table should be saved in an instance variable so that you can access it from other methods. The Table object provides over 40 methods for you to call to build, trade, access your resources, etc.


What methods should I change in my bot?
You'll almost certainly want to change both tradeAndBuild and moveRobber.


What should my tradeAndBuild method do?
This method is called whenever it is your turn to trade and build. Build/buy/trade by calling the following methods in Table.

void buildRoadAt(Location edge)
void buildSettlementAt(Location corner)
void buildCityAt(Location corner)
void buyCard()
void tradeWithMarket(int resourceLost, int resourceGained)
void tradeWithBank(int resourceLost, int resourceGained)


How do I know if I can afford to build?

Check if you have enough resources to build/buy by calling the following methods in Table. For example, canAffordRoad returns true if you have at least 1 lumber + 1 road, and you have not already placed 15 roads.

boolean canAffordRoad()
boolean canAffordSettlement()
boolean canAffordCity()
boolean canAffordCard()


How do I know if I can build at a particular location?

Check if you can build at a particular location by calling the following methods in Table.

boolean canBuildRoadAt(Location edge)
boolean canBuildSettlementAt(Location corner)
boolean canBuildCityAt(Location corner)


What are locations and where do I get them?

Every hex, corner, and edge has a unique location (with coordinates that you don't need to know). You can get a list of all locations by calling the following methods.

List<Location> getHexes()
List<Location> getCorners()
List<Location> getEdges()

For example, a strategy might loop through all edges to test if it can build a road at each edge.


How do I know what resources my bot has?

Call Table's getResources method, which takes in your seat number and returns an array of 5 integers. 

int[] resources = table.getResources(table.getSeat());

Use the constants BRICK, LUMBER, GRAIN, WOOL, and ORE to check how many of each resource you hold. For the code above, resources[BRICK] tells you how many bricks you have.


Tips for Winning

Testing is the key to coding a bot that defeats your classmates. That might mean:


Consider assigning numeric values to potential moves, and then choosing the move with the maximum (or minimum) value. For example, you might assign a numeric value to each hex, and then move the robber to the hex with the greatest value.


Reference

Do not modify any of the classes or interfaces provided (except for testing / debugging).


interface Constants

static final int NONE;

static final int LUMBER, BRICK, GRAIN, WOOL, ORE;

static final int THREE_TO_ONE;

static String toResourceString(int resource)


class Location implements Comparable<Location>

You should not need to call any methods in the Location class.


interface Strategy extends Constants

String getName()
Returns a school-appropriate name for your bot, to appear in the Display

void gameStarted(Table table)
Called once at the beginning of each game. table should be saved in an instance variable so that you can access it from other methods. Because a single instance of your strategy may play multiple games, you will most likely want to initialize all instance variables in this method. (Only use a constructor to initialize instance variables that are not reset each time a new game is played.)

void tradeAndBuild()
Called on your turn (unless a 7 was rolled). In this method, you may build, buy, or trade as much or as little as you like.

Location moveRobber()
Called on your turn when a 7 is rolled or when you buy a knight card. The robber will be moved to the location of whatever hex you return. You cannot return the robber's current location. It must move to a different hex.


interface Table extends Constants

Actions

void buildRoadAt(Location edge)

void buildSettlementAt(Location corner)

void buildCityAt(Location corner)

void buyCard()

For example, buildRoadAt(edge) requires that (1) you have at least 1 lumber + 1 brick, (2) you have placed fewer than 15 roads, and (3) edge is a valid unowned edge adjacent to one of your roads.


void tradeWithMarket(int resourceLost, int resourceGained)

For example, tradeWithMarket(ORE, WOOL) trades 1 ore for 1 wool. For this trade, you must have at least 1 ore and the market must have at least 1 wool.


void tradeWithBank(int resourceLost, int resourceGained)

For example, tradeWithBank(ORE, WOOL) trades 4 ores for 1 wool. For this trade, you must have at least 4 ores. If you own a 3:1 harbor, then you only need 3 ores for this trade. If you own an ore harbor, then you only need 2 ores for this trade.


Conditions

boolean canAffordRoad()

boolean canAffordSettlement()

boolean canAffordCity()

boolean canAffordCard()

For example, canAffordRoad() returns true if you have at least 1 lumber + 1 brick and you have placed fewer than 15 roads.


boolean canBuildRoadAt(Location edge)

boolean canBuildSettlementAt(Location corner)

boolean canBuildCityAt(Location corner)

For example, canBuildRoadAt(edge) returns true if edge is a valid unowned edge adjacent to one of your roads.


Board

List<Location> getHexes()

List<Location> getCorners()

List<Location> getEdges()


Hex Properties

int getResource(Location hex) returns LUMBER, BRICK, GRAIN, WOOL, ORE, or NONE

int getRollNeeded(Location hex) returns 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, or 0 for desert

List<Location> getCornersOfHex(Location hex) returns 6 adjacent corners

List<Location> getEdgesOfHex(Location hex) returns 6 adjacent edges

 

Corner Properties

int getCornerOwner(Location corner) returns seat that owns settlement/city, or NONE

boolean hasCity(Location corner) returns true if city, false if settlement/unowned

int getHarborResource(Location corner) returns LUMBER, BRICK, GRAIN, WOOL, ORE, THREE_TO_ONE, or NONE

List<Location> getEdgesOfCorner(Location corner) returns 2 or 3 adjacent edges

List<Location> getHexesOfCorner(Location corner) returns 1, 2, or 3 adjacent hexes


Edge Properties

int getEdgeOwner(Location edge) returns seat that owns road, or NONE

Location getCorner1(Location edge) returns an adjacent corner

Location getCorner2(Location edge) returns other adjacent corner

List<Location> getHexesOfEdge(Location edge) returns 1 or 2 adjacent hexes (special thanks to Clara S and Alex P for whining about the need for this method, and to Andrew S for coding the same thing better)

    

Other Essentials

Location getRobber() returns hex with robber


int getSeat() returns your seat number (0, 1, 2, or 3)


int[] getResources(int seat) returns an array of 5 integers indicating how many of each resource I have

For example, getResources(mySeat)[ORE] is the number of ores I have.

Because you cannot view your opponent's resources, getResources(opponentSeat) returns {0, 0, 0, 0, 0}.


Trading

int[] getMarket() returns an array of 5 integers indicating how many of each resource the market has

For example, getMarket()[ORE] is the number of ores available at the market.


int getExchangeRate(int resource) returns how many (2, 3, or 4) of this resource to trade to the bank

For example, getExchangeRate(ORE) returns 4 at the beginning of the game, 3 if you have a settlement/city on a 3:1 harbor, and 2 if you have a settlement/city on an ore harbor.


Less Useful

int getNumPlayers() returns the number of players, including your bot

int getNumKnights(int seat) returns the number of knights in seat's army

int getSeatWithMostKnights() returns NONE if no one has collected at least 3 knights

int getLongestRoadLength(int seat) returns the length of seat's longest road

int getSeatWithLongestRoad() returns NONE if no one has a road with at least 5 segments

int getNumCardsRemaining() returns an integer from 0 to 25.

int getRolled() returns the most recent roll of the dice (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, or 12)


int getVictoryCards(int seat) returns the number of victory point cards I have drawn

Because you cannot view your opponents' victory cards, getVictoryCards(opponentSeat) returns 0.


String getName(int seat) returns my name

Because you can't view your opponents' names, getName(opponentSeat) returns the String value of opponentSeat.


Type-Checking

boolean isHex(Location loc)

boolean isEdge(Location loc)

boolean isCorner(Location loc)


class Game implements Table

Game(Strategy seat0)
Game(Strategy seat0, Strategy seat1)
Game(Strategy seat0, Strategy seat1, Strategy seat2)
Game(Strategy seat0, Strategy seat1, Strategy seat2, Strategy seat3)
Constructs and sets up a game of Catan with the given Strategys.

void setDisplay(Display display)
Call if you wish to watch a game.

int play()
Plays one complete game and returns the seat number of the winner (0, 1, 2, or 3).


class Display

Display(Game game, int delayInMilliseconds)
Display(Table table, int delayInMilliseconds)
Constructs a Display window for viewing a game of Catan. Pass a Game to see all player's information. Pass a particular bot's Table (e.g. human.getTable()) to hide opponent names, resources, and victory card points. delayInMilliseconds is the number of milliseconds the Display will wait each time an update is performed.


class Human implements Strategy

Human(String name)
Constructs a Strategy that allows a user to play the game.

void setDisplay(Display display)
Call this method before playing a game with a Human.


class RemoteStrategy implements Strategy

RemoteStrategy()
To host a network game, construct one RemoteStrategy for each bot that will be joining over the local area network. Look up your computer's IP address and share it with anyone connecting to your game. Start your game first, and then let others know you're ready for them to join. (Make sure to agree on whether you should use a Display.) For example, the following plays a game between Jon Catan and 2 players connecting over the network.

Game game = new Game(new JonCatan(), new RemoteStrategy(), new RemoteStrategy());

game.setDisplay(new Display(game, 500));
System.out.println(game.play());


class RemoteTable implements Table

RemoteTable(String hostIPAddress, Strategy strategy)
To join a network game, construct a RemoteTable. Pass the host's IP address, along with an instance of the strategy you wish to test. For example:

RemoteTable table = new RemoteTable("10.13.32.150", new JonCatan());


Known Bugs

None


Updates

January 18, 2024 - original release

January 28, 2024 - Colors are not randomized (i.e. red player always starts in same positions). User interface prevent moving robber to itself, and closing dialog ends turn. Can ask Table how many cards remain. Robber starts in desert. Display updates immediately after buying a card to remove spent resources. Constants.toResourceString(NONE) returns "None". (Files changed: BoardComponent.java, Constants.java, Display.java, Game.java, LocalTable.java, PlayerPanel.java, RemoteStrategy.java, RemoteTable.java, Table.java)

February 13, 2024 - Can use player name with a space in network games now (not tested). Added getEdgesOfHex and getHexesOfEdge to Table. isHex, isCorner, and isEdge return false for locations that are out-of-bounds (instead of crashing).

February 27, 2024 - Network games will be much faster, because RemoteTable now caches lots of data. (Also had to change Game.)


Board

The picture below shows the 4 possible starting positions. Each player is assigned to one of these four starting positions at random. Each player begins the game with 3 resources determined by their starting position (specifically, the 3 resources that border their leftmost settlement).

Blue: 1 lumber + 1 brick + 1 grain
White: 2 lumber + 1 grain
Orange: 1 lumber + 1 brick + 1 ore
Red: 2 grain + 1 ore

The following image shows the coordinates of all locations on the board. These are unlikely to appear in your code, but they may be helpful for debugging.