Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

2. June 2014

 

So far in working with TypeScript I’ve exclusively used Visual Studio and that hasn’t been entirely trouble free.  There are many people out there that wont have access to Visual Studio ( working on Mac or Linux perhaps? ) or simply wont want to.  Thankfully there is a great alternative, WebStorm.  Granted WebStorm isn’t free, although there is a 30 day trial.  It is however a very well spent $50 if you are working in JavaScript ( or TypeScript ).  This post looks at working in TypeScript with Webstorm.

 

First step of course it download and install WebStorm.  The trial is fully functioning by the way.

During the install, if you havent already, you will be prompted to install Java 6.  Don’t worry, WebStorm takes care of the process for you.

Next you need to install Node.  Node.js is a JavaScript environment that works outside of the browser.  I’ve worked with Node a number of times on this site.  Install Node using the default settings.

Now open up a terminal/command prompt and type npm install –g typescript

The results should look like:

image

 

This installed the TypeScript language.  We are now ready to go.

 

When you open a project with a typescript file a file watcher should kick in automatically.  If not, its easy enough to define one.

Select File->Settings.  ( This menu has a different location on MacOS I believe )

On the left hand side, locate File Watchers:

image

 

On the right hand side, if none exist for TypeScript, click plus.  If one exists, make sure its checked.

image

Then select TypeScript from the list:

image

 

Default values should be correct:

image

 

If you have any problems at this point, make sure that Node was installed correctly and that you installed TypeScript, these are the most common problems.

 

Now with a File Watcher created, whenever you save a change to a TS file, it while automoatically compile the JS file.  Like so:

image

 

From this point on you will be able to see full syntax highlighting as well as code completion:

image

 

 

Additionally, you can set a breakpoint in WebStorm:

image

 

And assuming you’ve enabled the WebStorm plugin, you can debug in Chrome:

image

 

Which then allows you to perform the standard debugging tasks in WebStorm:

image

 

If you are coming from Visual Studio, there is one major difference to be aware of.  In Visual Studio, adding a file to your project makes it available for code completion.  In WebStorm this isn’t the case.  If you include a library, such as Phaser, you need to add a reference identifier at the top of your ts file, like so:

 

/// <reference path="phaser.d.ts"/>

 

Then code completion will work properly.

Programming ,

31. July 2012

 

I had intended to end this series on part 4, all I had left to do was add a layer of persistence because Heroku didn’t keep files for more than a few hours, which was a pretty heavy limitation.  So I looked into various storage options, and I ended up going with CouchDB.  The process was a bit more involved ( and interesting ) than I suspected, so I decided to add another post in the series.  This part covers setting up cloud based database, and changing our code to persist to the database.

 

First off, I went to IrisCouch.com and signed up for a free database.  They provide (free!) cloud hosted CouchDB installs, including a full Futon management system, pictured below:

image

 

You can use this interface to create new databases, configure security etc.  It’s a bit tricky at times to navigate, but it gets the job done.

 

I created a new database called firstthis, then immediately secured it ( by default your website is publically accessible to everyone! ).  CouchDB works by storing information in documents instead of tables in a traditional database.  Instead of updating individual fields, you update the data in your document then replace the entire thing.  Tracking the newest revision becomes incredibly important when working with CouchDB.  Perhaps most key of all, CouchDB adds two fields to your data, _id and _rev.  _rev represents the newest revision, while _id represents the unique key.  In our case, for our user settings, we are going to use their email address as the key.  We simply store the files variable from our script to the server.  Here is a sample of files stored in CouchDB ( shown in the iriscouch admin page ):

 

image

 

As you can see, its simply our JavaScript variable, with an _id and _rev added. 

 

When you sign up for IrisCouch, you are given a URL where your database server is located, in the form of yoursite.iriscouch.com.

 

Now let’s take a look at the code differences.  It is pretty thoroughly documented ( combined with the above explaination ), so I won’t go into a lot of detail.  CouchDB is accessed using REST requests, but I instead used a node library Nano for access.  This was a bit of a double edged sword, as it took away a great deal of the complexity and grunt work, however it also removed me a step away from the well documented CouchDB. 

 

All of the code changes are in server.js

