Last active
December 3, 2024 13:15
-
-
Save cyang-el/f53d2c1e42a0bdf8c64e964404b5f2a4 to your computer and use it in GitHub Desktop.
Revisions
-
cyang-el revised this gist
Dec 3, 2024 . No changes.There are no files selected for viewing
-
cyang-el created this gist
Dec 3, 2024 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,260 @@ # 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