I saw this tuto­r­ial the other day, it’s cool but I fan­cied tak­ing it a step fur­ther and make it use­ful as an app rather than just a fancy way to show some struc­tured data on the screen. Here’s a list of the fea­tures which I wanted to add:

  • being able to edit the title and con­tent of the notes
  • being able to save all your notes
  • being able to add new notes
  • being able to delete notes you no longer want
  • ran­dom­ize the colour of the notes more

HTML and CSS changes

I ini­tially con­sid­ered using the new con­tente­d­itable attribute in HTML5 to make the title and con­tent editable, but set­tled on using textarea instead as it offers bet­ter sup­port for mul­ti­ple line text and you can eas­ily add place­holder text as well as set a limit on the length of the title.

I added two images as but­tons to add a new note and save the state of all the notes and put a a bor­der around the notes to make them stand out a lit­tle more.

Here’s the updated HTML:

   1: <body>

   2:     <div id="controls">

   3:         <img id="btnNew" src="images/newnote.png"/>

   4:         <img id="btnSave" src="images/save.png"/>

   5:     </div>

   6:

   7:     <ul id="notes">

   8:     </ul>

   9: </body>

And the CSS to go along with it:

   1: * {

   2:     maring: 0;

   3:     padding: 0;

   4: }

   5:

   6: .hide {

   7:     display: none;

   8: }

   9:

  10: body {

  11:     font-size: 100%;

  12:     margin: 15px;

  13:     background: #666;

  14:     color: #fff;

  15: }

  16:

  17: #controls img {

  18:     width: 28px;

  19:     height: 30px;

  20:     background: rgba(0, 0, 0, 0);

  21: }

  22:

  23: #controls img:hover, #controls img:focus {

  24:     -webkit-transform: scale(1.2);

  25:       -moz-transform: scale(1.2);

  26:       -o-transform: scale(1.2);

  27:       position:relative;

  28: }

  29:

  30: ul, li {

  31:     list-style: none;

  32: }

  33:

  34: ul {

  35:     overflow: hidden;

  36:     padding: 15px;

  37: }

  38:

  39: ul li {

  40:     margin: 15px;

  41:     float: left;

  42:     position: relative;

  43: }

  44:

  45: ul li div {

  46:     text-decoration: none;

  47:     color: #000;

  48:     background: #ffc;

  49:     display: block;

  50:     height: 150px;

  51:     width: 150px;

  52:     padding: 10px;

  53:     -moz-box-shadow: 5px 5px 7px rgba(33, 33, 33, 1);

  54:     -webkit-box-shadow: 5px 5px 7px  rgba(33, 33, 33, .7);

  55:     box-shadow: 5px 5px 7px rgba(33, 33, 33, .7);

  56:     -moz-transition: -moz-transform .15s linear;

  57:     -o-transition: -o-transform .15s linear;

  58:     -webkit-transition: -webkit-transform .15s linear;

  59:     border-style: solid;

  60: }

  61:

  62: ul li div img {

  63:     padding: 1px 3px;

  64:     margin: 10px;

  65:     position: absolute;

  66:     top: 0;

  67:     right: 0;

  68: }

  69:

  70: ul li textarea {

  71:     font-family: 'Chewy', arial, sans-serif;

  72:     background: rgba(0, 0, 0, 0); /* transparent background */

  73:     resize: none;

  74:     padding: 3px;

  75:     border-style: none;

  76: }

  77:

  78: .note-title {

  79:     font-size: 140%;

  80:     font-weight: bold;

  81:     height: 30px;

  82:     width: 70%;

  83: }

  84:

  85: .note-content {

  86:     font-size:120%;

  87:     height: 100px;

  88:     width: 95%;

  89: }

  90:

  91: ul li:nth-child(even) div {

  92:   -o-transform:rotate(4deg);

  93:   -webkit-transform:rotate(4deg);

  94:   -moz-transform:rotate(4deg);

  95:   position:relative;

  96:   top:5px;

  97: }

  98:

  99: ul li:nth-child(3n) div {

 100:   -o-transform:rotate(-3deg);

 101:   -webkit-transform:rotate(-3deg);

 102:   -moz-transform:rotate(-3deg);

 103:   position:relative;

 104:   top:-5px;

 105: }

 106:

 107: ul li:nth-child(5n) div {

 108:   -o-transform:rotate(5deg);

 109:   -webkit-transform:rotate(5deg);

 110:   -moz-transform:rotate(5deg);

 111:   position:relative;

 112:   top:-10px;

 113: }

 114:

 115: ul li div:hover, ul li div:focus {

 116:   -moz-box-shadow:10px 10px 7px rgba(0,0,0,.7);

 117:   -webkit-box-shadow: 10px 10px 7px rgba(0,0,0,.7);

 118:   box-shadow:10px 10px 7px rgba(0,0,0,.7);

 119:   -webkit-transform: scale(1.25);

 120:   -moz-transform: scale(1.25);

 121:   -o-transform: scale(1.25);

 122:   position:relative;

 123:   z-index:5;

 124: }

 125:

 126: /* define 3 different colours for the notes */

 127: ul li div.colour1 {

 128:     background: #ffc;

 129: }

 130: ul li div.colour2 {

 131:     background: #cfc;

 132: }

 133: ul li div.colour3 {

 134:     background: #ccf;

 135: }

There’s a cou­ple of things to note here:

  • the textarea ele­ment has a trans­par­ent background
  • the textarea ele­ment is not resizable
  • the textarea ele­ment has a place­holder text

Here’s what the page looks like now:

image

Adding inter­ac­tions with Javascript

Adding a new note

In the CSS above, you can see that I had defined three CSS class:

   1: /* define 3 different colours for the notes */

   2: ul li div.colour1 {

   3:     background: #ffc;

   4: }

   5: ul li div.colour2 {

   6:     background: #cfc;

   7: }

   8: ul li div.colour3 {

   9:     background: #ccf;

  10: }

when we cre­ate a new note, all we need to do to ran­dom­ize its colour is to add one of these classes to the ele­ment randomly.

Here’s the event han­dler for the ‘new’ image button:

   1: $(document).ready(function() {

   2:     notes = $("#notes"); // get references to the 'notes' list

   3:     ...

   4:     // clicking the 'New Note' button adds a new note to the list

   5:     $("#btnNew").click(function() {

   6:         addNewNote();

   7:     });

   8:     ...

   9: });

  10:

  11: //  adds a new note to the 'notes' list

  12: function addNewNote(class, title, content) {

  13:     // if class is not specified, use a random colour class

  14:     if (!class) {

  15:         class = "colour" + Math.ceil(Math.random() * 3);

  16:     }

  17:

  18:     // add a new note to the end of the list

  19:     notes.append("<li><div class='" + class + "'>" +

  20:                  "<textarea class='note-title' placeholder='Untitled' maxlength='10'/>" +

  21:                  "<textarea class='note-content' placeholder='Your content here'/>" +

  22:                  "<img class='hide' src='images/close.png'/>" +

  23:                  "</div></li>");

  24:

  25:     // get the new note that's just been added and attach the click event handler to its close button

  26:     var newNote = notes.find("li:last");

  27:     newNote.find("img").click(function() {

  28:         newNote.remove();

  29:     });

  30:

  31:     // hook up event handlers to show/hide close button as appropriate

  32:     addNoteEvent(newNote);

  33:

  34:     // if a title is provided then set the title of the new note

  35:     if (title) {

  36:         // get the title textarea element and set its value

  37:         newNote.find("textarea.note-title").val(title);

  38:     }

  39:

  40:     // if a content is provided then set the content of the new note

  41:     if (content) {

  42:         // get the content textarea element and set its value

  43:         newNote.find("textarea.note-content").val(content);

  44:     }

  45: }

  46:

  47: function addNoteEvent(noteElement) {

  48:     noteElement.focus(function () {

  49:         $(this).find(".img").removeClass("hide");

  50:     }).hover(function() {

  51:         $(this).find("img").removeClass("hide");

  52:     }, function () {

  53:         $(this).find("img").addClass("hide");

  54:     });

  55: }

