1. Trang chủ
  2. » Công Nghệ Thông Tin

Tài liệu Building OpenSocial Apps- P4 docx

50 272 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Building OpenSocial Apps
Trường học Ho Chi Minh University of Technology
Chuyên ngành Computer Science
Thể loại tutorial
Thành phố Ho Chi Minh City
Định dạng
Số trang 50
Dung lượng 292,9 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

App Data Game Store Our game store in app data will consist of some JSON objects in an array.We’ll build onsome of the code shown previously in the book for using the app data store.This

Trang 1

4 Immediately after the opponentPickedfunction, add the following

loadFriendPickerfunction This function makes use of the widgetBootstrapper object to initialize the FriendPicker widget The Bootstrapper allows

us to safely create a widget from a dynamically included script library.The call to

createWidgettakes three parameters: the object name of the widget being created, an initializer function to fire when the widget has been created, and anobject of parameters to pass into the widget at initialization In this case, we’repassing in the ID of our target div element, a callback function to executewhenever a friend is picked, and a flag to build the “Selected User” user interface element

function loadFriendPicker(){

MyOpenSpace.Widgets.Bootstrapper.createWidget(

"MyOpenSpace.Widgets.FriendPicker", function(p){

window.friendPicker = p;

}, { element: "opponentPicker", buildSelectedUI: true, friendClickAction: opponentPicked });

try{

initializeGame();

} catch(ex){}

loadFriendPicker();

}

Trang 2

Warning

The call to initialize a FriendPicker must be made from a gadgets.util.

registerOnLoadHandler-triggered function If we try to initialize from an inline call, the initializer will fail in some browsers Not all browsers handle dynamic script loading in the same manner, so we must use the least common denominator behavior.

If we save and view our app, we’ll see the FriendPicker initialized below the currentviewer UI Clicking on the [Click for Recipient] item shown in Figure 7.4 brings upthe FriendPicker UI and allows the user to pick a friend

App Data Game Store

Our game store in app data will consist of some JSON objects in an array.We’ll build onsome of the code shown previously in the book for using the app data store.This timewe’ll clean things up a bit by placing all our code under the TTTnamespace, instead ofleaving a bunch of dangling functions hanging off the global window object

Storage JSON ObjectsThe first step is to create the GameInfostorage object In your script code, add the following code:

Figure 7.4 FriendPicker below player info.

Trang 3

In earlier chapters we’ve shown some techniques by making use of globalfunctions While convenient for small projects, it is good practice to namespace yourscript code.We’re going to encapsulate all of this in a static object namespace of

TTT.AppDataPlay

Logging to Debug Apps

The gadgets spec provides for a logging mechanism similar to the one popularized by log4j under the gadgets.log call Logging is an incredibly useful way to debug an app.

It’s not always convenient to set breakpoints and single steps A log lets you dissect the execution sequence after the fact.

At the time of this writing, gadgets.log isn’t implemented in the MySpace container.

It’s simple to add, though In our case, we just want to write out to a scrolling div After development we can hide the div and leave the log calls in place Here’s how:

Trang 4

1 Create the basic object skeleton and add it into your script source.The code isshown here:

TTT.AppDataPlay = { }

2 Add a Keysobject to this namespace that will encapsulate all the app data keysused for game play:

}

}

3 Now we will add some empty object key definitions to hold our data Eventhough JavaScript allows for dynamic creation of variables, it is good practice toexplicitly declare and comment any variables that are used in multiple places Addthis code inside our TTT.AppDataPlay object:

Trang 5

