James Williams's complete blog can be found at: http://jameswilliams.be/blog
2011-10-02 02:00:00.0
Groovy's SimpleTemplateEngine provides a very simple way to create dynamic HTML from templates. It is lightweight enough that it is compatible with almost any templating engine. I started playing around with it because I wanted to find a way to create some dynamic client code and wanted something more expressive and compact than regular HTML but not as complex as a JSP. Using Markdown and SimpleTemplateEngine together allowed me to hit that sweet spot.
Overview of Markdown
Markdown was created by John Gruber (of Daring Fireball fame) and Aaron Schwartz to allow one to easily translate a shorthand written in text files to HTML. It uses a limited set of special characters to designate headings, listings, figures, links and code blocks. It's the type of system that allows the formatting of an article to get out of your way and not disrupt your flow. Below is a sample Markdown file.
A First Level Header
====================
This is just a regular paragraph.
<% num.times { %>;
<%= it %>. <%= it+1 %> squared = <%= (it+1)*(it+1) %>
<% } %>
The following are code blocks and will print as is:
`<br> means break
<hr> means horizontal rule
`
In addition to parsers/translators for almost every programming language imaginable, Markdown is one of the formats that PanDoc can convert to a number of other formats including DocBook and PDF. For this post, I used MarkdownJ.
Using Markdown in A Route
Ratpack receives a request for an endpoint which retrieves the Markdown template, injects the given params, and evaluates the Groovy code contained in it. The resulting text is passed to MarkdownJ where it is converted into HTML and pushed into the response. The code snippet below shows how it all works.
import com.petebevin.markdown.*
//... truncated code
get("/") {
def m = new MarkdownProcessor()
def p = render 'template.md', [num:4]
m.markdown(p)
}
Links
2011-09-20 02:00:00.0
One thing that I had always wanted to do is to redirect from my domain to my Google+ profile; to have http://jameswilliams.be/+ redirect to Google+. Some folks have accomplished this by modifying their .htaccess file in Apache. I wanted to try to come up with a solution using Ratpack.
get("/\\+") {
response.sendRedirect("http://profiles.google.com/105400736676917752271/posts")
}
Once I figured out how to use "+" in an endpoint(which requires escaping as listed above), it was really simple to get working: use the injected response object to send a 302 redirect to my profile. Easy peasy.
2011-08-26 21:00:00.0
When I first received a CR-48 back in December of last year, I mused on how it would be cool to be able to create Chrome Web Apps solely using a ChromeBook. Like the iPad before it, the ChromeBook was largely conceived as a device to consume content (save GDocs and such). More and more, Chrome web apps are changing that. In the last post, I noted some of the apps one could use as a developer to do dev work. In this post, I'll be taking it a bit further in using a stock ChromeBook to create a Chrome app.
Chrome OS's file manager gives you some limited copy, paste, and rename functions and the ability to create folders. Chrome OS doesn't allow you to create or edit individual files from the File Manager so we have to use HTML and JavaScript. This is not without reason. Even though the files in your Downloads directory can persist for a long time, Chrome can within reason purge those files if space is needed.
A Chrome web app( or extension or theme) is fairly simple to construct. It consists of a JSON file that describes metadata about the application, some HTML files, and some icon images. Chrome allows you to run a app or extension by pointing your browser to the directory holding your app. You can even package an application for deployment outside the Web Store as a crx file. To deploy an app to the Chrome Web Store, you need to upload a zip file of your application. Though beta and dev channel builds of Chrome OS give you the capability to extract zip files, they don't currently let you create zip files. Coupled with the limited file creation/editing options, we have to push those tasks to Cloud9.
Below is some inline CoffeeScript that I used to create some HTML text, store the base64 representations of a couple of icon images and create a JSON object. CoffeeScript has implicity typing and commas for a object that is properly indented and places one key-value pair on each line. To create the zip archive, I found a small JavaScript library called JSZip.
htmlText = "<html><head></head><body>sample extension html</body></html>"
a =
name: 'Sample Extension'
description: 'Created totally on Chrome OS'
version: '3'
icons:
"128": "HTML5_Badge_128.png"
"16": "HTML5_Badge_16.png"
app:
launch:
"local_path": "index.html"
img1 = "..." # truncated base64 text
img2 = "..." # truncated base64 text
Now that the assets have been created, we have to package them up into a zip file. JSZip can create either text or binary file entries using an optional map of attributes for the data. base64 and binary are the most commonly used. In the snippet below, I added the entries one by one to the zip file and used the location.href property to force the browser to download the generated archive. You're now set to upload it to the Chrome Web Store.
zip = new JSZip()
zip.add("manifest.json", JSON.stringify(a))
zip.add("index.html", htmlText)
zip.add("HTML5_Badge_128.png", img1, {base64: true, binary:true})
zip.add("HTML5_Badge_16.png", img2, {base64: true, binary:true})
content = zip.generate();
location.href="data:application/zip;base64,"+content;
I love Cloud9 but this is definitely a hack. A useful hack but a hack all the same. If I were planning to use this tactic for a productive solution, I would investigate the FileSystem APIs as they allow you to interact with the file system in a more natural way. It gives you a means to list files, create subdirectories, and append to files in a way that a regular browser does not. Adding and encoding each required image individually becomes unwieldy real fast.
It would be awesome to have an option to install a zipped Chrome app locally along the other Developer options. Just as AppInventor opens up the possibility for novices to create applications, I'm hoping that the community creates more means for people using ChromeBooks to be creators and not just consumers.
2011-08-24 16:30:00.0
+Louis Gray has blogged pretty prolifically about the ChromeBook as a day-to-day machine. AFAIK I he doesn't use it to development work so I thought I'd chime in on that respect. Despite not being about to run typical GNU apps and version control in secure mode, the machine is still very capable. Like Louis I have a MacBook Air as well. One reason I decided to give the ChromeBook a try was to avoid the fire crotch caused by doing anything CPU-intensive on the Air. I've gotten in the habit of coding while on Google+ Hangouts so reduced fertility as a side-effect won't do. ;)
You could flip the dev switch and install Ubuntu as I have on my CR-48 but this post will talk about apps that you can use on an unmodified ChromeBook.
Cloud9 (requires Internet connection)
Cloud9 IDEis my go-to tool for doing JavaScript, CoffeeScript, and Node.js development. For the past few weeks, I have been using it for 95% of my dev work on book examples, the HTML5 Video Poker game included. Cloud9 allows you to interact with either Github or Bitbucket(Mercurial) repositories. For a simple JS application, you can use the preview function to quickly test your apps. Reportedly the Cloud9 guys will have offline support soon. The jury is out on whether this will include ChromeBook and offline checkin support.
SourceKit (requires Internet connection)
For creating text-based files that aren't tied to a repository, you can use SourceKit. SourceKit syncs to Dropbox but is unusable while offline.
Quick Note(offline cache, online sync)
Quick Note is a note taking app that allows you to sync to Diigo when online (using your Google credentials) but also maintains an offline cache of the notes you have created. I'm writing this post in Quick Note right now.
Scribble (offline cache, online sync)
Scribble is another note taking app with offline support. The only difference between it and Quick Note is the location of the online sync and the UI. Otherwise it's a matter of personal preference. I often switch between the two depending on my mood.
Amazon Cloud Reader (offline book pinning)
For reference books that I might need to check while working on samples, I use Amazon Cloud Reader. Using it requires a little pre-thought because you have to download the books that you want to read offline in advance. Luckily, the books download fairly quickly so that even if you are running late, you could probably download whatever books you needed in under a couple minutes.
WriteSpace (offline cache)
WriteSpace is a minimalist text editor that is modeled after the OS X application WriteRoom. You are given an empty window that is essentially a giant text area. At the lower edge of the screen, it shows the number of words, lines, and characters in the document. The application gives you a couple options to change the background and text colors along with some editor settings. WriteSpace doesn't have multiple document support and stores its data using localStorage. In my opinion, it's good for writing longish documents, that don't require images or formatted text. Absent from WriteSpace is the built-in support to export documents (though it can import text fles). Hopefully as the FileSystem Chrome APIs become more codified, it will emerge as a feature. FWIW, neither Quick Note nor Scribble support exporting to a file. For now, the solution with any of the three is to wait for online access and pasting into a Google Doc.
While I would certainly take pause if going somewhere without reliable Internet access for at least 70% of a trip, contrary to what has been reported, the ChromeBook doesn't become a brick without an Internet connection. Using the ChromeBook instead of my Air has actually been freeing because I no longer have to worry about what would happen if it got lost or stolen. Sure it would be a sucky couple of days but otherwise, it wouldn't be a big issue. Though updates aren't that often, if you have files that you need to guarantee persistence, save them to a USB stick and avoid saving to the HDD in the machine(especially if you aren't the owner user on the machine).
2011-08-14 02:00:00.0
In this post, we will continue our examination of Raphaël in creating a video poker game. As we learned in the previous post, SVG lets you easily keep track of objects in the DOM and have them respond to events. Coupled with the fact that besides the cards, most of the objects in the game don't change very much and the few number of objects makes this game perfect for Raphaël/SVG.
Video Poker Basics
Video Poker is a variant of poker typically played on touchscreen devices in casinos. Before the hand begins the player makes a bet. Five cards are then drawn from the deck. Any or none of the cards can be selected to be held through the next draw. The cards that were not held are discarded and new cards are drawn. At that point, the hand is evaluated for a win. The list below shows the winning hands from strongest to weakest.
- Royal Flush - 10, J, Q, K, A of the same suit
- Straight Flush - five cards of the same suit in numeric order
- Four of a Kind
- Full House - A three of a kind and a pair
- Flush - five cards of the same suit
- Straight - cards of any suit in numeric order
- Three of a Kind
- Two Pair
- Pair - generally Jacks or Higher
Creating a Deck and Cards
The Deck and Card classes are both classes that were written for a Concentration game in the book that have been tweaked to work better with Poker. The Deck class is essentially a array with some extra functions to facilitate shuffling, dealing, and instantiating cards. The Card class contains two images, one each for the front and back of the card with some metadata to help in evaluating poker hands. The images for the cards were generated from data included in the SVG-cards project. While the raw SVG paths could have been used instead, dealing with images is a touch bit more concise. The code block below shows the function for shuffling a deck. The current shuffling algorithm guarantees that each card will be swapped. The number of swaps will grow with the number of decks. Luckily, swaps are cheap and most games will use 5 or less decks and only re-shuffle when deck runs out.
self.shuffleDecks = function () {
var swap = function(i,j) {
var temp = self.cards[j];
self.cards[j] = self.cards[i];
self.cards[i] = temp;
}
for(var j = 0; j<numDecks; j++) {
for(var i = (numDecks * 51); i>=0; i--) {
var r = self.rand(i);
swap(i,r);
}
}
To counter card-counting in games like BlackJack, some casinos employ continuous shuffling machines. Card-counting is not an issue in Poker but if modeling a BlackJack game, it's one thing you might consider.
Evaluating Poker Hands
Having already created Decks, Cards, and Buttons for other applications, most of the new work for this game centered around finding an algorithm to evaluate poker hands. It's a bit of a stumper for a while until you realize that a Hand is just an array of Cards. Evaluating them is easy as long as you have good tools to do array manipulation.
I chose underscore.js which is a utility kit for interacting with arrays, maps, functions, and objects. What JQuery does for the DOM, underscore.js does for everything else. As its name implies, all of its functions attach to the _ namespace. There are four functions that are of particular use to us: compact, groupBy, max, and pluck. compact returns an array with all the "falsy" values removed(NaN, 0, false, undefined, ''). groupBy returns an object grouping the values as indicated in a function parameter. The snippet below shows a groupBy applied to a pseudo-hand.
_.groupBy( ["A", "3", "5", "5", "5"], (num) -> num);
Returns => {A:["A"], 3:[3],5:[5,5,5]}
Our conditions require us to consider both the ordinal value of the cards and the suit. Listed below are functions to make the groupBy operate based on the ordinal or suit value.
ordinalHandler: (card) ->
return card.val
suitHandler: (card) ->
return card.suit
We next need a way to check the length of individual keys in the object groupBy returns. findLength walks the keys of an object and attempts to find a key with the indicated length. The presence of a match means there is a pair, three of a kind, or four of a kind present in the hand. The last line of the function uses compact to remove all the misses. Just below it, we can see how the those functions combine to find a pair.
findLength: (grouped, value) ->
x = []
for key of grouped
if grouped[key].length is value
x.push(key)
_.compact(x)
checkPair: (hand) ->
sorted = _.groupBy hand.cards, @ordinalHandler
pair = @findLength(sorted, 2)
return pair
Finding a Straight hand is easy thanks to the pluck function. It returns an array of values that correspond to the given property. In this case, it is Card.val. After sorting the array, we evaluate each value to see if it corresponds to the expected value in the range. We can see the code for this in the snippet below.
checkStraight: (hand) ->
vals = _.pluck(hand.cards, "val")
vals.sort()
startValue = vals[0]
for i in [0...5]
return false if startValue+i isnt vals[i]
return "Straight" if vals is [1, 10, 11, 12, 13]
return "Straight"
checkFlush: (hand) ->
sorted = _.groupBy hand.cards, @suitHandler
flush = @findLength(sorted, 5)
if flush.length isnt 0
royal = @checkRoyalFlush(hand)
straightFlush = @checkStraightFlush(hand)
return royal if royal
return straightFlush if straightFlush
return "Flush"
Because there are only seven hand variants to check, I went with a mode of attack that checks them all. All the returned values get popped into an array and that array is walked to generate a new array with the winnings for each possible hand(shown in basePayouts). compact is used to remove the values that don't result in a win. From those values, the maximum is found. This tactic allows the proper payout to be calculated even though for instance, a Royal Flush is a Flush and a Straight Flush as well.
evaluate: (hand) ->
currentValue = 0
a = @checkFlush(hand)
b = @checkFourKind(hand)
c = @checkFullHouse(hand)
d = @checkThreeKind(hand)
e = @checkStraight(hand)
f = @checkTwoPair(hand)
g = @checkJacksOrBetter(hand)
values = (@basePayouts[val] for val in [a,b,c,d,e,f,g])
values = _.compact(values)
_.max(values)
@basePayouts = {
JacksOrBetter: 1
TwoPair: 2
ThreeKind: 3
Straight: 4
Flush: 6
FullHouse: 9
FourKind: 25
StraightFlush: 50
RoyalFlush: 250
}