First commit

This commit is contained in:
Rampoina 2023-03-04 18:38:17 +01:00
commit bfd5cd52a2
6 changed files with 190 additions and 0 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "CBQN"]
path = CBQN
url = https://github.com/dzaima/CBQN.git

1
CBQN Submodule

@ -0,0 +1 @@
Subproject commit 05c1270344908e98c9f2d06b3671c3646f8634c3

12
Makefile Normal file
View File

@ -0,0 +1,12 @@
.PHONY: check-and-reinit-submodules all
all: .check-and-reinit-submodules
@${MAKE} -C CBQN/ MAKEFLAGS=
run: arc.bqn
./CBQN/BQN arc.bqn
.check-and-reinit-submodules:
@if git submodule status | egrep -q '^[-]|^[+]' ; then \
echo "INFO: reinitializing git submodules"; \
git submodule update --init; \
fi

25
README.md Normal file
View File

@ -0,0 +1,25 @@
# Arc, a puzzle game about directing laser beams with mirrors
## Controls
Vim style movement keys or WASD keys
- a/h: left
- s/j: down
- w/k: up
- d/l: right
- u: undo
- r: restart
- q: quit
## Running
If you already have BQN and you have it on your path just run `./soko.bqn`
Otherwise:
1. `make` to compile CBQN (`make CC=cc` if clang isn't installed)
2. `make run` to run the game
## License
Sokobqn is licensed as AGPLv3

103
arc.bqn Executable file
View File

@ -0,0 +1,103 @@
#!/usr/bin/env BQN
# SPDX-License-Identifier: AGPL-3.0-or-later
# SPDX-FileCopyrightText: 2023 Rampoina <rampoina@protonmail.com>
#
# 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 ⟩ #####"
# ┘ ┘
moves00 # list of moves, each move is a direction, we start without moving
chars" λ$⊕λ⭍#/\-|+<>^v" # legal characters
movables127812131415 # player, box mirrors and lasers
opaque123121314156 # non laser reflecting
empties091011 # floor and laser beams
ansi{
e@+27
rede"[31m"
cyane"[36m"
yellowe"[33m"
defaultBe"[0m"
}
SplitOnEmpty{𝕩˜(-˜+`׬)0=¨𝕩}
Ascii2Matrix{(chars𝕩)0,10,20,3,13,23,6,70,80,9,10,11,120,130,140,150}¨(˝·¬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/()
# 𝕨 Beam 𝕩 | 𝕨: level | 𝕩: position | replace positions with the laser beam
Beam{{"--++"˜" -|+"𝕩}(𝕩)𝕨}
# Recursive Bounce
Bounce{(wd)S x:{
opaque˜wx?(3=wx)x,5˙(w)x@; # laser hits a non-mirror
empties˜wx ? # Empty space
w+d,dS{((×d)991111,10111011)˜091011𝕩}(w)x; # Draw laser and recurse
#⟨w+d,d⟩S{((×⊑d)⊑"--++"‿"|+|+")⊏˜" -|+"⊐𝕩}⌾(w⊸⊑)x; # Draw laser and recurse
# laser hits a mirror
dd×-¬7=wx # calculate the mirror bounce direction
w+d,dS x } # recurse to the next position
}
Shoot{𝕩 {𝕨Bounce´𝕩} <0¯1,<01,<¯10,<10(˜¨+)¨ FindIdx(¨𝕩)¨ 12131415}
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{movesmoves<𝕩}
Undo{𝕊:moves(-1<)moves}
# Main loop
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""
{𝕤 # 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¨ (currentLevellevels) Draw moves
key•term.CharB @
{𝕤Next ("hjkl"=key)/0¯1,10,¯10,01}(key"hjkl")@
{𝕤Next ("aswd"=key)/0¯1,10,¯10,01}(key"aswd")@
{𝕤Undo @}(key='u')@
{𝕤•Out e"[?12l"e"[?25h"•Exit 0}(key='q')@
{𝕤moves00}(key='r')@
{𝕤cleare"[2J"}(((1+>(1+101))¯1+moves)key='u')@
•Out e"8" # Restore cursor position
{𝕤•Out clearansi.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¨ (currentLevellevels) Draw moves
•Out "Moves: "•Repr ¯1+moves
{𝕤•Out "Good job!, press any key to continue to the next level"•term.CharB @e"[H"currentLevelcurrentLevel+1moves00}(Win (currentLevellevels) Step´moves)@
}•_While_{𝕤currentLevel<levels}@
•Out "Well played, you win!"
•Out e"[?12l"e"[?25h"

46
levels Normal file
View File

@ -0,0 +1,46 @@
##############
# #
# ######⊕ #
# #
# #
# > / #
# # λ #
##############
###############
# # ⊕ #
# v / #### #
# #/ # \ #
# # ## #
# #\ λ / #
# #### ## / #
# \ #
###############
##############
# #
# ⊕ #
# > λ #
# ⊕ #
# > / #
# #
##############
##############
# #
# > / #
# λ #
# #
# ⊕ \ #
# #
##############
###############
# ⊕ #
# v / # \#
# / # \ #
# # /#
# #\ / #
# # λ # / #
# \ #
###############