Hacking the MMORPP for fun and profit(?)
This 'ironic' Mark Beasely thought it'd be funny to make a game where no one does anything, there are no objectives, no interactive elements to play with, just cursors. Everyone has a cursor, and you have a cursor, and you see everyones' cursor updated in real-time. He called it "MMORPP" which supposedly stands for 'massively multiplayer online role playing peripheral'. At first I thought he was going for a 'multiboxing' joke, but now I have no idea what he means.
It seems the game was written using NodeJS's extraordinarily useful Open Sockets concept. NodeJS can have clients query its server about 20 times per second for updates to the scene, and it makes multiple users on the same scene pretty easy.Somehow this site usually has 5+ players at any given time (or there was a bolus of activity when I was linked to it). Usually, these players like to fling their cursors around in circles, shake them back and forth, or try to draw things with one another. Some find crafty tricks to duplicate cursors (or they're just opening multiple browsers, each sending 20 requests/second lol). Sometimes they try to draw shapes. I saw a group come real close to drawing a diamond once.
The Protocol
Open up your trusty Javascript Debugging plugin (Firebug for Firefox in this case, not too into Chrome yet) and watch the requests fly. You'll notice two types of requests: one that leads to /get and one that leads to /get-and-update.
/get/?client=node_1300962752
the URL parameter here is client=node_1300962752, this is the string "node_" with the current Unixtime appended to it. Your ID number for your cursor on the page is this client variable. This usually works out well, but it is possible for two people to join in the same second and fight for mouse pointer control.If you follow this link, you'll see output similar to this:
node_1300962752:796|2,node_1300905762:787|6,node_1300961848:788|591
it's a list of other node_SECONDS people who had joined, and their x and y positions on the board. The protocol goes like this
name:x|y
separated by commas.
/get-and-update/?client=node_1300962752&pos_x=801&pos_y=276
The /get-and-update action takes the same client=node_SECONDS variable as /get, and you can specify the x and y positions of your own cursor with the pos_x and pos_y variables. You can click both of these links in your web browser right now to see them work. The first is read-only, but if you open up the MMORPP and run the second you will see a new cursor named node_1300962752 open up at point (800,276)
The Scripts
After running a simple bash script to draw a horizontal line from the upper left to the lower right:
for i in 0 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200; dowget -U firefox "http://duskjacket.com/MMORPP/get-and-update/?client=node_1300962752$RANDOM&pos_x=$i&pos_y=$i" &done
I wr0te a script to display graphics in cursor-pixels. You will need to install Ruby, Rubygems, Imagemagick, RMagick, and wget to use these scripts.
sudo apt-get install ruby rubygems imagemagicksudo gem install rmagick
This is the ruby script I used for turning a black and white monochrome image into a list of x,y coordinates that are black:
filename: process_out.gif.rbrequire 'rubygems'require 'RMagick'originx = rand(824)originy = rand(568)image = Magick::ImageList.new("out.gif")(0..image.columns).collect do |x|(0..image.rows).collect do |y|"pos_x=#{originx+x*10+(2-rand(5))}&pos_y=#{originy+y*10+(2-rand(5))}" if image.pixel_color(x,y).red == 0endend.flatten.select{|t| not t.nil? }.shuffle.each do |poses|puts posesend
And here's the script I used for generating those out.gif files from text input:
filename: run_em.sh#!/bin/bashecho -n "Talk to the MMORPP> "while read line; dotext2gif -t "$line" -q | convert gif:- -negate out.gifruby process_out.gif.rb | while read plot; dowget "http://duskjacket.com/MMORPP/get-and-update/?client=node_`date +%s`$RANDOM&$plot" -U firefox -qO - >/dev/null &sleep 0.01doneecho -n "Talk to the MMORPP> "done
The $RANDOM littered around the wget commands is to reduce collisions. The text2gif command is provided by ImageMagick, but the text is inverted (background is black, text is white), so I invert (negate) it with convert (also provided by ImageMagick). The & at the end of the wget line is what makes it multithreaded - & forks the process into the background, so it starts between 10 and 200 instances of wget to draw your text. The text can only be about 2 words long. 4 letter words fit best. I'd suggest 'derp', 'OBEY', and 'I love you!'.I'm on a Mac, so I found an MSPaint-like application called SeaShore that lets me edit monochrome bitmaps and export as GIF. I save the file in the same directory as my "process_out.gif.rb" and "run_em.sh" files under the file name "out.gif", and hit enter in my running "run_em.sh" script, and it will process my graphic art.
Update #1 -- 2011-03-24 8:20p
I've been kickin around this ruby script today, giving more interaction to other users with fun little iterative math functions :3. It's fun to watch them react to suddenly having an interface.
require 'net/http'scene = {}actors = {}#my_id = "node_1301003699"my_id = ARGV[0]actor_1 = "node_#{Time.now.to_i.to_s}"10.times do |n|actors["node_#{Time.now.to_i.to_s}#{n}"] = [0,0]endh = Net::HTTP.new("duskjacket.com", 80)while true do#100.times doresp, data = h.get('/MMORPP/get/?client=node_8798357924', nil )node_name = ""data.split(/[,:|]/).each_with_index { |v, i|case i % 3when 0node_name = vscene[node_name] = [] if ! scene.has_key?(node_name)when 1scene[node_name][0] = v.to_iwhen 2scene[node_name][1] = v.to_iend}# puts scene.inspect# puts "#{scene[my_id].inspect}\t#{scene[actor_1].inspect}\n"last = my_idtotal_actors = actors.countactors.keys.each_with_index do |actor, index|th = Thread.new() {this_h = Net::HTTP.new("duskjacket.com", 80)# radians = index.to_f/total_actors * Math::PI * 2# actors[actor] = [ scene[last][0] + 10, scene[last][1] + 10*index ]# actors[actor] = [ scene[last][0]+4, scene[last][1]+4 ]# actors[actor] = [ scene[last][0] + 40*Math.cos(radians), scene[last][1] + 40*Math.sin(radians) ]# actors[actor] = [ scene[last][0] - 10*index, scene[last][1] + 3 ]# actors[actor] = [ scene[last][0] - 10*index, scene[last][1] + 3 ]actors[actor] = [ (scene[last][0] + actors[actor][0])/2, (scene[last][1] + actors[actor][1])/2]resp, data = this_h.get("/MMORPP/get-and-update/?client=#{actor}&pos_x=#{actors[actor][0]}&pos_y=#{actors[actor][1]}", nil )this_h.close}last = actorendend