#!/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 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 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 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 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;imilli # 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