placehold-TTT.AppDataPlay = {

.

/**

* Retrieves the gameInfo object for the specified opponent.

* If one doesn't exist, a new gameInfo object is created.

*/

getCurrentGameObject: function(opponentId){},

/**

* Convenience method to save the data currently in

* this.myGameAppData to the backing app data store.

*/

updateStoredGameObject: function(){},

/**

* Loads the specified gameInfo object into

* window.currentGame and updates the board

Trang 6

getGameMovesString: function(game){},

}

5 Add the methods for retrieval and saving of the Viewer’s app data.These methodsare adaptations of what we wrote in Chapter 4, Persisting Information.The onlyreal difference is that we include them in our TTT.AppDataPlaynamespace

TTT.AppDataPlay = {

/**

* Retrieve the current Viewer's app data.

* This is an upgrade of the method introduced in

* Chapter 4, Persisting Information.

*/

getMyAppData: function (){

var idparams = {};

idparams[opensocial.IdSpec.Field.USER_ID] = opensocial.IdSpec.PersonId.VIEWER;

idparams[opensocial.IdSpec.Field.NETWORK_DISTANCE] = 0;

idparams[opensocial.IdSpec.Field.GROUP_ID] = opensocial.IdSpec.GroupId.SELF;

var id = opensocial.newIdSpec(idparams);

var req = opensocial.newDataRequest();

req.add(

req.newFetchPersonAppDataRequest(id, "*"), TTT.AppDataPlay.Keys.AppDataGetKey);

req.send(function(data){

TTT.AppDataPlay.loadAppDataCallback(data, TTT.AppDataPlay.myGameAppData);

});

},

/**

* Sets an AppData key for the current Viewer

* @param {String} key

* @param {JSON object} value

*/

setAppDataKey: function (key, value) {

var req = opensocial.newDataRequest();

Trang 7

6 Referenced in the methods just shown is a general-purpose app data callbackhandler adapted from Chapter 4, Persisting Information Add the function

loadAppDataCallback in as well.This function places all the AppDatakeys andvalues in the response into the specified object hash In that way we make onlyone call for all the data

TTT.AppDataPlay = {

/**

* Callback wrapper method for loadAppData.

* This loads all the data into myLocalAppData

* and triggers the loadedCallback function, if specified

* @param {Object} data

* @param {Function} loadedCallback

var mydata = data.get(TTT.AppDataPlay.Keys.AppDataGetKey).getData();

//App data has an additional layer of indirection.

//We reassign mydata object to the data map object //Circumvent Viewer lookup by getting key

if(targetCollection == null){

gadgets.log("Bad collection in load callback");

Trang 8

targetCollection = {};

} var key;

for(key in mydata){

targetCollection[key] = mydata[key];

} } if(loadedCallback && typeof(loadedCallback) == "function"){

loadedCallback(targetCollection);

} },

}

7 Finally, add in the function that retrieves a friend’s app data We touched on thisbriefly in Chapter 4, Persisting Information, but did not write the code forretrieving other people’s app data.You can read, but cannot write, your friend’sdata, provided the friend has the app installed as well The main differencesbetween this and Viewer are the idSpecused and the callback function

idparams[opensocial.IdSpec.Field.NETWORK_DISTANCE] = 1;

if(!friendId){

friendId = window.friendPicker.selectedFriend.getId();

} idparams[opensocial.IdSpec.Field.GROUP_ID] = friendId;

//If MySpace fixes the idSpec bug, use this /*

if(!friendId){

friendId = window.friendPicker.selectedFriend.getId();

} idparams[opensocial.IdSpec.Field.USER_ID] = friendId;

idparams[opensocial.IdSpec.Field.GROUP_ID] = opensocial.IdSpec.GroupId.SELF;

*/

Trang 9

TTT.AppDataPlay.loadAppDataCallback(data, TTT.AppDataPlay.myOpponentAppData, TTT.AppDataPlay.selectedOpponentDataCallback);

});

},

}

Warning

You may notice a commented-out block of code within this function At the time of this writing, MySpace had an inconsistency in its interpretation of the idSpec object from how other OpenSocial implementations interpret the same object MySpace requires the user ID

to always be VIEWER and the target friend ID to be placed in the GROUP_ID field Other implementations put the target friend ID in the user ID field and use the reserved enum value opensocial.IdSpec.GroupId.SELF for the GROUP_ID value.

8 Stub out the callback function TTT.AppDataPlay.

selectedOpponentDataCallback.This function holds much of the logic forgame management For now, we’re just going to stub it out and add a log state-ment so our code will run

}

Trang 10

At this point, we have the skeleton of the AppDataPlayobject built and the utilitymethods required for communicating with the app data store of both players

Supporting Person-to-Person Game Play

