RosettaCodeData/Task/2048/Ruby/2048.rb

199 lines
5.0 KiB
Ruby

#!/usr/bin/ruby
require 'io/console'
class Board
def initialize size=4, win_limit=2048, cell_width = 6
@size = size; @cw = cell_width; @win_limit = win_limit
@board = Array.new(size) {Array.new(size, 0)}
@moved = true; @score = 0; @no_more_moves = false
spawn
end
def draw
print "\n\n" if @r_vert
print ' ' if @r_hori
print '┌' + (['─' * @cw] * @size).join('┬') + '┐'
@board.each do |row|
print "\n"
formated = row.map {|num| num == 0 ? ' ' * @cw : format(num)}
print ' ' if @r_hori
puts '│' + formated.join('│') + '│'
print ' ' if @r_hori
print '├' + ([' ' * @cw] * @size).join('┼') + '┤'
end
print "\r"
print ' ' if @r_hori
puts '└' + (['─' * @cw] * @size).join('┴') + '┘'
end
def move direction
case direction
when :up
@board = column_map {|c| logic(c)}
@r_vert = false if $rumble
when :down
@board = column_map {|c| logic(c.reverse).reverse}
@r_vert = true if $rumble
when :left
@board = row_map {|r| logic(r)}
@r_hori = false if $rumble
when :right
@board = row_map {|r| logic(r.reverse).reverse}
@r_hori = true if $rumble
end
spawn
@moved = false
end
def print_score
puts "Your Score is #@score."
puts "Congratulations, you have won!" if to_enum.any? {|e| e >= @win_limit}
end
def no_more_moves?; @no_more_moves; end
def won?; to_enum.any? {|e| e >= @win_limit}; end
def reset!; initialize @size, @win_limit, @cw; end
private
def set x, y, val
@board[y][x] = val
end
def spawn
free_pos = to_enum.select{|elem,x,y| elem == 0}.map{|_,x,y| [x,y]}
unless free_pos.empty?
set *free_pos.sample, rand > 0.1 ? 2 : 4 if @moved
else
snap = @board
unless @stop
@stop = true
%i{up down left right}.each{|s| move(s)}
@no_more_moves = true if snap.flatten == @board.flatten
@board = snap
@stop = false
end
end
end
def logic list
jump = false
result =
list.reduce([]) do |res, val|
if res.last == val && !jump
res[-1] += val
@score += val
jump = true
elsif val != 0
res.push val
jump = false
end
res
end
result += [0] * (@size - result.length)
@moved ||= list != result
result
end
def column_map
xboard = @board.transpose
xboard.map!{|c| yield c }
xboard.transpose
end
def row_map
@board.map {|r| yield r }
end
def to_enum
@enum ||= Enumerator.new(@size * @size) do |yielder|
(@size*@size).times do |i|
yielder.yield (@board[i / @size][i % @size]), (i % @size), (i / @size )
end
end
@enum.rewind
end
def format(num)
if $color
cstart = "\e[" + $colors[Math.log(num, 2)] + "m"
cend = "\e[0m"
else
cstart = cend = ""
end
cstart + num.to_s.center(@cw) + cend
end
end
$color = true
$colors = %W{0 1;97 1;93 1;92 1;96 1;91 1;95 1;94 1;30;47 1;43 1;42
1;46 1;41 1;45 1;44 1;33;43 1;33;42 1;33;41 1;33;44}
$rumble = false
$check_score = true
unless ARGV.empty?
puts "Usage: #$0 [gridsize] [score-threshold] [padwidth] [--no-color] [--rumble]"; exit if %W[-h --help].include?(ARGV[0])
args = ARGV.map(&:to_i).reject{|n| n == 0}
b = Board.new(*args) unless args.empty?
$rumble = true if ARGV.any?{|a| a =~ /rumble/i }
$color = false if ARGV.any?{|a| a =~ /no.?color/i}
end
b ||= Board.new
puts "\e[H\e[2J"
b.draw
puts "Press h for help, q to quit"
loop do
input = STDIN.getch
if input == "\e"
2.times {input << STDIN.getch}
end
case input
when "\e[A", "w" then b.move(:up)
when "\e[B", "s" then b.move(:down)
when "\e[C", "d" then b.move(:right)
when "\e[D", "a" then b.move(:left)
when "q","\u0003","\u0004" then b.print_score; exit
when "h"
puts <<-EOM.gsub(/^\s*/, '')
Use the arrow-keys or WASD on your keyboard to push board in the given direction.
Tiles with the same number merge into one.
Get a tile with a value of #{ARGV[1] || 2048} to win.
In case you cannot move or merge any tiles anymore, you loose.
You can start this game with different settings by providing commandline argument:
For instance:
%> #$0 6 8192 --rumble
PRESS q TO QUIT (or Ctrl-C or Ctrl-D)
EOM
input = STDIN.getch
end
puts "\e[H\e[2J"
b.draw
if b.no_more_moves? or $check_score && b.won?
b.print_score
if b.no_more_moves?
puts "No more moves possible"
puts "Again? (y/n)"
exit if STDIN.gets.chomp.downcase == "n"
$check_score = true
b.reset!
puts "\e[H\e[2J"
b.draw
else
puts "Continue? (y/n)"
exit if STDIN.gets.chomp.downcase == "n"
$check_score = false
puts "\e[H\e[2J"
b.draw
end
end
end