as you can see, this event han­dler calls the addNewNote func­tion which is respon­si­ble for adding a new note to the notes unordered list and sets its class, title or con­tent depend­ing on the argu­ments. In this par­tic­u­lar usage it’ll pick one of the three colour classes ran­domly and add the class to the new note.

Once a note has been added, I then get a ref­er­ence to it and cre­ate an event han­dler for its close but­ton so that when its clicked it’ll remove this note from the DOM.

The addNo­teEvent func­tion is then charged with adding the focus and hover in/out event han­dlers to the note to show and hide the close but­ton so that it’s only shown when the note is focused/hovered on.

Sav­ing the notes

I have pre­vi­ously cover the use of HTML5’s local and ses­sion stor­age in this post already, what I’m doing here is very sim­i­lar but a lit­tle more involved.

When the ‘save’ but­ton is clicked, I need to inspect each of the notes and remem­ber the class, title text (remem­ber, you can’t get the text that’s been entered into the textarea by get­ting the HTML of the ele­ment) and con­tent text in a bespoke object, the result­ing array of objects is then JSON seri­al­ized using Dou­glas Crock­ford’s Json.js library. It this is JSON string that’s saved into the local storage.

On the other end, when the page has been loaded and the DOM event fired (i.e. when JQuery.ready fires) I will need to check if the JSON string is in the local stor­age, if it is, then dese­ri­al­ize the string back to an object array and add the notes back in in the same order:

   1: var notes;

   2: var count = 0;

   3:

   4: $(document).ready(function() {

   5:     notes = $("#notes"); // get references to the 'notes' list

   6:

   7:     // load notes from local storage if one's available

   8:     var storedNotes = localStorage.getItem("notes");

   9:     if (storedNotes)

  10:     {

  11:         // passes the stored json back into an array of note objects

  12:         var notesArray = JSON.parse(storedNotes);

  13:         count = notesArray.length;

  14:

  15:         for (var i = 0; i < count; i++) {

  16:             var storedNote = notesArray[i];

  17:             addNewNote(storedNote.Class, storedNote.Title, storedNote.Content);

  18:         }

  19:     }

  20:

  21:     ...

  22:

  23:     // clicking the 'Save' button saves the state of the notes board to the local storage                

  24:     $("#btnSave").click(function() {

  25:         var notesArray = new Array();

  26:

  27:         // for each of the notes add a bespoke note object to the array

  28:         notes.find("li > div").each(function (i, e) {

  29:             // save the class attribute of the div, as well as the text for the title and content text areas

  30:             var colourClass = $(e).attr("class");

  31:             var title = $(e).find("textarea.note-title");

  32:             var content = $(e).find("textarea.note-content");

  33:

  34:             notesArray.push({ Index: i, Title: title.val(), Content: content.val(), Class: colourClass });

  35:         });

  36:

  37:         // json encode it

  38:         var jsonStr = JSON.stringify(notesArray);

  39:

  40:         // and save the json string into local storage

  41:         localStorage.setItem("notes", jsonStr);

  42:

  43:         // info the user it's done

  44:         alert("Notes saved");

  45:     });

  46:

  47:     // add a note to the list if there aren't any

  48:     if (count == 0)

  49:     {

  50:         $("#btnNew").click();

  51:     }

  52: });

Demo

You can try out the full demo at:

http://stickynote.theburningmonk.com

or using the short link:

http://LNK.by/fhbba

Enjoy!

UPDATE 27/02/2011:

Thanks to Joey Beechey for point­ing out to me in the com­ment that the demo doesn’t work in Safari and after a bit of dig­ging I found that the prob­lem is with the addNewNote func­tion which has a para­me­ter named ‘class’ which is a reserved word. I found another ref­er­ence of this prob­lem here too, which men­tions the same prob­lem but with the vari­able name ‘abstract’ instead!

I’ve also put the whole thing into a zip file which you can down­load from here, so feel free to play around with it yourself!

Ref­er­ences:

Dou­glas Crockford’s JSON-js project on github

John Resig’s blog post which includes some handy exam­ples on how to use the JSON-js library

Orig­i­nal tuto­r­ial which inspired this blog post

Share

If you’ve been using S3 client in the AWS SDK for .Net you might have noticed that there are no meth­ods that let you inter­act with the fold­ers in a bucket. As it turns out, S3 does not sup­port fold­ers in the con­ven­tional sense*, every­thing is still a key value pair, but tools such as Cloud Berry or indeed the Ama­zon web con­sole sim­ply uses ‘/’ char­ac­ters in the key to indi­cate a folder structure.

This might seem odd at first but when you think about it, there are no folder struc­ture on your hard drive either, it’s a log­i­cal struc­ture the OS pro­vides for you to make it eas­ier for us mere mor­tals to work with.

Back to the topic at hand, what this means is that:

  • if you add an object with key myfolder/ to S3, it’ll be seen as a folder
  • if you add an object with key myfolder/myfile.txt to S3, it’ll be seen as a file myfile.txt inside a myfolder folder, if the folder object doesn’t exist already it’ll be added automatically
  • when you make a Lis­tO­b­jects call both myfolder/ and myfolder/myfile.txt will be included in the result

Cre­at­ing folders

To cre­ate a folder, you just need to add an object which ends with ‘/’, like this:

public void CreateFolder(string bucket, string folder)
{
    var key = string.Format(@"{0}/", folder);
    var request = new PutObjectRequest().WithBucketName(bucket).WithKey(key);
    request.InputStream = new MemoryStream();
    _client.PutObject(request);
}

Here is a thread on the Ama­zon forum which cov­ers this technique.

List­ing con­tents of a folder

With the Lis­tO­b­jects method on the S3 client you can pro­vide a pre­fix require­ment, and to get the list of objects in a par­tic­u­lar folder sim­ply add the path of the folder (e.g. topfolder/middlefolder/) in the request:

var request = new ListObjectsRequest().WithBucketName(bucket).WithPrefix(folder);

If you are only inter­ested in the objects (includ­ing fold­ers) that are in the top level of your folder/bucket then you’d need to do some fil­ter­ing on the S3 objects returned in the response, some­thing along the line of:

// get the objects at the TOP LEVEL, i.e. not inside any folders
var objects = response.S3Objects.Where(o => !o.Key.Contains(@"/"));

// get the folders at the TOP LEVEL only
var folders = response.S3Objects.Except(objects)
                      .Where(o => o.Key.Last() == '/' &&
                                  o.Key.IndexOf(@"/") == o.Key.LastIndexOf(@"/"));
Share

In part 4, I put together the foun­da­tions for a mini shoot­ing game, with tar­gets mov­ing on the screen and you can ‘hit’ them by click­ing inside the tar­gets, etc. etc. I promised an update to make it feel more like a game, so here it is!

Adding a background

