Sunday, March 20, 2016

The Big Short

A short (no pun intended) offering today, as I'm currently intellectually and emotionally exhausted from worrying about the future of our country and the world in general...

Last night, Tanya and Marco and Tanya and I (yes, two separate Tanya's) watched The Big Short, the movie about the guys that made a ton of money off of betting against the American economy from 2005 through 2008 or so.  Well, that's not really what the movie (or book) is about.  The story actually follows and exposes the absolutely ridiculous series of unfortunate events that got us to the point of the housing market bubble bursting.  The fact that those people that bet against the market made a lot of money in the meantime is almost a footnote, which I thought was really interesting.

The movie was poignant and entertaining, which is also a pretty rare combination.  Every performance was memorable and well executed, with the exception of Brad Pitt -- sorry, he's really hit-or-miss with me and felt very average in this role.  There was a greater than usual amount of breaking the fourth wall, and it was always compelling and appropriate.

In light of our current political climate, I encourage everyone that isn't familiar with the story here to go watch the movie and/or read the book.  Think about how the candidate you favor relates to (or is actually culpable in) the events portrayed there.  Consider how they might behave on a going-forward basis with regard to Wall Street (and fiscal responsibility overall).

Now bear in mind that this is only *one* of the really important issues that probably don't have enough substantive discourse in the debates from either party.  It is my sincere hope that everyone that can *will* participate in this year's political process and will do so thoughtfully.

Pick your top three issues.  Really think them through from as many perspectives as you can so that you can have a fair view of them.  Find the candidate that best aligns with those top three, and vett that they do so as much as you possibly can.  Don't just listen to what the candidate is saying -- look at their public/behavioral record.  This is relatively easy to do with current/former government officials, less so with private citizens.  Do your best in this effort so that you can support and vote with a clear conscience.

Insofar as the opportunities present themselves, participate in meaningful, thoughtful, and respectful discussion with people that disagree with the importance of your issues or your stances.  Be open to learning something, understanding someone else's point of view, and potentially changing your attitude.

Above all, be kind to yourself and to everyone else.  There's too much hate and misery in the world as it is -- don't contribute to vitriol or negative behavior/attitude.  I read this commentary yesterday and found that it resonated with my own mental state pretty well.  It was written more eloquently than I could have done, so please give it a read.

Alright, that's it for politics and sadness for me.  I promise the next post will be back to the normal book reviews, writing, exercise, technology, etc.  If you need a diversion until then, check out Tim's Snake Lake blog post and play the game.  Yum!!

Saturday, March 12, 2016

Book review: "Mistborn" (original trilogy) by Brandon Sanderson

This is actually a general review of the entire Mistborn original trilogy, as I've read them over the past several months with the boys and on my own.  We started reading them together in the car via Audible, but Garrett couldn't wait for Gabriel and I and finished listening to the entire series on his own before we'd even finished the first book as a group.  Eventually, Gabriel said he was going to pause listening to them, leaving me to finish them on my own.

I also have the paperback collection, shown here.  I finished the series by both reading and listening to them on Audible.

This review contains minor spoilers at the very least.  Read on at your own peril!

Encapsulate the plot of this book in one sentence.

A group of thieves plot to overthrow the rule of an immortal emperor/god, deal with the repercussions of all actions to that end, and discover the full history, responsibilities, and sources of the mystical powers that both hold the world together and rip it asunder.

When and where did you get this book?

I originally got the first Mistborn book in November, 2014.  I subsequently bought the paperback trilogy set from Barnes and Noble in the summer of 2015.

What year or edition?

First editions, from audible.com and in paperback.

Did you finish it?

Yes, although it took me longer than expected... :-)

What's your verdict?

Fantastic series all around.  Brandon Sanderson is a really interesting world builder, and his magic systems are really well thought ought.  His characters are relatable, and this series features a really strong female lead.

The paperback versions have held up well under normal usage, although the paper and cover are a bit thin.  Considering the books are ~700 pages each, it doesn't seem like there's a lot that can be done about that.

The Audible series is narrated by Michael Kramer, who has become one of my favorite readers.  He found unique voices for the characters, and there are lots of them.  The only knock on him was that his voice for Elend (one of the main male characters) seemed to change between books 2 and 3.  There's quite a bit of character development between those two books, so it's certainly possible.  However, reading the words on the pages, I didn't make that change in my head.

Overall, both content and performance, 4.5 out of 5 stars!

What surprises did it hold, if any?

Without giving too much away, the first book had a really interesting and somewhat unexpected plot twist near the end.  That kind of thing continues to happen throughout the series, although never as dramatically as in the first book.

