Tuesday, July 25, 2006

Ruby : Tetris

If you've heard what Ruby on Rails has done for Web Development you may be interested in Shattered. It's like Ruby on Rails for games development. It's only at version 0.3.2 at the moment, early days but it looks promising already. I'm investigating it with a view of using it as a rapid prototyping tool for more avant-garde game designs.

Everything you need to know can be found on the Shattered Weblog which has some very useful links in the top right hand corner.

I've written a simple tetris game using it to limber up :) I'm currently in the process of writing a little tutorial on the Shattered Wiki to help people out. I found it quite hard to get going at first due to the lack of examples and documentation. Saying that though, if you're prepared to look through the Shattered sources and reference documentation it's not too hard to find out what's going on and it shows some very nice likke Ruby tricks as well.

Check it out

Wednesday, July 05, 2006

Ruby : Pretty Code

I've been through many beginners tutorials for Ruby and I must say I really like this language. I think it's going to be very useful. The Pragmatic Programmers - Dave Thomas and Andy Hunt - give some good advice to learning a new language ... they say you must ask the question, "How will I know when I'm done?". I decided that my first "bout" of learning would be concluded when I had written my own program to prettify the text files I write for this blog! Usually I have to search and replace certain characters, manually add html tags for line breaks and code snippets, ensure that any preformatted line doesn't exceed 62 characters ... in short, a bit of a chore.



So here's the Ruby blog prettifier ... and of course, this blog post is the first to have been run through it! :)




class String

def subAfter str, ext
self[0..self.index(str)-1]+"."+ext
end

end

$column = 62
filename = ARGV[0].to_s

puts "Processing "+filename+"...\n"

File.open(filename, "r") do |file|

outFile = File.new(filename.subAfter(".","htm"), "w")

code = false;

file.each_line do |line|

if line.chomp == "@@@"

if code
outFile.puts "</pre></tt></blockquote>"
else
outFile.puts "<blockquote><pre><tt>"
end

code = !code;

else

if code

long = line
line = ""

while long.length > $column

space = long[0..$column].rindex(" ")

if space == nil
space = $column
end

line += long[0..space]+"\n"
long = long[space+1..-1]

end

line += long


line.gsub!(/</,"<")
line.gsub!(/>/,">")
else
line.gsub!(/\n\r|\n|\r/, "<br>")
end

outFile.puts line

end

end

outFile.close()

# This is a really long comment which I've added to show you
how the code prettifier neatly looks for the last space before
col 80 and splits the line :)
#_This_is_another_really_long_comment_which_I've_added_to_show_
you_how_the_code_prettifier_simply_cuts_the_line_if_there_are_n
o_spaces!

end

Tuesday, July 04, 2006

Ruby : to_eng Integer class extension

Here's the "English Number" method implemented in a more useful form; as an extension to the Integer class. It now works with negative numbers to. The usage is simple.




puts 0.to_eng(0) # arg is 0 for British, 1 for American
puts 12.to_eng(1)
puts -54.to_eng(1)
puts (10**600).to_eng(0)






# extend the Integer class with a "to english" method

class Integer

def to_eng scale

number = self.abs

if number == 0
return 'zero'
end

# ... code removed see previous post

if write > 0

exps = write.to_eng(scale)

# ...

end
# ...

if self<0
numString = "Minus " + numString
end

# Now we just return "numString"...
numString

end

end

Ruby : English Numbers

Well, it's been almost a month since my last post and what a busy month it's been! I started to write an article about a wrapper I had written for C++ iostreams that automatically closes them on scope exit until I found out that they do that anyway!! Talk about redundant levels of abstraction!

I've also found myself over-using templates recently, I mean they're fun to use but they tend to obfusticate code and generate hard-to-read compiler errors. I had this realisation after discovering Ruby - one of the easiest, most concise and powerful "scripting" languages I've ever had the pleasure to use. Check it out www.rubycentral.com. I've been following a great article called Learn To Program (Ruby), a precursor to the book by the same name.

On Chapter 8 there's a nifty English Number Program example that takes any positive integer and returns the english name of that number. It's quite limited and only works properly between 0-999. The author sets an exersize to make it work for thousands and millions and asks, how high can you go? My answer? Is that British Scale or American Scale? Either? Ok, well, in either case I can go as high as 10^600, which is the largest number-word below googolplex which is too large to include anyway :)


Here's my first Ruby program (based on the original example), enjoy ... for an example of conciseness see the code I wrote to sort the array of name/exponent pairs by exponent ...



def englishNumber number, scale
if number < 0 # No negative numbers.
return 'Please enter a number that isn\'t negative.'
end
if number == 0
return 'zero'
end

# check scale:

if scale<0 or scale>1
return 'scale out of range'
end

# valid scales:
# 0) British (long scale)
# 1) American (short scale)

numString = '' # This is the string we will return.

onesPlace = ['one', 'two', 'three', 'four',
'five', 'six', 'seven', 'eight', 'nine']
tensPlace = ['ten', 'twenty', 'thirty', 'forty', 'fifty',
'sixty', 'seventy', 'eighty', 'ninety']
teenagers = ['eleven', 'twelve', 'thirteen', 'fourteen',
'fifteen', 'sixteen', 'seventeen', 'eighteen',
'nineteen']

# short scale words

expTens = [['b'],['tr'],['quadr'],['quint'],['sext'],
['sept'],['oct'],['non'],['dec'],['undec'],
['duodec'],['tredec'],['quattuordec'],['quindec'],
['sexdec'],['septendec'],
['octodec'],['novemdec'],['vigint']]

# add short scale exponents and append "illion"'s!

exp = 9

expTens.each do |expTen|

expTen[0] = expTen[0] + 'illion';
expTen.push(exp)
exp = exp + 3

end

if scale == 0 # British (long scale)
# not using uncommon "milliard" (10**9)

# convert exponents to long scale

expTens.each do |expTen|

expTen[1] = (expTen[1] - 3) * 2

end

end

# add words and exponents common to both scales

expTens = [ ['hundred', 2], ['thousand', 3], ['million', 6] ]
+ expTens
expTens = expTens + [ ['googol', 100], ['centillion', 600] ]

# rational.rb says googolplex i.e. 10**(10**100) => Infinity

# unfortunatly now after the possible conversion to British
# long scale the expTens array is not in order. A googol's
# exponent is 100 which means it should be between
# sexdecillion and septendecillion.

# let's simply sort the array every time in case other such
# ordering errors occur

expTens.sort! { |x, y| x[1]<=>y[1] } # how easy was that! :)

left = number

# handle hundreds and above

expTens.reverse.each do |expTen|

value = 10**expTen[1]
write = left/value
left = left - write*value

if write > 0

exps = englishNumber(write, scale)
numString = numString + exps + ' ' + expTen[0]

if left > 0

if left < 100
numString = numString + ' and '
elsif
numString = numString + ', '
end

end

end

end

# handle teens

write = left/10 # How many tens left to write out?
left = left - write*10 # Subtract off those tens.

if write > 0
if ((write == 1) and (left > 0))
# Since we can't write "tenty-two" instead of "twelve",
# we have to make a special exception for these.
numString = numString + teenagers[left-1]
# The "-1" is because teenagers[3] is 'fourteen',
# not 'thirteen'.

# Since we took care of the digit in the ones place
# already, we have nothing left to write.
left = 0
else
numString = numString + tensPlace[write-1]
# The "-1" is because tensPlace[3] is 'forty',
# not 'thirty'.
end

if left > 0
# So we don't write 'sixtyfour'...
numString = numString + '-'
end
end

# handle ones

write = left # How many ones left to write out?
left = 0 # Subtract off those ones.

if write > 0
numString = numString + onesPlace[write-1]
# The "-1" is because onesPlace[3] is 'four', not 'three'.
end

# Now we just return "numString"...
numString
end

def formatEnglishNumber value, scale
value.to_s+" = "+englishNumber( value, scale)
end

def formatEnglishNumberExp exp, scale
"10**"+exp.to_s+" = "+englishNumber(10**exp, scale)
end

scale = -1;
while scale<0 or scale>1
puts "Use 0) British Long Scale 1) American Short Scale?"
scale = gets.chomp.to_i
end

puts "Some Numbers:"
puts
puts formatEnglishNumber( 0, scale)
puts formatEnglishNumber( 9, scale)
puts formatEnglishNumber( 10, scale)
puts formatEnglishNumber( 11, scale)
puts formatEnglishNumber( 17, scale)
puts formatEnglishNumber( 32, scale)
puts formatEnglishNumber( 88, scale)
puts formatEnglishNumber( 99, scale)
puts formatEnglishNumber(100, scale)
puts formatEnglishNumber(101, scale)
puts formatEnglishNumber(234, scale)
puts formatEnglishNumber(1000, scale)
puts formatEnglishNumber(3211, scale)
puts formatEnglishNumber(10000, scale)
puts formatEnglishNumber(100000, scale)
puts formatEnglishNumber(999999, scale)

puts
puts "Let's test the scale: "
puts

scalemax = 64;
if scale == 0
scalemax = 121
end

scalemax.times do |exp|
puts formatEnglishNumberExp(exp, scale)
end

puts
puts "Some bigger numbers!"
puts
puts formatEnglishNumber(100098765432134, scale)
puts formatEnglishNumber(2348723948732948723, scale)
puts formatEnglishNumber(2342342324100098765432134, scale)
puts formatEnglishNumber(124523598054213453230980329480, scale)
puts formatEnglishNumberExp(100, scale)
puts formatEnglishNumberExp(600, scale)
# googolplex is too big!!
#puts formatEnglishNumberExp(10**100, scale)

puts
puts "Press Enter To Quit"
gets