Tuesday, December 29, 2015

Year-End Book Statistics

Looking at my largest keywords in the blog this year, I see that I've spent more time talking about books than anything else.  As such, I thought it would be fitting to share with you some analytics and statistics about my book consumption this year.

First of all, some notes:

  • The following data only includes books I read for fun/entertainment.  They do not include any technical books I read for work, although they do include books read on the craft of writing.
  • I make no distinction between books consumed via physical instance, kindle, or audible.  I consider books finished via all of those mediums as "read," even though some were listened to, strictly speaking.
  • I read at least one book on this list twice, but I only counted individual instances of books.

Now, for the high-level statistics:

Total books read 37
Total pages read 13089
Longest book 635 (American Gods, Neil Gaiman)
Shorted book 76 (The Last Airbender: The Search, Gene Luen Yang)
Average book length 354

These numbers are interesting, but let's get a little more detailed, shall we?

As you can see, the reading really picked up in the second half of the year.  This began with me reading "On Writing" by Stephen King.  One of the things he mentions in there is that in order to be a good writer, you need to be a prolific reader.  It reminded me of the fact that I used to be a prolific reader, but had really fallen out of the habit lately.  At that point, I decided to start reading more, and wound up getting a bunch of books for my birthday.


The breakdown by genre is pretty telling.  I'm most interested in the fantasy and sci-fi genres, but managed to get a couple of other ones in there.  The graphic novels and Christmas stories were actually really short, totaling less than 400 pages together.


I did manage to spread the love around quite a bit from the authorship perspective.  I read 4 Dresden novels by Jim Butcher, 3 each by Scott Meyer and Lev Grossman (two sets of trilogies), a couple of 2-fers (including the Christmas stories, both by Charles Dickens), and the rest just a single book from each.  In total, I read 26 different authors.  This is actually fairly uncommon for me.  My typical approach is to find a small set of prolific authors I like and stick with their stuff.  I'm really glad that I've finally branched out some, although I now find myself liking a much larger number of prolific authors.  I think that's a good problem to have.

It's possible I'll finish another book before the end of the year, but it really doesn't look like it.  My goal for next year is to read 50 books.  I think, given my pace during the last five months of this year, I'll be able to attain that goal.

The *real* question is: will I break down and *write* a book this year?  Only time will tell... ;)

Friday, December 25, 2015

Christmas Traditions

Around our house and family, we don't really have many Christmas/Holiday traditions.  The season is very busy -- socially, work-wise, and involves lots of travel.  This year was no exception.

It all starts one of two ways.  During years where the boys spend Christmas with me, I pick them up and bring them back to the metroplex with me on the Friday when they get out of school.  This is the first of a couple of 3-hour road trips we take over the course of less than two weeks.

Regardless of when we pick the boys up, Tanya's birthday party occurs on the Saturday prior to Christmas (unless Christmas falls on a Sunday, when it will be up in the air).  We (almost) always host it at our house, and we typically spend the day preparing the food and house for the occasion.  This year, Igor and Anjelika's youngest sons came with them, so I split time between hanging with the adults and playing video games with the boys.

This timing coincides with my father's side of the family having a Christmas celebration somewhere near Abilene, Texas.  I get to hear about all of the shenanigans without having to be caught up in them, which is probably a good thing.  One of these years, we'll be able to make it out there.  Until then, keep it silly, Bentley Clan!

Work continues until a couple of days before Christmas.  This year, we managed to not be on call during the week, although I still acknowledged a couple of alerts and checked on the dashboards.  So yes, I took my work computer with me, in case you were wondering.  I promise, most of the time I was on it, I was just checking social media or working on Cartagena (which is done-ish... more on that in another post).

