Tuesday, 8 January 2013

Hundreds - Next Riddle (31)

The next riddle wasn't too difficult. I simply went through all the letters one by one and filtered them into columns. Turns out 4 columns is the magic number. Reading down the columns revealed the answer.


Monday, 7 January 2013

Hundreds


Hundreds is a neat new game for iOS where you touch floating circles in an effort to bring their total up to 100. throughout the gameplay, random messages will appear on the screen. These messages are riddles and cyphers which you're expected to solve for the prize of achievements in Game Center. The first one is the only one that's immediately readable: "A mouse has one snout but a hand has five". After touching the screen with 5 fingers, the first riddle was solved. Easy enough. The rest of them, however, aren't even readable.

The second riddle mixed up the letters in each word, so each word had to be unscrambled. There were a few 9 letter words which took a while, but it was eventually solved. "Persistence without patience is a golden harp missing its chord". So these are probably going to require a bit of patience from here on out.

The third riddle (the furthest I've gotten so far) looked like a cypher. Letters looked grouped together alright, but the proper letters were not contained in each word. Being the programmer that I am, I set off  writing a script to help me.

I started off with two arrays of letters; one for the keys and one for the values. As I made a guess at a letter key pair, I would remove them from their respective lists, and then randomize the rest of the pairings every second, printing the potential sentence to the screen.


As you can see, this wasn't going anywhere very fast. But, subbing x for a, o for r, and l for o got me the first four words. Slowly over time, I kept making letter substitutions that seemed logical. The script started to become quite useful in seeing the currently deciphered letters, and possibilities for other letters. Eventually, I reached the end:


No idea what it means, but it works. Only when I went to input the letters into the game did I realize that the cypher was simply the current letter plus 3. So the script was kind of overkill. But it worked!

Below is the next riddle. I will make another post when I solve that one, but it might take me a bit longer...


Here's the ruby script I wrote quickly. Feel free to use it to make or break cyphers.


file = File.new("cypher.txt", "r")
cypher = file.gets
file.close

$key = {}

def randomizeKey()
  keyLetters = ["d","g","h","n","s","t""v","w","y"]
  letters = ["b","j","g","k","q","v","w","y","z"]
 
  $key["x"] = "a"
  $key["k"] = "n"
  $key["q"] = "t"
  $key["e"] = "h"
  $key["b"] = "e"
  $key["o"] = "r"
  $key["l"] = "o"
  $key["f"] = "i"
  $key["c"] = "f"
  $key["r"] = "u"
  $key["z"] = "c"
  $key["m"] = "p"
  $key["p"] = "s"
  $key["i"] = "l"
  $key["j"] = "m"
  $key["u"] = "x"
  $key["a"] = "d"
  $key[""] = ""
 
  while(letters.length > 0)
    keyIndex = rand(keyLetters.length)
    valueIndex = rand(letters.length)
    keyLetter = keyLetters[keyIndex]
    valueLetter = letters[valueIndex]

    $key[keyLetter] = valueLetter

    keyLetters.delete_at(keyIndex)
    letters.delete_at(valueIndex)
  end

end

randomizeKey()

while
  string = ""
  cypher.each_byte { |i|
    if(i.chr == " ")
      string = string + " "
    else
      string = string + $key[i.chr]
    end
  }
  puts string
  randomizeKey()
  sleep(1)
end

Monday, 31 December 2012

1000 Apps Downloaded!

MDF Projects has officially had its 1000th download. The 1000th app was Velocimeter, my speedometer app. It's been a crazy first year of iOS development, and I'm excited to embark on the second one. Also, Happy New Year!

Saturday, 29 December 2012

GKHelper: getting GKPlayer data

I'm going to start going over useful methods from my code in order to help people with similar problems, and give myself a form of self assessment. The first method I'm going to go over is the method I use to get GKPlayer objects from player IDs. This code is from my forthcoming turn based Game Center game (name TBD).

For all my Game Center interactions, I have created a singleton manager, named GKHelper. It handles creating the async calls to Game Center, and processes the information when it comes back. It also has several forms of caching to reduce web requests.

GKHelper has a dictionary which it uses to store GKPlayers by playerID. Whenever a GKPlayer object is fetched, it is first stored in this dictionary before being returned. Whenever a GKPlayer is requested via playerID, the dictionary is checked before the request is made. This logic is handled via the following method:


- (GKPlayer*)playerForPlayerID:(NSString*)playerID {
   if(playerID == nil || [playerID isEqualToString:@""]) {
      return nil;
   }
   if(self.playersDict == nil) {
      self.playersDict = [NSMutableDictionary dictionary];
   }
   
   if([self.playersDict objectForKey:playerID]) {
      return [self.playersDict objectForKey:playerID];
   } else {
      [GKPlayer loadPlayersForIdentifiers:[NSArray arrayWithObject:playerID] withCompletionHandler:^(NSArray *players, NSError *error) {
         
         if (error != nil) {
            NSLog(@"Error retrieving player info: %@", error.localizedDescription);
         } else {
            // Populate players dict
            for (GKPlayer *player in players) {
               [self.playersDict setObject:player forKey:player.playerID];
            }
            [self.delegate performSelectorOnMainThread:@selector(playersLoaded) withObject:nil waitUntilDone:NO];
         }
      }];
   }
   return nil;
}


First, the method checks the playerID to make sure it is valid. Then, it checks the dictionary for the specified player. On the case that it cannot find the player, it begins the async web request for the player data, which it then stores upon return. The delegate is then informed on the main thread (since it may return on a background thread) that the players list has been updated (the delegate usually reloads the data in the tableview). After the web request is sent, the function returns nil indicating the player could not be found. The function will get called a second time upon receiving the player info, and the correct information will be retrieved. This function is costly, however, since the async request takes an array of playerIDs, and we're only passing in a single ID.

That's where the next two functions come in.


- (void)getPlayersForMatch:(GKTurnBasedMatch*)match {
   
   NSMutableArray *identifiers = [NSMutableArray array];
   
   for(GKTurnBasedParticipant *participant in match.participants) {
      if(participant.playerID == nil) {
         continue;
      }
      [identifiers addObject:participant.playerID];
   }
   
   [GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
      
      if (error != nil) {
         NSLog(@"Error retrieving player info: %@", error.localizedDescription);
      } else {
         // Populate players dict
         for (GKPlayer *player in players) {
            [self.playersDict setObject:player forKey:player.playerID];
         }
         [self.delegate playersLoaded];
      }
   }];
}

