/** * External Libraries used for the WoShamBo Gadget * Author: Wei-Hwa Huang * * This file contains multiple utilities used for calculating * Woshambo competition information; it can be considered the * "Woshambo API". * * This file contains code that is very WoShamBo-specific; * code that is used for general Javascript manipulation, such * as DOM and UI stuff, is in general-lib.js. */ // The top-level object, all of our other functions and constants // are fields of this object. woshambo = {}; /////////////////////////////////////////////// // Constants woshambo.C = {}; // Version number. Bigger numbers are later versions; ideally, // when we encounter data in the datastore that is of an earlier // version, we should be able to handle it gracefully. woshambo.C.VERSION = 5; // Values that indicate outcomes of a specific combat. woshambo.C.WIN = 0; woshambo.C.TIE = 1; woshambo.C.LOSS = 2; // Storing an unlimited number of combats puts too much stress // on server storage; hence we will occasionally compress // previous results until something called a "past". A past // consists of just raw data recording the number of wins, // ties, and losses. woshambo.C.EMPTYPAST = [0,0,0]; // wins, ties, losses // The possible weapons a player can use in combat. woshambo.C.ROCK = 0; woshambo.C.PAPER = 1; woshambo.C.SCISSORS = 2; woshambo.C.INVALID = 99; // Source directory for the combat images. woshambo.C.IMAGES = "http://opensocial-resources.googlecode.com/" + "svn/samples/woshambo/images/"; // Status of opponents. woshambo.C.WAITING = 99; // opponent has not made a move yet. woshambo.C.DEAD = 100; // opponent hasn't played WoShamBo woshambo.C.SLOW = 101; // opponent has played but hasn't responded yet // Robot IDs. woshambo.C.ROCKMAN = 0; // "Rockman" robot. woshambo.C.FLIPPER = 1; // "Flipper" robot. // Scores for getting results woshambo.C.POINTS_HUMAN_WIN = 105; woshambo.C.POINTS_HUMAN_TIE = 60; woshambo.C.POINTS_HUMAN_LOSS = 15; woshambo.C.POINTS_HUMAN_AVERAGE = 60; // (105 + 15 + 60) / 3 woshambo.C.POINTS_ROBOT_WIN = 4; woshambo.C.POINTS_ROBOT_TIE = 3; woshambo.C.POINTS_ROBOT_LOSS = 2; ///////////////////////////////////////// // Charsheet // // A Charsheet, short for "character sheet", is a list // of stats and information about the player. If the player // levels up or has any personal information not related // to specific combat, it goes here. // // Fields in the charsheet: // version // spent: How many points the player has spent. // score: How many points the player has. // earned: How many points the player has earned over a lifetime. // Note that score and earned are "estimates" only, and may not be accurate // due to other combats. // storage: Weapons storage. A player only has a finite // number of weapons and needs to buy more with // their score. // robot_points: How many points the player has won against robots. // log: Text string of logs. // news: Alerts and information to the player. // Constructor. woshambo.Charsheet = function() { this.version = woshambo.C.VERSION; this.spent = 0; this.score = 0; this.earned = 0; this.robot_points = 0; this.storage = [20,20,20]; // 20 rocks, 20 paper, 20 scissors this.log = ""; this.news = ""; }; ///////////////////////////////////////// // History // // A History is a list of all the Moves a player // has used in a Match (a Match being a series of Combats // against a specific player). // // Fields: // version // weapons: an array of previous Moves (usually Weapons). // past: When the list of weapons gets too big, it gets // compressed into this "past", which is an aggregate // list of previous weapons. (Nothing does the compression // yet, though.) // past_points: The number of points earned by the "past". // Constructor. woshambo.History = function() { this.version = woshambo.C.VERSION; this.past = woshambo.F.copyPast(woshambo.C.EMPTYPAST); this.weapons = []; // list of Moves this.past_points = 0; // points_earned in the past }; /////////////////////////////////////////////// /////////////////////////////////////////////// // Functions for manipulating woshambo objects // // All these utility functions are stored under "F". woshambo.F = {}; /****************************************** This is the old spec for History and Charsheet, mostly used in upgrading and downgrading. VERSIONS 1-2: woshambo.Charsheet = function() { this.version = woshambo.C.VERSION; this.score = 0; this.earned = 0; this.storage = [100,100,100]; // 100 rocks, 100 paper, 100 scissors this.log = ""; this.news = ""; }; woshambo.History = function() { this.version = woshambo.C.VERSION; this.past = woshambo.F.copyPast(woshambo.C.EMPTYPAST); this.weapons = []; // list of Moves }; VERSION 3: Added "past_points" to History. VERSION 4: Added "spent" to Charsheet. VERSION 5: Added "robotpoints" to Charsheet. ******************************************/ /////////////////////////////////////////////////////// // Functions for encoding and decoding history objects. /////////////////////////////////////////////////////// // Given an object that is suspected to be a history object, // attempt to turn it into a workable history object of the // current version and return it. woshambo.F.canonHistory = function(history) { if (history == undefined || history.version == undefined) { debugLog("Invalid history! Making new one...
"); history = new woshambo.History(); } else if (history.version < woshambo.C.VERSION) { debugLog("History is an old version! Upgrading...
"); history = woshambo.F.upgradeHistory(history); } else if (history.version > woshambo.C.VERSION) { debugLog("History is an newer version! Downgrading...
"); history = woshambo.F.downgradeHistory(history); } return history; } // Given a history from an older version, return a upgraded version of it. woshambo.F.upgradeHistory = function(original) { // check to make sure we are supposed to be called. if (original.version == woshambo.C.VERSION) return original; if (original.version > woshambo.C.VERSION) return woshambo.F.downgradeHistory(original); var result = new woshambo.History(); if (original.version <= 2) { result.past = woshambo.F.copyPast(original.past); result.weapons = original.weapons.slice(); } else if (original.version <= 4) { // no change needed result.past = woshambo.F.copyPast(original.past); result.weapons = original.weapons.slice(); result.past_points = original.past_points; } else { // we shouldn't be here, but let's just make guesses. if (original.past != undefined) result.past = woshambo.F.copyPast(original.past); if (original.weapons != undefined) result.weapons = original.weapons.slice(); if (original.past_points != undefined) result.past_points = original.past_points; } return result; } // Given a history from an newer version, attempt to // return a current version of it. woshambo.F.downgradeHistory = function(original) { // check to make sure we are supposed to be called. if (original.version == woshambo.C.VERSION) return original; if (original.version < woshambo.C.VERSION) return woshambo.F.upgradeHistory(original); // Rewrite it to be the current version original.version = woshambo.C.VERSION; // all we can do is make guesses and hope that the fields are still there. // if they aren't, just initialize them. if (original.past != undefined) original.past = woshambo.F.copyPast(woshambo.C.EMPTYPAST); if (original.weapons != undefined) original.weapons = []; if (original.past_points != undefined) original.past_points = 0; return original; } /////////////////////////////////////////////////////// // Functions for encoding and decoding charsheet objects. /////////////////////////////////////////////////////// // Given an object that is suspected to be a charsheet object, // attempt to turn it into a workable charsheet object of the // current version and return it. woshambo.F.canonCharsheet = function(charsheet) { if (charsheet == undefined || charsheet.version == undefined) { debugLog("Invalid charsheet! Making new one...
"); charsheet = new woshambo.Charsheet(); } else if (charsheet.version < woshambo.C.VERSION) { debugLog("Charsheet is an old version! Upgrading...
"); charsheet = woshambo.F.upgradeCharsheet(charsheet); } else if (charsheet.version > woshambo.C.VERSION) { debugLog("Charsheet is an newer version! Downgrading...
"); charsheet = woshambo.F.downgradeCharsheet(charsheet); } return charsheet; } // Given a charsheet from an older version, return a upgraded version of it. woshambo.F.upgradeCharsheet = function(original) { // check to make sure we are supposed to be called. if (original.version == woshambo.C.VERSION) return original; if (original.version > woshambo.C.VERSION) return woshambo.F.downgradeCharsheet(original); var result = new woshambo.Charsheet(); if (original.version <= 4) { result.score = original.score; result.earned = original.earned; result.spent = original.earned - original.score; result.storage = original.storage.slice(); result.log = original.log; result.news = original.news; } else { // we shouldn't be here, but let's just make guesses. if (original.score != undefined) result.score = original.score; if (original.earned != undefined) result.earned = original.earned; if (original.spent != undefined) result.spent = 0; if (original.storage != undefined) result.storage = original.storage.slice(); if (original.log != undefined) result.log = original.log; if (original.news != undefined) result.news = original.news; if (original.robot_points != undefined) result.robot_points = original.robot_points; } return result; } // Given a charsheet from an newer version, attempt to // return a current version of it. woshambo.F.downgradeCharsheet = function(original) { // check to make sure we are supposed to be called. if (original.version == woshambo.C.VERSION) return original; if (original.version < woshambo.C.VERSION) return woshambo.F.upgradeCharsheet(original); original.version = woshambo.C.VERSION; // all we can do is make guesses and hope that the fields are still there. // if they aren't, just initialize them. if (original.score != undefined) original.score = 0; if (original.earned != undefined) original.earned = 0; if (original.spent != undefined) original.spent = 0; if (original.storage != undefined) original.storage = [20, 20, 20]; if (original.log != undefined) original.log = ""; if (original.news != undefined) original.news = ""; if (original.robot_points != undefined) original.robot_points = 0; return original; } //////////////////////////////////////////////// // functions that deal with combat and weapons //////////////////////////////////////////////// // Given an old "past" object, outputs a new copy of it. woshambo.F.copyPast = function(old) { var newPast = []; newPast[woshambo.C.WIN] = old[woshambo.C.WIN]; newPast[woshambo.C.TIE] = old[woshambo.C.TIE]; newPast[woshambo.C.LOSS] = old[woshambo.C.LOSS]; return newPast; } // Converts a combat result to a human-readable string. woshambo.F.resultToString = function(result) { if (result == woshambo.C.WIN) return "Win"; if (result == woshambo.C.LOSS) return "Loss"; if (result == woshambo.C.TIE) return "Tie"; return "?"; } // Converts a weapon to a single letter. woshambo.F.weaponToLetter = function(weapon) { if (weapon == woshambo.C.ROCK) return "R"; if (weapon == woshambo.C.PAPER) return "P"; if (weapon == woshambo.C.SCISSORS) return "S"; return "?"; } // Converts a weapon to a DOM object of the image. // explode should be a boolean value specifying whether // the weapon should have an "explosion" graphic or not. woshambo.F.weaponToImage = function(weapon, explode) { var result = document.createElement("img"); if (weapon == woshambo.C.ROCK && explode) { result.src = woshambo.C.IMAGES + "weapon-rock-inverse-small.png"; return result; } if (weapon == woshambo.C.ROCK && !explode) { result.src = woshambo.C.IMAGES + "weapon-rock-normal-small.png"; return result; } if (weapon == woshambo.C.PAPER && explode) { result.src = woshambo.C.IMAGES + "weapon-paper-inverse-small.png"; return result; } if (weapon == woshambo.C.PAPER && !explode) { result.src = woshambo.C.IMAGES + "weapon-paper-normal-small.png"; return result; } if (weapon == woshambo.C.SCISSORS && explode) { result.src = woshambo.C.IMAGES + "weapon-scissors-inverse-small.png"; return result; } if (weapon == woshambo.C.SCISSORS && !explode) { result.src = woshambo.C.IMAGES + "weapon-scissors-normal-small.png"; return result; } } // Calculates the result of a combat (relative to the host) // based on the weapons used. woshambo.F.combatResult = function(hostWeapon, guestWeapon) { if (hostWeapon == woshambo.C.ROCK) { // rock if (guestWeapon == woshambo.C.ROCK) return woshambo.C.TIE; if (guestWeapon == woshambo.C.PAPER) return woshambo.C.LOSS; if (guestWeapon == woshambo.C.SCISSORS) return woshambo.C.WIN; } else if (hostWeapon == woshambo.C.PAPER) { // paper if (guestWeapon == woshambo.C.ROCK) return woshambo.C.WIN; if (guestWeapon == woshambo.C.PAPER) return woshambo.C.TIE; if (guestWeapon == woshambo.C.SCISSORS) return woshambo.C.LOSS; } else if (hostWeapon == woshambo.C.SCISSORS) { // scissors if (guestWeapon == woshambo.C.ROCK) return woshambo.C.LOSS; if (guestWeapon == woshambo.C.PAPER) return woshambo.C.WIN; if (guestWeapon == woshambo.C.SCISSORS) return woshambo.C.TIE; } // if all else fails, tie. return woshambo.C.TIE; } // Given a history object, output how long the past in this history is. woshambo.F.pastLength = function(history) { var wins = history.past[woshambo.C.WIN]; var ties = history.past[woshambo.C.TIE]; var losses = history.past[woshambo.C.LOSS]; return (wins + ties + losses); } // Given a history object, output how many total weapons are in the history. woshambo.F.weaponCount = function(history) { return (woshambo.F.pastLength(history) + history.weapons.length); } // Given a history object, return a past that is the inverse of // what the past in the object is. woshambo.F.invertPast = function(history) { var result = woshambo.F.copyPast(woshambo.C.EMPTYPAST); result[woshambo.C.WIN] = history.past[woshambo.C.LOSS]; result[woshambo.C.TIE] = history.past[woshambo.C.TIE]; result[woshambo.C.LOSS] = history.past[woshambo.C.WIN]; return result; } // Given a combat result, calculate the number of points won. woshambo.F.calcPoints = function (is_human, result) { if (is_human) { if (result == woshambo.C.WIN) return woshambo.C.POINTS_HUMAN_WIN; if (result == woshambo.C.TIE) return woshambo.C.POINTS_HUMAN_TIE; if (result == woshambo.C.LOSS) return woshambo.C.POINTS_HUMAN_LOSS; } else { if (result == woshambo.C.WIN) return woshambo.C.POINTS_ROBOT_WIN; if (result == woshambo.C.TIE) return woshambo.C.POINTS_ROBOT_TIE; if (result == woshambo.C.LOSS) return woshambo.C.POINTS_ROBOT_LOSS; } } ////////////////////////////////////////////////// // functions that calculate values from charsheets and histories ////////////////////////////////////////////////// // Remove the points earned and points holding in a charsheet. woshambo.F.resetCharsheetPoints = function(charsheet) { charsheet.score = charsheet.robot_points - charsheet.spent; charsheet.earned = charsheet.robot_points; } // Given a host charsheet, increment by the specified number of points. woshambo.F.addCharsheetPoints = function(charsheet, pts) { charsheet.score += pts; charsheet.earned += pts; } // Given a host charsheet and two histories (that are involved in a war), // increment the charsheet to reflect the points earned in the two histories. woshambo.F.addCharsheetHistoryPoints = function(charsheet, hostHistory, guestHistory) { if (hostHistory == undefined) return; if (guestHistory == undefined) return; var pts = woshambo.F.totalPointsEarned(hostHistory, guestHistory, true); charsheet.score += pts; charsheet.earned += pts; } // Given two histories (that are involved in a war), output // the total number of points earned by the host. // // If the host's past is smaller than the guest's past, we have a // problem -- we don't know what moves the guest did in our first moves. // For now, let's assume that the host got an "average" score; in a // future version we'll attempt to do this the "correct" way, // maybe by recording the guest's moves as a backup. // woshambo.F.totalPointsEarned = function(hostHistory, guestHistory, is_human) { var answer = hostHistory.past_points; var hostPast = woshambo.F.pastLength(hostHistory); var guestPast = woshambo.F.pastLength(guestHistory); var result = null; if (hostPast < guestPast) { var hostPos = guestPast - hostPast; var guestPos = 0; while (hostPos < hostHistory.weapons.length && guestPos < guestHistory.weapons.length) { answer += woshambo.F.calcPoints( is_human, woshambo.F.combatResult(hostHistory.weapons[hostPos], guestHistory.weapons[guestPos]) ); hostPos++; guestPos++; } } else { result = woshambo.F.copyPast(hostHistory.past); var guestPos = hostPast - guestPast; answer += guestPos * woshambo.C.POINTS_HUMAN_AVERAGE; // don't count the stuff that is in the host's past var hostPos = 0; while (hostPos < hostHistory.weapons.length && guestPos < guestHistory.weapons.length) { answer += woshambo.F.calcPoints( is_human, woshambo.F.combatResult(hostHistory.weapons[hostPos], guestHistory.weapons[guestPos]) ); hostPos++; guestPos++; } } return answer; } // Given two histories (that are involved in a war), output // a past object that lists the the aggregate result of the two // histories facing off against each other. woshambo.F.netResults = function(hostHistory, guestHistory) { var hostPast = woshambo.F.pastLength(hostHistory); var guestPast = woshambo.F.pastLength(guestHistory); var result = null; if (hostPast < guestPast) { result = woshambo.F.copyPast(woshambo.F.invertPast(guestHistory)); var hostPos = guestPast - hostPast; var guestPos = 0; while (hostPos < hostHistory.weapons.length && guestPos < guestHistory.weapons.length) { result[woshambo.F.combatResult(hostHistory.weapons[hostPos], guestHistory.weapons[guestPos])]++; hostPos++; guestPos++; } } else { result = woshambo.F.copyPast(hostHistory.past); var guestPos = hostPast - guestPast; var hostPos = 0; while (hostPos < hostHistory.weapons.length && guestPos < guestHistory.weapons.length) { result[woshambo.F.combatResult(hostHistory.weapons[hostPos], guestHistory.weapons[guestPos])]++; hostPos++; guestPos++; } } return result; } // Add a move by the "guest", of the specified weapon. // Call the callback function with the combat result of this weapon. woshambo.F.makeMove = function (hostHistory, guestHistory, weapon, callback) { var turn = guestHistory.weapons.length + woshambo.F.pastLength(guestHistory); var myPosition = guestHistory.weapons.length; var hostPosition = turn - woshambo.F.pastLength(hostHistory); guestHistory.weapons.push(weapon); if (hostPosition < 0) { // host's move has been lost to history -- oops! callback(woshambo.C.INVALID); } else if (hostPosition >= hostHistory.weapons.length) { // host hasn't made a move yet callback(woshambo.C.WAITING); } else { callback(woshambo.F.combatResult(guestHistory.weapons[myPosition], hostHistory.weapons[hostPosition])); } } /////////////////////////////////////////////// // functions for woshambo robots /////////////////////////////////////////////// // Returns a random weapon. woshambo.F.randomWeapon = function () { var temp = Math.random() * 3; if (temp < 1) return woshambo.C.ROCK; if (temp < 2) return woshambo.C.PAPER; return woshambo.C.SCISSORS; } // Adds a move from the robot to robot_history. woshambo.F.robotMove = function (robotID, robot_history, opponent_history) { if (robotID == woshambo.C.ROCKMAN) { woshambo.F.robotRockmanMove(robot_history, opponent_history); } else if (robotID == woshambo.C.FLIPPER) { woshambo.F.robotFlipperMove(robot_history, opponent_history); } else { // don't recognize the robot; default to rockman. woshambo.F.robotRockmanMove(robot_history, opponent_history); } } woshambo.F.robotRockmanMove = function (robot_history, opponent_history) { robot_history.weapons.push(woshambo.C.ROCK); } woshambo.F.robotFlipperMove = function (robot_history, opponent_history) { if (robot_history.weapons.length == 0) { robot_history.weapons.push(woshambo.F.randomWeapon()); } else { var last = robot_history.weapons[robot_history.weapons.length-1]; var next = woshambo.F.randomWeapon(); while (next == last) { next = woshambo.F.randomWeapon(); } robot_history.weapons.push(next); } } // Returns a printable display name for that robot. woshambo.F.robotName = function (robotID) { if (robotID == woshambo.C.ROCKMAN) { return "Rockman the Robot"; } else if (robotID == woshambo.C.FLIPPER) { return "Flipper the Robot"; } else { // don't recognize the robot; default to rockman. return "Rockman the Robot"; } }