Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
11 changes: 11 additions & 0 deletions appengine/standard/firebase/firetactoe/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
runtime: python27
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Github won't let me comment on a binary file): Please remove .DS_Store. I'd recommend adding it to your global gitignore.

api_version: 1
threadsafe: true

handlers:
- url: /.*
script: firetactoe.application

libraries:
- name: pycrypto
version: latest
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always use explicit versions. Latest sometimes isn't the latest, and even if it were we don't want the sample to suddenly break.

4 changes: 4 additions & 0 deletions appengine/standard/firebase/firetactoe/appengine_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from google.appengine.ext import vendor

# Add any libraries installed in the "lib" folder.
vendor.add('lib')
304 changes: 304 additions & 0 deletions appengine/standard/firebase/firetactoe/fire_index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
<html>
<head>

<script src="https://www.gstatic.com/firebasejs/3.3.2/firebase.js"></script>
<!-- <script src="https://www.gstatic.com/firebasejs/3.3.0/firebase-database.js"></script> -->
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this isn't necessary, remove it.

<script>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move all this to a separate .js file.

// Initialize Firebase object
var config = {
apiKey: "AIzaSyB_pwNiBi8Q3iakJitkQHtEQ5SZWmp9EiY",
authDomain: "imposing-mind-131903.firebaseapp.com",
databaseURL: "https://imposing-mind-131903.firebaseio.com"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably best to put placeholders here, preferably put all this stuff in a single file that gets pulled in somehow.

storageBucket: "imposing-mind-131903.appspot.com"
};
firebase.initializeApp(config);
</script>

<style type='text/css'>
body {
font-family: 'Helvetica';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think we should also factor out css and js into separate files.

}

#board {
width:152px;
height: 152px;
margin: 20px auto;
}

#display-area {
text-align: center;
}

#this-game {
font-size: 9pt;
}

#winner {
}

table {
border-collapse: collapse;
}

td {
width: 50px;
height: 50px;
font-family: "Helvetica";
font-size: 16pt;
text-align: center;
vertical-align: middle;
margin:0px;
padding: 0px;
}

div.cell {
float: left;
width: 50px;
height: 50px;
border: none;
margin: 0px;
padding: 0px;
}

div.mark {
position: absolute;
top: 15px;
}

div.l {
border-right: 1pt solid black;
}

div.c {
}

div.r {
border-left: 1pt solid black;
}

div.t {
border-bottom: 1pt solid black;
}

div.m {
}

div.b {
border-top: 1pt solid black;
}
</style>
</head>
<body>
<script type='text/javascript'>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this to a js file, probably can combine it with the snippet above.

var state = {
game_key: '{{ game_key }}',
me: '{{ me }}'
};

// This is our Firebase realtime DB path that we'll listen to for updates
// We'll initialize this later in openChannel()
var channel = null;

updateGame = function() {
for (i = 0; i < 9; i++) {
var square = document.getElementById(i);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that jquery would make this section slightly more readable, please feel free to use it if you feel the same.

square.innerHTML = state.board[i];
if (state.winner != '' && state.winningBoard != '') {
if (state.winningBoard[i] == state.board[i]) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Believe we should be using triple =. Have you run this through jslint?

if (state.winner == state.me) {
square.style.background = "green";
} else {
square.style.background = "red";
}
} else {
square.style.background = "white";
}
}
}

var display = {
'other-player': 'none',
'your-move': 'none',
'their-move': 'none',
'you-won': 'none',
'you-lost': 'none',
'board': 'block',
'this-game': 'block',
};

if (!state.userO || state.userO == '') {
display['other-player'] = 'block';
display['board'] = 'none';
display['this-game'] = 'none';
} else if (state.winner == state.me) {
display['you-won'] = 'block';
} else if (state.winner != '') {
display['you-lost'] = 'block';
} else if (isMyMove()) {
display['your-move'] = 'block';
} else {
display['their-move'] = 'block';
}

for (var label in display) {
document.getElementById(label).style.display = display[label];
}
};

isMyMove = function() {
return (state.winner == "") &&
(state.moveX == (state.userX == state.me));
}

myPiece = function() {
return state.userX == state.me ? 'X' : 'O';
}

// This message sends POST requests back to the App Engine server
sendMessage = function(path, opt_param) {
path += '?g=' + state.game_key;
if (opt_param) {
path += '&' + opt_param;
}
var xhr = new XMLHttpRequest();
xhr.open('POST', path, true);
xhr.send();
};

