[SPOILER] Solution Expert Quiz 23 in Ruby/Tk
Frank Fischer <frank.fischer-JJ2xi2hz/[email protected]>
| Newsgroups | gmane.comp.lang.perl.qotw.discuss |
|---|---|
| Message-ID | <[email protected]> |
Hi, I implemented tetris in ruby with Ruby/Tk. Since im not an tk-expert, there is still much space for improvement of the interface (it's still very simply and slow). My version has two parts, a server and client. I didn't implement the 2-player variant of the 2nd milestone but another one: Two players play against each other. If one player deletes two or more lines, the opponent gets some extra lines. The player who crashes first loses the game. To start a game, first start the server with: ruby tserver.rb [-p port] and one or two clients with ruby tclient.rb -n nick [-a server-address] [-p port] The interface is very incomplete but does the job for this quiz, I think. I put the files as attachment to this mail because the are a little large, hope this is ok. Gruß Frank
tcore.rb
(text/plain, 6.6 KB)
BRICKS =
####
[ 8, 9,10,11, 2, 6,10,14, 8, 9,10,11, 2, 6,10,14,
##
##
9,10, 5, 6, 9,10, 5, 6, 9,10, 5, 6, 9,10, 5, 6,
##
##
5, 6,10,11, 14,10,11, 7, 5, 6,10,11, 14,10,11, 7,
##
##
9,10, 6, 7, 15,10,11, 6, 9,10, 6, 7, 15,10,11, 6,
###
#
9,10,11, 5, 13,14,10, 6, 9,10,11,15, 14,10, 6, 7,
###
#
9,10,11, 7, 14,10, 6, 5, 9,10,11,13, 15,14,10, 6,
###
#
9,10,11, 6, 9,14,10, 6, 9,10,11,14, 14,10, 6,11]
class Brick
attr_reader :color, :x, :y
def initialize(type, x = 0, y = 0)
@x = x
@y = y
@rot = 0
@offset = type * 16
@color = type+1
end
def down
if yield(blocks(nil, 0, -1))
@y -= 1
return true
else
return false
end
end
def left
if yield(blocks(nil, -1, 0))
@x -= 1
return true
else
return false
end
end
def right
if yield(blocks(nil, 1, 0))
@x += 1
return true
else
return false
end
end
def rotate_left
new_rot = @rot == 0 ? 3 : @rot - 1
if yield(blocks(new_rot)) then
@rot = new_rot
return true
else
return false
end
end
def rotate_right
new_rot = @rot == 3 ? 0 : @rot + 1
if yield(blocks(new_rot)) then
@rot = new_rot
return true
else
return false
end
end
def blocks(rot = nil, dx = 0, dy = 0)
BRICKS[@offset + (rot || @rot)*4, 4].collect do
|b|
y, x = b.divmod(4)
[x + @x + dx, y + @y + dy]
end
end
def blocks0
BRICKS[@offset + @rot*4, 4].collect do |b|
y, x = b.divmod(4)
[x, y]
end
end
end
class Game
attr_reader :board, :next_brick, :current_brick, :score, :last_full_lines
attr_reader :last_difference
GAME_OVER = 0
REMOVED_LINES = 1
NEXT_STONE = 2
OK = 3
def initialize(width = 10, height = 20, level = 1)
@width = width
@height = height
@level = level
@score = 0
@lines = 0
@board = Array.new(@height)
@last_board = Array.new(@height)
for i in 0...@height do
@board[i] = Array.new(@width, 0)
@last_board[i] = Array.new(@width, 0)
end
@last_difference = []
get_next_brick # the first one
get_next_brick # and the next one
@penalty_lines = 0
end
def next_level(incr = 1)
@level += incr
end
def gravity
1.0 / 1.3 ** (@level - 1)
end
def get_next_brick
@current_brick = @next_brick
@next_brick = Brick.new(rand(BRICKS.length/16),
@width/2-2, @height-3)
if @current_brick
# simply set the new blocks as difference
color = @current_brick.color
@current_brick.blocks.each do |x,y|
@last_difference << [x,y,color] if y < @height
end
end
end
def update_difference(last_blocks)
# first update the current board, i.e. do the last change
@last_difference.each { |x,y,c| @last_board[y][x] = c }
# new calculate the difference of the moved block
new_blocks = @current_brick.blocks
color = @current_brick.color
@last_difference = []
(new_blocks - last_blocks).each do |x,y|
@last_difference << [x,y,color] if y < @height
end
(last_blocks - new_blocks).each do |x,y|
@last_difference << [x,y,0] if y < @height
end
end
def check(brick)
brick.each { |x,y|
if not (0...@width).member?(x) or y < 0 or
(y < @height and @board[y][x] > 0 )
then
return false
end
}
return true
end
def check_full_lines
lines = @lines
full = []
@board.each_index { |i| full << i unless @board[i].member?(0) }
full.reverse_each { |i|
@score += @level * @width
@lines += 1
@board.delete_at(i)
@board << Array.new(@width, 0)
}
next_level if @lines/10 - lines/10 > 0
if full.empty?
return false
else
@last_full_lines = full.reverse
return true
end
end
def down_brick
last_blocks = @current_brick.blocks
if @current_brick.down { |b| check(b) }
update_difference(last_blocks)
return OK
else
score = 0
@current_brick.blocks.each { |x,y|
score +=1 if x == 0 or @board[y][x-1] > 0
score +=1 if x == @width-1 or @board[y][x+1] > 0
score +=1 if y == 0 or @board[y-1][x] > 0
}
@score += score
@current_brick.blocks.each { |x,y|
@board[y][x] = @current_brick.color
}
# now update differences
@last_difference.each { |x,y,c| @last_board[y][x] = c }
@last_difference = []
get_next_brick
return GAME_OVER unless check(@current_brick.blocks)
# check for full lines to clear
has_removed_lines = check_full_lines
# now check, if we have to insert some penalty lines from other players
has_inserted_random_lines = insert_random_lines
# if something changed (removed lines or penalty-lines), recalculate
# the difference
if has_inserted_random_lines or has_removed_lines then
for y in 0...@height
brow = @board[y]
lrow = @last_board[y]
for x in 0...@width
if brow[x] != lrow[x]
@last_difference << [x,y,brow[x]]
end
end
end
return REMOVED_LINES if has_removed_lines
end
return NEXT_STONE
end
end
def left_brick
last_blocks = @current_brick.blocks
if @current_brick.left { |b| check(b) }
update_difference(last_blocks)
return true
else
return false
end
end
def right_brick
last_blocks = @current_brick.blocks
if @current_brick.right { |b| check(b) }
update_difference(last_blocks)
return true
else
return false
end
end
def rotate_left_brick
last_blocks = @current_brick.blocks
if @current_brick.rotate_left { |b| check(b) }
update_difference(last_blocks)
return true
else
return false
end
end
def rotate_right_brick
last_blocks = @current_brick.blocks
if @current_brick.rotate_right { |b| check(b) }
update_difference(last_blocks)
return true
else
return false
end
end
def quick_down_brick
last_blocks = @current_brick.blocks
while @current_brick.down { |b| check(b) } do end
update_difference(last_blocks)
end
def insert_penalty_lines(n)
@penalty_lines += n
end
def insert_random_lines
return false if @penalty_lines == 0
random_lines = []
1.upto(@penalty_lines) do
line = []
while line.length < @width / 2
line << rand(@width)
line.uniq!
end
row = Array.new(@width, 0)
line.each { |col| row[col] = rand(BRICKS.length / 16) + 1 }
random_lines << row
end
@board = random_lines + @board[0...-@penalty_lines]
@penalty_lines = 0
return true
end
end
tclient.rb
(text/plain, 10.1 KB)
require 'tk'
require 'socket'
require 'thread'
require 'timeout'
require 'getopts'
$width = 10
$height = 20
$port = 47898
$host = 'localhost'
COLORS = {' ' => nil,
'+' => '#FF0000',
'#' => '#0000FF',
'@' => '#00FF00',
'%' => '#FFFF00',
'*' => '#FF00FF',
'&' => '#00FFFF',
'$' => '#A52A2A' }
class Player
attr_accessor :name, :board
def initialize(name)
@name = name
end
end
class Block
def initialize(parent, x1, y1, x2, y2, color = nil)
if color.nil? or color == COLORS[0]
@color_light = @color_inner = @color_dark = 'grey'
else
@color_light = darken(color, 100)
@color_inner = darken(color, 75)
@color_dark = darken(color, 50)
end
x1, x2 = [x1,x2].min, [x1,x2].max
y1, y2 = [y1,y2].min, [y1,y2].max
w = (0.1 * (x1-x2).abs).ceil
h = (0.1 * (y1-y2).abs).ceil
@inner = TkcRectangle.new(parent, x1+w, y1+h, x2-w, y2-h) {
fill @color_inner
outline @color_inner
}
@upright = TkcPolygon.new(parent,
x1,y1, x1+w,y1+h, x2-w,y1+h, x2-w,y2-h,
x2,y2, x2,y1) {
fill @color_light
outline @color_light
}
@downleft = TkcPolygon.new(parent,
x1,y1, x1+w,y1+h, x1+w,y2-h, x2-w,y2-h,
x2,y2, x1,y2) {
fill @color_dark
outline @color_dark
}
end
def color=(color)
if color.nil? or color == COLORS[0]
@color_light = @color_inner = @color_dark = 'grey'
else
@color_light = darken(color, 100)
@color_inner = darken(color, 75)
@color_dark = darken(color, 50)
end
@inner.fill @color_inner
@inner.outline @color_inner
@upright.fill @color_light
@upright.outline @color_light
@downleft.fill @color_dark
@downleft.outline @color_dark
end
def darken(color, percent)
if /^#([[:xdigit:]]{2})([[:xdigit:]]{2})([[:xdigit:]]{2})/ =~ color
return ("\#%02X%02X%02X" % [ $1, $2, $3 ].collect { |x|
x.hex * percent / 100 })
else
return color
end
end
end
class Tetris < TkFrame
def initialize(width, height, nick, *args, &block)
super(*args, &block)
@width = width
@height = height
@block_width = 30
@block_height = 30
@root = self
init_board
init_preview
@field_items.each { |row|
row.each { |item|
item.color = nil
}
}
@nick_label = TkLabel.new(@root) {
text nick
pack('anchor' => 'n')
}
@score_label = TkLabel.new(@root) {
text '0'
pack('anchor' => 's')
}
end
def init_board
w = @block_width * @width
h = @block_height * @height
@board_canvas = TkCanvas.new(@root) {
width w
height h
pack('padx' => 10, 'pady' => 10, 'side' => 'left' )
}
@board_canvas.pack
@field_items = Array.new(@height)
w = @board_canvas.width
h = @board_canvas.height
dx = w / @width
dy = h / @height
for y in 0...@height do
row_items = Array.new(@width, nil)
for x in 0...@width do
row_items[x] = Block.new(@board_canvas,
x*dx, h-y*dy, (x+1)*dx, h-(y+1)*dy, nil)
end
@field_items[y] = row_items
end
end
def init_preview
@preview_canvas = TkCanvas.new(@root) {
width 60
height 60
background 'grey'
pack('padx' => 10, 'pady' => 10,
'anchor' => 'n' )
}
@preview_canvas.pack
@preview_items = Array.new(4)
w = @preview_canvas.width
h = @preview_canvas.height
dx = w / 6
dy = h / 6
for y in 0...4 do
row_items = Array.new(4)
for x in 0...4 do
row_items[x] = Block.new(@preview_canvas, (x+1)*dx, h-(y+1)*dy,
(x+2)*dx, h-(y+2)*dy,
nil)
end
@preview_items[y] = row_items
end
end
def paint(diffs)
begin
diffs.each do |x,y,c|
@field_items[y][x].color = c
end
rescue Exception => e
puts e
puts e.backtrace
end
end
def paint_preview(diffs)
@preview_items.each do |row|
row.each do |item|
item.color = nil
end
end
diffs.each do |x,y,c|
@preview_items[y][x].color = c
end
end
def score=(score)
@score_label.text score.to_s
end
end
class Console < TkFrame
def initialize(*args, &block)
super(*args, &block)
@text = TkText.new(self) {
state 'disabled'
wrap 'word'
width 50
height 10
pack 'side' => 'left', 'expand' => 1, 'fill' => 'x'
}
@scrollbar = TkScrollbar.new(self) {
width 10
pack 'side' => 'right', 'fill' => 'y'
}
@scrollbar.command proc { |*args| @text.yview *args }
@text.yscrollcommand(proc { |first,last| @scrollbar.set(first, last) })
end
def add_message(msg)
@text.state 'normal'
@text.insert 'end', "#{msg}\n"
@text.state 'disabled'
end
def join_player(name)
add_message "#{name} joins game"
end
def leave_player(name)
add_message "#{name} leaves game"
end
def join_spectator(name)
add_message "#{name} joins as spectator"
end
def leave_spectator(name)
add_message "Spectator #{name} leaves game"
end
def start_game
add_message "Game begins"
end
def end_game
add_message "Game ends"
end
def cancel_game
add_message "Game aborted"
end
def win(name)
add_message "#{name} wins"
end
def lose(name)
add_message "#{name} loses"
end
end
class TetrisApp
def initialize(socket, name, be_player = true)
@socket = socket
@player_id = nil
@name = name
@players = {}
@be_player = be_player
@condition_start = ConditionVariable.new
@root = TkRoot.new
@start = TkButton.new(@root) {
text "Start";
pack 'side' => 'bottom', 'anchor' => 's'
}
@console = Console.new(@root) {
width 50
height 30
pack 'side' => 'bottom', 'fill' => 'x', 'anchor' => 'n'
}
@start.command(proc { do_start })
@root.bind("KeyPress-Left", proc { do_left })
@root.bind("KeyPress-Right", proc { do_right })
@root.bind("KeyPress-Up", proc { do_rotate })
@root.bind("KeyPress-Down", proc { do_down })
@update_mutex = Mutex.new
Thread.new { run }
end
def run
begin
@socket.write "HELLO\r\n"
line = nil
timeout(5) do
line = @socket.gets
return if line.nil?
end
line.chop!
return unless line =~ /^GAME ([A-Z])(\d+) (\d+) (\d+)/
$width = $1[0] - ?A + 1
$height = $2.to_i
n_players = $3.to_i
@max_players = $4.to_i
if n_players < @max_players and @be_player
@socket.write "PLAYER #{@name}\r\n"
else
@socket.write "SPECTATOR #{@name}\r\n"
end
while (line = @socket.gets)
line.chop!
@update_mutex.synchronize do
case line
when /^PLAYER (\d+)/
@player_id = $1.to_i
when /^DIFF (\d+) (.*)$/
handle_diff($1.to_i, $2)
when /^PREVIEW (\d+) (.*)$/
handle_preview($1.to_i, $2)
when /^SCORE (\d+) (\d+)/
handle_score($1.to_i, $2.to_i)
when /^INFO (.+)$/
handle_info($1)
when /^START/
handle_start_game
when /^CANCEL/
@console.cancel_game
when /^END/
@console.end_game
when /^RUNNING/
handle_start_game
when /^LOSE (\d+)/
handle_loose($1.to_i)
when /^WIN (\d+)/
handle_win($1.to_i)
end
end
end
rescue Exception => e
puts e
puts e.backtrace
ensure
@socket.close
end
end
def handle_diff(player, diff)
diffs = []
while diff =~ /^([A-Z])(\d+)([ \#@%*&+$])(.*)$/ do
diffs << [$1[0] - ?A, $2.to_i, COLORS[$3]]
diff = $4
end
if player == @player_id then
Tk.after(10, proc {
@tetris.paint(diffs)
})
else
Tk.after(10, proc {
@players[player].board.paint(diffs)
})
end
end
def handle_preview(player, diff)
diffs = []
while diff =~ /^([A-Z])(\d+)([ \#@%*&+$])(.*)$/ do
diffs << [$1[0] - ?A, $2.to_i, COLORS[$3]]
diff = $4
end
if player == @player_id then
Tk.after(10, proc {
@tetris.paint_preview(diffs)
})
else
Tk.after(10, proc {
@players[player].board.paint_preview(diffs)
})
end
end
def handle_score(player, score)
if player == @player_id
Tk.after(10, proc {
@tetris.score = score
})
else
Tk.after(10, proc {
@players[player].board.score = score
})
end
end
def handle_info(info)
case info
when /^([+-])PLAYER (\d+) (.*)$/
id = $2.to_i
if $1 == '+'
@players[id] = Player.new($3)
@console.join_player($3)
else
@console.leave_player(@players[id].name)
@players.delete(id)
end
when /^([+-])SPECTATOR (.*)$/
if $1 == '+'
@console.join_spectator($2)
else
@console.leave_spectator($2)
end
end
end
def handle_start_game
puts $width, $height
Tk.after 10, proc {
if @be_player and (@tetris.nil? or @tetris.width != $width or
@tetrix.height != $height)
@tetris.destroy if @tetris
@tetris = Tetris.new($width, $height, @name, @root) {
pack 'side' => 'left'
}
end
@players.each do |id, player|
next if id == @player_id
if player.board.nil? or player.board.width != $width or
player.board.height != $height
then
player.board.destroy if player.board
player.board = Tetris.new($width, $height, player.name, @root) {
pack 'side' => 'right'
}
puts "Create\n"
end
end
@console.start_game
@condition_start.signal
}
@condition_start.wait(@update_mutex)
end
def handle_win(id)
@console.win(@players[id].name)
end
def handle_loose(id)
@console.lose(@players[id].name)
end
def do_left
@socket.write "LEFT\r\n"
end
def do_right
@socket.write "RIGHT\r\n"
end
def do_rotate
@socket.write "ROTATE\r\n"
end
def do_down
@socket.write "DOWN\r\n"
end
def do_start
@socket.write "START\r\n"
end
end
getopts('', 'p:', 's', 'n:', 'h')
if $OPT_h
puts "Usage: tclient [-p port] [-a host] [-n nick] [-sh]"
exit 1
end
$port = $OPT_p || $port
$host = $OPT_a || $host
be_player = !$OPT_s
nick = $OPT_n
socket = TCPSocket.new($host, $port)
app = TetrisApp.new(socket, nick, be_player)
Tk.mainloop
tserver.rb
(text/plain, 9.1 KB)
require 'socket'
require 'thread'
require 'timeout'
require 'getopts'
require 'tcore'
$width = 10
$height = 20
$port = 47898
$max_players = 2
COLORS = [ ' ', '#', '@', '%', '*', '&', '+', '$' ]
class Player
attr_reader :id, :name, :socket, :game
attr_accessor :game_thread
def initialize(id, name, socket)
@id = id
@name = name
@socket = socket
@game = Game.new($width, $height)
@mutex = Mutex.new
end
def start(server)
@server = server
@game_thread = Thread.new { run_game }
end
def run_game
begin
delay = game.gravity
send_difference
send_preview
@wait_again = false
@stop_thread = false
loop do
sleep(delay)
return if @stop_thread
if @wait_again
sleep(delay)
@wait_again = false
end
@mutex.synchronize do
case @game.down_brick
when Game::GAME_OVER
@server.game_over(@id)
return
when Game::REMOVED_LINES
send_difference
send_preview
@server.removed_lines(@id, @game.last_full_lines.length)
delay = @game.gravity
when Game::NEXT_STONE
send_difference
send_preview
when Game::OK
send_difference
end
@server.send_broadcast "SCORE #{@id} #{@game.score}"
end
end
rescue Exception => e
puts e
puts e.backtrace
@server.game_over(@id)
ensure
@game_thread = nil
end
end
def wait_cancel
if @game_thread
thread = @game_thread
@stop_thread = true
thread.run
thread.join
end
end
def game_running?
return @game_thread != nil
end
def send_difference
diff = "DIFF #{@id} "
@game.last_difference.each do |x,y,c|
diff += (?A + x).chr + y.to_s + COLORS[c]
end
@server.send_broadcast diff
end
def send_preview
preview = "PREVIEW #{@id} "
if @game.next_brick
c = COLORS[@game.next_brick.color]
@game.next_brick.blocks0.each do |x,y|
preview += (?A + x).chr + y.to_s + c
end
end
@server.send_broadcast preview
end
def insert_random_lines(n)
@mutex.synchronize { @game.insert_penalty_lines(n) }
end
def do_left
@mutex.synchronize { send_difference if game_running? and @game.left_brick }
end
def do_right
@mutex.synchronize { send_difference if game_running? and
@game.right_brick }
end
def do_rotate
@mutex.synchronize { send_difference if game_running? and
@game.rotate_left_brick }
end
def do_down
@mutex.synchronize do
if game_running? and !@wait_again
game.quick_down_brick
send_difference
@wait_again = true
@game_thread.run
end
end
end
def complete_diff
diff = ""
board = @game.board
@mutex.synchronize do
for y in 0...board.length
row = board[y]
for x in 0...board[y].length
diff += (?A + x).to_s + y.to_s + COLORS[row[x]]
end
end
end
return diff
end
end
class Spectator
attr_reader :name, :socket
def initialize(name, socket)
@name = name
@socket = socket
end
end
class Server
NOID = -1
def initialize
@players = Array.new(2, nil)
@spectators = []
@client_mutex = Mutex.new
end
def run
@running = false
server = TCPServer.new($port)
while (client = server.accept)
Thread.new do
begin
run_client(client)
rescue Exception => e
puts e
puts e.backtrace
end
end
end
end
def run_client(socket)
begin
player = nil
spectator = nil
# wait for hello
line = nil
timeout(5) do
line = socket.gets
return unless line
end
line.chop!
return unless line =~ /^HELLO\b/
# send OK
@client_mutex.synchronize do
n_players = 0
@players.each { |p| n_players += 1 unless p.nil? }
socket.write "GAME #{(?A + $width-1).chr}#{$height} #{n_players}"+
" #{$max_players}\r\n"
end
# inform about all connected players
@client_mutex.synchronize do
@players.each_index do |id|
unless @players[id].nil?
socket.write "INFO +PLAYER #{id+1} #{@players[id].name}\r\n"
end
end
@spectators.each do |spectator|
socket.write "INFO +SPECTATOR #{spectator.name}\r\n"
end
if @running
socket.write "RUNNING\r\n"
@players.each do |player|
socket.write "DIFF #{player.id} #{player.complete_diff}\r\n";
end
end
end
while (line = socket.gets)
line.chop!
case line
when /^PLAYER ?(.*)/
if player = add_player(socket, $1)
spectator = nil
end
when /^SPECTATOR ?(.*)/
if spectator = add_spectator(socket, $1)
player = nil
end
when /^START\b/
start_game
when /^LEFT\b/
player.do_left if player
when /^RIGHT\b/
player.do_right if player
when /^ROTATE\b/
player.do_rotate if player
when /^DOWN\b/
player.do_down if player
else
puts "Ignore illegal client-command: #{line}"
end
end
rescue TimeoutError
puts "Client connection timed out"
rescue Exception => e
puts e, e.backtrace
ensure
socket.close
@client_mutex.synchronize do
if player
if @running then
@players.each { |player| player.wait_cancel }
send_broadcast "CANCEL"
@running = false
end
end
for i in 0...-mMg6cxhC0QCm/[email protected] do
if @players[i] and @players[i].socket == socket
@players[i] = nil
break
end
end
for i in [email protected] do
if @spectators[i].socket == socket
@spectators.delete_at(i)
break
end
end
end
end
end
def add_player(socket, name)
if @running
socket.write "ERR Game running\r\n"
return
end
player = nil
id = 0
@client_mutex.synchronize do
# first, check if player is already connected
@players.each_index do |i|
player = @players[i]
if player and player.socket == socket
return player
end
end
# now, look for a free slot
id = @players.index(nil)
if id.nil?
# no free slot
socket.write "ERR No free slot\r\n"
return nil
end
# found a free slot, so use it ...
# ...but first check if player is already a spectator
for spectator_id in [email protected] do
if @spectators[spectator_id].socket == socket
send_broadcast "INFO -SPECTATOR #{@spectators[spectator_id].name}",
false
@spectators.delete_at(spectator_id)
break
end
end
name ||= "Player #{id+1}"
player = Player.new(id+1, name, socket)
@players[id] = player
# inform the client for success
socket.write "PLAYER #{id+1} #{name}\r\n"
# and all the others for the new player
send_broadcast "INFO +PLAYER #{id+1} #{name}", false
return player
end
end
def add_spectator(socket, name)
spectator = nil
@client_mutex.synchronize do
# already a spectator?
@spectators.each do |s|
return s if s.socket == socket
end
player_id = nil
@players.each_index do |id|
player = @players[id]
if player and player.socket == socket
# if this is a player and game is running he can't leave game
if @running
socket.write "ERR Game running\r\n"
return nil
else
send_broadcast "INFO -PLAYER #{id+1} #{player.name}", false
@players[id] = nil
end
end
end
# everything OK, so add as new spectator
name ||= "Anonymous \##{socket.__id__}"
spectator = Spectator.new(name, socket)
@spectators << spectator
# inform the client for success
socket.write "SPECTATOR #{name}\r\n"
# and all the others for the new spectator
send_broadcast "INFO +SPECTATOR #{name}", false
return spectator
end
end
def send_broadcast(msg, block = true)
@client_mutex.lock if block
@players.each do |player|
unless player.nil?
player.socket.write msg
player.socket.write "\r\n"
end
end
@spectators.each do |spectator|
spectator.socket.write msg
spectator.socket.write "\r\n"
end
@client_mutex.unlock if block
end
def removed_lines(id, n)
return if n == 1
n -= 1 if n < 4
@players.each do |player|
if player and player.id != id
player.insert_random_lines(n)
end
end
end
def start_game
@client_mutex.synchronize do
@running = true
send_broadcast "START", false
@players.each do |player|
if player
player.start(self)
end
end
end
end
def game_over(id)
send_broadcast "LOSE #{id}"
running = []
@players.each do |player|
if player and player.id != id and player.game_running?
running << player.id
end
end
if running.length == 1
@players[running.first-1].wait_cancel
send_broadcast "WIN #{running.first}"
end
if running.length <= 1
send_broadcast "END"
end
end
end
getopts('p:', 'c:', 'r:', 'm:', 'h')
if $OPT_h
puts "Usage: tserver.rb [-p port] [-c columns] [-r rows] [-m maxplayers] [-h]"
exit 1
end
$port = ($OPT_p || $port).to_i
$width = ($OPT_c || $width).to_i
$height = ($OPT_r || $height).to_i
$max_players = ($OPT_m || $max_players).to_i
server = Server.new
server.run