Facebook Careers Puzzle—battleship

This is a solution to a Facebook Job Puzzle.

Well, now that I have no use for a Facebook job, I better start posting the rest of my solutions. This was a fun one… a straightforward puzzle with no requirements for fancy algorithms or data structures… just good old organic strategy.

In battleship, you can hit every boat at least once by laying down a checkerboard firing pattern:

Once you hit something, you can aim on a line until you finish it off.

You can either randomly aim at squares in the pattern or hard-code a certain sequence, which is what I did. There might be an optimal sequence/strategy for statistical reasons, but then again a smart opponent can adapt and counter any specific strategy. That’s all extra credit for you folks out there.

One little optimization I made: if we sink the 2-tile little ship, we change our firing pattern to one with two squares between shots, instead of one.

The following solution in Ruby passed the Facebook puzzle robot:

(battleship_client.rb) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#!/usr/bin/env ruby
require 'gen-rb/Battleship'

host = ARGV[0] ? ARGV[0] : "thriftpuzzle.facebook.com"
port = ARGV[1] ? ARGV[1].to_i : 9060

trans = Thrift::Socket.new(host, port)
trans.open

$battle = Battleship::Client.new(Thrift::BinaryProtocol.new(trans))
p $battle.registerClient

p $battle.placePiece(Ship::CARRIER,     Coordinate.new({:row => 2, :column => 3}), true)
p $battle.placePiece(Ship::BATTLESHIP,  Coordinate.new({:row => 1, :column => 2}), false)
p $battle.placePiece(Ship::DESTROYER,   Coordinate.new({:row => 3, :column => 4}), true)
p $battle.placePiece(Ship::SUBMARINE,   Coordinate.new({:row => 7, :column => 2}), false)
p $battle.placePiece(Ship::PATROL,      Coordinate.new({:row => 5, :column => 7}), false)

sequence = []
$done = []
$ptdown = false

sequence = [
  [0,0], [1,1], [2,2], [3,3], [4,4], [5,5], [6,6], [7,7], [8,8], [9,9],
  [2,0], [3,1], [4,2], [5,3], [6,4], [7,5], [8,6], [9,7],
  [4,0], [5,1], [6,2], [7,3], [8,4], [9,5],
  [6,0], [7,1], [8,2], [9,3],
  [8,0], [9,1] ]

strat1 = [
  [0,2], [1,3], [2,4], [3,5], [4,6], [5,7], [6,8], [7,9],
  [0,4], [1,5], [2,6], [3,7], [4,8], [5,9],
  [0,6], [1,7], [2,8], [3,9],
  [0,8], [1,9] ]

strat2 = [
  [0,3], [1,4], [2,5], [3,6], [4,7], [5,8], [6,9],
  [0,6], [1,7], [2,8], [3,9],
  [0,9] ]

def attack(pos)
  return nil if $done.include?(pos)
  return nil unless pos[0] >= 0 && pos[0] < 10 && pos[1] >= 0 && pos[1] < 10

  while !$battle.isMyTurn()
    1000.times { |i| i += 1 }
  end
  $done << pos
  result = $battle.attack(Coordinate.new({:row => pos[0], :column => pos[1] }))
  $ptdown = true if result == AttackResult::SUNK_PATROL
  return result
end

def neighbors(pos) # North, South, East, West
  [ [pos[0], pos[1] - 1], [pos[0], pos[1] + 1], [pos[0] + 1, pos[1]], [pos[0] - 1, pos[1]] ]
end

def hit(pos, direction=nil)
  puts "#{pos} #{direction}"
  neighs = neighbors(pos)
  if direction
    result = attack(pos)
    return false if result.nil?
    return hit(neighs[direction], direction) if result == AttackResult::HIT
    # return (result < 6) ? true : false
    return false
  else
    neighs.each_with_index { |n, i| return if hit(n, i) }
  end
end

begin
  loop do
    if sequence.empty?
      $ptdown ? sequence = strat2 : sequence = strat1
    end
    next_move = sequence.shift
    next if $done.include?(next_move)

    if attack(next_move) == AttackResult::HIT
      hit(next_move)
    end
  end

rescue Thrift::TransportException
  trans.close
  exit
end
Posted on