var express = require('express'), server = express.createServer(), im = require('imagemagick'), nano = require('nano')('http://Serapth:meat32ball@gfs.iriscouch.com'), db_name = "firstthis", db = nano.use(db_name), userEmail = 'mike@gamefromscratch.com', fs = require('fs'), files = { files:{}}; // Helper functions for getting and inserting docs in couchDB using nano function get_doc(docName,res){ db.get(docName,function(err,body){ if(!err) { res(body); } }); }; // There is no update in CouchDB. Just inserts, inserts and more inserts. // If you don't have the most current rev ( or it isn't a new insert ), an error (HTTP409) will occur. // TODO: Real error handling, attempt to get latest file, get it's rev, then try inserting again function insert_doc(doc,docname, tried) { db.insert(doc,docname, function (error,val,newval) { if(error) { return console.log(error); } // The insert will result in an updated rev, update our local files var to the most current rev. return files._rev = val.rev; }); } // Setup server static paths server.use('/cocos2d', express.static(__dirname + '/cocos2d') ); server.use('/cocosDenshion', express.static(__dirname + '/cocosDenshion') ); server.use('/classes', express.static(__dirname + '/classes') ); server.use('/resources', express.static(__dirname + '/resources') ); // Install the bodyParser middleware, which enables form data to be parsed when an upload occurs. server.use(express.bodyParser()); // Handle requests for / by returning index.html server.get('/', function(req,res){ res.sendfile('index.html'); console.log('Sent index.html'); }); // Handle requests for /settings by returning settings.html server.get('/settings',function(req,res){ res.sendfile('settings.html'); console.log('Send settings.html'); }); // Handle requests for images will be the form of site.com/image/imagename.png // Fetchs the image data from CouchDB and returns to user server.get('/image/:name', function(req,res){ if(files.files[req.params.name]) { res.contentType(files.files[req.params.name].contentType); db.attachment.get(userEmail + "/" + files.files[req.params.name].name,files.files[req.params.name].name).pipe(res); } }); // Uses ImageMagick to get image dimensions and return them as JSON data // This is to work around the requirement for Cocos2D sprites to know dimensions before loading image server.get('/imageSize/:name',function(req,res){ im.identify(files.files[req.params.name].path,function(err,features){ if(err) throw err; else res.json({ "width":features.width, "height":features.height }); }); }); // This gets the photo data, which is contained in our files variable. Simply return it JSON encoded server.get('/getPhotos', function(req,res){ res.json(files.files); }); // Erase all images : TODO: Remove images from database as well!!!! server.get('/clearAll', function(req,res){ files.files = {}; res.statusCode = 200; res.send(""); }) // Unfortunately there is no easy way to tell when a multi file upload is complete on the server, On('end') isnt called // Therefore we call /doneUpload from the client once we are done uploading. // Once we are done uploading files, we save our updated Files var up to couchDB, then get it again so it again immediately to have the most current rev server.get('/doneUpload', function(req,res){ insert_doc(files,userEmail,files._rev,0); get_doc(userEmail,function(res) { files = res; }); res.statusCode = 200; res.sendfile('settings.html'); }) server.post('/upload',function(req,res){ // This method is going to be called for each attached file. // Add the file details to files.files[] with the key set to the filename files["files"][req.files.Filedata.name] = { "name":req.files.Filedata.name, "path":req.files.Filedata.path, "size":req.files.Filedata.size, "contentType":req.files.Filedata.type, "description":req.body.description }; // Now read the file from disk and insert the file data in our CouchDB as an attachment named "emailAddress/filename.png" fs.readFile(req.files.Filedata.path,function(err,data){ if(!err){ db.attachment.insert(userEmail + "/" + req.files.Filedata.name,req.files.Filedata.name,data,req.files.Filedata.type , {}, function(err,body){ console.log(body); res.statusCode = 200; res.send(""); }); } }); }); server.listen(process.env.PORT || 3000); // Check the couchDB for an entry keyed to the users email address. If one exists, copy it into our files var get_doc(userEmail,function(results){ if(results){ files = results; } });

 

Now when you upload images, they will be stored to your CouchDB database on IrisCouch.  When you restart Node, it will automatically grab and populate files from the database. Note, the application from parts 1-4 aren’t updated to use this new code.

