Compare commits

..

No commits in common. "master" and "1.17" have entirely different histories.
master ... 1.17

32 changed files with 4024 additions and 4822 deletions

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: (C) Eric S. Raymond # SPDX-FileCopyrightText: Eric S. Raymond
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
advent advent
*.gcda *.gcda

View file

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: (C) Eric S. Raymond # SPDX-FileCopyrightText: Eric S. Raymond
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
stages: stages:
- ci-build - ci-build

View file

@ -1,4 +1,4 @@
#SPDX-FileCopyrightText: (C) Eric S. Raymond #SPDX-FileCopyrightText: Eric S. Raymond
#SPDX-License-Identifier: BSD-2-Clause #SPDX-License-Identifier: BSD-2-Clause
extralines=""" extralines="""
<p>There is a <a href="http://esr.gitlab.io/open-adventure/coverage/">code coverage analysis</a> and a <a href="http://esr.gitlab.io/open-adventure/coverage/adventure.yaml.html">symbol coverage analysis</p> <p>There is a <a href="http://esr.gitlab.io/open-adventure/coverage/">code coverage analysis</a> and a <a href="http://esr.gitlab.io/open-adventure/coverage/adventure.yaml.html">symbol coverage analysis</p>

View file

@ -1,6 +1,6 @@
# This image is built by the Gitlab CI pipeline to be used in subsequent # This image is built by the Gitlab CI pipeline to be used in subsequent
# pipeline steps. # pipeline steps.
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
FROM ubuntu FROM ubuntu
@ -9,4 +9,4 @@ FROM ubuntu
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update RUN apt-get update
RUN apt-get install --yes --no-install-recommends make gcc libedit-dev libasan6 libubsan1 python3 python3-yaml lcov asciidoctor libxslt1.1 pkg-config docbook-xml xsltproc RUN apt-get install --yes --no-install-recommends make gcc-10 libedit-dev libasan6 libubsan1 python3 python3-yaml lcov asciidoc libxslt1.1 pkg-config docbook-xml xsltproc

View file

@ -1,5 +1,5 @@
= Installing Open Adventure = = Installing Open Adventure =
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
// SPDX-License-Identifier: CC-BY-4.0 // SPDX-License-Identifier: CC-BY-4.0
Installation now requires Python3 due to a security issue Installation now requires Python3 due to a security issue
@ -21,7 +21,3 @@ You can also use pip to install PyYAML: `pip3 install PyYAML`.
4. Optionally run a regression test on the code with `make check`. 4. Optionally run a regression test on the code with `make check`.
5. Run `./advent` to play. 5. Run `./advent` to play.
6. If you want to buld the documentation you will need asciidoctor.
7. Running the regression tests requires batchspell

View file

@ -1,6 +1,6 @@
# Makefile for the open-source release of adventure 2.5 # Makefile for the open-source release of adventure 2.5
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
# To build with save/resume disabled, pass CFLAGS="-DADVENT_NOSAVE" # To build with save/resume disabled, pass CFLAGS="-DADVENT_NOSAVE"
@ -12,7 +12,7 @@ VERS=$(shell sed -n <NEWS.adoc '/^[0-9]/s/:.*//p' | head -1)
.PHONY: check coverage .PHONY: check coverage
CC?=gcc CC?=gcc
CCFLAGS+=-std=c99 -Wall -Wextra -D_DEFAULT_SOURCE -DVERSION=\"$(VERS)\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all $(CFLAGS) -g $(EXTRA) CCFLAGS+=-std=c99 -D_DEFAULT_SOURCE -DVERSION=\"$(VERS)\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all $(CFLAGS) -g $(EXTRA)
LIBS=$(shell pkg-config --libs libedit) LIBS=$(shell pkg-config --libs libedit)
INC+=$(shell pkg-config --cflags libedit) INC+=$(shell pkg-config --cflags libedit)
@ -65,23 +65,9 @@ clean:
cheat: $(CHEAT_OBJS) dungeon.o cheat: $(CHEAT_OBJS) dungeon.o
$(CC) $(CCFLAGS) $(DBX) -o cheat $(CHEAT_OBJS) dungeon.o $(LDFLAGS) $(LIBS) $(CC) $(CCFLAGS) $(DBX) -o cheat $(CHEAT_OBJS) dungeon.o $(LDFLAGS) $(LIBS)
CSUPPRESSIONS = --suppress=missingIncludeSystem --suppress=invalidscanf check: advent cheat
cppcheck:
@-cppcheck -I. --quiet --template gcc -UOBJECT_SET_SEEN --enable=all $(CSUPPRESSIONS) *.[ch]
pylint:
@-pylint --score=n *.py */*.py
check: advent cheat pylint cppcheck spellcheck
cd tests; $(MAKE) --quiet cd tests; $(MAKE) --quiet
spellcheck:
@batchspell adventure.yaml advent.adoc
reflow:
@clang-format --style="{IndentWidth: 8, UseTab: ForIndentation}" -i $$(find . -name "*.[ch]")
@black --quiet *.py
# Requires gcov, lcov, libasan6, and libubsan1 # Requires gcov, lcov, libasan6, and libubsan1
# The last two are Ubuntu names, might vary on other distributions. # The last two are Ubuntu names, might vary on other distributions.
# After this, run your browser on coverage/open-adventure/index.html # After this, run your browser on coverage/open-adventure/index.html
@ -90,16 +76,15 @@ reflow:
coverage: clean debug coverage: clean debug
cd tests; $(MAKE) coverage --quiet cd tests; $(MAKE) coverage --quiet
# Note: to suppress the footers with timestamps being generated in HTML, .SUFFIXES: .adoc .html .6
# we use "-a nofooter".
# To debug asciidoc problems, you may need to run "xmllint --nonet --noout --valid"
# on the intermediate XML that throws an error.
.SUFFIXES: .html .adoc .6
# Requires asciidoc and xsltproc/docbook stylesheets.
.adoc.6: .adoc.6:
asciidoctor -D. -a nofooter -b manpage $< a2x --doctype manpage --format manpage $<
.adoc.html: .adoc.html:
asciidoctor -D. -a nofooter -a webfonts! $< asciidoc $<
.adoc:
asciidoc $<
html: advent.html history.html hints.html html: advent.html history.html hints.html
@ -115,6 +100,9 @@ advent-$(VERS).tar.gz: $(SOURCES) $(DOCS)
(tar -T MANIFEST -czvf advent-$(VERS).tar.gz) (tar -T MANIFEST -czvf advent-$(VERS).tar.gz)
@(rm advent-$(VERS)) @(rm advent-$(VERS))
indent:
astyle -n -A3 --pad-header --min-conditional-indent=1 --pad-oper *.c
release: advent-$(VERS).tar.gz advent.html history.html hints.html notes.html release: advent-$(VERS).tar.gz advent.html history.html hints.html notes.html
shipper version=$(VERS) | sh -e -x shipper version=$(VERS) | sh -e -x
@ -159,3 +147,9 @@ debug: CCFLAGS += -fsanitize=address
debug: CCFLAGS += -fsanitize=undefined debug: CCFLAGS += -fsanitize=undefined
debug: linty debug: linty
CSUPPRESSIONS = --suppress=missingIncludeSystem --suppress=invalidscanf
cppcheck:
cppcheck -I. --template gcc --enable=all $(CSUPPRESSIONS) *.[ch]
pylint:
@pylint --score=n *.py */*.py

View file

@ -1,16 +1,7 @@
= Open Adventure project news = = Open Adventure project news =
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
// SPDX-License-Identifier: CC-BY-4.0 // SPDX-License-Identifier: CC-BY-4.0
1.20: 2024-09-23::
Make oldstyle correctly suppress line editing.
1.19: 2024-06-27::
Ensore that the KNIVES_VANISH message can't issue twice.
1.18: 2024-02-15::
Bring the manual page fully up to date.
1.17: 2024-01-02:: 1.17: 2024-01-02::
Saying Z'ZZZ at reservoir no longer causes the waters to part and crash. Saying Z'ZZZ at reservoir no longer causes the waters to part and crash.

View file

@ -1,5 +1,5 @@
= README for Open Adventure = = README for Open Adventure =
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
// SPDX-License-Identifier: CC-BY-4.0 // SPDX-License-Identifier: CC-BY-4.0
If you are reading this anywhere but at http://www.catb.org/~esr/open-adventure If you are reading this anywhere but at http://www.catb.org/~esr/open-adventure

3035
actions.c

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,8 @@
= advent(6) = = advent(6) =
:doctype: manpage :doctype: manpage
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
// SPDX-License-Identifier: CC-BY-4.0 // SPDX-License-Identifier: CC-BY-4.0
// batchspell: add advent logfile savefile roleplaying Gillogly PDP Ctrl-D
// batchspell: add EOF autosave endianness wumpus zork nethack
== NAME == == NAME ==
advent - Colossal Cave Adventure advent - Colossal Cave Adventure
@ -23,9 +20,9 @@ adventure". To learn more about the changes since the 350-point
original, type 'news' at the command prompt. original, type 'news' at the command prompt.
There is an 'adventure' in the BSD games package that is a C port by There is an 'adventure' in the BSD games package that is a C port by
Jim Gillogly of the 1977 version. To avoid a name collision, this game Jim Gillogly of the Don Woods's 1977 version of this game. To avoid a name
builds as 'advent', reflecting the fact that the PDP-10 on which the collision, this game builds as 'advent', reflecting the fact that the
game originally ran limited filenames to 6 characters. PDP-10 on which the game originally ran limited filenames to 6 characters.
This version is released as open source with the permission and This version is released as open source with the permission and
encouragement of the original authors. encouragement of the original authors.
@ -37,8 +34,7 @@ command history.
Some minor bugs and message typos have been fixed. Otherwise, the Some minor bugs and message typos have been fixed. Otherwise, the
"version" command is almost the only way to tell you're not running "version" command is almost the only way to tell you're not running
Don's 1977 version until you get to the new cave sections added for Don's 1977 version.
2.5.
To exit the game, type Ctrl-D (EOF). To exit the game, type Ctrl-D (EOF).
@ -48,9 +44,9 @@ There have been no gameplay changes.
-l:: Log commands to specified file. -l:: Log commands to specified file.
-r:: Restore game from specified save file -r:: Restore game from specified file
-a:: Load from specified save file and autosave to it on exit or signal. -a:: Load from specified file and autosave to it on exit or signal.
-o:: Old-style. Reverts some minor cosmetic fixes in game -o:: Old-style. Reverts some minor cosmetic fixes in game
messages. Restores original interface, no prompt or line editing. messages. Restores original interface, no prompt or line editing.
@ -63,9 +59,9 @@ argument of '-' is taken as a directive to read from standard input.
== BUGS == == BUGS ==
The binary save file format is fragile, dependent on your machine's The binary save file format is fragile, dependent on your machine word
endianness, and unlikely to survive through version bumps. There are size and endianness, and unlikely to survive through version bumps. There
version and endianness checks when attempting to restore from a save. is a version check.
The input parser was the first attempt *ever* at natural-language The input parser was the first attempt *ever* at natural-language
parsing in a game and has some known deficiencies. While later text parsing in a game and has some known deficiencies. While later text