The black back­ground was more than a lit­tle dull so I had a look around for some freely avail­able images, and to apply them I had two sim­ple choices:

  • draw the image onto the main can­vas on each frame
  • use the image as back­ground to the canvas

I opted for the lat­ter as it seems more effi­cient, it does mean that I need to change the way I’m clear­ing the can­vas for each frame though. In part 1 of this series I men­tioned that by set­ting the width or height of the can­vas ele­ment you can erase the con­tents and reset all the prop­er­ties of its draw­ing con­text, which seems to fit the bill of what I’m try­ing to do here.

The clear func­tion is there­fore changed to:

   1: // clear the canvas page

   2: function clear() {

   3:     // reset the canvas by resizing the canvas element

   4:     canvasElement.width = canvasElement.width;

   5: }

Limit the num­ber of Shots

To make the game more chal­leng­ing (hav­ing an unlim­ited num­ber of shots is just too easy), I decided to add a limit on the num­ber of shots you can have and after you’ve used up all of them it’s effec­tively game over.

To show how many shots you have left, I want to show a num­ber of bul­lets in the top left cor­ner and depend­ing on the num­ber of shots you have left the bul­lets are shown differently:

  • Five shots or less and you will see a bul­let for each
  • More than five shots and you will see a bul­let fol­lowed by the words x N where N is the num­ber of shots left
  • If no restric­tion on the num­ber of shots (for time attack mode for instance), you see an infin­ity sign next to a bullet

To facil­i­tate these require­ments, I need to be able to track the num­ber of shots left and decre­ment its value every time you click inside the can­vas ele­ment. Thank­fully, the struc­ture is there already, just need some small modifications:

   1: // first declare a new global variable

   2: /* GLOBAL VARIABLES */

   3: var WIDTH,              // width of the canvas area

   4:     HEIGHT,             // height of the canvas area

   5:     ...

   6:     bullets,            // how many shots left

   7:     ...

   8:

   9: // then add some more steps to the mousedown handler

  10: $("#canvas").mousedown(function (mouseEvent) {

  11:     // ignore if no more bullets left!

  12:     if (bullets <= 0) {

  13:         return;

  14:     }

  15:

  16:     // hit test and add message, etc.

  17:     ...

  18:

  19:     // deduct the number of shots left if a cap is defined

  20:     if (bullets != undefined) {

  21:         bullets--;

  22:     }

  23: });

Then I need to take care of the busi­ness of actu­ally show­ing them on the screen, luck­ily it wasn’t too hard to cre­ate a basic bul­let look­ing image and an infin­ity sym­bol. To make it eas­ier to use them over and over in my code, I added two <img> ele­ments to the HTML (along with a third which I will explain later) but they’ll be hidden:

   1: <div class="hide">

   2:     <img id="target" src="images/target.png"/>

   3:     <img id="bullet_target" src="images/bullet_target.png"/>

   4:     <img id="bullet" src="images/bullet.png"/>

   5:     <img id="infinity" src="images/infinity.png"/>

   6: </div>

Back to the Javascript code, there needs to be an extra step in the draw func­tion to add the bul­lets to the scene. To encap­su­late that logic I pulled it out into its own function:

   1: // redraw the target boards on the canvas

   2: function draw() {

   3:     // as before

   4:     ...

   5:

   6:     // then the bullets

   7:     drawBullets();

   8: }

   9:

  10: // draw the bullets onto the top left hand corner

  11: function drawBullets() {

  12:     // don't proceed any further if no more bullets left

  13:     if (bullets <= 0) {

  14:         return;

  15:     }

  16:

  17:     // define basica X and Y offsets

  18:     var offsetX = 10, offsetY = 10;

  19:

  20:     // draw the first bullet

  21:     context.drawImage(bulletElement, offsetX, offsetY);

  22:     offsetX += bulletElement.width;

  23:

  24:     if (bullets == undefined) {

  25:         // draw the infinity sign next to the bullet with some padding

  26:         context.drawImage(infinityElement, offsetX + 5, offsetY + 5);

  27:     } else if (bullets > 5) {

  28:         context.textBaseline = "top";

  29:         context.textAlign = "start";

  30:         context.fillStyle = "orange";

  31:         context.font = "bold 40px arial";

  32:

  33:         // draw a count next to the bullet, e.g. "x10"

  34:         context.fillText("x"+bullets, offsetX + 5, offsetY);

  35:     }

  36:     else {

  37:         // otherwise, draw the remaining bullets

  38:         for (var i = 1 ; i < bullets; i++) {

  39:             context.drawImage(bulletElement, offsetX, offsetY);

  40:             offsetX += bulletElement.width;

  41:         }

  42:     }

  43: }

And these are the results:

image image image

Looks the part, if I might say so myself :-)

Using a Cus­tom Cursor

Though a good approx­i­mate, the crosshair cur­sor just isn’t quite up to the task here, a scope-like cur­sor is needed here, like the one I cre­ated here:

image

(if you’re inter­ested in how I made the cur­sor, I used Axi­alis Cur­sor­Work­shop which makes the task simple)

All that’s left is to replace the crosshair cur­sor in CSS, but still keep­ing it as fallback:

   1: #canvas

   2: {

   3:     cursor: url(../cursors/scope.cur), crosshair;

   4:     background: url(../images/canvas_background.jpg);

   5: }

A sim­i­lar change needs to be applied to the HTML:

   1: <canvas id="canvas" class="block" width="800" height="700"

   2:         onSelectStart="this.style.cursor='url(cursors/scope.cur), crosshair'; return false;"/>

After that you’ll start see­ing the new cur­sor when you run the game.

Adding a Bul­let Target

Thanks to my wife Yinan for giv­ing me the idea of hav­ing bonus tar­gets which are harder to hit but give you extra bul­lets when they’re hit. You have already seen the HTML changes ear­lier, and prob­a­bly noticed that I have switched to using an image rather than draw­ing the reg­u­lar tar­get boards by hand..

Much of the changes to sup­port a dif­fer­ent type of tar­get hap­pened in the Javascript code, namely the Tar­get class, which is now extended by a new Bul­let­Tar­get class:

   1: // define the Target 'class' to represent an on-screen target

   2: var Target = new Class({

   3:     initialize: function (x, y, radius, dx, dy) {

   4:         // same as before

   5:         ...

   6:

   7:         // hit the target!

   8:         this.hit = function () {

   9:             deleteTarget(_id);

  10:         };

  11:     }

  12: });

  13:

  14: // a target which awards the player with an extra bullet when hit

  15: var BulletTarget = new Class({

  16:     Extends: Target,

  17:     initialize: function (x, y, radius, bonusBullets, dx, dy) {

  18:         var _bonusBullets = bonusBullets;

  19:

  20:         // initialize Target's properties

  21:         this.parent(x, y, radius, dx, dy);

  22:

  23:         this.getBonusBullets = function () {

  24:             return _bonusBullets;

  25:         }

  26:

  27:         // override the draw function to draw the bullet target instead

  28:         this.draw = function () {

  29:             context.drawImage(bulletTargetElement, this.getX(), this.getY(), radius*2, radius*2);

  30:         };

  31:

  32:         // override the hit function to the player some bullets back 

  33:         this.hit = function () {

  34:             bullets += _bonusBullets;

  35:             deleteTarget(this.getId());

  36:         }

  37:     }

  38: });

The logic of remov­ing a tar­get from the tar­gets array is now moved out of the Tar­get class and the code which gen­er­ates tar­gets dur­ing ini­tial­iza­tion has also changed to gen­er­ate a bul­let tar­get by a con­fig­urable chance:

   1: /* GLOBAL VARIABLES */

   2: var WIDTH,              // width of the canvas area

   3:     HEIGHT,             // height of the canvas area

   4:     ...

   5:     targetElement,      // the target img element

   6:     targetRadius = 50,  // the radius of the regular targets

   7:     bulletTargetElement,        // the bullet target img element

   8:     bulletTargetRadius = 30,    // the radius of the targets with bullet bonus

   9:     bulletTargetBonus = 3,      // the number of extra bullets for hitting a bullet target

  10:     bulletTargetChance = 0.1,   // the chance of getting a bullet target

  11:     ...

  12:

  13: // function to delete the target with the specified ID

  14: function deleteTarget(id) {

  15:     for (var i = 0; i < targets.length; i++) {

  16:         var target = targets[i];

  17:

  18:         if (target.getId() == id) {

  19:             targets.splice(i, 1);

  20:             break;

  21:         }

  22:     }

  23: }

  24:

  25: // Add targets to the game

  26: function createTargets() {

  27:     for (var i = 0; i < 10; i++) {

  28:         if (Math.random() < bulletTargetChance) {

  29:             targets[i] = new BulletTarget(0, 0, bulletTargetRadius, bulletTargetBonus);

  30:         } else {

  31:             targets[i] = new Target(0, 0, targetRadius);

  32:         }

  33:     }

  34: }

Demo

The full demo can be found here, hope you like it, I’ll be adding even more fea­tures in the near future so come back later if you’re inter­ested in see­ing how far this lit­tle game can go!

Related posts:

Hav­ing fun with HTML5 — Can­vas, part 1

Hav­ing fun with HTML5 — Can­vas, part 2

Hav­ing fun with HTML5 — Can­vas, part 3

Hav­ing fun with HTML5 — Can­vas, part 4

Share

Fol­low­ing on from part 3 where we basi­cally made a lit­tle app that lets you scrib­ble with HTML5’s can­vas ele­ment, let us push on and see what else we can achieve with the can­vas element.

The log­i­cal next step would be to made ani­ma­tions, and give you a way to inter­act with the ani­ma­tion. So how about a lit­tle game that lets you shoot mov­ing tar­gets like those clas­sic Point Blank games? Hands up who’s up for that?

image

Good good, that’s decided then! :-)

To Start Off..

First, we’ll need a sim­ple HTML page, some­thing like my pre­vi­ous demos would do:

   1: <div id="wrapper">

   2:     <h1>HTML5 Canvas Demo</h1>

   3:     <h3>Shoot 'em!</h3>

   4:     <aside id="message"></aside>

   5:

   6:     <div class="hide">

   7:         <canvas id="target" width="101" height="101" class="hide"/>

   8:     </div>

   9:

  10:     <div class="block">

  11:         <canvas id="canvas" class="block" width="800" height="700"

  12:                 onSelectStart="this.style.cursor='crosshair'; return false;"/>

  13:     </div>

  14: </div>

Noth­ing fancy here, the CSS for this page is also pretty basic too, the only thing worth not­ing is that to get the crosshair cur­sor inside the can­vas I included this in my CSS:

   1: #canvas

   2: {

   3:     cursor: crosshair;

   4: }

The onS­e­lect­Start line in the can­vas HTML is to ensure that the crosshair cur­sor is used when you click and hold inside the can­vas element.

You might have also noticed that I had spec­i­fied two can­vas ele­ments in the HTML, this is to sim­plify the task of draw­ing mov­ing tar­gets onto the main can­vas. With the 2D con­text object’s draw­Im­age func­tion, you can draw an img, can­vas, or video ele­ment so I can draw the tar­get onto the tar­get can­vas dur­ing ini­tial­iza­tion and then reuse it in the main canvas.

Draw­ing the Tar­get board

Next, I need to draw the tar­get boards, and make them look like this, but with­out the score on the rings:

image

(Why not just use an image? Because where’s the fun in that! ;-) )

As you can imag­ine, we can cre­ate a tar­get board look­ing like this by draw­ing 6 cir­cles, with the inner­most 2 cir­cles hav­ing roughly half the radius of the other 4 and the out­er­most 2 cir­cles hav­ing the inversed fill/stroke colour com­bi­na­tion. Expand the below sec­tion to see the ini­tial­ize­Tar­get­Can­vas func­tion which takes a can­vas ele­ment and draws a tar­get board in inside it:

   1: // Draws the target canvas

   2: function initializeTargetCanvas(element) {

   3:     var baseX = 1.5, baseY = 1.5;

   4:

   5:     // get the width and height of the element

   6:     var width = (element.width - baseX * 2) / 2,

   7:         height = (element.height - baseY * 2) / 2;

   8:

   9:     // work out the necessary metrics to draw the target

  10:     var radius = Math.min(width, height),

  11:         centreX = baseX + radius,

  12:         centreY = baseY + radius,

  13:         ringWidth = radius / 10;

  14:

  15:     // get the 2D context to start drawing the target!

  16:     var context = element.getContext("2d");

  17:     context.lineWidth = "2";

  18:

  19:     // define function to draw a ring

  20:     var drawRing = function (strokeStyle, fillStyle, ringRadius) {

  21:         context.strokeStyle = strokeStyle;

  22:         context.fillStyle = fillStyle;

  23:

  24:         // draw the circle

  25:         context.beginPath();

  26:         context.arc(centreX, centreY, ringRadius, 0, Math.PI * 2, true);

  27:         context.closePath();

  28:

  29:         context.stroke();

  30:         context.fill();

  31:     };

  32:

  33:     // draw the rings for each score

  34:     drawRing("#000", "#FFF", radius);

  35:     drawRing("#000", "#FFF", radius -= (ringWidth * 2));

  36:     drawRing("#FFF", "#000", radius -= (ringWidth * 2));

  37:     drawRing("#FFF", "#000", radius -= (ringWidth * 2));

  38:     drawRing("#FFF", "#000", radius -= (ringWidth * 2));

  39:     drawRing("#FFF", "#000", radius -= ringWidth);

  40: }

Ani­mat­ing the Tar­get Boards

Being able to draw an already cre­ated tar­get board onto the can­vas page is one thing, but how do I ani­mate them using the can­vas? Hav­ing looked at a few other exam­ples it seems the com­mon way to do ani­ma­tion with the can­vas is to sim­ply clear the can­vas and redraw the con­tents on a reg­u­lar inter­val. This seems a lit­tle low level and requires a bit of plumb­ing but I’m sure it won’t be long (if not already) before some good, solid frame­works emerge to make these tasks easier.

For now though, let me illus­trate how you might cre­ate this frame by frame ani­ma­tion yourself.

If you sep­a­rate the respon­si­bil­i­ties of the app, broadly speak­ing you end up with:

  • a View – i.e. the can­vas ele­ment, respon­si­ble for dis­play­ing the targets
  • a Model – the objects rep­re­sent­ing the tar­gets in the scene, respon­si­ble for keep­ing track of their cur­rent posi­tion, etc.

If you’ve dealt with any sort of MVC/MVP/MVVM pat­tern then this kind of sep­a­ra­tion of duty should be famil­iar to you already. On each frame (or if you pre­fer, every time the set­Inter­val del­e­gate func­tion gets invoked), you update the posi­tion of the tar­gets in the model, then redraw the tar­gets onto the can­vas to reflect their updated positions:

image

This is all you need to do to cre­ate a basic ani­ma­tion. So in my case, I need a sim­ple Tar­get object to keep track of:

  • X, Y coor­di­nates of the top left hand cor­ner of the target
  • the radius of the target
  • the direc­tion and speed (per frame) it’s mov­ing at

using MooTools here’s the class I arrived at this Tar­get ‘class’:

   1: /* GLOBAL VARIABLES */

   2: var WIDTH,              // width of the canvas area

   3:     HEIGHT,             // height of the canvas area

   4:

   5:     targets = new Array(),  // the live targets

   6:     targetId = 0,        // the current target ID

   7:

   8:

   9: // define the Target 'class' to represent an on-screen target

  10: var Target = new Class({

  11:     initialize: function (x, y, radius, dx, dy) {

  12:         var _id, _x, _y, _radius, _dx, _dy, is;

  13:

  14:         _id = targetId++;

  15:

  16:         // the X and Y coordinate of the top left corner

  17:         _x = x;

  18:         _y = y;

  19:

  20:         // the radius of the target

  21:         _radius = radius;

  22:

  23:         // the rate of movement in the X and Y direction

  24:         if (dx) {

  25:             _dx = dx;

  26:         } else {

  27:             _dx = Math.ceil(Math.random() * 10);

  28:         }

  29:         if (dy) {

  30:             _dy = dy;

  31:         } else {

  32:             _dy = Math.ceil(Math.random() * 10);

  33:         }

  34:

  35:         // getters

  36:         this.getId = function () {

  37:             return _id;

  38:         }

  39:

  40:         this.getX = function () {

  41:             return _x;

  42:         };

  43:

  44:         this.getY = function () {

  45:             return _y;

  46:         };

  47:

  48:         this.getRadius = function () {

  49:             return _radius;

  50:         };

  51:

  52:         // move the target to its position for the next frame

  53:         this.move = function () {

  54:             _x += _dx;

  55:             _y += _dy;

  56:

  57:             // change direction in X if it 'hits' the border

  58:             if ((_x + _radius * 2) >= WIDTH || _x <= 0) {

  59:                 _dx *= -1;

  60:             }

  61:

  62:             // change direction in Y if it 'hits' the border

  63:             if ((_y + _radius * 2) >= HEIGHT || _y <= 0) {

  64:                 _dy *= -1;

  65:             }

  66:         };

  67:

  68:         // draws the target on the canvas

  69:         this.draw = function () {

  70:             context.drawImage(targetElement, _x, _y);

  71:         };

  72:

  73:         // hit the target!

  74:         this.hit = function () {

  75:             for (var i = 0; i < targets.length; i++) {

  76:                 var target = targets[i];

  77:

  78:                 if (target.getId() == _id) {

  79:                     targets.splice(i, 1);

  80:                     break;

  81:                 }

  82:             }

  83:         };

  84:     }

  85: });

The draw func­tion, which is invoked at reg­u­lar inter­vals, first clears the can­vas (by fill­ing it with the back­ground colour) then goes through all the tar­gets in the tar­gets array and updates their loca­tion and then draw them onto the canvas:

   1: // clear the canvas page

   2: function clear() {

   3:     context.fillStyle = "#000";

   4:     context.fillRect(0, 0, WIDTH, HEIGHT);

   5: }

   6:

   7: // redraw the target boards on the canvas

   8: function draw() {

   9:     // clear the canvas page first

  10:     clear();

  11:

  12:     for (var i = 0; i < targets.length; i++) {

  13:         targets[i].move();

  14:         targets[i].draw();

  15:     }

  16: }

This will give you the basic ani­ma­tion loop, here’s how it looks with 10 mov­ing tar­gets on screen at the same time:

image

Adding Inter­ac­tions

Now that the ani­ma­tions are in place, let’s add some player inter­ac­tions. The inter­ac­tions I’m after here is sim­ple, click in the can­vas and knock out any (can be one or more) tar­get that’s clicked on.

I’ve already gone over the process of cal­cu­lat­ing the coor­di­nates of the click in respect to the can­vas in a pre­vi­ous blog post here, this is what that code looks like:

   1: // works out the X, Y position of the click INSIDE the canvas from the X, Y 

   2: // position on the page

   3: function getPosition(mouseEvent, element) {

   4:     var x, y;

   5:     if (mouseEvent.pageX != undefined && mouseEvent.pageY != undefined) {

   6:         x = mouseEvent.pageX;

   7:         y = mouseEvent.pageY;

   8:     } else {

   9:         x = mouseEvent.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;

  10:         y = mouseEvent.clientY + document.body.scrollTop + document.documentElement.scrollTop;

  11:     }

  12:

  13:     return { X: x - element.offsetLeft, Y: y - element.offsetTop };

  14: }

If I can work out where you’ve clicked in the can­vas’ coor­di­nate sys­tem, then I can sim­ply run a hit test against each mov­ing tar­get and com­pare the dis­tance between the click and the cen­tre of the tar­get and the target’s radius:

image

If the dis­tance is smaller or equal to the radius then the click hap­pened INSIDE the tar­get and there­fore it’s a hit, oth­er­wise it’s a miss. Sounds rea­son­able enough? Here’s the code that takes a posi­tion (a sim­ple object with X and Y posi­tion of the click inside the can­vas’ coor­di­nate sys­tem) and return the tar­gets it has hit:

   1: // check if the player managed to hit any of the live targets

   2: function hitTest(position) {

   3:     var hitTargets = new Array();

   4:

   5:     // check if the position is within the bounds of any of the live targets

   6:     for (var i = 0; i < targets.length; i++) {

   7:         var target = targets[i];

   8:

   9:         var targetCentreX = target.getX() + target.getRadius(),

  10:             targetCentreY = target.getY() + target.getRadius();

  11:

  12:         // work out the distance between the position and the target's centre

  13:         var xdiff = position.X - targetCentreX,

  14:             ydiff = position.Y - targetCentreY,

  15:             dist = Math.sqrt(Math.pow(xdiff, 2) + Math.pow(ydiff, 2));

  16:

  17:         // if that distance is less than the radius of the target then the

  18:         // position is inside the target

  19:         if (dist <= target.getRadius()) {

  20:             hitTargets.push(target);

  21:         }

  22:     }

  23:

  24:     return hitTargets;

  25: }

To hook this up, I added an event han­dler to the mouse­down event on the can­vas dur­ing initialization:

   1: $("#canvas").mousedown(function (mouseEvent) {

   2:     // get the coordinates of the click inside the canvas

   3:     var position = getPosition(mouseEvent, this);

   4:

   5:     // find out which targets were hit

   6:     var hitTargets = hitTest(position);

   7:

   8:     // hit the targets

   9:     for (var i = 0; i < hitTargets.length; i++) {

  10:         hitTargets[i].hit();

  11:     }

  12: }

And now, when you ‘hit’ a tar­get it’ll be removed from the array of mov­ing tar­gets and there­fore won’t be drawn again when the can­vas is refreshed in the next frame.

Adding Noti­fi­ca­tions

Finally, for this first-pass imple­men­ta­tion of a mini-shooting game, I’d like to add some noti­fi­ca­tions to tell you when you’ve hit some­thing, or when you’ve missed completely!

This is slightly trick­ier than the tar­gets as the mes­sages should not stay around for­ever until some user-triggered action, instead it should be shown for a given amount of time (or frames). To facil­i­tate this require­ment, I cre­ated another ‘class’ called Mes­sage:

   1: // define the Message 'class' to represent an on-screen message

   2: var Message = new Class({

   3:     initialize: function (x, y, message, duration) {

   4:         var _id, _x, _y, _message, _duration;

   5:

   6:         _id = messageId++;

   7:

   8:         // X, Y coordinates of where to display the message

   9:         _x = x;

  10:         _y = y;

  11:

  12:         // the message

  13:         _message = message;

  14:

  15:         // how many frames to display the message for

  16:         _duration = duration;

  17:

  18:         this.getId = function () {

  19:             return _id;

  20:         }

  21:

  22:         this.draw = function () {

  23:             if (_duration >= 0) {

  24:                 context.textBaseline = "middle";

  25:                 context.textAlign = "center";

  26:                 context.fillStyle = "#FFF";

  27:                 context.strokeStyle = "#000";

  28:                 context.font = "bold 40px arial";

  29:

  30:                 // draw the message at the specified X, Y coordinates

  31:                 context.fillText(_message, _x, _y);

  32:

  33:                 _duration--;

  34:             } else {

  35:                 // remove the message

  36:                 for (var i = 0; i < messages.length; i++) {

  37:                     var message = messages[i];

  38:

  39:                     if (message.getId() == _id) {

  40:                         messages.splice(i, 1);

  41:                         break;

  42:                     }

  43:                 }

  44:             }

  45:         }

  46:     }

  47: });

The Mes­sage objects can only been drawn a num­ber of times, after which it will remove itself from the array of mes­sages cur­rently being displayed.

To make it a bit more inter­est­ing, I defined a num­ber of mes­sages which will be dis­played depend­ing on how many tar­gets you’ve man­aged to hit at once:

   1: // define the messages to show

   2: var hitMessages = new Array();

   3: hitMessages[0] = "MISS";

   4: hitMessages[1] = "HIT!";

   5: hitMessages[2] = "DOUBLE HIT!!";

   6: hitMessages[3] = "HAT-TRICK!!!";

   7: hitMessages[4] = "UN~BELIEVABLE!!!!";

   8: hitMessages[5] = "OH MY GOSH!!";

On the mouse­down event han­dler (see above) I added these cou­ple of lines to push a new mes­sage to the mes­sages stack to be dis­played for 30 frames, and the mes­sage is deter­mined by how many tar­gets was hit:

   1: // use one of the defined messages if possible, otherwise use a default

   2: var hitMessage = hitMessages[hitTargets.length];

   3: if (hitMessage == undefined)

   4: {

   5:     hitMessage = "TOO GOOOOOOOOD..";

   6: }

   7:

   8: messages.push(new Message(position.X, position.Y, hitMessage, 30));

For exam­ple, if you man­aged to hit three tar­gets one shot:

image

Pretty neat, eh? :-)

Demo

Here is the full demo, over the next cou­ple of days, I will pol­ish it up and add some more fea­tures to make it feel more gamey and post another update. In the mean time though, feel free to play around with it and let me know of any sug­ges­tions you have on how to improve it!

Ref­er­ences:

HTML5 can­vas cheat sheet

Dive into HTML5 – peeks, pokes and pointers

Related posts:

Hav­ing fun with HTML5 — Can­vas, part 1

Hav­ing fun with HTML5 — Can­vas, part 2

Hav­ing fun with HTML5 — Can­vas, part 3

Hav­ing fun with HTML5 — Can­vas, part 5

Share

Time really does fly when you’re hav­ing fun! In a blink of the eye it’s been a whole year since I left Credit Suisse to start a career mak­ing social games with IwI. All and all, it’s been a year filled with lots of hard work, lots of learn­ing and hon­estly, a hell of a lot of fun! The great thing about doing some­thing com­pletely dif­fer­ent is that you get to learn about a new stack of tech­nolo­gies to tackle an alto­gether dif­fer­ent set of chal­lenges, and in the end I believe it has made me a bet­ter developer.

One of those new tech­nolo­gies which I’ve had to get used to is Ama­zon Web Ser­vices (AWS), Amazon’s cloud com­put­ing solu­tion, which I will give an account of my per­sonal expe­ri­ences of  work­ing close with it dur­ing this past year.

By now you’ve prob­a­bly heard plenty about cloud com­put­ing already (well chances are one of the providers have tried to sell their ser­vice to you already!), so I won’t bore you with those vague mar­ket­ing tagline (well, I got noth­ing to sell here any­way :-P) and won­der­ful sto­ries of how com­pany XYZ made a move to the cloud and are now able to serve mil­lions more cus­tomers whilst pay­ing peanuts to run their servers.

Well.. truth is, things aren’t always rosy and there had been sev­eral high pro­file dis­as­ters which resulted in com­pa­nies los­ing cred­i­bil­ity or in extreme cases, their entire business..(that’s what los­ing your entire data­base clus­ter does to you when you haven’t got ade­quate backup…). So there are new risks involved and new chal­lenges which need to be tackled.

AWS vs other cloud solutions

Whilst all my expe­ri­ence with cloud com­put­ing has been with AWS so I won’t com­ment on the pros and cons of other cloud solu­tions, but from my under­stand­ing of Microsoft’s Azure and Google’s AppEngine ser­vices they have a very dif­fer­ent model to that of AWS.

Azure and AppEngine’s model is best described as platform-as-a-service where you develop your code against a SDK sup­plied by Microsoft or Google which allows you to make use of their respec­tive SQL/NoSQL solu­tions, etc. Deploy­ments are usu­ally easy and the ser­vice will man­age the num­ber of instances (servers) needed to run your appli­ca­tion to meet the cur­rent demand for you whilst let­ting you set a max/min cap on the num­ber of instances so that your cost doesn’t spi­ral out of control.

One the other hand, Amazon’s model is infrastructure-as-a-service which is basi­cally the same as older vir­tual machine tech­nol­ogy but with lots of addi­tional knots and bots such as auto­mated pro­vi­sion­ing, auto scal­ing, auto­mated billing, etc. You have the abil­ity to cre­ate new machine images from exist­ing instances to use to spawn new instances and you can use the instances for pretty much all intents and pur­poses, e.g. web servers, MySQL server, cache clus­ter, etc. after all they’re just vir­tual machines.

In con­trast to Azure and AppEngine’s model, AWS’s model gives you more flex­i­bil­ity and con­trol but at the same time requires you to do a lit­tle bit more work to get going and take on the role of an IT pro as well as a developer.

Ven­dor Lock-in

Pos­si­bly the biggest com­plaint and worry peo­ple have about migrat­ing to the cloud is that you are locked in with a par­tic­u­lar ven­dor. Whilst that’s true, but given the cur­rent affairs and the way things are seem­ingly going, I’d say fears of the likes of Microsoft, Google or Ama­zon going bust and there­fore their respec­tive cloud ser­vices going up in smokes is.. well.. a lit­tle extreme.

It’s harder to move away from the Azure and AppEngine given that every­thing you’ve coded are against a par­tic­u­lar set of SDK but with AWS’s infrastructure-as-a-service model it’s pos­si­ble to migrate out of it with too much has­sle. In fact, Zynga uses AWS as incu­ba­tion cham­ber for their new games and allow them to mon­i­tor and learn about the host­ing require­ments for a new game in its tur­bu­lent early days (well they lit­er­ally go from hun­dreds to mil­lions of users in the mat­ter of days so you can imag­ine…) before mov­ing the game into their own data cen­tres once the traf­fic stabilises.

Pric­ing

In terms of pric­ing, there’s lit­tle that sep­a­rates the three providers I’ve men­tioned here, in fact, the last time I looked, the equiv­a­lent instances in Azure costs the equiv­a­lent amount as their AWS coun­ter­part so clearly the mar­ket research divi­sions have been work­ing hard to know exactly what their com­peti­tors have been up to :-P

Matu­rity

Hav­ing been released in 2006, AWS is one of the if not the old­est cloud ser­vice out there and even then it’s still barely four years old and still a lit­tle rough around the edges (they don’t call it cut­ting edge for no rea­son I sup­pose). It has steadily improved both in terms of fea­ture as well as tool­ing and there is an active com­mu­nity out there build­ing sup­ple­men­tary tools/frameworks for it in dif­fer­ent lan­guages. I also find that the doc­u­men­ta­tions Ama­zon pro­vides are in gen­eral up-to-date and useful.

Pop­u­lar­ity

Much to my sur­prise, I found out at a recent cloud com­put­ing con­fer­ence that none of Microsoft, Google or Ama­zon made the list of top 3 cloud com­put­ing ser­vice providers. Instead, SalesForce.com made num­ber one and I can’t remem­ber who the other two providers are…sorry..

Ser­vices

AWS offers a whole ecosys­tem of dif­fer­ent ser­vices which should cover most aspects of a given archi­tec­ture, from ser­vice host­ing to data stor­age, mes­sag­ing, they even recently announced a new DNS ser­vice called Route 53!

There’s a quick run down of the three most impor­tant ser­vices which AWS offers:

Elas­tic Cloud Com­put­ing (EC2)

EC2 forms the back­bone of Amazon’s cloud solu­tion, its key char­ac­ter­is­tics include:

  • you pay for what you use at a per instance per hour rate
  • you pay for the amount of data trans­fers in and out of Ama­zon EC2 (data trans­fer between Ama­zon EC2 and other Ama­zon Web Ser­vices in the same region is free)
  • there are a num­ber of dif­fer­ent OS’s to choose from includ­ing Linux and Win­dows, Linux instances are cheaper to run with­out the license cost asso­ci­ated with Windows
  • there are a good range of dif­fer­ent instance types to choose from, from the most basic (sin­gle CPU, 1.7GB ram) to high per­for­mance instances (22 GB ram, 2 x Intel Xeon X5570, quad-core Nehalem, 2 x NVIDIA Tesla Fermi M2050 GPUs)
  • a basic instance run­ning win­dows will cost you roughly $3 a day to run non-stop
  • default machine images are reg­u­larly patched by Ama­zon
  • once you’ve set up your server to be the way you want, includ­ing any server updates/patches, you can cre­ate your own AMI (Ama­zon Machine Image) which you can then use to bring up other iden­ti­cal instances
  • there’s a Elas­tic Load Bal­anc­ing ser­vice which pro­vides load bal­anc­ing capa­bil­i­ties at addi­tional cost (though most of the time you’ll only need one)
  • there’s a Cloud Watch ser­vice which you can enable on a per instance basis to help you mon­i­tor the CPU, net­work in/out, etc. of your instances, this ser­vice also has its own cost
  • you can use the Auto Scal­ing ser­vice to auto­mat­i­cally bring up or ter­mi­nate instances based on some met­ric, e.g. ter­mi­nate 1 instance at a time if aver­age CPU uti­liza­tion across all instances is less than 50% for 5 min­utes con­tin­u­ously, but bring up 1 new instance at a time if aver­age CPU goes beyond 70& for 5 minutes
  • you can use the instances as web servers, DB servers, Mem­cached clus­ter, etc. choice is yours
  • round trips within Ama­zon is very very fast, but trips out of Ama­zon are sig­nif­i­cantly slower, there­fore the usual approach is to use Ama­zon Sim­pleDB (see below) or Ama­zon RDS as the Data­base (should you need one that is)
  • Ama­zonSDK is pretty solid and con­tains enough classes to help you write some cus­tom monitoring/scaling ser­vice if you ever need to but the AWS Man­age­ment Con­sole (see lower down) lets you do most basic oper­a­tions anyway

Sim­pleDB

Amazon’s NoSQL data­base, it is a non-relational, dis­trib­uted, key-value data store, its key char­ac­ter­is­tics include:

  • com­pared to tra­di­tional rela­tional Data­bases it has lower per-request per­for­mance, typ­i­cal 15-20ms oper­a­tions tend to take any­thing between 75-100ms to complete
  • in return, you get high scal­a­bil­ity with­out hav­ing to do any work
  • you pay for usage – how much work it takes to exe­cute your query
  • you start off with a sin­gle instance host­ing your data, instances are auto scaled up and down depend­ing on traf­fic, there’s no way to change the num­ber of Data­base instance to start off with
  • sup­ports a SQL-like query­ing syn­tax, though still fairly limited
  • for .Net, MindScape’s Sim­pleDB Man­age­ment Tools is the best man­age­ment tool we’ve used, it inte­grates directly into Visual Stu­dio and at $29 a head it’s not expen­sive either
  • most per­form­ing when traf­fic increases/decreases steadily, there’s a notice­able slump in response times when there’s a sud­den surge of traf­fic as new instances takes around 10–15 mins to be ready to ser­vice requests
  • data are par­ti­tion into ‘domains’, which are equiv­a­lent to tables in a rela­tional Database
  • data are non-relational, if you need a rela­tional model then use Ama­zon RDS, I don’t have any expe­ri­ence with it so not the best per­son to com­ment on it
  • be aware of ‘even­tual con­sis­tency’, data are dupli­cated on mul­ti­ple instances after Ama­zon scales up your data­base to meet the cur­rent traf­fic, and syn­chro­niza­tion is not guar­an­teed when you do an update so it’s pos­si­ble though highly unlikely to update some data then read it back straight away and get the old data back
  • there are ‘con­sis­tent read’ and ‘con­di­tional update’ mech­a­nisms avail­able to guard against the even­tual con­sis­tency prob­lem, if you’re devel­op­ing in .Net, I sug­gest using Sim­ple­Sa­vant client to talk to Sim­pleDB, it’s a fairly feature-complete ORM for Sim­pleDB which already sup­ports both con­sis­tent reads and con­di­tional updates

Sim­ple Stor­age Ser­vice (S3)

Amazon’s stor­age ser­vice, again, extremely scal­able, and safe too – when you save a file to S3 it’s repli­cated across mul­ti­ple nodes so you get some DR abil­ity straight away. Many pop­u­late ser­vices such as Drop­Box uses it behind the scene and it’s also the stor­age of choice for many image host­ing ser­vices. Key char­ac­ter­is­tics include:

  • you pay for data trans­fer in and out (data trans­fer between EC2 and S3 in the same region is free)
  • files are stored against a key
  • you cre­ate ‘buck­ets’ to hold your files, and each bucket has a unique URL (unique across all of Ama­zon, and there­fore S3 accounts)
  • there’s a Cloud Front ser­vice for con­tent deliv­ery, data are cached on the first request and there­fore speeds up sub­se­quent requests from the same region
  • Cloud­Berry S3 Explorer is the best UI client we’ve used in Windows
  • you can use the Ama­zonSDK to write you own repos­i­tory layer which uti­lizes S3

These are the three core ser­vices which most peo­ple use AWS for, but there are other use­ful ser­vices which I haven’t men­tioned yet, such as the Sim­ple Queue Ser­vice (SQS) and Elas­tic MapRe­duce, but those are more for edge cases.

Cost

Lower cost of entry

The great thing about the pay-as-you-go pric­ing model for cloud com­put­ing solu­tions in gen­eral is that as a small start-up, or even indi­vid­u­als, you can have the capa­bil­ity to serve mil­lions of cus­tomers right from the word go with­out hav­ing to invest heav­ily up front on infra­struc­ture and hard­ware. This serves to lower the cost of entry and there­fore the risk involved, which con­se­quently encour­ages innovations.

Dimin­ish­ing value of renting

How­ever, draw­ing analo­gies from car rentals, if you only need a car occa­sion­ally it makes much more eco­nomic sense to sim­ply rent when­ever you need one, but as your needs increases there comes a point when it becomes cheaper to actu­ally own a car out right. The same can be said of the cost of run­ning all your ser­vices out of AWS, espe­cially for high per­for­mance instances required to run a Data­base for exam­ple, see below screen­shots for some exam­ples of the avail­able instance types and cor­re­spond­ing cost of rent­ing by the hour:

image

image

image

Reserv­ing instances

In addi­tion to the stan­dard ‘pay for what you use’ model, Ama­zon also offers dis­counts when you reserve an instance for 1 or 3 year terms for a one-time fee, after which the instance is reserved for you.

image

So one way to cut your costs is to reserve the min­i­mum num­ber of instances you will need to run con­stantly based on the min­i­mum usage of your ser­vice and sup­ple­ment them with spot instances you request dynam­i­cally to cope with spikes in traffic.

Using EC2 as a supplement

Some com­pa­nies such as Zynga uses a sim­i­lar approach, where they run major­ity of their ser­vices out of their own data cen­tres but uses EC2 instances to sup­ple­ment that and help them cope with surges in traffic.

This approach how­ever, doesn’t apply to ven­dors which uses a platform-as-a-service model (e.g. Azure and AppEngine) because you are more tightly locked in with the spe­cific ven­dor and can’t sim­ply run part of your ser­vice out­side of their plat­form. For exam­ple, if you’ve devel­oped your appli­ca­tion to use Azure, you won’t be able to run your appli­ca­tion out of your own servers because Azure as a plat­form is only pro­vided by Microsoft.

Be ware of the addi­tional costs

When it comes to esti­mat­ing your cost, it’s easy to for­get about all the other small charges you incur, such as the cost of data trans­fers, which whilst fairly cheap but depend­ing on your usage can eas­ily build up and eclipse the cost of run­ning the vir­tual servers. Take a flash game for exam­ple, the game and rel­e­vant assets etc. can eas­ily amount to a few megabytes. This on its own is noth­ing, but mul­ti­ply that by 500k, 1 mil­lion, 2 mil­lion, 10 mil­lion users, and then mul­ti­ply by the num­ber of updates/patches which requires the users to down­load the pack­age again, and soon you just might be look­ing at a rather siz­able bill in rela­tion to your data transfers..

Add to that the cost of other periph­eral ser­vices such as Cloud Watch or Load Bal­anc­ing, etc. etc. which are all per­fectly rea­son­able by any means, but they all add up in the end.

It’s pos­si­ble to mit­i­gate some of these addi­tional cost, for exam­ple, you could make use of the Cloud Front ser­vice to reduce the amount of data trans­fers from S3 (data is cached after the first request in a given region), or bet­ter still you can archi­tect your appli­ca­tion so that it only loads resources at the time when they’re required but obvi­ously this adds to the com­plex­ity of your appli­ca­tion and can also com­pli­cate the deploy­ment process.

Web Con­sole

The stan­dard AWS Man­age­ment Con­sole (see image below) is good if unspec­tac­u­lar, and doesn’t cover the full range of the ser­vices Ama­zon pro­vides. There are also some impor­tant fea­tures miss­ing too, for instance, in order to talk to the Auto Scal­ing ser­vice to adjust the scal­ing para­me­ters (change max num­ber of instances from 10 to 5), you have to either down­load and use a com­mand line tool or use the Ama­zon SDK, or build some UI around it to make life eas­ier for your­selves as we did.

image

There are other ven­dors such as RightScale which pro­vides you with bet­ter tool­ing to help you manage/automate a lot of the work you have to oth­er­wise do your­self, but they usu­ally have pretty steep pric­ing and doesn’t rep­re­sent great value for money for a small startup look­ing to get up and run­ning cheaply. You know, the sort of folks that are attracted by cloud computing’s low cost entry point.. wait a minute…

Reli­a­bil­ity

The well goes deep…

I read an inter­est­ing story not long ago about the attempted Dis­trib­uted DOS attack on Ama­zon the Anony­mous col­lec­tive of online pro­test­ers tried to pull off (in protest of Ama­zon cut­ting Wik­iLeaks loose) after suc­cess­ful attempts on sev­eral other high pro­file sites. The attacked ended in fail­ure as the orga­niz­ers admit­ted that Ama­zon was too hard a nut to crack, so clearly the well is deep enough for all of us, and some more!

Out­ages and Per­for­mance Drops

Over the last year there had been sev­eral out­ages but they were all resolved fairly quickly, but there had been sev­eral instances where the per­for­mance (in terms of response time and/or num­ber of timed-out requests) had notice­ably dropped for both Sim­pleDB and S3.

When you request a new EC2 instance, you have to spec­ify which avail­abil­ity zone the instance should be spawned in, but for ser­vices such as Sim­pleDB you don’t have this con­trol and new instances are always spawned in the default avail­abil­ity zone for your cur­rent region.

For instance, if your cur­rent region is US East (N. Vir­ginia) the default avail­abil­ity zone is us-east-1a, and every­one who requests EC2 instances with­out chang­ing the default avail­abil­ity zone will be using the same zone and there­fore likely to cause a lot of con­ges­tion in that zone and affect other ser­vices such as Sim­pleDB. There’s even been sev­eral times when we sim­ply weren’t able to scale up our appli­ca­tion because the us-east-1a avail­abil­ity zone had no spare capacity!

It’s very impor­tant to build in lots of fault tol­er­ance into your appli­ca­tion when you’re using AWS, you should also avoid (where pos­si­ble) using the default avail­abil­ity zones for your EC2 instances as they tend to be the most likely to congest.

NOTE: as I men­tioned before about data trans­fer costs, data trans­fers between dif­fer­ent avail­abil­ity zones in the same region are free, so there’s no need to worry about incur­ring extra costs by hav­ing your EC2 instances run­ning in a dif­fer­ent avail­abil­ity zone to that of Sim­pleDB/S3.

Bug Report­ing

Being a rapidly and con­stantly evolv­ing plat­form, it’s no sur­prise that there had been the odd bugs that have been intro­duced as the result of an update, e.g. for a lit­tle while no one was able to remote desk­top to any instance whose pub­lic IP starts with 50, e.g. 50.0.0.0…

There is an active dis­cus­sion forum where you can report any bugs you notice about the ser­vices, and Ama­zon employ­ees do mon­i­tor these forums and pro­vide help­ful feed­backs etc. In addi­tion to that, there’s also a ser­vice sta­tus dash­board you can use to check the cur­rent sta­tus of their ser­vices by date, and by region.

Part­ing thoughts…

So there, a not so quick :-P high level sum­mary of my expe­ri­ence with AWS over the last 12 months. To wrap things up, here’s just a cou­ple of blogs you could read reg­u­larly to find out what’s going on the ‘cloud’:

High Scal­a­bil­ity

Cloud­Har­mony

Ama­zon Web Ser­vices blog

Well, hope this helps you in some way, belated happy 2011!

Share