Lab 9: Railway
Due on Sunday, May 5 at 11:59 PM. This is a team lab. You and your assigned lab partner(s) will complete this lab together. Make sure that you are familiar with the Partner Etiquette guidelines. You may discuss the concepts of this lab with other classmates, but you may not share your code with anyone other than course staff and your lab partner(s). Do not look at solutions written by students other than your team. If your team needs help, please post on the Piazza forum or contact the instructor or ninjas. If you have any doubts about what is okay and what is not, it’s much safer to ask than to risk violating the Academic Integrity Policy.
We will be using Teammaker to form teams. You can log in to that site to indicate your preferred partner. Once you and your partner have specified each other, a GitHub repository will be created for your team. If you have any trouble using Teammaker, contact your instructor.
- Overview
- Railway
- Starting Code
- Advice
- Testing
- Memory
- Coding Style Requirements
- Summary of Requirements
Overview
This lab concerns the application of graph data structures to create an interesting program. The objectives of this lab include implementing graph algorithms, applying them to a graph-based problem domain, and performing some amount of design in the development of a piece of software. This lab should reinforce the graph algorithms you learned in lecture and improve your programming skills.
In order to complete the above objectives, we’re going to write a game! :-D
Railway
Railway is a two-player route-building game. The players are presented with a map of possible train routes and take turns laying tracks between them. Players are attempting to complete route goals by building a continuous path between two cities.
The GUI (graphical user interface) of Railway has been provided for you. When you launch the GUI, it will display something like the following:
Your task will be to use this GUI to write the game. In the process, you will need to implement some of the graph algorithms that we discussed in class and apply them to the game.
Rules of Railway
To write a program to allow users to play Railway, you will need a formal description of gameplay. The rules of the game are provided here. It may help to watch a video of the game in action before reading below. You should understand how the game works before beginning your design.
Terminology
Railway is played on a map represented by a graph. The vertices of the graph are cities; the edges on the graph are potential railway routes. Routes have a cost in tracks, the resource used to build railway routes. Routes may also have an owner but, at the start of the game, neither player owns any route. Players take turns acquiring routes, using tracks to “pay” for route.
Setup
Player 1 starts with 35 tracks. Player 2 starts with 50 tracks (to compensate for the disadvantage of making the second move).
Each player also starts with three randomly-chosen goals. Each goal instructs the player to connect two cities on the map; these cities are always at least three routes apart.
Each player begins with a score of 0. The game begins with Player 1’s turn.
Playing the game
Players alternate taking turns. A player must take one of two actions each turn: either pass or place a route. A player who passes gains one track for each city connected by that player’s network (each vertex connected to one of their edges).
To place a route, the following requirements must be met:
- The route must be unowned.
- The player must be able pay a number of tracks equal to the route’s cost.
If the requirements are met, the player must pay a number of tracks equal to the route cost. A player who places a route does not gain any tracks that turn. If a player attempts to claim a route but does not meet the requirement, the move is rejected and the player must make a different move.
Ending the game
The winner is the player with the highest score once all of the routes have been claimed.
Scoring
Each player’s score can be determined entirely by the state of the game board. A player receives one point for each city to which they have connected with a route. Completed goals are scored by calculating the shortest path between the two cities (even if this is not the route the player took to connect them), dividing by four, and rounding down.
Example
Consider the following image of a game in progress:
Observe the following:
- Player 1 (green) is out of tracks and will be forced to pass. This will give Player 1 a total of 8 more tracks as they have connected 8 cities (SEV, MAD, BOR, PAR, BRU, AMS, FRK, and BLN).
- Player 2 (purple) could acquire the route from BRN to FLC next turn (11 tracks) but cannot afford the route from ROM to ATH (20 tracks).
- Player 1 has completed the goal “SEV to AMS”. This is worth \(\lfloor\frac{4+5+8+10+8}{4}\rfloor = 8\) points. Player 1 has also connected eight cities, for a total score of 16 points.
- Player 2 has completed the goal “SRJ to ROM”, but will not be able to complete the goal “REN to PRA” because Player 1’s route through PAR blocks the only approach to REN.
Starting Code
Your starting repository includes quite a few files. You will be modifying the following ones:
main.cpp
: This program runs the main loop of the Railway game and serves as the interface between the GUI (which we have provided) and the game implementation (which you will write). The primary job of the main loop is to get inputs from the GUI, then call appropriate methods of the RailwayGame class, then send updates back to the GUI.railwayGame.h
andrailwayGame.cpp
: This is where you implement the rules of the game. TheRailwayGame
class, defined in these two files, should track the state of the game and perform all updates resulting from the players’ moves.graphAlgorithms-inl.h
: Here you will implement several standard graph algorithms. We expect that these algorithms will come in handy, but they are required even if your Railway game doesn’t end up using them. Pay attention to the requirements specified in the function comments ingraphAlgorithms.h
.
You will also want to read much of the other code that has been provided so that you understand the interface to the Railway game. The following sections discuss these files and what you should do with them.
Graph ADT
As usual, the adts/
directory contains the ADT declarations and implementations for you to use in this assignment. The graph.h
and edge.h
files are new; take a look at them first and connect them to
our discussions about graphs from lecture.
There are two implementations of the graph ADT in the main lab directory: AdjacencyListGraph
and AdjacencyListUndirectedGraph
. The latter is a subclass of the former which changes the insertEdge
and removeEdge
methods to simulate an undirected graph: each time an edge is added with insertEdge
, for instance, another edge is also added in the opposite direction.
We have also added an implementation of a min-heap priority queue STLMinPriorityQueue
and a hash table implementation STLHashTable
in
the adts
folder.
Graph Algorithms
As part of this lab, you will implement graph algorithms to solve the following problems:
- reachability: determine whether there is a path from a source vertex to a destination vertex
- shortestLengthPath: find the path from a source to a destination with the fewest edges
- singleSourceShortestPath: find the weight of the min-cost path from a source to every reachable vertex
These algorithms are required even if you can avoid using one of them in your Railway implementation. You will implement these algorithms in graphAlgorithms-inl.h
. The provided unit tests in tests.cpp
are designed to verify the correctness of these algorithms.
Railway GUI
The RailwayGUI
class (railwayGUI.h/cpp
) implements the graphical user interface.
When a RailwayGUI
object is created, it produces a window containing whatever information you provide.
This object allows you to update the graph on the map, change the message displayed at the bottom of the window, update each player’s score, and so on.
Important methods provided by this class include:
updateRailwayMap
: This updates the displayed map based on the graph that is passed in. Edge labels on the graph determine route colors on the displayed map: 0 for gray (unowned), 1 for green (player 1), and 2 for purple (player 2).updateMessage
: This produces the message text that appears at the bottom of the GUI.updatePlayerStatus
: This updates the score, tracks, and goals boxes for the specified player.getNextMove
: This method waits until the user selects a move in the GUI, then returns that move, represented as avector<string>
.
You should read the comments in railwayGUI.h
for much more detailed descriptions of how these methods work.
You should also read through the code and comments in main.cpp
for examples and suggestions on how to get started.
Railway Game
The RailwayGame
class (railwayGame.h/cpp
) has been left empty. You should use this class to represent the idea of a game of Railway: it should contain information about whose turn it is, what the map looks like (as a Graph*
), information about each player, and so on. You have also been provided with a Goal
class (goal.h/cpp
) that you can use
to encapsulate information about each route goal. This class does not
directly communicate with the GUI - you should primarily focus on using
this class to maintain the state of the game.
The main program
By the time you are finished, you should have a game that implements the rules of Railway as described above. To earn full credit, your program should function (no crashes or freezes) and play the game correctly (turns alternate correctly, players can’t steal each other’s routes, goals are scored correctly, etc.). Your UI must also provide the following features:
- If a goal has not yet been completed but is still possible, you must show its point value.
- Once a goal has been completed, you should indicate as much (HINT: study
the
Goal
class). - If a player makes an incorrect move, you must use the message bar at the bottom of the GUI to explain the problem (e.g. “Player 1: that route has already been taken.”).
Note: you should not pass the GUI object into your RailwayGame
class.
Instead, main
should be responsible for passing data between the GUI and the game class.
Running the Game
Part of the main
function – which reads the graph data files and creates the GUI – has been written for you. You can run your code (or even the initial starting code) by running make
and then
./railway test_data/Europe
The game will work with whatever data set you tell it to use. To make testing easier, we’ve included a map of Testaria, the Island of Tests, which you can use by running the following command instead:
./railway test_data/Testaria
See the discussion about testing your code below.
Advice
This lab is more open-ended than the previous labs: you are responsible for part of the design of the application as well as the code. Here are some suggestions to get you started.
- Work out how you’re going to solve this problem before you write any code! It may help for you to write your plan down or discuss it before you start implementation. Make a list of the information you need to keep (player’s score, graph, etc.) and where you plan to keep it (e.g. the graph can be a field of
RailwayGame
). Only after you have a plan should you start coding. - You may wish to create additional classes to help keep track of certain information. For example, a
Player
class could store and handle the updates for one player’s information. If you do so, you can either add the class declaration(s) and definition(s) alongside theRailwayGame
class or you can create new files, likeplayer.h
andplayer.cpp
. If you choose to create new files, make sure to update the Makefile, for example addingplayer.o
to theOFILES
variable; otherwise you will get linker errors! - You can select a random number using the
rand
function from thestdlib.h
library. For instance,rand()%10
will pick a random number between 0 and 9, inclusive. You will need this when picking goals at the start of the game. - Although you should plan your lab before you start writing your code, you should code one feature at a time! First, get the game working without objectives or the pass option: just allow players to claim routes. Then, add features one by one until you have a working game. And each time you complete a new feature, commit and push your code!
Testing
Use tests.cpp
to verify that your graph algorithms are working correctly.
You should not begin your railway implementation until all tests are passed.
make tests ./tests
To test your railway game, you can launch your game on a smaller, more predictable test map by running the following:
./railway test_data/Testaria
This map is designed to help you track down bugs. It has four vertices on each side of the map and one vertex in the middle which connects them. If your game is working properly, then the following should be true:
- Every legal goal connects an L vertex with an R vertex (because all of the other vertices are too close to be a legal goal). The MID vertex never appears in a goal.
- Only a player who claims both edges touching the MID vertex will be able to complete any goals.
Although the above does not guarantee that your code is correct, it gives you a means to determine if your code is generating and handling goals correctly.
Memory
Your code must run without any memory errors; make sure to use valgrind
to find them. Memory leaks, on the other hand, are acceptable in small amounts and will not significantly impact your grade. In particular, “still reachable” memory is acceptable; “lost” memory should not occur in large amounts (e.g. millions of bytes).
Coding Style Requirements
You are required to observe good coding practices. You have seen these requirements many times, and should know them by now. If you’re unsure, check previous labs where the style requirements were stated explicitly.
In this lab, you are taking on much more of a design role than on previous assignments. You are expected to adhere to good object-oriented design principles. Most importantly, this means that an object’s data members should be private, and the object’s should handle all of the work that depends on those data members. Also recall the lessons of top-down design from CS21: first start by planning the high-level structure of your program and the tasks it will need to accomplish. Then start breaking those tasks down into manageable pieces you can implement and test one at a time.
Summary of Requirements
When you are finished, you should have
- Implementations of the three graph algorithms
- A working Railway game
- No memory errors
- Code which conforms to the style requirements above
- A completed peer review
When you are finished with your lab, please fill out the post-lab survey. You should do this independently of your partner. The completion of these forms is part of your participation grade.