├── README.md
├── public
├── favicon.ico
├── tile_bg.jpg
└── js
│ ├── memorygame.js
│ └── datachannel.js
├── package.json
├── .gitignore
├── server.js
└── views
└── index.ejs
/README.md:
--------------------------------------------------------------------------------
1 | # memory-webrtc-data-channel
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agilityfeat/memory-webrtc-data-channel/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/public/tile_bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/agilityfeat/memory-webrtc-data-channel/HEAD/public/tile_bg.jpg
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "memory-webrtc-data-channel",
3 | "version": "0.0.1",
4 | "private": true,
5 | "dependencies": {
6 | "express": "4.x",
7 | "ejs": "2.x",
8 | "express.io": "1.x"
9 | }
10 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | /*
2 | This code was developed by @ArinSime and WebRTC.ventures for a WebRTC blog post.
3 | You are welcome to use it at your own risk as starter code for your applications,
4 | but please be aware that this is not a complete code example with all the necessary
5 | security and privacy considerations implemented that a production app would require.
6 | It is for educational purposes only, and any other use is done at your own risk.
7 | */
8 |
9 | //Server.js: This is the core Node.js configuration code, and also used for
10 | //setting up signaling channels to be used by socket.io
11 |
12 | var express = require('express.io');
13 | var app = express();
14 | app.http().io();
15 | var PORT = 3000;
16 | console.log('server started on port ' + PORT);
17 |
18 | app.use(express.static(__dirname + '/public'));
19 |
20 | app.get('/', function(req, res){
21 | res.render('index.ejs');
22 | });
23 |
24 | app.listen(process.env.PORT || PORT);
25 |
26 | app.io.route('ready', function(req) {
27 | req.io.join(req.data.signal_room);
28 | })
29 |
30 | app.io.route('signal', function(req) {
31 | //Note the use of req here for broadcasting so only the sender doesn't receive their own messages
32 | req.io.room(req.data.room).broadcast('signaling_message', {
33 | type: req.data.type,
34 | message: req.data.message
35 | });
36 | })
37 |
--------------------------------------------------------------------------------
/views/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | DataChannel Example
5 |
6 |
11 |
12 |
13 |
14 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
Data Channel Messages:
48 |
Signaling Messages:
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/public/js/memorygame.js:
--------------------------------------------------------------------------------
1 | // Scripted By Adam Khoury in connection with the following video tutorial:
2 | // http://www.youtube.com/watch?v=c_ohDPWmsM0
3 | // https://www.developphp.com/video/JavaScript/Memory-Game-Programming-Tutorial
4 |
5 | // Some changes made by @ArinSime to this Memory Game in order to accomodate use of WebRTC Data Channel to show a simple multi-player use case
6 |
7 | var memory_array = ['A','A','B','B','C','C','D','D','E','E','F','F','G','G','H','H','I','I','J','J','K','K','L','L'];
8 | var memory_values = [];
9 | var memory_tile_ids = [];
10 | var tiles_flipped = 0;
11 | Array.prototype.memory_tile_shuffle = function(){
12 | var i = this.length, j, temp;
13 | while(--i > 0){
14 | j = Math.floor(Math.random() * (i+1));
15 | temp = this[j];
16 | this[j] = this[i];
17 | this[i] = temp;
18 | }
19 | }
20 | function newBoard(){
21 | tiles_flipped = 0;
22 | var output = '';
23 | for(var i = 0; i < memory_array.length; i++){
24 | output += '';
25 | }
26 | document.getElementById('memory_board').innerHTML = output;
27 | }
28 | function memoryFlipTile(tile,val){
29 | dataChannel.send("memoryFlipTile " + tile.id);
30 | flipTheTile(tile,val);
31 | }
32 |
33 | function flipTheTile(tile,val){
34 |
35 | if(tile.innerHTML == "" && memory_values.length < 2){
36 | tile.style.background = '#FFF';
37 | tile.innerHTML = val;
38 | if(memory_values.length == 0){
39 | memory_values.push(val);
40 | memory_tile_ids.push(tile.id);
41 | } else if(memory_values.length == 1){
42 | memory_values.push(val);
43 | memory_tile_ids.push(tile.id);
44 | if(memory_values[0] == memory_values[1]){
45 | tiles_flipped += 2;
46 | // Clear both arrays
47 | memory_values = [];
48 | memory_tile_ids = [];
49 | // Check to see if the whole board is cleared
50 | if(tiles_flipped == memory_array.length){
51 | alert("Board cleared... generating new board");
52 | document.getElementById('memory_board').innerHTML = "";
53 | newBoard();
54 | }
55 | } else {
56 | function flip2Back(){
57 | // Flip the 2 tiles back over
58 | var tile_1 = document.getElementById(memory_tile_ids[0]);
59 | var tile_2 = document.getElementById(memory_tile_ids[1]);
60 | tile_1.style.background = 'url(tile_bg.jpg) no-repeat';
61 | tile_1.innerHTML = "";
62 | tile_2.style.background = 'url(tile_bg.jpg) no-repeat';
63 | tile_2.innerHTML = "";
64 | // Clear both arrays
65 | memory_values = [];
66 | memory_tile_ids = [];
67 | }
68 | setTimeout(flip2Back, 700);
69 | }
70 | }
71 | }
72 | }
73 |
74 | var setupBoard = document.querySelector("#setupBoard");
75 | setupBoard.addEventListener('click', function(ev){
76 | memory_array.memory_tile_shuffle();
77 | newBoard();
78 | dataChannel.send("newBoard " + memory_array.toString());
79 | ev.preventDefault();
80 | }, false);
--------------------------------------------------------------------------------
/public/js/datachannel.js:
--------------------------------------------------------------------------------
1 | /*
2 | This code was developed by @ArinSime and WebRTC.ventures for a WebRTC blog post.
3 | You are welcome to use it at your own risk as starter code for your applications,
4 | but please be aware that this is not a complete code example with all the necessary
5 | security and privacy considerations implemented that a production app would require.
6 | It is for educational purposes only, and any other use is done at your own risk.
7 | */
8 |
9 | //datachannel.js: This file contains the WebRTC and DataChannel specific code
10 |
11 | //Page controls
12 | var chatArea = document.querySelector("#chatArea");
13 | var signalingArea = document.querySelector("#signalingArea");
14 |
15 | //Signaling Code Setup
16 | var SIGNAL_ROOM = "signaling";
17 | var configuration = {
18 | 'iceServers': [{
19 | 'url': 'stun:stun.l.google.com:19302'
20 | }]
21 | };
22 | var rtcPeerConn;
23 | var dataChannelOptions = {
24 | ordered: false, //no guaranteed delivery, unreliable but faster
25 | maxRetransmitTime: 1000, //milliseconds
26 | };
27 | var dataChannel;
28 |
29 | io = io.connect();
30 | io.emit('ready', {"signal_room": SIGNAL_ROOM});
31 |
32 | //Send a first signaling message to anyone listening
33 | //In other apps this would be on a button click, we are just doing it on page load
34 | io.emit('signal',{"type":"user_here", "message":"Would you like to play a game?", "room":SIGNAL_ROOM});
35 |
36 | io.on('signaling_message', function(data) {
37 | displaySignalMessage("Signal received: " + data.type);
38 | //Setup the RTC Peer Connection object
39 | if (!rtcPeerConn)
40 | startSignaling();
41 |
42 | if (data.type != "user_here") {
43 | var message = JSON.parse(data.message);
44 | if (message.sdp) {
45 | rtcPeerConn.setRemoteDescription(new RTCSessionDescription(message.sdp), function () {
46 | // if we received an offer, we need to answer
47 | if (rtcPeerConn.remoteDescription.type == 'offer') {
48 | rtcPeerConn.createAnswer(sendLocalDesc, logError);
49 | }
50 | }, logError);
51 | }
52 | else {
53 | rtcPeerConn.addIceCandidate(new RTCIceCandidate(message.candidate));
54 | }
55 | }
56 |
57 | });
58 |
59 | function startSignaling() {
60 | displaySignalMessage("starting signaling...");
61 | rtcPeerConn = new webkitRTCPeerConnection(configuration, null);
62 | dataChannel = rtcPeerConn.createDataChannel('textMessages', dataChannelOptions);
63 |
64 | dataChannel.onopen = dataChannelStateChanged;
65 | rtcPeerConn.ondatachannel = receiveDataChannel;
66 |
67 | // send any ice candidates to the other peer
68 | rtcPeerConn.onicecandidate = function (evt) {
69 | if (evt.candidate)
70 | io.emit('signal',{"type":"ice candidate", "message": JSON.stringify({ 'candidate': evt.candidate }), "room":SIGNAL_ROOM});
71 | displaySignalMessage("completed that ice candidate...");
72 | };
73 |
74 | // let the 'negotiationneeded' event trigger offer generation
75 | rtcPeerConn.onnegotiationneeded = function () {
76 | displaySignalMessage("on negotiation called");
77 | rtcPeerConn.createOffer(sendLocalDesc, logError);
78 | }
79 | }
80 |
81 | function sendLocalDesc(desc) {
82 | rtcPeerConn.setLocalDescription(desc, function () {
83 | displaySignalMessage("sending local description");
84 | io.emit('signal',{"type":"SDP", "message": JSON.stringify({ 'sdp': rtcPeerConn.localDescription }), "room":SIGNAL_ROOM});
85 | }, logError);
86 | }
87 |
88 | //Data Channel Specific methods
89 | function dataChannelStateChanged() {
90 | if (dataChannel.readyState === 'open') {
91 | displaySignalMessage("Data Channel open");
92 | dataChannel.onmessage = receiveDataChannelMessage;
93 | }
94 | }
95 |
96 | function receiveDataChannel(event) {
97 | displaySignalMessage("Receiving a data channel");
98 | dataChannel = event.channel;
99 | dataChannel.onmessage = receiveDataChannelMessage;
100 | }
101 |
102 | function receiveDataChannelMessage(event) {
103 | displayMessage("From DataChannel: " + event.data);
104 |
105 | if (event.data.split(" ")[0] == "memoryFlipTile") {
106 | var tileToFlip = event.data.split(" ")[1];
107 | displayMessage("Flipping tile " + tileToFlip);
108 | var tile = document.querySelector("#" + tileToFlip);
109 | var index = tileToFlip.split("_")[1];
110 | var tile_value = memory_array[index];
111 | flipTheTile(tile,tile_value);
112 | } else if (event.data.split(" ")[0] == "newBoard") {
113 | displayMessage("Setting up new board");
114 | memory_array = event.data.split(" ")[1].split(",");
115 | newBoard();
116 | }
117 |
118 | }
119 |
120 | //Logging/Display Methods
121 | function logError(error) {
122 | displaySignalMessage(error.name + ': ' + error.message);
123 | }
124 |
125 | function displayMessage(message) {
126 | chatArea.innerHTML = chatArea.innerHTML + "
" + message;
127 | }
128 |
129 | function displaySignalMessage(message) {
130 | signalingArea.innerHTML = signalingArea.innerHTML + "
" + message;
131 | }
--------------------------------------------------------------------------------