Permalink
Browse files

Code quality, comments, refactoring

  • Loading branch information...
1 parent fdc8cfa commit c47fb2b4b90d5f1281ad5d33beee7518e3995372 @mthvedt committed Sep 26, 2011
Showing with 152 additions and 124 deletions.
  1. +1 −1 README.md
  2. BIN battleship.jar
  3. +1 −1 project.clj
  4. +9 −9 src/battleship/ai.clj
  5. +11 −0 src/battleship/core.clj
  6. +10 −113 src/battleship/game.clj
  7. +120 −0 src/battleship/interactive.clj
View
@@ -19,7 +19,7 @@ Download the distribution, or the standalone jar, and run:
java -jar battleship.jar
```
-and follow the very simple instructions. Enjoy!
+and follow the instructions. Enjoy!
### The solver
View
Binary file not shown.
View
@@ -1,6 +1,6 @@
(defproject battleship "0.1.0-SNAPSHOT"
:description "A Monte Carlo Battleship player."
:dependencies [[org.clojure/clojure "1.2.1"]]
- :main battleship.game
+ :main battleship.interactive
:warn-on-reflection true
:uberjar-name "battleship.jar")
View
@@ -3,7 +3,7 @@
; A Monte Carlo based AI for Battleship.
-; Helper for infinite-seq. Given a square on a board, tells what the AI
+; Given a square on a board, tells what the AI
; is "allowed to know" about it. A square may have one of the given pieces,
; not have a ship (blocked), or be unknown.
(defn get-knowledge [square mypieces]
@@ -13,8 +13,10 @@
:has-ship) ; We know there's an unsunk ship here
:unknown)) ; We don't know what's here
+; [x, y] running thru [10, 10].
(def all-coordinates (for [x (range 10) y (range 10)] [x y]))
+; A map [x, y] -> what is known about it
(defn knowledge-map [known-board mypieces]
(zipmap all-coordinates
(for [[x y] all-coordinates]
@@ -60,13 +62,10 @@
; Gets the (not normalized) distribution of targetable squares
; in a (finite) boardseq.
;
-; There are a number of doalls here. Two reasons for this.
-; The first is that reducing a lazy seq can produce a tower of calls.
-; While most functional languages will handle the case where these are
-; tailcalls, Clojure does not.
-; The second is that this is the most performance-critical fn.
-; in performance-critical areas, the JVM/JIT seems
-; to play much better with eager seqs than lazy ones.
+; Doalls are used here to prevent lazy reduction.
+; In some cases, reducing a lazy seq with a lazy fn
+; can produce a tower of calls. Most LISP-like languages take care of this
+; with tail-call optimization; the JVM can't.
(defn get-distribution [boardseq]
(reduce (fn [running-count board]
(doall (map (fn [running-count-row row]
@@ -75,7 +74,7 @@
running-count board)))
(repeat (repeat 0)) boardseq))
-; Gets the most valuable target to fire upon. Returns the coordinates.
+; Guesses the most valuable target to fire upon. Returns the coordinates.
(defn get-target-from-dist [theboard dist]
(let [coordinate-value-tuples ; tuples of [value, x, y]
(mapcat (fn [row y]
@@ -86,6 +85,7 @@
coordinate-value-tuples)]
(rest (apply max-key first filtered-cvt)))) ; return (x, y)
+; The punch line. Guesses the most valuable target from a sequence of boards.
(defn get-target [theboard theseq search-size]
(let [dist (get-distribution (take search-size theseq))]
(get-target-from-dist theboard dist)))
View
@@ -1,9 +1,11 @@
(ns battleship.core)
+; Very basic battleship stuff goes here.
; A square can be empty or contain a ship.
; A square can be in three states: unstruck, struck, or sunk.
(defrecord Square [piece state])
+; Boards and squares.
(def board-size 10)
(def newboard
(vec (repeat board-size (vec (repeat board-size (Square. nil :unstruck))))))
@@ -15,18 +17,24 @@
(let [row (nth board y)]
(assoc board y (assoc row x square))))
+; The pieces in the canonical US version of Battleship.
(def pieces
[["carrier" 5]
["battleship" 4]
["cruiser" 3]
["submarine" 3]
["destroyer" 2]])
+; A hash map version.
(def pieces-map (reduce conj {} pieces))
; places a piece on the board, or nil if it can't be placed according
; to the given validator fn
; the validator function should take in the original board, x, and y
+; and return true if the validator will allow a piece to place there
+;
+; this allows us to generate random boards (with randomly-try-place-piece
+; below) according to certain constraints.
(defn place-piece [board0 [piecename piecelen] x0 y0 is-horizontal validator]
(let [xstep (if is-horizontal 1 0)
ystep (if is-horizontal 0 1)]
@@ -66,13 +74,16 @@
#(randomly-place-piece % %2 validator)
board mypieces)))
+; Below are some methods for printing to console.
+; Gets a string given a square.
(defn get-square-str [{piece :piece, state :state} pieces is-friendly]
(case state
:struck (if (nil? piece) "."
(if (zero? (get pieces piece)) "#"
"*"))
:unstruck (if (and is-friendly (not (nil? piece))) "O" " ")))
+; General purpose helper fn.
; Concatenate all the things then apply str. Not the same as C 'strcat'
(defn strcat [& things] (apply str (apply concat things)))
View
@@ -5,13 +5,21 @@
; A board + some data
(defrecord DecoratedBoard [board pieces action])
+; a board together with some info about the game state
(defn new-decorated-board []
(DecoratedBoard. (place-all-pieces newboard) pieces-map nil))
+; canonically, board1 is player's board (he fires upon board2)
+(defrecord Game [board1 board2])
+(defn newgame []
+ (Game. (new-decorated-board)
+ (new-decorated-board)))
+
; If all pieces have no HP, the game is over for that player
(defn board-lost? [dboard]
(zero? (apply + (vals (:pieces dboard)))))
+; true if someone won
(defn game-won? [game]
(or (board-lost? (:board1 game)) (board-lost? (:board2 game))))
@@ -21,43 +29,14 @@
2
(if (board-lost? (:board2 game)) 1 0)))
-; canonically, board1 is player's board (he fires upon board2)
-(defrecord Game [board1 board2])
-(defn newgame []
- (Game. (new-decorated-board)
- (new-decorated-board)))
-
-; Helper for printgame
-(defn print-message [dboard is-player]
- (when-let [[x y result & more] (:action dboard)]
- (if is-player
- (print "You fire at ")
- (print "I fire at "))
- (print (str (char (+ y (int \A))) x ". "))
- (println (case result
- :ineffective "But that area has already been fired upon."
- :missed "It's a miss."
- :struck "It's a hit!"
- :sunk (str (if is-player
- "***You sunk my "
- "***I sunk your ") (first more) "!***")))))
-
-; Print a game to *out*
-(defn printgame [{dboard1 :board1, dboard2 :board2}]
- (print-message dboard2 true) (print-message dboard1 false)
- (println (str "My board" " " "Your board"))
- (dorun (map println
- (get-board-strs (:board dboard2) (:pieces dboard2) false)
- (repeat " ")
- (get-board-strs (:board dboard1) (:pieces dboard1) true))))
-
-; Helper fns for 'fire
+; Helper fns for 'fire--this one causes a square to be missed
(defn miss [dboard x y target]
(assoc dboard
:board (set-square (:board dboard) x y
(assoc target :state :struck))
:action [x y :missed]))
+; Causes a square to be struck
(defn hit [dboard x y target]
(let [{piece :piece, state :state} target]
(if (= :unstruck state) ; hit something unstruck
@@ -78,85 +57,3 @@
(if (nil? piece) ; miss
(miss dboard x y target)
(hit dboard x y target))))
-
-(def ai-search 100)
-
-; Turn for the AI. Returns [modified game, modified ai-dist]
-(defn do-computer-turn [game]
- (let [dboard1 (:board1 game)
- board1 (:board dboard1)
- board-samples (infinite-boards
- board1
- ; remove all dead pieces from the 'pieces set'
- ; before passing to infinite-boards
- (select-keys pieces-map
- (map first
- (remove #(= 0 (second %))
- (:pieces dboard1)))))
- [x y] (get-target board1 board-samples ai-search)
- was-occupied (not (nil? (:piece (get-square board1 x y))))
- newdboard1 (fire dboard1 x y)]
- (assoc game :board1 newdboard1)))
-
-; Helper fns for the battleship main loop
-; All fns below interact with the in and out streamsh
-(defn is-valid-coord [coordinate]
- (and (>= coordinate 0) (< coordinate 10)))
-
-(defn print-endgame [game]
- (let [winner (get-winner game)]
- (if (= winner 1)
- (println "*** YOU WIN! ***")
- (println "*** YOU LOSE! ***"))))
-
-; Parse input and fire
-(defn do-player-turn [game]
- (do
- (print "Fire: ") (flush)
- (let [input-line (read-line)
- letter (first (filter #(Character/isLetter %) input-line))
- number (first (filter #(Character/isDigit %) input-line))]
- (cond
- (or (= \q letter) (= \Q letter))
- (do
- (println "Be seeing you...")) ; return nil--game over
- (or (nil? letter) (nil? number))
- (do
- (println "Please input valid coordinates.")
- (recur game)) ; Go back to beginning, try again
- (not (nil? (nth (concat (filter #(not (Character/isWhitespace %))
- input-line)
- (repeat nil)) ; prevent NPE
- 2))) ; more than 2 things were input
- (do
- (println "Please input just a letter and a number.")
- (recur game))
- true
- (let [uppercase-letter (if (>= (int letter) (int \a))
- (char (- (int letter) 32))
- letter)
- letter-coord (- (int uppercase-letter) (int \A))
- number-coord (Character/getNumericValue number)]
- (if (and (is-valid-coord letter-coord)
- (is-valid-coord number-coord))
- (Game. (:board1 game)
- (fire (:board2 game) number-coord letter-coord))
- (do
- (println "Please input valid coordinates.")
- (recur game))))))))
-
-; And the mainloop. Phew!
-(defn -main [& args]
- (println
- "Welcome to Battleship. Input some coordinates to fire, or 'Q' to quit.")
- (loop [game (newgame)]
- (dotimes [i 8] (println))
- (printgame game)
- (if (game-won? game)
- (print-endgame game) ; do not recur, terminate
- (let [game (do-player-turn game)]
- (cond
- (nil? game) nil
- (game-won? game) (do (printgame game) (print-endgame game))
- true (recur (do-computer-turn game))))))
- (flush))
Oops, something went wrong.

0 comments on commit c47fb2b

Please sign in to comment.