From 7de90e7533fa0ba2276e1a3c3ebd22de1548d646 Mon Sep 17 00:00:00 2001 From: Eric Fredricksen Date: Tue, 20 Jul 2010 13:40:25 -0700 Subject: [PATCH] Use Web SQL if available. Works nicely on iPad now. --- config.js | 150 ++++++++++++++++++++++++++++++++++++++-------------- index.html | 25 +++++---- main.css | 4 ++ main.html | 11 ++-- main.js | 84 +++++++++++++++++++---------- newguy.js | 5 +- roster.html | 4 +- roster.js | 17 +++--- sim.js | 39 ++++++++------ 9 files changed, 228 insertions(+), 111 deletions(-) diff --git a/config.js b/config.js index 485ddf8..cf1af43 100644 --- a/config.js +++ b/config.js @@ -157,73 +157,141 @@ function GenerateName() { return result.charAt(0).toUpperCase() + result.slice(1); } +function LocalStorage() { + this.getItem = function (key, callback) { + var result = window.localStorage.getItem(key); + if (callback) + callback(result); + }; + + this.setItem = function (key, value, callback) { + window.localStorage.setItem(key, value); + if (callback) + callback(); + }; + + this.removeItem = function (key) { + window.localStorage.removeItem(key); + }; +} + + function CookieStorage() { - this.getItem = function(key) { - try { - var result; - $.each(document.cookie.split(";"), function (i,cook) { - if (cook.split("=")[0] === key) - result = unescape(cook.split("=")[1]); - }); - return result; - } catch (err) { - // Probably a JSON parse error - return null; - } + this.getItem = function(key, callback) { + var result; + $.each(document.cookie.split(";"), function (i,cook) { + if (cook.split("=")[0] === key) + result = unescape(cook.split("=")[1]); + }); + if (callback) + setTimeout(function () { callback(result); }, 0); + return result; }; - this.setItem = function (key, value) { + this.setItem = function (key, value, callback) { document.cookie = key + "=" + escape(value); + if (callback) + setTimeout(callback, 0); }; this.removeItem = function (key) { document.cookie = key + "=; expires=Thu, 01-Jan-70 00:00:01 GMT;"; }; } - -var storage = window.localStorage; -if (!storage) - storage = new CookieStorage(); +function SqlStorage() { + this.async = true; -function loadRoster() { - if (storage) { - var r = storage.getItem("roster"); - if (r) { + this.db = window.openDatabase("pq", "", "Progress Quest", 2500); + + this.db.transaction(function(tx) { + tx.executeSql("CREATE TABLE IF NOT EXISTS Storage(key TEXT UNIQUE, value TEXT)"); + }); + + this.getItem = function(key, callback) { + this.db.transaction(function (tx) { + tx.executeSql("SELECT value FROM Storage WHERE key=?", [key], function(tx, rs) { + if (rs.rows.length) + callback(rs.rows.item(0).value); + else + callback(); + }); + }); + }; + + this.setItem = function (key, value, callback) { + this.db.transaction(function (tx) { + tx.executeSql("INSERT OR REPLACE INTO Storage (key,value) VALUES (?,?)", + [key, value], + callback); + }); + }; + + this.removeItem = function (key) { + this.db.transaction(function (tx) { + tx.executeSql("DELETE FROM Storage WHERE key=?", [key]); + }); + }; +} + +var iPad = navigator.userAgent.match(/iPad/); +var iPod = navigator.userAgent.match(/iPod/); +var iPhone = navigator.userAgent.match(/iPhone/); +var iOS = iPad || iPod || iPhone; + +var storage = ((window.localStorage && !iOS) ? new LocalStorage() : + window.openDatabase ? new SqlStorage() : + new CookieStorage()); + +storage.loadRoster = function (callback) { + function gotItem(value) { + if (value) { try { - return JSON.parse(r); + value = JSON.parse(value); } catch (err) { // aight } } + storage.games = value || {}; + callback(storage.games); } - return {}; + this.getItem("roster", gotItem); } -function loadSheet(name) { - return loadRoster()[name]; +storage.loadSheet = function (name, callback) { + return this.loadRoster(function (games) { + if (callback) + callback(games[name]); + }); } -function storeRoster(roster) { - if (storage) { - try { - storage.setItem("roster", JSON.stringify(roster)); - return true; - } catch (err) { - if (err.toString().indexOf("QUOTA_EXCEEDED_ERR") != -1) { - alert("This browser lacks storage capacity to save this game. This game can continue but cannot be saved. (Mobile Safari, I'll wager?)"); - storage = null; - return false; - } + +storage.storeRoster = function (roster, callback) { + this.games = roster; + try { + this.setItem("roster", JSON.stringify(roster), callback); + } catch (err) { + if (err.toString().indexOf("QUOTA_EXCEEDED_ERR") != -1) { + alert("This browser lacks storage capacity to save this game. This game can continue but cannot be saved. (Mobile Safari, I'll wager?)"); + this.storeRoster = function (roster, callback) { + setTimeout(callback, 0); + }; + setTimeout(callback, 0); + } else { + throw err; } } } -function addToRoster(newguy) { - if (storage) { - var r = loadRoster(); - r[newguy.Traits.Name] = newguy; - storeRoster(r); +storage.addToRoster = function (newguy, callback) { + if (this.games) { + this.games[newguy.Traits.Name] = newguy; + this.storeRoster(this.games, callback); + } else { + this.loadRoster(function () { + if (storage.games) // should always be true + storage.addToRoster(newguy, callback); + }); } } diff --git a/index.html b/index.html index 81be992..81ef9b3 100644 --- a/index.html +++ b/index.html @@ -28,6 +28,13 @@ p { #go { font-size: 30pt; } +img { + border-style: none; +} +th { text-align: left; padding-right: 2em; } +.good { color: #2a2; } +.bad { color: #a22; } +.ugly { color: #c82; } @@ -62,16 +69,16 @@ coolant, and ported ProgrOS to that. Between you, me, and that talking pony over there, this is pretty much beta software at this point. Browser support breakdown:
- - + + +
Chrome, Firefox, Safari Aces! Works great! We think! -
Internet Explorer, Mobile Safari Visual problems. Can't save games. -
Opera Crashes quite a bit. +
Chrome, Firefox, Safari + Aces! Works great! We think! +
Opera, Mobile Safari + Visual glitches. +
Internet Explorer + Visual glitches. Can't save games.
diff --git a/main.css b/main.css index 830ac40..1859ff0 100644 --- a/main.css +++ b/main.css @@ -118,3 +118,7 @@ tr { height: 15px; } #Quests td, #Plots td { padding-left: 0; } + +body.iOS div.hint { + visibility: visible; +} diff --git a/main.html b/main.html index 989667a..8e812d7 100644 --- a/main.html +++ b/main.html @@ -12,9 +12,8 @@ maximum-scale=1.0; minimum-scale=1.0; user-scalable=0;" /> - - + + @@ -157,7 +156,7 @@
-
+
Quests @@ -170,7 +169,7 @@
-
+
@@ -180,7 +179,7 @@
-
+
diff --git a/main.js b/main.js index e64a0f6..e96c3ec 100644 --- a/main.js +++ b/main.js @@ -959,45 +959,43 @@ function FormCreate() { $(document).bind('beforeunload', function () { if (!storage) - return "Are you sure you want to quit? All your progress will be lost!"; + return "Are you sure you want to quit? All your progress will be lost!"; }); $(window).unload(function (event) { StopTimer(); SaveGame(); + if (storage.async) { + // Have to give SQL transaction a chance to complete + if (window.showModalDialog) + pause(100); + + // Just accept some data loss - alert is too ugly. Maybe increase save + // frequency. + // else alert("Game saved"); + } }); + + if (iOS) $("body").addClass("iOS"); } - LoadCharacter(); + var name = unescape(window.location.href.split('#')[1]); + storage.loadSheet(name, LoadGame); } + +function pause(msec) { + window.showModalDialog("javascript:document.writeln ('')", + null, + "dialogWidth:0;dialogHeight:0;dialogHide:yes;unadorned:yes;"+ + "status:no;scroll:no;center:no;dialogTop:-10000;dialogLeft:-10000"); +} + function quit() { - SaveGame(); - window.location = "roster.html"; -} - -function LoadCharacter() { - var result = true; - var name = unescape(window.location.href.split('#')[1]); - var sheet = loadSheet(name); - - if (!sheet) { - alert("Error loading " + name); - window.location = "roster.html"; - return; - } - - if (!window.localStorage) { - // Cookies can't hold a whole game save - storage.removeItem("roster"); - storage = null; - } - - LoadGame(sheet); - - if (document) - $("#title").text("Progress Quest - " + GameSaveName()); + $(window).unbind('unload'); + SaveGame(function () { window.location = "roster.html"; }); } @@ -1026,17 +1024,40 @@ function HotOrNot() { } -function SaveGame() { +function SaveGame(callback) { Log('Saving game: ' + GameSaveName()); HotOrNot(); game.date = ''+new Date(); game.stamp = +new Date(); game.seed = randseed(); - return addToRoster(game); + storage.addToRoster(game, callback); } function LoadGame(sheet) { + if (!sheet) { + alert("Error loading game"); + window.location = "roster.html"; + return; + } + game = sheet; + + /* + if (!window.localStorage) { + // Cookies can't hold a whole game save + storage.removeItem("roster"); + storage = null; + } +*/ + + if (document) { + var title = "Progress Quest - " + GameSaveName(); + $("#title").text(title); + if (iOS) title = GameSaveName(); + $("title").text(title); + } + + randseed(game.seed); $.each(AllBars.concat(AllLists), function (i, e) { e.load(game); }); if (Kill) @@ -1121,6 +1142,11 @@ function FormKeyDown(e) { quit(); } + if (e.which == 19) { // ^S + SaveGame(); + alert(JSON.stringify(game).length); + } + /* if (e.which == 20) { // ^T TaskBar.reposition(TaskBar.Max()); diff --git a/newguy.js b/newguy.js index aeb80ef..4289b5d 100644 --- a/newguy.js +++ b/newguy.js @@ -205,9 +205,10 @@ function sold() { newguy.Equips.Weapon = newguy.bestequip; newguy.Equips.Hauberk = "-3 Burlap"; - addToRoster(newguy); + storage.addToRoster(newguy, function () { + window.location.href = "main.html#" + escape(newguy.Traits.Name); + }); - window.location.href = "main.html#" + escape(newguy.Traits.Name); } function cancel() { diff --git a/roster.html b/roster.html index 2598d13..e64bd15 100644 --- a/roster.html +++ b/roster.html @@ -64,7 +64,9 @@ a.x, a.sheet { .banter { font-size: 11pt; } - +img { + border-style: none; +} diff --git a/roster.js b/roster.js index d488484..b005a7e 100644 --- a/roster.js +++ b/roster.js @@ -1,18 +1,22 @@ -var games; function load() { - var roster = $("#roster"); - roster.empty(); if (!window.localStorage) { roster.html("Hrumph: This browser does not support local storage. You can still play fast and loose: your character will live only as long as the game stays running in your browser."); return; } + storage.loadRoster(loadGames); +} + +function loadGames(games) { + var roster = $("#roster"); + roster.empty(); + var newone = window.location.href.split('#')[1]; var count = 0; - games = loadRoster(); + $.each(games, function (key, c) { var name = c.Traits.Name; @@ -24,7 +28,7 @@ function load() { if (confirm("Terminate " + Pick(["faithful","noble","loyal","brave"])+ " " + name + "?")) { delete games[name]; - storeRoster(games); + storage.storeRoster(games); load(); } }); @@ -58,8 +62,7 @@ function brag(sheet) { } function clearRoster() { - storeRoster({}); - load(); + storage.storeRoster({}, load); } $(document).ready(function () { diff --git a/sim.js b/sim.js index 12d9a98..da07e6e 100644 --- a/sim.js +++ b/sim.js @@ -30,6 +30,7 @@ var window = { }, }; +var navigator = { userAgent: "v8" }; var location = window.location; @@ -94,13 +95,13 @@ var timers = [{}]; var setInterval = global.setInterval = function (callback, interval) { timers.push({callback:callback, interval:interval}); return timers.length-1; -} +}; +var setTimeout = setInterval; // TODO: distinguish! load("config.js"); var cs = 0; -for (var c in loadRoster()) - cs++; +storage.loadRoster(function (cs) { for (var c in cs) cs++; }); print(cs, "characters"); load("newguy.js"); @@ -129,22 +130,28 @@ function charsheet(game) { // It takes 18 sec to simulate 18 hours of play when I just checked (to // level 10) -for (var j = 1, t = 0; j < 11; ++j) { +for (var j = 1, t = 0; j < 1001; ++j) { t += LevelUpTime(j); - //if (j % 100 == 0) + if (j % 100 == 0) print(j, RoughTime(LevelUpTime(j))+",", RoughTime(t)); } -var l = 0; -for (var i = 0; i < 100000000; ++i) { - if (game.Traits.Level != l) { - SaveGame(); - charsheet(game); - l = game.Traits.Level; - //if (l >= 5) break; +var tmpl = read("charsheet.txt"); +storage.loadSheet("Shienzid", function (sheet) { + game = sheet; + print(template(tmpl, sheet)); + + var l = 0; + for (var i = 0; i < 1000000000; ++i) { + if (game.Traits.Level != l) { + SaveGame(); + charsheet(game); + l = game.Traits.Level; + //if (l >= 5) break; + } + //assert(timers.length == 2);// TODO: this is for simplicity + now += timers[1].interval; + timers[1].callback(); } - //assert(timers.length == 2);// TODO: this is for simplicity - now += timers[1].interval; - timers[1].callback(); -} +});