Thus far, our actual game engine (TTT.Game) has supported only human-versus-computergame play.We need to make a few tweaks to allow for dual-mode play (person-to-person,

or P2P) In our game engine, we’ll always consider our opponent to be “Computer,”

even when the opponent is another person.This saves us from a broader rewrite of the display logic and game engine A simple flag value will give us enough information todifferentiate computer from human opponents

Adding P2P Game Play Support in the Game Engine

The first step is to make a few minor modifications to the original Tic-Tac-Toegame engine in order to support a human opponent in a turn-based system Wemust change the “made a move” trigger to store data and wait instead of triggering

a computer move We also now need to handle cases when the game ends in adraw

1 Add a property in the TTT.Gameobject to flag the game as being against a humanopponent.This will be used in the game logic to trigger a computer move ortrigger an app data store update

var TTT = { Game: function(){

2 Add a new method to the TTT.Gameprototype object to test for a draw Inhuman-versus-computer play we didn’t need to test for a draw When no squareswere available, there were no moves to make.With a human opponent we need to

be able to identify the end of a game when there is no winner Search for the

lookForWinfunction and add the isADrawfunction immediately below

Trang 11

var openCell = false;

for(var i=0; i < 9; i++){

if(!this.moves[i]){

openCell = true;

break;

} } return (!openCell);

},

3 Edit the game play event handler makePlayerMoveto support P2P game play

Previously, this function would trigger a computer move if a winner was not discovered.We need to update this ifstatement with an elseand include a checkforisHumanOpponent

function makePlayerMove(e){

if(!currentGame.hasWinner && !currentGame.isHumanOpponent){

window.setTimeout(function(){

currentGame.makeComputerMove();

}, 500);

} else{

} }

4 Add the game logic for taking a turn within the elsestatement in the

makePlayerMovefunction from the preceding step.You will notice some newmethods on the TTT.AppDataPlayobject.We’ll fill in those methods in the nextsection

else{

// USER FEEDBACK MESSAGES HERE

var adGame = TTT.AppDataPlay.getCurrentGameObject(

TTT.AppDataPlay.currentOpponentId);

adGame.moveNumber = adGame.moveNumber+1;

if(currentGame.hasWinner){

msg = "You Win!";

Trang 12

adGame.winner = window.viewer.getId();

} else{

msg += "<br />Waiting for your opponent";

}

adGame.boardLayout = TTT.AppDataPlay.getGameMovesString(currentGame);

//Wait for next move }

TTT.AppDataPlay.updateGameStatusMessage(msg);

}

Adding User Feedback

Feedback to the user is an important part of the game play In turn-based game play,sometimes it’s simply just not the Viewer’s turn.Without feedback the Viewer has theimpression that the game is broken

When our Tic-Tac-Toe game is in P2P game play mode instead of P2C to-computer), the computer takes on the role of commentator/narrator instead ofopponent Since this is a game, we like to add some sass to the commentary Feel free tocustomize the messages to your liking

(person-1 Above the table with an ID of gameboard, add a div element with an ID of

gameStatus.This is where the feedback messages are displayed

<div id="gameStatus"></div>

2 In the makePlayerMovefunction, add a check and associated message to short-circuit processing if it is not the Viewer’s turn to make a move Place this code immediately after the line where the var cell = statement

Trang 13

3 In the makePlayerMovefunction, add a random message generator within the

elseblock that is executed during P2P game play In our case we’ll create threemessages and use a modulo operation coupled with a timestamp to pick a message

in a quasi-random fashion.To the end user this will appear as a running tary by the computer

commen-var quotes = ["Nice Move!", " interesting strategy",

"What were you thinking?"];

var d = new Date();

var ndx = d.getMilliseconds() % 3;

var msg = quotes[ndx]; //Random quote

Modulo Magic

Modulo operations can be one of the most useful bits of programming pixie dust to put

in your bag of tricks A modulo operation calculates the remainder after a division tion between two numbers To illustrate, if we divide the number 5 by 2, it divides evenly into two parts with 1 left over The modulo (or “mod”) operation returns the 1.

opera-5/2 = 2 with remainder of 1,

or 5 mod 2 = 1

In JavaScript the statement would be written as 5 % 2.

By far the most common usage is for displaying rows with alternating colors This is accomplished with the following if statement:

for(var i=0; i<rows.length;i++){

There are many more uses for modulo operations Modulo is great any time you want

a quasi-random distribution of data All it takes is an incrementing counter (like a stamp) and the number of buckets to test for True random numbers are much more cumbersome and computationally expensive than a modulo operation.

time-The following code is the final code listing for makePlayerMove:

function makePlayerMove(e){

if(!e) var e = window.event;

var cell = (e.target) ?

Trang 14

e.target.getAttribute("gamecell") : e.srcElement.getAttribute("gamecell");

var quotes = [

"Nice Move!",

" interesting strategy",

"What were you thinking?"];

var d = new Date();

msg += "<br />Waiting for your opponent";

}

adGame.boardLayout = TTT.AppDataPlay.getGameMovesString(currentGame);

Trang 15

TTT.AppDataPlay.startGameplayPolling();

} TTT.AppDataPlay.updateStoredGameObject();

TTT.AppDataPlay.updateGameStatusMessage(msg);

}

}

