# Project : 2048 Game

load "stdlib.ring"
load "guilib.ring"

C_GAMETITLE		= '2048 Game'
C_WINDOWBACKGROUND 	= "background-color: gray;"
if isMobile()
	C_LABELFONTSIZE 	= "font-size:120px;"
	C_BUTTONFONTSIZE 	= "font-size:160px;"
else
	C_LABELFONTSIZE 	= "font-size:50px;"
	C_BUTTONFONTSIZE 	= "font-size:80px;"
ok
C_PLAYERSCORESTYLE	= "color:white;background-color:rgb(50,50,50);border-radius:17px;" + C_LABELFONTSIZE
C_NEWGAMESTYLE		= 'color:white;background-color:rgb(50,50,50);border-radius:17px;' + C_LABELFONTSIZE
C_EMPTYBUTTONSTYLE 	= 'border-radius:17px;background-color:silver;' + C_BUTTONFONTSIZE
C_BUTTON2STYLE 		= 'border-radius:17px;color:black; background-color: yellow ;' + C_BUTTONFONTSIZE
C_BUTTON4STYLE 		= 'border-radius:17px;color:black; background-color: violet ;' + C_BUTTONFONTSIZE
C_BUTTON8STYLE 		= 'border-radius:17px;color:white; background-color: purple ;' + C_BUTTONFONTSIZE
C_BUTTON16STYLE 	= 'border-radius:17px;color:white; background-color: blue ;' + C_BUTTONFONTSIZE
C_BUTTON32STYLE 	= 'border-radius:17px;color:white; background-color: red ;' + C_BUTTONFONTSIZE
C_BUTTON64STYLE 	= 'border-radius:17px;color:black; background-color: lightgray ;' + C_BUTTONFONTSIZE
C_BUTTON128STYLE 	= 'border-radius:17px;color:black; background-color: white ;' + C_BUTTONFONTSIZE
C_BUTTON256STYLE 	= 'border-radius:17px;color:white; background-color: black ;' + C_BUTTONFONTSIZE
C_BUTTON512STYLE 	= 'border-radius:17px;color:white; background-color: Purple ;' + C_BUTTONFONTSIZE
C_BUTTON1024STYLE 	= 'border-radius:17px;color:black; background-color: Yellow ;' + C_BUTTONFONTSIZE
C_BUTTON2048STYLE 	= 'border-radius:17px;color:white; background-color: Green ;' + C_BUTTONFONTSIZE
C_LAYOUTSPACING		= 10
C_PLAYERSCORE		= 'Player Score :  '

size = 4
limit = 2
num = 0
flag = 0
x1 = 0
x2 = 0
y1 = 0
y2 = 0
nScore = 0
button = newlist(size,size)
buttonsave = newlist(size,size)
LayoutButtonRow = list(size+2)
moveleft = []
moveright = []
moveup = []
movedown = []
myfilter2 = null
myfilter3 = null
winheight = 0
winwidth = 0

app = new qApp {
          StyleFusion()
          processevents()
          win = new qWidget() {
                  setWindowTitle(C_GAMETITLE)
                  setgeometry(100,100,600,700)
                  setminimumwidth(300)
                  setminimumheight(300)
		  if not isMobile()
	                  grabkeyboard()
		  ok
                  setstylesheet(C_WINDOWBACKGROUND)
                  move(490,100)
                  for n = 1 to size
                       for m = 1 to size
                            button[n][m] = new MyButton(win)
                       next
                  next
                  newgame = new qLabel(win)
                  playerscore = new qLabel(win)
                  myfilter3 = new qAllEvents(win) {
                  setMouseButtonPressEvent("pPress()")
                  setMouseButtonReleaseEvent("pRelease()")}
                  installeventfilter(myfilter3)
                  myfilter2 = new qAllEvents(win) {
                  setkeypressevent("keypress()") }
                  installeventfilter(myfilter2)
                  winwidth = win.width()
                  winheight = win.height()
                  for n = 1 to size + 2
			LayoutButtonRow[n] = new QHBoxLayout() {
				setSpacing(C_LAYOUTSPACING)
			}
                  next
                  for n = 1 to size
                       for m = 1 to size
                            button[n][m] { temp = text() }
                            buttonsave[n][m] = temp
                            button[n][m] = new MyButton(win) {
                                                   setalignment(Qt_AlignHCenter | Qt_AlignVCenter)
                                                   setstylesheet(C_EMPTYBUTTONSTYLE)
                                                   show()
                                           }
                       next
                  next
                  for n = 1 to size
                       for m = 1 to size
                            LayoutButtonRow[n].AddWidget(button[m][n])
                            win.show()
                            temp = buttonsave[n][m]
                            button[n][m].settext(temp)
                       next
  		       LayoutButtonRow[n].setSpacing(C_LAYOUTSPACING)
                  next
                  playerscore {
                                    setGeometry(0,4*floor(winheight/6),winwidth,floor(winheight/6))
                                    setalignment(Qt_AlignHCenter | Qt_AlignVCenter)
                                    settext(C_PLAYERSCORE + nScore)
				    setStylesheet(C_PLAYERSCORESTYLE)
                                    show()
		  }
                  newgame  {
                                  setGeometry(0,5*floor(winheight/6),winwidth,floor(winheight/6))
                                  setalignment(Qt_AlignHCenter | Qt_AlignVCenter)
                                  setstylesheet(C_NEWGAMESTYLE)
                                  settext('New Game')
                                  myfilter4 = new qallevents(newgame)
                                  myfilter4.setMouseButtonPressEvent("pbegin()")
                                  installeventfilter(myfilter4)
                                  show()
                                  }
                  LayoutButtonRow[size+1].AddWidget(playerscore)
                  LayoutButtonRow[size+2].AddWidget(newgame)
                  LayoutButtonMain = new QVBoxLayout() {
			setSpacing(C_LAYOUTSPACING)
			for n = 1 to size+2
				AddLayout(LayoutButtonRow[n])
				win.show()
			next
		  }
		  win.setLayout(LayoutButtonMain)
                  win.show()
                  pbegin()
                  show()
         }
    exec()
}

func pPress()
        x1 = myfilter3.getglobalx()
        y1 = myfilter3.getglobaly()

func pRelease()
        x2 = myfilter3.getglobalx()
        y2 = myfilter3.getglobaly()
        difx = x2 - x1
	dify = y2 - y1
	if fabs(difx) > fabs(dify)
		if difx < 0
			pleft()
		else
			pRight()
		ok
	else
		if dify < 0
			pUp()
		else
			pDown()
		ok
	ok

func keypress()
        nKey = myfilter2.getkeycode()
        switch nKey
                   on 16777234 pleft()
                   on 16777236 pright()
                   on 16777235 pup()
                   on 16777237 pdown()
        off