View file

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
[Desktop Entry] [Desktop Entry]
Type=Application Type=Application

429
advent.h
View file

@ -1,14 +1,15 @@
/* /*
* Dungeon types and macros. * Dungeon types and macros.
* *
* SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods
* SPDX-FileCopyrightText: 2017 by Eric S. Raymond
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <inttypes.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <inttypes.h>
#include "dungeon.h" #include "dungeon.h"
@ -18,35 +19,36 @@
#define LCG_C 221587L #define LCG_C 221587L
#define LCG_M 1048576L #define LCG_M 1048576L
#define LINESIZE 1024 #define LINESIZE 1024
#define TOKLEN 5 // # outputting characters in a token */ #define TOKLEN 5 // # outputting characters in a token */
#define PIRATE NDWARVES // must be NDWARVES-1 when zero-origin #define NDWARVES 6 // number of dwarves
#define DALTLC LOC_NUGGET // alternate dwarf location #define PIRATE NDWARVES // must be NDWARVES-1 when zero-origin
#define INVLIMIT 7 // inventory limit (# of objects) #define DALTLC LOC_NUGGET // alternate dwarf location
#define INTRANSITIVE -1 // illegal object number #define INVLIMIT 7 // inventory limit (# of objects)
#define GAMELIMIT 330 // base limit of turns #define INTRANSITIVE -1 // illegal object number
#define NOVICELIMIT 1000 // limit of turns for novice #define GAMELIMIT 330 // base limit of turns
#define WARNTIME 30 // late game starts at game.limit-this #define NOVICELIMIT 1000 // limit of turns for novice
#define FLASHTIME 50 // turns from first warning till blinding flash #define WARNTIME 30 // late game starts at game.limit-this
#define PANICTIME 15 // time left after closing #define FLASHTIME 50 // turns from first warning till blinding flash
#define BATTERYLIFE 2500 // turn limit increment from batteries #define PANICTIME 15 // time left after closing
#define WORD_NOT_FOUND \ #define BATTERYLIFE 2500 // turn limit increment from batteries
-1 // "Word not found" flag value for the vocab hash functions. #define WORD_NOT_FOUND -1 // "Word not found" flag value for the vocab hash functions.
#define WORD_EMPTY 0 // "Word empty" flag value for the vocab hash functions #define WORD_EMPTY 0 // "Word empty" flag value for the vocab hash functions
#define PIT_KILL_PROB 35 // Percentage probability of dying from fall in pit. #define PIT_KILL_PROB 35 // Percentage probability of dying from fall in pit.
#define CARRIED -1 // Player is toting it #define CARRIED -1 // Player is toting it
#define READ_MODE "rb" // b is not needed for POSIX but harmless #define READ_MODE "rb" // b is not needed for POSIX but harmless
#define WRITE_MODE "wb" // b is not needed for POSIX but harmless #define WRITE_MODE "wb" // b is not needed for POSIX but harmless
/* Special object-state values - integers > 0 are object-specific */ /* Special object-state values - integers > 0 are object-specific */
#define STATE_NOTFOUND -1 // 'Not found" state of treasures #define STATE_NOTFOUND -1 // 'Not found" state of treasures
#define STATE_FOUND 0 // After discovered, before messed with #define STATE_FOUND 0 // After discovered, before messed with
#define STATE_IN_CAVITY 1 // State value common to all gemstones #define STATE_IN_CAVITY 1 // State value common to all gemstones
/* Special fixed object-state values - integers > 0 are location */ /* Special fixed object-state values - integers > 0 are location */
#define IS_FIXED -1 #define IS_FIXED -1
#define IS_FREE 0 #define IS_FREE 0
#ifndef FOUNDBOOL
/* (ESR) It is fitting that translation of the original ADVENT should /* (ESR) It is fitting that translation of the original ADVENT should
* have left us a maze of twisty little conditionals that resists all * have left us a maze of twisty little conditionals that resists all
* understanding. Setting and use of what is now the per-object state * understanding. Setting and use of what is now the per-object state
@ -60,112 +62,118 @@
* STATE_NOTFOUND is only set on treasures. Non-treasures start the * STATE_NOTFOUND is only set on treasures. Non-treasures start the
* game in STATE_FOUND. * game in STATE_FOUND.
* *
* PROP_STASHIFY is supposed to map a state property value to a * PROP_STASHED is supposed to map a state property value to a
* negative range, where the object cannot be picked up but the value * negative range, where the object cannot be picked up but the value
* can be recovered later. Various objects get this property when * can be recovered later. Various objects get this property when
* the cave starts to close. Only seems to be significant for the bird * the cave starts to close. Only seems to be significant for the bird
* and readable objects, notably the clam/oyster - but the code around * and readable objects, notably the clam/oyster - but the code around
* those tests is difficult to read. * those test is difficult to read.
*
* All tests of the prop member are done with either these macros or ==.
*/ */
#define OBJECT_IS_NOTFOUND(obj) (game.objects[obj].prop == STATE_NOTFOUND) #define PROP_STASHIFY(n) (-1 - (n))
#define OBJECT_IS_FOUND(obj) (game.objects[obj].prop == STATE_FOUND) #define PROP_IS_STASHED(obj) (game.objects[obj].prop < STATE_NOTFOUND)
#define OBJECT_SET_FOUND(obj) (game.objects[obj].prop = STATE_FOUND) #define PROP_IS_NOTFOUND(obj) (game.objects[obj].prop == STATE_NOTFOUND)
#define OBJECT_SET_NOT_FOUND(obj) (game.objects[obj].prop = STATE_NOTFOUND) #define PROP_IS_FOUND(obj) (game.objects[obj].prop == STATE_FOUND)
#define OBJECT_IS_NOTFOUND2(g, o) (g.objects[o].prop == STATE_NOTFOUND) #define PROP_IS_STASHED_OR_UNSEEN(obj) (game.objects[obj].prop < 0)
#define PROP_IS_INVALID(val) (val < -MAX_STATE - 1 || val > MAX_STATE) #define PROP_SET_FOUND(obj) (game.objects[obj].prop = STATE_FOUND)
#define PROP_STASHIFY(n) (-1 - (n)) #define PROP_SET_NOT_FOUND(obj) (game.objects[obj].prop = STATE_NOTFOUND)
#define OBJECT_STASHIFY(obj, pval) game.objects[obj].prop = PROP_STASHIFY(pval) #define PROP_IS_NOTFOUND2(g, o) (g.objects[o].prop == STATE_NOTFOUND)
#define OBJECT_IS_STASHED(obj) (game.objects[obj].prop < STATE_NOTFOUND) #define PROP_IS_INVALID(val) (val < -MAX_STATE - 1 || val > MAX_STATE)
#define OBJECT_STATE_EQUALS(obj, pval) \ #else
((game.objects[obj].prop == pval) || \ /* (ESR) Only the boldest of adventurers will explore here. This
(game.objects[obj].prop == PROP_STASHIFY(pval))) * alternate set of definitions for the macros above was an attempt to
* break from out of the state encoding a per-object "found" member
* telling whether or not the player has seen the object.
*
* What's broken when you try to use thus is
* PROP_IS_STASHED_OR_UNSEEN. The symptom is game.tally getting
* decremented on non-treasures.
*/
#define PROP_STASHIFY(n) (-(n))
#define PROP_IS_STASHED(obj) (game.objects[obj].prop < 0)
#define PROP_IS_NOTFOUND(obj) (!game.objects[obj].found)
#define PROP_IS_FOUND(obj) (game.objects[obj].found && game.objects[obj].prop == 0)
#define PROP_IS_STASHED_OR_UNSEEN(obj) (!game.objects[obj].found || game.objects[obj].prop < 0)
#define PROP_SET_FOUND(obj) do {game.objects[obj].found = true; game.objects[obj].prop = STATE_FOUND;} while(0)
#define PROP_SET_NOT_FOUND(obj) game.objects[obj].found = false
#define PROP_IS_NOTFOUND2(g, o) (!g.objects[o].found)
#define PROP_IS_INVALID(val) (val < -MAX_STATE || val > MAX_STATE)
#define PROP_SET_SEEN(obj) game.objects[object].found = true
#endif
#define PROP_STASHED(obj) PROP_STASHIFY(game.objects[obj].prop)
#define PROMPT "> " #define PROMPT "> "
/* /*
* DESTROY(N) = Get rid of an item by putting it in LOC_NOWHERE * DESTROY(N) = Get rid of an item by putting it in LOC_NOWHERE
* MOD(N,M) = Arithmetic modulus * MOD(N,M) = Arithmetic modulus
* TOTING(OBJ) = true if the OBJ is being carried * TOTING(OBJ) = true if the OBJ is being carried
* AT(OBJ) = true if on either side of two-placed object * AT(OBJ) = true if on either side of two-placed object
* HERE(OBJ) = true if the OBJ is at "LOC" (or is being carried) * HERE(OBJ) = true if the OBJ is at "LOC" (or is being carried)
* CNDBIT(L,N) = true if COND(L) has bit n set (bit 0 is units bit) * CNDBIT(L,N) = true if COND(L) has bit n set (bit 0 is units bit)
* LIQUID() = object number of liquid in bottle * LIQUID() = object number of liquid in bottle
* LIQLOC(LOC) = object number of liquid (if any) at LOC * LIQLOC(LOC) = object number of liquid (if any) at LOC
* FORCED(LOC) = true if LOC moves without asking for input (COND=2) * FORCED(LOC) = true if LOC moves without asking for input (COND=2)
* IS_DARK_HERE() = true if location "LOC" is dark * DARK(LOC) = true if location "LOC" is dark
* PCT(N) = true N% of the time (N integer from 0 to 100) * PCT(N) = true N% of the time (N integer from 0 to 100)
* GSTONE(OBJ) = true if OBJ is a gemstone * GSTONE(OBJ) = true if OBJ is a gemstone
* FOREST(LOC) = true if LOC is part of the forest * FOREST(LOC) = true if LOC is part of the forest
* OUTSIDE(LOC) = true if location not in the cave * OUTSID(LOC) = true if location not in the cave
* INSIDE(LOC) = true if location is in the cave or the building at the * INSIDE(LOC) = true if location is in the cave or the building at the beginning of the game
* beginning of the game * INDEEP(LOC) = true if location is in the Hall of Mists or deeper
* INDEEP(LOC) = true if location is in the Hall of Mists or deeper * BUG(X) = report bug and exit
* BUG(X) = report bug and exit
*/ */
#define DESTROY(N) move(N, LOC_NOWHERE) #define DESTROY(N) move(N, LOC_NOWHERE)
#define MOD(N, M) ((N) % (M)) #define MOD(N,M) ((N) % (M))
#define TOTING(OBJ) (game.objects[OBJ].place == CARRIED) #define TOTING(OBJ) (game.objects[OBJ].place == CARRIED)
#define AT(OBJ) \ #define AT(OBJ) (game.objects[OBJ].place == game.loc || game.objects[OBJ].fixed == game.loc)
(game.objects[OBJ].place == game.loc || \ #define HERE(OBJ) (AT(OBJ) || TOTING(OBJ))
game.objects[OBJ].fixed == game.loc) #define CNDBIT(L,N) (tstbit(conditions[L],N))
#define HERE(OBJ) (AT(OBJ) || TOTING(OBJ)) #define LIQUID() (game.objects[BOTTLE].prop == WATER_BOTTLE? WATER : game.objects[BOTTLE].prop == OIL_BOTTLE ? OIL : NO_OBJECT )
#define CNDBIT(L, N) (tstbit(conditions[L], N)) #define LIQLOC(LOC) (CNDBIT((LOC),COND_FLUID)? CNDBIT((LOC),COND_OILY) ? OIL : WATER : NO_OBJECT)
#define LIQUID() \ #define FORCED(LOC) CNDBIT(LOC, COND_FORCED)
(game.objects[BOTTLE].prop == WATER_BOTTLE ? WATER \ #define DARK(DUMMY) (!CNDBIT(game.loc,COND_LIT) && (game.objects[LAMP].prop == LAMP_DARK || !HERE(LAMP)))
: game.objects[BOTTLE].prop == OIL_BOTTLE ? OIL \ #define PCT(N) (randrange(100) < (N))
: NO_OBJECT) #define GSTONE(OBJ) ((OBJ) == EMERALD || (OBJ) == RUBY || (OBJ) == AMBER || (OBJ) == SAPPH)
#define LIQLOC(LOC) \ #define FOREST(LOC) CNDBIT(LOC, COND_FOREST)
(CNDBIT((LOC), COND_FLUID) ? CNDBIT((LOC), COND_OILY) ? OIL : WATER \ #define OUTSID(LOC) (CNDBIT(LOC, COND_ABOVE) || FOREST(LOC))
: NO_OBJECT) #define INSIDE(LOC) (!OUTSID(LOC) || LOC == LOC_BUILDING)
#define FORCED(LOC) CNDBIT(LOC, COND_FORCED) #define INDEEP(LOC) CNDBIT((LOC),COND_DEEP)
#define IS_DARK_HERE() \ #define BUG(x) bug(x, #x)
(!CNDBIT(game.loc, COND_LIT) && \
(game.objects[LAMP].prop == LAMP_DARK || !HERE(LAMP)))
#define PCT(N) (randrange(100) < (N))
#define GSTONE(OBJ) \
((OBJ) == EMERALD || (OBJ) == RUBY || (OBJ) == AMBER || (OBJ) == SAPPH)
#define FOREST(LOC) CNDBIT(LOC, COND_FOREST)
#define OUTSIDE(LOC) (CNDBIT(LOC, COND_ABOVE) || FOREST(LOC))
#define INSIDE(LOC) (!OUTSIDE(LOC) || LOC == LOC_BUILDING)
#define INDEEP(LOC) CNDBIT((LOC), COND_DEEP)
#define BUG(x) bug(x, #x)
enum bugtype { enum bugtype {
SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST, SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST,
VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3, VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3,
INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST, INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST,
TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST, TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST,
CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION, CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION,
LOCATION_HAS_NO_TRAVEL_ENTRIES, LOCATION_HAS_NO_TRAVEL_ENTRIES,
HINT_NUMBER_EXCEEDS_GOTO_LIST, HINT_NUMBER_EXCEEDS_GOTO_LIST,
SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN, SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN,
ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH, ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH,
}; };
enum speaktype { touch, look, hear, study, change }; enum speaktype {touch, look, hear, study, change};
enum termination { endgame, quitgame, scoregame }; enum termination {endgame, quitgame, scoregame};
enum speechpart { unknown, intransitive, transitive }; enum speechpart {unknown, intransitive, transitive};
typedef enum { NO_WORD_TYPE, MOTION, OBJECT, ACTION, NUMERIC } word_type_t; typedef enum {NO_WORD_TYPE, MOTION, OBJECT, ACTION, NUMERIC} word_type_t;
typedef enum scorebonus { none, splatter, defeat, victory } score_t; typedef enum scorebonus {none, splatter, defeat, victory} score_t;
/* Phase codes for action returns. /* Phase codes for action returns.
* These were at one time FORTRAN line numbers. * These were at one time FORTRAN line numbers.
*/ */
typedef enum { typedef enum {
GO_TERMINATE, GO_TERMINATE,
GO_MOVE, GO_MOVE,
GO_TOP, GO_TOP,
GO_CLEAROBJ, GO_CLEAROBJ,
GO_CHECKHINT, GO_CHECKHINT,
GO_WORD2, GO_WORD2,
GO_UNKNOWN, GO_UNKNOWN,
GO_DWARFWAKE, GO_DWARFWAKE,
} phase_codes_t; } phase_codes_t;
/* Use fixed-lwength types to make the save format moore portable */ /* Use fixed-lwength types to make the save format moore portable */
@ -177,72 +185,75 @@ typedef int32_t turn_t; // turn counter or threshold */
typedef int32_t bool32_t; // turn counter or threshold */ typedef int32_t bool32_t; // turn counter or threshold */
struct game_t { struct game_t {
int32_t lcg_x; int32_t lcg_x;
int32_t abbnum; // How often to print int descriptions int32_t abbnum; // How often to print int descriptions
score_t bonus; // What kind of finishing bonus we are getting score_t bonus; // What kind of finishing bonus we are getting
loc_t chloc; // pirate chest location loc_t chloc; // pirate chest location
loc_t chloc2; // pirate chest alternate location loc_t chloc2; // pirate chest alternate location
turn_t clock1; // # turns from finding last treasure to close turn_t clock1; // # turns from finding last treasure to close
turn_t clock2; // # turns from warning till blinding flash turn_t clock2; // # turns from warning till blinding flash
bool32_t clshnt; // has player read the clue in the endgame? bool32_t clshnt; // has player read the clue in the endgame?
bool32_t closed; // whether we're all the way closed bool32_t closed; // whether we're all the way closed
bool32_t closng; // whether it's closing time yet bool32_t closng; // whether it's closing time yet
bool32_t lmwarn; // has player been warned about lamp going dim? bool32_t lmwarn; // has player been warned about lamp going dim?
bool32_t novice; // asked for instructions at start-up? bool32_t novice; // asked for instructions at start-up?
bool32_t panic; // has player found out he's trapped? bool32_t panic; // has player found out he's trapped?
bool32_t wzdark; // whether the loc he's leaving was dark bool32_t wzdark; // whether the loc he's leaving was dark
bool32_t blooded; // has player drunk of dragon's blood? bool32_t blooded; // has player drunk of dragon's blood?
int32_t conds; // min value for cond[loc] if loc has any hints int32_t conds; // min value for cond[loc] if loc has any hints
int32_t detail; // level of detail in descriptions int32_t detail; // level of detail in descriptions
/* dflag controls the level of activation of dwarves: /* dflag controls the level of activation of dwarves:
* 0 No dwarf stuff yet (wait until reaches Hall Of Mists) * 0 No dwarf stuff yet (wait until reaches Hall Of Mists)
* 1 Reached Hall Of Mists, but hasn't met first dwarf * 1 Reached Hall Of Mists, but hasn't met first dwarf
* 2 Met 1t dwarf, others start moving, no knives thrown yet * 2 Met first dwarf, others start moving, no knives thrown yet
* 3 A knife has been thrown (first set always misses) 3+ * 3 A knife has been thrown (first set always misses)
* Dwarves are mad (increases their accuracy) */ * 3+ Dwarves are mad (increases their accuracy) */
int32_t dflag; int32_t dflag;
int32_t dkill; // dwarves killed int32_t dkill; // dwarves killed
int32_t dtotal; // total dwarves (including pirate) in loc int32_t dtotal; // total dwarves (including pirate) in loc
int32_t foobar; // progress in saying "FEE FIE FOE FOO". int32_t foobar; // progress in saying "FEE FIE FOE FOO".
int32_t holdng; // number of objects being carried int32_t holdng; // number of objects being carried
int32_t igo; // # uses of "go" instead of a direction int32_t igo; // # uses of "go" instead of a direction
int32_t iwest; // # times he's said "west" instead of "w" int32_t iwest; // # times he's said "west" instead of "w"
loc_t knfloc; // knife location; LOC_NOWERE if none, -1 after caveat loc_t knfloc; // knife location; LOC_NOWERE if none, -1 after caveat
turn_t limit; // lifetime of lamp turn_t limit; // lifetime of lamp
loc_t loc; // where player is now loc_t loc; // where player is now
loc_t newloc; // where player is going loc_t newloc; // where player is going
turn_t numdie; // number of times killed so far turn_t numdie; // number of times killed so far
loc_t oldloc; // where player was loc_t oldloc; // where player was
loc_t oldlc2; // where player was two moves ago loc_t oldlc2; // where player was two moves ago
obj_t oldobj; // last object player handled obj_t oldobj; // last object player handled
int32_t saved; // point penalty for saves int32_t saved; // point penalty for saves
int32_t tally; // count of treasures gained int32_t tally; // count of treasures gained
int32_t thresh; // current threshold for endgame scoring tier int32_t thresh; // current threshold for endgame scoring tier
bool32_t seenbigwords; // have we red the graffiti in the Giant's Room? bool32_t seenbigwords; // have we red the graffiti in the Giant's Room?
turn_t trnluz; // # points lost so far due to turns used turn_t trnluz; // # points lost so far due to turns used
turn_t turns; // counts commands given (ignores yes/no) turn_t turns; // counts commands given (ignores yes/no)
char zzword[TOKLEN + 1]; // randomly generated magic word from bird char zzword[TOKLEN + 1]; // randomly generated magic word from bird
struct { struct {
int32_t abbrev; // has location been seen? int32_t abbrev; // has location been seen?
int32_t atloc; // head of object linked list per location int32_t atloc; // head of object linked list per location
} locs[NLOCATIONS + 1]; } locs[NLOCATIONS + 1];
struct { struct {
int32_t seen; // true if dwarf has seen him int32_t seen; // true if dwarf has seen him
loc_t loc; // location of dwarves, initially hard-wired in loc_t loc; // location of dwarves, initially hard-wired in
loc_t oldloc; // prior loc of each dwarf, initially garbage loc_t oldloc; // prior loc of each dwarf, initially garbage
} dwarves[NDWARVES + 1]; } dwarves[NDWARVES + 1];
struct { struct {
loc_t fixed; // fixed location of object (if not IS_FREE) #ifdef FOUNDBOOL
int32_t prop; // object state bool32_t found; // has the location of this object been found?
loc_t place; // location of object #endif
} objects[NOBJECTS + 1]; loc_t fixed; // fixed location of object (if not IS_FREE)
struct { int32_t prop; // object state */
bool32_t used; // hints[i].used = true iff hint i has been used. loc_t place; // location of object
int32_t lc; // hints[i].lc = show int at LOC with cond bit i } objects[NOBJECTS + 1];
} hints[NHINTS]; struct {
obj_t link[NOBJECTS * 2 + 1]; // object-list links bool32_t used; // hints[i].used = true iff hint i has been used.
int32_t lc; // hints[i].lc = show int at LOC with cond bit i
} hints[NHINTS];
obj_t link[NOBJECTS * 2 + 1];// object-list links
}; };
/* /*
@ -250,64 +261,56 @@ struct game_t {
* This data is not saved in a saved game. * This data is not saved in a saved game.
*/ */
struct settings_t { struct settings_t {
FILE *logfp; FILE *logfp;
bool oldstyle; bool oldstyle;
bool prompt; bool prompt;
char **argv; char **argv;
int argc; int argc;
int optind; int optind;
FILE *scriptfp; FILE *scriptfp;
int debug; int debug;
}; };
typedef struct { typedef struct {
char raw[LINESIZE]; char raw[LINESIZE];
vocab_t id; vocab_t id;
word_type_t type; word_type_t type;
} command_word_t; } command_word_t;
typedef enum { typedef enum {EMPTY, RAW, TOKENIZED, GIVEN, PREPROCESSED, PROCESSING, EXECUTED} command_state_t;
EMPTY,
RAW,
TOKENIZED,
GIVEN,
PREPROCESSED,
PROCESSING,
EXECUTED
} command_state_t;
typedef struct { typedef struct {
enum speechpart part; enum speechpart part;
command_word_t word[2]; command_word_t word[2];
verb_t verb; verb_t verb;
obj_t obj; obj_t obj;
command_state_t state; command_state_t state;
} command_t; } command_t;
/* /*
* Bump on save format change. * Bump on save format change.
* *
* Note: Verify that the tests run clean before bumping this, then rebuild the * Note: Verify that the tests run clean before bumping this, then rebuild the check
* check files afterwards. Otherwise you will get a spurious failure due to the * files afterwards. Otherwise you will get a spurious failure due to the old version
* old version having been generated into a check file. * having been generated into a check file.
*/ */
#define SAVE_VERSION 31 #define SAVE_VERSION 31
/* /*
* Goes at start of file so saves can be identified by file(1) and the like. * Goes at start of file so saves can be identified by file(1) and the like.
*/ */
#define ADVENT_MAGIC "open-adventure\n" #define ADVENT_MAGIC "open-adventure\n"
/* /*
* If you change the first three members, the resume function may not properly * If you change the first three members, the resume function may not properly
* reject saves from older versions. Later members can change, but bump the * reject saves from older versions. Later members can change, but bump the version
* version when you do that. * when you do that.
*/ */
struct save_t { struct save_t {
char magic[sizeof(ADVENT_MAGIC)]; char magic[sizeof(ADVENT_MAGIC)];
int32_t version; int32_t version;
int32_t canary; int32_t canary;
struct game_t game; struct game_t game;
}; };
extern struct game_t game; extern struct game_t game;
@ -317,13 +320,13 @@ extern struct settings_t settings;
extern char *myreadline(const char *); extern char *myreadline(const char *);
extern bool get_command_input(command_t *); extern bool get_command_input(command_t *);
extern void clear_command(command_t *); extern void clear_command(command_t *);
extern void speak(const char *, ...); extern void speak(const char*, ...);
extern void sspeak(int msg, ...); extern void sspeak(int msg, ...);
extern void pspeak(vocab_t, enum speaktype, bool, int, ...); extern void pspeak(vocab_t, enum speaktype, bool, int, ...);
extern void rspeak(vocab_t, ...); extern void rspeak(vocab_t, ...);
extern void echo_input(FILE *, const char *, const char *); extern void echo_input(FILE*, const char*, const char*);
extern bool silent_yes_or_no(void); extern bool silent_yes_or_no(void);
extern bool yes_or_no(const char *, const char *, const char *); extern bool yes_or_no(const char*, const char*, const char*);
extern void juggle(obj_t); extern void juggle(obj_t);
extern void move(obj_t, loc_t); extern void move(obj_t, loc_t);
extern void put(obj_t, loc_t, int); extern void put(obj_t, loc_t, int);

View file

@ -1,10 +1,6 @@
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
# #
# batchspell: add XYZZY Twopit Bedquilt ne se sw nw dwarves dwarvish gouts
# batchspell: add Crowther add axe pinin Har har meself Hmmm Adventuredom
# batchspell: add Tsk gameplay bugfixes
#
# This YAML file gets processed into a collection of data structures and # This YAML file gets processed into a collection of data structures and
# variable initializers describing Colossal Cave. It replaces an ad-hoc # variable initializers describing Colossal Cave. It replaces an ad-hoc
# text database shipped with Adventure versions up to 2.5. The format # text database shipped with Adventure versions up to 2.5. The format
@ -14,7 +10,7 @@
# We define a bunch of YAML structures: # We define a bunch of YAML structures:
# #
# motions: Motion words, grouped into synonyms. The 'oldstyle' # motions: Motion words, grouped into synonyms. The 'oldstyle'
# attribute, if false, means that single-letter synonyms should not be # attribute, if false, means that single-letter synonyms should be
# accepted in oldstyle mode; it defaults to true. # accepted in oldstyle mode; it defaults to true.
# #
# actions: Action words, grouped into synonyms, and their corresponding # actions: Action words, grouped into synonyms, and their corresponding
@ -3436,7 +3432,7 @@ objects: !!omap
- 'There are a few recent issues of "Spelunker Today" magazine here.' - 'There are a few recent issues of "Spelunker Today" magazine here.'
texts: texts:
- |- - |-
I'm afraid the magazine is written in dwarvish. But penciled on one I'm afraid the magazine is written in dwarvish. But pencilled on one
cover you see, "Please leave the magazines at the construction site." cover you see, "Please leave the magazines at the construction site."
- DWARF: - DWARF:
words: ['dwarf', 'dwarv'] words: ['dwarf', 'dwarv']
@ -3944,13 +3940,11 @@ obituaries:
Oh dear, you seem to have gotten yourself killed. I might be able to Oh dear, you seem to have gotten yourself killed. I might be able to
help you out, but I've never really done this before. Do you want me help you out, but I've never really done this before. Do you want me
to try to reincarnate you? to try to reincarnate you?
# batchspell: add wr
yes_response: |- yes_response: |-
All right. But don't blame me if something goes wr...... All right. But don't blame me if something goes wr......
--- POOF!! --- --- POOF!! ---
You are engulfed in a cloud of orange smoke. Coughing and gasping, You are engulfed in a cloud of orange smoke. Coughing and gasping,
you emerge from the smoke and find.... you emerge from the smoke and find....
# batchspell: remove wr
- query: |- - query: |-
You clumsy oaf, you've done it again! I don't know how long I can You clumsy oaf, you've done it again! I don't know how long I can
keep this up. Do you want me to try reincarnating you again? keep this up. Do you want me to try reincarnating you again?
@ -4232,7 +4226,7 @@ actions: !!omap
message: |- message: |-
There is a puff of orange smoke; within it, fiery runes spell out: There is a puff of orange smoke; within it, fiery runes spell out:
Open Adventure %V - http://www.catb.org/esr/open-adventure/ \tOpen Adventure %V - http://www.catb.org/esr/open-adventure/
words: ['versi'] words: ['versi']
noaction: true noaction: true

155
cheat.c
View file

@ -4,93 +4,97 @@
* savefile(), so we know we're always outputting save files that advent * savefile(), so we know we're always outputting save files that advent
* can import. * can import.
* *
* SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods
* SPDX-FileCopyrightText: 2017 by Eric S. Raymond
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include "advent.h"
#include <editline/readline.h>
#include <getopt.h> #include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <editline/readline.h>
#include "advent.h"
int main(int argc, char *argv[]) { int main(int argc, char *argv[])
int ch; {
char *savefilename = NULL; int ch;
FILE *fp = NULL; char *savefilename = NULL;
FILE *fp = NULL;
// Initialize game variables // Initialize game variables
initialise(); initialise();
/* we're generating a saved game, so saved once by default, /* we're generating a saved game, so saved once by default,
* unless overridden with command-line options below. * unless overridden with command-line options below.
*/ */
game.saved = 1; game.saved = 1;
/* Options. */ /* Options. */
const char *opts = "d:l:s:t:v:o:"; const char* opts = "d:l:s:t:v:o:";
const char *usage = const char* usage = "Usage: %s [-d numdie] [-s numsaves] [-v version] -o savefilename \n"
"Usage: %s [-d numdie] [-s numsaves] [-v version] -o savefilename " " -d number of deaths. Signed integer.\n"
"\n" " -l lifetime of lamp in turns. Signed integer.\n"
" -d number of deaths. Signed integer.\n" " -s number of saves. Signed integer.\n"
" -l lifetime of lamp in turns. Signed integer.\n" " -t number of turns. Signed integer.\n"
" -s number of saves. Signed integer.\n" " -v version number of save format.\n"
" -t number of turns. Signed integer.\n" " -o required. File name of save game to write.\n";
" -v version number of save format.\n"
" -o required. File name of save game to write.\n";
while ((ch = getopt(argc, argv, opts)) != EOF) { while ((ch = getopt(argc, argv, opts)) != EOF) {
switch (ch) { switch (ch) {
case 'd': case 'd':
game.numdie = (turn_t)atoi(optarg); game.numdie = (turn_t)atoi(optarg);
printf("cheat: game.numdie = %d\n", game.numdie); printf("cheat: game.numdie = %d\n", game.numdie);
break; break;
case 'l': case 'l':
game.limit = (turn_t)atoi(optarg); game.limit = (turn_t)atoi(optarg);
printf("cheat: game.limit = %d\n", game.limit); printf("cheat: game.limit = %d\n", game.limit);
break; break;
case 's': case 's':
game.saved = (int)atoi(optarg); game.saved = (int)atoi(optarg);
printf("cheat: game.saved = %d\n", game.saved); printf("cheat: game.saved = %d\n", game.saved);
break; break;
case 't': case 't':
game.turns = (turn_t)atoi(optarg); game.turns = (turn_t)atoi(optarg);
printf("cheat: game.turns = %d\n", game.turns); printf("cheat: game.turns = %d\n", game.turns);
break; break;
case 'v': case 'v':
save.version = atoi(optarg); save.version = atoi(optarg);
printf("cheat: version = %d\n", save.version); printf("cheat: version = %d\n", save.version);
break; break;
case 'o': case 'o':
savefilename = optarg; savefilename = optarg;
break; break;
default: default:
fprintf(stderr, usage, argv[0]); fprintf(stderr,
exit(EXIT_FAILURE); usage, argv[0]);
break; exit(EXIT_FAILURE);
} break;
} }
}
// Save filename required; the point of cheat is to generate save file // Save filename required; the point of cheat is to generate save file
if (savefilename == NULL) { if (savefilename == NULL) {
fprintf(stderr, usage, argv[0]); fprintf(stderr,
fprintf(stderr, "ERROR: filename required\n"); usage, argv[0]);
exit(EXIT_FAILURE); fprintf(stderr,
} "ERROR: filename required\n");
exit(EXIT_FAILURE);
}
fp = fopen(savefilename, WRITE_MODE); fp = fopen(savefilename, WRITE_MODE);
if (fp == NULL) { if (fp == NULL) {
fprintf(stderr, "Can't open file %s. Exiting.\n", savefilename); fprintf(stderr,
exit(EXIT_FAILURE); "Can't open file %s. Exiting.\n", savefilename);
} exit(EXIT_FAILURE);
}
savefile(fp); savefile(fp);
fclose(fp); fclose(fp);
printf("cheat: %s created.\n", savefilename); printf("cheat: %s created.\n", savefilename);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
// LCOV_EXCL_START // LCOV_EXCL_START
@ -99,7 +103,12 @@ int main(int argc, char *argv[]) {
* See the actually useful version of this in main.c * See the actually useful version of this in main.c
*/ */
char *myreadline(const char *prompt) { return readline(prompt); } char *myreadline(const char *prompt)
{
return readline(prompt);
}
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
/* end */ /* end */

View file

@ -5,10 +5,11 @@ Package: open-adventure
Description: Colossal Cave Adventure, the 1995 430-point version. Description: Colossal Cave Adventure, the 1995 430-point version.
This is the last descendant of the original 1976 Colossal Cave Adventure This is the last descendant of the original 1976 Colossal Cave Adventure
worked on by the original authors - Crowther & Woods; it is shipped with worked on by the original authors - Crowther & Woods. It has sometimes
their permission and encouragement. It has sometimes been known as been known as Adventure 2.5. The original PDP-10 name 'advent' is used
Adventure 2.5. The original PDP-10 name 'advent' is used for the for the built program to avoid collision with the BSD Games version.
built program to avoid collision with the BSD Games version.
XBS-Destinations: mailto:ubuntu-devel-discuss@lists.ubuntu.com
Homepage: http://www.catb.org/~esr/open-adventure Homepage: http://www.catb.org/~esr/open-adventure

View file

@ -1,5 +1,5 @@
= Non-spoiler hints = = Non-spoiler hints =
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
// SPDX-License-Identifier: CC-BY-4.0 // SPDX-License-Identifier: CC-BY-4.0
Say the words you see. They can have interesting effects. Say the words you see. They can have interesting effects.

View file

@ -1,6 +1,6 @@
= A brief history of Colossal Cave Adventure = = A brief history of Colossal Cave Adventure =
by Eric S. Raymond by Eric S. Raymond
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
// SPDX-License-Identifier: CC-BY-4.0 // SPDX-License-Identifier: CC-BY-4.0
Adventure is the fons et origo of all later dungeon-crawling computer Adventure is the fons et origo of all later dungeon-crawling computer

142
init.c
View file

@ -1,96 +1,100 @@
/* /*
* Initialisation * Initialisation
* *
* SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods
* SPDX-FileCopyrightText: 2017 by Eric S. Raymond
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#include <assert.h>
#include "advent.h" #include "advent.h"
struct settings_t settings = {.logfp = NULL, .oldstyle = false, .prompt = true}; struct settings_t settings = {
.logfp = NULL,
.oldstyle = false,
.prompt = true
};
struct game_t game = { struct game_t game = {
/* Last dwarf is special (the pirate). He always starts at his /* Last dwarf is special (the pirate). He always starts at his
* chest's eventual location inside the maze. This loc is saved * chest's eventual location inside the maze. This loc is saved
* in chloc for ref. The dead end in the other maze has its * in chloc for ref. The dead end in the other maze has its
* loc stored in chloc2. */ * loc stored in chloc2. */
.chloc = LOC_MAZEEND12, .chloc2 = LOC_DEADEND13, .abbnum = 5, .chloc = LOC_MAZEEND12,
.clock1 = WARNTIME, .clock2 = FLASHTIME, .newloc = LOC_START, .chloc2 = LOC_DEADEND13,
.loc = LOC_START, .limit = GAMELIMIT, .foobar = WORD_EMPTY, .abbnum = 5,
.clock1 = WARNTIME,
.clock2 = FLASHTIME,
.newloc = LOC_START,
.loc = LOC_START,
.limit = GAMELIMIT,
.foobar = WORD_EMPTY,
}; };
int initialise(void) { int initialise(void)
if (settings.oldstyle) { {
printf("Initialising...\n"); if (settings.oldstyle)
} printf("Initialising...\n");
srand(time(NULL)); srand(time(NULL));
int seedval = (int)rand(); int seedval = (int)rand();
set_seed(seedval); set_seed(seedval);
for (int i = 1; i <= NDWARVES; i++) { assert(NDWARVES == NDWARFLOCS);
game.dwarves[i].loc = dwarflocs[i - 1]; for (int i = 1; i <= NDWARFLOCS; i++) {
} game.dwarves[i].loc = dwarflocs[i-1];
}
for (int i = 1; i <= NOBJECTS; i++) { for (int i = 1; i <= NOBJECTS; i++) {
game.objects[i].place = LOC_NOWHERE; game.objects[i].place = LOC_NOWHERE;
} }
for (int i = 1; i <= NLOCATIONS; i++) { for (int i = 1; i <= NLOCATIONS; i++) {
if (!(locations[i].description.big == 0 || tkey[i] == 0)) { if (!(locations[i].description.big == 0 || tkey[i] == 0)) {
int k = tkey[i]; int k = tkey[i];
if (travel[k].motion == HERE) { if (travel[k].motion == HERE)
conditions[i] |= (1 << COND_FORCED); conditions[i] |= (1 << COND_FORCED);
} }
} }
}
/* Set up the game.locs atloc and game.link arrays. /* Set up the game.locs atloc and game.link arrays.
* We'll use the DROP subroutine, which prefaces new objects on the * We'll use the DROP subroutine, which prefaces new objects on the
* lists. Since we want things in the other order, we'll run the * lists. Since we want things in the other order, we'll run the
* loop backwards. If the object is in two locs, we drop it twice. * loop backwards. If the object is in two locs, we drop it twice.
* Also, since two-placed objects are typically best described * Also, since two-placed objects are typically best described
* last, we'll drop them first. */ * last, we'll drop them first. */
for (int i = NOBJECTS; i >= 1; i--) { for (int i = NOBJECTS; i >= 1; i--) {
if (objects[i].fixd > 0) { if (objects[i].fixd > 0) {
drop(i + NOBJECTS, objects[i].fixd); drop(i + NOBJECTS, objects[i].fixd);
drop(i, objects[i].plac); drop(i, objects[i].plac);
} }
} }
for (int i = 1; i <= NOBJECTS; i++) { for (int i = 1; i <= NOBJECTS; i++) {
int k = NOBJECTS + 1 - i; int k = NOBJECTS + 1 - i;
game.objects[k].fixed = objects[k].fixd; game.objects[k].fixed = objects[k].fixd;
if (objects[k].plac != 0 && objects[k].fixd <= 0) { if (objects[k].plac != 0 && objects[k].fixd <= 0)
drop(k, objects[k].plac); drop(k, objects[k].plac);
} }
}
/* Treasure props are initially STATE_NOTFOUND, and are set to /* Treasure props are initially STATE_NOTFOUND, and are set to
* STATE_FOUND the first time they are described. game.tally * STATE_FOUND the first time they are described. game.tally
* keeps track of how many are not yet found, so we know when to * keeps track of how many are not yet found, so we know when to
* close the cave. * close the cave. */
* (ESR) Non-treasures are set to STATE_FOUND explicitly so we for (int treasure = 1; treasure <= NOBJECTS; treasure++) {
* don't rely on the value of uninitialized storage. This is to if (objects[treasure].is_treasure) {
* make translation to future languages easier. */ ++game.tally;
for (int object = 1; object <= NOBJECTS; object++) { if (objects[treasure].inventory != 0)
if (objects[object].is_treasure) { PROP_SET_NOT_FOUND(treasure);
++game.tally; }
if (objects[object].inventory != NULL) { }
OBJECT_SET_NOT_FOUND(object); game.conds = setbit(COND_HBASE);
}
} else {
OBJECT_SET_FOUND(object);
}
}
game.conds = setbit(COND_HBASE);
return seedval; return seedval;
} }

2548
main.c

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
""" """
This is the open-adventure dungeon generator. It consumes a YAML description of This is the open-adventure dungeon generator. It consumes a YAML description of

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
"""\ """\
usage: make_graph.py [-a] [-d] [-m] [-s] [-v] usage: make_graph.py [-a] [-d] [-m] [-s] [-v]

1240
misc.c

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
= Open Adventure Maintainer's Notes = = Open Adventure Maintainer's Notes =
by Eric S. Raymond by Eric S. Raymond
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
// SPDX-License-Identifier: CC-BY-4.0 // SPDX-License-Identifier: CC-BY-4.0
In which we explain what has been done to this code since Don Woods In which we explain what has been done to this code since Don Woods
@ -16,8 +16,8 @@ of Peje Nilsson in restructuring some particularly grotty gotos is
gratefully acknowledged. Petr Voropaev contributed fuzz testing and gratefully acknowledged. Petr Voropaev contributed fuzz testing and
code cleanups. Aaron Traas did a lot of painstaking work to improve code cleanups. Aaron Traas did a lot of painstaking work to improve
test coverage, and factored out the last handful of gotos. Ryan test coverage, and factored out the last handful of gotos. Ryan
Sarson nudged us into fixing a longstanding minor bug in the Sarson nudged us into fixing a longstannding minor bug in the
handling of incorrect magic-word sequences, handling of incorrect magic-word sequebcesm,
== Nomenclature == == Nomenclature ==
@ -75,15 +75,10 @@ Bug fixes:
* A few minor typos have been corrected: absence of capitalization on * A few minor typos have been corrected: absence of capitalization on
"Swiss" and "Persian", inconsistent spelling of "imbedded" vs. "embedded", "Swiss" and "Persian", inconsistent spelling of "imbedded" vs. "embedded",
"eying" for "eyeing", "thresholds" for "threshholds", "pencilled" "eying" for "eyeing", "thresholds" for "threshholds".
for "penciled".
* Under odd circumstances (dropping rug or vase outdoors) the game could * Under odd circumstances (dropping rug or vase outdoors) the game could
formerly say "floor" when it should say "ground" (or "dirt", or formerly say "floor" when it should say "ground" (or "dirt", or something).
something).
* The "knives vanish" message could formerly be emitted when "I see no
knife here." would be appropriate.
Enhancements: Enhancements:
@ -144,7 +139,7 @@ whitespace before processing.
A -r command-line option has been added. When it is given (with a file A -r command-line option has been added. When it is given (with a file
path argument) it is functionally equivalent to a RESTORE command. path argument) it is functionally equivalent to a RESTORE command.
An -a command-line option has been added (conditionally on An -a command-line option has been added (comditionally on
ADVENT_AUTOSAVE) for use in BBS door systems. When this option is ADVENT_AUTOSAVE) for use in BBS door systems. When this option is
given, the game roads from the specified filename argument on startup given, the game roads from the specified filename argument on startup
and saves to it on quit or a received signal. There is a new nmessage and saves to it on quit or a received signal. There is a new nmessage

View file

@ -4,264 +4,249 @@
* (ESR) This replaces a bunch of particularly nasty FORTRAN-derived code; * (ESR) This replaces a bunch of particularly nasty FORTRAN-derived code;
* see the history.adoc file in the source distribution for discussion. * see the history.adoc file in the source distribution for discussion.
* *
* SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods
* SPDX-FileCopyrightText: 2017 by Eric S. Raymond
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <ctype.h>
#include <inttypes.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h>
#include <time.h> #include <time.h>
#include <inttypes.h>
#include "advent.h" #include "advent.h"
#include "dungeon.h"
/* /*
* Use this to detect endianness mismatch. Can't be unchanged by byte-swapping. * Use this to detect endianness mismatch. Can't be unchanged by byte-swapping.
*/ */
#define ENDIAN_MAGIC 2317 #define ENDIAN_MAGIC 2317
struct save_t save; struct save_t save;
#define IGNORE(r) \ #define IGNORE(r) do{if (r){}}while(0)
do { \
if (r) { \
} \
} while (0)
int savefile(FILE *fp) { int savefile(FILE *fp)
/* Save game to file. No input or output from user. */ /* Save game to file. No input or output from user. */
memcpy(&save.magic, ADVENT_MAGIC, sizeof(ADVENT_MAGIC)); {
if (save.version == 0) { memcpy(&save.magic, ADVENT_MAGIC, sizeof(ADVENT_MAGIC));
save.version = SAVE_VERSION; if (save.version == 0)
} save.version = SAVE_VERSION;
if (save.canary == 0) { if (save.canary == 0)
save.canary = ENDIAN_MAGIC; save.canary = ENDIAN_MAGIC;
}
save.game = game; save.game = game;
IGNORE(fwrite(&save, sizeof(struct save_t), 1, fp)); IGNORE(fwrite(&save, sizeof(struct save_t), 1, fp));
return (0); return (0);
} }
/* Suspend and resume */ /* Suspend and resume */
static char *strip(char *name) { static char *strip(char *name)
// Trim leading whitespace {
while (isspace((unsigned char)*name)) { // Trim leading whitespace
name++; // LCOV_EXCL_LINE while(isspace((unsigned char)*name))
} name++; // LCOV_EXCL_LINE
if (*name != '\0') { if(*name != '\0') {
// Trim trailing whitespace; // Trim trailing whitespace;
// might be left there by autocomplete // might be left there by autocomplete
char *end = name + strlen(name) - 1; char *end = name + strlen(name) - 1;
while (end > name && isspace((unsigned char)*end)) { while(end > name && isspace((unsigned char)*end))
end--; end--;
} // Write new null terminator character
// Write new null terminator character end[1] = '\0';
end[1] = '\0'; }
}
return name; return name;
} }
int suspend(void) { int suspend(void)
/* Suspend. Offer to save things in a file, but charging {
* some points (so can't win by using saved games to retry /* Suspend. Offer to save things in a file, but charging
* battles or to start over after learning zzword). * some points (so can't win by using saved games to retry
* If ADVENT_NOSAVE is defined, gripe instead. */ * battles or to start over after learning zzword).
* If ADVENT_NOSAVE is defined, gripe instead. */
#if defined ADVENT_NOSAVE || defined ADVENT_AUTOSAVE #if defined ADVENT_NOSAVE || defined ADVENT_AUTOSAVE
rspeak(SAVERESUME_DISABLED); rspeak(SAVERESUME_DISABLED);
return GO_TOP; return GO_TOP;
#endif #endif
FILE *fp = NULL; FILE *fp = NULL;
rspeak(SUSPEND_WARNING); rspeak(SUSPEND_WARNING);
if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE], if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN]))
arbitrary_messages[OK_MAN], return GO_CLEAROBJ;
arbitrary_messages[OK_MAN])) { game.saved = game.saved + 5;
return GO_CLEAROBJ;
}
game.saved = game.saved + 5;
while (fp == NULL) { while (fp == NULL) {
char *name = myreadline("\nFile name: "); char* name = myreadline("\nFile name: ");
if (name == NULL) { if (name == NULL)
return GO_TOP; return GO_TOP;
} name = strip(name);
name = strip(name); if (strlen(name) == 0)
if (strlen(name) == 0) { return GO_TOP; // LCOV_EXCL_LINE
return GO_TOP; // LCOV_EXCL_LINE fp = fopen(strip(name), WRITE_MODE);
} if (fp == NULL)
fp = fopen(strip(name), WRITE_MODE); printf("Can't open file %s, try again.\n", name);
if (fp == NULL) { free(name);
printf("Can't open file %s, try again.\n", name); }
}
free(name);
}
savefile(fp); savefile(fp);
fclose(fp); fclose(fp);
rspeak(RESUME_HELP); rspeak(RESUME_HELP);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} }
int resume(void) { int resume(void)
/* Resume. Read a suspended game back from a file. {
* If ADVENT_NOSAVE is defined, gripe instead. */ /* Resume. Read a suspended game back from a file.
* If ADVENT_NOSAVE is defined, gripe instead. */
#if defined ADVENT_NOSAVE || defined ADVENT_AUTOSAVE #if defined ADVENT_NOSAVE || defined ADVENT_AUTOSAVE
rspeak(SAVERESUME_DISABLED); rspeak(SAVERESUME_DISABLED);
return GO_TOP; return GO_TOP;
#endif #endif
FILE *fp = NULL; FILE *fp = NULL;
if (game.loc != LOC_START || game.locs[LOC_START].abbrev != 1) { if (game.loc != LOC_START || game.locs[LOC_START].abbrev != 1) {
rspeak(RESUME_ABANDON); rspeak(RESUME_ABANDON);
if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE], if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN]))
arbitrary_messages[OK_MAN], return GO_CLEAROBJ;
arbitrary_messages[OK_MAN])) { }
return GO_CLEAROBJ;
}
}
while (fp == NULL) { while (fp == NULL) {
char *name = myreadline("\nFile name: "); char* name = myreadline("\nFile name: ");
if (name == NULL) { if (name == NULL)
return GO_TOP; return GO_TOP;
} name = strip(name);
name = strip(name); if (strlen(name) == 0)
if (strlen(name) == 0) { return GO_TOP; // LCOV_EXCL_LINE
return GO_TOP; // LCOV_EXCL_LINE fp = fopen(name, READ_MODE);
} if (fp == NULL)
fp = fopen(name, READ_MODE); printf("Can't open file %s, try again.\n", name);
if (fp == NULL) { free(name);
printf("Can't open file %s, try again.\n", name); }
}
free(name);
}
return restore(fp); return restore(fp);
} }
int restore(FILE *fp) { int restore(FILE* fp)
/* Read and restore game state from file, assuming {
* sane initial state. /* Read and restore game state from file, assuming
* If ADVENT_NOSAVE is defined, gripe instead. */ * sane initial state.
* If ADVENT_NOSAVE is defined, gripe instead. */
#ifdef ADVENT_NOSAVE #ifdef ADVENT_NOSAVE
rspeak(SAVERESUME_DISABLED); rspeak(SAVERESUME_DISABLED);
return GO_TOP; return GO_TOP;
#endif #endif
IGNORE(fread(&save, sizeof(struct save_t), 1, fp)); IGNORE(fread(&save, sizeof(struct save_t), 1, fp));
fclose(fp); fclose(fp);
if (memcmp(save.magic, ADVENT_MAGIC, sizeof(ADVENT_MAGIC)) != 0 || if (memcmp(save.magic, ADVENT_MAGIC, sizeof(ADVENT_MAGIC)) != 0 || save.canary != ENDIAN_MAGIC)
save.canary != ENDIAN_MAGIC) { rspeak(BAD_SAVE);
rspeak(BAD_SAVE); else if (save.version != SAVE_VERSION) {
} else if (save.version != SAVE_VERSION) { rspeak(VERSION_SKEW, save.version / 10, MOD(save.version, 10), SAVE_VERSION / 10, MOD(SAVE_VERSION, 10));
rspeak(VERSION_SKEW, save.version / 10, MOD(save.version, 10), } else if (!is_valid(save.game)) {
SAVE_VERSION / 10, MOD(SAVE_VERSION, 10)); rspeak(SAVE_TAMPERING);
} else if (!is_valid(save.game)) { exit(EXIT_SUCCESS);
rspeak(SAVE_TAMPERING); } else {
exit(EXIT_SUCCESS); game = save.game;
} else { }
game = save.game; return GO_TOP;
}
return GO_TOP;
} }
bool is_valid(struct game_t valgame) { bool is_valid(struct game_t valgame)
/* Save files can be roughly grouped into three groups: {
* With valid, reachable state, with valid, but unreachable /* Save files can be roughly grouped into three groups:
* state and with invalid state. We check that state is * With valid, reachable state, with valid, but unreachable
* valid: no states are outside minimal or maximal value * state and with invalid state. We check that state is
*/ * valid: no states are outside minimal or maximal value
*/
/* Prevent division by zero */ /* Prevent division by zero */
if (valgame.abbnum == 0) { if (valgame.abbnum == 0) {
return false; // LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE
} }
/* Check for RNG overflow. Truncate */ /* Check for RNG overflow. Truncate */
if (valgame.lcg_x >= LCG_M) { if (valgame.lcg_x >= LCG_M) {
return false; valgame.lcg_x %= LCG_M; // LCOV_EXCL_LINE
} }
/* Bounds check for locations */ /* Check for RNG underflow. Transpose */
if (valgame.chloc < -1 || valgame.chloc > NLOCATIONS || if (valgame.lcg_x < LCG_M) {
valgame.chloc2 < -1 || valgame.chloc2 > NLOCATIONS || valgame.lcg_x = LCG_M + (valgame.lcg_x % LCG_M);
valgame.loc < 0 || valgame.loc > NLOCATIONS || valgame.newloc < 0 || }
valgame.newloc > NLOCATIONS || valgame.oldloc < 0 ||
valgame.oldloc > NLOCATIONS || valgame.oldlc2 < 0 ||
valgame.oldlc2 > NLOCATIONS) {
return false; // LCOV_EXCL_LINE
}
/* Bounds check for location arrays */
for (int i = 0; i <= NDWARVES; i++) {
if (valgame.dwarves[i].loc < -1 ||
valgame.dwarves[i].loc > NLOCATIONS ||
valgame.dwarves[i].oldloc < -1 ||
valgame.dwarves[i].oldloc > NLOCATIONS) {
return false; // LCOV_EXCL_LINE
}
}
for (int i = 0; i <= NOBJECTS; i++) { /* Bounds check for locations */
if (valgame.objects[i].place < -1 || if ( valgame.chloc < -1 || valgame.chloc > NLOCATIONS ||
valgame.objects[i].place > NLOCATIONS || valgame.chloc2 < -1 || valgame.chloc2 > NLOCATIONS ||
valgame.objects[i].fixed < -1 || valgame.loc < 0 || valgame.loc > NLOCATIONS ||
valgame.objects[i].fixed > NLOCATIONS) { valgame.newloc < 0 || valgame.newloc > NLOCATIONS ||
return false; // LCOV_EXCL_LINE valgame.oldloc < 0 || valgame.oldloc > NLOCATIONS ||
} valgame.oldlc2 < 0 || valgame.oldlc2 > NLOCATIONS) {
} return false; // LCOV_EXCL_LINE
}
/* Bounds check for location arrays */
for (int i = 0; i <= NDWARVES; i++) {
if (valgame.dwarves[i].loc < -1 || valgame.dwarves[i].loc > NLOCATIONS ||
valgame.dwarves[i].oldloc < -1 || valgame.dwarves[i].oldloc > NLOCATIONS) {
return false; // LCOV_EXCL_LINE
}
}
/* Bounds check for dwarves */ for (int i = 0; i <= NOBJECTS; i++) {
if (valgame.dtotal < 0 || valgame.dtotal > NDWARVES || if (valgame.objects[i].place < -1 || valgame.objects[i].place > NLOCATIONS ||
valgame.dkill < 0 || valgame.dkill > NDWARVES) { valgame.objects[i].fixed < -1 || valgame.objects[i].fixed > NLOCATIONS) {
return false; // LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE
} }
}
/* Validate that we didn't die too many times in save */ /* Bounds check for dwarves */
if (valgame.numdie >= NDEATHS) { if (valgame.dtotal < 0 || valgame.dtotal > NDWARVES ||
return false; // LCOV_EXCL_LINE valgame.dkill < 0 || valgame.dkill > NDWARVES) {
} return false; // LCOV_EXCL_LINE
}
/* Recalculate tally, throw the towel if in disagreement */ /* Validate that we didn't die too many times in save */
int temp_tally = 0; if (valgame.numdie >= NDEATHS) {
for (int treasure = 1; treasure <= NOBJECTS; treasure++) { return false; // LCOV_EXCL_LINE
if (objects[treasure].is_treasure) { }
if (OBJECT_IS_NOTFOUND2(valgame, treasure)) {
++temp_tally;
}
}
}
if (temp_tally != valgame.tally) {
return false; // LCOV_EXCL_LINE
}
/* Check that properties of objects aren't beyond expected */ /* Recalculate tally, throw the towel if in disagreement */
for (obj_t obj = 0; obj <= NOBJECTS; obj++) { int temp_tally = 0;
if (PROP_IS_INVALID(valgame.objects[obj].prop)) { for (int treasure = 1; treasure <= NOBJECTS; treasure++) {
return false; // LCOV_EXCL_LINE if (objects[treasure].is_treasure) {
} if (PROP_IS_NOTFOUND2(valgame, treasure)) {
} ++temp_tally;
}
}
}
if (temp_tally != valgame.tally) {
return false; // LCOV_EXCL_LINE
}
/* Check that values in linked lists for objects in locations are inside /* Check that properties of objects aren't beyond expected */
* bounds */ for (obj_t obj = 0; obj <= NOBJECTS; obj++) {
for (loc_t loc = LOC_NOWHERE; loc <= NLOCATIONS; loc++) { if (PROP_IS_INVALID(valgame.objects[obj].prop)) {
if (valgame.locs[loc].atloc < NO_OBJECT || return false; // LCOV_EXCL_LINE
valgame.locs[loc].atloc > NOBJECTS * 2) { }
return false; // LCOV_EXCL_LINE }
}
}
for (obj_t obj = 0; obj <= NOBJECTS * 2; obj++) {
if (valgame.link[obj] < NO_OBJECT ||
valgame.link[obj] > NOBJECTS * 2) {
return false; // LCOV_EXCL_LINE
}
}
return true; /* Check that values in linked lists for objects in locations are inside bounds */
for (loc_t loc = LOC_NOWHERE; loc <= NLOCATIONS; loc++) {
if (valgame.locs[loc].atloc < NO_OBJECT || valgame.locs[loc].atloc > NOBJECTS * 2) {
return false; // LCOV_EXCL_LINE
}
}
for (obj_t obj = 0; obj <= NOBJECTS * 2; obj++ ) {
if (valgame.link[obj] < NO_OBJECT || valgame.link[obj] > NOBJECTS * 2) {
return false; // LCOV_EXCL_LINE
}
}
return true;
} }
/* end */ /* end */

257
score.c
View file

@ -1,162 +1,145 @@
/* /*
* Scoring and wrap-up. * Scoring and wrap-up.
* *
* SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods * SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods
* SPDX-FileCopyrightText: 2017 by Eric S. Raymond
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <stdlib.h>
#include "advent.h" #include "advent.h"
#include "dungeon.h" #include "dungeon.h"
#include <stdlib.h>
static int mxscor; /* ugh..the price for having score() not exit. */ static int mxscor; /* ugh..the price for having score() not exit. */
int score(enum termination mode) { int score(enum termination mode)
/* mode is 'scoregame' if scoring, 'quitgame' if quitting, 'endgame' if /* mode is 'scoregame' if scoring, 'quitgame' if quitting, 'endgame' if died
* died or won */ * or won */
int score = 0; {
int score = 0;
/* The present scoring algorithm is as follows: /* The present scoring algorithm is as follows:
* Objective: Points: Present total possible: * Objective: Points: Present total possible:
* Getting well into cave 25 25 * Getting well into cave 25 25
* Each treasure < chest 12 60 * Each treasure < chest 12 60
* Treasure chest itself 14 14 * Treasure chest itself 14 14
* Each treasure > chest 16 224 * Each treasure > chest 16 224
* Surviving (MAX-NUM)*10 30 * Surviving (MAX-NUM)*10 30
* Not quitting 4 4 * Not quitting 4 4
* Reaching "game.closng" 25 25 * Reaching "game.closng" 25 25
* "Closed": Quit/Killed 10 * "Closed": Quit/Killed 10
* Klutzed 25 * Klutzed 25
* Wrong way 30 * Wrong way 30
* Success 45 45 * Success 45 45
* Came to Witt's End 1 1 * Came to Witt's End 1 1
* Round out the total 2 2 * Round out the total 2 2
* TOTAL: 430 * TOTAL: 430
* Points can also be deducted for using hints or too many turns, or * Points can also be deducted for using hints or too many turns, or for
* for saving intermediate positions. */ * saving intermediate positions. */
/* First tally up the treasures. Must be in building and not broken. /* First tally up the treasures. Must be in building and not broken.
* Give the poor guy 2 points just for finding each treasure. */ * Give the poor guy 2 points just for finding each treasure. */
mxscor = 0; mxscor = 0;
for (int i = 1; i <= NOBJECTS; i++) { for (int i = 1; i <= NOBJECTS; i++) {
if (!objects[i].is_treasure) { if (!objects[i].is_treasure)
continue; continue;
} if (objects[i].inventory != 0) {
if (objects[i].inventory != 0) { int k = 12;
int k = 12; if (i == CHEST)
if (i == CHEST) { k = 14;
k = 14; if (i > CHEST)
} k = 16;
if (i > CHEST) { if (!PROP_IS_STASHED(i) && !PROP_IS_NOTFOUND(i))
k = 16; score += 2;
} if (game.objects[i].place == LOC_BUILDING && PROP_IS_FOUND(i))
if (!OBJECT_IS_STASHED(i) && !OBJECT_IS_NOTFOUND(i)) { score += k - 2;
score += 2; mxscor += k;
} }
if (game.objects[i].place == LOC_BUILDING && }
OBJECT_IS_FOUND(i)) {
score += k - 2;
}
mxscor += k;
}
}
/* Now look at how he finished and how far he got. NDEATHS and /* Now look at how he finished and how far he got. NDEATHS and
* game.numdie tell us how well he survived. game.dflag will tell us * game.numdie tell us how well he survived. game.dflag will tell us
* if he ever got suitably deep into the cave. game.closng still * if he ever got suitably deep into the cave. game.closng still
* indicates whether he reached the endgame. And if he got as far as * indicates whether he reached the endgame. And if he got as far as
* "cave closed" (indicated by "game.closed"), then bonus is zero for * "cave closed" (indicated by "game.closed"), then bonus is zero for
* mundane exits or 133, 134, 135 if he blew it (so to speak). */ * mundane exits or 133, 134, 135 if he blew it (so to speak). */
score += (NDEATHS - game.numdie) * 10; score += (NDEATHS - game.numdie) * 10;
mxscor += NDEATHS * 10; mxscor += NDEATHS * 10;
if (mode == endgame) { if (mode == endgame)
score += 4; score += 4;
} mxscor += 4;
mxscor += 4; if (game.dflag != 0)
if (game.dflag != 0) { score += 25;
score += 25; mxscor += 25;
} if (game.closng)
mxscor += 25; score += 25;
if (game.closng) { mxscor += 25;
score += 25; if (game.closed) {
} if (game.bonus == none)
mxscor += 25; score += 10;
if (game.closed) { if (game.bonus == splatter)
if (game.bonus == none) { score += 25;
score += 10; if (game.bonus == defeat)
} score += 30;
if (game.bonus == splatter) { if (game.bonus == victory)
score += 25; score += 45;
} }
if (game.bonus == defeat) { mxscor += 45;
score += 30;
}
if (game.bonus == victory) {
score += 45;
}
}
mxscor += 45;
/* Did he come to Witt's End as he should? */ /* Did he come to Witt's End as he should? */
if (game.objects[MAGAZINE].place == LOC_WITTSEND) { if (game.objects[MAGAZINE].place == LOC_WITTSEND)
score += 1; score += 1;
} mxscor += 1;
mxscor += 1;
/* Round it off. */ /* Round it off. */
score += 2; score += 2;
mxscor += 2; mxscor += 2;
/* Deduct for hints/turns/saves. Hints < 4 are special; see database /* Deduct for hints/turns/saves. Hints < 4 are special; see database desc. */
* desc. */ for (int i = 0; i < NHINTS; i++) {
for (int i = 0; i < NHINTS; i++) { if (game.hints[i].used)
if (game.hints[i].used) { score = score - hints[i].penalty;
score = score - hints[i].penalty; }
} if (game.novice)
} score -= 5;
if (game.novice) { if (game.clshnt)
score -= 5; score -= 10;
} score = score - game.trnluz - game.saved;
if (game.clshnt) {
score -= 10;
}
score = score - game.trnluz - game.saved;
/* Return to score command if that's where we came from. */ /* Return to score command if that's where we came from. */
if (mode == scoregame) { if (mode == scoregame) {
rspeak(GARNERED_POINTS, score, mxscor, game.turns, game.turns); rspeak(GARNERED_POINTS, score, mxscor, game.turns, game.turns);
} }
return score; return score;
} }
void terminate(enum termination mode) { void terminate(enum termination mode)
/* End of game. Let's tell him all about it. */ /* End of game. Let's tell him all about it. */
int points = score(mode); {
int points = score(mode);
#if defined ADVENT_AUTOSAVE #if defined ADVENT_AUTOSAVE
autosave(); autosave();
#endif #endif
if (points + game.trnluz + 1 >= mxscor && game.trnluz != 0) { if (points + game.trnluz + 1 >= mxscor && game.trnluz != 0)
rspeak(TOOK_LONG); rspeak(TOOK_LONG);
} if (points + game.saved + 1 >= mxscor && game.saved != 0)
if (points + game.saved + 1 >= mxscor && game.saved != 0) { rspeak(WITHOUT_SUSPENDS);
rspeak(WITHOUT_SUSPENDS); rspeak(TOTAL_SCORE, points, mxscor, game.turns, game.turns);
} for (int i = 1; i <= (int)NCLASSES; i++) {
rspeak(TOTAL_SCORE, points, mxscor, game.turns, game.turns); if (classes[i].threshold >= points) {
for (int i = 1; i <= (int)NCLASSES; i++) { speak(classes[i].message);
if (classes[i].threshold >= points) { if (i < (int)NCLASSES) {
speak(classes[i].message); int nxt = classes[i].threshold + 1 - points;
if (i < (int)NCLASSES) { rspeak(NEXT_HIGHER, nxt, nxt);
int nxt = classes[i].threshold + 1 - points; } else {
rspeak(NEXT_HIGHER, nxt, nxt); rspeak(NO_HIGHER);
} else { }
rspeak(NO_HIGHER); exit(EXIT_SUCCESS);
} }
exit(EXIT_SUCCESS); }
} rspeak(OFF_SCALE);
} exit(EXIT_SUCCESS);
rspeak(OFF_SCALE);
exit(EXIT_SUCCESS);
} }
/* end */ /* end */

