1 /* Scores and scoring in Xconq.
2 Copyright (C) 1987-1989, 1991-2000 Stanley T. Shebs.
3 Copyright (C) 2005 Eric A. McDonald.
4
5 Xconq is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version. See the file COPYING. */
9
10 /* Scoring in Xconq does not happen by default; instead a game design
11 may include one or more "scorekeepers", which are objects with
12 properties that define how winning and losing will be decided.
13
14 There are two kinds of functionality in scorekeepers. One kind is
15 the ability to end a game based on a specified condition, such as
16 "the Star of Satyria enters Noblum". The other is the accumulation
17 of a numeric score. A scorekeeper may include both, so it can (for
18 instance) end the game the instant that a player's score reaches
19 100, or 100 more than another player, or whatever.
20
21 The indepside should not participate in scoring, so the iterations
22 in this file generally iterate only over the "real" sides. */
23
24 #include "conq.h"
25 #include "kernel.h"
26
27 typedef struct a_score_record {
28 char *gamename;
29 Obj *sides;
30 Obj *raw;
31 int numturns;
32 Obj *varsets;
33 struct a_score_record *next;
34 } ScoreRecord;
35
36 /* This is true when the given side should be tested against the given
37 scorekeeper. */
38
39 #define scorekeeper_applicable(side,sk) \
40 ((side)->ingame && (side_in_set((side), (sk)->whomask)))
41
42 /* Iteration over all recorded scores. */
43
44 #define for_all_score_records(sr) \
45 for ((sr) = records; (sr) != NULL; (sr) = (sr)->next)
46
47 static int read_scorefile(void);
48 static int interp_score_record(Obj *form);
49 static char *basic_player_name(Player *player);
50 static void eval_sk_last_side_wins(Scorekeeper *sk);
51 static void eval_sk_last_alliance_wins(Scorekeeper *sk);
52 static void score_variant_desc(ScoreRecord *sr, char *varbuf);
53
54 /* The head of the list of scorekeepers. */
55
56 Scorekeeper *scorekeepers;
57
58 /* The end of the list of scorekeepers. */
59
60 Scorekeeper *last_scorekeeper;
61
62 /* The total number of scorekeepers defined. */
63
64 int numscorekeepers;
65
66 int nextskid;
67
68 /* The number of scorekeepers maintaining numeric scores. */
69
70 int numscores;
71
72 /* True if any pre-turn scorekeepers are defined. */
73
74 int any_pre_turn_scores;
75
76 /* True if any post-turn scorekeepers are defined. */
77
78 int any_post_turn_scores;
79
80 /* True if any post-action scorekeepers are defined. */
81
82 int any_post_action_scores;
83
84 /* True if any post-event scorekeepers are defined. */
85
86 int any_post_event_scores;
87
88 /* True if any turn-specific scorekeepers are defined. */
89
90 int any_turn_specific_scores;
91
92 /* The count of sides in the game when the last-side-wins scorekeeper
93 first tested. */
94
95 static int num_sides_originally;
96
97 static int only_checking;
98
99 static int we_have_a_winner;
100
101 ScoreRecord *records;
102
103 /* Clear out any possible scorekeepers. */
104
105 void
init_scorekeepers(void)106 init_scorekeepers(void)
107 {
108 scorekeepers = last_scorekeeper = NULL;
109 numscorekeepers = 0;
110 nextskid = 1;
111 any_pre_turn_scores = FALSE;
112 any_post_turn_scores = FALSE;
113 any_post_action_scores = FALSE;
114 any_post_event_scores = FALSE;
115 any_turn_specific_scores = FALSE;
116 }
117
118 Scorekeeper *
create_scorekeeper(void)119 create_scorekeeper(void)
120 {
121 Scorekeeper *sk = (Scorekeeper *) xmalloc(sizeof(Scorekeeper));
122
123 /* Initialize any nonzero fields. */
124 sk->id = nextskid++;
125 sk->when = lispnil;
126 sk->who = lispnil;
127 sk->whomask = ALLSIDES;
128 sk->knownto = lispnil;
129 sk->knowntomask = ALLSIDES;
130 sk->trigger = lispnil;
131 sk->body = lispnil;
132 sk->record = lispnil;
133 sk->notes = lispnil;
134 sk->scorenum = -1;
135 sk->keepscore = TRUE;
136 sk->initial = 0;
137 sk->triggered = FALSE;
138 /* Add the new scorekeeper to the end of the list. */
139 if (last_scorekeeper != NULL) {
140 last_scorekeeper->next = sk;
141 last_scorekeeper = sk;
142 } else {
143 scorekeepers = last_scorekeeper = sk;
144 }
145 ++numscorekeepers;
146 return sk;
147 }
148
149 Scorekeeper *
find_scorekeeper(int id)150 find_scorekeeper(int id)
151 {
152 Scorekeeper *sk;
153
154 for_all_scorekeepers(sk) {
155 if (sk->id == id)
156 return sk;
157 }
158 return NULL;
159 }
160
161 /* Allocate and fill in the initial score records for each side. This
162 must happen after all scorekeepers have been defined. */
163
164 void
init_scores(void)165 init_scores(void)
166 {
167 int score;
168 Side *side;
169 Scorekeeper *sk;
170 Obj *savedscores, *when;
171
172 /* First count and index all the scorekeepers that maintain
173 a numeric score. */
174 numscores = 0;
175 for_all_scorekeepers(sk) {
176 if (sk->keepscore) {
177 sk->scorenum = numscores++;
178 }
179 }
180 /* Allocate an appropriately-sized scorecard for each side. Note that a
181 particular position in the scorecard might not apply to all sides. */
182 for_all_sides(side) {
183 /* Collect any Lisp object that might have been stashed here
184 while reading a game module. */
185 savedscores = side->rawscores;
186 side->scores = NULL;
187 if (numscores > 0) {
188 side->scores = (short *) xmalloc(numscores * sizeof(short));
189 for_all_scorekeepers(sk) {
190 if (!sk->keepscore)
191 continue;
192 score = sk->initial;
193 /* Collect a saved score if there is one to collect. */
194 if (savedscores != NULL
195 && savedscores != lispnil
196 && numberp(car(savedscores))) {
197 score = c_number(car(savedscores));
198 savedscores = cdr(savedscores);
199 }
200 side->scores[sk->scorenum] = score;
201 }
202 }
203 }
204 /* Some kinds of scorekeepers are expensive to run, so we set flags to
205 indicate directly that we need to make the check. */
206 for_all_scorekeepers(sk) {
207 when = sk->when;
208 if (consp(when)) {
209 if (cdr(when) != lispnil) {
210 any_turn_specific_scores = TRUE;
211 }
212 when = car(when);
213 }
214 if (match_keyword(when, K_BEFORE_TURN)) {
215 any_pre_turn_scores = TRUE;
216 }
217 if (when == lispnil || match_keyword(when, K_AFTER_TURN)) {
218 any_post_turn_scores = TRUE;
219 }
220 if (match_keyword(when, K_AFTER_ACTION)) {
221 any_post_action_scores = TRUE;
222 }
223 if (match_keyword(when, K_AFTER_EVENT)) {
224 any_post_event_scores = TRUE;
225 }
226 }
227 /* Compute the number of sides in the game initially. This is so
228 we don't force the game to end because (for whatever reason)
229 only one side was active from the beginning. */
230 num_sides_originally = 0;
231 for_all_sides(side) {
232 if (side->ingame)
233 ++num_sides_originally;
234 }
235 }
236
237 /* Generate a sidemask for sides that the scorekeeper should run for.
238 Note that this can be a subset of the 'whomask' for the scorekeeper,
239 since the 'when' condition may limit the sk from being applied to some
240 sides.
241 */
242
243 SideMask
generate_effective_sk_whomask(Scorekeeper * sk)244 generate_effective_sk_whomask(Scorekeeper *sk)
245 {
246 SideMask ewhomask = NOSIDES;
247 Side *side = NULL;
248 Obj *whenexpr = NULL;
249
250 assert_error(sk, "Attempted to access a NULL scorekeeper");
251 assert_error(sk->when,
252 "Attempted to access a NULL scorekeeper \"when expression\"");
253 /* If the when expression is NIL or not a full expression,
254 then just return the whomask. */
255 if ((sk->when == lispnil) || !consp(sk->when))
256 return sk->whomask;
257 /* Get the actual when expression.
258 We do not care about the when keyword here. */
259 whenexpr = cdr(sk->when);
260 /* If the when expression is a number,
261 then return NOSIDES if it is 0 (false), else return the sidemask. */
262 if (numberp(whenexpr)) {
263 if (c_number(whenexpr))
264 ewhomask = sk->whomask;
265 else
266 ewhomask = NOSIDES;
267 }
268 /* Else if the expression is NIL, then return NOSIDES. */
269 else if (lispnil == whenexpr)
270 ewhomask = NOSIDES;
271 /* Else if the expression is a cons,
272 then evaluate it for each side and build the sidemask accordingly. */
273 else if (consp(whenexpr)) {
274 /* If the test is a cons, then evaluate it. */
275 if (consp(car(whenexpr))) {
276 for_all_sides(side) {
277 if (!scorekeeper_applicable(side, sk))
278 continue;
279 if (lispnil != eval_sk_test(side, sk, car(whenexpr)))
280 ewhomask = add_side_to_set(side, ewhomask);
281 }
282 }
283 /* Else if the test is just a number, then assume <= g_turn(). */
284 /*! \todo Decide based on the when type once we support event and
285 action scorekeepers. */
286 else if (numberp(car(whenexpr))) {
287 if (c_number(car(whenexpr)) <= g_turn())
288 ewhomask = sk->whomask;
289 }
290 /* Else we don't know how to handle the test... */
291 else {
292 run_warning(
293 "Garbled \"when expression\" for scorekeeper %s",
294 (sk->title ? sk->title : ""));
295 ewhomask = NOSIDES;
296 }
297 }
298 /* Else if the expression cannot be understood,
299 then warn and return NOSIDES. */
300 else {
301 run_warning(
302 "Garbled \"when expression\" for scorekeeper %s",
303 (sk->title ? sk->title : ""));
304 ewhomask = NOSIDES;
305 }
306 return ewhomask;
307 }
308
309 /* Test all the scorekeepers that should be run immediately before any
310 side moves anything. */
311
312 void
check_pre_turn_scores(void)313 check_pre_turn_scores(void)
314 {
315 Side *side;
316 Scorekeeper *sk;
317
318 if (any_pre_turn_scores) {
319 for_all_scorekeepers(sk) {
320 if (match_keyword(sk->when, K_BEFORE_TURN)) {
321 for_all_sides(side) {
322 if (scorekeeper_applicable(side, sk)) {
323 run_scorekeeper(side, sk);
324 } else {
325 Dprintf("sk %d not applicable to %s\n",
326 sk->id, side_desig(side));
327 }
328 }
329 }
330 }
331 }
332 }
333
334 /* Test all the scorekeepers that should be run only at the end of a turn. */
335
336 void
check_post_turn_scores(void)337 check_post_turn_scores(void)
338 {
339 Side *side;
340 Scorekeeper *sk;
341 Obj *when = lispnil;
342 SideMask ewhomask = NOSIDES;
343
344 if (any_post_turn_scores) {
345 for_all_scorekeepers(sk) {
346 Dprintf("Checking post-turn scorekeeper %d\n", sk->id);
347 ewhomask = NOSIDES;
348 /* Parse the when expression. */
349 when = sk->when;
350 if ((when != lispnil) && consp(when))
351 when = car(when);
352 /* See if when expression is applicable here,
353 and act accordingly. */
354 if ((when == lispnil)
355 || match_keyword(when, K_AFTER_TURN)) {
356 ewhomask = generate_effective_sk_whomask(sk);
357 if (NOSIDES == ewhomask)
358 continue;
359 if (symbolp(sk->body)
360 && match_keyword(sk->body, K_LAST_SIDE_WINS)) {
361 eval_sk_last_side_wins(sk);
362 } else if (symbolp(sk->body)
363 && match_keyword(sk->body, K_LAST_ALLIANCE_WINS)) {
364 eval_sk_last_alliance_wins(sk);
365 } else {
366 for_all_sides(side) {
367 if (side_in_set(side, ewhomask)) {
368 run_scorekeeper(side, sk);
369 } else {
370 Dprintf("sk %d not applicable to %s\n",
371 sk->id, side_desig(side));
372 }
373 }
374 }
375 }
376 }
377 }
378 }
379
380 void
check_post_action_scores(void)381 check_post_action_scores(void)
382 {
383 Side *side;
384 Scorekeeper *sk;
385
386 if (any_post_action_scores) {
387 Dprintf("Checking post-action scorekeepers\n");
388 for_all_scorekeepers(sk) {
389 if (match_keyword(sk->when, K_AFTER_ACTION)) {
390 if (sk->trigger == lispnil || sk->triggered) {
391 for_all_sides(side) {
392 if (scorekeeper_applicable(side, sk)) {
393 run_scorekeeper(side, sk);
394 } else {
395 Dprintf("sk %d not applicable to %s\n",
396 sk->id, side_desig(side));
397 }
398 }
399 }
400 }
401 }
402 }
403 }
404
405 void
check_post_event_scores(void)406 check_post_event_scores(void)
407 {
408 Side *side;
409 Scorekeeper *sk;
410
411 if (any_post_event_scores) {
412 Dprintf("Checking post-event scorekeepers\n");
413 for_all_scorekeepers(sk) {
414 if (match_keyword(sk->when, K_AFTER_EVENT)) {
415 if (sk->trigger == lispnil || sk->triggered) {
416 for_all_sides(side) {
417 if (scorekeeper_applicable(side, sk)) {
418 run_scorekeeper(side, sk);
419 } else {
420 Dprintf("sk %d not applicable to %s\n",
421 sk->id, side_desig(side));
422 }
423 }
424 }
425 }
426 }
427 }
428 }
429
430 /* This is what actually does the test and effect of the scorekeeper
431 on the given side. This can be expensive to run. */
432
433 void
run_scorekeeper(Side * side,Scorekeeper * sk)434 run_scorekeeper(Side *side, Scorekeeper *sk)
435 {
436 eval_sk_form(side, sk, sk->body);
437 }
438
439 /* Sum an integer uprop across a given list of utypes. */
440 /* If side is NULL, then all sides should be considered. */
441
442 Obj *
sum_uprop(Side * side,Obj * form)443 sum_uprop(Side *side, Obj *form)
444 {
445 int u = NONUTYPE;
446 Obj *uprop = lispnil;
447 Side *side2 = NULL;
448 Unit *unit = NULL;
449 int i = 0, usepointval = FALSE, sum = 0;
450 char *upropname = NULL;
451 int (*intgetter)(int) = NULL;
452
453 /* Sanity Checks. */
454 assert_error(form, "Attempted to access a NULL GDL form");
455 if (lispnil == form) {
456 run_warning("Bad 'sum-uprop' form encountered.");
457 return lispnil;
458 }
459 /* Prep data structures. */
460 for_all_unit_types(u)
461 tmp_u_array[u] = FALSE;
462 /* Parse utype(s). */
463 fill_utype_array_from_lisp(tmp_u_array, car(form));
464 /* Retrieve uprop. */
465 if (lispnil == cdr(form)) {
466 run_warning("Bad 'sum-uprop' form encountered.");
467 return lispnil;
468 }
469 form = cdr(form);
470 uprop = car(form);
471 if (lispnil == uprop) {
472 run_warning("Bad 'sum-uprop' form encountered.");
473 return lispnil;
474 }
475 /*! \todo Generate optional sidemask. */
476 /*! \todo Handle other filters. */
477 /* Parse uprop. */
478 while (symbolp(uprop) && boundp(uprop))
479 uprop = eval_symbol(uprop);
480 if (symbolp(uprop))
481 /*! \todo Add a GC to GDL to handle memory leaks like the following. */
482 uprop = new_string(c_string(uprop));
483 if (stringp(uprop)) {
484 upropname = c_string(uprop);
485 /* Handle special cases. */
486 if (!strcmp(upropname, "point-value"))
487 usepointval = TRUE;
488 /* Handle general cases. */
489 else {
490 for (i = 0; utypedefns[i].name; ++i) {
491 if (!strcmp(upropname, utypedefns[i].name)) {
492 intgetter = utypedefns[i].intgetter;
493 if (!intgetter) {
494 run_warning(
495 "'sum-uprop' encountered non-integer uprop, \"%s\"", upropname);
496 return lispnil;
497 }
498 break;
499 }
500 }
501 if (!utypedefns[i].name) {
502 run_warning("No uprop matching name, \"%s\", found",
503 upropname);
504 return lispnil;
505 }
506 }
507 }
508 /* Sum it across relevant units. */
509 for_all_sides(side2) {
510 /*! \note For now, we only sum across the given side. */
511 if (side != side2)
512 continue;
513 for_all_side_units(side2, unit) {
514 /* Skip any unit type not in the utype array. */
515 if (!tmp_u_array[unit->type])
516 continue;
517 /* Handle special cases. */
518 if (usepointval)
519 sum += point_value(unit);
520 /* Handle general cases. */
521 else
522 sum += intgetter(unit->type);
523 }
524 }
525 /*! \todo Add a GC to GDL to handle memory leaks like the following. */
526 return new_number(sum);
527 }
528
529 /* Return the point value of a particular unit. */
530
531 int
point_value(Unit * unit)532 point_value(Unit *unit)
533 {
534 /* Incomplete units are always worthless. */
535 if (unit->cp > 0 && !completed(unit))
536 return 0;
537 if (unit_point_value(unit) >= 0)
538 return unit_point_value(unit);
539 return u_point_value(unit->type);
540 }
541
542 /* Return the sum of a side's units' point values. */
543
544 int
side_point_value(Side * side)545 side_point_value(Side *side)
546 {
547 Unit *unit;
548
549 if (!side->point_value_valid) {
550 side->point_value_cache = 0;
551 for_all_side_units(side, unit) {
552 /* Note that we want to count units that may appear in
553 the future. */
554 if ((in_play(unit) && completed(unit))
555 || (alive(unit) && unit->cp < 0)) {
556 side->point_value_cache += point_value(unit);
557 }
558 }
559 side->point_value_valid = TRUE;
560 }
561 return side->point_value_cache;
562 }
563
564 int
alliance_point_value(Side * side)565 alliance_point_value(Side *side)
566 {
567 int score = 0;
568 Side *side2;
569
570 for_all_sides(side2) {
571 if (side2->ingame
572 && (side2 == side || trusted_side(side2, side))) {
573 score += side_point_value(side2);
574 }
575 }
576 return score;
577 }
578
579 int
has_allies(Side * side)580 has_allies(Side *side)
581 {
582 Side *side2;
583
584 for_all_sides(side2) {
585 if (side2->ingame
586 && side2 != side
587 && trusted_side(side2, side)) {
588 return TRUE;
589 }
590 }
591 return FALSE;
592 }
593
594 /* Evaluate a given scorekeeper test form wrt to a given side and
595 scorekeeper. */
596 /* This function walks through the expression tree, and only expands things
597 related to scorekeeper tests. */
598
599 Obj *
eval_sk_test(Side * side,Scorekeeper * sk,Obj * form)600 eval_sk_test(Side *side, Scorekeeper *sk, Obj *form)
601 {
602 Obj *curobj = lispnil, *nextobj = lispnil, *newform = lispnil;
603 Obj *rest = lispnil;
604 enum keywords code;
605
606 /* Sanity checks. */
607 assert_error(sk, "No scorekeeper provided for scorekeeper test");
608 assert_error(form,
609 "No scorekeeper test expression provided for scorekeeper test");
610 /* If test form is nil, then return nil. */
611 if (lispnil == form)
612 return lispnil;
613 /* If test form is list... */
614 if (consp(form)) {
615 /* If action form has a test or action form keyword. */
616 curobj = car(form);
617 if (symbolp(curobj) && !boundp(curobj)) {
618 code = (enum keywords)keyword_code(c_string(curobj));
619 switch (code) {
620 /* Sum an uprop over a list of utypes. */
621 case K_SUM_UPROP:
622 return sum_uprop(side, cdr(form));
623 /*! \todo Add other interesting cases. */
624 default: break;
625 }
626 }
627 /* Iterate through test form in order. */
628 curobj = lispnil;
629 for_all_list(form, rest) {
630 nextobj = eval_sk_test(side, sk, car(rest));
631 nextobj = cons(nextobj, lispnil);
632 if (lispnil != curobj)
633 set_cdr(curobj, nextobj);
634 else
635 newform = nextobj;
636 curobj = nextobj;
637 }
638 }
639 /* If test form is symbol... */
640 else if (symbolp(form)) {
641 /* If symbol is bound, then substitute and re-test. */
642 if (boundp(form))
643 newform = eval_sk_test(side, sk, eval_symbol(form));
644 /* Else, try to substitute a scorekeeper keyword. */
645 code = (enum keywords)keyword_code(c_string(form));
646 switch (code) {
647 /* Current turn number. */
648 case K_TURN:
649 return new_number(g_turn());
650 /* Current score for given side and scorekeeper. */
651 case K_SCORE:
652 if (!sk->keepscore) {
653 run_warning(
654 "Attempted to use 'score' with non-numeric scorekeeper");
655 return lispnil;
656 }
657 /*! \todo Need GC so these memleaks don't accumulate. */
658 return new_number(side->scores[sk->scorenum]);
659 /*! \todo Add other interesting cases here. */
660 /* No matching keyword, so leave it be. */
661 default:
662 return form;
663 }
664 }
665 else
666 newform = form;
667 /* Evaluate new form and return. */
668 return eval(newform);
669 }
670
671 /* Evaluate a given scorekeeper action form wrt to a given side and
672 scorekeeper. */
673 /* This function walks through the expression tree, and only expands things
674 related to scorekeeper actions. */
675
676 Obj *
eval_sk_form(Side * side,Scorekeeper * sk,Obj * form)677 eval_sk_form(Side *side, Scorekeeper *sk, Obj *form)
678 {
679 Obj *curobj = lispnil, *nextobj = lispnil, *newform = lispnil;
680 Obj *clauses = lispnil, *clause = lispnil, *rest = lispnil;
681 enum keywords code;
682
683 /* Sanity checks. */
684 assert_error(sk, "No scorekeeper provided for scorekeeper body");
685 assert_error(form,
686 "No scorekeeper body expression provided for scorekeeper body");
687 /* If action form is nil, then return nil. */
688 if (lispnil == form)
689 return lispnil;
690 /* If action form is list... */
691 if (consp(form)) {
692 /* If action form has a test or action form keyword. */
693 curobj = car(form);
694 if (symbolp(curobj) && !boundp(curobj)) {
695 code = (enum keywords)keyword_code(c_string(curobj));
696 switch (code) {
697 /* Interpret Common Lisp 'if' form. */
698 case K_IF:
699 if (lispnil != eval_sk_test(side, sk, cadr(form)))
700 return eval_sk_form(side, sk, caddr(form));
701 else {
702 if (lispnil != cdr(cddr(form)))
703 return eval_sk_form(side, sk, cadr(cddr(form)));
704 }
705 return lispnil;
706 /* Interpret the traditional cond form. */
707 case K_COND:
708 for_all_list(cdr(form), clauses) {
709 clause = car(clauses);
710 if (lispnil !=
711 (newform = eval_sk_test(side, sk, car(clause)))) {
712 for_all_list(cdr(clause), rest)
713 newform = eval_sk_form(side, sk, car(rest));
714 return newform;
715 }
716 }
717 return lispnil;
718 case K_SET_SCORE:
719 if (!sk->keepscore) {
720 run_warning(
721 "Attempted to use 'set-score' with non-numeric scorekeeper");
722 return lispnil;
723 }
724 curobj = eval_sk_test(side, sk, cadr(form));
725 if (!numberp(curobj))
726 run_warning("Invalid 'set-score' form encountered");
727 else
728 side->scores[sk->scorenum] = c_number(curobj);
729 return curobj;
730 case K_ADD_SCORE:
731 if (!sk->keepscore) {
732 run_warning(
733 "Attempted to use 'add-score' with non-numeric scorekeeper");
734 return lispnil;
735 }
736 curobj = eval_sk_test(side, sk, cadr(form));
737 if (!numberp(curobj))
738 run_warning("Invalid 'add-score' form encountered");
739 else
740 side->scores[sk->scorenum] += c_number(curobj);
741 return curobj;
742 /*! \todo Add other interesting cases. */
743 default: break;
744 }
745 }
746 /* Iterate through action form in order. */
747 curobj = lispnil;
748 for_all_list(form, rest) {
749 nextobj = eval_sk_form(side, sk, car(rest));
750 nextobj = cons(nextobj, lispnil);
751 if (lispnil != curobj)
752 set_cdr(curobj, nextobj);
753 else
754 newform = nextobj;
755 curobj = nextobj;
756 }
757 }
758 /* If test form is symbol... */
759 else if (symbolp(form)) {
760 /* If symbol is bound, then substitute and re-eval. */
761 if (boundp(form))
762 newform = eval_sk_form(side, sk, eval_symbol(form));
763 /* Else, try to substitute a scorekeeper keyword. */
764 code = (enum keywords)keyword_code(c_string(form));
765 switch (code) {
766 case K_LAST_SIDE_WINS:
767 eval_sk_last_side_wins(sk);
768 break;
769 case K_LAST_ALLIANCE_WINS:
770 eval_sk_last_alliance_wins(sk);
771 break;
772 case K_WIN:
773 if (side->ingame && !only_checking)
774 side_wins(side, sk->id);
775 break;
776 case K_LOSE:
777 if (side->ingame && !only_checking)
778 side_loses(side, NULL, sk->id);
779 break;
780 case K_END:
781 if (!only_checking)
782 all_sides_draw();
783 break;
784 /*! \todo Add other interesting cases here. */
785 /* No matching keyword, so leave it be. */
786 default:
787 return form;
788 }
789 }
790 else
791 newform = form;
792 return eval(newform);
793 }
794
795 /* Test whether a single side is still alive in the game. */
796
797 static void
eval_sk_last_side_wins(Scorekeeper * sk)798 eval_sk_last_side_wins(Scorekeeper *sk)
799 {
800 Side *side2, *winner = NULL;
801 int numleft = 0, points;
802
803 /* This is only meaningful in games with at least two sides. */
804 if (num_sides_originally < 2)
805 return;
806 for_all_sides(side2) {
807 /* The independent side should not be counted among the sides
808 left (unless it has somebody running it), otherwise the
809 "but I already won!" player has to scour the map looking for
810 remaining inert independents. Even if someone is running the
811 indepside, it may be reasonable to exclude it from scorekeeping. */
812 if (inactive_indepside(side2))
813 continue;
814 /* Exclude any side that is not in the scorekeeper's sidemask. */
815 if (!side_in_set(side2, sk->whomask))
816 continue;
817 if (side2->ingame) {
818 points = side_point_value(side2);
819 Dprintf("%s has %d points worth of units\n",
820 side_desig(side2), points);
821 if (points == 0) {
822 if (!only_checking)
823 side_loses(side2, NULL, sk->id);
824 } else {
825 ++numleft;
826 /* Take note of the possible winner. */
827 winner = side2;
828 }
829 }
830 }
831 if (numleft == 1) {
832 we_have_a_winner = TRUE;
833 if (!only_checking)
834 side_wins(winner, sk->id);
835 }
836 }
837
838 /* Test whether a single alliance (a group of trusting sides) is all
839 that remains in the game. */
840
841 static void
eval_sk_last_alliance_wins(Scorekeeper * sk)842 eval_sk_last_alliance_wins(Scorekeeper *sk)
843 {
844 Side *side2, *side3, *winner = NULL;
845 int numleft = 0, sidepoints[MAXSIDES+1], alliancepoints[MAXSIDES+1];
846 int alliancewins;
847
848 /* This is only meaningful in games with at least two sides. */
849 if (num_sides_originally < 2)
850 return;
851 for_all_sides(side2) {
852 sidepoints[side2->id] = alliancepoints[side2->id] = 0;
853 if (side2->ingame) {
854 sidepoints[side2->id] = side_point_value(side2);
855 Dprintf("%s has %d points worth of units\n",
856 side_desig(side2), sidepoints[side2->id]);
857 }
858 }
859 /* Sum up to get points for each alliance. */
860 for_all_sides(side2) {
861 if (side2->ingame) {
862 for_all_sides(side3) {
863 if (side2 == side3 || trusted_side(side2, side3)) {
864 alliancepoints[side2->id] += sidepoints[side3->id];
865 }
866 }
867 }
868 }
869 /* Make all the sides belonging to point-less alliances lose now,
870 and look for a non-losing side. */
871 for_all_sides(side2) {
872 if (inactive_indepside(side2))
873 continue;
874 /* Exclude any side that is not in the scorekeeper's sidemask. */
875 if (!side_in_set(side2, sk->whomask))
876 continue;
877 if (side2->ingame) {
878 if (alliancepoints[side2->id] == 0) {
879 if (!only_checking)
880 side_loses(side2, NULL, sk->id);
881 } else {
882 ++numleft;
883 /* We still need to pick a single side, we'll take
884 care of all its buddies shortly. */
885 winner = side2;
886 }
887 }
888 }
889 /* It may happen that all sides lose (for instance if the last two
890 units die in a duel). */
891 if (numleft == 0)
892 return;
893 /* See if the non-losers all belong to a single alliance. */
894 alliancewins = TRUE;
895 for_all_sides(side2) {
896 if (inactive_indepside(side2))
897 continue;
898 /* Exclude any side that is not in the scorekeeper's sidemask. */
899 if (!side_in_set(side2, sk->whomask))
900 continue;
901 if (side2->ingame
902 && side2 != indepside
903 && side2 != winner
904 && !trusted_side(winner, side2)) {
905 alliancewins = FALSE;
906 break;
907 }
908 }
909 /* If so, everybody in the alliance wins equally. */
910 if (alliancewins) {
911 we_have_a_winner = TRUE;
912 for_all_sides(side2) {
913 if (inactive_indepside(side2))
914 continue;
915 /* Exclude any side that is not in the scorekeeper's sidemask. */
916 if (!side_in_set(side2, sk->whomask))
917 continue;
918 if (side2->ingame
919 && (side2 == winner || trusted_side(winner, side2))) {
920 if (!only_checking)
921 side_wins(side2, sk->id);
922 }
923 }
924 }
925 }
926
927 /* Implement the effects of a side winning. */
928
929 void
side_wins(Side * side,int why)930 side_wins(Side *side, int why)
931 {
932 /* Nothing happens to the side's units or people. */
933 side->status = 1;
934 remove_side_from_game(side);
935 /* Record the event after the side is removed, so we don't get infinite
936 recursion if there is an event-triggered scorekeeper. */
937 record_event(H_SIDE_WON, ALLSIDES, side_number(side), why);
938 }
939
940 /* Implement the effects of a side losing. */
941
942 void
side_loses(Side * side,Side * side2,int why)943 side_loses(Side *side, Side *side2, int why)
944 {
945 int x, y, s, s2, ux, uy, changed;
946 Unit *unit;
947 Side *side3;
948
949 /* We can't lose twice. */
950 if (!side->ingame)
951 return;
952 /* The independents cannot lose by definition even if they
953 are down to zero points (unless played by a human). */
954 if (side == indepside && !side_has_display(side))
955 return;
956 /* These should not happen, but a stupid AI might try, so just
957 and clear the mistaken side. */
958 if (side == side2) {
959 run_warning("losing to self, ignoring side");
960 side2 = NULL;
961 }
962 if (side2 != NULL && !side2->ingame) {
963 run_warning("losing to side not in game, ignoring side");
964 side2 = NULL;
965 }
966 /* If there's a controlling side, it gets everything. */
967 if (side->controlled_by != NULL && side->controlled_by->ingame) {
968 side2 = side->controlled_by;
969 }
970 if (side2 != NULL) {
971 /* Dispose of all of a side's units. */
972 for_all_units(unit) {
973 if (unit->side == side) {
974 if (in_play(unit)) {
975 ux = unit->x; uy = unit->y;
976 change_unit_side(unit, side2, H_SIDE_LOST, NULL);
977 /* Everybody gets to see this change. */
978 all_see_cell(ux, uy);
979 } else {
980 /* Even out-of-play units need to have their side set. */
981 set_unit_side(unit, side2);
982 }
983 }
984 }
985 /* The people also change sides. */
986 if (people_sides_defined() || control_sides_defined()) {
987 s = side_number(side);
988 s2 = side_number(side2);
989 for_all_cells(x, y) {
990 changed = FALSE;
991 if (people_sides_defined() && people_side_at(x, y) == s) {
992 set_people_side_at(x, y, s2);
993 changed = TRUE;
994 }
995 if (control_sides_defined() && control_side_at(x, y) == s) {
996 set_control_side_at(x, y, s2);
997 changed = TRUE;
998 }
999 if (changed)
1000 all_see_cell(x, y);
1001 }
1002 }
1003 /* Reset view coverage everywhere. */
1004 if (!side->see_all) {
1005 for_all_cells(x, y) {
1006 set_cover(side, x, y, 0);
1007 }
1008 }
1009 }
1010
1011 /* Advanced units (cities) should not disappear - make them independent. */
1012 else if (indepside->ingame) {
1013 for_all_units(unit) {
1014 if (unit->side == side
1015 && u_advanced(unit->type)) {
1016 if (in_play(unit)) {
1017 ux = unit->x;
1018 uy = unit->y;
1019 change_unit_side(unit, indepside, H_SIDE_LOST, NULL);
1020 /* Everybody gets to see this change. */
1021 all_see_cell(ux, uy);
1022 } else {
1023 /* Even out-of-play units need to have their side set. */
1024 set_unit_side(unit, indepside);
1025 }
1026 }
1027 }
1028 }
1029
1030 /* Add the mark of shame itself. */
1031 side->status = -1;
1032 remove_side_from_game(side);
1033 /* Record the event after the side is removed, so we don't get infinite
1034 recursion if there is an event-triggered scorekeeper. */
1035 record_event(H_SIDE_LOST, ALLSIDES, side_number(side), why);
1036 /* As a special case, look at post-turn scorekeepers to see if
1037 sides should end their turns early because the game is over. */
1038 only_checking = TRUE;
1039 check_post_turn_scores();
1040 only_checking = FALSE;
1041 if (we_have_a_winner) {
1042 for_all_sides(side3) {
1043 if (side3->ingame) {
1044 finish_turn(side3);
1045 }
1046 }
1047 }
1048 /* When the remaining sides' turns are finished, the end-of-turn
1049 processing will commence, and it includes the real run of
1050 post-turn scorekeeper checking. */
1051 }
1052
1053 /* Implement a draw. Note that unlike winning or losing, the draw
1054 applies to all sides currently in the game. */
1055
1056 void
all_sides_draw(void)1057 all_sides_draw(void)
1058 {
1059 SideMask drew;
1060 Side *side;
1061
1062 drew = NOSIDES;
1063 for_all_sides(side) {
1064 if (side->ingame) {
1065 side->status = 0;
1066 remove_side_from_game(side);
1067 drew = add_side_to_set(side, drew);
1068 }
1069 }
1070 /* Now that all sides are out, safe to record events to that effect
1071 (no possibility of triggering post-event scorekeepers). */
1072 for_all_sides(side) {
1073 if (side_in_set(side, drew)) {
1074 record_event(H_SIDE_WITHDREW, ALLSIDES, side_number(side));
1075 }
1076 }
1077 }
1078
1079 /* (should move this to nlang.c?) */
1080 static char *
basic_player_name(Player * player)1081 basic_player_name(Player *player)
1082 {
1083 char *playername;
1084
1085 playername = "";
1086 if (player) {
1087 if (!empty_string(player->name))
1088 playername = player->name;
1089 else if (!empty_string(player->displayname))
1090 playername = player->displayname;
1091 else if (!empty_string(player->aitypename))
1092 playername = player->aitypename;
1093 }
1094 return playername;
1095 }
1096
1097 /* Record the outcome of the game into the scorefile. */
1098
1099 void
record_into_scorefile(void)1100 record_into_scorefile(void)
1101 {
1102 int i;
1103 int any_advantage_variation = FALSE, adv, adv2, advantage;
1104 char *filename, *mversion, *playername, *varname;
1105 Variant *variants, *var;
1106 FILE *fp;
1107 Side *side;
1108
1109 /* (should make following code into a separate routine) */
1110 adv = -1;
1111 for_all_sides(side) {
1112 adv2 = actual_advantage(side);
1113 if (adv < 1)
1114 adv = adv2;
1115 if (adv != adv2) {
1116 any_advantage_variation = TRUE;
1117 break;
1118 }
1119 }
1120 filename = SCOREFILE;
1121 if (!empty_string(g_scorefile_name()))
1122 filename = g_scorefile_name();
1123 fp = open_scorefile_for_writing(filename);
1124 if (fp == NULL) {
1125 run_warning("%s cannot be opened for writing, will not record score",
1126 filename);
1127 /* (should provide some sort of retry here) */
1128 } else {
1129 fprintf(fp, "(g %s",
1130 /* (should record this for comparison when displaying scores) */
1131 escaped_symbol((mainmodule->origmodulename
1132 ? mainmodule->origmodulename
1133 : mainmodule->name)));
1134 /* Record the module's version if defined. */
1135 mversion = (mainmodule->origversion
1136 ? mainmodule->origversion
1137 : mainmodule->version);
1138 if (!empty_string(mversion))
1139 fprintf(fp, " (ve \"%s\")", mversion);
1140 /* Record all the choices of variant. */
1141 variants = (mainmodule->origvariants
1142 ? mainmodule->origvariants
1143 : mainmodule->variants);
1144 if (variants) {
1145 fprintf(fp, " (v");
1146 for (i = 0; variants[i].id != lispnil; ++i) {
1147 var = &(variants[i]);
1148 varname = c_string(var->id);
1149 fprintf(fp, " (");
1150 /* Encode the common variants by number, for
1151 compactness. Note that once assigned, these
1152 numbers can never change. */
1153 switch (keyword_code(varname)) {
1154 case K_WORLD_SEEN:
1155 fprintf(fp, "1 %d", var->intvalue);
1156 break;
1157 case K_SEE_ALL:
1158 fprintf(fp, "2 %d", var->intvalue);
1159 break;
1160 case K_SEQUENTIAL:
1161 fprintf(fp, "3 %d", var->intvalue);
1162 break;
1163 case K_PEOPLE:
1164 fprintf(fp, "4 %d", var->intvalue);
1165 break;
1166 case K_ECONOMY:
1167 fprintf(fp, "5 %d", var->intvalue);
1168 break;
1169 case K_SUPPLY:
1170 fprintf(fp, "6 %d", var->intvalue);
1171 break;
1172 case K_WORLD_SIZE:
1173 fprintf(fp, "11 %d %d %d",
1174 area.width, area.height, world.circumference);
1175 break;
1176 case K_REAL_TIME:
1177 fprintf(fp, "12 %d %d %d",
1178 g_rt_for_game(),
1179 g_rt_per_side(),
1180 g_rt_per_turn());
1181 break;
1182 default:
1183 fprintf(fp, "%s", escaped_symbol(varname));
1184 if (var->hasintvalue) {
1185 fprintf(fp, " %d", var->intvalue);
1186 }
1187 /* Variants with unknown types of data end up getting
1188 recorded as just "(<var-name>)", which is OK. */
1189 }
1190 fprintf(fp, ")");
1191 }
1192 fprintf(fp, ")");
1193 }
1194 fprintf(fp, " (t %d)", g_turn());
1195 /* End of the first line of a score record. */
1196 fprintf(fp, "\n");
1197 /* Record all the participants and how they fared. */
1198 fprintf(fp, " (s");
1199 for_all_sides(side) {
1200 /* Only record info about sides that actually participated. */
1201 if (side->everingame) {
1202 playername = basic_player_name(side->player);
1203 /* If a side has no player, don't record it. */
1204 if (empty_string(playername))
1205 continue;
1206 fprintf(fp, " (");
1207 fprintf(fp, "%s", escaped_symbol(playername));
1208 fprintf(fp, " %s",
1209 (side_won(side)
1210 ? "won"
1211 : (side_lost(side)
1212 ? "lost"
1213 : "drew")));
1214 /* (should write info about ai helping human players) */
1215 if (any_advantage_variation) {
1216 advantage = actual_advantage(side);
1217 if (advantage > 1)
1218 fprintf(fp, " (a %d)", advantage);
1219 }
1220 if (numscores > 0) {
1221 fprintf(fp, " (sc");
1222 for (i = 0; i < numscores; ++i) {
1223 fprintf(fp, " %d", side->scores[i]);
1224 }
1225 fprintf(fp, ")");
1226 }
1227 fprintf(fp, ")");
1228 }
1229 }
1230 fprintf(fp, ")");
1231 /* (should record other useful info about game, such as
1232 date(s) played) */
1233 fprintf(fp, ")\n");
1234 close_scorefile_for_writing(fp);
1235 }
1236 }
1237
1238 static int
read_scorefile(void)1239 read_scorefile(void)
1240 {
1241 int startlineno = 1, endlineno = 1;
1242 int numrecs;
1243 char *filename;
1244 Obj *form;
1245 FILE *fp;
1246 ScoreRecord *sr;
1247
1248 filename = SCOREFILE;
1249 if (!empty_string(g_scorefile_name()))
1250 filename = g_scorefile_name();
1251 fp = open_scorefile_for_reading(filename);
1252 if ((NULL == fp) && !strcmp(filename, SCOREFILE))
1253 fp = open_scorefile_for_reading(OLD_SCOREFILE);
1254 if (fp != NULL) {
1255 /* Note that we clear all existing score records. This is because
1256 the score file may be shared and thus getting multiple updates. */
1257 /* (should free any existing records) */
1258 records = NULL;
1259 numrecs = 0;
1260 /* Read everything in the file. */
1261 while ((form = read_form(fp, &startlineno, &endlineno)) != lispeof) {
1262 if (interp_score_record(form)) {
1263 ++numrecs;
1264 }
1265 }
1266 fclose(fp);
1267 Dprintf("%d score records read.\n", numrecs);
1268 for_all_score_records(sr) {
1269 Dprintf("%s\n", sr->gamename);
1270 }
1271 return TRUE;
1272 }
1273 return FALSE;
1274 }
1275
1276 static int
interp_score_record(Obj * form)1277 interp_score_record(Obj *form)
1278 {
1279 char *propname;
1280 Obj *props, *prop;
1281 ScoreRecord *sr;
1282
1283 if (consp(form)
1284 && symbolp(car(form))
1285 && strcmp(c_string(car(form)), "g") == 0
1286 && (stringp(cadr(form)) || symbolp(cadr(form)))) {
1287 sr = (ScoreRecord *) xmalloc(sizeof(ScoreRecord));
1288 sr->gamename = c_string(cadr(form));
1289 sr->sides = lispnil;
1290 sr->varsets = lispnil;
1291 for_all_list(cddr(form), props) {
1292 prop = car(props);
1293 if (symbolp(car(prop))) {
1294 propname = c_string(car(prop));
1295 if (strcmp(propname, "s") == 0) {
1296 sr->sides = cdr(prop);
1297 } else if (strcmp(propname, "t") == 0) {
1298 sr->numturns = c_number(cadr(prop));
1299 } else if (strcmp(propname, "v") == 0) {
1300 sr->varsets = cdr(prop);
1301 } else if (strcmp(propname, "ve") == 0) {
1302 /* (should do something with module version) */
1303 } else {
1304 run_warning("Score record prop name `%s' not recognized, ignoring",
1305 propname);
1306 }
1307 }
1308 }
1309 sr->raw = form;
1310 /* Add the record to the beginning of the list. This is so displays list
1311 the most recent game first. */
1312 sr->next = records;
1313 records = sr;
1314 return TRUE;
1315 } else {
1316 run_warning("Garbage in scorefile, ignoring");
1317 }
1318 return FALSE;
1319 }
1320
1321 /* Collect scorefile contents and format them. This routine starts from
1322 a fresh set of records each time, and allocates a new formatting
1323 buffer each time, so should be called sparingly. */
1324
1325 char *
get_scores(Side * side)1326 get_scores(Side *side)
1327 {
1328 int wins, losses, draws, plays, allplays;
1329 char *buf, sdadvbuf[20], varbuf[BUFSIZE];
1330 char *thisgame, *thisgametitle, *playername, *sdnamestr, *sdfatestr;
1331 Obj *sds, *sd, *sdname, *sdfate, *more, *more1;
1332 ScoreRecord *sr;
1333
1334 thisgame = (mainmodule->origmodulename
1335 ? mainmodule->origmodulename
1336 : mainmodule->name);
1337 if (thisgame == NULL)
1338 return "???";
1339 read_scorefile();
1340 if (records == NULL)
1341 return "No scores available.\n";
1342 buf = (char *)xmalloc(5000);
1343 thisgametitle = thisgame;
1344 if (!empty_string(mainmodule->title))
1345 thisgametitle = mainmodule->title;
1346 sprintf(buf, "Scores for %s", thisgametitle);
1347 strcat(buf, ":\n");
1348 playername = NULL;
1349 if (side != NULL) {
1350 playername = basic_player_name(side->player);
1351 }
1352 if (1 /* summarize */) {
1353 wins = losses = draws = plays = 0;
1354 allplays = 0;
1355 for_all_score_records(sr) {
1356 if (!empty_string(sr->gamename)
1357 && strcmp(thisgame, sr->gamename) == 0) {
1358 if (playername != NULL) {
1359 /* Scan all the sides listed as having played in
1360 the game. */
1361 for_all_list(sr->sides, sds) {
1362 sd = car(sds);
1363 if (numberp(car(sd)))
1364 sd = cdr(sd);
1365 sdname = car(sd);
1366 sdnamestr = ((symbolp(sdname) || stringp(sdname))
1367 ? c_string(sdname) : (char *)"");
1368 sdfate = cadr(sd);
1369 sdfatestr = (symbolp(sdfate) ? c_string(sdfate) : (char *)"");
1370 if (sdnamestr != NULL
1371 && strcmp(playername, sdnamestr) == 0) {
1372 if (strcmp("won", sdfatestr) == 0) {
1373 ++wins;
1374 } else if (strcmp("lost", sdfatestr) == 0) {
1375 ++losses;
1376 } else if (strcmp("drew", sdfatestr) == 0) {
1377 ++draws;
1378 }
1379 ++plays;
1380 /* Only count the first appearance of the
1381 player in the list; multiple
1382 appearances can occur, but are likely
1383 to be test games (two players with same
1384 username open on the same X screen, for
1385 instance) */
1386 break;
1387 }
1388 /* (should study scores also?) */
1389 }
1390 }
1391 ++allplays;
1392 }
1393 }
1394 /* (should go to nlang.c) */
1395 tprintf(buf, "You (%s) won %d, lost %d, and drew %d of %d game%s played.\n",
1396 playername, wins, losses, draws, plays, (plays == 1 ? "" : "s"));
1397 if (allplays != plays)
1398 tprintf(buf, "Altogether, this game has been played %d time%s.\n",
1399 allplays, (allplays == 1 ? "" : "s"));
1400 tprintf(buf, "\n\n");
1401 }
1402 /* List all the games explicitly. */
1403 if (1 /* complete listing */) {
1404 tprintf(buf, "Listing of games played:\n");
1405 for_all_score_records(sr) {
1406 if (!empty_string(sr->gamename)
1407 && strcmp(thisgame, sr->gamename) == 0) {
1408 if (playername == NULL
1409 || 1 /* matching playername */) {
1410 for_all_list(sr->sides, sds) {
1411 sd = car(sds);
1412 if (numberp(car(sd)))
1413 sd = cdr(sd);
1414 sdname = car(sd);
1415 sdnamestr = ((symbolp(sdname) || stringp(sdname))
1416 ? c_string(sdname) : (char *)"?");
1417 sdfate = cadr(sd);
1418 sdfatestr = (symbolp(sdfate) ? c_string(sdfate) : (char *)"?");
1419 strcpy(sdadvbuf, "");
1420 for_all_list(cddr(sd), more) {
1421 more1 = car(more);
1422 Dprintf("entry is");
1423 Dprintlisp(more1);
1424 Dprintf("\n");
1425 if (consp(more1)
1426 && symbolp(car(more1))
1427 && strcmp(c_string(car(more1)), "a") == 0
1428 && numberp(cadr(more1))) {
1429 sprintf(sdadvbuf, " +%d",
1430 c_number(cadr(more1)));
1431 break;
1432 }
1433 }
1434 if (sds != sr->sides)
1435 tprintf(buf, ", ");
1436 tprintf(buf, "%s%s %s",
1437 (!empty_string(sdnamestr)
1438 ? sdnamestr : "(no player)"),
1439 sdadvbuf, sdfatestr);
1440 }
1441 if (sr->numturns > 0)
1442 tprintf(buf, " (in %d turn%s)",
1443 sr->numturns, (sr->numturns != 1 ? "s" : ""));
1444 score_variant_desc(sr, varbuf);
1445 if (!empty_string(varbuf)) {
1446 strcat(buf, " (variants");
1447 strcat(buf, varbuf);
1448 strcat(buf, ")");
1449 }
1450 tprintf(buf, "\n");
1451 if (strlen(buf) > 4500)
1452 break;
1453 }
1454 }
1455 }
1456 }
1457 return buf;
1458 }
1459
1460 /* Given a score record, report any variants that were specified and
1461 different from the current module's defaults. */
1462
1463 static void
score_variant_desc(ScoreRecord * sr,char * varbuf)1464 score_variant_desc(ScoreRecord *sr, char *varbuf)
1465 {
1466 int i, hasdflt, dfltval, val;
1467 Obj *restvarset, *varset, *varsetname, *rslt;
1468 Variant *variants, *tmpvar, *var;
1469
1470 varbuf[0] = '\0';
1471 if (sr->varsets != lispnil) {
1472 variants = (mainmodule->origvariants
1473 ? mainmodule->origvariants
1474 : mainmodule->variants);
1475 for_all_list(sr->varsets, restvarset) {
1476 varset = car(restvarset);
1477 if (consp(varset)) {
1478 if (numberp(car(varset))) {
1479 /* Get the names of the standard variants from
1480 their numbers. */
1481 switch (c_number(car(varset))) {
1482 case 1:
1483 varsetname = intern_symbol(keyword_name(K_WORLD_SEEN));
1484 break;
1485 case 2:
1486 varsetname = intern_symbol(keyword_name(K_SEE_ALL));
1487 break;
1488 case 3:
1489 varsetname = intern_symbol(keyword_name(K_SEQUENTIAL));
1490 break;
1491 case 4:
1492 varsetname = intern_symbol(keyword_name(K_PEOPLE));
1493 break;
1494 case 5:
1495 varsetname = intern_symbol(keyword_name(K_ECONOMY));
1496 break;
1497 case 6:
1498 varsetname = intern_symbol(keyword_name(K_SUPPLY));
1499 break;
1500 case 11:
1501 varsetname = intern_symbol(keyword_name(K_WORLD_SIZE));
1502 break;
1503 case 12:
1504 varsetname = intern_symbol(keyword_name(K_REAL_TIME));
1505 break;
1506 default:
1507 break;
1508 }
1509 } else {
1510 varsetname = car(varset);
1511 }
1512 var = NULL;
1513 if (variants != NULL) {
1514 for (i = 0; variants[i].id != lispnil; ++i) {
1515 tmpvar = &(variants[i]);
1516 if (tmpvar->id == varsetname) {
1517 var = tmpvar;
1518 break;
1519 }
1520 }
1521 }
1522 hasdflt = dfltval = FALSE;
1523 /* Don't complain if variant not found, might have been
1524 removed from current module. */
1525 if (var != NULL) {
1526 rslt = eval(var->dflt);
1527 if (numberp(rslt))
1528 dfltval = c_number(rslt);
1529 hasdflt = TRUE;
1530 }
1531 val = 54321;
1532 /* The numeric value is the second element in the variant
1533 setting. */
1534 if (numberp(cadr(varset)) && cddr(varset) == lispnil)
1535 val = c_number(cadr(varset));
1536 /* Handle variants with several values. */
1537 if (keyword_code(c_string(varsetname)) == K_WORLD_SIZE) {
1538 if (!hasdflt || !equal(rslt, cdr(varset)))
1539 tprintf(varbuf, " %s=%dx%dW%d", c_string(varsetname),
1540 c_number(cadr(varset)), c_number(caddr(varset)),
1541 c_number(cadr(cddr(varset))));
1542 } else if (keyword_code(c_string(varsetname)) == K_REAL_TIME) {
1543 if (!hasdflt || !equal(rslt, cdr(varset)))
1544 tprintf(varbuf, " %s=%d,%d,%d", c_string(varsetname),
1545 c_number(cadr(varset)), c_number(caddr(varset)),
1546 c_number(cadr(cddr(varset))));
1547 } else {
1548 if (!hasdflt || val != dfltval)
1549 tprintf(varbuf, " %s=%d", c_string(varsetname), val);
1550 }
1551 }
1552 }
1553 }
1554 }
1555
1556