awk-gold-collection

My best software created for POSIX AWK
git clone git://git.luxferre.top/awk-gold-collection.git
Log | Files | Refs | Submodules | README

awpix.awk (21650B)


      1 #!/sbin/env awk -f
      2 # AWPix - a prototype port of Pix64 console to POSIX AWK
      3 # Requires png2ppm command (netpbm package) to decode PNG carts
      4 # Usage: LANG=C awk -f awpix.awk cart.png[ cart_2.png] ...
      5 # Controls: WASD - movement, R - reset, Esc - exit
      6 # Created by Luxferre in 2023, released into public domain
      7 
      8 # fatal error reporting function
      9 function trapout(msg) {
     10   shutdown()
     11   cmd = "cat 1>&2"
     12   printf("Fatal: %s\n", msg) | cmd
     13   close(cmd)
     14   exit(1)
     15 }
     16 
     17 # graceful shutdown function - restore the terminal state
     18 function shutdown() {printf(SCR_CLR); altbufoff(); close(KEY_INPUT_STREAM); setterm(0)}
     19 
     20 # terminal control routines
     21 function altbufon() {printf("\033[?47h")}
     22 function altbufoff() {printf("\033[?47l")}
     23 function setterm(mode, cmd) {
     24   if(system("stty >/dev/null 2>&1")) return 0 # exit code 0 means we're in a tty
     25   if(!TGL_TERMMODE) { # cache the original terminal input mode
     26     (cmd = "stty -g") | getline TGL_TERMMODE
     27     close(cmd)
     28   }
     29   if(mode == 1) cmd = "-icanon"
     30   else if(mode == 2) cmd = "-icanon -echo"
     31   else if(mode == 3) cmd = "-icanon time 0 min 0 -echo"
     32   else cmd = TGL_TERMMODE # restore the original mode
     33   return system("stty " cmd ">/dev/null 2>&1") # execute the stty command
     34 }
     35 
     36 function readkeynb(key) { # read a key, non-blocking fashion
     37   KEY_INPUT_STREAM | getline key # open the subprocess
     38   key = int(key) # read the key state
     39   close(KEY_INPUT_STREAM)
     40   if(key == 27) {shutdown(); exit(0)} # exit on Esc
     41   if(key == 119 || key == 87) return 1 # W
     42   if(key == 115 || key == 83) return 2 # S
     43   if(key == 97 || key == 65) return 4 # A
     44   if(key == 100 || key == 68) return 8 # D
     45   if(key == 114 || key == 82) return 16 # R
     46   return -1 # if not found, return -1
     47 }
     48 
     49 # draw a pixel pair according to the color codes
     50 function getcolorpxl(val1, val2) {
     51   return sprintf("\033[3%u;4%um\342\226\200", val1, val2)
     52 }
     53 
     54 # all main rendering is done offscreen and then a single printf is called
     55 function drawscreen(s, i) {
     56   s = SCR_CLR # clear the screen
     57   for(i=screenWidth;i<screenSize;i++) {
     58     # render two pixel lines into one text line
     59     s = s getcolorpxl(screen[i-screenWidth], screen[i])
     60     if((i % screenWidth) == (screenWidth - 1)) {
     61       s = s "\n"
     62       i += screenWidth
     63     }
     64   }
     65   s = s SCR_SRESET # reset styling
     66   printf("%s", s) # output everything
     67 }
     68 
     69 # show the game over banner
     70 function showGameover(w, h, x, y, i, j, datastr, banner) {
     71   w = 35 # banner width
     72   h = 5 # banner height
     73   x = int((screenWidth - w) / 2)  # start x position
     74   y = int((screenHeight - h) / 2) # start y position
     75   datastr = \
     76   "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 " \
     77   "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 " \
     78   "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 " \
     79   "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 " \
     80   "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"
     81   split(datastr, banner)
     82   for(i=0;i<screenSize;i++) screen[i] = 0 # clear the screen
     83   # fill the banner
     84   for(j=0;j<h;j++)
     85     for(i=0;i<w;i++)
     86       screen[(y+j) * screenWidth + x + i] = banner[1 + j*w + i]
     87 }
     88 
     89 # show the victory banner
     90 function showVictory(w, h, x, y, i, j, datastr, banner) {
     91   w = 27 # banner width
     92   h = 5 # banner height
     93   x = int((screenWidth - w) / 2)  # start x position
     94   y = int((screenHeight - h) / 2) # start y position
     95   datastr = \
     96   "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 " \
     97   "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 " \
     98   "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 " \
     99   "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 " \
    100   "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 "
    101   split(datastr, banner)
    102   for(i=0;i<screenSize;i++) screen[i] = 0 # clear the screen
    103   # fill the banner
    104   for(j=0;j<h;j++)
    105     for(i=0;i<w;i++)
    106       screen[(y+j) * screenWidth + x + i] = banner[1 + j*w + i]
    107 }
    108 
    109 # game logic implemented here
    110 
    111 function getPos(x, y) { # calculate the actual screen position
    112   x = (screenWidth + x) % screenWidth
    113   y = (screenHeight + y) % screenHeight
    114   return y * screenWidth + x
    115 }
    116 
    117 # locate and initialize all sprite objects
    118 # a sprite here is a sequence of connected same-color pixels
    119 # any spritemem entry is a sequence of numbers:
    120 # color pos1 pos2 pos3 ...
    121 
    122 function initsprite(pos, color, sid,  x, y, i, scross, si) {
    123   if(screen[pos] != color) return # do nothing if the color doesn't match
    124   # restore the coordinates (it's more convenient)
    125   x = pos % screenWidth
    126   y = int(pos / screenWidth)
    127   if(!(sid in spritemem)) { # first-time sprite adding logic
    128     spritemem[sid] = color # start the sprite line
    129     if(color == 1) enemies[sid] = sid
    130     else if(color == 2) {goals[sid] = sid; goalCount++}
    131     else if(color == 3) barriers[sid] = sid
    132     else if(color == 6) players[sid] = sid
    133     else if(color == 7) walls[sid] = sid
    134   }
    135   split("", scross) # init sprite cross
    136   scross[0] = pos
    137   si = 1 # sprite cross index
    138   # try to identify same sprite pixels on the same line and column
    139   for(i=1;i<screenWidth;i++) {
    140     pos = getPos(x+i, y)
    141     if(screen[pos] == color)
    142       scross[si++] = pos # append this position
    143     else break
    144   }
    145   for(i=1;i<screenWidth;i++) {
    146     pos = getPos(x-i, y)
    147     if(screen[pos] == color)
    148       scross[si++] = pos # append this position
    149     else break
    150   }
    151   for(i=1;i<screenHeight;i++) {
    152     pos = getPos(x, y+i)
    153     if(screen[pos] == color)
    154       scross[si++] = pos # append this position
    155     else break
    156   }
    157   for(i=1;i<screenHeight;i++) {
    158     pos = getPos(x, y-i)
    159     if(screen[pos] == color)
    160       scross[si++] = pos # append this position
    161     else break
    162   }
    163   for(i in scross) { # iterate over the cross
    164     screen[scross[i]] = 0 # clear this pixel
    165     spritemem[sid] = spritemem[sid] " " scross[i]
    166     x = scross[i] % screenWidth
    167     y = int(scross[i] / screenWidth)
    168     # now, recursively call this function for all edges
    169     pos = getPos(x - 1, y - 1) # upper left
    170     if(screen[pos] == color) initsprite(pos, color, sid)
    171     pos = getPos(x + 1, y - 1) # upper right
    172     if(screen[pos] == color) initsprite(pos, color, sid)
    173     pos = getPos(x - 1, y + 1) # lower left
    174     if(screen[pos] == color) initsprite(pos, color, sid)
    175     pos = getPos(x + 1, y + 1) # lower right
    176     if(screen[pos] == color) initsprite(pos, color, sid)
    177   }
    178 }
    179 
    180 function buildsprites(sid, pos) {
    181   spritemem[0] = 0 # the first entry is always 0
    182   sid = 1 # start from sprite id 1
    183   for(pos=0;pos<screenSize;pos++) {
    184     if(screen[pos] > 0) # non-empty pixel
    185       initsprite(pos, screen[pos], sid++)
    186   }
    187 }
    188 
    189 # find a sprite ID by the screen position
    190 # return 0 if not found
    191 function findsprite(pos,  sid, i, l, tarr) {
    192   for(sid in spritemem) {
    193     l = split(spritemem[sid], tarr)
    194     for(i=2;i<=l;i++)
    195       if(int(tarr[i]) == pos) return sid
    196   }
    197   return 0
    198 }
    199 
    200 # raw sprite movement (no blitting)
    201 function movesprite(sid, dx, dy,  px, py, tarr, rs, i, l) {
    202   if(dx == 0 && dy == 0) return spritemem[sid]
    203   l = split(spritemem[sid], tarr)
    204   rs = int(tarr[1]) # start the resulting sprite line
    205   for(i=2;i<=l;i++) {
    206     px = int(tarr[i]) % screenWidth
    207     py = int(int(tarr[i]) / screenWidth)
    208     rs = rs " " getPos(px + dx, py + dy)
    209   }
    210   return rs
    211 }
    212 
    213 # collision detection function that takes sprite ID and target X/Y
    214 # return value:
    215 # 0 if no collisions
    216 # 1 if collision CANNOT be resolved
    217 # 2 if collision was resolved by the deletion of a sprite
    218 # 3 if collision leads to game over
    219 # 4 if collision leads to victory
    220 function collide(sid, dx, dy,  tarr, i, l, dsid, pos, stype, dtype, cst) {
    221   l = split(movesprite(sid, dx, dy), tarr) # temporary move
    222   cst = 0 # collision status
    223   stype = int(tarr[1]) # source pixel type
    224   for(i=2;i<=l;i++) { # collision detection loop
    225     pos = tarr[i] # get current position
    226     dtype = screen[pos] # get destination pixel type
    227     if(dtype > 0 && (dsid = findsprite(pos)) != sid) { # collision detected
    228       if((stype == 6 && dtype == 1) || (stype == 1 && dtype == 6))
    229         return 3 # player-enemy collision, game over
    230       else if((stype == 6 && dtype == 3) || (stype == 3 && dtype == 6)) {
    231         # player-barrier collision
    232         if(cst != 1) cst = 2
    233         sweeps[stype == 3 ? sid : dsid] = 1
    234         break
    235       }
    236       else if((stype == 6 && dtype == 2) || (stype == 2 && dtype == 6)) {
    237         # player-goal collision
    238         if(cst != 1) cst = 2
    239         goalCount--
    240         sweeps[stype == 2 ? sid : dsid] = 1
    241         break
    242       }
    243       else { # any other type of collision is marked as unresolved
    244         cst = 1
    245         break
    246       }
    247     }
    248   }
    249   if(goalCount <= 0) return 4 # victory condition
    250   return cst
    251 }
    252 
    253 # draw a single sprite onto the screen
    254 function drawsprite(sid,  tarr, i, l) {
    255   if(sid in spritemem) { # sprite still here => let's draw
    256     l = split(spritemem[sid], tarr)
    257     for(i=2;i<=l;i++) # actual drawing loop
    258       screen[tarr[i]] = tarr[1] # draw this pixel
    259   }
    260 }
    261 
    262 # sprite auto-movement engine
    263 
    264 # some quicksort implementation
    265 function qsort(A, left, right,   i, last) {
    266   if(left >= right) return
    267   swap(A, left, left+int((right-left+1)*rand()))
    268   last = left
    269   for(i = left+1; i <= right; i++)
    270     if(int(A[i]) < int(A[left]))
    271       swap(A, ++last, i)
    272   swap(A, left, last)
    273   qsort(A, left, last-1)
    274   qsort(A, last+1, right)
    275 }
    276 
    277 function swap(A, i, j,   t) {
    278   t = A[i]; A[i] = A[j]; A[j] = t
    279 }
    280 
    281 # uniq implementation
    282 function uniq(A, l,  tmpx, i, c) {
    283   for(i in A) {
    284     tmpx[int(A[i])] = i
    285     delete A[i]
    286   }
    287   c = 1 # counter
    288   for(i in tmpx) { 
    289     A[c++] = int(i)
    290     delete tmpx[i]
    291   }
    292   return c-1 # new length of A
    293 }
    294 
    295 # detect the box under which the sprite pixels are drawn
    296 # return the following concatenated values:
    297 # width height startx starty
    298 function detectbox(pxl, l,  i, x, y, minx, miny, maxx, maxy) {
    299   maxx = maxy = 0
    300   minx = screenWidth
    301   miny = screenHeight
    302   for(i=1;i<=l;i++) {
    303     x = pxl[i] % screenWidth
    304     y = int(pxl[i] / screenWidth)
    305     if(x > maxx) maxx = x
    306     if(y > maxy) maxy = y
    307     if(x < minx) minx = x
    308     if(y < miny) miny = y
    309   }
    310   return (maxx - minx + 1) " " (maxy - miny + 1) " " minx " " miny
    311 }
    312 
    313 function abs(v) {return v < 0 ? -v : v}
    314 
    315 # detect movement direction from the sorted sprite shape
    316 # returned direction value is:
    317 # up-left 5
    318 # up 1
    319 # up-right 9
    320 # left 4
    321 # right 8
    322 # down-left 6
    323 # down 2
    324 # down-right 10
    325 function detectdir(pxl, l,  i, sw, sh, md, xs, box, f, hf) {
    326   if(l%2 == 0 || l < 3) return 0 # all arrows have odd number of pixels
    327   split(detectbox(pxl, l), box)
    328   sw = box[1] # sprite width
    329   sh = box[2] # sprite height
    330   md = sw < sh ? sw : sh # minimum dimension
    331   if(md < 2) return 0 # all arrow sprites are at least 2x2
    332   if(l != 2*md - 1) return 0 # all arrow sprites have 2*md - 1 entries
    333   split("", xs) # clear x coordinate vector
    334   for(i=1;i<=l;i++) xs[i-1] = (pxl[i] % screenWidth) - box[3]
    335   # now, we have a clear pattern of X coordinate numbers
    336   # (because the pixels are ordered, we don't need to check Y coordinates)
    337   if(sw == sh) { # diagonal movement is only defined for square boxes
    338     hf = 1 # horizontal line detection flag
    339     for(i=0;i<sw;i++) hf = hf && (xs[i] == i)
    340     if(hf) { # up-left or up-right
    341       f = 1 # detection flag
    342       for(i=sw;i<(2*sw)-1;i++) f = f && (xs[i] == 0)
    343       if(f) return 5 # up-left
    344       f = 1 # detection flag
    345       for(i=sw;i<(2*sw)-1;i++) f = f && (xs[i] == (sw-1))
    346       if(f) return 9 # up-right
    347     }
    348     hf = 1 # horizontal line detection flag
    349     for(i=sw-1;i<(2*sw)-1;i++) hf = hf && (xs[i] == (i-sw+1))
    350     if(hf) { # down-left or down-right
    351       f = 1 # detection flag
    352       for(i=0;i<sw-1;i++) f = f && (xs[i] == 0)
    353       if(f) return 6 # down-left
    354       f = 1 # detection flag
    355       for(i=0;i<sw-1;i++) f = f && (xs[i] == sw-1)
    356       if(f) return 10 # down-right
    357     }
    358   } else if(sw == 2*sh - 1) { # try to detect a vertically moving arrow
    359     f = 1 # detection flag
    360     for(i=0;i<l;i++)
    361       f = f && (xs[i] == sh - 1 + int((i+1)/2)*(i%2 ? -1 : 1))
    362     if(f) return 1 # arrow up detected
    363     f = 1 # detection flag
    364     for(i=0;i<l;i++)
    365       f = f && (xs[l - 1 - i] == sh - 1 + int((i+1)/2)*(i%2 ? 1 : -1))
    366     if(f) return 2 # arrow down detected
    367   } else if(sh == 2*sw - 1) { # try to detect a horizontally moving arrow
    368     f = 1 # detection flag
    369     for(i=0;i<l;i++) f = f && (xs[i] == abs(sw - i - 1))
    370     if(f) return 4 # arrow left detected
    371     f = 1 # detection flag
    372     for(i=0;i<l;i++) f = f && (xs[i] == sw - abs(sw - i - 1) - 1)
    373     if(f) return 8 # arrow right detected
    374   }
    375   return 0 # no movement detected
    376 }
    377 
    378 # detect auto-moving sprites from sprite memory
    379 function buildautos(sid, tarr, i, l, pxl, rs) {
    380   split("", autos) # clear the array
    381   for(sid in spritemem) {
    382     l = split(spritemem[sid], tarr)
    383     split("", pxl) # clear the pixel array
    384     for(i=2;i<=l;i++) # iterate over pixel positions 
    385       pxl[i-1] = int(tarr[i])
    386     l-- # get the pixel array length into l
    387     l = uniq(pxl, l)
    388     qsort(pxl, 1, l) # get sorted pixel positions into pxl
    389     rs = int(tarr[1]) # build the sorted sprite
    390     for(i=1;i<=l;i++) rs = rs " " pxl[i]
    391     spritemem[sid] = rs # save the sorted sprite
    392     if((rs = detectdir(pxl, l)) > 0) # arrow sprite detected
    393       autos[sid] = rs # save the direction
    394   }
    395 }
    396 
    397 # flip an auto-moving sprite direction and redraw it
    398 function flipdirection(sid, fliph, flipv,  tarr, i, l, pxl, \
    399                        dir, rs, box, x, y, sw, sh, sx, sy) {
    400   # change the direction
    401   dir = int(autos[sid])
    402   if(flipv && (dir%4)) # vertical flip logic
    403     dir = int(dir/4) * 4 + (3 - (dir%4))
    404   if(fliph && int(dir/4)) # horizontal flip logic
    405     dir = (int(dir/8) ? 4 : 8) + (dir%4)
    406   autos[sid] = dir
    407   # redraw the sprite
    408   l = split(spritemem[sid], tarr)
    409   rs = tarr[1] # start the resulting sprite line
    410   split("", pxl) # clear the pixel array
    411   for(i=2;i<=l;i++) # iterate over pixel positions 
    412     pxl[i-1] = int(tarr[i])
    413   l-- # get the pixel array length into l
    414   split(detectbox(pxl, l), box) # get the box
    415   sw = box[1] # sprite width
    416   sh = box[2] # sprite height
    417   sx = box[3] # start x coord
    418   sy = box[4] # start y coord
    419   for(i in pxl) { # flip individual pixels according to the box
    420     x = pxl[i] % screenWidth
    421     y = int(pxl[i] / screenWidth)
    422     if(fliph) x = sx + sw - (x - sx) - 1 
    423     if(flipv) y = sy + sh - (y - sy) - 1 
    424     rs = rs " " getPos(x, y)
    425   }
    426   spritemem[sid] = rs # save the updated sprite
    427 }
    428 
    429 # perform all logic here
    430 function logicloop(i, dx, dy, adx, ady, cres, deltas) {
    431   if(victoryFlag) {
    432     showVictory() # show victory banner
    433     if(keystatus > 0) return 999 # exit on any key
    434     else return 0
    435   }
    436   else if(gameoverFlag) {
    437     showGameover() # show game over banner
    438     return 0
    439   }
    440   dx = dy = adx = ady = 0
    441   if(keystatus == 1) dy = -1 # move up
    442   else if(keystatus == 2) dy = 1 # move down
    443   else if(keystatus == 4) dx = -1 # move left
    444   else if(keystatus == 8) dx = 1 # move right
    445   # clear the screen buffer
    446   for(i=0;i<screenSize;i++) screen[i] = 0
    447   split("", moves) # clear the move map
    448   # pre-draw the objects for collision detection
    449   for(i in walls) drawsprite(i)
    450   for(i in enemies) drawsprite(i)
    451   for(i in goals) drawsprite(i)
    452   for(i in barriers) drawsprite(i)
    453   # pre-draw and pre-move all manually movable sprites
    454   for(i in players) {
    455     drawsprite(i)
    456     if(!(i in autos)) moves[i] = dx " " dy
    457   }
    458   # pre-move all automatically movable sprites
    459   for(i in autos) { # key: sid, value: 1248 up down left right
    460     adx = ady = 0
    461     if(autos[i]%2) ady = -1
    462     if(int(autos[i]/2)%2) ady = 1
    463     if(int(autos[i]/4)%2) adx = -1
    464     if(int(autos[i]/8)%2) adx = 1
    465     moves[i] = adx " " ady
    466   }
    467   # perform all movements with collision detection
    468   for(i in moves) { # key: sid, value: dx dy pair
    469     split(moves[i], deltas)
    470     dx = int(deltas[1]); dy = int(deltas[2])
    471     if(dx || dy) { # only do anything if movement is performed
    472       cres = collide(i, dx, dy) # run the collision simulator
    473       if(cres == 1 || cres == 2) { # unresolvable collision
    474         keystatus = 0
    475         # don't do anything unless this is an auto-moving sprite
    476         if(i in autos) { # reuse adx and ady to save additional results
    477           adx = ady = 0
    478           if(dx == 0) ady = 1 # only vertical flip
    479           else if(dy == 0) adx = 1 # only horizontal flip
    480           else { # we need to detect what side we collided with
    481             if(collide(i, dx, 0) == cres) adx = 1 # left/right side
    482             if(collide(i, 0, dy) == cres) ady = 1 # lower/upper side
    483             if(adx == 0 && ady == 0) adx = ady = 1
    484           }
    485           flipdirection(i, adx, ady) # flip the sprite and its direction  
    486           while(collide(i, 0, 0) == cres) # we still are in a collision state
    487             spritemem[i] = movesprite(i, -dx, -dy)
    488         }
    489       } else { # no collision or it's resolved
    490         if(cres == 3) {gameoverFlag = 1;keystatus = 0}
    491         else if(cres == 4) {victoryFlag = 1;keystatus = 0}
    492         if(i in spritemem) # sprite still here, move it for real
    493           spritemem[i] = movesprite(i, dx, dy)
    494       }
    495     }
    496   }
    497   # sweep all the sprites pending deletion
    498   for(i in sweeps) {
    499     if(i in spritemem) delete spritemem[i]
    500     if(i in players) delete players[i]
    501     if(i in goals) delete goals[i]
    502     if(i in walls) delete walls[i]
    503     if(i in enemies) delete enemies[i]
    504     if(i in barriers) delete barriers[i]
    505     if(i in moves) delete moves[i]
    506     if(i in autos) delete autos[i]
    507     if(i in sweeps) delete sweeps[i]
    508   }
    509   # clear the screen buffer
    510   for(i=0;i<screenSize;i++) screen[i] = 0
    511   # update the screen buffer in the correct order
    512   for(i in walls) drawsprite(i)
    513   for(i in enemies) drawsprite(i)
    514   for(i in goals) drawsprite(i)
    515   for(i in barriers) drawsprite(i)
    516   for(i in players) drawsprite(i)
    517   return 0 # normal loop iteration
    518 }
    519 
    520 # entry point code here
    521 
    522 function runmachine(fname) {
    523   # clear the arrays
    524   split("", screen)
    525   split("", spritemem)
    526   split("", sweeps)
    527   split("", walls) # 7
    528   split("", enemies) # 1
    529   split("", goals) # 2
    530   split("", barriers) # 3
    531   split("", players) # 6
    532 
    533   # load the rom in a clever way:
    534   cmd = "png2pnm -n \"" fname "\""
    535   cmd | getline pformat
    536   if(pformat != "P3") trapout("Invalid image format!")
    537   i = 0
    538   while((cmd | getline) > 0) { # fill raw image data
    539     if(NF > 0)
    540       for(j=1;j<=NF;j++)
    541         IMGDATA[i++] = int($j)
    542   }
    543   close(cmd)
    544   # the first three values are width, height and maxval
    545   screenWidth = IMGDATA[0]
    546   screenHeight = IMGDATA[1]
    547   mval = IMGDATA[2]
    548   # now, convert the image data into the actual field data
    549   # according to the terminal color codes:
    550   # black 0, red 1, green 2, yellow 3, cyan 6, white 7
    551   screenSize = screenWidth * screenHeight
    552   goalCount = 0 # green pixel count
    553   gameoverFlag = 0 # game over flag
    554   victoryFlag = 0 # game victory flag
    555   for(i=0;i<screenSize;i++) {
    556     j = (i+1) * 3 # base index to read from
    557     r = IMGDATA[j]; g = IMGDATA[j+1]; b = IMGDATA[j+2]
    558     if(r == 0 && g == 0 && b == 0) screen[i] = 0 # black
    559     else if(r == mval && g == 0 && b == 0) screen[i] = 1 # red
    560     else if(r == 0 && g == mval && b == 0) screen[i] = 2 # green
    561     else if(r == mval && g == mval && b == 0) screen[i] = 3 # yellow
    562     else if(r == 0 && g == mval && b == mval) screen[i] = 6 # cyan
    563     else if(r == mval && g == mval && b == mval) screen[i] = 7 # white
    564     else trapout(sprintf("invalid color %d, %d, %d!", r, g, b))
    565     delete IMGDATA[j]; delete IMGDATA[j+1]; delete IMGDATA[j+2]
    566   }
    567   delete IMGDATA[0]
    568   delete IMGDATA[1]
    569   delete IMGDATA[2]
    570   # now, we have all screen data in screen array
    571   buildsprites() # build the spritemem array with all sprites
    572   buildautos() # build the autos array with auto-moving sprites
    573   # main execution logic starts here
    574   altbufon() # enter the alternative screen buffer
    575   setterm(3) # enter the non-blocking input mode before the event loop
    576   while(1) { # our event loop is here
    577     if((key = readkeynb()) > 0) keystatus = key
    578     else keystatus = 0
    579     if(keystatus == 16) {loopstatus = 888; break}
    580     loopstatus = logicloop() # handle all events
    581     if(loopstatus > 0) break # break on anomaly
    582     drawscreen()
    583     a=0
    584     for(i=0;i<framecycle;i++) a+=i # sleep on 1/15 sec, more efficiently
    585   }
    586   if(loopstatus == 888) # game over/restart trigger
    587     runmachine(fname) # restart from the beginning on the loop break
    588   else return # victory
    589 }
    590 
    591 # get current Unix timestamp with millisecond precision with various methods
    592 function timestampms(cmd, res) {
    593   cmd = "echo $EPOCHREALTIME"
    594   cmd | getline res
    595   close(cmd)
    596   sub(/[,\.]/,"", res)
    597   res = int(res)
    598   if(res) return res / 1000 # micro=>milli
    599   # otherwise we need to use an alternate, POSIX-compatible method
    600   cmd = "date +%s"
    601   cmd | getline res
    602   close(cmd)
    603   return int(res) * 1000 # s=>milli
    604 }
    605 
    606 # determine the amount of empty cycles needed to fill a single frame
    607 function hostprofile(i, cps, sc, st, et) {
    608   sc = 2000000 # this is an arbitrarily large (but not too large) cycle count
    609   do {
    610     sc += 200000
    611     st = timestampms()
    612     a = 0
    613     for(i=0;i<sc;i++) a += i
    614     et = timestampms()
    615   } while(et == st)
    616   # now, we have our cps metric
    617   cps = 1000 * sc / (int(et) - int(st))
    618   # but we need 1/15 second
    619   return int(cps / 15)
    620 }
    621 
    622 BEGIN {
    623   print "Profiling the frame timing..."
    624   framecycle = hostprofile() # get the amount of host cycles to skip
    625   print "Detected cycles per frame:", framecycle
    626   if(ARGC < 2) trapout("no cart .png file specified!")
    627   # init some string constants and parameters
    628   SCR_CLR = sprintf("\033[2J") # screen clear command
    629   SCR_SRESET = sprintf("\033[0m\033[0;0H")
    630   KEY_INPUT_STREAM = "od -tu1 -w1 -An -N1 -v"
    631   for(c=1;c<ARGC;c++) runmachine(ARGV[c]) # run all arguments sequentially
    632   shutdown()
    633 }