Fleshing Out P2P Game Logic

We have now created all the underpinnings of the game play Now it’s time to glue it alltogether and implement our logic flows from the beginning of this chapter

Logic for Selecting an Opponent and Loading the GameSelecting an opponent and activating a game constitute the bulk of the workflowoutlined at the beginning of the chapter.The app has to get both the Viewer’s and theopponent’s game data, reconcile which version is the latest, and determine whose move

it is If there is no active game, a new one is created

Find the function selectedOpponentDataCallbackin the TTT.AppDataPlay

object namespace that we stubbed out earlier.This is a big function, so we will discusseach section of it in turn

The first section of this function retrieves the GameInfoobject for the two associatedplayers by examining all the games stored in the selected opponent’s app data store until

it finds one where the current Viewer’s ID matches the opponentIdvalue in the

GameInfoobject.This object is then assigned to the variable TTT.AppDataPlay.

myOpponentCurrentGame At the very end of this section the code block similarlyretrieves the GameInfoobject associated with this opponent from the Viewer’s localcopy of the app data store by calling getCurrentGameObject

if(!data){

return;

} //Since we're in a callback, "this" has lost scope var thisObj = TTT.AppDataPlay;

var myId = window.viewer.getId();

thisObj.myOpponentCurrentGame = null;

var opponentStoredGames = data[thisObj.Keys.activeGames];

if(opponentStoredGames != undefined && opponentStoredGames != null){

Trang 16

var myGameInfo = thisObj.getCurrentGameObject(thisObj.currentOpponentId);

The second section of the selectedOpponentDataCallbackfunction reconcilesthe two GameInfoobjects It does this by identifying which object has the latest move

TheboardLayoutis then copied into the Viewer’s GameInfoobject to ensure that theViewer’s copy is the latest copy

latestGameInfo = myGameInfo;

}

Once the latestGameInfohas been determined, the code tests to see if a winner hasbeen declared.When a winner has been declared, the ID of the winning player is placed inthis field If there is no winner, this value is 0 (equivalent to false) If there is a winner,the board is loaded, a message is displayed to state who won, and the game is marked asfinished

If there isn’t a winner, the game is loaded into view via a call to the loadGamefunction(described later in this section) User feedback is generated to indicate whose turn it is

if(latestGameInfo.winner != 0){

if(latestGameInfo.winner == myId){

thisObj.updateGameStatusMessage("YOU WON!!!");

}

Trang 17

else if(latestGameInfo.winner == -1){

thisObj.updateGameStatusMessage("ITS A DRAW!!!");

} else{

thisObj.updateGameStatusMessage("YOUR OPPONENT WON!!!");

} //debugger;

msg += "<br />It is your turn";

} else{

msg += "<br />Still waiting on opponent";

thisObj.startGameplayPolling();

}

The preceding code describes what occurs when a game is discovered to be inprogress.The second branch of this is what occurs when this is a new game or theopponent has not yet accepted the game In that instance the Viewer’s game is loadedand appropriate user feedback is displayed

} else{

latestGameInfo = myGameInfo thisObj.loadGame(latestGameInfo);

//Save the new game back if(latestGameInfo.moveNumber==0){

msg += "<br />New game started - make your move";

} else{

msg += "<br />Waiting for your opponent to move";

} } thisObj.updateGameStatusMessage(msg);

