1 /* based on txtelite.c v1.5
2 * Textual version of Elite trading
3 * Converted by Ian Bell from 6502 Elite sources.
4 * Original 6502 Elite by Ian Bell & David Braben.
5 * Edited by Richard Carlsson to compile cleanly under gcc
6 * and to fix a bug in the goat soup algorithm.
8 * Ported to D by Ketmar // Invisible Vector
10 /* ----------------------------------------------------------------------------
11 * The nature of basic mechanisms used to generate the Elite socio-economic
12 * universe are now widely known. A competant games programmer should be able to
13 * produce equivalent functionality. A competant hacker should be able to lift
14 * the exact system from the object code base of official conversions.
16 * This file may be regarded as defining the Classic Elite universe.
18 * It contains a C implementation of the precise 6502 algorithms used in the
19 * original BBC Micro version of Acornsoft Elite together with a parsed textual
22 * Note that this is not the universe of David Braben's 'Frontier' series.
27 * ---------------------------------------------------------------------------- */
31 // ////////////////////////////////////////////////////////////////////////// //
32 public struct Galaxy
{
33 // some numbers for galaxy 1
39 enum PlanetsInGalaxy
= 256;
61 enum Unit
{ Tonne
, Kg
, G
}
63 static struct TradeGood
{
65 string unitName
; // unit name
67 bool alien
; // alien items?
68 bool forbidden
; // narcotics or alike?
71 static TradeGood
good(T
: ulong) (T idx
) {
72 if (idx
> Goods
.max
) assert(0, "invalid trade good index");
74 res
.name
= commodities
[idx
].name
;
75 res
.unitName
= unitnames
[commodities
[idx
].units
];
76 res
.unit
= cast(Unit
)commodities
[idx
].units
;
77 res
.alien
= (idx
== Galaxy
.Goods
.AlienItems
);
78 res
.forbidden
= (idx
== Galaxy
.Goods
.Slaves || idx
== Galaxy
.Goods
.Narcotics
);
93 enum Economy
: ubyte {
104 enum MaxTechLevel
= 16;
106 string
economyName(T
: ulong) (T v
) const pure nothrow @safe @nogc { return (v
>= 0 && v
<= Economy
.max ? econnames
[economy
] : "Unknown"); }
107 string
govName(T
: ulong) (T v
) const pure nothrow @safe @nogc { return (v
>= 0 && v
<= Gov
.max ? govnames
[govtype
] : "Unknown"); }
109 static struct Planet
{
110 ubyte number
; // in galaxy
112 Economy economy
; // 0..7
114 ubyte techlev
; // 0..16
120 ushort lastFluct
; // fluct used in last genMarket call
122 private FastPRng goatsoupseed
;
125 // market; please, generate it before using! ;-)
126 ushort[Goods
.max
+1] quantity
;
127 ushort[Goods
.max
+1] price
;
129 string
economyName () const pure nothrow @safe @nogc { return econnames
[economy
]; }
130 string
govName () const pure nothrow @safe @nogc { return govnames
[govtype
]; }
132 /* Prices and availabilities are influenced by the planet's economy type
133 * (0-7) and a random "fluctuation" byte that was kept within the saved
134 * commander position to keep the market prices constant over gamesaves.
135 * Availabilities must be saved with the game since the player alters them
136 * by buying (and selling(?))
138 * Almost all operations are one byte only and overflow "errors" are
139 * extremely frequent and exploited.
141 * Trade Item prices are held internally in a single byte=true value/4.
142 * The decimal point in prices is introduced only when printing them.
143 * Internally, all prices are integers.
144 * The player's cash is held in four bytes.
146 void genMarket (ushort fluct
) {
148 foreach (uint i
; 0..Galaxy
.Goods
.max
+1) {
149 int product
= economy
*commodities
[i
].gradient
;
150 int changing
= fluct
&commodities
[i
].maskbyte
;
151 int q
= commodities
[i
].basequant
+changing
-product
;
153 if (q
&0x80) q
= 0; // clip to positive 8-bit
154 quantity
[i
] = cast(ushort)(q
&0x3F); // mask to 6 bits
155 q
= commodities
[i
].baseprice
+changing
+product
;
157 price
[i
] = cast(ushort)(q
*4);
159 quantity
[Galaxy
.Goods
.AlienItems
] = 0; // override to force nonavailability
162 // seperation between two planets (4*sqrt(X*X+Y*Y/4))
163 int distanceTo() (in auto ref Planet b
) const nothrow @safe @nogc {
164 import std
.math
: floor
, sqrt
;
165 return cast(int)floor((4.0*sqrt((this.x
-b
.x
)*(this.x
-b
.x
)+(this.y
-b
.y
)*(this.y
-b
.y
)/4.0))+0.5);
169 string
description () {
170 if (desc
.length
== 0) {
171 FastPRng prng
= goatsoupseed
;
172 desc
= goatSoup(prng
, "\x8F is \x97.", this);
178 Planet
[PlanetsInGalaxy
] planets
;
179 private GalaxySeed galseed
;
181 Planet currPlanet
; // current planet
182 private ulong curGalSeed
;
184 this (ubyte galnum
) { build(galnum
); }
186 @property ulong galSeed () const pure nothrow @nogc { return curGalSeed
; }
187 @property void galSeed (ulong v
) pure nothrow @nogc { curGalSeed
= v
; }
190 void build (ubyte galnum
) {
191 galseed
= GalaxySeed
.galaxy(galnum
);
192 curGalSeed
= galseed
.seed
;
193 foreach (immutable idx
, ref Planet ps
; planets
) { makeSystem(ps
); ps
.number
= cast(ubyte)idx
; }
195 // generate data for Lave (ignore the fact that we can be in another galaxy)
196 currPlanet
= planets
[Lave
];
197 currPlanet
.genMarket(0);
202 galseed
.seed
= curGalSeed
;
203 foreach (immutable idx
, ref Planet ps
; planets
) { makeSystem(ps
); ps
.number
= cast(ubyte)idx
; }
205 currPlanet
= planets
[currPlanet
.number
];
206 currPlanet
.genMarket(randbyte());
210 galseed
.seed
= curGalSeed
;
211 galseed
.nextgalaxy();
212 curGalSeed
= galseed
.seed
;
213 foreach (immutable idx
, ref Planet ps
; planets
) { makeSystem(ps
); ps
.number
= cast(ubyte)idx
; }
215 // generate data for Lave (ignore the fact that we can be in another galaxy)
216 currPlanet
= planets
[currPlanet
.number
];
217 currPlanet
.genMarket(randbyte());
222 if (i
>= 0 && i
< planets
.length
) {
223 currPlanet
= planets
[i
];
224 currPlanet
.genMarket(randbyte());
231 // return id of the planet whose name matches passed string closest to currentplanet; if none, return current planet
232 int find (const(char)[] s
) {
234 int res
= currPlanet
.number
;
235 foreach (immutable idx
, const ref p
; planets
) {
236 if (startsWithCI(p
.name
, s
)) {
237 auto nd
= currPlanet
.distanceTo(p
);
247 static startsWithCI (const(char)[] s
, const(char)[] pat
) nothrow @trusted @nogc {
248 import std
.ascii
: toUpper
, toLower
;
249 if (pat
.length
== 0 || pat
.length
> s
.length
) return false;
250 foreach (immutable idx
, char ch
; pat
) {
251 if (toLower(s
.ptr
[idx
]) != toLower(ch
)) return false;
257 // generate system info from seed
258 void makeSystem (out Planet thissys
) {
261 ushort pair1
, pair2
, pair3
, pair4
;
262 ushort longnameflag
= s
.w0
&64;
263 const(char)* pairs1
= pairs
.ptr
+24; // start of pairs used by this routine
268 thissys
.govtype
= cast(Galaxy
.Gov
)((s
.w1
>>3)&7); // bits 3, 4 & 5 of w1
270 ubyte ec
= (s
.w0
>>8)&7; // bits 8, 9 & A of w0
271 if (thissys
.govtype
<= 1) ec |
= 2;
272 thissys
.economy
= cast(Galaxy
.Economy
)ec
;
274 thissys
.techlev
= cast(ubyte)(((s
.w1
>>8)&3)+(ec^
7));
275 thissys
.techlev
+= thissys
.govtype
>>1;
276 if (thissys
.govtype
&1) thissys
.techlev
+= 1; // simulation of 6502's LSR then ADC
278 thissys
.population
= cast(ubyte)(4*thissys
.techlev
+thissys
.economy
);
279 thissys
.population
+= thissys
.govtype
+1;
281 thissys
.productivity
= cast(ushort)(((thissys
.economy^
7)+3)*(thissys
.govtype
+4));
282 thissys
.productivity
*= thissys
.population
*8;
284 thissys
.radius
= cast(ushort)(256*(((s
.w2
>>8)&15)+11)+thissys
.x
);
286 thissys
.goatsoupseed
.a
= s
.w1
&0xFF;
287 thissys
.goatsoupseed
.b
= s
.w1
>>8;
288 thissys
.goatsoupseed
.c
= s
.w2
&0xFF;
289 thissys
.goatsoupseed
.d
= s
.w2
>>8;
291 pair1
= 2*((s
.w2
>>8)&31); s
.tweak
;
292 pair2
= 2*((s
.w2
>>8)&31); s
.tweak
;
293 pair3
= 2*((s
.w2
>>8)&31); s
.tweak
;
294 pair4
= 2*((s
.w2
>>8)&31); s
.tweak
;
295 // always four iterations of random number
300 void putCh (char ch
) nothrow @trusted @nogc {
301 import std
.ascii
: toUpper
, toLower
;
302 if (ch
== '.') return;
303 if (nbpos
== 0) ch
= toUpper(ch
); else ch
= toLower(ch
);
304 namebuf
.ptr
[nbpos
++] = ch
;
307 putCh(pairs1
[pair1
]);
308 putCh(pairs1
[pair1
+1]);
309 putCh(pairs1
[pair2
]);
310 putCh(pairs1
[pair2
+1]);
311 putCh(pairs1
[pair3
]);
312 putCh(pairs1
[pair3
+1]);
314 // bit 6 of ORIGINAL w0 flags a four-pair name
316 putCh(pairs1
[pair4
]);
317 putCh(pairs1
[pair4
+1]);
319 thissys
.name
= namebuf
[0..nbpos
].idup
;
323 // prng for planet price fluctuations
324 static uint lastrand
= 0x29a;
325 static void mysrand (uint seed
) { lastrand
= seed
-1; }
326 static int myrand () {
328 // As supplied by D McDonnell from SAS Insititute C
329 r
= (((((((((((lastrand
<<3)-lastrand
)<<3)+lastrand
)<<1)+lastrand
)<<4)-lastrand
)<<1)-lastrand
)+0xe60)&0x7fffffff;
333 static ubyte randbyte () { return (myrand()&0xFF); }
337 // ////////////////////////////////////////////////////////////////////////// //
340 // four byte random number used for planet description
344 pure nothrow @safe @nogc:
349 if (this.a
> 127) ++a
;
351 this.c
= cast(ubyte)x
;
352 a
/= 256; // a = any carry left from above
354 a
= (a
+x
+this.d
)&0xFF;
355 this.b
= cast(ubyte)a
;
356 this.d
= cast(ubyte)x
;
362 // six byte random number used as seed for planets
363 // initied with base seed for galaxy 1
369 pure nothrow @safe @nogc:
370 // create seed for the corresponding galaxy
371 this (ubyte galnum
) {
373 foreach (immutable _
; 0..galnum
) nextgalaxy();
376 static GalaxySeed
galaxy (ubyte galnum
) { return GalaxySeed(galnum
); }
378 @property ulong seed () const {
380 ((cast(ulong)w0
)<<2*16)|
381 ((cast(ulong)w1
)<<1*16)|
385 @property void seed (ulong v
) {
386 w0
= cast(ushort)(v
>>(2*16));
387 w1
= cast(ushort)(v
>>(1*16));
388 w2
= cast(ushort)(v
);
391 private void tweak () {
392 ushort temp
= cast(ushort)(cast(ushort)(w0
+w1
)+w2
); // 2 byte aritmetic
398 // apply to base seed; once for galaxy 2, etc.
400 // rotate 8 bit number leftwards
401 static ushort rotatel() (ushort x
) { ushort temp
= x
&128; return (2*(x
&127))+(temp
>>7); }
402 static ushort twist() (ushort x
) { return cast(ushort)((256*rotatel(x
>>8))+rotatel(x
&255)); }
403 w0
= twist(w0
); // twice for galaxy 3, etc.
404 w1
= twist(w1
); // Eighth application gives galaxy 1 again
410 // ////////////////////////////////////////////////////////////////////////// //
411 struct TradeGoodIntr
{
412 // In 6502 version these were:
413 ushort baseprice
; // one byte
414 short gradient
; // five bits plus sign
415 ushort basequant
; // one byte
416 ushort maskbyte
; // one byte
417 ushort units
; // two bits
418 string name
; // longest="Radioactives"
421 static immutable string
[8] govnames
= ["Anarchy", "Feudal", "Multi-gov", "Dictatorship", "Communist", "Confederacy", "Democracy", "Corporate State"];
422 static immutable string
[8] econnames
= ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial", "Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"];
423 static immutable string
[3] unitnames
= ["t", "kg", "g"];
425 /* Data for DB's price/availability generation system */
426 /* Base Grad Base Mask Un Name
427 price ient quant it */
428 static immutable TradeGoodIntr
[17] commodities
= [
429 TradeGoodIntr(0x13, -0x02, 0x06, 0x01, 0, "Food"),
430 TradeGoodIntr(0x14, -0x01, 0x0A, 0x03, 0, "Textiles"),
431 TradeGoodIntr(0x41, -0x03, 0x02, 0x07, 0, "Radioactives"),
432 TradeGoodIntr(0x28, -0x05, 0xE2, 0x1F, 0, "Slaves"),
433 TradeGoodIntr(0x53, -0x05, 0xFB, 0x0F, 0, "Liquor/Wines"),
434 TradeGoodIntr(0xC4, +0x08, 0x36, 0x03, 0, "Luxuries"),
435 TradeGoodIntr(0xEB, +0x1D, 0x08, 0x78, 0, "Narcotics"),
436 TradeGoodIntr(0x9A, +0x0E, 0x38, 0x03, 0, "Computers"),
437 TradeGoodIntr(0x75, +0x06, 0x28, 0x07, 0, "Machinery"),
438 TradeGoodIntr(0x4E, +0x01, 0x11, 0x1F, 0, "Alloys"),
439 TradeGoodIntr(0x7C, +0x0d, 0x1D, 0x07, 0, "Firearms"),
440 TradeGoodIntr(0xB0, -0x09, 0xDC, 0x3F, 0, "Furs"),
441 TradeGoodIntr(0x20, -0x01, 0x35, 0x03, 0, "Minerals"),
442 TradeGoodIntr(0x61, -0x01, 0x42, 0x07, 1, "Gold"),
443 TradeGoodIntr(0xAB, -0x02, 0x37, 0x1F, 1, "Platinum"),
444 TradeGoodIntr(0x2D, -0x01, 0xFA, 0x0F, 2, "Gem Stones"),
445 TradeGoodIntr(0x35, +0x0F, 0xC0, 0x07, 0, "Alien Items"),
449 // ////////////////////////////////////////////////////////////////////////// //
450 // fixed (R.C): can't assume that two separate array declarations are adjacently located in memory -- unified in a sigle array
451 string pairs
= "ABOUSEITILETSTONLONUTHNO..LEXEGEZACEBISOUSESARMAINDIREA.ERATENBERALAVETIEDORQUANTEISRION"; // dots should be nullprint characters
453 /* "Goat Soup" planetary description string code - adapted from Christian Pinder's reverse engineered sources. */
454 static immutable string
[5][36] descList
= [
455 /* 81 */ ["fabled", "notable", "well known", "famous", "noted"],
456 /* 82 */ ["very", "mildly", "most", "reasonably", ""],
457 /* 83 */ ["ancient", "\x95", "great", "vast", "pink"],
458 /* 84 */ ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"],
459 /* 85 */ ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"],
460 /* 86 */ ["food blenders", "tourists", "poetry", "discos", "\x8E"],
461 /* 87 */ ["talking tree", "crab", "bat", "lobst", "\xB2"],
462 /* 88 */ ["beset", "plagued", "ravaged", "cursed", "scourged"],
463 /* 89 */ ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"],
464 /* 8A */ ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"],
465 /* 8B */ ["juice", "brandy", "water", "brew", "gargle blasters"],
466 /* 8C */ ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"],
467 /* 8D */ ["fabulous", "exotic", "hoopy", "unusual", "exciting"],
468 /* 8E */ ["cuisine", "night life", "casinos", "sit coms", " \xA1 "],
469 /* 8F */ ["\xB0", "The planet \xB0", "The world \xB0", "This planet", "This world"],
470 /* 90 */ ["n unremarkable", " boring", " dull", " tedious", " revolting"],
471 /* 91 */ ["planet", "world", "place", "little planet", "dump"],
472 /* 92 */ ["wasp", "moth", "grub", "ant", "\xB2"],
473 /* 93 */ ["poet", "arts graduate", "yak", "snail", "slug"],
474 /* 94 */ ["tropical", "dense", "rain", "impenetrable", "exuberant"],
475 /* 95 */ ["funny", "wierd", "unusual", "strange", "peculiar"],
476 /* 96 */ ["frequent", "occasional", "unpredictable", "dreadful", "deadly"],
477 /* 97 */ ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"],
478 /* 98 */ ["\x9B", "mountain", "edible", "tree", "spotted"],
479 /* 99 */ ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"],
480 /* 9A */ ["ancient", "exceptional", "eccentric", "ingrained", "\x95"],
481 /* 9B */ ["killer", "deadly", "evil", "lethal", "vicious"],
482 /* 9C */ ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"],
483 /* 9D */ ["plant", "tulip", "banana", "corn", "\xB2weed"],
484 /* 9E */ ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"],
485 /* 9F */ ["shrew", "beast", "bison", "snake", "wolf"],
486 /* A0 */ ["leopard", "cat", "monkey", "goat", "fish"],
487 /* A1 */ ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"],
488 /* A2 */ ["meat", "cutlet", "steak", "burgers", "soup"],
489 /* A3 */ ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"],
490 /* A4 */ ["hockey", "cricket", "karate", "polo", "tennis"]
493 /* B0 = <planet name>
494 B1 = <planet name>ian
498 string
goatSoup (ref FastPRng prng
, string source
, in ref Galaxy
.Planet psy
) {
499 import std
.ascii
: toLower
;
502 void addCh (char ch
) {
503 if (!ch
) return; // just in case
504 if (ch
<= ' ') ch
= ' ';
506 if (res
.length
== 0 || res
[$-1] <= ' ') return;
508 } else if ((ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z') ||
(ch
>= '0' && ch
<= '9')) {
511 // punctuation -- remove trailing spaces
512 while (res
.length
&& res
[$-1] <= ' ') res
= res
[0..$-1];
517 while (source
.length
) {
519 source
= source
[1..$];
525 res
~= goatSoup(prng
, descList
[c
-0x81][(rnd
>= 0x33)+(rnd
>= 0x66)+(rnd
>= 0x99)+(rnd
>= 0xCC)], psy
);
528 case 0xB0: // planet name
530 foreach (char ch
; psy
.name
[1..$]) addCh(toLower(ch
));
532 case 0xB1: // <planet name>ian
534 string t
= psy
.name
[1..$];
536 if (t
.length
> 1 ||
(t
[0] != 'e' && t
[0] != 'i')) addCh(toLower(t
[0]));
541 case 0xB2: // random name
542 int len
= prng
.next
&3;
543 for (int i
= 0; i
<= len
; ++i
) {
544 int x
= prng
.next
&0x3e;
545 /* fixed (R.C.): transform chars to lowercase unless
546 first char of first pair, or second char of first
547 pair when first char was not printed */
549 char p2
= pairs
[x
+1];
551 if (i
) p1
= toLower(p1
);
555 if (i || p1
!= '.') p2
= toLower(p2
);
568 // ////////////////////////////////////////////////////////////////////////// //
569 version(elite_world_test
) unittest {
571 auto uni
= Galaxy(0);
573 writeln("System: ", uni
.currPlanet
.name
);
574 writeln("Position: (", uni
.currPlanet
.x
, ",", uni
.currPlanet
.y
, ")");
575 writeln("Economy: ", uni
.currPlanet
.economyName
);
576 writeln("Government: ", uni
.currPlanet
.govName
);
577 writeln("Tech Level: ", uni
.currPlanet
.techlev
+1);
578 writeln("Turnover: ", uni
.currPlanet
.productivity
);
579 writeln("Radius: ", uni
.currPlanet
.radius
);
580 writeln("Population: ", uni
.currPlanet
.population
/10, ".", uni
.currPlanet
.population
%10, " Billion");
581 writeln(uni
.currPlanet
.description
);
583 writeln("--------------------------------");
584 foreach (immutable idx
; 0..Galaxy
.Goods
.max
+1) {
585 auto good
= Galaxy
.good(idx
);
586 writefln("%-12s %3s.%s %2s%s", good
.name
, uni
.currPlanet
.price
[idx
]/10, uni
.currPlanet
.price
[idx
]%10, uni
.currPlanet
.quantity
[idx
], good
.unitName
);