Hi, I'm Amir.
I ported

to iOS.
It took 12,000 (awesome) Lines of

A Dark Room ranks as one of the top 100 RPGs in the App Store!

And I have made millions thousands hundreds...

Twitter: @amirrajan
Web: amirrajan.net
Source: github.com/amirrajan/12k-lines-of-RM
How'd the heck did I get here?
http://iwdrm.tumblr.com/
Saw this...
Got addicted and played for an hour ಠ_ಠ
Then saw this...
So like any normal person curious developer I viewed source:
<!--
A Dark Room (v1.2)
==================
A minimalist text adventure by Michael Townsend.
Inspired by Candy Box (http://candies.aniwey.net/)
Please don't steal me.
-->
    
The game had an unexplainable charm to it... so I asked Michael if I could port it:
Yay!!! Wait...
XCode and Cocoa go against the grain of everything I value in development workflows, and apis.
Bulky, complex, unintuitive.
First I had to mentally map Objective C to Ruby...

http://iwdrm.tumblr.com/
Newing up a class, setting some properties, calling a method in Objective C:

Person *person = [[Person alloc] init];
person.firstName = @"Amir";
person.lastName = @"Rajan";
NSLog([person sayHello]);
    
Newing up a class, setting some properties, calling a method in Ruby

person = Person.alloc.init
person.firstName = "Amir"
person.lastName = "Rajan"
NSLog(person.sayHello)
    
Person class definition in Objective C:

//Person.h
@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
- (NSString*)sayHello;
@end

//Person.m
#import "Person.h"
@implementation Person
- (NSString*)sayHello {
  return [NSString stringWithFormat:@"Hello, %@ %@.",
                                    _firstName,
                                    _lastName];
}
@end
    
Person class definition in Ruby:

class Person
  attr_accessor :firstName, :lastName

  def sayHello
    "Hello, #{@firstName} #{@lastName}"
  end
end
    
Declaring and calling an Objective C method with 3 parameters:

- (void)setDobWithMonth:(NSInteger)month
                withDay:(NSInteger)day
               withYear:(NSInteger)year {

}

[person setDobWithMonth:1 withDay:1 withYear:2013];
    
Declaring and calling a Ruby method with 3 parameters:

def setDobWithMonth(month,
  withDay: day,
  withYear: year)

end

person.setDobWithMonth(1, withDay: 1, withYear: 2013)
    
Declaring and executing blocks in Objective C:

- (void)
   post:(NSDictionary *)objectToPost
  toUrl:(NSString *)toUrl
success:(void (^)(RKMappingResult *mappingResult))success { }

[client post: @{ @"firstName": @"Amir", @"lastName": @"Rajan" }
       toUrl: @"http://localhost/people"
     success:^(RKMappingResult *result) {
       //callback code here
     }];
    
Declaring and executing blocks in Ruby:

def post(objectToPost, toUrl: toUrl, success: success)

end

client post({ firstName: "Amir", lastName: "Rajan" },
  toUrl: "http://localhost/people",
  success: lambda { |result|
    #callback code here
  })
    
Finally, let's translate this code from a StackOverflow answer:

_label.layer.backgroundColor = [UIColor whiteColor].CGColor;

[UIView animateWithDuration:2.0 animations:^{
  _label.layer.backgroundColor = [UIColor greenColor].CGColor;
} completion:NULL];
    
Bam:

@label.layer.backgroundColor = UIColor.whiteColor.CGColor

UIView.animateWithDuration(2.0, animations: lambda {
  @label.layer.backgroundColor = UIColor.greenColor.CGColor
}, completion: nil)
    
Achievement Unlocked
http://iwdrm.tumblr.com/
With Objective C to Ruby mentally mapped.
I started to take advantage of Ruby (and RubyMotion's dev workflow)...
Opening an existing class:

class UIView
  def animate duration = 0.5, &block
    UIView.beginAnimations(nil, context:nil)
    UIView.setAnimationDuration(duration)
    instance_eval &block
    UIView.commitAnimations
  end
end

label.setAlpha 0
label.animate 1 { label.setAlpha 1 }
    
Using Objective C Categories:

@interface UIView (UIViewExtensions)
- (void)animate:(double)duration block:(void (^)(void))block;
- (void)animate:(void (^)(void))block;
@end

@implementation UIView (UIViewExtensions)
- (void)animate:(void (^)(void))block {
  [self animate:0.5 block:block];
}

- (void)animate:(double)duration block:(void (^)(void))block {
  [UIView beginAnimations:NULL context:NULL];
  [UIView setAnimationDuration:duration];
  block();
  [UIView commitAnimations];
}
@end

[label setAlpha:0];
[label animate:1 block:^{ [label setAlpha:1]; }];
    
Class macros:

class SnarlingBeastEvent < EncounterEvent
  title "a snarling beast"
  text "a snarling beast leaps out of the underbrush." 
  damage 1

  def loot
    { 
      fur: { min: 3, max: 7, chance: 1.0 }
    }
  end
end
    
Class macros:

class EncounterEvent
  def self.title title
    define_method("title") { title }
  end

  def self.text text
    define_method("text") { text }
  end

  def self.health health
    define_method("health") { health }
  end
end
    
Class methods:

@interface SnarlingBeastEvent : EncounterEvent
+ (id)initNew;
- (NSDictionary *)loot;
@end

@implementation SnarlingBeastEvent
+ (id)initNew {
  return [super initWithAttributes: @{
    @"health": [[NSNumber alloc]initWithInt: 1],
    @"title": @"a snarling beast",
    @"text": @"a snarling beast leaps out of the underbrush."
  }];
}
  
- (NSDictionary *)loot {
  return @{
    @"fur": @{
      @"min": [[NSNumber alloc]initWithDouble:3.0],
      @"max": [[NSNumber alloc]initWithDouble:7.0],
      @"chance": [[NSNumber alloc]initWithDouble:1.7]
    }
  };
}
@end
Class methods:

@interface EncounterEvent : NSObject
@property NSString *title;
@property NSString *text;
@property NSInteger health;
+ (id)initWithAttributes:(NSDictionary *)attributes;
@end

@implementation EncounterEvent
+ (id)initWithAttributes:(NSDictionary *)attributes {
  EncounterEvent *e = [[EncounterEvent alloc] init];
  e.title = [attributes valueForKey:@"title"];
  e.text = [attributes valueForKey:@"text"];
  NSNumber *num = [attributes valueForKey:@"health"];
  e.health = [num integerValue];
  return e;
}
@end
      
Testing with MacBacon:

describe "population" do
  before do
  end

  it "kills population (except for one dude)" do
  end
end
      
Testing with Cedar:

SPEC_BEGIN(PopulationSpec)
describe(@"population", ^{
  before(^{
  });

  it(@"kills population (except for one dude)", ^{
  });
}
SPEC_END
      
Build this
with ...
Views using Cocoa Apis:

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
    @window.makeKeyAndVisible
    room_controller = RoomViewController.alloc.init
    navigation_controller =
      UINavigationController.alloc.initWithRootViewController(room_controller)
    @window.rootViewController = navigation_controller
    true
  end
end

class RoomViewController < UIViewController
  def viewDidLoad
    setTitle "a dark room"
    view.backgroundColor = UIColor.whiteColor
    @button = UIButton.buttonWithType(UIButtonTypeRoundedRect)
    @button.setTitle "light fire", forState: UIControlStateNormal 
    @button.frame = CGRectMake(0, 100, 320, 40)
    @button.addTarget(self, action: :button_clicked,
      forControlEvents: UIControlEventTouchUpInside)
    view.addSubview @button
  end

  def button_clicked
  end
end
    
Views using ProMotion and BubbleWrap :

class AppDelegate < PM::Delegate
  def on_load(app, options)
    open RoomScreen.new(nav_bar: true)
  end
end

class RoomScreen < PM::Screen
  title "a dark room"

  def on_load
    @button = UIButton.buttonWithType(UIButtonTypeRoundedRect)
    view.backgroundColor = UIColor.whiteColor
    @button.setTitle "light fire", forState: UIControlStateNormal 
    @button.frame = CGRectMake(0, 100, 320, 40)
    add @button
    @button.when(UIControlEventTouchUpInside) {  }
  end
end
    
Other nice things:


For what it's worth. With enough time and effort. You can get XCode, Objective C, and Cocoa to work as nice as Ruby and RubyMotion...
But you might feel like this afterwards:


http://iwdrm.tumblr.com/
I'd rather leverage RM's ecosystem, get my app done, and go get a shawarma:


http://technoir.nl/
Downloads:
Initial Release: Nov. 27th to Dec. 7th
GOTY 2013 Lists - Giant Bomb, Paste Magazine:
Dec. 27th
VoiceOver updates for the blind:
Jan. 4th
Tweet about ADR by users with 6k, 12k followers
Jan. 10th, 16th
Promocode give-aways
Jan. 19th, 26th, 30th
Price Drop to $0.99, leads to 600 downloads:
Feb 12th - 14th
Price Drop to free, leads to another 8,993 downloads:
Feb 15th
Price goes back to $0.99, downloads fall back to norms:
Feb 16th
Revenue:
Top Charts rank under the Games, Role Playing Category:
No happy ending.
No overnight millionaires.
Just steady improvements month over month.
Because of an unrelenting persistence to share this story.
The one that happens in the game and the one that exists outside.

Use a spacebar or arrow keys to navigate