├── drawing
├── rules.json
├── index.html
├── css
│ └── drawing.css
└── js
│ └── drawing.js
├── presence
├── index.html
├── rules.json
├── css
│ └── presence.css
└── js
│ ├── presence.js
│ └── idle.js
├── chat
├── index.html
├── rules.json
├── js
│ └── chat.js
└── css
│ └── chat.css
├── tetris
├── index.html
├── css
│ └── tetris.css
├── rules.json
└── js
│ └── tetris.js
├── leaderboard
├── index.html
├── rules.json
├── css
│ └── leaderboard.css
└── js
│ └── leaderboard.js
├── LICENSE
└── README.md
/drawing/rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | // Let everyone read and write to the "drawing" node
4 | "drawing": {
5 | ".read": true,
6 | ".write": true,
7 |
8 | "$pixel": {
9 | // The key name must have the format "##:##" and the value must be a string of three characters
10 | ".validate": "$pixel.matches(/^\\d{1,2}:\\d{1,2}$/) && newData.isString() && newData.val().length === 3"
11 | }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/presence/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Firebase Presence Example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Firebase Realtime Presence
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/presence/rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | // Let everyone read and write to the "presence" node
4 | "presence": {
5 | ".read": true,
6 | ".write": true,
7 |
8 | "$user": {
9 | // Each user must contain a "name" and "status"
10 | ".validate": "newData.hasChildren(['name', 'status'])",
11 |
12 | // "name" must be a string
13 | "name": {
14 | ".validate": "newData.isString()"
15 | },
16 |
17 | // "status" must be a string
18 | "status": {
19 | ".validate": "newData.isString()"
20 | },
21 |
22 | // No other children are allowed
23 | "$other": {
24 | ".validate": false
25 | }
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/chat/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Firebase Basic Chat Example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Firebase Realtime Chat
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/drawing/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Firebase Collaborative Drawing Example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Firebase Realtime Collaborative Drawing
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/chat/rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | // Let everyone read and write to the "chat" node
4 | "chat": {
5 | ".read": true,
6 | ".write": true,
7 |
8 | "$message": {
9 | // Existing messages cannot be overwritten and each message must contain a "name" and "text"
10 | ".validate": "!data.exists() && newData.hasChildren(['name', 'text'])",
11 |
12 | // "name" must be a string
13 | "name": {
14 | ".validate": "newData.isString()"
15 | },
16 |
17 | // "text" must be a string
18 | "text": {
19 | ".validate": "newData.isString()"
20 | },
21 |
22 | // No other children are allowed
23 | "$other": {
24 | ".validate": false
25 | }
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/chat/js/chat.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 | // Get a reference to the root of the chat data.
3 | var messagesRef = new Firebase("https://INSTANCE.firebaseio.com/chat");
4 |
5 | // When the user presses enter on the message input, write the message to firebase.
6 | $("#messageInput").keypress(function (e) {
7 | if (e.keyCode == 13) {
8 | var name = $("#nameInput").val();
9 | var text = $("#messageInput").val();
10 | messagesRef.push({name:name, text:text});
11 | $("#messageInput").val("");
12 | }
13 | });
14 |
15 | // Add a callback that is triggered for each chat message.
16 | messagesRef.limitToLast(10).on("child_added", function (snapshot) {
17 | var message = snapshot.val();
18 | $("").text(message.text).prepend($("")
19 | .text(message.name + ": ")).appendTo($("#messagesDiv"));
20 | $("#messagesDiv")[0].scrollTop = $("#messagesDiv")[0].scrollHeight;
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/tetris/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Firebase Tetris Example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Firebase Realtime Tetris
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Game is in progress. You will automatically join if either player leaves.
29 |
30 |
31 |
--------------------------------------------------------------------------------
/presence/css/presence.css:
--------------------------------------------------------------------------------
1 | /* Global */
2 | body {
3 | margin-top: 10px;
4 | margin-left: auto;
5 | margin-right: auto;
6 | width: 500px;
7 | background-color: #f8f8f8;
8 | font-size: 24px;
9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
10 | color: #424547;
11 | text-align: center;
12 | }
13 |
14 | h1 {
15 | font-size: 36px;
16 | font-weight: bold;
17 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
18 | color: #424547;
19 | }
20 |
21 | h3 {
22 | font-size: 24px;
23 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
24 | color: #424547;
25 | }
26 |
27 | p {
28 | font-size: 16px;
29 | }
30 |
31 | input {
32 | font-size: 24px;
33 | }
34 |
35 | input[type=text] {
36 | color: #424547;
37 | border: 1px solid #c2c2c2;
38 | background-color: white;
39 | }
40 |
41 | em {
42 | font-style: normal;
43 | font-weight: bold;
44 | color: black;
45 | }
46 |
47 | /* Presence */
48 | #presenceDiv {
49 | text-align: center;
50 | }
51 |
--------------------------------------------------------------------------------
/leaderboard/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Firebase Leaderboard Example
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Firebase Realtime Leaderboard
20 |
21 |
22 |
23 | | Name |
24 | Score |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Highest score so far:
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/leaderboard/rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | // Let everyone read and write to the "leaderboard" node
4 | "leaderboard": {
5 | ".read": true,
6 | ".write": true,
7 |
8 | // The highest score must be a number
9 | "highestScore": {
10 | ".validate": "newData.isNumber()"
11 | },
12 |
13 | "scoreList": {
14 | "$name": {
15 | // Each score must contain a "name" and "text"
16 | ".validate": "newData.hasChildren(['name', 'score'])",
17 |
18 | // "name" must be a string
19 | "name": {
20 | ".validate": "newData.isString()"
21 | },
22 |
23 | // "score" must be a number
24 | "score": {
25 | ".validate": "newData.isNumber()"
26 | },
27 |
28 | // No other children are allowed
29 | "$other": {
30 | ".validate": false
31 | }
32 | }
33 | },
34 |
35 | // No other children are allowed
36 | "$other": {
37 | ".validate": false
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Firebase
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/leaderboard/css/leaderboard.css:
--------------------------------------------------------------------------------
1 | /* Global */
2 | body {
3 | margin-top: 10px;
4 | margin-left: auto;
5 | margin-right: auto;
6 | width: 500px;
7 | background-color: #f8f8f8;
8 | font-size: 24px;
9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
10 | color: #424547;
11 | text-align: center;
12 | }
13 |
14 | h1 {
15 | font-size: 36px;
16 | font-weight: bold;
17 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
18 | color: #424547;
19 | }
20 |
21 | h3 {
22 | font-size: 24px;
23 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
24 | color: #424547;
25 | }
26 |
27 | p {
28 | font-size: 16px;
29 | }
30 |
31 | input {
32 | font-size: 24px;
33 | }
34 |
35 | input[type=text] {
36 | color: #424547;
37 | border: 1px solid #c2c2c2;
38 | background-color: white;
39 | }
40 |
41 | em {
42 | font-style: normal;
43 | font-weight: bold;
44 | color: black;
45 | }
46 |
47 |
48 | /* Leaderboard */
49 | #leaderboardTable {
50 | margin: 0 auto 20px auto;
51 | text-align: center;
52 | }
53 |
54 | #nameInput {
55 | width: 26%;
56 | }
57 |
58 | #messageInput {
59 | width: 68%;
60 | }
61 |
62 | #highestScore {
63 | margin-top: 20px;
64 | }
65 |
--------------------------------------------------------------------------------
/tetris/css/tetris.css:
--------------------------------------------------------------------------------
1 | /* Global */
2 | body {
3 | margin-top: 10px;
4 | margin-left: auto;
5 | margin-right: auto;
6 | width: 600px;
7 | background-color: #f8f8f8;
8 | font-size: 24px;
9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
10 | color: #424547;
11 | text-align: center;
12 | }
13 |
14 | h1 {
15 | font-size: 36px;
16 | font-weight: bold;
17 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
18 | color: #424547;
19 | }
20 |
21 | h3 {
22 | font-size: 24px;
23 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
24 | color: #424547;
25 | }
26 |
27 | p {
28 | font-size: 16px;
29 | }
30 |
31 | input {
32 | font-size: 24px;
33 | }
34 |
35 | input[type=text] {
36 | color: #424547;
37 | border: 1px solid #c2c2c2;
38 | background-color: white;
39 | }
40 |
41 | em {
42 | font-style: normal;
43 | font-weight: bold;
44 | color: black;
45 | }
46 |
47 |
48 | /* Tetris */
49 | #canvas0, #canvas1 {
50 | display: inline-block;
51 | border: 4px solid #424547;
52 | }
53 |
54 | #canvas0 {
55 | margin-right: 50px;
56 | }
57 |
58 | #restartButton {
59 | margin-top: 5px;
60 | }
61 |
62 | #gameInProgress {
63 | font-size: 14px;
64 | }
65 |
66 | .hide {
67 | display: none;
68 | }
69 |
--------------------------------------------------------------------------------
/chat/css/chat.css:
--------------------------------------------------------------------------------
1 | /* Global */
2 | body {
3 | margin-top: 10px;
4 | margin-left: auto;
5 | margin-right: auto;
6 | width: 500px;
7 | background-color: #f8f8f8;
8 | font-size: 24px;
9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
10 | color: #424547;
11 | text-align: center;
12 | }
13 |
14 | h1 {
15 | font-size: 36px;
16 | font-weight: bold;
17 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
18 | color: #424547;
19 | }
20 |
21 | h3 {
22 | font-size: 24px;
23 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
24 | color: #424547;
25 | }
26 |
27 | p {
28 | font-size: 16px;
29 | }
30 |
31 | input {
32 | font-size: 24px;
33 | }
34 |
35 | input[type=text] {
36 | color: #424547;
37 | border: 1px solid #c2c2c2;
38 | background-color: white;
39 | }
40 |
41 | em {
42 | font-style: normal;
43 | font-weight: bold;
44 | color: black;
45 | }
46 |
47 |
48 | /* Chat */
49 | #messagesDiv {
50 | background-color: white;
51 | overflow: auto;
52 | height: 230px;
53 | width: 100%;
54 | padding: 10px;
55 | border: 8px solid #424547;
56 | margin-bottom: 5px;
57 | text-align: left;
58 | }
59 |
60 | #nameInput {
61 | width: 26%;
62 | }
63 |
64 | #messageInput {
65 | width: 68%;
66 | }
67 |
--------------------------------------------------------------------------------
/drawing/css/drawing.css:
--------------------------------------------------------------------------------
1 | /* Global */
2 | body {
3 | margin-top: 10px;
4 | margin-left: auto;
5 | margin-right: auto;
6 | width: 500px;
7 | background-color: #f8f8f8;
8 | font-size: 24px;
9 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
10 | color: #424547;
11 | text-align: center;
12 | }
13 |
14 | h1 {
15 | font-size: 36px;
16 | font-weight: bold;
17 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
18 | color: #424547;
19 | }
20 |
21 | h3 {
22 | font-size: 24px;
23 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
24 | color: #424547;
25 | }
26 |
27 | p {
28 | font-size: 16px;
29 | }
30 |
31 | input {
32 | font-size: 24px;
33 | }
34 |
35 | input[type=text] {
36 | color: #424547;
37 | border: 1px solid #c2c2c2;
38 | background-color: white;
39 | }
40 |
41 | em {
42 | font-style: normal;
43 | font-weight: bold;
44 | color: black;
45 | }
46 |
47 |
48 | /* Drawing */
49 | #colorholder {
50 | width: 480px;
51 | height: 30px;
52 | border: 2px solid #424547;
53 | margin-top: 5px;
54 | margin-left: auto;
55 | margin-right: auto;
56 | }
57 |
58 | #drawing-canvas {
59 | border: 3px solid #999
60 | }
61 |
62 | .colorbox {
63 | width: 22px;
64 | height: 22px;
65 | margin: 1px;
66 | display: inline-block;
67 | border: 3px solid black;
68 | }
69 |
--------------------------------------------------------------------------------
/tetris/rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | // Let everyone read and write to the "tetris" node
4 | "tetris": {
5 | ".read": true,
6 | ".write": true,
7 |
8 | "$player": {
9 | // The parent key name must be "player0" or "player1" and each user must contain "board", "piece", and "restart"
10 | ".validate": "($player === 'player0' || $player === 'player1')",
11 |
12 | // "online" must be a boolean
13 | "online": {
14 | ".validate": "newData.isBoolean()"
15 | },
16 |
17 | // "board" must be a list of strings representing each line
18 | "board": {
19 | "$lineNum": {
20 | ".validate": "$lineNum.length === 2 && newData.isString() && newData.val().length === 10"
21 | }
22 | },
23 |
24 | // "piece" must contain a bunch of numeric fields
25 | "piece": {
26 | "pieceNum": {
27 | ".validate": "newData.isNumber()"
28 | },
29 | "rotation": {
30 | ".validate": "newData.isNumber()"
31 | },
32 | "x": {
33 | ".validate": "newData.isNumber()"
34 | },
35 | "y": {
36 | ".validate": "newData.isNumber()"
37 | },
38 |
39 | // No other children are allowed
40 | "$other": {
41 | ".validate": false
42 | }
43 | },
44 |
45 | // "restart" must be a number
46 | "restart": {
47 | ".validate": "newData.isNumber()"
48 | },
49 |
50 | // No other children are allowed
51 | "$other": {
52 | ".validate": false
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Status: Archived
2 | This repository has been archived and is no longer maintained.
3 |
4 | 
5 |
6 | # These are legacy Firebase example (for SDK 2.x.x). You probably want to use one of the up-to-date examples at https://firebase.google.com/docs/samples
7 |
8 | ---
9 |
10 |
11 | # Firebase Examples
12 |
13 | A set of single-page examples demonstrating the use of [Firebase](https://www.firebase.com?utm_source=examples).
14 | The following examples are included:
15 |
16 | * [Drawing](https://www.firebase.com/tutorial/#example/drawing) - share a canvas with other users and draw together
17 | * [Chat](https://www.firebase.com/tutorial/#example/chat) - an easy-to-integrate chatroom for your site
18 | * [Presence](https://www.firebase.com/tutorial/#example/presence) - show who is available, who is idle, and who is gone
19 | * [Leaderboard](https://www.firebase.com/tutorial/#example/leaderboard) - see who is winning and how it changes as scores come in
20 | * [Multiplayer Tetris](https://www.firebase.com/tutorial/#example/tetris) - play head-to-head Tetris in your browser
21 |
22 |
23 | ## Getting Started with Firebase
24 |
25 | These examples use Firebase to store and sync data. You can [sign up here for a free
26 | account](https://www.firebase.com/signup/?utm_source=examples).
27 |
28 |
29 | ## Live Demos
30 |
31 | All of these examples can be downloaded and run a live website in a few seconds using
32 | [Firebase Hosting](https://www.firebase.com/hosting.html) and the [Firebase
33 | Tools](https://github.com/firebase/firebase-tools). First, you need to download `firebase-tools`:
34 |
35 | ```bash
36 | npm install -g firebase-tools
37 | ```
38 |
39 | Then, choose a template from the bootstrap list:
40 |
41 | ```bash
42 | firebase bootstrap
43 | ```
44 |
45 | Once the template is downloaded, `cd` to the created directory and deploy to Firebase Hosting:
46 |
47 | ```bash
48 | firebase deploy
49 | ```
50 |
51 | You can then view the example on your web browser by running:
52 |
53 | ```bash
54 | firebase open
55 | ```
56 |
--------------------------------------------------------------------------------
/presence/js/presence.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 | // Prompt the user for a name to use.
3 | var name = prompt("Your name?", "Guest"),
4 | currentStatus = "\u2605 online";
5 |
6 | var firebaseRef = new Firebase("https://INSTANCE.firebaseio.com");
7 |
8 | // Get a reference to the presence data in Firebase.
9 | var userListRef = firebaseRef.child("presence");
10 |
11 | // Generate a reference to a new location for my user with push.
12 | var myUserRef = userListRef.push();
13 |
14 | // Get a reference to my own presence status.
15 | var connectedRef = firebaseRef.child(".info/connected");
16 | connectedRef.on("value", function(isOnline) {
17 | if (isOnline.val()) {
18 | // If we lose our internet connection, we want ourselves removed from the list.
19 | myUserRef.onDisconnect().remove();
20 |
21 | // Set our initial online status.
22 | setUserStatus("\u2605 online");
23 | } else {
24 |
25 | // We need to catch anytime we are marked as offline and then set the correct status. We
26 | // could be marked as offline 1) on page load or 2) when we lose our internet connection
27 | // temporarily.
28 | setUserStatus(currentStatus);
29 | }
30 | });
31 |
32 | // A helper function to let us set our own state.
33 | function setUserStatus(status) {
34 | // Set our status in the list of online users.
35 | currentStatus = status;
36 | myUserRef.set({ name: name, status: status });
37 | }
38 |
39 | // Update our GUI to show someone"s online status.
40 | userListRef.on("child_added", function(snapshot) {
41 | var user = snapshot.val();
42 | $("#presenceDiv").append($("").attr("id", snapshot.key()));
43 | $("#" + snapshot.key()).text(user.name + " is currently " + user.status);
44 | });
45 |
46 | // Update our GUI to remove the status of a user who has left.
47 | userListRef.on("child_removed", function(snapshot) {
48 | $("#" + snapshot.key()).remove();
49 | });
50 |
51 | // Update our GUI to change a user"s status.
52 | userListRef.on("child_changed", function(snapshot) {
53 | var user = snapshot.val();
54 | $("#" + snapshot.key()).text(user.name + " is currently " + user.status);
55 | });
56 |
57 | // Use idle/away/back events created by idle.js to update our status information.
58 | document.onIdle = function () {
59 | setUserStatus("\u2606 idle");
60 | }
61 | document.onAway = function () {
62 | setUserStatus("\u2604 away");
63 | }
64 | document.onBack = function (isIdle, isAway) {
65 | setUserStatus("\u2605 online");
66 | }
67 |
68 | setIdleTimeout(5000);
69 | setAwayTimeout(10000);
70 | });
71 |
--------------------------------------------------------------------------------
/drawing/js/drawing.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 | //Set up some globals
3 | var pixSize = 8, lastPoint = null, currentColor = "000", mouseDown = 0;
4 |
5 | //Create a reference to the pixel data for our drawing.
6 | var pixelDataRef = new Firebase("https://INSTANCE.firebaseio.com/drawing");
7 |
8 | // Set up our canvas
9 | var myCanvas = document.getElementById("drawing-canvas");
10 | var myContext = myCanvas.getContext ? myCanvas.getContext("2d") : null;
11 | if (myContext == null) {
12 | alert("You must use a browser that supports HTML5 Canvas to run this demo.");
13 | return;
14 | }
15 |
16 | //Setup each color palette & add it to the screen
17 | var colors = ["fff","000","f00","0f0","00f","88f","f8d","f88","f05","f80","0f8","cf0","08f","408","ff8","8ff"];
18 | for (c in colors) {
19 | var item = $("").css("background-color", "#" + colors[c]).addClass("colorbox");
20 | item.click((function () {
21 | var col = colors[c];
22 | return function () {
23 | currentColor = col;
24 | };
25 | })());
26 | item.appendTo("#colorholder");
27 | }
28 |
29 | //Keep track of if the mouse is up or down
30 | myCanvas.onmousedown = function () {mouseDown = 1;};
31 | myCanvas.onmouseout = myCanvas.onmouseup = function () {
32 | mouseDown = 0, lastPoint = null;
33 | };
34 |
35 | //Draw a line from the mouse's last position to its current position
36 | var drawLineOnMouseMove = function(e) {
37 | if (!mouseDown) return;
38 |
39 | // Bresenham's line algorithm. We use this to ensure smooth lines are drawn
40 | var offset = $("canvas").offset();
41 | var x1 = Math.floor((e.pageX - offset.left) / pixSize - 1),
42 | y1 = Math.floor((e.pageY - offset.top) / pixSize - 1);
43 | var x0 = (lastPoint == null) ? x1 : lastPoint[0];
44 | var y0 = (lastPoint == null) ? y1 : lastPoint[1];
45 | var dx = Math.abs(x1 - x0), dy = Math.abs(y1 - y0);
46 | var sx = (x0 < x1) ? 1 : -1, sy = (y0 < y1) ? 1 : -1, err = dx - dy;
47 | while (true) {
48 | //write the pixel into Firebase, or if we are drawing white, remove the pixel
49 | pixelDataRef.child(x0 + ":" + y0).set(currentColor === "fff" ? null : currentColor);
50 |
51 | if (x0 == x1 && y0 == y1) break;
52 | var e2 = 2 * err;
53 | if (e2 > -dy) {
54 | err = err - dy;
55 | x0 = x0 + sx;
56 | }
57 | if (e2 < dx) {
58 | err = err + dx;
59 | y0 = y0 + sy;
60 | }
61 | }
62 | lastPoint = [x1, y1];
63 | }
64 | $(myCanvas).mousemove(drawLineOnMouseMove);
65 | $(myCanvas).mousedown(drawLineOnMouseMove);
66 |
67 | // Add callbacks that are fired any time the pixel data changes and adjusts the canvas appropriately.
68 | // Note that child_added events will be fired for initial pixel data as well.
69 | var drawPixel = function(snapshot) {
70 | var coords = snapshot.key().split(":");
71 | myContext.fillStyle = "#" + snapshot.val();
72 | myContext.fillRect(parseInt(coords[0]) * pixSize, parseInt(coords[1]) * pixSize, pixSize, pixSize);
73 | }
74 | var clearPixel = function(snapshot) {
75 | var coords = snapshot.key().split(":");
76 | myContext.clearRect(parseInt(coords[0]) * pixSize, parseInt(coords[1]) * pixSize, pixSize, pixSize);
77 | }
78 |
79 | pixelDataRef.on("child_added", drawPixel);
80 | pixelDataRef.on("child_changed", drawPixel);
81 | pixelDataRef.on("child_removed", clearPixel);
82 | });
83 |
--------------------------------------------------------------------------------
/leaderboard/js/leaderboard.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 | var LEADERBOARD_SIZE = 5;
3 |
4 | // Build some firebase references.
5 | var rootRef = new Firebase("https://INSTANCE.firebaseio.com/leaderboard");
6 | var scoreListRef = rootRef.child("scoreList");
7 | var highestScoreRef = rootRef.child("highestScore");
8 |
9 | // Keep a mapping of firebase locations to HTML elements, so we can move / remove elements as necessary.
10 | var htmlForPath = {};
11 |
12 | // Helper function that takes a new score snapshot and adds an appropriate row to our leaderboard table.
13 | function handleScoreAdded(scoreSnapshot, prevScoreName) {
14 | var newScoreRow = $("
");
15 | newScoreRow.append($(" | ").text(scoreSnapshot.val().name));
16 | newScoreRow.append($(" | ").text(scoreSnapshot.val().score));
17 |
18 | // Store a reference to the table row so we can get it again later.
19 | htmlForPath[scoreSnapshot.key()] = newScoreRow;
20 |
21 | // Insert the new score in the appropriate place in the table.
22 | if (prevScoreName === null) {
23 | $("#leaderboardTable").append(newScoreRow);
24 | }
25 | else {
26 | var lowerScoreRow = htmlForPath[prevScoreName];
27 | lowerScoreRow.before(newScoreRow);
28 | }
29 | }
30 |
31 | // Helper function to handle a score object being removed; just removes the corresponding table row.
32 | function handleScoreRemoved(scoreSnapshot) {
33 | var removedScoreRow = htmlForPath[scoreSnapshot.key()];
34 | removedScoreRow.remove();
35 | delete htmlForPath[scoreSnapshot.key()];
36 | }
37 |
38 | // Create a view to only receive callbacks for the last LEADERBOARD_SIZE scores
39 | var scoreListView = scoreListRef.limitToLast(LEADERBOARD_SIZE);
40 |
41 | // Add a callback to handle when a new score is added.
42 | scoreListView.on("child_added", function (newScoreSnapshot, prevScoreName) {
43 | handleScoreAdded(newScoreSnapshot, prevScoreName);
44 | });
45 |
46 | // Add a callback to handle when a score is removed
47 | scoreListView.on("child_removed", function (oldScoreSnapshot) {
48 | handleScoreRemoved(oldScoreSnapshot);
49 | });
50 |
51 | // Add a callback to handle when a score changes or moves positions.
52 | var changedCallback = function (scoreSnapshot, prevScoreName) {
53 | handleScoreRemoved(scoreSnapshot);
54 | handleScoreAdded(scoreSnapshot, prevScoreName);
55 | };
56 | scoreListView.on("child_moved", changedCallback);
57 | scoreListView.on("child_changed", changedCallback);
58 |
59 | // When the user presses enter on scoreInput, add the score, and update the highest score.
60 | $("#scoreInput").keypress(function (e) {
61 | if (e.keyCode == 13) {
62 | var newScore = Number($("#scoreInput").val());
63 | var name = $("#nameInput").val();
64 | $("#scoreInput").val("");
65 |
66 | if (name.length === 0)
67 | return;
68 |
69 | var userScoreRef = scoreListRef.child(name);
70 |
71 | // Use setWithPriority to put the name / score in Firebase, and set the priority to be the score.
72 | userScoreRef.setWithPriority({ name:name, score:newScore }, newScore);
73 |
74 | // Track the highest score using a transaction. A transaction guarantees that the code inside the block is
75 | // executed on the latest data from the server, so transactions should be used if you have multiple
76 | // clients writing to the same data and you want to avoid conflicting changes.
77 | highestScoreRef.transaction(function (currentHighestScore) {
78 | if (currentHighestScore === null || newScore > currentHighestScore) {
79 | // The return value of this function gets saved to the server as the new highest score.
80 | return newScore;
81 | }
82 | // if we return with no arguments, it cancels the transaction.
83 | return;
84 | });
85 | }
86 | });
87 |
88 | // Add a callback to the highest score in Firebase so we can update the GUI any time it changes.
89 | highestScoreRef.on("value", function (newHighestScore) {
90 | $("#highestScoreDiv").text(newHighestScore.val());
91 | });
92 | });
93 |
--------------------------------------------------------------------------------
/presence/js/idle.js:
--------------------------------------------------------------------------------
1 | /** @license
2 | // idle.js (c) Alexios Chouchoulas 2009 http://www.bedroomlan.org/coding/detecting-%E2%80%98idle%E2%80%99-and-%E2%80%98away%E2%80%99-timeouts-javascript
3 | // Released under the terms of the GNU Public License version 2.0 (or later).
4 | */http://www.bedroomlan.org/coding/detecting-%E2%80%98idle%E2%80%99-and-%E2%80%98away%E2%80%99-timeouts-javascript
5 |
6 | var _API_JQUERY = 1;
7 | var _API_PROTOTYPE = 2;
8 | var _api;
9 |
10 | var _idleTimeout = 5000;
11 | var _awayTimeout = 10000;
12 |
13 | var _idleNow = false;
14 | var _idleTimestamp = null;
15 | var _idleTimer = null;
16 | var _awayNow = false;
17 | var _awayTimestamp = null;
18 | var _awayTimer = null;
19 |
20 | function setIdleTimeout(ms)
21 | {
22 | _idleTimeout = ms;
23 | _idleTimestamp = new Date().getTime() + ms;
24 | if (_idleTimer != null) {
25 | clearTimeout (_idleTimer);
26 | }
27 | _idleTimer = setTimeout(_makeIdle, ms + 50);
28 | //console.log('idle in ' + ms + ', tid = ' + _idleTimer);
29 | }
30 |
31 | function setAwayTimeout(ms)
32 | {
33 | _awayTimeout = ms;
34 | _awayTimestamp = new Date().getTime() + ms;
35 | if (_awayTimer != null) {
36 | clearTimeout (_awayTimer);
37 | }
38 | _awayTimer = setTimeout(_makeAway, ms + 50);
39 | //console.log('away in ' + ms);
40 | }
41 |
42 | function _makeIdle()
43 | {
44 | var t = new Date().getTime();
45 | if (t < _idleTimestamp) {
46 | //console.log('Not idle yet. Idle in ' + (_idleTimestamp - t + 50));
47 | _idleTimer = setTimeout(_makeIdle, _idleTimestamp - t + 50);
48 | return;
49 | }
50 | //console.log('** IDLE **');
51 | _idleNow = true;
52 |
53 | try {
54 | if (document.onIdle) document.onIdle();
55 | } catch (err) {
56 | }
57 | }
58 |
59 | function _makeAway()
60 | {
61 | var t = new Date().getTime();
62 | if (t < _awayTimestamp) {
63 | //console.log('Not away yet. Away in ' + (_awayTimestamp - t + 50));
64 | _awayTimer = setTimeout(_makeAway, _awayTimestamp - t + 50);
65 | return;
66 | }
67 | //console.log('** AWAY **');
68 | _awayNow = true;
69 |
70 | try {
71 | if (document.onAway) document.onAway();
72 | } catch (err) {
73 | }
74 | }
75 |
76 |
77 | function _initPrototype()
78 | {
79 | _api = _API_PROTOTYPE;
80 | }
81 |
82 | function _active(event)
83 | {
84 | var t = new Date().getTime();
85 | _idleTimestamp = t + _idleTimeout;
86 | _awayTimestamp = t + _awayTimeout;
87 | //console.log('not idle.');
88 |
89 | if (_idleNow) {
90 | setIdleTimeout(_idleTimeout);
91 | }
92 |
93 | if (_awayNow) {
94 | setAwayTimeout(_awayTimeout);
95 | }
96 |
97 | try {
98 | //console.log('** BACK **');
99 | if ((_idleNow || _awayNow) && document.onBack) document.onBack(_idleNow, _awayNow);
100 | } catch (err) {
101 | }
102 |
103 | _idleNow = false;
104 | _awayNow = false;
105 | }
106 |
107 | function _initJQuery()
108 | {
109 | _api = _API_JQUERY;
110 | var doc = $(document);
111 | doc.ready(function(){
112 | doc.mousemove(_active);
113 | try {
114 | doc.mouseenter(_active);
115 | } catch (err) { }
116 | try {
117 | doc.scroll(_active);
118 | } catch (err) { }
119 | try {
120 | doc.keydown(_active);
121 | } catch (err) { }
122 | try {
123 | doc.click(_active);
124 | } catch (err) { }
125 | try {
126 | doc.dblclick(_active);
127 | } catch (err) { }
128 | });
129 | }
130 |
131 | function _initPrototype()
132 | {
133 | _api = _API_PROTOTYPE;
134 | var doc = $(document);
135 | Event.observe (window, 'load', function(event) {
136 | Event.observe(window, 'click', _active);
137 | Event.observe(window, 'mousemove', _active);
138 | Event.observe(window, 'mouseenter', _active);
139 | Event.observe(window, 'scroll', _active);
140 | Event.observe(window, 'keydown', _active);
141 | Event.observe(window, 'click', _active);
142 | Event.observe(window, 'dblclick', _active);
143 | });
144 | }
145 |
146 | // Detect the API
147 | try {
148 | if (Prototype) _initPrototype();
149 | } catch (err) { }
150 |
151 | try {
152 | if (jQuery) _initJQuery();
153 | } catch (err) { }
154 |
155 | // End of file.
156 |
--------------------------------------------------------------------------------
/tetris/js/tetris.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 | var canvas = $("#canvas0").get(0);
3 | if (!canvas || !canvas.getContext || !canvas.getContext("2d"))
4 | alert("You must use a browser that supports HTML5 Canvas to run this demo.");
5 |
6 | function start() {
7 | var tetrisRef = new Firebase("https://INSTANCE.firebaseio.com/tetris");
8 | var tetrisController = new Tetris.Controller(tetrisRef);
9 | }
10 |
11 | var Tetris = { };
12 |
13 |
14 | /**
15 | * Various constants related to board size / drawing.
16 | */
17 | Tetris.BOARD_WIDTH = 10; // (in "blocks", not pixels)
18 | Tetris.BOARD_HEIGHT = 20;
19 |
20 | Tetris.BLOCK_SIZE_PIXELS = 25;
21 | Tetris.BOARD_HEIGHT_PIXELS = Tetris.BOARD_HEIGHT * Tetris.BLOCK_SIZE_PIXELS;
22 | Tetris.BOARD_WIDTH_PIXELS = Tetris.BOARD_WIDTH * Tetris.BLOCK_SIZE_PIXELS;
23 |
24 | Tetris.BLOCK_BORDER_COLOR = "#484848";
25 | Tetris.BLOCK_COLORS = { "X": "black", "b": "cyan", "B": "blue", "O": "orange",
26 | "Y": "yellow", "G": "green", "P": "#9370D8", "R": "red" };
27 |
28 | Tetris.GRAVITY_DELAY = 300; // 300ms
29 |
30 | Tetris.EMPTY_LINE = " ";
31 | Tetris.FILLED_LINE = "XXXXXXXXXX";
32 | Tetris.COMPLETE_LINE_PATTERN = /[^ ]{10}/;
33 |
34 | // Pieces. (Indexed by piece rotation (0-3), row (0-3), piece number (0-6))
35 | Tetris.PIECES = [];
36 | for (var i = 0; i < 4; i++) { Tetris.PIECES[i] = []; }
37 | Tetris.PIECES[0][0] = [ " ", " ", " ", " ", " ", " ", " " ];
38 | Tetris.PIECES[0][1] = [ " ", "B ", " O ", " YY ", " GG ", " P ", "RR " ];
39 | Tetris.PIECES[0][2] = [ "bbbb", "BBB ", "OOO ", " YY ", "GG ", "PPP ", " RR " ];
40 | Tetris.PIECES[0][3] = [ " ", " ", " ", " ", " ", " ", " " ];
41 | Tetris.PIECES[1][0] = [ " b ", " ", " ", " ", " ", " ", " R " ];
42 | Tetris.PIECES[1][1] = [ " b ", " B ", "OO ", " YY ", " G ", " P ", " RR " ];
43 | Tetris.PIECES[1][2] = [ " b ", " B ", " O ", " YY ", " GG ", " PP ", " R " ];
44 | Tetris.PIECES[1][3] = [ " b ", "BB ", " O ", " ", " G ", " P ", " " ];
45 | Tetris.PIECES[2][0] = [ " ", " ", " ", " ", " ", " ", " " ];
46 | Tetris.PIECES[2][1] = [ " ", " ", " ", " YY ", " GG ", " ", "RR " ];
47 | Tetris.PIECES[2][2] = [ "bbbb", "BBB ", "OOO ", " YY ", "GG ", "PPP ", " RR " ];
48 | Tetris.PIECES[2][3] = [ " ", " B ", "O ", " ", " ", " P ", " " ];
49 | Tetris.PIECES[3][0] = [ " b ", " ", " ", " ", " ", " ", " R " ];
50 | Tetris.PIECES[3][1] = [ " b ", " BB ", " O ", " YY ", " G ", " P ", " RR " ];
51 | Tetris.PIECES[3][2] = [ " b ", " B ", " O ", " YY ", " GG ", "PP ", " R " ];
52 | Tetris.PIECES[3][3] = [ " b ", " B ", " OO ", " ", " G ", " P ", " " ];
53 |
54 |
55 |
56 | /**
57 | * Stores the state of a tetris board and handles drawing it.
58 | */
59 | Tetris.Board = function (canvas, playerRef) {
60 | this.context = canvas.getContext("2d");
61 | this.playerRef = playerRef;
62 | this.snapshot = null;
63 | this.isMyBoard = false;
64 |
65 | // Listen for changes to our board.
66 | var self = this;
67 | playerRef.on("value", function(snapshot) {
68 | self.snapshot = snapshot;
69 | self.draw();
70 | });
71 | };
72 |
73 |
74 | /**
75 | * Draws the contents of the board as well as the current piece.
76 | */
77 | Tetris.Board.prototype.draw = function () {
78 | // Clear canvas.
79 | this.context.clearRect(0, 0, Tetris.BOARD_WIDTH_PIXELS, Tetris.BOARD_HEIGHT_PIXELS);
80 |
81 | // Iterate over columns / rows in board data and draw each non-empty block.
82 | for (var x = 0; x < Tetris.BOARD_WIDTH; x++) {
83 | for (var y = 0; y < Tetris.BOARD_HEIGHT; y++) {
84 | var colorValue = this.getBlockVal(x, y);
85 | if (colorValue != " ") {
86 | // Calculate block position and draw a correctly-colored square.
87 | var left = x * Tetris.BLOCK_SIZE_PIXELS;
88 | var top = y * Tetris.BLOCK_SIZE_PIXELS;
89 | this.context.fillStyle = Tetris.BLOCK_COLORS[colorValue];
90 | this.context.fillRect(left, top, Tetris.BLOCK_SIZE_PIXELS, Tetris.BLOCK_SIZE_PIXELS);
91 | this.context.lineWidth = 1;
92 | this.context.strokeStyle = Tetris.BLOCK_BORDER_COLOR;
93 | this.context.strokeRect(left, top, Tetris.BLOCK_SIZE_PIXELS, Tetris.BLOCK_SIZE_PIXELS);
94 | }
95 | }
96 | }
97 |
98 | // If there's a falling piece, draw it.
99 | if (this.snapshot !== null && this.snapshot.hasChild("piece")) {
100 | var piece = Tetris.Piece.fromSnapshot(this.snapshot.child("piece"));
101 | this.drawPiece(piece);
102 | }
103 |
104 | // If this isn't my board, dim it out with a 25% opacity black rectangle.
105 | if (!this.isMyBoard) {
106 | this.context.fillStyle = "rgba(0, 0, 0, 0.25)";
107 | this.context.fillRect(0, 0, Tetris.BOARD_WIDTH_PIXELS, Tetris.BOARD_HEIGHT_PIXELS);
108 | }
109 | };
110 |
111 |
112 | /**
113 | * Draw the currently falling piece.
114 | */
115 | Tetris.Board.prototype.drawPiece = function (piece) {
116 | var self = this;
117 | this.forEachBlockOfPiece(piece,
118 | function (x, y, colorValue) {
119 | var left = x * Tetris.BLOCK_SIZE_PIXELS;
120 | var top = y * Tetris.BLOCK_SIZE_PIXELS;
121 |
122 | self.context.fillStyle = Tetris.BLOCK_COLORS[colorValue];
123 | self.context.fillRect(left, top, Tetris.BLOCK_SIZE_PIXELS, Tetris.BLOCK_SIZE_PIXELS);
124 | self.context.lineWidth = 1;
125 | self.context.strokeStyle = Tetris.BLOCK_BORDER_COLOR;
126 | self.context.strokeRect(left, top, Tetris.BLOCK_SIZE_PIXELS, Tetris.BLOCK_SIZE_PIXELS);
127 | });
128 | };
129 |
130 |
131 | /**
132 | * Clear the board contents.
133 | */
134 | Tetris.Board.prototype.clear = function () {
135 | for (var row = 0; row < Tetris.BOARD_HEIGHT; row++) {
136 | this.setRow(row, Tetris.EMPTY_LINE);
137 | }
138 | };
139 |
140 |
141 | /**
142 | * Given a Tetris.Piece, returns true if it has collided with the board (i.e. its current position
143 | * and rotation causes it to overlap blocks already on the board).
144 | */
145 | Tetris.Board.prototype.checkForPieceCollision = function (piece) {
146 | var collision = false;
147 | var self = this;
148 | this.forEachBlockOfPiece(piece,
149 | function (x, y, colorValue) {
150 | // NOTE: we explicitly allow y < 0 since pieces can be partially visible.
151 | if (x < 0 || x >= Tetris.BOARD_WIDTH || y >= Tetris.BOARD_HEIGHT) {
152 | collision = true;
153 | }
154 | else if (y >= 0 && self.getBlockVal(x, y) != " ") {
155 | collision = true; // collision with board contents.
156 | }
157 | }, /*includeInvalid=*/ true);
158 |
159 | return collision;
160 | };
161 |
162 |
163 | /**
164 | * Given a Tetris.Piece that has landed, add it to the board contents.
165 | */
166 | Tetris.Board.prototype.addLandedPiece = function (piece) {
167 | var self = this;
168 | // We go out of our way to set an entire row at a time just so the rows show up as
169 | // child_added in the graphical debugger, rather than child_changed.
170 | var rowY = -1, rowContents = null;
171 | this.forEachBlockOfPiece(piece,
172 | function (x, y, val) {
173 | if (y != rowY) {
174 | if (rowY !== -1)
175 | self.setRow(rowY, rowContents);
176 |
177 | rowContents = self.getRow(y);
178 | rowY = y;
179 | }
180 | rowContents = rowContents.substring(0, x).concat(val).concat(rowContents.substring(x + 1, Tetris.BOARD_WIDTH));
181 | });
182 |
183 | if (rowY !== -1)
184 | self.setRow(rowY, rowContents);
185 | };
186 |
187 |
188 | /**
189 | * Check for any completed lines (no gaps) and remove them, then return the number
190 | * of removed lines.
191 | */
192 | Tetris.Board.prototype.removeCompletedRows = function () {
193 | // Start at the bottom of the board, working up, removing completed lines.
194 | var copyFrom = Tetris.BOARD_HEIGHT - 1;
195 | var copyTo = copyFrom;
196 |
197 | var completedRows = 0;
198 | while (copyFrom >= 0) {
199 | var fromContents = this.getRow(copyFrom);
200 |
201 | // See if the line is complete (if so, we'll skip it)
202 | if (fromContents.match(Tetris.COMPLETE_LINE_PATTERN)) {
203 | copyFrom--;
204 | completedRows++;
205 | } else {
206 | // Copy the row down (to fill the gap from any removed rows) and continue on.
207 | this.setRow(copyTo, fromContents);
208 | copyFrom--;
209 | copyTo--;
210 | }
211 | }
212 |
213 | return completedRows;
214 | };
215 |
216 |
217 | /**
218 | * Generate the specified number of junk rows at the bottom of the board. Return true if the added
219 | * rows overflowed the board (in which case the player loses).
220 | */
221 | Tetris.Board.prototype.addJunkRows = function (numRows) {
222 | var overflow = false;
223 | // First, check if any blocks are going to overflow off the top of the screen.
224 | var topRowContents = this.getRow(numRows - 1);
225 | overflow = topRowContents.match(/[^ ]/);
226 |
227 | // Shift rows up to make room for the new rows.
228 | for (var i = 0; i < Tetris.BOARD_HEIGHT - numRows; i++) {
229 | var moveLineContents = this.getRow(i + numRows);
230 | this.setRow(i, moveLineContents);
231 | }
232 |
233 | // Fill the bottom with junk rows that are full except for a single random gap.
234 | var gap = Math.floor(Math.random() * Tetris.FILLED_LINE.length);
235 | var junkRow = Tetris.FILLED_LINE.substring(0, gap) + " " + Tetris.FILLED_LINE.substring(gap + 1);
236 | for (i = Tetris.BOARD_HEIGHT - numRows; i < Tetris.BOARD_HEIGHT; i++) {
237 | this.setRow(i, junkRow);
238 | }
239 |
240 | return overflow;
241 | };
242 |
243 |
244 | /**
245 | * Helper to enumerate the blocks that make up a particular piece. Calls fn() for each block,
246 | * passing the x and y position of the block and the color value. If includeInvalid is true, it
247 | * includes blocks that would fall outside the bounds of the board.
248 | */
249 | Tetris.Board.prototype.forEachBlockOfPiece = function (piece, fn, includeInvalid) {
250 | for (var blockY = 0; blockY < 4; blockY++) {
251 | for (var blockX = 0; blockX < 4; blockX++) {
252 | var colorValue = Tetris.PIECES[piece.rotation][blockY][piece.pieceNum].charAt(blockX);
253 | if (colorValue != " ") {
254 | var x = piece.x + blockX, y = piece.y + blockY;
255 | if (includeInvalid || (x >= 0 && x < Tetris.BOARD_WIDTH && y >= 0 && y < Tetris.BOARD_HEIGHT)) {
256 | fn(x, y, colorValue);
257 | }
258 | }
259 | }
260 | }
261 | };
262 |
263 |
264 | Tetris.Board.prototype.getRow = function (y) {
265 | var row = (y < 10) ? ("0" + y) : ("" + y); // Pad row so they sort nicely in debugger. :-)
266 |
267 | var rowContents = this.snapshot === null ? null : this.snapshot.child("board/" + row).val();
268 | return rowContents || Tetris.EMPTY_LINE;
269 | };
270 |
271 |
272 | Tetris.Board.prototype.getBlockVal = function (x, y) {
273 | return this.getRow(y).charAt(x);
274 | };
275 |
276 |
277 | Tetris.Board.prototype.setRow = function (y, rowContents) {
278 | var row = (y < 10) ? ("0" + y) : ("" + y); // Pad row so they sort nicely in debugger. :-)
279 |
280 | if (rowContents === Tetris.EMPTY_LINE)
281 | rowContents = null; // delete empty lines so we get remove / added events in debugger. :-)
282 |
283 | this.playerRef.child("board").child(row).set(rowContents);
284 | };
285 |
286 |
287 | Tetris.Board.prototype.setBlockVal = function (x, y, val) {
288 | var rowContents = this.getRow(y);
289 | rowContents = rowContents.substring(0, x) + val + rowContents.substring(x+1);
290 | this.setRow(y, rowContents);
291 | };
292 |
293 |
294 | /**
295 | * Immutable object representing a falling piece along with its rotation and board position.
296 | * Has helpers for generating mutated Tetris.Piece objects (e.g. rotated or dropped).
297 | */
298 | Tetris.Piece = function (pieceNum, x, y, rotation) {
299 | if (arguments.length > 0) {
300 | this.pieceNum = pieceNum;
301 | this.x = x;
302 | this.y = y;
303 | this.rotation = rotation;
304 | } else {
305 | // Initialize new random piece.
306 | this.pieceNum = Math.floor(Math.random() * 7);
307 | this.x = 4; // "center" it.
308 | this.y = -2; // this will make the bottom line of the piece visible.
309 | this.rotation = 0;
310 | }
311 | };
312 |
313 |
314 | /**
315 | * Create a piece from a Firebase snapshot representing a piece.
316 | */
317 | Tetris.Piece.fromSnapshot = function (snapshot) {
318 | var piece = snapshot.val();
319 | return new Tetris.Piece(piece.pieceNum, piece.x, piece.y, piece.rotation);
320 | };
321 |
322 |
323 | /**
324 | * Writes the current piece data into Firebase.
325 | */
326 | Tetris.Piece.prototype.writeToFirebase = function (pieceRef) {
327 | pieceRef.set({pieceNum: this.pieceNum, x: this.x, y: this.y, rotation: this.rotation});
328 | };
329 |
330 |
331 | Tetris.Piece.prototype.drop = function () {
332 | return new Tetris.Piece(this.pieceNum, this.x, this.y + 1, this.rotation);
333 | };
334 |
335 |
336 | Tetris.Piece.prototype.rotate = function () {
337 | return new Tetris.Piece(this.pieceNum, this.x, this.y, (this.rotation + 1) % 4);
338 | };
339 |
340 |
341 | Tetris.Piece.prototype.moveLeft = function () {
342 | return new Tetris.Piece(this.pieceNum, this.x - 1, this.y, this.rotation);
343 | };
344 |
345 |
346 | Tetris.Piece.prototype.moveRight = function () {
347 | return new Tetris.Piece(this.pieceNum, this.x + 1, this.y, this.rotation);
348 | };
349 |
350 |
351 |
352 | /**
353 | * Manages joining the game, responding to keypresses, making the piece drop, etc.
354 | */
355 | Tetris.PlayingState = { Watching: 0, Joining: 1, Playing: 2 };
356 | Tetris.Controller = function (tetrisRef) {
357 | this.tetrisRef = tetrisRef;
358 | this.createBoards();
359 |
360 | this.playingState = Tetris.PlayingState.Watching;
361 | this.waitToJoin();
362 | };
363 |
364 |
365 | Tetris.Controller.prototype.createBoards = function () {
366 | this.boards = [];
367 | for(var i = 0; i <= 1; i++) {
368 | var playerRef = this.tetrisRef.child("player" + i);
369 | var canvas = $("#canvas" + i).get(0);
370 | this.boards.push(new Tetris.Board(canvas, playerRef));
371 | }
372 | };
373 |
374 |
375 | Tetris.Controller.prototype.waitToJoin = function() {
376 | var self = this;
377 |
378 | // Listen on "online" location for player0 and player1.
379 | this.tetrisRef.child("player0/online").on("value", function(onlineSnap) {
380 | if (onlineSnap.val() === null && self.playingState === Tetris.PlayingState.Watching) {
381 | self.tryToJoin(0);
382 | }
383 | });
384 |
385 | this.tetrisRef.child("player1/online").on("value", function(onlineSnap) {
386 | if (onlineSnap.val() === null && self.playingState === Tetris.PlayingState.Watching) {
387 | self.tryToJoin(1);
388 | }
389 | });
390 | };
391 |
392 |
393 | /**
394 | * Try to join the game as the specified playerNum.
395 | */
396 | Tetris.Controller.prototype.tryToJoin = function(playerNum) {
397 | // Set ourselves as joining to make sure we don't try to join as both players. :-)
398 | this.playingState = Tetris.PlayingState.Joining;
399 |
400 | // Use a transaction to make sure we don't conflict with other people trying to join.
401 | var self = this;
402 | this.tetrisRef.child("player" + playerNum + "/online").transaction(function(onlineVal) {
403 | if (onlineVal === null) {
404 | return true; // Try to set online to true.
405 | } else {
406 | return; // Somebody must have beat us. Abort the transaction.
407 | }
408 | }, function(error, committed) {
409 | if (committed) { // We got in!
410 | self.playingState = Tetris.PlayingState.Playing;
411 | self.startPlaying(playerNum);
412 | } else {
413 | self.playingState = Tetris.PlayingState.Watching;
414 | }
415 | });
416 | };
417 |
418 |
419 | /**
420 | * Once we've joined, enable controlling our player.
421 | */
422 | Tetris.Controller.prototype.startPlaying = function (playerNum) {
423 | this.myPlayerRef = this.tetrisRef.child("player" + playerNum);
424 | this.opponentPlayerRef = this.tetrisRef.child("player" + (1 - playerNum));
425 | this.myBoard = this.boards[playerNum];
426 | this.myBoard.isMyBoard = true;
427 | this.myBoard.draw();
428 |
429 | // Clear our "online" status when we disconnect so somebody else can join.
430 | this.myPlayerRef.child("online").onDisconnect().remove();
431 |
432 | // Detect when other player pushes rows to our board.
433 | this.watchForExtraRows();
434 |
435 | // Detect when game is restarted by other player.
436 | this.watchForRestart();
437 |
438 | $("#gameInProgress").hide();
439 |
440 | var self = this;
441 | $("#restartButton").removeAttr("disabled");
442 | $("#restartButton").click(function () {
443 | self.restartGame();
444 | });
445 |
446 | this.initializePiece();
447 | this.enableKeyboard();
448 | this.resetGravity();
449 | };
450 |
451 |
452 | Tetris.Controller.prototype.initializePiece = function() {
453 | this.fallingPiece = null;
454 | var pieceRef = this.myPlayerRef.child("piece");
455 | var self = this;
456 |
457 | // Watch for changes to the current piece (and initialize it if it's null).
458 | pieceRef.on("value", function(snapshot) {
459 | if (snapshot.val() === null) {
460 | var newPiece = new Tetris.Piece();
461 | newPiece.writeToFirebase(pieceRef);
462 | } else {
463 | self.fallingPiece = Tetris.Piece.fromSnapshot(snapshot);
464 | }
465 | });
466 | };
467 |
468 |
469 | /**
470 | * Sets up handlers for all keyboard commands.
471 | */
472 | Tetris.Controller.prototype.enableKeyboard = function () {
473 | var self = this;
474 | $(document).on("keydown", function (evt) {
475 | if (self.fallingPiece === null)
476 | return; // piece isn't initialized yet.
477 |
478 | var keyCode = evt.which;
479 | var key = { space:32, left:37, up:38, right:39, down:40 };
480 |
481 | var newPiece = null;
482 | switch (keyCode) {
483 | case key.left:
484 | newPiece = self.fallingPiece.moveLeft();
485 | break;
486 | case key.up:
487 | newPiece = self.fallingPiece.rotate();
488 | break;
489 | case key.right:
490 | newPiece = self.fallingPiece.moveRight();
491 | break;
492 | case key.down:
493 | newPiece = self.fallingPiece.drop();
494 | break;
495 | case key.space:
496 | // Drop as far as we can.
497 | var droppedPiece = self.fallingPiece;
498 | do {
499 | newPiece = droppedPiece;
500 | droppedPiece = droppedPiece.drop();
501 | } while (!self.myBoard.checkForPieceCollision(droppedPiece));
502 | break;
503 | }
504 |
505 | if (newPiece !== null) {
506 | // If the new piece position / rotation is valid, update self.fallingPiece and firebase.
507 | if (!self.myBoard.checkForPieceCollision(newPiece)) {
508 | // If the keypress moved the piece down, reset gravity.
509 | if (self.fallingPiece.y != newPiece.y) {
510 | self.resetGravity();
511 | }
512 |
513 | newPiece.writeToFirebase(self.myPlayerRef.child("piece"));
514 | }
515 | return false; // handled
516 | }
517 |
518 | return true;
519 | });
520 | };
521 |
522 |
523 | /**
524 | * Sets a timer to make the piece repeatedly drop after GRAVITY_DELAY ms.
525 | */
526 | Tetris.Controller.prototype.resetGravity = function () {
527 | // If there's a timer already active, clear it first.
528 | if (this.gravityIntervalId !== null) {
529 | clearInterval(this.gravityIntervalId);
530 | }
531 |
532 | var self = this;
533 | this.gravityIntervalId = setInterval(function() {
534 | self.doGravity();
535 | }, Tetris.GRAVITY_DELAY);
536 | };
537 |
538 |
539 | Tetris.Controller.prototype.doGravity = function () {
540 | if (this.fallingPiece === null)
541 | return; // piece isn't initialized yet.
542 |
543 | var newPiece = this.fallingPiece.drop();
544 |
545 | // If we've hit the bottom, add the (pre-drop) piece to the board and create a new piece.
546 | if (this.myBoard.checkForPieceCollision(newPiece)) {
547 | this.myBoard.addLandedPiece(this.fallingPiece);
548 |
549 | // Check for completed lines and if appropriate, push extra rows to our opponent.
550 | var completedRows = this.myBoard.removeCompletedRows();
551 | var rowsToPush = (completedRows === 4) ? 4 : completedRows - 1;
552 | if (rowsToPush > 0)
553 | this.opponentPlayerRef.child("extrarows").push(rowsToPush);
554 |
555 | // Create new piece (it'll be initialized to a random piece at the top of the screen).
556 | newPiece = new Tetris.Piece();
557 |
558 | // Is the board full?
559 | if (this.myBoard.checkForPieceCollision(newPiece))
560 | this.gameOver();
561 | }
562 |
563 | newPiece.writeToFirebase(this.myPlayerRef.child("piece"));
564 | };
565 |
566 |
567 | /**
568 | * Detect when our opponent pushes extra rows to us.
569 | */
570 | Tetris.Controller.prototype.watchForExtraRows = function () {
571 | var self = this;
572 | var extraRowsRef = this.myPlayerRef.child("extrarows");
573 | extraRowsRef.on("child_added", function(snapshot) {
574 | var rows = snapshot.val();
575 | extraRowsRef.child(snapshot.key()).remove();
576 |
577 | var overflow = self.myBoard.addJunkRows(rows);
578 | if (overflow)
579 | self.gameOver();
580 |
581 | // Also move piece up to avoid collisions.
582 | if (self.fallingPiece) {
583 | self.fallingPiece.y -= rows;
584 | self.fallingPiece.writeToFirebase(self.myPlayerRef.child("piece"));
585 | }
586 | });
587 | };
588 |
589 |
590 | /**
591 | * Detect when our opponent restarts the game.
592 | */
593 | Tetris.Controller.prototype.watchForRestart = function () {
594 | var self = this;
595 | var restartRef = this.myPlayerRef.child("restart");
596 | restartRef.on("value", function(snap) {
597 | if (snap.val() === 1) {
598 | restartRef.set(0);
599 | self.resetMyBoardAndPiece();
600 | }
601 | });
602 | };
603 |
604 |
605 | Tetris.Controller.prototype.gameOver = function () {
606 | this.restartGame();
607 | };
608 |
609 |
610 | Tetris.Controller.prototype.restartGame = function () {
611 | this.opponentPlayerRef.child("restart").set(1);
612 | this.resetMyBoardAndPiece();
613 | };
614 |
615 |
616 | Tetris.Controller.prototype.resetMyBoardAndPiece = function () {
617 | this.myBoard.clear();
618 | var newPiece = new Tetris.Piece();
619 | newPiece.writeToFirebase(this.myPlayerRef.child("piece"));
620 | };
621 |
622 |
623 | start();
624 | });
625 |
--------------------------------------------------------------------------------