1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-12-01 13:50:30 +00:00
8bitworkshop/presets/apple2/farmhouse.c
2019-09-08 16:42:32 -05:00

834 lines
22 KiB
C

/*
*
* The Abandoned Farm House Adventure
*
* Jeff Tranter <tranter@pobox.com>
*
* 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 <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __CC65__
#include <conio.h>
#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 <object>\nexamine <object>\ntake <object>\ndrop <object>\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 <tranter@pobox.com>\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;
}