Skip to content

Instantly share code, notes, and snippets.

@cyang-el
Last active December 3, 2024 13:15
Show Gist options
  • Save cyang-el/f53d2c1e42a0bdf8c64e964404b5f2a4 to your computer and use it in GitHub Desktop.
Save cyang-el/f53d2c1e42a0bdf8c64e964404b5f2a4 to your computer and use it in GitHub Desktop.
a from scrach terminal 2048 game in Ruby :) asciinema: https://asciinema.org/a/693144
# frozen_string_literal: true
require 'io/console'
SIZE = 5
def run
$stdin.echo = false
board = init_board
render!(board)
loop do
render!(update(board))
end
ensure
$stdin.echo = true
end
def init_board
board = []
4.times { board << [nil, nil, nil, nil] } # 4 x 4 game
srand(Time.now.to_i)
rand(0..3)
pos1 = rand(0..3)
pos2 = (pos1 + 2) % 4
board[pos1][pos1] = 2
board[pos2][pos2] = 2
board
end
def update(board)
key_in = $stdin.getch
# ^ blocking
exit if key_in.eql?('q')
return board unless %w[h j k l].include? key_in
update_board(key_in, board)
end
def update_board(key_in, board)
handler = {
'j' => method(:down!),
'k' => method(:up!),
'h' => method(:left!),
'l' => method(:right!)
}[key_in]
raise RuntimeError if handler.nil?
handler.call(board)
if win?(board)
raise 'congrats, you win!!!!!'
elsif fail?(board)
raise 'oh no, you lost :('
else
add_2!(board)
end
board
end
def up!(board)
move_along_x!(board, true)
end
def down!(board)
move_along_x!(board, false)
end
def right!(board)
move_along_y!(board, false)
end
def left!(board)
move_along_y!(board, true)
end
def win?(board)
board.any? { |row| row.any? { |n| n.eql? 2048 } }
end
def fail?(board)
board.all? { |row| row.all? { |n| !n.nil? } }
end
def add_2!(board)
nil_coords = []
board.each_with_index do |row, y|
row.each_with_index do |val, x|
nil_coords << [x, y] if val.nil?
end
end
ty, tx = nil_coords.sample
board[ty][tx] = 2
end
def column_at(board, x)
0.upto(3).each.map { |y| board[y][x] }.filter { |v| !v.nil? }
end
def collect(line)
0.upto(line.length - 1).each do |i|
if line[i].eql?(line[i + 1])
line[i] = line[i] + line[i + 1]
line[i + 1] = 0
end
end
line.filter(&:positive?)
end
def fold_column_at(board, x, is_up)
column = is_up ? column_at(board, x) : column_at(board, x).reverse
column = collect(column)
is_up ? column : column.reverse
end
def move_along_x!(board, is_up)
# move
0.upto(3).each do |x|
column = fold_column_at(board, x, is_up)
if is_up
column.fill(nil, column.length...4)
else
column = Array.new(4 - column.length) + column
end
0.upto(3).each { |y| board[y][x] = column[y] }
end
end
def fold_row_at(board, y, is_left)
row = board[y].filter { |v| !v.nil? }
row = is_left ? row : row.reverse
row = collect(row)
is_left ? row : row.reverse
end
def move_along_y!(board, is_left)
0.upto(3).each do |y|
row = fold_row_at(board, y, is_left)
if is_left
row.fill(nil, row.length...4)
else
row = Array.new(4 - row.length) + row
end
board[y] = row
end
end
def map_bg_color(val)
case val
when nil
"\e[42m" # Green
when 2
"\e[105m" # Bright Magenta
when 4
"\e[96m" # Bright Cyan
when 8
"\e[43m" # Yellow
when 16
"\e[104m" # Bright Blue
when 32
"\e[45m" # Magenta
when 64
"\e[46m" # Cyan
when 128
"\e[101m" # Bright Red
when 256
"\e[102m" # Bright Green
when 512
"\e[103m" # Bright Yellow
when 1024
"\e[44m" # Blue
when 2048
"\e[41m" # Red
else
raise ArgumentError, 'only nil or 2048 nums can be colored'
end
end
def color_txt(txt, bg_color)
raise ArgumentError if txt.nil?
reset = "\e[0m"
"#{bg_color}#{txt}#{reset}"
end
def pos_block(pos, size = SIZE)
pos_start = pos * size
pos_end = pos_start + size - 1
[pos_start, pos_end]
end
def pos_val_in_row(val, xhead, xtail)
txt_len = val.to_s.length
center = (xhead + xtail) / 2
center - txt_len / 2
end
def center_row(ytop, ybottom)
(ytop + ybottom) / 2
end
def place_block!(pos_x, pos_y, val, color, canvas)
ytop, ybottom = pos_block(pos_y)
xhead, xtail = pos_block(pos_x)
val = val.nil? ? ' ' : val
(ytop..ybottom).each do |y|
(xhead..xtail).each do |x|
canvas[y][x] = color_txt(' ', color)
end
end
place_txt!(val, xhead, xtail,
ytop, ybottom, color, canvas)
end
def place_txt!(val, xhead, xtail,
ytop, ybottom, color, canvas)
return if val.nil?
xtxt = pos_val_in_row val, xhead, xtail
ytxt = center_row ytop, ybottom
(0...val.to_s.length).each do |idx|
canvas[ytxt][xtxt + idx] = color_txt(val.to_s[idx], color)
end
end
def place_board!(board, canvas)
board.each_with_index do |row, y|
row.each_with_index do |val, x|
color = map_bg_color(val)
place_block! x, y, val, color, canvas
end
end
end
def a_clean_canvas(size = SIZE)
# 4 x 4 game
len = size * 4
Array.new(len) { Array.new(len, nil) }
end
def print_canvas!(canvas)
canvas.each { |row| puts row.join }
end
def render_board(board)
canvas = a_clean_canvas
place_board! board, canvas
print_canvas! canvas
end
def render!(board)
print "\033[2J" # clear screen
print "\033[H" # cursor to the top left
render_board board
print "\n"
end
run if __FILE__ == $PROGRAM_NAME
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment