RosettaCodeData/Task/Snake/Wren/snake-1.wren
2024-03-06 22:25:12 -08:00

136 lines
2.8 KiB
Text

/* Snake.wren */
import "random" for Random
import "./dynamic" for Enum, Lower
foreign class Window {
construct initscr() {}
foreign nodelay(bf)
}
class Ncurses {
foreign static cbreak()
foreign static noecho()
foreign static refresh()
foreign static getch()
foreign static mvaddch(y, x, ch)
foreign static endwin()
}
class C {
foreign static usleep(usec)
}
var Dir = Enum.create("Dir", ["N", "E", "S", "W"])
var State = Enum.create("State", ["space", "food", "border"])
var w = 80
var h = 40
var board = List.filled(w * h, 0)
var rand = Random.new()
var head
var dir
var quit
// ASCII values
var hash = 35
var at = 64
var dot = 46
var spc = 32
/* negative values denote the snake (a negated time-to-live in given cell) */
// reduce a time-to-live, effectively erasing the tail
var age = Fn.new {
for (i in 0...w * h) {
if (board[i] < 0) board[i] = board[i] + 1
}
}
// put a piece of food at random empty position
var plant = Fn.new {
var r
while (true) {
r = rand.int(w * h)
if (board[r] = State.space) break
}
board[r] = State.food
}
// initialize the board, plant a very first food item
var start = Fn.new {
for (i in 0...w) board[i] = board[i + (h - 1) * w] = State.border
for (i in 0...h) board[i * w] = board[i * w + w - 1] = State.border
head = (w * (h - 1 - h % 2) / 2).floor // screen center for any h
board[head] = -5
dir = Dir.N
quit = false
plant.call()
}
var step = Fn.new {
var len = board[head]
if (dir == Dir.N) {
head = head - w
} else if (dir == Dir.S) {
head = head + w
} else if (dir == Dir.W) {
head = head - 1
} else if (dir == Dir.E) {
head = head + 1
}
if (board[head] == State.space) {
board[head] = len - 1 // keep in mind len is negative
age.call()
} else if (board[head] == State.food) {
board[head] = len - 1
plant.call()
} else {
quit = true
}
}
var show = Fn.new {
var symbol = [spc, at, dot]
for (i in 0...w*h) {
Ncurses.mvaddch((i/w).floor, i % w, board[i] < 0 ? hash : symbol[board[i]])
}
Ncurses.refresh()
}
var initScreen = Fn.new {
var win = Window.initscr()
Ncurses.cbreak()
Ncurses.noecho()
win.nodelay(true)
}
initScreen.call()
start.call()
while (true) {
show.call()
var ch = Ncurses.getch()
if (ch == Lower.i) {
dir = Dir.N
} else if (ch == Lower.j) {
dir = Dir.W
} else if (ch == Lower.k) {
dir = Dir.S
} else if (ch == Lower.l) {
dir = Dir.E
} else if (ch == Lower.q) {
quit = true
}
step.call()
C.usleep(300 * 1000) // 300 ms is a reasonable delay
if (quit) break
}
C.usleep(999 * 1000)
Ncurses.endwin()