func pbegin()
       numbers = [['2','2'],['2','4']]
       randnew = newlist(2,2)
       for n = 1 to size
            for m = 1 to size
                 button[n][m].setStylesheet(C_EMPTYBUTTONSTYLE)
                 button[n][m].settext('')
            next
        next
        while true
                rn1 = random(size - 1) + 1
                rm1 = random(size - 1) + 1
                rn2 = random(size - 1) + 1
                rm2 = random(size - 1) + 1
                bool = (rn1 = rn2) and (rm1 = rm2)
                if not bool
                   exit
                ok
        end
        rand = random(limit - 1) + 1
        button[rn1][rm1].settext(numbers[rand][1])
        button[rn2][rm2].settext(numbers[rand][2])
        nScore = 0
        playerscore.settext(C_PLAYERSCORE)

func pMoveInDirection cFunc
        num = gameover()
        if num = size*size
           flag = 1
           msgBox('You lost!')
           pbegin()
        ok
        if flag = 0
           call cFunc()
           sleep(0.5)
           newnum()
        ok

func pdown()
	pMoveInDirection(:pMoveDown)

func pup()
	pMoveInDirection(:pMoveUp)

func pleft()
	pMoveInDirection(:pMoveLeft)

func pright()
	pMoveInDirection(:pMoveRight)

func pmoveleft()
       for n = 1 to size
            moveleft = []
            for m = 1 to size
                 button[m][n] {temp = text()}
                 if temp != ''
                    add(moveleft,temp)
                 ok
            next
            movetilesleft(n,moveleft)
       next

func pmoveright()
       for n = 1 to size
            moveright = []
            for m = size to 1 step -1
                 button[m][n] {temp = text()}
                 if temp != ''
                    add(moveright,temp)
                 ok
            next
            movetilesright(n,moveright)
        next
        return

func pmoveup()
       for n = 1 to size
            moveup = []
            for m = 1 to size
                 button[n][m] {temp = text()}
                 if temp != ''
                    add(moveup,temp)
                 ok
            next
            movetilesup(n,moveup)
        next
        return

func pmovedown()
       for n = 1 to size
            movedown = []
            for m = size to 1 step -1
                 button[n][m] {temp = text()}
                 if temp != ''
                    add(movedown,temp)
                 ok
            next
            movetilesdown(n,movedown)
        next
        return

func movetilesleft(nr,moveleft)
       for p = 1 to len(moveleft) - 1
            temp1 = moveleft[p]
            temp2 = moveleft[p+1]
            temp = string(number(temp1) + number(temp2))
            if (temp1 = temp2) and (temp1 != '0') and (temp2 != '0') and (temp1 != '') and (temp2 != '')
               if temp != '0' and temp != ''
                  nScore = nScore + temp
                  playerscore.settext(C_PLAYERSCORE + nScore)
                  flag = 1
                  moveleft[p] = temp
                  del(moveleft,p+1)
               ok
            ok
       next
       for n = 1 to len(moveleft)
            button[n][nr].settext(moveleft[n])
       next
       for n = len(moveleft) + 1 to size
            if n <= size
               button[n][nr].setStylesheet(C_EMPTYBUTTONSTYLE)
               button[n][nr].settext('')
            ok
       next
       return

func movetilesright(nr,moveright)
       flag = 0
       for p = 2 to len(moveright)
            temp1 = moveright[p]
            temp2 = moveright[p-1]
             if (temp1 = temp2) and (temp1 != '0') and (temp2 != '0') and (temp1 != '') and (temp2 != '')
               temp = string(number(temp1) + number(temp2))
               if temp != '0' and temp != ''
                  nScore = nScore + temp
                  playerscore.settext(C_PLAYERSCORE + nScore)
                  flag = 1
                  moveright[p] = temp
                  del(moveright,p-1)
               ok
            ok
       next
       for n = 1 to len(moveright)
            button[size-n+1][nr].settext(moveright[n])
       next
       for n = 1 to size - len(moveright)
            if n <= size
               button[n][nr].setStylesheet(C_EMPTYBUTTONSTYLE)
               button[n][nr].settext('')
            ok
       next

func movetilesup(nr,moveup)
        flag = 0
        for p = 1 to len(moveup) - 1
             temp1 = moveup[p]
             temp2 = moveup[p+1]
             if (temp1 = temp2) and (temp1 != '0') and (temp2 != '0') and (temp1 != '') and (temp2 != '')
               temp = string(number(temp1) + number(temp2))
               if temp != '0' and temp != ''
                  nScore = nScore + temp
                  playerscore.settext(C_PLAYERSCORE + nScore)
                  flag = 1
                  moveup[p] = temp
                  del(moveup,p+1)
               ok
            ok
       next
       for n = 1 to len(moveup)
            button[nr][n].settext(moveup[n])
       next
       for n = len(moveup) + 1 to size
            if n <= size
               button[nr][n].setStylesheet(C_EMPTYBUTTONSTYLE)
               button[nr][n].settext('')
            ok
       next

func movetilesdown(nr,movedown)
        flag = 0
        for p = 1 to len(movedown) - 1
             temp1 = movedown[p]
             temp2 = movedown[p+1]
             if (temp1 = temp2) and (temp1 != '0') and (temp2 != '0') and (temp1 != '') and (temp2 != '')
               temp = string(number(temp1) + number(temp2))
               if temp != '0' and temp != ''
                  nScore = nScore + temp
                  playerscore.settext(C_PLAYERSCORE + nScore)
                  flag = 1
                  movedown[p] = temp
                  del(movedown,p+1)
               ok
            ok
       next
       for n = 1 to len(movedown)
            button[nr][size-n+1].settext(movedown[n])
       next
       for n = size - len(movedown) to 1 step -1
            if n <= size
               button[nr][n].setStylesheet(C_EMPTYBUTTONSTYLE)
               app.processevents()
               button[nr][n].settext('')
            ok
       next

func newnum()
        while true
                rn = random(size - 1) + 1
                rm = random(size - 1) + 1
                if button[rn][rm].text() = ''
                   button[rn][rm].settext('2')
                   exit
                ok
        end
        return

func gameover()
        num = 0
        flag = 0
        for n = 1 to size
             for m = 1 to size
                  if button[n][m].text() != ''
                     num = num + 1
                  ok
              next
        next
        return num

func msgBox(text) {
	m = new qMessageBox(win) {
	       setWindowTitle('2048 Game')
	       setText(text)
	       show()
	       }
        }

func showarray(vect)
        see "["
        svect = ""
        for n = 1 to len(vect)
              svect = svect + vect[n] + " "
        next
        svect = left(svect, len(svect) - 1)
        see svect
        see "]" + nl

class MyButton from qLabel
       func setText(cValue)
              Super.setText(cValue)
              switch cValue
                        on '2' 		setStyleSheet(C_BUTTON2STYLE)
                        on '4' 		setStylesheet(C_BUTTON4STYLE)
                        on '8' 		setStylesheet(C_BUTTON8STYLE)
                        on '16' 	setStylesheet(C_BUTTON16STYLE)
                        on '32' 	setStylesheet(C_BUTTON32STYLE)
                        on '64' 	setStylesheet(C_BUTTON64STYLE)
                        on '128' 	setStylesheet(C_BUTTON128STYLE)
			on '256'	setStylesheet(C_BUTTON256STYLE)
			on '512'	setStylesheet(C_BUTTON512STYLE)
			on '1024'	setStylesheet(C_BUTTON1024STYLE)
			on '2048'	setStylesheet(C_BUTTON2048STYLE)
              off


