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 }