Task: 2048




Go: package main

import (
	"bufio"
	"fmt"
	"log"
	"math/rand"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"text/template"
	"time"
	"unicode"

	"golang.org/x/crypto/ssh/terminal"
)

const maxPoints = 2048
const (
	fieldSizeX = 4
	fieldSizeY = 4
)
const tilesAtStart = 2
const probFor2 = 0.9

type button int

const (
	_ button = iota
	up
	down
	right
	left
	quit
)

var labels = func() map[button]rune {
	m := make(map[button]rune, 4)
	m[up] = 'W'
	m[down] = 'S'
	m[right] = 'D'
	m[left] = 'A'
	return m
}()
var keybinding = func() map[rune]button {
	m := make(map[rune]button, 8)
	for b, r := range labels {
		m[r] = b
		if unicode.IsUpper(r) {
			r = unicode.ToLower(r)
		} else {
			r = unicode.ToUpper(r)
		}
		m[r] = b
	}
	m[0x03] = quit
	return m
}()

var model = struct {
	Score int
	Field [fieldSizeY][fieldSizeX]int
}{}

var view = func() *template.Template {
	maxWidth := 1
	for i := maxPoints; i >= 10; i /= 10 {
		maxWidth++
	}

	w := maxWidth + 3
	r := make([]byte, fieldSizeX*w+1)
	for i := range r {
		if i%w == 0 {
			r[i] = '+'
		} else {
			r[i] = '-'
		}
	}
	rawBorder := string(r)

	v, err := template.New("").Parse(`SCORE: {{.Score}}
{{range .Field}}
` + rawBorder + `
|{{range .}} {{if .}}{{printf "%` + strconv.Itoa(maxWidth) + `d" .}}{{else}}` +
		strings.Repeat(" ", maxWidth) + `{{end}} |{{end}}{{end}}
` + rawBorder + `

(` + string(labels[up]) + `)Up (` +
		string(labels[down]) + `)Down (` +
		string(labels[left]) + `)Left (` +
		string(labels[right]) + `)Right
`)
	check(err)
	return v
}()

func check(err error) {
	if err != nil {
		log.Panicln(err)
	}
}

func clear() {
	c := exec.Command("clear")
	c.Stdout = os.Stdout
	check(c.Run())
}

func draw() {
	clear()
	check(view.Execute(os.Stdout, model))
}

func addRandTile() (full bool) {
	free := make([]*int, 0, fieldSizeX*fieldSizeY)

	for x := 0; x < fieldSizeX; x++ {
		for y := 0; y < fieldSizeY; y++ {
			if model.Field[y][x] == 0 {
				free = append(free, &model.Field[y][x])
			}
		}
	}

	val := 4
	if rand.Float64() < probFor2 {
		val = 2
	}
	*free[rand.Intn(len(free))] = val

	return len(free) == 1
}

type point struct{ x, y int }

func (p point) get() int      { return model.Field[p.y][p.x] }
func (p point) set(n int)     { model.Field[p.y][p.x] = n }
func (p point) inField() bool { return p.x >= 0 && p.y >= 0 && p.x < fieldSizeX && p.y < fieldSizeY }
func (p *point) next(n point) { p.x += n.x; p.y += n.y }

func controller(key rune) (gameOver bool) {
	b := keybinding[key]

	if b == 0 {
		return false
	}
	if b == quit {
		return true
	}

	var starts []point
	var next point

	switch b {
	case up:
		next = point{0, 1}
		starts = make([]point, fieldSizeX)
		for x := 0; x < fieldSizeX; x++ {
			starts[x] = point{x, 0}
		}
	case down:
		next = point{0, -1}
		starts = make([]point, fieldSizeX)
		for x := 0; x < fieldSizeX; x++ {
			starts[x] = point{x, fieldSizeY - 1}
		}
	case right:
		next = point{-1, 0}
		starts = make([]point, fieldSizeY)
		for y := 0; y < fieldSizeY; y++ {
			starts[y] = point{fieldSizeX - 1, y}
		}
	case left:
		next = point{1, 0}
		starts = make([]point, fieldSizeY)
		for y := 0; y < fieldSizeY; y++ {
			starts[y] = point{0, y}
		}
	}

	moved := false
	winning := false

	for _, s := range starts {
		n := s
		move := func(set int) {
			moved = true
			s.set(set)
			n.set(0)
		}
		for n.next(next); n.inField(); n.next(next) {
			if s.get() != 0 {
				if n.get() == s.get() {
					score := s.get() * 2
					model.Score += score
					winning = score >= maxPoints

					move(score)
					s.next(next)
				} else if n.get() != 0 {
					s.next(next)
					if s.get() == 0 {
						move(n.get())
					}
				}
			} else if n.get() != 0 {
				move(n.get())
			}
		}
	}

	if !moved {
		return false
	}

	lost := false
	if addRandTile() {
		lost = true
	Out:
		for x := 0; x < fieldSizeX; x++ {
			for y := 0; y < fieldSizeY; y++ {
				if (y > 0 && model.Field[y][x] == model.Field[y-1][x]) ||
					(x > 0 && model.Field[y][x] == model.Field[y][x-1]) {
					lost = false
					break Out
				}
			}
		}
	}

	draw()

	if winning {
		fmt.Println("You win!")
		return true
	}
	if lost {
		fmt.Println("Game Over")
		return true
	}

	return false
}

func main() {
	oldState, err := terminal.MakeRaw(0)
	check(err)
	defer terminal.Restore(0, oldState)

	rand.Seed(time.Now().Unix())

	for i := tilesAtStart; i > 0; i-- {
		addRandTile()
	}
	draw()

	stdin := bufio.NewReader(os.Stdin)

	readKey := func() rune {
		r, _, err := stdin.ReadRune()
		check(err)
		return r
	}

	for !controller(readKey()) {
	}
}








