Compare commits

...

55 commits
1.17 ... master

Author SHA1 Message Date
Eric S. Raymond
7d848c89e1 Add spell-checking to the regression tests. 2025-05-25 11:06:46 -04:00
Eric S. Raymond
7bbf994fce Spellcheck the manual page a well. No errors. 2025-05-25 11:03:34 -04:00
Eric S. Raymond
3e989aec53 Spellcheck the YAML - yielded one trivial fix. 2025-05-25 10:57:34 -04:00
Eric S. Raymond
9df69fe034 Ready to ship 1.20. 2024-09-23 18:48:11 -04:00
Eric S. Raymond
a2bb39dc7e Eliminate a confusing dummy argument in a macro. 2024-09-23 14:22:25 -04:00
Eric S. Raymond
5c90880f0a Comment and macro cleanup. 2024-09-23 06:06:32 -04:00
Eric S. Raymond
92451f1fff Eliminate thew last property inequality outside a macro. 2024-09-23 05:08:11 -04:00
Eric S. Raymond
96ad6c6245 Reflow. 2024-09-23 04:38:24 -04:00
Eric S. Raymond
40742e112b Expand a macro to simplify code. 2024-09-23 04:26:59 -04:00
Eric S. Raymond
a4fd14caf7 Back away from trying for the found member until we make stashed work. 2024-09-23 04:26:59 -04:00
Eric S. Raymond
9a6e4406f5 Confine uses of PROP_STASHIFY() to advent.h
Now it shouyld be possible to manipulate a stashed flag by only
changing macros.
2024-09-23 04:26:59 -04:00
Eric S. Raymond
08f0351817 Introduce OBJECT_STASHIFY. 2024-09-23 04:26:59 -04:00
Eric S. Raymond
0157e58668 Remove an unneeded layer of macro indirection. 2024-09-23 03:35:52 -04:00
Eric S. Raymond
354e56a69b Clean up some comments. 2024-09-23 03:35:52 -04:00
Eric S. Raymond
9c3f4b0c90 Reflow. 2024-09-22 13:08:31 -04:00
Eric S. Raymond
20fd7c589f Typo fix. 2024-09-20 22:19:14 -04:00
Eric S. Raymond
8c553af53e Avoid a GNUism, POSIX strncasecmp() is declarted in strings.h. 2024-09-20 22:05:25 -04:00
Eric S. Raymond
f1cb740c41 Define TRUNCLEN and explain its issues. 2024-09-20 22:04:16 -04:00
Eric S. Raymond
cf4adf8d02 Repair truncation in oldstyle mode.
Sure would be nice to remember while this code had TOKEN + TOKEN
where one would think it should just say TOKEN.
2024-09-20 10:49:44 -04:00
Eric S. Raymond
acdfa96315 Fix a busted comment. 2024-09-20 10:40:22 -04:00
Eric S. Raymond
3d6c689ffa Make oldstyle correctly suppress line editing. 2024-09-20 10:29:37 -04:00
Eric S. Raymond
8dcc6e6641 Correct missing negative. 2024-09-20 10:03:15 -04:00
Eric S. Raymond
e17ff128da Remove obsolete comment part. 2024-06-30 17:20:46 -04:00
Eric S. Raymond
cb293f4aa4 Rename some macos for clarity. 2024-06-30 14:48:44 -04:00
Eric S. Raymond
124e7768b4 Cease relying on C storage starting zeroed. 2024-06-27 19:50:56 -04:00
Eric S. Raymond
63e8579f74 Ready to shp 1.19. 2024-06-27 13:39:27 -04:00
Eric S. Raymond
1080b45d39 Comment typo fix. 2024-06-27 13:29:28 -04:00
Eric S. Raymond
86fe4bd121 Verify that tesrts still match advent430 where expected. 2024-06-27 13:04:33 -04:00
Eric S. Raymond
bd499dc532 Fix GitLab issue #69: repeated knive caveat message 2024-06-25 16:42:14 -04:00
Eric S. Raymond
ae6eced72d Incorporate Ryan Sarson's test for correct knife message. 2024-06-25 13:08:50 -04:00
Eric S. Raymond
7903ac1bb8 More validation, with -Wall and -Wextra. 2024-06-03 21:12:41 -04:00
Eric S. Raymond
b51612131d Typo fix. 2024-04-29 11:43:41 -04:00
Eric S. Raymond
62cd0c78da Reissue 1.18 - same code, corrected metadata. 2024-02-15 12:56:01 -05:00
Eric S. Raymond
bad34acf1e Improve the project summary. 2024-02-05 08:07:31 -05:00
Eric S. Raymond
0fafbe379f Ubuntu-discuss doesn't want to see release notifications. 2024-02-05 07:51:00 -05:00
Eric S. Raymond
fcf6935689 Ready to ship 1.18. 2024-02-05 07:49:07 -05:00
Eric S. Raymond
b610a62685 Documentation polishing. 2024-02-05 07:43:23 -05:00
Eric S. Raymond
6174129116 Add a detail to the installation instructiions. 2024-02-04 16:56:20 -05:00
Eric S. Raymond
a2fa136988 Remove unused production. 2024-02-04 16:16:05 -05:00
Eric S. Raymond
43a08621e4 asciidoc -> asciidoctor. 2024-02-04 15:52:03 -05:00
Eric S. Raymond
f24fcd2971 Perform full code validation on every make check. 2024-02-04 12:48:33 -05:00
Eric S. Raymond
580f409ee6 At this revision, make cppcheck runs clean. 2024-02-04 11:44:04 -05:00
Eric S. Raymond
5b917084b0 Minor repair of savefile validation code. 2024-02-04 11:43:36 -05:00
Eric S. Raymond
1ef39055f3 Make reflow run black. 2024-02-04 10:07:58 -05:00
Eric S. Raymond
0175344caa 1TBS reflow, the bracening. 2024-01-29 12:14:56 -05:00
Eric S. Raymond
be429016af 1TBS reflow with clang-format. 2024-01-28 08:09:39 -05:00
Eric S. Raymond
c11938aed5 Place1TBS mandatory braces. 2024-01-28 07:14:46 -05:00
Eric S. Raymond
258b7703f2 Simplify SPDX copyright lines to the shortest canonical form...
...because if we leave them longer than 80 chars, reflow is going to
mess them up.
2024-01-28 07:14:46 -05:00
Eric S. Raymond
83c6a66887 Typo fix. 2024-01-15 05:10:46 -05:00
Eric S. Raymond
3c09c25cc3 Update tapview and tapdiffer. 2024-01-15 05:03:53 -05:00
Eric S. Raymond
f26514b5dd Fix SPDX headers. 2024-01-15 04:58:21 -05:00
Eric S. Raymond
cf7ea0cd89 Remove debugging debris. 2024-01-15 04:57:47 -05:00
Eric S. Raymond
2db3bed3f1 Fix up copyright notices. SPDX wants only one per file. 2024-01-03 06:30:40 -05:00
Eric S. Raymond
a6ec8a9595 Simplify some dependencies. 2024-01-03 06:15:04 -05:00
Eric S. Raymond
75bc031b46 Reduce include complexity. 2024-01-03 05:55:57 -05:00
32 changed files with 4835 additions and 4037 deletions

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Eric S. Raymond # SPDX-FileCopyrightText: (C) 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: Eric S. Raymond # SPDX-FileCopyrightText: (C) 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: Eric S. Raymond #SPDX-FileCopyrightText: (C) 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: Copyright Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: (C) 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-10 libedit-dev libasan6 libubsan1 python3 python3-yaml lcov asciidoc libxslt1.1 pkg-config docbook-xml xsltproc 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

View file

@ -1,5 +1,5 @@
= Installing Open Adventure = = Installing Open Adventure =
// SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: (C) 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,3 +21,7 @@ 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: Copyright Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: (C) 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 -D_DEFAULT_SOURCE -DVERSION=\"$(VERS)\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all $(CFLAGS) -g $(EXTRA) CCFLAGS+=-std=c99 -Wall -Wextra -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,9 +65,23 @@ 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)
check: advent cheat CSUPPRESSIONS = --suppress=missingIncludeSystem --suppress=invalidscanf
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
@ -76,15 +90,16 @@ check: advent cheat
coverage: clean debug coverage: clean debug
cd tests; $(MAKE) coverage --quiet cd tests; $(MAKE) coverage --quiet
.SUFFIXES: .adoc .html .6 # Note: to suppress the footers with timestamps being generated in HTML,
# 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:
a2x --doctype manpage --format manpage $< asciidoctor -D. -a nofooter -b manpage $<
.adoc.html: .adoc.html:
asciidoc $< asciidoctor -D. -a nofooter -a webfonts! $<
.adoc:
asciidoc $<
html: advent.html history.html hints.html html: advent.html history.html hints.html
@ -100,9 +115,6 @@ 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
@ -147,9 +159,3 @@ 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,7 +1,16 @@
= Open Adventure project news = = Open Adventure project news =
// SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: (C) 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: Copyright Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: (C) 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