- (void)getPlayersForMatches:(NSArray*)matches {
   NSMutableArray *identifiers = [NSMutableArray array];
   
   for(GKTurnBasedMatch *match in matches) {
      for(GKTurnBasedParticipant *participant in match.participants) {
         if(participant.playerID == nil) {
            continue;
         }
         [identifiers addObject:participant.playerID];
      }
   }
   
   [GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
      
      if (error != nil) {
         NSLog(@"Error retrieving player info: %@", error.localizedDescription);
      } else {
         // Populate players dict
         for (GKPlayer *player in players) {
            [self.playersDict setObject:player forKey:player.playerID];
         }
         [self.delegate playersLoaded];
      }
   }];
}


These two functions take a GKTurnBasedMatch, and simply fill up the player dictionary with the GKPlayer objects. The first method I call when a new match is created, and the second method is called when the app is first opened, right after the current matches are loaded. They insure that the loadPlayers method is only called once at app loading, and once per new match. There are a few problems with this setup although.

For one, the latter two methods have no way of communicating with the former method regarding making async web requests. In other words, getPlayersForMatches could be called, immediately followed by playerForPlayerID. This would start two async web requests for the same information, with potentially bad results. There's also the case where the async web request fails to get a certain GKPlayer for a playerID, but still calls the delegate's playersLoaded method. Thus, an infinite loop will be formed.

If I ever get around to optimizing these methods, I'll post the new ones below. Also, make sure to comment if you have any questions. Also, sorry about the code formatting, haven't figured out how to make it format properly yet...

Tuesday, 11 December 2012

Lego Trains are awesome

Found my old Lego train, so I set it up around the tree. Here are some pictures.




AirBoard Overview

AirBoard is a handy app I made to keep score during spontaneous tournaments that sometimes occur. The app is compatible with an Apple Tv, so when you mirror your iOS device's screen on the ATV, it shows the overall scoreboard.

This allows you to edit the list on the fly, and the secondary screen will update once you're finished. I'm planning to add a boatload of features to this app, such as saving a scoreboard, creating a tournament bracket from a scoreboard, and possibly inter-device communication. The UI assets do need an upgrade though...

Another cool feature is the ability to name the scoreboard by typing directly on the navigation bar.

Screenshots below:



Tuesday, 20 November 2012

It's been a while

It's been a long time since I last posted. Since then I've added another app, called Air Board.

Air Board is a handy scoreboard app that takes advantage of linking your iOS device to a secondary screen. When connected to an Apple TV, Air Board will show the top players sorted by score. Updates made on the device are reflected on screen with animations. I'll post some pictures soon.

I'm also working hard on another Game Center based app which will be my best one yet. More info to come.