First upload
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
[submodule "engines/dale-8a"]
|
||||
path = engines/dale-8a
|
||||
url = git://git.luxferre.top/dale-8a.git
|
||||
[submodule "games/nnfc"]
|
||||
path = games/nnfc
|
||||
url = git://git.luxferre.top/nnfc.git
|
||||
[submodule "games/awlite"]
|
||||
path = games/awlite
|
||||
url = git://git.luxferre.top/awlite.git
|
||||
@@ -0,0 +1,32 @@
|
||||
AWK Gold Collection: my best software created for POSIX AWK
|
||||
-----------------------------------------------------------
|
||||
In this repo, I decided to collect all useful software I have written in AWK
|
||||
throughout the recent years. All of it is strictly POSIX-compliant. The list
|
||||
is divided into three directories: engines, games and utils.
|
||||
|
||||
Some software contains its own README files, please read them carefully.
|
||||
Other AWK scripts have the usage described in their comment headers.
|
||||
|
||||
= Emulators, interpreters and game engines =
|
||||
|
||||
* engines/awpix.awk: Pix64 game engine port (prototype)
|
||||
* engines/dale-8a: CHIP-8 virtual machine port
|
||||
(a submodule from git://git.luxferre.top/dale-8a.git)
|
||||
* engines/lvtl.awk: VTL-2 programming language interpreter
|
||||
* engines/subleq.awk: Subleq-16 VM port (can run .dec files)
|
||||
* engines/tch.awk: TinyChoice game engine port
|
||||
|
||||
= Games =
|
||||
|
||||
* games/nnfc: FreeCell solitaire game
|
||||
(a submodule from git://git.luxferre.top/nnfc.git)
|
||||
* games/awlite: Text Elite 1.5 port with further improvements and fixes
|
||||
(a submodule from git://git.luxferre.top/awlite.git)
|
||||
|
||||
= Utilities =
|
||||
|
||||
* utils/textereo.awk: ASCII art stereogram generator
|
||||
* utils/tgl.awk: The Great Library of useful functions missing in POSIX AWK
|
||||
|
||||
|
||||
--- Luxferre ---
|
||||
@@ -0,0 +1,633 @@
|
||||
#!/sbin/env awk -f
|
||||
# AWPix - a prototype port of Pix64 console to POSIX AWK
|
||||
# Requires png2ppm command (netpbm package) to decode PNG carts
|
||||
# Usage: LANG=C awk -f awpix.awk cart.png[ cart_2.png] ...
|
||||
# Controls: WASD - movement, R - reset, Esc - exit
|
||||
# Created by Luxferre in 2023, released into public domain
|
||||
|
||||
# fatal error reporting function
|
||||
function trapout(msg) {
|
||||
shutdown()
|
||||
cmd = "cat 1>&2"
|
||||
printf("Fatal: %s\n", msg) | cmd
|
||||
close(cmd)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
# graceful shutdown function - restore the terminal state
|
||||
function shutdown() {printf(SCR_CLR); altbufoff(); close(KEY_INPUT_STREAM); setterm(0)}
|
||||
|
||||
# terminal control routines
|
||||
function altbufon() {printf("\033[?47h")}
|
||||
function altbufoff() {printf("\033[?47l")}
|
||||
function setterm(mode, cmd) {
|
||||
if(system("stty >/dev/null 2>&1")) return 0 # exit code 0 means we're in a tty
|
||||
if(!TGL_TERMMODE) { # cache the original terminal input mode
|
||||
(cmd = "stty -g") | getline TGL_TERMMODE
|
||||
close(cmd)
|
||||
}
|
||||
if(mode == 1) cmd = "-icanon"
|
||||
else if(mode == 2) cmd = "-icanon -echo"
|
||||
else if(mode == 3) cmd = "-icanon time 0 min 0 -echo"
|
||||
else cmd = TGL_TERMMODE # restore the original mode
|
||||
return system("stty " cmd ">/dev/null 2>&1") # execute the stty command
|
||||
}
|
||||
|
||||
function readkeynb(key) { # read a key, non-blocking fashion
|
||||
KEY_INPUT_STREAM | getline key # open the subprocess
|
||||
key = int(key) # read the key state
|
||||
close(KEY_INPUT_STREAM)
|
||||
if(key == 27) {shutdown(); exit(0)} # exit on Esc
|
||||
if(key == 119 || key == 87) return 1 # W
|
||||
if(key == 115 || key == 83) return 2 # S
|
||||
if(key == 97 || key == 65) return 4 # A
|
||||
if(key == 100 || key == 68) return 8 # D
|
||||
if(key == 114 || key == 82) return 16 # R
|
||||
return -1 # if not found, return -1
|
||||
}
|
||||
|
||||
# draw a pixel pair according to the color codes
|
||||
function getcolorpxl(val1, val2) {
|
||||
return sprintf("\033[3%u;4%um\342\226\200", val1, val2)
|
||||
}
|
||||
|
||||
# all main rendering is done offscreen and then a single printf is called
|
||||
function drawscreen(s, i) {
|
||||
s = SCR_CLR # clear the screen
|
||||
for(i=screenWidth;i<screenSize;i++) {
|
||||
# render two pixel lines into one text line
|
||||
s = s getcolorpxl(screen[i-screenWidth], screen[i])
|
||||
if((i % screenWidth) == (screenWidth - 1)) {
|
||||
s = s "\n"
|
||||
i += screenWidth
|
||||
}
|
||||
}
|
||||
s = s SCR_SRESET # reset styling
|
||||
printf("%s", s) # output everything
|
||||
}
|
||||
|
||||
# show the game over banner
|
||||
function showGameover(w, h, x, y, i, j, datastr, banner) {
|
||||
w = 35 # banner width
|
||||
h = 5 # banner height
|
||||
x = int((screenWidth - w) / 2) # start x position
|
||||
y = int((screenHeight - h) / 2) # start y position
|
||||
datastr = \
|
||||
"0 1 1 0 0 1 0 0 1 0 1 0 1 1 1 0 0 0 0 0 0 1 0 0 1 0 1 0 1 1 1 0 1 1 0 " \
|
||||
"1 0 0 0 1 0 1 0 1 1 1 0 1 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 1 0 0 0 1 0 1 " \
|
||||
"1 0 1 0 1 1 1 0 1 1 1 0 1 1 0 0 0 0 0 0 1 0 1 0 1 0 1 0 1 1 0 0 1 1 0 " \
|
||||
"1 0 1 0 1 0 1 0 1 0 1 0 1 0 0 0 0 0 0 0 1 0 1 0 1 0 1 0 1 0 0 0 1 0 1 " \
|
||||
"0 1 1 0 1 0 1 0 1 0 1 0 1 1 1 0 0 0 0 0 0 1 0 0 0 1 0 0 1 1 1 0 1 0 1"
|
||||
split(datastr, banner)
|
||||
for(i=0;i<screenSize;i++) screen[i] = 0 # clear the screen
|
||||
# fill the banner
|
||||
for(j=0;j<h;j++)
|
||||
for(i=0;i<w;i++)
|
||||
screen[(y+j) * screenWidth + x + i] = banner[1 + j*w + i]
|
||||
}
|
||||
|
||||
# show the victory banner
|
||||
function showVictory(w, h, x, y, i, j, datastr, banner) {
|
||||
w = 27 # banner width
|
||||
h = 5 # banner height
|
||||
x = int((screenWidth - w) / 2) # start x position
|
||||
y = int((screenHeight - h) / 2) # start y position
|
||||
datastr = \
|
||||
"2 0 2 0 2 2 2 0 0 2 2 0 2 2 2 0 0 2 0 0 2 2 0 0 2 0 2 " \
|
||||
"2 0 2 0 0 2 0 0 2 0 0 0 0 2 0 0 2 0 2 0 2 0 2 0 2 0 2 " \
|
||||
"2 0 2 0 0 2 0 0 2 0 0 0 0 2 0 0 2 0 2 0 2 2 0 0 0 2 0 " \
|
||||
"2 0 2 0 0 2 0 0 2 0 0 0 0 2 0 0 2 0 2 0 2 0 2 0 0 2 0 " \
|
||||
"0 2 0 0 2 2 2 0 0 2 2 0 0 2 0 0 0 2 0 0 2 0 2 0 0 2 0 "
|
||||
split(datastr, banner)
|
||||
for(i=0;i<screenSize;i++) screen[i] = 0 # clear the screen
|
||||
# fill the banner
|
||||
for(j=0;j<h;j++)
|
||||
for(i=0;i<w;i++)
|
||||
screen[(y+j) * screenWidth + x + i] = banner[1 + j*w + i]
|
||||
}
|
||||
|
||||
# game logic implemented here
|
||||
|
||||
function getPos(x, y) { # calculate the actual screen position
|
||||
x = (screenWidth + x) % screenWidth
|
||||
y = (screenHeight + y) % screenHeight
|
||||
return y * screenWidth + x
|
||||
}
|
||||
|
||||
# locate and initialize all sprite objects
|
||||
# a sprite here is a sequence of connected same-color pixels
|
||||
# any spritemem entry is a sequence of numbers:
|
||||
# color pos1 pos2 pos3 ...
|
||||
|
||||
function initsprite(pos, color, sid, x, y, i, scross, si) {
|
||||
if(screen[pos] != color) return # do nothing if the color doesn't match
|
||||
# restore the coordinates (it's more convenient)
|
||||
x = pos % screenWidth
|
||||
y = int(pos / screenWidth)
|
||||
if(!(sid in spritemem)) { # first-time sprite adding logic
|
||||
spritemem[sid] = color # start the sprite line
|
||||
if(color == 1) enemies[sid] = sid
|
||||
else if(color == 2) {goals[sid] = sid; goalCount++}
|
||||
else if(color == 3) barriers[sid] = sid
|
||||
else if(color == 6) players[sid] = sid
|
||||
else if(color == 7) walls[sid] = sid
|
||||
}
|
||||
split("", scross) # init sprite cross
|
||||
scross[0] = pos
|
||||
si = 1 # sprite cross index
|
||||
# try to identify same sprite pixels on the same line and column
|
||||
for(i=1;i<screenWidth;i++) {
|
||||
pos = getPos(x+i, y)
|
||||
if(screen[pos] == color)
|
||||
scross[si++] = pos # append this position
|
||||
else break
|
||||
}
|
||||
for(i=1;i<screenWidth;i++) {
|
||||
pos = getPos(x-i, y)
|
||||
if(screen[pos] == color)
|
||||
scross[si++] = pos # append this position
|
||||
else break
|
||||
}
|
||||
for(i=1;i<screenHeight;i++) {
|
||||
pos = getPos(x, y+i)
|
||||
if(screen[pos] == color)
|
||||
scross[si++] = pos # append this position
|
||||
else break
|
||||
}
|
||||
for(i=1;i<screenHeight;i++) {
|
||||
pos = getPos(x, y-i)
|
||||
if(screen[pos] == color)
|
||||
scross[si++] = pos # append this position
|
||||
else break
|
||||
}
|
||||
for(i in scross) { # iterate over the cross
|
||||
screen[scross[i]] = 0 # clear this pixel
|
||||
spritemem[sid] = spritemem[sid] " " scross[i]
|
||||
x = scross[i] % screenWidth
|
||||
y = int(scross[i] / screenWidth)
|
||||
# now, recursively call this function for all edges
|
||||
pos = getPos(x - 1, y - 1) # upper left
|
||||
if(screen[pos] == color) initsprite(pos, color, sid)
|
||||
pos = getPos(x + 1, y - 1) # upper right
|
||||
if(screen[pos] == color) initsprite(pos, color, sid)
|
||||
pos = getPos(x - 1, y + 1) # lower left
|
||||
if(screen[pos] == color) initsprite(pos, color, sid)
|
||||
pos = getPos(x + 1, y + 1) # lower right
|
||||
if(screen[pos] == color) initsprite(pos, color, sid)
|
||||
}
|
||||
}
|
||||
|
||||
function buildsprites(sid, pos) {
|
||||
spritemem[0] = 0 # the first entry is always 0
|
||||
sid = 1 # start from sprite id 1
|
||||
for(pos=0;pos<screenSize;pos++) {
|
||||
if(screen[pos] > 0) # non-empty pixel
|
||||
initsprite(pos, screen[pos], sid++)
|
||||
}
|
||||
}
|
||||
|
||||
# find a sprite ID by the screen position
|
||||
# return 0 if not found
|
||||
function findsprite(pos, sid, i, l, tarr) {
|
||||
for(sid in spritemem) {
|
||||
l = split(spritemem[sid], tarr)
|
||||
for(i=2;i<=l;i++)
|
||||
if(int(tarr[i]) == pos) return sid
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
# raw sprite movement (no blitting)
|
||||
function movesprite(sid, dx, dy, px, py, tarr, rs, i, l) {
|
||||
if(dx == 0 && dy == 0) return spritemem[sid]
|
||||
l = split(spritemem[sid], tarr)
|
||||
rs = int(tarr[1]) # start the resulting sprite line
|
||||
for(i=2;i<=l;i++) {
|
||||
px = int(tarr[i]) % screenWidth
|
||||
py = int(int(tarr[i]) / screenWidth)
|
||||
rs = rs " " getPos(px + dx, py + dy)
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
# collision detection function that takes sprite ID and target X/Y
|
||||
# return value:
|
||||
# 0 if no collisions
|
||||
# 1 if collision CANNOT be resolved
|
||||
# 2 if collision was resolved by the deletion of a sprite
|
||||
# 3 if collision leads to game over
|
||||
# 4 if collision leads to victory
|
||||
function collide(sid, dx, dy, tarr, i, l, dsid, pos, stype, dtype, cst) {
|
||||
l = split(movesprite(sid, dx, dy), tarr) # temporary move
|
||||
cst = 0 # collision status
|
||||
stype = int(tarr[1]) # source pixel type
|
||||
for(i=2;i<=l;i++) { # collision detection loop
|
||||
pos = tarr[i] # get current position
|
||||
dtype = screen[pos] # get destination pixel type
|
||||
if(dtype > 0 && (dsid = findsprite(pos)) != sid) { # collision detected
|
||||
if((stype == 6 && dtype == 1) || (stype == 1 && dtype == 6))
|
||||
return 3 # player-enemy collision, game over
|
||||
else if((stype == 6 && dtype == 3) || (stype == 3 && dtype == 6)) {
|
||||
# player-barrier collision
|
||||
if(cst != 1) cst = 2
|
||||
sweeps[stype == 3 ? sid : dsid] = 1
|
||||
break
|
||||
}
|
||||
else if((stype == 6 && dtype == 2) || (stype == 2 && dtype == 6)) {
|
||||
# player-goal collision
|
||||
if(cst != 1) cst = 2
|
||||
goalCount--
|
||||
sweeps[stype == 2 ? sid : dsid] = 1
|
||||
break
|
||||
}
|
||||
else { # any other type of collision is marked as unresolved
|
||||
cst = 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if(goalCount <= 0) return 4 # victory condition
|
||||
return cst
|
||||
}
|
||||
|
||||
# draw a single sprite onto the screen
|
||||
function drawsprite(sid, tarr, i, l) {
|
||||
if(sid in spritemem) { # sprite still here => let's draw
|
||||
l = split(spritemem[sid], tarr)
|
||||
for(i=2;i<=l;i++) # actual drawing loop
|
||||
screen[tarr[i]] = tarr[1] # draw this pixel
|
||||
}
|
||||
}
|
||||
|
||||
# sprite auto-movement engine
|
||||
|
||||
# some quicksort implementation
|
||||
function qsort(A, left, right, i, last) {
|
||||
if(left >= right) return
|
||||
swap(A, left, left+int((right-left+1)*rand()))
|
||||
last = left
|
||||
for(i = left+1; i <= right; i++)
|
||||
if(int(A[i]) < int(A[left]))
|
||||
swap(A, ++last, i)
|
||||
swap(A, left, last)
|
||||
qsort(A, left, last-1)
|
||||
qsort(A, last+1, right)
|
||||
}
|
||||
|
||||
function swap(A, i, j, t) {
|
||||
t = A[i]; A[i] = A[j]; A[j] = t
|
||||
}
|
||||
|
||||
# uniq implementation
|
||||
function uniq(A, l, tmpx, i, c) {
|
||||
for(i in A) {
|
||||
tmpx[int(A[i])] = i
|
||||
delete A[i]
|
||||
}
|
||||
c = 1 # counter
|
||||
for(i in tmpx) {
|
||||
A[c++] = int(i)
|
||||
delete tmpx[i]
|
||||
}
|
||||
return c-1 # new length of A
|
||||
}
|
||||
|
||||
# detect the box under which the sprite pixels are drawn
|
||||
# return the following concatenated values:
|
||||
# width height startx starty
|
||||
function detectbox(pxl, l, i, x, y, minx, miny, maxx, maxy) {
|
||||
maxx = maxy = 0
|
||||
minx = screenWidth
|
||||
miny = screenHeight
|
||||
for(i=1;i<=l;i++) {
|
||||
x = pxl[i] % screenWidth
|
||||
y = int(pxl[i] / screenWidth)
|
||||
if(x > maxx) maxx = x
|
||||
if(y > maxy) maxy = y
|
||||
if(x < minx) minx = x
|
||||
if(y < miny) miny = y
|
||||
}
|
||||
return (maxx - minx + 1) " " (maxy - miny + 1) " " minx " " miny
|
||||
}
|
||||
|
||||
function abs(v) {return v < 0 ? -v : v}
|
||||
|
||||
# detect movement direction from the sorted sprite shape
|
||||
# returned direction value is:
|
||||
# up-left 5
|
||||
# up 1
|
||||
# up-right 9
|
||||
# left 4
|
||||
# right 8
|
||||
# down-left 6
|
||||
# down 2
|
||||
# down-right 10
|
||||
function detectdir(pxl, l, i, sw, sh, md, xs, box, f, hf) {
|
||||
if(l%2 == 0 || l < 3) return 0 # all arrows have odd number of pixels
|
||||
split(detectbox(pxl, l), box)
|
||||
sw = box[1] # sprite width
|
||||
sh = box[2] # sprite height
|
||||
md = sw < sh ? sw : sh # minimum dimension
|
||||
if(md < 2) return 0 # all arrow sprites are at least 2x2
|
||||
if(l != 2*md - 1) return 0 # all arrow sprites have 2*md - 1 entries
|
||||
split("", xs) # clear x coordinate vector
|
||||
for(i=1;i<=l;i++) xs[i-1] = (pxl[i] % screenWidth) - box[3]
|
||||
# now, we have a clear pattern of X coordinate numbers
|
||||
# (because the pixels are ordered, we don't need to check Y coordinates)
|
||||
if(sw == sh) { # diagonal movement is only defined for square boxes
|
||||
hf = 1 # horizontal line detection flag
|
||||
for(i=0;i<sw;i++) hf = hf && (xs[i] == i)
|
||||
if(hf) { # up-left or up-right
|
||||
f = 1 # detection flag
|
||||
for(i=sw;i<(2*sw)-1;i++) f = f && (xs[i] == 0)
|
||||
if(f) return 5 # up-left
|
||||
f = 1 # detection flag
|
||||
for(i=sw;i<(2*sw)-1;i++) f = f && (xs[i] == (sw-1))
|
||||
if(f) return 9 # up-right
|
||||
}
|
||||
hf = 1 # horizontal line detection flag
|
||||
for(i=sw-1;i<(2*sw)-1;i++) hf = hf && (xs[i] == (i-sw+1))
|
||||
if(hf) { # down-left or down-right
|
||||
f = 1 # detection flag
|
||||
for(i=0;i<sw-1;i++) f = f && (xs[i] == 0)
|
||||
if(f) return 6 # down-left
|
||||
f = 1 # detection flag
|
||||
for(i=0;i<sw-1;i++) f = f && (xs[i] == sw-1)
|
||||
if(f) return 10 # down-right
|
||||
}
|
||||
} else if(sw == 2*sh - 1) { # try to detect a vertically moving arrow
|
||||
f = 1 # detection flag
|
||||
for(i=0;i<l;i++)
|
||||
f = f && (xs[i] == sh - 1 + int((i+1)/2)*(i%2 ? -1 : 1))
|
||||
if(f) return 1 # arrow up detected
|
||||
f = 1 # detection flag
|
||||
for(i=0;i<l;i++)
|
||||
f = f && (xs[l - 1 - i] == sh - 1 + int((i+1)/2)*(i%2 ? 1 : -1))
|
||||
if(f) return 2 # arrow down detected
|
||||
} else if(sh == 2*sw - 1) { # try to detect a horizontally moving arrow
|
||||
f = 1 # detection flag
|
||||
for(i=0;i<l;i++) f = f && (xs[i] == abs(sw - i - 1))
|
||||
if(f) return 4 # arrow left detected
|
||||
f = 1 # detection flag
|
||||
for(i=0;i<l;i++) f = f && (xs[i] == sw - abs(sw - i - 1) - 1)
|
||||
if(f) return 8 # arrow right detected
|
||||
}
|
||||
return 0 # no movement detected
|
||||
}
|
||||
|
||||
# detect auto-moving sprites from sprite memory
|
||||
function buildautos(sid, tarr, i, l, pxl, rs) {
|
||||
split("", autos) # clear the array
|
||||
for(sid in spritemem) {
|
||||
l = split(spritemem[sid], tarr)
|
||||
split("", pxl) # clear the pixel array
|
||||
for(i=2;i<=l;i++) # iterate over pixel positions
|
||||
pxl[i-1] = int(tarr[i])
|
||||
l-- # get the pixel array length into l
|
||||
l = uniq(pxl, l)
|
||||
qsort(pxl, 1, l) # get sorted pixel positions into pxl
|
||||
rs = int(tarr[1]) # build the sorted sprite
|
||||
for(i=1;i<=l;i++) rs = rs " " pxl[i]
|
||||
spritemem[sid] = rs # save the sorted sprite
|
||||
if((rs = detectdir(pxl, l)) > 0) # arrow sprite detected
|
||||
autos[sid] = rs # save the direction
|
||||
}
|
||||
}
|
||||
|
||||
# flip an auto-moving sprite direction and redraw it
|
||||
function flipdirection(sid, fliph, flipv, tarr, i, l, pxl, \
|
||||
dir, rs, box, x, y, sw, sh, sx, sy) {
|
||||
# change the direction
|
||||
dir = int(autos[sid])
|
||||
if(flipv && (dir%4)) # vertical flip logic
|
||||
dir = int(dir/4) * 4 + (3 - (dir%4))
|
||||
if(fliph && int(dir/4)) # horizontal flip logic
|
||||
dir = (int(dir/8) ? 4 : 8) + (dir%4)
|
||||
autos[sid] = dir
|
||||
# redraw the sprite
|
||||
l = split(spritemem[sid], tarr)
|
||||
rs = tarr[1] # start the resulting sprite line
|
||||
split("", pxl) # clear the pixel array
|
||||
for(i=2;i<=l;i++) # iterate over pixel positions
|
||||
pxl[i-1] = int(tarr[i])
|
||||
l-- # get the pixel array length into l
|
||||
split(detectbox(pxl, l), box) # get the box
|
||||
sw = box[1] # sprite width
|
||||
sh = box[2] # sprite height
|
||||
sx = box[3] # start x coord
|
||||
sy = box[4] # start y coord
|
||||
for(i in pxl) { # flip individual pixels according to the box
|
||||
x = pxl[i] % screenWidth
|
||||
y = int(pxl[i] / screenWidth)
|
||||
if(fliph) x = sx + sw - (x - sx) - 1
|
||||
if(flipv) y = sy + sh - (y - sy) - 1
|
||||
rs = rs " " getPos(x, y)
|
||||
}
|
||||
spritemem[sid] = rs # save the updated sprite
|
||||
}
|
||||
|
||||
# perform all logic here
|
||||
function logicloop(i, dx, dy, adx, ady, cres, deltas) {
|
||||
if(victoryFlag) {
|
||||
showVictory() # show victory banner
|
||||
if(keystatus > 0) return 999 # exit on any key
|
||||
else return 0
|
||||
}
|
||||
else if(gameoverFlag) {
|
||||
showGameover() # show game over banner
|
||||
return 0
|
||||
}
|
||||
dx = dy = adx = ady = 0
|
||||
if(keystatus == 1) dy = -1 # move up
|
||||
else if(keystatus == 2) dy = 1 # move down
|
||||
else if(keystatus == 4) dx = -1 # move left
|
||||
else if(keystatus == 8) dx = 1 # move right
|
||||
# clear the screen buffer
|
||||
for(i=0;i<screenSize;i++) screen[i] = 0
|
||||
split("", moves) # clear the move map
|
||||
# pre-draw the objects for collision detection
|
||||
for(i in walls) drawsprite(i)
|
||||
for(i in enemies) drawsprite(i)
|
||||
for(i in goals) drawsprite(i)
|
||||
for(i in barriers) drawsprite(i)
|
||||
# pre-draw and pre-move all manually movable sprites
|
||||
for(i in players) {
|
||||
drawsprite(i)
|
||||
if(!(i in autos)) moves[i] = dx " " dy
|
||||
}
|
||||
# pre-move all automatically movable sprites
|
||||
for(i in autos) { # key: sid, value: 1248 up down left right
|
||||
adx = ady = 0
|
||||
if(autos[i]%2) ady = -1
|
||||
if(int(autos[i]/2)%2) ady = 1
|
||||
if(int(autos[i]/4)%2) adx = -1
|
||||
if(int(autos[i]/8)%2) adx = 1
|
||||
moves[i] = adx " " ady
|
||||
}
|
||||
# perform all movements with collision detection
|
||||
for(i in moves) { # key: sid, value: dx dy pair
|
||||
split(moves[i], deltas)
|
||||
dx = int(deltas[1]); dy = int(deltas[2])
|
||||
if(dx || dy) { # only do anything if movement is performed
|
||||
cres = collide(i, dx, dy) # run the collision simulator
|
||||
if(cres == 1 || cres == 2) { # unresolvable collision
|
||||
keystatus = 0
|
||||
# don't do anything unless this is an auto-moving sprite
|
||||
if(i in autos) { # reuse adx and ady to save additional results
|
||||
adx = ady = 0
|
||||
if(dx == 0) ady = 1 # only vertical flip
|
||||
else if(dy == 0) adx = 1 # only horizontal flip
|
||||
else { # we need to detect what side we collided with
|
||||
if(collide(i, dx, 0) == cres) adx = 1 # left/right side
|
||||
if(collide(i, 0, dy) == cres) ady = 1 # lower/upper side
|
||||
if(adx == 0 && ady == 0) adx = ady = 1
|
||||
}
|
||||
flipdirection(i, adx, ady) # flip the sprite and its direction
|
||||
while(collide(i, 0, 0) == cres) # we still are in a collision state
|
||||
spritemem[i] = movesprite(i, -dx, -dy)
|
||||
}
|
||||
} else { # no collision or it's resolved
|
||||
if(cres == 3) {gameoverFlag = 1;keystatus = 0}
|
||||
else if(cres == 4) {victoryFlag = 1;keystatus = 0}
|
||||
if(i in spritemem) # sprite still here, move it for real
|
||||
spritemem[i] = movesprite(i, dx, dy)
|
||||
}
|
||||
}
|
||||
}
|
||||
# sweep all the sprites pending deletion
|
||||
for(i in sweeps) {
|
||||
if(i in spritemem) delete spritemem[i]
|
||||
if(i in players) delete players[i]
|
||||
if(i in goals) delete goals[i]
|
||||
if(i in walls) delete walls[i]
|
||||
if(i in enemies) delete enemies[i]
|
||||
if(i in barriers) delete barriers[i]
|
||||
if(i in moves) delete moves[i]
|
||||
if(i in autos) delete autos[i]
|
||||
if(i in sweeps) delete sweeps[i]
|
||||
}
|
||||
# clear the screen buffer
|
||||
for(i=0;i<screenSize;i++) screen[i] = 0
|
||||
# update the screen buffer in the correct order
|
||||
for(i in walls) drawsprite(i)
|
||||
for(i in enemies) drawsprite(i)
|
||||
for(i in goals) drawsprite(i)
|
||||
for(i in barriers) drawsprite(i)
|
||||
for(i in players) drawsprite(i)
|
||||
return 0 # normal loop iteration
|
||||
}
|
||||
|
||||
# entry point code here
|
||||
|
||||
function runmachine(fname) {
|
||||
# clear the arrays
|
||||
split("", screen)
|
||||
split("", spritemem)
|
||||
split("", sweeps)
|
||||
split("", walls) # 7
|
||||
split("", enemies) # 1
|
||||
split("", goals) # 2
|
||||
split("", barriers) # 3
|
||||
split("", players) # 6
|
||||
|
||||
# load the rom in a clever way:
|
||||
cmd = "png2pnm -n \"" fname "\""
|
||||
cmd | getline pformat
|
||||
if(pformat != "P3") trapout("Invalid image format!")
|
||||
i = 0
|
||||
while((cmd | getline) > 0) { # fill raw image data
|
||||
if(NF > 0)
|
||||
for(j=1;j<=NF;j++)
|
||||
IMGDATA[i++] = int($j)
|
||||
}
|
||||
close(cmd)
|
||||
# the first three values are width, height and maxval
|
||||
screenWidth = IMGDATA[0]
|
||||
screenHeight = IMGDATA[1]
|
||||
mval = IMGDATA[2]
|
||||
# now, convert the image data into the actual field data
|
||||
# according to the terminal color codes:
|
||||
# black 0, red 1, green 2, yellow 3, cyan 6, white 7
|
||||
screenSize = screenWidth * screenHeight
|
||||
goalCount = 0 # green pixel count
|
||||
gameoverFlag = 0 # game over flag
|
||||
victoryFlag = 0 # game victory flag
|
||||
for(i=0;i<screenSize;i++) {
|
||||
j = (i+1) * 3 # base index to read from
|
||||
r = IMGDATA[j]; g = IMGDATA[j+1]; b = IMGDATA[j+2]
|
||||
if(r == 0 && g == 0 && b == 0) screen[i] = 0 # black
|
||||
else if(r == mval && g == 0 && b == 0) screen[i] = 1 # red
|
||||
else if(r == 0 && g == mval && b == 0) screen[i] = 2 # green
|
||||
else if(r == mval && g == mval && b == 0) screen[i] = 3 # yellow
|
||||
else if(r == 0 && g == mval && b == mval) screen[i] = 6 # cyan
|
||||
else if(r == mval && g == mval && b == mval) screen[i] = 7 # white
|
||||
else trapout(sprintf("invalid color %d, %d, %d!", r, g, b))
|
||||
delete IMGDATA[j]; delete IMGDATA[j+1]; delete IMGDATA[j+2]
|
||||
}
|
||||
delete IMGDATA[0]
|
||||
delete IMGDATA[1]
|
||||
delete IMGDATA[2]
|
||||
# now, we have all screen data in screen array
|
||||
buildsprites() # build the spritemem array with all sprites
|
||||
buildautos() # build the autos array with auto-moving sprites
|
||||
# main execution logic starts here
|
||||
altbufon() # enter the alternative screen buffer
|
||||
setterm(3) # enter the non-blocking input mode before the event loop
|
||||
while(1) { # our event loop is here
|
||||
if((key = readkeynb()) > 0) keystatus = key
|
||||
else keystatus = 0
|
||||
if(keystatus == 16) {loopstatus = 888; break}
|
||||
loopstatus = logicloop() # handle all events
|
||||
if(loopstatus > 0) break # break on anomaly
|
||||
drawscreen()
|
||||
a=0
|
||||
for(i=0;i<framecycle;i++) a+=i # sleep on 1/15 sec, more efficiently
|
||||
}
|
||||
if(loopstatus == 888) # game over/restart trigger
|
||||
runmachine(fname) # restart from the beginning on the loop break
|
||||
else return # victory
|
||||
}
|
||||
|
||||
# get current Unix timestamp with millisecond precision with various methods
|
||||
function timestampms(cmd, res) {
|
||||
cmd = "echo $EPOCHREALTIME"
|
||||
cmd | getline res
|
||||
close(cmd)
|
||||
sub(/[,\.]/,"", res)
|
||||
res = int(res)
|
||||
if(res) return res / 1000 # micro=>milli
|
||||
# otherwise we need to use an alternate, POSIX-compatible method
|
||||
cmd = "date +%s"
|
||||
cmd | getline res
|
||||
close(cmd)
|
||||
return int(res) * 1000 # s=>milli
|
||||
}
|
||||
|
||||
# determine the amount of empty cycles needed to fill a single frame
|
||||
function hostprofile(i, cps, sc, st, et) {
|
||||
sc = 2000000 # this is an arbitrarily large (but not too large) cycle count
|
||||
do {
|
||||
sc += 200000
|
||||
st = timestampms()
|
||||
a = 0
|
||||
for(i=0;i<sc;i++) a += i
|
||||
et = timestampms()
|
||||
} while(et == st)
|
||||
# now, we have our cps metric
|
||||
cps = 1000 * sc / (int(et) - int(st))
|
||||
# but we need 1/15 second
|
||||
return int(cps / 15)
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
print "Profiling the frame timing..."
|
||||
framecycle = hostprofile() # get the amount of host cycles to skip
|
||||
print "Detected cycles per frame:", framecycle
|
||||
if(ARGC < 2) trapout("no cart .png file specified!")
|
||||
# init some string constants and parameters
|
||||
SCR_CLR = sprintf("\033[2J") # screen clear command
|
||||
SCR_SRESET = sprintf("\033[0m\033[0;0H")
|
||||
KEY_INPUT_STREAM = "od -tu1 -w1 -An -N1 -v"
|
||||
for(c=1;c<ARGC;c++) runmachine(ARGV[c]) # run all arguments sequentially
|
||||
shutdown()
|
||||
}
|
||||
Submodule
+1
Submodule engines/dale-8a added at c504f446ee
@@ -0,0 +1,187 @@
|
||||
# LVTL-W: port of LVTL-R to Busybox/GNU AWK in under 130 SLOC
|
||||
# should also work in any other AWK version with bitwise operations
|
||||
#
|
||||
# Usage: [busybox] awk -f lvtl.awk [prog.vtl -]
|
||||
# (don't forget the - after the program or LVTL will exit upon preloading)
|
||||
#
|
||||
# Any program valid for LVTL-W will also run on LVTL-R and LVTL-O and,
|
||||
# if it doesn't use bitwise ops, on the original VTL-2 and VTL02 too
|
||||
#
|
||||
# Differences from the C version of LVTL-R (besides being much slower):
|
||||
# - lines are stored in an AWK array where line numbers are keys
|
||||
# - maximum line length is not enforced
|
||||
# - the variables and array areas are fully separated from line memory
|
||||
# - because of all this, & is always 0 and * is always 65535
|
||||
# - extra whitespace before and after line numbers is accepted
|
||||
# - only LFs are printed instead of CRLFs
|
||||
# - strings are allowed in immediate statements
|
||||
#
|
||||
# Differences from the original VTL-2 for Altair:
|
||||
# - both LF and CRLF are accepted (but not saved into RAM)
|
||||
# - only LFs are printed instead of CRLFs
|
||||
# - maximum line length is not enforced
|
||||
# - all whitespace after the line number is fully ignored
|
||||
# - any whitespace within expressions is also insignificant
|
||||
# - parentheses are NOT auto-closed at the end of the statement
|
||||
# - input with ? is NOT evaluated and only numbers are accepted
|
||||
# - only 26 characters (A-Z) are valid generic variable names
|
||||
# - supports all standard VTL-2 binary operators: +, -, *, /, =, >, <
|
||||
# - also supports bitwise operators: & (and), ! (or), # (xor)
|
||||
# - jumps to itself (like 10 #=10) are prohibited and counted as nops
|
||||
# - no file I/O (only the option to preload VTL code from a file)
|
||||
# - self-modifying programs won't work correctly
|
||||
#
|
||||
# Created by Luxferre in 2023, released into public domain
|
||||
|
||||
# Global arrays and vars: ORD, PROGLINES, VARS, SCRATCH, PROCCHARS
|
||||
|
||||
# fatal error reporter
|
||||
function trapout(errmsg) {printf("Fatal error: %s\n", errmsg); exit(1)}
|
||||
|
||||
# read a generic/system variable
|
||||
function getvar(varname, c) {
|
||||
res = 0
|
||||
if(varname == "$") { # get a single character from stdin
|
||||
(cmd="c='';read -r -n 1 c;echo \"$c\"") | getline c
|
||||
close(cmd)
|
||||
res = ORD[substr(c,1,1)]
|
||||
}
|
||||
else if(varname == "?") { # get a number from stdin
|
||||
getline c # read the line
|
||||
res = and(int(c), 65535) # cast to 16-bit integer
|
||||
}
|
||||
else if(varname == "'") res = int(rand()*65536) # get a pseudorandom number
|
||||
else if(varname in VARS) res = VARS[varname] # regular or system variable
|
||||
return res
|
||||
}
|
||||
|
||||
# set a generic/system variable
|
||||
function setvar(varname, value, qi) {
|
||||
if(varname == "$") printf("%c", and(int(value), 255)) # output a character
|
||||
else if(varname == "?") { # output a string or a number
|
||||
if(VARS["\""]) { # we print a string
|
||||
qi = index(value, "\"") # find the ending quote
|
||||
printf("%s", substr(value, 1, qi - 1))
|
||||
# also print LF unless stated otherwise with ; modifier
|
||||
if(substr(value, qi+1, 1) != ";") printf("\n")
|
||||
VARS["\""] = 0 # reset the printing flag
|
||||
}
|
||||
else printf("%u", and(int(value), 65535)) # we print a number, no LF
|
||||
}
|
||||
else if(varname in VARS) { # regular or system variable
|
||||
value = and(int(value), 65535) # cast to 16-bit integer
|
||||
# if the var is #, cache the return address before writing
|
||||
if(varname == "#" && value > 0) VARS["!"] = VARS["#"] + 1
|
||||
VARS[varname] = value # update the value
|
||||
}
|
||||
else trapout("invalid variable name!")
|
||||
}
|
||||
|
||||
# strict LTR evaluator
|
||||
function evalexpr(expr, tkn, i, l, opex, acc, cop, oprnd, subs, res) {
|
||||
opex = 1
|
||||
res = cop = acc = oprnd = 0
|
||||
l = length(expr) # GAWK doesn't support multi-init in fors, busybox does
|
||||
for(i=1;i<=l;i++) { # token position is stored in i
|
||||
tkn = substr(expr, i, 1) # get a single character token
|
||||
if(tkn == ")") break # ditch the rest of expression
|
||||
if(tkn == " " || tkn == "\t") continue # skip all whitespace
|
||||
if(opex = 1 - opex) { # we expect an operator
|
||||
if(index("+-*/=<>&!#", tkn)) cop = tkn # save the valid operator
|
||||
else trapout(sprintf("unexpected binary operator at %u\n", VARS["#"]))
|
||||
}
|
||||
else { # we expect a value, a variable, a subscript or a subexpr
|
||||
if(tkn == "\"") { # quote immediately prints and returns
|
||||
VARS[tkn] = 1
|
||||
return substr(expr, i+1) # return everything after the quote
|
||||
}
|
||||
# extract and evaluate a parens or subscript expression
|
||||
else if(tkn == ":" || tkn == "(") {
|
||||
subs = evalexpr(substr(expr, i+1)) # process everything after tkn
|
||||
# set the operand to array elem or eval result
|
||||
oprnd = (tkn == ":") ? SCRATCH[subs] : subs
|
||||
i += PROCCHARS # update the outer index
|
||||
}
|
||||
else if(index("0123456789", tkn)) { # it's a digit
|
||||
oprnd = int(substr(expr, i)) # read the integer
|
||||
i += length(oprnd) - 1 # skip the rest
|
||||
}
|
||||
else oprnd = getvar(tkn) # we assume it's a valid variable name
|
||||
# now, perform the calculation - no default branch!
|
||||
if(oprnd < 0) oprnd += 65536 # keep the operand positive
|
||||
oprnd = and(oprnd, 65535) # keep the operand within 16 bits
|
||||
if(cop == 0) acc = oprnd
|
||||
else if(cop == "+") acc += oprnd
|
||||
else if(cop == "-") acc -= oprnd
|
||||
else if(cop == "*") acc *= oprnd
|
||||
else if(cop == "/" && oprnd) {
|
||||
VARS["%"] = acc % oprnd # keep the remainder in the % sysvar
|
||||
acc = int(acc/oprnd)
|
||||
}
|
||||
else if(cop == "=") acc = (acc == oprnd) ? 1 : 0
|
||||
else if(cop == ">") acc = (acc >= oprnd) ? 1 : 0
|
||||
else if(cop == "<") acc = (acc < oprnd) ? 1 : 0
|
||||
else if(cop == "&") acc = and(acc, oprnd)
|
||||
else if(cop == "!") acc = or(acc, oprnd)
|
||||
else if(cop == "#") acc = xor(acc, oprnd)
|
||||
if(acc < 0) acc += 65536 # keep the accumulator positive
|
||||
res = acc = and(acc, 65535) # keep the accumulator within 16 bits
|
||||
}
|
||||
}
|
||||
PROCCHARS = i # because we started from 1
|
||||
return res
|
||||
}
|
||||
|
||||
# run the statement
|
||||
function exec_stmt(stmt, ln, lcache, lhs, rhs, ei, vn) {
|
||||
lcache = ln # cache the line number
|
||||
ei = index(stmt, "=") # get the first occurrence of =
|
||||
lhs = substr(stmt, 1, ei - 1) # extract LHS as everything before =
|
||||
rhs = substr(stmt, ei + 1) # extract RHS as everything after =
|
||||
if(!length(rhs) || !length(lhs)) return # nop on invalid statements
|
||||
ei = evalexpr(rhs) # evaluate RHS
|
||||
vn = substr(lhs, 1, 1) # extract the varname
|
||||
# evaluate the array subscript and update the array
|
||||
if(vn == ":") SCRATCH[evalexpr(substr(lhs, 2))] = ei
|
||||
else setvar(vn, ei) # update the (pseudo) variable
|
||||
# seek the next statement
|
||||
ln = VARS["#"]
|
||||
# we're starting from interactive mode OR updating the number
|
||||
if((!ln && lcache) || (ln > 0 && ln == lcache)) ln = lcache + 1
|
||||
if(ln > 0) {
|
||||
for(ei=ln;ei<65536;ei++) if(ei in PROGLINES) {ln = ei; break} # search
|
||||
if(ei >= 65536) ln = 0 # line not found
|
||||
VARS["#"] = ln # save the closest line number
|
||||
if(ln) exec_stmt(PROGLINES[ln], ln) # pass new statement for execution
|
||||
}
|
||||
}
|
||||
|
||||
BEGIN { # interpreter init
|
||||
"date +%N"|getline rseed;srand(rseed) # init the PRNG
|
||||
PROCCHARS = 0 # global cache to track processed characters
|
||||
for(i=0;i<256;i++) ORD[sprintf("%c", i)] = i # init char-to-ASCII mapping
|
||||
for(i=0;i<26;i++) VARS[sprintf("%c", i+ORD["A"])] = 0 # init the var memory
|
||||
# initialize sysvars
|
||||
VARS["!"] = VARS["\""] = VARS["#"] = VARS["%"] = VARS["&"] = 0
|
||||
VARS["*"] = 65535
|
||||
for(i=0;i<65536;i++) SCRATCH[i] = 0 # initialize the scratch area
|
||||
print "LVTL-W by Luxferre\nPress Ctrl+C to exit\n\nOK"
|
||||
}
|
||||
|
||||
{ # main line-by-line processing
|
||||
ln = and(int($1), 65535) # attempt to scan line number (default to 0)
|
||||
if(NF > 1) $1 = "" # prepare to scan the statement
|
||||
gsub(/^[ \t]+|[ \t]+$/, "") # trim the statement
|
||||
if(ln > 0) { # we have a line number
|
||||
if($0 == "" || $0 == ln) delete PROGLINES[ln] # delete the line
|
||||
else PROGLINES[ln] = $0 # cache the statement
|
||||
}
|
||||
else { # it's an immediate statement
|
||||
if($0 == "0") { # list the program
|
||||
for(i=1;i<65536;i++) # start from 1 because 0 is not a valid line number
|
||||
if(i in PROGLINES) print i, PROGLINES[i] # list the found line
|
||||
}
|
||||
else exec_stmt($0, 0) # pass to immediate execution
|
||||
print "\nOK"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/sbin/env awk -f
|
||||
# POSIX AWK port of Subleq VM (16-bit variant)
|
||||
# Accepts .dec files as input
|
||||
# Usage: [busybox] awk -f subleq.awk program.dec
|
||||
# Created by Luxferre in 2023, released into public domain
|
||||
|
||||
function L(v) { # cast any value to unsigned 16-bit integer
|
||||
v = int(v)
|
||||
while(v < 0) v += 65536
|
||||
return int(v%65536)
|
||||
}
|
||||
|
||||
function getchar(c, cmd) { # POSIX-compatible getchar emulation with sh read
|
||||
(cmd="c='';IFS= read -r -n 1 -d $'\\0' c;printf '%u' \"'$c\"") | getline c
|
||||
close(cmd)
|
||||
return int(c)
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
for(pc=0;pc<65536;pc++) MEM[pc] = 0 # init the memory array
|
||||
pc = a = b = c = 0 # reset the program counter and other vars
|
||||
}
|
||||
|
||||
# match on any 16-bit signed integer in the .dec file
|
||||
{ for(i=1;i<=NF;i++) if($i ~ /^[-0-9][0-9]*$/) MEM[pc++] = L($i) }
|
||||
|
||||
END { # start the actual execution
|
||||
for(pc=0;pc<32768;) {
|
||||
a = MEM[pc++]; b = MEM[pc++]; c = MEM[pc++] # fill the cell addresses
|
||||
# -1 in cell A => input to cell B
|
||||
if(a == 65535) MEM[b] = L(getchar())
|
||||
# -1 in cell B => output cell A
|
||||
else if(b == 65535) printf("%c", MEM[a]%256)
|
||||
# main OISC logic here
|
||||
else {
|
||||
MEM[b] = L(MEM[b] - MEM[a]) # subtract the first 2 cells and cast
|
||||
if(MEM[b] == 0 || (MEM[b] > 32767)) pc = c # jump if result <=0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#!/sbin/env awk -f
|
||||
# TCh: the simplest TinyChoice game engine port to POSIX AWK
|
||||
# Usage: awk -f tch.awk your-story.txt
|
||||
# Controls: respond with a number to go to a particular choice, q to quit
|
||||
# Created by Luxferre in 2023, released into public domain
|
||||
|
||||
BEGIN {
|
||||
curscreen = "" # store the current screen key
|
||||
firstscreen = ""
|
||||
split("", screens) # init the screens map
|
||||
if(ARGC < 2) {print "No input story file!"; exit(1)}
|
||||
while((getline < ARGV[1]) > 0) {
|
||||
if($0 ~ /^=.*=$/) { # header line processing
|
||||
if(curscreen) { # finalize the previous entry
|
||||
gsub(/^\n+/, "", screens[curscreen])
|
||||
gsub(/\n+$/, "", screens[curscreen])
|
||||
} else firstscreen = -999
|
||||
curscreen = tolower(substr($0, 2, length($0)-2))
|
||||
if(firstscreen == -999) firstscreen = curscreen # save the key value
|
||||
} else if(curscreen) screens[curscreen] = screens[curscreen] $0 "\n"
|
||||
}
|
||||
close(ARGV[1])
|
||||
delete ARGV[1]
|
||||
# actual runtime logic
|
||||
curscreen = ("start" in screens) ? "start" : firstscreen
|
||||
while(length(curscreen) > 0) { # get the lines of the current screen
|
||||
l = split(screens[curscreen], lines, "\n")
|
||||
split("", choices) # init the choices array
|
||||
ch = 1 # init the choice number
|
||||
for(i=1;i<=l;i++) { # we must iterate in order
|
||||
line = lines[i]
|
||||
if(index(line, "->") > 0) { # we have a choice spec
|
||||
split(line, rch, /[ \t]*\->[ \t]*/)
|
||||
tlabel = rch[1] # label is before ->
|
||||
tscreen = rch[2] # target screen is after ->
|
||||
gsub(/^[ \t]+/, "", tlabel) # trim leading spaces in the label
|
||||
gsub(/[ \t]+$/, "", tlabel) # trim trailing spaces in the label
|
||||
gsub(/^[ \t]+/, "", tscreen) # trim leading spaces in the ref
|
||||
gsub(/[ \t]+$/, "", tscreen) # trim trailing spaces in the ref
|
||||
if(!(tscreen in screens)) {
|
||||
printf("Invalid reference %s!\n", tscreen)
|
||||
exit(1)
|
||||
}
|
||||
choices[ch] = tscreen
|
||||
printf("%u) %s\n", ch++, tlabel)
|
||||
} else print line # just print the line "as is"
|
||||
}
|
||||
do {
|
||||
printf "\n> "; if((getline) <= 0) break # prompt for the user choice
|
||||
if($1 == "q" || $1 == "Q" || $1 == "quit" || $1 == "QUIT") {
|
||||
print "Bye!"; exit(0)
|
||||
}
|
||||
ch = int($1)
|
||||
} while(!(ch && (ch in choices) && (choices[ch] in screens)))
|
||||
curscreen = choices[ch] # jump to the selected screen
|
||||
}
|
||||
}
|
||||
Submodule
+1
Submodule games/awlite added at 026c365e50
Submodule
+1
Submodule games/nnfc added at 4b5c40b637
@@ -0,0 +1,73 @@
|
||||
# Textereo: ASCII stereogram generator in AWK
|
||||
# Usage: [busybox] awk -f textereo.awk map.txt
|
||||
# Map format:
|
||||
# First line: [desired width] [maxpattern length]
|
||||
# Second line: alphabet (allowed characters to generate patterns from)
|
||||
# Next H lines: depth map, W digits from 0 to 7 without spaces
|
||||
# You can insert single zeros or empty lines into the map to adjust height
|
||||
#
|
||||
# Created by Luxferre in 2023, released into public domain
|
||||
|
||||
function dchar(str, pos) { # delete a character from str at position pos
|
||||
if(!pos) return str
|
||||
else return substr(str, 1, pos - 1) substr(str, pos + 1)
|
||||
}
|
||||
|
||||
function ichar(str, pos, c) { # insert a character c into str at position pos
|
||||
return substr(str, 1, pos - 1) c substr(str, pos)
|
||||
}
|
||||
|
||||
BEGIN { # we use 95-character subset from ASCII by default
|
||||
"date +%N"|getline rseed;srand(rseed) # init the PRNG
|
||||
getline # read the first line from the file to parse parameters
|
||||
WIDTH = int($1) # desired width of the image to generate
|
||||
PATLEN = int($2) # generated pattern length (must be between 8 and W/2)
|
||||
getline # read the full character set
|
||||
CHARSET = $0
|
||||
CHRANGE = length(CHARSET)
|
||||
if(WIDTH == 0) WIDTH = 78
|
||||
if(PATLEN < 8 || PATLEN > (WIDTH/2)) PATLEN = 10 # 10 to 15 is optimal
|
||||
}
|
||||
|
||||
{ # process the map itself
|
||||
j = 0 # set the secondary counter to store previous value
|
||||
FREEBUF = CHARSET # start with the full charset
|
||||
PATBUF = "" # start with empty pattern buffer
|
||||
mapline = $0 # cache the map line
|
||||
RWIDTH = length(mapline); # real pattern width
|
||||
tlen = (WIDTH - RWIDTH) / 2 # trailer AND header length
|
||||
for(i=0;i<tlen;i++) mapline = "0" mapline "0" # grow the line to the width
|
||||
if(length(mapline) > WIDTH) mapline = substr(mapline, 1, WIDTH)
|
||||
for(i=0;i<PATLEN;i++) { # fill in the pattern buffer
|
||||
do # we can repeat the characters in the pattern but not immediately
|
||||
pbc = substr(CHARSET, 1+int(rand()*CHRANGE), 1)
|
||||
while(pbc == j || !length(pbc)) # don't allow empty or repeating chars
|
||||
PATBUF = PATBUF pbc # concatenate the character to the pattern
|
||||
j = pbc # save as the previous value to j
|
||||
# delete this character from the unused buffer
|
||||
FREEBUF = dchar(FREEBUF, index(FREEBUF, pbc))
|
||||
}
|
||||
j = 0 # reset the secondary counter
|
||||
prevlen = PATLEN # cache the previous pattern length
|
||||
maplen = length(mapline)
|
||||
patpos = 0 # track pattern position
|
||||
for(i=0;++i<=maplen;) { # iterate over every character
|
||||
curlen = PATLEN - int(substr(mapline, i, 1)) # extract depth value and length
|
||||
delta = curlen - prevlen
|
||||
if(delta > 0) # add unused characters if decreasing depth value
|
||||
for(j=0;j<delta;j++) {
|
||||
ri = 1+int(rand()*length(FREEBUF)) # get random index
|
||||
rc = substr(FREEBUF, ri, 1) # retrieve the character
|
||||
FREEBUF = dchar(FREEBUF, ri) # delete it from the buffer
|
||||
PATBUF = ichar(PATBUF, 1+patpos, rc) # insert it here
|
||||
}
|
||||
else if(delta < 0) # remove characters if increasing depth value
|
||||
for(j=0;j<-delta;j++)
|
||||
PATBUF = dchar(PATBUF, 1+patpos)
|
||||
prevlen = curlen # update the previous length
|
||||
# now, output the current pattern character
|
||||
printf("%c", substr(PATBUF, 1 + (patpos % curlen), 1))
|
||||
patpos = (patpos + 1) % curlen # update pattern position
|
||||
}
|
||||
printf("\n")
|
||||
}
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
# The Great Library of useful AWK functions
|
||||
# Fully POSIX-compatible but sometimes depends on other POSIX commands
|
||||
# Use with your programs like this:
|
||||
# LANG=C awk -f tgl.awk -f your_prog.awk [args]
|
||||
#
|
||||
# Current functionality:
|
||||
# * single character input: setterm, getchar
|
||||
# * ASCII and UTF-8 codepoint conversion: ord, wctomb, mbtowc
|
||||
# * loading binary files as decimal integers into arrays: loadbin
|
||||
# * saving binary files from arrays with decimal integers: savebin
|
||||
# * tangent and cotangent functions: tan, cotan
|
||||
# * signum, floor and ceiling functions: sign, floor, ceil
|
||||
# * test for native bitwise operation support: bw_native_support
|
||||
# * reimplementation of most bitwise operations (unsigned 32-bit):
|
||||
# - NOT: bw_compl
|
||||
# - AND: bw_and
|
||||
# - OR: bw_or
|
||||
# - XOR: bw_xor
|
||||
# - NAND: bw_nand
|
||||
# - NOR: bw_nor
|
||||
# - >>: bw_rshift
|
||||
# - <<: bw_lshift
|
||||
#
|
||||
# Created by Luxferre in 2023, released into public domain
|
||||
|
||||
# set/restore the terminal input mode using stty
|
||||
# usage: setterm(0|1|2|3)
|
||||
# 0 - restore the original terminal input mode
|
||||
# 1 - blocking single-character input with echo
|
||||
# 2 - blocking single-character input without echo
|
||||
# 3 - non-blocking single-character input without echo
|
||||
# in pipes, this function doesn't do anything
|
||||
# (but returns 0 since it's not an error)
|
||||
# otherwise an actual stty exit code is returned
|
||||
function setterm(mode, cmd) {
|
||||
if(system("stty >/dev/null 2>&1")) return 0 # exit code 0 means we're in a tty
|
||||
if(!TGL_TERMMODE) { # cache the original terminal input mode
|
||||
(cmd = "stty -g") | getline TGL_TERMMODE
|
||||
close(cmd)
|
||||
}
|
||||
if(mode == 1) cmd = "-icanon"
|
||||
else if(mode == 2) cmd = "-icanon -echo"
|
||||
else if(mode == 3) cmd = "-icanon time 0 min 0 -echo"
|
||||
else cmd = TGL_TERMMODE # restore the original mode
|
||||
return system("stty " cmd ">/dev/null 2>&1") # execute the stty command
|
||||
}
|
||||
|
||||
# getchar emulation using od
|
||||
# caches the read command for further usage
|
||||
# also able to capture null bytes, unlike read/printf approach
|
||||
# use in conjunction with setterm to achieve different input modes
|
||||
# setting LANG=C envvar is recommended, for GAWK it is required
|
||||
# usage: getchar() => integer
|
||||
function getchar(c) {
|
||||
if(!TGL_GCH_CMD) TGL_GCH_CMD = "od -tu1 -w1 -N1 -An -v" # first time usage
|
||||
TGL_GCH_CMD | getline c
|
||||
close(TGL_GCH_CMD)
|
||||
return int(c)
|
||||
}
|
||||
|
||||
# get the ASCII code of a character
|
||||
# setting LANG=C envvar is recommended, for GAWK it is required
|
||||
# usage: ord(c) => integer
|
||||
function ord(c, b) {
|
||||
# init char-to-ASCII mapping if it's not there yet
|
||||
if(!TGL_ORD["#"]) for(b=0;b<256;b++) TGL_ORD[sprintf("%c", b)] = b
|
||||
return int(TGL_ORD[c])
|
||||
}
|
||||
|
||||
# encode a single integer UTF-8 codepoint into a byte sequence in a string
|
||||
# setting LANG=C envvar is recommended, for GAWK it is required
|
||||
# usage: wctomb(code) => string
|
||||
# we can safely use the string type for all codepoints above 0 as all
|
||||
# multibyte sequences have a high bit set, so no null byte is there
|
||||
# for invalid codepoints, an empty string will be returned
|
||||
function wctomb(code, s) {
|
||||
code = int(code)
|
||||
if(code < 0 || code > 1114109) s = "" # invalid codepoint
|
||||
else if(code < 128) s = sprintf("%c", code) # single byte
|
||||
else if(code < 2048) # 2-byte sequence
|
||||
s = sprintf("%c%c", \
|
||||
192 + (int(code/64) % 32), \
|
||||
128 + (code % 64))
|
||||
else if(code < 65536) # 3-byte sequence
|
||||
s = sprintf("%c%c%c", \
|
||||
224 + (int(code/4096) % 16), \
|
||||
128 + (int(code/64) % 64), \
|
||||
128 + (code % 64))
|
||||
else # 4-byte sequence
|
||||
s = sprintf("%c%c%c%c", \
|
||||
240 + (int(code/262144) % 8), \
|
||||
128 + (int(code/4096) % 64), \
|
||||
128 + (int(code/64) % 64), \
|
||||
128 + (code % 64))
|
||||
return s
|
||||
}
|
||||
|
||||
# decode a byte string into a UTF-8 codepoint
|
||||
# setting LANG=C envvar is recommended, for GAWK it is required
|
||||
# usage: mbtowc(s) => integer
|
||||
# decoding stops on the first encountered invalid byte
|
||||
function mbtowc(s, len, code, b, pos) {
|
||||
len = length(s)
|
||||
code = 0
|
||||
for(pos=1;pos<=len;pos++) {
|
||||
code *= 64 # shift the code 6 bits left
|
||||
b = ord(substr(s, pos, 1))
|
||||
if(pos == 1) { # expect a single or header byte
|
||||
if(b < 128) {code = b; break} # it resolves into a single byte
|
||||
else if(b >= 192 && b < 224) # it's a header byte of 2-byte sequence
|
||||
code += b % 32
|
||||
else if(b >= 224 && b < 240) # it's a header byte of 3-byte sequence
|
||||
code += b % 16
|
||||
else if(b >= 240) # it's a header byte of 4-byte sequence
|
||||
code += b % 8
|
||||
else break # a trailer byte in the header position is invalid
|
||||
}
|
||||
else if(b >= 128 && b < 192) # it must be a trailer byte
|
||||
code += b % 64
|
||||
else break # a header byte in the trailer position is invalid
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
# load any binary file into an AWK array (0-indexed), depends on od
|
||||
# returns the resulting array length
|
||||
# usage: loadbin(fname, arr, len, wordsize) => integer
|
||||
# len parameter is optional, specifies how many bytes to read
|
||||
# (if 0 or unset, read everything)
|
||||
# wordsize parameter is optional, 1 byte by default
|
||||
# multibyte words are considered little-endian
|
||||
function loadbin(fname, arr, len, wordsize, cmd, i) {
|
||||
wordsize = int(wordsize)
|
||||
if(wordsize < 1) wordsize = 1
|
||||
len = int(len)
|
||||
i = (len > 0) ? (" -N" len " ") : ""
|
||||
cmd = "od -tu" wordsize " -An -w" wordsize i " -v \"" fname "\""
|
||||
# every line should be a single decimal integer (with some whitespace)
|
||||
i = 0
|
||||
while((cmd | getline) > 0) # read the next line from the stream
|
||||
if(NF) arr[i++] = int($1) # read the first and only field
|
||||
close(cmd) # close the od process
|
||||
return i
|
||||
}
|
||||
|
||||
# save an AWK array (0-indexed) into a binary file
|
||||
# setting LANG=C envvar is recommended, for GAWK it is required
|
||||
# returns the amount of written elements
|
||||
# usage: savebin(fname, arr, len, wordsize) => integer
|
||||
# wordsize parameter is optional, 1 byte by default
|
||||
# multibyte words are considered little-endian
|
||||
function savebin(fname, arr, len, wordsize, i, j) {
|
||||
wordsize = int(wordsize)
|
||||
if(wordsize < 1) wordsize = 1
|
||||
printf("") > fname # truncate the file and open the stream
|
||||
for(i=0;i<len;i++) {
|
||||
if(wordsize == 1) printf("%c", arr[i]) >> fname
|
||||
else # we have a multibyte word size
|
||||
for(j=0;j<wordsize;j++)
|
||||
printf("%c", int(arr[i]/2^(8*j))%256) >> fname
|
||||
}
|
||||
close(fname) # close the output file
|
||||
return i
|
||||
}
|
||||
|
||||
# the missing tangent/cotangent functions
|
||||
|
||||
function tan(x) {return sin(x)/cos(x)}
|
||||
function cotan(x) {return cos(x)/sin(x)}
|
||||
|
||||
# the missing sign/floor/ceil functions
|
||||
|
||||
function sign(x) {return x < 0 ? -1 : !!x}
|
||||
function floor(x, f) {
|
||||
f = int(x)
|
||||
if(x == f) return x
|
||||
else return x >= 0 ? f : (f - 1)
|
||||
}
|
||||
function ceil(x, f) {
|
||||
f = int(x)
|
||||
if(x == f) return x
|
||||
else return x >= 0 ? (f + 1) : f
|
||||
}
|
||||
|
||||
# Bitwise operations section
|
||||
|
||||
# test if the AWK engine has non-POSIX bitwise operation functions
|
||||
# (and, or, xor, compl, lshift, rshift) implemented natively:
|
||||
# if compl is missing, it will be concatenated with 1 and equal to 1
|
||||
# so the inverse of this condition will be the result
|
||||
function bw_native_support() {return (compl (1) != 1)}
|
||||
|
||||
# now, the implementation of the operations themselves
|
||||
# note that all complements are 32-bit and all operands must be non-negative
|
||||
|
||||
function bw_compl(a) {return 4294967295 - int(a)}
|
||||
function bw_lshift(a, b) {for(;b>0;b--) a = int(a/2);return a}
|
||||
function bw_rshift(a, b) {for(;b>0;b--) a *= 2;return int(a)}
|
||||
function bw_and(a, b, v, r) {
|
||||
v = 1; r = 0
|
||||
while(a > 0 || b > 0) {
|
||||
if((a%2) == 1 && (b%2) == 1) r += v
|
||||
a = int(a/2)
|
||||
b = int(b/2)
|
||||
v *= 2
|
||||
}
|
||||
return int(r)
|
||||
}
|
||||
function bw_or(a, b, v, r) {
|
||||
v = 1; r = 0
|
||||
while(a > 0 || b > 0) {
|
||||
if((a%2) == 1 || (b%2) == 1) r += v
|
||||
a = int(a/2)
|
||||
b = int(b/2)
|
||||
v *= 2
|
||||
}
|
||||
return int(r)
|
||||
}
|
||||
function bw_xor(a, b, v, r) {
|
||||
v = 1; r = 0
|
||||
while(a > 0 || b > 0) {
|
||||
if((a%2) != (b%2)) r += v
|
||||
a = int(a/2)
|
||||
b = int(b/2)
|
||||
v *= 2
|
||||
}
|
||||
return int(r)
|
||||
}
|
||||
function bw_nand(a, b) {return bw_compl(bw_and(a,b))}
|
||||
function bw_nor(a, b) {return bw_compl(bw_or(a,b))}
|
||||
|
||||
Reference in New Issue
Block a user