Keep in mind, this code isn’t production ready… error handling is sparse, there is no authentication, it would be easy to exploit with a DoS attack for example.  However, if you are interested in storing data from your Node App in a CouchDB database, I hope this sample was useful.

Programming , , ,

29. July 2012

 

I recently completed work on a tutorial series A Simple JavaScript Game using Node, cocos2D, YUI and Heroku, which followed the creation of a simple app for my own use.  It was a pretty complete application with one glaring fault…  there was no persistence.  The application was hosted using Heroku’s free tier which doesn’t keep files for more then a few hours.  This obviously leads to a bit of a problem.

 

So I have been looking into the myriad of options for persistence with Node. There are a few options, all of which have various advantages and disadvantages.

 

The easiest solution would probably be some persistent storage like Amazon’s S3 or even my local file system ( and move the application from Heroku to my servers that are running on Windows Server ).  There is nothing wrong with either solution, but I don’t really want to add more load to my servers with a technology I am still learn learning, least of all to minimize security issues.  Also, I started thinking I wanted a bit more database functionality as I may be adding more functionality.

 

Once I start thinking Database + Node, that changes the landscape quite a bit.  I am already running SQL Server, and amazingly enough Microsoft has been embracing and contributing to Node, including a driver for MS SQL Database with Node.  However a) it is extremely early in development b) the syntax looks… wordy and crude, hopefully this improves massively, because accessing data values by offsets seems so very… retro.

 

In the world of Node, there seems to be 3 front runners:

 

Redis

MongoDB

CouchDB

 

All of them have strengths and weaknesses.  All three are part of the NoSQL movement, but each approaches things quite differently.

 

Redis is stored entirely in memory ( but syncs to disk ), and works with key/value pairs.  It is not ideal for file storage, but is wonderful for quickly storing away JavaScript objects.  Also, Heroku has Redis support as an addon.  Now the downside, Redis isn’t available on Windows, at least not in a supported capacity.  As I develop using both Windows and Ubuntu, this was pretty much a deal breaker for me.

MongoDB is another NoSQL option and to be honest, I forget why I didn’t go with it, at least not initially.  I know I didn’t particularly want to install the underlying DB server, but it was at least supported on Windows.

CouchDB is what I ultimately went with.  It is another NoSQL database, but it could probably be best considered a document store, that stores JSON documents ( and other files ).  Given the nature of my application ( serving lots of files that don’t often change ), this is actually a very good thing.  That said, my SQL trained brain is having a whole lot of difficulty dealing with the change in mindset.  Storing “data” in documents that aren’t in fact documents seems horrifically unnatural to me.  Worse, I am really having trouble coming to grips with the idea of not being able to delete versions!  The idea that every time I change data it creates a new document, there is no update, only inserts.  These seems horrifically inefficient, but I have to assume I am thinking about things wrong.

 

What ultimately sold me on CouchDB was the low barrier of entry in the form of Iris Couch, which is a cloud hosted Couch DB, with a very generous free option.  Like Heroku, having someone else handle the heavy lifting is always enjoyable.

 

Being new to NoSQL, I am still going through the learning curve, so there is nothing to say I will stay with CouchDB, but I will say, I have gotten some impressive results very quickly.  I really wish Redis was available on Windows, as I would probably use redis for “data” and Couch for documents.  Anyone have alternative suggestions?

Totally Off Topic ,

25. July 2012

 

This post is a table of contents of sorts for the recently completed series documenting the creation of a simple game (web application) for my daughter.  Although the game is quite simple, the application itself covers quite a bit.  There are a ton of tutorials on the internet about the various individual pieces I use, but very few illustrating putting them all together to create a complete app.

 

In this tutorial, we cover:

 

Part 1 -- Node and cocos2D

Setting up a NodeJS server, that is able to serve both static and dynamic content using express.  By the end of this part we are successfully hosting a cocos2D application.

 

Part 2 -- Deploying to Heroku

This part covers deploying your Node application into the cloud, using Heroku’s completely free tier.  This part is optional, you can run your application anywhere you want so long as Node is supported.

 

Part 3 – The guts and plumbing