#!/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


use std::io::{self,BufRead};
extern crate rand;

enum Usermove {
    Up,
    Down,
    Left,
    Right,
}

fn print_game(field :& [[u32;4];4] ){
    println!("{:?}",&field[0] );
    println!("{:?}",&field[1] );
    println!("{:?}",&field[2] );
    println!("{:?}",&field[3] );
}

fn get_usermove()-> Usermove {
    let umove: Usermove ;
    loop{
        let mut input = String::new();
        io::stdin().read_line(&mut input).unwrap();

        match input.chars().nth(0){
            Some('a') =>{umove = Usermove::Left ;break },
            Some('w') =>{umove = Usermove::Up   ;break },
            Some('s') =>{umove = Usermove::Down ;break },
            Some('d') =>{umove = Usermove::Right;break },
            _   => {println!("input was {}: invalid character should be a,s,w or d ",input.chars().nth(0).unwrap());} ,
        }
    }
    umove
}

//this function inplements the user moves.
//for every element it looks if the element is zero
// if the element is zero it looks against the direction of the movement if any
//element is not zero then it will move it to the element its place then it will look for
//a matching element
//  if the element is not zero then it will look for a match if no match is found
// then it will look for the next element

fn do_game_step(step : &Usermove, field:&mut [[u32;4];4]){
    match *step {
        Usermove::Left =>{
            for array in field{
                for  col in 0..4 {
                    for testcol in (col+1)..4 {
                        if array[testcol] != 0 {
                            if array[col] == 0 {
                                array[col] += array[testcol];
                                array[testcol] = 0;
                            }
                            else if array[col] == array[testcol] {
                                array[col] += array[testcol];
                                array[testcol] = 0;
                                break;
                            } else {
                                break
                            }
                        }
                    }
                }
            }
        } ,
        Usermove::Right=>{
            for array in field{
                for  col in (0..4).rev() {
                    for testcol in (0..col).rev() {
                        if array[testcol] != 0 {
                            if array[col] == 0 {
                                array[col] += array[testcol];
                                array[testcol] = 0;
                            }
                            else if array[col] == array[testcol] {
                                array[col] += array[testcol];
                                array[testcol] = 0;
                                break;
                            }else {
                                break;
                            }
                        }
                    }
                }
            }
        } ,
        Usermove::Down   =>{
            for col in 0..4 {
                for row in (0..4).rev() {
                    for testrow in (0..row).rev() {
                        if field[testrow][col] != 0 {
                            if field[row][col] == 0 {
                                field[row][col] += field[testrow][col];
                                field[testrow][col] = 0;
                            } else if field[row][col] == field[testrow][col] {
                                field[row][col] += field[testrow][col];
                                field[testrow][col] = 0;
                                break;
                            }else {
                                break;
                            }

                        }
                    }
                }
            }
        } ,
        Usermove::Up =>{
            for col in 0..4 {
                for row in 0..4{
                    for testrow in (row+1)..4 {
                        if field[testrow][col] != 0 {
                            if field[row][col] == 0 {
                                field[row][col] += field[testrow][col];
                                field[testrow][col] = 0;
                            } else if field[row][col] == field[testrow][col] {
                                field[row][col] += field[testrow][col];
                                field[testrow][col] = 0;
                                break;
                            }else {
                                break;
                            }
                        }
                    }
                }
            }
        },
    }
}

fn spawn( field: &mut  [[u32;4];4]){
    loop{
        let x = rand::random::<usize>();
        if field[x % 4][(x/4)%4] == 0 {
            if x % 10 == 0 {
                field[x % 4][(x/4)%4]= 4;
            }else{
                field[x % 4][(x/4)%4]= 2;
            }
            break;
        }
    }
}

fn main() {
    let mut field : [[u32; 4];4] =  [[0;4];4];
    let mut test : [[u32; 4];4] ;
    'gameloop:loop {
        //check if there is still an open space
        test=field.clone();
        spawn(&mut field);
        //if all possible moves do not yield a change then there is no valid move left
        //and it will be game over
        for i in [Usermove::Up,Usermove::Down,Usermove::Left,Usermove::Right].into_iter(){
            do_game_step(i, &mut test);
            if test != field{
                break;//found a valid move
            }
            match *i{
                Usermove::Right=> {
                    println!("No more valid move, you lose");
                    break 'gameloop;
                },
                _=>{},
            }
        }
        print_game(&field);
        println!("move the blocks");

        test=field.clone();
        while test==field {
            do_game_step(&get_usermove(), &mut field);
        }

        for row in field.iter(){
            if row.iter().any(|x| *x == 2048){
                print_game(&field );
                println!("You Won!!");
                break;
            }
        }
    }
}


import java.awt.event.{KeyAdapter, KeyEvent, MouseAdapter, MouseEvent}
import java.awt.{BorderLayout, Color, Dimension, Font, Graphics2D, Graphics, RenderingHints}
import java.util.Random

import javax.swing.{JFrame, JPanel, SwingUtilities}

object Game2048 {
  val target = 2048
  var highest = 0

  def main(args: Array[String]): Unit = {
    SwingUtilities.invokeLater(() => {
      val f = new JFrame
      f.setDefaultCloseOperation(3)
      f.setTitle("2048")
      f.add(new Game, BorderLayout.CENTER)
      f.pack()
      f.setLocationRelativeTo(null)
      f.setVisible(true)
    })
  }

  class Game extends JPanel {
    private val (rand , side)= (new Random, 4)
    private var (tiles, gamestate)= (Array.ofDim[Tile](side, side), Game2048.State.start)

    final private val colorTable =
      Seq(new Color(0x701710), new Color(0xFFE4C3), new Color(0xfff4d3), new Color(0xffdac3), new Color(0xe7b08e), new Color(0xe7bf8e),
        new Color(0xffc4c3), new Color(0xE7948e), new Color(0xbe7e56), new Color(0xbe5e56), new Color(0x9c3931), new Color(0x701710))

    setPreferredSize(new Dimension(900, 700))
    setBackground(new Color(0xFAF8EF))
    setFont(new Font("SansSerif", Font.BOLD, 48))
    setFocusable(true)
    addMouseListener(new MouseAdapter() {
      override def mousePressed(e: MouseEvent): Unit = {
        startGame()
        repaint()
      }
    })
    addKeyListener(new KeyAdapter() {
      override def keyPressed(e: KeyEvent): Unit = {
        e.getKeyCode match {
          case KeyEvent.VK_UP => moveUp()
          case KeyEvent.VK_DOWN => moveDown()
          case KeyEvent.VK_LEFT => moveLeft()
          case KeyEvent.VK_RIGHT => moveRight()
          case _ =>
        }
        repaint()
      }
    })

