Created
November 16, 2023 18:26
-
-
Save ab5tract/d71b8e2f449a27ba2b709202ac08cc4c to your computer and use it in GitHub Desktop.
Tetris in 228 un-golfed lines of 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
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