707
actions.c

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,11 @@
= advent(6) = = advent(6) =
:doctype: manpage :doctype: manpage
// SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: (C) 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
@ -20,9 +23,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 Don Woods's 1977 version of this game. To avoid a name Jim Gillogly of the 1977 version. To avoid a name collision, this game
collision, this game builds as 'advent', reflecting the fact that the builds as 'advent', reflecting the fact that the PDP-10 on which the
PDP-10 on which the game originally ran limited filenames to 6 characters. 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.
@ -34,7 +37,8 @@ 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. Don's 1977 version until you get to the new cave sections added for
2.5.
To exit the game, type Ctrl-D (EOF). To exit the game, type Ctrl-D (EOF).
@ -44,9 +48,9 @@ There have been no gameplay changes.
-l:: Log commands to specified file. -l:: Log commands to specified file.
-r:: Restore game from specified file -r:: Restore game from specified save file
-a:: Load from specified file and autosave to it on exit or signal. -a:: Load from specified save 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.
@ -59,9 +63,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 word The binary save file format is fragile, dependent on your machine's
size and endianness, and unlikely to survive through version bumps. There endianness, and unlikely to survive through version bumps. There are
is a version check. version and endianness checks when attempting to restore from a save.
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: Copyright Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: (C) 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

147
advent.h
View file

