

 This page uses content from Wikipedia. The original article was at Snake_(video_game). The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance)


Snake is a game where the player maneuvers a line which grows in length every time the snake reaches a food source.



Task
Implement a variant of the Snake game, in any interactive environment, in which a sole player attempts to eat items by running into them with the head of the snake.

Each item eaten makes the snake longer and a new item is randomly generated somewhere else on the plane.

The game ends when the snake attempts to eat himself.




class SnakeGame(w, h) {
    const readkey = frequire('Term::ReadKey')
    const ansi    = frequire('Term::ANSIColor')
 
    enum (VOID, HEAD, BODY, TAIL, FOOD)
 
    define (
        LEFT  = [+0, -1],
        RIGHT = [+0, +1],
        UP    = [-1, +0],
        DOWN  = [+1, +0],
    )
 
    define BG_COLOR    = "on_black"
    define FOOD_COLOR  = ("red"        + " " + BG_COLOR)
    define SNAKE_COLOR = ("bold green" + " " + BG_COLOR)
    define SLEEP_SEC   = 0.02
 
    const (
        A_VOID  = ansi.colored(' ', BG_COLOR),
        A_FOOD  = ansi.colored('❇', FOOD_COLOR),
        A_BLOCK = ansi.colored('■', SNAKE_COLOR),
    )
 
    has dir = LEFT
    has grid = [[]]
    has head_pos = [0, 0]
    has tail_pos = [0, 0]
 
    method init {
        grid = h.of { w.of { [VOID] } }
 
        head_pos = [h//2, w//2]
        tail_pos = [head_pos[0], head_pos[1]+1]
 
        grid[head_pos[0]][head_pos[1]] = [HEAD, dir]    # head
        grid[tail_pos[0]][tail_pos[1]] = [TAIL, dir]    # tail
 
        self.make_food()
    }
 
    method make_food {
        var (food_x, food_y)
 
        do {
            food_x = w.rand.int
            food_y = h.rand.int
        } while (grid[food_y][food_x][0] != VOID)
 
        grid[food_y][food_x][0] = FOOD
    }
 
    method display {
        print("\033[H", grid.map { |row|
            row.map { |cell|
                given (cell[0]) {
                    when (VOID) { A_VOID }
                    when (FOOD) { A_FOOD }
                    default     { A_BLOCK }
                }
              }.join('')
            }.join("\n")
        )
    }
 
    method move {
        var grew = false
 
        # Move the head
        var (y, x) = head_pos...
 
        var new_y = (y+dir[0] % h)
        var new_x = (x+dir[1] % w)
 
        var cell = grid[new_y][new_x]
 
        given (cell[0]) {
            when (BODY) { die "\nYou just bit your own body!\n" }
            when (TAIL) { die "\nYou just bit your own tail!\n" }
            when (FOOD) { grew = true; self.make_food()         }
        }
 
        # Create a new head
        grid[new_y][new_x] = [HEAD, dir]
 
        # Replace the current head with body
        grid[y][x] = [BODY, dir]
 
        # Update the head position
        head_pos = [new_y, new_x]
 
        # Move the tail
        if (!grew) {
            var (y, x) = tail_pos...
 
            var pos   = grid[y][x][1]
            var new_y = (y+pos[0] % h)
            var new_x = (x+pos[1] % w)
 
            grid[y][x][0]         = VOID    # erase the current tail
            grid[new_y][new_x][0] = TAIL    # create a new tail
 
            tail_pos = [new_y, new_x]
        }
    }
 
    method play {
        STDOUT.autoflush(true)
        readkey.ReadMode(3)
 
        try {
            loop {
                var key
                while (!defined(key = readkey.ReadLine(-1))) {
                    self.move()
                    self.display()
                    Sys.sleep(SLEEP_SEC)
                }
 
                given (key) {
                    when ("\e[A") { if (dir != DOWN ) { dir = UP    } }
                    when ("\e[B") { if (dir != UP   ) { dir = DOWN  } }
                    when ("\e[C") { if (dir != LEFT ) { dir = RIGHT } }
                    when ("\e[D") { if (dir != RIGHT) { dir = LEFT  } }
                }
            }
        }
        catch {
            readkey.ReadMode(0)
        }
    }
}
 
var w = `tput cols`.to_i
var h = `tput lines`.to_i
 
SnakeGame(w || 80, h || 24).play