Created
February 1, 2023 02:34
-
-
Save 0racle/9852f5903879addbcaa7666375c04840 to your computer and use it in GitHub Desktop.
Terminal Snake game in Raku
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env raku | |
use Terminal::Print; | |
use Terminal::Print::RawInput; | |
use Terminal::ANSIColor; | |
use Point; | |
my constant COLS = 22; | |
my constant ROWS = 22; | |
class BlockyTerm { | |
has $.scr; | |
submethod BUILD { | |
$!scr = Terminal::Print.new; | |
$!scr.initialize-screen; | |
} | |
submethod DESTROY { | |
$!scr.shutdown-screen; | |
} | |
method beep($x, $y) { | |
$!scr.print-string($x × 2, $y, '██') | |
} | |
method boop($x, $y) { | |
$!scr.print-string($x × 2, $y, ' ') | |
} | |
} | |
my \T = BlockyTerm.new; | |
END { T.DESTROY } | |
print color('blue'); | |
for (0 .. COLS + 1) -> \i { | |
T.beep(i, 0); | |
T.beep(i, ROWS + 1); | |
} | |
for (1 .. ROWS) -> \i { | |
T.beep(0, i); | |
T.beep(COLS + 1, i); | |
} | |
print color('magenta'); | |
T.scr.print-string(4, 5, title-text()); | |
class Snake { | |
has Point $.head = point(2, 2); | |
has Point $.dir = point(1, 0); | |
has Point @.tail; | |
has Int $.length is rw = 5; | |
my $U = point( 0, -1); | |
my $L = point(-1, 0); | |
my $D = point( 0, 1); | |
my $R = point( 1, 0); | |
my %dirmap = ( | |
w => $U, a => $L, s => $D, d => $R, | |
h => $L, j => $D, k => $U, l => $R, | |
"\e[A" => $U, "\e[B" => $D, "\e[C" => $R, "\e[D" => $L | |
); | |
method process-input($k) { | |
with %dirmap{$k} -> $d { | |
$!dir = $d | |
if $d.x == $!dir.y | |
|| $d.y == $!dir.x | |
} | |
} | |
method move() { | |
@!tail.unshift: $!head; | |
$!head += $!dir; | |
} | |
method draw-head() { | |
print color('yellow'); | |
T.beep(.x, .y) with $!head; | |
print color('green'); | |
T.beep(.x, .y) with @!tail.head; | |
print color('reset'); | |
} | |
method chop-tail() { | |
T.boop(.x, .y) with @!tail.pop | |
} | |
method display() { | |
self.draw-head; | |
if @!tail ≥ $!length { | |
self.chop-tail | |
} | |
} | |
method hit-wall() { | |
!(0 < $!head.x < ROWS + 1 && | |
0 < $!head.y < COLS + 1 ) | |
} | |
method hit-tail() { | |
@!tail.first($!head) | |
} | |
method hide() { | |
for $!head, |@!tail { | |
T.boop(.x, .y); | |
} | |
} | |
method show() { | |
print color('yellow'); | |
T.beep(.x, .y) with $!head; | |
print color('green'); | |
for @!tail { | |
T.beep(.x, .y) | |
} | |
} | |
method show-death() { | |
sleep 0.5; | |
for @!tail { | |
T.boop(.x, .y); | |
sleep 0.025; | |
} | |
} | |
} | |
my $snake = Snake.new; | |
my $delay = 0.125; | |
sub random-nibble() { | |
state @points = ((1 ..^ COLS - 1) X (1 ..^ ROWS - 1)).map: -> ($x, $y) { | |
point($x, $y) | |
} | |
my $nibble = @points.pick(*).first: -> $p { | |
$p ∉ ($snake.head, |$snake.tail) | |
} | |
print color('red'); | |
T.beep($nibble.x, $nibble.y); | |
return $nibble; | |
} | |
my $score = 0; | |
sub draw-score() { | |
print color('black on_cyan'); | |
T.scr.print-string(18, ROWS + 1, " SCORE: $score.fmt('%3d') ") | |
} | |
draw-score(); | |
my @input; | |
my $key-pressed = raw-input-supply(); | |
my $tap = $key-pressed.tap: -> $k { | |
@input.push: $k; | |
} | |
sub flush-and-await-key() { | |
@input = (); | |
loop { | |
if @input.shift -> $k { | |
if $k.ord == 3 { die } | |
return $k | |
} | |
sleep 0.02 | |
} | |
} | |
sub process-input() { | |
loop { | |
if @input.shift -> $k is copy { | |
if $k.ord == 3 { die } | |
if $k.ord == 27 { | |
$k ~= @input.splice(0, 2).join | |
} | |
if $k eq 'p' { | |
pause() | |
} | |
if $k eq '=' { $delay += 0.01 } | |
if $k eq '-' { $delay -= 0.01 } | |
$snake.process-input($k) or next | |
} | |
last | |
} | |
} | |
flush-and-await-key; | |
print color('reset'); | |
T.scr.print-string(4, 5, title-text(:clear)); | |
my $nibble = random-nibble(); | |
sub pause() { | |
$snake.hide(); | |
T.boop(.x, .y) with $nibble; | |
print color('magenta'); | |
T.scr.print-string(4, 5, pause-text()); | |
flush-and-await-key(); | |
T.scr.print-string(4, 5, pause-text(:clear)); | |
print color('red'); | |
T.beep(.x, .y) with $nibble; | |
$snake.show(); | |
} | |
sub game-over() { | |
T.boop(.x, .y) with $nibble; | |
$snake.show-death; | |
print color('magenta'); | |
T.scr.print-string(6, 5, end-text()); | |
print color('reset'); | |
flush-and-await-key(); | |
T.scr.print-string(6, 5, end-text(:clear)); | |
} | |
sub play-again() { | |
print color('magenta'); | |
T.scr.print-string(6, 3, play-again-text()); | |
loop { | |
given flush-and-await-key().lc { | |
when 'n' { return False } | |
when 'y' { | |
T.scr.print-string(6, 3, play-again-text(:clear)); | |
$score = 0; | |
draw-score(); | |
$snake = Snake.new; | |
$nibble = random-nibble(); | |
return True | |
} | |
} | |
} | |
} | |
sub eat-nibble() { | |
$score += 1; | |
$delay -= 0.0001; | |
$snake.length += 1; | |
$nibble = random-nibble(); | |
draw-score(); | |
} | |
sub update-state() { | |
$snake.move; | |
if T.scr.grid[$snake.head.y][$snake.head.x × 2] eq ' ' { | |
return True; | |
} | |
if $snake.head ~~ $nibble { | |
eat-nibble(); | |
return True; | |
} | |
game-over(); | |
return False; | |
} | |
sub draw-state() { | |
$snake.display() | |
} | |
print color('reset'); | |
loop { | |
loop { | |
process-input(); | |
update-state() or last; | |
draw-state(); | |
sleep $delay; | |
} | |
play-again() or last | |
} | |
CATCH { | |
default { | |
exit | |
} | |
} | |
$tap.close; | |
multi sub trait_mod:<is>(Routine $func, :$clearable) { | |
$func.wrap: -> :$clear { | |
$clear ?? callwith().trans(/\S/ => ' ') !! callwith() | |
} | |
} | |
sub title-text() is clearable { | |
q:to<END> | |
██████╗███╗ ██╗ █████╗ ██╗ ██╗███████╗ | |
██╔════╝████╗ ██║██╔══██╗██║ ██╔╝██╔════╝ | |
╚█████╗ ██╔██╗██║███████║█████═╝ █████╗ | |
╚═══██╗██║╚████║██╔══██║██╔═██╗ ██╔══╝ | |
██████╔╝██║ ╚███║██║ ██║██║ ╚██╗███████╗ | |
╚═════╝ ╚═╝ ╚══╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ | |
END | |
} | |
sub pause-text() is clearable { | |
q:to<END> | |
██████╗ █████╗ ██╗ ██╗ ██████╗███████╗ | |
██╔══██╗██╔══██╗██║ ██║██╔════╝██╔════╝ | |
██████╔╝███████║██║ ██║╚█████╗ █████╗ | |
██╔═══╝ ██╔══██║██║ ██║ ╚═══██╗██╔══╝ | |
██║ ██║ ██║╚██████╔╝██████╔╝███████╗ | |
╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ | |
END | |
} | |
sub end-text() is clearable { | |
q:to<END> | |
██████╗ █████╗ ███╗ ███╗███████╗ | |
██╔════╝ ██╔══██╗████╗ ████║██╔════╝ | |
██║ ██╗ ███████║██╔████╔██║█████╗ | |
██║ ╚██╗██╔══██║██║╚██╔╝██║██╔══╝ | |
╚██████╔╝██║ ██║██║ ╚═╝ ██║███████╗ | |
╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ | |
█████╗ ██╗ ██╗███████╗██████╗ | |
██╔══██╗██║ ██║██╔════╝██╔══██╗ | |
██║ ██║╚██╗ ██╔╝█████╗ ██████╔╝ | |
██║ ██║ ╚████╔╝ ██╔══╝ ██╔══██╗ | |
╚█████╔╝ ╚██╔╝ ███████╗██║ ██║ | |
╚════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ | |
END | |
} | |
sub play-again-text() is clearable { | |
q:to<END> | |
██████╗ ██╗ █████╗ ██╗ ██╗ | |
██╔══██╗██║ ██╔══██╗╚██╗ ██╔╝ | |
██████╔╝██║ ███████║ ╚████╔╝ | |
██╔═══╝ ██║ ██╔══██║ ╚██╔╝ | |
██║ ███████╗██║ ██║ ██║ | |
╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ | |
█████╗ ██████╗ █████╗ ██╗███╗ ██╗ | |
██╔══██╗██╔════╝ ██╔══██╗██║████╗ ██║ | |
███████║██║ ██╗ ███████║██║██╔██╗██║ | |
██╔══██║██║ ╚██╗██╔══██║██║██║╚████║ | |
██║ ██║╚██████╔╝██║ ██║██║██║ ╚███║ | |
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚══╝ | |
██╗ ██╗ ██╗███╗ ██╗ | |
╚██╗ ██╔╝ ██╔╝████╗ ██║ | |
╚████╔╝ ██╔╝ ██╔██╗██║ | |
╚██╔╝ ██╔╝ ██║╚████║ | |
██║ ██╔╝ ██║ ╚███║ | |
╚═╝ ╚═╝ ╚═╝ ╚══╝ | |
END | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment