#!/usr/bin/ruby
# Author: Daniel "Trizen" Șuteu
# License: GPLv3
# Date: 25 June 2015
# Website: https://github.com/trizen
# The snake game. (with colors + Unicode)
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]
)
const (
BG_COLOR = "on_black"
FOOD_COLOR = ("red" + " " + BG_COLOR)
SNAKE_COLOR = ("bold green" + " " + BG_COLOR)
U_HEAD = ansi.colored('▲', SNAKE_COLOR)
D_HEAD = ansi.colored('▼', SNAKE_COLOR)
L_HEAD = ansi.colored('◀', SNAKE_COLOR)
R_HEAD = ansi.colored('▶', SNAKE_COLOR)
U_BODY = ansi.colored('╹', SNAKE_COLOR)
D_BODY = ansi.colored('╻', SNAKE_COLOR)
L_BODY = ansi.colored('╴', SNAKE_COLOR)
R_BODY = ansi.colored('╶', SNAKE_COLOR)
U_TAIL = ansi.colored('╽', SNAKE_COLOR)
D_TAIL = ansi.colored('╿', SNAKE_COLOR)
L_TAIL = ansi.colored('╼', SNAKE_COLOR)
R_TAIL = ansi.colored('╾', SNAKE_COLOR)
A_VOID = ansi.colored(' ', BG_COLOR)
A_FOOD = ansi.colored('❇', FOOD_COLOR)
)
var sleep = 0.02; # sleep duration between updates
var food_num = 10; # number of initial food sources
var w = Number(`tput cols` || 80)
var h = Number(`tput lines` || 24)
var r = "\033[H";
var dir = LEFT;
var grid = h.of { w.of { Array.new(VOID) } };
var head_pos = [h>>1, w>>1];
var 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
func 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;
}
{ make_food() } * food_num;
func display {
static i = 0;
static s = [UP, DOWN, LEFT, RIGHT];
print(r, grid.map { |row|
row.map { |cell|
if (cell[0] != VOID) {
i = s.index(cell[1])
}
given (cell[0]) {
when (VOID) { A_VOID }
when (FOOD) { A_FOOD }
when (BODY) { [U_BODY, D_BODY, L_BODY, R_BODY][i] }
when (HEAD) { [U_HEAD, D_HEAD, L_HEAD, R_HEAD][i] }
when (TAIL) { [U_TAIL, D_TAIL, L_TAIL, R_TAIL][i] }
}
}.join('')
}.join("\n")
);
}
func 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; 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];
}
}
readkey.ReadMode(3);
STDOUT.autoflush(true);
loop {
var key;
while (!defined(key = readkey.ReadLine(-1))) {
move();
display();
Sys.sleep(sleep);
}
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 } }
}
}