From bfd5cd52a2a3fc1c3ba8ba0c22384fd43bd1a70d Mon Sep 17 00:00:00 2001 From: Rampoina Date: Sat, 4 Mar 2023 18:38:17 +0100 Subject: [PATCH] First commit --- .gitmodules | 3 ++ CBQN | 1 + Makefile | 12 ++++++ README.md | 25 +++++++++++++ arc.bqn | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++ levels | 46 +++++++++++++++++++++++ 6 files changed, 190 insertions(+) create mode 100644 .gitmodules create mode 160000 CBQN create mode 100644 Makefile create mode 100644 README.md create mode 100755 arc.bqn create mode 100644 levels diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6506fc6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "CBQN"] + path = CBQN + url = https://github.com/dzaima/CBQN.git diff --git a/CBQN b/CBQN new file mode 160000 index 0000000..05c1270 --- /dev/null +++ b/CBQN @@ -0,0 +1 @@ +Subproject commit 05c1270344908e98c9f2d06b3671c3646f8634c3 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4a39ce5 --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..65d9af6 --- /dev/null +++ b/README.md @@ -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 diff --git a/arc.bqn b/arc.bqn new file mode 100755 index 0000000..9803a0c --- /dev/null +++ b/arc.bqn @@ -0,0 +1,103 @@ +#!/usr/bin/env BQN +# SPDX-License-Identifier: AGPL-3.0-or-later +# SPDX-FileCopyrightText: 2023 Rampoina +# +# 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 ⟩ #####" +# ┘ ┘ +moves←⟨0‿0⟩ # list of moves, each move is a direction, we start without moving +chars←" λ$⊕λ⭍#/\-|+<>^v" # legal characters +movables←1‿2‿7‿8‿12‿13‿14‿15 # player, box mirrors and lasers +opaque←1‿2‿3‿12‿13‿14‿15‿6 # non laser reflecting +empties←0‿9‿10‿11 # floor and laser beams +ansi←{ + e⇐@+27 + red⇐e∾"[31m" + cyan⇐e∾"[36m" + yellow⇐e∾"[33m" + defaultB⇐e∾"[0m" +} + +SplitOnEmpty←{𝕩⊔˜(⊢-˜+`׬)0=≠¨𝕩} +Ascii2Matrix←{(⊑chars⊐𝕩)⊑⟨≍0,1‿0,2‿0,≍3,1‿3,2‿3,≍6,7‿0,8‿0,≍9,≍10,≍11,12‿0,13‿0,14‿0,15‿0⟩}¨(⊢↑˝·≍⟜¬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←/○⥊⟜(↕≢)∘⍷ + +# 𝕨 Beam 𝕩 | 𝕨: level | 𝕩: position | replace positions with the laser beam +Beam←{{"--++"⊏˜" -|+"⊐𝕩}⌾(𝕩⊸⊑)𝕨} + +# Recursive Bounce +Bounce←{(w‿d)S x:{ + ⊑opaque∊˜⊑w⊑x?(3=⊑w⊑x)◶⟨x,⟨5⟩˙⌾(w⊸⊑)x⟩@; # laser hits a non-mirror + ⊑empties∊˜⊑w⊑x ? # Empty space + ⟨w+d,d⟩S{((×⊑d)⊑⟨9‿9‿11‿11,10‿11‿10‿11⟩)⊏˜0‿9‿10‿11⊐𝕩}⌾(w⊸⊑)x; # Draw laser and recurse + #⟨w+d,d⟩S{((×⊑d)⊑"--++"‿"|+|+")⊏˜" -|+"⊐𝕩}⌾(w⊸⊑)x; # Draw laser and recurse + # laser hits a mirror + d←⌽d×-⊸¬7=⊑w⊑x # calculate the mirror bounce direction + ⟨w+d,d⟩S x } # recurse to the next position +} + +Shoot←{𝕩 {𝕨Bounce´⌽𝕩} ∾⟨<0‿¯1,<0‿1,<¯1‿0,<1‿0⟩(⊣⋈˜¨+)¨ FindIdx⟜(⊑¨𝕩)¨ 12‿13‿14‿15} +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←{moves↩moves∾<𝕩} +Undo←{𝕊:moves↩(-1<≠)⊸↓moves} + +# Main loop +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←"" +{𝕤 # 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¨ (currentLevel⊑levels) Draw moves + key←•term.CharB @ + {𝕤⋄Next ⊑("hjkl"=key)/⟨0‿¯1,1‿0,¯1‿0,0‿1⟩}⍟(⊑key∊"hjkl")@ + {𝕤⋄Next ⊑("aswd"=key)/⟨0‿¯1,1‿0,¯1‿0,0‿1⟩}⍟(⊑key∊"aswd")@ + {𝕤⋄Undo @}⍟(key='u')@ + {𝕤⋄•Out e∾"[?12l"∾e∾"[?25h"⋄•Exit 0}⍟(key='q')@ + {𝕤⋄moves↩⟨0‿0⟩}⍟(key='r')@ + {𝕤⋄clear↩e∾"[2J"}⍟(((1⊸+>○(⌊1+10⋆⁼1⌈⊢)⊢)¯1+≠moves)∧key='u')@ + •Out e∾"8" # Restore cursor position + + {𝕤⋄•Out clear∾ansi.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¨ (currentLevel⊑levels) Draw moves + •Out "Moves: "∾•Repr ¯1+≠moves + {𝕤⋄•Out "Good job!, press any key to continue to the next level"⋄•term.CharB @⋄e∾"[H"⋄currentLevel↩currentLevel+1⋄moves↩⟨0‿0⟩}⍟(Win (currentLevel⊑levels) Step´⌽moves)@ +}•_While_{𝕤⋄currentLevel<≠levels}@ +•Out "Well played, you win!" +•Out e∾"[?12l"∾e∾"[?25h" diff --git a/levels b/levels new file mode 100644 index 0000000..6e7bbd9 --- /dev/null +++ b/levels @@ -0,0 +1,46 @@ +############## +# # +# ######⊕ # +# # +# # +# > / # +# # λ # +############## + +############### +# # ⊕ # +# v / #### # +# #/ # \ # +# # ## # +# #\ λ / # +# #### ## / # +# \ # +############### + +############## +# # +# ⊕ # +# > λ # +# ⊕ # +# > / # +# # +############## + +############## +# # +# > / # +# λ # +# # +# ⊕ \ # +# # +############## + +############### +# ⊕ # +# v / # \# +# / # \ # +# # /# +# #\ / # +# # λ # / # +# \ # +###############