Separate the main loop from the logic
This commit is contained in:
parent
7ef2e3cd09
commit
2333c02699
83
arc.bqn
83
arc.bqn
|
@ -6,93 +6,34 @@
|
|||
# The level is a 2d matrix of lists (tiles)
|
||||
# Each list contains the objects of the game represented as numbers
|
||||
⟨ansi⟩←•Import "ansi.bqn"
|
||||
⟨Game⟩←•Import "logic.bqn"
|
||||
|
||||
moves←⟨0‿0⟩ # list of moves, each move is a direction, we start without moving
|
||||
chars←" λ$⊕⭍#/\-|+<>^v" # legal characters:
|
||||
⟨floor,player,box,machine,pmachine,wall,lmirror,rmirror,hbeam,vbeam,xbeam,llaser,rlaser,ulaser,dlaser⟩←↕≠chars
|
||||
beams←hbeam‿vbeam‿xbeam
|
||||
mirrors←lmirror‿rmirror
|
||||
lasers←llaser‿rlaser‿ulaser‿dlaser
|
||||
movables←player‿box∾mirrors # player, box and mirrors
|
||||
opaque←player‿box‿machine‿wall∾lasers # non laser reflecting
|
||||
empties←floor∾beams # floor and laser beams
|
||||
|
||||
colors←(≠chars)⥊<ansi.defaultB # start with all glyphs being the default color
|
||||
colors ansi.yellow˙⌾(pmachine⊸⊑)↩
|
||||
colors ansi.cyan¨⌾(mirrors⊸⊏)↩
|
||||
colors ansi.red¨⌾(beams⊸⊏)↩
|
||||
Colorize←{𝕩∾˜colors⊑˜⊑chars⊐𝕩} # 𝕩: character | Turn character into color+character
|
||||
|
||||
lTiles←{⊑𝕩∊movables ? 𝕩‿floor; ≍𝕩}¨↕≠chars # list of all the possible tiles as lists of numbers
|
||||
# Turns the level from ascii strings into a 2d matrix of lists
|
||||
Ascii2Matrix←{(⊑chars⊐𝕩)⊑lTiles}¨(⊢↑˝·≍⟜¬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←/○⥊⟜(↕≢)∘⍷
|
||||
# w‿d Bounce x | x: map | w‿d: w: current position, d: direction of the laser
|
||||
# Calculates the bounces of a laser beam recursively
|
||||
Bounce←{(w‿d)S x:{
|
||||
⊑opaque∊˜⊑w⊑x?(machine=⊑w⊑x)◶⟨x,⟨pmachine⟩˙⌾(w⊸⊑)x⟩@; # laser hits a non-mirror
|
||||
⊑empties∊˜⊑w⊑x ? # Empty space
|
||||
⟨w+d,d⟩S{((×⊑d)⊑⟨hbeam‿hbeam‿xbeam‿xbeam,vbeam‿xbeam‿vbeam‿xbeam⟩)⊏˜floor‿hbeam‿vbeam‿xbeam⊐𝕩}⌾(w⊸⊑)x; # Draw laser and recurse
|
||||
# laser hits a mirror
|
||||
d←⌽d×-⊸¬lmirror=⊑w⊑x # calculate the mirror bounce direction
|
||||
⟨w+d,d⟩S x } # recurse to the next position
|
||||
}
|
||||
|
||||
# Shoot 𝕩 | 𝕩: map | calculates the bounces for each laser
|
||||
Shoot←{𝕩 {𝕨Bounce´⌽𝕩} ∾⟨<0‿¯1,<0‿1,<¯1‿0,<1‿0⟩(⊣⋈˜¨+)¨ FindIdx⟜(⊑¨𝕩)¨ lasers}
|
||||
Step←{Push⌾((𝕨 Tiles ⊑player FindIdx ⊑¨𝕩)⊸⊑)𝕩} # 𝕨 S 𝕩 | 𝕨: direction | 𝕩:level | Step the game
|
||||
Draw←{∾´¨<˘Colorize¨chars⊏˜+´¨Shoot 𝕨 Step´ ⌽𝕩} # 𝕨 Draw 𝕩 | 𝕨: levels | 𝕩: moves | Draw the game in ASCII
|
||||
Win←{¬∨´∨˝machine=⊑¨Shoot 𝕩}# W 𝕩 | 𝕩: level | [W]in condition, no unpowered machines after shooting laser
|
||||
Next←{moves↩moves∾<𝕩}
|
||||
Undo←{𝕊:moves↩(-1<≠)⊸↓moves}
|
||||
|
||||
# Main loop
|
||||
SplitOnEmpty←{𝕩⊔˜(⊢-˜+`׬)0=≠¨𝕩}
|
||||
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←""
|
||||
g←Game "levels"
|
||||
{𝕤 # Loop until the user wins
|
||||
•Out clear∾e∾"[H" # Cursor to origin
|
||||
clear↩""
|
||||
•Out "Level: "∾•Repr 1+currentLevel
|
||||
•Out "Level: "∾•Repr 1+g.currentLevel
|
||||
•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
|
||||
•Out "Moves: "∾•Repr ¯1+≠moves
|
||||
Win (currentLevel⊑levels) Step´⌽moves ?
|
||||
g.Draw @
|
||||
•Out "Moves: "∾•Repr ¯1+≠g.moves
|
||||
g.WinLevel @ ?
|
||||
•Out "Good job!, press any key to continue to the next level"
|
||||
key←•term.CharB @
|
||||
clear↩e∾"[2J"
|
||||
currentLevel↩currentLevel+1⋄moves↩⟨0‿0⟩
|
||||
g.NextLevel @
|
||||
{𝕤⋄•Out e∾"[?12l"∾e∾"[?25h"⋄•Exit 0}⍟(key='q')@
|
||||
;
|
||||
key←•term.CharB @
|
||||
{𝕤⋄Next ⊑("hjklaswd"=key)/∾˜⟨0‿¯1,1‿0,¯1‿0,0‿1⟩}⍟(⊑key∊"hjklaswd")@
|
||||
{𝕤⋄Undo @}⍟(key='u')@
|
||||
{𝕤⋄g.Next ⊑("hjklaswd"=key)/∾˜⟨0‿¯1,1‿0,¯1‿0,0‿1⟩}⍟(⊑key∊"hjklaswd")@
|
||||
{𝕤⋄g.Undo @}⍟(key='u')@
|
||||
{𝕤⋄•Out e∾"[?12l"∾e∾"[?25h"⋄•Exit 0}⍟(key='q')@
|
||||
{𝕤⋄clear↩e∾"[2J"⋄moves↩⟨0‿0⟩}⍟(key='r')@
|
||||
{𝕤⋄clear↩e∾"[2J"}⍟(((1⊸+>○(⌊1+10⋆⁼1⌈⊢)⊢)¯1+≠moves)∧key='u')@
|
||||
}•_While_{𝕤⋄currentLevel<≠levels}@
|
||||
{𝕤⋄clear↩e∾"[2J"⋄g.Reset @}⍟(key='r')@
|
||||
{𝕤⋄clear↩e∾"[2J"}⍟(((1⊸+>○(⌊1+10⋆⁼1⌈⊢)⊢)¯1+≠g.moves)∧key='u')@
|
||||
}•_While_{𝕤⋄g.Over @}@
|
||||
•Out "Well played, you win!"
|
||||
•Out e∾"[?12l"∾e∾"[?25h"
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
#!/usr/bin/env BQN
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
# SPDX-FileCopyrightText: 2023 Rampoina <rampoina@protonmail.com>
|
||||
⟨ansi⟩←•Import "ansi.bqn"
|
||||
|
||||
Game⇐{
|
||||
moves⇐⟨0‿0⟩ # list of moves, each move is a direction, we start without moving
|
||||
chars←" λ$⊕⭍#/\-|+<>^v" # legal characters:
|
||||
⟨floor,player,box,machine,pmachine,wall,lmirror,rmirror,hbeam,vbeam,xbeam,llaser,rlaser,ulaser,dlaser⟩←↕≠chars
|
||||
beams←hbeam‿vbeam‿xbeam
|
||||
mirrors←lmirror‿rmirror
|
||||
lasers←llaser‿rlaser‿ulaser‿dlaser
|
||||
movables←player‿box∾mirrors # player, box and mirrors
|
||||
opaque←player‿box‿machine‿wall∾lasers # non laser reflecting
|
||||
empties←floor∾beams # floor and laser beams
|
||||
|
||||
colors←(≠chars)⥊<ansi.defaultB # start with all glyphs being the default color
|
||||
colors ansi.yellow˙⌾(pmachine⊸⊑)↩
|
||||
colors ansi.cyan¨⌾(mirrors⊸⊏)↩
|
||||
colors ansi.red¨⌾(beams⊸⊏)↩
|
||||
Colorize←{𝕩∾˜colors⊑˜⊑chars⊐𝕩} # 𝕩: character | Turn character into color+character
|
||||
|
||||
lTiles←{⊑𝕩∊movables ? 𝕩‿floor; ≍𝕩}¨↕≠chars # list of all the possible tiles as lists of numbers
|
||||
# Turns the level from ascii strings into a 2d matrix of lists
|
||||
Ascii2Matrix←{(⊑chars⊐𝕩)⊑lTiles}¨(⊢↑˝·≍⟜¬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←/○⥊⟜(↕≢)∘⍷
|
||||
# w‿d Bounce x | x: map | w‿d: w: current position, d: direction of the laser
|
||||
# Calculates the bounces of a laser beam recursively
|
||||
Bounce←{(w‿d)S x:{
|
||||
⊑opaque∊˜⊑w⊑x?(machine=⊑w⊑x)◶⟨x,⟨pmachine⟩˙⌾(w⊸⊑)x⟩@; # laser hits a non-mirror
|
||||
⊑empties∊˜⊑w⊑x ? # Empty space
|
||||
⟨w+d,d⟩S{((×⊑d)⊑⟨hbeam‿hbeam‿xbeam‿xbeam,vbeam‿xbeam‿vbeam‿xbeam⟩)⊏˜floor‿hbeam‿vbeam‿xbeam⊐𝕩}⌾(w⊸⊑)x; # Draw laser and recurse
|
||||
# laser hits a mirror
|
||||
d←⌽d×-⊸¬lmirror=⊑w⊑x # calculate the mirror bounce direction
|
||||
⟨w+d,d⟩S x } # recurse to the next position
|
||||
}
|
||||
|
||||
# Shoot 𝕩 | 𝕩: map | calculates the bounces for each laser
|
||||
Shoot←{𝕩 {𝕨Bounce´⌽𝕩} ∾⟨<0‿¯1,<0‿1,<¯1‿0,<1‿0⟩(⊣⋈˜¨+)¨ FindIdx⟜(⊑¨𝕩)¨ lasers}
|
||||
Step←{Push⌾((𝕨 Tiles ⊑player FindIdx ⊑¨𝕩)⊸⊑)𝕩} # 𝕨 S 𝕩 | 𝕨: direction | 𝕩:level | Step the game
|
||||
DrawLevel←{∾´¨<˘Colorize¨chars⊏˜+´¨Shoot 𝕨 Step´ ⌽𝕩} # 𝕨 Draw 𝕩 | 𝕨: levels | 𝕩: moves | Draw the game in ASCII
|
||||
Win←{¬∨´∨˝machine=⊑¨Shoot 𝕩}# W 𝕩 | 𝕩: level | [W]in condition, no unpowered machines after shooting laser
|
||||
Next⇐{moves↩moves∾<𝕩}
|
||||
Undo⇐{𝕊:moves↩(-1<≠)⊸↓moves}
|
||||
Draw⇐{𝕊:•Out¨ (currentLevel⊑levels) DrawLevel moves}
|
||||
WinLevel⇐{𝕊:Win(currentLevel⊑levels)Step´⌽moves}
|
||||
Reset⇐{𝕊:moves↩⟨0‿0⟩}
|
||||
NextLevel⇐{𝕊:currentLevel↩currentLevel+1⋄Reset @}
|
||||
Over⇐{𝕊:currentLevel<≠levels}
|
||||
SplitOnEmpty←{𝕩⊔˜(⊢-˜+`׬)0=≠¨𝕩}
|
||||
levels←Ascii2Matrix¨>¨SplitOnEmpty•FLines 𝕩 # Load file containing levels
|
||||
currentLevel⇐0
|
||||
}
|
Loading…
Reference in New Issue