Skip to content

Instantly share code, notes, and snippets.

@glongman
Created May 4, 2010 04:03
Show Gist options
  • Save glongman/388941 to your computer and use it in GitHub Desktop.
Save glongman/388941 to your computer and use it in GitHub Desktop.
HTML5 web socket bot for Unreal3 Bots. Includes HTML bot impl and ruby proxy server
<html>
<head>
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js'></script>
<script>
// Note: the Bot implementation is called MYBOT and is defined after UT3BOT.
/* UT3BOT is a Utility that handles initiating the connection to the Unreal server
* via a web socket.
* To begin, you call
* UT3Bot.start(server_host, name, callback)
* - server_name is the hostname or IP address of the Unreal Server
* - name is the name you want displayed for the bot on the Unreal Server
* - callback is a function that takes an argument. invoked when a message arrives.
* To send messages:
* UT3BOT.send_msg(command, arg1, arg2, .., argn)
* we take care of formatting and sending the message to the Unreal Server
*
* Limitations:
* - assumes an unreal.rb proxy is running on localhost, port 8080
* - on connection fail UT3BOT becomes a stone cold lump.
*/
if (!window.UT3BOT) (function() {
var ws = null;
var debug = function(str) {
$("#debug").append("<p>" + str + "</p>");
};
var ws_send = function(str) {
$("#msg").append("<p>[ OUT ] "+str+"</p>");
ws.send(str+"\n");
};
var onopen = function () {
debug("web socket connected...waiting for unreal to say hello.");
};
var onmessage = function(evt) {
$("#msg").append("<p>[ IN ] "+evt.data+"</p>");
if (UT3BOT.message_handler) UT3BOT.message_handler(eval(evt.data));
};
var onclose = function() {
debug("socket closed");
}
var connected = false;
var connector = function(evt, user_message_handler) {
msg = evt.data;
if (!connected) {
if (evt.data == 'unreal connection lost') {
UT3BOT.connected(null);
} else {
connected = true;
UT3BOT.connected(user_message_handler)
}
} else {
throw("Illegal State: announcement callback called after successful connection.")
}
};
var connect = function(host) {
ws = new WebSocket("ws://localhost:8080/"+host);
ws.onopen = onopen;
ws.onmessage = onmessage;
ws.onclose = onclose;
};
UT3BOT = {
bot_name: null,
message_handler: null,
send_msg: function() {
var args = Array.prototype.slice.call(arguments);
ws_send(args.join("|"));
},
start: function(host, bot_name, user_message_callback) {
if (ws) throw "start was already called.";
self.bot_name = bot_name;
self.message_handler = function(evt) {
connector(evt, user_message_callback);
};
connect(host)
},
connected: function(callback) {
if (!callback) {
debug("The connection to unreal failed. We're borked.")
self.message_handler = null;
} else {
debug("unreal server said hello.. starting.");
self.message_handler = callback
self.send_msg("INIT", self.bot_name,"IronGuard","0");
}
}
}
var self = UT3BOT;
})();
</script>
<script>
// JS version of testbot.py - not very smart.
// does nothing until it sees another bot or gets shot.
(function() {
MYBOT = {
start: function() {
UT3BOT.start("192.168.0.24", "Madman", self.onmessage );
},
send: UT3BOT.send_msg,
onmessage: function(msg) {
handler = self.map[msg[0]]
if (handler) handler(msg);
},
nav: function(msg) {
self.send('RUNTO','0.0','0.0',msg[2]);
},
seen: function(msg) {
self.send('FIRE', msg[1], 'False');
self.send('RUNTO', '0.0', '0.0', msg[6], msg[1]);
},
damaged: function(msg) {
self.send('FIRE', msg[1], 'False');
self.send('STRAFETO','0.0','0.0', msg[1], msg[2]);
}
}
var self = MYBOT;
self.map = {
'NAV': self.nav,
'PICKUP': self.nav,
'SEENPLAYER': self.seen,
'HEARDNOISE': self.damaged,
'BUMPED': self.damaged,
'DAMAGED': self.damaged,
}
})();
$(document).ready(function(){
MYBOT.start();
});
</script>
</head>
<body>
<div id="debug"></div>
<div id="msg"></div>
</body>
</html>
require 'rubygems'
require 'json' # gem install json
require 'em-websocket' # gem install em-websocket
require 'em-proxy/backend' # gem install em-proxy
DEBUG = true # make the proxy verbose, and probably a bit slower.
# An EM thang that proxies a web socket connection to a UT3Bot backend.
# Server Side
# ruby unreal.rb
#
# Expects that the ws:// url used to make the connection includes the hostname of the UT3Bot server in
# the path part.
# Example: ws://localhost:8080/192.168.3.155
#
# sends UT3Bot messages back through the web socket as a JSON array string.
#
# See the example html file for JS side
#
# Limitations/TODO
# The JS code as presented in the gist is a semi-functional, mentally challenged bot.
# - tested only on a Mac in Chrome 5.0.342.9
# - can't reconnect to the UT2Bot server if the backend connection is lost
#
# Kudos to @igrigorik (http://github.com/igrigorik) for the rockin em-websocket and
# em-proxy gems. And for a blog that is overflowing with awesome (http://www.igvita.com).
class UnrealBackend
def initialize(ws, options = {})
@ws = ws
@debug = options[:debug] || true
@defer = EM::DefaultDeferrable.new
@leftover = ''
end
def start
if self.host.size == 0
@ws.send("error: no unreal host found in the websocket url path. This connection is toast.")
@defer.callback {@ws.close_connection_after_writing}
else
debug :onopen, "connecting to Unreal"
@server = EventMachine.connect(self.host, 4530, EventMachine::ProxyServer::Backend, false) do |c|
c.name = :unreal
c.plexer = self
end
end
end
def stop
@server.close_connection if @server
end
def send(data)
@server.send data
end
def host
return @host if @host
@host = @ws.request['Path']
@host.slice! 0
debug :host, "Host = #{@host.inspect}"
@host
end
def relay_from_backend(name, data)
raw = @leftover + data
@leftover = ''
lines = raw.split("\n")
lines.each do |msg|
cleaned = clean_message(msg)
@leftover = cleaned and return unless cleaned.is_a? Array
debug :ws_sending, cleaned
@ws.send cleaned.to_json
end
end
# called when backend unreal connection is established
def connected(*args)
# do nothing
end
# called when backend unreal connection closes
def unbind_backend(*args)
@ws.send("unreal connection lost".to_json)
@defer.callback {ws.close_connection_after_writing}
end
def clean_message(data)
msg = data.strip
if msg =~/(.+)EOM$/
$1.split('|')
else
# not a complete message, return it
data
end
end
def debug(*data)
if @debug
require 'pp'
pp data
puts
end
end
end
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080, :debug => DEBUG) do |ws|
@backend = UnrealBackend.new(ws, :debug => DEBUG)
ws.onopen { @backend.start }
ws.onmessage { |msg| @backend.send msg }
ws.onclose { @backend.stop }
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment