Separate the main loop from the logic

This commit is contained in:
Rampoina 2023-03-05 15:58:49 +01:00
parent 7ef2e3cd09
commit 2333c02699
2 changed files with 83 additions and 71 deletions

83
arc.bqn
View File

@ -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"
moves00 # 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,dlaserchars
beamshbeamvbeamxbeam
mirrorslmirrorrmirror
lasersllaserrlaserulaserdlaser
movablesplayerboxmirrors # player, box and mirrors
opaqueplayerboxmachinewalllasers # non laser reflecting
emptiesfloorbeams # 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{ab:1a,(a)b}{´(movablesempties)<¨𝕩}
# 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
PushMove(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{(wd)S x:{
opaque˜wx?(machine=wx)x,pmachine˙(w)x@; # laser hits a non-mirror
empties˜wx ? # Empty space
w+d,dS{((×d)hbeamhbeamxbeamxbeam,vbeamxbeamvbeamxbeam)˜floorhbeamvbeamxbeam𝕩}(w)x; # Draw laser and recurse
# laser hits a mirror
dd×-¬lmirror=wx # calculate the mirror bounce direction
w+d,dS x } # recurse to the next position
}
# Shoot 𝕩 | 𝕩: map | calculates the bounces for each laser
Shoot{𝕩 {𝕨Bounce´𝕩} <0¯1,<01,<¯10,<10(˜¨+)¨ 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{movesmoves<𝕩}
Undo{𝕊:moves(-1<)moves}
# Main loop
SplitOnEmpty{𝕩˜(-˜+`׬)0=¨𝕩}
levelsAscii2Matrix¨>¨SplitOnEmpty•FLines "levels" # Load file containing levels
currentLevel0
eansi.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""
gGame "levels"
{𝕤 # Loop until the user wins
•Out cleare"[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¨ (currentLevellevels) Draw moves
•Out "Moves: "•Repr ¯1+moves
Win (currentLevellevels) 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 @
cleare"[2J"
currentLevel↩currentLevel+1moves00
g.NextLevel @
{𝕤•Out e"[?12l"e"[?25h"•Exit 0}(key='q')@
;
key•term.CharB @
{𝕤Next ("hjklaswd"=key)/˜0¯1,10,¯10,01}(key"hjklaswd")@
{𝕤Undo @}(key='u')@
{𝕤g.Next ("hjklaswd"=key)/˜0¯1,10,¯10,01}(key"hjklaswd")@
{𝕤g.Undo @}(key='u')@
{𝕤•Out e"[?12l"e"[?25h"•Exit 0}(key='q')@
{𝕤cleare"[2J"moves↩00}(key='r')@
{𝕤cleare"[2J"}(((1+>(1+101))¯1+moves)key='u')@
}•_While_{𝕤currentLevel<levels}@
{𝕤cleare"[2J"g.Reset @}(key='r')@
{𝕤cleare"[2J"}(((1+>(1+101))¯1+g.moves)key='u')@
}•_While_{𝕤g.Over @}@
•Out "Well played, you win!"
•Out e"[?12l"e"[?25h"

71
logic.bqn Normal file
View File

@ -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{
moves00 # 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,dlaserchars
beamshbeamvbeamxbeam
mirrorslmirrorrmirror
lasersllaserrlaserulaserdlaser
movablesplayerboxmirrors # player, box and mirrors
opaqueplayerboxmachinewalllasers # non laser reflecting
emptiesfloorbeams # 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{ab:1a,(a)b}{´(movablesempties)<¨𝕩}
# 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
PushMove(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{(wd)S x:{
opaque˜wx?(machine=wx)x,pmachine˙(w)x@; # laser hits a non-mirror
empties˜wx ? # Empty space
w+d,dS{((×d)hbeamhbeamxbeamxbeam,vbeamxbeamvbeamxbeam)˜floorhbeamvbeamxbeam𝕩}(w)x; # Draw laser and recurse
# laser hits a mirror
dd×-¬lmirror=wx # calculate the mirror bounce direction
w+d,dS x } # recurse to the next position
}
# Shoot 𝕩 | 𝕩: map | calculates the bounces for each laser
Shoot{𝕩 {𝕨Bounce´𝕩} <0¯1,<01,<¯10,<10(˜¨+)¨ 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{movesmoves<𝕩}
Undo{𝕊:moves(-1<)moves}
Draw{𝕊:•Out¨ (currentLevellevels) DrawLevel moves}
WinLevel{𝕊:Win(currentLevellevels)Step´moves}
Reset{𝕊:moves00}
NextLevel{𝕊:currentLevelcurrentLevel+1Reset @}
Over{𝕊:currentLevel<levels}
SplitOnEmpty{𝕩˜(-˜+`׬)0=¨𝕩}
levelsAscii2Matrix¨>¨SplitOnEmpty•FLines 𝕩 # Load file containing levels
currentLevel0
}