    override def paintComponent(gg: Graphics): Unit = {
      super.paintComponent(gg)
      val g = gg.asInstanceOf[Graphics2D]
      g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
      drawGrid(g)
    }

    private def drawGrid(g: Graphics2D): Unit = {
      val (gridColor, emptyColor, startColor) = (new Color(0xBBADA0), new Color(0xCDC1B4), new Color(0xFFEBCD))

      if (gamestate == State.running) {
        g.setColor(gridColor)
        g.fillRoundRect(200, 100, 499, 499, 15, 15)
        for (
          r <- 0 until side;
          c <- 0 until side
        ) if (Option(tiles(r)(c)).isEmpty) {
          g.setColor(emptyColor)
          g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7)
        }
        else drawTile(g, r, c)
      } else {
        g.setColor(startColor)
        g.fillRoundRect(215, 115, 469, 469, 7, 7)
        g.setColor(gridColor.darker)
        g.setFont(new Font("SansSerif", Font.BOLD, 128))
        g.drawString("2048", 310, 270)
        g.setFont(new Font("SansSerif", Font.BOLD, 20))
        if (gamestate == Game2048.State.won) g.drawString("you made it!", 390, 350)
        else if (gamestate == Game2048.State.over) g.drawString("game over", 400, 350)
        g.setColor(gridColor)
        g.drawString("click to start a new game", 330, 470)
        g.drawString("(use arrow keys to move tiles)", 310, 530)
      }
    }

    private def drawTile(g: Graphics2D, r: Int, c: Int): Unit = {
      val value = tiles(r)(c).value
      g.setColor(colorTable((math.log(value) / math.log(2)).toInt + 1))
      g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7)
      g.setColor(if (value < 128) colorTable.head else colorTable(1))
      val (s , fm)= (value.toString, g.getFontMetrics)
      val asc = fm.getAscent
      val (x, y) = (215 + c * 121 + (106 - fm.stringWidth(s)) / 2,115 + r * 121 + (asc + (106 - (asc + fm.getDescent)) / 2))
      g.drawString(s, x, y)
    }

    private def moveUp(checkingAvailableMoves: Boolean = false) = move(0, -1, 0, checkingAvailableMoves)

    private def moveDown(checkingAvailableMoves: Boolean = false) = move(side * side - 1, 1, 0, checkingAvailableMoves)

    private def moveLeft(checkingAvailableMoves: Boolean = false) = move(0, 0, -1, checkingAvailableMoves)

    private def moveRight(checkingAvailableMoves: Boolean = false) = move(side * side - 1, 0, 1, checkingAvailableMoves)

    private def clearMerged(): Unit = for (row <- tiles; tile <- row) if (Option(tile).isDefined) tile.setMerged()

    private def movesAvailable() = moveUp(true) || moveDown(true) || moveLeft(true) || moveRight(true)

    def move(countDownFrom: Int, yIncr: Int, xIncr: Int, checkingAvailableMoves: Boolean): Boolean = {
      var moved = false
      for (i <- 0 until side * side) {
        val j = math.abs(countDownFrom - i)
        var( r,c) = (j / side,  j % side)
        if (Option(tiles(r)(c)).isDefined) {
          var (nextR, nextC, breek) = (r + yIncr, c + xIncr, false)
          while ((nextR >= 0 && nextR < side && nextC >= 0 && nextC < side) && !breek) {
            val (next, curr) = (tiles(nextR)(nextC),tiles(r)(c))
            if (Option(next).isEmpty)
              if (checkingAvailableMoves) return true
              else {
                tiles(nextR)(nextC) = curr
                tiles(r)(c) = null
                r = nextR
                c = nextC
                nextR += yIncr
                nextC += xIncr
                moved = true
              }
            else if (next.canMergeWith(curr)) {
              if (checkingAvailableMoves) return true
              Game2048.highest = math.max(next.mergeWith(curr), Game2048.highest)
              tiles(r)(c) = null
              breek = true
              moved = true
            } else breek = true
          }
        }
      }
      if (moved) if (Game2048.highest < Game2048.target) {
        clearMerged()
        addRandomTile()
        if (!movesAvailable) gamestate = State.over
      }
      else if (Game2048.highest == Game2048.target) gamestate = State.won
      moved
    }

    private def startGame(): Unit = {
      if (gamestate ne Game2048.State.running) {
        Game2048.highest = 0
        gamestate = Game2048.State.running
        tiles = Array.ofDim[Tile](side, side)
        addRandomTile()
        addRandomTile()
      }
    }

    private def addRandomTile(): Unit = {
      var pos = rand.nextInt(side * side)
      var (row, col) = (0, 0)
      do {
        pos = (pos + 1) % (side * side)
        row = pos / side
        col = pos % side
      } while (Option(tiles(row)(col)).isDefined)
      tiles(row)(col) = new Tile(if (rand.nextInt(10) == 0) 4 else 2)
    }

    class Tile(var value: Int) {
      private var merged = false

      def setMerged(): Unit = merged = false

      def mergeWith(other: Tile): Int = if (canMergeWith(other)) {
        merged = true
        value *= 2
        value
      } else -1

      def canMergeWith(other: Tile): Boolean = !merged && Option(other).isDefined && !other.merged && value == other.value
    }
  }

  object State extends Enumeration {
    type State = Value
    val start, won, running, over = Value
  }

}


$ include "seed7_05.s7i";
  include "console.s7i";
  include "keybd.s7i";

const integer: boardLength is 4;
const integer: boardSize is boardLength * boardLength;
const integer: target is 2048;

const type: stateType is new struct
    var integer: fieldsOccupied is 0;
    var integer: largestNumber is 0;
    var integer: score is 0;
    var array array integer: board is boardLength times boardLength times 0;
  end struct;

const proc: addTile (inout stateType: state) is func
  local
    var integer: row is 0;
    var integer: col is 0;
    var integer: field is 2;
  begin
    if state.fieldsOccupied < boardSize then
      repeat
        col := rand(1, boardLength);
        row := rand(1, boardLength);
      until state.board[row][col] = 0;
      if rand(1, 10) = 10 then
        field := 4;
      end if;
      state.board[row][col] := field;
      incr(state.fieldsOccupied);
      state.largestNumber := max(field, state.largestNumber);
    end if;
  end func;

const proc: showBoard (in stateType: state) is func
  local
    var integer: row is 0;
    var integer: field is 0;
  begin
    writeln("┌────┬────┬────┬────┐");
    for key row range state.board do
      for field range state.board[row] do
        if field = 0 then
          write("│    ");
        else
          write("│" <& field lpad (5 + length(str(field))) div 2 rpad 4);
        end if;
      end for;
      writeln("│");
      if row < maxIdx(state.board) then
        writeln("├────┼────┼────┼────┤");
      end if;
    end for;
    writeln("└────┴────┴────┴────┘");
  end func;

const func boolean: doMove (inout stateType: state, in integer: startRow,
    in integer: startCol, in integer: deltaRow, in integer: deltaCol, in boolean: doMerge) is func
  result
    var boolean: boardChanged is FALSE;
  local
    const set of integer: boardRange is {1 .. boardLength};
    var integer: row is 1;
    var integer: col is 1;
    var integer: nextRow is 0;
    var integer: nextCol is 0;
  begin
    row := startRow;
    col := startCol;
    while row in boardRange and col in boardRange do
      while row in boardRange and col in boardRange do
        nextRow := row + deltaRow;
        nextCol := col + deltaCol;
        if state.board[row][col] = 0 and
            nextRow in boardRange and nextCol in boardRange and
            state.board[nextRow][nextCol] <> 0 then
          boardChanged := TRUE;
          state.board[row][col] := state.board[nextRow][nextCol];
          state.board[nextRow][nextCol] := 0;
          if row - deltaRow in boardRange and col - deltaCol in boardRange then
            nextRow := row - deltaRow;
            nextCol := col - deltaCol;
          end if;
        end if;
        row := nextRow;
        col := nextCol;
      end while;

      if doMerge then
        if deltaRow <> 0 then
          row := startRow;
        elsif deltaCol <> 0 then
          col := startCol;
        end if;
        while row in boardRange and col in boardRange do
          nextRow := row + deltaRow;
          nextCol := col + deltaCol;
          if state.board[row][col] <> 0 and
              nextRow in boardRange and nextCol in boardRange and
              state.board[nextRow][nextCol] = state.board[row][col] then
            boardChanged := TRUE;
            state.board[row][col] *:= 2;
            state.largestNumber := max(state.board[row][col], state.largestNumber);
            state.score +:= state.board[row][col];
            state.board[nextRow][nextCol] := 0;
            decr(state.fieldsOccupied);
          end if;
          row := nextRow;
          col := nextCol;
        end while;
      end if;

      if deltaRow = 0 then
        incr(row);
        col := startCol;
      elsif deltaCol = 0 then
        incr(col);
        row := startRow;
      end if;
    end while;
    if doMerge and boardChanged then
      ignore(doMove(state, startRow, startCol, deltaRow, deltaCol, FALSE));
    end if;
  end func;

const func boolean: canMove (in stateType: state) is func
  result
    var boolean: canMove is FALSE;
  local
    var integer: row is 0;
    var integer: col is 0;
  begin
    for row range 1 to boardLength until canMove do
      for col range 1 to boardLength until canMove do
        canMove := state.board[row][col] = 0 or
            (row < boardLength and state.board[row][col] = state.board[succ(row)][col]) or
            (col < boardLength and state.board[row][col] = state.board[row][succ(col)]);
      end for;
    end for;
  end func;

const proc: main is func
  local
    var stateType: state is stateType.value;
    var integer: highscore is 0;
    var char: command is ' ';
    var boolean: quit is FALSE;
    var boolean: moveOkay is FALSE;
  begin
    OUT := open(CONSOLE);
    addTile(state);
    repeat
      setPos(STD_CONSOLE, 1, 1);
      showBoard(state);
      highscore := max(highscore, state.score);
      writeln("Score = " <& state.score <& "  Highscore = " <& highscore);
      if canMove(state) and state.largestNumber < target then
        writeln("Press arrow keys to move, R to Restart, Q to Quit");
      elsif state.largestNumber >= target then
        writeln("You win! Press R to Restart, Q to Quit           ");
      else
        writeln("Game over! Press R to Restart, Q to Quit         ");
      end if;
      repeat
        moveOkay := FALSE;
        command := getc(KEYBOARD);
        case command of
          when {'r', 'R'}:
            state := stateType.value;
            clear(STD_CONSOLE);
            moveOkay := TRUE;
          when {'q', 'Q'}:
            moveOkay := TRUE;
            quit := TRUE;
          when {KEY_LEFT}:
            moveOkay := doMove(state,           1,           1,  0,  1, TRUE);
          when {KEY_RIGHT}:
            moveOkay := doMove(state,           1, boardLength,  0, -1, TRUE);
          when {KEY_UP}:
            moveOkay := doMove(state,           1,           1,  1,  0, TRUE);
          when {KEY_DOWN}:
            moveOkay := doMove(state, boardLength,           1, -1,  0, TRUE);
        end case;
	if moveOkay and not quit then
	  addTile(state);
        end if;
      until moveOkay;
    until quit;
  end func;


# A minimal implementation of the game 2048 in Tcl.
# For a maintained version with expanded functionality see
# https://tcl.wiki/40557.
package require Tcl 8.5
package require struct::matrix
package require struct::list

# Board size.
set size 4

# Iterate over all cells of the game board and run script for each.
#
# The game board is a 2D matrix of a fixed size that consists of elements
# called "cells" that each can contain a game tile (corresponds to numerical
# values of 2, 4, 8, ..., 2048) or nothing (zero).
#
# - cellList is a list of cell indexes (coordinates), which are
# themselves lists of two numbers each. They each represent the location
# of a given cell on the board.
# - varName1 are varName2 are names of the variables the will be assigned
# the index values.
# - cellVarName is the name of the variable that at each step of iteration
# will contain the numerical value of the present cell. Assigning to it will
# change the cell's value.
# - script is the script to run.
proc forcells {cellList varName1 varName2 cellVarName script} {
    upvar $varName1 i
    upvar $varName2 j
    upvar $cellVarName c
    foreach cell $cellList {
        set i [lindex $cell 0]
        set j [lindex $cell 1]
        set c [cell-get $cell]
        uplevel $script
        cell-set "$i $j" $c
    }
}

# Generate a list of cell indexes for all cells on the board, i.e.,
# {{0 0} {0 1} ... {0 size-1} {1 0} {1 1} ... {size-1 size-1}}.
proc cell-indexes {} {
    global size
    set list {}
    foreach i [::struct::list iota $size] {
        foreach j [::struct::list iota $size] {
            lappend list [list $i $j]
        }
    }
    return $list
}

# Check if a number is a valid cell index (is 0 to size-1).
proc valid-index {i} {
    global size
    expr {0 <= $i && $i < $size}
}

# Return 1 if the predicate pred is true when applied to all items on the list
# or 0 otherwise.
proc map-and {list pred} {
    set res 1
    foreach item $list {
        set res [expr {$res && [$pred $item]}]
        if {! $res} break
    }
    return $res
}

# Check if list represents valid cell coordinates.
proc valid-cell? cell {
    map-and $cell valid-index
}

# Get the value of a game board cell.
proc cell-get cell {
    board get cell {*}$cell
}

# Set the value of a game board cell.
proc cell-set {cell value} {
    board set cell {*}$cell $value
}

# Filter a list of board cell indexes cellList to only have those indexes
# that correspond to empty board cells.
proc empty {cellList} {
    ::struct::list filterfor x $cellList {[cell-get $x] == 0}
}

# Pick a random item from the given list.
proc pick list {
    lindex $list [expr {int(rand() * [llength $list])}]
}

# Put a "2" into an empty cell on the board.
proc spawn-new {} {
    set emptyCell [pick [empty [cell-indexes]]]
    if {[llength $emptyCell] > 0} {
        forcells [list $emptyCell] i j cell {
            set cell 2
        }
    }
    return $emptyCell
}

# Return vector sum of lists v1 and v2.
proc vector-add {v1 v2} {
    set result {}
    foreach a $v1 b $v2 {
        lappend result [expr {$a + $b}]
    }
    return $result
}

# If checkOnly is false try to shift all cells one step in the direction of
# directionVect. If checkOnly is true just say if that move is possible.
proc move-all {directionVect {checkOnly 0}} {
    set changedCells 0

    forcells [cell-indexes] i j cell {
        set newIndex [vector-add "$i $j" $directionVect]
        set removedStar 0

        # For every nonempty source cell and valid destination cell...
        if {$cell != 0 && [valid-cell? $newIndex]} {
            if {[cell-get $newIndex] == 0} {
                # Destination is empty.
                if {$checkOnly} {
                    # -level 2 is to return from both forcells and move-all.
                    return -level 2 true
                } else {
                    # Move tile to empty cell.
                    cell-set $newIndex $cell
                    set cell 0
                    incr changedCells
                }
            } elseif {([cell-get $newIndex] eq $cell) &&
                      [string first + $cell] == -1} {
                # Destination is the same number as source.
                if {$checkOnly} {
                    return -level 2 true
                } else {
                    # When merging two tiles into one mark the new tile with
                    # the marker of "+" to ensure it doesn't get combined
                    # again this turn.
                    cell-set $newIndex [expr {2 * $cell}]+
                    set cell 0
                    incr changedCells
                }
            }
        }
    }

    if {$checkOnly} {
        return false
    }

    # Remove "changed this turn" markers at the end of the turn.
    if {$changedCells == 0} {
        forcells [cell-indexes] i j cell {
            set cell [string trim $cell +]
        }
    }
    return $changedCells
}

# Is it possible to move any tiles in the direction of directionVect?
proc can-move? {directionVect} {
    move-all $directionVect 1
}

# Check win condition. The player wins when there's a 2048 tile.
proc check-win {} {
    forcells [cell-indexes] i j cell {
        if {$cell == 2048} {
            puts "You win!"
            exit 0
        }
    }
}

# Check lose condition. The player loses when the win condition isn't met and
# there are no possible moves.
proc check-lose {possibleMoves} {
    set values [dict values $possibleMoves]
    if {!(true in $values || 1 in $values)} {
        puts "You lose."
        exit 0
    }
}

# Pretty-print the board. Specify an index in highlight to highlight a cell.
proc print-board {{highlight {-1 -1}}} {
    forcells [cell-indexes] i j cell {
        if {$j == 0} {
            puts ""
        }
        puts -nonewline [
            if {$cell != 0} {
                if {[::struct::list equal "$i $j" $highlight]} {
                    format "\[%4s\]" $cell*
                } else {
                    format "\[%4s\]" $cell
                }

            } else {
                lindex "......"
            }
        ]
    }
    puts "\n"
}

proc main {} {
    global size

    struct::matrix board

    # Generate an empty board of a given size.
    board add columns $size
    board add rows $size
    forcells [cell-indexes] i j cell {
        set cell 0
    }

    set controls {
        h {0 -1}
        j {1 0}
        k {-1 0}
        l {0 1}
    }

    # Game loop.
    while true {
        set playerMove 0
        set possibleMoves {}

        # Add new tile to the board and print the board highlighting this tile.
        print-board [spawn-new]

        check-win

        # Find possible moves.
        foreach {button vector} $controls {
            dict set possibleMoves $button [can-move? $vector]
        }
        check-lose $possibleMoves

        # Get valid input from the player.
        while {$playerMove == 0} {
            # Print prompt.
            puts -nonewline "Move ("
            foreach {button vector} $controls {
                if {[dict get $possibleMoves $button]} {
                    puts -nonewline $button
                }
            }
            puts ")?"

            set playerInput [gets stdin]

            # Validate input.
            if {[dict exists $possibleMoves $playerInput] &&
                [dict get $possibleMoves $playerInput]} {
                set playerMove [dict get $controls $playerInput]
            }
        }

        # Apply current move until no changes occur on the board.
        while true {
            if {[move-all $playerMove] == 0} break
        }
    }
}

main


Friend Class Tile
    Public Sub New()
        Me.Value = 0
        Me.IsBlocked = False
    End Sub
    Public Property Value As Integer
    Public Property IsBlocked As Boolean
End Class

Friend Enum MoveDirection
     Up
     Down
     Left
     Right
