Maintaining a reading list on GitHub

Like any decent programmer, I spend a lot of time reading. For the most part it runs in cycles: read a bunch of stuff, apply what you’ve learned, read a bunch more, and so on. This is a pretty typical pattern as becoming a strong developer requires a good mix of learning and doing. So, inspired in part by Apprenticeship Patterns, I’ve decided to start maintaining a reading list.

Screenshot of reading list

Some simple ground rules for myself.

  • List should be publicly available on github.
  • Be honest about what books I’ve read, partially read, and plan to read.
  • Write a few sentences about each book – mostly for myself but also for anyone who might be interested.
  • Only include books related to development, user experience design, and entrepreneurship.
  • Include screencasts as well since that’s how I do much of my learning.

I’ve already noticed some benefits.

Although it’s only been a short time, I can already attest to the usefulness of keeping track of what you’ve read. Just building the list requires going back through all the stuff you’ve read and watched over the past few years. It’ll get you thinking about how you learn, including some really interesting questions:

  • How have your interests moved between topics?
  • What topics do you keep coming back to?
  • What periods were most productive?
  • What topics required a few books before you really understood?
  • What type of books are most useful for the way you learn? (e.g. Do you learn better with lots of examples or would you rather read a short book twice?)
  • Were any of the books disappointing? Why?

I think it’s important that your list be more than a catalogue of titles and author names. You should take the time to reflect on why the book mattered (i.e. what did you take away from the read?). While this means writing a few sentences for each entry, don’t get too fixated. There’s no reason to write a summary or book review. The writeup is just to get you thinking broadly about the book.

And you should do it too.

Finally, I’d like to see other developers maintaining a reading list – specifically on GitHub. It’d be awesome if I could look up serious developers to check out what they’re reading and, more importantly, what they recommend. It’d be a nice adjunct to reading their code.

View my reading list on Github

Tags: , 2 Comments


jQuery Splatter Plugin

Splatter is a jQuery plugin which applies a random position, size, and color to elements on a page. The most basic implementation adds randomly colored and positioned asterisks to the element:

simple_example

To see some working examples, check out the demo page.

Basic usage.

Add these scripts in the head element of your page:

        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js" type="text/javascript" charset="utf-8"></script>
        <script src="jquery.splatter.js" type="text/javascript" charset="utf-8"></script>
    

Invoke the plugin, specifying configuration options as needed. For example, pass an array of splats if you’d like to use words rather than asterisks:

        $('#word_splatter').splatter({
          min_font_size: 20,
          max_font_size: 50,
          splats: ["Lorem", "Ipsum", "Dolor", "Sit", "Amet", "Consectetur", "Adipisicing", "Elit", "Sed", "Do", "Eiusmod", "Tempor", "Incididunt", "Ut", "Labore", "Et", "Dolore", "Magna", "Aliqua", "Ut", "Enim", "Ad", "Minim", "Veniam", "Quis", "Nostrud", "Exercitation", "Ullamco", "Laboris", "Nisi", "Ut", "Aliquip", "Ex", "Ea", "Commodo", "Consequat"]
        });
    

Which creates:

Splatter example using words

Configuration options.

Splatter has lots of configuration options but most of them are pretty simple and none of them are required:

        $('#splatter_box').splatter({
          custom_attributes: [],      // specify custom attributes to add to splats which can be used to store data
          colors: [],                 // specify the colors to be randomly applied to the splats
          splats: [],                 // specify strings to be used as splats – defaults to *
          hover_on: function() {},    // add a custom function to be called when hovering on a splat
          hover_off: function() {},   // add a custom function to be called when hovering off a splat
          splat_count: 20,            // number of splats that will be drawn
          min_font_size: 20,          // minimum font size for splats
          max_font_size: 300,         // maximum font size for splats
          height: $(window).height(), // height of splatter
          width: $(window).width()    // width of splatter
        });
    

Most of these should be fairly obvious but let’s go into a bit more detail on how to use custom attributes. Custom attributes allow you to apply data attributes with random values to each splat. They are specified as an array of objects:

        $('#splatter_box').splatter({
          custom_attributes: [
            { name: "data-hover_color", values: ['#f68a00', '#069', '#a70'] },
            { name: "data-slide_position", values: ['20', '50', '100'] }
          ]
        });
    

Each custom attribute object should have two keys: a name and values. The name will be used as the attribute’s name. The values key takes an array of possible values. The actual value for any given custom attribute is randomly selected from the values array.

So the the above example might create splats that look like:

        *
        *
    

Often custom attributes work in conjunction with custom hover events, allowing you to use jQuery to fetch these custom values and do something interesting (e.g. fade in a different color, animate a splats position, or make a splat randomly rotate). For example, let’s create 100 splats in shades of gray that fade into random colors on hover:

        $('#animated_colors').splatter({
          width: 700,
          height: 250,
          splat_count: 100,
          colors: ['#ccc', '#e0e0e0', '#333', '#666', '#999'],
          custom_attributes: [
            { name: "data-hover_color", values: ['#f68a00', '#069', '#a70', '#090', '#ff1493', '#0066CC', '#ff1493', '#F0E356'] }
          ],
          hover_on: function(splat) {
            var original_color = splat.css('color');
            var hover_color = splat.attr('data-hover_color')
            splat.css('color', hover_color).attr('data-hover_color', original_color);
          },
          hover_off: function(splat) {
            var original_color = splat.css('color');
            var hover_color = splat.attr('data-hover_color');
            splat.css('color', hover_color).attr('data-hover_color', original_color);
          }
        });
    

And add some CSS3 to handle the color transitions:

        .splatter_box .splat {
          transition: color 0.5s linear;
          -moz-transition: color 0.5s linear;
          -o-transition: color 0.5s linear;
          -webkit-transition: color 0.5s linear;
          cursor: pointer;
        }
    

Which will create something like:

Example with color transitions

If you need additional help understanding the various configuration options, check out the live demos as well as the unit tests.

Download the project from GitHub

11 Comments


Making Breakout with Processing.js.

As few months back, I spent the weekend reverse engineering breakout with Processing.js. If you haven’t had a chance to spend some time with this language, you really should. Processing is an easy-to-learn, open-source programming language for making images, animation, and interactions.

Click the image below to play the demo. Control paddle using your mouse.


Breakout Screenshot

Here’s a high level overview of the code. Like any processing script, we start by initializing some variables:

      // --- colors ---
      color black = color(0);
      color bright_green = color(110,212,111);
      color pink = color(190,81,163);
      color purple = color(97,81,190);
      color light_blue = color(142, 185,234);
      color yellow = color(212,197,110);

      // --- initialize arrays for bricks ---
      Brick[] pink_bricks = new Brick[10];
      Brick[] green_bricks = new Brick[10];
      Brick[] navy_bricks = new Brick[10];
      Brick[] blue_bricks = new Brick[10];
      Brick[] yellow_bricks = new Brick[10];
      array[] bricks = new array[5];
    

Then we do some configuration in our setup block:

      void setup() {
       size(400,400);
       frameRate(80);

       // initialize game, paddle, ball
       game = new Game();
       paddle = new Paddle();
       ball = new Ball(200, 375);

       // initialize bricks
       for (int a=0; a<10; a++) {
           pink_bricks[a] = new Brick(a*40, 0, pink);
           green_bricks[a] = new Brick(a*40, 20, bright_green);
           navy_bricks[a] = new Brick(a*40, 40, purple);
           blue_bricks[a] = new Brick(a*40, 60, light_blue);
           yellow_bricks[a] = new Brick(a*40, 80, yellow);
       }

       // combine all bricks into single array of arrays
       bricks[0] = pink_bricks;
       bricks[1] = green_bricks;
       bricks[2] = navy_bricks;
       bricks[3] = blue_bricks;
       bricks[4] = yellow_bricks;
      }

Then we have a draw loop. This is an endless loop where we can place our game logic:

      void draw() {
       background(black);

       // draw bricks
       game.build(bricks);

       // draw paddle and ball
       paddle.display(mouseX);
       ball.display(); 

       // animate ball
       if (game.active) {
           ball.move();
       } else {
           ball.rest_on_paddle(paddle);
           game.reset(bricks);
           $('#messages #start').show();
       }

       // if ball goes off screen
       if ( ball.off_screen() ) {
           game.active = false;
       }

       // if ball hit paddle
       if ( ball.hit_paddle(paddle) ) {
           ball.rebound_off(paddle);
       }

       // if ball hits bricks
       if (ball_y <= 100) {
           hit_brick = game.determine_colision(ball, bricks);

           if (hit_brick.is_active()) {
               hit_brick.deactivate();
               ball.bounce_off_brick(hit_brick);
               game.increment_points(10);
           }
       }
      }
    

Finally we have a simple keyPressed function to allow the user to pause the game.

      void keyPressed() {
       // when user presses space bar
       if (key == 64) {
           if ( ball.is_in_start_position() ) {
               ball.move_upward(); // start game
               game.active = true;
               $('#messages #start').hide();
           } else {
               ball.toggle_pause(); // pause/resume game
           }
       }
      }
    

From here on out, it's pretty simple. Everything is object oriented in a sensible manner. First, we have a game class. This holds all the application wide functionality as well as things that generally don't fit well into other classes but also don't merit a separate class (e.g. points):

      class Game {
       int points;
       int level;
       boolean active;

       Game() {
           points = 0;
           level = 1;
           active = false;
       }

       void build(bricks) {
           for (int i=0; i < bricks.length; i++) {
               for (int x=0; x < bricks[i].length; x++) {
                   bricks[i][x].display();
               }
           }
       }

       void reset(bricks) {
           for (int i=0; i < bricks.length; i++) {
               for(int x=0; x < bricks[i].length; x++) {
                   brick = bricks[i][x];
                   brick.brick_active = true;
                   brick.brick_color = brick.original_color;
               }
           }
       }

       void increment_points(points_earned) {
           points += points_earned;
           $('#game_stats #points span').html(points);
       }

       void determine_colision(ball, bricks) {
           y = ball.show_ball_y();
           x = ball.show_ball_x();     

           // find brick row based on ball location
           if      (y >= 0 && y <=  20) { row = 0; }
           else if (y > 20 && y <=  40) { row = 1; }
           else if (y > 40 && y <=  60) { row = 2; }
           else if (y > 60 && y <=  80) { row = 3; }
           else if (y > 80 && y <= 100) { row = 4; }

           // find brick column based on ball location
           if      (x >=  0 && x <=  40) { col = 0; }
           else if (x >  40 && x <=  80) { col = 1; }
           else if (x >  80 && x <= 120) { col = 2; }
           else if (x > 120 && x <= 160) { col = 3; }
           else if (x > 160 && x <= 200) { col = 4; }
           else if (x > 200 && x <= 240) { col = 5; }
           else if (x > 240 && x <= 280) { col = 6; }
           else if (x > 280 && x <= 320) { col = 7; }
           else if (x > 320 && x <= 360) { col = 8; }
           else if (x > 360 && x <= 400) { col = 9; }

           return bricks[row][col];
       }
      }
    

