Doesn't it seem odd how people are willing to spend so much time and effort on doing everything that's best for their in-game characters, and yet they never do anything for their real lives?
And it's not just highly complex MMORPGs like World of Warcraft and Eve Online - look how many people spend how much effort on FarmVille - which is about logging it at scheduled times and clicking harvest/plow/plant seeds on a large number of virtual squares! Many of them are the same people who avoid putting any effort into their real life characters as much as possible.
There's been some interesting discussion about this real world - gaming world divide, started by this TED talk by Jane McGonigal - like vast majority of TED talks it's totally wrong, and totally worth watching for entertainment value and some intellectual stimulation:
By the way - to people who are new to this blog - links here are like on Wikipedia - they usually lead somewhere interesting.
Anyway. Assuming you want to achieve some real life outcomes but your laziness and disorganization stops you - like most people - why couldn't you simply approach life just as if it was a game? Well for one thing games give you a lot of immediate feedback that you're doing well, but real life doesn't.
So why not fix the problem and create such immediate feedback? This "feedback" is usually little more than just some numbers, progress bars, and make some badges or other icons. Let's do it!
Experience points log
Well, first thing you need is log actions which give your real life character experience points.A log can be a simple semi-structured text file like this.
= RULES = # Every time +10 write a blog post +2 read a book +1 go to gym +1 play with cat # Just once +100 setup a backup system +500 organize free elections in Belarus = 2010-03-27 Sat = +10 blog post +1 played with the cat = 2010-03-28 Sun = +1 played with the cat +2 read a bookExample is completely made-up but you can think of some points for yourself. And of course you can go back and change these rules and numbers any time you want.
Recording actions
Unfortunately there are no iPhone apps for that yet, so we'll have to record our actions manually - but let's make it as easy as possible. The log is located at /home/taw/all/xp/xp.txt and I have the bound one of the keys on the most awesome keyboard in the world to open this log in case I want to edit it.And because I almost always have terminal open, I wrote a script which appends current date header if it changed, and if you passed it any arguments it appends them to the log, otherwise it opens it. Actually now that I think about it, it wouldn't be a bad idea to get the keyboard shortcut to do this date change too... The script also edits itself if I pass it --edit
#!/usr/bin/env ruby1.9 exec "mate", __FILE__ if ARGV[0] == '--edit' require "time" require "date" fn = "/home/taw/all/xp/xp.txt" last_day = Date.parse(File.read(fn).scan(/^= (\d{4}-\d{2}-\d{2} \S{3}) =$/)[-1][0]) rescue nil out = [] unless last_day == Date.today out << Date.today.strftime("\n= %Y-%m-%d %a =\n") end out << ARGV.join(" ") unless ARGV.empty? unless out.empty? open(fn, "a"){|fh| fh.puts out.join} end system "mate", fn if ARGV.empty?
It's all highly portable so far as long as you adjust paths etc. - nothing OSX-specific about it.
Drawing progress bar
It's fairly simple to think of a regexp to parse experience points out of the log - the only nontrivial bit is skipping rules section. But we'd much rather have pretty icon than numbers.
Well, first let's parse XP and convert it to levels, in a typical quadratic system (code assumes Symbol#to_proc; upgrade your Ruby to 1.8.7 or paste it from core_ext if it dies).
module XP class << self def read_xp log = File.read("/home/taw/all/xp/xp.txt").sub(/\A.*?= \d{4}-\d{2}-\d{2} \S{3} =\n/m, "") log.scan(/^\+\d+/).map(&:to_i).inject(&:+) end def threshold(level) 10 * (level ** 2) end def stats(xp=(ARGV[0] || read_xp).to_i) level = 0 level += 1 while xp >= threshold(level) [xp, level, xp-threshold(level-1), threshold(level)-threshold(level-1)] end end end
XP.stats returns an array of [experience ponits, level, XP since acquiring current level, XP needed from current level to next] so code drawing pictures doesn't have to think about it.
Now the drawing part. We want 32x16 icon - height is constrained by OSX menu bar height, this ratio just looks good. On the bottom we have progress bar, on the top 1-4 colored circles showing your level - levels 1-4 are green, 5-8 are cyan etc. Here are some examples:
And the code:
require "rubygems" require 'RMagick' canvas = Magick::Image.new(32, 16){ self.background_color = 'transparent' } Magick::Draw.new.instance_eval { stroke('black') fill('none') rectangle(2, 9, 30, 14) xp, level, xp_got, xp_needed = XP.stats stroke('none') fill('#0F0') rectangle(3, 10, 3 + (29-3)*xp_got/xp_needed, 13) level_colors = %w[#0FF #FF0 #F0F #00F] while level > 4 and !level_colors.empty? fill(level_colors.shift) level -= 4 end level.times{|i| x = 4+8*i circle(x, 4, x, 1) } draw(canvas) } canvas.write(ARGV[0] ? "xp-#{ARGV[0]}.png" : "/home/taw/all/xp/xp.png")
In both parts of the code weird ARGV[0] code is just there for making sample images instead of reading the actual log. Not exactly a masterpiece of modularity, but that's real world Unix scripting for you ;-)
Putting it in OSX menu bar
All this work is fairly useless we put the icons generated somewhere no display - like on OSX menu bar. So far everything has been reasonable system-independent - this part is highly OS-specific - unfortunately all cross-platform GUI toolkits take lowest common denominator approach, and look like shit on all systems equally. But it's really simple code. It's really easy to make the menu icon interactive but then I don't have any particular need for it to be interactive yet.It must use OSX-shipped ruby interpretter (/usr/bin/ruby), not one from MacPorts unless you want to deal with some serious library hell.
#!/usr/bin/ruby require "osx/cocoa" include OSX class Timer def init tick end def tick system "./draw_xp.rb" $statusitem.setImage(NSImage.alloc().initWithContentsOfFile("/home/taw/all/xp/xp.png")) end end app = NSApplication.sharedApplication $statusitem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength) NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats(1.0, Timer.new, 'tick:', nil, true) app.run