Fractalbot
From Telecomix Crypto Munitions Bureau
Fractalbot is written in ruby, as an experiment in ruby.
What does it do? It is hardcoded to connect to telecomix IRC and join the channel #test2, where users can ask it to draw ASCII fractals. Typing "!fractal help" will show the command syntax.
[edit] Conclusions
- ruby is nice
- rubys lack of types was INSANELY exhausting, had to debug all the time :O :O :O
[edit] Code
#!/usr/bin/env ruby1.9.1
require 'socket'
class Fraktal
def initialize(width=120, height=60, top_left=Complex(-1.5, 1.0), bottom_right=Complex(1.0,-1.0), c=nil)
span_real = bottom_right.real - top_left.real
span_im = (top_left.imag - bottom_right.imag ).abs
@width = width
@height = height
@top_left = top_left
@bottom_right = bottom_right
@c = c # Complex(0,-1)
@matrix = []
@x_step = Complex(span_real.to_f / width, 0)
@y_step = Complex(0, span_im.to_f / height)
@max_it = 1000
puts("pixels height: #{height}")
puts("pixels width: #{width}")
puts("x-stepping: #{@x_step}")
puts("y-stepping: #{@y_step}")
puts("top left corner: #{@top_left}")
puts("bottom right corner: #{@bottom_right}")
puts("real span: #{span_real}")
puts("imaginary span: #{span_im}")
compute
end
## print the fractal
def get_ascii
picture = []
for y in (0..@height)
picture[y] = ""
for x in (0..@width)
picture[y] += color( @matrix[y][x] )
end
end
return picture
end
private
def compute
z = @top_left
for y in (0..@height)
@matrix[y] = Array.new(@width)
for x in (0..@width)
@matrix[y][x] = iterate(z,@c)
z += @x_step
end
z = @top_left - y * @y_step
end
end
def iterate(z, c)
c ? c : c = Complex(z)
(0..@max_it).each { |n|
z = z*z + c
if z.abs > 4
return n
end
}
return @max_it
end
## calculate the color of the pseudopixel
def color(n)
n > 9 ? " " : " " + (0+n).to_s
end
end
##################################################################################################
class Kaosbot
attr_accessor :session
def initialize(server="irc.telecomix.org", port=6667, nickname="kaosbot", verbosity=0)
@nickname = nickname
@server_name = server
@server_port = port
@verbosity = verbosity
@threads = Array.new
( verbosity > 0 ) ? puts("verbosity: #{verbosity}") :
@verbosity = verbosity
end
def lprint(string, level=3)
if level <= @verbosity
puts(string)
end
end
def connect()
new_thread = Thread.new() {
lprint("connecting to #{@server_name}", 0)
until @session
@server_ip = IPSocket.getaddress(@server_name)
lprint("Connecting to #{@server_ip}:#{@server_port}", 1)
begin
@session = TCPSocket.open(@server_ip, @server_port)
rescue => error
lprint("failed to connect, trying again..", 0)
lprint("TCP Socket error --> " + error.to_s, 1)
end
end
lprint("connected to #{@server_name} --> #{@server_ip}:#{@server_port}", 0)
@session.write("NICK #{@nickname}\r\n")
@session.write("USER #{@nickname} 8 d :#{@nickname}\r\n")
@connected = true
main_loop
}
@threads.push(new_thread)
end
def disconnect()
if @connected
@connected = false
@session.close()
lprint("disconnected.", 0)
else
puts("disconnect called without being connected!")
end
end
def reconnect()
if @connected
lprint("reconnecting", 0)
disconnect()
connect()
else
puts("reconnect called without being connected!")
end
end
def server_name?
@server_name
end
def server_port?
@server_port
end
def join(channel="#test2")
@connected ? lprint("waiting for bot to connect to server..", 1) :
until @connected
Thread.pass
end
@session.write("JOIN #{channel}\n")
lprint("tried to join #{channel}", 0)
end
def main_loop
while true
string = @session.gets
line = string.chomp
lprint(line, 3)
puts "test0"
primitive = /^(?<command>[A-Z]+){1}/.match(line)
system = /^:(?<sender>[[:graph:]]+)[\s]+(?<cmd>[a-zA-Z0-9]{1,})\s+(?<target>[[:graph:]]+)(\s:)*(?<rest>(.*))$/.match(line)
puts "test1"
if primitive
case primitive[:command]
when "PING"
lprint("received PING, sending PONG", 3)
@session.write("PONG\n")
when "ERROR"
lprint("WARNING: Received this error-message from the server: #{line}", 0)
else
lprint("NOTICE: Received unknown message : #{line}", 2)
end
elsif system
puts "test2"
words = system[:rest].split(/[\s]+/)
if words == nil
next
end
puts "test3"
user_msg = system[:rest].split
if "PRIVMSG".match( system[:cmd] ) && user_msg
if "!fractal".match( user_msg[0] )
puts "test4"
parsefractal(system[:sender], system[:target], words, words.length)
end
end
end
end
end
def parsefractal(from, target, umsg, len)
puts "from: #{from}, target: #{target}, umsg: #{umsg}, len: #{len}"
if len < 2
Thread.new { print_fractal(target) }
elsif /^\s*help\s*$/.match(umsg[1])
@session.write("PRIVMSG #{target} :!fractal [h=int] [w=int] [c=a+bi]\n")
else
c, c_re, c_im = nil, nil, nil
height, width = 50, 79
matched = false
umsg.each do |msg|
puts "trying to match " + msg
complex_regexp.match(msg) { |complex|
c = Complex(complex[:re], complex[:im])
puts "this c is what we got:" + c.to_s
matched = true
}
/^w=(?<w>[1-9]{1}[0-9]*){1}$/.match(msg) { |m|
width = m[:w].to_i
matched = true
}
/^h=(?<h>[1-9]{1}[0-9]*){1}$/.match(msg) { |m|
height = m[:h].to_i
matched = true
}
end
if matched
puts "printing fractal"
Thread.new { print_fractal(target, width, height, c) }
end
end
end
def complex_regexp
pat_begin = "^"
pat_end = "$"
prefix = "c="
re = "(?<re>[0-9]*(([.,]{1})[0-9]{1,12}){0,1}){1}"
im = "(?<im>[+-]{1}[0-9]*(([.,]{1})[0-9]{1,12}){0,1}i{1}){1}"
return Regexp.new( pat_begin + prefix + re + im + pat_end )
end
def print_fractal(target, width=79, height=50, type=nil)
puts "creating fractal.."
fraktal = Fraktal.new(width, height, Complex(-1.8, 1.3), Complex(0.8,-1.3), type)
puts "tries to get picture.."
picture = fraktal.get_ascii
puts "got ascii.."
picture.each { |row|
puts( "PRIVMSG #{target} :#{row}\n")
@session.write("PRIVMSG #{target} :#{row}\n")
}
end
end
irc = Kaosbot.new("irc.telecomix.org", 6667, "kaosbot", 3)
irc.connect
irc.join
while true
Thread.pass
end
[edit] How to run it
put the above code in a file, chmod+x it and type "./filename"