Next, we have a ball class...

      class Ball {

       Ball(temp_ball_x, temp_ball_y) {
           ball_diameter = 10;
           ball_x = temp_ball_x;
           ball_y = temp_ball_y;
           speed_x = 0;
           speed_y = 0;
           speed_max = 4;
           pre_pause_speed  = [0,0];
       }

       void display() {
           noStroke();
           fill(bright_green);
           ellipse(ball_x, ball_y, ball_diameter, ball_diameter);
       }

       void move() {
           // make ball bounce off ceiling
           if (ball.edge("top") < 0) {
               speed_y = speed_y * -1;
           }
           // make ball bounce off either sidewall
           if ( ball.edge("left") < 0 || ball.edge("right") > width ) {
               speed_x = speed_x * -1;
           }
           // animate ball
           ball_x += speed_x;
           ball_y += speed_y;
       }

       void toggle_pause() {
           if (speed_x == 0 && speed_y == 0) {
               speed_x = pre_pause_speed[0];
               speed_y = pre_pause_speed[1];
               $('#messages #pause').hide();
           } else {
               pre_pause_speed[0] = speed_x;
               pre_pause_speed[1] = speed_y;
               speed_x = 0;
               speed_y = 0;
               $('#messages #pause').show();
           }
       }

       void rest_on_paddle(paddle) {
           // stop ball from moving
           speed_x = 0;
           speed_y = 0;
           // constrain resting position to keep ball on center of paddle
           resting_position = constrain(mouseX, paddle.half_width(), width - paddle.half_width());
           // place ball atop paddle
           ball_y = 375;
           ball_x = resting_position;
       }

       void is_in_start_position() {
           if ( ball_y == 375 && (speed_y == 0 && speed_x == 0) ) {
               return true;
           } else {
               return false;
           }
       }

       void move_upward() {
           speed_x = 0;
           speed_y = -3;
       }

       void bounce_off_brick(brick) {
           bcp = brick.center_point();
           distance_at_bounce = dist(bcp[0],bcp[1], ball_x,ball_y);

           if (distance_at_bounce >= 20) {
               speed_x = speed_x * -1;
           } else {
               speed_y = speed_y * -1;
           }
       }

       void edge(edge) {
           if (edge == "top") {
               ball_edge = ball_y - 5;
           } else if (edge == "bottom") {
               ball_edge = ball_y + 5;
           } else if (edge == "left") {
               ball_edge = ball_x - 5;
           } else if (edge == "right") {
               ball_edge = ball_x + 5;
           } else {
               return console.log("Argument Error - Valid arguements: top, bottom, left, right");
           }
           return ball_edge;
       }

       void off_screen() {
           if (ball_y > height) {return true;} else {return false;}
       }

       void hit_paddle(paddle) {
           if (ball_y > paddle.edge("top") && (ball_x > paddle.edge("left") && ball_x < paddle.edge("right") )) {
               return true;
           } else {
               return false;
           }
       }

       void rebound_off(paddle) {
           // cacluate distance between point of contact and center of paddle
           bounce_point = dist(
               ball_x, paddle.show_paddle_y(), // point of contact
               paddle.show_paddle_x(), paddle.show_paddle_y() // center of paddle
           );

           // calculate rebound angle relative to bounce point
           if (ball_x < paddle.show_paddle_x()) {
               speed_x = (bounce_point * 0.05) * -1;
           } else {
               speed_x = bounce_point * 0.05;
           }

           // calculate y speed relative to rebound angle
           speed_y = (speed_max - abs(speed_x)) * -1;
       }

       void show_ball_x() {
           return ball_x;
       }

       void show_ball_y() {
           return ball_y;
       }

      }
    

Then a brick class...

       color brick_stroke;
       color brick_color;
       color original_color;
       int brick_width;
       int brick_height;
       int brick_x;
       int brick_y;
       boolean brick_active;   

       Brick(temp_brick_x, temp_brick_y, temp_color) {
           brick_stroke = black;
           brick_color = temp_color;
           brick_width = 40;
           brick_height = 20;
           brick_x = temp_brick_x;
           brick_y = temp_brick_y;
           brick_active = true;
           original_color = temp_color;
       }

       void display() {
           rectMode(CORNER);
           stroke(brick_stroke);
           fill(brick_color);
           rect(brick_x, brick_y, brick_width, brick_height);
       }

       void is_active() {
           return brick_active;
       }

       void deactivate() {
           brick_active = false;
           brick_color = black;
       }

       void center_point() {
           return [brick_x + brick_width/2, brick_y + brick_height/2 ];
       }
      }
    

Finally, a paddle class...

      class Paddle {
       int paddle_width;
       int paddle_height;
       color paddle_color;
       float paddle_x;
       float paddle_y;

       Paddle() {
           paddle_width = 80;
           paddle_height = 7;
           paddle_color = bright_green;
           paddle_x = width/2;
           paddle_y = 385;
       }

       void display() {
           // determine paddle_y and constrain so paddle cannot leave screen
           paddle_x = constrain(mouseX, paddle_width/2, width - paddle_width/2);

           // draw paddle
           rectMode(CENTER);
           noStroke();
           fill(paddle_color);
           rect(paddle_x, paddle_y, paddle_width, paddle_height);
       }

       void edge(edge) {
           if (edge == "left") {
               return paddle_x - (paddle_width/2);
           } else if (edge == "right") {
               return paddle_x + (paddle_width/2);
           } else if (edge == "top") {
               return paddle_y -5;
           } else {
               return console.log("Argument Error - Valid arguements: left, right, top");
           }
       }

       void half_width() {
           return paddle_width/2;
       }

       void show_paddle_x() {
           return paddle_x;
       }

       void show_paddle_y() {
           return paddle_y;
       }
      }
    

Download the project from Github

1 Comment


« Older Entries | Newer Entries »