Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 50 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
50
Dung lượng
292,9 KB
Nội dung
ptg only the picked opponent in the availableGames div. Later on we’ll make it do something more interesting. function opponentPickedAction(person){ var elem = document.getElementById("availableGames"); elem.innerHTML = person.getDisplayName(); } 4. Immediately after the opponentPicked function, add the following loadFriendPicker function.This function makes use of the widget Bootstrapper object to initialize the FriendPicker widget.The Bootstrapper allows us to safely create a widget from a dynamically included script library.The call to createWidget takes three parameters: the object name of the widget being created, an initializer function to fire when the widget has been created, and an object of parameters to pass into the widget at initialization. In this case, we’re passing in the ID of our target div element, a callback function to execute whenever 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 }); } 5. In our loadInitializer function, add a call to loadFriendPicker : function loadInitializer(){ try{ loadWordOfTheDay(); } catch(ex){} try{ initializeGame(); } catch(ex){} loadFriendPicker(); } 124 Chapter 7 Flushing and Fleshing: Expanding Your App and Person-to-Person Game Play From the Library of Lee Bogdanoff Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 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 current viewer UI. Clicking on the [Click for Recipient] item shown in Figure 7.4 brings up the 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 on some of the code shown previously in the book for using the app data store.This time we’ll clean things up a bit by placing all our code under the TTT namespace, instead of leaving a bunch of dangling functions hanging off the global window object. Storage JSON Objects The first step is to create the GameInfo storage object. In your script code, add the following code: TTT.GameInfo = function(opponentId){ this.opponentId = opponentId; this.dataSourceId = window.viewer.getId(); this.boardLayout = null; this.moveNumber = 0; this.currentPlayer = 0; this.winner=0; } With this object, our game can track the opponent, whose move it is, if there is a win- ner, and all the moves on the board.The active games are stored as an array of these objects. AppDataPlay Game Object The next step is to set up our basic game data manager.This will contain the logic described earlier in the chapter. It will also encapsulate some utility methods for managing the data store. Turn-Based Games 125 Figure 7.4 FriendPicker below player info. From the Library of Lee Bogdanoff Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg In earlier chapters we’ve shown some techniques by making use of global functions.While convenient for small projects, it is good practice to namespace your script code.We’re going to encapsulate all of this in a static object namespace of TTT.AppDataPlay . 126 Chapter 7 Flushing and Fleshing: Expanding Your App and Person-to-Person Game Play 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: <style type="text/css"> #log{ float:left; margin:5px; padding:5px; border:5px solid #888; width:350px; height:350px; overflow:auto; background:white; } </style> <div id="log"> </div> . if(!gadgets.log){ gadgets.log = function(msg){ var el = document.getElementById("log"); if(!el) return; var m = document.createElement("div"); m.innerHTML = msg; el.appendChild(m); } } From the Library of Lee Bogdanoff Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 1. Create the basic object skeleton and add it into your script source.The code is shown here: TTT. AppData Play = { } 2. Add a Keys object to this namespace that will encapsulate all the app data keys used for game play: TTT. AppData Play = { /** * Keys into the AppData store */ Keys: { activeGames: "activeGames", AppDataGetKey: "getData", finishedGames: "finishedGames" } } 3. Now we will add some empty object key definitions to hold our data. Even though JavaScript allows for dynamic creation of variables, it is good practice to explicitly declare and comment any variables that are used in multiple places. Add this code inside our TTT.AppDataPlay object: TTT.AppDataPlay = { . . . /** * Local cache of game app data for Viewer. */ myGameAppData: { "activeGames":null }, /** * Local cache of opponent game app data */ myOpponentAppData: {}, /** * Local cache of opponent in current game */ myOpponentCurrentGame: {}, Turn-Based Games 127 From the Library of Lee Bogdanoff Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg /** * Current opponent being played against */ currentOpponentId: null, . . . } 4. Add method stubs for game play support.These functions are set up as placehold- ers so that our code won’t break as we build it out.We will fill in the functions with real logic later. 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 */ loadGame: function(gameInfo){}, /** * Displays a message regarding game play */ updateGameStatusMessage: function(message){}, /** * Serializes the board information into a string for storage */ 128 Chapter 7 Flushing and Fleshing: Expanding Your App and Person-to-Person Game Play From the Library of Lee Bogdanoff Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg getGameMovesString: function(game){}, . . . } 5. Add the methods for retrieval and saving of the Viewer’s app data.These methods are adaptations of what we wrote in Chapter 4, Persisting Information.The only real difference is that we include them in our TTT.AppDataPlay namespace. 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(); Turn-Based Games 129 From the Library of Lee Bogdanoff Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg req.add(req.newUpdatePersonAppDataRequest( opensocial.IdSpec.PersonId.VIEWER, key, value), "set_" + key); req.send(); }, . . . } 6. Referenced in the methods just shown is a general-purpose app data callback handler adapted from Chapter 4, Persisting Information. Add the function loadAppDataCallback in as well.This function places all the AppData keys and values in the response into the specified object hash. In that way we make only one 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 */ loadAppDataCallback: function (data, targetCollection, loadedCallback){ logDataError(data); if (data.hadError()) { return; //Exit if nothing found } 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 var ok = false; for(var vkey in mydata){ mydata = mydata[vkey]; ok = true; break; } if(ok){ if(targetCollection == null){ gadgets.log("Bad collection in load callback"); 130 Chapter 7 Flushing and Fleshing: Expanding Your App and Person-to-Person Game Play From the Library of Lee Bogdanoff Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 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 this briefly in Chapter 4, Persisting Information, but did not write the code for retrieving other people’s app data.You can read, but cannot write, your friend’s data, provided the friend has the app installed as well.The main differences between this and Viewer are the idSpec used and the callback function. TTT.AppDataPlay = { . . . /** * Retrieve app data for an opponent. */ getFriendGameData: function (friendId){ var idparams = {}; idparams[opensocial.IdSpec.Field.USER_ID] = opensocial.IdSpec.PersonId.VIEWER; 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; */ Turn-Based Games 131 From the Library of Lee Bogdanoff Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg if(TTT.AppDataPlay.currentOpponentId != friendId && window.currentGame){ currentGame.clear(); } TTT.AppDataPlay.currentOpponentId = friendId; 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.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 for game management. For now, we’re just going to stub it out and add a log state- ment so our code will run. TTT.AppDataPlay = { . . . selectedOpponentDataCallback: function(data){ gadgets.log("Got opponent data: " + data); } . . . } 132 Chapter 7 Flushing and Fleshing: Expanding Your App and Person-to-Person Game Play From the Library of Lee Bogdanoff Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg At this point, we have the skeleton of the AppDataPlay object built and the utility methods 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-computer game 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 to differentiate 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-Toe game engine in order to support a human opponent in a turn-based system. We must 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 a draw. 1. Add a property in the TTT.Game object to flag the game as being against a human opponent.This will be used in the game logic to trigger a computer move or trigger an app data store update. var TTT = { . . . Game: function(){ . . . /** * Flag to set if this is a vs. computer * or vs. human game */ this.isHumanOpponent = false; . . . } } 2. Add a new method to the TTT.Game prototype object to test for a draw. In human-versus-computer play we didn’t need to test for a draw. When no squares were 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 lookForWin function and add the isADraw function immediately below. Supporting Person-to-Person Game Play 133 From the Library of Lee Bogdanoff Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. [...]... development of the OpenSocial spec) about the shortcomings of the app data store Currently the main discussions center around 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 Code site, http://code.google.com/p/opensocialtictactoe, or directly from the source repository at http://code.google.com/p/opensocialtictactoe/source/browse/#svn/trunk/chapter7... identifying that the opponent’s current game is not null with the call if(thisObj myOpponentCurrentGame != null) Download the full app code for Chapter 7 from 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){ thisObj.removeGame(thisObj.currentOpponentId);... of casual and not-so-casual gaming sites are available online Most of them have games of much greater polish and budget than are found on social networks It is the social interaction aspect that makes building P2P games on MySpace interesting Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark From the Library of Lee Bogdanoff Summary 149 The technique we’ve shown in this chapter... it wasn’t designed to do, while technically interesting, is not always the most prudent path Note Code listings and/or code examples for this chapter can be found on our Google Code page under http://opensocialtictactoe.googlecode.com Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark From the Library of Lee Bogdanoff This page intentionally left blank Please purchase PDF Split-Merge... simplejson to from django.utils import simplejson Note Google App Engine (GAE) provides some convenient tools to help you when developing your app, but its uses could probably fill another book, and this is an OpenSocial book To learn more about GAE and what it can do for you, go right to the source: http://code.google.com/appengine/docs/python/gettingstarted/ Please purchase PDF Split-Merge on www.verypdf.com... in our working root and added a script file to it: \ws\ws.py Inside your app.yaml file, add a handler for this new script; don’t forget to change your application name as necessary: application: opensocial tic-tac-toe version: 1 runtime: python api_version: 1 handlers: - url: /ws script: ws/ws.py Let’s look at the code for ws.py, which right now handles an incoming GET request: import myspace.app . idparams [opensocial. IdSpec.Field.USER_ID] = opensocial. IdSpec.PersonId.VIEWER; idparams [opensocial. IdSpec.Field.NETWORK_DISTANCE] = 0; idparams [opensocial. IdSpec.Field.GROUP_ID]. idparams [opensocial. IdSpec.Field.GROUP_ID] = opensocial. IdSpec.GroupId.SELF; var id = opensocial. newIdSpec(idparams); var req = opensocial. newDataRequest(); req.add(