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 * ---------------------------------------------------------------------------- */
33 // ////////////////////////////////////////////////////////////////////////// //
34 public struct Galaxy
{
35 // some numbers for galaxy 1
41 enum PlanetsInGalaxy
= 256;
63 enum Unit
{ Tonne
, Kg
, G
}
65 static struct TradeGood
{
67 string unitName
; // unit name
69 bool alien
; // alien items?
70 bool forbidden
; // narcotics or alike?
73 static TradeGood
good(T
: ulong) (T idx
) {
74 if (idx
> Goods
.max
) assert(0, "invalid trade good index");
76 res
.name
= commodities
[idx
].name
;
77 res
.unitName
= unitnames
[commodities
[idx
].units
];
78 res
.unit
= cast(Unit
)commodities
[idx
].units
;
79 res
.alien
= (idx
== Galaxy
.Goods
.AlienItems
);
80 res
.forbidden
= (idx
== Galaxy
.Goods
.Slaves || idx
== Galaxy
.Goods
.Narcotics
);
95 enum Economy
: ubyte {
106 enum MaxTechLevel
= 16;
108 string
economyName(T
: ulong) (T v
) const pure nothrow @safe @nogc { return (v
>= 0 && v
<= Economy
.max ? econnames
[economy
] : "Unknown"); }
109 string
govName(T
: ulong) (T v
) const pure nothrow @safe @nogc { return (v
>= 0 && v
<= Gov
.max ? govnames
[govtype
] : "Unknown"); }
111 static struct Planet
{
112 ubyte number
; // in galaxy
114 Economy economy
; // 0..7
116 ubyte techlev
; // 0..16
122 ushort lastFluct
; // fluct used in last genMarket call
124 private FastPRng goatsoupseed
;
127 // market; please, generate it before using! ;-)
128 ushort[Goods
.max
+1] quantity
;
129 ushort[Goods
.max
+1] price
;
131 string
economyName () const pure nothrow @safe @nogc { return econnames
[economy
]; }
132 string
govName () const pure nothrow @safe @nogc { return govnames
[govtype
]; }
134 /* Prices and availabilities are influenced by the planet's economy type
135 * (0-7) and a random "fluctuation" byte that was kept within the saved
136 * commander position to keep the market prices constant over gamesaves.
137 * Availabilities must be saved with the game since the player alters them
138 * by buying (and selling(?))
140 * Almost all operations are one byte only and overflow "errors" are
141 * extremely frequent and exploited.
143 * Trade Item prices are held internally in a single byte=true value/4.
144 * The decimal point in prices is introduced only when printing them.
145 * Internally, all prices are integers.
146 * The player's cash is held in four bytes.
148 void genMarket (ushort fluct
) {
150 foreach (uint i
; 0..Galaxy
.Goods
.max
+1) {
151 int product
= economy
*commodities
[i
].gradient
;
152 int changing
= fluct
&commodities
[i
].maskbyte
;
153 int q
= commodities
[i
].basequant
+changing
-product
;
155 if (q
&0x80) q
= 0; // clip to positive 8-bit
156 quantity
[i
] = cast(ushort)(q
&0x3F); // mask to 6 bits
157 q
= commodities
[i
].baseprice
+changing
+product
;
159 price
[i
] = cast(ushort)(q
*4);
161 quantity
[Galaxy
.Goods
.AlienItems
] = 0; // override to force nonavailability
164 // seperation between two planets (4*sqrt(X*X+Y*Y/4))
165 int distanceTo() (in auto ref Planet b
) const nothrow @safe @nogc {
166 import std
.math
: floor
, sqrt
;
167 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);
171 string
description () {
172 if (desc
.length
== 0) {
173 FastPRng prng
= goatsoupseed
;
174 desc
= goatSoup(prng
, "\x8F is \x97.", this);
180 Planet
[PlanetsInGalaxy
] planets
;
181 private GalaxySeed galseed
;
183 Planet currPlanet
; // current planet
184 private ulong curGalSeed
;
186 this (ubyte galnum
) { build(galnum
); }
188 @property ulong galSeed () const pure nothrow @nogc { return curGalSeed
; }
189 @property void galSeed (ulong v
) pure nothrow @nogc { curGalSeed
= v
; }
192 void build (ubyte galnum
) {
193 galseed
= GalaxySeed
.galaxy(galnum
);
194 curGalSeed
= galseed
.seed
;
195 foreach (immutable idx
, ref Planet ps
; planets
) { makeSystem(ps
); ps
.number
= cast(ubyte)idx
; }
197 // generate data for Lave (ignore the fact that we can be in another galaxy)
198 currPlanet
= planets
[Lave
];
199 currPlanet
.genMarket(0);
204 galseed
.seed
= curGalSeed
;
205 foreach (immutable idx
, ref Planet ps
; planets
) { makeSystem(ps
); ps
.number
= cast(ubyte)idx
; }
207 currPlanet
= planets
[currPlanet
.number
];
208 currPlanet
.genMarket(randbyte());
212 galseed
.seed
= curGalSeed
;
213 galseed
.nextgalaxy();
214 curGalSeed
= galseed
.seed
;
215 foreach (immutable idx
, ref Planet ps
; planets
) { makeSystem(ps
); ps
.number
= cast(ubyte)idx
; }
217 // generate data for Lave (ignore the fact that we can be in another galaxy)
218 currPlanet
= planets
[currPlanet
.number
];
219 currPlanet
.genMarket(randbyte());
224 if (i
>= 0 && i
< planets
.length
) {
225 currPlanet
= planets
[i
];
226 currPlanet
.genMarket(randbyte());
233 // return id of the planet whose name matches passed string closest to currentplanet; if none, return current planet
234 int find (const(char)[] s
) {
236 int res
= currPlanet
.number
;
237 foreach (immutable idx
, const ref p
; planets
) {
238 if (startsWithCI(p
.name
, s
)) {
239 auto nd
= currPlanet
.distanceTo(p
);
249 static startsWithCI (const(char)[] s
, const(char)[] pat
) nothrow @trusted @nogc {
250 import std
.ascii
: toUpper
, toLower
;
251 if (pat
.length
== 0 || pat
.length
> s
.length
) return false;
252 foreach (immutable idx
, char ch
; pat
) {
253 if (toLower(s
.ptr
[idx
]) != toLower(ch
)) return false;
259 // generate system info from seed
260 void makeSystem (out Planet thissys
) {
263 ushort pair1
, pair2
, pair3
, pair4
;
264 ushort longnameflag
= s
.w0
&64;
265 const(char)* pairs1
= pairs
.ptr
+24; // start of pairs used by this routine
270 thissys
.govtype
= cast(Galaxy
.Gov
)((s
.w1
>>3)&7); // bits 3, 4 & 5 of w1
272 ubyte ec
= (s
.w0
>>8)&7; // bits 8, 9 & A of w0
273 if (thissys
.govtype
<= 1) ec |
= 2;
274 thissys
.economy
= cast(Galaxy
.Economy
)ec
;
276 thissys
.techlev
= cast(ubyte)(((s
.w1
>>8)&3)+(ec^
7));
277 thissys
.techlev
+= thissys
.govtype
>>1;
278 if (thissys
.govtype
&1) thissys
.techlev
+= 1; // simulation of 6502's LSR then ADC
280 thissys
.population
= cast(ubyte)(4*thissys
.techlev
+thissys
.economy
);
281 thissys
.population
+= thissys
.govtype
+1;
283 thissys
.productivity
= cast(ushort)(((thissys
.economy^
7)+3)*(thissys
.govtype
+4));
284 thissys
.productivity
*= thissys
.population
*8;
286 thissys
.radius
= cast(ushort)(256*(((s
.w2
>>8)&15)+11)+thissys
.x
);
288 thissys
.goatsoupseed
.a
= s
.w1
&0xFF;
289 thissys
.goatsoupseed
.b
= s
.w1
>>8;
290 thissys
.goatsoupseed
.c
= s
.w2
&0xFF;
291 thissys
.goatsoupseed
.d
= s
.w2
>>8;
293 pair1
= 2*((s
.w2
>>8)&31); s
.tweak
;
294 pair2
= 2*((s
.w2
>>8)&31); s
.tweak
;
295 pair3
= 2*((s
.w2
>>8)&31); s
.tweak
;
296 pair4
= 2*((s
.w2
>>8)&31); s
.tweak
;
297 // always four iterations of random number
302 void putCh (char ch
) nothrow @trusted @nogc {
303 import std
.ascii
: toUpper
, toLower
;
304 if (ch
== '.') return;
305 if (nbpos
== 0) ch
= toUpper(ch
); else ch
= toLower(ch
);
306 namebuf
.ptr
[nbpos
++] = ch
;
309 putCh(pairs1
[pair1
]);
310 putCh(pairs1
[pair1
+1]);
311 putCh(pairs1
[pair2
]);
312 putCh(pairs1
[pair2
+1]);
313 putCh(pairs1
[pair3
]);
314 putCh(pairs1
[pair3
+1]);
316 // bit 6 of ORIGINAL w0 flags a four-pair name
318 putCh(pairs1
[pair4
]);
319 putCh(pairs1
[pair4
+1]);
321 thissys
.name
= namebuf
[0..nbpos
].idup
;
325 // prng for planet price fluctuations
326 static uint lastrand
= 0x29a;
327 static void mysrand (uint seed
) { lastrand
= seed
-1; }
328 static int myrand () {
330 // As supplied by D McDonnell from SAS Insititute C
331 r
= (((((((((((lastrand
<<3)-lastrand
)<<3)+lastrand
)<<1)+lastrand
)<<4)-lastrand
)<<1)-lastrand
)+0xe60)&0x7fffffff;
335 static ubyte randbyte () { return (myrand()&0xFF); }
339 // ////////////////////////////////////////////////////////////////////////// //
342 // four byte random number used for planet description
346 pure nothrow @safe @nogc:
351 if (this.a
> 127) ++a
;
353 this.c
= cast(ubyte)x
;
354 a
/= 256; // a = any carry left from above
356 a
= (a
+x
+this.d
)&0xFF;
357 this.b
= cast(ubyte)a
;
358 this.d
= cast(ubyte)x
;
364 // six byte random number used as seed for planets
365 // initied with base seed for galaxy 1
371 pure nothrow @safe @nogc:
372 // create seed for the corresponding galaxy
373 this (ubyte galnum
) {
375 foreach (immutable _
; 0..galnum
) nextgalaxy();
378 static GalaxySeed
galaxy (ubyte galnum
) { return GalaxySeed(galnum
); }
380 @property ulong seed () const {
382 ((cast(ulong)w0
)<<2*16)|
383 ((cast(ulong)w1
)<<1*16)|
387 @property void seed (ulong v
) {
388 w0
= cast(ushort)(v
>>(2*16));
389 w1
= cast(ushort)(v
>>(1*16));
390 w2
= cast(ushort)(v
);
393 private void tweak () {
394 ushort temp
= cast(ushort)(cast(ushort)(w0
+w1
)+w2
); // 2 byte aritmetic
400 // apply to base seed; once for galaxy 2, etc.
402 // rotate 8 bit number leftwards
403 static ushort rotatel() (ushort x
) { ushort temp
= x
&128; return (2*(x
&127))+(temp
>>7); }
404 static ushort twist() (ushort x
) { return cast(ushort)((256*rotatel(x
>>8))+rotatel(x
&255)); }
405 w0
= twist(w0
); // twice for galaxy 3, etc.
406 w1
= twist(w1
); // Eighth application gives galaxy 1 again
412 // ////////////////////////////////////////////////////////////////////////// //
413 struct TradeGoodIntr
{
414 // In 6502 version these were:
415 ushort baseprice
; // one byte
416 short gradient
; // five bits plus sign
417 ushort basequant
; // one byte
418 ushort maskbyte
; // one byte
419 ushort units
; // two bits
420 string name
; // longest="Radioactives"
423 static immutable string
[8] govnames
= ["Anarchy", "Feudal", "Multi-gov", "Dictatorship", "Communist", "Confederacy", "Democracy", "Corporate State"];
424 static immutable string
[8] econnames
= ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial", "Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"];
425 static immutable string
[3] unitnames
= ["t", "kg", "g"];
427 /* Data for DB's price/availability generation system */
428 /* Base Grad Base Mask Un Name
429 price ient quant it */
430 static immutable TradeGoodIntr
[17] commodities
= [
431 TradeGoodIntr(0x13, -0x02, 0x06, 0x01, 0, "Food"),
432 TradeGoodIntr(0x14, -0x01, 0x0A, 0x03, 0, "Textiles"),
433 TradeGoodIntr(0x41, -0x03, 0x02, 0x07, 0, "Radioactives"),
434 TradeGoodIntr(0x28, -0x05, 0xE2, 0x1F, 0, "Slaves"),
435 TradeGoodIntr(0x53, -0x05, 0xFB, 0x0F, 0, "Liquor/Wines"),
436 TradeGoodIntr(0xC4, +0x08, 0x36, 0x03, 0, "Luxuries"),
437 TradeGoodIntr(0xEB, +0x1D, 0x08, 0x78, 0, "Narcotics"),
438 TradeGoodIntr(0x9A, +0x0E, 0x38, 0x03, 0, "Computers"),
439 TradeGoodIntr(0x75, +0x06, 0x28, 0x07, 0, "Machinery"),
440 TradeGoodIntr(0x4E, +0x01, 0x11, 0x1F, 0, "Alloys"),
441 TradeGoodIntr(0x7C, +0x0d, 0x1D, 0x07, 0, "Firearms"),
442 TradeGoodIntr(0xB0, -0x09, 0xDC, 0x3F, 0, "Furs"),
443 TradeGoodIntr(0x20, -0x01, 0x35, 0x03, 0, "Minerals"),
444 TradeGoodIntr(0x61, -0x01, 0x42, 0x07, 1, "Gold"),
445 TradeGoodIntr(0xAB, -0x02, 0x37, 0x1F, 1, "Platinum"),
446 TradeGoodIntr(0x2D, -0x01, 0xFA, 0x0F, 2, "Gem Stones"),
447 TradeGoodIntr(0x35, +0x0F, 0xC0, 0x07, 0, "Alien Items"),
451 // ////////////////////////////////////////////////////////////////////////// //
452 // fixed (R.C): can't assume that two separate array declarations are adjacently located in memory -- unified in a sigle array
453 string pairs
= "ABOUSEITILETSTONLONUTHNO..LEXEGEZACEBISOUSESARMAINDIREA.ERATENBERALAVETIEDORQUANTEISRION"; // dots should be nullprint characters
455 /* "Goat Soup" planetary description string code - adapted from Christian Pinder's reverse engineered sources. */
456 static immutable string
[5][36] descList
= [
457 /* 81 */ ["fabled", "notable", "well known", "famous", "noted"],
458 /* 82 */ ["very", "mildly", "most", "reasonably", ""],
459 /* 83 */ ["ancient", "\x95", "great", "vast", "pink"],
460 /* 84 */ ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"],
461 /* 85 */ ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"],
462 /* 86 */ ["food blenders", "tourists", "poetry", "discos", "\x8E"],
463 /* 87 */ ["talking tree", "crab", "bat", "lobst", "\xB2"],
464 /* 88 */ ["beset", "plagued", "ravaged", "cursed", "scourged"],
465 /* 89 */ ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"],
466 /* 8A */ ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"],
467 /* 8B */ ["juice", "brandy", "water", "brew", "gargle blasters"],
468 /* 8C */ ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"],
469 /* 8D */ ["fabulous", "exotic", "hoopy", "unusual", "exciting"],
470 /* 8E */ ["cuisine", "night life", "casinos", "sit coms", " \xA1 "],
471 /* 8F */ ["\xB0", "The planet \xB0", "The world \xB0", "This planet", "This world"],
472 /* 90 */ ["n unremarkable", " boring", " dull", " tedious", " revolting"],
473 /* 91 */ ["planet", "world", "place", "little planet", "dump"],
474 /* 92 */ ["wasp", "moth", "grub", "ant", "\xB2"],
475 /* 93 */ ["poet", "arts graduate", "yak", "snail", "slug"],
476 /* 94 */ ["tropical", "dense", "rain", "impenetrable", "exuberant"],
477 /* 95 */ ["funny", "wierd", "unusual", "strange", "peculiar"],
478 /* 96 */ ["frequent", "occasional", "unpredictable", "dreadful", "deadly"],
479 /* 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"],
480 /* 98 */ ["\x9B", "mountain", "edible", "tree", "spotted"],
481 /* 99 */ ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"],
482 /* 9A */ ["ancient", "exceptional", "eccentric", "ingrained", "\x95"],
483 /* 9B */ ["killer", "deadly", "evil", "lethal", "vicious"],
484 /* 9C */ ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"],
485 /* 9D */ ["plant", "tulip", "banana", "corn", "\xB2weed"],
486 /* 9E */ ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"],
487 /* 9F */ ["shrew", "beast", "bison", "snake", "wolf"],
488 /* A0 */ ["leopard", "cat", "monkey", "goat", "fish"],
489 /* A1 */ ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"],
490 /* A2 */ ["meat", "cutlet", "steak", "burgers", "soup"],
491 /* A3 */ ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"],
492 /* A4 */ ["hockey", "cricket", "karate", "polo", "tennis"]
495 /* B0 = <planet name>
496 B1 = <planet name>ian
500 string
goatSoup (ref FastPRng prng
, string source
, in ref Galaxy
.Planet psy
) {
501 import std
.ascii
: toLower
;
504 void addCh (char ch
) {
505 if (!ch
) return; // just in case
506 if (ch
<= ' ') ch
= ' ';
508 if (res
.length
== 0 || res
[$-1] <= ' ') return;
510 } else if ((ch
>= 'A' && ch
<= 'Z') ||
(ch
>= 'a' && ch
<= 'z') ||
(ch
>= '0' && ch
<= '9')) {
513 // punctuation -- remove trailing spaces
514 while (res
.length
&& res
[$-1] <= ' ') res
= res
[0..$-1];
519 while (source
.length
) {
521 source
= source
[1..$];
527 res
~= goatSoup(prng
, descList
[c
-0x81][(rnd
>= 0x33)+(rnd
>= 0x66)+(rnd
>= 0x99)+(rnd
>= 0xCC)], psy
);
530 case 0xB0: // planet name
532 foreach (char ch
; psy
.name
[1..$]) addCh(toLower(ch
));
534 case 0xB1: // <planet name>ian
536 string t
= psy
.name
[1..$];
538 if (t
.length
> 1 ||
(t
[0] != 'e' && t
[0] != 'i')) addCh(toLower(t
[0]));
543 case 0xB2: // random name
544 int len
= prng
.next
&3;
545 for (int i
= 0; i
<= len
; ++i
) {
546 int x
= prng
.next
&0x3e;
547 /* fixed (R.C.): transform chars to lowercase unless
548 first char of first pair, or second char of first
549 pair when first char was not printed */
551 char p2
= pairs
[x
+1];
553 if (i
) p1
= toLower(p1
);
557 if (i || p1
!= '.') p2
= toLower(p2
);
570 // ////////////////////////////////////////////////////////////////////////// //
571 version(elite_world_test
) unittest {
573 auto uni
= Galaxy(0);
575 conwriteln("System: ", uni
.currPlanet
.name
);
576 conwriteln("Position: (", uni
.currPlanet
.x
, ",", uni
.currPlanet
.y
, ")");
577 conwriteln("Economy: ", uni
.currPlanet
.economyName
);
578 conwriteln("Government: ", uni
.currPlanet
.govName
);
579 conwriteln("Tech Level: ", uni
.currPlanet
.techlev
+1);
580 conwriteln("Turnover: ", uni
.currPlanet
.productivity
);
581 conwriteln("Radius: ", uni
.currPlanet
.radius
);
582 conwriteln("Population: ", uni
.currPlanet
.population
/10, ".", uni
.currPlanet
.population
%10, " Billion");
583 conwriteln(uni
.currPlanet
.description
);
585 conwriteln("--------------------------------");
586 foreach (immutable idx
; 0..Galaxy
.Goods
.max
+1) {
587 auto good
= Galaxy
.good(idx
);
588 conwritefln
!"%-12s %3s.%s %2s%s"(good
.name
, uni
.currPlanet
.price
[idx
]/10, uni
.currPlanet
.price
[idx
]%10, uni
.currPlanet
.quantity
[idx
], good
.unitName
);