Arc/logic.bqn

145 lines
7.1 KiB
BQN
Raw Normal View History

2023-03-05 15:58:49 +01:00
#!/usr/bin/env BQN
# SPDX-License-Identifier: AGPL-3.0-or-later
# SPDX-FileCopyrightText: 2023 Rampoina <rampoina@protonmail.com>
2023-03-05 17:55:25 +01:00
FindIdx,SplitOnEmpty•Import "utils.bqn"
Game{ # The Game function creates a game object
2023-03-05 21:15:08 +01:00
𝕊 nlevelPathdcharscharscolors: # from parameters:
# n: starting level
2023-03-05 17:55:25 +01:00
# levelPath: the path of the file containing the levels
# dchars: the characters to use for drawing
2023-03-05 18:24:16 +01:00
# chars: the characters that are used in the level representation
2023-03-05 17:55:25 +01:00
# 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,dlaserchars
2023-03-05 17:55:25 +01:00
mirrorslmirrorrmirror # the mirrors to reflect
2023-03-05 17:55:25 +01:00
beamshbeamvbeamxbeam # the laser beams
lasersllaserrlaserulaserdlaser # shot by the laser
# Each with every possible orientation
# There are *movable* objects like the following:
2023-03-05 15:58:49 +01:00
movablesplayerboxmirrors # player, box and mirrors
2023-03-05 17:55:25 +01:00
# And *opaque* objects which don't reflect lasers:
opaqueplayerboxmachinewalllasers
# *Empty* objects can contain other ones on top:
emptiesfloorbeams
2023-03-05 15:58:49 +01:00
2023-03-05 17:55:25 +01:00
# We use a list of game objects (ints) to represent each tile
lTiles{𝕩movables ?
𝕩floor; # the movables are on top of the floor (list of 2 elements)
𝕩 # And the rest are a list of just that element
}¨chars
# We transform each character into its tile representation and we transform
# it into a 2d matrix
# the matrix is padded to ensure that that we can always get 3 tiles
# centered on the player and pointing to the current direction
Ascii2Matrix{(chars𝕩)lTiles}¨(˝·¬2+) # 𝕩 : Matrix of characters
# Rules:
# -------------------------------------------------------------------------------
#
2023-03-05 18:16:57 +01:00
# --- Rule 1: -------------------------------------------------------------------
2023-03-05 17:55:25 +01:00
# - 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
2023-03-05 15:58:49 +01:00
# 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{ab:1a,(a)b}{´(movablesempties)<¨𝕩}
2023-03-05 17:55:25 +01:00
# 𝕩: ⟨⟨1,0⟩,⟨2,0⟩,⟨0,0)⟩ (3 tiles) | result: ⟨⟨0⟩,⟨1,0⟩,⟨2,0)⟩
# Given 3 tiles try to Push the second tile (possibly a movable object)
# and afterwards try to move the first one (the player) if possible
2023-03-05 15:58:49 +01:00
PushMove(2)Move(1)
2023-03-05 17:55:25 +01:00
# 𝕩: 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
2023-03-05 17:58:00 +01:00
# - The lasers shoot lasers beams that get intercepted by opaque objects and
2023-03-05 17:55:25 +01:00
# bounce on mirrors:
2023-03-05 15:58:49 +01:00
# w‿d Bounce x | x: map | w‿d: w: current position, d: direction of the laser
# Calculates the bounces of a laser beam recursively
2023-03-05 18:07:44 +01:00
Bounce{(wd)S x:{ # Base case:
2023-03-05 17:55:25 +01:00
opaque˜wx? # When the beam touches an opaque object (not a mirror):
2023-03-05 18:16:57 +01:00
(machine=wx) # if it's not a machine:
x, # we do nothing
pmachine˙(w)x # and if it is, we change it to a powered machine
@; # and the recursion stops
empties˜wx ? # When the beam passes through an empty space:
# we draw the laser beam and recurse to the next:
w+d,dS{ # we choose the type of laser beam to draw
cTile(floorhbeamvbeamxbeam𝕩) # depending on the current tile:
# floor hbeam vbeam xbeam | floor hbeam vbeam xbeam
cBeam ((×d) hbeamhbeamxbeamxbeam , vbeamxbeamvbeamxbeam)
# and beam direction Horizontal | Vertical
cTilecBeam
}(w)x;
# When the beam touches a mirror:
dd×-¬lmirror=wx # calculate the mirror bounce direction
w+d,dS x # and recurse to the next position
}
2023-03-05 15:58:49 +01:00
}
2023-03-05 18:07:44 +01:00
# We find each laser machine and shoot a beam in its direction
2023-03-05 15:58:49 +01:00
# Shoot 𝕩 | 𝕩: map | calculates the bounces for each laser
Shoot{𝕩 {𝕨Bounce´𝕩} <0¯1,<01,<¯10,<10(˜¨+)¨ FindIdx(¨𝕩)¨ lasers}
2023-03-05 17:55:25 +01:00
2023-03-05 18:16:57 +01:00
# --- Rule 2: -------------------------------------------------------------------
2023-03-05 17:55:25 +01:00
# - The Player wins when all machines are powered:
Win{¬´˝machine=¨Shoot 𝕩}# 𝕩: level | Win condition, no unpowered machines after shooting laser
# Drawing:
# -------------------------------------------------------------------------------
2023-03-05 18:24:16 +01:00
# Colors:
# The parameter 'color' is a list of colors passed to the Game function to alter the
cols(chars)<(colors) # base color,
cols (1colors)˙(pmachine) # the color for the powered machine
cols (2colors)¨(mirrors) # the color for the mirrors
cols (3colors)¨(beams) # the color for the laser beams
Color{(𝕩cols)𝕩dchars} # 𝕩: game Object (int) | draw object with color
DrawLevel{´¨<˘Color¨+´¨Shoot 𝕨 Step´ 𝕩} # 𝕨 Draw 𝕩 | 𝕨: levels | 𝕩: moves | Draw the game in ASCII
2023-03-05 17:55:25 +01:00
# State and state mutating functions:
# -------------------------------------------------------------------------------
moves00 # list of moves, each move is a direction, we start without moving
2023-03-05 21:15:08 +01:00
currentLeveln-1
"Invalid number of fchars" ! 15=chars
"Invalid number of chars" ! 15=dchars
"The level file contains illegal characters" ! ´chars˜´•Flines levelPath
2023-03-05 17:55:25 +01:00
levelsAscii2Matrix¨>¨SplitOnEmpty•FLines levelPath # Load file containing levels
2023-03-05 21:15:08 +01:00
"Some levels don't contain any player" ! ¬´0=¨{player FindIdx ¨𝕩}¨levels
"Some levels don't contain any machine" ! ¬´0=¨{machine FindIdx ¨𝕩}¨levels
"The starting level is higher than the number of levels" ! currentLevel<levels
2023-03-05 15:58:49 +01:00
Next{movesmoves<𝕩}
Undo{𝕊:moves(-1<)moves}
Draw{𝕊:•Out¨ (currentLevellevels) DrawLevel moves}
WinLevel{𝕊:Win(currentLevellevels)Step´moves}
Reset{𝕊:moves00}
NextLevel{𝕊:currentLevelcurrentLevel+1Reset @}
2023-03-05 17:58:00 +01:00
Over{𝕊:currentLevel<levels} # has the user beaten all of the levels?
2023-03-05 15:58:49 +01:00
}