Skip to content

Instantly share code, notes, and snippets.

@ab5tract
Created November 16, 2023 18:26
Show Gist options
  • Save ab5tract/d71b8e2f449a27ba2b709202ac08cc4c to your computer and use it in GitHub Desktop.
Save ab5tract/d71b8e2f449a27ba2b709202ac08cc4c to your computer and use it in GitHub Desktop.
Tetris in 228 un-golfed lines of Raku
use Raylib::Bindings;
constant screen-width = 500;
constant screen-height = 620;
constant drop-speed = 0.333;
constant cell-size = 30;
constant num-rows = 20;
constant num-columns = 10;
constant coordinates = [ ^num-rows X ^num-columns ];
enum Colors <FG BG L J I O S T Z Text>;
constant rgbs = [(243,243,243), (218,220,228), (150,252,181), (252,151,222), (252,151,172), (222,252,151), (182,151,252), (151,252,232), (126,214,251), (51,51,53)];
constant colors = [ rgbs.map: { Color.init: |$_, 255 } ];
constant score-rectangle = Rectangle.init(320e0, 55e0, 170e0, 60e0);
constant next-rectangle = Rectangle.init(320e0, 215e0, 170e0, 180e0);
constant scores = [0, 100, 300, 500, 1000];
constant game-over-pos-x = ((125 - measure-text("GAME OVER", 32))/2).Int;
class Grid {
has @.grid = [0 xx num-columns] xx num-rows;
method is-cell-overlapping($row, $column) { so @!grid[$row][$column] }
method is-cell-outside($row, $column) { !(($row >= 0 && $row < num-rows) && ($column >= 0 && $column < num-columns)) }
method is-row-full($row) { [&&] |@!grid[$row] }
method clear-row($row --> True) { @!grid[$row][$_] = 0 for ^num-columns }
method clear-grid { $.clear-row($_) for ^num-rows }
method move-row-down($row, $num-rows) {
for ^num-columns -> $column {
@!grid[$row + $num-rows][$column] = @!grid[$row][$column];
@!grid[$row][$column] = 0;
}
}
method clear-full-rows {
my Int $completed = 0;
for (^num-rows).reverse -> $row {
if $.is-row-full($row) {
$.clear-row($row);
$completed++;
} elsif $completed > 0 {
self.move-row-down($row, $completed);
}
}
$completed
}
method draw {
for coordinates -> ($row, $column) {
draw-rectangle($column * cell-size+11, $row * cell-size+11, cell-size-1, cell-size-1, colors[ @!grid[$row][$column] ]);
}
}
}
role Block {
has Int $.id;
has Int $.rotation-state = 0;
has Int $!row-offset = 0;
has Int $!column-offset = 0;
method cells { ... }
method cell-positions { |$.cells[$!rotation-state].map: -> ($r, $c) { ($r+$!row-offset, $c+$!column-offset) } }
method rotate { $!rotation-state = 0 if ++$!rotation-state > self.cells-1 }
method undo-rotation { $!rotation-state = self.cells-1 if --$!rotation-state < 0 }
method move($rows, $columns --> True) { $!row-offset += $rows; $!column-offset += $columns }
method draw($offset-x = 11, $offset-y = 11) {
for self.cell-positions -> ($row, $column) {
draw-rectangle(($column * cell-size)+$offset-x, ($row * cell-size)+$offset-y, cell-size-1, cell-size-1, colors[$!id])
}
}
}
class Tetroid::L does Block {
method new() { self.bless: :id(Colors::L) }
submethod TWEAK { self.move(0,3) }
method cells { BEGIN [(0,2), (1,0), (1,1), (1,2)], [(0,1), (1,1), (2,1), (2,2)], [(1,0), (1,1), (1,2), (2,0)], [(0,0), (0,1), (1,1), (2,1)] }
}
class Tetroid::J does Block {
method new() { self.bless: :id(Colors::J) }
submethod TWEAK { self.move(0,3) }
method cells { BEGIN [(0,0), (1,0), (1,1), (1,2)], [(0,1), (0,2), (1,1), (2,1)], [(1,0), (1,1), (1,2), (2,2)], [(0,1), (1,1), (2,0), (2,1)] }
}
class Tetroid::I does Block {
method new { self.bless: :id(Colors::I) }
submethod TWEAK { self.move(-1,3) }
method cells { BEGIN [(1,0), (1,1), (1,2), (1,3)], [(0,2), (1,2), (2,2), (3,2)], [(2,0), (2,1), (2,2), (2,3)], [(0,1), (1,1), (2,1), (3,1)] }
}
class Tetroid::O does Block {
method new { self.bless: :id(Colors::O) }
submethod TWEAK { self.move(0,4) }
method cells { BEGIN [(0,0), (0,1), (1,0), (1,1)], [(0,0), (0,1), (1,0), (1,1)], [(0,0), (0,1), (1,0), (1,1)], [(0,0), (0,1), (1,0), (1,1)] }
}
class Tetroid::S does Block {
method new { self.bless: :id(Colors::S) }
submethod TWEAK { self.move(0,3) }
method cells { [(0,1), (0,2), (1,0), (1,1)], [(0,1), (1,1), (1,2), (2,2)], [(1,1), (1,2), (2,0), (2,1)], [(0,0), (1,0), (1,1), (2,1)] }
}
class Tetroid::T does Block {
method new { self.bless: :id(Colors::T) }
submethod TWEAK { self.move(0,3) }
method cells { [(0,1), (1,0), (1,1), (1,2)], [(0,1), (1,1), (1,2), (2,1)], [(1,0), (1,1), (1,2), (2,1)], [(0,1), (1,0), (1,1), (2,1)] }
}
class Tetroid::Z does Block {
method new { self.bless: :id(Colors::Z) }
submethod TWEAK { self.move(0,3) }
method cells { [(0,0), (0,1), (1,1), (1,2)], [(0,2), (1,1), (1,2), (2,1)], [(1,0), (1,1), (2,1), (2,2)], [(0,1), (1,0), (1,1), (2,0)] }
}
class Game {
has Grid $.grid = Grid.new;
has Int $.score = 0;
has Bool $.game-over is rw = False;
has Bool $.schedule-drop is rw = False;
has $!current-block = self!random-block;
has $!next-block = self!random-block;
has $!drop-supply = Supply.interval(drop-speed);
has Bool $!initial-run = True;
method start-drop-supply($SELF:) {
$!drop-supply.act: { $SELF.schedule-drop = True unless $!initial-run };
start { sleep (drop-speed * 2)-0.001; $!initial-run = False }
}
method restart {
$!initial-run = True;
$!schedule-drop = False;
$!grid.clear-grid;
$!game-over = False;
$!score = 0;
self.start-drop-supply;
}
method handle-input {
self.restart if $!game-over && get-key-pressed != 0;
self!rotate-block if is-key-pressed(KEY_UP) || is-key-pressed(KEY_Z);
self!move-block-left if is-key-pressed(KEY_LEFT);
self!move-block-right if is-key-pressed(KEY_RIGHT);
self!move-block-down if is-key-pressed(KEY_DOWN);
}
method draw {
$!grid.draw;
$!current-block.draw;
self!draw-ui;
$!next-block.draw(270, 270);
}
method drop-block-down {
$!schedule-drop = False if $!schedule-drop;
self!move-block-down
}
method !draw-ui {
state $color-fg = colors[Colors::FG];
state $color-text = colors[Colors::Text];
draw-text("GAME OVER", game-over-pos-x, 300, 32, $color-text) if $!game-over;
draw-text("Score", 364, 15, 32, $color-text);
draw-rectangle-rounded(score-rectangle, 0.3e0, 6, $color-fg);
my $size = measure-text(~$!score, 32);
draw-text(~$!score, (320 + (170 - $size)/2).Int, 75, 32, $color-text);
draw-text("Next", 373, 175, 32, $color-text);
draw-rectangle-rounded(next-rectangle, 0.3e0, 6, $color-fg);
}
# Private
method !blocks { (Tetroid::L, Tetroid::J, Tetroid::I, Tetroid::O, Tetroid::T, Tetroid::Z, Tetroid::S)>>.new }
method !random-block {
state @batch ||= self!blocks.pick(*);
@batch.pop
}
method !move-block-left {
return if $!game-over;
$!current-block.move(0,-1);
$!current-block.move(0,1) if self!is-block-outside || self!is-overlap;
}
method !move-block-right {
return if $!game-over;
$!current-block.move(0,1);
$!current-block.move(0,-1) if self!is-block-outside || self!is-overlap;
}
method !move-block-down {
return if $!game-over;
$!current-block.move(1,0);
$!current-block.move(-1,0) && self!lock-box if self!is-block-outside || self!is-overlap;
}
method !rotate-block {
return if $!game-over;
$!current-block.rotate;
$!current-block.undo-rotation if self!is-block-outside || self!is-overlap
}
method !is-block-outside { [||] $!current-block.cell-positions.map: -> ($r,$c) { $!grid.is-cell-outside($r,$c) } }
method !is-overlap { [||] $!current-block.cell-positions.map: -> ($r,$c) { $!grid.is-cell-overlapping($r,$c)} }
method !update-score($cleared-rows) { $!score += 1 + scores[$cleared-rows] }
method !lock-box {
$!current-block.cell-positions.map: -> ($r,$c) { $!grid.grid[$r][$c] = $!current-block.id };
$!current-block = $!next-block;
$!game-over = True if self!is-overlap;
$!next-block = self!random-block;
my $cleared-rows = $!grid.clear-full-rows;
self!update-score($cleared-rows);
}
}
my Game $game = Game.new;
$game.start-drop-supply;
init-window(screen-width, screen-height, "Tetrix");
set-target-fps(60);
while !window-should-close {
$game.handle-input;
$game.drop-block-down if $game.schedule-drop;
begin-drawing;
clear-background(colors[Colors::BG]);
$game.draw;
end-drawing;
}
close-window;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment