Add extensive comments to logic.bqn
This commit is contained in:
parent
a894380402
commit
37d75d3087
145
logic.bqn
145
logic.bqn
|
@ -1,61 +1,123 @@
|
||||||
#!/usr/bin/env BQN
|
#!/usr/bin/env BQN
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
# SPDX-FileCopyrightText: 2023 Rampoina <rampoina@protonmail.com>
|
# SPDX-FileCopyrightText: 2023 Rampoina <rampoina@protonmail.com>
|
||||||
Game⇐{
|
|
||||||
𝕊 levelPath‿chars‿colors:
|
⟨FindIdx,SplitOnEmpty⟩←•Import "utils.bqn"
|
||||||
moves⇐⟨0‿0⟩ # list of moves, each move is a direction, we start without moving
|
|
||||||
|
Game⇐{ # The Game function creates a game object
|
||||||
|
𝕊 levelPath‿dchars‿chars‿colors: # from parameters:
|
||||||
|
# levelPath: the path of the file containing the levels
|
||||||
|
# dchars: the characters to use for drawing
|
||||||
|
# colors: a list with colors
|
||||||
|
|
||||||
|
# Game representation:
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
# The game has the following objects represented as consecutive integers:
|
||||||
⟨floor,player,box,machine,pmachine,wall,lmirror,rmirror,hbeam,vbeam,xbeam,llaser,rlaser,ulaser,dlaser⟩←↕≠chars
|
⟨floor,player,box,machine,pmachine,wall,lmirror,rmirror,hbeam,vbeam,xbeam,llaser,rlaser,ulaser,dlaser⟩←↕≠chars
|
||||||
beams←hbeam‿vbeam‿xbeam
|
|
||||||
mirrors←lmirror‿rmirror
|
mirrors←lmirror‿rmirror # the mirrors to reflect
|
||||||
lasers←llaser‿rlaser‿ulaser‿dlaser
|
beams←hbeam‿vbeam‿xbeam # the laser beams
|
||||||
|
lasers←llaser‿rlaser‿ulaser‿dlaser # shot by the laser
|
||||||
|
# Each with every possible orientation
|
||||||
|
|
||||||
|
# There are *movable* objects like the following:
|
||||||
movables←player‿box∾mirrors # player, box and mirrors
|
movables←player‿box∾mirrors # player, box and mirrors
|
||||||
opaque←player‿box‿machine‿wall∾lasers # non laser reflecting
|
|
||||||
empties←floor∾beams # floor and laser beams
|
# And *opaque* objects which don't reflect lasers:
|
||||||
|
opaque←player‿box‿machine‿wall∾lasers
|
||||||
|
|
||||||
|
# *Empty* objects can contain other ones on top:
|
||||||
|
empties←floor∾beams
|
||||||
|
|
||||||
cols←(≠chars)⥊<(⊑colors) # start with all glyphs being the default color
|
# Colors:
|
||||||
cols (1⊑colors)˙⌾(pmachine⊸⊑)↩
|
# The parameter 'color' is a list of colors passed to the Game function to alter the
|
||||||
cols (2⊑colors)¨⌾(mirrors⊸⊏)↩
|
cols←(≠chars)⥊<(⊑colors) # base color,
|
||||||
cols (3⊑colors)¨⌾(beams⊸⊏)↩
|
cols (1⊑colors)˙⌾(pmachine⊸⊑)↩ # the color for the powered machine
|
||||||
Colorize←{𝕩∾˜cols⊑˜⊑chars⊐𝕩} # 𝕩: character | Turn character into color+character
|
cols (2⊑colors)¨⌾(mirrors⊸⊏)↩ # the color for the mirrors
|
||||||
|
cols (3⊑colors)¨⌾(beams⊸⊏)↩ # the color for the laser beams
|
||||||
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
|
# We use a list of game objects (ints) to represent each tile
|
||||||
Ascii2Matrix←{(⊑chars⊐𝕩)⊑lTiles}¨(⊢↑˝·≍⟜¬2+≢)
|
lTiles←{⊑𝕩∊movables ?
|
||||||
|
𝕩‿floor; # the movables are on top of the floor (list of 2 elements)
|
||||||
# 𝕨 Tiles 𝕩 | 𝕩: object coordinate (3‿1) | 𝕨: direction vector (¯1‿0)
|
≍𝕩 # And the rest are a list of just that element
|
||||||
# result: ⟨ ⟨ 3 1 ⟩ ⟨ 2 1 ⟩ ⟨ 1 1 ⟩ ⟩
|
}¨↕≠chars
|
||||||
# returns 3 tiles in the specified direction from the
|
|
||||||
Tiles←{⟨𝕩,𝕩+𝕨,𝕩+2×𝕨⟩} # given object (including itself)
|
# We transform each character into its tile representation and we transform
|
||||||
|
# it into a 2d matrix
|
||||||
# Move 𝕩 | 𝕩: ⟨⟨1,0⟩,⟨0⟩⟩ (2 tiles) | result: ⟨⟨0⟩,⟨1,0⟩⟩
|
# the matrix is padded to ensure that that we can always get 3 tiles
|
||||||
# Move the first object in the first tile to the second tile.
|
# centered on the player and pointing to the current direction
|
||||||
# Only move Movable tile -> Empty tile
|
Ascii2Matrix←{(⊑chars⊐𝕩)⊑lTiles}¨(⊢↑˝·≍⟜¬2+≢) # 𝕩 : Matrix of characters
|
||||||
|
|
||||||
|
# Rules:
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# - The player can push but not pull any movable object one tile away in the
|
||||||
|
# current direction to an empty tile:
|
||||||
|
|
||||||
|
# 𝕩: ⟨⟨1,0⟩,⟨0⟩⟩ (2 tiles) | result: ⟨⟨0⟩,⟨1,0⟩⟩
|
||||||
|
# (Try to) 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
|
# 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
|
# 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)≡⌜<⊑¨𝕩}
|
Move←{a‿b:⟨1↓a,(⊑a)∾b⟩}⍟{∨´(⥊movables≍⌜empties)≡⌜<⊑¨𝕩}
|
||||||
|
|
||||||
# Push 𝕩 | 𝕩: ⟨⟨1,0⟩,⟨2,0⟩,⟨0,0)⟩ (3 tiles) | result: ⟨⟨0⟩,⟨1,0⟩,⟨2,0)⟩
|
# 𝕩: ⟨⟨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)
|
# Given 3 tiles try to Push the second tile (possibly a movable object)
|
||||||
# and afterwards try to move the first one (player) if possible
|
# and afterwards try to move the first one (the player) if possible
|
||||||
Push←Move⌾(2↑⊢)Move⌾(1↓⊢)
|
Push←Move⌾(2↑⊢)Move⌾(1↓⊢)
|
||||||
|
|
||||||
FindIdx←/○⥊⟜(↕≢)∘⍷
|
# 𝕩: 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)
|
||||||
|
|
||||||
|
# We (try to) push the tile in front of the player
|
||||||
|
Step←{Push⌾((𝕨 Tiles ⊑player FindIdx ⊑¨𝕩)⊸⊑)𝕩} # 𝕨 S 𝕩 | 𝕨: direction | 𝕩:level | Step the game
|
||||||
|
|
||||||
|
# - The lasers shoot lasers that get intercepted by opaque objects and
|
||||||
|
# bounce on mirrors:
|
||||||
|
|
||||||
# w‿d Bounce x | x: map | w‿d: w: current position, d: direction of the laser
|
# w‿d Bounce x | x: map | w‿d: w: current position, d: direction of the laser
|
||||||
# Calculates the bounces of a laser beam recursively
|
# Calculates the bounces of a laser beam recursively
|
||||||
Bounce←{(w‿d)S x:{
|
Bounce←{(w‿d)S x:{
|
||||||
⊑opaque∊˜⊑w⊑x?(machine=⊑w⊑x)◶⟨x,⟨pmachine⟩˙⌾(w⊸⊑)x⟩@; # laser hits a non-mirror
|
⊑opaque∊˜⊑w⊑x? # When the beam touches an opaque object (not a mirror):
|
||||||
⊑empties∊˜⊑w⊑x ? # Empty space
|
(machine=⊑w⊑x)◶⟨x, # we do nothing if it doesn't touch a machine
|
||||||
⟨w+d,d⟩S{((×⊑d)⊑⟨hbeam‿hbeam‿xbeam‿xbeam,vbeam‿xbeam‿vbeam‿xbeam⟩)⊏˜floor‿hbeam‿vbeam‿xbeam⊐𝕩}⌾(w⊸⊑)x; # Draw laser and recurse
|
⟨pmachine⟩˙⌾(w⊸⊑)x # and if it does we change it to a powered machine
|
||||||
# laser hits a mirror
|
⟩@;
|
||||||
d←⌽d×-⊸¬lmirror=⊑w⊑x # calculate the mirror bounce direction
|
⊑empties∊˜⊑w⊑x ? # When the beam passes through an empty space:
|
||||||
⟨w+d,d⟩S x } # recurse to the next position
|
# we draw the laser beam and recurse to the next:
|
||||||
|
⟨w+d,d⟩S{ # we choose the type of laser beam to draw
|
||||||
|
cTile←(floor‿hbeam‿vbeam‿xbeam⊐𝕩) # depending on the current tile:
|
||||||
|
# floor hbeam vbeam xbeam | floor hbeam vbeam xbeam
|
||||||
|
cBeam← ((×⊑d) ⊑ ⟨hbeam‿hbeam‿xbeam‿xbeam , vbeam‿xbeam‿vbeam‿xbeam⟩)
|
||||||
|
# and beam direction Horizontal | Vertical
|
||||||
|
cTile⊏cBeam
|
||||||
|
}⌾(w⊸⊑)x;
|
||||||
|
# When the beam touches a mirror:
|
||||||
|
d←⌽d×-⊸¬lmirror=⊑w⊑x # calculate the mirror bounce direction
|
||||||
|
⟨w+d,d⟩S x # and recurse to the next position
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Shoot 𝕩 | 𝕩: map | calculates the bounces for each laser
|
# Shoot 𝕩 | 𝕩: map | calculates the bounces for each laser
|
||||||
Shoot←{𝕩 {𝕨Bounce´⌽𝕩} ∾⟨<0‿¯1,<0‿1,<¯1‿0,<1‿0⟩(⊣⋈˜¨+)¨ FindIdx⟜(⊑¨𝕩)¨ lasers}
|
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
|
# - The Player wins when all machines are powered:
|
||||||
Win←{¬∨´∨˝machine=⊑¨Shoot 𝕩}# W 𝕩 | 𝕩: level | [W]in condition, no unpowered machines after shooting laser
|
Win←{¬∨´∨˝machine=⊑¨Shoot 𝕩}# 𝕩: level | Win condition, no unpowered machines after shooting laser
|
||||||
|
|
||||||
|
|
||||||
|
# Drawing:
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Colorize←{𝕩∾˜cols⊑˜⊑dchars⊐𝕩} # 𝕩: character | Turn character into color+character
|
||||||
|
DrawLevel←{∾´¨<˘Colorize¨dchars⊏˜+´¨Shoot 𝕨 Step´ ⌽𝕩} # 𝕨 Draw 𝕩 | 𝕨: levels | 𝕩: moves | Draw the game in ASCII
|
||||||
|
|
||||||
|
# State and state mutating functions:
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
moves⇐⟨0‿0⟩ # list of moves, each move is a direction, we start without moving
|
||||||
|
currentLevel⇐0
|
||||||
|
levels←Ascii2Matrix¨>¨SplitOnEmpty•FLines levelPath # Load file containing levels
|
||||||
Next⇐{moves↩moves∾<𝕩}
|
Next⇐{moves↩moves∾<𝕩}
|
||||||
Undo⇐{𝕊:moves↩(-1<≠)⊸↓moves}
|
Undo⇐{𝕊:moves↩(-1<≠)⊸↓moves}
|
||||||
Draw⇐{𝕊:•Out¨ (currentLevel⊑levels) DrawLevel moves}
|
Draw⇐{𝕊:•Out¨ (currentLevel⊑levels) DrawLevel moves}
|
||||||
|
@ -63,7 +125,4 @@ Game⇐{
|
||||||
Reset⇐{𝕊:moves↩⟨0‿0⟩}
|
Reset⇐{𝕊:moves↩⟨0‿0⟩}
|
||||||
NextLevel⇐{𝕊:currentLevel↩currentLevel+1⋄Reset @}
|
NextLevel⇐{𝕊:currentLevel↩currentLevel+1⋄Reset @}
|
||||||
Over⇐{𝕊:currentLevel<≠levels}
|
Over⇐{𝕊:currentLevel<≠levels}
|
||||||
SplitOnEmpty←{𝕩⊔˜(⊢-˜+`׬)0=≠¨𝕩}
|
|
||||||
levels←Ascii2Matrix¨>¨SplitOnEmpty•FLines levelPath # Load file containing levels
|
|
||||||
currentLevel⇐0
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue