 
Late November I made this Wooden Dowel Christmas Tree with advent-style felt ornaments (with some talented friends helping with all the ornaments).
The tree is collapsible (you just pull the branches out) which is useful for the rest of the year when its not Christmas!
 
 
Even Steven is now available as a free download on the App Store
Feedback welcomed via Twitter
Thanks to people that helped test it!
 
Update Now available on the App Store!
Even Steven is a handy app that records group spending for events such as holidays and suggests who needs to pay what to whom in order to get even!
Even Steven is designed for iPhone and:
Behind the scenes, Even Steven is a pure Swift3 iOS app and uses no third party libraries.
... ➦
Still horrendous at making things with my hands but will keep trying! This is a tealight holder that is basically three bits of wood, two drill holes and glue.
Tensorflow seems like a pretty exciting alternative to scikit-learn that works just as effectively for beginners as it does for pros.
For my team’s monthly drink’n’sync I wrote an interactive worksheet on using TensorFlow to build a handwritten number detector.
Check it out on github!
This is a crude example of a playable game that can be expressed in less than 250 lines of code!
  1<!DOCTYPE html>
  2<html>
  3<body>
  4  <canvas id="game"> </canvas>
  5  <script type="text/javascript">
  6    var game = (function() {
  7      var Width = 800, Height = 450;
  8      var canvas = document.getElementById("game");
  9      var FPS = 1000 / 60;
 10      canvas.width = Width;
 11      canvas.height = Height;
 12      canvas.setAttribute('tabindex', 1);
 13      var ctx = canvas.getContext("2d");
 14
 15      var BG = {
 16        Color: '#333',
 17        Paint: function() {
 18          ctx.fillStyle = this.Color;
 19          ctx.fillRect(0, 0, Width, Height);
 20        }
 21      };
 22
 23      var Ball = { Radius: 5, Color: '#999', X: 0, Y: 0, VelX: 0, VelY: 0 };
 24      Ball.Paint = function() {
 25        ctx.beginPath();
 26        ctx.fillStyle = this.Color;
 27        ctx.arc(this.X, this.Y, this.Radius, 0, Math.PI * 2, false);
 28        ctx.fill();
 29        this.Update();
 30      };
 31      Ball.Update = function() {
 32        this.X += this.VelX;
 33        this.Y += this.VelY;
 34      };
 35      Ball.Reset = function() {
 36        this.X = Width / 2;
 37        this.Y = Height / 2;
 38        this.VelX = (!!Math.round(Math.random() * 1) ? 1.5 : -1.5);
 39        this.VelY = (!!Math.round(Math.random() * 1) ? 1.5 : -1.5);
 40      };
 41
 42      function Paddle(position) {
 43        this.Color = '#999';
 44        this.Width = 5;
 45        this.Height = 100;
 46        this.X = 0;
 47        this.Y = Height / 2 - this.Height / 2;
 48        this.Score = 0;
 49        if (position == 'left')
 50          this.X = 0;
 51        else this.X = Width - this.Width;
 52        this.Paint = function() {
 53          ctx.fillStyle = this.Color;
 54          ctx.fillRect(this.X, this.Y, this.Width, this.Height);
 55          ctx.fillStyle = this.Color;
 56          ctx.font = "normal 10pt Calibri";
 57          if (position == 'left') {
 58            ctx.textAlign = "left";
 59            ctx.fillText("score: " + Player.Score, 10, 10);
 60          } else {
 61            ctx.textAlign = "right";
 62            ctx.fillText("score: " + Computer.Score, Width - 10, 10);
 63          }
 64        };
 65        this.IsCollision = function() {
 66          if (Ball.X - Ball.Radius > this.Width + this.X || this.X > Ball.Radius * 2 + Ball.X - Ball.Radius)
 67            return false;
 68          if (Ball.Y - Ball.Radius > this.Height + this.Y || this.Y > Ball.Radius * 2 + Ball.Y - Ball.Radius)
 69            return false;
 70          return true;
 71        };
 72      };
 73
 74      window.requestAnimFrame = (function() {
 75        return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
 76          return window.setTimeout(callback, FPS);
 77        };
 78      })();
 79      window.cancelRequestAnimFrame = (function() {
 80        return window.cancelAnimationFrame || window.webkitCancelRequestAnimationFrame || window.mozCancelRequestAnimationFrame || window.oCancelRequestAnimationFrame || window.msCancelRequestAnimationFrame || clearTimeout
 81      })();
 82
 83      var Computer = new Paddle();
 84      var Player = new Paddle('left');
 85
 86      function Paint() {
 87        ctx.beginPath();
 88        BG.Paint();
 89        Computer.Paint();
 90        Player.Paint();
 91        Ball.Paint();
 92      }
 93
 94      function MouseMove(e) {
 95        Player.Y = e.pageY - Player.Height / 2;
 96      }
 97      canvas.addEventListener("mousemove", MouseMove, true);
 98
 99      function Loop() {
100        init = requestAnimFrame(Loop);
101        Paint();
102        if (Player.IsCollision() || Computer.IsCollision()) {
103          Ball.VelX = Ball.VelX * -1;
104          Ball.VelX += (Ball.VelX > 0 ? 0.5 : -0.5);
105          if (Math.abs(Ball.VelX) > Ball.Radius * 1.5)
106            Ball.VelX = (Ball.VelX > 0 ? Ball.Radius * 1.5 : Ball.Radius * -1.5);
107        }
108        if (Ball.Y - Ball.Radius < 0 || Ball.Y + Ball.Radius > Height)
109          Ball.VelY = Ball.VelY * -1;
110        if (Ball.X - Ball.Radius <= 0) {
111          Computer.Score++;
112          Ball.Reset();
113        } else if (Ball.X + Ball.Radius > Width) {
114          Player.Score++;
115          Ball.Reset();
116        }
117        if (Computer.Score === 10)
118          GameOver(false);
119        else if (Player.Score === 10)
120          GameOver(true);
121        Computer.Y = (Computer.Y + Computer.Height / 2 < Ball.Y ? Computer.Y + Computer.Vel : Computer.Y - Computer.Vel);
122      };
123
124      function GameOver(win) {
125        cancelRequestAnimFrame(init);
126        BG.Paint();
127        ctx.fillStyle = "#999";
128        ctx.font = "bold 40px Calibri";
129        ctx.textAlign = "center";
130        ctx.fillText((win ? "YOU WON!" : "GAME OVER"), Width / 2, Height / 2);
131        ctx.font = "normal 16px Calibri";
132        ctx.fillText("refresh to replay", Width / 2, Height / 2 + 20);
133      }
134      return {
135        Start: function() {
136          Ball.Reset();
137          Player.Score = 0;
138          Computer.Score = 0;
139          Computer.Vel = 1.25;
140          Loop();
141        }
142      };
143    })();
144    game.Start();
145  </script>
146</body>
147</html>My team recently switched our applications from Heroku to barebones AWS. The experience made me truely appreciate just how effective Heroku is and how it has made deploying apps as painfree as could be. After all, what good is making an app if nobody can see it!
I’ve also been a huge fan of my Raspberry Pi. Every time I look at it, I feel challenged to think of a cool use for it.
... ➦