Skip to content

Instantly share code, notes, and snippets.

@sbenhaim
Created August 24, 2010 02:00
Show Gist options
  • Save sbenhaim/546755 to your computer and use it in GitHub Desktop.
Save sbenhaim/546755 to your computer and use it in GitHub Desktop.
(ns bike-bingo
(:require
;; We'll use cl-format to draw the board.
[clojure.contrib.pprint :as pp]))
;; Here's our 5x5 bingo board. We're counting horizontal, vertical,
;; diagonal, four corners, and blackout. The numbers in each square
;; represent how many bingos that square is involved in (excluding
;; blackout).
;;
;; It's been predetermined that the middle square, [2 2], will be "Ride
;; more than 1 mile", so I'm leaving it out.
;;
;; | 0 | 1 | 2 | 3 | 4 |
;; ---+---+---+---+---+---|
;; 0 | 4 | 2 | 2 | 2 | 4 |
;; ---+---+---+---+---+---|
;; 1 | 2 | 3 | 2 | 3 | 2 |
;; ---+---+---+---+---+---|
;; 2 | 2 | 2 | x | 2 | 2 |
;; ---+---+---+---+---+---|
;; 3 | 2 | 3 | 2 | 3 | 2 |
;; ---+---+---+---+---+---|
;; 4 | 4 | 2 | 2 | 2 | 4 |
;; +---+---+---+---+---|
;;
;; Given the illustration above, we're going to fill the most valuable
;; slots first using the most difficult tiles. Here's our board
;; filling order:
(def *board-priority*
[[0 0] [0 4] [4 0] [4 4]
[1 1] [1 3] [3 1] [3 3]
[0 1] [0 2] [0 3] [1 0]
[1 2] [1 4] [2 0] [2 1]
[2 3] [2 4] [3 0] [3 2]
[3 4] [4 1] [4 2] [4 3]])
;; To determine which tiles are the hardest, we'll take a look at how
;; many people earned them. Not perfect, but it'll do. Here's a map of
;; tiles to times completed.
(def *freq-map*
{"Ride more than 10 miles" 7
"Ride more than 20 miles" 3
"Ride more than 30 miles" 3
"Ride more than 40 miles" 2
"Ride more than 50 miles" 4
"Ride more than 100 miles" 1
"Ride between 12:00AM and 5:00AM" 2
"Ride in inclement weather" 5
"Get yelled at by a stranger" 5
"Hit 30mph (downhill OK)" 8
"Ride to work every day one week" 5
"Go mountain biking" 2
"Bike home from the bar" 5
"Bike up Summit (Ramsey) hill" 1
"Bike on the Greenway" 5
"Ride the Minneapolis Grand Rounds" 2
"Ride from DT Minneapolis to DT St. Paul or vice versa" 1
"Ride outside the 494-694 loop" 5
"Follow all traffic laws (yes, even stop signs)" 8
"Get a flat tire" 5
"Change a flat tire" 3
"Get honked at by a stranger" 5
"Bike in a group of 5 or more people" 4
"Pass someone on a Segway" 2
"Pass someone on a tall bike" 2
"Participate in a public bike event" 4
"Watch a public bike event" 1
"Track stand for 30 seconds" 2})
;; Here's a simple HTML board template with some `cl-format'
;; directives in it. `~{' and `~}' start and end loops, `~a' is
;; replaced with some value. (Todo: It might be interesting to use
;; Enlive for this instead (http://github.com/cgrand/enlive).)
(def *template*
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">
<head>
<style type=\"text/css\" media=\"screen\">
td {
border: 2px solid black;
padding: 10px;
width: 150px;
height: 150px;
text-align: center;
font-weight: bold;
}
</style>
</head>
<body>
<table>
~{<tr>
~{<td>~a</td>
~}
</tr>
~}
</table>
</body>
</html>
")
;; Each tile will be selected at random, but I'd really like to give
;; the hardest tiles a good chance to hit the most valuable
;; squares. This function will transform their weight into the number
;; of entries they'll be getting in the hat.
;;
;; If someone has a more mathematically clever solution for this
;; transformation, I'm all ears.
(defn weight-transform [x]
(* x x x))
;; Weight is inversely proportional to frequency from our freq-map, so
;; we subtract the times seen from 1 + most frequent to determine
;; it.
;;
;; Though, would it make more sense to make this an inverse of the
;; actually frequency divided by the number of people in the
;; contest (greatest possible frequency)?
(defn freq-to-weight [freq-map]
(let [biggest (inc (apply max (vals freq-map)))]
(apply hash-map (flatten
(for [pair freq-map] (list (pair 0) (- biggest (pair 1))))))))
;; Given the weight map and our transforming function, fill the hat
;; with an appropriate number of entries for each tile.
(defn fill-hat [weight-map weight-transform]
(flatten (for [pair weight-map] (repeat (weight-transform (pair 1)) (pair 0)))))
;; Now we build up our board in order of priority.
(defn build-board [hat priority]
(loop [board {} hat hat priority priority]
;; Once we've filled all our tiles from the priority list, we'll
;; place "Bike more than 1 mile" at the center.
(if (= (count priority) 0)
(assoc board [2 2] "Bike more than 1 mile")
;; Pull a random item from the hat. More heavily rated items
;; have more entries in the hat and are more likely to be drawn.
(let [value (nth hat (rand-int (count hat)))]
(recur
;; Place the tile on the board.
(assoc board (first priority) value)
;; Remove all entries for that tile from the hat. (Good thing
;; this isn't a real hat.)
(remove #(= % value) hat)
;; Continue with the remaining priority list.
(rest priority))))))
;; `cl-format' will really like our board if it's nested vectors. Hash
;; table, not so much.
(defn board-to-nested-vector [board]
(vec (for [row (range 5)]
(vec (for [col (range 5)]
(board [row col]))))))
;; A simple bit of business
(defn print-board [board template]
(pp/cl-format true template (board-to-nested-vector board)))
;; And finally, we do the work.
(-> *freq-map*
(freq-to-weight)
(fill-hat weight-transform)
(build-board *board-priority*)
(print-board *template*))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment