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()) { } }