chiark / gitweb /
4f63e0c2f8e8b9297db5940bfb6f49c3cdca7de1
[rocl] / elite.tcl
1 #! /usr/bin/tclsh
2 #
3 # $Id: elite.tcl,v 1.3 2003/02/26 01:13:22 mdw Exp $
4
5 package require "elite-bits" "1.0.0"
6
7 set galaxy1 "4a5a480253b7"              ;# Seed for standard galaxy 1
8
9 # --- tab ARR NAME NAME ... ---
10 #
11 # Construct an array mapping integers 0, 1, ... to the given NAMEs, in order.
12
13 proc tab {arr args} {
14   upvar 1 $arr a
15   set i 0
16   foreach v $args {
17     set a($i) $v
18     incr i
19   }
20 }
21
22 # --- Various standard tables ---
23
24 tab government \
25     "anarchy" "feudal" "multi-government" "dictatorship" \
26     "communist" "confederacy" "democracy" "corporate state"
27
28 tab economy \
29     "rich industrial" "average industrial" "poor industrial" \
30     "mainly industrial" "mainly agricultural" "rich agricultural" \
31     "average agricultural" "poor agricultural"
32
33 tab gov \
34     anarchy feudal multi-gov dictator \
35     communist confed democracy corp-state
36
37 tab eco \
38     rich-ind avg-ind poor-ind mainly-ind \
39     mainly-agri rich-agri avg-agri poor-agri
40
41 set products {
42   food          "Food"
43   textiles      "Textiles"
44   radioactives  "Radioactives"
45   slaves        "Slaves"
46   liquor-wines  "Liquor & wines"
47   luxuries      "Luxuries"
48   narcotics     "Narcotics"
49   computers     "Computers"
50   machinery     "Machinery"
51   alloys        "Alloys"
52   firearms      "Firearms"
53   furs          "Furs"
54   minerals      "Minerals"
55   gold          "Gold"
56   platinum      "Platinum"
57   gem-stones    "Gem-stones"
58   alien-items   "Alien items"
59 }
60
61 foreach p $products { set unit($p) t }
62 foreach p {gold platinum} { set unit($p) kg }
63 set unit(gem-stones) g
64 unset p
65
66 # --- galaxy N [GAL] ---
67 #
68 # Compute the seed of the Nth galaxy, if GAL is the seed of galaxy 1.  By
69 # default, GAL is the standard galaxy 1 seed.
70
71 proc galaxy [list n [list g $galaxy1]] {
72   for {set i 1} {$i < $n} {incr i} {
73     set g [elite-nextgalaxy $g]
74   }
75   return $g
76 }
77
78 # --- foreach-world GAL ARR SCRIPT ---
79 #
80 # For each world in galaxy GAL (a seed), set ARR to the world information
81 # and evaluate SCRIPT.  The usual loop control commands can be used in
82 # SCRIPT.
83
84 proc foreach-world {g p act} {
85   upvar 1 $p pp
86   for {set i 0} {$i < 256} {incr i; set g [elite-nextworld $g]} {
87     elite-worldinfo pp $g
88     uplevel 1 $act    
89   }
90 }
91
92 # --- find-world GAL PAT ---
93 #
94 # Return a list of seeds for the worlds in galaxy GAL (a seed) whose names
95 # match the glob pattern PAT.
96
97 proc find-world {g pat} {
98   set l {}
99   foreach-world $g p {
100     if {[string match -nocase $pat $p(name)]} {
101       lappend l $p(seed)
102     }
103   }
104   return $l
105 }
106
107 # --- destructure PAT LIST ---
108 #
109 # Destrcture LIST according to PAT.  If PAT is a single name, set the
110 # variable PAT to LIST; otherwise, if PAT is a list, each of its elements
111 # must correspond to an element of LIST, so recursively destructure the
112 # corresponding elements of each.  It is not an error if the PAT list is
113 # shorter than LIST.  The special variable name `.' indicates that no
114 # assignment is to be made.
115
116 proc destructure {pp xx} {
117   if {![string compare $pp "."]} {
118     return
119   } elseif {[llength $pp] == 0} {
120     return
121   } elseif {[llength $pp] == 1} {
122     upvar 1 $pp p
123     set p $xx
124   } else {
125     foreach p $pp x $xx {
126       uplevel 1 [list destructure $p $x]
127     }
128   }
129 }
130
131 # --- worldinfo GAL ---
132 #
133 # Return a list describing the worlds in galaxy GAL (a seed).  The list
134 # contains a group of three elements for each world: the seed, x and y
135 # coordinates (in decilightyears).
136
137 proc worldinfo {g} {
138   foreach-world $g p {
139     lappend i $p(seed) $p(x) $p(y)
140   }
141   return $i
142 }
143
144 # --- world-distance X Y XX YY ---
145 #
146 # Computes the correct game distance in decilightyears between two worlds,
147 # one at X, Y and the other at XX, YY.
148
149 proc world-distance {x y xx yy} {
150   set dx [expr {abs($x - $xx)/4}]
151   set dy [expr {abs($y - $yy)/4}]
152   return [expr {4 * floor(sqrt($dx * $dx + $dy * $dy))}]
153 }
154
155 # --- nearest-planet WW X Y ---
156 #
157 # Returns the seed of the `nearest' planet given in the worldinfo list WW to
158 # the point X Y (in decilightyears).
159
160 proc nearest-planet {ww x y} {
161   set min 10000
162   foreach {ss xx yy} $ww {
163     set dx [expr {abs($x - $xx)/4}]
164     set dy [expr {abs($y - $yy)/2}]
165     if {$dx > $dy} {
166       set d [expr {($dx * 2 + $dy)/2}]
167     } else {
168       set d [expr {($dx + $dy * 2)/2}]
169     }
170     if {$d < $min} {
171       set p $ss
172       set min $d
173     }
174   }
175   return $p
176 }
177
178 # --- adjacency WW ADJ [D] ---
179 #
180 # Fill in the array ADJ with the adjacency table for the worlds listed in the
181 # worldinfo list WW.  That is, for each world seed S, ADJ(S) is set to a
182 # worldinfo list containing the worlds within D (default 70) decilightyears
183 # of S.
184
185 proc adjacency {p adj {d 70}} {
186   upvar 1 $adj a
187   array set a {}
188   foreach {s x y} $p {
189     set done($s) 1
190     lappend a($s)
191     foreach {ss xx yy} $p {
192       if {[info exists done($ss)]} { continue }
193       if {abs($x - $xx) > $d + 10 || abs($y - $yy) > $d + 10 ||
194           [world-distance $x $y $xx $yy] > $d} { continue }
195       lappend a($s) $ss $xx $yy
196       lappend a($ss) $s $x $y
197     }
198   }
199 }
200
201 # --- worldname W ---
202 #
203 # Returns the name of the world with seed W.
204
205 proc worldname {w} {
206   elite-worldinfo p $w
207   return $p(name)
208 }
209
210 # --- shortest-path ADJ FROM TO WEIGHT ---
211 #
212 # Computes the shortest path and shortest distance between the worlds wose
213 # seeds are FROM and TO respectively.  ADJ must be an adjacency table for the
214 # galaxy containing FROM and TO.  WEIGHT is a command such that WEIGHT A B
215 # returns the `distance' for the simple path between A and B.  The return
216 # value is a list P D, where D is the weight of the path found, and P is a
217 # simple list of seeds for the worlds on the path.  P starts with FROM and
218 # ends with TO.
219
220 proc shortest-path {adjvar from to weight} {
221   upvar 1 $adjvar adj
222   if {[string equal $from $to]} { return [list $to 0] }
223   set l($from) 0
224   set p($from) $from
225   set c $from
226   while 1 {
227     foreach {n x y} $adj($c) {
228       if {[info exists l($n)]} {
229         continue
230       }
231       set w [expr {$l($c) + [uplevel 1 $weight [list $c $n]]}]
232       if {![info exists ll($n)] || $w < $ll($n)} {
233         set ll($n) $w
234         set p($n) [concat $p($c) [list $n]]
235       }
236     }
237     set s [array startsearch ll]
238     if {![array anymore ll $s]} {
239       return {{} 0}
240     }
241     set c [array nextelement ll $s]
242     set w $ll($c)
243     while {[array anymore ll $s]} {
244       set n [array nextelement ll $s]
245       if {$ll($n) < $w} {
246         set c $n
247         set w $ll($n)
248       }
249     }
250     if {[string equal $c $to]} { return [list $p($to) $ll($to)] }
251     set l($c) $ll($c)
252     unset ll($c)
253   }
254 }
255
256 # --- weight-hops A B ---
257 #
258 # shortest-path weight function giving each hop the same weight.
259
260 proc weight-hops {from to} {
261   return 1
262 }
263
264 # --- weight-fuel A B ---
265 #
266 # shortest-path weight function measuring the distance between FROM and TO.
267
268 proc weight-fuel {from to} {
269   elite-worldinfo f $from
270   elite-worldinfo t $to
271   return [world-distance $f(x) $f(y) $t(x) $t(y)]
272 }
273
274 # --- weight-safety A B ---
275 #
276 # shortest-path weight function attempting to maximize safety of the journey
277 # by giving high weight (square-law) to worlds with unstable governments.
278
279 proc weight-safety {from to} {
280   elite-worldinfo t $to
281   set w [expr {8 - $t(government)}]
282   return [expr {$w * $w}]
283 }
284
285 # --- weight-encounters A B ---
286 #
287 # shortest-path weight function attempting to maximize encounters on the
288 # journey by giving high weight (square law) to worlds with stable
289 # governments.
290
291 proc weight-encounters {from to} {
292   elite-worldinfo f $from
293   elite-worldinfo t $to
294   set w [expr {1 + $t(government)}]
295   return [expr {$w * $w}]
296 }
297
298 # --- weight-trading A B ---
299 #
300 # shortest-path weight function attempting to maximize trading opportunities
301 # along the journey by giving high weight (square law) to pairs of worlds
302 # with small differences between their economic statuses.
303
304 proc weight-trading {from to} {
305   elite-worldinfo f $from
306   elite-worldinfo t $to
307   set w [expr {8 - abs($f(economy) - $t(economy))}]
308   return [expr {$w * $w}]
309 }
310
311 # --- parse-galaxy-spec G ---
312 #
313 # Parses a galaxy spec and returns a list containing a description of the
314 # galaxy and the corresponding galaxy seed.  A galaxy spec is one of:
315 #
316 #   * a number between 1 and 8, corresponding to one of the standard
317 #     galaxies;
318 #
319 #   * a 12-digit hex string, which is a galaxy seed (and is returned
320 #     unchanged); or
321 #
322 #   * a string of the form S:N where S is a 12-hex-digit seed and N is a
323 #     galaxy number, corresponding to the Nth galaxy starting with S as
324 #     galaxy 1.
325 #
326 # If the string is unrecognized, an empty list is returned.
327
328 proc parse-galaxy-spec {g} {
329   switch -regexp -- $g {
330     {^[1-8]$} { return [list $g [galaxy $g]] }
331     {^[0-9a-fA-F]{12}$} { return [list $g $g] }
332     default {
333       if {[regexp {^([0-9a-fA-F]{12}):([1-8])$} $g . b n]} {
334         return [list $g [galaxy $n $b]]
335       }
336     }
337   }
338   return {}
339 }
340
341 # --- parse-planet-spec G P ---
342 #
343 # Parses a planet spec and returns the planet seed.  The planet spec P is
344 # interpreted relative to galaxy G.  A planet spec is one of:
345 #
346 #   * a simple integer, corresponding to a planet number;
347 #
348 #   * a 12-hex-digit seed, which is returned unchanged;
349 #
350 #   * a pair of integers separated by commas, corresponding to the nearest
351 #     planet to those coordinates;
352 #
353 #   * a glob pattern, corresponding to the lowest-numbered planet in the
354 #     galaxy whose name matches the pattern case-insensitively; or
355 #
356 #   * a string of the form G.P where G is a galaxy spec and P is a planet
357 #     spec, corresponding to the planet specified by P relative to galaxy G.
358 #
359 # If the string is unrecognized, an empty string is returned.
360
361 proc parse-planet-spec {g p} {
362   if {[regexp {^[0-9a-fA-F]{12}$} $p]} { return $p }
363   if {[regexp {^(.+)\.(.+)$} $p . g p]} {
364     set g [parse-galaxy-spec $g]
365     if {[string equal $g ""]} { return {} }
366     destructure {. g} $g
367     return [parse-planet-spec $g $p]
368   }
369   if {[regexp {^(0x[0-9a-fA-F]+|[0-9]+)$} $p]} {
370     for {set s $g; set i 0} {$i < $p} {incr i; set s [elite-nextworld $s]} {}
371     return $s
372   }
373   if {[regexp {^(0x[0-9a-fA-F]+|[0-9]+),\s*(0x[0-9a-fA-F]+|[0-9]+)$} \
374       $p . x y]} {
375     return [nearest-planet [worldinfo $g] $x $y]
376   }
377   set l [find-world $g $p]
378   if {[llength $l]} { return [lindex $l 0] }
379   return {}
380 }
381
382 # --- in-galaxy-p G PP ---
383 #
384 # Returns nonzero if the planets (seeds) listed in PP are in galaxy G.
385 # Doesn't mind if the planet seeds are invalid.
386
387 proc in-galaxy-p {g pp} {
388   foreach-world $g i { set x($i(seed)) 1 }
389   foreach p $pp { if {![info exists x($p)]} { return 0 } }
390   return 1
391 }
392
393 # --- world-summary PLANET ---
394 #
395 # Return a one-line summary string for PLANET.
396
397 proc world-summary {s} {
398   global eco gov
399   elite-worldinfo p $s
400   return [format "%-12s %4d %4d %-11s %-10s %2d %s" \
401       $p(name) $p(x) $p(y) \
402       $eco($p(economy)) $gov($p(government)) $p(techlevel) $p(seed)]
403 }
404
405 #----- That's all, folks ----------------------------------------------------
406
407 package provide "elite" "1.0.0"