@ -1,15 +1,14 @@
/* /*
* Dungeon types and macros. * Dungeon types and macros.
* *
* SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods * SPDX-FileCopyrightText: (C) 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"
@ -21,7 +20,6 @@
#define LINESIZE 1024 #define LINESIZE 1024
#define TOKLEN 5 // # outputting characters in a token */ #define TOKLEN 5 // # outputting characters in a token */
#define NDWARVES 6 // number of dwarves
#define PIRATE NDWARVES // must be NDWARVES-1 when zero-origin #define PIRATE NDWARVES // must be NDWARVES-1 when zero-origin
#define DALTLC LOC_NUGGET // alternate dwarf location #define DALTLC LOC_NUGGET // alternate dwarf location
#define INVLIMIT 7 // inventory limit (# of objects) #define INVLIMIT 7 // inventory limit (# of objects)
@ -32,7 +30,8 @@
#define FLASHTIME 50 // turns from first warning till blinding flash #define FLASHTIME 50 // turns from first warning till blinding flash
#define PANICTIME 15 // time left after closing #define PANICTIME 15 // time left after closing
#define BATTERYLIFE 2500 // turn limit increment from batteries #define BATTERYLIFE 2500 // turn limit increment from batteries
#define WORD_NOT_FOUND -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
@ -48,7 +47,6 @@
#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
@ -62,44 +60,27 @@
* 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_STASHED is supposed to map a state property value to a * PROP_STASHIFY 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 test is difficult to read. * those tests is difficult to read.
*/
#define PROP_STASHIFY(n) (-1 - (n))
#define PROP_IS_STASHED(obj) (game.objects[obj].prop < STATE_NOTFOUND)
#define PROP_IS_NOTFOUND(obj) (game.objects[obj].prop == STATE_NOTFOUND)
#define PROP_IS_FOUND(obj) (game.objects[obj].prop == STATE_FOUND)
#define PROP_IS_STASHED_OR_UNSEEN(obj) (game.objects[obj].prop < 0)
#define PROP_SET_FOUND(obj) (game.objects[obj].prop = STATE_FOUND)
#define PROP_SET_NOT_FOUND(obj) (game.objects[obj].prop = STATE_NOTFOUND)
#define PROP_IS_NOTFOUND2(g, o) (g.objects[o].prop == STATE_NOTFOUND)
#define PROP_IS_INVALID(val) (val < -MAX_STATE - 1 || val > MAX_STATE)
#else
/* (ESR) Only the boldest of adventurers will explore here. This
* 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 * All tests of the prop member are done with either these macros or ==.
* PROP_IS_STASHED_OR_UNSEEN. The symptom is game.tally getting
* decremented on non-treasures.
*/ */
#define PROP_STASHIFY(n) (-(n)) #define OBJECT_IS_NOTFOUND(obj) (game.objects[obj].prop == STATE_NOTFOUND)
#define PROP_IS_STASHED(obj) (game.objects[obj].prop < 0) #define OBJECT_IS_FOUND(obj) (game.objects[obj].prop == STATE_FOUND)
#define PROP_IS_NOTFOUND(obj) (!game.objects[obj].found) #define OBJECT_SET_FOUND(obj) (game.objects[obj].prop = STATE_FOUND)
#define PROP_IS_FOUND(obj) (game.objects[obj].found && game.objects[obj].prop == 0) #define OBJECT_SET_NOT_FOUND(obj) (game.objects[obj].prop = STATE_NOTFOUND)
#define PROP_IS_STASHED_OR_UNSEEN(obj) (!game.objects[obj].found || game.objects[obj].prop < 0) #define OBJECT_IS_NOTFOUND2(g, o) (g.objects[o].prop == STATE_NOTFOUND)
#define PROP_SET_FOUND(obj) do {game.objects[obj].found = true; game.objects[obj].prop = STATE_FOUND;} while(0) #define PROP_IS_INVALID(val) (val < -MAX_STATE - 1 || val > MAX_STATE)
#define PROP_SET_NOT_FOUND(obj) game.objects[obj].found = false #define PROP_STASHIFY(n) (-1 - (n))
#define PROP_IS_NOTFOUND2(g, o) (!g.objects[o].found) #define OBJECT_STASHIFY(obj, pval) game.objects[obj].prop = PROP_STASHIFY(pval)
#define PROP_IS_INVALID(val) (val < -MAX_STATE || val > MAX_STATE) #define OBJECT_IS_STASHED(obj) (game.objects[obj].prop < STATE_NOTFOUND)
#define PROP_SET_SEEN(obj) game.objects[object].found = true #define OBJECT_STATE_EQUALS(obj, pval) \
#endif ((game.objects[obj].prop == pval) || \
#define PROP_STASHED(obj) PROP_STASHIFY(game.objects[obj].prop) (game.objects[obj].prop == PROP_STASHIFY(pval)))
#define PROMPT "> " #define PROMPT "> "
@ -113,31 +94,42 @@
* 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)
* DARK(LOC) = true if location "LOC" is dark * IS_DARK_HERE() = 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
* OUTSID(LOC) = true if location not in the cave * OUTSIDE(LOC) = true if location not in the cave
* INSIDE(LOC) = true if location is in the cave or the building at the beginning of the game * INSIDE(LOC) = true if location is in the cave or the building at the
* 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) (game.objects[OBJ].place == game.loc || game.objects[OBJ].fixed == game.loc) #define AT(OBJ) \
(game.objects[OBJ].place == game.loc || \
game.objects[OBJ].fixed == game.loc)
#define HERE(OBJ) (AT(OBJ) || TOTING(OBJ)) #define HERE(OBJ) (AT(OBJ) || TOTING(OBJ))
#define CNDBIT(L,N) (tstbit(conditions[L],N)) #define CNDBIT(L, N) (tstbit(conditions[L], N))
#define LIQUID() (game.objects[BOTTLE].prop == WATER_BOTTLE? WATER : game.objects[BOTTLE].prop == OIL_BOTTLE ? OIL : NO_OBJECT ) #define LIQUID() \
#define LIQLOC(LOC) (CNDBIT((LOC),COND_FLUID)? CNDBIT((LOC),COND_OILY) ? OIL : WATER : NO_OBJECT) (game.objects[BOTTLE].prop == WATER_BOTTLE ? WATER \
: game.objects[BOTTLE].prop == OIL_BOTTLE ? OIL \
: NO_OBJECT)
#define LIQLOC(LOC) \
(CNDBIT((LOC), COND_FLUID) ? CNDBIT((LOC), COND_OILY) ? OIL : WATER \
: NO_OBJECT)
#define FORCED(LOC) CNDBIT(LOC, COND_FORCED) #define FORCED(LOC) CNDBIT(LOC, COND_FORCED)
#define DARK(DUMMY) (!CNDBIT(game.loc,COND_LIT) && (game.objects[LAMP].prop == LAMP_DARK || !HERE(LAMP))) #define IS_DARK_HERE() \
(!CNDBIT(game.loc, COND_LIT) && \
(game.objects[LAMP].prop == LAMP_DARK || !HERE(LAMP)))
#define PCT(N) (randrange(100) < (N)) #define PCT(N) (randrange(100) < (N))
#define GSTONE(OBJ) ((OBJ) == EMERALD || (OBJ) == RUBY || (OBJ) == AMBER || (OBJ) == SAPPH) #define GSTONE(OBJ) \
((OBJ) == EMERALD || (OBJ) == RUBY || (OBJ) == AMBER || (OBJ) == SAPPH)
#define FOREST(LOC) CNDBIT(LOC, COND_FOREST) #define FOREST(LOC) CNDBIT(LOC, COND_FOREST)
#define OUTSID(LOC) (CNDBIT(LOC, COND_ABOVE) || FOREST(LOC)) #define OUTSIDE(LOC) (CNDBIT(LOC, COND_ABOVE) || FOREST(LOC))
#define INSIDE(LOC) (!OUTSID(LOC) || LOC == LOC_BUILDING) #define INSIDE(LOC) (!OUTSIDE(LOC) || LOC == LOC_BUILDING)
#define INDEEP(LOC) CNDBIT((LOC),COND_DEEP) #define INDEEP(LOC) CNDBIT((LOC), COND_DEEP)
#define BUG(x) bug(x, #x) #define BUG(x) bug(x, #x)
enum bugtype { enum bugtype {
@ -152,15 +144,15 @@ enum bugtype {
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.
@ -206,9 +198,9 @@ struct game_t {
/* 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 first dwarf, others start moving, no knives thrown yet * 2 Met 1t dwarf, others start moving, no knives thrown yet
* 3 A knife has been thrown (first set always misses) * 3 A knife has been thrown (first set always misses) 3+
* 3+ Dwarves are mad (increases their accuracy) */ * Dwarves are mad (increases their accuracy) */
int32_t dflag; int32_t dflag;
int32_t dkill; // dwarves killed int32_t dkill; // dwarves killed
@ -242,18 +234,15 @@ struct game_t {
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 {
#ifdef FOUNDBOOL
bool32_t found; // has the location of this object been found?
#endif
loc_t fixed; // fixed location of object (if not IS_FREE) loc_t fixed; // fixed location of object (if not IS_FREE)
int32_t prop; // object state */ int32_t prop; // object state
loc_t place; // location of object loc_t place; // location of object
} objects[NOBJECTS + 1]; } objects[NOBJECTS + 1];
struct { struct {
bool32_t used; // hints[i].used = true iff hint i has been used. 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 int32_t lc; // hints[i].lc = show int at LOC with cond bit i
} hints[NHINTS]; } hints[NHINTS];
obj_t link[NOBJECTS * 2 + 1];// object-list links obj_t link[NOBJECTS * 2 + 1]; // object-list links
}; };
/* /*
@ -277,7 +266,15 @@ typedef struct {
word_type_t type; word_type_t type;
} command_word_t; } command_word_t;
typedef enum {EMPTY, RAW, TOKENIZED, GIVEN, PREPROCESSED, PROCESSING, EXECUTED} command_state_t; typedef enum {
EMPTY,
RAW,
TOKENIZED,
GIVEN,
PREPROCESSED,
PROCESSING,
EXECUTED
} command_state_t;
typedef struct { typedef struct {
enum speechpart part; enum speechpart part;
@ -290,9 +287,9 @@ typedef struct {
/* /*
* Bump on save format change. * Bump on save format change.
* *
* Note: Verify that the tests run clean before bumping this, then rebuild the check * Note: Verify that the tests run clean before bumping this, then rebuild the
* files afterwards. Otherwise you will get a spurious failure due to the old version * check files afterwards. Otherwise you will get a spurious failure due to the
* having been generated into a check file. * old version having been generated into a check file.
*/ */
#define SAVE_VERSION 31 #define SAVE_VERSION 31
@ -303,8 +300,8 @@ typedef struct {
/* /*
* 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 version * reject saves from older versions. Later members can change, but bump the
* when you do that. * version when you do that.
*/ */
struct save_t { struct save_t {
char magic[sizeof(ADVENT_MAGIC)]; char magic[sizeof(ADVENT_MAGIC)];
@ -320,13 +317,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,6 +1,10 @@
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: (C) 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
@ -10,7 +14,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 be # attribute, if false, means that single-letter synonyms should not 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
@ -3432,7 +3436,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 pencilled on one I'm afraid the magazine is written in dwarvish. But penciled 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']
@ -3940,11 +3944,13 @@ 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?
@ -4226,7 +4232,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:
\tOpen Adventure %V - http://www.catb.org/esr/open-adventure/ Open Adventure %V - http://www.catb.org/esr/open-adventure/
words: ['versi'] words: ['versi']
noaction: true noaction: true

41
cheat.c
View file

@ -4,19 +4,17 @@
* 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: 1977, 2005 by Will Crowther and Don Woods * SPDX-FileCopyrightText: (C) 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 <getopt.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <editline/readline.h>
#include "advent.h" #include "advent.h"
#include <editline/readline.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) int main(int argc, char *argv[]) {
{
int ch; int ch;
char *savefilename = NULL; char *savefilename = NULL;
FILE *fp = NULL; FILE *fp = NULL;
@ -30,8 +28,10 @@ int main(int argc, char *argv[])
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 = "Usage: %s [-d numdie] [-s numsaves] [-v version] -o savefilename \n" const char *usage =
"Usage: %s [-d numdie] [-s numsaves] [-v version] -o savefilename "
"\n"
" -d number of deaths. Signed integer.\n" " -d number of deaths. Signed integer.\n"
" -l lifetime of lamp in turns. Signed integer.\n" " -l lifetime of lamp in turns. Signed integer.\n"
" -s number of saves. Signed integer.\n" " -s number of saves. Signed integer.\n"
@ -65,8 +65,7 @@ int main(int argc, char *argv[])
savefilename = optarg; savefilename = optarg;
break; break;
default: default:
fprintf(stderr, fprintf(stderr, usage, argv[0]);
usage, argv[0]);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
break; break;
} }
@ -74,17 +73,14 @@ int main(int argc, char *argv[])
// 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, fprintf(stderr, usage, argv[0]);
usage, argv[0]); fprintf(stderr, "ERROR: filename required\n");
fprintf(stderr,
"ERROR: filename required\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
fp = fopen(savefilename, WRITE_MODE); fp = fopen(savefilename, WRITE_MODE);
if (fp == NULL) { if (fp == NULL) {
fprintf(stderr, fprintf(stderr, "Can't open file %s. Exiting.\n", savefilename);
"Can't open file %s. Exiting.\n", savefilename);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -103,12 +99,7 @@ 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) char *myreadline(const char *prompt) { return readline(prompt); }
{
return readline(prompt);
}
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
/* end */ /* end */

View file

@ -5,11 +5,10 @@ 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 has sometimes worked on by the original authors - Crowther & Woods; it is shipped with
been known as Adventure 2.5. The original PDP-10 name 'advent' is used their permission and encouragement. It has sometimes been known as
for the built program to avoid collision with the BSD Games version. Adventure 2.5. The original PDP-10 name 'advent' is used for the
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: Copyright Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: (C) 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: Copyright Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: (C) 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

64
init.c
View file

@ -1,54 +1,42 @@
/* /*
* Initialisation * Initialisation
* *
* SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods * SPDX-FileCopyrightText: (C) 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 <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#include <assert.h> #include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "advent.h" #include "advent.h"
struct settings_t settings = { struct settings_t settings = {.logfp = NULL, .oldstyle = false, .prompt = true};
.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, .chloc = LOC_MAZEEND12, .chloc2 = LOC_DEADEND13, .abbnum = 5,
.chloc2 = LOC_DEADEND13, .clock1 = WARNTIME, .clock2 = FLASHTIME, .newloc = LOC_START,
.abbnum = 5, .loc = LOC_START, .limit = GAMELIMIT, .foobar = WORD_EMPTY,
.clock1 = WARNTIME,
.clock2 = FLASHTIME,
.newloc = LOC_START,
.loc = LOC_START,
.limit = GAMELIMIT,
.foobar = WORD_EMPTY,
}; };
int initialise(void) int initialise(void) {
{ if (settings.oldstyle) {
if (settings.oldstyle)
printf("Initialising...\n"); printf("Initialising...\n");
}
srand(time(NULL)); srand(time(NULL));
int seedval = (int)rand(); int seedval = (int)rand();
set_seed(seedval); set_seed(seedval);
assert(NDWARVES == NDWARFLOCS); for (int i = 1; i <= NDWARVES; i++) {
for (int i = 1; i <= NDWARFLOCS; i++) { game.dwarves[i].loc = dwarflocs[i - 1];
game.dwarves[i].loc = dwarflocs[i-1];
} }
for (int i = 1; i <= NOBJECTS; i++) { for (int i = 1; i <= NOBJECTS; i++) {
@ -58,10 +46,11 @@ int initialise(void)
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
@ -79,19 +68,26 @@ int initialise(void)
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.
for (int treasure = 1; treasure <= NOBJECTS; treasure++) { * (ESR) Non-treasures are set to STATE_FOUND explicitly so we
if (objects[treasure].is_treasure) { * don't rely on the value of uninitialized storage. This is to
* make translation to future languages easier. */
for (int object = 1; object <= NOBJECTS; object++) {
if (objects[object].is_treasure) {
++game.tally; ++game.tally;
if (objects[treasure].inventory != 0) if (objects[object].inventory != NULL) {
PROP_SET_NOT_FOUND(treasure); OBJECT_SET_NOT_FOUND(object);
}
} else {
OBJECT_SET_FOUND(object);
} }
} }
game.conds = setbit(COND_HBASE); game.conds = setbit(COND_HBASE);

902
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: Copyright Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: (C) 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: Copyright Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: (C) 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]

390
misc.c
View file

@ -1,30 +1,30 @@
/* /*
* I/O and support routines. * I/O and support routines.
* *
* SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods * SPDX-FileCopyrightText: (C) 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 <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <sys/time.h>
#include <ctype.h> #include <ctype.h>
#include <editline/readline.h> #include <editline/readline.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/time.h>
#include <unistd.h>
#include "advent.h" #include "advent.h"
#include "dungeon.h" #include "dungeon.h"
static void* xcalloc(size_t size) static void *xcalloc(size_t size) {
{ void *ptr = calloc(size, 1);
void* ptr = calloc(size, 1);
if (ptr == NULL) { if (ptr == NULL) {
// LCOV_EXCL_START // LCOV_EXCL_START
// exclude from coverage analysis because we can't simulate an out of memory error in testing // exclude from coverage analysis because we can't simulate an
// out of memory error in testing
fprintf(stderr, "Out of memory!\n"); fprintf(stderr, "Out of memory!\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
@ -34,26 +34,28 @@ static void* xcalloc(size_t size)
/* I/O routines (speak, pspeak, rspeak, sspeak, get_input, yes) */ /* I/O routines (speak, pspeak, rspeak, sspeak, get_input, yes) */
static void vspeak(const char* msg, bool blank, va_list ap) static void vspeak(const char *msg, bool blank, va_list ap) {
/* Engine for various speak functions */ /* Engine for various speak functions */
{
// Do nothing if we got a null pointer. // Do nothing if we got a null pointer.
if (msg == NULL) if (msg == NULL) {
return; return;
}
// Do nothing if we got an empty string. // Do nothing if we got an empty string.
if (strlen(msg) == 0) if (strlen(msg) == 0) {
return; return;
}
if (blank == true) if (blank == true) {
printf("\n"); printf("\n");
}
int msglen = strlen(msg); int msglen = strlen(msg);
// Rendered string // Rendered string
ssize_t size = 2000; /* msglen > 50 ? msglen*2 : 100; */ ssize_t size = 2000; /* msglen > 50 ? msglen*2 : 100; */
char* rendered = xcalloc(size); char *rendered = xcalloc(size);
char* renderp = rendered; char *renderp = rendered;
// Handle format specifiers (including the custom %S) by // Handle format specifiers (including the custom %S) by
// adjusting the parameter accordingly, and replacing the // adjusting the parameter accordingly, and replacing the
@ -61,9 +63,11 @@ static void vspeak(const char* msg, bool blank, va_list ap)
bool pluralize = false; bool pluralize = false;
for (int i = 0; i < msglen; i++) { for (int i = 0; i < msglen; i++) {
if (msg[i] != '%') { if (msg[i] != '%') {
/* Ugh. Least obtrusive way to deal with artifacts "on the floor" /* Ugh. Least obtrusive way to deal with artifacts "on
* being dropped outside of both cave and building. */ * the floor" being dropped outside of both cave and
if (strncmp(msg + i, "floor", 5) == 0 && strchr(" .", msg[i + 5]) && !INSIDE(game.loc)) { * building. */
if (strncmp(msg + i, "floor", 5) == 0 &&
strchr(" .", msg[i + 5]) && !INSIDE(game.loc)) {
strcpy(renderp, "ground"); strcpy(renderp, "ground");
renderp += 6; renderp += 6;
i += 4; i += 4;
@ -77,7 +81,8 @@ static void vspeak(const char* msg, bool blank, va_list ap)
// Integer specifier. // Integer specifier.
if (msg[i] == 'd') { if (msg[i] == 'd') {
int32_t arg = va_arg(ap, int32_t); int32_t arg = va_arg(ap, int32_t);
int ret = snprintf(renderp, size, "%" PRId32, arg); int ret =
snprintf(renderp, size, "%" PRId32, arg);
if (ret < size) { if (ret < size) {
renderp += ret; renderp += ret;
size -= ret; size -= ret;
@ -122,18 +127,16 @@ static void vspeak(const char* msg, bool blank, va_list ap)
free(rendered); free(rendered);
} }
void speak(const char* msg, ...) void speak(const char *msg, ...) {
/* speak a specified string */ /* speak a specified string */
{
va_list ap; va_list ap;
va_start(ap, msg); va_start(ap, msg);
vspeak(msg, true, ap); vspeak(msg, true, ap);
va_end(ap); va_end(ap);
} }
void sspeak(const int msg, ...) void sspeak(const int msg, ...) {
/* Speak a message from the arbitrary-messages list */ /* Speak a message from the arbitrary-messages list */
{
va_list ap; va_list ap;
va_start(ap, msg); va_start(ap, msg);
fputc('\n', stdout); fputc('\n', stdout);
@ -142,13 +145,12 @@ void sspeak(const int msg, ...)
va_end(ap); va_end(ap);
} }
void pspeak(vocab_t msg, enum speaktype mode, bool blank, int skip, ...) void pspeak(vocab_t msg, enum speaktype mode, bool blank, int skip, ...) {
/* Find the skip+1st message from msg and print it. Modes are: /* Find the skip+1st message from msg and print it. Modes are:
* feel = for inventory, what you can touch * feel = for inventory, what you can touch
* look = the full description for the state the object is in * look = the full description for the state the object is in
* listen = the sound for the state the object is in * listen = the sound for the state the object is in
* study = text on the object. */ * study = text on the object. */
{
va_list ap; va_list ap;
va_start(ap, skip); va_start(ap, skip);
switch (mode) { switch (mode) {
@ -171,32 +173,30 @@ void pspeak(vocab_t msg, enum speaktype mode, bool blank, int skip, ...)
va_end(ap); va_end(ap);
} }
void rspeak(vocab_t i, ...) void rspeak(vocab_t i, ...) {
/* Print the i-th "random" message (section 6 of database). */ /* Print the i-th "random" message (section 6 of database). */
{
va_list ap; va_list ap;
va_start(ap, i); va_start(ap, i);
vspeak(arbitrary_messages[i], true, ap); vspeak(arbitrary_messages[i], true, ap);
va_end(ap); va_end(ap);
} }
void echo_input(FILE* destination, const char* input_prompt, const char* input) void echo_input(FILE *destination, const char *input_prompt,
{ const char *input) {
size_t len = strlen(input_prompt) + strlen(input) + 1; size_t len = strlen(input_prompt) + strlen(input) + 1;
char* prompt_and_input = (char*) xcalloc(len); char *prompt_and_input = (char *)xcalloc(len);
strcpy(prompt_and_input, input_prompt); strcpy(prompt_and_input, input_prompt);
strcat(prompt_and_input, input); strcat(prompt_and_input, input);
fprintf(destination, "%s\n", prompt_and_input); fprintf(destination, "%s\n", prompt_and_input);
free(prompt_and_input); free(prompt_and_input);
} }
static int word_count(char* str) static int word_count(char *str) {
{
char delims[] = " \t"; char delims[] = " \t";
int count = 0; int count = 0;
int inblanks = true; int inblanks = true;
for (char *s = str; *s; s++) for (char *s = str; *s; s++) {
if (inblanks) { if (inblanks) {
if (strchr(delims, *s) == 0) { if (strchr(delims, *s) == 0) {
++count; ++count;
@ -207,26 +207,28 @@ static int word_count(char* str)
inblanks = true; inblanks = true;
} }
} }
}
return (count); return (count);
} }
static char* get_input(void) static char *get_input(void) {
{
// Set up the prompt // Set up the prompt
char input_prompt[] = PROMPT; char input_prompt[] = PROMPT;
if (!settings.prompt) if (!settings.prompt) {
input_prompt[0] = '\0'; input_prompt[0] = '\0';
}
// Print a blank line // Print a blank line
printf("\n"); printf("\n");
char* input; char *input;
for (;;) { for (;;) {
input = myreadline(input_prompt); input = myreadline(input_prompt);
if (input == NULL) // Got EOF; return with it. if (input == NULL) { // Got EOF; return with it.
return (input); return (input);
}
if (input[0] == '#') { // Ignore comments. if (input[0] == '#') { // Ignore comments.
free(input); free(input);
continue; continue;
@ -240,21 +242,22 @@ static char* get_input(void)
add_history(input); add_history(input);
if (!isatty(0)) if (!isatty(0)) {
echo_input(stdout, input_prompt, input); echo_input(stdout, input_prompt, input);
}
if (settings.logfp) if (settings.logfp) {
echo_input(settings.logfp, "", input); echo_input(settings.logfp, "", input);
}
return (input); return (input);
} }
bool silent_yes_or_no(void) bool silent_yes_or_no(void) {
{
bool outcome = false; bool outcome = false;
for (;;) { for (;;) {
char* reply = get_input(); char *reply = get_input();
if (reply == NULL) { if (reply == NULL) {
// LCOV_EXCL_START // LCOV_EXCL_START
// Should be unreachable. Reply should never be NULL // Should be unreachable. Reply should never be NULL
@ -268,13 +271,14 @@ bool silent_yes_or_no(void)
continue; continue;
} }
char* firstword = (char*) xcalloc(strlen(reply) + 1); char *firstword = (char *)xcalloc(strlen(reply) + 1);
sscanf(reply, "%s", firstword); sscanf(reply, "%s", firstword);
free(reply); free(reply);
for (int i = 0; i < (int)strlen(firstword); ++i) for (int i = 0; i < (int)strlen(firstword); ++i) {
firstword[i] = tolower(firstword[i]); firstword[i] = tolower(firstword[i]);
}
int yes = strncmp("yes", firstword, sizeof("yes") - 1); int yes = strncmp("yes", firstword, sizeof("yes") - 1);
int y = strncmp("y", firstword, sizeof("y") - 1); int y = strncmp("y", firstword, sizeof("y") - 1);
@ -289,23 +293,23 @@ bool silent_yes_or_no(void)
} else if (no == 0 || n == 0) { } else if (no == 0 || n == 0) {
outcome = false; outcome = false;
break; break;
} else } else {
rspeak(PLEASE_ANSWER); rspeak(PLEASE_ANSWER);
} }
}
return (outcome); return (outcome);
} }
bool yes_or_no(const char *question, const char *yes_response,
bool yes_or_no(const char* question, const char* yes_response, const char* no_response) const char *no_response) {
/* Print message X, wait for yes/no answer. If yes, print Y and return true; /* Print message X, wait for yes/no answer. If yes, print Y and return
* if no, print Z and return false. */ * true; if no, print Z and return false. */
{
bool outcome = false; bool outcome = false;
for (;;) { for (;;) {
speak(question); speak(question);
char* reply = get_input(); char *reply = get_input();
if (reply == NULL) { if (reply == NULL) {
// LCOV_EXCL_START // LCOV_EXCL_START
// Should be unreachable. Reply should never be NULL // Should be unreachable. Reply should never be NULL
@ -320,13 +324,14 @@ bool yes_or_no(const char* question, const char* yes_response, const char* no_re
continue; continue;
} }
char* firstword = (char*) xcalloc(strlen(reply) + 1); char *firstword = (char *)xcalloc(strlen(reply) + 1);
sscanf(reply, "%s", firstword); sscanf(reply, "%s", firstword);
free(reply); free(reply);
for (int i = 0; i < (int)strlen(firstword); ++i) for (int i = 0; i < (int)strlen(firstword); ++i) {
firstword[i] = tolower(firstword[i]); firstword[i] = tolower(firstword[i]);
}
int yes = strncmp("yes", firstword, sizeof("yes") - 1); int yes = strncmp("yes", firstword, sizeof("yes") - 1);
int y = strncmp("y", firstword, sizeof("y") - 1); int y = strncmp("y", firstword, sizeof("y") - 1);
@ -343,9 +348,9 @@ bool yes_or_no(const char* question, const char* yes_response, const char* no_re
speak(no_response); speak(no_response);
outcome = false; outcome = false;
break; break;
} else } else {
rspeak(PLEASE_ANSWER); rspeak(PLEASE_ANSWER);
}
} }
return (outcome); return (outcome);
@ -353,75 +358,84 @@ bool yes_or_no(const char* question, const char* yes_response, const char* no_re
/* Data structure routines */ /* Data structure routines */
static int get_motion_vocab_id(const char* word) static int get_motion_vocab_id(const char *word) {
// Return the first motion number that has 'word' as one of its words. // Return the first motion number that has 'word' as one of its words.
{
for (int i = 0; i < NMOTIONS; ++i) { for (int i = 0; i < NMOTIONS; ++i) {
for (int j = 0; j < motions[i].words.n; ++j) { for (int j = 0; j < motions[i].words.n; ++j) {
if (strncasecmp(word, motions[i].words.strs[j], TOKLEN) == 0 && (strlen(word) > 1 || if (strncasecmp(word, motions[i].words.strs[j],
TOKLEN) == 0 &&
(strlen(word) > 1 ||
strchr(ignore, word[0]) == NULL || strchr(ignore, word[0]) == NULL ||
!settings.oldstyle)) !settings.oldstyle)) {
return (i); return (i);
} }
} }
}
// If execution reaches here, we didn't find the word. // If execution reaches here, we didn't find the word.
return (WORD_NOT_FOUND); return (WORD_NOT_FOUND);
} }
static int get_object_vocab_id(const char* word) static int get_object_vocab_id(const char *word) {
// Return the first object number that has 'word' as one of its words. // Return the first object number that has 'word' as one of its words.
{ for (int i = 0; i < NOBJECTS + 1;
for (int i = 0; i < NOBJECTS + 1; ++i) { // FIXME: the + 1 should go when 1-indexing for objects is removed ++i) { // FIXME: the + 1 should go when 1-indexing for objects is
// removed
for (int j = 0; j < objects[i].words.n; ++j) { for (int j = 0; j < objects[i].words.n; ++j) {
if (strncasecmp(word, objects[i].words.strs[j], TOKLEN) == 0) if (strncasecmp(word, objects[i].words.strs[j],
TOKLEN) == 0) {
return (i); return (i);
} }
} }
}
// If execution reaches here, we didn't find the word. // If execution reaches here, we didn't find the word.
return (WORD_NOT_FOUND); return (WORD_NOT_FOUND);
} }
static int get_action_vocab_id(const char* word) static int get_action_vocab_id(const char *word) {
// Return the first motion number that has 'word' as one of its words. // Return the first motion number that has 'word' as one of its words.
{
for (int i = 0; i < NACTIONS; ++i) { for (int i = 0; i < NACTIONS; ++i) {
for (int j = 0; j < actions[i].words.n; ++j) { for (int j = 0; j < actions[i].words.n; ++j) {
if (strncasecmp(word, actions[i].words.strs[j], TOKLEN) == 0 && (strlen(word) > 1 || if (strncasecmp(word, actions[i].words.strs[j],
TOKLEN) == 0 &&
(strlen(word) > 1 ||
strchr(ignore, word[0]) == NULL || strchr(ignore, word[0]) == NULL ||
!settings.oldstyle)) !settings.oldstyle)) {
return (i); return (i);
} }
} }
}
// If execution reaches here, we didn't find the word. // If execution reaches here, we didn't find the word.
return (WORD_NOT_FOUND); return (WORD_NOT_FOUND);
} }
static bool is_valid_int(const char *str) static bool is_valid_int(const char *str) {
/* Returns true if the string passed in is represents a valid integer, /* Returns true if the string passed in is represents a valid integer,
* that could then be parsed by atoi() */ * that could then be parsed by atoi() */
{
// Handle negative number // Handle negative number
if (*str == '-') if (*str == '-') {
++str; ++str;
}
// Handle empty string or just "-". Should never reach this // Handle empty string or just "-". Should never reach this
// point, because this is only used with transitive verbs. // point, because this is only used with transitive verbs.
if (!*str) if (!*str) {
return false; // LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE
}
// Check for non-digit chars in the rest of the string. // Check for non-digit chars in the rest of the string.
while (*str) { while (*str) {
if (!isdigit(*str)) if (!isdigit(*str)) {
return false; return false;
else } else {
++str; ++str;
} }
}
return true; return true;
} }
static void get_vocab_metadata(const char* word, vocab_t* id, word_type_t* type) static void get_vocab_metadata(const char *word, vocab_t *id,
{ word_type_t *type) {
/* Check for an empty string */ /* Check for an empty string */
if (strncmp(word, "", sizeof("")) == 0) { if (strncmp(word, "", sizeof("")) == 0) {
*id = WORD_EMPTY; *id = WORD_EMPTY;
@ -432,7 +446,8 @@ static void get_vocab_metadata(const char* word, vocab_t* id, word_type_t* type)
vocab_t ref_num; vocab_t ref_num;
ref_num = get_motion_vocab_id(word); ref_num = get_motion_vocab_id(word);
// Second conjunct is because the magic-word placeholder is a bit special // Second conjunct is because the magic-word placeholder is a bit
// special
if (ref_num != WORD_NOT_FOUND) { if (ref_num != WORD_NOT_FOUND) {
*id = ref_num; *id = ref_num;
*type = MOTION; *type = MOTION;
@ -472,8 +487,7 @@ static void get_vocab_metadata(const char* word, vocab_t* id, word_type_t* type)
return; return;
} }
static void tokenize(char* raw, command_t *cmd) static void tokenize(char *raw, command_t *cmd) {
{
/* /*
* Be careful about modifying this. We do not want to nuke the * Be careful about modifying this. We do not want to nuke the
* the speech part or ID from the previous turn. * the speech part or ID from the previous turn.
@ -482,8 +496,8 @@ static void tokenize(char* raw, command_t *cmd)
memset(&cmd->word[1].raw, '\0', sizeof(cmd->word[1].raw)); memset(&cmd->word[1].raw, '\0', sizeof(cmd->word[1].raw));
/* Bound prefix on the %s would be needed to prevent buffer /* Bound prefix on the %s would be needed to prevent buffer
* overflow. but we shortstop this more simply by making each * overflow. We shortstop this more simply by making each
* raw-input buffer as int as the entire input buffer. */ * raw-input buffer as long as the entire input buffer. */
sscanf(raw, "%s%s", cmd->word[0].raw, cmd->word[1].raw); sscanf(raw, "%s%s", cmd->word[0].raw, cmd->word[1].raw);
/* (ESR) In oldstyle mode, simulate the uppercasing and truncating /* (ESR) In oldstyle mode, simulate the uppercasing and truncating
@ -499,38 +513,48 @@ static void tokenize(char* raw, command_t *cmd)
* in their tools. On the other, not simulating this misbehavior * in their tools. On the other, not simulating this misbehavior
* goes against the goal of making oldstyle as accurate as * goes against the goal of making oldstyle as accurate as
* possible an emulation of the original UI. * possible an emulation of the original UI.
*
* The definition of TRUNCLEN is dubious. It accurately reflects the
* FORTRAN, but it's possible that was a bug and the proper definition
* is (TOKLEN).
*/ */
#define TRUNCLEN (TOKLEN + TOKLEN)
if (settings.oldstyle) { if (settings.oldstyle) {
cmd->word[0].raw[TOKLEN + TOKLEN] = cmd->word[1].raw[TOKLEN + TOKLEN] = '\0'; cmd->word[0].raw[TRUNCLEN] = cmd->word[1].raw[TRUNCLEN] = '\0';
for (size_t i = 0; i < strlen(cmd->word[0].raw); i++) for (size_t i = 0; i < strlen(cmd->word[0].raw); i++) {
cmd->word[0].raw[i] = toupper(cmd->word[0].raw[i]); cmd->word[0].raw[i] = toupper(cmd->word[0].raw[i]);
for (size_t i = 0; i < strlen(cmd->word[1].raw); i++) }
for (size_t i = 0; i < strlen(cmd->word[1].raw); i++) {
cmd->word[1].raw[i] = toupper(cmd->word[1].raw[i]); cmd->word[1].raw[i] = toupper(cmd->word[1].raw[i]);
} }
}
/* populate command with parsed vocabulary metadata */ /* populate command with parsed vocabulary metadata */
get_vocab_metadata(cmd->word[0].raw, &(cmd->word[0].id), &(cmd->word[0].type)); get_vocab_metadata(cmd->word[0].raw, &(cmd->word[0].id),
get_vocab_metadata(cmd->word[1].raw, &(cmd->word[1].id), &(cmd->word[1].type)); &(cmd->word[0].type));
get_vocab_metadata(cmd->word[1].raw, &(cmd->word[1].id),
&(cmd->word[1].type));
cmd->state = TOKENIZED; cmd->state = TOKENIZED;
} }
bool get_command_input(command_t *command) bool get_command_input(command_t *command) {
/* Get user input on stdin, parse and map to command */ /* Get user input on stdin, parse and map to command */
{
char inputbuf[LINESIZE]; char inputbuf[LINESIZE];
char* input; char *input;
for (;;) { for (;;) {
input = get_input(); input = get_input();
if (input == NULL) if (input == NULL) {
return false; return false;
}
if (word_count(input) > 2) { if (word_count(input) > 2) {
rspeak(TWO_WORDS); rspeak(TWO_WORDS);
free(input); free(input);
continue; continue;
} }
if (strcmp(input, "") != 0) if (strcmp(input, "") != 0) {
break; break;
}
free(input); free(input);
} }
@ -541,14 +565,14 @@ bool get_command_input(command_t *command)
#ifdef GDEBUG #ifdef GDEBUG
/* Needs to stay synced with enum word_type_t */ /* Needs to stay synced with enum word_type_t */
const char *types[] = {"NO_WORD_TYPE", "MOTION", "OBJECT", "ACTION", "NUMERIC"}; const char *types[] = {"NO_WORD_TYPE", "MOTION", "OBJECT", "ACTION",
"NUMERIC"};
/* needs to stay synced with enum speechpart */ /* needs to stay synced with enum speechpart */
const char *roles[] = {"unknown", "intransitive", "transitive"}; const char *roles[] = {"unknown", "intransitive", "transitive"};
printf("Command: role = %s type1 = %s, id1 = %d, type2 = %s, id2 = %d\n", printf(
roles[command->part], "Command: role = %s type1 = %s, id1 = %d, type2 = %s, id2 = %d\n",
types[command->word[0].type], roles[command->part], types[command->word[0].type],
command->word[0].id, command->word[0].id, types[command->word[1].type],
types[command->word[1].type],
command->word[1].id); command->word[1].id);
#endif #endif
@ -556,9 +580,8 @@ bool get_command_input(command_t *command)
return true; return true;
} }
void clear_command(command_t *cmd) void clear_command(command_t *cmd) {
/* Resets the state of the command to empty */ /* Resets the state of the command to empty */
{
cmd->verb = ACT_NULL; cmd->verb = ACT_NULL;
cmd->part = unknown; cmd->part = unknown;
game.oldobj = cmd->obj; game.oldobj = cmd->obj;
@ -566,10 +589,10 @@ void clear_command(command_t *cmd)
cmd->state = EMPTY; cmd->state = EMPTY;
} }
void juggle(obj_t object) void juggle(obj_t object) {
/* Juggle an object by picking it up and putting it down again, the purpose /* Juggle an object by picking it up and putting it down again, the
* being to get the object to the front of the chain of things at its loc. */ * purpose being to get the object to the front of the chain of things
{ * at its loc. */
loc_t i, j; loc_t i, j;
i = game.objects[object].place; i = game.objects[object].place;
@ -578,47 +601,47 @@ void juggle(obj_t object)
move(object + NOBJECTS, j); move(object + NOBJECTS, j);
} }
void move(obj_t object, loc_t where) void move(obj_t object, loc_t where) {
/* Place any object anywhere by picking it up and dropping it. May /* Place any object anywhere by picking it up and dropping it. May
* already be toting, in which case the carry is a no-op. Mustn't * already be toting, in which case the carry is a no-op. Mustn't
* pick up objects which are not at any loc, since carry wants to * pick up objects which are not at any loc, since carry wants to
* remove objects from game atloc chains. */ * remove objects from game atloc chains. */
{
loc_t from; loc_t from;
if (object > NOBJECTS) if (object > NOBJECTS) {
from = game.objects[object - NOBJECTS].fixed; from = game.objects[object - NOBJECTS].fixed;
else } else {
from = game.objects[object].place; from = game.objects[object].place;
/* (ESR) Used to check for !SPECIAL(from). I *think* that was wrong... */ }
if (from != LOC_NOWHERE && from != CARRIED) /* (ESR) Used to check for !SPECIAL(from). I *think* that was wrong...
*/
if (from != LOC_NOWHERE && from != CARRIED) {
carry(object, from); carry(object, from);
}
drop(object, where); drop(object, where);
} }
void put(obj_t object, loc_t where, int pval) void put(obj_t object, loc_t where, int pval) {
/* put() is the same as move(), except it returns a value used to set up the /* put() is the same as move(), except the object is stashed and
* negated game.prop values for the repository objects. */ * can no longer be picked up. */
{
move(object, where); move(object, where);
/* (ESR) Read this in combination with the macro defintions in advebt.h. OBJECT_STASHIFY(object, pval);
*/ #ifdef OBJECT_SET_SEEN
game.objects[object].prop = PROP_STASHIFY(pval); OBJECT_SET_SEEN(object);
#ifdef PROP_SET_SEEN
PROP_SET_SEEN(object);
#endif #endif
} }
void carry(obj_t object, loc_t where) void carry(obj_t object, loc_t where) {
/* Start toting an object, removing it from the list of things at its former /* Start toting an object, removing it from the list of things at its
* location. Incr holdng unless it was already being toted. If object>NOBJECTS * former location. Incr holdng unless it was already being toted. If
* (moving "fixed" second loc), don't change game.place or game.holdng. */ * object>NOBJECTS (moving "fixed" second loc), don't change game.place
{ * or game.holdng. */
int temp; int temp;
if (object <= NOBJECTS) { if (object <= NOBJECTS) {
if (game.objects[object].place == CARRIED) if (game.objects[object].place == CARRIED) {
return; return;
}
game.objects[object].place = CARRIED; game.objects[object].place = CARRIED;
/* /*
@ -628,9 +651,10 @@ void carry(obj_t object, loc_t where)
* *
* Possibly this check should be skipped whwn oldstyle is on. * Possibly this check should be skipped whwn oldstyle is on.
*/ */
if (object != BIRD) if (object != BIRD) {
++game.holdng; ++game.holdng;
} }
}
if (game.locs[where].atloc == object) { if (game.locs[where].atloc == object) {
game.locs[where].atloc = game.link[object]; game.locs[where].atloc = game.link[object];
return; return;
@ -642,68 +666,71 @@ void carry(obj_t object, loc_t where)
game.link[temp] = game.link[object]; game.link[temp] = game.link[object];
} }
void drop(obj_t object, loc_t where) void drop(obj_t object, loc_t where) {
/* Place an object at a given loc, prefixing it onto the game atloc list. Decr /* Place an object at a given loc, prefixing it onto the game atloc
* game.holdng if the object was being toted. No state change on the object. */ * list. Decr game.holdng if the object was being toted. No state
{ * change on the object. */
if (object > NOBJECTS) if (object > NOBJECTS) {
game.objects[object - NOBJECTS].fixed = where; game.objects[object - NOBJECTS].fixed = where;
else { } else {
if (game.objects[object].place == CARRIED) if (game.objects[object].place == CARRIED) {
if (object != BIRD) if (object != BIRD) {
/* The bird has to be weightless. This ugly hack (and the /* The bird has to be weightless. This ugly
* corresponding code in the carry function) brought to you * hack (and the corresponding code in the carry
* by the fact that when the bird is caged, we need to be able * function) brought to you by the fact that
* to either 'take bird' or 'take cage' and have the right thing * when the bird is caged, we need to be able to
* happen. * either 'take bird' or 'take cage' and have
* the right thing happen.
*/ */
--game.holdng; --game.holdng;
}
}
game.objects[object].place = where; game.objects[object].place = where;
} }
if (where == LOC_NOWHERE || where == CARRIED) if (where == LOC_NOWHERE || where == CARRIED) {
return; return;
}
game.link[object] = game.locs[where].atloc; game.link[object] = game.locs[where].atloc;
game.locs[where].atloc = object; game.locs[where].atloc = object;
} }
int atdwrf(loc_t where) int atdwrf(loc_t where) {
/* Return the index of first dwarf at the given location, zero if no dwarf is /* Return the index of first dwarf at the given location, zero if no
* there (or if dwarves not active yet), -1 if all dwarves are dead. Ignore * dwarf is there (or if dwarves not active yet), -1 if all dwarves are
* the pirate (6th dwarf). */ * dead. Ignore the pirate (6th dwarf). */
{
int at; int at;
at = 0; at = 0;
if (game.dflag < 2) if (game.dflag < 2) {
return at; return at;
}
at = -1; at = -1;
for (int i = 1; i <= NDWARVES - 1; i++) { for (int i = 1; i <= NDWARVES - 1; i++) {
if (game.dwarves[i].loc == where) if (game.dwarves[i].loc == where) {
return i; return i;
if (game.dwarves[i].loc != 0) }
if (game.dwarves[i].loc != 0) {
at = 0; at = 0;
} }
}
return at; return at;
} }
/* Utility routines (setbit, tstbit, set_seed, get_next_lcg_value, /* Utility routines (setbit, tstbit, set_seed, get_next_lcg_value,
* randrange) */ * randrange) */
int setbit(int bit) int setbit(int bit) {
/* Returns 2**bit for use in constructing bit-masks. */ /* Returns 2**bit for use in constructing bit-masks. */
{
return (1L << bit); return (1L << bit);
} }
bool tstbit(int mask, int bit) bool tstbit(int mask, int bit) {
/* Returns true if the specified bit is set in the mask. */ /* Returns true if the specified bit is set in the mask. */
{
return (mask & (1 << bit)) != 0; return (mask & (1 << bit)) != 0;
} }
void set_seed(int32_t seedval) void set_seed(int32_t seedval) {
/* Set the LCG1 seed */ /* Set the LCG1 seed */
{
game.lcg_x = seedval % LCG_M; game.lcg_x = seedval % LCG_M;
if (game.lcg_x < 0) { if (game.lcg_x < 0) {
game.lcg_x = LCG_M + game.lcg_x; game.lcg_x = LCG_M + game.lcg_x;
@ -716,9 +743,8 @@ void set_seed(int32_t seedval)
game.zzword[5] = '\0'; game.zzword[5] = '\0';
} }
static int32_t get_next_lcg_value(void) static int32_t get_next_lcg_value(void) {
/* Return the LCG's current value, and then iterate it. */ /* Return the LCG's current value, and then iterate it. */
{
int32_t old_x = game.lcg_x; int32_t old_x = game.lcg_x;
game.lcg_x = (LCG_A * game.lcg_x + LCG_C) % LCG_M; game.lcg_x = (LCG_A * game.lcg_x + LCG_C) % LCG_M;
if (settings.debug) { if (settings.debug) {
@ -727,23 +753,21 @@ static int32_t get_next_lcg_value(void)
return old_x; return old_x;
} }
int32_t randrange(int32_t range) int32_t randrange(int32_t range) {
/* Return a random integer from [0, range). */ /* Return a random integer from [0, range). */
{
return range * get_next_lcg_value() / LCG_M; return range * get_next_lcg_value() / LCG_M;
} }
// LCOV_EXCL_START // LCOV_EXCL_START
void bug(enum bugtype num, const char *error_string) void bug(enum bugtype num, const char *error_string) {
{
fprintf(stderr, "Fatal error %d, %s.\n", num, error_string); fprintf(stderr, "Fatal error %d, %s.\n", num, error_string);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
void state_change(obj_t obj, int state) void state_change(obj_t obj, int state) {
/* Object must have a change-message list for this to be useful; only some do */ /* Object must have a change-message list for this to be useful; only
{ * some do */
game.objects[obj].prop = state; game.objects[obj].prop = state;
pspeak(obj, change, true, state); pspeak(obj, change, true, state);
} }

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: Copyright Eric S. Raymond <esr@thyrsus.com> // SPDX-FileCopyrightText: (C) 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 longstannding minor bug in the Sarson nudged us into fixing a longstanding minor bug in the
handling of incorrect magic-word sequebcesm, handling of incorrect magic-word sequences,
== Nomenclature == == Nomenclature ==
@ -75,10 +75,15 @@ 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". "eying" for "eyeing", "thresholds" for "threshholds", "pencilled"
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 something). formerly say "floor" when it should say "ground" (or "dirt", or
something).
* The "knives vanish" message could formerly be emitted when "I see no
knife here." would be appropriate.
Enhancements: Enhancements:
@ -139,7 +144,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 (comditionally on An -a command-line option has been added (conditionally 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,19 +4,17 @@
* (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: 1977, 2005 by Will Crowther and Don Woods * SPDX-FileCopyrightText: (C) 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.
@ -25,17 +23,21 @@
struct save_t save; struct save_t save;
#define IGNORE(r) do{if (r){}}while(0) #define IGNORE(r) \
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)); memcpy(&save.magic, ADVENT_MAGIC, sizeof(ADVENT_MAGIC));
if (save.version == 0) if (save.version == 0) {
save.version = SAVE_VERSION; 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);
@ -43,17 +45,18 @@ int savefile(FILE *fp)
/* Suspend and resume */ /* Suspend and resume */
static char *strip(char *name) static char *strip(char *name) {
{
// Trim leading whitespace // Trim leading whitespace
while(isspace((unsigned char)*name)) while (isspace((unsigned char)*name)) {
name++; // LCOV_EXCL_LINE 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';
} }
@ -61,8 +64,7 @@ static char *strip(char *name)
return name; return name;
} }
int suspend(void) int suspend(void) {
{
/* Suspend. Offer to save things in a file, but charging /* Suspend. Offer to save things in a file, but charging
* some points (so can't win by using saved games to retry * some points (so can't win by using saved games to retry
* battles or to start over after learning zzword). * battles or to start over after learning zzword).
@ -75,20 +77,26 @@ int suspend(void)
FILE *fp = NULL; FILE *fp = NULL;
rspeak(SUSPEND_WARNING); rspeak(SUSPEND_WARNING);
if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN])) if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE],
arbitrary_messages[OK_MAN],
arbitrary_messages[OK_MAN])) {
return GO_CLEAROBJ; return GO_CLEAROBJ;
}
game.saved = game.saved + 5; 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); fp = fopen(strip(name), WRITE_MODE);
if (fp == NULL) if (fp == NULL) {
printf("Can't open file %s, try again.\n", name); printf("Can't open file %s, try again.\n", name);
}
free(name); free(name);
} }
@ -98,8 +106,7 @@ int suspend(void)
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} }
int resume(void) int resume(void) {
{
/* Resume. Read a suspended game back from a file. /* Resume. Read a suspended game back from a file.
* If ADVENT_NOSAVE is defined, gripe instead. */ * If ADVENT_NOSAVE is defined, gripe instead. */
@ -111,28 +118,33 @@ int resume(void)
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], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN])) if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE],
arbitrary_messages[OK_MAN],
arbitrary_messages[OK_MAN])) {
return GO_CLEAROBJ; 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); fp = fopen(name, READ_MODE);
if (fp == NULL) if (fp == NULL) {
printf("Can't open file %s, try again.\n", name); printf("Can't open file %s, try again.\n", name);
}
free(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 /* Read and restore game state from file, assuming
* sane initial state. * sane initial state.
* If ADVENT_NOSAVE is defined, gripe instead. */ * If ADVENT_NOSAVE is defined, gripe instead. */
@ -143,10 +155,12 @@ int restore(FILE* fp)
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 || save.canary != ENDIAN_MAGIC) if (memcmp(save.magic, ADVENT_MAGIC, sizeof(ADVENT_MAGIC)) != 0 ||
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),
SAVE_VERSION / 10, MOD(SAVE_VERSION, 10));
} else if (!is_valid(save.game)) { } else if (!is_valid(save.game)) {
rspeak(SAVE_TAMPERING); rspeak(SAVE_TAMPERING);
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
@ -156,8 +170,7 @@ int restore(FILE* fp)
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: /* Save files can be roughly grouped into three groups:
* With valid, reachable state, with valid, but unreachable * With valid, reachable state, with valid, but unreachable
* state and with invalid state. We check that state is * state and with invalid state. We check that state is
@ -171,34 +184,33 @@ bool is_valid(struct game_t valgame)
/* Check for RNG overflow. Truncate */ /* Check for RNG overflow. Truncate */
if (valgame.lcg_x >= LCG_M) { if (valgame.lcg_x >= LCG_M) {
valgame.lcg_x %= LCG_M; // LCOV_EXCL_LINE return false;
}
/* Check for RNG underflow. Transpose */
if (valgame.lcg_x < LCG_M) {
valgame.lcg_x = LCG_M + (valgame.lcg_x % LCG_M);
} }
/* Bounds check for locations */ /* Bounds check for locations */
if ( valgame.chloc < -1 || valgame.chloc > NLOCATIONS || if (valgame.chloc < -1 || valgame.chloc > NLOCATIONS ||
valgame.chloc2 < -1 || valgame.chloc2 > NLOCATIONS || valgame.chloc2 < -1 || valgame.chloc2 > NLOCATIONS ||
valgame.loc < 0 || valgame.loc > NLOCATIONS || valgame.loc < 0 || valgame.loc > NLOCATIONS || valgame.newloc < 0 ||
valgame.newloc < 0 || valgame.newloc > NLOCATIONS || valgame.newloc > NLOCATIONS || valgame.oldloc < 0 ||
valgame.oldloc < 0 || valgame.oldloc > NLOCATIONS || valgame.oldloc > NLOCATIONS || valgame.oldlc2 < 0 ||
valgame.oldlc2 < 0 || valgame.oldlc2 > NLOCATIONS) { valgame.oldlc2 > NLOCATIONS) {
return false; // LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE
} }
/* Bounds check for location arrays */ /* Bounds check for location arrays */
for (int i = 0; i <= NDWARVES; i++) { for (int i = 0; i <= NDWARVES; i++) {
if (valgame.dwarves[i].loc < -1 || valgame.dwarves[i].loc > NLOCATIONS || if (valgame.dwarves[i].loc < -1 ||
valgame.dwarves[i].oldloc < -1 || valgame.dwarves[i].oldloc > NLOCATIONS) { valgame.dwarves[i].loc > NLOCATIONS ||
valgame.dwarves[i].oldloc < -1 ||
valgame.dwarves[i].oldloc > NLOCATIONS) {
return false; // LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE
} }
} }
for (int i = 0; i <= NOBJECTS; i++) { for (int i = 0; i <= NOBJECTS; i++) {
if (valgame.objects[i].place < -1 || valgame.objects[i].place > NLOCATIONS || if (valgame.objects[i].place < -1 ||
valgame.objects[i].fixed < -1 || valgame.objects[i].fixed > NLOCATIONS) { valgame.objects[i].place > NLOCATIONS ||
valgame.objects[i].fixed < -1 ||
valgame.objects[i].fixed > NLOCATIONS) {
return false; // LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE
} }
} }
@ -218,7 +230,7 @@ bool is_valid(struct game_t valgame)
int temp_tally = 0; int temp_tally = 0;
for (int treasure = 1; treasure <= NOBJECTS; treasure++) { for (int treasure = 1; treasure <= NOBJECTS; treasure++) {
if (objects[treasure].is_treasure) { if (objects[treasure].is_treasure) {
if (PROP_IS_NOTFOUND2(valgame, treasure)) { if (OBJECT_IS_NOTFOUND2(valgame, treasure)) {
++temp_tally; ++temp_tally;
} }
} }
@ -234,14 +246,17 @@ bool is_valid(struct game_t valgame)
} }
} }
/* Check that values in linked lists for objects in locations are inside bounds */ /* Check that values in linked lists for objects in locations are inside
* bounds */
for (loc_t loc = LOC_NOWHERE; loc <= NLOCATIONS; loc++) { for (loc_t loc = LOC_NOWHERE; loc <= NLOCATIONS; loc++) {
if (valgame.locs[loc].atloc < NO_OBJECT || valgame.locs[loc].atloc > NOBJECTS * 2) { if (valgame.locs[loc].atloc < NO_OBJECT ||
valgame.locs[loc].atloc > NOBJECTS * 2) {
return false; // LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE
} }
} }
for (obj_t obj = 0; obj <= NOBJECTS * 2; obj++ ) { for (obj_t obj = 0; obj <= NOBJECTS * 2; obj++) {
if (valgame.link[obj] < NO_OBJECT || valgame.link[obj] > NOBJECTS * 2) { if (valgame.link[obj] < NO_OBJECT ||
valgame.link[obj] > NOBJECTS * 2) {
return false; // LCOV_EXCL_LINE return false; // LCOV_EXCL_LINE
} }
} }

79
score.c
View file

@ -1,20 +1,18 @@
/* /*
* Scoring and wrap-up. * Scoring and wrap-up.
* *
* SPDX-FileCopyrightText: 1977, 2005 by Will Crowther and Don Woods * SPDX-FileCopyrightText: (C) 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 died /* mode is 'scoregame' if scoring, 'quitgame' if quitting, 'endgame' if
* or won */ * died or won */
{
int score = 0; int score = 0;
/* The present scoring algorithm is as follows: /* The present scoring algorithm is as follows:
@ -33,25 +31,31 @@ int score(enum termination mode)
* 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 for * Points can also be deducted for using hints or too many turns, or
* saving intermediate positions. */ * for 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) }
if (i > CHEST) {
k = 16; k = 16;
if (!PROP_IS_STASHED(i) && !PROP_IS_NOTFOUND(i)) }
if (!OBJECT_IS_STASHED(i) && !OBJECT_IS_NOTFOUND(i)) {
score += 2; score += 2;
if (game.objects[i].place == LOC_BUILDING && PROP_IS_FOUND(i)) }
if (game.objects[i].place == LOC_BUILDING &&
OBJECT_IS_FOUND(i)) {
score += k - 2; score += k - 2;
}
mxscor += k; mxscor += k;
} }
} }
@ -64,45 +68,57 @@ int score(enum termination mode)
* 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; mxscor += 25;
if (game.closng) if (game.closng) {
score += 25; score += 25;
}
mxscor += 25; mxscor += 25;
if (game.closed) { if (game.closed) {
if (game.bonus == none) if (game.bonus == none) {
score += 10; score += 10;
if (game.bonus == splatter) }
if (game.bonus == splatter) {
score += 25; score += 25;
if (game.bonus == defeat) }
if (game.bonus == defeat) {
score += 30; score += 30;
if (game.bonus == victory) }
if (game.bonus == victory) {
score += 45; score += 45;
} }
}
mxscor += 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 desc. */ /* Deduct for hints/turns/saves. Hints < 4 are special; see database
* 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) }
if (game.novice) {
score -= 5; score -= 5;
if (game.clshnt) }
if (game.clshnt) {
score -= 10; score -= 10;
}
score = score - game.trnluz - game.saved; 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. */
@ -113,18 +129,19 @@ int score(enum termination mode)
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); rspeak(TOTAL_SCORE, points, mxscor, game.turns, game.turns);
for (int i = 1; i <= (int)NCLASSES; i++) { for (int i = 1; i <= (int)NCLASSES; i++) {
if (classes[i].threshold >= points) { if (classes[i].threshold >= points) {

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[NDWARFLOCS] = {{{dwarflocs}}}; const int dwarflocs[NDWARVES] = {{{dwarflocs}}};
/* end */ /* end */

View file

@ -35,9 +35,8 @@ 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 */
/* Count of dwarf starting locations */ #define NDWARVES {ndwarflocs} // number of dwarves
#define NDWARFLOCS {ndwarflocs} extern const int dwarflocs[NDWARVES];
extern const int dwarflocs[NDWARFLOCS];
typedef struct {{ typedef struct {{
const char** strs; const char** strs;

View file

@ -124,10 +124,6 @@ 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
@ -174,7 +170,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 NOMPARE tests. # List all NOCOMPARE tests.
residuals: residuals:
@grep -n NOCOMPARE *.log @grep -n NOCOMPARE *.log

386
tests/knife.chk Normal file
View file

@ -0,0 +1,386 @@
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.

57
tests/knife.log Normal file
View file

@ -0,0 +1,57 @@
## 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,6 +1,4 @@
#! /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
# #
@ -16,17 +14,23 @@
# 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.
# #
if [ "$1" = "-b" ] diffopts=-u
then while getopts bn opt
diffopts=-ub do
shift case $opt in
else b) diffopts=-ub;;
diffopts=-u *) echo "tapdiffer: unknown option ${opt}."; exit 1;;
fi esac
done
# shellcheck disable=SC2004
shift $(($OPTIND - 1))
legend=$1 legend=$1
checkfile=$2 checkfile=$2
@ -37,7 +41,7 @@ if diff --text "${diffopts}" "${checkfile}" - >/tmp/tapdiff$$
then then
echo "ok - ${legend}" echo "ok - ${legend}"
else else
echo "not ok - ${checkfile}: ${legend}" echo "not ok - ${legend}"
if [ ! "${QUIET}" = 1 ] if [ ! "${QUIET}" = 1 ]
then then
echo " --- |" echo " --- |"

View file

@ -4,14 +4,15 @@
# 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 a # OSD-compliant; otherwise the following SPDX tag incorporates the
# license by reference. # MIT No Attribution license by reference.
# #
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com> # SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: MIT-0
# #
# 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"
@ -19,13 +20,16 @@ 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}\n" report="${report}${1}$LF"
} }
ship_error() { ship_error() {
@ -34,7 +38,7 @@ ship_error() {
then then
echo "" echo ""
fi fi
report="${report}${1}\n" report="${report}${1}$LF"
echo "${report}" echo "${report}"
exit 1 exit 1
} }
@ -76,6 +80,10 @@ 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
} }