Friday, May 31, 2013

almost ready to submit to the games sites

Changes and additions:
  • I did add more locations. I forgot to do Israel, which is a darned interesting place to look at.  We have Bangkok, Chiang Mai, Singapore, and Taiwan.  I also added three US urban areas in an attempt to increase the chances of an urban area being selected: SF, LA, and NY (and surrounding areas of each).  Moscow was already included in the Europe ring. This seems to have reduced the chances of being on a long, straight, empty road.
  • I wrote another Javascript/JQuery/HTML file to give players a map, let them navigate in and out of StreetView, and select the location they're at as the starting point for a game.  With huge amounts of help from boyfriend, we got the game storing and retrieving those locations on AppEngine (again! start small! go slow!) And finally I modified index.html to figure out whether it's starting a pre-set game, a user-created game, or a random game.
It's just about ready to release now, I think, with one more fix: if someone calls a game that doesn't exist, the program needs to do something graceful.

Edit, a few hours later: got Israel, got a graceful error message to pop up.  The boyfriend also hacked the page and found he could store junk text in the datastore, but that didn't affect playability. He added code to address the vulnerability and so it seems more or less ready to roll now.

Monday, May 27, 2013

the world is large...

...and has a lot of very straight, very empty roads on it.

MapTag tips (which are really StreetView tips):

There is really no easy way to move a long distance in a single click using StreetView. The step distance seems to depend on how fast the car was going at the time.

The circle, which helps you take the largest step possible, is imperfect. You can't just leave the mouse on the same spot onscreen and keep clicking.  Eventually it will fuck up.  It seems if you move it a bit between each click, that will reduce the chances of it thinking you want to zoom, or adjust the view.  Occasionally I have seen it spin me 90-180 degrees off from where I was headed, so keep half an eye on your general heading in case that happens.

I find it rather interesting, after writing the game, that it's still fun to play.

At some point I may try to increase the chances of hitting urban/suburban areas.  Right now the valid random places are based on about 8 circles of varying sizes: Australia, New Zealand, South Africa, continental US/Canada/Mexico, Alaska/NW Canada, Hawaii, Europe, Japan, and Brazil.  Each has a probability, initially based on area size, but also hand-jiggered a tiny bit based on how much empty expanse it has.  I should tweak them more to have a stronger bias toward Europe, Japan, and such places that have less empty space.  I could specify urban areas (e.g. the US coasts) and give them a bit more probability of being selected.  Other places that should be added: Moscow, Israel, Thailand, Taiwan, Hong Kong, Singapore.

Sunday, May 26, 2013

finally wrote another game (but not Android)

Back in 2006 I wrote a Flash game, sort of an escaper.  In my hackathon fashion, I drew the scenes and worked out the puzzles first so I could download the 30-day Flash trial and learn enough about Flash to create the game before the trial ran out. I had it done in 20 days, a friend hosted it, and people liked it. But it was difficult enough that I didn't really want to do it again.

Fast foward to now, coming up on a year of Android coding under my belt, working with various Google APIs, along with a bit of JQuery and straight Java.  Things make a lot more sense now.  And I come across this neat little game called Pursued, which uses Google Streetview.  You get plopped into a location and you need to figure out what city you're in within a certain time limit.  It's got a lovely, slick HTML5 UI. You get achievement badges for going through themed sets of cities, and it has a separate section for players to add their own games.

Around the same time Yonatan Zunger posted about a game called Geoguessr.  Same general principle, but there's no time limit, and you're shooting for accuracy -- not a city name, but getting as close to the start point as possible.  The UI is less pretty but it's in many ways more challenging.

I mentioned Pursued to Yonatan, saying it would be nice to have a hybrid of the two games, with a slick interface and points for accuracy.  He agreed and suggested I write it.

Well, sometimes I laugh at suggestions.  Sometimes I take them as a challenge.

My all-things-coding tutor, aka boyfriend, has taught me one very important principle: start simple. Don't try to write the entire program at once.  So I got a div to display a navigable Streetview map. Then added a second large map that you could slide in and out from the side.  Then added the ability to drop a marker on that map, calculate where it is versus where the user is, and score it.  Then added another slide-out for an array of pre-set games. And finally some help and credits screens.

And this is all in Javascript, with a bunch of JQuery to listen for clicks and CSS to drive the animations.  Did I mention I don't really know much Javascript?

All in all, I wouldn't say it has a totally slick interface, but it has both a random-location game and a set of pre-set games based on a theme. Plus it scores you based on where you are in Streetview -- for some odd reason, Geoguessr scores your answer based on the original location it dropped you into, even though you might have had to travel for miles to figure out where you are.

I definitely needed help with the trigonometry of choosing a random location and measuring distance (I'm a little jealous that boyfriend didn't even need a refresher).  I received hints and general guidance, and occasionally outright coding. Apparently there are timing issues when you're asking someone else's server to give you large quantities of data: there are things you simply have to wait for, and you can't continue until you have that data in hand, so to speak. He taught me a bit about callbacks and (the formal term escapes me) chaining functions to guarantee one will start executing only after another is finished.  And of course he helped get my AppEngine settings set up so I could upload it there.