The other surprise has to do with the magic system.  You figure everything out about the races, the settings, etc, but a surprise about the magic system is revealed near the end of book 3 and is never resolved.  Apparently, Brandon Sanderson decided to play that one REALLY close to the vest... ;-)

Which scenes will stay with you?

There are lots of pretty memorable scenes.  The first time we see a mistwraith, the first ball, seeing the Lord Ruler (book 1), the assault on Cett and the attack on Luthadel (book 2), the rush to the end (book 3).  Describing these in any more detail would give more away than I want to.

Which characters will stay with you?

Vin, Vin, Vin, and Vin.  She's astounding.  Powerful, tough, vulnerable, delicate, complex, blunt -- like I said earlier, a fantastic lead.

Elend and Sazed to lesser extents, and Kelsier (of course).  Spook and Breeze as well.  And I suspect I'll think of TenSoon every time I see a big black wolf-ish canine for the near future at least.

What genre would you say it is?

Fantasy all the way.

Have you read anything else by this author?

Yes!  The first two of The Stormlight Archive and The Rithmatist. I've also got Elantris and Warbreaker on the shelf.  (and maybe Steelheart?  I'll have to check...)

Is it available today?

Yes, and you should go get it and read it, especially if you like fantasy.

Sunday, March 6, 2016

Cartagena, part 5

When last we spoke, we had managed to draw a board with spaces and icons.  Now it's time to fill in the players and finish up the web implementation.

Recall that we had left the server code in a state that if we called the new-game route without a :players keyword, we would default the new game to return a two-player game featuring tanya as orange and rusty as black.  I decided to work forward from here, because it let me defer the decision about how to capture the player data up front.  As it turns out, this was a great decision, because I'm terrible at user experience.  :-)

Calling the endpoint resulted in the server giving us a set of game data with two players. The players start in jail.  Now I needed to add the code that would actually display the players in that spot.

(defn jail []
  (when-let [jail (get-in @app-state [:board 0])]
    (apply concat
           (let [pirate-frequencies (frequencies (:pirates jail))
                 pirate-colors (vec (keys pirate-frequencies))]
             (for [player-index (range (count pirate-frequencies))]
               (let [pirate-color (get pirate-colors player-index)
                     pirate-count (pirate-color pirate-frequencies)
                     color-name (name pirate-color)
                     x (to-scale (+ 5 (* 10 player-index)))]
                 (for [pirate-index (range pirate-count)]
                   (let [y (to-scale (+ 35 (* 10 pirate-index)))]
                     [:circle
                      {:cx x
                       :cy y
                       :r (to-scale 4)
                       :fill color-name}]))))))))

I also needed to add this bit of rendering code to the main-view function, which was easy.

(defn main-view []
  [:center
   [:h1 "CARTAGENA"]
   [:div
    [:button
     {:class "btn btn-primary"
      :on-click (fn button-click [e]
                  (select-players!))}
     "New Game"]]

   [:div
    {:class "row"
     :style {:float "left" :margin-left 10 :margin-right 10}}
    [:div
     {:class "col-md-5"}
     (-> [:svg
          {:view-box (str "0 0 " (to-scale 501) " " (to-scale 301))
           :width (to-scale 501)
           :height (to-scale 301)}]
         (into (static-board))
         (into (jail))
         )]]])

The result was marvelously exciting!

Alright, I might've oversold that a little.  But the fact of the matter is, I had a set of players on the board, in the right position!  SO CLOSE TO BEING FINISHED!

...except that I really wasn't.  How in the world was I going to handle moving the pieces around?  Recall that the rules state that you can take three actions on a turn: playing a card to move a pirate forward, selecting a pirate to move backward in order to draw cards, and passing.  At this point, I can't play any cards because I can't tell who all has what.  I can't move backward, because there's nowhere to move backward to.  I can't even pass, because there's no action button or keystroke that will allow me to do that.

It looks like I'm going to have to break down and build a player area.  <insert ominous music here>

As I've already stated, I don't consider myself to be much of a user experience expert.  This became doubly apparent as I iterated through several different versions of the player area.  I finally landed on a design such that the player area is to the right of the game board.  Here's the code.  Notice that I baked it right into the main-view function, which makes that function needlessly cluttered.  I'm unapologetic.  It's not time to refactor yet...  :-D

(defn main-view []
  [:center
   [:h1 "CARTAGENA"]
   [:div
    [:button
     {:class "btn btn-primary"
      :on-click (fn button-click [e]
                  (select-players!))}
     "New Game"]]

   [:div
    {:class "row"
     :style {:float "left" :margin-left 10 :margin-right 10}}
    [:div
     {:class "col-md-5"}
     (-> [:svg
          {:view-box (str "0 0 " (to-scale 501) " " (to-scale 301))
           :width (to-scale 501)
           :height (to-scale 301)}]
         (into (static-board))
         (into (jail))
         )]]
   (when-let [active-player (active-player @app-state)]
     (let [{:keys [color cards] player-name :name} active-player
           card-groups (frequencies cards)]
       [:div
        {:class "col-md-4"}
        [:div {:style {:margin-right 10}}
         [:table {:class "table table-bordered table-responsive"}
          [:tbody
           [:tr
            [:td "Current Player"]
            [:td player-name]]]
          [:tr
           [:td "Color"]
           [:td
            [:svg
             {:width (to-scale 20)
              :height (to-scale 20)}
             [:circle
              {:cx (to-scale 10)
               :cy (to-scale 10)
               :r (to-scale 7)
               :fill (name color)}]]
            [:span {:style {:color (name color)}} (name color)]]]
          [:tr
           [:td "Actions Remaining"]
           [:td (:actions-remaining @app-state)]]
          [:tr
           [:td "Cards"]
           [:td (for [[card num] card-groups]
                  ^{:key card}
                  [:span {:style {:float "left"}}
                   [:figure
                    [:img
                     {:src (card icon-images)
                      :width (to-scale 30)
                      :height (to-scale 30)
                      :on-click (fn img-click [e]
                                  (select-card! card))}]
                    [:center [:figcaption num]]]])]]
          [:tr
           [:td "Selected Card"]
           [:td [:span {:style {:float "left"}}
                 (when-let [selected-card (:selected-card @app-state)]
                   [:img
                    {:src (selected-card icon-images)
                     :width (to-scale 30)
                     :height (to-scale 30)
                     :on-click (fn img-click [e]
                                 (unselect-card!))}])]]]
          [:tr
           [:td {:colSpan 2}
            [:center
             [:button
              {:class "btn"
               :on-click (fn btn-click [e]
                                   (update-active-player! @app-state))} "Pass"]]]]]]
        [:div
         [:p "To move forward, click a card, then click the target pirate.  To undo card selection, click the selected card."]
         [:p "To move backward, click the target pirate."]]]))])

This also required a couple of handler functions: select-card!, unselect-card! and update-active-player!  The update-active-player! function is actually a server request, since all of that logic is built into the game engine.

(defn update-active-player [state response]
  (assoc state :actions-remaining (:actions-remaining response)
               :current-player (:current-player response)))

(defn on-update-active-player [response]
  (swap! app-state update-active-player response))

(defn update-active-player! [{:keys [actions-remaining current-player player-order]}]
  (ajax/POST "http://localhost:3000/update-active-player"
             {:params {:actions-remaining actions-remaining
                       :current-player current-player
                       :player-order player-order}
              :handler on-update-active-player
              :error on-error}))

(defn select-card! [card]
  (reset! app-state (assoc @app-state :selected-card card)))

(defn unselect-card! []
  (reset! app-state (dissoc @app-state :selected-card)))

This set of code led to a usable UI, if not an aesthetically pleasing one.


Now we have the ability to choose cards (and unchoose them, if we accidentally clicked one) and pass, but we don't have the ability to choose a specific pirate to move forward or backward.  We also don't have any code to display pirates on any board space except the jail.  Time to add those bits to the appropriate spots.

;; TODO: this looks almost exactly like jail
(defn ship []
  (when-let [ship (get-in @app-state [:board 37])]
    (apply concat
           (let [pirate-frequencies (frequencies (:pirates ship))
                 pirate-colors (vec (keys pirate-frequencies))]
             (for [player-index (range (count pirate-frequencies))]
               (let [pirate-color (get pirate-colors player-index)
                     pirate-count (pirate-color pirate-frequencies)
                     color-name (name pirate-color)
                     x (to-scale (+ 445 (* 10 player-index)))]
                 (for [pirate-index (range pirate-count)]
                   (let [y (to-scale (+ 245 (* 10 pirate-index)))]
                     [:circle
                      {:cx x
                       :cy y
                       :r (to-scale 4)
                       :fill color-name
                       :on-click (fn ship-click [e]
                                   (pirate-click pirate-color 37))}]))))))))
                                   