My parents have an anniversary two days before Christmas, and we like to take them out to dinner to celebrate.  We drive to Salado (second 3-hour road trip) to do so (picking up my boys on the way in years when they're not already with us).  When you've been together as long as my parents have, I think treating them to a meal which they didn't have to prepare or clean up from is a better gift than something coinciding with a yearly theme.

Christmas Eve is the time for opening gifts for us.  This is usually because everyone wants to do a little something in their own houses early on Christmas morning.  It's also relatively easy to get everyone together on Christmas Eve.  I was, apparently, a good boy this year, receiving headphones, a book, a day-hiking backpack, a Miyazaki movie, a t-shirt, CDs, candy, and cash.

Christmas Day, then, is for stockings.  More candy to be found in mine, along with an orange.  The orange is a Christmas Tradition, meant to represent prosperity (monetary, usually), that my mom always adheres to.  The irony is that I always appreciate getting it, but I rarely actually eat it.  Honestly, who wants to eat a piece of fruit that they've just pulled out of a musty old sock?

We then travel back home (3rd 3-hour road trip) in order to prepare for Russian Christmas, which takes place on the Saturday following Christmas (tomorrow, in this year's case).  In years where the boys start Christmas with us, they get dropped off on the way; otherwise, they come with us.  Russian Christmas is hosted by either Tanya G. or Anjelika.  Pictured here is goroshek (Russian pea salad), which we will be bringing.  This is another evening with the folks from Tanya's party, so time is spent playing with kids and adults in their turn.

The following week, counter to popular belief, is actually a productive work week for us.  We'll stop working early on New Year's Eve, however.  In years when we pick the boys up after Christmas, we will stay up playing board games or video games until midnight on New Year's Eve, at which point we'll watch the ball drop and drink grape juice of varying ferment levels.  The trip back to drop the boys off happens the next day at some point (although I'm not counting this 3-hour trip, since I counted the one where I pick them up first).

You know, I started off this blog post by stating that we don't have many traditions.  Upon further reflection, I'm forced to admit that is not the case.  Everything I've described above has happened in multiple years, and if it's not a tradition yet, it soon will be.

All of that to say, I'm very thankful that these traditions, and everyone involved with them, exist.  I hope that everyone reading this has had and continues to have very Happy Holidays!

Sunday, December 20, 2015

Cartagena, part 2


This week brought "interesting" progress on the implementation of Cartagena.  A note before we begin, however.  Tim's version of pirates is NOT a Cartagena implementation as I'd originally thought.  My apologies if this caused any confusion.  Tim's game is actually WAY MORE COOL, as you get to sail your pirate ship to all the ports in the Caribbean.  In any case, it's a different game (at least, for now).

OK, now on with the show.  I started this week by refactoring the board to "pieces" instead of "cards."  I also added stubbed tests to outline my thoughts of how the implementation should go from that point forward.  This is almost like taking notes and can help you think through a problem, which I definitely need help with.

(deftest shuffle-cards-test
  (testing "Returns a random collection of N cards"))

(deftest initialize-player-test
  (testing "Returns a player data structure full of initial state data")
  (testing "Each player should have six pirates")
  (testing "Each player should have six cards"))

(deftest new-game-test
  (testing "Player count should be equal to the number to which it was initialized")
  (testing "All players should be in jail")
  (testing "Active player should be player 1"))

(deftest player-move-test
  (testing "Player can play card and move pieces")
  (testing "Player can move backward and receives cards")
  (testing "Player without cards must move backward")
  (testing "Player with no available spaces behind cannot move backward")
  (testing "Player with pirate on ship can move that pirate backward"))

(deftest game-over-test
  (testing "Game ends when a player has all pirates on the ship"))

The next bits involved managing some state.  I created atoms for managing the card draw and discard piles while writing tests and implementing card shuffling.  Once cards are shuffled, a player could be initialized.  While implementing that function, I realized I hadn't implemented a function for drawing cards, so I paused the player initialization in favor of writing a function for drawing cards from the draw pile.  After that, I finished out the player initialization.


(def draw-pile (atom []))
(def discard-pile (atom []))
(defn place-cards!
  "Puts the full set of cards into the discard pile"
  []
  (reset! discard-pile (->> icons
                            (map #(repeat 17 %))
                            flatten
                            vec)))

(defn shuffle-cards!
  "Shuffles the card in the discard pile, placing them in the draw pile"
  []
  (when (and (not (empty? @discard-pile))
             (empty? @draw-pile))
    (reset! draw-pile (vec (shuffle @discard-pile)))
    (reset! discard-pile [])))

(defn initialize-board!
  "Returns a vector populated with icons from the 6 of the board pieces concatenated."
  []
  (->> all-board-pieces
       shuffle
       (take 6)
       flatten
       vec))

(defn draw-cards!
  "Takes n cards off of the top of the draw pile"
  [n]
  (let [cards (take n @draw-pile)]
    (reset! draw-pile (drop n @draw-pile))
    (vec cards)))

(defn initialize-player
  "Initializes a player data structure"
  [{:keys [name color]}]
  {:name name :color color :pirates [-1 -1 -1 -1 -1 -1] :cards (draw-cards! 6)})

Now that we have cards and players, it seemed like it was time to try to initialize a new game.  I created yet another atom to hold general game state.  At this point, I had several atoms, which is a TERRIBLE smell.  I stopped forward progress in order to refactor all of the state storage into a single atom.  This caused a fair amount of refactoring at each function's level, mainly due to removal of state management from those functions.  This actually felt REALLY nice, and I was more pleased with the implementation at that point.


(def game-state (atom {}))

(defn initialize-board
  "Generates a board from 6 random pieces"
  []
  (->> all-board-pieces
       shuffle
       (take 6)
       flatten
       vec))

(defn shuffle-cards
  "Shuffles and returns passed cards"
  [cards]
  (vec (shuffle cards)))

(defn initialize-cards
  "Puts the full set of cards into the discard pile"
  []
  (->> icons
       (map #(repeat 17 %))
       flatten
       shuffle-cards
       vec))

(defn initialize-player
  "Initializes a player data structure"
  [{:keys [name color]}]
  {:name name :color color :pirates [-1 -1 -1 -1 -1 -1] :cards []})

(defn draw-cards
  "Pulls cards off the top of the draw pile, returning a map of the new hand and what remains in the draw pile"
  [n player draw-pile]
  {:player (assoc player :cards (apply conj (:cards player) (take n draw-pile)))
   :draw-pile (vec (drop n draw-pile))})

(defn new-game!
  "Initializes a new game"
  [players]
  (let [board (initialize-board)
        players-draw-pile (loop [ps (vec (map initialize-player players))
                                 cards (initialize-cards)
                                 acc []]
                            (if (empty? ps)
                              acc
                              (let [player-draw-pile (draw-cards 6 (first ps) cards)]
                                (recur (rest ps) (:draw-pile player-draw-pile) (conj acc player-draw-pile)))))
        init-players (vec (map :player players-draw-pile))
        draw-pile (:draw-pile (last players-draw-pile))]
    (reset! game-state {:board-spaces board
                        :players init-players
                        :current-player 0
                        :draw-pile draw-pile
                        :discard-pile []}))


  #_(let [game-state (assoc {}
                     :players
                     (vec (for [player players]
                            (initialize-player player))))]
    (assoc game-state :current-player 0)))

With that done, I moved forward with implementing the actions a player can take on a turn, beginning with playing a card.  While beginning this code, I realized that I'd missed a case for drawing cards: what happens when there aren't enough cards in the draw pile?  Shuffle the discard pile back into the draw pile.  I quickly added a couple of tests (draw has no cards, draw has cards but not enough to cover the entire need) and the code to make the tests pass.

Then, something unfortunate happened.  I had this notion that perhaps the player portion of the game state would be better managed by color instead of by name.  I spend several hours refactoring the code to try to support that notion.  At the end of that effort, I still had a couple of broken tests, but the worst part was that when I looked at the code, it was LESS consumable than the prior version.  I reverted that work (thank you for making that painless, git!).

Let this serve as a lesson for you, kids.  When you're doing TDD and the tests don't lead you toward a design (meaning, you have a notion about a design that hasn't really emerged), don't change the design.  Wait until the test reveals the need for the design change, THEN do the necessary refactoring.

However, this surfaced a larger issue: I wasn't convinced that the way I was managing state was good enough.  I decided to fill out a sample game state map and get some feedback on it.  I have the luxury of being married to a divine software engineer, and asked her for her opinion on it.  She looked at it and gave some small but critical feedback: the board should know which pieces are on which spaces; the player shouldn't care about anything other than its cards.

Let this serve as a second lesson for you, kids.  Asking for feedback or help is ALWAYS preferable to spinning your wheels.  It's ok to try to figure something out on your own.  In fact, you SHOULD try to figure it out on your own.  However, put a time box around the effort.  For me, if I work with something for more than four hours without making tangible progress, it's time to punt.  By that point, I've not only exhausted myself, but I'm pretty crotchety about not being able to figure it out.  Maybe I should cut my time box in half so that I don't get to the "unapproachable" point...

From there, I was able to move forward with implementing the playing of a card.  This actually turned into a small series of functions, each of which was covered by an independent set of unit tests.


(defn is-open-target?
  "Returns true if the space matches the icon and has fewer than three pirates"
  [space icon]
  (and (= icon (:icon space))
       (< (count (:pirates space)) 3)))

(defn open-space-index
  "Returns the index of the first open space for the given icon after the starting index."
  [starting-index board icon]
  (or
    (some #(let [space (get board %)]
            (when (is-open-target? space icon) %))
          (range (inc starting-index) (count board)))
    (dec (count board))))

(defn play-card
  "Discards the card and moves a single pirate from the space to the next available space."
  [player icon from-space board discard-pile]
  (let [[pre-cards post-cards] (split-with #(not= icon %) (:cards player))
        [pre-pirates post-pirates] (split-with #(not= (:color player) %) (:pirates from-space))
        space-index (.indexOf board from-space)
        next-open-space-index (open-space-index space-index board icon)
        next-open-space (get board next-open-space-index)]
    {:player {:cards (concat pre-cards (rest post-cards))}
     :board-spaces (assoc board space-index (assoc from-space :pirates (vec (flatten (concat pre-pirates (rest post-pirates)))))
                         next-open-space-index (assoc next-open-space :pirates (conj (:pirates next-open-space) (:color player))))
     :discard-pile (conj discard-pile icon)}))

Even though there are a couple of spots in the play-card function that should be scrutinized further, I feel much better about this design.  I don't have any justification for that feeling aside from the fact that it emerged from the needs driven by the tests and it appears to actually work.  :)

That's it on progress for the week.  The good news is that I continue to work on it a little bit every day, and enjoy doing so (generally).  I'll try to sneak in a non-dev blog post sometime this week.  Since it's Christmas week, I don't think it'll be a problem from the content perspective...

Sunday, December 13, 2015

Cartagena, part 1


WARNING: this entry is technical.  I suspect several of the next few in the weeks to come will be.  I'll label them with Cartagena (with parts).

Impetus

Several weeks ago, Tanya and I attended clojure/conj, an industry conference for functional developers using the clojure language.  It was a great experience on several levels.  First of all, it was my first trip to Philadelphia, where the weather was fantastic and the cheesesteaks were... actually not as good as I'd hoped.  We saw several former coworkers as well as a lot of really interesting discussions about clojure and how it's being used to make the planet a better place.  Or, at least, how it's being used.  :-)

One of the presentations we saw was a juxtaposition of object oriented design and functional design.  If you're really interested, you can have a look at API First vs Data First design. I'll skip over the first part of the presentation, as he engages in a bit of reductio ad absurdum.  The second part, however, he talks about data first design, and he uses an implementation of the game Cartagena as his example.  It prompted me to at least consider doing my own implementation, except as a TDD exercise.

Full disclosure: my brilliant coworker Timothy Pratley is also working on an implementation.  His is going to be prettier, I have no doubt.  It'll be full of figwheel and buttons and menus and all sorts of nice things.  Mine's going to be a plain ol' TDD, starting with the engine and *maybe* working up to a nicer UI at some point... :-)

Starting

First things first: I've never played the game, so I needed to find a set of rules and pictures for references.  Luckily, the game is popular enough to have the instructions/rules listed on a handy-dandy webpage.  I read through them briefly to make sure they were complete.

Next, I created a new clojure project.  Luckily, accomplishing this in clojure is ridiculously busy.

$ lein new app cartagena

Having an application locally doesn't do anyone any good if my machine gets abducted by aliens.  Why aliens would abduct it is beyond me.  I am, after all, only a human.  To avoid the consequences of that potential catastrophe, I need to get the project under source control.  I do that by using git and github.  The commands are really simple.
$ cd cartagena
$ git init

I am also using a tool called scm_breeze locally to help manage git repositories.  As such, adding the files to the local code repository I just initialized is a *breeze*.  I hope you see what I did there...


$ gca

After this, a copy of my project is in a local source code/version control repository.  I still need to get it onto the Internet so that I can destroy my computer if need be.  To do this, I need to create a repository on the Internet and then synchronize it with my local repository.  I use github to accomplish this.  I won't go through the details here, aside from saying that you have to have a github account, and you have to press the "New Repository" button.  I name my remote repository (the one on the Internet, in github) the same as my local to avoid confusion.

All that's left to do is tell my local repository about my remote repository.  Just a couple more commands to enter.

$ git remote add origin git@github.com:rusty-software/cartagena.git
$ git push -u origin master

Github actually tells you about these commands, so you don't even need to figure them out yourself.  I love it when tools help me look smart!

First Content and Tests

Now that I've got a remote repository, and no longer have to live with the constant fear of catastrophic failure of my machine, I can settle down enough to get to implementation work.  The first real changes I made to the code base have to do with the README file.  I want to document at least the basic instructions and rules locally just in case the webpage I'm using as a reference is unexpectedly removed.  

I spend a few minutes reviewing the source webpage and realize that it refers to the board configuration, but it feels like there might be specific board constructs that aren't covered.  I spend a few more minutes looking for pictures of the playing pieces and find a few.  From those pictures, I figure out that the board is made up of six pieces with specific configurations of the iconography.  I make note of this in the README and move on to documenting the rest of the basic rules.  I read over the full set, then commit the changed content to the local repository and push those changes to the remote repository.

I'm finally ready to start coding.  The first thing I want to do is initialize a board.  I implement a test to ascertain when I've done this successfully.

(deftest initialize-board-test
  (let [board (initialize-board)]
    (is (= 36 (count board)))
    (doseq [icon [:bottle :gun :hat :key :knife :skull]]
      (is (= 6 (count (filter #(= icon %) board)))))))

Running this test fails.  In fact, since I haven't defined the calling function, it fails pretty miserably.  I stub out the function and have a legitimate failure on my hands.  It doesn't do at all what I want it to do, and that's perfect.  I have a RED test!  Let's make it green!

Notice that I have two basic assertions here.  The first is that I think the board should have 36 spaces.  The second is that each icon should be represented 6 times.  To that end, I begin to codify what I know the board segments are.

(def card1 [:bottle :gun :hat :skull :knife :key])
(def card2 [:knife :bottle :key :gun :hat :skull])
(def card3 [:hat :key :gun :bottle :skull :knife])
(def card4 [:key :bottle :skull :knife :hat :gun])
(def card5 [:gun :key :knife :hat :skull :bottle])
(def card6 [:hat :knife :key :bottle :gun :skull])
(def card1r (vec (reverse card1)))
(def card2r (vec (reverse card2)))
(def card3r (vec (reverse card3)))
(def card4r (vec (reverse card4)))
(def card5r (vec (reverse card5)))
(def card6r (vec (reverse card6)))
(def all-cards [card1 card2 card3 card4 card5 card6
                card1r card2r card3r card4r card5r card6r])

I've named the vars "cards for now.  This is probably a bad idea, as cards are other things in the game.  However, it's good enough for this first test to pass, and I just want to get something accomplished.

The next thing to do is implement the initialize board function.  Given that I have a collection of all of the possible cards, getting a random sample of them is simplicity in itself.

(defn initialize-board
  "Returns a vector populated with icons from the 6 of the board pieces concatenated."
  []
  (->> all-cards
       shuffle
       (take 6)
       flatten
       vec))

I'm using the thread-last operator here.  My initial implementation consisted of the function calls embedded within each other.  This seems slightly clearer to me.

Given this implementation, I re-run the test, and now they pass!  Yay!  This is a good commit point, so I commit my changes and push them to the remote repo as well.

I realize that there is another test to write.  I want to ensure that boards are varying as they are instantiated.  One more test should do the trick.

(deftest initialize-board-test
  (testing "Returns the right number of spaces as well as icons"
    (let [board (initialize-board)]
      (is (= 36 (count board)))
      (doseq [icon [:bottle :gun :hat :key :knife :skull]]
        (is (= 6 (count (filter #(= icon %) board)))))))
  (testing "Boards are not exactly alike"
    (is (not (= (initialize-board) (initialize-board))))))

As you can see, I extended my original test, adding some testing description.  Now I've got a test to make sure the boards are different.  Running the test, everything passes as expected.

There are two important points here.  The first is that I make small, incremental steps toward a larger solution.  The second is that I have now ensured that this rules are enforced on a going-forward basis.  These are good practices, as if something breaks I can be fairly certain the scope of the breakage is small and if someone else does something to break my rules, they know about it.

I stopped here for the day, realizing that there are two immediate changes I'll be making next time -- the aforementioned card var name, and the board itself is too simplistic.  We'll need to store state about which pieces are on which spots.  However, that didn't happen in THIS set of rules, so we delayed implementing that complexity until the simpler things were done.

More on this as it develops.  In the meantime, I hope you'll let me know if you have questions or comments.  I like feedback!  :-D


Saturday, December 5, 2015

Oops!


Oops, there I go again... not posting for a month or more...

I mean, uh, I MEANT to do that!  Yeah, that's it!  That's the ticket!  I didn't post ON PURPOSE so that life would have a chance to catch up with me.

A lot has happened in the last six weeks or so -- some good, some bad.  The bad is on a much larger scale than my day-to-day life, so you don't need to worry about me personally.  The good has been very and inexorably good.  Things like:


  • My youngest son turned 15 years old.  Wow.
  • Both of my sons made it through the classroom portion of drivers education.  One of them procured a learner's permit, and the other will have it once his documentation has been processed.  Double-wow.
  • Tanya and I went to Philadelphia for a professional conference, learned a lot, and ran into a bunch of friends and former coworkers.
  • Thanksgiving, and all that entails.
  • beasanta.org raised over $20,000 for the kids of Penny Lane.  If you don't know what this is, you should check it out.  :-)


This is also the time of year when I'm most reminded that I have control of absolutely nothing in my life.  OK, that's not entirely true.  I have at least perceived control over little things, but I find myself struggling with things that were easier in the spring and summer.  If you're thinking "he's talking about his belly (or exercise in general), isn't he?", then you are as astute as post-Thanksgiving pumpkin-pie-reduced mental faculties allow for.  I find that, despite my "best" efforts, losing weight and/or staying in shape in the winter is hard.  I suppose there's a large set of biological tendencies working against me, but dammit! -- I'm an evolved human, capable of conscious thought and control of my impulses, aren't I?

Willpower is lower for me in the winter.  Maybe that's another reason blog posts are harder to come by... ;-)  I'll try to do better, gentle reader.  I'll try.

Right after I make these cookies.