This part is the heart of the application itself.  It illustrates how to upload and serve data from a Node server.  The upload portion is managed using the YUI framework from Yahoo.  This is how you could make a more traditional web application using JavaScript and illustrates.

 

Part 4 – The Game

This part creates the actual “game” if you can call it that.  It illustrates creating a simple cocos2D HTML game that interacts with the NodeJS server side.

 

Part 5 – Adding a Database to the mix

Losing all your data every time Node restarts or Heroku feels like erasing them gets old quick, so I added a database to the mix.  In this case I used the CouchDB NoSQL database, hosted on IrisCouch using the Nano library.

 

Part 6 – Phonegap?

Ok, this part is actually TBD.  I am in the process of porting to PhoneGap, to bundle this application as a native phone application.  Will update here if it was successful with another tutorial post

 

 

 

The Results

 

You can see the application running here

 

It is pretty simple over all.  Choose a pair of images using the dropdowns at the top.  Then you can click to cycle through the various images.  Additionally, you can click the settings button, which will bring you to the settings app we created in Part 3.  Here you can upload new images and manage existing one.   Warning, anyone can upload images, so I take no responsibility for what they might contain!  Anyone can also delete images at any times, so if it is empty or your images disappear, this is probably why.

 

Finally, I have pushed the complete source tree up to GitHub, which is available here.

General , , , ,

21. July 2012

Now that we have a cocos2D app being served from NodeJS and being hosted in the cloud we now need to get down to the application itself.

 

One of the biggest parts of this app is actually the plumbing behind the scenes.  Essentially I need to be able to upload images to a server and retrieve them on demand.  For each image I need to be able to add a description.  So that is what we are going to create here.

 

If you were ever wondering how to create a full web application using just JavaScript on the client and server, the end result of this exercise is going to show you exactly that.

 

There are a few warnings before hand!

 

1- Heroku doesn’t persist files.  So anything you upload to this example application, it will eventually be lost.  In my actual application I am going to be hosting the actual images to my Dropbox account or using a private Redis database, but in either case, it’s not information I want to share publically.  If you have questions about how I do either, I will gladly answer them.

 

2- THIS ONE IS IMPORTANT!!! I have shared full access to this application to everyone.  That means anyone at any time can upload any picture they want!  So, be aware before you click any links, who knows what you might see!  Also, anyone can delete all the existing images too, so again, don’t expect your uploads to survive long.  It is live for demonstration purposes only, if you want to run something similar, fork it and host your own privately.  Use at your own risk!  GameFromScratch.com takes no responsibility for any content that might be updated.

 

Finally, please be civilized about it.

 

Alright, the warnings and disclaimers out of the way, let’s continue.

 

We are going to take our existing cocos2D audio sample application and add a settings button to the top right corner, like so:

image

 

In order to do so, we are going to make the following changes to Index.html, the file that is served if people request the root of our application:

index.html

<html>
<body style="padding:0; margin: 0; background: #fff;">
<div stype="width:100%;height:50px">
    <form>
        <div align=right>
            <input align=right type=button value=settings id=settings
                  onclick="document.location.href='/settings';"  />
        </div>
    </form>
</div>
<div style="text-align: center; font-size: 0">
    <canvas id="gameCanvas" width="600" height="600">
        Your browser does not support the canvas tag
    </canvas>
</div>
</body>
</html>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.1/jquery.min.js">
</script>
<script src="classes/cocos2d.js"></script>

 

Really, the only major changes here is the addition of a form, with a button inside with a crude JavaScript redirect to /settings.

 

In our node server application, we now need to handle this new route:

Add the following to server.js

server.get('/settings',function(req,res){
   res.sendfile('settings.html');
   console.log('Send settings.html');
});

This will now cause any requests to /settings to return settings.html.  This document is the webpage where most of the new functionality occurs.  It allows you to select multiple images ( at once using CTRL or SHIFT click ), apply a description to each, and upload it to the server.  It also allows you to review the existing images, as well as clear them all.

 

Let’s take a look at the code. 

