#!/usr/bin/env BQN # SPDX-License-Identifier: AGPL-3.0-or-later # SPDX-FileCopyrightText: 2023 Rampoina # # Arc # The level is a 2d matrix of lists (tiles) # Each list contains the objects of the game: # 0: floor, 1: player, 2: box, 3: goal, 4: wall # 4: player on goal, 5: box on goal # # Example: ASCII: # ┌─ ┌─ # ╵ ⟨ 4 ⟩ ⟨ 4 ⟩ ⟨ 4 ⟩ ⟨ 4 ⟩ ⟨ 4 ⟩ ╵"##### # ⟨ 4 ⟩ ⟨ 1 0 ⟩ ⟨ 2 0 ⟩ ⟨ 0 ⟩ ⟨ 4 ⟩ #@*.# # ⟨ 4 ⟩ ⟨ 4 ⟩ ⟨ 4 ⟩ ⟨ 4 ⟩ ⟨ 4 ⟩ #####" # ┘ ┘ moves←⟨0‿0⟩ # list of moves, each move is a direction, we start without moving chars←" λ$⊕λ⭍#/\-|+<>^v" # legal characters movables←1‿2‿7‿8‿12‿13‿14‿15 # player, box mirrors and lasers opaque←1‿2‿3‿12‿13‿14‿15‿6 # non laser reflecting empties←0‿9‿10‿11 # floor and laser beams ansi←{ e⇐@+27 red⇐e∾"[31m" cyan⇐e∾"[36m" yellow⇐e∾"[33m" defaultB⇐e∾"[0m" } SplitOnEmpty←{𝕩⊔˜(⊢-˜+`׬)0=≠¨𝕩} Ascii2Matrix←{(⊑chars⊐𝕩)⊑⟨≍0,1‿0,2‿0,≍3,1‿3,2‿3,≍6,7‿0,8‿0,≍9,≍10,≍11,12‿0,13‿0,14‿0,15‿0⟩}¨(⊢↑˝·≍⟜¬2+≢) # 𝕨 Tiles 𝕩 | 𝕩: object coordinate (3‿1) | 𝕨: direction vector (¯1‿0) # result: ⟨ ⟨ 3 1 ⟩ ⟨ 2 1 ⟩ ⟨ 1 1 ⟩ ⟩ # returns 3 tiles in the specified direction from the Tiles←{⟨𝕩,𝕩+𝕨,𝕩+2×𝕨⟩} # given object (including itself) # Move 𝕩 | 𝕩: ⟨⟨1,0⟩,⟨0⟩⟩ (2 tiles) | result: ⟨⟨0⟩,⟨1,0⟩⟩ # Move the first object in the first tile to the second tile. # Only move Movable tile -> Empty tile # the second tile can't be a movable object because we moved it previously # if it is it means that the object was unmovable (next to a wall) so we do nothing Move←{a‿b:⟨1↓a,(⊑a)∾b⟩}⍟{∨´(⥊movables≍⌜empties)≡⌜<⊑¨𝕩} # Push 𝕩 | 𝕩: ⟨⟨1,0⟩,⟨2,0⟩,⟨0,0)⟩ (3 tiles) | result: ⟨⟨0⟩,⟨1,0⟩,⟨2,0)⟩ # Given 3 tiles try to [P]ush the second tile (possible box) # and afterwards try to move the first one (player) if possible Push←Move⌾(2↑⊢)Move⌾(1↓⊢) FindIdx←/○⥊⟜(↕≢)∘⍷ # 𝕨 Beam 𝕩 | 𝕨: level | 𝕩: position | replace positions with the laser beam Beam←{{"--++"⊏˜" -|+"⊐𝕩}⌾(𝕩⊸⊑)𝕨} # Recursive Bounce Bounce←{(w‿d)S x:{ ⊑opaque∊˜⊑w⊑x?(3=⊑w⊑x)◶⟨x,⟨5⟩˙⌾(w⊸⊑)x⟩@; # laser hits a non-mirror ⊑empties∊˜⊑w⊑x ? # Empty space ⟨w+d,d⟩S{((×⊑d)⊑⟨9‿9‿11‿11,10‿11‿10‿11⟩)⊏˜0‿9‿10‿11⊐𝕩}⌾(w⊸⊑)x; # Draw laser and recurse #⟨w+d,d⟩S{((×⊑d)⊑"--++"‿"|+|+")⊏˜" -|+"⊐𝕩}⌾(w⊸⊑)x; # Draw laser and recurse # laser hits a mirror d←⌽d×-⊸¬7=⊑w⊑x # calculate the mirror bounce direction ⟨w+d,d⟩S x } # recurse to the next position } Shoot←{𝕩 {𝕨Bounce´⌽𝕩} ∾⟨<0‿¯1,<0‿1,<¯1‿0,<1‿0⟩(⊣⋈˜¨+)¨ FindIdx⟜(⊑¨𝕩)¨ 12‿13‿14‿15} Step←{Push⌾((𝕨 Tiles ⊑1 FindIdx ⊑¨𝕩)⊸⊑)𝕩} # 𝕨 S 𝕩 | 𝕨: direction | 𝕩:level | Step the game Draw←{∾´¨<˘{𝕊'⭍':ansi.yellow∾𝕩;⊑𝕩∊"|-+"?ansi.red∾𝕩;⊑𝕩∊"\/"?ansi.cyan∾𝕩;ansi.defaultB∾𝕩}¨chars⊏˜+´¨Shoot 𝕨 Step´ ⌽𝕩} # 𝕨 Draw 𝕩 | 𝕨: levels | 𝕩: moves | Draw the game in ASCII Win←{¬∨´∨˝3=⊑¨Shoot 𝕩}# W 𝕩 | 𝕩: level | [W]in condition, no unpowered goals after shooting laser Next←{moves↩moves∾<𝕩} Undo←{𝕊:moves↩(-1<≠)⊸↓moves} # Main loop levels←Ascii2Matrix¨>¨SplitOnEmpty•FLines "levels" # Load file containing levels currentLevel←0 e←ansi.e •term.RawMode 1 # set terminal to raw mode •Out e∾"[?25l"∾e∾"[2J"∾e∾"[H" # Cursor to origin, hide it and clear screen clear←"" {𝕤 # Loop until the user wins •Out e∾"[H" # Cursor to origin •Out "Level: "∾•Repr 1+currentLevel •Out e∾"7" # Save cursor position •Out ansi.yellow∾"⭍"∾ansi.defaultB∾" Power the machines (⊕) by moving the mirrors ("∾ansi.cyan∾"\/"∾ansi.defaultB∾") " •Out "Controls: (hjkl or wasd) to move, u to undo, r to reset level, q to quit" •Out¨ (currentLevel⊑levels) Draw moves key←•term.CharB @ {𝕤⋄Next ⊑("hjkl"=key)/⟨0‿¯1,1‿0,¯1‿0,0‿1⟩}⍟(⊑key∊"hjkl")@ {𝕤⋄Next ⊑("aswd"=key)/⟨0‿¯1,1‿0,¯1‿0,0‿1⟩}⍟(⊑key∊"aswd")@ {𝕤⋄Undo @}⍟(key='u')@ {𝕤⋄•Out e∾"[?12l"∾e∾"[?25h"⋄•Exit 0}⍟(key='q')@ {𝕤⋄moves↩⟨0‿0⟩}⍟(key='r')@ {𝕤⋄clear↩e∾"[2J"}⍟(((1⊸+>○(⌊1+10⋆⁼1⌈⊢)⊢)¯1+≠moves)∧key='u')@ •Out e∾"8" # Restore cursor position {𝕤⋄•Out clear∾ansi.yellow∾"⭍"∾ansi.defaultB∾" Power the machines (⊕) by moving the mirrors ("∾ansi.cyan∾"\/"∾ansi.defaultB∾") "∾(@+10)∾"Controls: (hjkl or wasd) to move, u to undo, r to reset level, q to quit"}⍟(⊑key∊"wasdhjkluqr")@ {𝕤⋄•Out "Invalid key: (hjkl or wasd) to move, u to undo, r to reset level"}⍟(¬⊑key∊"wasdhjkluqr")@ •Out¨ (currentLevel⊑levels) Draw moves •Out "Moves: "∾•Repr ¯1+≠moves {𝕤⋄•Out "Good job!, press any key to continue to the next level"⋄•term.CharB @⋄e∾"[H"⋄currentLevel↩currentLevel+1⋄moves↩⟨0‿0⟩}⍟(Win (currentLevel⊑levels) Step´⌽moves)@ }•_While_{𝕤⋄currentLevel<≠levels}@ •Out "Well played, you win!" •Out e∾"[?12l"∾e∾"[?25h"