Sokobqn/soko.bqn

77 lines
3.8 KiB
BQN
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env BQN
# SPDX-License-Identifier: AGPL-3.0-or-later
# SPDX-FileCopyrightText: 2022 Rampoina <rampoina@protonmail.com>
#
# Sokobqn
# 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" @$.+*#" # legal characters
SplitOnEmpty{𝕩˜(-˜+`׬)0=¨𝕩}
Ascii2Matrix{(chars𝕩)0,10,20,3,13,23,6}¨(˝·¬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)
Player{/()1¨𝕩} # Player 𝕩 | 𝕩:level | returns the coordinate of the [P]layer
# Move 𝕩 | 𝕩: ⟨⟨1,0⟩,⟨0⟩⟩ (2 tiles) | result: ⟨⟨0⟩,⟨1,0⟩⟩
# Move the first object in the first tile to the second tile.
# Only move Player/Box -> Floor/Goal
# the second tile can't be a box because we moved it previously
# if it is it means that the box was unmovable (next to a wall) so we do nothing
Move{ab:1a,(a)b}{´(1203)<¨𝕩}
# 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)
S{Push((𝕨 Tiles Player 𝕩 ))𝕩} # 𝕨 S 𝕩 | 𝕨: direction | 𝕩:level | Step the game
Draw{chars˜+´¨𝕨 S´ 𝕩} # 𝕨 Draw 𝕩 | 𝕨: levels | 𝕩: moves | Draw the game in ASCII
W{(2¨¨𝕩) =(+´) (<23)¨𝕩} # W 𝕩 | 𝕩: level | [W]in condition
N{movesmoves<𝕩}
Undo{𝕊:moves(-1<)moves}
While {𝕨{𝕊𝔾𝔽𝕩}𝕩@}´
# Main loop
levelsAscii2Matrix¨>¨SplitOnEmpty•FLines "levels" # Load file containing levels
currentLevel0
•term.RawMode 1 # set terminal to raw mode
•Out "[?25l" # Cursor to origin, hide it and clear screen
clear""
While {𝕤currentLevel<levels}{𝕤 # Loop until the user wins
•Out "" # Cursor to origin
•Out "Level: "•Repr 1+currentLevel
•Out "7" # Save cursor position
•Out "Controls: (hjkl) to move, u to undo, r to reset level"
•Out˘ (currentLevellevels) Draw moves
key•term.CharB @
{𝕤N ("hjkl"=key)/0¯1,10,¯10,01}(key"hjkl")@
{𝕤Undo @}(key='u')@
{𝕤•Out "[?12l[?25h"•Exit 0}(key='q')@
{𝕤moves00}(key='r')@
{𝕤clear""}(((1+>(1+101))¯1+moves)key='u')@
•Out "8" # Restore cursor position
{𝕤•Out clear"Controls: (hjkl) to move, u to undo, r to reset level"}(key"hjkluqr")@
{𝕤•Out "Invalid key: (hjkl) to move, u to undo, r to reset level"}(¬key"hjkluqr")@
•Out˘ (currentLevellevels) Draw moves
•Out "Moves: "•Repr ¯1+moves
{𝕤•Out ""currentLevelcurrentLevel+1moves00}(W (currentLevellevels) S´moves)@
}
•Out "Well played, you win!"
•Out "[?12l[?25h"