(defn circles-for [space-index x y colors]
  (for [color-index (range (count colors))]
    (let [color (get colors color-index)
          color-name (name color)
          cx (to-scale (+ 35 x))
          cy (to-scale (+ y 5 (* 10 color-index)))]
      ^{:key color-index}
      [:circle
       {:cx cx
        :cy cy
        :r (to-scale 4)
        :fill color-name
        :on-click (fn circle-click [e]
                    (pirate-click color space-index))}])))

(defn normal-spaces []
  (apply concat
         (for [i (range 1 37)]
           (when-let [space-data (get-in @app-state [:board i])]
             (let [position (get piece-positions i)
                   x (:x position)
                   y (:y position)
                   space (normal-space x y)
                   image (space-image x y (:icon space-data))
                   pirates (circles-for i x y (:pirates space-data))]
               (conj [space image] pirates))))))

Notice the prolific pirate-click! function.  This is the function from whence the remaining server calls initiate.  It and its associated functions are a decent chunk of code, but it all feels very similar to the code I did for the console version.

;; TODO: figure out a way to make this work elegantly with callbacks
;; swiped from server code
(defn game-over?
  "Returns truthy if a player has 6 pirates on the ship; otherwise nil."
  [board]
  (let [ship (first (filter #(= :ship (:icon %)) board))
        pirate-counts-by-color (frequencies (:pirates ship))]
    (some #(>= (second %) 6) pirate-counts-by-color)))

(defn end-game [state]
  (assoc state :game-over true))

(defn end-game! []
  (swap! app-state end-game))

(defn play-card [state response]
  (-> state
      (assoc :board (:board response)
             :discard-pile (:discard-pile response)
             :players (conj (remove #{(active-player state)} (:players state)) (:player response)))
      (dissoc state :selected-card)))

(defn on-play-card [response]
  (let [board (:board response)]
    (swap! app-state play-card response)
    (if (game-over? board)
      (end-game!)
      (update-active-player! @app-state))))

(defn play-card! [player card from-space board discard-pile]
  (ajax/POST "http://localhost:3000/play-card"
             {:params {:player player
                       :icon card
                       :from-space from-space
                       :board board
                       :discard-pile discard-pile}
              :handler on-play-card
              :error-handler on-error}))

(defn move-back [state board response]
  (assoc state :board board
         :draw-pile (:draw-pile response)
         :discard-pile (:discard-pile response)
         :players (conj (remove #{(active-player state)} (:players state)) (:player response))))

(defn on-move-back [response]
  (when-let [board (:board response)]
    (swap! app-state move-back board response)
    (update-active-player! @app-state)))

(defn move-back! [player from-space board draw-pile discard-pile]
  (ajax/POST "http://localhost:3000/move-back"
             {:params {:player player
                       :from-space from-space
                       :board board
                       :draw-pile draw-pile
                       :discard-pile discard-pile}
              :handler on-move-back
              :error-handler on-error}))

(defn pirate-click [color from-space-index]
  (when (= color (:color (active-player @app-state)))
    (let [player (active-player @app-state)
          board (:board @app-state)
          from-space (get board from-space-index)
          discard-pile (:discard-pile @app-state)]
      (if-let [selected-card (:selected-card @app-state)]
        (play-card! player selected-card from-space board discard-pile)
        (move-back! player from-space board (:draw-pile @app-state) discard-pile)))))

Now, FINALLY, I can click pirates and expect them to render properly.  Every click of a pirate calls the server, passing the current game state, and receiving an updated board from the engine.  I'm not completely satisfied with it, since I wound up duplicating some server code in the web implementation to check for the end game condition.  However, given the amount of typing I'd already done and the fact that I want to make it multi-player next, I decided it wasn't worth the effort to clean up.

All that's left is gathering the initial player data.  As you might've guessed, there was more than a little give-up in me at this point.  I decided the simplest thing that could work would be to create a silly little set of inputs, one for each color.  I won't bother showing you the hiccup for that, but the form looks like:


Are you impressed yet?!

As you might imagine, filling in names and pressing the Start button creates a players map that gets sent in the request to the new-game endpoint.  The server initializes a game state given the player data, and away we go!

So this is the end.  We've finally slogged through the initial web implementation for Cartagena.  If you've followed me thus far, you deserve a medal for your determination. I certainly learned a lot along the way, and hope that you have too (or at least that you were entertained by my bumbling through it).

I do intend to implement the multi-player non-local version of this at some point in the future, but it might be a while.  If you have a burning desire to see it done, let me know in the comments.  Otherwise, thanks for sharing my adventures through this series, and stay tuned for non-Cartagena posts coming your way soon!  :-D