# awlite: POSIX AWK port of Text Elite # Version 1.7 # Original high-level algorithms from Text Elite 1.5 by Ian Bell, 1999, 2015 # Porting, corrections, improvements and optimizations by Luxferre, 2024 # # Tested on: nawk/one-true-awk, busybox awk, gawk --posix, mawk -W posix # Should run on any current POSIX-compliant AWK implementation but # must be run with LC_ALL=C environment variable to work correctly! # # Gameplay differences from the original C version of TE 1.5: # - multiple typos corrected in text strings # - galaxy jumps are no longer free and cost 5000 credits each (like in classic Elite and Oolite) # - cargo hold expansion also is non-free (400 credits) and can only be done once from 20t to 35t (like in classic Elite) # - alien items are available by default, can be overridden with -v NO_ALIEN_ITEMS=1 # - economy names are no longer shortened # - the "politically correct" goods names are turned on with -v CENSORED=1 # - the following goods have been renamed: "Robot Slaves" to "Robots", "Liquor/Wines" to "Liquors", "Gem-Strones" to "Gem-stones" # - market and local info tables are better aligned # - the "cash" and "sneak" commands are removed since v1.6 (use saves to cheat) # - state saving functionality since v1.6 with "save" and "load" commands # - since v1.6.1, the game gives you a hint if you might have found Raxxla # - since v1.6.3, galaxy names are actually displayed (taken from ArcElite) # - since v1.7, "stat" command and basic ranking was introduced # ... to be continued ... # utility functions # determine (ordered) array length function alen(a, i, k) { k = 0 for(i in a) k++ return k } # split a string with separator into an array but with 0-based indexes function asplit(s, a, sep, len, i) { split(s, a, sep) # get 1-based-index array in a len = alen(a) for(i=0;i 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]) } # Required bitwise operations (as POSIX AWK doesn't support them) 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) } # ============================ # # Game code itself starts here # # ============================ # # PRNG part function mysrand(seed) { srand(seed); rng_lastrand = seed - 1 } function myrand(r) { if(game["use_native_rand"] > 0) return int(rand() * 65536) else { # attempt to optimize McDonnell's generator r = (rng_lastrand * 3677 + 3680) % 2147483648 rng_lastrand = r -1 return r } } function randbyte() {return myrand()%256} # other functions function mymin(a,b) {return (a < b) ? a : b} # distance between 2 planets function distance(p1x, p1y, p2x, p2y, dx, dy) { dx = p1x - p2x dy = p1y - p2y return int(0.5 + 4*sqrt(dx*dx + dy*dy/4)) } function rotl(x) { # rotate left 1 bit mod 256 x = x % 256 # truncate to a byte first return (x * 2 + ((x > 127) ? 1 : 0)) % 256 } function twist(x) { # twister function return (256 * rotl(int(x/256)) + rotl(x%256)) % 65536 } function galswitch(seeds) { # galaxy switcher (accepts an array) seeds[0] = twist(seeds[0]) seeds[1] = twist(seeds[1]) seeds[2] = twist(seeds[2]) } function tweakseed(seeds, t) { # another seed tweaker t = (seeds[0] + seeds[1] + seeds[2]) % 65536 seeds[0] = seeds[1] seeds[1] = seeds[2] seeds[2] = t } # Generate system info from seed # populates the sinfo map function makesystem(seeds, sinfo, p1, p2, p3, p4, lnf, ecof, sname) { lnf = bw_and(seeds[0], 64) sinfo["x"] = int(seeds[1] / 256) sinfo["y"] = int(seeds[0] / 256) sinfo["govtype"] = int(seeds[1] / 8) % 8 sinfo["economy"] = int(seeds[0] / 256) % 8 if(sinfo["govtype"] <= 1) sinfo["economy"] = 2 + (sinfo["economy"]%2) + (sinfo["economy"]>3?4:0) ecof = 7 - sinfo["economy"] sinfo["techlev"] = (sinfo["x"] % 4) + ecof + int(sinfo["govtype"] / 2) if((sinfo["govtype"] % 2) == 1) sinfo["techlev"]++ sinfo["population"] = 4 * (sinfo["techlev"]) + sinfo["economy"] + sinfo["govtype"] + 1 sinfo["productivity"] = ((ecof + 3) * (sinfo["govtype"] + 4)) * sinfo["population"] * 8 sinfo["radius"] = 256 * ((int(seeds[2] / 256) % 16) + 11) + sinfo["x"] # goat soup seed parts sinfo["gsseed_a"] = seeds[1] % 256 sinfo["gsseed_b"] = int(seeds[1] / 256) % 256 sinfo["gsseed_c"] = seeds[2] % 256 sinfo["gsseed_d"] = int(seeds[2] / 256) % 256 # name pair indexes p1 = 2 * (int(seeds[2] / 256) % 32); tweakseed(seeds) p2 = 2 * (int(seeds[2] / 256) % 32); tweakseed(seeds) p3 = 2 * (int(seeds[2] / 256) % 32); tweakseed(seeds) p4 = 2 * (int(seeds[2] / 256) % 32); tweakseed(seeds) sname = pairs[p1] pairs[p1+1] pairs[p2] pairs[p2+1] pairs[p3] pairs[p3+1] if(lnf) sname = sname pairs[p4] pairs[p4+1] gsub(/\./, "", sname) # remove all dots from sname sinfo["name"] = sname # write the final name variant # return sinfo } # "Goat Soup" planetary description string code function gs_rnd(sinfo, a, x) { # accepts system info to modify gsseed x = (sinfo["gsseed_a"] * 2) % 256 a = x + sinfo["gsseed_c"] if(sinfo["gsseed_a"] > 127) a++ sinfo["gsseed_a"] = a % 256 sinfo["gsseed_c"] = x x = sinfo["gsseed_b"] a = (int(a / 256) + x + sinfo["gsseed_d"]) % 256 sinfo["gsseed_b"] = a sinfo["gsseed_d"] = x return a } # main desc generator, recursive, modifies sinfo as well function goat_soup(source, sinfo, l, c, i, j, k, v, rnd, dsel) { l = length(source) for(i=0;i= 51) ? 1 : 0) + ((rnd >= 102) ? 1 : 0) j += ((rnd >= 153) ? 1 : 0) + ((rnd >= 204) ? 1 : 0) goat_soup(dsel[j], sinfo) } else { if(c == 176) { # planet name v = sinfo["name"] v = substr(v, 1, 1) tolower(substr(v, 2)) printf("%s", v) } else if(c == 177) { # planet name + ian v = sinfo["name"] v = substr(v, 1, 1) tolower(substr(v, 2)) # find last "e" or "i" occurrence in v j = match(v, /[eia]+$/) if(j == 0) j = length(v) + 1 printf("%sian", substr(v, 1, j - 1)) } else if(c == 178) { # random name v = gs_rnd(sinfo) % 4 for(j = 0; j <= v; j++) { k = bw_and(62, gs_rnd(sinfo)) printf("%c", (j > 0) ? tolower(pairs0[k]) : pairs0[k]) printf("%c", tolower(pairs0[k+1])) } } else { printf("",c); return } } } } } # Galaxy builder (populates seeds array and global galaxy arrays) function buildgalaxy(gnum, seeds, si, i) { asplit("23114 584 46931", seeds, " ") for(i=1;i 127) q = 0 localmarket_quantity[i] = q % 64 q = (goods_bprices[i] + changing + product) % 256 localmarket_price[i] = q * 4 } if(NO_ALIEN_ITEMS) localmarket_quantity[IDX_ALIEN_ITEMS] = 0 } # display local market from localmarket_quantity and localmarket_price function displaymarket(i) { for(i=0;i<=IDX_ALIEN_ITEMS;i++) { printf("\n%-16s", goods_names[i]) printf("%-5s", sprintf("%.1f", localmarket_price[i]/10)) printf("\t%u%s", localmarket_quantity[i], unitnames[goods_units[i]]) printf("\t\t%u", shipshold[i]) } } # move to system i function gamejump(i, si) { player["currentplanet"] = i player["jumps"]++ assocdes(galaxy[i], si, "|") genmarket(randbyte(), int(si["economy"])) } # print data for given system function prisys(sinfo, compressed) { if(compressed) { printf("%10s TL: %2i ", sinfo["name"], sinfo["techlev"] + 1) printf("%-20s %-15s", econnames[sinfo["economy"]], govnames[sinfo["govtype"]]) } else { printf("\nSystem: %s", sinfo["name"]) printf("\nPosition (%i, %i)", sinfo["x"], sinfo["y"]) printf("\nEconomy: (%i) %s", sinfo["economy"], econnames[sinfo["economy"]]) printf("\nGovernment: (%i) %s", sinfo["govtype"], govnames[sinfo["govtype"]]) printf("\nTech level: %2i", sinfo["techlev"] + 1) printf("\nTurnover: %u", sinfo["productivity"]) printf("\nRadius: %u", sinfo["radius"]) printf("\nPopulation: %u billion\n", int(sinfo["population"]/8)) goat_soup("\217 is \227.", sinfo) # generate and print the description if(match(sinfo["name"], /RA..LA/)) # found Raxxla? printf("\n%s", "Wait a sec... Is this the legendary Raxxla?\nWe'll never know for sure...") } } # return id of the planet whose name matches passed string # closest to current planet - if none, return current planet function matchsys(s, d, i, p, cd) { p = player["currentplanet"] d = 9999 s = toupper(s) # system names are stored in uppercase for(i=0;i player["fuel"]) {printf("\nJump too far"); return 0} player["fuel"] -= d gamejump(dest, si) prisys(si, 0) return 1 } # jump to next galaxy (planet number is preserved) function dogalhyp() { if(player["cash"] > game["galhypcost"]) { player["cash"] -= game["galhypcost"] player["expense"] += game["galhypcost"] player["galaxynum"]++ player["jumps"]++ if(player["galaxynum"] == 9) player["galaxynum"] = 1 buildgalaxy(player["galaxynum"], globalseeds) # build a new galaxy } else printf("Not enough credits for hyperspace jump!\nMust have at least %.1f CR", game["galhypcost"]/10) } # print planet info function doinfo(s, dest, si) { dest = matchsys(s) # find the system assocdes(galaxy[dest], si, "|") # deserialize found system prisys(si, 0) # print its info } # hold command implementation function dohold(a, t, i) { if(player["holdspace"] >= game["cargoexpsize"]) { printf("\nExpansion not possible! The cargo bay already is at maximum capacity") return 0 } if(player["cash"] < game["cargoexpcost"]) { printf("\nNot enough cash! Need %.1f CR for upgrading the cargo bay", game["cargoexpcost"]/10) return 0 } t = 0 a = int(a) for(i=0;i<=IDX_ALIEN_ITEMS;i++) if(goods_units[i] == 0) t += shipshold[i] if(t > a) {printf("\nHold too full"); return 0} player["holdspace"] = a - t player["cash"] -= game["cargoexpcost"] player["expense"] += game["cargoexpcost"] printf("\nCargo bay expanded to %dt", game["cargoexpsize"]) return 1 } # check string s against n options in array a # if matches ith element return i+1 else return 0 function stringmatch(s, a, n, i) { s = tolower(s) for(i=0;i 0) { printf("\nSelling %i%s of %s", t, unitnames[goods_units[i]], pname) } else printf("\nCannot sell any %s", pname) return 1 } # buy command implementation (accepts space-separated name and amount) function dobuy(s, sp, pname, pquant, i, t) { split(s, sp, " ") pname = sp[1] pquant = int(sp[2]) if(pquant < 1) pquant = 1 i = stringmatch(pname, goods_names, IDX_ALIEN_ITEMS + 1) if(i == 0) {printf("\nUnknown product"); return 0} i -= 1 # switch to the real product index t = gamebuy(i, pquant) pname = goods_names[i] # update real product name if(t > 0) { printf("\nBuying %i%s of %s", t, unitnames[goods_units[i]], pname) } else printf("\nCannot buy any %s", pname) return 1 } # attempt to buy f tonnes of fuel function gamefuel(f) { if((f + player["fuel"]) > game["maxfuel"]) f = game["maxfuel"] - player["fuel"] if(game["fuelcost"] > 0 && (f * game["fuelcost"] > player["cash"])) f = int(player["cash"] / game["fuelcost"]) player["fuel"] += f player["cash"] -= f * game["fuelcost"] player["expense"] += f * game["fuelcost"] return f } # fuel command implementation function dofuel(f) { f = gamefuel(int(10 * f)) if(f == 0) printf("\nCan't buy any fuel") else printf("\nBuying %.1fLY fuel", f/10) return 1 } # show stock market function domkt() { displaymarket() printf("\n\nFuel: %.1f\nHoldspace: %it", player["fuel"]/10, player["holdspace"]) } # display help function dohelp() { printf("\nCommands are:") printf("\nBuy product amount") printf("\nSell product amount") printf("\nFuel amount (buy amount LY of fuel)") printf("\nJump planetname (limited by fuel)") printf("\nGalhyp (jump to next galaxy)") printf("\nInfo planetname (print info on system)") printf("\nMkt (show market prices)") printf("\nLocal (list systems within 7 lightyears)") printf("\nHold (expand cargo bay to %dt)", game["cargoexpsize"]) printf("\nStat (show player statistics)") printf("\nSave playername (save game into current working directory)") printf("\nLoad playername (load game from current working directory)") printf("\nQuit or ^D (exit)") printf("\nHelp (display this text)") printf("\nRand (toggle RNG)") printf("\n\nAbbreviations allowed eg. b fo 5 = Buy Food 5, m = Mkt") } # Save/load functionality function dosave(savename, fs, fn) { gsub(/[^[:alnum:]]/, "_", savename) # sanitize all non-alnum chars player["cargo"] = assocser(shipshold, "@") fs = assocser(player, "|") delete player["cargo"] fn = savename ".awlite" printf("%s\n", fs) > fn # write the savefile close(fn) printf("\nGame saved in %s", fn) } function doload(savename, fn, fs) { gsub(/[^[:alnum:]]/, "_", savename) # sanitize all non-alnum chars fn = savename ".awlite" if((getline fs < fn) > 0) { # read the savefile split("", player) # clear the player array split("", shipshold) # clear the shipshold array assocdes(fs, player, "|") assocdes(player["cargo"], shipshold, "@") delete player["cargo"] split("", globalseeds) # init galaxy seeds buildgalaxy(player["galaxynum"], globalseeds) # build the current galaxy genmarket(0, economies[player["currentplanet"]]) # build local market printf("\nGame loaded from %s", fn) } else printf("\nError reading the savefile!") close(fn) } # player statistics and ranking display # ranking formula might be subject to change in future versions function dostat(profind, rank) { profind = 0 # profit margin index rank = 0 if(player["expense"] > 0) { profind = (player["cash"]-1000) / player["expense"] rank = int(log(1 + profind) / log(2)) # log base 2 if(rank < 0) rank = 0 if(rank > 8) rank = 8 } printf("\nRank: %s", ranknames[rank]) printf("\n\n========= Money =========") printf("\nBalance %17s", sprintf("%.1f CR",player["cash"]/10)) printf("\nExpenses %16s", sprintf("%.1f CR", player["expense"]/10)) printf("\nProfit margin %11s", sprintf("%.2f%%", profind*100)) printf("\n\n======= Ship info =======") printf("\nFuel %20s", sprintf("%.1f LY", player["fuel"]/10)) printf("\nCargo space %13s", sprintf("%ut", player["holdspace"])) printf("\nJumps %19s", sprintf("%u", player["jumps"])) } # Game init procedure: first thing to call in the BEGIN block function gameinit() { rng_lastrand = 0 # state for custom PRNG mysrand(12345); # ensure repeatability numForLave = 7 # Lave is the 7th planet in galaxy 1 split("",game) # overall game state is stored here game["use_native_rand"] = 1 game["galhypcost"] = 50000 # 5000 CR game["cargoexpcost"] = 4000 # 400 CR for one-time upgrade game["cargoexpsize"] = 35 # 35t after the upgrade game["fuelcost"] = 2 # 0.2 CR/lightyear game["maxfuel"] = 70 # 7.0 LY tank split("",player) # current (saveable) player state is stored here player["currentplanet"] = numForLave # current planet (1 to 256) player["galaxynum"] = 1 # current galaxy (1 to 8) player["cash"] = 1000 # current cash (100 CR) player["expense"] = 0 # for tracking overall expenses player["jumps"] = 0 # for counting interplanetary jumps player["fuel"] = game["maxfuel"] # current fuel amount player["holdspace"] = 20 # max cargo hold space split("",shipshold) # (saveable) contents of cargo bay, up to 16 items # constants and tables # unit names asplit("t kg g", unitnames, " ") # galaxy names (1-indexed) split("Santaari Colesque Lara'tan Angiana Proximus Sol Jaftra Xrata", galnames, " ") # government names asplit("Anarchy,Feudal,Multi-gov,Dictatorship,Communist,Confederacy,Democracy,Corporate State", govnames, ",") # economy names asplit("Rich Industrial,Average Industrial,Poor Industrial,Mainly Industrial,Mainly Agricultural,Rich Agricultural,Average Agricultural,Poor Agricultural", econnames, ",") # rank names asplit("Penniless,Mostly Penniless,Peddler,Dealer,Merchant,Broker,Entrepreneur,Tycoon,Elite", ranknames, ",") # Goods # Data for DB's price/availability generation system: # Base price, Gradient, Base quantity, Mask, Unit, Name split("", commodities) # will split on demand commodities[0] = "19,-2,6,1,0,Food" commodities[1] = "20,-1,10,3,0,Textiles" commodities[2] = "65,-3,2,7,0,Radioactives" commodities[3] = "40,-5,226,31,0," (CENSORED ? "Robots" : "Slaves") commodities[4] = "83,-5,251,15,0," (CENSORED ? "Beverages" : "Liquors") commodities[5] = "196,8,54,3,0,Luxuries" commodities[6] = "235,29,8,120,0," (CENSORED ? "Rare Species" : "Narcotics") commodities[7] = "154,14,56,3,0,Computers" commodities[8] = "117,6,40,7,0,Machinery" commodities[9] = "78,1,17,31,0,Alloys" commodities[10] = "124,13,29,7,0,Firearms" commodities[11] = "176,-9,220,63,0,Furs" commodities[12] = "32,-1,53,3,0,Minerals" commodities[13] = "97,-1,66,7,1,Gold" commodities[14] = "171,-2,55,31,1,Platinum" commodities[15] = "45,-1,250,15,2,Gem-stones" commodities[16] = "53,15,192,7,0,Alien Items" IDX_ALIEN_ITEMS=16 split("", goods_bprices) # holds base prices split("", goods_grads) # holds gradients split("", goods_bquants) # holds base quantities split("", goods_masks) # holds masks split("", goods_units) # holds measurement units split("", goods_names) # holds goods names for(i in commodities) { # it's pre-ordered anyway split(commodities[i], parts, ",") # we can use 1-based here goods_bprices[i] = int(parts[1]) goods_grads[i] = int(parts[2]) goods_bquants[i] = int(parts[3]) goods_masks[i] = int(parts[4]) goods_units[i] = int(parts[5]) goods_names[i] = parts[6] } # localmarket assoc arrays split("", localmarket_quantity) split("", localmarket_price) # digrams for planet names asplit("ABOUSEITILETSTONLONUTHNOALLEXEGEZACEBISOUSESARMAINDIREA.ERATENBERALAVETIEDORQUANTEISRION", pairs0, "") asplit("..LEXEGEZACEBISOUSESARMAINDIREA.ERATENBERALAVETIEDORQUANTEISRION", pairs, "") # planet description string parts desc_list_str = "fabled,notable,well known,famous,noted|very,mildly,most,reasonably,|ancient,\225,great,vast,pink|\236 \235 plantations,mountains,\234,\224 forests,oceans|shyness,silliness,mating traditions,loathing of \206,love for \206|food blenders,tourists,poetry,discos,\216|talking tree,crab,bat,lobst,\262|beset,plagued,ravaged,cursed,scourged|\226 civil war,\233 \230 \231s,a \233 disease,\226 earthquakes,\226 solar activity|its \203 \204,the \261 \230 \231,its inhabitants' \232 \205,\241,its \215 \216|juice,brandy,water,brew,gargle blasters|\262,\261 \231,\261 \262,\261 \233,\233 \262|fabulous,exotic,hoopy,unusual,exciting|cuisine,night life,casinos,sit coms, \241 |\260,The planet \260,The world \260,This planet,This world|n unremarkable, boring, dull, tedious, revolting|planet,world,place,little planet,dump|wasp,moth,grub,ant,\262|poet,arts graduate,yak,snail,slug|tropical,dense,rain,impenetrable,exuberant|funny,weird,unusual,strange,peculiar|frequent,occasional,unpredictable,dreadful,deadly|\202 \201 for \212,\202 \201 for \212 and \212,\210 by \211,\202 \201 for \212 but \210 by \211,a\220 \221|\233,mountain,edible,tree,spotted|\237,\240,\207oid,\223,\222|ancient,exceptional,eccentric,ingrained,\225|killer,deadly,evil,lethal,vicious|parking meters,dust clouds,ice bergs,rock formations,volcanoes|plant,tulip,banana,corn,\262weed|\262,\261 \262,\261 \233,inhabitant,\261 \262|shrew,beast,bison,snake,wolf|leopard,cat,monkey,goat,fish|\214 \213,\261 \237 \242,its \215 \240 \242,\243 \244,\214 \213|meat,cutlet,steak,burgers,soup|ice,mud,Zero-G,vacuum,\261 ultra|hockey,cricket,karate,polo,tennis" asplit(desc_list_str, desc_list, "|") galsize = 256 # number of systems in one galaxy } # main code block BEGIN { gameinit() # init everything split("", globalseeds) # init galaxy seeds buildgalaxy(player["galaxynum"], globalseeds) # build the current galaxy genmarket(0, economies[player["currentplanet"]]) # build local market printf("\nWelcome to awlite 1.7\n") dohelp() # start the REPL printf("\n\nCash: %.1f> ", player["cash"]/10) while((getline cmd) > 0) { cmd = tolower(cmd) # accept both uppercase and lowercase spi = index(cmd, " ") # first whitespace in cmd action = (spi == 0) ? cmd : substr(cmd, 1, spi - 1) paramstr = (spi == 0) ? "" : substr(cmd, spi+1) if(cmd == "q" || cmd == "quit") break else if(cmd == "h" || cmd == "help") dohelp() else if(cmd == "r" || cmd == "rand") dotweakrand() else if(cmd == "m" || cmd == "mkt") domkt() else if(cmd == "l" || cmd == "local") dolocal() else if(cmd == "g" || cmd == "galhyp") dogalhyp() else if(cmd == "st" || cmd == "stat") dostat() else if(action == "i" || action == "info") doinfo(paramstr) else if(action == "b" || action == "buy") dobuy(paramstr) else if(action == "s" || action == "sell") dosell(paramstr) else if(action == "j" || action == "jump") dojump(paramstr) else if(action == "f" || action == "fuel") dofuel(paramstr) else if(action == "hold") dohold(game["cargoexpsize"]) else if(action == "save") dosave(paramstr) else if(action == "load") doload(paramstr) else printf("Unknown command") printf("\n\nCash: %.1f> ", player["cash"]/10) } print "\nBye!" }