diff options
author | vamsee <vamsee.somasi@gmail.com> | 2021-11-19 16:46:51 +0530 |
---|---|---|
committer | vamsee <vamsee.somasi@gmail.com> | 2021-11-19 16:46:51 +0530 |
commit | 80c7c780cd74504673a131220c5ab312c806498b (patch) | |
tree | 55e08ad76c8de522c6584c33d51a7bf57478d51b | |
parent | 2e45d29d4519fff0615b719d99c52e787c3f4a34 (diff) | |
parent | b1259dc7b9aba4831cd61bf3521708ec3089ed3c (diff) | |
download | fornax-80c7c780cd74504673a131220c5ab312c806498b.tar.gz |
Merge branch 'main' of https://github.com/andinus/fornax
-rw-r--r-- | META6.json | 5 | ||||
-rw-r--r-- | README | 142 | ||||
-rw-r--r-- | README.org | 98 | ||||
-rw-r--r-- | algorithms/raku/DFS.raku | 124 | ||||
-rw-r--r-- | lib/Fornax/CLI.rakumod | 135 | ||||
-rw-r--r-- | lib/Fornax/GenerateFrame.rakumod | 100 | ||||
-rw-r--r-- | resources/input/06 | 18 | ||||
-rw-r--r-- | t/00-basic.rakutest | 3 |
8 files changed, 435 insertions, 190 deletions
diff --git a/META6.json b/META6.json index 1cf95de..3fc67d1 100644 --- a/META6.json +++ b/META6.json @@ -1,14 +1,15 @@ { "name" : "fornax", "auth" : "zef:andinus", - "version" : "0.1.0", + "version" : "0.2.0", "description" : "Collection of tools to visualize Path Finding Algorithms", "authors" : [ "Andinus <andinus@nand.sh>" ], "license" : "ISC", "perl" : "6.d", "provides" : { "Fornax::CLI" : "lib/Fornax/CLI.rakumod", - "Fornax::Hex2RGB" : "lib/Fornax/Hex2RGB.rakumod" + "Fornax::Hex2RGB" : "lib/Fornax/Hex2RGB.rakumod", + "Fornax::GenerateFrame" : "lib/Fornax/GenerateFrame.rakumod" }, "depends" : [ "Cairo:ver<0.2.7+>" diff --git a/README b/README index 76c9420..af6327f 100644 --- a/README +++ b/README @@ -11,11 +11,18 @@ Table of Contents ───────────────── 1. Demo -2. Installation -3. Documentation -4. Project Structure -5. Fornax Format +2. Usage +3. Installation +.. 1. Release +.. 2. From Source +4. Documentation +.. 1. Options +.. 2. Fornax Format +.. 3. Project Structure +5. Bugs 6. News +.. 1. v0.1.1 - 2021-11-16 +.. 2. v0.1.0 - 2021-11-03 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -56,7 +63,19 @@ Writings: <https://andinus.unfla.me/resources/projects/fornax/2021-11-16-DFS-60.mp4> -2 Installation +2 Usage +═══════ + + ┌──── + │ # Solve the maze. + │ raku algorithms/raku/DFS.raku resources/input/06 > /tmp/solution.fornax + │ + │ # Visualize the solution. + │ raku -Ilib bin/fornax /tmp/solution.fornax + └──── + + +3 Installation ══════════════ `fornax' is written in Raku, it can be installed with `zef'. You can @@ -66,7 +85,7 @@ Writings: • *Note*: `Cairo' module & `ffmpeg' program is required. -2.1 Release +3.1 Release ─────────── 1. Run `zef install 'fornax:auth<zef:andinus>'' @@ -77,14 +96,14 @@ Writings: get them from this repository. -2.2 From Source +3.2 From Source ─────────────── You can either download the release archive generated by cgit/GitHub or clone the project if you have `git' installed. -2.2.1 Without `git' +3.2.1 Without `git' ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ 1. Download the release: @@ -94,7 +113,7 @@ Writings: 3. Run `zef install .' in source directory. -2.2.2 With `git' +3.2.2 With `git' ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ All commits by /Andinus/ will be signed by this [PGP Key]. @@ -113,7 +132,7 @@ Writings: <https://andinus.nand.sh/static/D9AE4AEEE1F1B3598E81D9DFB67D55D482A799FD.asc> -3 Documentation +4 Documentation ═══════════════ Fornax parses /Fornax format/, generates a `PNG' for each iteration @@ -122,41 +141,60 @@ Writings: • Solved paths are highlighted if the iteration is preceded by `|'. • Illegal paths are highlighted if the iteration is preceded by `!'. - • *Note*: If the number of iterations are greater than an 8 digit - number then the slideshow might be incorrect. - -3.1 Options +4.1 Options ─────────── • `input': This takes solved input file in the /Fornax/ format. - • `frame-rate': Frame rate for the video. - • `output': Output directory (for solution video/images). + • `fps': Frame rate for the video solution. + • `skip-video': Skip generating the video solution. + • `batch': Number of iterations to process at once. -4 Project Structure -═══════════════════ +4.2 Fornax Format +───────────────── - • Algorithms are located in `algorithms/' directory, sub-directory - needs to be created for programming languages which will hold the - actual source. + Fornax format defines 2 formats: + • Maze (input) + • Solution (output) - • Sample solutions can be found in `resources/solutions/' directory. - • *Note*: Some solutions might output illegal moves (like walking - over blocked path), this error is only in visualization, the - solution is correct. +4.2.1 Grids +╌╌╌╌╌╌╌╌╌╌╌ - This has been fixed in commit - `8cef86f0eb8b46b0ed2d7c37fa216890300249f6'. + A grid is printed for every iteration. Grids are composed of cells. + + ━━━━━━━━━━━━━━━━━━━━━━━━━━ + Cell Symbol + ────────────────────────── + Path `.' + Blocked `#' + Start `^' + Destination `$' + ────────────────────────── + Visited `-' + Current Path `~' + Current Position `@' + ━━━━━━━━━━━━━━━━━━━━━━━━━━ -5 Fornax Format -═══════════════ +4.2.2 Maze (input) +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + Maze input must be in this format: + ┌──── + │ ...rows + └──── + + It is upto the program to infer the number of rows & columns from the + input file or it ask the user. - Fornax format is an intermediate output file generated after solving - the maze. Algorithms must output the solution in this format. +4.2.3 Solution (output) +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + Fornax solution format is an intermediate output file generated after + solving the maze. Algorithms must output the solution in this format: ┌──── │ rows:<number of rows> cols:<number of columns> │ @@ -182,26 +220,34 @@ Writings: • First iteration is assumed to be the maze. -5.1 Grids -───────── +4.3 Project Structure +───────────────────── - A grid is printed for every iteration. Grids are composed of cells. + • Algorithms are located in `algorithms/' directory, sub-directory + needs to be created for programming languages which will hold the + actual source. - ━━━━━━━━━━━━━━━━━━━━━━━━━━ - Cell Symbol - ────────────────────────── - Path `.' - Blocked `#' - Start `^' - Destination `$' - ────────────────────────── - Visited `-' - Current Path `~' - Current Position `@' - ━━━━━━━━━━━━━━━━━━━━━━━━━━ + • Sample solutions can be found in `resources/solutions/' directory. + + • *Note*: Some solutions might output illegal moves (like walking + over blocked path), this error is only in visualization, the + solution is correct. + + This has been fixed in commit + `8cef86f0eb8b46b0ed2d7c37fa216890300249f6'. + + +5 Bugs +══════ + + • If the number of iterations are greater than an 8 digit number then + the slideshow might be incorrect. + + • `/tmp' is assumed to exist. - • /Current Position/ is prioritized over /Blocked/ & /Destination/ - symbol if it makes sense. + • Might panic with: `MoarVM oops: MVM_str_hash_entry_size called with + a stale hashtable pointer'. This has been fixed: + <https://github.com/rakudo/rakudo/pull/4634>. 6 News diff --git a/README.org b/README.org index 20a8b0b..ec8d3f0 100644 --- a/README.org +++ b/README.org @@ -1,7 +1,8 @@ #+title: Fornax #+subtitle: Collection of tools to visualize Path Finding Algorithms #+export_file_name: index -#+options: toc:1 +#+options: toc:2 +#+startup: overview #+setupfile: ~/.emacs.d/org-templates/projects.org | Website | https://andinus.unfla.me/projects/fornax | @@ -32,6 +33,16 @@ Fornax v0.1.0: - DFS-51-incomplete (upto 4,000 frames; 120 fps): https://diode.zone/w/hWWaw8uKHCP5weUP5WWArD +* Usage + +#+begin_src sh +# Solve the maze. +raku algorithms/raku/DFS.raku resources/input/06 > /tmp/solution.fornax + +# Visualize the solution. +raku -Ilib bin/fornax /tmp/solution.fornax +#+end_src + * Installation ~fornax~ is written in Raku, it can be installed with ~zef~. You can also @@ -83,35 +94,48 @@ later converted to a slideshow with ~ffmpeg~. - Solved paths are highlighted if the iteration is preceded by ~|~. - Illegal paths are highlighted if the iteration is preceded by ~!~. -- *Note*: If the number of iterations are greater than an 8 digit number - then the slideshow might be incorrect. - ** Options - ~input~: This takes solved input file in the /Fornax/ format. -- ~frame-rate~: Frame rate for the video. -- ~output~: Output directory (for solution video/images). +- ~fps~: Frame rate for the video solution. +- ~skip-video~: Skip generating the video solution. +- ~batch~: Number of iterations to process at once. -* Project Structure +** Fornax Format -- Algorithms are located in ~algorithms/~ directory, sub-directory needs - to be created for programming languages which will hold the actual - source. +Fornax format defines 2 formats: +- Maze (input) +- Solution (output) -- Sample solutions can be found in ~resources/solutions/~ directory. +*** Grids - - *Note*: Some solutions might output illegal moves (like walking over - blocked path), this error is only in visualization, the solution is - correct. +A grid is printed for every iteration. Grids are composed of cells. - This has been fixed in commit - ~8cef86f0eb8b46b0ed2d7c37fa216890300249f6~. +| Cell | Symbol | +|------------------+--------| +| Path | ~.~ | +| Blocked | ~#~ | +| Start | ~^~ | +| Destination | ~$~ | +|------------------+--------| +| Visited | ~-~ | +| Current Path | ~~~ | +| Current Position | ~@~ | -* Fornax Format +*** Maze (input) -Fornax format is an intermediate output file generated after solving the -maze. Algorithms must output the solution in this format. +Maze input must be in this format: +#+begin_src +...rows +#+end_src +It is upto the program to infer the number of rows & columns from the +input file or it ask the user. + +*** Solution (output) + +Fornax solution format is an intermediate output file generated after +solving the maze. Algorithms must output the solution in this format: #+begin_src rows:<number of rows> cols:<number of columns> @@ -135,23 +159,31 @@ columns is known, the whole grid should be printed in a single line. - First iteration is assumed to be the maze. -** Grids +** Project Structure -A grid is printed for every iteration. Grids are composed of cells. +- Algorithms are located in ~algorithms/~ directory, sub-directory needs + to be created for programming languages which will hold the actual + source. -| Cell | Symbol | -|------------------+--------| -| Path | ~.~ | -| Blocked | ~#~ | -| Start | ~^~ | -| Destination | ~$~ | -|------------------+--------| -| Visited | ~-~ | -| Current Path | ~~~ | -| Current Position | ~@~ | +- Sample solutions can be found in ~resources/solutions/~ directory. + + - *Note*: Some solutions might output illegal moves (like walking over + blocked path), this error is only in visualization, the solution is + correct. + + This has been fixed in commit + ~8cef86f0eb8b46b0ed2d7c37fa216890300249f6~. + +* Bugs + +- If the number of iterations are greater than an 8 digit number then + the slideshow might be incorrect. + +- ~/tmp~ is assumed to exist. -- /Current Position/ is prioritized over /Blocked/ & /Destination/ symbol if - it makes sense. +- Might panic with: ~MoarVM oops: MVM_str_hash_entry_size called with a + stale hashtable pointer~. This has been fixed: + https://github.com/rakudo/rakudo/pull/4634. * News diff --git a/algorithms/raku/DFS.raku b/algorithms/raku/DFS.raku new file mode 100644 index 0000000..0cc7207 --- /dev/null +++ b/algorithms/raku/DFS.raku @@ -0,0 +1,124 @@ +subset File of Str where *.IO.f; + +# Cells as defined by fornax format. +constant $PATH = '.'; +constant $BLOK = '#'; +constant $DEST = '$'; +constant $STRT = '^'; +constant $VIS = '-'; +constant $CUR = '@'; +constant $CURPATH = '~'; + +sub MAIN(File $input) { + my @maze = $input.IO.lines.map(*.comb); + die "Inconsistent maze" unless [==] @maze.map(*.elems); + + put "rows:{@maze.elems} cols:{@maze[0].elems}"; + dfs(@maze, 0, 0); +} + +sub dfs( + @maze, Int $y, Int $x, @visited?, @cur-path? --> Bool +) { + # If @visited was not passed then mark the given cell as visited + # because it's the cell we're starting at. + @visited[$y][$x] = True unless @visited; + @cur-path[$y][$x] = True unless @cur-path; + + # neighbor block loops over the neighbors of $y, $x. + neighbor: for neighbors(@maze, $y, $x).List.pick(*) -> $pos { + # Move on to next neighbor if we've already visited this one. + next neighbor if @visited[$pos[0]][$pos[1]]; + + # Printing Marker cells. + given @maze[$pos[0]][$pos[1]] { + when $DEST { print "|" } + when $BLOK { print "!" } + } + + # Print the maze on every iteration. + for 0..@maze.end -> $j { + for 0..@maze[0].end -> $k { + if @maze[$j][$k] eq $STRT | $DEST { + print @maze[$j][$k]; + } else { + if $j == $pos[0] and $k == $pos[1] { + print "@"; + } else { + print( + @cur-path[$j][$k] + ?? "~" !! @visited[$j][$k] ?? "-" !! @maze[$j][$k] + ); + } + } + } + } + print "\n"; + + given @maze[$pos[0]][$pos[1]] { + when $DEST { exit; } + when $PATH|$STRT { + @visited[$pos[0]][$pos[1]] = @cur-path[$pos[0]][$pos[1]] = True; + dfs(@maze, $pos[0], $pos[1], @visited, @cur-path); + @cur-path[$pos[0]][$pos[1]] = False; + } + } + } +} + +# neighbors returns the neighbors of given index. Neighbors are cached +# in @neighbors array. This way we don't have to compute them +# everytime neighbors subroutine is called for the same position. We +# don't need this caching here since every cell will be visited only +# once. This subroutine was taken from Octans::Neighbors. +sub neighbors( + @puzzle, Int $y, Int $x --> List +) is export { + # @directions is holding a list of directions we can move in. It's + # used later for neighbors subroutine. + state List @directions = ( + # $y, $x + ( +1, +0 ), # bottom + ( -1, +0 ), # top + ( +0, +1 ), # left + ( +0, -1 ), # right + ); + + # @neighbors holds the neighbors of given position. + state Array @neighbors; + + if @puzzle[$y][$x] { + # Don't re-compute neighbors. + unless @neighbors[$y][$x] { + # Set it to an empty array because otherwise if it has no + # neighbors then it would've be recomputed everytime + # neighbors() was called. + @neighbors[$y][$x] = []; + + my Int $pos-x; + my Int $pos-y; + + # Starting from the intital position of $y, $x we move to + # each direction according to the values specified in + # @directions array. In this case we're just trying to + # move in 4 directions (top, bottom, left & right). + direction: for @directions -> $direction { + $pos-y = $y + $direction[0]; + $pos-x = $x + $direction[1]; + + # If movement in this direction is out of puzzle grid + # boundary then move on to next direction. + next direction unless @puzzle[$pos-y][$pos-x]; + + # If neighbors exist in this direction then add them + # to @neighbors[$y][$x] array. + push @neighbors[$y][$x], [$pos-y, $pos-x]; + } + } + } else { + # If it's out of boundary then return no neighbor. + @neighbors[$y][$x] = []; + } + + return @neighbors[$y][$x]; +} diff --git a/lib/Fornax/CLI.rakumod b/lib/Fornax/CLI.rakumod index 35109b7..98f20a7 100644 --- a/lib/Fornax/CLI.rakumod +++ b/lib/Fornax/CLI.rakumod @@ -1,5 +1,4 @@ -use Cairo; -use Fornax::Hex2RGB; +use Fornax::GenerateFrame; subset File of Str where *.IO.f; @@ -15,52 +14,26 @@ proto MAIN(|) is export { unless so @*ARGS { put $*USAGE; exit }; {*} } #| Collection of tools to visualize Path Finding Algorithms multi sub MAIN( File $input, #= fornax format file (solved) - IO() :$out = '/tmp', #= output directory (default: /tmp) - Int() :$batch = 4, #= batch size (generate frames in parallel) - Rat() :$frame-rate = 1, #= frame rate (default: 1) + + Int() :$batch = 4, #= number of iterations to process at once (default: 4) + Int() :fps($frame-rate) = 1, #= frame rate for video solution (default: 1) Bool :$skip-video, #= skip video solution - Bool :$verbose = True, #= verbosity + Bool :$debug, #= debug logs ) is export { my IO() $output = "%s/fornax-%s".sprintf( - $out.absolute, ('a'...'z', 'A'...'Z', 0...9).roll(8).join + '/tmp', ('a'...'z', 'A'...'Z', 0...9).roll(8).join ); mkdir $output; die "Output directory doesn't exist" unless $output.d; - put "[fornax] Output: '$output'" if $verbose; + put "[fornax] Output: '$output'"; my Str @lines = $input.IO.lines; my Int() %meta{Str} = Metadata.parse(@lines.first).Hash - or die "Cannot parse metadata"; - - # Cells as defined by fornax format. - constant $PATH = '.'; - constant $BLOK = '#'; - constant $DEST = '$'; - constant $STRT = '^'; - constant $VIS = '-'; - constant $CUR = '@'; - constant $CURPATH = '~'; + or die "Cannot parse metadata"; constant %CANVAS = :1920width, :1080height; - # Colors. - constant %C = ( - bg-main => "#ffffff", - - red-subtle-bg => "#f2b0a2", - blue-subtle-bg => "#b5d0ff", - cyan-subtle-bg => "#c0efff", - green-subtle-bg => "#aecf90", - - fg-main => "#000000", - - fg-special-cold => "#093060", - fg-special-warm => "#5d3026", - fg-special-mild => "#184034", - fg-special-calm => "#61284f", - ).map: {.key => hex2rgb(.value)}; - # Every cell must be square. Get the maximum width, height and use # that to decide which is to be used. my Int %cell = width => %CANVAS<width> div %meta<cols>, @@ -79,7 +52,8 @@ multi sub MAIN( $side = %cell<height>; } - enum IterStatus <Walking Blocked Completed>; + my $render-start = now; + my Int $total-frames = @lines.elems - 1; my Promise @p; for @lines.skip.kv -> $idx, $iter is rw { @@ -88,89 +62,38 @@ multi sub MAIN( if @p.elems == $batch { await @p; @p = []; + + print "\r"; + print "%s Remaining: %.2fs Elapsed: %.2fs %s".sprintf( + "[fornax $idx/$total-frames]", + ((now - $render-start) / $idx) * ($total-frames - $idx), + now - $render-start, " ", + ); } push @p, start { - my IterStatus $status; - given $iter.substr(0, 1) { - when '|' { $status = Completed } - when '!' { $status = Blocked } - default { $status = Walking } - }; - - # Remove marker. - $iter .= substr(1) if $status == Completed|Blocked; - - put "[fornax] $idx $iter $status" if $verbose; - - my @grid = $iter.comb.rotor: %meta<cols>; - warn "Invalid grid: $idx $iter $status" unless @grid.elems == %meta<rows>; - - given Cairo::Image.create( - Cairo::FORMAT_ARGB32, %CANVAS<width>, %CANVAS<height> - ) { - given Cairo::Context.new($_) { - # Paint the entire canvas white. - .rgb: |%C<bg-main>; - .rectangle(0, 0, %CANVAS<width>, %CANVAS<height>); - .fill; - - for ^%meta<rows> -> $r { - for ^%meta<cols> -> $c { - my Int @target = %excess<width> div 2 + $c * $side, - %excess<height> div 2 + $r * $side, - $side, $side; - - .rectangle: |@target; - - given @grid[$r][$c] -> $cell { - # According to the format, current - # position may be prioritized over - # Destination symbol so we colorize it - # according to $status. - when $cell eq $CUR { - .rgba: |%C<fg-special-cold>, 0.56; - .rgba: |%C<fg-special-mild>, 0.72 if $status == Completed; - .rgba: |%C<fg-special-warm>, 0.72 if $status == Blocked; - } - when $cell eq $CURPATH { - .rgba: |%C<blue-subtle-bg>, 0.84; - .rgba: |%C<green-subtle-bg>, 0.96 if $status == Completed; - .rgba: |%C<red-subtle-bg>, 0.96 if $status == Blocked; - } - when $cell eq $VIS { - .rgba: |%C<cyan-subtle-bg>, 0.72; - } - when $cell eq $BLOK { .rgba: |%C<fg-main>, 0.56 } - when $cell eq $STRT|$DEST { .rgba: |%C<fg-special-mild>, 0.72 } - default { .rgba: |%C<fg-main>, 0.08 } - } - .fill :preserve; - - .rgb: |%C<fg-main>; - .stroke; - } - } - } - .write_png("%s/%08d.png".sprintf: $output, $idx); - .finish; - } + generate-frame( + :%CANVAS, :%excess, :$side, :%meta, :$iter, :$idx, :$debug, + :out("%s/%08d.png".sprintf: $output, $idx), + ); } } # Wait for remaining jobs to finish. await @p; - put "[fornax] Generated images." if $verbose; + print "\r"; + put "[fornax] Generated $total-frames frames in %.2fs. %s".sprintf( + now - $render-start, " " x 16, + ); unless $skip-video { - put "[fornax] Creating a slideshow." if $verbose; + put "[fornax] Creating a slideshow."; - my Str $log-level = $verbose ?? "info" !! "error"; + my Str $log-level = $debug ?? "info" !! "error"; run «ffmpeg -loglevel "$log-level" -r "$frame-rate" -i "$output/\%08d.png" - -vf 'tpad=stop_mode=clone:stop_duration=4' - -vcodec libx264 -crf 28 -pix_fmt yuv420p "$output/solution.mp4"»; + -vf 'tpad=stop_mode=clone:stop_duration=2' + -vcodec libx264 -crf 24 -pix_fmt yuv420p "$output/solution.mp4"»; } - put "[fornax] Output: '$output'"; } multi sub MAIN( diff --git a/lib/Fornax/GenerateFrame.rakumod b/lib/Fornax/GenerateFrame.rakumod new file mode 100644 index 0000000..e19d342 --- /dev/null +++ b/lib/Fornax/GenerateFrame.rakumod @@ -0,0 +1,100 @@ +use Cairo; +use Fornax::Hex2RGB; + +# Cells as defined by fornax format. +constant $PATH = '.'; +constant $BLOK = '#'; +constant $DEST = '$'; +constant $STRT = '^'; +constant $VIS = '-'; +constant $CUR = '@'; +constant $CURPATH = '~'; + +# Colors. +constant %C = ( + bg-main => "#ffffff", + + red-subtle-bg => "#f2b0a2", + blue-subtle-bg => "#b5d0ff", + cyan-subtle-bg => "#c0efff", + green-subtle-bg => "#aecf90", + + fg-main => "#000000", + + fg-special-cold => "#093060", + fg-special-warm => "#5d3026", + fg-special-mild => "#184034", + fg-special-calm => "#61284f", +).map: {.key => hex2rgb(.value)}; + +enum IterStatus <Walking Blocked Completed>; + +sub generate-frame( + :%CANVAS, :$out, :%excess, :$side, :%meta, :$iter is copy + , :$idx, :$debug, +) is export { + my IterStatus $status; + given $iter.substr(0, 1) { + when '|' { $status = Completed } + when '!' { $status = Blocked } + default { $status = Walking } + }; + + # Remove marker. + $iter .= substr(1) if $status == Completed|Blocked; + + put "\n[fornax] $idx $iter $status" if $debug; + + my @grid = $iter.comb.rotor: %meta<cols>; + warn "Invalid grid: $idx $iter $status" unless @grid.elems == %meta<rows>; + + given Cairo::Image.create( + Cairo::FORMAT_ARGB32, %CANVAS<width>, %CANVAS<height> + ) { + given Cairo::Context.new($_) { + # Paint the entire canvas white. + .rgb: |%C<bg-main>; + .rectangle(0, 0, %CANVAS<width>, %CANVAS<height>); + .fill; + + # This seems to be slower than creating an intermediate + # variable and assigning from that. Difference is not much + # so we'll ignore it. + for ^%meta<rows> X ^%meta<cols> -> ($r, $c) { + my Int @target = %excess<width> div 2 + $c * $side, + %excess<height> div 2 + $r * $side, + $side, $side; + + .rectangle: |@target; + + given @grid[$r][$c] -> $cell { + # According to the format, current position may be + # prioritized over Destination symbol so we + # colorize it according to $status. + when $cell eq $CUR { + .rgba: |%C<fg-special-cold>, 0.56; + .rgba: |%C<fg-special-mild>, 0.72 if $status == Completed; + .rgba: |%C<fg-special-warm>, 0.72 if $status == Blocked; + } + when $cell eq $CURPATH { + .rgba: |%C<blue-subtle-bg>, 0.84; + .rgba: |%C<green-subtle-bg>, 0.96 if $status == Completed; + .rgba: |%C<red-subtle-bg>, 0.96 if $status == Blocked; + } + when $cell eq $VIS { + .rgba: |%C<cyan-subtle-bg>, 0.72; + } + when $cell eq $BLOK { .rgba: |%C<fg-main>, 0.56 } + when $cell eq $STRT|$DEST { .rgba: |%C<fg-special-mild>, 0.72 } + default { .rgba: |%C<fg-main>, 0.08 } + } + .fill :preserve; + + .rgb: |%C<fg-main>; + .stroke; + } + } + .write_png($out); + .finish; + } +} diff --git a/resources/input/06 b/resources/input/06 new file mode 100644 index 0000000..8e58064 --- /dev/null +++ b/resources/input/06 @@ -0,0 +1,18 @@ +^...........#................... +........#...#................... +.......#..########.............. +.......#........................ +.......#........................ +......#......................... +...#.........#....############## +...#.........#.................. +.......#.....#.....#............ +...................#............ +........#..........#............ +....#...#...##.....#............ +........#..........#...######### +...................#............ +...................#......$..... +................................ +.............#.................. +.............#.................. diff --git a/t/00-basic.rakutest b/t/00-basic.rakutest index a119847..9731395 100644 --- a/t/00-basic.rakutest +++ b/t/00-basic.rakutest @@ -1,6 +1,7 @@ use Test; -plan 2; +plan 3; use-ok 'Fornax::CLI'; use-ok 'Fornax::Hex2RGB'; +use-ok 'Fornax::GenerateFrame'; |