So without further ado: MapTag

Thursday, May 9, 2013

reading/writing Google Drive

Here are the broad steps to write a file from an Android device to Google Drive.  You create a unique SHA1 fingerprint for your application (step 1), tell Drive your application exists and will be utilising the Drive API (step 2).  The branding information is the "pretty" name for your app; the package name is its name in your code (e.g. com.example.myapp). Library and dependency installation (step 3) is easy if you have Eclipse; if you have IntelliJ, one of their programmers has shown how to create the analogous configuration on StackOverflow.

The sample code, Drive Quickstart, starts up the built-in camera app, grabs the Uri of the saved image, and uploads it to Drive.  In the process it needs to get a login name and authorization to write to Drive.  Since life is uncertain, it uses startActivityForResult, which returns a result code to determine the next action.  I needed significantly more complicated behavior for writing the file, but this is the basic sequence of events.


My application stores the user login (e.g. "mightyjoeyoung@gmail.com") in SharedPreferences, so once it's set, it won't ask again. There are some initial checks if a) a stored name exists and b) the stored name actually associates with a valid account. If it finds nothing, it starts an account picker Intent.


app  = (App) getApplication();
// check for a previously saved login
settings = getSharedPreferences(app.PREFS_NAME, 0);
String savedLogin = settings.getString("savedlogin", "");
if (!savedLogin.equals("")) {
    app.credential.setSelectedAccountName(savedLogin);
    service = getDriveService(app.credential);
}
if (savedInstanceState != null) {
    inProgress = savedInstanceState.getBoolean("inProgress");
}
if (app.credential.getSelectedAccount() == null) {
    startActivityForResult(app.credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}


When a valid account is set, it authorizes and creates a Drive service object

Drive service = new Drive.Builder(AndroidHttp.newCompatibleTransport(), new GsonFactory(), credential).build();

which is used for the reading/writing transactions.  Failure to authorize results in going back to the account picker.

If all is well, it grabs the Uri of the file on the phone (which was created before starting the whole process) and attempts to upload it.

Drive Quickstart's sample code has no UI beyond the bare minimum.  While it's busy trying to upload, by all appearances the app has hung. And the first time it's run, it can take up to a minute to complete the process (or at least that's what happened to me).  They don't even bother to compensate for changes in screen orientation.  With a progress dialog popup and ongoing status messages, users will be less inclined to cancel.

Drive has a quirk (as mentioned in an earlier post) of keeping track of files with an internal ID rather than the file name. Multiple writes of the same file name results in multiple files with the same name.  Rather than confusing the user (and the macro that adds the records to the spreadsheet) I had to get hold of that file ID, save it to SharedPreferences, and when time came for rewriting, write to that ID.  And, if the user had happened to trash it, fish it out of the trash.

So the logic goes: if you have an ID previously saved in SharedPreferences, and that file exists, you're good to go and overwrite. (And fish it out of the trash, whether or not it's in trash, because it's not super easy to tell if it's in trash.)  If you don't have a saved ID, or you do but the file isn't there, you have to create a new file (and after it's written to Drive, get its file ID and save it).

Here is a list of the methods to work with Drive files. There is also some sample code at the end of each method's page.

One weirdness about the Drive Quickstart sample code: for reasons unknown, it asks for full access to all the files on the Drive.
credential = GoogleAccountCredential.usingOAuth2(this, DriveScopes.DRIVE);
It doesn't need this, and your users will be O_o if your app asks for the same permission. You really want your app to access only the files it creates:
credential = GoogleAccountCredential.usingOAuth2(this, DriveScopes.DRIVE_FILE);

Lastly, the boyfriend's phone seemed to need this added to the manifest:
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
I'm not sure if this was due to his Android version, or that he has more than one account registered on the phone, or that he has two-stage authentication.

Wednesday, May 1, 2013

Well, scratch that

I still don't know if I want to put it in the Play Store or not, but I restored (since that's what I had it doing before we decided to use Drive) a Share button.  That brings up the generic dialog to send a text file: your choice of Bluetooth, Copy to clipboard, Drive, Email, Gmail, Google, G+, and Text message. So it doesn't require Drive and the Drive spreadsheet macro, and the user is free to pop the data into their own spreadsheet or database.  It's just a CSV file.

And yes, I did make a steampunk scale. I photographed the top surface of a Deco vanity, mushed it into the right proportions, and stuck on Googled images of brass gears and wood borders and such.


However, There are semitransparent XML borders around the weight "dials" and the Save button (see previous post, updated to include the new Notes field and the changes mentioned above), and I haven't coded to compensate for the significantly darker color of the wood scale.The font color is that not-quite-black of Android text, and though the digits are nicely visible, Save is not.  It looks, in a word, awful.  So I'm not posting the pic.

The ideal thing would be to have a semitransparent white for the Save button and remove the border entirely for the dials. We'll just have to see how easy that is to do programmatically.