# Battleship: Building the Server

> Last updated: 2017-08-16

I’ve been enjoying the battleship project. It seemed so simple, but I’m finding tons of little things that are tricky in ways I didn’t anticipate. That has taken it from the level of “toy project” to “actually interesting but small engineering challenge.” This post is about taking the classes I had written for playing battleship locally and creating a server that allows others to play against my client.

## My Tech Stack

I’m a Rubyist at heart. Even though this project is designed to get me to branch out a bit, it is fastest for me to start with what I know. This project feels too small for something as heavy as Rails, so I’m using [Sinatra](http://www.sinatrarb.com/). For the database layer, I’m using [ActiveRecord](https://github.com/rails/rails/tree/master/activerecord) since I like ActiveRecord Query Interface and I assume many folks don’t know you can use AR outside of Rails. Since this project is focused on computers talking to other computers, I’m not worrying about a templating tool for my views. [Embedded Ruby - ERB](https://ruby-doc.org/stdlib-2.4.1/libdoc/erb/rdoc/ERB.html) and HTML are more than sufficient. I’m using [minitest](https://github.com/seattlerb/minitest) and [ZenTest](https://github.com/seattlerb/ZenTest) for testing.

In summary:

- Ruby
- Sinatra
- ActiveRecord
- ERB & HTML
- Minitest

## The Endpoints

My initial design called for two endpoints, `/new_game` and `/turn`. I added a third endpoint at `/` that displays the rules and the format for messages to the other endpoints. One assumption I made is that the server always goes second. This isn’t necessary but made figuring out the logic much simpler.

`/new_game` starts a game with the server and returns a game ID. The endpoint accepts a `get` request and returns a JSON object of this form: `{ game_id: <id> }`.

`/turn` takes a guess/move/turn posted from the client, processes it, and responds with a guess of its own. Here’s the message format for both the request and the response. The first move will have no response and is left empty. The last move will have no guess and is also left empty.

```
{ game_id: <id>,
  response: { hit: [true|false],
              sunk: <ship name>,
              turn_id: i },
  guess: { guess: <A7, B4, E1, etc>,
              turn_id: i + 1 }
}
```

## The Models

I created two models to store game state. The game model is simple: it keeps track of what games have been started.

```
class Game < ActiveRecord::Base
  has_many :turns
end

class CreateGames < ActiveRecord::Migration[5.1]
  def change
    create_table :games do |t|
      t.string :game_id
      t.timestamps
    end
  end
end
```

The game model has a one-to-many relationship with the turn model. Turn keeps track of what happens each time the `/turn` endpoint is hit. Turn records a game ID and a turn ID. It also records the message from the client. The last field, state, records the client state at the end of the turn so that the next time a message for this game comes in the server can rebuild the appropriate client object.

```
class Turn < ActiveRecord::Base
  belongs_to :game
end

class CreateTurns < ActiveRecord::Migration[5.1]
  def change
    create_table :turns do |t|
      t.belongs_to :game, index: true
      t.integer :turn_id, index: true
      t.text :body      
      t.binary :state
      t.timestamps
    end
  end
end
```

## Marshaling

I chose to use [marshaling](https://ruby-doc.org/core-2.4.0/Marshal.html) to save the client state and then reconstitute it. In Ruby, you can marshal most objects without having to do anything special. The board object, however, has a Hash that runs some code on key misses and this can’t be marshaled as is. Since my client object has a board object, I had to solve this before I could finish implementing my server.

The board object’s board instance variable is defined as a hash with array values, like this:

```
@board = Hash.new { |h, k| h[k] = [] }
```

To marshal this object I had to write my own `marshal_dump` and `marshal_load` methods. Hashes in Ruby decompose nicely to nested arrays of the form `[[key, value], [key, value]]` so I implemented `marshal_dump` by converting the hash to an array.

```
def marshal_dump
    @board.to_a
  end
```

To recreate the hash in `marshal_load` I simply iterated over the dumped array to recreate the hash.

```
def marshal_load data
    @board = Hash.new { |h, k| h[k] = [] }

    data.each do |location, value|
      @board[location] = value
    end
  end
```

I chose this method because it seemed a more idiomatic way to set the default\_proc on a hash. I could have also used `Hash::[]` and `Hash.default_proc=`. But I find many people don’t know `Hash::[]` so I chose the more obvious solution. For completeness though, here’s the other definition I tried.

```
def marshal_load data
    @board = Hash[data]
    @board.default_proc = proc { |h, k| h[k] = [] }
  end
```

## Actual Server Code

The `new_game` endpoint is super simple. It just creates a new game record.

```
get '/new_game' do
  g = Game.create!

  content_type :json
  { game_id: g.id }.to_json
end
```

The `turn` endpoint is more complicated. The first bit is just processing the incoming message and then retrieving the game and last turn from the database.

```
post '/turn' do
  body = request.body.read
  params = JSON.parse(body)

  response = {}

  g = Game.find( params["game_id"] )

  turns = Turn.where(game_id: g.id)
  last = turns.last
```

After that, I have to recreate a client, or if this is the first move, create a new one. This is where I use `Marshal.load`.

```
# Load in state from the previous turns and create a new client object.
  if last
    c = Marshal.load(last.state)
  else
    c = Client.new
    c.place_ships
  end
```

The next step is to process the user’s last move and create the response section of the response. I also record this move as a turn in the turns table.

```
response[:response] = c.process_move params["guess"]["guess"]
  response[:response][:turn_id] = params["guess"]["turn_id"]
  response[:response][:lost] = c.lost?

  t = Turn.new
  t.game_id = g.id
  t.state = Marshal.dump(c)
  t.body = params
  t.turn_id = params["guess"]["turn_id"]
  t.save!
```

Then I need to create my guess and record that in the turns table as well.

```
guess = c.guess
  t = Turn.new
  t.game_id = g.id
  t.state = Marshal.dump(c)
  t.turn_id = params["guess"]["turn_id"] + 1
  t.save!

  response[:guess] = { guess: guess, turn_id: t.turn_id}
```

Finally, I can send the response to the client.

```
response[:game_id] = g.id

  content_type :json
  response.to_json
```

Stylistically I’m breaking a lot of rules of good design. This method is about 50 lines long. It has five distinct phases that could conceivably be refactored into their own methods. Perhaps the turn class should have a client object and be able to reconstitute it on its own. I’m creating turn objects in six or more lines instead of using ActiveRecord hash syntax to create them in a single line. I may come back to this at some point to try out different refactorings based on design patterns just to see if I can clean this up. Suggestions welcome.

## Mistakes and Uncertainties

As I coded this up, I realized I hadn’t included any way for someone to say that they had lost. So I’ve added that to the protocol. I’m not confident in my choice to include turn\_ids manually specified by the client and the server. I can order the turns via timestamps just as quickly so I may end up removing that in the future. In early September, [Seattle.rb](http://www.seattlerb.org/) will be putting my server to the test at our semi-regular workshop meetup. I’m confident that I will learn a lot about the weaknesses in my design that evening. I’m looking forward to it.

terminal
 The code for this post is located [here](https://github.com/thagomizer/battleship). 

Please enable JavaScript to view the [comments powered by Disqus.](https://disqus.com/?ref_noscript)

 ![Aja Hammerly](/img/traditionalheadshot.jpg)

Aja Hammerly

Seattle, WA

### library\_books Series Index 

- [Developer Relations](/series#developer-relations)4
- [Machine Learning Basics](/series#machine-learning-basics)3
- [Battleship](/series#battleship)5
- [Talks I Love](/series#talks-i-love)6
- [Algorithms with Auntie Aja](/series#algorithms-with-auntie-aja)3
- [Ruby and GCP](/series#ruby-and-gcp)5
- [Playing with Data](/series#playing-with-data)3
- [Kubernetes & Rails](/series#kubernetes-rails)3
- [Learn To Code Resources](/series#learn-to-code-resources)3
- [Testing Anti-Patterns](/series#testing-anti-patterns)5