That finishes the selectedOpponentDataCallbackfunction.There were two otherfunctions touched on in the preceding description:getCurrentGameObjectand

loadGame Both methods are fairly simple

Trang 18

The function getCurrentGameObjectretrieves the GameInfoobject from theViewer’s app data store for the associated opponent If there is no game in progress, anew GameInfoobject is initialized for this opponent and returned

/**

* Retrieves the gameInfo object for the specified opponent.

* If one doesn't exist, a new gameInfo object is created.

*

*/

getCurrentGameObject: function(opponentId){

var result = null;

if(this.myGameAppData && this.myGameAppData.activeGames != null){

var items = this.myGameAppData.activeGames;

//Is object var x;

result = new TTT.GameInfo(opponentId);

if(!this.myGameAppData.activeGames){

this.myGameAppData.activeGames = [];

} this.myGameAppData.activeGames.push(result);

}

return result;

}

The function loadGameloads the GameInfointo the game board and initializes the

window.currentGameobject.The bulk of this method is display logic In our

Trang 19

implementation, the Viewer’s moves are always represented by an X (TTT.Players.

Human) and the opponent’s moves are always represented by an O (TTT.Players.

Computer)

The board layout is represented by a pipe-delimited list of moves.This represents the nine

squares of the Tic-Tac-Toe board Each move is either an X, an O, or nothing Depending on

the originating source of the GameInfoobject (Viewer’s object or opponent’s object), asdetermined by matching the Viewer’s ID against gameInfo.dataSourceId, these moves aretranscribed to be appropriate for the Viewer For example, if the latest GameInfoobject

originated from the Viewer’s app data store, the X’s and O’s remain the same If the latest

GameInfoobject was from the opponent’s app data store, the X’s and O’s have to be flipped.

/**

* Loads the specified gameInfo object into

* window.currentGame and updates the board

currentGame = new TTT.Game();

} currentGame.isHumanOpponent = true; //Flag to not make computer moves var myId = window.viewer.getId();

var fromMe = (gameInfo.dataSourceId == myId);

if(gameInfo.boardLayout){

var moves = gameInfo.boardLayout.split("|");

var nOneMark, nZeroMark;

if(fromMe){

nOneMark = TTT.Players.Human;

nZeroMark = TTT.Players.Computer;

} else{

if(moves[i]=="1"){

Trang 20

currentGame.currentPlayer = nOneMark;

currentGame.makeMove(nOneMark, i);

} else{

currentGame.currentPlayer = nZeroMark;

currentGame.makeMove(nZeroMark, i);

} } }

//Now init to current player //debugger;

if(gameInfo.currentPlayer == myId){

currentGame.currentPlayer = TTT.Players.Human;

} else{

if(game.moves[i] == TTT.Players.Computer){

result += "0|";

} else{

result += "1|";

} }

Trang 21

} return result;

}

Finishing and Clearing a Game

Finishing a game ends up being a complex problem If you remember the earlier sion, we had two options to handle finishing and clearing a game.The first was through

discus-a complex three-wdiscus-ay hdiscus-andshdiscus-ake.The second wdiscus-as to discus-allow for mdiscus-anudiscus-al clediscus-aring We’regoing to implement the latter

1 The first step is to add in some methods for removal of a game.This deletes thecurrent client snapshot of the app data store games, saves an updated app data storekey back to the server, and clears currentGame Add the three methods

removeGame, clearGame, and clearAllGamesto the TTT.AppDataPlay

object:

removeGame: function(opponentId){

if(this.myGameAppData && this.myGameAppData.activeGames != null){

var items = this.myGameAppData.activeGames;

//Is object var x;

for(x in items){

if(items[x].opponentId != opponentId){

newItems.push(items[x]);

} } } } this.myGameAppData.activeGames = newItems;

this.updateStoredGameObject();

}, clearGame: function(opponentId){

//debugger;

this.cancelPolling();

Trang 22

var gameInfo = this.getCurrentGameObject(this.currentOpponentId);

var myId = window.viewer.getId();

Clear All games</button>

3 Now we have to modify the selectedOpponentDataCallbackfunction torecognize when one person has started a new game.The following code listingdemonstrates identifying a winner It is the first thing checked after identifyingthat the opponent’s current game is not null with the call if(thisObj.

myOpponentCurrentGame != null) Download the full app code for Chapter 7from our Google Code listing (http://code.google.com/p/opensocialtictactoe/

source/browse/#svn/trunk/chapter7) if you wish to see it in place

var isNewGame = false;

if(myGameInfo.winner != 0 && thisObj.myOpponentCurrentGame.moves < 3){

isNewGame = true;

latestGameInfo = myGameInfo;

}

Trang 23

“Real-Time” Play

We use quotes to signify the air quotes we’d use if this conversation was happening face

to face It’s not actually real-time play.That would require an event-routing system, which

we don’t have at our disposal in this design Instead we’ll set up a polling system so thatthe game can periodically update without reloading the page

A simple polling design sets a time-out after the player makes a move.This time-outcontinues to retrigger at intervals until the code detects that the other player has made amove and control has returned to the Viewer In practice, however, a polling designrequires numerous safety checks and throttling mechanisms

Throughout this section we’ll talk about each part of the code in turn.You may alsohave noticed calls to the polling triggers in the code we’ve introduced already.We won’t

be reviewing those calls in detail but will leave it to the reader to explore how they allinteract in depth

Before we start, there are two constraints to put in place.The first is the polling val.The second is the maximum number of times to retrigger the poll test If a userwalks away from the machine for half a day, we don’t want the game to continue pollingevery ten seconds, so we set a maximum count it can trigger before getting a reply

inter-/**

* Time (in seconds) that the system will wait

* between polling to see

* if the opponent has made a move

*/