Ada: with Ada.Text_IO; use Ada.Text_IO;
with System.Random_Numbers;
procedure Play_2048 is
   
   type t_Keystroke is (Up, Down, Right, Left, Quit, Restart, Invalid);
   
   function Get_Immediate return Character is
   begin
      return Answer : Character do Ada.Text_IO.Get_Immediate(Answer);
      end return;
   end Get_Immediate;
   Arrow_Prefix : constant Character := Character'Val(224); 
   function Get_Keystroke return t_Keystroke is
     (case Get_Immediate is
         when 'Q' | 'q' => Quit,
         when 'R' | 'r' => Restart,
         when 'W' | 'w' => Left,
         when 'A' | 'a' => Up,
         when 'S' | 's' => Down,
         when 'D' | 'd' => Right,
         
         when Arrow_Prefix => (case Character'Pos(Get_Immediate) is
                                  when 72 => Up,
                                  when 75 => Left,
                                  when 77 => Right,
                                  when 80 => Down,
                                  when others => Invalid),
         
         when ASCII.ESC => (case Get_Immediate is
                               when '[' => (case Get_Immediate is
                                               when 'A' => Up,
                                               when 'B' => Down,
                                               when 'C' => Right,
                                               when 'D' => Left,
                                               when others => Invalid),
                               when others => Invalid),
         when others => Invalid);
 
   
   function Random_Int is new System.Random_Numbers.Random_Discrete(Integer);
   type    t_List  is array (Positive range <>) of Natural;
   subtype t_Row   is t_List (1..4);
   type    t_Board is array  (1..4) of t_Row;
   Board     : t_Board;
   New_Board : t_Board;
   Blanks    : Natural;
   Score     : Natural;
   Generator : System.Random_Numbers.Generator;
 
   
   procedure Display_Board is
      Horizontal_Rule : constant String := "+
      function Center (Value : in String) return String is
        ((1..(2-(Value'Length-1)/2) => ' ') & 
         Value(Value'First+1..Value'Last)   & 
         (1..(2-Value'Length/2) => ' '));     
   begin
      Put_Line (Horizontal_Rule);
      for Row of Board loop
         for Cell of Row loop
            Put('|' & (if Cell = 0 then "    " else Center(Cell'Img)));
         end loop;
         Put_Line("|");
         Put_Line (Horizontal_Rule);
      end loop;
      Put_Line("Score =" & Score'Img);
   end Display_Board;
 
   
   procedure Add_Block is
      Block_Offset : Positive := Random_Int(Generator, 1, Blanks);
   begin
      Blanks := Blanks-1;
      for Row of Board loop
         for Cell of Row loop
            if Cell = 0 then
               if Block_Offset = 1 then
                  Cell := (if Random_Int(Generator,1,10) = 1 then 4 else 2);
                  return;
               else
                  Block_Offset := Block_Offset-1;
               end if;
            end if;
         end loop;
      end loop;
   end Add_Block;
 
   procedure Reset_Game is
   begin
      Board  := (others => (others => 0));
      Blanks := 16;
      Score  := 0;
      Add_Block;
      Add_Block;
   end Reset_Game;
 
   
   function HFlip (What : in t_Row) return t_Row is
     (What(4),What(3),What(2),What(1));
   function VFlip (What : in t_Board) return t_Board is
     (HFlip(What(1)),HFlip(What(2)),HFlip(What(3)),HFlip(What(4)));
   function Transpose (What : in t_Board) return t_Board is
   begin
      return Answer : t_Board do
         for Row in t_Board'Range loop
            for Column in t_Row'Range loop
               Answer(Column)(Row) := What(Row)(Column);
            end loop;
         end loop;
      end return;
   end Transpose;
 
   
   
   function Add_Blank (Delta_Score : in Natural) return t_List is
   begin
      Blanks := Blanks+1;
      Score  := Score+Delta_Score;
      return (1 => 0);
   end Add_Blank;
 
   function Move_Row (What : in t_List) return t_List is
     (if What'Length = 1 then What
      elsif What(What'First) = 0
      then Move_Row(What(What'First+1..What'Last)) & (1 => 0)
      else (1 => What(What'First)) & Move_Row(What(What'First+1..What'Last)));
 
   function Merge (What : in t_List) return t_List is
     (if What'Length <= 1 or else What(What'First) = 0 then What
      elsif What(What'First) = What(What'First+1)
      then (1 => 2*What(What'First)) & Merge(What(What'First+2..What'Last)) & Add_Blank(What(What'First))
      else (1 => What(What'First)) & Merge(What(What'First+1..What'Last)));
 
   function Move (What : in t_Board) return t_Board is
     (Merge(Move_Row(What(1))),Merge(Move_Row(What(2))),Merge(Move_Row(What(3))),Merge(Move_Row(What(4))));
 
begin
   System.Random_Numbers.Reset(Generator);
   
   Main_Loop: loop
      Reset_Game;
      Game_Loop: loop    
         Display_Board;
         case Get_Keystroke is
            when Restart => exit Game_Loop;
            when Quit    => exit Main_Loop;
            when Left    => New_Board := Move(Board);
            when Right   => New_Board := VFlip(Move(VFlip(Board)));
            when Up      => New_Board := Transpose(Move(Transpose(Board)));
            when Down    => New_Board := Transpose(VFlip(Move(VFlip(Transpose(Board)))));
            when others  => null;
         end case;
         
         if New_Board = Board then
            Put_Line ("Invalid move...");
         elsif (for some Row of New_Board => (for some Cell of Row => Cell = 2048)) then
            Display_Board;
            Put_Line ("Win !");
            exit Main_Loop;
         else
            Board := New_Board;
            Add_Block; 
            if Blanks = 0
               and then (for all Row in 1..4 =>
                           (for all Column in 1..3 =>
                                (Board(Row)(Column) /= Board(Row)(Column+1))))
               and then (for all Row in 1..3 =>
                           (for all Column in 1..4 =>
                                (Board(Row)(Column) /= Board(Row+1)(Column)))) then
               Display_Board;
               Put_Line ("Lost !");
               exit Main_Loop;
            end if;
         end if;
      end loop Game_Loop;
   end loop Main_Loop;
end Play_2048;