├── .gitignore ├── .gitmodules ├── README.markdown ├── blog ├── History.md ├── README.md ├── lib │ ├── secure.js │ ├── storage.js │ └── storage │ │ ├── fs.js │ │ └── memory.js ├── public │ ├── images │ │ ├── darkbg.png │ │ └── grid.png │ └── stylesheets │ │ └── style.css ├── server.js ├── test │ └── app.test.js └── views │ ├── admin.jade │ ├── archive.jade │ ├── index.jade │ ├── layout.jade │ ├── login.jade │ └── post.jade ├── chat ├── README.md ├── public │ ├── javascripts │ │ ├── json.js │ │ └── main.js │ └── stylesheets │ │ └── style.css ├── server.js ├── test │ └── app.test.js └── views │ ├── index.jade │ └── layout.jade ├── exploder ├── README.markdown ├── launch.sh ├── resources │ └── sprites.svg ├── server.js └── web │ ├── appinfo.json │ ├── engine.js │ ├── game.js │ ├── icon.png │ ├── index.html │ ├── sprites.png │ └── style.css ├── hexes ├── README.markdown ├── console.js ├── launch.sh ├── package.json ├── resources │ └── sprites.svg ├── server.js └── web │ ├── appinfo.json │ ├── art │ ├── board.png │ ├── logo.png │ ├── sprites.png │ └── style.css │ ├── client.js │ ├── icon.png │ └── index.html └── nodeFor145.markdown /.gitignore: -------------------------------------------------------------------------------- 1 | *.ipk 2 | *.swp 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/socket.io"] 2 | path = lib/socket.io 3 | url = https://github.com/LearnBoost/Socket.IO.git 4 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | 2 | 3 | git submodule init 4 | git submodule update --recursive 5 | 6 | -------------------------------------------------------------------------------- /blog/History.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joyent/node-camp/245abe18b079548cfb29adb0612a5a6fdd48aeb2/blog/History.md -------------------------------------------------------------------------------- /blog/README.md: -------------------------------------------------------------------------------- 1 | Simple Blog with Express+Jade 2 | ============================= 3 | 4 | ## Installation requirements 5 | 6 | Install the packages `express`, `expresso`, and `jade` with [NPM](http://npmjs.org) 7 | 8 | npm install express 9 | npm install expresso 10 | npm install jade 11 | 12 | ## Project description 13 | 14 | The aim of the project is to show how to create a simple common application that 15 | involves the following concepts 16 | 17 | * Project structure 18 | * Routing 19 | * Async data storage 20 | * Authentication 21 | * Views (through the Jade template engine) 22 | * Decoration (layouts) 23 | * Static files serving (CSS, client side JS) 24 | * Testing 25 | 26 | ## Running it 27 | 28 | You can run this project by 29 | 30 | * Executing `node server.js` 31 | * Deploying to Joyent's `no.de` service 32 | -------------------------------------------------------------------------------- /blog/lib/secure.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Security middleware 4 | * 5 | */ 6 | 7 | module.exports = function(req, res, next){ 8 | if (req.session.authenticated){ 9 | next(); 10 | } else { 11 | res.redirect('/admin/login'); 12 | } 13 | }; 14 | 15 | /** 16 | * Extend Request to provide logging in capabilities 17 | * 18 | * @api public 19 | */ 20 | 21 | require('http').IncomingMessage.prototype.login = function(){ 22 | this.session.authenticated = true; 23 | }; 24 | 25 | /** 26 | * Extend Request to provide logging out capabilities 27 | * 28 | * @param text 29 | */ 30 | 31 | require('http').IncomingMessage.prototype.logout = function(){ 32 | this.session.authenticated = false; 33 | }; 34 | -------------------------------------------------------------------------------- /blog/lib/storage.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Constructor 4 | * 5 | * @api private 6 | */ 7 | 8 | function Storage(options){ 9 | this.options = options; 10 | }; 11 | 12 | /** 13 | * Exports 14 | * 15 | */ 16 | 17 | module.exports = Storage; 18 | -------------------------------------------------------------------------------- /blog/lib/storage/fs.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module requirements. 4 | * 5 | */ 6 | 7 | var Storage = require('../storage') 8 | , fs = require('fs'); 9 | 10 | /** 11 | * Constructor 12 | * 13 | * @api public 14 | */ 15 | 16 | function FS(options){ 17 | Storage.call(this, options); 18 | var self = this; 19 | fs.readdir(this.options.dir, function(err, files){ 20 | self.count = files.length; 21 | }); 22 | }; 23 | 24 | /** 25 | * Inherit from Storage 26 | * 27 | */ 28 | 29 | FS.prototype.__proto__ = Storage; 30 | 31 | /** 32 | * Adds a new item 33 | * 34 | * @api public 35 | */ 36 | 37 | FS.prototype.add = function(obj, fn){ 38 | var count = this.count++; 39 | obj.id = count; 40 | fs.writeFile(this.options.dir + '/' + count, JSON.stringify(obj), fn); 41 | return this; 42 | }; 43 | 44 | /** 45 | * Gets an item by id 46 | * 47 | * @api public 48 | */ 49 | 50 | FS.prototype.lookup = function(id, fn){ 51 | fs.readFile(this.options.dir + '/' + id, function(err, data){ 52 | if (err) return fn(err); 53 | fn(null, JSON.parse(data.toString())); 54 | }); 55 | return this; 56 | }; 57 | 58 | /** 59 | * Removes an item 60 | * 61 | * @api public 62 | */ 63 | 64 | FS.prototype.remove = function(id, fn){ 65 | fs.unlink(this.options.dir + '/' + id, fn); 66 | return this; 67 | }; 68 | 69 | /** 70 | * Gets all items 71 | * 72 | * @api public 73 | */ 74 | 75 | FS.prototype.find = function(fn){ 76 | var dir = this.options.dir; 77 | fs.readdir(dir, function(err, files){ 78 | if (err) return fn(err); 79 | var data = [] 80 | , count = files.length; 81 | if (!count) fn(null, data); 82 | files.forEach(function(file, i){ 83 | fs.readFile(dir + '/' + file, function(err, buf){ 84 | if (!err) data[i] = JSON.parse(buf.toString()); 85 | --count || fn(null, data); 86 | }); 87 | }); 88 | }); 89 | return this; 90 | }; 91 | 92 | /** 93 | * Exports 94 | * 95 | */ 96 | 97 | module.exports = FS; 98 | -------------------------------------------------------------------------------- /blog/lib/storage/memory.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module requirements. 4 | * 5 | */ 6 | 7 | var Storage = require('../storage'); 8 | 9 | /** 10 | * Constructor 11 | * 12 | * @api public 13 | */ 14 | 15 | function Memory(options){ 16 | Storage.call(this, options); 17 | this.data = []; 18 | }; 19 | 20 | /** 21 | * Inherit from Storage 22 | * 23 | */ 24 | 25 | Memory.prototype.__proto__ = Storage; 26 | 27 | /** 28 | * Adds a new item 29 | * 30 | * @api public 31 | */ 32 | 33 | Memory.prototype.add = function(obj, fn){ 34 | obj.id = this.data.length; 35 | this.data.push(obj); 36 | fn(null); 37 | return this; 38 | }; 39 | /** 40 | * Gets an item by id 41 | * 42 | * @api public 43 | */ 44 | 45 | Memory.prototype.lookup = function(id, fn){ 46 | fn(this.data[id] == undefined ? new Error : null, this.data[id]); 47 | return this; 48 | }; 49 | 50 | /** 51 | * Removes an item 52 | * 53 | * @api public 54 | */ 55 | 56 | Memory.prototype.remove = function(id, fn){ 57 | var und = this.data[id] == undefined; 58 | if (!und) this.data.splice(id, 1); 59 | fn(und ? new Error : null); 60 | return this; 61 | }; 62 | 63 | /** 64 | * Gets all items 65 | * 66 | * @api public 67 | */ 68 | 69 | Memory.prototype.find = function(fn){ 70 | fn(null, this.data); 71 | return this; 72 | }; 73 | 74 | /** 75 | * Exports 76 | * 77 | */ 78 | 79 | module.exports = Memory; 80 | -------------------------------------------------------------------------------- /blog/public/images/darkbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joyent/node-camp/245abe18b079548cfb29adb0612a5a6fdd48aeb2/blog/public/images/darkbg.png -------------------------------------------------------------------------------- /blog/public/images/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joyent/node-camp/245abe18b079548cfb29adb0612a5a6fdd48aeb2/blog/public/images/grid.png -------------------------------------------------------------------------------- /blog/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | background: url('/images/grid.png'); 4 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 5 | } 6 | 7 | #header { 8 | -webkit-box-shadow: #42697B 1px 1px 5px; 9 | background: url(/images/darkbg.png); 10 | border-bottom-left-radius: 7px 7px; 11 | border-bottom-left-radius: 7px 7px; 12 | border-bottom-right-radius: 7px 7px; 13 | border-bottom-right-radius: 7px 7px; 14 | border-top-left-radius: 7px 7px; 15 | border-top-left-radius: 7px 7px; 16 | border-top-right-radius: 7px 7px; 17 | border-top-right-radius: 7px 7px; 18 | padding: 20px; 19 | } 20 | 21 | #header h1 { 22 | margin: 0; 23 | color: #B6CAD4; 24 | font-size: 17px; 25 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif; 26 | font-weight: bold; 27 | text-transform: uppercase; 28 | } 29 | 30 | #header nav a { 31 | color: #999; 32 | font-size: 12px; 33 | text-decoration: none; 34 | } 35 | 36 | #header nav a:hover { 37 | color: #fff; 38 | } 39 | 40 | fieldset, .post { 41 | background-color: white; 42 | border: 1px solid #E2ECF0; 43 | border-bottom-left-radius: 7px 7px; 44 | border-bottom-right-radius: 7px 7px; 45 | border-top-left-radius: 7px 7px; 46 | border-top-right-radius: 7px 7px; 47 | margin-bottom: 20px; 48 | padding: 20px; 49 | } 50 | 51 | legend { 52 | text-transform: uppercase; 53 | font-weight: bold; 54 | background: #eee; 55 | padding: 5px 15px; 56 | } 57 | 58 | dl { 59 | overflow: hidden; 60 | } 61 | 62 | dt { 63 | font-weight: bold; 64 | } 65 | 66 | dd { 67 | margin: 10px 0; 68 | } 69 | 70 | input { 71 | width: 250px; 72 | padding: 5px; 73 | } 74 | 75 | textarea { 76 | height: 100px; 77 | width: 400px; 78 | padding: 5px; 79 | } 80 | 81 | table { 82 | border-collapse: collapse; 83 | width: 100%; 84 | } 85 | 86 | table td, table th { 87 | border: 1px solid #333; 88 | padding: 10px; 89 | } 90 | 91 | table th { 92 | background: #fff; 93 | } 94 | 95 | .post h1 { 96 | margin: 10px 0 0; 97 | } 98 | 99 | .post h1 a { 100 | color: #142833; 101 | font: normal normal normal 18px/normal 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif; 102 | font-weight: bold; 103 | text-decoration: none; 104 | } 105 | 106 | .post h4 { 107 | margin: 3px 0 15px; 108 | } 109 | 110 | .archive { 111 | background: #fff; 112 | border-bottom: 1px solid #333; 113 | padding: 5px 10px; 114 | } 115 | 116 | .archive span.date { 117 | float: right; 118 | padding-top: 15px; 119 | } 120 | 121 | .post-view { 122 | margin-top: 18px; 123 | } 124 | -------------------------------------------------------------------------------- /blog/server.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('express') 7 | , secure = require('./lib/secure') 8 | , storage; 9 | 10 | var app = module.exports = express.createServer(); 11 | 12 | // Configuration 13 | 14 | app.configure(function(){ 15 | app.set('name', 'My cool blog'); 16 | app.set('views', __dirname + '/views'); 17 | app.set('view engine', 'jade'); 18 | app.use(express.cookieDecoder()); 19 | app.use(express.session()) 20 | app.use(express.bodyDecoder()); 21 | app.use(express.methodOverride()); 22 | app.use(app.router); 23 | app.use(express.staticProvider(__dirname + '/public')); 24 | }); 25 | 26 | app.configure('development', function(){ 27 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 28 | storage = new (require('./lib/storage/memory')); 29 | }); 30 | 31 | app.configure('production', function(){ 32 | //app.use(express.errorHandler()); 33 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 34 | storage = new (require('./lib/storage/fs'))({ dir: __dirname + '/data'}); 35 | }); 36 | 37 | // Routes 38 | 39 | app.get('/', function(req, res, next){ 40 | storage.find(function(err, posts){ 41 | if (err) return next(err); 42 | res.render('index', {locals: { posts: posts, localVariable: 'This is my local variable' } }); 43 | }); 44 | }); 45 | 46 | app.get('/archive', function(req, res, next){ 47 | storage.find(function(err, posts){ 48 | if (err) return next(err); 49 | res.render('archive', {locals: { posts: posts } }); 50 | }); 51 | }); 52 | 53 | app.get('/post/:id', function(req, res, next){ 54 | storage.lookup(req.param('id'), function(err, post){ 55 | if (err) return next(err); 56 | res.render('post', {locals: { post: post } }); 57 | }); 58 | }); 59 | 60 | app.get('/admin', secure, function(res, res, next){ 61 | storage.find(function(err, posts){ 62 | if (err) return next(err); 63 | res.render('admin', {locals: { posts: posts } }); 64 | }); 65 | }); 66 | 67 | app.get('/admin/remove/:id', secure, function(req, res, next){ 68 | storage.remove(req.param('id'), function(err){ 69 | if (err) return next(err); 70 | res.redirect('/admin'); 71 | }); 72 | }); 73 | 74 | app.post('/admin/new', secure, function(req, res, next){ 75 | storage.add({ 76 | title: req.param('title'), 77 | content: req.param('content'), 78 | date: new Date 79 | }, function(err){ 80 | if (err) return next(err); 81 | res.redirect('/admin'); 82 | }); 83 | }); 84 | 85 | app.get('/admin/login', function(req, res){ 86 | res.render('login'); 87 | }); 88 | 89 | app.get('/admin/logout', function(req, res){ 90 | setTimeout(function(){ 91 | req.logout(); 92 | res.redirect('/admin'); 93 | }, 2000); 94 | }); 95 | 96 | app.post('/admin/login', function(req, res){ 97 | if (req.param('username') == 'john' && req.param('password') == 'test'){ 98 | req.login(); 99 | res.redirect('/admin'); 100 | } else { 101 | res.render('login', {locals: {error: 'Bad login'}}); 102 | } 103 | }); 104 | 105 | // Only listen on $ node app.js 106 | 107 | if (!module.parent) { 108 | app.listen(3000); 109 | console.log("Express server listening on port %d", app.address().port) 110 | } 111 | -------------------------------------------------------------------------------- /blog/test/app.test.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var app = require('../server'); 7 | 8 | var assert = require('assert'); 9 | 10 | module.exports = { 11 | 12 | 'GET /': function(){ 13 | assert.response(app, 14 | { url: '/' }, 15 | { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' }}, 16 | function(res){ 17 | assert.includes(res.body, 'My cool blog'); 18 | }); 19 | } 20 | 21 | }; 22 | -------------------------------------------------------------------------------- /blog/views/admin.jade: -------------------------------------------------------------------------------- 1 | 2 | h2 3 | | Admin panel 4 | a(href: '/admin/logout') Logout 5 | 6 | h3 Posts 7 | table 8 | thead 9 | tr 10 | th Title 11 | th Date 12 | th Action 13 | tbody 14 | - if (posts.length) 15 | - each post in posts 16 | tr 17 | td= post.title 18 | td= post.date 19 | td 20 | a(href: '/admin/remove/' + post.id) Remove 21 | - else 22 | tr 23 | td(colspan: 3) No posts to display 24 | 25 | h3 New post 26 | form(action: '/admin/new', method: 'post') 27 | fieldset 28 | legend Create 29 | dl 30 | dt Title 31 | dd 32 | input(type: 'text', name: 'title') 33 | dt Content 34 | dd 35 | textarea(name: 'content') 36 | button Submit 37 | -------------------------------------------------------------------------------- /blog/views/archive.jade: -------------------------------------------------------------------------------- 1 | h2 Archive 2 | 3 | :markdown 4 | | ## Hello 5 | 6 | :javascript 7 | | var a = '5'; 8 | | var b = '5'; 9 | 10 | :realtime(repaint: 'connections') 11 | - if (!posts.length) 12 | p 13 | | No posts to show. 14 | a(href: '/admin') Go to admin? 15 | - else 16 | - each post in posts 17 | .archive 18 | span.date= post.date 19 | h3 20 | a(href: '/post/' + post.id)= post.title 21 | -------------------------------------------------------------------------------- /blog/views/index.jade: -------------------------------------------------------------------------------- 1 | h2 Latest posts 2 | 3 | - if (!posts.length) 4 | p 5 | | No posts to show. 6 | a(href: '/admin') Go to admin? 7 | - else 8 | - each post in posts 9 | .post 10 | h1 11 | a(href: '/post/' + post.id)= post.title 12 | h4= post.date 13 | = post.content 14 | -------------------------------------------------------------------------------- /blog/views/layout.jade: -------------------------------------------------------------------------------- 1 | !!! 2 | html 3 | head 4 | title= this.app.set('name') 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | h1= localVariable 8 | h2= this.connection.remoteAddress 9 | #header 10 | h1= this.app.set('name') 11 | nav 12 | a(href: '/') Index 13 | | | 14 | a(href: '/archive') Archive 15 | | | 16 | a(href: '/admin') Admin 17 | != body 18 | -------------------------------------------------------------------------------- /blog/views/login.jade: -------------------------------------------------------------------------------- 1 | h2 Login 2 | 3 | - if (typeof error != 'undefined') 4 | p 5 | b ERROR! 6 | = error 7 | 8 | form(action: '/admin/login', method: 'post') 9 | fieldset 10 | legend Login 11 | dl 12 | dt User 13 | dd 14 | input(type: 'text', name: 'username') 15 | dl 16 | dt Pass 17 | dd 18 | input(type: 'password', name: 'password') 19 | button Send! 20 | -------------------------------------------------------------------------------- /blog/views/post.jade: -------------------------------------------------------------------------------- 1 | .post.post-view 2 | h2 Viewing post #{post.title} 3 | p Date: #{post.date} 4 | != post.content 5 | -------------------------------------------------------------------------------- /chat/README.md: -------------------------------------------------------------------------------- 1 | Chat Example with Socket.IO 2 | =========================== 3 | 4 | ## Installation requirements 5 | 6 | Install the packages `express`, `socket.io`, and `jade` with [NPM](http://npmjs.org) 7 | 8 | npm install express 9 | npm install socket.io 10 | npm install jade 11 | 12 | ## Project description 13 | 14 | The goal is to create the quintessential realtime application on the web: 15 | a chat client. It involves the following concepts 16 | 17 | * Realtime communication schemes 18 | * Connect/Express + Socket.IO integration 19 | * JSON as a transport encoding mechanism 20 | -------------------------------------------------------------------------------- /chat/public/javascripts/json.js: -------------------------------------------------------------------------------- 1 | if(!this.JSON){JSON=function(){function f(n){return n<10?'0'+n:n;} 2 | Date.prototype.toJSON=function(){return this.getUTCFullYear()+'-'+ 3 | f(this.getUTCMonth()+1)+'-'+ 4 | f(this.getUTCDate())+'T'+ 5 | f(this.getUTCHours())+':'+ 6 | f(this.getUTCMinutes())+':'+ 7 | f(this.getUTCSeconds())+'Z';};var m={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};function stringify(value,whitelist){var a,i,k,l,r=/["\\\x00-\x1f\x7f-\x9f]/g,v;switch(typeof value){case'string':return r.test(value)?'"'+value.replace(r,function(a){var c=m[a];if(c){return c;} 8 | c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+ 9 | (c%16).toString(16);})+'"':'"'+value+'"';case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';} 10 | if(typeof value.toJSON==='function'){return stringify(value.toJSON());} 11 | a=[];if(typeof value.length==='number'&&!(value.propertyIsEnumerable('length'))){l=value.length;for(i=0;i'; 4 | else if ('message' in obj) el.innerHTML = '' + esc(obj.message[0]) + ': ' + esc(obj.message[1]); 5 | document.getElementById('chat').appendChild(el); 6 | document.getElementById('chat').scrollTop = 1000000; 7 | } 8 | 9 | function send(){ 10 | var val = document.getElementById('text').value; 11 | socket.send(val); 12 | message({ message: ['you', val] }); 13 | document.getElementById('text').value = ''; 14 | } 15 | 16 | function esc(msg){ 17 | return String(msg).replace(//g, '>'); 18 | }; 19 | 20 | var socket = new io.Socket(null, {port: 8080, rememberTransport: false}); 21 | socket.connect(); 22 | socket.on('message', function(obj){ 23 | if ('buffer' in obj){ 24 | document.getElementById('form').style.display='block'; 25 | document.getElementById('chat').innerHTML = ''; 26 | 27 | for (var i in obj.buffer) message(obj.buffer[i]); 28 | } else message(obj); 29 | }); 30 | -------------------------------------------------------------------------------- /chat/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | #chat { height: 400px; overflow: auto; border: 1px solid #eee; font: 13px Helvetica, Arial; } 7 | 8 | #chat p { padding: 8px; margin: 0; } 9 | 10 | #chat p:nth-child(odd) { background: #F6F6F6; } 11 | 12 | #form { background: #333; padding: 5px 10px; display: none; } 13 | 14 | #form input[type=text] { width: 90%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 5px; background: #fff; border: 1px solid #fff; } 15 | 16 | #form input[type=submit] { cursor: pointer; background: #999; border: none; padding: 6px 8px; -moz-border-radius: 8px; -webkit-border-radius: 8px; margin-left: 5px; text-shadow: 0 1px 0 #fff; } 17 | 18 | #form input[type=submit]:hover { background: #A2A2A2; } 19 | 20 | #form input[type=submit]:active { position: relative; top: 2px; } 21 | -------------------------------------------------------------------------------- /chat/server.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var express = require('express'); 7 | 8 | var app = module.exports = express.createServer(); 9 | 10 | var io = require('socket.io'); 11 | 12 | var http = require('http'); 13 | 14 | // Configuration 15 | 16 | app.configure(function(){ 17 | app.set('views', __dirname + '/views'); 18 | app.set('view engine', 'jade'); 19 | app.use(express.bodyDecoder()); 20 | app.use(express.methodOverride()); 21 | app.use(app.router); 22 | app.use(express.staticProvider(__dirname + '/public')); 23 | }); 24 | 25 | app.configure('development', function(){ 26 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 27 | }); 28 | 29 | app.configure('production', function(){ 30 | app.use(express.errorHandler()); 31 | }); 32 | 33 | // Routes 34 | 35 | app.get('/', function(req, res){ 36 | res.render('index', { 37 | locals: { 38 | title: 'Express' 39 | } 40 | }); 41 | }); 42 | 43 | app.listen(8080); 44 | 45 | var io = io.listen(app) 46 | , buffer = []; 47 | 48 | io.on('connection', function(client){ 49 | client.send({ buffer: buffer }); 50 | client.broadcast({ announcement: client.sessionId + ' connected' }); 51 | 52 | client.on('message', function(message){ 53 | var msg = { message: [client.sessionId, message] }; 54 | buffer.push(msg); 55 | if (buffer.length > 15) buffer.shift(); 56 | client.broadcast(msg); 57 | }); 58 | 59 | client.on('disconnect', function(){ 60 | client.broadcast({ announcement: client.sessionId + ' disconnected' }); 61 | }); 62 | }); 63 | 64 | // twitter streaming 65 | var lastTweetId; 66 | 67 | setInterval(function(){ 68 | var client = http.createClient(80, 'search.twitter.com'); 69 | var request = client.request('GET', '/search.json?q=bieber', {Host: 'search.twitter.com'}); 70 | request.end(); 71 | request.on('response', function(response){ 72 | var data = ''; 73 | response.on('data', function(chunk){ 74 | data += chunk; 75 | }); 76 | response.on('end', function(){ 77 | var obj = JSON.parse(data); 78 | var lastTweet = obj.results.shift(); 79 | if (lastTweet.id_str != lastTweetId){ 80 | io.broadcast({ announcement: lastTweet.text }); 81 | lastTweetId = lastTweet.id_str; 82 | } 83 | }); 84 | }); 85 | }, 5000); 86 | 87 | console.log("Express server listening on port %d", app.address().port) 88 | -------------------------------------------------------------------------------- /chat/test/app.test.js: -------------------------------------------------------------------------------- 1 | 2 | // Run $ expresso 3 | 4 | /** 5 | * Module dependencies. 6 | */ 7 | 8 | var app = require('../app'); 9 | 10 | 11 | module.exports = { 12 | 'GET /': function(assert){ 13 | assert.response(app, 14 | { url: '/' }, 15 | { status: 200, headers: { 'Content-Type': 'text/html; charset=utf-8' }}, 16 | function(res){ 17 | assert.includes(res.body, 'Express'); 18 | }); 19 | } 20 | }; -------------------------------------------------------------------------------- /chat/views/index.jade: -------------------------------------------------------------------------------- 1 | h1 Socket.IO Chat 2 | 3 | #chat 4 | p Connecting 5 | 6 | form(id: 'form', onsubmit: 'send(); return false;') 7 | input(type: 'text', autocomplete: 'off', id: 'text') 8 | input(type: 'submit', value: 'Send') 9 | -------------------------------------------------------------------------------- /chat/views/layout.jade: -------------------------------------------------------------------------------- 1 | !!! 2 | html 3 | head 4 | title Socket.IO Chat 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | script(src: '/javascripts/json.js') 7 | script(src: '/socket.io/socket.io.js'); 8 | script(src: '/javascripts/main.js') 9 | body!= body 10 | -------------------------------------------------------------------------------- /exploder/README.markdown: -------------------------------------------------------------------------------- 1 | # Exploder Demo 2 | 3 | This is a simple client-only demo showing how to create two sprite types. 4 | 5 | Click and drag on the screen to see the explosions. 6 | 7 | The first type is the circles. They are created at page load and bounce off the walls forever. 8 | 9 | The second type is the sparks. They fly faster and die after either a specified timeout or fly off the screen. 10 | 11 | The bounding box is the entire window and is dynamic. 12 | -------------------------------------------------------------------------------- /exploder/launch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | palm-package web 3 | palm-install *.ipk 4 | palm-launch org.nodejs.camp.exploder 5 | rm *.ipk 6 | -------------------------------------------------------------------------------- /exploder/resources/sprites.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 26 | 30 | 34 | 38 | 39 | 41 | 45 | 49 | 50 | 57 | 67 | 68 | 89 | 99 | 103 | 104 | 106 | 107 | 109 | image/svg+xml 110 | 112 | 113 | 114 | 115 | 116 | 121 | 124 | 134 | 138 | 142 | 143 | 146 | 156 | 160 | 164 | 165 | 168 | 178 | 182 | 186 | 187 | 190 | 200 | 204 | 208 | 209 | 211 | 221 | 225 | 229 | 230 | 232 | 242 | 246 | 250 | 251 | 253 | 263 | 267 | 271 | 272 | 274 | 284 | 288 | 292 | 293 | 295 | 305 | 309 | 313 | 314 | 321 | 322 | 323 | -------------------------------------------------------------------------------- /exploder/server.js: -------------------------------------------------------------------------------- 1 | var Connect = require('connect'); 2 | 3 | var PORT = process.env.PORT || 8081; 4 | 5 | // Serve static files and log requests to the server 6 | Connect.createServer( 7 | Connect.logger(), 8 | Connect.staticProvider('web') 9 | ).listen(PORT); 10 | 11 | console.log("Exploder server running at http://localhost:%s", PORT); 12 | 13 | -------------------------------------------------------------------------------- /exploder/web/appinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "org.nodejs.camp.exploder", 3 | "version": "1.0.0", 4 | "vendor": "Node Camp", 5 | "type": "web", 6 | "main": "index.html", 7 | "title": "Exploder", 8 | "icon": "icon.png" 9 | } 10 | -------------------------------------------------------------------------------- /exploder/web/engine.js: -------------------------------------------------------------------------------- 1 | /*global window document PalmSystem */ 2 | 3 | function Engine(containerId, setup) { 4 | 5 | var sprites = [], 6 | container; 7 | 8 | ///////////////////////////////////////////////////////////////////////////// 9 | 10 | function Sprite(x, y, r, img) { 11 | this.x = x || 0; 12 | this.y = y || 0; 13 | this.r = r || 0; 14 | this.img = img; 15 | this.div = undefined; 16 | } 17 | 18 | Sprite.prototype.renderDiv = function () { 19 | var div = document.createElement('div'); 20 | div.setAttribute('class', this.img); 21 | return div; 22 | }; 23 | 24 | // Add a sprite to the dom and the animation list 25 | Sprite.prototype.show = function () { 26 | var div = this.div = this.renderDiv(); 27 | container.appendChild(div); 28 | sprites.push(this); 29 | this.update(); 30 | }; 31 | 32 | // Remove a sprite from the dom and the animation list 33 | Sprite.prototype.hide = function () { 34 | container.removeChild(this.div); 35 | this.div = undefined; 36 | sprites.splice(sprites.indexOf(this), 1); 37 | }; 38 | 39 | // Hook for custom logic 40 | Sprite.prototype.animate = function (delta) { }; 41 | 42 | // Move the sprite to it's new location 43 | Sprite.prototype.update = function () { 44 | if (this.x === this.ox && this.y === this.oy && this.r === this.or) { 45 | return; 46 | } 47 | var transform = 48 | 'translate3d(' + this.x + 'px,' + this.y + 'px,0) ' + 49 | 'rotate(' + this.r + 'deg)'; 50 | this.ox = this.x; 51 | this.oy = this.y; 52 | this.or = this.r; 53 | this.div.style.webkitTransform = transform; 54 | }; 55 | 56 | ///////////////////////////////////////////////////////////////////////////// 57 | 58 | var listeners = {}, 59 | engine; 60 | engine = { 61 | Sprite: Sprite, 62 | sprites: sprites, 63 | addListener: function (type, listener) { 64 | if (!listeners.hasOwnProperty(type)) { 65 | listeners[type] = []; 66 | } 67 | listeners[type].push(listener); 68 | }, 69 | removeListener: function (type, listener) { 70 | var list = listeners[type]; 71 | list.splice(list.indexOf(listener), 1); 72 | }, 73 | emit: function (type/* args */) { 74 | var list = listeners[type], 75 | args = Array.prototype.slice.call(arguments, 1); 76 | if (!list) { 77 | if (type === 'error') { 78 | throw args[0]; 79 | } 80 | return; 81 | } 82 | list.forEach(function (listener) { 83 | listener.apply(this, args); 84 | }, this); 85 | } 86 | }; 87 | engine.on = engine.addListener; 88 | 89 | ///////////////////////////////////////////////////////////////////////////// 90 | 91 | function onDown(e) { 92 | engine.emit('down', e); 93 | } 94 | function onMove(e) { 95 | engine.emit('move', e); 96 | } 97 | function onUp(e) { 98 | engine.emit('up', e); 99 | } 100 | 101 | // Initialize the game on window load 102 | window.addEventListener('load', function () { 103 | 104 | container = document.getElementById(containerId); 105 | // Start the animation loop 106 | var old = Date.now(); 107 | setInterval(function () { 108 | var now = Date.now(), 109 | delta = now - old; 110 | engine.emit('animate', delta); 111 | sprites.forEach(function (sprite) { 112 | sprite.animate(delta); 113 | }); 114 | sprites.forEach(function (sprite) { 115 | sprite.update(); 116 | }); 117 | old = now; 118 | }, 33); 119 | 120 | // Listen for mouse and touch events 121 | var element = document.body; 122 | element.addEventListener('mousedown', onDown, false); 123 | element.addEventListener('mousemove', onMove, false); 124 | element.addEventListener('mouseup', onUp, false); 125 | element.addEventListener('touchstart', function (e) { 126 | if (e.touches.length === 1) { 127 | var touch = e.touches[0]; 128 | touch.stopPropagation = function () { 129 | e.stopPropagation(); 130 | }; 131 | touch.preventDefault = function () { 132 | e.preventDefault(); 133 | }; 134 | onDown(touch); 135 | } 136 | }, false); 137 | element.addEventListener('touchmove', function (e) { 138 | if (e.touches.length === 1) { 139 | var touch = e.touches[0]; 140 | touch.stopPropagation = function () { 141 | e.stopPropagation(); 142 | }; 143 | touch.preventDefault = function () { 144 | e.preventDefault(); 145 | }; 146 | onMove(touch); 147 | } 148 | }, false); 149 | element.addEventListener('touchend', onUp, false); 150 | 151 | setup(engine); 152 | 153 | // Start the palm system if we're in a webOS app 154 | if (typeof PalmSystem !== 'undefined') { 155 | PalmSystem.stageReady(); 156 | if (PalmSystem.enableFullScreenMode) { 157 | PalmSystem.enableFullScreenMode(true); 158 | } 159 | } 160 | 161 | }); 162 | 163 | // Move this to the end since jslint hates it 164 | Sprite.adopt = function (child) { 165 | child.__proto__ = this; 166 | child.prototype.__proto__ = this.prototype; 167 | }; 168 | 169 | } 170 | -------------------------------------------------------------------------------- /exploder/web/game.js: -------------------------------------------------------------------------------- 1 | /*global Engine document window*/ 2 | 3 | Engine('sprites', function (engine) { 4 | var Sprite = engine.Sprite; 5 | var colors = ['green', 'blue', 'brown', 'white', 'yellow', 'orange', 6 | 'purple', 'red', 'grey']; 7 | // Grid used for quick collision detection 8 | var ballGrid = {}; 9 | 10 | // Watch the height of the window. 11 | var width, height; 12 | function onResize() { 13 | width = document.width - 48; 14 | height = document.height - 48; 15 | } 16 | onResize(); 17 | 18 | var angleToRadian = Math.PI / 180; 19 | 20 | function Igniter(x, y, color) { 21 | Sprite.call(this, x, y, 0, color); 22 | this.life = 1000; 23 | } 24 | Igniter.prototype.animate = function (delta) { 25 | this.life -= delta; 26 | this.r += delta / 300; 27 | explode({ 28 | clientX: this.x + 24, 29 | clientY: this.y + 24 30 | }); 31 | if (this.life < 0) { 32 | this.hide(); 33 | } 34 | }; 35 | Sprite.adopt(Igniter); 36 | 37 | 38 | 39 | function Spark(x, y, angle) { 40 | Sprite.call(this, x, y, angle, 'dart'); 41 | angle *= angleToRadian; 42 | this.mx = Math.sin(angle) * 300; 43 | this.my = -Math.cos(angle) * 300; 44 | this.life = 1000; 45 | } 46 | Spark.prototype.animate = function (delta) { 47 | this.life -= delta; 48 | this.x += this.mx * delta / 1000; 49 | this.y += this.my * delta / 1000; 50 | this.checkCollisions(ballGrid, 40); 51 | if (this.life < 0 || 52 | this.x < -48 || this.y < -48 || 53 | this.x > width + 48 || this.y > height + 48) { 54 | this.hide(); 55 | } 56 | }; 57 | Spark.prototype.checkCollisions = function (grid, distance) { 58 | var cx = Math.floor(this.x / 48); 59 | var cy = Math.floor(this.y / 48); 60 | var self = this; 61 | function checkBall(ball) { 62 | var d = Math.sqrt((ball.x - self.x) * (ball.x - self.x) + 63 | (ball.y - self.y) * (ball.y - self.y)); 64 | if (d <= distance) { 65 | ball.mx += self.mx / 15; 66 | ball.my += self.my / 15; 67 | self.life = -1; 68 | } 69 | 70 | } 71 | for (var gx = cx - 1; gx <= cx + 1; gx++) { 72 | for (var gy = cy - 1; gy <= cy + 2; gy++) { 73 | var key = gx + "," + gy; 74 | if (grid.hasOwnProperty(key)) { 75 | grid[key].forEach(checkBall); 76 | } 77 | } 78 | } 79 | }; 80 | Sprite.adopt(Spark); 81 | 82 | function Ball(x, y, angle) { 83 | var n = Math.floor(angle / 360 * colors.length); 84 | Sprite.call(this, x, y, 0, colors[n]); 85 | this.mx = 0; 86 | this.my = 0; 87 | this.show(); 88 | this.updateGrid(); 89 | } 90 | Ball.prototype.hide = function () { 91 | Sprite.prototype.hide.call(this); 92 | this.updateGrid(); 93 | }; 94 | Ball.prototype.updateGrid = function () { 95 | // Remove the old entry in the collision grid 96 | if (this.gx !== undefined) { 97 | var list = ballGrid[this.gx + "," + this.gy]; 98 | list.splice(list.indexOf(this), 1); 99 | } 100 | if (!this.div) { 101 | return; 102 | } 103 | var gx = this.gx = Math.floor(this.x / 48); 104 | var gy = this.gy = Math.floor(this.y / 48); 105 | var key = gx + "," + gy; 106 | if (!ballGrid.hasOwnProperty(key)) { 107 | ballGrid[key] = []; 108 | } 109 | ballGrid[key].push(this); 110 | }; 111 | Ball.prototype.animate = function (delta) { 112 | this.x += this.mx * delta / 1000; 113 | this.y += this.my * delta / 1000; 114 | if (this.mx || this.my) { 115 | this.updateGrid(); 116 | } 117 | if (this.x < 0) { 118 | this.x *= -1; 119 | this.mx *= -1; 120 | this.checkBounce(); 121 | } else if (this.x > width) { 122 | this.x = 2 * width - this.x; 123 | this.mx *= -1; 124 | this.checkBounce(); 125 | } 126 | if (this.y < 0) { 127 | this.y *= -1; 128 | this.my *= -1; 129 | this.checkBounce(); 130 | } else if (this.y > height) { 131 | this.y = 2 * height - this.y; 132 | this.my *= -1; 133 | this.checkBounce(); 134 | } 135 | }; 136 | Ball.prototype.checkBounce = function () { 137 | var speed = Math.sqrt(this.mx * this.mx + this.my * this.my); 138 | if (speed > 400) { 139 | this.hide(); 140 | (new Igniter(this.x, this.y, this.img)).show(); 141 | } 142 | 143 | }; 144 | Sprite.adopt(Ball); 145 | 146 | // Create a few balls on the screen 147 | for (var i = 1; i < 360; i += 12) { 148 | var rad = i * angleToRadian; 149 | var ball = new Ball( 150 | width / 2 + Math.sin(rad) * 100, 151 | height / 2 + Math.cos(rad) * 100, 152 | i 153 | ); 154 | } 155 | 156 | window.addEventListener('resize', onResize, true); 157 | 158 | var down = false; 159 | var fire = false; 160 | var start = 0; 161 | 162 | engine.on('down', function (evt) { 163 | down = true; 164 | evt.stopPropagation(); 165 | evt.preventDefault(); 166 | }); 167 | engine.on('move', function (evt) { 168 | if (!down) { 169 | return; 170 | } 171 | fire = evt; 172 | evt.stopPropagation(); 173 | evt.preventDefault(); 174 | }); 175 | engine.on('up', function (evt) { 176 | down = false; 177 | evt.stopPropagation(); 178 | evt.preventDefault(); 179 | }); 180 | 181 | function explode(evt) { 182 | start = (start + 19) % 48; 183 | for (var i = start; i < 360; i += 48) { 184 | (new Spark(evt.clientX - 24, evt.clientY - 24, i)).show(); 185 | } 186 | } 187 | var fpsDiv = document.getElementById('fps'); 188 | engine.on('animate', function (delta) { 189 | if (!engine.sprites.length) { 190 | explode({ 191 | clientX: width / 2 + 24, 192 | clientY: height / 2 + 24 193 | }); 194 | } 195 | fpsDiv.innerText = (Math.floor(1000 / delta) / 1) + " fps"; 196 | if (fire) { 197 | explode(fire); 198 | fire = false; 199 | } 200 | }); 201 | 202 | }); 203 | -------------------------------------------------------------------------------- /exploder/web/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joyent/node-camp/245abe18b079548cfb29adb0612a5a6fdd48aeb2/exploder/web/icon.png -------------------------------------------------------------------------------- /exploder/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exploder 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /exploder/web/sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joyent/node-camp/245abe18b079548cfb29adb0612a5a6fdd48aeb2/exploder/web/sprites.png -------------------------------------------------------------------------------- /exploder/web/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | padding: 0; 3 | margin: 0; 4 | border: 0; 5 | position: absolute; 6 | left: 0; right: 0; bottom: 0; top: 0; 7 | width: 100%; 8 | height: 100%; 9 | background-color :#000; 10 | color: #fff; 11 | overflow: hidden; 12 | } 13 | 14 | #sprites div { 15 | position: absolute; 16 | width: 48px; 17 | height: 48px; 18 | background-image: url(sprites.png); 19 | } 20 | 21 | #sprites .dart { background-position: 0px 0px; } 22 | #sprites .white { background-position: 48px 0px; } 23 | #sprites .brown { background-position: 96px 0px; } 24 | #sprites .blue { background-position: 144px 0px; } 25 | #sprites .green { background-position: 192px 0px; } 26 | #sprites .yellow { background-position: 0px 48px; } 27 | #sprites .grey { background-position: 48px 48px; } 28 | #sprites .red { background-position: 96px 48px; } 29 | #sprites .purple { background-position: 144px 48px; } 30 | #sprites .orange { background-position: 192px 48px; } 31 | -------------------------------------------------------------------------------- /hexes/README.markdown: -------------------------------------------------------------------------------- 1 | # Hexes 2 | 3 | This is a simple game that uses a remote nodejs server to sync up piece movements between all players. 4 | 5 | Before you can run this, you need connect, and socket.io libraries installed. 6 | 7 | Install node + npm, and then install the deps with: 8 | 9 | npm install connect socket.io 10 | 11 | To start the server simply type the following in this folder: 12 | 13 | node server.js 14 | 15 | You can also use npm to do both steps: 16 | 17 | npm link # or `npm install` for non-link install 18 | npm start hexes 19 | 20 | Then go to http://localhost:8080/ in a webkit browser (chrome, safari, etc..) to see the game served from your local laptop. 21 | 22 | To play, simply click (or tap) on a piece, and then click where you want it to go. Open the same url in a couple browser windows to see them stay synced. 23 | 24 | # iOS Install 25 | 26 | Go to the url in Safari and add to desktop as an icon. Then when you launch this icon, it will appear as a local native app, but really load from your server/laptop. 27 | 28 | # webOS Experiments 29 | 30 | To test on the device, you need to modify the url in web/client.js to point to your server from the phone's point of view. 31 | 32 | Then you plug up the phone via USB (assuming you have the Palm SDK installed) and run the local launch script: 33 | 34 | ./launch.sh 35 | 36 | Also you can run the node server on the phone. To do this, install the nodeFor145 package on the phone or emulator (unless you have webOS 2.0 already) 37 | 38 | Then just wget this entire repo from github using the download link, untar it, and run `node server.js` from the this folder on the phone. 39 | 40 | -------------------------------------------------------------------------------- /hexes/console.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | var sys = require('sys'); 3 | var host = process.argv[2] || "localhost"; 4 | var port = process.argv[3] || 9000; 5 | 6 | var client = net.createConnection(port, host); 7 | client.on('connect', function () { 8 | console.log("Connected! Use Control+C to exit\n"); 9 | }); 10 | 11 | sys.pump(client, process.stdout); 12 | sys.pump(process.openStdin(), client); 13 | -------------------------------------------------------------------------------- /hexes/launch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | palm-package web 3 | palm-install *.ipk 4 | palm-launch org.nodejs.camp.hexes 5 | rm *.ipk 6 | -------------------------------------------------------------------------------- /hexes/package.json: -------------------------------------------------------------------------------- 1 | { "name" : "hexes" 2 | , "version" : "0.0.1" 3 | , "description" : "An example node program using web sockets" 4 | , "scripts" : { "start" : "node server.js" } 5 | , "dependencies" : { "connect" : "0.5", "socket.io" : "0.6" } 6 | } 7 | -------------------------------------------------------------------------------- /hexes/resources/sprites.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 30 | 37 | 41 | 42 | 49 | 52 | 56 | 57 | 64 | 68 | 69 | 70 | 88 | 95 | 96 | 98 | 99 | 101 | image/svg+xml 102 | 104 | 105 | 106 | 107 | 108 | 113 | 118 | 123 | 127 | 131 | 132 | 137 | 142 | 146 | 150 | 151 | 156 | 161 | 165 | 169 | 170 | 175 | 180 | 184 | 188 | 189 | 194 | 199 | 203 | 207 | 208 | 213 | 218 | 222 | 226 | 227 | 232 | 238 | 243 | 248 | 249 | 255 | 260 | 264 | 268 | 269 | 275 | 280 | 284 | 288 | 289 | 295 | 300 | 304 | 308 | 309 | 315 | 320 | 324 | 328 | 329 | 335 | 340 | 344 | 348 | 349 | 355 | 360 | 364 | 368 | 369 | 375 | 380 | 384 | 388 | 389 | 395 | 400 | 404 | 408 | 409 | 415 | 420 | 424 | 428 | 429 | 435 | 440 | 444 | 448 | 449 | 455 | 460 | 464 | 468 | 469 | 475 | 480 | 484 | 488 | 489 | 495 | 500 | 504 | 508 | 509 | 515 | 520 | 524 | 528 | 529 | 535 | 540 | 544 | 548 | 549 | 555 | 560 | 564 | 568 | 569 | 575 | 580 | 584 | 588 | 589 | 595 | 600 | 604 | 608 | 609 | 615 | 620 | 624 | 628 | 629 | 635 | 640 | 644 | 648 | 649 | 655 | 660 | 664 | 668 | 669 | 675 | 680 | 684 | 688 | 689 | 695 | 700 | 704 | 708 | 709 | 715 | 720 | 724 | 728 | 729 | 735 | 740 | 744 | 748 | 749 | 755 | 760 | 764 | 768 | 769 | 775 | 780 | 784 | 788 | 789 | 795 | 800 | 804 | 808 | 809 | 815 | 820 | 824 | 828 | 829 | 835 | 840 | 844 | 848 | 849 | 855 | 860 | 864 | 868 | 869 | 875 | 880 | 884 | 888 | 889 | 895 | 900 | 904 | 905 | 913 | 914 | 915 | -------------------------------------------------------------------------------- /hexes/server.js: -------------------------------------------------------------------------------- 1 | var Connect = require('connect'), 2 | io = require('socket.io'); 3 | 4 | // Allow the user to specify the port via environment variables 5 | var PORT = process.env.PORT || 8080; 6 | 7 | // Serve static files in the web folder 8 | var server = Connect.createServer( 9 | Connect.staticProvider(__dirname + '/web') 10 | ); 11 | 12 | // Listen to socket.io traffic too 13 | var socket = io.listen(server); 14 | 15 | // Keep a mapping of all the pieces in the game 16 | var map = { 17 | 1: {x: 1, y: 0}, 18 | 2: {x: 3, y: 1}, 19 | 3: {x: 1, y: 2}, 20 | 4: {x: 3, y: 3}, 21 | 5: {x: 1, y: 4}, 22 | 6: {x: 3, y: 5}, 23 | 7: {x: 1, y: 6} 24 | }; 25 | 26 | 27 | var clients = []; 28 | 29 | // Every time a new client connects or reconnects, we get this 30 | socket.on('connection', function (client) { 31 | 32 | // Send the client te initial map 33 | client.send({map: map}); 34 | 35 | // Define the commands we're willing to accept from the client 36 | var Commands = { 37 | // In this simple example, we re-broadcast the move to all clients. 38 | // In a real game there would some rule checking and other logic here. 39 | move: function (params) { 40 | map[params.id] = params; 41 | socket.broadcast({move: params}); 42 | } 43 | }; 44 | Commands.__proto__ = client; 45 | clients.push(Commands); 46 | 47 | client.on('disconnect', function () { 48 | clients.splice(clients.indexOf(Commands), 1); 49 | }); 50 | 51 | // Route messages to the Commands object 52 | client.on('message', function (message) { 53 | Object.keys(message).forEach(function (command) { 54 | if (Commands.hasOwnProperty(command)) { 55 | Commands[command](message[command]); 56 | } else { 57 | console.error("Invalid command " + command); 58 | } 59 | }); 60 | }); 61 | }); 62 | 63 | server.listen(PORT); 64 | console.log("Server running at port http://localhost:%s/", PORT); 65 | 66 | var repl = require('repl'); 67 | var net = require('net'); 68 | net.createServer(function (connection) { 69 | connection.write("Welcome to the backdoor\n"); 70 | require('child_process').exec("uname -a", function (err, stdout, stderr) { 71 | connection.write(stdout + "\n"); 72 | var context = repl.start("hexes server> ", connection).context; 73 | context.socket = socket; 74 | context.map = map; 75 | context.server = server; 76 | context.clients = clients; 77 | context.reload = function () { 78 | socket.broadcast({reload:true}); 79 | }; 80 | context.move = function (id, x, y) { 81 | socket.broadcast({move: {id: id, x: x, y: y}}); 82 | }; 83 | }); 84 | }).listen(9000); 85 | 86 | -------------------------------------------------------------------------------- /hexes/web/appinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "org.nodejs.camp.hexes", 3 | "version": "1.0.0", 4 | "vendor": "Node Camp", 5 | "type": "web", 6 | "main": "index.html", 7 | "title": "Hexes", 8 | "icon": "icon.png" 9 | } 10 | -------------------------------------------------------------------------------- /hexes/web/art/board.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joyent/node-camp/245abe18b079548cfb29adb0612a5a6fdd48aeb2/hexes/web/art/board.png -------------------------------------------------------------------------------- /hexes/web/art/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joyent/node-camp/245abe18b079548cfb29adb0612a5a6fdd48aeb2/hexes/web/art/logo.png -------------------------------------------------------------------------------- /hexes/web/art/sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joyent/node-camp/245abe18b079548cfb29adb0612a5a6fdd48aeb2/hexes/web/art/sprites.png -------------------------------------------------------------------------------- /hexes/web/art/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | border: 0; 3 | padding: 0; 4 | margin: 0; 5 | height: 100%; 6 | width: 100%; 7 | background-color: #000; 8 | } 9 | #sprites { 10 | position: absolute; 11 | overflow: hidden; 12 | top: 50%; 13 | left: 50%; 14 | margin-top: -240px; 15 | margin-left: -160px; 16 | width: 320px; 17 | height: 480px; 18 | background: url(logo.png) center no-repeat; 19 | } 20 | #sprites div { 21 | position: absolute; 22 | background: url(sprites.png); 23 | border-radius: 24px; 24 | width: 74px; 25 | height: 64px; 26 | -webkit-transition-property: -webkit-transform; 27 | -webkit-transition-duration: 1s; 28 | -webkit-transition-timing-function: linear; 29 | } 30 | #sprites .highlight { background-position: 0px 0px; } 31 | #sprites .space { background-position: 0px 512px; } 32 | #sprites .red { background-position: 0px 448px; } 33 | #sprites .brown { background-position: 0px 384px; } 34 | #sprites .purple { background-position: 0px 320px; } 35 | #sprites .blue { background-position: 0px 256px; } 36 | #sprites .orange { background-position: 0px 192px; } 37 | #sprites .green { background-position: 0px 128px; } 38 | #sprites .yellow { background-position: 0px 64px; } 39 | 40 | -------------------------------------------------------------------------------- /hexes/web/client.js: -------------------------------------------------------------------------------- 1 | /*global PalmSystem io */ 2 | 3 | // Set up a socket.io channel with the backend server 4 | var socket, // socket.io connection with server 5 | Commands, // Local commands the server can send us 6 | container; // The HTML container for all the Sprites 7 | 8 | // Number to color lookup table. 9 | // The backend sends numbers, the css expects strings. 10 | var colors = ['space', 'red', 'brown', 'purple', 'blue', 'orange', 11 | 'green', 'yellow']; 12 | 13 | var selected, // The currently selected piece (if any) 14 | pieces = {}, // Index of all pieces for easy reference 15 | zIndex = 1000; // Used to make moving divs always on top. 16 | 17 | /////////////////////////////////////////////////////////////////////////////// 18 | // Constructors and Prototypes (Classes) // 19 | /////////////////////////////////////////////////////////////////////////////// 20 | 21 | // Tile - shared parent for Piece and Space 22 | function Tile(x, y, colorCode) { 23 | this.renderDiv(colors[colorCode]); 24 | this.setTransform(x, y); 25 | } 26 | Tile.prototype.renderDiv = function (className) { 27 | var div = this.div = document.createElement('div'); 28 | div.setAttribute('class', className); 29 | div.sprite = this; 30 | container.appendChild(div); 31 | return div; 32 | }; 33 | // Move the sprite to a specified offset using hardware accel. 34 | Tile.prototype.setTransform = function (x, y) { 35 | this.x = x; 36 | this.y = y; 37 | var px = x * 55 + 10, 38 | py = 47 + y * 64 - (x % 2) * 32; 39 | this.div.style.webkitTransform = "translate3d(" + px + "px, " + py + "px, 0)"; 40 | }; 41 | Tile.prototype.destroy = function () { 42 | container.removeChild(this.div); 43 | }; 44 | 45 | // Super minimal OOP inheritance library 46 | Tile.adopt = function (child) { 47 | child.__proto__ = this; 48 | child.prototype.__proto__ = this.prototype; 49 | child.parent = this; 50 | }; 51 | 52 | /////////////////////////////////////////////////////////////////////////////// 53 | // Space - grid spaces on the board 54 | function Space(x, y) { 55 | Tile.call(this, x, y, 0); 56 | } 57 | Tile.adopt(Space); 58 | Space.prototype.onClick = function (evt) { 59 | if (selected) { 60 | socket.send({move: {id: selected.id, x: this.x, y: this.y}}); 61 | selected.deselect(); 62 | } 63 | }; 64 | 65 | 66 | /////////////////////////////////////////////////////////////////////////////// 67 | // Piece - moving, colored hexagon piece 68 | function Piece(x, y, colorCode) { 69 | // If this piece already exists, then reuse it and move 70 | // to the new location. 71 | if (pieces.hasOwnProperty(colorCode)) { 72 | pieces[colorCode].moveTo(x, y); 73 | return pieces[colorCode]; 74 | } 75 | // Create a new Piece and put in the index 76 | Tile.call(this, x, y, colorCode); 77 | // In this simple I'm using the colorCode as the ID, in a real game these 78 | // probably need to be separate 79 | this.id = colorCode; 80 | pieces[colorCode] = this; 81 | } 82 | Tile.adopt(Piece); // Piece inherits from Tile 83 | Piece.prototype.renderDiv = function () { 84 | var div = Piece.parent.prototype.renderDiv.apply(this, arguments); 85 | var child = document.createElement('div'); 86 | this.child = child; 87 | child.style.display = "none"; 88 | div.appendChild(child); 89 | return div; 90 | }; 91 | // Move the piece, but animate over a time lapse 92 | Piece.prototype.moveTo = function (x, y, time) { 93 | time = time || 1; 94 | this.div.style.zIndex = zIndex++; 95 | this.div.style.webkitTransitionDuration = time + "s"; 96 | this.setTransform(x, y); 97 | }; 98 | Piece.prototype.select = function () { 99 | if (selected) { 100 | selected.deselect(); 101 | } 102 | selected = this; 103 | this.child.style.display = "block"; 104 | }; 105 | Piece.prototype.deselect = function () { 106 | if (selected === this) { 107 | selected = null; 108 | } 109 | this.child.style.display = "none"; 110 | }; 111 | Piece.prototype.onClick = Piece.prototype.select; 112 | 113 | /////////////////////////////////////////////////////////////////////////////// 114 | // External API Commands // 115 | /////////////////////////////////////////////////////////////////////////////// 116 | Commands = { 117 | reload: function () { 118 | window.location.reload(); 119 | }, 120 | map: function (map) { 121 | Object.keys(map).forEach(function (id) { 122 | var params = map[id]; 123 | var piece = new Piece(params.x, params.y, id); 124 | }); 125 | }, 126 | move: function (params) { 127 | if (selected && params.id === selected.id) { 128 | selected.deselect(); 129 | } 130 | var piece = pieces[params.id]; 131 | var distance = Math.sqrt((piece.x - params.x) * (piece.x - params.x) + 132 | (piece.y - params.y) * (piece.y - params.y)); 133 | piece.moveTo(params.x, params.y, distance / 5); 134 | } 135 | }; 136 | 137 | 138 | /////////////////////////////////////////////////////////////////////////////// 139 | // Initialization of window // 140 | /////////////////////////////////////////////////////////////////////////////// 141 | window.addEventListener('load', function () { 142 | 143 | // Connect to the backend server for duplex communication 144 | if (window.location.protocol === 'file:') { 145 | socket = new io.Socket("creationix.com", { 146 | port: 8080, 147 | transports: ['xhr-polling'] 148 | }); 149 | } else { 150 | socket = new io.Socket(); 151 | } 152 | var flail = true; 153 | function tryConnect() { 154 | if (flail) { 155 | socket.connect(); 156 | } 157 | } 158 | setTimeout(tryConnect); 159 | setInterval(tryConnect, 10000); 160 | socket.on('connect', function () { 161 | flail = false; 162 | }); 163 | socket.on('disconnect', function () { 164 | console.error("Got disconnected from server!"); 165 | flail = true; 166 | }); 167 | 168 | // Forward messages from the backend to the Commands object in the client 169 | socket.on('message', function (message) { 170 | Object.keys(message).forEach(function (command) { 171 | if (Commands.hasOwnProperty(command)) { 172 | Commands[command](message[command]); 173 | } else { 174 | console.error("Invalid command " + command); 175 | } 176 | }); 177 | }); 178 | 179 | // Store a reference to the container div in the dom 180 | container = document.getElementById('sprites'); 181 | 182 | // Always fit the sprite container to the window and even auto-rotate 183 | var width = container.clientWidth, 184 | height = container.clientHeight; 185 | function onResize() { 186 | var winWidth = window.innerWidth, 187 | winHeight = window.innerHeight; 188 | var vertical = (height > width) === (winHeight > winWidth); 189 | var transform; 190 | if (vertical) { 191 | transform = "scale(" + 192 | Math.min(winWidth / width, winHeight / height) + ")"; 193 | } else { 194 | transform = "scale(" + 195 | Math.min(winWidth / height, winHeight / width) + ") rotate(-90deg)"; 196 | } 197 | container.style.webkitTransform = transform; 198 | } 199 | window.addEventListener('resize', onResize); 200 | onResize(); 201 | 202 | // Hook up mouse(down, move, up) and touch(down, move, up) to sprites 203 | function findSprite(target) { 204 | if (target === container) { 205 | return; 206 | } 207 | if (target.sprite) { 208 | return target.sprite; 209 | } 210 | if (!target.parentNode) { 211 | return; 212 | } 213 | return findSprite(target.parentNode); 214 | } 215 | var start; 216 | // Listen for mouse and touch events 217 | function onDown(evt) { 218 | start = findSprite(evt.target); 219 | if (!start) { 220 | return; 221 | } 222 | evt.stopPropagation(); 223 | evt.preventDefault(); 224 | if (start.onDown) { 225 | start.onDown(evt); 226 | } 227 | } 228 | function onMove(evt) { 229 | if (!start) { 230 | return; 231 | } 232 | evt.stopPropagation(); 233 | evt.preventDefault(); 234 | if (start.onMove) { 235 | start.onMove(evt); 236 | } 237 | } 238 | function onUp(evt) { 239 | if (!start) { 240 | return; 241 | } 242 | evt.stopPropagation(); 243 | evt.preventDefault(); 244 | if (start.onUp) { 245 | start.onUp(evt); 246 | } else if (start.onClick) { 247 | var end = findSprite(evt.target); 248 | if (start === end) { 249 | start.onClick(evt); 250 | } 251 | } 252 | start = undefined; 253 | } 254 | container.addEventListener('mousedown', onDown, false); 255 | document.addEventListener('mousemove', onMove, false); 256 | document.addEventListener('mouseup', onUp, false); 257 | container.addEventListener('touchstart', function (e) { 258 | if (e.touches.length === 1) { 259 | var touch = e.touches[0]; 260 | touch.stopPropagation = function () { 261 | e.stopPropagation(); 262 | }; 263 | touch.preventDefault = function () { 264 | e.preventDefault(); 265 | }; 266 | onDown(touch); 267 | } 268 | }, false); 269 | document.addEventListener('touchmove', function (e) { 270 | if (e.touches.length === 1) { 271 | var touch = e.touches[0]; 272 | touch.stopPropagation = function () { 273 | e.stopPropagation(); 274 | }; 275 | touch.preventDefault = function () { 276 | e.preventDefault(); 277 | }; 278 | onMove(touch); 279 | } 280 | }, false); 281 | document.addEventListener('touchend', onUp, false); 282 | 283 | // Draw the board on load 284 | for (var x = 0; x < 5; x++) { 285 | for (var y = 0; y < (6 + x % 2); y++) { 286 | new Space(x, y); 287 | } 288 | } 289 | 290 | // Start the palm system if we're in a webOS app 291 | if (typeof PalmSystem !== 'undefined') { 292 | PalmSystem.stageReady(); 293 | if (PalmSystem.enableFullScreenMode) { 294 | PalmSystem.enableFullScreenMode(true); 295 | } 296 | } 297 | 298 | }, false); 299 | -------------------------------------------------------------------------------- /hexes/web/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joyent/node-camp/245abe18b079548cfb29adb0612a5a6fdd48aeb2/hexes/web/icon.png -------------------------------------------------------------------------------- /hexes/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hexes 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /nodeFor145.markdown: -------------------------------------------------------------------------------- 1 | # Node on WebOS 2 | 3 | If you're already a member of the webOS 2.0 Early Access program, then you've 4 | already got all the tools you need to dive in to this workshop. Otherwise, 5 | here's what you need to do to get set up: 6 | 7 | 1. Visit developer.palm.com and click Download SDK 8 | 2. Follow the directions for your operating system to install the SDK and 9 | VirtualBox 10 | 3. Download this backported version of node that works on webOS v1.4.5 11 | 4. Start the emulator by typing `palm-emulator` or clicking on the icon it installed (depending on your OS) 12 | 4. Open a terminal and type`novaterm` to open a shell on the emulator 13 | 5. Change to the internal drive: `cd /media/internal` 14 | 6. Download the zip file to the emulator/phone: 15 | `wget http://nodejs.org/nodeFor145.tar.gz` 16 | 7. Unzip the file: 17 | `tar -xzvf nodeFor145.tar.gz` 18 | 8. Install node by creating prepending it to the system path: 19 | `echo "export PATH=/media/internal/node/bin:\$PATH" >> /etc/profile` 20 | 9. Log out with Control+d and log back in with `novaterm` 21 | 10. Verify it works by typing: `which node` 22 | 23 | (If you're on an actual device, there are alternate binaries in the node/bin folder) 24 | 25 | 26 | To forward port 9000 from the emulator to your local computer do this: 27 | 28 | VBoxManage setextradata "SDK 1.4.5.465 (320x400)" "VBoxInternal/Devices/pcnet/0/LUN#0/Config/ssh/HostPort" 9000 29 | VBoxManage setextradata "SDK 1.4.5.465 (320x400)" "VBoxInternal/Devices/pcnet/0/LUN#0/Config/ssh/GuestPort" 9000 30 | VBoxManage setextradata "SDK 1.4.5.465 (320x400)" "VBoxInternal/Devices/pcnet/0/LUN#0/Config/ssh/Protocol" TCP 31 | 32 | Once you have typed the above commands, you need to close the Guest Machine 33 | (a reboot won't be sufficient). 34 | --------------------------------------------------------------------------------