settings.html

 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>First This Settings</title>
    <script src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js"></script>
    <script>
        YUI().use('uploader', function(Y){
            var uploader =
                new Y.Uploader(
                        {type:"HTML5",
                        uploadURL:"/upload/",
                        multipleFiles:true
                        }
                );

            uploader.render("#selectFiles");

            uploader.after("fileselect", function(event){
                var files = event.fileList;
                var table = Y.one("#filenames tbody");

                if(files.length > 0)
                {
                    Y.one("#buttonUpload").set("disabled",false);
                }

                Y.each(files,function(file){
                    table.append("<tr>" +
                            "<td>" + file.get("name") + "</td>" +
                            "<td>" + file.get("size") + "</td>" +
                            "<td><input type=text id=" + file.get("name") + "></td></tr>");
                });
            });

            uploader.on("alluploadscomplete", function(event) {
                // Hackish page reload
                alert("Files added successfully");
                document.location.href="/settings";
            });

            Y.one("#buttonUpload").on("click", function(){
                if(uploader.get("fileList").length > 0){
                    var perFileVars = {};
                    Y.each(uploader.get("fileList"),function(curFile){
                        perFileVars[curFile.get("id")] =
                        {description:Y.DOM.byId(curFile.get("name")).value};
                    });
                    uploader.set("postVarsPerFile",
                        Y.merge(uploader.get("postVarsPerFile"),perFileVars));


                    uploader.uploadAll();
                }
            });


        });

        YUI().use('io-base','node',function(Y){
            Y.io('/getPhotos',{
                on: {
                    complete:function(id,response){
                        var files = JSON.parse(response.responseText);
                        Y.log(files);
                        var table = Y.one("#existingFiles tbody");
                        for(var key in files)
                        {

                            table.append("<tr>" +
                                    "<td><a href=/image/" + files[key].name + ">"
                                    + files[key].name + "</a></td>" +
                                    "<td>" + files[key].size + "</td>" +
                                    "<td>" + files[key].description + "</td></tr>");
                        }
                    }
                }
            });

            Y.one("#buttonClear").on("click", function(){
                Y.io('/clearAll',{
                    on:{
                        complete:function(id,response){
                            document.location.href="/settings";
                        }
                    }
                });
            });

        });
    </script>
</head>
<body>
<form>
<div style="border:50px">
    <div id=selectFiles>
    </div>
    <div>
        <table id="filenames">
            <thead><tr>
                <th width=350px align=left>File Name</th>
                <th align=left>File Size</th>
                <th align=left>Description</th>
            </tr></thead>
            <tbody></tbody>
        </table>
    </div>
    <div align=right>
        <br /><br />
        <hr />
        <button disabled="true" type="button" id="buttonUpload"class="yui3-button"
                style="width:250px;height:40px;align:right;">Upload Files</button>
    </div>
    <div>
        <br /><hr />
        <table id="existingFiles">
            <thead><tr>
                <th width=150px align=left>File Name</th>
                <th align=left width=60px>File Size</th>
                <th align=left>Description</th></tr></thead>
            <tbody></tbody>
        </table>
    </div>
    <div align=right>
        <br /><br />
        <hr />
        <button type="button" id="buttonHome"class="yui3-button"
                onclick="document.location.href='/'"
                style="width:250px;height:40px;align:right;">Done</button>
        <button type="button" id="buttonClear"class="yui3-button"
                style="width:250px;height:40px;align:right;">Clear All Files</button>
    </div>
</div>

</form>
</body>
</html>

 

Please ignore the wonky formatting, it was done to fit it to this blog width.

As you can see right off the hop, this code makes use of the YUI libraries, a handy set of utilities to perform many common tasks.  In this case I am using them for the Uploader control, as well as for making networked requests back to the server with Y.io.  The YUI libraries are beyond the scope of this document, but fortunately their documentation is extremely good. If you would prefer to use jQuery instead, feel free.

 

In a nutshell, here is what we are doing ( from the top ):

  • create a YUI closure Y, with the uploader module functionality supported
  • create a new uploader, HTML5 style (instead of Flash), set its upload path ( the url it will submit to ) to /upload and finally tell it to support multiple files at once.  This is a big part of why I chose it in the first place, as otherwise with the standard HTML controls, you need to upload files one by one.
  • we then set the uploader to render to the selectFiles div we will define shortly. This simply tells the control where to draw itself.
  • We then wire up an event handler for after the user has selected files. If the user has selected at least one file, we enable the upload button, we then loop through the selected files and dynamically populate the table named filenames.  Perhaps most importantly, for each table row, we create a text field for entering the description, with the filename as it’s id.
  • Next we wire up an event handler for the “alluploadscomplete” event, which is fired predictably enough, when all uploads are complete. In this case we just display a message and redirect to the settings page.
  • We then wire up a click handler for the upload button.  This loops through all of the files select, and grabs the description value we specified in the text field and merges in to the data that is going to be posted back to the server.  Essentially this means the data you enter is each description field is going to be posted along with your files back to the server.  Finally we actually perform the upload by calling uploadAll().
  • Next I create a different YUI closure with different dependencies ( io-base and node ).  I do this simply as personal preference, as I find it makes code easier to move around.  If you preferred, you could have made a single call to YUI().use.
  • This code is basically going to be executed on page load or there abouts ( we don’t really care when ), which makes a network request to /getPhotos, which is a web service that returns a JSON structure of all the photos on the server.  We then loop through the results and display them to another table, existingFiles.
  • next we wire-up another button handler, this one is if the user clicks the Clear All button, which calls the clear all web service, which predictably enough, clears all the photos. Finally it reloads the page, so the tables will be redrawn.
  • Everything else is just standard HTML.

 

There is actually quite a bit of functionality packed in to a fairly small amount of code.  As we saw, we added a number of new functions to the server, lets take a look at them. 

 

server.js

var express = require('express'),
    server = express.createServer(),
    files = {};

server.use('/cocos2d', express.static(__dirname + '/cocos2d') );
server.use('/cocosDenshion', express.static(__dirname + '/cocosDenshion') );
server.use('/classes', express.static(__dirname + '/classes') );
server.use('/resources', express.static(__dirname + '/resources') );

server.use(express.bodyParser());

server.get('/', function(req,res){
    res.sendfile('index.html');
    console.log('Sent index.html');
});

server.get('/settings',function(req,res){
   res.sendfile('settings.html');
   console.log('Send settings.html');
});

// API calls
server.get('/image/:name', function(req,res){
    if(files[req.params.name])
    {
        res.contentType(files[req.params.name].contentType);
        res.sendfile(files[req.params.name].path);

        console.log("Returning file" + req.params.name);
    }
});

server.get('/getPhotos', function(req,res){
    res.json(files);

});

server.get('/clearAll', function(req,res){
    files = {};
    res.statusCode = 200;
    res.send("");
})

server.post('/upload',function(req,res){
    files[req.files.Filedata.name] = {
        "name":req.files.Filedata.name,
        "path":req.files.Filedata.path,
        "size":req.files.Filedata.size,
        "contentType":req.files.Filedata.type,
        "description":req.body.description };
        console.log(req.files.Filedata);

    console.log(Object.keys(files).length);
    res.statusCode = 200;
    res.send("");
});
server.listen(process.env.PORT || 3000);

 

First off, we have added the files var, which is going to hold our file data in memory.  The server.post() handler for upload is probably the most significant change.  This code takes the uploaded form information and populations our files data.  One key addition though was:

server.use(express.bodyParser());

This tells express to install a piece of middleware, that actually processes the form data, and is what makes the /upload method actually have data.

/clearAll is handled by simply erasing our files data, while /getPhotos simply returns that data in JSON format.

 

 

At this point if you are following along at home, you may be wondering how to submit your changes up to heroku.  The process is fairly simple, from a command prompt in your app folder:

heroku login ( give username and password )

git add .

git commit –m “description of changes”

git push master heroku

 

 

In a nutshell these commands a) log you in to heroku b) adds all the new files to your local git repository c) commits the changes you have made into your git repository d) pushes that repository up to heroku. Heroku will automatically restart your app.

 

Here then is our application running on heroku.  Click the settings link in the top right corner to bring up the new settings page we just created.

 

 

In the next part, now that we have a server, a client and some data to work with, we will get down to the actual task of creating our simple game.

 

Here ( assuming it is running ), is our application.  Again, I warn you about whatever content might be live!

General, Programming , , , ,

Month List

Popular Comments