Ticket To Ride Project
Technologies:
- TypeScript
- Node
- React
- PostgreSQL
- MongoDB
- GitHub
Summary
This is my largest school project and it was for a class called Software Design and Testing (CS 340). As a part of a team with three other developers, I opted to do a web based app rather than an Android App. We worked for five weeks and spent roughly 90 hours each on this project. Almost all of that time we spent coding and collaborating together.
We have two repositories, one for the React Client and one for the Node Backend written in TypeScript. Obviously the following metrics are insufficient to accurately indicate one’s contribution but, for what it’s worth, I included them to give some idea to the project scale. On GitHub I am listed as the #2 contributor for the client with 61 commits, 7,990 additions and 7,052 deletions. For the backend, where I spend most of my time, I am listed as the #1 contributor with 98 commits, 22,698 additions and 13,188 deletions. Of course my fellow teammates, Dustin, Tyler and Chris all have comparable levels of contribution as well.
Because this was part of a school project, I am not allowed to make the repository publicly accessible. Also, because of copyright restrictions, I cannot include the working demonstration of the game. Below I have included screenshots showcasing features of the software where I made special contributions.
I was in charge principally of architecture, backend implementation and database management. For the frontend though I did handle the claiming a route modal as well as the text for Chat and Game History with a lot of help from Tyler. I was, admittedly, the least skilled with frontend web development of my group members. But what I lacked in frontend skills, I made up for with my passion for design patterns and principles.
This class was largely focused on using design patterns. Patterns that we leveraged for this project include:
- Command Pattern
- Observer Pattern
- Model View Presenter
- Plugin Pattern*
- Facade Pattern
- Singleton Pattern
- Proxy Pattern
- Factory Pattern
- State Pattern
*For our last sprint I was voted MVP for my work on the database implementation that would allow for ‘plugable’ persistence manager. This software is able to choose between a PostgreSQL DBMS and a NoSQL MongoDB DBMS based on a commandline argument. I was largely responsible for the architecture and implementation of this feature.
Though there were many features implemented over the 5 week period. I will explore small subset of the features where I played a more central role in the implementation:
- Route Claiming
- Game History
Route Claiming
If you have ever played Ticket to Ride (I hadn’t when I started the project), you know that a central activity is claiming a route. For our implementation, Chris came up with the idea to use CSS absolute positioning for each of the individual rectangular squares on the map. He used CSS properties to trigger a color change for all sibling rectangles whenever one is hovered over. He also put on-click listeners that would trigger the modal that contained my logic to appear.
Also, whenever a route is claimed he would color the owned rectangles with the player’s color to denote ownership. While I did play a significant role in this feature, I would be remiss if I failed to acknowledge Chris’ dedication to the UI for this feature. In my opinion, this is one of the features that made our project the best in the class.
Determining whether a player may claim a route and assignment of route ownership was where I came in. There are many rules for Ticket To Ride. When I was placed in charge of the claim route feature, frontend and backend, I was struck by the complexity of the game conditions.
While reading the rules I decided to dedicate a function for each condition or group of conditions and that helped to decompose the
problem a great deal. Here is a snippet of the high level function called canClaim()
to help demonstrate the decomposition.
1 private canClaim (): boolean {
2 if (this.notAsManyRainbowsAsSpecified()) {
3 return false
4 }
5 if (this.claimingWithWrongColor()) {
6 return false
7 }
8 if (this.notEnoughCards()) {
9 return false
10 }
11 if (this.notEnoughMarkers()) {
12 this.state.errorMsg = 'You do not have enough markers left to claim this route'
13 return false
14 }
15 if (this.alreadyOwned()) {
16 return false
17 }
18 if (this.violatesDoubleRoute()) {
19 return false
20 }
21 return true
22 }
My teammates would often joke by saying, “That’s a very ‘Justinesque’ name.” In fact one teammate would often get frustrated with how long some of my variable names were.
I admit that sometimes I do get carried away but I am a firm believer that
when a function is appropriately named it can remove so much need for clarification. And for me, when I looked at the
list of rules for claiming a route and this function I can say to myself,
“Self, if it is true that each of these functions live up to their
names, so too will the canClaim()
function live up to its name.”
When I drill deeper into any of these functions, I find either further decomposition or a few lines of code so simple and digestible that it is clear it lives up to its name. Like this one demonstrating further decomposition:
1 private violatesDoubleRoute (): boolean {
2 if (!this.isDoubleRoute()) {
3 return false
4 }
5 if (!this.otherRouteIsClaimed()) {
6 return false
7 }
8 if (this.tooFewPlayersForDoubleRoute()) {
9 this.state.errorMsg = 'Double route claiming is enabled when you have more than 3 players'
10 return true
11 }
12 if (this.ownsOtherRoute()) {
13 this.state.errorMsg = 'You cannot own both of these routes'
14 return true
15 }
16 return false
17 }
And this one that clearly honors its name:
1 private tooFewPlayersForDoubleRoute (): boolean {
2 return Model.get_instance().active_game.players.length < MIN_PLAYERS_FOR_DOUBLE_ROUTES
3 }
What confirmed my practice of longer function names and thorough decomposition was how accurately it behaved after the first iteration. There were bugs of course but I was shocked at how few. And for the bugs that I did find I knew right where to look for them to get them resolved.
Here is a screen shot of what the user sees when they click on a route to claim it.
This idea also led me to believe that function extraction is more effective for communicating code than commenting. I am not saying that comments are bad, but my rule of thumb is, if you feel you need to write a comment, you probably could extract a function. Comments don’t compile! And comments don’t have scope!
Game History
Although this feature is not particularly visually impressive it was made possible to implement due to an interesting and powerful design pattern we implemented called the Command Pattern.
As a player interacts with the application, the client and server communicate via commands. Commands, in this case, are generic objects indicating that a particular event occurred. For example, there is a command representing the idea that ‘player 1 blind drew a card’. When the client receives this command, they will remove a card from the blind deck and place it in player 1’s hand.
While this may seem complicated for such a simple task there are many benefits to representing all client server communication via a command. For example, If you logout and log back in on a different machine, our server will actually resume your state by giving all commands associated with your player and current game. Pretty neat huh? This also makes persistence management easier to implement because we represent the state of the games based on the commands that occurred.
Even more interesting is that the server actually doesn’t contain objects representing important artifacts of the game like the train card deck or the current state of the game or which cards the players have in each hand. Instead each client has a copy of the game on their individual machines and they are kept in sync through commands that the server propagates to the clients. Think about that… each client has it’s own set of decks!!
So from a Game History perspective, my job was to present the information that a given player would know if they were playing the game face to face with other players. If a player drew from the face up cards for example, in a physical game (if you are paying attention) you will see which color it was. But of course, a blind draw is a mystery. But you would see how many they blind drew for example.
By leveraging the command pattern I was able to glean from the incoming commands public information and provide it to the Game History view for all the world to see. Here is an example of some commands:
Probably the most important feature for this game is that it really is fun to play. When my teammates and I shared it with our loved ones they were all very engaged and wanted to keep playing. The other thing I love about this game that is harder to perceive is the personal growth that it wrought for each of us as we built it. In such a short time period to work so hard was such an amazing experience and I am a different programmer now because of CS 340.