diff --git a/presets/apple2/farmhouse.c b/presets/apple2/farmhouse.c new file mode 100644 index 00000000..745d06ce --- /dev/null +++ b/presets/apple2/farmhouse.c @@ -0,0 +1,833 @@ +/* + * + * The Abandoned Farm House Adventure + * + * Jeff Tranter + * + * Written in standard C but designed to run on the Apple Replica 1 + * or Apple II using the CC65 6502 assembler. + * + * Copyright 2012-2015 Jeff Tranter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Revision History: + * + * Version Date Comments + * ------- ---- -------- + * 0.0 13 Mar 2012 First alpha version + * 0.1 18 Mar 2012 First beta version + * 0.9 19 Mar 2012 First public release + * 1.0 06 Sep 2015 Lower case and other Apple II improvements. + * + */ + +#include +#include +#include +#include +#ifdef __CC65__ +#include +#endif + +/* CONSTANTS */ + +/* Maximum number of items user can carry */ +#define MAXITEMS 5 + +/* Number of locations */ +#define NUMLOCATIONS 32 + +/* TYPES */ + +/* To optimize for code size and speed, most numbers are 8-bit chars when compiling for CC65. */ +#ifdef __CC65__ +typedef char number; +#else +typedef int number; +#endif + +/* Directions */ +typedef enum { + North, + South, + East, + West, + Up, + Down +} Direction_t; + +/* Items */ +typedef enum { + NoItem, + Key, + Pitchfork, + Flashlight, + Lamp, + Oil, + Candybar, + Bottle, + Doll, + ToyCar, + Matches, + GoldCoin, + SilverCoin, + StaleMeat, + Book, + Cheese, + OldRadio, + LastItem=OldRadio +} Item_t; + +/* Locations */ +typedef enum { + NoLocation, + Driveway1, + Driveway2, + Driveway3, + Driveway4, + Driveway5, + Garage, + WorkRoom, + Hayloft, + Kitchen, + DiningRoom, + BottomStairs, + DrawingRoom, + Study, + TopStairs, + BoysBedroom, + GirlsBedroom, + MasterBedroom, + ServantsQuarters, + LaundryRoom, + FurnaceRoom, + VacantRoom, + Cistern, + Tunnel, + Woods24, + Woods25, + Woods26, + WolfTree, + Woods28, + Woods29, + Woods30, + Woods31, +} Location_t; + +/* TABLES */ + +/* Names of directions */ +char *DescriptionOfDirection[] = { + "north", "south", "east", "west", "up", "down" +}; + +/* Names of items */ +char *DescriptionOfItem[LastItem+1] = { + "", + "key", + "pitchfork", + "flashlight", + "lamp", + "oil", + "candybar", + "bottle", + "doll", + "toy car", + "matches", + "gold coin", + "silver coin", + "stale meat", + "book", + "cheese", + "old radio", +}; + +/* Names of locations */ +char *DescriptionOfLocation[NUMLOCATIONS] = { + "", + "in the driveway near your car", + "in the driveway", + "in front of the garage", + "in front of the barn", + "at the door to the house", + "in the garage", + "in the workroom of the barn", + "in the hayloft of the barn", + "in the kitchen", + "in the dining room", + "at the bottom of the stairs", + "in the drawing room", + "in the study", + "at the top of the stairs", + "in a boy's bedroom", + "in a girl's bedroom", + "in the master bedroom next to\na bookcase", + "in the servant's quarters", + "in the basement laundry room", + "in the furnace room", + "in a vacant room next to a\nlocked door", + "in the cistern", + "in an underground tunnel. There are rats here", + "in the woods near a trapdoor", + "in the woods", + "in the woods", + "in the woods next to a tree", + "in the woods", + "in the woods", + "in the woods", + "in the woods", +}; + +/* DATA */ + +/* Inventory of what player is carrying */ +Item_t Inventory[MAXITEMS]; + +/* Location of each item. Index is the item number, returns the location. 0 if item is gone */ +Location_t locationOfItem[LastItem+1]; + +/* Map. Given a location and a direction to move, returns the location it connects to, or 0 if not a valid move. Map can change during game play. */ +Direction_t Move[NUMLOCATIONS][6] = { + /* N S E W U D */ + { 0, 0, 0, 0, 0, 0 }, /* 0 */ + { 2, 0, 0, 0, 0, 0 }, /* 1 */ + { 4, 1, 3, 5, 0, 0 }, /* 2 */ + { 0, 0, 6, 2, 0, 0 }, /* 3 */ + { 7, 2, 0, 0, 0, 0 }, /* 4 */ + { 0, 0, 2, 9, 0, 0 }, /* 5 */ + { 0, 0, 0, 3, 0, 0 }, /* 6 */ + { 0, 4, 0, 0, 8, 0 }, /* 7 */ + { 0, 0, 0, 0, 0, 7 }, /* 8 */ + { 0,10, 5, 0, 0,19 }, /* 9 */ + { 9, 0, 0,11, 0, 0 }, /* 10 */ + { 0, 0,10,12,14, 0 }, /* 11 */ + { 13, 0,11, 0, 0, 0 }, /* 12 */ + { 0,12, 0, 0, 0, 0 }, /* 13 */ + { 16, 0,15,17, 0,11 }, /* 14 */ + { 0, 0, 0,14, 0, 0 }, /* 15 */ + { 0,14, 0, 0, 0, 0 }, /* 16 */ + { 0, 0,14, 0, 0, 0 }, /* 17 */ + { 0, 0, 0, 0, 0,13 }, /* 18 */ + { 0, 0, 0,20, 9, 0 }, /* 19 */ + { 21, 0,19, 0, 0, 0 }, /* 20 */ + { 0,20, 0,22, 0, 0 }, /* 21 */ + { 0, 0,21, 0, 0, 0 }, /* 22 */ + { 24,21, 0, 0, 0, 0 }, /* 23 */ + { 29,23, 0,26, 0, 0 }, /* 24 */ + { 26, 0,24, 0, 0, 0 }, /* 25 */ + { 27,25,29, 0, 0, 0 }, /* 26 */ + { 0,26,28, 0, 0, 0 }, /* 27 */ + { 0,29,31,27, 0, 0 }, /* 28 */ + { 28,24,30,26, 0, 0 }, /* 29 */ + { 31, 0, 0,29, 0, 0 }, /* 30 */ + { 0,30, 0,29, 0, 0 }, /* 31 */ +}; + +/* Current location */ +number currentLocation; + +/* Number of turns played in game */ +int turnsPlayed; + +/* True if player has lit the lamp. */ +number lampLit; + +/* True if lamp filled with oil. */ +number lampFilled; + +/* True if player ate food. */ +number ateFood; + +/* True if player drank water. */ +number drankWater; + +/* Incremented each turn you are in the tunnel. */ +number ratAttack; + +/* Tracks state of wolf attack */ +number wolfState; + +/* Set when game is over */ +number gameOver; + +const char *introText = " Abandoned Farmhouse Adventure\n By Jeff Tranter\n\nYour three-year-old grandson has gone\nmissing and was last seen headed in the\ndirection of the abandoned family farm.\nIt's a dangerous place to play. You\nhave to find him before he gets hurt,\nand it will be getting dark soon...\n"; + +const char *helpString = "Valid commands:\ngo east/west/north/south/up/down \nlook\nuse \nexamine \ntake \ndrop \ninventory\nhelp\nquit\nYou can abbreviate commands and\ndirections to the first letter.\nType just the first letter of\na direction to move.\n"; + +/* Line of user input */ +char buffer[40]; + +/* Clear the screen */ +void clearScreen() +{ +#if defined(__APPLE2__) + clrscr(); +#else + number i; + for (i = 0; i < 24; ++i) + printf("\n"); +#endif +} + +/* Return 1 if carrying an item */ +number carryingItem(char *item) +{ + number i; + + for (i = 0; i < MAXITEMS; i++) { + if ((Inventory[i] != 0) && (!strcasecmp(DescriptionOfItem[Inventory[i]], item))) + return 1; + } + return 0; +} + +/* Return 1 if item it at current location (not carried) */ +number itemIsHere(char *item) +{ + number i; + + /* Find number of the item. */ + for (i = 1; i <= LastItem; i++) { + if (!strcasecmp(item, DescriptionOfItem[i])) { + /* Found it, but is it here? */ + if (locationOfItem[i] == currentLocation) { + return 1; + } else { + return 0; + } + } + } + return 0; +} + +/* Inventory command */ +void doInventory() +{ + number i; + int found = 0; + + printf("%s", "You are carrying:\n"); + for (i = 0; i < MAXITEMS; i++) { + if (Inventory[i] != 0) { + printf(" %s\n", DescriptionOfItem[Inventory[i]]); + found = 1; + } + } + if (!found) + printf(" nothing\n"); +} + +/* Help command */ +void doHelp() +{ + printf("%s", helpString); +} + +/* Look command */ +void doLook() +{ + number i, loc, seen; + + printf("You are %s.\n", DescriptionOfLocation[currentLocation]); + + seen = 0; + printf("You see:\n"); + for (i = 1; i <= LastItem; i++) { + if (locationOfItem[i] == currentLocation) { + printf(" %s\n", DescriptionOfItem[i]); + seen = 1; + } + } + if (!seen) + printf(" nothing special\n"); + + printf("You can go:"); + + for (i = North; i <= Down; i++) { + loc = Move[currentLocation][i]; + if (loc != 0) { + printf(" %s", DescriptionOfDirection[i]); + } + } + printf("\n"); +} + +/* Quit command */ +void doQuit() +{ + printf("%s", "Are you sure you want to quit (y/n)? "); + fgets(buffer, sizeof(buffer)-1, stdin); + if (tolower(buffer[0]) == 'y') { + gameOver = 1; + } +} + +/* Drop command */ +void doDrop() +{ + number i; + char *sp; + char *item; + + /* Command line should be like "D[ROP] ITEM" Item name will be after after first space. */ + sp = strchr(buffer, ' '); + if (sp == NULL) { + printf("Drop what?\n"); + return; + } + + item = sp + 1; + + /* See if we have this item */ + for (i = 0; i < MAXITEMS; i++) { + if ((Inventory[i] != 0) && (!strcasecmp(DescriptionOfItem[Inventory[i]], item))) { + /* We have it. Add to location. */ + locationOfItem[Inventory[i]] = currentLocation; + /* And remove from inventory */ + Inventory[i] = 0; + printf("Dropped %s.\n", item); + ++turnsPlayed; + return; + } + } + /* If here, don't have it. */ + printf("Not carrying %s.\n", item); +} + +/* Take command */ +void doTake() +{ + number i, j; + char *sp; + char *item; + + /* Command line should be like "T[AKE] ITEM" Item name will be after after first space. */ + sp = strchr(buffer, ' '); + if (sp == NULL) { + printf("Take what?\n"); + return; + } + + item = sp + 1; + + if (carryingItem(item)) { + printf("Already carrying it.\n"); + return; + } + + /* Find number of the item. */ + for (i = 1; i <= LastItem; i++) { + if (!strcasecmp(item, DescriptionOfItem[i])) { + /* Found it, but is it here? */ + if (locationOfItem[i] == currentLocation) { + /* It is here. Add to inventory. */ + for (j = 0; j < MAXITEMS; j++) { + if (Inventory[j] == 0) { + Inventory[j] = i; + /* And remove from location. */ + locationOfItem[i] = 0; + printf("Took %s.\n", item); + ++turnsPlayed; + return; + } + } + + /* Reached maximum number of items to carry */ + printf("You can't carry any more. Drop something.\n"); + return; + } + } + } + + /* If here, don't see it. */ + printf("I see no %s here.\n", item); +} + +/* Go command */ +void doGo() +{ + char *sp; + char dirChar; + Direction_t dir; + + /* Command line should be like "G[O] N[ORTH]" Direction will be + the first letter after a space. Or just a single letter + direction N S E W U D or full directon NORTH etc. */ + + sp = strrchr(buffer, ' '); + if (sp != NULL) { + dirChar = *(sp+1); + } else { + dirChar = buffer[0]; + } + dirChar = tolower(dirChar); + + if (dirChar == 'n') { + dir = North; + } else if (dirChar == 's') { + dir = South; + } else if (dirChar == 'e') { + dir = East; + } else if (dirChar == 'w') { + dir = West; + } else if (dirChar == 'u') { + dir = Up; + } else if (dirChar == 'd') { + dir = Down; + } else { + printf("Go where?\n"); + return; + } + + if (Move[currentLocation][dir] == 0) { + printf("You can't go %s from here.\n", DescriptionOfDirection[dir]); + return; + } + + /* We can move */ + currentLocation = Move[currentLocation][dir]; + printf("You are %s.\n", DescriptionOfLocation[currentLocation]); + ++turnsPlayed; +} + +/* Examine command */ +void doExamine() +{ + char *sp; + char *item; + + /* Command line should be like "E[XAMINE] ITEM" Item name will be after after first space. */ + sp = strchr(buffer, ' '); + if (sp == NULL) { + printf("Examine what?\n"); + return; + } + + item = sp + 1; + ++turnsPlayed; + + /* Examine bookcase - not an object */ + if (!strcasecmp(item, "bookcase")) { + printf("You pull back a book and the bookcase\nopens up to reveal a secret room.\n"); + Move[17][North] = 18; + return; + } + + /* Make sure item is being carried or is in the current location */ + if (!carryingItem(item) && !itemIsHere(item)) { + printf("I don't see it here.\n"); + return; + } + + /* Examine Book */ + if (!strcasecmp(item, "book")) { + printf("It is a very old book entitled\n\"Apple 1 operation manual\".\n"); + return; + } + + /* Examine Flashlight */ + if (!strcasecmp(item, "flashlight")) { + printf("It doesn't have any batteries.\n"); + return; + } + + /* Examine toy car */ + if (!strcasecmp(item, "toy car")) { + printf("It is a nice toy car.\nYour grandson Matthew would like it.\n"); + return; + } + + /* Examine old radio */ + if (!strcasecmp(item, "old radio")) { + printf("It is a 1940 Zenith 8-S-563 console\nwith an 8A02 chassis. You'd turn it on\nbut the electricity is off.\n"); + return; + } + + /* Nothing special about this item */ + printf("You see nothing special about it.\n"); +} + +/* Use command */ +void doUse() +{ + char *sp; + char *item; + + /* Command line should be like "U[SE] ITEM" Item name will be after after first space. */ + sp = strchr(buffer, ' '); + if (sp == NULL) { + printf("Use what?\n"); + return; + } + + item = sp + 1; + + /* Make sure item is being carried or is in the current location */ + if (!carryingItem(item) && !itemIsHere(item)) { + printf("I don't see it here.\n"); + return; + } + + ++turnsPlayed; + + /* Use key */ + if (!strcasecmp(item, "key") && (currentLocation == VacantRoom)) { + printf("You insert the key in the door and it\nopens, revealing a tunnel.\n"); + Move[21][North] = 23; + return; + } + + /* Use pitchfork */ + if (!strcasecmp(item, "pitchfork") && (currentLocation == WolfTree) && (wolfState == 0)) { + printf("You jab the wolf with the pitchfork.\nIt howls and runs away.\n"); + wolfState = 1; + return; + } + + /* Use toy car */ + if (!strcasecmp(item, "toy car") && (currentLocation == WolfTree && wolfState == 1)) { + printf("You show Matthew the toy car and he\ncomes down to take it. You take Matthew\nin your arms and carry him home.\n"); + wolfState = 2; + return; + } + + /* Use oil */ + if (!strcasecmp(item, "oil")) { + if (carryingItem("lamp")) { + printf("You fill the lamp with oil.\n"); + lampFilled = 1; + return; + } else { + printf("You don't have anything to use it with.\n"); + return; + } + } + + /* Use matches */ + if (!strcasecmp(item, "matches")) { + if (carryingItem("lamp")) { + if (lampFilled) { + printf("You light the lamp. You can see!\n"); + lampLit = 1; + return; + } else { + printf("You can't light the lamp. It needs oil.\n"); + return; + } + } else { + printf("Nothing here to light\n"); + } + } + + /* Use candybar */ + if (!strcasecmp(item, "candybar")) { + printf("That hit the spot. You no longer feel\nhungry.\n"); + ateFood = 1; + return; + } + + /* Use bottle */ + if (!strcasecmp(item, "bottle")) { + if (currentLocation == Cistern) { + printf("You fill the bottle with water from the\ncistern and take a drink. You no longer\nfeel thirsty.\n"); + drankWater = 1; + return; + } else { + printf("The bottle is empty. If only you had\nsome water to fill it!\n"); + return; + } + } + + /* Use stale meat */ + if (!strcasecmp(item, "stale meat")) { + printf("The meat looked and tasted bad. You\nfeel very sick and pass out.\n"); + gameOver = 1; + return; + } + + /* Default */ + printf("Nothing happens\n"); +} + +/* Prompt user and get a line of input */ +void prompt() +{ + printf("? "); + fgets(buffer, sizeof(buffer)-1, stdin); + + /* Remove trailing newline */ + buffer[strlen(buffer)-1] = '\0'; +} + +/* Do special things unrelated to command typed. */ +void doActions() +{ + if ((turnsPlayed == 10) && !lampLit) { + printf("It will be getting dark soon. You need\nsome kind of light or soon you won't\nbe able to see.\n"); + } + + if ((turnsPlayed >= 60) && (!lampLit || (!itemIsHere("lamp") && !carryingItem("lamp")))) { + printf("It is dark out and you have no light.\nYou stumble around for a while and\nthen fall, hit your head, and pass out.\n"); + gameOver = 1; + return; + } + + if ((turnsPlayed == 20) && !drankWater) { + printf("You are getting very thirsty.\nYou need to get a drink soon.\n"); + } + + if ((turnsPlayed == 30) && !ateFood) { + printf("You are getting very hungry.\nYou need to find something to eat.\n"); + } + + if ((turnsPlayed == 50) && !drankWater) { + printf("You pass out due to thirst.\n"); + gameOver = 1; + return; + } + + if ((turnsPlayed == 40) && !ateFood) { + printf("You pass out from hunger.\n"); + gameOver = 1; + return; + } + + if (currentLocation == Tunnel) { + if (itemIsHere("cheese")) { + printf("The rats go after the cheese.\n"); + } else { + if (ratAttack < 3) { + printf("The rats are coming towards you!\n"); + ++ratAttack; + } else { + printf("The rats attack and you pass out.\n"); + gameOver = 1; + return; + } + } + } + + /* wolfState values: 0 - wolf attacking 1 - wolf gone, Matthew in tree. 2 - Matthew safe, you won. Game over. */ + if (currentLocation == WolfTree) { + switch (wolfState) { + case 0: + printf("A wolf is circling around the tree.\nMatthew is up in the tree. You have to\nsave him! If only you had some kind of\nweapon!\n"); + break; + case 1: + printf("Matthew is afraid to come\ndown from the tree. If only you had\nsomething to coax him with.\n"); + break; + case 2: + printf("Congratulations! You succeeded and won\nthe game. I hope you had as much fun\nplaying the game as I did creating it.\n- Jeff Tranter \n"); + gameOver = 1; + return; + break; + } + } +} + +/* Set variables to values for start of game */ +void initialize() +{ + currentLocation = Driveway1; + lampFilled = 0; + lampLit = 0; + ateFood = 0; + drankWater = 0; + ratAttack = 0; + wolfState = 0; + turnsPlayed = 0; + gameOver= 0; + + /* These doors can get changed during game and may need to be reset O*/ + Move[17][North] = 0; + Move[21][North] = 0; + + /* Set inventory to default */ + memset(Inventory, 0, sizeof(Inventory[0])*MAXITEMS); + Inventory[0] = Flashlight; + + /* Put items in their default locations */ + locationOfItem[0] = 0; /* NoItem */ + locationOfItem[1] = Driveway1; /* Key */ + locationOfItem[2] = Hayloft; /* Pitchfork */ + locationOfItem[3] = 0; /* Flashlight */ + locationOfItem[4] = WorkRoom; /* Lamp */ + locationOfItem[5] = Garage; /* Oil */ + locationOfItem[6] = Kitchen; /* Candybar */ + locationOfItem[7] = Driveway2; /* Bottle */ + locationOfItem[8] = GirlsBedroom; /* Doll */ + locationOfItem[9] = BoysBedroom; /* ToyCar */ + locationOfItem[10] = ServantsQuarters; /* Matches */ + locationOfItem[11] = Woods25; /* GoldCoin */ + locationOfItem[12] = Woods29; /* SilverCoin */ + locationOfItem[13] = DiningRoom; /* StaleMeat */ + locationOfItem[14] = DrawingRoom; /* Book */ + locationOfItem[15] = LaundryRoom; /* Cheese */ + locationOfItem[16] = MasterBedroom; /* OldRadio */ +} + +/* Main program (obviously) */ +int main(void) +{ + while (1) { + initialize(); + clearScreen(); + printf("%s", introText); + + while (!gameOver) { + prompt(); + if (buffer[0] == '\0') { + } else if (tolower(buffer[0]) == 'h') { + doHelp(); + } else if (tolower(buffer[0]) == 'i') { + doInventory(); + } else if ((tolower(buffer[0]) == 'g') + || !strcasecmp(buffer, "n") || !strcasecmp(buffer, "s") + || !strcasecmp(buffer, "e") || !strcasecmp(buffer, "w") + || !strcasecmp(buffer, "u") || !strcasecmp(buffer, "d") + || !strcasecmp(buffer, "north") || !strcasecmp(buffer, "south") + || !strcasecmp(buffer, "east") || !strcasecmp(buffer, "west") + || !strcasecmp(buffer, "up") || !strcasecmp(buffer, "down")) { + doGo(); + } else if (tolower(buffer[0]) == 'l') { + doLook(); + } else if (tolower(buffer[0]) == 't') { + doTake(); + } else if (tolower(buffer[0]) == 'e') { + doExamine(); + } else if (tolower(buffer[0]) == 'u') { + doUse(); + } else if (tolower(buffer[0]) == 'd') { + doDrop(); + } else if (tolower(buffer[0]) == 'q') { + doQuit(); + } else if (!strcasecmp(buffer, "xyzzy")) { + printf("Nice try, but that won't work here.\n"); + } else { + printf("I don't understand. Try 'help'.\n"); + } + + /* Handle special actions. */ + doActions(); + } + + printf("Game over after %d turns.\n", turnsPlayed); + printf("%s", "Do you want to play again (y/n)? "); + fgets(buffer, sizeof(buffer)-1, stdin); + if (tolower(buffer[0]) == 'n') { + break; + } + } + return 0; +} diff --git a/presets/apple2/yum.c b/presets/apple2/yum.c new file mode 100644 index 00000000..4bc42b2d --- /dev/null +++ b/presets/apple2/yum.c @@ -0,0 +1,1186 @@ +/* + * YUM + * + * Jeff Tranter + * + * Written in standard C but designed to run on the Apple Replica 1 + * or Apple II using the CC65 6502 assembler. + * + * Copyright 2012-2015 Jeff Tranter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License four the specific language governing permissions and + * limitations under the License. + * + * TODO: + * - optimize code size (e.g. use char instead of int) + * - make computer player smarter + * + * Revision History: + * + * Version Date Comments + * ------- ---- -------- + * 0.0 25 Jul 2012 Started coding + * 0.1 31 Jul 2012 First release. + * 0.2 02 Sep 2015 Apple II improvements. + * + */ + +#include +#include +#include +#include +#include +#include +#ifdef __CC65__ +#include +#endif +#ifndef __CC65__ +#include +#endif + +/* + +YUM - a variation of the game Yahtzee + +Up to 3 players (set at compile time, limited by screen size). +Players can be the computer. + +*/ + +/* CONSTANTS */ + +/* Sentinel value indicating that a category has not been played yet. */ +#define UNSET -1 + +/* Maximum number of players */ +#define MAXPLAYERS 3 + +/* Number of categories in score. */ +#define MAXCATEGORY 15 + +/* Number of dice. */ +#define MAXDICE 5 + +/* Number of rounds. */ +#define MAXROUNDS 12 + +/* Number of dice rolls. */ +#define MAXROLLS 3 + +/* VARIABLES */ + +/* Number of human players. */ +int numHumanPlayers; + +/* Number of computer players. */ +int numComputerPlayers; + +/* Current round. */ +int currentRound; + +/* Current player. */ +int player; + +/* Current dice roll number (1-3). */ +int roll; + +/* Seed value for random numbers. */ +int randomSeed; + +/* Returns true if a given player is a computer. */ +bool isComputerPlayer[MAXPLAYERS]; + +/* Names of each player. */ +char playerName[MAXPLAYERS][10]; + +/* 2D array of values given player number and category. */ +int scoreSheet[MAXPLAYERS][MAXCATEGORY]; + +/* Current dice for player. */ +int dice[MAXDICE]; + +/* General purpose buffer for user input. */ +char buffer[40]; + +/* Names of computer players. */ +char *computerNames[MAXPLAYERS] = { "Apple 1", "Apple 2", "Woz" }; + +/* Names of categories. */ +char *labels[MAXCATEGORY] = { "1's", "2's", "3's", "4's", "5's", "6's", "Sub-total", "Bonus", "Low Straight", "High Straight", "Low Score", "High Score", "Full House", "YUM", "Total" }; + +/* FUNCTIONS */ + +/* Mark all dice to be rolled. */ +void markAllDiceToBeRolled() +{ + int p; + + for (p = 0; p < MAXDICE; p++) { + dice[p] = UNSET; + } +} + +/* + * Initialize score table to initial values for start of a game. Don't + * clear player names since we may be starting a new game with the + * same players. + */ +void initialize() +{ + int p, c; + + for (p = 0; p < MAXPLAYERS; p++) { + for (c = 0; c < MAXCATEGORY; c++) { + scoreSheet[p][c] = UNSET; + } + } + + /* Initialize all dice to UNSET state too. */ + markAllDiceToBeRolled(); +} + +/* Clear the screen. */ +void clearScreen() +{ +#if defined(__APPLE2__) || defined(__C64__) + clrscr(); +#else + int i; + + for (i = 0; i < 24; ++i) + printf("\n"); +#endif +} + +/* Print a string, wait for user to press enter, then continue. */ +void pressEnter(char *s) +{ + + /* Default string is printed if s is 0. */ + if (s == 0) { + printf("\nPress to continue"); + } else { + printf("%s", s); + } + +#ifdef __CC65__ + /* On CC65 platform use keyPressed() routine and use this to set the random seed. */ + +#if defined(__APPLE2__) || defined(__C64__) + while (!kbhit()) { + randomSeed++; + } + cgetc(); +#else + while (!keypressed()) { + randomSeed++; + } + readkey(); +#endif + printf("\n"); +#else + fgets(buffer, sizeof(buffer)-1, stdin); +#endif +} + +/* Print a score value as a number. If set to UNSET, display blanks. */ +void printField(int i) +{ + if (i == UNSET) { + printf(" "); + } else { + printf("%-8d", i); + } +} + +/* Print a numeric row of the score card. */ +void printRow(char *label, int row) +{ + int p; + + printf("%-15s", label); + for (p = 0; p < numHumanPlayers + numComputerPlayers; p++) { + printField(scoreSheet[p][row]); + } + printf("\n"); +} + +/* Update scores after a turn, i.e. recalculate totals. */ +void updateScore() +{ + int p, i, total; + + for (p = 0; p < numHumanPlayers + numComputerPlayers; p++) { + + /* Calculate sub-total. */ + total = 0; + for (i = 0; i <= 5 ; i++) { + if (scoreSheet[p][i] != -1) { + total += scoreSheet[p][i]; + } + } + scoreSheet[p][6] = total; + + /* Calculate bonus. */ + if (scoreSheet[p][6] >= 63) { + scoreSheet[p][7] = 25; + } else { + scoreSheet[p][7] = 0; + } + + /* Calculate total. */ + total = 0; + for (i = 6; i <= 13 ; i++) { + if (scoreSheet[p][i] != -1) { + total += scoreSheet[p][i]; + } + } + scoreSheet[p][14] = total; + } +} + +/* Display the current score card. Displays final results if all categories are played. */ +void displayScore() +{ + int i; + + printf("Score after %d of 12 rounds:\n\n", currentRound); + printf("Roll "); + for (i = 0; i < numHumanPlayers + numComputerPlayers; i++) { + printf("%-8s", playerName[i]); + } + printf("\n"); + + for (i = 0; i < MAXCATEGORY; i++) { + printRow(labels[i], i); + } +} + +/* Display help information. Needs to fit on 40 char by 22 line screen. */ +void displayHelp() +{ + printf("%s", + "This is a computer version of the game\n" + "YUM, similar to games known as Yahtzee,\n" + "Yacht and Generala. Each player rolls\n" + "five dice up to three times and then\n" + "applies the dice toward a category to\n" + "claim points. The game has 12 rounds\n" + "during which each player attempts to\n" + "claim the most points in each category.\n" + "\n" + "The winner is the person scoring the\n" + "most points at the end of the game.\n" + "\n" + "This version supports up to three\n" + "players of which any can be human or\n" + "computer players.\n"); + + pressEnter(0); + clearScreen(); + + printf("%s", + "Categories are as follows:\n\n" + "1's through 6's - dice of same type\n" + "Low Straight (15) - 1 2 3 4 5\n" + "High Straight (20) - 2 3 4 5 6\n" + "Low Score - total 21 or more\n" + "High Score - total 22 or more\n" + "Full House (25) - 3 of a kind and pair\n" + "YUM (30) - 5 dice the same\n\n" + "Bonus of 25 points if upper section\n" + "is 63 or more.\n\n"); +} + +/* Return location of number i in dice array. */ +int find(int die) +{ + int i; + + for (i = 0; i < MAXDICE; i++) { + if (dice[i] == die) + return i; + } + return UNSET; +} + +/* Return number of dice that are n. */ +int numberOf(int n) +{ + int i; + int sum = 0; + + for (i = 0; i < MAXDICE; i++) { + if (dice[i] == n) { + sum += 1; + } + } + return sum; +} + +/* + * Return if dice form a low straight. + * The dice must be sorted in order from low to high. + */ +bool haveLowStraight() +{ + int i; + + for (i = 0; i < MAXDICE; i++) { + if (dice[i] != i + 1) { + return false; + } + } + return true; +} + +/* + * Return if dice form a high straight. + * The dice must be sorted in order from low to high. + */ +bool haveHighStraight() +{ + int i; + + for (i = 0; i < MAXDICE; i++) { + if (dice[i] != i + 2) { + return false; + } + } + return true; +} + +/* Return sum of dice. */ +int sum() +{ + int i; + int sum = 0; + + for (i = 0; i < MAXDICE; i++) { + sum += dice[i]; + } + return sum; +} + +/* Return if dice contains number i. */ +bool contains(int die) +{ + int i; + + for (i = 0; i < MAXDICE; i++) { + if (dice[i] == die) + return true; + } + return false; +} + +/* Return if dice form a full house. Dice must be sorted in order. */ +bool haveFullHouse() +{ + if ((dice[0] == dice[1]) && (dice[1] == dice[2]) && (dice[3] == dice[4])) + return true; + if ((dice[0] == dice[1]) && (dice[2] == dice[3]) && (dice[3] == dice[4])) + return true; + return false; +} + +/* Return if dice form a Yum. */ +bool haveYum() +{ + int i; + + for (i = 1; i < MAXDICE; i++) { + if (dice[i] != dice[0]) { + return false; + } + } + return true; +} + +/* Determine if we have four the same. If so, return the number we have and set d to the dice that is wrong. */ +int haveFourTheSame(int *d) +{ + int i, j; + + for (i = 1; i <= 6; i++) { + if (numberOf(i) == 4) { + /* Found 4 the same. */ + for (j = 0; j < MAXDICE; j++) { + /* Find the one that doesn't match. */ + if (dice[j] != i) { + *d = j; + return i; + } + } + } + } + + /* We don't have 4 the same. */ + return 0; +} + +/* Determine if we have three the same. If so, return the number we have. */ +int haveThreeTheSame() +{ + int i; + + for (i = 1; i <= 6; i++) { + if (numberOf(i) == 3) { + return i; + } + } + + /* We don't have 3 the same. */ + return 0; +} +/* Determine if we have two the same. If so, return the number we have. */ +int haveTwoTheSame() +{ + int i; + + for (i = 1; i <= 6; i++) { + if (numberOf(i) == 2) { + return i; + } + } + + /* We don't have 2 the same. */ + return 0; +} + +/* + * Determine if we almost have a full house. Returns true if so, and + * sets d to the index of dice that is wrong. e.g. for 22335 would + * return true and 4. + */ +bool possibleFullHouse(int *d) +{ + /* Three possibilities: ijjkk iijkk iijjk */ + + if ((dice[1] == dice[2]) && (dice[3] == dice[4])) { + *d = 0; + return true; + } else if ((dice[0] == dice[1]) && (dice[3] == dice[4])) { + *d = 2; + return true; + } else if ((dice[0] == dice[1]) && (dice[2] == dice[3])) { + *d = 4; + return true; + } + + return false; +} + +/* + * Determine if we almost have a high straight. Returns true if so, and + * sets d to the index of dice that is wrong. e.g. for 23356 would + * return true and 2. + */ +bool possibleHighStraight(int *d) +{ + int i; + int count = 0; + + /* See if each value from 2 to 6 appears. */ + for (i = 2; i <= 6; i++) { + if (contains(i)) { + count += 1; + } + } + + if (count == 4) { + /* We have a possible low straight. Now which dice is wrong? Either one that occurs twice or is a 1. */ + for (i = 0; i < MAXDICE; i++) { + if ((dice[i] == 1) || (numberOf(dice[i]) == 2)) { + *d = i; + return true; + } + } + } + return false; +} + +/* Same as above but for low straight, i.e. 12345 */ +bool possibleLowStraight(int *d) +{ + int i; + int count = 0; + + /* See if each value from 1 to 5 appears. */ + for (i = 1; i <= 5; i++) { + if (contains(i)) { + count += 1; + } + } + + if (count == 4) { + /* We have a possible low straight. Now which dice is wrong? Either one that occurs twice or is a 6. */ + for (i = 0; i < MAXDICE; i++) { + if ((dice[i] == 6) || (numberOf(dice[i]) == 2)) { + *d = i; + return true; + } + } + } + return false; +} + +/* Return if we have three of a kind. If so, set the dice that are not the same to UNSET. */ +bool handleThreeOfAKind() +{ + int i, kind; + + for (i = 6; i > 0; i--) { + if (numberOf(i) == 3) { + kind = i; + for (i = 0; i < MAXDICE; i++) { + if (dice[i] != kind) { + dice[i] = UNSET; + } + } + return true; + } + } + return false; +} + +/* Return if we have two of a kind. If so, set the dice that are not the same to UNSET. */ +bool handleTwoOfAKind() +{ + int i, kind; + + for (i = 6; i > 0; i--) { + if (numberOf(i) == 2) { + kind = i; + for (i = 0; i < MAXDICE; i++) { + if (dice[i] != kind) { + dice[i] = UNSET; + } + } + return true; + } + } + return false; +} + +/* Keep the single highest die in a category we have not used. If none, return false. */ +bool keepHighest() +{ + int i, kind; + + for (i = MAXDICE - 1; i >= 0; i--) { + if (scoreSheet[player][i] == UNSET) { + kind = i - 1; + for (i = 0; i < MAXDICE; i++) { + if (dice[i] != kind) { + dice[i] = UNSET; + } + } + return true; + } + } + return false; +} + +/* Display dice being kept and being rolled again. */ +void displayDiceRolledAgain() +{ + int i; + + printf("%s keeps:", playerName[player]); + + for (i = 0; i < MAXDICE; i++) { + if (dice[i] != UNSET) + printf(" %d", dice[i]); + } + + printf("\n"); +} + +/* Display a string and get an input string. */ +char *promptString(char *string) +{ + printf("%s?", string); + fgets(buffer, sizeof(buffer)-1, stdin); + buffer[strlen(buffer)-1] = '\0'; /* Remove newline at end of string */ + return buffer; +} + +/* Display a string and prompt for a number. Number must be in range + * min through max. Returns numeric value. + */ +int promptNumber(char *string, int min, int max) +{ + int val = 0; + char *endptr = 0; + + while (true) { + printf("%s (%d-%d)?", string, min, max); + fgets(buffer, sizeof(buffer)-1, stdin); + errno = 0; + val = strtol(buffer, &endptr, 10); + if ((errno == 0) /*&& (endptr != buffer)*/ && (val >= min) && (val <= max)) + return val; + } +} + +/* + * Ask number and names of players. Sets values of numPlayers, + * isComputerPlayer, and playerName. + */ +void setPlayers() +{ + int i; + + numHumanPlayers = promptNumber("How many human players", 0, MAXPLAYERS); + if (numHumanPlayers < MAXPLAYERS) { + numComputerPlayers = promptNumber("How many computer players", (numHumanPlayers == 0) ? 1 : 0, MAXPLAYERS - numHumanPlayers); + } else { + numComputerPlayers = 0; + } + + for (i = 0; i < numHumanPlayers; i++) { + snprintf(buffer, sizeof(buffer), "Name of player %d", i+1); + strncpy(playerName[i], promptString(buffer), sizeof(playerName[i]) - 1); + playerName[i][strlen(playerName[i])] = 0; /* May need to add terminating null */ + isComputerPlayer[i] = false; + } + + for (i = numHumanPlayers; i < numHumanPlayers + numComputerPlayers; i++) { + strcpy(playerName[i], computerNames[i - numHumanPlayers]); + isComputerPlayer[i] = true; + } +} + +/* Display a string and prompt for Y or N. Return boolean value. */ +bool promptYesNo(char *string) +{ + while (true) { + printf("%s (y/n)?", string); + fgets(buffer, sizeof(buffer)-1, stdin); + if (toupper(buffer[0]) == 'Y') + return true; + if (toupper(buffer[0]) == 'N') + return false; + } +} + +/* Compare function for sorting. */ +int compare(const void *i, const void *j) +{ + return *(int *)(i) - *(int *)(j); +} + +/* Sort dice. */ +void sortDice() +{ + qsort(dice, sizeof(dice) / sizeof(dice[0]), sizeof(dice[0]), compare); +} + +/* Display what dice user has. */ +void displayDice() +{ + int i; + + for (i = 0; i < MAXDICE; i++) { + printf(" %d", dice[i]); + } + printf("\n"); +} + +/* Ask what what dice to roll again. Return false if does not want to roll any. */ +int askPlayerDiceToRollAgain() +{ + int i; + bool valid; + int tempDice[MAXDICE]; + + /* Make a copy of the dice in case we have to undo changes after an error. */ + for (i = 0; i < MAXDICE; i++) { + tempDice[i] = dice[i]; + } + + while (true) { + + printf("Enter dice to roll again or\nD for dice or S for score:"); + fgets(buffer, sizeof(buffer)-1, stdin); + buffer[strlen(buffer)-1] = '\0'; /* Remove newline at end of string */ + + /* Entering 'S' will display the current score. Useful if you can't remember what categories you used. */ + if (toupper(buffer[0]) == 'S') { + displayScore(); + continue; + } + + /* Entering 'D' will display the dice. Useful if it scrolled off the screen. */ + if (toupper(buffer[0]) == 'D') { + displayDice(); + continue; + } + + /* If empty string, no dice to roll again and we return with false status. */ + if (strlen(buffer) == 0) { + return false; + } + + valid = true; + /* First validate the input line. */ + for (i = 0; i < strlen(buffer); i++) { + /* Validate character. */ + if ((buffer[i] != ' ') && (buffer[i] != ',') && ((buffer[i] < '1') || (buffer[i] > '6'))) { + printf("Invalid input: '%c', Try again.\n", buffer[i]); + valid = false; + break; + } + } + + /* Try again if not valid. */ + if (!valid) { + continue; + } + + /* Now examine the input line */ + for (i = 0; i < strlen(buffer); i++) { + /* Skip any spaces or commas */ + if ((buffer[i] == ' ') || (buffer[i] == ',')) { + continue; + } + + /* Does it match a die we have? If so, unset it to mark it to be rolled again. */ + if (contains(buffer[i] - '0')) { + dice[find(buffer[i] - '0')] = UNSET; + } else { + printf("You don't have a '%c', Try again.\n", buffer[i]); + valid = false; + /* Revert the dice to the original saved values since we may have changed it before the error was detected. */ + for (i = 0; i < MAXDICE; i++) { + dice[i] = tempDice[i]; + } + break; + } + } + + /* Try again if not valid. */ + if (!valid) { + continue; + } + + return true; + } +} + +/* Generate random number from low and high inclusive, e.g. randomNumber(1, 6) for a die. Calls rand(). */ +int randomNumber(int low, int high) +{ + return rand() % high + low; +} + +/* Roll 1 die. */ +int rollDie() +{ + return randomNumber(1, 6); +} + +/* Roll the dice. Only some dice need to be rolled, the ones that are set to UNSET. */ +void rollDice() +{ + int i; + + for (i = 0; i < MAXDICE; i++) { + if (dice[i] == UNSET) { + dice[i] = randomNumber(1, 6); + } + } +} + +/* Play a category. */ +void playCategory(int category) +{ + if (scoreSheet[player][category] != UNSET) { + printf("Internal error: Tried to play a category that was already selected!\n"); + return; + } + + switch (category) { + case 0: case 1: case 2: case 3: case 4: case 5: + /* Score is number of the dice times the die value. */ + scoreSheet[player][category] = numberOf(category + 1) * (category + 1); + break; + case 8: /* Low straight. */ + scoreSheet[player][category] = haveLowStraight() ? 15 : 0; + break; + case 9: /* High straight. */ + scoreSheet[player][category] = haveHighStraight() ? 20 : 0; + break; + case 10: /* Low score. Must be 21 or more and less than high score. */ + scoreSheet[player][category] = (sum() >= 21) ? sum() : 0; + if ((sum() >= 21) && ((scoreSheet[player][10] == UNSET) || (sum() < scoreSheet[player][11]))) { + scoreSheet[player][category] = sum(); + } else { + scoreSheet[player][category] = 0; + } + break; + case 11: /* High score. Must be 22 or more and more than low score. */ + if ((sum() >= 22) && (sum() > scoreSheet[player][10])) { + scoreSheet[player][category] = sum(); + } else { + scoreSheet[player][category] = 0; + } + break; + case 12: /* Full House. */ + scoreSheet[player][category] = haveFullHouse() ? 25 : 0; + break; + case 13: /* YUM. */ + scoreSheet[player][category] = haveYum() ? 30 : 0; + break; + } +} + +/* Ask what category to claim (only show possible ones). */ +int humanPickCategory() +{ + int category; + char buffer[50]; + + printf("\n"); + + for (category = 0; category < MAXCATEGORY; category++) { + /* Some categories need to be skipped. */ + if ((category == 6) || (category == 7) || (category == 14)) { + continue; + } + + if (scoreSheet[player][category] == UNSET) { + printf("%2d - %s\n", category + 1, labels[category]); + } + } + + while (true) { + snprintf(buffer, sizeof(buffer), "\n%s, What category do\nyou want to claim?", playerName[player]); + category = promptNumber(buffer, 1, MAXCATEGORY-1) - 1; + if (scoreSheet[player][category] != UNSET) { + printf("You already used that category.\nTry again.\n"); + } else { + break; + } + } + + return category; +} + +/* Display winner. */ +void displayWinner() +{ + int max = UNSET; + int winner = UNSET; + int p; + + for (p = 0; p < numHumanPlayers + numComputerPlayers; p++) { + if (scoreSheet[p][14] > max) { + max = scoreSheet[p][14]; + winner = p; + } + } + + printf("\n"); + + /* Display the winner. */ + if (numHumanPlayers + numComputerPlayers == 3) { + if ((scoreSheet[0][14] == scoreSheet[1][14]) && (scoreSheet[1][14] == scoreSheet[2][14])) { + printf("It was a three way tie!\n"); + } else if ((scoreSheet[0][14] == scoreSheet[1][14]) && (scoreSheet[0][14] == max)) { + printf("It was a tie between %s and %s\n", playerName[0], playerName[1]); + } else if ((scoreSheet[1][14] == scoreSheet[2][14]) && (scoreSheet[1][14] == max)) { + printf("It was a tie between %s and %s\n", playerName[1], playerName[2]); + } else if ((scoreSheet[0][14] == scoreSheet[2][14]) && (scoreSheet[0][14] == max)) { + printf("It was a tie between %s and %s\n", playerName[0], playerName[2]); + } else { + printf("The winner is %s.\n", playerName[winner]); + } + } + + if (numHumanPlayers + numComputerPlayers == 2) { + if (scoreSheet[0][14] == scoreSheet[1][14]) { + printf("It was a tie between %s and %s\n", playerName[0], playerName[1]); + } else { + printf("The winner is %s.\n", playerName[winner]); + } + } + + /* Display player's scores. */ + for (p = 0; p < numHumanPlayers + numComputerPlayers; p++) { + printf("%s has %d points.\n", playerName[p], scoreSheet[p][14]); + } +} + +/* + * Computer decides what dice to roll again (by setting them to + * UNSET). Returns false if does not want to roll any. +*/ +bool askComputerDiceToRollAgain() +{ + int n, d; + + /* If we have YUM and have not claimed it, don't roll any. */ + if (haveYum() && scoreSheet[player][13] == UNSET) { + return false; + } + + /* If we have all the same and have not claimed that category don't roll any. */ + if (haveYum() && scoreSheet[player][dice[0] - 1] == UNSET) { + return false; + } + + /* If we have a full house and have not claimed that category don't roll any. */ + if (haveFullHouse() && scoreSheet[player][12] == UNSET) { + return false; + } + + /* If we have a high straight and have not claimed that category don't roll any. */ + if (haveHighStraight() && scoreSheet[player][9] == UNSET) { + return false; + } + + /* If we have a low straight and have not claimed that category don't roll any. */ + if (haveLowStraight() && scoreSheet[player][8] == UNSET) { + return false; + } + + /* If we have 4 the same and have not claimed that category then roll the remaining die. */ + if ((n = haveFourTheSame(&d)) && scoreSheet[player][dice[n - 1]] == UNSET) { + dice[d] = UNSET; + return true; + } + + /* If we almost have a full house and have not claimed that category then roll the remaining die. */ + if (possibleFullHouse(&d) && scoreSheet[player][12] == UNSET) { + dice[d] = UNSET; + return true; + } + + /* If we almost have a high straight and have not claimed that category then roll the remaining die. */ + if (possibleHighStraight(&d) && scoreSheet[player][9] == UNSET) { + dice[d] = UNSET; + return true; + } + + /* If we almost have a low straight and have not claimed that category then roll the remaining die. */ + if (possibleLowStraight(&d) && scoreSheet[player][8] == UNSET) { + dice[d] = UNSET; + return true; + } + + /* If we have 3 the same, roll the remaining dice. */ + if (handleThreeOfAKind()) { + return true; + } + + /* If we have 2 the same, roll the remaining dice. */ + if (handleTwoOfAKind()) { + return true; + } + + /* Keep the 1 highest dice in a category we have not completed. */ + if (keepHighest()) { + return true; + } + + /* If all else fails, roll them all again */ + markAllDiceToBeRolled(); + return true; +} + +/* Computer decides what category to play. */ +int computerPickCategory() +{ + int n, d; + + /* Try for YUM. */ + if (haveYum() && scoreSheet[player][13] == UNSET) { + return 13; + } + + /* Try all the same. */ + if (haveYum() && scoreSheet[player][dice[0] - 1] == UNSET) { + return dice[0] - 1; + } + + /* Try full house. */ + if (haveFullHouse() && scoreSheet[player][12] == UNSET) { + return 12; + } + + /* Try high straight. */ + if (haveHighStraight() && scoreSheet[player][9] == UNSET) { + return 9; + } + + /* Try low straight. */ + if (haveLowStraight() && scoreSheet[player][8] == UNSET) { + return 8; + } + + /* Try 4 the same. */ + if ((n = haveFourTheSame(&d)) && scoreSheet[player][n - 1] == UNSET) { + return n - 1; + } + + /* Try 3 the same. */ + if ((n = haveThreeTheSame()) && scoreSheet[player][n - 1] == UNSET) { + return n - 1; + } + + /* Try high score. Must be 22 or more. */ + if ((sum() >= 22) && (scoreSheet[player][11] == UNSET) && (sum() > scoreSheet[player][10])) { + return 11; + } + + /* Try low score. Must be 21 or more. */ + if ((sum() >= 21) && (scoreSheet[player][10] == UNSET) && ((scoreSheet[player][11] == UNSET) || (sum() < scoreSheet[player][11]))) { + return 10; + } + + /* Try the highest 2 the same. */ + if ((n = haveTwoTheSame()) && scoreSheet[player][n - 1] == UNSET) { + return n - 1; + } + + /* Try the highest 1 the same. */ + for (d = MAXDICE - 1; d >= 0; d--) { + if (scoreSheet[player][dice[d] - 1] == UNSET) { + return dice[d] - 1; + } + } + + /* Throw away the lowest unused category. */ + for (d = 0; d < MAXCATEGORY; d++) { + if (scoreSheet[player][d] == UNSET) { + return d; + } + } + + return 0; +} + +/* Set random seed for the rand() function. */ +void setRandomSeed() +{ +#ifdef __CC65__ + + /* On embedded CC65 systems like the Replica use time taken for key to be pressed. */ + srand(randomSeed); + +#else + + /* On desktop systems use system time as random seed. */ + srand(time(0)); + +#endif +} + +/* Main program. */ +int main(void) +{ + initialize(); + clearScreen(); + + printf("%s", + "# # # # # #\n" + " # # # # ## ##\n" + " # # # # # # # #\n" + " # # # # # #\n" + " # # # # #\n" + " # # # # #\n" + " # ##### # #\n" + "\n\nWelcome to YUM!\n"); + + if (promptYesNo("Do you want instructions")) { + clearScreen(); + displayHelp(); + } + + setPlayers(); + + while (true) { + + pressEnter("Press to start the game"); + setRandomSeed(); + + for (currentRound = 1; currentRound <= MAXROUNDS; currentRound++) { + for (player = 0; player < numHumanPlayers + numComputerPlayers; player++) { + + clearScreen(); + snprintf(buffer, sizeof(buffer), "%s's turn. Press to roll", playerName[player]); + pressEnter(buffer); + markAllDiceToBeRolled(); + + for (roll = 1; roll <= MAXROLLS; roll++) { + bool ret; + + rollDice(); + sortDice(); + if (roll == 1) { + printf("First roll is:"); + } else if (roll == 2) { + printf("Second roll is:"); + } else { + printf("Last roll is:"); + } + displayDice(); + if (roll < 3) { + if (isComputerPlayer[player]) { + ret = askComputerDiceToRollAgain(); + displayDiceRolledAgain(); + } else { + ret = askPlayerDiceToRollAgain(); + } + /* Player wants to roll again? */ + if (ret == false) { + break; + } + } + } + + if (isComputerPlayer[player]) { + int c; + c = computerPickCategory(); + printf("%s plays %s\n", playerName[player], labels[c]); + playCategory(c); + pressEnter(0); + } else { + playCategory(humanPickCategory()); + } + + } + clearScreen(); + updateScore(); + displayScore(); + pressEnter(0); + } + + displayWinner(); + + pressEnter(0); + clearScreen(); + + if (!promptYesNo("Would you like to play again")) { + break; + } + + if (!promptYesNo("Same players as last time")) { + setPlayers(); + } + + initialize(); + } + return 0; +} diff --git a/src/platform/apple2.ts b/src/platform/apple2.ts index e28c3537..7ebf10ab 100644 --- a/src/platform/apple2.ts +++ b/src/platform/apple2.ts @@ -10,6 +10,8 @@ const APPLE2_PRESETS = [ {id:'Eliza.c', name:'Eliza'}, {id:'siegegame.c', name:'Siege Game'}, {id:'cosmic.c', name:'Cosmic Impalas'}, + {id:'farmhouse.c', name:"Farmhouse Adventure"}, + {id:'yum.c', name:"Yum Dice Game"}, {id:'hgrtest.a', name:"HGR Test (ASM)"}, {id:'conway.a', name:"Conway's Game of Life (ASM)"}, {id:'lz4fh.a', name:"LZ4FH Graphics Compression (ASM)"},