Project 14: Pixel Play with Firebase
July 09, 2015 |
Pixel drawing goes back to the good (tougher?) old days of programming where you drew graphics pixel by pixel. With a basic palette of colors, it is a fun challenge to draw things dot by dot on a small pixel canvas.
For this fourteeth project of my 15 projects in 30 days challenge, I’m going to build a real-time pixel drawing game that can be used by multiple players at the same time. The backend will use Firebase’s API to distribute changes made on the board to all connected clients.
Firebase
To get started, sign up for a Firebase account. Once you have an account, create an app. You will be given two URLs, a data URL which is what the app uses to communicate with Firebase to store and retrieve data, and a separate URL where you can access an application that you deploy to Firebase.
Setup
There are four files in this project, which make up the AngularJS app. There is no backend Node.js app for this project because Firebase takes care of the backend.
// Filename: public/pixelplay.js
var FIREBASE_ENDPOINT = '';
angular.module('PixelPlayApp', ['firebase', 'ngRoute'])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'lobby.html',
controller: 'LobbyCtrl'
}).
when('/play/:boardId', {
templateUrl: 'play.html',
controller: 'PlayCtrl'
}).
otherwise({
redirectTo: '/'
});
}])
.controller('LobbyCtrl', ['$scope', '$location', '$firebaseArray', function($scope, $location, $firebaseArray) {
$scope.title = '';
$scope.width = $scope.height = 8;
var ref = new Firebase(FIREBASE_ENDPOINT);
$scope.newBoard = function() {
var row = [];
for(var i=0; i<parseInt($scope.width); i++) {
row.push('');
}
var data = [];
for(var i=0; i<parseInt($scope.height); i++) {
data.push(row);
}
$scope.boards.$add({title: $scope.title, data: data}).then(function(ref) {
$location.path('/play/'+ref.key());
});
}
$scope.boards = $firebaseArray(ref.child('boards'));
}])
.controller('PlayCtrl', ['$scope', '$routeParams', '$firebaseObject', function($scope, $routeParams, $firebaseObject) {
$scope.colors = [
{name: 'Red', hex: 'FF0000'},
{name: 'Blue', hex: '0000FF'},
{name: 'Green', hex: '00FF00'},
{name: 'Yellow', hex: 'FFFF00'},
{name: 'Purple', hex: '800080'},
{name: 'White', hex: 'FFFFFF'},
{name: 'Black', hex: '000000'},
];
$scope.currentColor = $scope.colors[0].hex;
$scope.setColor = function(hex) {
$scope.currentColor = hex;
}
var ref = new Firebase(FIREBASE_ENDPOINT);
$scope.board = $firebaseObject(ref.child('boards/'+$routeParams.boardId));
$scope.click = function(row, cell) {
$scope.board.data[row][cell] = $scope.currentColor;
$scope.board.$save();
}
}]);
<!-- Filename: public/index.html -->
<!DOCTYPE html>
<html ng-app="PixelPlayApp">
<head>
<title>Pixel Play</title>
<script src="https://cdn.firebase.com/js/client/2.2.7/firebase.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.16/angular.min.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.1.1/angularfire.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular-route.min.js"></script>
<script src="pixelplay.js"></script>
</head>
<body style="font-family:Arial">
## <a href="#/" style="color:black; text-decoration:none">Pixel Play</a>
<div ng-view></div>
</body>
</html>
<!-- Filename: public/lobby.html -->
<h2>Boards</h2>
<div ng-show="boards.length == 0">No games available.</div>
<div ng-repeat="board in boards">
<a href="#/play/{{board.$id}}" ng-click="joinBoard(board)">{{board.title}} {{board.data.length}} x {{board.data[0].length}}</a>
</div>
<h2>New Board</h2>
Board title: <input type="text" ng-model="title" /><br />
Size: <input type="number" ng-model="width" value="1" min="1" max="10" /> (width) X <input type="number" ng-model="height" value="1" min="1" max="10" /> (height)
<input type="button" ng-click="newBoard()" value="New Board" />
<!-- Filename: public/play.html -->
<h2>{{board.title}} ({{board.data.length}} x {{board.data[0].length}})</h2>
Colors: <input type="button" ng-repeat="color in colors" value="{{color.name}}" ng-click="setColor(color.hex)" style="{{currentColor == color.hex ? "background-color: yellow" : "" }}" />
<table border="1" cellspacing="0">
<tr ng-repeat="row in board.data">
<td ng-repeat="cell in row track by $index" ng-click="click($parent.$index, $index)" style="width:50px; height:50px; background-color: #{{cell.length == 0 ? "FFFFFF" : cell}}">
&nbsp;
</td>
</tr>
</table>
In pixelplay.js, add the Firebase data URL for your app to the FIREBASE_ENDPOINT variable.
You can access the index.html file directly on a webserver. However, Firebase can also host this application. In order to do this, install the Firebase command line tools by running the command:
npm install -g firebase-tools
Initialize the directory with Firebase:
firebase init
After you provide your user credentials, you’ll be asked which Firebase app you want to use for hosting. Lastly, specify the public directory, or document root, for the Firebase app. If everything works out, the app will be initialized successfully. To deploy it, run the command:
firebase deploy
You can access the app using the Site URL.
Pixel Play
You should see something like this when you visit the Site URL.
Before I continue, open another browser tab to point to the Firebase Data URL. This isn’t required, but it is a great way to visualize the data behind the scenes and see changes happen in realtime. Since no boards have been created, there is no data yet.
Back to the app, create a new board by entering a title. You can choose the dimension of the board, by default an 8X8 grid.
A new board will be created in Firebase, which you can view in the Data Dashboard. The browser will redirect to another view and load the board.
Copy the URL that the browser is redirected to after the board is created. It contains the board id that represents the board in Firebase. Open another browser tab, and visit this URL. You can visit it on any computer or device. Click on a color and click on a square on the board. The square turns that color, and all connected clients also automatically change. Continue clicking on squares to color a picture.
If you look at the Data Dashboard, you can see the board data has changed. If a cell is colored, its value is the HEX representation of the color.
That’s it for this project. This project is a great base for some expansions:
- Add a timer and a name of a random (but drawable) object that participants should draw collaboratively together. It becomes a challenge if there is no communication to collectively decide how players can work together.
- Limit colors that each participant can color with. Again, this can be a challenge to draw something together with limited communcations and abilities.
- Add user accounts with names of participants so others know who's on the board. Add a status bar with who performed the last action.
Source Code
You can find the repo on GitHub.
Post Mortem
I have to say Firebase now tops my list of awesome developer tools. It is so simple to sync data across clients in a fraction of a second. On top of that, the Data Dashboard makes it easy to visualize the data and manage any data.
AngularFire also abstracts all the little details of integrating Firebase and AngularJS together.
15 Projects in 30 Days Challenge
This blog post is part of my 15 projects in 30 days challenge. I’m hacking together 15 projects with different APIs, services, and technologies that I’ve had little to no exposure to. If my code isn’t completely efficient or accurate, please understand it isn’t meant to be complete and bulletproof. When something is left out, I try to mention it. Reach out to me and kindly teach me if I go towards the dark side. ?
This challenge serves a couple of purposes. First, I’ve always enjoyed hacking new things together and using APIs. And I haven’t had the chance (more like a reason) to dive in head first with things like AngularJS and Firebase. This project demonstrated AngularJS and Firebase.