├── .gitignore
├── README
├── index.html
├── server.js
├── style.css
├── client.js
└── sb.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | socket.io
3 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | You need:
2 |
3 | - an iPad >= 4.2.1
4 | - node.js
5 | - socket.io (server + client)
6 |
7 | Test URL (sorry the socket server won't be online all the time): http://cubiq.org/dropbox/sb/
8 | Lock the device rotation before trying. What you'll see is a red ball you can crontrol by tilting the iPad.
9 | If other users are connected you'll see them as grey balls. Communication happens thanks to websockets.
10 |
11 | *Not ready for production*, this is just a proof of concept.
12 |
13 | To-do:
14 | - would be nice to add collision detection between balls.
15 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Socket Balls!
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var sys = require('sys'),
2 | http = require('http'),
3 | io = require('socket.io'),
4 | server = http.createServer(),
5 | socket = io.listen(server, { transports: ['websocket']}),
6 | players = {};
7 |
8 | // Set up events
9 | socket.on('connection', function (client) {
10 | // Send to the new user the list of active players
11 | client.send({ type: 'playerslist', list: players });
12 |
13 | // Add the new user to the list of players
14 | players[client.sessionId] = { x:0, y:0 }
15 |
16 | // Broadcast the new user to all players
17 | socket.broadcast({ type: 'new', id: client.sessionId }, [client.sessionId]);
18 |
19 | client.on('message', function (message) {
20 | if (message.type != 'position') {
21 | return;
22 | }
23 |
24 | // Broadcast the new user position
25 | players[message.id] = { x: message.x, y: message.y };
26 | //socket.broadcast({ type: 'position', id: message.id, x: message.x, y: message.y }, [client.sessionId]);
27 | client.send({ type: 'position', list: players });
28 | });
29 |
30 | client.on('disconnect', function () {
31 | // Remove the user from the list of players
32 | delete players[this.sessionId];
33 |
34 | // Broadcast the logged out user's id
35 | socket.broadcast({ type: 'leave', id: this.sessionId });
36 | });
37 |
38 | });
39 |
40 | // Start listening
41 | server.listen(8008);
42 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | html, body { height:100%; }
2 | body {
3 | padding:0; margin:0; border:0;
4 | }
5 |
6 | body {
7 | background:#999;
8 | font-family:helvetica;
9 | }
10 |
11 | #stage {
12 | position:absolute;
13 | top:50%; left:50%;
14 | width:700px; height:700px;
15 | margin-top:-350px; margin-left:-350px;
16 | -webkit-border-radius:8px;
17 | background:#fff;
18 | }
19 |
20 | #popup {
21 | display:none;
22 | position:absolute; z-index:1000;
23 | width:600px; height:100px; line-height:100px;
24 | top:50%; left:50%;
25 | margin-left:-300px; margin-top:-50px;
26 | -webkit-border-radius:10px;
27 | background:rgba(0,0,0,0.5);
28 | color:#fff;
29 | text-align:center;
30 | }
31 |
32 | .ball {
33 | position:absolute;
34 | top:0; left:0;
35 | width:40px;
36 | height:40px;
37 | -webkit-border-radius:20px;
38 | -webkit-box-shadow:2px 2px 6px rgba(0,0,0,0.3);
39 | -webkit-transition-property:-webkit-transform;
40 | -webkit-transition-timing-function:linear;
41 | -webkit-transition-duration:0;
42 | -webkit-transform:translate3d(0,0,0);
43 | }
44 |
45 | .player {
46 | z-index:100;
47 | background-color:#c30;
48 | background-image:-webkit-gradient(radial, 15 15, 0, 15 15, 40, from(rgba(255,60,60,1)), color-stop(0.1, rgba(255,60,60,1)), to(rgba(20,0,0,1)));
49 | }
50 |
51 | .opponent {
52 | z-index:10;
53 | background-color:#ccc;
54 | background-image:-webkit-gradient(radial, 15 15, 0, 15 15, 40, from(rgba(250,250,250,1)), color-stop(0.1, rgba(250,250,250,1)), to(rgba(20,20,20,1)));
55 | }
--------------------------------------------------------------------------------
/client.js:
--------------------------------------------------------------------------------
1 |
2 | var socket = new io.Socket(null, { port: 8008, transports: ['websocket'] }),
3 | sessionid = 0,
4 | connectTimeout;
5 |
6 | function socketInit() {
7 | var connection = socket.connect();
8 |
9 | connectTimeout = setTimeout(function () {
10 | if (connection.connected === true) {
11 | return;
12 | }
13 |
14 | var popup = document.getElementById('popup');
15 | popup.innerHTML = 'Sorry, the server is probably down. Retry later';
16 | popup.style.display = 'block';
17 | }, 3000);
18 |
19 | socket.on('connect', function () {
20 | clearTimeout(connectTimeout);
21 |
22 | sessionid = this.transport.sessionid;
23 |
24 | me = new Player(sessionid, 'player');
25 |
26 | mainLoop = setInterval(moveMe);
27 | });
28 |
29 | socket.on('message', function (message) {
30 | switch (message.type) {
31 | case 'position':
32 | // Update players position
33 | updatePosition(message.list);
34 | ready = true;
35 | break;
36 | case 'playerslist':
37 | // Create all opponents
38 | createOpponents(message.list);
39 | ready = true; // ready to communicate with socket server
40 | break;
41 | case 'new':
42 | // New player joined
43 | players.push(new Player(message.id, 'opponent'));
44 | break;
45 | case 'leave':
46 | // Player disconnected
47 | leave(message.id);
48 | break;
49 | }
50 | });
51 |
52 | socket.on('disconnect', function () {
53 | document.getElementById('popup').style.display = 'block';
54 | });
55 | }
56 |
57 | function sendPosition () {
58 | if (ready) {
59 | ready = false;
60 |
61 | var pos = buffer.length ? buffer[0] : { x:me.x, y:me.y };
62 | buffer.shift();
63 |
64 | socket.send({ type:'position', id:me.id, x:pos.x, y:pos.y });
65 | }
66 | }
67 |
68 | function updatePosition (data) {
69 | var id, i, l;
70 |
71 | for (i=0, l=players.length; i stageW - diameter) {
47 | x = stageW - diameter;
48 | that.vx *= bounce;
49 | } else if (x < 0) {
50 | x = 0;
51 | that.vx *= bounce;
52 | }
53 |
54 | if (y > stageH - diameter) {
55 | y = stageH - diameter;
56 | that.vy *= bounce;
57 | } else if (y < 0) {
58 | y = 0;
59 | that.vy *= bounce;
60 | }
61 |
62 | that.vx += ax;
63 | that.vy += ay;
64 |
65 | that.update(x, y);
66 | },
67 |
68 | update: function (x, y) {
69 | var that = this;
70 | that.x = x;
71 | that.y = y;
72 | that.ball.style.webkitTransform = 'translate3d(' + that.x + 'px,'+ that.y +'px,0)';
73 | },
74 |
75 | remove: function () {
76 | stage.removeChild(this.ball);
77 | }
78 | }
79 |
80 | // Main Game Loop
81 | function moveMe () {
82 | me.move();
83 |
84 | buffer[buffer.length] = { x: me.x, y: me.y };
85 |
86 | if (buffer.length > 10) buffer.shift();
87 |
88 | sendPosition();
89 | }
90 |
91 | window.addEventListener('devicemotion', function (e) {
92 | ax = e.accelerationIncludingGravity.x * sensitivity;
93 | ay = -e.accelerationIncludingGravity.y * sensitivity;
94 | }, false);
95 |
96 | window.addEventListener('load', function () {
97 | stage = document.getElementById('stage');
98 | stageW = stage.clientWidth;
99 | stageH = stage.clientHeight;
100 |
101 | var popup = document.getElementById('popup');
102 |
103 | if (!navigator.appVersion.match(/ipad/gi)) {
104 | popup.innerHTML = 'Sorry, this app is for iPad only';
105 | popup.style.display = 'block';
106 | return;
107 | }
108 |
109 | if (!('ondevicemotion' in window)) {
110 | popup.innerHTML = 'Sorry, you need iPad ≥ 4.2 to run this app';
111 | document.getElementById('popup').style.display = 'block';
112 | return;
113 | }
114 |
115 | socketInit();
116 | }, false);
117 |
--------------------------------------------------------------------------------