View file

@ -54,6 +54,6 @@ const travelop_t travel[] = {{
const char *ignore = "{ignore}"; const char *ignore = "{ignore}";
/* Dwarf starting locations */ /* Dwarf starting locations */
const int dwarflocs[NDWARVES] = {{{dwarflocs}}}; const int dwarflocs[NDWARFLOCS] = {{{dwarflocs}}};
/* end */ /* end */

View file

@ -35,8 +35,9 @@ SPDX-License-Identifier: BSD-2-Clause
#define COND_HOGRE 20 /* Trying to deal with ogre */ #define COND_HOGRE 20 /* Trying to deal with ogre */
#define COND_HJADE 21 /* Found all treasures except jade */ #define COND_HJADE 21 /* Found all treasures except jade */
#define NDWARVES {ndwarflocs} // number of dwarves /* Count of dwarf starting locations */
extern const int dwarflocs[NDWARVES]; #define NDWARFLOCS {ndwarflocs}
extern const int dwarflocs[NDWARFLOCS];
typedef struct {{ typedef struct {{
const char** strs; const char** strs;

View file

@ -124,6 +124,10 @@ tap: count $(SGAMES) $(TEST_TARGETS)
count: count:
@echo 1..$(words $(TEST_TARGETS)) @echo 1..$(words $(TEST_TARGETS))
foobar:
exit 1
# The following machinery tests the game against a binary made from # The following machinery tests the game against a binary made from
# the advent430 branch To use it, switch to that branch, build the # the advent430 branch To use it, switch to that branch, build the
# binary, run it once to generate adventure.data, then switch back to # binary, run it once to generate adventure.data, then switch back to
@ -170,7 +174,7 @@ oldcompare:
echo 1..$(words $(shell ls *.log))) | $(TAPFILTER) echo 1..$(words $(shell ls *.log))) | $(TAPFILTER)
@rm *.ochk *-new advent430 adventure.data @rm *.ochk *-new advent430 adventure.data
# List all NOCOMPARE tests. # List all NOMPARE tests.
residuals: residuals:
@grep -n NOCOMPARE *.log @grep -n NOCOMPARE *.log

View file

@ -1,386 +0,0 @@
Welcome to Adventure!! Would you like instructions?
> no
You are standing at the end of a road before a small brick building.
Around you is a forest. A small stream flows out of the building and
down a gully.
> seed 1640849217
Seed set to 1640849217
You're in front of building.
> e
You are inside a building, a well house for a large spring.
There are some keys on the ground here.
There is a shiny brass lamp nearby.
There is food here.
There is a bottle of water here.
> get lamp
OK
> xyzzy
>>Foof!<<
It is now pitch dark. If you proceed you will likely fall into a pit.
> get rod
OK
> on
Your lamp is now on.
You are in a debris room filled with stuff washed in from the surface.
A low wide passage with cobbles becomes plugged with mud and debris
here, but an awkward canyon leads upward and west. In the mud someone
has scrawled, "MAGIC WORD XYZZY".
> w
You are in an awkward sloping east/west canyon.
> w
You are in a splendid chamber thirty feet high. The walls are frozen
rivers of orange stone. An awkward canyon and a good passage exit
from east and west sides of the chamber.
A cheerful little bird is sitting here singing.
> w
At your feet is a small pit breathing traces of white mist. An east
passage ends here except for a small crack leading on.
Rough stone steps lead down the pit.
> d
You are at one end of a vast hall stretching forward out of sight to
the west. There are openings to either side. Nearby, a wide stone
staircase leads downward. The hall is filled with wisps of white mist
swaying to and fro almost as if alive. A cold wind blows up the
staircase. There is a passage at the top of a dome behind you.
Rough stone steps lead up the dome.
> w
You are on the east bank of a fissure slicing clear across the hall.
The mist is quite thick here, and the fissure is too wide to jump.
> wave rod
A crystal bridge now spans the fissure.
> w
You are on the west side of the fissure in the Hall of Mists.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You are on the east bank of a fissure slicing clear across the hall.
The mist is quite thick here, and the fissure is too wide to jump.
A crystal bridge spans the fissure.
> w
You are on the west side of the fissure in the Hall of Mists.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You are on the east bank of a fissure slicing clear across the hall.
The mist is quite thick here, and the fissure is too wide to jump.
A crystal bridge spans the fissure.
> w
You are on the west side of the fissure in the Hall of Mists.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You are on the east bank of a fissure slicing clear across the hall.
The mist is quite thick here, and the fissure is too wide to jump.
A crystal bridge spans the fissure.
> w
You are on the west side of the fissure in the Hall of Mists.
There are diamonds here!
A crystal bridge spans the fissure.
> e
You're on east bank of fissure.
A crystal bridge spans the fissure.
> w
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> e
A little dwarf just walked around a corner, saw you, threw a little
axe at you which missed, cursed, and ran away.
You're on east bank of fissure.
There is a little axe here.
A crystal bridge spans the fissure.
> w
There is a threatening little dwarf in the room with you!
One sharp nasty knife is thrown at you!
It misses!
You're on west bank of fissure.
There are diamonds here!
A crystal bridge spans the fissure.
> get knife
The dwarves' knives vanish as they strike the walls of the cave.
> look
Sorry, but I am not allowed to give more detail. I will repeat the
long description of your location.
There is a threatening little dwarf in the room with you!
One sharp nasty knife is thrown at you!
It misses!
You are on the west side of the fissure in the Hall of Mists.
There are diamonds here!
A crystal bridge spans the fissure.
> get knife
I see no knife here.
> quit
Do you really want to quit now?
> yes
OK
You scored 59 out of a possible 430, using 50 turns.
Your score qualifies you as a novice class adventurer.
To achieve the next higher rating, you need 62 more points.

View file

@ -1,57 +0,0 @@
## Test whether KNIVES_VANISH can be issued twice
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
# SPDX-License-Identifier: BSD-2-Clause
#NOCOMPARE avoide spuriuous failure on second "get knife"
no
seed 1640849217
e
get lamp
xyzzy
get rod
on
w
w
w
d
w
wave rod
w
e
w
e
w
e
w
e
w
e
w
e
w
e
w
e
w
e
w
e
w
e
w
e
w
e
w
e
w
e
w
e
w
e
w
get knife
look
get knife
quit
yes

View file

@ -1,4 +1,6 @@
#! /bin/sh #! /bin/sh
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
# SPDX-License-Identifier: MIT-0
# #
# tapdiffer - Render diff between input and checkfile as a TAP report # tapdiffer - Render diff between input and checkfile as a TAP report
# #
@ -14,23 +16,17 @@
# OSD-compliant; otherwise the following SPDX tag incorporates the # OSD-compliant; otherwise the following SPDX tag incorporates the
# MIT No Attribution license by reference. # MIT No Attribution license by reference.
# #
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
# SPDX-License-Identifier: MIT-0
#
# A newer version may be available at https://gitlab.com/esr/tapview # A newer version may be available at https://gitlab.com/esr/tapview
# Check your last commit dqte for this file against the commit list # Check your last commit dqte for this file against the commit list
# there to see if it might be a good idea to update. # there to see if it might be a good idea to update.
# #
diffopts=-u if [ "$1" = "-b" ]
while getopts bn opt then
do diffopts=-ub
case $opt in shift
b) diffopts=-ub;; else
*) echo "tapdiffer: unknown option ${opt}."; exit 1;; diffopts=-u
esac fi
done
# shellcheck disable=SC2004
shift $(($OPTIND - 1))
legend=$1 legend=$1
checkfile=$2 checkfile=$2
@ -41,7 +37,7 @@ if diff --text "${diffopts}" "${checkfile}" - >/tmp/tapdiff$$
then then
echo "ok - ${legend}" echo "ok - ${legend}"
else else
echo "not ok - ${legend}" echo "not ok - ${checkfile}: ${legend}"
if [ ! "${QUIET}" = 1 ] if [ ! "${QUIET}" = 1 ]
then then
echo " --- |" echo " --- |"

View file

@ -4,15 +4,14 @@
# This code is intended to be embedded in your project. The author # This code is intended to be embedded in your project. The author
# grants permission for it to be distributed under the prevailing # grants permission for it to be distributed under the prevailing
# license of your project if you choose, provided that license is # license of your project if you choose, provided that license is
# OSD-compliant; otherwise the following SPDX tag incorporates the # OSD-compliant; otherwise the following SPDX tag incorporates a
# MIT No Attribution license by reference. # license by reference.
# #
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
# SPDX-License-Identifier: MIT-0 # SPDX-License-Identifier: BSD-2-Clause
# #
# This is version 1.6
# A newer version may be available at https://gitlab.com/esr/tapview # A newer version may be available at https://gitlab.com/esr/tapview
# Check your last commit date for this file against the commit list
# there to see if it might be a good idea to update.
# #
OK="." OK="."
FAIL="F" FAIL="F"
@ -20,16 +19,13 @@ SKIP="s"
TODO_NOT_OK="x" TODO_NOT_OK="x"
TODO_OK="u" TODO_OK="u"
LF='
'
ship_char() { ship_char() {
# shellcheck disable=SC2039 # shellcheck disable=SC2039
printf '%s' "$1" # https://www.etalabs.net/sh_tricks.html printf '%s' "$1" # https://www.etalabs.net/sh_tricks.html
} }
ship_line() { ship_line() {
report="${report}${1}$LF" report="${report}${1}\n"
} }
ship_error() { ship_error() {
@ -38,7 +34,7 @@ ship_error() {
then then
echo "" echo ""
fi fi
report="${report}${1}$LF" report="${report}${1}\n"
echo "${report}" echo "${report}"
exit 1 exit 1
} }
@ -80,10 +76,6 @@ context_pop () {
then then
ship_line "Expected $(context_get expect) tests but only ${testcount} ran." ship_line "Expected $(context_get expect) tests but only ${testcount} ran."
status=1 status=1
elif [ "$(context_get plan)" != "" ] && [ "$(context_get expect)" -lt "$(context_get count)" ]
then
ship_line "${testcount} ran but $(context_get expect) expected."
status=1
fi fi
} }