End Enum

    Friend Class G2048
        Public Sub New()
            _isDone = False
            _isWon = False
            _isMoved = True
            _score = 0
            InitializeBoard()
        End Sub

        Private Sub InitializeBoard()
            For y As Integer = 0 To 3
                For x As Integer = 0 To 3
                    _board(x, y) = New Tile()
                Next
            Next
        End Sub

        Private _isDone As Boolean
        Private _isWon As Boolean
        Private _isMoved As Boolean
        Private _score As Integer
        Private ReadOnly _board As Tile(,) = New Tile(3, 3) {}
        Private ReadOnly _rand As Random = New Random()
        Const empty As String = " "

        Public Sub [Loop]()
            AddTile()
            While True
                If _isMoved Then AddTile()
                DrawBoard()
                If _isDone Then Exit While
                WaitKey()
            End While
            Dim endMessage As String = If(_isWon, "You've made it!", "Game Over!")
            Console.WriteLine(endMessage)
        End Sub

        Public Sub DrawBoard()
            Console.Clear()
            Console.WriteLine("Score: " & _score & vbNewLine)
            For y As Integer = 0 To 3
                Console.WriteLine("+------+------+------+------+")
                Console.Write("| ")
                For x As Integer = 0 To 3
                    If _board(x, y).Value = 0 Then
                        Console.Write(empty.PadRight(4))
                    Else
                        Console.Write(_board(x, y).Value.ToString().PadRight(4))
                    End If
                    Console.Write(" | ")
                Next
                Console.WriteLine()
            Next
            Console.WriteLine("+------+------+------+------+" & vbNewLine & vbNewLine)
        End Sub

        Private Sub WaitKey()
            _isMoved = False
            Console.WriteLine("(W) Up (S) Down (A) Left (D) Right")
            Dim input As Char
            Char.TryParse(Console.ReadKey().Key.ToString(), input)
            Select Case input
                Case "W"c
                    Move(MoveDirection.Up)
                Case "A"c
                    Move(MoveDirection.Left)
                Case "S"c
                    Move(MoveDirection.Down)
                Case "D"c
                    Move(MoveDirection.Right)
            End Select
            For y As Integer = 0 To 3
                For x As Integer = 0 To 3
                    _board(x, y).IsBlocked = False
                Next
            Next
        End Sub

        Private Sub AddTile()
            For y As Integer = 0 To 3
                For x As Integer = 0 To 3
                    If _board(x, y).Value <> 0 Then Continue For
                    Dim a As Integer, b As Integer
                    Do
                        a = _rand.Next(0, 4)
                        b = _rand.Next(0, 4)
                    Loop While _board(a, b).Value <> 0
                    Dim r As Double = _rand.NextDouble()
                    _board(a, b).Value = If(r > 0.89F, 4, 2)
                    If CanMove() Then Return
                Next
            Next
            _isDone = True
        End Sub

        Private Function CanMove() As Boolean
            For y As Integer = 0 To 3
                For x As Integer = 0 To 3
                    If _board(x, y).Value = 0 Then Return True
                Next
            Next
            For y As Integer = 0 To 3
                For x As Integer = 0 To 3
                    If TestAdd(x + 1, y, _board(x, y).Value) OrElse TestAdd(x - 1, y, _board(x, y).Value) OrElse TestAdd(x, y + 1, _board(x, y).Value) OrElse TestAdd(x, y - 1, _board(x, y).Value) Then Return True
                Next
            Next
            Return False
        End Function

        Private Function TestAdd(ByVal x As Integer, ByVal y As Integer, ByVal value As Integer) As Boolean
            If x < 0 OrElse x > 3 OrElse y < 0 OrElse y > 3 Then Return False
            Return _board(x, y).Value = value
        End Function

        Private Sub MoveVertically(ByVal x As Integer, ByVal y As Integer, ByVal d As Integer)
            If _board(x, y + d).Value <> 0 AndAlso _board(x, y + d).Value = _board(x, y).Value AndAlso Not _board(x, y).IsBlocked AndAlso Not _board(x, y + d).IsBlocked Then
                _board(x, y).Value = 0
                _board(x, y + d).Value *= 2
                _score += _board(x, y + d).Value
                _board(x, y + d).IsBlocked = True
                _isMoved = True
            ElseIf _board(x, y + d).Value = 0 AndAlso _board(x, y).Value <> 0 Then
                _board(x, y + d).Value = _board(x, y).Value
                _board(x, y).Value = 0
                _isMoved = True
            End If
            If d > 0 Then
                If y + d < 3 Then MoveVertically(x, y + d, 1)
            Else
                If y + d > 0 Then MoveVertically(x, y + d, -1)
            End If
        End Sub

        Private Sub MoveHorizontally(ByVal x As Integer, ByVal y As Integer, ByVal d As Integer)
            If _board(x + d, y).Value <> 0 AndAlso _board(x + d, y).Value = _board(x, y).Value AndAlso Not _board(x + d, y).IsBlocked AndAlso Not _board(x, y).IsBlocked Then
                _board(x, y).Value = 0
                _board(x + d, y).Value *= 2
                _score += _board(x + d, y).Value
                _board(x + d, y).IsBlocked = True
                _isMoved = True
            ElseIf _board(x + d, y).Value = 0 AndAlso _board(x, y).Value <> 0 Then
                _board(x + d, y).Value = _board(x, y).Value
                _board(x, y).Value = 0
                _isMoved = True
            End If
            If d > 0 Then
                If x + d < 3 Then MoveHorizontally(x + d, y, 1)
            Else
                If x + d > 0 Then MoveHorizontally(x + d, y, -1)
            End If
        End Sub

        Private Sub Move(ByVal direction As MoveDirection)
            Select Case direction
                Case MoveDirection.Up
                    For x As Integer = 0 To 3
                        Dim y As Integer = 1
                        While y < 4
                            If _board(x, y).Value <> 0 Then MoveVertically(x, y, -1)
                            y += 1
                        End While
                    Next
                Case MoveDirection.Down
                    For x As Integer = 0 To 3
                        Dim y As Integer = 2
                        While y >= 0
                            If _board(x, y).Value <> 0 Then MoveVertically(x, y, 1)
                            y -= 1
                        End While
                    Next
                Case MoveDirection.Left
                    For y As Integer = 0 To 3
                        Dim x As Integer = 1
                        While x < 4
                            If _board(x, y).Value <> 0 Then MoveHorizontally(x, y, -1)
                            x += 1
                        End While
                    Next
                Case MoveDirection.Right
                    For y As Integer = 0 To 3
                        Dim x As Integer = 2
                        While x >= 0
                            If _board(x, y).Value <> 0 Then MoveHorizontally(x, y, 1)
                            x -= 1
                        End While
                    Next
            End Select
        End Sub
    End Class

    Module Module1
        Sub Main()
            RunGame()
        End Sub

        Private Sub RunGame()
            Dim game As G2048 = New G2048()
            game.Loop()
            CheckRestart()
        End Sub

        Private Sub CheckRestart()
            Console.WriteLine("(N) New game (P) Exit")
            While True
                Dim input As Char
                Char.TryParse(Console.ReadKey().Key.ToString(), input)
                Select Case input
                    Case "N"c
                        RunGame()
                    Case "P"c
                        Return
                    Case Else
                        ClearLastLine()
                End Select
            End While
        End Sub

        Private Sub ClearLastLine()
            Console.SetCursorPosition(0, Console.CursorTop)
            Console.Write(New String(" ", Console.BufferWidth))
            Console.SetCursorPosition(0, Console.CursorTop - 1)
        End Sub
    End Module


import "/dynamic" for Enum, Struct
import "random" for Random
import "/ioutil" for Input
import "/fmt" for Fmt
import "/str" for Str

var MoveDirection = Enum.create("MoveDirection", ["up", "down", "left", "right"])

var Tile = Struct.create("Tile", ["value", "isBlocked"])

class G2048 {
    construct new() {
        _isDone  = false
        _isWon   = false
        _isMoved = true
        _score = 0
        _board = List.filled(4, null)
        for (i in 0..3) {
            _board[i] = List.filled(4, null)
            for (j in 0..3) _board[i][j] = Tile.new(0, false)
        }
        _rand = Random.new()
        initializeBoard()
    }

    initializeBoard() {
        for (y in 0..3) {
            for (x in 0..3) _board[x][y] = Tile.new(0, false)
        }
    }

    loop() {
        addTile()
        while (true) {
            if (_isMoved) addTile()
            drawBoard()
            if (_isDone) break
            waitKey()
        }
        var endMessage = _isWon ? "You've made it!" : "Game Over!"
        System.print(endMessage)
    }

    drawBoard() {
        System.print("\e[2J") // clear terminal
        System.print("Score: %(_score)\n")
        for (y in 0..3) {
            System.print("+------+------+------+------+")
            System.write("| ")
            for (x in 0..3) {
                if (_board[x][y].value == 0) {
                    System.write("    ")
                } else {
                    Fmt.write("$-4s", _board[x][y].value)
                }
                System.write(" | ")
            }
            System.print()
        }
        System.print("+------+------+------+------+\n\n")
    }

    waitKey() {
        _isMoved = false
        var input = Str.upper(Input.option("(W) Up (S) Down (A) Left (D) Right: ", "WSADwsad"))
        if (input == "W") {
            move(MoveDirection.up)
        } else if (input == "A") {
            move(MoveDirection.left)
        } else if (input == "S") {
            move(MoveDirection.down)
        } else if (input == "D") {
            move(MoveDirection.right)
        }
        for (y in 0..3) {
            for (x in 0..3) _board[x][y].isBlocked = false
        }
    }

    addTile() {
        for (y in 0..3) {
            for (x in 0..3) {
                if (_board[x][y].value != 0) continue
                var a
                var b
                while (true) {
                    a = _rand.int(4)
                    b = _rand.int(4)
                    if (_board[a][b].value == 0) break
                }
                var r = _rand.float()
                _board[a][b].value = (r > 0.89) ? 4 : 2
                if (canMove()) return
            }
        }
        _isDone = true
    }

    canMove() {
        for (y in 0..3) {
            for (x in 0..3) {
                if (_board[x][y].value == 0) return true
            }
        }

        for (y in 0..3) {
            for (x in 0..3) {
                if (testAdd(x + 1, y, _board[x][y].value) ||
                    testAdd(x - 1, y, _board[x][y].value) ||
                    testAdd(x, y + 1, _board[x][y].value) ||
                    testAdd(x, y - 1, _board[x][y].value)) return true
            }
        }

        return false
    }

    testAdd(x, y, value) {
        if (x < 0 || x > 3 || y < 0 || y > 3) return false
        return _board[x][y].value == value
    }

    moveVertically(x, y, d) {
        if (_board[x][y + d].value != 0 &&
            _board[x][y + d].value == _board[x][y].value &&
            !_board[x][y].isBlocked &&
            !_board[x][y + d].isBlocked) {
            _board[x][y].value = 0
            _board[x][y + d].value = _board[x][y + d].value * 2
            _score = _score + _board[x][y + d].value
            _board[x][y + d].isBlocked = true
            _isMoved = true
        } else if (_board[x][y + d].value == 0  && _board[x][y].value != 0) {
            _board[x][y + d].value = _board[x][y].value
            _board[x][y].value = 0
            _isMoved = true
        }

        if (d > 0) {
            if (y + d < 3) moveVertically(x, y + d, 1)
        } else {
            if (y + d > 0) moveVertically(x, y + d, -1)
        }
    }

    moveHorizontally(x, y, d) {
        if (_board[x + d][y].value != 0 &&
            _board[x + d][y].value == _board[x][y].value &&
            !_board[x + d][y].isBlocked &&
            !_board[x][y].isBlocked) {
            _board[x][y].value = 0
            _board[x + d][y].value = _board[x + d][y].value * 2
            _score = _score + _board[x + d][y].value
            _board[x + d][y].isBlocked = true
            _isMoved = true
        } else if (_board[x + d][y].value == 0  && _board[x][y].value != 0) {
            _board[x + d][y].value = _board[x][y].value
            _board[x][y].value = 0
            _isMoved = true
        }

        if (d > 0) {
            if (x + d < 3) moveHorizontally(x + d, y, 1)
        } else {
            if (x + d > 0) moveHorizontally(x + d, y, -1)
        }
    }

    move(direction) {
        if (direction == MoveDirection.up) {
            for (x in 0..3) {
               for (y in 1..3) {
                    if (_board[x][y].value != 0) moveVertically(x, y, -1)
               }
            }
        } else if (direction == MoveDirection.down) {
            for (x in 0..3) {
                for (y in 2..0) {
                    if (_board[x][y].value != 0) moveVertically(x, y, 1)
                }
            }
        } else if (direction == MoveDirection.left) {
            for (y in 0..3) {
                for (x in 1..3) {
                    if (_board[x][y].value != 0) moveHorizontally(x, y, -1)
                }
            }
        } else if (direction == MoveDirection.right) {
            for (y in 0..3) {
                for (x in 2..0) {
                    if (_board[x][y].value != 0) moveHorizontally(x, y, 1)
                }
            }
        }
    }
}

var runGame  // forward declaration

var checkRestart = Fn.new {
    var input = Str.upper(Input.option("(N) New game (P) Exit: ", "NPnp"))
    if (input == "N") {
        runGame.call()
    } else if (input == "P") return
}

runGame = Fn.new {
    var game = G2048.new()
    game.loop()
    checkRestart.call()
}

runGame.call()


include c:\cxpl\codes;  \intrinsic 'code' declarations
int     Box(16), Moved;

proc    ShiftTiles(I0, DI);     \Shift tiles, add adjacents, shift again
int     I0, DI;
int     Done, M, N, I;
[Done:= false;
loop    [for M:= 1 to 3 do      \shift all tiles in a single row or column
            [I:= I0;
            for N:= 1 to 3 do
                [if Box(I)=0 & Box(I+DI)#0 then
                        [Box(I):= Box(I+DI);  Box(I+DI):= 0;  Moved:= true];
                I:= I+DI;
                ];
            ];
        if Done then return;
        Done:= true;
        I:= I0;                 \add identical adjacent tiles into a new tile
        for N:= 1 to 3 do
                [if Box(I)=Box(I+DI) & Box(I)#0 then
                        [Box(I):= Box(I)+1;  Box(I+DI):= 0;  Moved:= true];
                I:= I+DI;
                ];
        ];                      \loop back to close any gaps that were opened
];      \ShiftTiles

int     I, J, X, Y, C;
[Clear;
for I:= 0 to 15 do Box(I):= 0;                  \empty the box of tiles
loop    [repeat I:= Ran(16) until Box(I)=0;     \in a random empty location
        Box(I):= if Ran(10) then 1 else 2;      \insert a 2^1=2 or 2^2=4
        for I:= 0 to 15 do                      \show board with its tiles
                [X:= ((I&3)+5)*6;               \get coordinates of tile
                 Y:= I>>2*3+6;
                 Attrib(((Box(I)+1)&7)<<4 + $F);\set color based on tile value
                 for J:= 0 to 2 do              \draw a square (6*8x3*16)
                        [Cursor(X, Y+J);
                        Text(6, "      ");
                        ];
                 if Box(I)#0 then               \box contains a tile
                        [J:= 1;                 \center numbers somewhat
                        if Box(I) <= 9 then J:= 2;
                        if Box(I) <= 3 then J:= 3;
                        Cursor(X+J, Y+1);
                        IntOut(6, 1<<Box(I));
                        ];
                ];
        Moved:= false;                          \a tile must move to continue
        repeat  repeat C:= ChIn(1) until C#0;   \get key scan code, or ASCII
                for I:= 3 downto 0 do           \for all rows or columns
                        [case C of
                          $4B:  ShiftTiles(I*4, 1);     \left arrow
                          $4D:  ShiftTiles(I*4+3, -1);  \right arrow
                          $50:  ShiftTiles(I+12, -4);   \down arrow
                          $48:  ShiftTiles(I, 4);       \up arrow
                          $1B:  [Clear;  exit]          \Esc
                        other   [];                     \ignore all other keys
                        ];
        until   Moved;
        ];
]