Robots/Go

From Rosetta Code

Code

Translation of: C++
Library: termbox-go
Works with: Ubuntu 16.04


This hasn't been tested on Windows 10 but should work.

Note that this version uses the Z key (rather than the Y key) to move diagonally downwards to the left and the H key (rather than the Z key) to wait for the end. A leave key, L, has also been added in case one wants to end the game immediately.

package main

import (
    "fmt"
    "github.com/nsf/termbox-go"
    "log"
    "math/rand"
    "time"
)

type coord struct{ x, y int }

const (
    width  = 62
    height = 42
    inc    = 10
)

var (
    board       [width * height]rune
    robotsCount = 0
    score       = 0
    aliveRobots = 0
    bold        = termbox.AttrBold
    cursor      coord
    alive       bool
)

var colors = [10]termbox.Attribute{
    termbox.ColorDefault,
    termbox.ColorWhite,
    termbox.ColorBlack | bold,
    termbox.ColorBlue | bold,
    termbox.ColorGreen | bold,
    termbox.ColorCyan | bold,
    termbox.ColorRed | bold,
    termbox.ColorMagenta | bold,
    termbox.ColorYellow | bold,
    termbox.ColorWhite | bold,
}

func printAt(x, y int, s string, fg, bg termbox.Attribute) {
    for _, r := range s {
        termbox.SetCell(x, y, r, fg, bg)
        x++
    }
}

func clearBoard() {
    for y := 0; y < height; y++ {
        for x := 0; x < width; x++ {
            board[x+width*y] = 32
            if x == 0 || x == width-1 || y == 0 || y == height-1 {
                board[x+width*y] = '#'
            }
        }
    }
}

func createBoard() {
    aliveRobots = robotsCount
    for x := 0; x < robotsCount; x++ {
        var a, b int
        for {
            a = rand.Intn(width)
            b = rand.Intn(height)
            if board[a+width*b] == 32 {
                break
            }
        }
        board[a+width*b] = '+'
    }
    printScore()
}

func displayBoard() {
    var fg termbox.Attribute
    bg := colors[0]
    for y := 0; y < height; y++ {
        for x := 0; x < width; x++ {
            t := board[x+width*y]
            switch t {
            case ' ':
                fg = colors[0]
            case '#':
                fg = colors[3]
            case '+':
                fg = colors[8]
            case 'Å', '*':
                fg = colors[6]
            case '@':
                fg = colors[4]
            }
            printAt(x, y, string(t), fg, bg)
        }
    }
    termbox.Flush()
}

func teleport() {
    board[cursor.x+width*cursor.y] = 32
    cursor.x = rand.Intn(width-2) + 1
    cursor.y = rand.Intn(height-2) + 1
    x := cursor.x + width*cursor.y
    if board[x] == '*' || board[x] == '+' || board[x] == '~' {
        alive = false
        board[x] = 'Å'
    } else {
        board[x] = '@'
    }
}

func printScore() {
    fg := colors[4]
    bg := termbox.ColorGreen
    s := fmt.Sprintf("      SCORE: %d      ", score)
    printAt(0, height, s, fg, bg)
    termbox.Flush()
}

func execute(x, y int) {
    board[cursor.x+width*cursor.y] = 32
    cursor.x += x
    cursor.y += y
    board[cursor.x+width*cursor.y] = '@'
    moveRobots()
}

func waitForEnd() {
    for aliveRobots != 0 && alive {
        moveRobots()
        displayBoard()
        time.Sleep(500 * time.Millisecond)
    }
}

func moveRobots() {
    for y := 0; y < height; y++ {
        for x := 0; x < width; x++ {
            if board[x+width*y] != '+' {
                continue
            }
            tx, ty := x, y
            if tx < cursor.x {
                tx++
            } else if tx > cursor.x {
                tx--
            }
            if ty < cursor.y {
                ty++
            } else if ty > cursor.y {
                ty--
            }
            if tx != x || ty != y {
                board[x+width*y] = 32
                if board[tx+width*ty] == 32 {
                    board[tx+width*ty] = '~'
                } else {
                    checkCollision(tx, ty)
                }
            }
        }
    }
    for x := 0; x < width*height; x++ {
        if board[x] == '~' {
            board[x] = '+'
        }
    }
}

func checkCollision(x, y int) {
    if cursor.x == x && cursor.y == y {
        alive = false
        board[x+width*y] = 'Å'
        return
    }
    x += y * width
    if board[x] == '*' || board[x] == '+' || board[x] == '~' {
        if board[x] != '*' {
            aliveRobots--
            score++
        }
        board[x] = '*'
        aliveRobots--
        score++
    }
}

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

func main() {
    rand.Seed(time.Now().UnixNano())
    err := termbox.Init()
    check(err)
    defer termbox.Close()

    eventQueue := make(chan termbox.Event)
    go func() {
        for {
            eventQueue <- termbox.PollEvent()
        }
    }()

    for {
        termbox.HideCursor()
        robotsCount = 10
        score = 0
        alive = true
        clearBoard()
        cursor.x = rand.Intn(width-2) + 1
        cursor.y = rand.Intn(height-2) + 1
        board[cursor.x+width*cursor.y] = '@'
        createBoard()
        for {
            displayBoard()
            select {
            case ev := <-eventQueue:
                if ev.Type == termbox.EventKey {
                    switch ev.Ch {
                    case 'q', 'Q':
                        if cursor.x > 1 && cursor.y > 1 {
                            execute(-1, -1)
                        }
                    case 'w', 'W':
                        if cursor.y > 1 {
                            execute(0, -1)
                        }
                    case 'e', 'E':
                        if cursor.x < width-2 && cursor.y > 1 {
                            execute(1, -1)
                        }
                    case 'a', 'A':
                        if cursor.x > 1 {
                            execute(-1, 0)
                        }
                    case 'd', 'D':
                        if cursor.x < width-2 {
                            execute(1, 0)
                        }
                    case 'z', 'Z':
                        if cursor.x > 1 && cursor.y < height-2 {
                            execute(-1, 1)
                        }
                    case 'x', 'X':
                        if cursor.y < height-2 {
                            execute(0, 1)
                        }
                    case 'c', 'C':
                        if cursor.x < width-2 && cursor.y < height-2 {
                            execute(1, 1)
                        }
                    case 't', 'T':
                        teleport()
                        moveRobots()
                    case 'h', 'H':
                        waitForEnd()
                    case 'l', 'L': // leave key
                        return
                    }
                } else if ev.Type == termbox.EventResize {
                    termbox.Flush()
                }
            }
            printScore()
            if aliveRobots == 0 {
                robotsCount += inc
                clearBoard()
                board[cursor.x+width*cursor.y] = '@'
                createBoard()
            }
            if !alive {
                break
            }
        }
        displayBoard()
        fg := colors[7]
        bg := colors[0]
        printAt(10, 8, "+----------------------------------------+", fg, bg)
        printAt(10, 9, "|               GAME OVER                |", fg, bg)
        printAt(10, 10, "|            PLAY AGAIN(Y/N)?            |", fg, bg)
        printAt(10, 11, "+----------------------------------------+", fg, bg)
        termbox.SetCursor(39, 10)
        termbox.Flush()
        select {
        case ev := <-eventQueue:
            if ev.Type == termbox.EventKey {
                if ev.Ch == 'y' || ev.Ch == 'Y' {
                    break
                } else {
                    return
                }
            }
        }
    }
}