OPPONENT_POLLING_TIME: 10,

/**

* Maximum number of times the poll will fire

* without a response before

Trang 24

}, (TTT.AppDataPlay.OPPONENT_POLLING_TIME * 1000));

}

This code sets the initial time-out trigger, which in turn calls the function

pollForOpponentUpdatedMove.The pollForOpponentUpdatedMovefunction makes

a call to re-retrieve the opponent’s app data and reinitialize the game

}

Within the code path of getFriendGameDatais a trigger to cancel the polling.Thisinvolves clearing the time-out handle we saved in startGameplayPollingandresetting all the flags and counters

if(this.gamePollTimer && this.gamePollTimer > 0){

return;

}

Unfortunately, this was too heavy-handed, as the timers would simply die out in somesituations.To combat this, we added an additional counter to allow the trigger to sleep afew times before triggering the die code path:

if(this.gamePollTimer && this.gamePollTimer > 0){

if(TTT.AppDataPlay.pollSleepCount++ > 5){

Trang 25

TTT.AppDataPlay.pollSleepCount = 0;

return;

} }

That seemed to work.We may have been able to get there more cleanly by using

window.setIntervalinstead of a retriggering time-out.We’ll leave it to the reader toexplore different permutations of this design Asynchronous processing, as many of youmay know, can be a complicated problem

Advantages and Disadvantages of App Data P2P Play

There are a few advantages and a few disadvantages to this kind of architecture for P2Pgame play First, let’s go with the advantages:

n There’s no infrastructure required for your game to operate

n There are no explicit scaling considerations—the problem of scaling the game ispushed onto MySpace

Now let’s consider the downsides of this technique:

n Logic can be much more complex than in a shared read/write database

n The app data store can be read and written to by anyone with a little knowledge

of Firebug, essentially allowing a malicious user to cheat

n If your app takes off, it could flatten the MySpace app data infrastructure

Oh, we didn’t mention that before? App data in its current state is not explicitlydesigned for high-volume read/write operations Its initial conception was more forstorage of settings, such as background colors and default fonts.There is much discussionwithin the spec group (those involved with the development of the OpenSocial spec)about the shortcomings of the app data store Currently the main discussions centeraround both a private (not friend-readable) store and a real-time messaging system

As always, you can download the source code for this chapter from the Google Codesite, http://code.google.com/p/opensocialtictactoe, or directly from the source repository

Ngày đăng: 24/12/2013, 06:17

w