634 lines
21 KiB
Awk
634 lines
21 KiB
Awk
#!/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()
|
|
}
|