// Send the user's latest move back to the server
moveInSquare = function(id) {
if (isMyMove() && state.board[id] == ' ') {
sendMessage('/move', 'i=' + id);
}
}

highlightSquare = function(id) {
if (state.winner != "") {
return;
}
for (i = 0; i < 9; i++) {
if (i == id && isMyMove()) {
if (state.board[i] = ' ') {
color = 'lightBlue';
} else {
color = 'lightGrey';
}
} else {
color = 'white';
}

document.getElementById(i).style['background'] = color;
}
}

// This method lets the server know that the user has opened the channel
// After this method is called, the server may begin to send updates
onOpened = function() {
sendMessage('/opened');
};

// This deletes the data associated with the Firebase path
// it is critical that this data be deleted since it costs money
deleteChannel = function() {
sendMessage('/delete');
};

// This method is called every time an event is fired from Firebase
// it updates the entire game state and checks for a winner
// if a player has won the game, this function calls the server to delete
// the data stored in Firebase
onMessage = function(m) {
newState = m;
state.board = newState.board || state.board;
state.userX = newState.userX || state.userX;
state.userO = newState.userO || state.userO;
state.moveX = newState.moveX;
state.winner = newState.winner || "";
state.winningBoard = newState.winningBoard || "";
updateGame();
// now check to see if there is a winner //
if (channel && state.winner != '' && state.winningBoard != '') {
channel.off(); //stop listening on this path
deleteChannel(); //delete the data we wrote
}
}

// This function opens a realtime communication channel with Firebase
// It logs in securely using the client token passed from the server
// then it sets up a listener on the proper database path (also passed by server)
// finally, it calls onOpened() to let the server know it is ready to receive messages
openChannel = function() {
var token = '{{ token }}'; // secure token passed from the server //
var channel_id = '{{ channel_id }}'; // id of the 'channel' we'll be listening to //

// sign into Firebase with the token passed from the server
firebase.auth().signInWithCustomToken(token).catch(function(error) {
console.log("Login Failed!", error.code);
console.log("Error message: ", error.message);
});

// setup a database reference at path /channels/channel_id
channel = firebase.database().ref('channels/' + channel_id);
// add a listener to the path that fires any time the value of the data changes
channel.on('value', function(data) {
onMessage(data.val());
});
onOpened();
// let the server know that the channel is open
}

// This function opens a communication channel with the server
// then it adds listeners to all the squares on the board
// next it pulls down the initial game state from template values
// finally it updates the game state with those values by calling onMessage()
initialize = function() {
openChannel();
var i;
for (i = 0; i < 9; i++) {
var square = document.getElementById(i);
square.onmouseover = new Function('highlightSquare(' + i + ')');
square.onclick = new Function('moveInSquare(' + i + ')');
}
initial_message = {{ initial_message|safe }}; // use the |safe flag to get unescaped json to render //
onMessage(initial_message);
}

setTimeout(initialize, 100);

</script>
<div id='display-area'>
<h2>Firebase-enabled Tic Tac Toe</h2>
<div id='other-player' style='display:none'>
Waiting for another player to join.<br>
Send them this link to play:<br>
<div id='game-link'><a href='{{ game_link }}'>{{ game_link }}</a></div>
</div>
<div id='your-move' style='display:none'>
Your move! Click a square to place your piece.
</div>
<div id='their-move' style='display:none'>
Waiting for other player to move...
</div>
<div id='you-won'>
You won this game!
</div>
<div id='you-lost'>
You lost this game.
</div>
<div id='board'>
<div class='t l cell'><table><tr><td id='0'></td></tr></td></table></div>
<div class='t c cell'><table><tr><td id='1'></td></tr></td></table></div>
<div class='t r cell'><table><tr><td id='2'></td></tr></td></table></div>
<div class='m l cell'><table><tr><td id='3'></td></tr></td></table></div>
<div class='m c cell'><table><tr><td id='4'></td></tr></td></table></div>
<div class='m r cell'><table><tr><td id='5'></td></tr></td></table></div>
<div class='b l cell'><table><tr><td id='6'></td></tr></td></table></div>
<div class='b c cell'><table><tr><td id='7'></td></tr></td></table></div>
<div class='b r cell'><table><tr><td id='8'></td></tr></td></table></div>
</div>
<div id='this-game' float='top'>
Quick link to this game: <span id='this-game-link'><a href='{{ game_link }}'>{{ game_link }}</a></span>
</div>
</div>
</body>
</html>
Loading