1 /*
2 * Gyatzee: Gnomified Yahtzee game.
3 * (C) 1998 the Free Software Foundation
4 *
5 * File: yahtzee.c
6 *
7 * Author: Scott Heavner
8 *
9 * Window manager independent yahtzee routines.
10 *
11 * Variables are exported in yahtzee.h
12 *
13 * This program is based on based on orest zborowski's curses based
14 * yahtze (c)1992.
15 *
16 * This program is free software; you can redistribute it and/or modify
17 * it under the terms of the GNU General Public License as published by
18 * the Free Software Foundation; either version 2 of the License, or
19 * (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 */
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <time.h>
35 #include <glib.h>
36
37 #include "yahtzee.h"
38
39 char *ProgramHeader = "Yahtzee Version 2.00 (c)1998 SDH, (c)1992 by zorst";
40
41 GList *UndoList = NULL;
42 GList *RedoList = NULL;
43 UndoScoreElement lastRoll;
44
45 /*=== Exported variables ===*/
46 DiceInfo DiceValues[NUMBER_OF_DICE] = { {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0} };
47 Player players[MAX_NUMBER_OF_PLAYERS] = {
48 {NULL, {0}, {0}, 0, 0},
49 {NULL, {0}, {0}, 0, 0},
50 {NULL, {0}, {0}, 0, 0},
51 {NULL, {0}, {0}, 0, 0},
52 {NULL, {0}, {0}, 0, 0},
53 {NULL, {0}, {0}, 0, 0}
54 };
55 int NumberOfPlayers = 0;
56 int NumberOfComputers = 0;
57 int NumberOfHumans = 0;
58 int DoDelay = 0;
59 int NumberOfRolls;
60 int LastHumanNumberOfRolls;
61 int WinningScore;
62 int DisplayComputerThoughts = 0;
63 int CurrentPlayer;
64 GameType game_type = GAME_YAHTZEE;
65 int NUM_FIELDS = NUM_FIELDS_YAHTZEE;
66 int NUM_LOWER = NUM_LOWER_YAHTZEE;
67
68 char *DefaultPlayerNames[MAX_NUMBER_OF_PLAYERS] = { N_("Human"),
69 "Alice",
70 "Bob",
71 "Carol",
72 "Dave",
73 "Eve"
74 };
75
76 typedef struct field_info_t {
77 char *label;
78 int yahtzee_row;
79 int kismet_row;
80 int (*score_func) (int);
81 } FieldInfo;
82
83 char *FieldLabelsYahtzee[NUM_FIELDS_YAHTZEE + EXTRA_FIELDS] = {
84 N_("1s [total of 1s]"),
85 N_("2s [total of 2s]"),
86 N_("3s [total of 3s]"),
87 N_("4s [total of 4s]"),
88 N_("5s [total of 5s]"),
89 N_("6s [total of 6s]"),
90 /* End of upper panel */
91 N_("3 of a Kind [total]"),
92 N_("4 of a Kind [total]"),
93 N_("Full House [25]"),
94 N_("Small Straight [30]"),
95 N_("Large Straight [40]"),
96 N_("5 of a Kind [50]"),
97 N_("Chance [total]"),
98 /* End of lower panel */
99 N_("Lower Total"),
100 N_("Grand Total"),
101 /* Need to squish between upper and lower pannel */
102 N_("Upper total"),
103 N_("Bonus if >62"),
104 };
105
106 char *FieldLabelsKismet[NUM_FIELDS_KISMET+EXTRA_FIELDS] =
107 {
108 N_("1s [total of 1s]"),
109 N_("2s [total of 2s]"),
110 N_("3s [total of 3s]"),
111 N_("4s [total of 4s]"),
112 N_("5s [total of 5s]"),
113 N_("6s [total of 6s]"),
114 /* End of upper panel */
115 N_("2 pair Same Color [total]"),
116 N_("3 of a Kind [total]"),
117 N_("Full House [15 + total]"),
118 N_("Full House Same Color [20 + total]"),
119 N_("Flush (all same color) [35]"),
120 N_("Large Straight [40]"),
121 N_("4 of a Kind [25 + total]"),
122 N_("5 of a Kind [50 + total]"),
123 N_("Chance [total]"),
124 /* End of lower panel */
125 N_("Lower Total"),
126 N_("Grand Total"),
127 /* Need to squish between upper and lower pannel */
128 N_("Upper total"),
129 N_("Bonus if >62"),
130 };
131
132 char **FieldLabels = FieldLabelsKismet;
133
134 int
NoDiceSelected(void)135 NoDiceSelected (void)
136 {
137 int i, j = 1;
138 for (i = 0; i < NUMBER_OF_DICE; i++)
139 if (DiceValues[i].sel)
140 j = 0;
141 return j;
142 }
143
144 void
SelectAllDice(void)145 SelectAllDice (void)
146 {
147 int i;
148 for (i = 0; i < NUMBER_OF_DICE; i++)
149 DiceValues[i].sel = 1;
150 }
151
152 void
YahtzeeInit(void)153 YahtzeeInit (void)
154 {
155 int i;
156
157 srand (time (NULL));
158
159 for (i = 0; i < MAX_NUMBER_OF_PLAYERS; ++i) {
160 players[i].name = _(DefaultPlayerNames[i]);
161 players[i].comp = 1;
162 }
163
164 /* Make player number one human */
165 players[0].comp = 0;
166
167 }
168
169 /* Must be called after window system is initted */
170 void
NewGame(void)171 NewGame (void)
172 {
173 int i, j;
174
175 if (game_type == GAME_YAHTZEE) {
176 FieldLabels = FieldLabelsYahtzee;
177 NUM_FIELDS = NUM_FIELDS_YAHTZEE;
178 NUM_LOWER = NUM_LOWER_YAHTZEE;
179 }
180 else if (game_type == GAME_KISMET) {
181 FieldLabels = FieldLabelsKismet;
182 NUM_FIELDS = NUM_FIELDS_KISMET;
183 NUM_LOWER = NUM_LOWER_KISMET;
184 }
185
186 CurrentPlayer = 0;
187 NumberOfRolls = 0;
188 LastHumanNumberOfRolls = 0;
189 FreeUndoRedoLists ();
190
191 NumberOfPlayers = NumberOfComputers + NumberOfHumans;
192
193 for (i = 0; i < MAX_NUMBER_OF_PLAYERS; ++i) {
194 players[i].finished = 0;
195 players[i].comp = 1;
196
197 for (j = 0; j < NUM_FIELDS; ++j) {
198 players[i].score[j] = 0;
199 players[i].used[j] = 0;
200 }
201 }
202
203 /* Possibly 0 humans? */
204 for (i = 0; i < NumberOfHumans; i++)
205 players[i].comp = 0;
206
207 SelectAllDice ();
208 RollSelectedDice ();
209 }
210
211 int
RollDie(void)212 RollDie (void)
213 {
214 return rand () % 6 + 1;
215 }
216
217 void
RollSelectedDice(void)218 RollSelectedDice (void)
219 {
220 int i, cnt = 0;
221
222 if (NumberOfRolls >= NUM_ROLLS) {
223 return;
224 }
225
226 for (i = 0; i < NUMBER_OF_DICE; i++) {
227 if (DiceValues[i].sel) {
228 DiceValues[i].val = RollDie ();
229 DiceValues[i].sel = 0;
230 cnt++;
231 lastRoll.DiceValues[i] = DiceValues[i].val;
232 }
233 }
234
235 /* If no dice is selcted roll them all */
236 if (cnt == 0) {
237 for (i = 0; i < NUMBER_OF_DICE; i++) {
238 DiceValues[i].val = RollDie ();
239 lastRoll.DiceValues[i] = DiceValues[i].val;
240 }
241 }
242
243 UpdateAllDicePixmaps ();
244 DeselectAllDice ();
245
246 NumberOfRolls++;
247
248 if (NumberOfRolls >= NUM_ROLLS) {
249 say (_("Choose a score slot."));
250 }
251
252 lastRoll.roll = NumberOfRolls;
253 lastRoll.player = CurrentPlayer;
254 }
255
256 int
GameIsOver(void)257 GameIsOver (void)
258 {
259 int i;
260
261 for (i = 0; i < NumberOfPlayers; i++)
262 if (players[i].finished == 0)
263 return 0;
264 return 1;
265 }
266
267 int
upper_total(int num)268 upper_total (int num)
269 {
270 int val;
271 int i;
272
273 val = 0;
274
275 for (i = 0; i < NUM_UPPER; ++i)
276 val += players[num].score[i];
277
278 return (val);
279 }
280
281 int
lower_total(int num)282 lower_total (int num)
283 {
284 int val;
285 int i;
286
287 val = 0;
288
289 for (i = 0; i < NUM_LOWER; ++i)
290 val += players[num].score[i + NUM_UPPER];
291
292 return (val);
293 }
294
295 int
total_score(int num)296 total_score (int num)
297 {
298 int upper_tot;
299 int lower_tot;
300
301 lower_tot = lower_total (num);
302 upper_tot = upper_total (num);
303
304 if (game_type == GAME_KISMET && upper_tot >= 78)
305 upper_tot += 75;
306 else if (game_type == GAME_KISMET && upper_tot >= 71)
307 upper_tot += 55;
308 else if (upper_tot >= 63)
309 upper_tot += 35;
310
311 return (upper_tot + lower_tot);
312 }
313
314 int
count(int val)315 count (int val)
316 {
317 int i;
318 int num;
319
320 num = 0;
321
322 for (i = 0; i < NUMBER_OF_DICE; ++i)
323 if (DiceValues[i].val == val)
324 ++num;
325
326 return (num);
327 }
328
329 int
find_n_of_a_kind(int n,int but_not)330 find_n_of_a_kind (int n, int but_not)
331 {
332 int i;
333
334 for (i = 0; i < NUMBER_OF_DICE; ++i) {
335 if (DiceValues[i].val == but_not)
336 continue;
337
338 if (count (DiceValues[i].val) >= n)
339 return (DiceValues[i].val);
340 }
341
342 return (0);
343 }
344
345 int
find_straight(int run,int notstart,int notrun)346 find_straight (int run, int notstart, int notrun)
347 {
348 int i;
349 int j;
350
351 for (i = 1; i < 7; ++i) {
352 if (i >= notstart && i < notstart + notrun)
353 continue;
354
355 for (j = 0; j < run; ++j)
356 if (!count (i + j))
357 break;
358
359 if (j == run)
360 return (i);
361 }
362
363 return (0);
364 }
365
366 int
find_yahtzee(void)367 find_yahtzee (void)
368 {
369 int i;
370
371 for (i = 1; i < 7; ++i)
372 if (count (i) == 5)
373 return (i);
374
375 return (0);
376 }
377
378 int
add_dice(void)379 add_dice (void)
380 {
381 int i;
382 int val;
383
384 val = 0;
385
386 for (i = 0; i < NUMBER_OF_DICE; ++i)
387 val += DiceValues[i].val;
388
389 return (val);
390 }
391
392 static int
score_basic(int field)393 score_basic (int field) {
394 field++;
395 return count (field) * field;
396 }
397
398 static int
score_3_of_a_kind(int field)399 score_3_of_a_kind (int field) {
400 if (find_n_of_a_kind (3, 0))
401 return add_dice ();
402 return 0;
403 }
404
405 static int
score_4_of_a_kind(int field)406 score_4_of_a_kind (int field) {
407 if (find_n_of_a_kind (4, 0))
408 return add_dice ();
409 return 0;
410 }
411
412 static int
score_full_house(int field)413 score_full_house (int field) {
414 int i = find_n_of_a_kind (3, 0);
415 if (i) {
416 if (find_n_of_a_kind (2, i) || find_n_of_a_kind (5, 0))
417 return 25;
418 }
419
420 return 0;
421 }
422
423 static int
score_small_straight(int field)424 score_small_straight (int field) {
425 if (find_straight (4, 0, 0))
426 return 30;
427
428 return 0;
429 }
430
431 static int
score_large_straight(int field)432 score_large_straight (int field) {
433 if (find_straight (5, 0, 0))
434 return 40;
435
436 return 0;
437 }
438
439 static int
score_yahtzee(int field)440 score_yahtzee (int field) {
441 if (find_n_of_a_kind (5, 0))
442 return 50;
443
444 return 0;
445 }
446
447 static int
score_chance(int field)448 score_chance (int field) {
449 return add_dice ();
450 }
451
452 static int
score_2_pair_same_color(int field)453 score_2_pair_same_color (int field) {
454 int i = find_n_of_a_kind (2, 0);
455 if (i) {
456 if (find_n_of_a_kind (2, i) + i == 7 || find_n_of_a_kind (4, 0))
457 return add_dice ();
458 }
459
460 return 0;
461 }
462
463 static int
score_full_house_kismet(int field)464 score_full_house_kismet (int field) {
465 int i = find_n_of_a_kind (3, 0);
466 if (i) {
467 if (find_n_of_a_kind (2, i) || find_n_of_a_kind (5, 0))
468 return 15 + add_dice ();
469 }
470
471 return 0;
472 }
473
474 static int
score_full_house_same_color(int field)475 score_full_house_same_color (int field) {
476 int i = find_n_of_a_kind (3, 0);
477 if (i) {
478 if (find_n_of_a_kind (2, i) + i == 7 || find_n_of_a_kind (5, 0))
479 return 20 + add_dice ();
480 }
481
482 return 0;
483 }
484
485 static int
score_flush(int field)486 score_flush (int field) {
487 int i = find_n_of_a_kind (3, 0);
488
489 if (i && i + find_n_of_a_kind (2, i) == 7) return 35;
490 i = find_n_of_a_kind (4, 0);
491 if (i && i + find_n_of_a_kind (1, i) == 7) return 35;
492 if (find_n_of_a_kind (5, 0)) return 35;
493
494 return 0;
495 }
496
497 static int
score_4_of_a_kind_kismet(int field)498 score_4_of_a_kind_kismet (int field) {
499 if (find_n_of_a_kind (4, 0))
500 return 25 + add_dice ();
501 return 0;
502 }
503
504 static int
score_kismet(int field)505 score_kismet (int field) {
506 if (find_n_of_a_kind (5, 0))
507 return 50 + add_dice ();
508 return 0;
509 }
510
511 FieldInfo field_table[] = {
512 { N_("1s [total of 1s]"), 0, 0, score_basic },
513 { N_("2s [total of 2s]"), 1, 1, score_basic },
514 { N_("3s [total of 3s]"), 2, 2, score_basic },
515 { N_("4s [total of 4s]"), 3, 3, score_basic },
516 { N_("5s [total of 5s]"), 4, 4, score_basic },
517 { N_("6s [total of 6s]"), 5, 5, score_basic },
518 { N_("3 of a Kind [total]"), 6, 7, score_3_of_a_kind },
519 { N_("4 of a Kind [total]"), 7, -1, score_4_of_a_kind },
520 { N_("Full House [25]"), 8, -1, score_full_house },
521 { N_("Small Straight [30]"), 9, -1, score_small_straight },
522 { N_("Large Straight [40]"), 10, 11, score_large_straight },
523 { N_("5 of a Kind [total]"), 11, -1, score_yahtzee },
524 { N_("Chance [total]"), 12, 14, score_chance },
525 { N_("2 pair Same Color [total]"), -1, 6, score_2_pair_same_color },
526 { N_("Full House [15 + total]"), -1, 8, score_full_house_kismet },
527 { N_("Full House Same Color [20 + total]"), -1, 9, score_full_house_same_color },
528 { N_("Flush (all same color) [35]"), -1, 10, score_flush },
529 { N_("4 of a Kind [25 + total]"), -1, 12, score_4_of_a_kind_kismet },
530 { N_("5 of a Kind [50 + total]"), -1, 13, score_kismet },
531 };
532
533 #define FIELD_TABLE_SIZE (sizeof (field_table) / sizeof (FieldInfo))
534
535 static FieldInfo
get_field_info(int field)536 *get_field_info (int field)
537 {
538 gint ii;
539
540 for (ii = 0; ii < FIELD_TABLE_SIZE; ii++)
541 if (field == (game_type == GAME_KISMET ? field_table[ii].kismet_row :
542 field_table[ii].yahtzee_row))
543 return &field_table[ii];
544
545 return NULL;
546 }
547
548 gint
field_score(gint field)549 field_score (gint field)
550 {
551 FieldInfo *info = get_field_info (field);
552 gint rval = 0;
553
554 if (info) {
555 return info->score_func (field);
556 }
557
558 return rval;
559 }
560
561 gint
player_field_score(gint player,gint field)562 player_field_score (gint player, gint field)
563 {
564 /* A player can still score in H_YA even if it's used in the
565 * regular game, but only if they have a non-zero value there */
566 if (field == H_YA && game_type == GAME_YAHTZEE) {
567 if (players[player].used[field]) {
568 if (field_score (field) > 0 && players[player].score[field] > 0)
569 return field_score (field);
570 else
571 return -1;
572 }
573 }
574 else if (players[player].used[field])
575 return -1;
576
577 return field_score (field);
578 }
579
580 void
PrependUndoList(gint player,gint field,gint score)581 PrependUndoList (gint player, gint field, gint score) {
582 UndoScoreElement *elem = g_new0 (UndoScoreElement, 1);
583 gint ii;
584 elem->player = player;
585 elem->field = field;
586 elem->score = score;
587
588 for (ii = 0; ii < NUMBER_OF_DICE; ii++)
589 elem->DiceValues[ii] = DiceValues[ii].val;
590 elem->roll = NumberOfRolls;
591
592 if (!players[player].comp)
593 FreeUndoList ();
594 UndoList = g_list_prepend (UndoList, elem);
595 }
596
597 void
ResetDiceState(UndoScoreElement * elem)598 ResetDiceState (UndoScoreElement *elem) {
599 gint ii;
600 for (ii = 0; ii < NUMBER_OF_DICE; ii++) {
601 DiceValues[ii].val = elem->DiceValues[ii];
602 DiceValues[ii].sel = 0;
603 }
604 NumberOfRolls = elem->roll;
605 ShowPlayer (elem->player, elem->field);
606 }
607
608 gint
UndoLastMove(void)609 UndoLastMove (void) {
610 if (UndoList) {
611 UndoScoreElement *elem = UndoList->data;
612 if (elem->field == H_YA && game_type == GAME_YAHTZEE) {
613 if (players[elem->player].score[elem->field] != 0)
614 players[elem->player].score[elem->field] -= 50;
615 players[elem->player].used [elem->field] = players[elem->player].score[elem->field] > 0;
616 } else {
617 players[elem->player].score[elem->field] = 0;
618 players[elem->player].used [elem->field] = 0;
619 }
620
621 ResetDiceState (elem);
622 UndoList = g_list_remove (UndoList, elem);
623 RedoList = g_list_prepend (RedoList, elem);
624 return elem->player;
625 }
626
627 return CurrentPlayer;
628 }
629
630 gint
RedoLastMove(void)631 RedoLastMove (void) {
632 gint rval = (CurrentPlayer + 1) % NumberOfPlayers;
633 if (RedoList) {
634 gint ii;
635 UndoScoreElement *elem = RedoList->data;
636
637 for (ii = 0; ii < NUMBER_OF_DICE; ii++) {
638 DiceValues[ii].val = elem->DiceValues[ii];
639 DiceValues[ii].sel = 0;
640 }
641
642 RedoList = g_list_remove (RedoList, elem);
643 play_score (elem->player, elem->field);
644 rval = (elem->player + 1) % NumberOfPlayers;
645 g_free (elem);
646 if (RedoList) {
647 elem = RedoList->data;
648 rval = elem->player;
649 }
650 }
651
652 return rval;
653 }
654
655 void
RestoreLastRoll(void)656 RestoreLastRoll (void) {
657 ResetDiceState (&lastRoll);
658 }
659
660 UndoScoreElement*
RedoHead(void)661 RedoHead (void) {
662 if (RedoList) {
663 UndoScoreElement *elem = RedoList->data;
664 return elem;
665 }
666
667 return NULL;
668 }
669
670 void
FreeUndoList(void)671 FreeUndoList (void) {
672 while (UndoList) {
673 UndoScoreElement *elem = UndoList->data;
674 UndoList = g_list_remove (UndoList, elem);
675 g_free (elem);
676 }
677 }
678
679 void
FreeRedoList(void)680 FreeRedoList (void) {
681 while (RedoList) {
682 UndoScoreElement *elem = RedoList->data;
683 RedoList = g_list_remove (RedoList, elem);
684 g_free (elem);
685 }
686 }
687
688 void
FreeUndoRedoLists(void)689 FreeUndoRedoLists (void) {
690 FreeUndoList ();
691 FreeRedoList ();
692 }
693
694 void
FreeRedoListHead(void)695 FreeRedoListHead (void) {
696 if (RedoList) {
697 UndoScoreElement *elem = RedoList->data;
698 RedoList = g_list_remove (RedoList, elem);
699 g_free (elem);
700 }
701 }
702
703 /* Test if we can use suggested score slot */
704 int
play_score(int player,int field)705 play_score (int player, int field)
706 {
707 int i;
708
709 /* Special case for yahtzee, allow multiple calls if 1st wasn't 0 */
710
711 /* This, however, was broken: it didn't actually check to see if the
712 * user had a yahtzee if this wasn't their first time clicking on it.
713 * Bad. -- pschwan@cmu.edu */
714 if (field == 11 && game_type == GAME_YAHTZEE) {
715 if ((players[player].used[11] && (players[player].score[11] == 0)))
716 return SLOT_USED;
717
718 if ((players[player].used[11] && !find_yahtzee ()))
719 return SLOT_USED;
720 } else if (players[player].used[field])
721 return SLOT_USED;
722
723 players[player].used[field] = 1;
724 players[player].score[field] = players[player].score[field] + field_score (field);
725 PrependUndoList (player, field, players[player].score[field]);
726
727 ShowPlayer (player, field);
728
729 for (i = 0; i < NUM_FIELDS; ++i)
730 if (!players[player].used[i])
731 return SCORE_OK;
732
733 players[player].finished = 1;
734
735 return SCORE_OK;
736 }
737
738 int
FindWinner(void)739 FindWinner (void)
740 {
741 int i;
742 int winner = 0;
743 int total;
744
745 WinningScore = 0;
746 FreeUndoRedoLists ();
747
748 for (i = 0; i < NumberOfPlayers; ++i) {
749 total = total_score (i);
750 if (total > WinningScore) {
751 WinningScore = total_score (i);
752 winner = i;
753 }
754 }
755
756 /* Detect a drawn game. Returning the negative of the score
757 * is a bit of a hack, but it allows us to find out who the winners
758 * were without having to pass around a list. */
759 for (i = 0; i < NumberOfPlayers; ++i) {
760 total = total_score (i);
761 if ((total == WinningScore) && (i != winner))
762 return -total;
763 }
764
765 return winner;
766 }
767
768 /* Undo is possible when the Undo List isn't NULL */
769 int
UndoPossible(void)770 UndoPossible (void)
771 {
772 return UndoList != NULL;
773 }
774
775 /* Undo option should be visible only when the player is human */
776 int
UndoVisible(void)777 UndoVisible (void)
778 {
779 return UndoPossible () && !players[CurrentPlayer].comp;
780 }
781
782 int
RedoPossible(void)783 RedoPossible (void)
784 {
785 return RedoList != NULL;
786 }
787