Since different language solutions are growing more common, I'll submit
my Ruby solution.
I've been learning Ruby these past few weeks, so it's still probably
rough around the edges. That would be my fault, not Ruby's, which is a
fun language that I think is worth a look.
My solution only implements the first milestone, because that was a lot
of work and I'm tired now. <laughs>
I built it with Ruby 1.8.2 preview 2, but I BELIEVE it will run on
1.6.x (standard in the mentioned Mac OS X testing environment). All
you would have to do is:
% ruby unix_term_tetris.rb
WARNING: As the name suggests, the interface I built only runs on
Unix. It would be trivial to convert it to Windows, but since I'm not
a Windows guy it's someone else's triviality. <laughs>
My solution uses two files, which should be placed in the same
directory.
Enjoy.
James Edward Gray II
=== unix_term_tetris.rb ===
#!/usr/bin/ruby -w
require "io/wait"
require "tetris"
system "stty raw -echo"
game = Tetris::Game.new
system "clear"
puts game.draw
game.start
loop do
if game.run
system "clear"
puts game.draw
end
break if game.over?
if STDIN.ready?
case STDIN.getc
when ?q, ?Q, ?\C-c
break
when ?z, ?Z
game.move_left
when ?x, ?X
game.move_right
when ?/, ??
game.move_drop
when ?', ?"
game.move_rotate
end
system "clear"
puts game.draw
end
end
END { system "stty -raw echo" }
__END__
=== tetris.rb ===
#!/usr/bin/ruby
module Tetris
class Block
@@blocks = [ [ ["#", "#"],
["#", "#"] ],
[ [" ", "@", "@"],
["@", "@", " "] ],
[ ["%", "%", " "],
[" ", "%", "%"] ],
[ ["*", "*", "*"],
["*", " ", " "] ],
[ ["&", "&", "&"],
[" ", " ", "&"] ],
[ ["$", "$", "$"],
[" ", "$", " "] ],
[ ["+", "+", "+", "+"] ] ]
attr_reader :x, :y
def initialize(x = nil, y = nil, type = rand(7))
@x = if x.nil?
if type == 6
3
else
4
end
else
x
end
@y = if x.nil?
if type == 6
0
else
-1
end
else
y
end
@dir = 1
@type = type
@block = @@blocks[@type]
end
def drop
@y += 1
end
def move_left
@x -= 1
end
def move_right
@x += 1
end
def rotate
copy = [ ]
@block[0].size.times { copy.push((" " * @block.size).split
"") }
@block.each_with_index do |row, y|
row.each_with_index do |square, x|
copy[x][copy[0].size - 1 - y] = square
end
end
@block = copy
@dir = if @dir == 4 then 1 else @dir + 1 end
case @type
when 6
case @dir
when 1, 3
@x -= 2
@y += 2
when 2, 4
@x += 2
@y -= 2
end
when 1, 2
case @dir
when 1, 3
@x -= 1
@y += 1
when 2, 4
@x += 1
@y -= 1
end
when 3, 4, 5
case @dir
when 1
@x -= 1
@y += 1
when 2
@y -= 1
when 4
@x += 1
end
end
end
def to_a
return Marshal.load(Marshal.dump(@block))
end
def to_s
str = ""
@block.each do |row|
str += row.join() + "\r\n"
end
return str
end
end
class Board
attr_reader :line_count, :touch_count
def initialize(block = Block.new, next_block = Block.new)
@board = [ ]
1.upto 20 do
row = (" " * 10).split ""
@board << row
end
@block = block
@next_block = next_block
@line_count = 0
@touch_count = 0
end
def block=(block)
@board = to_a
@line_count = 0
0.upto @board.size - 1 do |i|
unless @board[i].include? " "
@board.delete_at i
@board.unshift((" " * 10).split "")
@line_count += 1
end
end
@touch_count = 0
count_block = @block.to_a
count_block.each_with_index do |row, i|
next if @block.y + i < 0
row.each_with_index do |square, j|
if row[j] != " "
if j == 0 and ( @block.x - 1 ==
-1 or
@board[@block.y + i][@block.x - 1] !=
" " )
@touch_count += 1
end
if j == row.size - 1 and (
@block.x + 1 + j >= 10 or
@board[@block.y + i][@block.x + 1 + j]
!= " " )
@touch_count += 1
end
if i == 0 and ( @block.y - 1 ==
-1 or
@board[@block.y - 1][@block.x + j] !=
" " )
@touch_count += 1
end
if i == count_block.size - 1 and
( @block.y + 1 + i >= 20 or
@board[@block.y + 1 + i][@block.x + j]
!= " " )
@touch_count += 1
end
end
end
end
@block = @next_block
@next_block = block
end
def can_drop?
block = @block.to_a
block.each_with_index do |row, i|
next if @block.y + 1 + i < 0
return false if @block.y + 1 + i > 19
row.each_with_index do |square, j|
if row[j] != " " and
@board[@block.y + 1 + i][@block.x + j] !=
" "
return false
end
end
end
return true
end
def can_left?
block = @block.to_a
block.each_with_index do |row, i|
next if @block.y + i < 0
row.each_with_index do |square, j|
return false if @block.x - 1 + j < 0
if row[j] != " " and
@board[@block.y + i][@block.x - 1 + j] !=
" "
return false
end
end
end
return true
end
def can_right?
block = @block.to_a
block.each_with_index do |row, i|
next if @block.y + i < 0
row.each_with_index do |square, j|
return false if @block.x + 1 + j >= 10
if row[j] != " " and
@board[@block.y + i][@block.x + 1 + j] !=
" "
return false
end
end
end
return true
end
def can_rotate?
begin
@block.rotate
block = @block.to_a
block.each_with_index do |row, i|
next if @block.y + i < 0
return false if @block.y + i > 19
row.each_with_index do |square, j|
return false if @block.x + j < 0
return false if @block.x + j >=
10
if row[j] != " " and
@board[@block.y + i][@block.x + j] !=
" "
return false
end
end
end
return true
ensure
3.times { @block.rotate }
end
end
def drop
if can_drop?
@block.drop
return true
else
return false
end
end
def full?
if @board[0].join("") !~ /^ {10}$/
return true
else
return false
end
end
def move_left
if can_left?
@block.move_left
return true
else
return false
end
end
def move_right
if can_right?
@block.move_right
return true
else
return false
end
end
def rotate
if can_rotate?
@block.rotate
return true
else
return false
end
end
def to_a
copy = Marshal.load(Marshal.dump(@board))
block = @block.to_a
block.each_with_index do |row, i|
next if @block.y + i < 0
row.each_with_index do |square, j|
next if row[j] == " "
copy[@block.y + i][@block.x + j] =
square
end
end
return copy
end
def to_s(level, lines, score)
str = ""
to_a.each do |row|
str += "|" + row.join() + "|\r\n"
end
str += ("-" * 12) + "\r\n"
details = [ "Level #{level}", "Lines #{lines}", "Score
#{score}",
"", "Next", "" ]
@next_block.to_a.each do |row|
details.push(row.join(""))
end
details.push("")
display = str.split(/\r\n/).zip(details)
display.collect! do |e|
if e[1].nil?
e[0] + "\r\n"
else
e[0] + " " + e[1] + "\r\n"
end
end
return display.join("");
end
end
class Game
attr_reader :board
def initialize
@board = Board.new
@piece_floating = false
@level = 1
@lines = 0
@score = 0
end
def draw
return @board.to_s(@level, @lines, @score)
end
def move_drop
@board.drop
end
def move_left
@board.move_left
end
def move_right
@board.move_right
end
def move_rotate
@board.rotate
end
def over?
return @board.full?
end
def run
if (@last_drop + (1 / 1.3 ** (@level - 1))) - Time.now
<= 0
unless @board.drop
if @piece_floating
@board.block = Block.new
@lines += @board.line_count
@score += @board.touch_count
@score += @board.line_count *
10 * @level
@level += 1 if @lines > 0 and
@lines % 10 == 0
@piece_floating = false
else
@piece_floating = true
end
end
@last_drop = Time.now
return true
end
return false
end
def start
@last_drop = Time.now
end
end
end
__END__
|