# awlite: POSIX AWK port of Text Elite 1.5 # Version 1.5.2 # Original C version 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 # - the "cash" and "sneak" commands are only available to the REPL if running with -v CHEAT=1 # - 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 # - 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 # ... 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) } function bw_or(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) } function bw_xor(a, b, v, r) { v = 1; r = 0 while(a > 0 || b > 0) { if((a%2) != (b%2)) 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"] = bw_or(sinfo["economy"], 2) ecof = bw_xor(sinfo["economy"], 7) 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 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 } } # 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 } # dojump without fuel expense function dosneak(s, kp, r) { kp = player["fuel"] player["fuel"] = 666 r = dojump(s) player["fuel"] = kp return r } # jump to next galaxy (planet number is preserved) function dogalhyp() { if(player["cash"] > game["galhypcost"]) { player["cash"] -= game["galhypcost"] player["galaxynum"]++ 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 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, 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, 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"] 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 } # (cheat) alter cash command implementation function docash(s) { player["cash"] += int(10 * s) if(player["cash"] < 0) player["cash"] = 0 } # 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)") if(CHEAT) printf("\nSneak planetname (any distance - no fuel cost)") printf("\nGalhyp (jumps to next galaxy)") printf("\nInfo planetname (prints info on system)") printf("\nMkt (shows market prices)") printf("\nLocal (lists systems within 7 light years)") if(CHEAT) printf("\nCash number (alters cash - cheating!)") printf("\nHold (expand cargo bay to %dt)", game["cargoexpsize"]) 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") } # 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["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, " ") # 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, ",") # 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.5.1 (based on Text Elite 1.5)\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(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 == "ho" || action == "hold") dohold(game["cargoexpsize"]) else if(CHEAT && action == "cash") docash(paramstr) else if(CHEAT && action == "sneak") dosneak(paramstr) else printf("Unknown command") printf("\n\nCash: %.1f> ", player["cash"]/10) } print "\nBye!" }