xref: /netbsd/games/rogue/score.c (revision 70862b7a)
1 /*	$NetBSD: score.c,v 1.16 2011/08/26 06:18:17 dholland Exp $	*/
2 
3 /*
4  * Copyright (c) 1988, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Timothy C. Stoehr.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 #ifndef lint
37 #if 0
38 static char sccsid[] = "@(#)score.c	8.1 (Berkeley) 5/31/93";
39 #else
40 __RCSID("$NetBSD: score.c,v 1.16 2011/08/26 06:18:17 dholland Exp $");
41 #endif
42 #endif /* not lint */
43 
44 /*
45  * score.c
46  *
47  * This source herein may be modified and/or distributed by anybody who
48  * so desires, with the following restrictions:
49  *    1.)  No portion of this notice shall be removed.
50  *    2.)  Credit shall not be taken for the creation of this source.
51  *    3.)  This code is not to be traded, sold, or used for personal
52  *         gain or profit.
53  *
54  */
55 
56 #include <stdio.h>
57 #include "rogue.h"
58 #include "pathnames.h"
59 
60 static void center(short, const char *);
61 static int get_value(const object *);
62 static void id_all(void);
63 static void sell_pack(void);
64 static void sf_error(void) __dead;
65 
66 void
killed_by(const object * monster,short other)67 killed_by(const object *monster, short other)
68 {
69 	const char *mechanism = "killed by something unknown (?)";
70 	char mechanism_buf[128];
71 	const char *article;
72 	char message_buf[128];
73 
74 	md_ignore_signals();
75 
76 	if (other != QUIT) {
77 		rogue.gold = ((rogue.gold * 9) / 10);
78 	}
79 
80 	if (other) {
81 		switch(other) {
82 		case HYPOTHERMIA:
83 			mechanism = "died of hypothermia";
84 			break;
85 		case STARVATION:
86 			mechanism = "died of starvation";
87 			break;
88 		case POISON_DART:
89 			mechanism = "killed by a dart";
90 			break;
91 		case QUIT:
92 			mechanism = "quit";
93 			break;
94 		case KFIRE:
95 			mechanism = "killed by fire";
96 			break;
97 		}
98 	} else {
99 		if (is_vowel(m_names[monster->m_char - 'A'][0])) {
100 			article = "an";
101 		} else {
102 			article = "a";
103 		}
104 		snprintf(mechanism_buf, sizeof(mechanism_buf),
105 			"Killed by %s %s",
106 			 article, m_names[monster->m_char - 'A']);
107 		mechanism = mechanism_buf;
108 	}
109 	snprintf(message_buf, sizeof(message_buf),
110 		 "%s with %ld gold", mechanism, rogue.gold);
111 
112 	if ((!other) && (!no_skull)) {
113 		clear();
114 		mvaddstr(4, 32, "__---------__");
115 		mvaddstr(5, 30, "_~             ~_");
116 		mvaddstr(6, 29, "/                 \\");
117 		mvaddstr(7, 28, "~                   ~");
118 		mvaddstr(8, 27, "/                     \\");
119 		mvaddstr(9, 27, "|    XXXX     XXXX    |");
120 		mvaddstr(10, 27, "|    XXXX     XXXX    |");
121 		mvaddstr(11, 27, "|    XXX       XXX    |");
122 		mvaddstr(12, 28, "\\         @         /");
123 		mvaddstr(13, 29, "--\\     @@@     /--");
124 		mvaddstr(14, 30, "| |    @@@    | |");
125 		mvaddstr(15, 30, "| |           | |");
126 		mvaddstr(16, 30, "| vvVvvvvvvvVvv |");
127 		mvaddstr(17, 30, "|  ^^^^^^^^^^^  |");
128 		mvaddstr(18, 31, "\\_           _/");
129 		mvaddstr(19, 33, "~---------~");
130 		center(21, nick_name);
131 		center(22, message_buf);
132 	} else {
133 		messagef(0, "%s", message_buf);
134 	}
135 	messagef(0, "%s", "");		/* gcc objects to just "" */
136 	put_scores(monster, other);
137 }
138 
139 void
win(void)140 win(void)
141 {
142 	unwield(rogue.weapon);		/* disarm and relax */
143 	unwear(rogue.armor);
144 	un_put_on(rogue.left_ring);
145 	un_put_on(rogue.right_ring);
146 
147 	clear();
148 	mvaddstr(10, 11, "@   @  @@@   @   @      @  @  @   @@@   @   @   @");
149 	mvaddstr(11, 11, " @ @  @   @  @   @      @  @  @  @   @  @@  @   @");
150 	mvaddstr(12, 11, "  @   @   @  @   @      @  @  @  @   @  @ @ @   @");
151 	mvaddstr(13, 11, "  @   @   @  @   @      @  @  @  @   @  @  @@");
152 	mvaddstr(14, 11, "  @    @@@    @@@        @@ @@    @@@   @   @   @");
153 	mvaddstr(17, 11, "Congratulations,  you have  been admitted  to  the");
154 	mvaddstr(18, 11, "Fighters' Guild.   You return home,  sell all your");
155 	mvaddstr(19, 11, "treasures at great profit and retire into comfort.");
156 	messagef(0, "%s", "");		/* gcc objects to just "" */
157 	messagef(0, "%s", "");		/* gcc objects to just "" */
158 	id_all();
159 	sell_pack();
160 	put_scores(NULL, WIN);
161 }
162 
163 void
quit(boolean from_intrpt)164 quit(boolean from_intrpt)
165 {
166 	char buf[DCOLS];
167 	short i, orow, ocol;
168 	boolean mc;
169 
170 	orow = ocol = 0;
171 	mc = FALSE;
172 	md_ignore_signals();
173 
174 	if (from_intrpt) {
175 		orow = rogue.row;
176 		ocol = rogue.col;
177 
178 		mc = msg_cleared;
179 
180 		for (i = 0; i < DCOLS; i++) {
181 			buf[i] = mvinch(0, i);
182 		}
183 	}
184 	check_message();
185 	messagef(1, "really quit?");
186 	if (rgetchar() != 'y') {
187 		md_heed_signals();
188 		check_message();
189 		if (from_intrpt) {
190 			for (i = 0; i < DCOLS; i++) {
191 				mvaddch(0, i, buf[i]);
192 			}
193 			msg_cleared = mc;
194 			move(orow, ocol);
195 			refresh();
196 		}
197 		return;
198 	}
199 	if (from_intrpt) {
200 		clean_up(byebye_string);
201 	}
202 	check_message();
203 	killed_by(NULL, QUIT);
204 }
205 
206 /*
207  * The score file on disk is up to ten entries of the form
208  *      score block [80 bytes]
209  *      nickname block [30 bytes]
210  *
211  * The score block is to be parsed as follows:
212  *      bytes 0-1	Rank (" 1" to "10")
213  *      bytes 2-4	space padding
214  *      bytes 5-15	Score/gold
215  *      byte 15 up to a ':'	Login name
216  *      past the ':'    Death mechanism
217  *
218  * The nickname block is an alternate name to be printed in place of the
219  * login name. Both blocks are supposed to contain a null-terminator.
220  */
221 
222 struct score_entry {
223 	long gold;
224 	char username[80];
225 	char death[80];
226 	char nickname[30];
227 };
228 
229 #define NUM_SCORE_ENTRIES 10
230 
231 static void make_score(struct score_entry *, const object *, int);
232 
233 static
234 void
pad_spaces(char * str,size_t len)235 pad_spaces(char *str, size_t len)
236 {
237 	size_t x;
238 	for (x=strlen(str); x<len-1; x++) {
239 		str[x] = ' ';
240 	}
241 	str[len-1] = 0;
242 }
243 
244 static
245 void
unpad_spaces(char * str)246 unpad_spaces(char *str)
247 {
248 	size_t x;
249 	for (x=strlen(str); x>0 && str[x-1]==' '; x--);
250 	str[x] = 0;
251 }
252 
253 static
254 int
read_score_entry(struct score_entry * se,FILE * fp)255 read_score_entry(struct score_entry *se, FILE *fp)
256 {
257 	char score_block[80];
258 	char nickname_block[30];
259 	size_t n, x;
260 
261 	n = fread(score_block, 1, sizeof(score_block), fp);
262 	if (n==0) {
263 		/* EOF */
264 		return 0;
265 	}
266 	if (n != sizeof(score_block)) {
267 		sf_error();
268 	}
269 
270 	n = fread(nickname_block, 1, sizeof(nickname_block), fp);
271 	if (n != sizeof(nickname_block)) {
272 		sf_error();
273 	}
274 
275 	xxxx(score_block, sizeof(score_block));
276 	xxxx(nickname_block, sizeof(nickname_block));
277 
278 	/* Ensure null termination */
279 	score_block[sizeof(score_block)-1] = 0;
280 	nickname_block[sizeof(nickname_block)-1] = 0;
281 
282 	/* If there are other nulls in the score block, file is corrupt */
283 	if (strlen(score_block)!=sizeof(score_block)-1) {
284 		sf_error();
285 	}
286 	/* but this is NOT true of the nickname block */
287 
288 	/* quash trailing spaces */
289 	unpad_spaces(score_block);
290 	unpad_spaces(nickname_block);
291 
292 	for (x=5; score_block[x] == ' '; x++);
293 	se->gold = lget_number(score_block+x);
294 
295 	for (x=15; score_block[x] != 0 && score_block[x] != ':'; x++);
296 	if (score_block[x] == 0) {
297 		sf_error();
298 	}
299 	score_block[x++] = 0;
300 	strlcpy(se->username, score_block+15, sizeof(se->username));
301 
302 	strlcpy(se->death, score_block+x, sizeof(se->death));
303 	strlcpy(se->nickname, nickname_block, sizeof(se->nickname));
304 
305 	return 1;
306 }
307 
308 static
309 void
write_score_entry(const struct score_entry * se,int rank,FILE * fp)310 write_score_entry(const struct score_entry *se, int rank, FILE *fp)
311 {
312 	char score_block[80];
313 	char nickname_block[30];
314 
315 	/* avoid writing crap to score file */
316 	memset(score_block, 0, sizeof(score_block));
317 	memset(nickname_block, 0, sizeof(nickname_block));
318 
319 	snprintf(score_block, sizeof(score_block),
320 		 "%2d    %6ld   %s: %s",
321 		 rank+1, se->gold, se->username, se->death);
322 	strlcpy(nickname_block, se->nickname, sizeof(nickname_block));
323 
324 	/* pad blocks out with spaces */
325 	pad_spaces(score_block, sizeof(score_block));
326 	/*pad_spaces(nickname_block, sizeof(nickname_block)); -- wrong! */
327 
328 	xxxx(score_block, sizeof(score_block));
329 	xxxx(nickname_block, sizeof(nickname_block));
330 
331 	fwrite(score_block, 1, sizeof(score_block), fp);
332 	fwrite(nickname_block, 1, sizeof(nickname_block), fp);
333 }
334 
335 void
put_scores(const object * monster,short other)336 put_scores(const object *monster, short other)
337 {
338 	short i, rank=-1, found_player = -1, numscores = 0;
339 	struct score_entry scores[NUM_SCORE_ENTRIES];
340 	const char *name;
341 	FILE *fp;
342 	boolean dopause = score_only;
343 
344 	md_lock(1);
345 
346 	setegid(egid);
347 	if ((fp = fopen(_PATH_SCOREFILE, "r+")) == NULL &&
348 	    (fp = fopen(_PATH_SCOREFILE, "w+")) == NULL) {
349 		setegid(gid);
350 		messagef(0, "cannot read/write/create score file");
351 		sf_error();
352 	}
353 	setegid(gid);
354 	rewind(fp);
355 	(void)xxx(1);
356 
357 	for (numscores = 0; numscores < NUM_SCORE_ENTRIES; numscores++) {
358 		if (read_score_entry(&scores[numscores], fp) == 0) {
359 			break;
360 		}
361 	}
362 
363 	/* Search the score list. */
364 	for (i=0; i<numscores; i++) {
365 		if (!strcmp(scores[i].username, login_name)) {
366 			/* found our score */
367 			if (rogue.gold < scores[i].gold) {
368 				/* we didn't do as well as last time */
369 				score_only = 1;
370 			} else {
371 				/* we did better; mark entry for removal */
372 				found_player = i;
373 			}
374 			break;
375 		}
376 	}
377 
378 	/* Remove a superseded entry, if any. */
379 	if (found_player != -1) {
380 		numscores--;
381 		for (i = found_player; i < numscores; i++) {
382 			scores[i] = scores[i+1];
383 		}
384 	}
385 
386 	/* If we're going to insert ourselves, do it now */
387 	if (!score_only) {
388 
389 		/* if we aren't better than anyone, add at end. */
390 		rank = numscores;
391 
392 		/* Otherwise, find our slot. */
393 		for (i = 0; i < numscores; i++) {
394 			if (rogue.gold >= scores[i].gold) {
395 				rank = i;
396 				break;
397 			}
398 		}
399 
400 		if (rank < NUM_SCORE_ENTRIES) {
401 			/* Open up a slot */
402 			for (i = numscores; i > rank; i--) {
403 				scores[i] = scores[i-1];
404 			}
405 			numscores++;
406 
407 			/* Put our info in the slot */
408 			make_score(&scores[rank], monster, other);
409 		}
410 
411 		/* Now rewrite the score file */
412 
413 		md_ignore_signals();
414 		rewind(fp);
415 		(void)xxx(1);
416 
417 		for (i = 0; i < numscores; i++) {
418 			write_score_entry(&scores[i], i, fp);
419 		}
420 	}
421 	md_lock(0);
422 	fclose(fp);
423 
424 	/* Display the scores */
425 
426 	clear();
427 	mvaddstr(3, 30, "Top  Ten  Rogueists");
428 	mvaddstr(8, 0, "Rank   Score   Name");
429 
430 	for (i = 0; i < numscores; i++) {
431 		if (i == rank) {
432 			standout();
433 		}
434 
435 		if (scores[i].nickname[0]) {
436 			name = scores[i].nickname;
437 		} else {
438 			name = scores[i].username;
439 		}
440 
441 		mvprintw(i+10, 0, "%2d    %6ld   %s: %s",
442 			 i+1, scores[i].gold, name, scores[i].death);
443 
444 		if (i == rank) {
445 			standend();
446 		}
447 	}
448 	refresh();
449 	messagef(0, "%s", "");		/* gcc objects to just "" */
450 	if (dopause) {
451 		messagef(0, "%s", "");
452 	}
453 	clean_up("");
454 }
455 
456 static
457 void
make_score(struct score_entry * se,const object * monster,int other)458 make_score(struct score_entry *se, const object *monster, int other)
459 {
460 	const char *death = "bolts from the blue (?)";
461 	const char *hasamulet;
462 	char deathbuf[80];
463 
464 	se->gold = rogue.gold;
465 	strlcpy(se->username, login_name, sizeof(se->username));
466 
467 	if (other) {
468 		switch(other) {
469 		case HYPOTHERMIA:
470 			death = "died of hypothermia";
471 			break;
472 		case STARVATION:
473 			death = "died of starvation";
474 			break;
475 		case POISON_DART:
476 			death = "killed by a dart";
477 			break;
478 		case QUIT:
479 			death = "quit";
480 			break;
481 		case WIN:
482 			death = "a total winner";
483 			break;
484 		case KFIRE:
485 			death = "killed by fire";
486 			break;
487 		}
488 	} else {
489 		const char *mn, *article;
490 
491 		mn = m_names[monster->m_char - 'A'];
492 		if (is_vowel(mn[0])) {
493 			article = "an";
494 		} else {
495 			article = "a";
496 		}
497 
498 		snprintf(deathbuf, sizeof(deathbuf),
499 			 "killed by %s %s", article, mn);
500 		death = deathbuf;
501 	}
502 
503 	if (other != WIN && has_amulet()) {
504 		hasamulet = " with amulet";
505 	} else {
506 		hasamulet = "";
507 	}
508 
509 	snprintf(se->death, sizeof(se->death), "%s on level %d%s",
510 		 death, max_level, hasamulet);
511 
512 	strlcpy(se->nickname, nick_name, sizeof(se->nickname));
513 }
514 
515 boolean
is_vowel(short ch)516 is_vowel(short ch)
517 {
518 	return( (ch == 'a') ||
519 		(ch == 'e') ||
520 		(ch == 'i') ||
521 		(ch == 'o') ||
522 		(ch == 'u') );
523 }
524 
525 static void
sell_pack(void)526 sell_pack(void)
527 {
528 	object *obj;
529 	short row = 2, val;
530 	char buf[DCOLS];
531 
532 	obj = rogue.pack.next_object;
533 
534 	clear();
535 	mvaddstr(1, 0, "Value      Item");
536 
537 	while (obj) {
538 		if (obj->what_is != FOOD) {
539 			obj->identified = 1;
540 			val = get_value(obj);
541 			rogue.gold += val;
542 
543 			if (row < DROWS) {
544 				get_desc(obj, buf, sizeof(buf));
545 				mvprintw(row++, 0, "%5d      %s", val, buf);
546 			}
547 		}
548 		obj = obj->next_object;
549 	}
550 	refresh();
551 	if (rogue.gold > MAX_GOLD) {
552 		rogue.gold = MAX_GOLD;
553 	}
554 	messagef(0, "%s", "");		/* gcc objects to just "" */
555 }
556 
557 static int
get_value(const object * obj)558 get_value(const object *obj)
559 {
560 	short wc;
561 	int val;
562 
563 	val = 0;
564 	wc = obj->which_kind;
565 
566 	switch(obj->what_is) {
567 	case WEAPON:
568 		val = id_weapons[wc].value;
569 		if ((wc == ARROW) || (wc == DAGGER) || (wc == SHURIKEN) ||
570 			(wc == DART)) {
571 			val *= obj->quantity;
572 		}
573 		val += (obj->d_enchant * 85);
574 		val += (obj->hit_enchant * 85);
575 		break;
576 	case ARMOR:
577 		val = id_armors[wc].value;
578 		val += (obj->d_enchant * 75);
579 		if (obj->is_protected) {
580 			val += 200;
581 		}
582 		break;
583 	case WAND:
584 		val = id_wands[wc].value * (obj->class + 1);
585 		break;
586 	case SCROL:
587 		val = id_scrolls[wc].value * obj->quantity;
588 		break;
589 	case POTION:
590 		val = id_potions[wc].value * obj->quantity;
591 		break;
592 	case AMULET:
593 		val = 5000;
594 		break;
595 	case RING:
596 		val = id_rings[wc].value * (obj->class + 1);
597 		break;
598 	}
599 	if (val <= 0) {
600 		val = 10;
601 	}
602 	return(val);
603 }
604 
605 static void
id_all(void)606 id_all(void)
607 {
608 	short i;
609 
610 	for (i = 0; i < SCROLS; i++) {
611 		id_scrolls[i].id_status = IDENTIFIED;
612 	}
613 	for (i = 0; i < WEAPONS; i++) {
614 		id_weapons[i].id_status = IDENTIFIED;
615 	}
616 	for (i = 0; i < ARMORS; i++) {
617 		id_armors[i].id_status = IDENTIFIED;
618 	}
619 	for (i = 0; i < WANDS; i++) {
620 		id_wands[i].id_status = IDENTIFIED;
621 	}
622 	for (i = 0; i < POTIONS; i++) {
623 		id_potions[i].id_status = IDENTIFIED;
624 	}
625 }
626 
627 void
xxxx(char * buf,short n)628 xxxx(char *buf, short n)
629 {
630 	short i;
631 	unsigned char c;
632 
633 	for (i = 0; i < n; i++) {
634 
635 		/* It does not matter if accuracy is lost during this assignment */
636 		c = (unsigned char)xxx(0);
637 
638 		buf[i] ^= c;
639 	}
640 }
641 
642 long
xxx(boolean st)643 xxx(boolean st)
644 {
645 	static long f, s;
646 	long r;
647 
648 	if (st) {
649 		f = 37;
650 		s = 7;
651 		return(0L);
652 	}
653 	r = ((f * s) + 9337) % 8887;
654 	f = s;
655 	s = r;
656 	return(r);
657 }
658 
659 static void
center(short row,const char * buf)660 center(short row, const char *buf)
661 {
662 	short margin;
663 
664 	margin = ((DCOLS - strlen(buf)) / 2);
665 	mvaddstr(row, margin, buf);
666 }
667 
668 static void
sf_error(void)669 sf_error(void)
670 {
671 	md_lock(0);
672 	messagef(1, "%s", "");		/* gcc objects to just "" */
673 	clean_up("sorry, score file is out of order");
674 }
675