Compare commits
213 commits
Author | SHA1 | Date | |
---|---|---|---|
|
7d848c89e1 | ||
|
7bbf994fce | ||
|
3e989aec53 | ||
|
9df69fe034 | ||
|
a2bb39dc7e | ||
|
5c90880f0a | ||
|
92451f1fff | ||
|
96ad6c6245 | ||
|
40742e112b | ||
|
a4fd14caf7 | ||
|
9a6e4406f5 | ||
|
08f0351817 | ||
|
0157e58668 | ||
|
354e56a69b | ||
|
9c3f4b0c90 | ||
|
20fd7c589f | ||
|
8c553af53e | ||
|
f1cb740c41 | ||
|
cf4adf8d02 | ||
|
acdfa96315 | ||
|
3d6c689ffa | ||
|
8dcc6e6641 | ||
|
e17ff128da | ||
|
cb293f4aa4 | ||
|
124e7768b4 | ||
|
63e8579f74 | ||
|
1080b45d39 | ||
|
86fe4bd121 | ||
|
bd499dc532 | ||
|
ae6eced72d | ||
|
7903ac1bb8 | ||
|
b51612131d | ||
|
62cd0c78da | ||
|
bad34acf1e | ||
|
0fafbe379f | ||
|
fcf6935689 | ||
|
b610a62685 | ||
|
6174129116 | ||
|
a2fa136988 | ||
|
43a08621e4 | ||
|
f24fcd2971 | ||
|
580f409ee6 | ||
|
5b917084b0 | ||
|
1ef39055f3 | ||
|
0175344caa | ||
|
be429016af | ||
|
c11938aed5 | ||
|
258b7703f2 | ||
|
83c6a66887 | ||
|
3c09c25cc3 | ||
|
f26514b5dd | ||
|
cf7ea0cd89 | ||
|
2db3bed3f1 | ||
|
a6ec8a9595 | ||
|
75bc031b46 | ||
|
dd65960ec5 | ||
|
90f96a25da | ||
|
c10e9694e3 | ||
|
11d966f349 | ||
|
e1a17fd442 | ||
|
eb63994931 | ||
|
0b82afa5c1 | ||
|
b247359d7f | ||
|
1f9f39d789 | ||
|
2582e240bd | ||
|
9fbd603f8d | ||
|
5016978156 | ||
|
7d4dd78679 | ||
|
ab4653b89c | ||
|
b28eb66868 | ||
|
f0119f8431 | ||
|
5075c63cf4 | ||
|
ebf3e389c0 | ||
|
6cef68cda5 | ||
|
234da6b468 | ||
|
adbb5c1204 | ||
|
869c53d1b1 | ||
|
ef78c36ddb | ||
|
5d3205e1e9 | ||
|
a6b41fdb8f | ||
|
9ff036d789 | ||
|
88feaab0c0 | ||
|
4d4e8dce96 | ||
|
82c3ae5e65 | ||
|
1af01ff91f | ||
|
9e9731d59b | ||
|
191d3cb043 | ||
|
c82d1214fa | ||
|
02987d0330 | ||
|
9cd7c53d78 | ||
|
ab2779cd93 | ||
|
819aed5c4e | ||
|
8d4d64fafb | ||
|
eebc87f889 | ||
|
8fe07c8bf3 | ||
|
ff9c73a37d | ||
|
195e6e149b | ||
|
4369284c75 | ||
|
7e21108e95 | ||
|
829c13d1d5 | ||
|
2f60504e07 | ||
|
5de3b8ff70 | ||
|
520d365f74 | ||
|
4b08b726f9 | ||
|
58cf204eba | ||
|
d16822a583 | ||
|
3640e5cb96 | ||
|
e99cc69de0 | ||
|
e8991f69e9 | ||
|
3e72486724 | ||
|
f5ff25f52a | ||
|
e1a528a4c5 | ||
|
9726d8207c | ||
|
fe378b9e13 | ||
|
4ce4de190e | ||
|
9b53140f1c | ||
|
3c6648882d | ||
|
00bdc2133a | ||
|
20e1b9d930 | ||
|
1ce4fc2ac3 | ||
|
642b7e4890 | ||
|
ec1d99f354 | ||
|
6ebc2bf0e3 | ||
|
25230068fe | ||
|
71abcb4e65 | ||
|
2dd1ccc535 | ||
|
0a1f5dbb43 | ||
|
20cb8b9ffb | ||
|
b86d4afd20 | ||
|
d9ddf4d805 | ||
|
9c6219a27d | ||
|
34516ecf39 | ||
|
7dc3482c5b | ||
|
40a4acb868 | ||
|
54927c33e5 | ||
|
e49fb5f367 | ||
|
59a5afb72e | ||
|
f95442b310 | ||
|
97a69d8cbd | ||
|
0d2332573b | ||
|
7af8492169 | ||
|
3af590e972 | ||
|
2c8aa9668c | ||
|
30a98cc916 | ||
|
872b94f927 | ||
|
74c3158f42 | ||
|
ef180a0731 | ||
|
77ac2f1570 | ||
|
ff46cf7fac | ||
|
c80162b467 | ||
|
65f221e03f | ||
|
cbb5572b21 | ||
|
a5eeb0b2bc | ||
|
4eccfa127c | ||
|
dc8b19bcdc | ||
|
2070db3a2a | ||
|
e3fbac804b | ||
|
5f7ce87040 | ||
|
1e05134b47 | ||
|
1ffb81b70b | ||
|
fefd2a19fb | ||
|
4fa3d4d758 | ||
|
07a0f066ba | ||
|
a006bdd272 | ||
|
327efd3678 | ||
|
b044e9ab42 | ||
|
8fd3eb8b92 | ||
|
9fdbe73315 | ||
|
9758883ea2 | ||
|
f5d15ab1f2 | ||
|
f07b3ba2d4 | ||
|
87855f8124 | ||
|
786832210e | ||
|
9b89dd2829 | ||
|
17840d0e15 | ||
|
5929a68b88 | ||
|
43af075fa9 | ||
|
47c3d14a11 | ||
|
db8ca5eb26 | ||
|
ca5b6975dc | ||
|
282842c4a9 | ||
|
7723f3fc1a | ||
|
0ffb297801 | ||
|
04df0ce64c | ||
|
569a39aa7c | ||
|
1652df4540 | ||
|
ba3248224e | ||
|
b7bf85904d | ||
|
71f05c4567 | ||
|
fe8a82927c | ||
|
b125fe7b2a | ||
|
3971a61ab0 | ||
|
8f527fb433 | ||
|
643eab4e9c | ||
|
f53476f826 | ||
|
83c32598be | ||
|
98b95e92ee | ||
|
8d409c6b3b | ||
|
e1ce7d6b6a | ||
|
f911e42453 | ||
|
12d39ef72b | ||
|
1efd1027f7 | ||
|
1121bb8aa5 | ||
|
a01c08385c | ||
|
643656fcc3 | ||
|
ac0c5fc024 | ||
|
dfff80faa8 | ||
|
426684fec2 | ||
|
162c5abc8a | ||
|
1f644a1d7d | ||
|
17782cab67 | ||
|
94e7cc6505 | ||
|
3f34adad3b |
236 changed files with 15709 additions and 7382 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
# SPDX-FileCopyrightText: (C) Eric S. Raymond
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
advent
|
||||
*.gcda
|
||||
*.gcno
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# SPDX-FileCopyrightText: (C) Eric S. Raymond
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
stages:
|
||||
- ci-build
|
||||
- build
|
||||
|
|
2
.shipper
2
.shipper
|
@ -1,3 +1,5 @@
|
|||
#SPDX-FileCopyrightText: (C) Eric S. Raymond
|
||||
#SPDX-License-Identifier: BSD-2-Clause
|
||||
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>
|
||||
"""
|
||||
|
|
5
COPYING
5
COPYING
|
@ -1,7 +1,4 @@
|
|||
BSD LICENSE
|
||||
|
||||
Copyright (c) 1977, 2005 by Will Crowther and Don Woods
|
||||
Copyright (c) 2017 by Eric S. Raymond
|
||||
BSD 2-Clause LICENSE
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# This image is built by the Gitlab CI pipeline to be used in subsequent
|
||||
# pipeline steps.
|
||||
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
FROM ubuntu
|
||||
|
||||
|
@ -7,4 +9,4 @@ FROM ubuntu
|
|||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
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
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
= Installing Open Adventure =
|
||||
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
// SPDX-License-Identifier: CC-BY-4.0
|
||||
|
||||
Installation now requires Python3 due to a security issue
|
||||
with the YAML library.
|
||||
|
@ -19,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`.
|
||||
|
||||
5. Run `./advent` to play.
|
||||
|
||||
6. If you want to buld the documentation you will need asciidoctor.
|
||||
|
||||
7. Running the regression tests requires batchspell
|
||||
|
|
60
Makefile
60
Makefile
|
@ -1,14 +1,18 @@
|
|||
# Makefile for the open-source release of adventure 2.5
|
||||
|
||||
# To build with save/resume disabled, pass CFLAGS="-DADVENT_NOSAVE"
|
||||
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
VERS=$(shell sed -n <NEWS '/^[0-9]/s/:.*//p' | head -1)
|
||||
# To build with save/resume disabled, pass CFLAGS="-DADVENT_NOSAVE"
|
||||
# To build with auto-save/resume enabled, pass CFLAGS="-DADVENT_AUTOSAVE"
|
||||
|
||||
VERS=$(shell sed -n <NEWS.adoc '/^[0-9]/s/:.*//p' | head -1)
|
||||
|
||||
.PHONY: debug indent release refresh dist linty html clean
|
||||
.PHONY: check coverage
|
||||
|
||||
CC?=gcc
|
||||
CCFLAGS+=-std=c99 -D_DEFAULT_SOURCE -DVERSION=\"$(VERS)\" -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-all $(CFLAGS) -g
|
||||
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)
|
||||
INC+=$(shell pkg-config --cflags libedit)
|
||||
|
||||
|
@ -61,29 +65,46 @@ clean:
|
|||
cheat: $(CHEAT_OBJS) dungeon.o
|
||||
$(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
|
||||
|
||||
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
|
||||
# The last two are Ubuntu names, might vary on other distributions.
|
||||
# After this, run your browser on coverage/open-adventure/index.html
|
||||
# to see coverage results. Browse coverage/adventure.yaml.html
|
||||
# to see symbol coverage over the YAML file.
|
||||
coverage: debug
|
||||
coverage: clean debug
|
||||
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:
|
||||
a2x --doctype manpage --format manpage $<
|
||||
asciidoctor -D. -a nofooter -b manpage $<
|
||||
.adoc.html:
|
||||
asciidoc $<
|
||||
.adoc:
|
||||
asciidoc $<
|
||||
asciidoctor -D. -a nofooter -a webfonts! $<
|
||||
|
||||
html: advent.html history.html hints.html
|
||||
|
||||
# README.adoc exists because that filename is magic on GitLab.
|
||||
DOCS=COPYING NEWS README.adoc advent.adoc history.adoc notes.adoc hints.adoc advent.6 INSTALL.adoc
|
||||
DOCS=COPYING NEWS.adoc README.adoc advent.adoc history.adoc notes.adoc hints.adoc advent.6 INSTALL.adoc
|
||||
TESTFILES=tests/*.log tests/*.chk tests/README tests/decheck tests/Makefile
|
||||
|
||||
# Can't use GNU tar's --transform, needs to build under Alpine Linux.
|
||||
|
@ -94,9 +115,6 @@ advent-$(VERS).tar.gz: $(SOURCES) $(DOCS)
|
|||
(tar -T MANIFEST -czvf advent-$(VERS).tar.gz)
|
||||
@(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
|
||||
shipper version=$(VERS) | sh -e -x
|
||||
|
||||
|
@ -128,6 +146,11 @@ linty: CCFLAGS += -Winit-self
|
|||
linty: CCFLAGS += -Wpointer-arith
|
||||
linty: advent cheat
|
||||
|
||||
# These seem to be more modern options for enabling coverage testing.
|
||||
# Documenting them here in case a future version bump disables --coverage.
|
||||
#debug: CCFLAGS += -ftest-coverage
|
||||
#debug: CCFLAGS += -fprofile-arcs
|
||||
|
||||
debug: CCFLAGS += -O0
|
||||
debug: CCFLAGS += --coverage
|
||||
debug: CCFLAGS += -ggdb
|
||||
|
@ -136,10 +159,3 @@ debug: CCFLAGS += -fsanitize=address
|
|||
debug: CCFLAGS += -fsanitize=undefined
|
||||
debug: linty
|
||||
|
||||
CSUPPRESSIONS = --suppress=missingIncludeSystem --suppress=invalidscanf
|
||||
cppcheck:
|
||||
cppcheck -I. --template gcc --enable=all $(CSUPPRESSIONS) *.[ch]
|
||||
|
||||
PYSUPPRESSIONS = line-too-long,invalid-name,missing-function-docstring,too-many-lines,too-many-branches,global-statement,multiple-imports,too-many-locals,too-many-statements,too-many-nested-blocks,no-else-return,raise-missing-from,redefined-outer-name,consider-using-in,dict-iter-missing-items
|
||||
pylint:
|
||||
@pylint --score=n --disable=$(PYSUPPRESSIONS) *.py */*.py
|
||||
|
|
|
@ -1,10 +1,37 @@
|
|||
= Open Adventure project news =
|
||||
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
// 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::
|
||||
Saying Z'ZZZ at reservoir no longer causes the waters to part and crash.
|
||||
|
||||
1.16: 2023-04-15::
|
||||
Savefiles now have an identifying magic cookie at the front.
|
||||
Resume detects if a save has incompatible endianness.
|
||||
|
||||
1.15: 2023-04-03::
|
||||
Commands in magic-word sequence now interrupt it, as in original.
|
||||
Bug fix for bird not starting caged in endgame.
|
||||
|
||||
1.14: 2023-03-09::
|
||||
Added -a option for BBS door systems.
|
||||
-o reverts to the old message on some failed magic words.
|
||||
Typo fixes and documentation polishing.
|
||||
|
||||
1.13: 2023-02-28::
|
||||
Fixed slightly buggy emission of end-of-game messages on a win.
|
||||
|
||||
1.12: 2023-02-06::
|
||||
The bug and todo list has been cleared.
|
||||
The bug and todo list has been cleared; project declared finished.
|
||||
Correctness has been systematically tested against the 1995 code.
|
||||
Typo fixes and documentation polishing.
|
||||
|
||||
|
@ -14,7 +41,7 @@
|
|||
|
||||
1.10: 2022-04-06::
|
||||
Fixed a bug that manifested after two "fly rug" commands - third one fails.
|
||||
Fix some giltches in processing fee fie foe foo.
|
||||
Fix some glitches in processing fee fie foe foo.
|
||||
Correct some object start states and reading-related glitches in the endgame.
|
||||
|
||||
1.9: 2020-08-27::
|
|
@ -1,4 +1,6 @@
|
|||
= README for Open Adventure =
|
||||
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
// SPDX-License-Identifier: CC-BY-4.0
|
||||
|
||||
If you are reading this anywhere but at http://www.catb.org/~esr/open-adventure
|
||||
you can go there for tarball downloads and other resources.
|
||||
|
|
43
advent.adoc
43
advent.adoc
|
@ -1,14 +1,19 @@
|
|||
= advent(6) =
|
||||
:doctype: manpage
|
||||
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
// 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 ==
|
||||
advent - Colossal Cave Adventure
|
||||
|
||||
== SYNOPSIS ==
|
||||
*advent* [-l logfile] [-o] [-r savefile] [script...]
|
||||
*advent* [-l logfile] [-o] [-r savefile] [-a savefile] [script...]
|
||||
|
||||
== DESCRIPTION ==
|
||||
The original Colossal Cave Adventure from 1976-77 was the origin of all
|
||||
The original Colossal Cave Adventure from 1976-1977 was the origin of all
|
||||
later text adventures, dungeon-crawl (computer) games, and computer-hosted
|
||||
roleplaying games.
|
||||
|
||||
|
@ -18,19 +23,22 @@ adventure". To learn more about the changes since the 350-point
|
|||
original, type 'news' at the command prompt.
|
||||
|
||||
There is an 'adventure' in the BSD games package that is a C port by
|
||||
Jim Gillogly of the 1976 ancestor of this game. To avoid a name
|
||||
collision, this game builds as 'advent', reflecting the fact that the
|
||||
PDP-10 on which the game originally ran limited filenames to 6 characters.
|
||||
Jim Gillogly of the 1977 version. To avoid a name collision, this game
|
||||
builds as 'advent', reflecting the fact that the PDP-10 on which the
|
||||
game originally ran limited filenames to 6 characters.
|
||||
|
||||
This version is released as open source with the permission and
|
||||
encouragement of the original authors.
|
||||
|
||||
Unlike the original, this version supports use of your arrow keys to edit
|
||||
your command line in place. Basic Emacs keystrokes are supported, and
|
||||
your up/down arrows access a command history.
|
||||
Unlike the original, this version has a command prompt and supports
|
||||
use of your arrow keys to edit your command line in place. Basic
|
||||
Emacs keystrokes are supported, and your up/down arrows access a
|
||||
command history.
|
||||
|
||||
Otherwise, the "version" command is about the only way to tell you're not
|
||||
running Don's original.
|
||||
Some minor bugs and message typos have been fixed. Otherwise, the
|
||||
"version" command is almost the only way to tell you're not running
|
||||
Don's 1977 version until you get to the new cave sections added for
|
||||
2.5.
|
||||
|
||||
To exit the game, type Ctrl-D (EOF).
|
||||
|
||||
|
@ -40,21 +48,24 @@ There have been no gameplay changes.
|
|||
|
||||
-l:: Log commands to specified file.
|
||||
|
||||
-r:: Restore game from specified file
|
||||
-r:: Restore game from specified save file
|
||||
|
||||
-o:: Old-style. Restores original interface, no prompt or line editing.
|
||||
-a:: Load from specified save file and autosave to it on exit or signal.
|
||||
|
||||
-o:: Old-style. Reverts some minor cosmetic fixes in game
|
||||
messages. Restores original interface, no prompt or line editing.
|
||||
Also ignores new-school one-letter commands l, x, g, z, i. Also
|
||||
case-smashes and truncates unrecognized text when echoed.
|
||||
|
||||
Normally, game input is taken from standard input. If script file
|
||||
arguments are given, input is taken fron them instead. A script file
|
||||
arguments are given, input is taken from them instead. A script file
|
||||
argument of '-' is taken as a directive to read from standard input.
|
||||
|
||||
== BUGS ==
|
||||
|
||||
The binary save file format is fragile, dependent on your machine word
|
||||
size and endianness, and unlikely to survive through version bumps. There
|
||||
is a version check.
|
||||
The binary save file format is fragile, dependent on your machine's
|
||||
endianness, and unlikely to survive through version bumps. There are
|
||||
version and endianness checks when attempting to restore from a save.
|
||||
|
||||
The input parser was the first attempt *ever* at natural-language
|
||||
parsing in a game and has some known deficiencies. While later text
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Open Adventure
|
||||
|
|
435
advent.h
435
advent.h
|
@ -1,8 +1,14 @@
|
|||
/*
|
||||
* Dungeon types and macros.
|
||||
*
|
||||
* SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "dungeon.h"
|
||||
|
||||
|
@ -12,180 +18,231 @@
|
|||
#define LCG_C 221587L
|
||||
#define LCG_M 1048576L
|
||||
|
||||
#define LINESIZE 1024
|
||||
#define TOKLEN 5 // # sigificant characters in a token */
|
||||
#define NDWARVES 6 // number of dwarves
|
||||
#define PIRATE NDWARVES // must be NDWARVES-1 when zero-origin
|
||||
#define DALTLC LOC_NUGGET // alternate dwarf location
|
||||
#define INVLIMIT 7 // inventory limit (# of objects)
|
||||
#define INTRANSITIVE -1 // illegal object number
|
||||
#define GAMELIMIT 330 // base limit of turns
|
||||
#define NOVICELIMIT 1000 // limit of turns for novice
|
||||
#define WARNTIME 30 // late game starts at game.limit-this
|
||||
#define FLASHTIME 50 // turns from first warning till blinding flash
|
||||
#define PANICTIME 15 // time left after closing
|
||||
#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_EMPTY 0 // "Word empty" flag value for the vocab hash functions
|
||||
#define CARRIED -1 // Player is toting it
|
||||
#define READ_MODE "rb" // b is not needed for POSIX but harmless
|
||||
#define WRITE_MODE "wb" // b is not needed for POSIX but harmless
|
||||
#define LINESIZE 1024
|
||||
#define TOKLEN 5 // # outputting characters in a token */
|
||||
#define PIRATE NDWARVES // must be NDWARVES-1 when zero-origin
|
||||
#define DALTLC LOC_NUGGET // alternate dwarf location
|
||||
#define INVLIMIT 7 // inventory limit (# of objects)
|
||||
#define INTRANSITIVE -1 // illegal object number
|
||||
#define GAMELIMIT 330 // base limit of turns
|
||||
#define NOVICELIMIT 1000 // limit of turns for novice
|
||||
#define WARNTIME 30 // late game starts at game.limit-this
|
||||
#define FLASHTIME 50 // turns from first warning till blinding flash
|
||||
#define PANICTIME 15 // time left after closing
|
||||
#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_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 CARRIED -1 // Player is toting it
|
||||
#define READ_MODE "rb" // b is not needed for POSIX but harmless
|
||||
#define WRITE_MODE "wb" // b is not needed for POSIX but harmless
|
||||
|
||||
/* Special object-state values - integers > 0 are object-specific */
|
||||
#define STATE_NOTFOUND -1 // 'Not found" state of treasures */
|
||||
#define STATE_FOUND 0 // After discovered, before messed with
|
||||
#define STATE_IN_CAVITY 1 // State value common to all gemstones
|
||||
#define STATE_NOTFOUND -1 // 'Not found" state of treasures
|
||||
#define STATE_FOUND 0 // After discovered, before messed with
|
||||
#define STATE_IN_CAVITY 1 // State value common to all gemstones
|
||||
|
||||
/* Special fixed object-state values - integers > 0 are location */
|
||||
#define IS_FIXED -1
|
||||
#define IS_FREE 0
|
||||
|
||||
/* Map a state property value to a negative range, where the object cannot be
|
||||
* picked up but the value can be recovered later. Avoid colliding with -1,
|
||||
* which has its own meaning. */
|
||||
#define STASHED(obj) (-1 - game.prop[obj])
|
||||
/* (ESR) It is fitting that translation of the original ADVENT should
|
||||
* have left us a maze of twisty little conditionals that resists all
|
||||
* understanding. Setting and use of what is now the per-object state
|
||||
* member (which used to be an array of its own) is our mystery. This
|
||||
* state tangles together information about whether the object is a
|
||||
* treasure, whether the player has seen it yet, and its activation
|
||||
* state.
|
||||
*
|
||||
* Things we think we know:
|
||||
*
|
||||
* STATE_NOTFOUND is only set on treasures. Non-treasures start the
|
||||
* game in STATE_FOUND.
|
||||
*
|
||||
* PROP_STASHIFY is supposed to map a state property value to a
|
||||
* negative range, where the object cannot be picked up but the value
|
||||
* can be recovered later. Various objects get this property when
|
||||
* the cave starts to close. Only seems to be significant for the bird
|
||||
* and readable objects, notably the clam/oyster - but the code around
|
||||
* those tests is difficult to read.
|
||||
*
|
||||
* All tests of the prop member are done with either these macros or ==.
|
||||
*/
|
||||
#define OBJECT_IS_NOTFOUND(obj) (game.objects[obj].prop == STATE_NOTFOUND)
|
||||
#define OBJECT_IS_FOUND(obj) (game.objects[obj].prop == STATE_FOUND)
|
||||
#define OBJECT_SET_FOUND(obj) (game.objects[obj].prop = STATE_FOUND)
|
||||
#define OBJECT_SET_NOT_FOUND(obj) (game.objects[obj].prop = STATE_NOTFOUND)
|
||||
#define OBJECT_IS_NOTFOUND2(g, o) (g.objects[o].prop == STATE_NOTFOUND)
|
||||
#define PROP_IS_INVALID(val) (val < -MAX_STATE - 1 || val > MAX_STATE)
|
||||
#define PROP_STASHIFY(n) (-1 - (n))
|
||||
#define OBJECT_STASHIFY(obj, pval) game.objects[obj].prop = PROP_STASHIFY(pval)
|
||||
#define OBJECT_IS_STASHED(obj) (game.objects[obj].prop < STATE_NOTFOUND)
|
||||
#define OBJECT_STATE_EQUALS(obj, pval) \
|
||||
((game.objects[obj].prop == pval) || \
|
||||
(game.objects[obj].prop == PROP_STASHIFY(pval)))
|
||||
|
||||
#define PROMPT "> "
|
||||
#define PROMPT "> "
|
||||
|
||||
/*
|
||||
* DESTROY(N) = Get rid of an item by putting it in LOC_NOWHERE
|
||||
* MOD(N,M) = Arithmetic modulus
|
||||
* TOTING(OBJ) = true if the OBJ is being carried
|
||||
* AT(OBJ) = true if on either side of two-placed object
|
||||
* HERE(OBJ) = true if the OBJ is at "LOC" (or is being carried)
|
||||
* CNDBIT(L,N) = true if COND(L) has bit n set (bit 0 is units bit)
|
||||
* LIQUID() = object number of liquid in bottle
|
||||
* LIQLOC(LOC) = object number of liquid (if any) at LOC
|
||||
* FORCED(LOC) = true if LOC moves without asking for input (COND=2)
|
||||
* DARK(LOC) = true if location "LOC" is dark
|
||||
* PCT(N) = true N% of the time (N integer from 0 to 100)
|
||||
* GSTONE(OBJ) = true if OBJ is a gemstone
|
||||
* FOREST(LOC) = true if LOC is part of the forest
|
||||
* OUTSID(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
|
||||
* INDEEP(LOC) = true if location is in the Hall of Mists or deeper
|
||||
* BUG(X) = report bug and exit
|
||||
* DESTROY(N) = Get rid of an item by putting it in LOC_NOWHERE
|
||||
* MOD(N,M) = Arithmetic modulus
|
||||
* TOTING(OBJ) = true if the OBJ is being carried
|
||||
* AT(OBJ) = true if on either side of two-placed object
|
||||
* HERE(OBJ) = true if the OBJ is at "LOC" (or is being carried)
|
||||
* CNDBIT(L,N) = true if COND(L) has bit n set (bit 0 is units bit)
|
||||
* LIQUID() = object number of liquid in bottle
|
||||
* LIQLOC(LOC) = object number of liquid (if any) at LOC
|
||||
* FORCED(LOC) = true if LOC moves without asking for input (COND=2)
|
||||
* IS_DARK_HERE() = true if location "LOC" is dark
|
||||
* PCT(N) = true N% of the time (N integer from 0 to 100)
|
||||
* GSTONE(OBJ) = true if OBJ is a gemstone
|
||||
* FOREST(LOC) = true if LOC is part of the forest
|
||||
* 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
|
||||
* INDEEP(LOC) = true if location is in the Hall of Mists or deeper
|
||||
* BUG(X) = report bug and exit
|
||||
*/
|
||||
#define DESTROY(N) move(N, LOC_NOWHERE)
|
||||
#define MOD(N,M) ((N) % (M))
|
||||
#define TOTING(OBJ) (game.place[OBJ] == CARRIED)
|
||||
#define AT(OBJ) (game.place[OBJ] == game.loc || game.fixed[OBJ] == game.loc)
|
||||
#define HERE(OBJ) (AT(OBJ) || TOTING(OBJ))
|
||||
#define CNDBIT(L,N) (tstbit(conditions[L],N))
|
||||
#define LIQUID() (game.prop[BOTTLE] == WATER_BOTTLE? WATER : game.prop[BOTTLE] == 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 DARK(DUMMY) (!CNDBIT(game.loc,COND_LIT) && (game.prop[LAMP] == LAMP_DARK || !HERE(LAMP)))
|
||||
#define PCT(N) (randrange(100) < (N))
|
||||
#define GSTONE(OBJ) ((OBJ) == EMERALD || (OBJ) == RUBY || (OBJ) == AMBER || (OBJ) == SAPPH)
|
||||
#define FOREST(LOC) CNDBIT(LOC, COND_FOREST)
|
||||
#define OUTSID(LOC) (CNDBIT(LOC, COND_ABOVE) || FOREST(LOC))
|
||||
#define INSIDE(LOC) (!OUTSID(LOC) || LOC == LOC_BUILDING)
|
||||
#define INDEEP(LOC) CNDBIT((LOC),COND_DEEP)
|
||||
#define BUG(x) bug(x, #x)
|
||||
#define DESTROY(N) move(N, LOC_NOWHERE)
|
||||
#define MOD(N, M) ((N) % (M))
|
||||
#define TOTING(OBJ) (game.objects[OBJ].place == CARRIED)
|
||||
#define AT(OBJ) \
|
||||
(game.objects[OBJ].place == game.loc || \
|
||||
game.objects[OBJ].fixed == game.loc)
|
||||
#define HERE(OBJ) (AT(OBJ) || TOTING(OBJ))
|
||||
#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 LIQLOC(LOC) \
|
||||
(CNDBIT((LOC), COND_FLUID) ? CNDBIT((LOC), COND_OILY) ? OIL : WATER \
|
||||
: NO_OBJECT)
|
||||
#define FORCED(LOC) CNDBIT(LOC, COND_FORCED)
|
||||
#define IS_DARK_HERE() \
|
||||
(!CNDBIT(game.loc, COND_LIT) && \
|
||||
(game.objects[LAMP].prop == LAMP_DARK || !HERE(LAMP)))
|
||||
#define PCT(N) (randrange(100) < (N))
|
||||
#define GSTONE(OBJ) \
|
||||
((OBJ) == EMERALD || (OBJ) == RUBY || (OBJ) == AMBER || (OBJ) == SAPPH)
|
||||
#define FOREST(LOC) CNDBIT(LOC, COND_FOREST)
|
||||
#define OUTSIDE(LOC) (CNDBIT(LOC, COND_ABOVE) || FOREST(LOC))
|
||||
#define INSIDE(LOC) (!OUTSIDE(LOC) || LOC == LOC_BUILDING)
|
||||
#define INDEEP(LOC) CNDBIT((LOC), COND_DEEP)
|
||||
#define BUG(x) bug(x, #x)
|
||||
|
||||
enum bugtype {
|
||||
SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST,
|
||||
VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3,
|
||||
INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST,
|
||||
TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST,
|
||||
CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION,
|
||||
LOCATION_HAS_NO_TRAVEL_ENTRIES,
|
||||
HINT_NUMBER_EXCEEDS_GOTO_LIST,
|
||||
SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN,
|
||||
ACTION_RETURNED_PHASE_CODE_BEYOND_END_OF_SWITCH,
|
||||
SPECIAL_TRAVEL_500_GT_L_GT_300_EXCEEDS_GOTO_LIST,
|
||||
VOCABULARY_TYPE_N_OVER_1000_NOT_BETWEEN_0_AND_3,
|
||||
INTRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST,
|
||||
TRANSITIVE_ACTION_VERB_EXCEEDS_GOTO_LIST,
|
||||
CONDITIONAL_TRAVEL_ENTRY_WITH_NO_ALTERATION,
|
||||
LOCATION_HAS_NO_TRAVEL_ENTRIES,
|
||||
HINT_NUMBER_EXCEEDS_GOTO_LIST,
|
||||
SPEECHPART_NOT_TRANSITIVE_OR_INTRANSITIVE_OR_UNKNOWN,
|
||||
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.
|
||||
* These were at one time FORTRAN line numbers.
|
||||
* The values don't matter, but perturb their order at your peril.
|
||||
*/
|
||||
typedef enum {
|
||||
GO_TERMINATE,
|
||||
GO_MOVE,
|
||||
GO_TOP,
|
||||
GO_CLEAROBJ,
|
||||
GO_CHECKHINT,
|
||||
GO_WORD2,
|
||||
GO_UNKNOWN,
|
||||
GO_DWARFWAKE,
|
||||
GO_TERMINATE,
|
||||
GO_MOVE,
|
||||
GO_TOP,
|
||||
GO_CLEAROBJ,
|
||||
GO_CHECKHINT,
|
||||
GO_WORD2,
|
||||
GO_UNKNOWN,
|
||||
GO_DWARFWAKE,
|
||||
} phase_codes_t;
|
||||
|
||||
typedef int vocab_t; // index into a vocabulary array */
|
||||
typedef int verb_t; // index into an actions array */
|
||||
typedef int obj_t; // index into the object array */
|
||||
typedef int loc_t; // index into the locations array */
|
||||
typedef int turn_t; // turn counter or threshold */
|
||||
/* Use fixed-lwength types to make the save format moore portable */
|
||||
typedef int32_t vocab_t; // index into a vocabulary array */
|
||||
typedef int32_t verb_t; // index into an actions array */
|
||||
typedef int32_t obj_t; // index into the object array */
|
||||
typedef int32_t loc_t; // index into the locations array */
|
||||
typedef int32_t turn_t; // turn counter or threshold */
|
||||
typedef int32_t bool32_t; // turn counter or threshold */
|
||||
|
||||
struct game_t {
|
||||
int32_t lcg_x;
|
||||
int abbnum; // How often to print int descriptions
|
||||
score_t bonus; // What kind of finishing bonus we are getting
|
||||
loc_t chloc; // pirate chest location
|
||||
loc_t chloc2; // pirate chest alternate location
|
||||
turn_t clock1; // # turns from finding last treasure to close
|
||||
turn_t clock2; // # turns from warning till blinding flash
|
||||
bool clshnt; // has player read the clue in the endgame?
|
||||
bool closed; // whether we're all the way closed
|
||||
bool closng; // whether it's closing time yet
|
||||
bool lmwarn; // has player been warned about lamp going dim?
|
||||
bool novice; // asked for instructions at start-up?
|
||||
bool panic; // has player found out he's trapped?
|
||||
bool wzdark; // whether the loc he's leaving was dark
|
||||
bool blooded; // has player drunk of dragon's blood?
|
||||
int conds; // min value for cond[loc] if loc has any hints
|
||||
int detail; // level of detail in descriptions
|
||||
int32_t lcg_x;
|
||||
int32_t abbnum; // How often to print int descriptions
|
||||
score_t bonus; // What kind of finishing bonus we are getting
|
||||
loc_t chloc; // pirate chest location
|
||||
loc_t chloc2; // pirate chest alternate location
|
||||
turn_t clock1; // # turns from finding last treasure to close
|
||||
turn_t clock2; // # turns from warning till blinding flash
|
||||
bool32_t clshnt; // has player read the clue in the endgame?
|
||||
bool32_t closed; // whether we're all the way closed
|
||||
bool32_t closng; // whether it's closing time yet
|
||||
bool32_t lmwarn; // has player been warned about lamp going dim?
|
||||
bool32_t novice; // asked for instructions at start-up?
|
||||
bool32_t panic; // has player found out he's trapped?
|
||||
bool32_t wzdark; // whether the loc he's leaving was dark
|
||||
bool32_t blooded; // has player drunk of dragon's blood?
|
||||
int32_t conds; // min value for cond[loc] if loc has any hints
|
||||
int32_t detail; // level of detail in descriptions
|
||||
|
||||
/* dflag controls the level of activation of dwarves:
|
||||
* 0 No dwarf stuff yet (wait until reaches Hall Of Mists)
|
||||
* 1 Reached Hall Of Mists, but hasn't met first dwarf
|
||||
* 2 Met first dwarf, others start moving, no knives thrown yet
|
||||
* 3 A knife has been thrown (first set always misses)
|
||||
* 3+ Dwarves are mad (increases their accuracy) */
|
||||
int dflag;
|
||||
/* dflag controls the level of activation of dwarves:
|
||||
* 0 No dwarf stuff yet (wait until reaches Hall Of Mists)
|
||||
* 1 Reached Hall Of Mists, but hasn't met first dwarf
|
||||
* 2 Met 1t dwarf, others start moving, no knives thrown yet
|
||||
* 3 A knife has been thrown (first set always misses) 3+
|
||||
* Dwarves are mad (increases their accuracy) */
|
||||
int32_t dflag;
|
||||
|
||||
int dkill; // dwarves killed
|
||||
int dtotal; // total dwarves (including pirate) in loc
|
||||
int foobar; // progress in saying "FEE FIE FOE FOO".
|
||||
int holdng; // number of objects being carried
|
||||
int igo; // # uses of "go" instead of a direction
|
||||
int iwest; // # times he's said "west" instead of "w"
|
||||
int knfloc; // knife location; 0 if none, -1 after caveat
|
||||
turn_t limit; // lifetime of lamp
|
||||
loc_t loc; // where player is now
|
||||
loc_t newloc; // where player is going
|
||||
turn_t numdie; // number of times killed so far
|
||||
loc_t oldloc; // where player was
|
||||
loc_t oldlc2; // where player was two moves ago
|
||||
obj_t oldobj; // last object player handled
|
||||
int saved; // point penalty for saves
|
||||
int tally; // count of treasures gained
|
||||
int thresh; // current threshold for endgame scoring tier
|
||||
turn_t trndex; // FIXME: not used, remove on next format bump
|
||||
turn_t trnluz; // # points lost so far due to turns used
|
||||
turn_t turns; // counts commands given (ignores yes/no)
|
||||
char zzword[TOKLEN + 1]; // randomly generated magic word from bird
|
||||
int abbrev[NLOCATIONS + 1]; // has location been seen?
|
||||
int atloc[NLOCATIONS + 1]; // head of object linked list per location
|
||||
int dseen[NDWARVES + 1]; // true if dwarf has seen him
|
||||
loc_t dloc[NDWARVES + 1]; // location of dwarves, initially hard-wired in
|
||||
loc_t odloc[NDWARVES + 1]; // prior loc of each dwarf, initially garbage
|
||||
loc_t fixed[NOBJECTS + 1]; // fixed location of object (if not IS_FREE)
|
||||
obj_t link[NOBJECTS * 2 + 1];// object-list links
|
||||
loc_t place[NOBJECTS + 1]; // location of object
|
||||
int hinted[NHINTS]; // hinted[i] = true iff hint i has been used.
|
||||
int hintlc[NHINTS]; // hintlc[i] = how int at LOC with cond bit i
|
||||
int prop[NOBJECTS + 1]; // object state array */
|
||||
int32_t dkill; // dwarves killed
|
||||
int32_t dtotal; // total dwarves (including pirate) in loc
|
||||
int32_t foobar; // progress in saying "FEE FIE FOE FOO".
|
||||
int32_t holdng; // number of objects being carried
|
||||
int32_t igo; // # uses of "go" instead of a direction
|
||||
int32_t iwest; // # times he's said "west" instead of "w"
|
||||
loc_t knfloc; // knife location; LOC_NOWERE if none, -1 after caveat
|
||||
turn_t limit; // lifetime of lamp
|
||||
loc_t loc; // where player is now
|
||||
loc_t newloc; // where player is going
|
||||
turn_t numdie; // number of times killed so far
|
||||
loc_t oldloc; // where player was
|
||||
loc_t oldlc2; // where player was two moves ago
|
||||
obj_t oldobj; // last object player handled
|
||||
int32_t saved; // point penalty for saves
|
||||
int32_t tally; // count of treasures gained
|
||||
int32_t thresh; // current threshold for endgame scoring tier
|
||||
bool32_t seenbigwords; // have we red the graffiti in the Giant's Room?
|
||||
turn_t trnluz; // # points lost so far due to turns used
|
||||
turn_t turns; // counts commands given (ignores yes/no)
|
||||
char zzword[TOKLEN + 1]; // randomly generated magic word from bird
|
||||
struct {
|
||||
int32_t abbrev; // has location been seen?
|
||||
int32_t atloc; // head of object linked list per location
|
||||
} locs[NLOCATIONS + 1];
|
||||
struct {
|
||||
int32_t seen; // true if dwarf has seen him
|
||||
loc_t loc; // location of dwarves, initially hard-wired in
|
||||
loc_t oldloc; // prior loc of each dwarf, initially garbage
|
||||
} dwarves[NDWARVES + 1];
|
||||
struct {
|
||||
loc_t fixed; // fixed location of object (if not IS_FREE)
|
||||
int32_t prop; // object state
|
||||
loc_t place; // location of object
|
||||
} objects[NOBJECTS + 1];
|
||||
struct {
|
||||
bool32_t used; // hints[i].used = true iff hint i has been used.
|
||||
int32_t lc; // hints[i].lc = show int at LOC with cond bit i
|
||||
} hints[NHINTS];
|
||||
obj_t link[NOBJECTS * 2 + 1]; // object-list links
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -193,47 +250,83 @@ struct game_t {
|
|||
* This data is not saved in a saved game.
|
||||
*/
|
||||
struct settings_t {
|
||||
FILE *logfp;
|
||||
bool oldstyle;
|
||||
bool prompt;
|
||||
char **argv;
|
||||
int argc;
|
||||
int optind;
|
||||
FILE *scriptfp;
|
||||
FILE *logfp;
|
||||
bool oldstyle;
|
||||
bool prompt;
|
||||
char **argv;
|
||||
int argc;
|
||||
int optind;
|
||||
FILE *scriptfp;
|
||||
int debug;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
char raw[LINESIZE];
|
||||
vocab_t id;
|
||||
word_type_t type;
|
||||
char raw[LINESIZE];
|
||||
vocab_t id;
|
||||
word_type_t type;
|
||||
} 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 {
|
||||
enum speechpart part;
|
||||
command_word_t word[2];
|
||||
verb_t verb;
|
||||
obj_t obj;
|
||||
command_state_t state;
|
||||
enum speechpart part;
|
||||
command_word_t word[2];
|
||||
verb_t verb;
|
||||
obj_t obj;
|
||||
command_state_t state;
|
||||
} command_t;
|
||||
|
||||
/*
|
||||
* Bump on save format change.
|
||||
*
|
||||
* Note: Verify that the tests run clean before bumping this, then rebuild the
|
||||
* check files afterwards. Otherwise you will get a spurious failure due to the
|
||||
* old version having been generated into a check file.
|
||||
*/
|
||||
#define SAVE_VERSION 31
|
||||
|
||||
/*
|
||||
* Goes at start of file so saves can be identified by file(1) and the like.
|
||||
*/
|
||||
#define ADVENT_MAGIC "open-adventure\n"
|
||||
|
||||
/*
|
||||
* 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 when you do that.
|
||||
*/
|
||||
struct save_t {
|
||||
char magic[sizeof(ADVENT_MAGIC)];
|
||||
int32_t version;
|
||||
int32_t canary;
|
||||
struct game_t game;
|
||||
};
|
||||
|
||||
extern struct game_t game;
|
||||
extern struct save_t save;
|
||||
extern struct settings_t settings;
|
||||
|
||||
extern char *myreadline(const char *);
|
||||
extern bool get_command_input(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 pspeak(vocab_t, enum speaktype, bool, int, ...);
|
||||
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 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 move(obj_t, loc_t);
|
||||
extern loc_t put(obj_t, int, int);
|
||||
extern void put(obj_t, loc_t, int);
|
||||
extern void carry(obj_t, loc_t);
|
||||
extern void drop(obj_t, loc_t);
|
||||
extern int atdwrf(loc_t);
|
||||
|
@ -243,7 +336,10 @@ extern void set_seed(int32_t);
|
|||
extern int32_t randrange(int32_t);
|
||||
extern int score(enum termination);
|
||||
extern void terminate(enum termination) __attribute__((noreturn));
|
||||
extern int savefile(FILE *, int32_t);
|
||||
extern int savefile(FILE *);
|
||||
#if defined ADVENT_AUTOSAVE
|
||||
extern void autosave(void);
|
||||
#endif
|
||||
extern int suspend(void);
|
||||
extern int resume(void);
|
||||
extern int restore(FILE *);
|
||||
|
@ -251,8 +347,7 @@ extern int initialise(void);
|
|||
extern phase_codes_t action(command_t);
|
||||
extern void state_change(obj_t, int);
|
||||
extern bool is_valid(struct game_t);
|
||||
|
||||
void bug(enum bugtype, const char *) __attribute__((__noreturn__));
|
||||
extern void bug(enum bugtype, const char *) __attribute__((__noreturn__));
|
||||
|
||||
/* represent an empty command word */
|
||||
static const command_word_t empty_command_word = {
|
||||
|
|
26
advent.svg
26
advent.svg
|
@ -2,30 +2,8 @@
|
|||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!--
|
||||
Copyright © 2017 Dr. Tobias Quathamer <toddy@debian.org>
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
SPDX-FileCopyrightText: 2017 Dr. Tobias Quathamer <toddy@debian.org>
|
||||
SPDX-License-Identifier: BSD-2-Clause
|
||||
-->
|
||||
<svg width="128" height="128" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
|
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 4 KiB |
|
@ -1,3 +1,10 @@
|
|||
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
# 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
|
||||
# variable initializers describing Colossal Cave. It replaces an ad-hoc
|
||||
# text database shipped with Adventure versions up to 2.5. The format
|
||||
|
@ -7,7 +14,7 @@
|
|||
# We define a bunch of YAML structures:
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# actions: Action words, grouped into synonyms, and their corresponding
|
||||
|
@ -24,7 +31,7 @@
|
|||
# long: Long description, always shown on first encounter.
|
||||
# short: Short description. If none, use long description.
|
||||
# maptag: Tag for mapping, not used by the game itself.
|
||||
# Only used if the "short" property in !!null.
|
||||
# Only used if the "short" property is !!null.
|
||||
# conditions: A dictionary of attributes
|
||||
# LIT Light
|
||||
# OILY If FLUID flag is on: true for oil, false for water
|
||||
|
@ -115,8 +122,6 @@
|
|||
# %S = the letter 's' or nothing (if a previous %d value is exactly 1)
|
||||
# %V = substitute program version string
|
||||
#
|
||||
# Copyright (c) 2017 by Eric S. Raymond
|
||||
# SPDX-License-Identifier: BSD-2-clause
|
||||
|
||||
# Motion names of the form MOT_* are not explicitly referenced in the
|
||||
# locations YAML, but usually get compiled into generated C.
|
||||
|
@ -783,7 +788,7 @@ locations: !!omap
|
|||
You are in a low n/s passage at a hole in the floor. The hole goes
|
||||
down to an e/w passage.
|
||||
short: 'You''re in n/s passage above e/w passage.'
|
||||
maptag: !!null
|
||||
maptag: "Floor hole."
|
||||
conditions: {DEEP: true}
|
||||
travel: [
|
||||
{verbs: [HALL, OUT, SOUTH], action: [goto, LOC_KINGHALL]},
|
||||
|
@ -1729,7 +1734,7 @@ locations: !!omap
|
|||
description:
|
||||
long: 'You are in a long sloping corridor with ragged sharp walls.'
|
||||
short: !!null
|
||||
maptag: !!null
|
||||
maptag: "Sloping corridor"
|
||||
conditions: {DEEP: true}
|
||||
travel: [
|
||||
{verbs: [UPWAR, SHELL], action: [goto, LOC_SHELLROOM]},
|
||||
|
@ -2047,7 +2052,7 @@ locations: !!omap
|
|||
roar, so loud that the entire cave seems to be trembling. Another
|
||||
passage leads south, and a low crawl goes east.
|
||||
short: 'You''re at junction with warm walls.'
|
||||
maptag: !!null
|
||||
maptag: "Warm junction"
|
||||
conditions: {NOARRR: true, DEEP: true}
|
||||
sound: LOUD_ROAR
|
||||
travel: [
|
||||
|
@ -2107,7 +2112,7 @@ locations: !!omap
|
|||
]
|
||||
- LOC_LIMESTONE:
|
||||
description:
|
||||
long:
|
||||
long: |-
|
||||
You are walking along a gently sloping north/south passage lined with
|
||||
oddly shaped limestone formations.
|
||||
short: 'You''re in limestone passage.'
|
||||
|
@ -2755,7 +2760,7 @@ locations: !!omap
|
|||
You are on a small ledge at the top of a nearly vertical cliff.
|
||||
There is a low crawl leading off to the northeast.
|
||||
short: 'You''re at top of cliff.'
|
||||
maptag: 'Cliftop'
|
||||
maptag: 'Clifftop'
|
||||
conditions: {DEEP: true}
|
||||
travel: [
|
||||
{verbs: [CLIMB, DOWN], action: [goto, LOC_CLIFFACE]},
|
||||
|
@ -2834,6 +2839,18 @@ locations: !!omap
|
|||
{verbs: [], action: [goto, LOC_Y2]},
|
||||
]
|
||||
|
||||
# Starting locations of dwarves.
|
||||
# Sixth dwarf is special (the pirate). He always starts at his
|
||||
# chest's eventual location inside the maze.
|
||||
dwarflocs: [
|
||||
LOC_KINGHALL,
|
||||
LOC_WESTBANK,
|
||||
LOC_Y2,
|
||||
LOC_ALIKE3,
|
||||
LOC_COMPLEX,
|
||||
LOC_MAZEEND12,
|
||||
]
|
||||
|
||||
arbitrary_messages: !!omap
|
||||
- NO_MESSAGE: !!null
|
||||
- CAVE_NEARBY: |-
|
||||
|
@ -3015,6 +3032,7 @@ arbitrary_messages: !!omap
|
|||
black smoke.
|
||||
- SHELL_IMPERVIOUS: 'The shell is very strong and is impervious to attack.'
|
||||
- START_OVER: 'What''s the matter, can''t you read? Now you''d best start over.'
|
||||
- WELL_POINTLESS: 'Well, that was remarkably pointless!'
|
||||
- DRAGON_SCALES: 'The axe bounces harmlessly off the dragon''s thick scales.'
|
||||
- NASTY_DRAGON: 'The dragon looks rather nasty. You''d best not try to get by.'
|
||||
- BIRD_BURNT: |-
|
||||
|
@ -3173,6 +3191,7 @@ arbitrary_messages: !!omap
|
|||
To achieve the next higher rating would be a neat trick!
|
||||
Congratulations!!
|
||||
- OFF_SCALE: 'You just went off my scale!!'
|
||||
- SAVERESUME_DISABLED: 'Save and resume are disabled.'
|
||||
- RESUME_HELP: 'To resume your Adventure, start a new game and then say "RESUME".'
|
||||
# This message is not currently used
|
||||
#- TABLE_SPACE: |-
|
||||
|
@ -3183,18 +3202,18 @@ arbitrary_messages: !!omap
|
|||
# %d of %d "random" messages %d of %d "class" messages
|
||||
# %d of %d hints %d of %d turn thresholds'
|
||||
- RESUME_ABANDON: 'To resume an earlier Adventure, you must abandon the current one.'
|
||||
- BAD_SAVE: 'Oops, that does not look like a valid save file.'
|
||||
- VERSION_SKEW: |-
|
||||
I'm sorry, but that Adventure was begun using Version %d.%d of the
|
||||
save file format, and this program uses Version %d.%d. You must find an instance
|
||||
using that other version in order to resume that Adventure.
|
||||
# This message is not currently used
|
||||
#- SAVE_TAMPERING: |-
|
||||
# A dark fog creeps in to surround you. From somewhere in the fog you
|
||||
# hear a stern voice. "This Adventure has been tampered with! You have
|
||||
# been dabbling in magic, knowing not the havoc you might cause thereby.
|
||||
# Leave at once, before you do irrevocable harm!" The fog thickens,
|
||||
# until at last you can see nothing at all. Your vision then clears,
|
||||
# and you find yourself back in The Real World.
|
||||
- SAVE_TAMPERING: |-
|
||||
A dark fog creeps in to surround you. From somewhere in the fog you
|
||||
hear a stern voice. "This Adventure has been tampered with! You have
|
||||
been dabbling in magic, knowing not the havoc you might cause thereby.
|
||||
Leave at once, before you do irrevocable harm!" The fog thickens,
|
||||
until at last you can see nothing at all. Your vision then clears,
|
||||
and you find yourself back in The Real World.
|
||||
- TWIST_TURN: |-
|
||||
Sorry, but the path twisted and turned so much that I can't figure
|
||||
out which way to go to get back.
|
||||
|
@ -3227,10 +3246,10 @@ classes:
|
|||
message: 'All of Adventuredom gives tribute to you, Adventurer Grandmaster!'
|
||||
- threshold: 9999
|
||||
message: |-
|
||||
'Adventuredom stands in awe -- you have now joined the ranks of the
|
||||
Adventuredom stands in awe -- you have now joined the ranks of the
|
||||
W O R L D C H A M P I O N A D V E N T U R E R S !
|
||||
It may interest you to know that the Dungeon-Master himself has, to
|
||||
my knowledge, never achieved this threshold in fewer than 330 turns.'
|
||||
my knowledge, never achieved this threshold in fewer than 330 turns.
|
||||
|
||||
turn_thresholds:
|
||||
- threshold: 350
|
||||
|
@ -3250,7 +3269,9 @@ turn_thresholds:
|
|||
Good grief, don't you *EVER* give up? Do you realize you've spent
|
||||
over 2500 turns at this? That's another ten points off, a total of
|
||||
twenty points lost for taking so long.
|
||||
|
||||
|
||||
# Objects names OBJ_* are not made visible by the map-graph generator.
|
||||
# Don't change these casually.
|
||||
objects: !!omap
|
||||
- NO_OBJECT:
|
||||
inventory: !!null
|
||||
|
@ -3415,7 +3436,7 @@ objects: !!omap
|
|||
- 'There are a few recent issues of "Spelunker Today" magazine here.'
|
||||
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."
|
||||
- DWARF:
|
||||
words: ['dwarf', 'dwarv']
|
||||
|
@ -3923,11 +3944,13 @@ obituaries:
|
|||
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
|
||||
to try to reincarnate you?
|
||||
# batchspell: add wr
|
||||
yes_response: |-
|
||||
All right. But don't blame me if something goes wr......
|
||||
--- POOF!! ---
|
||||
You are engulfed in a cloud of orange smoke. Coughing and gasping,
|
||||
you emerge from the smoke and find....
|
||||
# batchspell: remove wr
|
||||
- query: |-
|
||||
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?
|
||||
|
@ -4144,7 +4167,7 @@ actions: !!omap
|
|||
message: |-
|
||||
Mist is a white vapor, usually water, seen from time to time in
|
||||
caverns. It can be found anywhere but is frequently a sign of a deep
|
||||
pit leading down to water.'
|
||||
pit leading down to water.
|
||||
words: ['mist']
|
||||
noaction: true
|
||||
- FBOMB:
|
||||
|
@ -4209,7 +4232,7 @@ actions: !!omap
|
|||
message: |-
|
||||
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']
|
||||
noaction: true
|
||||
|
||||
|
|
162
cheat.c
162
cheat.c
|
@ -1,101 +1,96 @@
|
|||
/*
|
||||
* 'cheat' is a tool for generating save game files to test states that ought
|
||||
* not happen. It leverages chunks of advent, mostly initialize() and
|
||||
* savefile(), so we know we're always outputing save files that advent
|
||||
* savefile(), so we know we're always outputting save files that advent
|
||||
* can import.
|
||||
*
|
||||
* Copyright (c) 1977, 2005 by Will Crowther and Don Woods
|
||||
* Copyright (c) 2017 by Eric S. Raymond
|
||||
* SPDX-License-Identifier: BSD-2-clause
|
||||
* SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods
|
||||
* 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 <editline/readline.h>
|
||||
#include <getopt.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ch;
|
||||
char *savefilename = NULL;
|
||||
int version = 0;
|
||||
FILE *fp = NULL;
|
||||
int main(int argc, char *argv[]) {
|
||||
int ch;
|
||||
char *savefilename = NULL;
|
||||
FILE *fp = NULL;
|
||||
|
||||
// Initialize game variables
|
||||
initialise();
|
||||
// Initialize game variables
|
||||
initialise();
|
||||
|
||||
/* we're generating a saved game, so saved once by default,
|
||||
* unless overridden with command-line options below.
|
||||
*/
|
||||
game.saved = 1;
|
||||
/* we're generating a saved game, so saved once by default,
|
||||
* unless overridden with command-line options below.
|
||||
*/
|
||||
game.saved = 1;
|
||||
|
||||
/* Options. */
|
||||
const char* opts = "d:l:s:t:v:o:";
|
||||
const char* usage = "Usage: %s [-d numdie] [-s numsaves] [-v version] -o savefilename \n"
|
||||
" -d number of deaths. Signed integer.\n"
|
||||
" -l lifetime of lamp in turns. Signed integer.\n"
|
||||
" -s number of saves. Signed integer.\n"
|
||||
" -t number of turns. Signed integer.\n"
|
||||
" -v version number of save format.\n"
|
||||
" -o required. File name of save game to write.\n";
|
||||
/* Options. */
|
||||
const char *opts = "d:l:s:t:v:o:";
|
||||
const char *usage =
|
||||
"Usage: %s [-d numdie] [-s numsaves] [-v version] -o savefilename "
|
||||
"\n"
|
||||
" -d number of deaths. Signed integer.\n"
|
||||
" -l lifetime of lamp in turns. Signed integer.\n"
|
||||
" -s number of saves. Signed integer.\n"
|
||||
" -t number of turns. Signed integer.\n"
|
||||
" -v version number of save format.\n"
|
||||
" -o required. File name of save game to write.\n";
|
||||
|
||||
while ((ch = getopt(argc, argv, opts)) != EOF) {
|
||||
switch (ch) {
|
||||
case 'd':
|
||||
game.numdie = (turn_t)atoi(optarg);
|
||||
printf("cheat: game.numdie = %d\n", game.numdie);
|
||||
break;
|
||||
case 'l':
|
||||
game.limit = (turn_t)atoi(optarg);
|
||||
printf("cheat: game.limit = %d\n", game.limit);
|
||||
break;
|
||||
case 's':
|
||||
game.saved = (int)atoi(optarg);
|
||||
printf("cheat: game.saved = %d\n", game.saved);
|
||||
break;
|
||||
case 't':
|
||||
game.turns = (turn_t)atoi(optarg);
|
||||
printf("cheat: game.turns = %d\n", game.turns);
|
||||
break;
|
||||
case 'v':
|
||||
version = atoi(optarg);
|
||||
printf("cheat: version = %d\n", version);
|
||||
break;
|
||||
case 'o':
|
||||
savefilename = optarg;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr,
|
||||
usage, argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
while ((ch = getopt(argc, argv, opts)) != EOF) {
|
||||
switch (ch) {
|
||||
case 'd':
|
||||
game.numdie = (turn_t)atoi(optarg);
|
||||
printf("cheat: game.numdie = %d\n", game.numdie);
|
||||
break;
|
||||
case 'l':
|
||||
game.limit = (turn_t)atoi(optarg);
|
||||
printf("cheat: game.limit = %d\n", game.limit);
|
||||
break;
|
||||
case 's':
|
||||
game.saved = (int)atoi(optarg);
|
||||
printf("cheat: game.saved = %d\n", game.saved);
|
||||
break;
|
||||
case 't':
|
||||
game.turns = (turn_t)atoi(optarg);
|
||||
printf("cheat: game.turns = %d\n", game.turns);
|
||||
break;
|
||||
case 'v':
|
||||
save.version = atoi(optarg);
|
||||
printf("cheat: version = %d\n", save.version);
|
||||
break;
|
||||
case 'o':
|
||||
savefilename = optarg;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, usage, argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Save filename required; the point of cheat is to generate save file
|
||||
if (savefilename == NULL) {
|
||||
fprintf(stderr,
|
||||
usage, argv[0]);
|
||||
fprintf(stderr,
|
||||
"ERROR: filename required\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
// Save filename required; the point of cheat is to generate save file
|
||||
if (savefilename == NULL) {
|
||||
fprintf(stderr, usage, argv[0]);
|
||||
fprintf(stderr, "ERROR: filename required\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
fp = fopen(savefilename, WRITE_MODE);
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr,
|
||||
"Can't open file %s. Exiting.\n", savefilename);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
fp = fopen(savefilename, WRITE_MODE);
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "Can't open file %s. Exiting.\n", savefilename);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
savefile(fp, version);
|
||||
savefile(fp);
|
||||
|
||||
fclose(fp);
|
||||
fclose(fp);
|
||||
|
||||
printf("cheat: %s created.\n", savefilename);
|
||||
printf("cheat: %s created.\n", savefilename);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
|
@ -104,12 +99,7 @@ int main(int argc, char *argv[])
|
|||
* See the actually useful version of this in main.c
|
||||
*/
|
||||
|
||||
char *myreadline(const char *prompt)
|
||||
{
|
||||
return readline(prompt);
|
||||
}
|
||||
char *myreadline(const char *prompt) { return readline(prompt); }
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
/* end */
|
||||
|
||||
|
||||
|
|
9
control
9
control
|
@ -5,11 +5,10 @@ Package: open-adventure
|
|||
|
||||
Description: Colossal Cave Adventure, the 1995 430-point version.
|
||||
This is the last descendant of the original 1976 Colossal Cave Adventure
|
||||
worked on by the original authors - Crowther & Woods. It has sometimes
|
||||
been known as 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
|
||||
worked on by the original authors - Crowther & Woods; it is shipped with
|
||||
their permission and encouragement. It has sometimes been known as
|
||||
Adventure 2.5. The original PDP-10 name 'advent' is used for the
|
||||
built program to avoid collision with the BSD Games version.
|
||||
|
||||
Homepage: http://www.catb.org/~esr/open-adventure
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
= Non-spoiler hints =
|
||||
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
// SPDX-License-Identifier: CC-BY-4.0
|
||||
|
||||
Say the words you see. They can have interesting effects.
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
= A brief history of Colossal Cave Adventure =
|
||||
by Eric S. Raymond
|
||||
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
// SPDX-License-Identifier: CC-BY-4.0
|
||||
|
||||
Adventure is the fons et origo of all later dungeon-crawling computer
|
||||
games, the granddaddy of interactive fiction, and one of the hallowed
|
||||
|
@ -163,9 +165,9 @@ even as primitive as Adventure's.
|
|||
|
||||
- [[[SN]]]
|
||||
http://www.digitalhumanities.org/dhq/vol/1/2/000009/000009.html[Digital
|
||||
Humanties Quarterly]
|
||||
Humanities Quarterly]
|
||||
|
||||
- [[[DND]]] https://en.wikipedia.org/wiki/Dnd_(video_game)[dnd (ivdeo game)]
|
||||
- [[[DND]]] https://en.wikipedia.org/wiki/Dnd_(video_game)[dnd (video game)]
|
||||
|
||||
- [[[WUMPUS]]] https://en.wikipedia.org/wiki/Hunt_the_Wumpus[Hunt The Wumpus]
|
||||
|
||||
|
|
147
init.c
147
init.c
|
@ -1,101 +1,96 @@
|
|||
/*
|
||||
* Initialisation
|
||||
*
|
||||
* Copyright (c) 1977, 2005 by Will Crowther and Don Woods
|
||||
* Copyright (c) 2017 by Eric S. Raymond
|
||||
* SPDX-License-Identifier: BSD-2-clause
|
||||
* SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "advent.h"
|
||||
|
||||
struct settings_t settings = {
|
||||
.logfp = NULL,
|
||||
.oldstyle = false,
|
||||
.prompt = true
|
||||
};
|
||||
struct settings_t settings = {.logfp = NULL, .oldstyle = false, .prompt = true};
|
||||
|
||||
struct game_t game = {
|
||||
.dloc[1] = LOC_KINGHALL,
|
||||
.dloc[2] = LOC_WESTBANK,
|
||||
.dloc[3] = LOC_Y2,
|
||||
.dloc[4] = LOC_ALIKE3,
|
||||
.dloc[5] = LOC_COMPLEX,
|
||||
|
||||
/* Sixth 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
|
||||
* in chloc for ref. The dead end in the other maze has its
|
||||
* loc stored in chloc2. */
|
||||
.dloc[6] = LOC_MAZEEND12,
|
||||
.chloc = LOC_MAZEEND12,
|
||||
.chloc2 = LOC_DEADEND13,
|
||||
.abbnum = 5,
|
||||
.clock1 = WARNTIME,
|
||||
.clock2 = FLASHTIME,
|
||||
.newloc = LOC_START,
|
||||
.loc = LOC_START,
|
||||
.limit = GAMELIMIT,
|
||||
.foobar = WORD_EMPTY,
|
||||
.chloc = LOC_MAZEEND12, .chloc2 = LOC_DEADEND13, .abbnum = 5,
|
||||
.clock1 = WARNTIME, .clock2 = FLASHTIME, .newloc = LOC_START,
|
||||
.loc = LOC_START, .limit = GAMELIMIT, .foobar = WORD_EMPTY,
|
||||
};
|
||||
|
||||
int initialise(void)
|
||||
{
|
||||
if (settings.oldstyle)
|
||||
printf("Initialising...\n");
|
||||
int initialise(void) {
|
||||
if (settings.oldstyle) {
|
||||
printf("Initialising...\n");
|
||||
}
|
||||
|
||||
srand(time(NULL));
|
||||
int seedval = (int)rand();
|
||||
set_seed(seedval);
|
||||
srand(time(NULL));
|
||||
int seedval = (int)rand();
|
||||
set_seed(seedval);
|
||||
|
||||
for (int i = 1; i <= NOBJECTS; i++) {
|
||||
game.place[i] = LOC_NOWHERE;
|
||||
}
|
||||
for (int i = 1; i <= NDWARVES; i++) {
|
||||
game.dwarves[i].loc = dwarflocs[i - 1];
|
||||
}
|
||||
|
||||
for (int i = 1; i <= NLOCATIONS; i++) {
|
||||
if (!(locations[i].description.big == 0 ||
|
||||
tkey[i] == 0)) {
|
||||
int k = tkey[i];
|
||||
if (travel[k].motion == HERE)
|
||||
conditions[i] |= (1 << COND_FORCED);
|
||||
}
|
||||
}
|
||||
for (int i = 1; i <= NOBJECTS; i++) {
|
||||
game.objects[i].place = LOC_NOWHERE;
|
||||
}
|
||||
|
||||
/* Set up the game.atloc and game.link arrays.
|
||||
* We'll use the DROP subroutine, which prefaces new objects on the
|
||||
* lists. Since we want things in the other order, we'll run the
|
||||
* loop backwards. If the object is in two locs, we drop it twice.
|
||||
* Also, since two-placed objects are typically best described
|
||||
* last, we'll drop them first. */
|
||||
for (int i = NOBJECTS; i >= 1; i--) {
|
||||
if (objects[i].fixd > 0) {
|
||||
drop(i + NOBJECTS, objects[i].fixd);
|
||||
drop(i, objects[i].plac);
|
||||
}
|
||||
}
|
||||
for (int i = 1; i <= NLOCATIONS; i++) {
|
||||
if (!(locations[i].description.big == 0 || tkey[i] == 0)) {
|
||||
int k = tkey[i];
|
||||
if (travel[k].motion == HERE) {
|
||||
conditions[i] |= (1 << COND_FORCED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i <= NOBJECTS; i++) {
|
||||
int k = NOBJECTS + 1 - i;
|
||||
game.fixed[k] = objects[k].fixd;
|
||||
if (objects[k].plac != 0 && objects[k].fixd <= 0)
|
||||
drop(k, objects[k].plac);
|
||||
}
|
||||
/* Set up the game.locs atloc and game.link arrays.
|
||||
* We'll use the DROP subroutine, which prefaces new objects on the
|
||||
* lists. Since we want things in the other order, we'll run the
|
||||
* loop backwards. If the object is in two locs, we drop it twice.
|
||||
* Also, since two-placed objects are typically best described
|
||||
* last, we'll drop them first. */
|
||||
for (int i = NOBJECTS; i >= 1; i--) {
|
||||
if (objects[i].fixd > 0) {
|
||||
drop(i + NOBJECTS, objects[i].fixd);
|
||||
drop(i, objects[i].plac);
|
||||
}
|
||||
}
|
||||
|
||||
/* Treasure props are initially -1, and are set to 0 the first time
|
||||
* they are described. game.tally keeps track of how many are
|
||||
* not yet found, so we know when to close the cave. */
|
||||
for (int treasure = 1; treasure <= NOBJECTS; treasure++) {
|
||||
if (objects[treasure].is_treasure) {
|
||||
if (objects[treasure].inventory != 0)
|
||||
game.prop[treasure] = STATE_NOTFOUND;
|
||||
game.tally = game.tally - game.prop[treasure];
|
||||
}
|
||||
}
|
||||
game.conds = setbit(11);
|
||||
for (int i = 1; i <= NOBJECTS; i++) {
|
||||
int k = NOBJECTS + 1 - i;
|
||||
game.objects[k].fixed = objects[k].fixd;
|
||||
if (objects[k].plac != 0 && objects[k].fixd <= 0) {
|
||||
drop(k, objects[k].plac);
|
||||
}
|
||||
}
|
||||
|
||||
return seedval;
|
||||
/* Treasure props are initially STATE_NOTFOUND, and are set to
|
||||
* STATE_FOUND the first time they are described. game.tally
|
||||
* keeps track of how many are not yet found, so we know when to
|
||||
* close the cave.
|
||||
* (ESR) Non-treasures are set to STATE_FOUND explicitly so we
|
||||
* 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;
|
||||
if (objects[object].inventory != NULL) {
|
||||
OBJECT_SET_NOT_FOUND(object);
|
||||
}
|
||||
} else {
|
||||
OBJECT_SET_FOUND(object);
|
||||
}
|
||||
}
|
||||
game.conds = setbit(COND_HBASE);
|
||||
|
||||
return seedval;
|
||||
}
|
||||
|
|
264
make_dungeon.py
264
make_dungeon.py
|
@ -1,4 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
"""
|
||||
This is the open-adventure dungeon generator. It consumes a YAML description of
|
||||
the dungeon and outputs a dungeon.h and dungeon.c pair of C code files.
|
||||
|
@ -6,12 +8,9 @@ the dungeon and outputs a dungeon.h and dungeon.c pair of C code files.
|
|||
The nontrivial part of this is the compilation of the YAML for
|
||||
movement rules to the travel array that's actually used by
|
||||
playermove().
|
||||
|
||||
Copyright (c) 2017 by Eric S. Raymond
|
||||
SPDX-License-Identifier: BSD-2-clause
|
||||
"""
|
||||
|
||||
# pylint: disable=consider-using-f-string
|
||||
# pylint: disable=consider-using-f-string,line-too-long,invalid-name,missing-function-docstring,too-many-branches,global-statement,multiple-imports,too-many-locals,too-many-statements,too-many-nested-blocks,no-else-return,raise-missing-from,redefined-outer-name
|
||||
|
||||
import sys, yaml
|
||||
|
||||
|
@ -21,10 +20,11 @@ C_NAME = "dungeon.c"
|
|||
H_TEMPLATE_PATH = "templates/dungeon.h.tpl"
|
||||
C_TEMPLATE_PATH = "templates/dungeon.c.tpl"
|
||||
|
||||
DONOTEDIT_COMMENT = "/* Generated from adventure.yaml - do not hand-hack! */\n/* SPDX-License-Identifier: BSD-2-clause */\n\n"
|
||||
DONOTEDIT_COMMENT = "/* Generated from adventure.yaml - do not hand-hack! */\n\n"
|
||||
|
||||
statedefines = ""
|
||||
|
||||
|
||||
def make_c_string(string):
|
||||
"""Render a Python string into C string literal format."""
|
||||
if string is None:
|
||||
|
@ -36,14 +36,16 @@ def make_c_string(string):
|
|||
string = '"' + string + '"'
|
||||
return string
|
||||
|
||||
|
||||
def get_refs(l):
|
||||
reflist = [x[0] for x in l]
|
||||
ref_str = ""
|
||||
for ref in reflist:
|
||||
ref_str += " {},\n".format(ref)
|
||||
ref_str = ref_str[:-1] # trim trailing newline
|
||||
ref_str = ref_str[:-1] # trim trailing newline
|
||||
return ref_str
|
||||
|
||||
|
||||
def get_string_group(strings):
|
||||
template = """{{
|
||||
.strs = {},
|
||||
|
@ -52,20 +54,24 @@ def get_string_group(strings):
|
|||
if strings == []:
|
||||
strs = "NULL"
|
||||
else:
|
||||
strs = "(const char* []) {" + ", ".join([make_c_string(s) for s in strings]) + "}"
|
||||
strs = (
|
||||
"(const char* []) {" + ", ".join([make_c_string(s) for s in strings]) + "}"
|
||||
)
|
||||
n = len(strings)
|
||||
sg_str = template.format(strs, n)
|
||||
return sg_str
|
||||
|
||||
|
||||
def get_arbitrary_messages(arb):
|
||||
template = """ {},
|
||||
"""
|
||||
arb_str = ""
|
||||
for item in arb:
|
||||
arb_str += template.format(make_c_string(item[1]))
|
||||
arb_str = arb_str[:-1] # trim trailing newline
|
||||
arb_str = arb_str[:-1] # trim trailing newline
|
||||
return arb_str
|
||||
|
||||
|
||||
def get_class_messages(cls):
|
||||
template = """ {{
|
||||
.threshold = {},
|
||||
|
@ -77,9 +83,10 @@ def get_class_messages(cls):
|
|||
threshold = item["threshold"]
|
||||
message = make_c_string(item["message"])
|
||||
cls_str += template.format(threshold, message)
|
||||
cls_str = cls_str[:-1] # trim trailing newline
|
||||
cls_str = cls_str[:-1] # trim trailing newline
|
||||
return cls_str
|
||||
|
||||
|
||||
def get_turn_thresholds(trn):
|
||||
template = """ {{
|
||||
.threshold = {},
|
||||
|
@ -93,9 +100,10 @@ def get_turn_thresholds(trn):
|
|||
point_loss = item["point_loss"]
|
||||
message = make_c_string(item["message"])
|
||||
trn_str += template.format(threshold, point_loss, message)
|
||||
trn_str = trn_str[:-1] # trim trailing newline
|
||||
trn_str = trn_str[:-1] # trim trailing newline
|
||||
return trn_str
|
||||
|
||||
|
||||
def get_locations(loc):
|
||||
template = """ {{ // {}: {}
|
||||
.description = {{
|
||||
|
@ -113,9 +121,10 @@ def get_locations(loc):
|
|||
sound = item[1].get("sound", "SILENT")
|
||||
loud = "true" if item[1].get("loud") else "false"
|
||||
loc_str += template.format(i, item[0], short_d, long_d, sound, loud)
|
||||
loc_str = loc_str[:-1] # trim trailing newline
|
||||
loc_str = loc_str[:-1] # trim trailing newline
|
||||
return loc_str
|
||||
|
||||
|
||||
def get_objects(obj):
|
||||
template = """ {{ // {}: {}
|
||||
.words = {},
|
||||
|
@ -137,6 +146,7 @@ def get_objects(obj):
|
|||
}},
|
||||
}},
|
||||
"""
|
||||
max_state = 0
|
||||
obj_str = ""
|
||||
for (i, item) in enumerate(obj):
|
||||
attr = item[1]
|
||||
|
@ -154,12 +164,13 @@ def get_objects(obj):
|
|||
descriptions_str += " " * 12 + make_c_string(l_msg) + ",\n"
|
||||
for label in attr.get("states", []):
|
||||
labels.append(label)
|
||||
descriptions_str = descriptions_str[:-1] # trim trailing newline
|
||||
descriptions_str = descriptions_str[:-1] # trim trailing newline
|
||||
if labels:
|
||||
global statedefines
|
||||
statedefines += "/* States for %s */\n" % item[0]
|
||||
for (n, label) in enumerate(labels):
|
||||
statedefines += "#define %s\t%d\n" % (label, n)
|
||||
max_state = max(max_state, n)
|
||||
statedefines += "\n"
|
||||
sounds_str = ""
|
||||
if attr.get("sounds") is None:
|
||||
|
@ -167,21 +178,21 @@ def get_objects(obj):
|
|||
else:
|
||||
for l_msg in attr["sounds"]:
|
||||
sounds_str += " " * 12 + make_c_string(l_msg) + ",\n"
|
||||
sounds_str = sounds_str[:-1] # trim trailing newline
|
||||
sounds_str = sounds_str[:-1] # trim trailing newline
|
||||
texts_str = ""
|
||||
if attr.get("texts") is None:
|
||||
texts_str = " " * 12 + "NULL,"
|
||||
else:
|
||||
for l_msg in attr["texts"]:
|
||||
texts_str += " " * 12 + make_c_string(l_msg) + ",\n"
|
||||
texts_str = texts_str[:-1] # trim trailing newline
|
||||
texts_str = texts_str[:-1] # trim trailing newline
|
||||
changes_str = ""
|
||||
if attr.get("changes") is None:
|
||||
changes_str = " " * 12 + "NULL,"
|
||||
else:
|
||||
for l_msg in attr["changes"]:
|
||||
changes_str += " " * 12 + make_c_string(l_msg) + ",\n"
|
||||
changes_str = changes_str[:-1] # trim trailing newline
|
||||
changes_str = changes_str[:-1] # trim trailing newline
|
||||
locs = attr.get("locations", ["LOC_NOWHERE", "LOC_NOWHERE"])
|
||||
immovable = attr.get("immovable", False)
|
||||
try:
|
||||
|
@ -191,10 +202,24 @@ def get_objects(obj):
|
|||
sys.stderr.write("dungeon: unknown object location in %s\n" % locs)
|
||||
sys.exit(1)
|
||||
treasure = "true" if attr.get("treasure") else "false"
|
||||
obj_str += template.format(i, item[0], words_str, i_msg, locs[0], locs[1], treasure, descriptions_str, sounds_str, texts_str, changes_str)
|
||||
obj_str = obj_str[:-1] # trim trailing newline
|
||||
obj_str += template.format(
|
||||
i,
|
||||
item[0],
|
||||
words_str,
|
||||
i_msg,
|
||||
locs[0],
|
||||
locs[1],
|
||||
treasure,
|
||||
descriptions_str,
|
||||
sounds_str,
|
||||
texts_str,
|
||||
changes_str,
|
||||
)
|
||||
obj_str = obj_str[:-1] # trim trailing newline
|
||||
statedefines += "/* Maximum state value */\n#define MAX_STATE %d\n" % max_state
|
||||
return obj_str
|
||||
|
||||
|
||||
def get_obituaries(obit):
|
||||
template = """ {{
|
||||
.query = {},
|
||||
|
@ -206,9 +231,10 @@ def get_obituaries(obit):
|
|||
query = make_c_string(o["query"])
|
||||
yes = make_c_string(o["yes_response"])
|
||||
obit_str += template.format(query, yes)
|
||||
obit_str = obit_str[:-1] # trim trailing newline
|
||||
obit_str = obit_str[:-1] # trim trailing newline
|
||||
return obit_str
|
||||
|
||||
|
||||
def get_hints(hnt):
|
||||
template = """ {{
|
||||
.number = {},
|
||||
|
@ -227,9 +253,10 @@ def get_hints(hnt):
|
|||
question = make_c_string(item["question"])
|
||||
hint = make_c_string(item["hint"])
|
||||
hnt_str += template.format(number, penalty, turns, question, hint)
|
||||
hnt_str = hnt_str[:-1] # trim trailing newline
|
||||
hnt_str = hnt_str[:-1] # trim trailing newline
|
||||
return hnt_str
|
||||
|
||||
|
||||
def get_condbits(locations):
|
||||
cnd_str = ""
|
||||
for (name, loc) in locations:
|
||||
|
@ -240,7 +267,7 @@ def get_condbits(locations):
|
|||
if conditions[flag]:
|
||||
flaglist.append(flag)
|
||||
line = "|".join([("(1<<COND_%s)" % f) for f in flaglist])
|
||||
trail = "|".join([("(1<<COND_H%s)" % f['name']) for f in hints])
|
||||
trail = "|".join([("(1<<COND_H%s)" % f["name"]) for f in hints])
|
||||
if trail:
|
||||
line += "|" + trail
|
||||
if line.startswith("|"):
|
||||
|
@ -250,6 +277,7 @@ def get_condbits(locations):
|
|||
cnd_str += " " + line + ",\t// " + name + "\n"
|
||||
return cnd_str
|
||||
|
||||
|
||||
def get_motions(motions):
|
||||
template = """ {{
|
||||
.words = {},
|
||||
|
@ -270,6 +298,7 @@ def get_motions(motions):
|
|||
ignore += word.upper()
|
||||
return mot_str
|
||||
|
||||
|
||||
def get_actions(actions):
|
||||
template = """ {{
|
||||
.words = {},
|
||||
|
@ -302,20 +331,22 @@ def get_actions(actions):
|
|||
for word in contents["words"]:
|
||||
if len(word) == 1:
|
||||
ignore += word.upper()
|
||||
act_str = act_str[:-1] # trim trailing newline
|
||||
act_str = act_str[:-1] # trim trailing newline
|
||||
return act_str
|
||||
|
||||
|
||||
def bigdump(arr):
|
||||
out = ""
|
||||
for (i, _) in enumerate(arr):
|
||||
if i % 10 == 0:
|
||||
if out and out[-1] == ' ':
|
||||
if out and out[-1] == " ":
|
||||
out = out[:-1]
|
||||
out += "\n "
|
||||
out += str(arr[i]).lower() + ", "
|
||||
out = out[:-2] + "\n"
|
||||
return out
|
||||
|
||||
|
||||
def buildtravel(locs, objs):
|
||||
assert len(locs) <= 300
|
||||
assert len(objs) <= 100
|
||||
|
@ -335,31 +366,31 @@ def buildtravel(locs, objs):
|
|||
# location number (Y), and a list of motion numbers (see section 4).
|
||||
# each motion represents a verb which will go to Y if currently at X.
|
||||
# Y, in turn, is interpreted as follows. Let M=Y/1000, N=Y mod 1000.
|
||||
# If N<=300 it is the location to go to.
|
||||
# If 300<N<=500 N-300 is used in a computed goto to
|
||||
# a section of special code.
|
||||
# If N>500 message N-500 from section 6 is printed,
|
||||
# and he stays wherever he is.
|
||||
# If N<=300 it is the location to go to.
|
||||
# If 300<N<=500 N-300 is used in a computed goto to
|
||||
# a section of special code.
|
||||
# If N>500 message N-500 from section 6 is printed,
|
||||
# and he stays wherever he is.
|
||||
# Meanwhile, M specifies the conditions on the motion.
|
||||
# If M=0 it's unconditional.
|
||||
# If 0<M<100 it is done with M% probability.
|
||||
# If M=100 unconditional, but forbidden to dwarves.
|
||||
# If 100<M<=200 he must be carrying object M-100.
|
||||
# If 200<M<=300 must be carrying or in same room as M-200.
|
||||
# If 300<M<=400 game.prop(M % 100) must *not* be 0.
|
||||
# If 400<M<=500 game.prop(M % 100) must *not* be 1.
|
||||
# If 500<M<=600 game.prop(M % 100) must *not* be 2, etc.
|
||||
# If M=0 it's unconditional.
|
||||
# If 0<M<100 it is done with M% probability.
|
||||
# If M=100 unconditional, but forbidden to dwarves.
|
||||
# If 100<M<=200 he must be carrying object M-100.
|
||||
# If 200<M<=300 must be carrying or in same room as M-200.
|
||||
# If 300<M<=400 game.prop(M % 100) must *not* be 0.
|
||||
# If 400<M<=500 game.prop(M % 100) must *not* be 1.
|
||||
# If 500<M<=600 game.prop(M % 100) must *not* be 2, etc.
|
||||
# If the condition (if any) is not met, then the next *different*
|
||||
# "destination" value is used (unless it fails to meet *its* conditions,
|
||||
# in which case the next is found, etc.). Typically, the next dest will
|
||||
# be for one of the same verbs, so that its only use is as the alternate
|
||||
# destination for those verbs. For instance:
|
||||
# 15 110022 29 31 34 35 23 43
|
||||
# 15 14 29
|
||||
# 15 110022 29 31 34 35 23 43
|
||||
# 15 14 29
|
||||
# This says that, from loc 15, any of the verbs 29, 31, etc., will take
|
||||
# him to 22 if he's carrying object 10, and otherwise will go to 14.
|
||||
# 11 303008 49
|
||||
# 11 9 50
|
||||
# 11 303008 49
|
||||
# 11 9 50
|
||||
# This says that, from 11, 49 takes him to 8 unless game.prop[3]=0, in which
|
||||
# case he goes to 9. Verb 50 takes him to 9 regardless of game.prop[3].
|
||||
ltravel = []
|
||||
|
@ -370,13 +401,17 @@ def buildtravel(locs, objs):
|
|||
verbmap[word.upper()] = i
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
def dencode(action, name):
|
||||
"Decode a destination number"
|
||||
if action[0] == "goto":
|
||||
try:
|
||||
return locnames.index(action[1])
|
||||
except ValueError:
|
||||
sys.stderr.write("dungeon: unknown location %s in goto clause of %s\n" % (action[1], name))
|
||||
sys.stderr.write(
|
||||
"dungeon: unknown location %s in goto clause of %s\n"
|
||||
% (action[1], name)
|
||||
)
|
||||
raise ValueError
|
||||
elif action[0] == "special":
|
||||
return 300 + action[1]
|
||||
|
@ -384,11 +419,15 @@ def buildtravel(locs, objs):
|
|||
try:
|
||||
return 500 + msgnames.index(action[1])
|
||||
except ValueError:
|
||||
sys.stderr.write("dungeon: unknown location %s in carry clause of %s\n" % (cond[1], name))
|
||||
sys.stderr.write(
|
||||
"dungeon: unknown location %s in carry clause of %s\n"
|
||||
% (cond[1], name)
|
||||
)
|
||||
else:
|
||||
print(cond)
|
||||
raise ValueError
|
||||
return '' # Pacify pylint
|
||||
return "" # Pacify pylint
|
||||
|
||||
def cencode(cond, name):
|
||||
if cond is None:
|
||||
return 0
|
||||
|
@ -400,13 +439,19 @@ def buildtravel(locs, objs):
|
|||
try:
|
||||
return 100 + objnames.index(cond[1])
|
||||
except ValueError:
|
||||
sys.stderr.write("dungeon: unknown object name %s in carry clause of %s\n" % (cond[1], name))
|
||||
sys.stderr.write(
|
||||
"dungeon: unknown object name %s in carry clause of %s\n"
|
||||
% (cond[1], name)
|
||||
)
|
||||
sys.exit(1)
|
||||
elif cond[0] == "with":
|
||||
try:
|
||||
return 200 + objnames.index(cond[1])
|
||||
except IndexError:
|
||||
sys.stderr.write("dungeon: unknown object name %s in with clause of %s\n" % (cond[1], name))
|
||||
sys.stderr.write(
|
||||
"dungeon: unknown object name %s in with clause of %s\n"
|
||||
% (cond[1], name)
|
||||
)
|
||||
sys.exit(1)
|
||||
elif cond[0] == "not":
|
||||
try:
|
||||
|
@ -422,11 +467,17 @@ def buildtravel(locs, objs):
|
|||
state = i
|
||||
break
|
||||
else:
|
||||
sys.stderr.write("dungeon: unmatched state symbol %s in not clause of %s\n" % (cond[2], name))
|
||||
sys.stderr.write(
|
||||
"dungeon: unmatched state symbol %s in not clause of %s\n"
|
||||
% (cond[2], name)
|
||||
)
|
||||
sys.exit(0)
|
||||
return 300 + obj + 100 * state
|
||||
except ValueError:
|
||||
sys.stderr.write("dungeon: unknown object name %s in not clause of %s\n" % (cond[1], name))
|
||||
sys.stderr.write(
|
||||
"dungeon: unknown object name %s in not clause of %s\n"
|
||||
% (cond[1], name)
|
||||
)
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(cond)
|
||||
|
@ -436,11 +487,13 @@ def buildtravel(locs, objs):
|
|||
if "travel" in loc:
|
||||
for rule in loc["travel"]:
|
||||
tt = [i]
|
||||
dest = dencode(rule["action"], name) + 1000 * cencode(rule.get("cond"), name)
|
||||
dest = dencode(rule["action"], name) + 1000 * cencode(
|
||||
rule.get("cond"), name
|
||||
)
|
||||
tt.append(dest)
|
||||
tt += [motionnames[verbmap[e]].upper() for e in rule["verbs"]]
|
||||
if not rule["verbs"]:
|
||||
tt.append(1) # Magic dummy entry for null rules
|
||||
tt.append(1) # Magic dummy entry for null rules
|
||||
ltravel.append(tuple(tt))
|
||||
|
||||
# At this point the ltravel data is in the Section 3
|
||||
|
@ -461,7 +514,7 @@ def buildtravel(locs, objs):
|
|||
travel[-1][-1] = "false" if travel[-1][-1] == "true" else "true"
|
||||
while rule:
|
||||
cond = newloc // 1000
|
||||
nodwarves = (cond == 100)
|
||||
nodwarves = cond == 100
|
||||
if cond == 0:
|
||||
condtype = "cond_goto"
|
||||
condarg1 = condarg2 = 0
|
||||
|
@ -484,7 +537,7 @@ def buildtravel(locs, objs):
|
|||
else:
|
||||
condtype = "cond_not"
|
||||
condarg1 = cond % 100
|
||||
condarg2 = (cond - 300) // 100.
|
||||
condarg2 = (cond - 300) // 100.0
|
||||
dest = newloc % 1000
|
||||
if dest <= 300:
|
||||
desttype = "dest_goto"
|
||||
|
@ -495,19 +548,24 @@ def buildtravel(locs, objs):
|
|||
else:
|
||||
desttype = "dest_special"
|
||||
destval = locnames[dest - 300]
|
||||
travel.append([len(tkey)-1,
|
||||
locnames[len(tkey)-1],
|
||||
rule.pop(0),
|
||||
condtype,
|
||||
condarg1,
|
||||
condarg2,
|
||||
desttype,
|
||||
destval,
|
||||
"true" if nodwarves else "false",
|
||||
"false"])
|
||||
travel.append(
|
||||
[
|
||||
len(tkey) - 1,
|
||||
locnames[len(tkey) - 1],
|
||||
rule.pop(0),
|
||||
condtype,
|
||||
condarg1,
|
||||
condarg2,
|
||||
desttype,
|
||||
destval,
|
||||
"true" if nodwarves else "false",
|
||||
"false",
|
||||
]
|
||||
)
|
||||
travel[-1][-1] = "true"
|
||||
return (travel, tkey)
|
||||
|
||||
|
||||
def get_travel(travel):
|
||||
template = """ {{ // from {}: {}
|
||||
.motion = {},
|
||||
|
@ -523,11 +581,12 @@ def get_travel(travel):
|
|||
out = ""
|
||||
for entry in travel:
|
||||
out += template.format(*entry)
|
||||
out = out[:-1] # trim trailing newline
|
||||
out = out[:-1] # trim trailing newline
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open(YAML_NAME, "r", encoding='ascii', errors='surrogateescape') as f:
|
||||
with open(YAML_NAME, "r", encoding="ascii", errors="surrogateescape") as f:
|
||||
db = yaml.safe_load(f)
|
||||
|
||||
locnames = [x[0] for x in db["locations"]]
|
||||
|
@ -535,35 +594,39 @@ if __name__ == "__main__":
|
|||
objnames = [el[0] for el in db["objects"]]
|
||||
motionnames = [el[0] for el in db["motions"]]
|
||||
|
||||
(travel, tkey) = buildtravel(db["locations"],
|
||||
db["objects"])
|
||||
(travel, tkey) = buildtravel(db["locations"], db["objects"])
|
||||
ignore = ""
|
||||
try:
|
||||
with open(H_TEMPLATE_PATH, "r", encoding='ascii', errors='surrogateescape') as htf:
|
||||
with open(
|
||||
H_TEMPLATE_PATH, "r", encoding="ascii", errors="surrogateescape"
|
||||
) as htf:
|
||||
# read in dungeon.h template
|
||||
h_template = DONOTEDIT_COMMENT + htf.read()
|
||||
with open(C_TEMPLATE_PATH, "r", encoding='ascii', errors='surrogateescape') as ctf:
|
||||
with open(
|
||||
C_TEMPLATE_PATH, "r", encoding="ascii", errors="surrogateescape"
|
||||
) as ctf:
|
||||
# read in dungeon.c template
|
||||
c_template = DONOTEDIT_COMMENT + ctf.read()
|
||||
except IOError as e:
|
||||
print('ERROR: reading template failed ({})'.format(e.strerror))
|
||||
print("ERROR: reading template failed ({})".format(e.strerror))
|
||||
sys.exit(-1)
|
||||
|
||||
c = c_template.format(
|
||||
h_file = H_NAME,
|
||||
arbitrary_messages = get_arbitrary_messages(db["arbitrary_messages"]),
|
||||
classes = get_class_messages(db["classes"]),
|
||||
turn_thresholds = get_turn_thresholds(db["turn_thresholds"]),
|
||||
locations = get_locations(db["locations"]),
|
||||
objects = get_objects(db["objects"]),
|
||||
obituaries = get_obituaries(db["obituaries"]),
|
||||
hints = get_hints(db["hints"]),
|
||||
conditions = get_condbits(db["locations"]),
|
||||
motions = get_motions(db["motions"]),
|
||||
actions = get_actions(db["actions"]),
|
||||
tkeys = bigdump(tkey),
|
||||
travel = get_travel(travel),
|
||||
ignore = ignore
|
||||
h_file=H_NAME,
|
||||
arbitrary_messages=get_arbitrary_messages(db["arbitrary_messages"]),
|
||||
classes=get_class_messages(db["classes"]),
|
||||
turn_thresholds=get_turn_thresholds(db["turn_thresholds"]),
|
||||
locations=get_locations(db["locations"]),
|
||||
objects=get_objects(db["objects"]),
|
||||
obituaries=get_obituaries(db["obituaries"]),
|
||||
hints=get_hints(db["hints"]),
|
||||
conditions=get_condbits(db["locations"]),
|
||||
motions=get_motions(db["motions"]),
|
||||
actions=get_actions(db["actions"]),
|
||||
tkeys=bigdump(tkey),
|
||||
travel=get_travel(travel),
|
||||
ignore=ignore,
|
||||
dwarflocs=", ".join(db["dwarflocs"]) + ",",
|
||||
)
|
||||
|
||||
# 0-origin index of birds's last song. Bird should
|
||||
|
@ -571,29 +634,30 @@ if __name__ == "__main__":
|
|||
deathbird = len(dict(db["objects"])["BIRD"]["sounds"]) - 1
|
||||
|
||||
h = h_template.format(
|
||||
num_locations = len(db["locations"])-1,
|
||||
num_objects = len(db["objects"])-1,
|
||||
num_hints = len(db["hints"]),
|
||||
num_classes = len(db["classes"])-1,
|
||||
num_deaths = len(db["obituaries"]),
|
||||
num_thresholds = len(db["turn_thresholds"]),
|
||||
num_motions = len(db["motions"]),
|
||||
num_actions = len(db["actions"]),
|
||||
num_travel = len(travel),
|
||||
num_keys = len(tkey),
|
||||
bird_endstate = deathbird,
|
||||
arbitrary_messages = get_refs(db["arbitrary_messages"]),
|
||||
locations = get_refs(db["locations"]),
|
||||
objects = get_refs(db["objects"]),
|
||||
motions = get_refs(db["motions"]),
|
||||
actions = get_refs(db["actions"]),
|
||||
state_definitions = statedefines
|
||||
num_locations=len(db["locations"]) - 1,
|
||||
num_objects=len(db["objects"]) - 1,
|
||||
num_hints=len(db["hints"]),
|
||||
num_classes=len(db["classes"]) - 1,
|
||||
num_deaths=len(db["obituaries"]),
|
||||
num_thresholds=len(db["turn_thresholds"]),
|
||||
num_motions=len(db["motions"]),
|
||||
num_actions=len(db["actions"]),
|
||||
num_travel=len(travel),
|
||||
num_keys=len(tkey),
|
||||
bird_endstate=deathbird,
|
||||
arbitrary_messages=get_refs(db["arbitrary_messages"]),
|
||||
locations=get_refs(db["locations"]),
|
||||
objects=get_refs(db["objects"]),
|
||||
motions=get_refs(db["motions"]),
|
||||
actions=get_refs(db["actions"]),
|
||||
state_definitions=statedefines,
|
||||
ndwarflocs=str(len(db["dwarflocs"])),
|
||||
)
|
||||
|
||||
with open(H_NAME, "w", encoding='ascii', errors='surrogateescape') as hf:
|
||||
with open(H_NAME, "w", encoding="ascii", errors="surrogateescape") as hf:
|
||||
hf.write(h)
|
||||
|
||||
with open(C_NAME, "w", encoding='ascii', errors='surrogateescape') as cf:
|
||||
with open(C_NAME, "w", encoding="ascii", errors="surrogateescape") as cf:
|
||||
cf.write(c)
|
||||
|
||||
# end
|
||||
|
|
|
@ -1,45 +1,58 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
"""\
|
||||
usage: make-graph.py [-a] -d] [-m] [-s]
|
||||
usage: make_graph.py [-a] [-d] [-m] [-s] [-v]
|
||||
|
||||
Make a DOT graph of Colossal Cave.
|
||||
|
||||
-a = emit graph of entire dungeon
|
||||
-d = emit graoh of mazw all different
|
||||
-d = emit graph of maze all different
|
||||
-f = emit graph of forest locations
|
||||
-m = emit graph of maze all alike
|
||||
-s = emit graph of non-forest surface locations
|
||||
-v = include internal symbols in room labels
|
||||
"""
|
||||
# Copyright (c) 2017 by Eric S. Raymond
|
||||
# SPDX-License-Identifier: BSD-2-clause
|
||||
|
||||
# pylint: disable=consider-using-f-string
|
||||
# pylint: disable=consider-using-f-string,line-too-long,invalid-name,missing-function-docstring,multiple-imports,redefined-outer-name
|
||||
|
||||
import sys, getopt, yaml
|
||||
|
||||
|
||||
def allalike(loc):
|
||||
"Select out loci related to the Maze All Alike"
|
||||
return location_lookup[loc]["conditions"].get("ALLALIKE")
|
||||
|
||||
|
||||
def alldifferent(loc):
|
||||
"Select out loci related to the Maze All Alike"
|
||||
return location_lookup[loc]["conditions"].get("ALLDIFFERENT")
|
||||
|
||||
|
||||
def surface(loc):
|
||||
"Select out surface locations"
|
||||
return location_lookup[loc]["conditions"].get("ABOVE")
|
||||
|
||||
|
||||
def forest(loc):
|
||||
return location_lookup[loc]["conditions"].get("FOREST")
|
||||
|
||||
|
||||
def abbreviate(d):
|
||||
m = {"NORTH":"N", "EAST":"E", "SOUTH":"S", "WEST":"W", "UPWAR":"U", "DOWN":"D"}
|
||||
m = {
|
||||
"NORTH": "N",
|
||||
"EAST": "E",
|
||||
"SOUTH": "S",
|
||||
"WEST": "W",
|
||||
"UPWAR": "U",
|
||||
"DOWN": "D",
|
||||
}
|
||||
return m.get(d, d)
|
||||
|
||||
|
||||
def roomlabel(loc):
|
||||
"Generate a room label from the description, if possible"
|
||||
loc_descriptions = location_lookup[loc]['description']
|
||||
loc_descriptions = location_lookup[loc]["description"]
|
||||
description = ""
|
||||
if debug:
|
||||
description = loc[4:]
|
||||
|
@ -51,8 +64,12 @@ def roomlabel(loc):
|
|||
if short.startswith("You're "):
|
||||
short = short[7:]
|
||||
if short.startswith("You are "):
|
||||
short = short[8 :]
|
||||
if short.startswith("in ") or short.startswith("at ") or short.startswith("on "):
|
||||
short = short[8:]
|
||||
if (
|
||||
short.startswith("in ")
|
||||
or short.startswith("at ")
|
||||
or short.startswith("on ")
|
||||
):
|
||||
short = short[3:]
|
||||
if short.startswith("the "):
|
||||
short = short[4:]
|
||||
|
@ -69,8 +86,9 @@ def roomlabel(loc):
|
|||
description += "\\n(" + ",".join(startlocs[loc]).lower() + ")"
|
||||
return description
|
||||
|
||||
|
||||
# A forwarder is a location that you can't actually stop in - when you go there
|
||||
# it ships some message (which is the point) then shifts you to a nexr location.
|
||||
# it ships some message (which is the point) then shifts you to a next location.
|
||||
# A forwarder has a zero-length array of notion verbs in its travel section.
|
||||
#
|
||||
# Here is an example forwarder declaration:
|
||||
|
@ -85,10 +103,12 @@ def roomlabel(loc):
|
|||
# {verbs: [], action: [goto, LOC_NOWHERE]},
|
||||
# ]
|
||||
|
||||
|
||||
def is_forwarder(loc):
|
||||
"Is a location a forwarder?"
|
||||
travel = location_lookup[loc]['travel']
|
||||
return len(travel) == 1 and len(travel[0]['verbs']) == 0
|
||||
travel = location_lookup[loc]["travel"]
|
||||
return len(travel) == 1 and len(travel[0]["verbs"]) == 0
|
||||
|
||||
|
||||
def forward(loc):
|
||||
"Chase a location through forwarding links."
|
||||
|
@ -96,8 +116,9 @@ def forward(loc):
|
|||
loc = location_lookup[loc]["travel"][0]["action"][1]
|
||||
return loc
|
||||
|
||||
|
||||
def reveal(objname):
|
||||
"Should this object be revealed when mappinmg?"
|
||||
"Should this object be revealed when mapping?"
|
||||
if "OBJ_" in objname:
|
||||
return False
|
||||
if objname == "VEND":
|
||||
|
@ -105,8 +126,9 @@ def reveal(objname):
|
|||
obj = object_lookup[objname]
|
||||
return not obj.get("immovable")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
with open("adventure.yaml", "r", encoding='ascii', errors='surrogateescape') as f:
|
||||
with open("adventure.yaml", "r", encoding="ascii", errors="surrogateescape") as f:
|
||||
db = yaml.safe_load(f)
|
||||
|
||||
location_lookup = dict(db["locations"])
|
||||
|
@ -121,17 +143,18 @@ if __name__ == "__main__":
|
|||
subset = allalike
|
||||
debug = False
|
||||
for (switch, val) in options:
|
||||
if switch == '-a':
|
||||
if switch == "-a":
|
||||
# pylint: disable=unnecessary-lambda-assignment
|
||||
subset = lambda loc: True
|
||||
elif switch == '-d':
|
||||
elif switch == "-d":
|
||||
subset = alldifferent
|
||||
elif switch == '-f':
|
||||
elif switch == "-f":
|
||||
subset = forest
|
||||
elif switch == '-m':
|
||||
elif switch == "-m":
|
||||
subset = allalike
|
||||
elif switch == '-s':
|
||||
elif switch == "-s":
|
||||
subset = surface
|
||||
elif switch == '-v':
|
||||
elif switch == "-v":
|
||||
debug = True
|
||||
else:
|
||||
sys.stderr.write(__doc__)
|
||||
|
@ -148,8 +171,8 @@ if __name__ == "__main__":
|
|||
startlocs[location] = [objname]
|
||||
|
||||
# Compute reachability, using forwards.
|
||||
# Dictionary ke6y is (from, to) iff its a valid link,
|
||||
# value is correspoinding motion verbs.
|
||||
# Dictionary key is (from, to) iff its a valid link,
|
||||
# value is corresponding motion verbs.
|
||||
links = {}
|
||||
nodes = []
|
||||
for (loc, attrs) in db["locations"]:
|
||||
|
@ -170,7 +193,7 @@ if __name__ == "__main__":
|
|||
neighbors = set()
|
||||
for loc in nodes:
|
||||
for (f, t) in links:
|
||||
if f == 'LOC_NOWHERE' or t == 'LOC_NOWHERE':
|
||||
if f == "LOC_NOWHERE" or t == "LOC_NOWHERE":
|
||||
continue
|
||||
if (f == loc and subset(t)) or (t == loc and subset(f)):
|
||||
if loc not in neighbors:
|
||||
|
@ -189,7 +212,7 @@ if __name__ == "__main__":
|
|||
# Draw arcs
|
||||
for (f, t) in links:
|
||||
arc = "%s -> %s" % (f[4:], t[4:])
|
||||
label=",".join(links[(f, t)]).lower()
|
||||
label = ",".join(links[(f, t)]).lower()
|
||||
if len(label) > 0:
|
||||
arc += ' [label="%s"]' % label
|
||||
print(" " + arc)
|
||||
|
|
65
notes.adoc
65
notes.adoc
|
@ -1,5 +1,7 @@
|
|||
= Open Adventure Maintainer's Notes =
|
||||
by Eric S. Raymond
|
||||
// SPDX-FileCopyrightText: (C) Eric S. Raymond <esr@thyrsus.com>
|
||||
// SPDX-License-Identifier: CC-BY-4.0
|
||||
|
||||
In which we explain what has been done to this code since Don Woods
|
||||
authorized us to ship it under an open-source license. There's a
|
||||
|
@ -13,7 +15,9 @@ the game; Jason signed on early in the process to help. The assistance
|
|||
of Peje Nilsson in restructuring some particularly grotty gotos is
|
||||
gratefully acknowledged. Petr Voropaev contributed fuzz testing and
|
||||
code cleanups. Aaron Traas did a lot of painstaking work to improve
|
||||
test coverage, and factored out the last handful of gotos.
|
||||
test coverage, and factored out the last handful of gotos. Ryan
|
||||
Sarson nudged us into fixing a longstanding minor bug in the
|
||||
handling of incorrect magic-word sequences,
|
||||
|
||||
== Nomenclature ==
|
||||
|
||||
|
@ -41,6 +45,11 @@ form that is (a) readable, and (b) friendly to forward translation to
|
|||
future languages. It has already survived a move from FORTRAN to C; a
|
||||
future as a Python or Go translation seems possible, even probable.
|
||||
|
||||
Compatibility with the 2.5 source we found has been checked by
|
||||
building a version patched minimally to support the seed command and
|
||||
running it against the entire test suite, which has 100% code
|
||||
coverage.
|
||||
|
||||
== Functional changes ==
|
||||
|
||||
Bug fixes:
|
||||
|
@ -51,10 +60,13 @@ Bug fixes:
|
|||
|
||||
* Oyster was readable after first gotten even when not carried.
|
||||
|
||||
* Behavior when saying the giant's magic words outside his room wasn't
|
||||
quite correct - the game responded as though the player were in
|
||||
the room ("...can't you read?"). The new message is "Nothing happens."
|
||||
|
||||
* Response to an attempt to unlock the oyster while carrying it was incorrect.
|
||||
|
||||
* Behavior when saying the giant's magic words before having seen them
|
||||
wasn't quite correct - the game responded as though the player had
|
||||
already read them ("...can't you read?"). The new message is "Well,
|
||||
that was remarkably pointless!" The -o option reverts this change.
|
||||
|
||||
* Attempting to extinguish an unlit urn caused it to lose its oil.
|
||||
|
||||
* "A crystal bridge now spans the fissure." (progressive present) was
|
||||
|
@ -62,18 +74,16 @@ Bug fixes:
|
|||
bridge spans the fissure." (timeless present).
|
||||
|
||||
* A few minor typos have been corrected: absence of capitalization on
|
||||
"Swiss" and "Persian", inconsistent selling of "imbedded" vs. "embedded",
|
||||
"eying" for "eyeing". "thresholds" for "threshholds".
|
||||
"Swiss" and "Persian", inconsistent spelling of "imbedded" vs. "embedded",
|
||||
"eying" for "eyeing", "thresholds" for "threshholds", "pencilled"
|
||||
for "penciled".
|
||||
|
||||
* Under odd circumstances (dropping rug or vase outdoors) the game could
|
||||
say "floor" when it should say "ground" (or "dirt", or something).
|
||||
formerly say "floor" when it should say "ground" (or "dirt", or
|
||||
something).
|
||||
|
||||
Bugs (accidental changes that don't seem worth the effort to fix):
|
||||
|
||||
* Commands that are not moves (e.g. "look" and "inven") can be used
|
||||
during fee fie fo foo without breaking recognition of the sequence.
|
||||
|
||||
* Bird starts uncaged in the endgame.
|
||||
* The "knives vanish" message could formerly be emitted when "I see no
|
||||
knife here." would be appropriate.
|
||||
|
||||
Enhancements:
|
||||
|
||||
|
@ -98,6 +108,7 @@ that random events (dwarf & pirate appearances, the bird's magic word)
|
|||
will be reproducible.
|
||||
|
||||
A "version" command has been added. This has no effect on gameplay.
|
||||
|
||||
The text displayed by the "news" command has been updated.
|
||||
|
||||
A -l command-line option has been added. When this is given (with a
|
||||
|
@ -121,11 +132,28 @@ FORTRAN-derived code that formerly implemented the save/restore
|
|||
functions; without C's fread(3)/fwrite() and structs it was
|
||||
necessarily pretty ugly by modern standards. Encryption and
|
||||
checksumming have been discarded - it's pointless to try
|
||||
tamper-proofing saves when everyone has the source code.
|
||||
tamper-proofing saves when everyone has the source code. However
|
||||
the game still integrity-checks savefiles on resume, including an
|
||||
abort if the endianness of the restoring machine does not match that of
|
||||
the saving machine. There is a magic-cookie header on the saves so
|
||||
in theory they could be identified by programs like file(1).
|
||||
|
||||
Save and resume filenames are stripped of leading and trailing
|
||||
whitespace before processing.
|
||||
|
||||
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.
|
||||
|
||||
An -a command-line option has been added (conditionally on
|
||||
ADVENT_AUTOSAVE) for use in BBS door systems. When this option is
|
||||
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
|
||||
to inform the user about this.
|
||||
|
||||
The game can be built in a mode that entirely disables save/resume
|
||||
(-DADVENT_NOSAVE). If the game had been built this way, a diagnostic is
|
||||
emitted if you try to save or resume.
|
||||
|
||||
== Translation ==
|
||||
|
||||
The 2.5 code was a mechanical C translation of a FORTRAN original.
|
||||
|
@ -162,6 +190,9 @@ afl (American Fuzzy Lop). We've found and fixed some crashers in
|
|||
our new code (which occasionally uses malloc(3)), but none as yet
|
||||
in Don's old code (which didn't).
|
||||
|
||||
After version 1.11, correctness was carefully checked against the
|
||||
behavior of a binary from before the big refactoring.
|
||||
|
||||
The code falls short of being fully modern C in the following
|
||||
ways:
|
||||
|
||||
|
@ -192,7 +223,7 @@ messages with the objects that conceptually own them.
|
|||
We consider this project finished. All issues and TODOs have been
|
||||
cleared, behavior has been carefully checked against original ADVENT,
|
||||
no future demand for new features is expected, and the test suite has
|
||||
100% code coverage. If the toolchain bit-rots out from under it,
|
||||
we will fix that.
|
||||
100% code coverage. If new bugs appear as the toolchain bit-rots out
|
||||
from under underneath, we will fix those problems.
|
||||
|
||||
// end
|
||||
|
|
422
saveresume.c
422
saveresume.c
|
@ -4,254 +4,264 @@
|
|||
* (ESR) This replaces a bunch of particularly nasty FORTRAN-derived code;
|
||||
* see the history.adoc file in the source distribution for discussion.
|
||||
*
|
||||
* Copyright (c) 1977, 2005 by Will Crowther and Don Woods
|
||||
* Copyright (c) 2017 by Eric S. Raymond
|
||||
* SPDX-License-Identifier: BSD-2-clause
|
||||
* SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "advent.h"
|
||||
#include "dungeon.h"
|
||||
|
||||
/*
|
||||
* Bump on save format change.
|
||||
*
|
||||
* Note: Verify that the tests run clean before bumping this, then rebuild the check
|
||||
* files afterwards. Otherwise you will get a spurious failure due to the old version
|
||||
* having been generated into a check file.
|
||||
* Use this to detect endianness mismatch. Can't be unchanged by byte-swapping.
|
||||
*/
|
||||
#define VRSION 29
|
||||
#define ENDIAN_MAGIC 2317
|
||||
|
||||
/*
|
||||
* If you change the first three members, the resume function may not properly
|
||||
* reject saves from older versions. Yes, this glues us to a hardware-
|
||||
* dependent length of int. Later members can change, but bump the version
|
||||
* when you do that.
|
||||
*/
|
||||
struct save_t {
|
||||
int64_t savetime;
|
||||
int32_t mode; /* not used, must be present for version detection */
|
||||
int32_t version;
|
||||
struct game_t game;
|
||||
};
|
||||
struct save_t save;
|
||||
|
||||
#define IGNORE(r) do{if (r){}}while(0)
|
||||
#define IGNORE(r) \
|
||||
do { \
|
||||
if (r) { \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
int savefile(FILE *fp, int32_t version)
|
||||
/* Save game to file. No input or output from user. */
|
||||
{
|
||||
save.savetime = time(NULL);
|
||||
save.mode = -1;
|
||||
save.version = (version == 0) ? VRSION : version;
|
||||
|
||||
save.game = game;
|
||||
IGNORE(fwrite(&save, sizeof(struct save_t), 1, fp));
|
||||
return (0);
|
||||
int savefile(FILE *fp) {
|
||||
/* Save game to file. No input or output from user. */
|
||||
memcpy(&save.magic, ADVENT_MAGIC, sizeof(ADVENT_MAGIC));
|
||||
if (save.version == 0) {
|
||||
save.version = SAVE_VERSION;
|
||||
}
|
||||
if (save.canary == 0) {
|
||||
save.canary = ENDIAN_MAGIC;
|
||||
}
|
||||
save.game = game;
|
||||
IGNORE(fwrite(&save, sizeof(struct save_t), 1, fp));
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Suspend and resume */
|
||||
int suspend(void)
|
||||
{
|
||||
/* Suspend. Offer to save things in a file, but charging
|
||||
* some points (so can't win by using saved games to retry
|
||||
* battles or to start over after learning zzword).
|
||||
* If ADVENT_NOSAVE is defined, do nothing instead. */
|
||||
|
||||
#ifdef ADVENT_NOSAVE
|
||||
return GO_UNKNOWN;
|
||||
#endif
|
||||
FILE *fp = NULL;
|
||||
static char *strip(char *name) {
|
||||
// Trim leading whitespace
|
||||
while (isspace((unsigned char)*name)) {
|
||||
name++; // LCOV_EXCL_LINE
|
||||
}
|
||||
if (*name != '\0') {
|
||||
// Trim trailing whitespace;
|
||||
// might be left there by autocomplete
|
||||
char *end = name + strlen(name) - 1;
|
||||
while (end > name && isspace((unsigned char)*end)) {
|
||||
end--;
|
||||
}
|
||||
// Write new null terminator character
|
||||
end[1] = '\0';
|
||||
}
|
||||
|
||||
rspeak(SUSPEND_WARNING);
|
||||
if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN]))
|
||||
return GO_CLEAROBJ;
|
||||
game.saved = game.saved + 5;
|
||||
|
||||
while (fp == NULL) {
|
||||
char* name = myreadline("\nFile name: ");
|
||||
if (name == NULL)
|
||||
return GO_TOP;
|
||||
fp = fopen(name, WRITE_MODE);
|
||||
if (fp == NULL)
|
||||
printf("Can't open file %s, try again.\n", name);
|
||||
free(name);
|
||||
}
|
||||
|
||||
savefile(fp, VRSION);
|
||||
fclose(fp);
|
||||
rspeak(RESUME_HELP);
|
||||
exit(EXIT_SUCCESS);
|
||||
return name;
|
||||
}
|
||||
|
||||
int resume(void)
|
||||
{
|
||||
/* Resume. Read a suspended game back from a file.
|
||||
* If ADVENT_NOSAVE is defined, do nothing instead. */
|
||||
int suspend(void) {
|
||||
/* Suspend. Offer to save things in a file, but charging
|
||||
* some points (so can't win by using saved games to retry
|
||||
* battles or to start over after learning zzword).
|
||||
* If ADVENT_NOSAVE is defined, gripe instead. */
|
||||
|
||||
#ifdef ADVENT_NOSAVE
|
||||
return GO_UNKNOWN;
|
||||
#if defined ADVENT_NOSAVE || defined ADVENT_AUTOSAVE
|
||||
rspeak(SAVERESUME_DISABLED);
|
||||
return GO_TOP;
|
||||
#endif
|
||||
FILE *fp = NULL;
|
||||
FILE *fp = NULL;
|
||||
|
||||
if (game.loc != 1 ||
|
||||
game.abbrev[1] != 1) {
|
||||
rspeak(RESUME_ABANDON);
|
||||
if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE], arbitrary_messages[OK_MAN], arbitrary_messages[OK_MAN]))
|
||||
return GO_CLEAROBJ;
|
||||
}
|
||||
rspeak(SUSPEND_WARNING);
|
||||
if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE],
|
||||
arbitrary_messages[OK_MAN],
|
||||
arbitrary_messages[OK_MAN])) {
|
||||
return GO_CLEAROBJ;
|
||||
}
|
||||
game.saved = game.saved + 5;
|
||||
|
||||
while (fp == NULL) {
|
||||
char* name = myreadline("\nFile name: ");
|
||||
// Autocomplete can leave the input with an extra trailing space.
|
||||
if (name != NULL && strlen(name) > 0 && name[strlen(name) - 1] == ' ')
|
||||
name[strlen(name) - 1] = '\0';
|
||||
if (name == NULL)
|
||||
return GO_TOP;
|
||||
fp = fopen(name, READ_MODE);
|
||||
if (fp == NULL)
|
||||
printf("Can't open file %s, try again.\n", name);
|
||||
free(name);
|
||||
}
|
||||
while (fp == NULL) {
|
||||
char *name = myreadline("\nFile name: ");
|
||||
if (name == NULL) {
|
||||
return GO_TOP;
|
||||
}
|
||||
name = strip(name);
|
||||
if (strlen(name) == 0) {
|
||||
return GO_TOP; // LCOV_EXCL_LINE
|
||||
}
|
||||
fp = fopen(strip(name), WRITE_MODE);
|
||||
if (fp == NULL) {
|
||||
printf("Can't open file %s, try again.\n", name);
|
||||
}
|
||||
free(name);
|
||||
}
|
||||
|
||||
return restore(fp);
|
||||
savefile(fp);
|
||||
fclose(fp);
|
||||
rspeak(RESUME_HELP);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
int restore(FILE* fp)
|
||||
{
|
||||
/* Read and restore game state from file, assuming
|
||||
* sane initial state.
|
||||
* If ADVENT_NOSAVE is defined, do nothing instead. */
|
||||
#ifdef ADVENT_NOSAVE
|
||||
return GO_UNKNOWN;
|
||||
#endif
|
||||
int resume(void) {
|
||||
/* Resume. Read a suspended game back from a file.
|
||||
* If ADVENT_NOSAVE is defined, gripe instead. */
|
||||
|
||||
IGNORE(fread(&save, sizeof(struct save_t), 1, fp));
|
||||
fclose(fp);
|
||||
if (save.version != VRSION) {
|
||||
rspeak(VERSION_SKEW, save.version / 10, MOD(save.version, 10), VRSION / 10, MOD(VRSION, 10));
|
||||
} else if (is_valid(save.game)) {
|
||||
game = save.game;
|
||||
}
|
||||
return GO_TOP;
|
||||
#if defined ADVENT_NOSAVE || defined ADVENT_AUTOSAVE
|
||||
rspeak(SAVERESUME_DISABLED);
|
||||
return GO_TOP;
|
||||
#endif
|
||||
FILE *fp = NULL;
|
||||
|
||||
if (game.loc != LOC_START || game.locs[LOC_START].abbrev != 1) {
|
||||
rspeak(RESUME_ABANDON);
|
||||
if (!yes_or_no(arbitrary_messages[THIS_ACCEPTABLE],
|
||||
arbitrary_messages[OK_MAN],
|
||||
arbitrary_messages[OK_MAN])) {
|
||||
return GO_CLEAROBJ;
|
||||
}
|
||||
}
|
||||
|
||||
while (fp == NULL) {
|
||||
char *name = myreadline("\nFile name: ");
|
||||
if (name == NULL) {
|
||||
return GO_TOP;
|
||||
}
|
||||
name = strip(name);
|
||||
if (strlen(name) == 0) {
|
||||
return GO_TOP; // LCOV_EXCL_LINE
|
||||
}
|
||||
fp = fopen(name, READ_MODE);
|
||||
if (fp == NULL) {
|
||||
printf("Can't open file %s, try again.\n", name);
|
||||
}
|
||||
free(name);
|
||||
}
|
||||
|
||||
return restore(fp);
|
||||
}
|
||||
|
||||
bool is_valid(struct game_t valgame)
|
||||
{
|
||||
/* Save files can be roughly grouped into three groups:
|
||||
* With valid, reaceable state, with valid, but unreachable
|
||||
* state and with invaild state. We check that state is
|
||||
* valid: no states are outside minimal or maximal value
|
||||
*/
|
||||
int restore(FILE *fp) {
|
||||
/* Read and restore game state from file, assuming
|
||||
* sane initial state.
|
||||
* If ADVENT_NOSAVE is defined, gripe instead. */
|
||||
#ifdef ADVENT_NOSAVE
|
||||
rspeak(SAVERESUME_DISABLED);
|
||||
return GO_TOP;
|
||||
#endif
|
||||
|
||||
/* Prevent division by zero */
|
||||
if (valgame.abbnum == 0) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
IGNORE(fread(&save, sizeof(struct save_t), 1, fp));
|
||||
fclose(fp);
|
||||
if (memcmp(save.magic, ADVENT_MAGIC, sizeof(ADVENT_MAGIC)) != 0 ||
|
||||
save.canary != ENDIAN_MAGIC) {
|
||||
rspeak(BAD_SAVE);
|
||||
} else if (save.version != SAVE_VERSION) {
|
||||
rspeak(VERSION_SKEW, save.version / 10, MOD(save.version, 10),
|
||||
SAVE_VERSION / 10, MOD(SAVE_VERSION, 10));
|
||||
} else if (!is_valid(save.game)) {
|
||||
rspeak(SAVE_TAMPERING);
|
||||
exit(EXIT_SUCCESS);
|
||||
} else {
|
||||
game = save.game;
|
||||
}
|
||||
return GO_TOP;
|
||||
}
|
||||
|
||||
/* Check for RNG overflow. Truncate */
|
||||
if (valgame.lcg_x >= LCG_M) {
|
||||
valgame.lcg_x %= LCG_M; // LCOV_EXCL_LINE
|
||||
}
|
||||
bool is_valid(struct game_t valgame) {
|
||||
/* Save files can be roughly grouped into three groups:
|
||||
* With valid, reachable state, with valid, but unreachable
|
||||
* state and with invalid state. We check that state is
|
||||
* valid: no states are outside minimal or maximal value
|
||||
*/
|
||||
|
||||
/* Check for RNG underflow. Transpose */
|
||||
if (valgame.lcg_x < LCG_M) {
|
||||
valgame.lcg_x = LCG_M + (valgame.lcg_x % LCG_M);
|
||||
}
|
||||
/* Prevent division by zero */
|
||||
if (valgame.abbnum == 0) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
/* Bounds check for locations */
|
||||
if ( valgame.chloc < -1 || valgame.chloc > NLOCATIONS ||
|
||||
valgame.chloc2 < -1 || valgame.chloc2 > NLOCATIONS ||
|
||||
valgame.loc < 0 || valgame.loc > NLOCATIONS ||
|
||||
valgame.newloc < 0 || valgame.newloc > NLOCATIONS ||
|
||||
valgame.oldloc < 0 || valgame.oldloc > NLOCATIONS ||
|
||||
valgame.oldlc2 < 0 || valgame.oldlc2 > NLOCATIONS) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
/* Bounds check for location arrays */
|
||||
for (int i = 0; i <= NDWARVES; i++) {
|
||||
if (valgame.dloc[i] < -1 || valgame.dloc[i] > NLOCATIONS ||
|
||||
valgame.odloc[i] < -1 || valgame.odloc[i] > NLOCATIONS) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
/* Check for RNG overflow. Truncate */
|
||||
if (valgame.lcg_x >= LCG_M) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i <= NOBJECTS; i++) {
|
||||
if (valgame.place[i] < -1 || valgame.place[i] > NLOCATIONS ||
|
||||
valgame.fixed[i] < -1 || valgame.fixed[i] > NLOCATIONS) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
/* Bounds check for locations */
|
||||
if (valgame.chloc < -1 || valgame.chloc > NLOCATIONS ||
|
||||
valgame.chloc2 < -1 || valgame.chloc2 > NLOCATIONS ||
|
||||
valgame.loc < 0 || valgame.loc > NLOCATIONS || valgame.newloc < 0 ||
|
||||
valgame.newloc > NLOCATIONS || valgame.oldloc < 0 ||
|
||||
valgame.oldloc > NLOCATIONS || valgame.oldlc2 < 0 ||
|
||||
valgame.oldlc2 > NLOCATIONS) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
/* Bounds check for location arrays */
|
||||
for (int i = 0; i <= NDWARVES; i++) {
|
||||
if (valgame.dwarves[i].loc < -1 ||
|
||||
valgame.dwarves[i].loc > NLOCATIONS ||
|
||||
valgame.dwarves[i].oldloc < -1 ||
|
||||
valgame.dwarves[i].oldloc > NLOCATIONS) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
/* Bounds check for dwarves */
|
||||
if (valgame.dtotal < 0 || valgame.dtotal > NDWARVES ||
|
||||
valgame.dkill < 0 || valgame.dkill > NDWARVES) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
for (int i = 0; i <= NOBJECTS; i++) {
|
||||
if (valgame.objects[i].place < -1 ||
|
||||
valgame.objects[i].place > NLOCATIONS ||
|
||||
valgame.objects[i].fixed < -1 ||
|
||||
valgame.objects[i].fixed > NLOCATIONS) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
/* Validate that we didn't die too many times in save */
|
||||
if (valgame.numdie >= NDEATHS) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
/* Bounds check for dwarves */
|
||||
if (valgame.dtotal < 0 || valgame.dtotal > NDWARVES ||
|
||||
valgame.dkill < 0 || valgame.dkill > NDWARVES) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
/* Recalculate tally, throw the towel if in disagreement */
|
||||
int temp_tally = 0;
|
||||
for (int treasure = 1; treasure <= NOBJECTS; treasure++) {
|
||||
if (objects[treasure].is_treasure) {
|
||||
if (valgame.prop[treasure] == STATE_NOTFOUND) {
|
||||
++temp_tally;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (temp_tally != valgame.tally) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
/* Validate that we didn't die too many times in save */
|
||||
if (valgame.numdie >= NDEATHS) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
/* Check that properties of objects aren't beyond expected */
|
||||
for (obj_t obj = 0; obj <= NOBJECTS; obj++) {
|
||||
if (valgame.prop[obj] < STATE_NOTFOUND || valgame.prop[obj] > 1) {
|
||||
switch (obj) {
|
||||
case RUG:
|
||||
case DRAGON:
|
||||
case BIRD:
|
||||
case BOTTLE:
|
||||
case PLANT:
|
||||
case PLANT2:
|
||||
case TROLL:
|
||||
case URN:
|
||||
case EGGS:
|
||||
case VASE:
|
||||
case CHAIN:
|
||||
if (valgame.prop[obj] == 2) // There are multiple different states, but it's convenient to clump them together
|
||||
continue;
|
||||
/* FALLTHRU */
|
||||
case BEAR:
|
||||
if (valgame.prop[BEAR] == CONTENTED_BEAR || valgame.prop[BEAR] == BEAR_DEAD)
|
||||
continue;
|
||||
/* FALLTHRU */
|
||||
default:
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Recalculate tally, throw the towel if in disagreement */
|
||||
int temp_tally = 0;
|
||||
for (int treasure = 1; treasure <= NOBJECTS; treasure++) {
|
||||
if (objects[treasure].is_treasure) {
|
||||
if (OBJECT_IS_NOTFOUND2(valgame, treasure)) {
|
||||
++temp_tally;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (temp_tally != valgame.tally) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
/* Check that values in linked lists for objects in locations are inside bounds */
|
||||
for (loc_t loc = LOC_NOWHERE; loc <= NLOCATIONS; loc++) {
|
||||
if (valgame.atloc[loc] < NO_OBJECT || valgame.atloc[loc] > NOBJECTS * 2) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
for (obj_t obj = 0; obj <= NOBJECTS * 2; obj++ ) {
|
||||
if (valgame.link[obj] < NO_OBJECT || valgame.link[obj] > NOBJECTS * 2) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
/* Check that properties of objects aren't beyond expected */
|
||||
for (obj_t obj = 0; obj <= NOBJECTS; obj++) {
|
||||
if (PROP_IS_INVALID(valgame.objects[obj].prop)) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
/* Check that values in linked lists for objects in locations are inside
|
||||
* bounds */
|
||||
for (loc_t loc = LOC_NOWHERE; loc <= NLOCATIONS; loc++) {
|
||||
if (valgame.locs[loc].atloc < NO_OBJECT ||
|
||||
valgame.locs[loc].atloc > NOBJECTS * 2) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
for (obj_t obj = 0; obj <= NOBJECTS * 2; obj++) {
|
||||
if (valgame.link[obj] < NO_OBJECT ||
|
||||
valgame.link[obj] > NOBJECTS * 2) {
|
||||
return false; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* end */
|
||||
|
|
260
score.c
260
score.c
|
@ -1,142 +1,162 @@
|
|||
/*
|
||||
* Scoring and wrap-up.
|
||||
*
|
||||
* Copyright (c) 1977, 2005 by Will Crowther and Don Woods
|
||||
* Copyright (c) 2017 by Eric S. Raymond
|
||||
* SPDX-License-Identifier: BSD-2-clause
|
||||
* SPDX-FileCopyrightText: (C) 1977, 2005 by Will Crowther and Don Woods
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include "advent.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)
|
||||
/* mode is 'scoregame' if scoring, 'quitgame' if quitting, 'endgame' if died
|
||||
* or won */
|
||||
{
|
||||
int score = 0;
|
||||
int score(enum termination mode) {
|
||||
/* mode is 'scoregame' if scoring, 'quitgame' if quitting, 'endgame' if
|
||||
* died or won */
|
||||
int score = 0;
|
||||
|
||||
/* The present scoring algorithm is as follows:
|
||||
* Objective: Points: Present total possible:
|
||||
* Getting well into cave 25 25
|
||||
* Each treasure < chest 12 60
|
||||
* Treasure chest itself 14 14
|
||||
* Each treasure > chest 16 224
|
||||
* Surviving (MAX-NUM)*10 30
|
||||
* Not quitting 4 4
|
||||
* Reaching "game.closng" 25 25
|
||||
* "Closed": Quit/Killed 10
|
||||
* Klutzed 25
|
||||
* Wrong way 30
|
||||
* Success 45 45
|
||||
* Came to Witt's End 1 1
|
||||
* Round out the total 2 2
|
||||
* TOTAL: 430
|
||||
* Points can also be deducted for using hints or too many turns, or for
|
||||
* saving intermediate positions. */
|
||||
/* The present scoring algorithm is as follows:
|
||||
* Objective: Points: Present total possible:
|
||||
* Getting well into cave 25 25
|
||||
* Each treasure < chest 12 60
|
||||
* Treasure chest itself 14 14
|
||||
* Each treasure > chest 16 224
|
||||
* Surviving (MAX-NUM)*10 30
|
||||
* Not quitting 4 4
|
||||
* Reaching "game.closng" 25 25
|
||||
* "Closed": Quit/Killed 10
|
||||
* Klutzed 25
|
||||
* Wrong way 30
|
||||
* Success 45 45
|
||||
* Came to Witt's End 1 1
|
||||
* Round out the total 2 2
|
||||
* TOTAL: 430
|
||||
* Points can also be deducted for using hints or too many turns, or
|
||||
* for saving intermediate positions. */
|
||||
|
||||
/* First tally up the treasures. Must be in building and not broken.
|
||||
* Give the poor guy 2 points just for finding each treasure. */
|
||||
mxscor = 0;
|
||||
for (int i = 1; i <= NOBJECTS; i++) {
|
||||
if (!objects[i].is_treasure)
|
||||
continue;
|
||||
if (objects[i].inventory != 0) {
|
||||
int k = 12;
|
||||
if (i == CHEST)
|
||||
k = 14;
|
||||
if (i > CHEST)
|
||||
k = 16;
|
||||
if (game.prop[i] > STATE_NOTFOUND)
|
||||
score += 2;
|
||||
if (game.place[i] == LOC_BUILDING && game.prop[i] == STATE_FOUND)
|
||||
score += k - 2;
|
||||
mxscor += k;
|
||||
}
|
||||
}
|
||||
/* First tally up the treasures. Must be in building and not broken.
|
||||
* Give the poor guy 2 points just for finding each treasure. */
|
||||
mxscor = 0;
|
||||
for (int i = 1; i <= NOBJECTS; i++) {
|
||||
if (!objects[i].is_treasure) {
|
||||
continue;
|
||||
}
|
||||
if (objects[i].inventory != 0) {
|
||||
int k = 12;
|
||||
if (i == CHEST) {
|
||||
k = 14;
|
||||
}
|
||||
if (i > CHEST) {
|
||||
k = 16;
|
||||
}
|
||||
if (!OBJECT_IS_STASHED(i) && !OBJECT_IS_NOTFOUND(i)) {
|
||||
score += 2;
|
||||
}
|
||||
if (game.objects[i].place == LOC_BUILDING &&
|
||||
OBJECT_IS_FOUND(i)) {
|
||||
score += k - 2;
|
||||
}
|
||||
mxscor += k;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now look at how he finished and how far he got. NDEATHS and
|
||||
* game.numdie tell us how well he survived. game.dflag will tell us
|
||||
* if he ever got suitably deep into the cave. game.closng still
|
||||
* indicates whether he reached the endgame. And if he got as far as
|
||||
* "cave closed" (indicated by "game.closed"), then bonus is zero for
|
||||
* mundane exits or 133, 134, 135 if he blew it (so to speak). */
|
||||
score += (NDEATHS - game.numdie) * 10;
|
||||
mxscor += NDEATHS * 10;
|
||||
if (mode == endgame)
|
||||
score += 4;
|
||||
mxscor += 4;
|
||||
if (game.dflag != 0)
|
||||
score += 25;
|
||||
mxscor += 25;
|
||||
if (game.closng)
|
||||
score += 25;
|
||||
mxscor += 25;
|
||||
if (game.closed) {
|
||||
if (game.bonus == none)
|
||||
score += 10;
|
||||
if (game.bonus == splatter)
|
||||
score += 25;
|
||||
if (game.bonus == defeat)
|
||||
score += 30;
|
||||
if (game.bonus == victory)
|
||||
score += 45;
|
||||
}
|
||||
mxscor += 45;
|
||||
/* Now look at how he finished and how far he got. NDEATHS and
|
||||
* game.numdie tell us how well he survived. game.dflag will tell us
|
||||
* if he ever got suitably deep into the cave. game.closng still
|
||||
* indicates whether he reached the endgame. And if he got as far as
|
||||
* "cave closed" (indicated by "game.closed"), then bonus is zero for
|
||||
* mundane exits or 133, 134, 135 if he blew it (so to speak). */
|
||||
score += (NDEATHS - game.numdie) * 10;
|
||||
mxscor += NDEATHS * 10;
|
||||
if (mode == endgame) {
|
||||
score += 4;
|
||||
}
|
||||
mxscor += 4;
|
||||
if (game.dflag != 0) {
|
||||
score += 25;
|
||||
}
|
||||
mxscor += 25;
|
||||
if (game.closng) {
|
||||
score += 25;
|
||||
}
|
||||
mxscor += 25;
|
||||
if (game.closed) {
|
||||
if (game.bonus == none) {
|
||||
score += 10;
|
||||
}
|
||||
if (game.bonus == splatter) {
|
||||
score += 25;
|
||||
}
|
||||
if (game.bonus == defeat) {
|
||||
score += 30;
|
||||
}
|
||||
if (game.bonus == victory) {
|
||||
score += 45;
|
||||
}
|
||||
}
|
||||
mxscor += 45;
|
||||
|
||||
/* Did he come to Witt's End as he should? */
|
||||
if (game.place[MAGAZINE] == LOC_WITTSEND)
|
||||
score += 1;
|
||||
mxscor += 1;
|
||||
/* Did he come to Witt's End as he should? */
|
||||
if (game.objects[MAGAZINE].place == LOC_WITTSEND) {
|
||||
score += 1;
|
||||
}
|
||||
mxscor += 1;
|
||||
|
||||
/* Round it off. */
|
||||
score += 2;
|
||||
mxscor += 2;
|
||||
/* Round it off. */
|
||||
score += 2;
|
||||
mxscor += 2;
|
||||
|
||||
/* Deduct for hints/turns/saves. Hints < 4 are special; see database desc. */
|
||||
for (int i = 0; i < NHINTS; i++) {
|
||||
if (game.hinted[i])
|
||||
score = score - hints[i].penalty;
|
||||
}
|
||||
if (game.novice)
|
||||
score -= 5;
|
||||
if (game.clshnt)
|
||||
score -= 10;
|
||||
score = score - game.trnluz - game.saved;
|
||||
/* Deduct for hints/turns/saves. Hints < 4 are special; see database
|
||||
* desc. */
|
||||
for (int i = 0; i < NHINTS; i++) {
|
||||
if (game.hints[i].used) {
|
||||
score = score - hints[i].penalty;
|
||||
}
|
||||
}
|
||||
if (game.novice) {
|
||||
score -= 5;
|
||||
}
|
||||
if (game.clshnt) {
|
||||
score -= 10;
|
||||
}
|
||||
score = score - game.trnluz - game.saved;
|
||||
|
||||
/* Return to score command if that's where we came from. */
|
||||
if (mode == scoregame) {
|
||||
rspeak(GARNERED_POINTS, score, mxscor, game.turns, game.turns);
|
||||
}
|
||||
/* Return to score command if that's where we came from. */
|
||||
if (mode == scoregame) {
|
||||
rspeak(GARNERED_POINTS, score, mxscor, game.turns, game.turns);
|
||||
}
|
||||
|
||||
return score;
|
||||
return score;
|
||||
}
|
||||
|
||||
void terminate(enum termination mode)
|
||||
/* End of game. Let's tell him all about it. */
|
||||
{
|
||||
int points = score(mode);
|
||||
void terminate(enum termination mode) {
|
||||
/* End of game. Let's tell him all about it. */
|
||||
int points = score(mode);
|
||||
#if defined ADVENT_AUTOSAVE
|
||||
autosave();
|
||||
#endif
|
||||
|
||||
if (points + game.trnluz + 1 >= mxscor && game.trnluz != 0)
|
||||
rspeak(TOOK_LONG);
|
||||
if (points + game.saved + 1 >= mxscor && game.saved != 0)
|
||||
rspeak(WITHOUT_SUSPENDS);
|
||||
rspeak(TOTAL_SCORE, points, mxscor, game.turns, game.turns);
|
||||
for (int i = 1; i <= (int)NCLASSES; i++) {
|
||||
if (classes[i].threshold >= points) {
|
||||
speak(classes[i].message);
|
||||
if (i < (int)NCLASSES) {
|
||||
int nxt = classes[i].threshold + 1 - points;
|
||||
rspeak(NEXT_HIGHER, nxt, nxt);
|
||||
} else {
|
||||
rspeak(NO_HIGHER);
|
||||
}
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
rspeak(OFF_SCALE);
|
||||
exit(EXIT_SUCCESS);
|
||||
if (points + game.trnluz + 1 >= mxscor && game.trnluz != 0) {
|
||||
rspeak(TOOK_LONG);
|
||||
}
|
||||
if (points + game.saved + 1 >= mxscor && game.saved != 0) {
|
||||
rspeak(WITHOUT_SUSPENDS);
|
||||
}
|
||||
rspeak(TOTAL_SCORE, points, mxscor, game.turns, game.turns);
|
||||
for (int i = 1; i <= (int)NCLASSES; i++) {
|
||||
if (classes[i].threshold >= points) {
|
||||
speak(classes[i].message);
|
||||
if (i < (int)NCLASSES) {
|
||||
int nxt = classes[i].threshold + 1 - points;
|
||||
rspeak(NEXT_HIGHER, nxt, nxt);
|
||||
} else {
|
||||
rspeak(NO_HIGHER);
|
||||
}
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
}
|
||||
rspeak(OFF_SCALE);
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* end */
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
<--
|
||||
SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
SPDX-License-Identifier: BSD-2-Clause
|
||||
-->
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "{h_file}"
|
||||
|
||||
const char* arbitrary_messages[] = {{
|
||||
|
@ -48,4 +53,7 @@ const travelop_t travel[] = {{
|
|||
|
||||
const char *ignore = "{ignore}";
|
||||
|
||||
/* end */
|
||||
/* Dwarf starting locations */
|
||||
const int dwarflocs[NDWARVES] = {{{dwarflocs}}};
|
||||
|
||||
/* end */
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
#ifndef DUNGEON_H
|
||||
#define DUNGEON_H
|
||||
|
||||
|
@ -31,6 +35,9 @@
|
|||
#define COND_HOGRE 20 /* Trying to deal with ogre */
|
||||
#define COND_HJADE 21 /* Found all treasures except jade */
|
||||
|
||||
#define NDWARVES {ndwarflocs} // number of dwarves
|
||||
extern const int dwarflocs[NDWARVES];
|
||||
|
||||
typedef struct {{
|
||||
const char** strs;
|
||||
const int n;
|
||||
|
|
125
tests/Makefile
125
tests/Makefile
|
@ -1,4 +1,7 @@
|
|||
# Test-suite makefile for opeb-adventure
|
||||
# Test-suite makefile for open-adventure
|
||||
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
# Use absolute path so tests that change working directory still use
|
||||
# scripts from parent directory. Note that using $PWD seems to fail
|
||||
|
@ -29,14 +32,12 @@ TESTLOADS := $(shell ls -1 *.log | sed '/.log/s///' | sort)
|
|||
|
||||
check: savecheck
|
||||
@make tap | tapview
|
||||
@echo "=== No diff output is good news."
|
||||
@-advent -x 2>/dev/null || exit 0 # Get usage message into coverage tests
|
||||
@-advent -l /dev/null <pitfall.log >/dev/null
|
||||
|
||||
.SUFFIXES: .chk
|
||||
|
||||
clean:
|
||||
rm -fr *~ adventure.text *.adv scratch.tmp
|
||||
rm -fr *~ *.adv scratch.tmp *.ochk advent430 adventure.data
|
||||
|
||||
# Show summary lines for all tests.
|
||||
testlist:
|
||||
|
@ -47,39 +48,50 @@ listcheck:
|
|||
done
|
||||
|
||||
# Generate bogus savegames.
|
||||
savegames:
|
||||
@$(ECHO) "cheat: Generate save file with -900 deaths"
|
||||
cheat_numdie.adv:
|
||||
@$(PARDIR)/cheat -d -900 -o cheat_numdie.adv > /tmp/cheat_numdie
|
||||
@$(ECHO) "cheat: Generate save file with -1000 deaths"
|
||||
cheat_numdie1000.adv:
|
||||
@$(PARDIR)/cheat -d -1000 -o cheat_numdie1000.adv > /tmp/cheat_numdie1000
|
||||
@$(ECHO) "cheat: Generate save file with version -1337"
|
||||
cheat_savetamper.adv:
|
||||
@$(PARDIR)/cheat -d 2000 -o cheat_savetamper.adv > /tmp/cheat_savetamper
|
||||
resume_badversion.adv:
|
||||
@$(PARDIR)/cheat -v -1337 -o resume_badversion.adv > /tmp/cheat_badversion
|
||||
@$(ECHO) "cheat: Generate save file 1000 saves"
|
||||
thousand_saves.adv:
|
||||
@$(PARDIR)/cheat -s -1000 -o thousand_saves.adv > /tmp/cheat_1000saves
|
||||
@$(ECHO) "cheat: Generate save file 1000 turns"
|
||||
@$(PARDIR)/cheat -t -1000 -o thousand_saves.adv > /tmp/cheat_1000turns
|
||||
@$(ECHO) "cheat: Generate save file 1000 turns"
|
||||
@$(PARDIR)/cheat -l -1000 -o thousand_lamp.adv > /tmp/cheat_1000lamp
|
||||
@rm -f /tmp/cheat*
|
||||
thousand_turns.adv:
|
||||
@$(PARDIR)/cheat -t -1000 -o thousand_turns.adv > /tmp/cheat_1000turns
|
||||
thousand_limit.adv:
|
||||
@$(PARDIR)/cheat -l -1000 -o thousand_limit.adv > /tmp/cheat_1000limit
|
||||
SGAMES = cheat_numdie.adv cheat_numdie1000.adv cheat_savetamper.adv resume_badversion.adv \
|
||||
thousand_saves.adv thousand_turns.adv thousand_limit.adv
|
||||
|
||||
# Force coverage of cheat edgecases
|
||||
savecheck: savegames
|
||||
@$(ECHO) "TEST cheat: Bogus option for save file generation"
|
||||
scheck1:
|
||||
@$(PARDIR)/cheat -QqQ 2> /tmp/coverage_cheat_batopt | true
|
||||
@$(ECHO) "TEST cheat: No save file specified"
|
||||
@./outcheck.sh "cheat: bogus option for save file generation"
|
||||
scheck2:
|
||||
@$(PARDIR)/cheat 2>/dev/null | true
|
||||
@$(ECHO) "TEST cheat: Fail to save because we omit -o"
|
||||
@./outcheck.sh "cheat: No save file specified"
|
||||
scheck3:
|
||||
@$(PARDIR)/cheat -d 1 2> /tmp/coverage_cheat_nooutput | true
|
||||
@$(ECHO) "TEST cheat: Fail to save to invalid path"
|
||||
@./outcheck.sh "cheat: doesn't save because we omit -o"
|
||||
scheck4:
|
||||
@$(PARDIR)/cheat -o / 2> /tmp/coverage_cheat_badoutput | true
|
||||
@$(ECHO) "TEST advent: Start with invalid file with -r"
|
||||
@advent -r /badfilename < pitfall.log > /tmp/coverage_advent_readfail 2>&1 || exit 1
|
||||
@$(ECHO) "TEST advent: Start with invalid file with -l"
|
||||
@advent -l / < pitfall.log > /tmp/coverage_advent_logfail 2>&1 || exit 1
|
||||
@$(ECHO) "TEST advent: Test -r with valid input"
|
||||
@advent -r thousand_saves.adv < pitfall.log > /tmp/coverage_advent_readfail 2>&1 || exit 1
|
||||
@rm -f /tmp/coverage*
|
||||
@./outcheck.sh "cheat: doesn't save to invalid path"
|
||||
scheck5:
|
||||
@$(advent) -r /badfilename < pitfall.log > /tmp/coverage_advent_readfail 2>&1 || exit 1
|
||||
@./outcheck.sh "cheat: doesn't start with invalid file with -r"
|
||||
scheck6:
|
||||
@$(advent) -l / < pitfall.log > /tmp/coverage_advent_logfail 2>&1 || exit 1
|
||||
@./outcheck.sh "cheat: doesn't start with invalid file passed to -l"
|
||||
scheck7:
|
||||
@$(advent) -r thousand_saves.adv < pitfall.log > /tmp/coverage_advent_readfail 2>&1 || exit 1
|
||||
@./outcheck.sh "test -r with valid input"
|
||||
SCHECKS = scheck1 scheck2 scheck3 scheck4 scheck5 scheck6 scheck7
|
||||
|
||||
# Don't run this from here, you'll get cryptic warnings and no good result
|
||||
# if the advent binary wasn't built with coverage flags. Do "make clean coverage"
|
||||
# from the top-level directory.
|
||||
coverage: check
|
||||
lcov -t "advent" -o $(PARDIR)/advent.info -c -d $(PARDIR) --gcov-tool=$(GCOV)
|
||||
genhtml -o $(PARDIR)/coverage/ $(PARDIR)/advent.info
|
||||
|
@ -93,22 +105,73 @@ buildchecks: savegames
|
|||
OPTS=`sed -n /#options:/s///p <$${file}.log`; \
|
||||
advent $$OPTS <$${file}.log >$${file}.chk 2>&1 || exit 1; \
|
||||
done; \
|
||||
echo "inven" | advent isofoo.log /dev/stdin >multifile.chk
|
||||
echo "inven" | advent issue36.log /dev/stdin >multifile.chk; \
|
||||
rm -f scratch.tmp
|
||||
|
||||
RUN_TARGETS=$(TESTLOADS:%=run-regress-%)
|
||||
$(RUN_TARGETS): run-regress-%: %.log
|
||||
@(test=$(<:.log=); legend=$$(sed -n '/^## /s///p' <"$<" 2>/dev/null || echo "(no description)"); \
|
||||
OPTS=`sed -n /#options:/s///p $<`; \
|
||||
$(advent) $$OPTS <$< | tapdiffer "$<: $${legend}" "$${test}.chk")
|
||||
$(advent) $$OPTS <$< | tapdiffer "$${test}: $${legend}" "$${test}.chk")
|
||||
|
||||
multifile-regress:
|
||||
@(echo "inven" | advent isofoo.log /dev/stdin) | tapdiffer "multifile: multiple-file test" multifile.chk
|
||||
@(echo "inven" | advent issue36.log /dev/stdin) | tapdiffer "multifile: multiple-file test" multifile.chk
|
||||
|
||||
TEST_TARGETS = $(RUN_TARGETS) multifile-regress
|
||||
TEST_TARGETS = $(SCHECKS) $(RUN_TARGETS) multifile-regress
|
||||
|
||||
tap: count $(TEST_TARGETS)
|
||||
tap: count $(SGAMES) $(TEST_TARGETS)
|
||||
@rm -f scratch.tmp /tmp/coverage* /tmp/cheat*
|
||||
count:
|
||||
@echo 1..$(words $(TEST_TARGETS))
|
||||
|
||||
# The following machinery tests the game against a binary made from
|
||||
# the advent430 branch To use it, switch to that branch, build the
|
||||
# binary, run it once to generate adventure.data, then switch back to
|
||||
# master leaving advent430 and adventure.data in place (make clean
|
||||
# does not remove them).
|
||||
#
|
||||
# make clean # Clean up object files, laving a bare source tree
|
||||
# git checkout advent430 # Check out the advent430 branch
|
||||
# make # Build the advent430 binary
|
||||
# advent430 # Run it. Answer the novice question and quit
|
||||
# make clean # Remove .o files
|
||||
# git checkout master # Go back to master branch
|
||||
# make # Rebuild advent.
|
||||
#
|
||||
# The diff file produced has corrected spellings in it. That's what oldfilter
|
||||
# is for, to massage out the original spellings and avoid noise diffs.
|
||||
# Diffs in amount of whitespace and trailing whitespace are ignored
|
||||
#
|
||||
# A magic comment of NOCOMPARE in a log file excludes it from this comparison.
|
||||
# making it a skipped test in the TAP view. First use of this was to avoid a
|
||||
# spurious mismatch on the news text. Other uses avoid spurious mismatches due
|
||||
# to bug fixes.
|
||||
#
|
||||
# When adding more tests, bear in mind that any game that continues after a
|
||||
# resurrection will need a NOCOMPARE. At some point in the forward port,
|
||||
# resurrection was accidentally changed in a way that messed with the LCG chain.
|
||||
#
|
||||
# The *.chk files need not be up-to-date for this to work.
|
||||
#
|
||||
TAPFILTER=tapview
|
||||
oldcompare:
|
||||
@if [ -f ../advent430 ]; then cp ../advent430 ../adventure.data .; else echo "advent430 nonexistent"; exit 1; fi
|
||||
@-(for x in *.log; do \
|
||||
stem=$${x%.log}; \
|
||||
legend=$$(sed -n '/^## /s///p' <$$x 2>/dev/null || echo "(no description)"); \
|
||||
if grep NOCOMPARE $$x >/dev/null; \
|
||||
then echo "not ok - $${stem}.ochk: $${legend} # SKIP"; \
|
||||
else \
|
||||
./advent430 <$${stem}.log | oldfilter >$${stem}.ochk; \
|
||||
../advent <$${stem}.log >$${stem}.log-new; \
|
||||
./newfilter <$${stem}.log-new | tapdiffer -b "$${stem}: $${legend}" $${stem}.ochk; \
|
||||
fi; \
|
||||
done; \
|
||||
echo 1..$(words $(shell ls *.log))) | $(TAPFILTER)
|
||||
@rm *.ochk *-new advent430 adventure.data
|
||||
|
||||
# List all NOCOMPARE tests.
|
||||
residuals:
|
||||
@grep -n NOCOMPARE *.log
|
||||
|
||||
# end
|
||||
|
|
10
tests/README
10
tests/README
|
@ -17,9 +17,9 @@ To see summary lines from all tests, 'make testlist'. The summary lines
|
|||
are those led with ##; you should have one such descriptive line at the
|
||||
head of each file.
|
||||
|
||||
To run the tests, "make regress".
|
||||
To run the tests, "make check".
|
||||
|
||||
To remake the check files, "make buildregress".
|
||||
To remake the check files, "make buildchecks".
|
||||
|
||||
== Composing tests ==
|
||||
|
||||
|
@ -29,12 +29,8 @@ option giving a log path. Commands will be captured to that log.
|
|||
To re-use a command sequence from an existing log, run advent and
|
||||
paste it to the advent command log from the clipboard.
|
||||
|
||||
To see where we can use more tests, have a look at our test coverage
|
||||
report:
|
||||
To see where we can use more tests, "make coverage".
|
||||
|
||||
http://esr.gitlab.io/open-adventure/coverage/
|
||||
|
||||
Improvements in test coverage are always welcome.
|
||||
|
||||
// end
|
||||
|
||||
|
|
|
@ -1631,7 +1631,7 @@ There is a little axe lying beside the bear.
|
|||
|
||||
The bear is locked to the wall with a golden chain!
|
||||
|
||||
|
||||
>
|
||||
You scored 251 out of a possible 430, using 271 turns.
|
||||
|
||||
You have reached "Junior Master" status.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Observe axe after throwing at bear
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1838473132
|
||||
in
|
||||
|
|
|
@ -652,7 +652,7 @@ mist. A notice posted on the bridge reads, "Stop! Pay troll!"
|
|||
A burly troll stands by the bridge and insists you throw him a
|
||||
treasure before you may cross.
|
||||
|
||||
|
||||
>
|
||||
You scored 105 out of a possible 430, using 109 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Test throwing axe at non-dwarves.
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Added coverage of LOC_DEADCRAWL and CROSS_BRIDGE
|
||||
n
|
||||
seed 1838473132
|
||||
|
|
|
@ -7,11 +7,14 @@ 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.
|
||||
|
||||
> foo
|
||||
> resume
|
||||
Can't open file y, try again.
|
||||
|
||||
Nothing happens.
|
||||
Oops, that does not look like a valid save file.
|
||||
|
||||
You're in front of building.
|
||||
|
||||
>
|
||||
You scored 32 out of a possible 430, using 1 turn.
|
||||
|
||||
You are obviously a rank amateur. Better luck next time.
|
8
tests/badmagic.log
Normal file
8
tests/badmagic.log
Normal file
|
@ -0,0 +1,8 @@
|
|||
## Resume from filename withoy the right magic at the front
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#NOCOMPARE advent430 doesn't have this test
|
||||
n
|
||||
resume
|
||||
y
|
||||
../main.o
|
|
@ -307,7 +307,7 @@ The blood-specked body of a huge green dead dragon lies to one side.
|
|||
|
||||
For crying out loud, the poor thing is already dead!
|
||||
|
||||
|
||||
>
|
||||
You scored 77 out of a possible 430, using 49 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
## Get to dragon, refuse to use bare hands
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#NOCOMPARE Fails due uninteresting difference in whitespace process.
|
||||
# Based on walkthrough at http://www.ecsoftwareconsulting.com/node/56
|
||||
n
|
||||
seed 1635997320
|
||||
|
|
|
@ -1800,746 +1800,12 @@ 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
|
||||
to try to reincarnate you?
|
||||
|
||||
> yes
|
||||
|
||||
All right. But don't blame me if something goes wr......
|
||||
--- POOF!! ---
|
||||
You are engulfed in a cloud of orange smoke. Coughing and gasping,
|
||||
you emerge from the smoke and find....
|
||||
|
||||
You're inside building.
|
||||
|
||||
There is a richly-carved ebony statuette here!
|
||||
|
||||
There is an emerald here the size of a plover's egg!
|
||||
|
||||
There is a jewel-encrusted trident here!
|
||||
|
||||
There is a platinum pyramid here, 8 inches on a side!
|
||||
|
||||
There are diamonds here!
|
||||
|
||||
There is an enormous ruby here!
|
||||
|
||||
There are bars of silver here!
|
||||
|
||||
There is a Persian rug spread out on the floor!
|
||||
|
||||
There is a large sparkling nugget of gold here!
|
||||
|
||||
A precious jade necklace has been dropped here!
|
||||
|
||||
There is precious jewelry here!
|
||||
|
||||
> inven
|
||||
|
||||
You're not carrying anything.
|
||||
|
||||
> out
|
||||
|
||||
You're in front of building.
|
||||
|
||||
There is a shiny brass lamp nearby.
|
||||
|
||||
> take lamp
|
||||
> n
|
||||
|
||||
OK
|
||||
|
||||
> light lamp
|
||||
|
||||
Your lamp is now on.
|
||||
|
||||
> in
|
||||
|
||||
You're inside building.
|
||||
|
||||
There is a richly-carved ebony statuette here!
|
||||
|
||||
There is an emerald here the size of a plover's egg!
|
||||
|
||||
There is a jewel-encrusted trident here!
|
||||
|
||||
There is a platinum pyramid here, 8 inches on a side!
|
||||
|
||||
There are diamonds here!
|
||||
|
||||
There is an enormous ruby here!
|
||||
|
||||
There are bars of silver here!
|
||||
|
||||
There is a Persian rug spread out on the floor!
|
||||
|
||||
There is a large sparkling nugget of gold here!
|
||||
|
||||
A precious jade necklace has been dropped here!
|
||||
|
||||
There is precious jewelry here!
|
||||
|
||||
> plugh
|
||||
|
||||
>>Foof!<<
|
||||
|
||||
You're at "Y2".
|
||||
|
||||
> s
|
||||
|
||||
You're in n/s passage above e/w passage.
|
||||
|
||||
> d
|
||||
|
||||
You're in dirty passage.
|
||||
|
||||
> bedquilt
|
||||
|
||||
You're in Bedquilt.
|
||||
|
||||
> n
|
||||
|
||||
You're in large low room.
|
||||
|
||||
> sw
|
||||
|
||||
You're in sloping corridor.
|
||||
|
||||
> up
|
||||
|
||||
You're on sw side of chasm.
|
||||
|
||||
There is a little axe here.
|
||||
|
||||
There are many coins here!
|
||||
|
||||
Off to one side lies a glistening pearl!
|
||||
|
||||
There is a golden chain lying in a heap on the floor!
|
||||
|
||||
The wreckage of a bridge (and a dead bear) can be seen at the bottom
|
||||
of the chasm.
|
||||
|
||||
The troll is nowhere to be seen.
|
||||
|
||||
> over
|
||||
|
||||
There is no longer any way across the chasm.
|
||||
|
||||
You're on sw side of chasm.
|
||||
|
||||
There is a little axe here.
|
||||
|
||||
There are many coins here!
|
||||
|
||||
Off to one side lies a glistening pearl!
|
||||
|
||||
There is a golden chain lying in a heap on the floor!
|
||||
|
||||
The wreckage of a bridge (and a dead bear) can be seen at the bottom
|
||||
of the chasm.
|
||||
|
||||
The troll is nowhere to be seen.
|
||||
|
||||
> feed bear
|
||||
|
||||
Don't be ridiculous!
|
||||
|
||||
> attack bear
|
||||
|
||||
For crying out loud, the poor thing is already dead!
|
||||
|
||||
> take coins
|
||||
|
||||
OK
|
||||
|
||||
> take axe
|
||||
|
||||
OK
|
||||
|
||||
> sw
|
||||
|
||||
You're in sloping corridor.
|
||||
|
||||
> d
|
||||
|
||||
You're in large low room.
|
||||
|
||||
> se
|
||||
|
||||
You're in Oriental Room.
|
||||
|
||||
The floor is littered with worthless shards of pottery.
|
||||
|
||||
> se
|
||||
|
||||
You're in Swiss Cheese Room.
|
||||
|
||||
> ne
|
||||
|
||||
You are in Bedquilt, a long east/west passage with holes everywhere.
|
||||
To explore at random select north, south, up, or down.
|
||||
|
||||
> e
|
||||
|
||||
You're at complex junction.
|
||||
|
||||
> up
|
||||
|
||||
You're in dusty rock room.
|
||||
|
||||
> e
|
||||
|
||||
You're in dirty passage.
|
||||
|
||||
> up
|
||||
|
||||
You are in a low n/s passage at a hole in the floor. The hole goes
|
||||
down to an e/w passage.
|
||||
|
||||
> s
|
||||
|
||||
You're in Hall of Mt King.
|
||||
|
||||
> up
|
||||
|
||||
You're in Hall of Mists.
|
||||
|
||||
Rough stone steps lead up the dome.
|
||||
|
||||
> w
|
||||
|
||||
You're on east bank of fissure.
|
||||
|
||||
A three foot black rod with a rusty star on an end lies nearby.
|
||||
|
||||
A crystal bridge spans the fissure.
|
||||
|
||||
> take rod
|
||||
|
||||
OK
|
||||
|
||||
> wave rod
|
||||
|
||||
The crystal bridge has vanished!
|
||||
|
||||
> across
|
||||
|
||||
There is no way across the fissure.
|
||||
|
||||
You're on east bank of fissure.
|
||||
|
||||
> wave rod
|
||||
|
||||
A crystal bridge now spans the fissure.
|
||||
|
||||
> across
|
||||
|
||||
You're on west bank of fissure.
|
||||
|
||||
A crystal bridge spans the fissure.
|
||||
|
||||
> w
|
||||
|
||||
You're at west end of Hall of Mists.
|
||||
|
||||
> w
|
||||
|
||||
You're at east end of long hall.
|
||||
|
||||
> w
|
||||
|
||||
You're at west end of long hall.
|
||||
|
||||
> s
|
||||
|
||||
You are in a maze of twisty little passages, all different.
|
||||
|
||||
> s
|
||||
|
||||
You are in a maze of twisting little passages, all different.
|
||||
|
||||
> sw
|
||||
|
||||
You are in a twisting little maze of passages, all different.
|
||||
|
||||
> se
|
||||
|
||||
You are in a twisting maze of little passages, all different.
|
||||
|
||||
> s
|
||||
|
||||
You are in a little maze of twisting passages, all different.
|
||||
|
||||
> s
|
||||
|
||||
Dead end
|
||||
|
||||
There is a massive vending machine here, swung back to reveal a
|
||||
southward passage.
|
||||
|
||||
> drop coins
|
||||
|
||||
There are fresh batteries here.
|
||||
|
||||
> look
|
||||
|
||||
Sorry, but I am not allowed to give more detail. I will repeat the
|
||||
long description of your location.
|
||||
|
||||
Dead end
|
||||
|
||||
There are fresh batteries here.
|
||||
|
||||
There is a massive vending machine here, swung back to reveal a
|
||||
southward passage.
|
||||
|
||||
> n
|
||||
|
||||
Your lamp is getting dim. I'm taking the liberty of replacing the
|
||||
batteries.
|
||||
|
||||
You are in a little maze of twisting passages, all different.
|
||||
|
||||
> z
|
||||
|
||||
OK
|
||||
|
||||
> z
|
||||
|
||||
Tsk! A wizard wouldn't have to take 350 turns. This is going to cost
|
||||
you a couple of points.
|
||||
|
||||
OK
|
||||
|
||||
> z
|
||||
|
||||
OK
|
||||
|
||||
> z
|
||||
|
||||
OK
|
||||
|
||||
> s
|
||||
|
||||
Dead end
|
||||
|
||||
Some worn-out batteries have been discarded nearby.
|
||||
|
||||
There is a massive vending machine here, swung back to reveal a
|
||||
southward passage.
|
||||
|
||||
> take batteries
|
||||
|
||||
OK
|
||||
|
||||
> n
|
||||
|
||||
You are in a little maze of twisting passages, all different.
|
||||
|
||||
> back
|
||||
|
||||
Sorry, but the path twisted and turned so much that I can't figure
|
||||
out which way to go to get back.
|
||||
|
||||
You are in a little maze of twisting passages, all different.
|
||||
|
||||
> n
|
||||
|
||||
You are in a little maze of twisty passages, all different.
|
||||
|
||||
> nw
|
||||
|
||||
You are in a maze of twisty little passages, all different.
|
||||
|
||||
> d
|
||||
|
||||
You're at west end of long hall.
|
||||
|
||||
> drink
|
||||
|
||||
Drink what?
|
||||
|
||||
> e
|
||||
|
||||
You're at east end of long hall.
|
||||
|
||||
> e
|
||||
|
||||
You're at west end of Hall of Mists.
|
||||
|
||||
> n
|
||||
|
||||
You have crawled through a very low wide passage parallel to and north
|
||||
of the Hall of Mists.
|
||||
|
||||
You're on west bank of fissure.
|
||||
|
||||
A crystal bridge spans the fissure.
|
||||
|
||||
> w
|
||||
|
||||
You're at west end of Hall of Mists.
|
||||
|
||||
> w
|
||||
|
||||
You're at east end of long hall.
|
||||
|
||||
> n
|
||||
|
||||
You are at a crossover of a high n/s passage and a low e/w one.
|
||||
|
||||
> e
|
||||
|
||||
You're in the west side chamber.
|
||||
|
||||
> d
|
||||
|
||||
There is no way to go that direction.
|
||||
|
||||
You're in the west side chamber.
|
||||
|
||||
> e
|
||||
|
||||
You're in Hall of Mt King.
|
||||
|
||||
> u
|
||||
|
||||
There is a threatening little dwarf in the room with you!
|
||||
|
||||
You're in Hall of Mists.
|
||||
|
||||
Rough stone steps lead up the dome.
|
||||
|
||||
> throw axe
|
||||
|
||||
You killed a little dwarf.
|
||||
|
||||
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.
|
||||
|
||||
There is a little axe here.
|
||||
|
||||
Rough stone steps lead up the dome.
|
||||
|
||||
> d
|
||||
|
||||
You're in Hall of Mt King.
|
||||
|
||||
> n
|
||||
|
||||
You're in n/s passage above e/w passage.
|
||||
|
||||
> d
|
||||
|
||||
You are in a dirty broken passage. To the east is a crawl. To the
|
||||
west is a large passage. Above you is a hole to another passage.
|
||||
|
||||
> e
|
||||
|
||||
You are on the brink of a small clean climbable pit. A crawl leads
|
||||
west.
|
||||
|
||||
> d
|
||||
|
||||
You are in the bottom of a small pit with a little stream, which
|
||||
enters and exits through tiny slits.
|
||||
|
||||
> listen
|
||||
|
||||
The stream is gurgling placidly.
|
||||
|
||||
> u
|
||||
|
||||
You're at brink of small pit.
|
||||
|
||||
> w
|
||||
|
||||
You're in dirty passage.
|
||||
|
||||
> bedquilt
|
||||
|
||||
You're in Bedquilt.
|
||||
|
||||
> z
|
||||
|
||||
OK
|
||||
|
||||
> z
|
||||
|
||||
OK
|
||||
|
||||
> z
|
||||
|
||||
OK
|
||||
|
||||
> n
|
||||
|
||||
You have crawled around in some little holes and wound up back in the
|
||||
main passage.
|
||||
|
||||
You're in Bedquilt.
|
||||
|
||||
> n
|
||||
|
||||
You have crawled around in some little holes and wound up back in the
|
||||
main passage.
|
||||
|
||||
You're in Bedquilt.
|
||||
|
||||
> n
|
||||
|
||||
You have crawled around in some little holes and wound up back in the
|
||||
main passage.
|
||||
|
||||
You're in Bedquilt.
|
||||
|
||||
> n
|
||||
|
||||
You are in a secret canyon at a junction of three canyons, bearing
|
||||
north, south, and se. The north one is as tall as the other two
|
||||
combined.
|
||||
|
||||
> w
|
||||
|
||||
There is no way to go that direction.
|
||||
|
||||
You're at junction of three secret canyons.
|
||||
|
||||
> n
|
||||
|
||||
You're at a low window overlooking a huge pit, which extends up out of
|
||||
sight. A floor is indistinctly visible over 50 feet below. Traces of
|
||||
white mist cover the floor of the pit, becoming thicker to the left.
|
||||
Marks in the dust around the window would seem to indicate that
|
||||
someone has been here recently. Directly across the pit from you and
|
||||
25 feet away there is a similar window looking into a lighted room. A
|
||||
shadowy figure can be seen there peering back at you.
|
||||
|
||||
The shadowy figure seems to be trying to attract your attention.
|
||||
|
||||
> w
|
||||
|
||||
You're at junction of three secret canyons.
|
||||
|
||||
> s
|
||||
|
||||
You are in a secret n/s canyon above a sizable passage.
|
||||
|
||||
> s
|
||||
|
||||
A large stalactite extends from the roof and almost reaches the floor
|
||||
below. You could climb down it, and jump from it to the floor, but
|
||||
having done so you would be unable to reach it to climb back up.
|
||||
|
||||
> n
|
||||
|
||||
You are in a secret n/s canyon above a sizable passage.
|
||||
|
||||
> s
|
||||
|
||||
There are faint rustling noises from the darkness behind you.
|
||||
|
||||
You're at top of stalactite.
|
||||
|
||||
> n
|
||||
|
||||
You are in a secret n/s canyon above a sizable passage.
|
||||
|
||||
> n
|
||||
|
||||
You're at junction of three secret canyons.
|
||||
|
||||
> n
|
||||
|
||||
You're at window on pit.
|
||||
|
||||
The shadowy figure seems to be trying to attract your attention.
|
||||
|
||||
> jump
|
||||
|
||||
You are at the bottom of the pit with a broken neck.
|
||||
|
||||
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?
|
||||
|
||||
> y
|
||||
|
||||
Okay, now where did I put my orange smoke?.... >POOF!<
|
||||
Everything disappears in a dense cloud of orange smoke.
|
||||
|
||||
You're inside building.
|
||||
|
||||
There is a richly-carved ebony statuette here!
|
||||
|
||||
There is an emerald here the size of a plover's egg!
|
||||
|
||||
There is a jewel-encrusted trident here!
|
||||
|
||||
There is a platinum pyramid here, 8 inches on a side!
|
||||
|
||||
There are diamonds here!
|
||||
|
||||
There is an enormous ruby here!
|
||||
|
||||
There are bars of silver here!
|
||||
|
||||
There is a Persian rug spread out on the floor!
|
||||
|
||||
There is a large sparkling nugget of gold here!
|
||||
|
||||
A precious jade necklace has been dropped here!
|
||||
|
||||
There is precious jewelry here!
|
||||
|
||||
> out
|
||||
|
||||
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.
|
||||
|
||||
There is a shiny brass lamp nearby.
|
||||
|
||||
> take lamp
|
||||
|
||||
OK
|
||||
|
||||
> in
|
||||
|
||||
You're inside building.
|
||||
|
||||
There is a richly-carved ebony statuette here!
|
||||
|
||||
There is an emerald here the size of a plover's egg!
|
||||
|
||||
There is a jewel-encrusted trident here!
|
||||
|
||||
There is a platinum pyramid here, 8 inches on a side!
|
||||
|
||||
There are diamonds here!
|
||||
|
||||
There is an enormous ruby here!
|
||||
|
||||
There are bars of silver here!
|
||||
|
||||
There is a Persian rug spread out on the floor!
|
||||
|
||||
There is a large sparkling nugget of gold here!
|
||||
|
||||
A precious jade necklace has been dropped here!
|
||||
|
||||
There is precious jewelry here!
|
||||
|
||||
> downstream
|
||||
|
||||
The stream flows out through a pair of 1 foot diameter sewer pipes.
|
||||
It would be advisable to use the exit.
|
||||
|
||||
You are inside a building, a well house for a large spring.
|
||||
|
||||
There is a richly-carved ebony statuette here!
|
||||
|
||||
There is an emerald here the size of a plover's egg!
|
||||
|
||||
There is a jewel-encrusted trident here!
|
||||
|
||||
There is a platinum pyramid here, 8 inches on a side!
|
||||
|
||||
There are diamonds here!
|
||||
|
||||
There is an enormous ruby here!
|
||||
|
||||
There are bars of silver here!
|
||||
|
||||
There is a Persian rug spread out on the floor!
|
||||
|
||||
There is a large sparkling nugget of gold here!
|
||||
|
||||
A precious jade necklace has been dropped here!
|
||||
|
||||
There is precious jewelry here!
|
||||
|
||||
> plugh
|
||||
|
||||
>>Foof!<<
|
||||
|
||||
It is now pitch dark. If you proceed you will likely fall into a pit.
|
||||
|
||||
> on lamp
|
||||
|
||||
Your lamp is now on.
|
||||
|
||||
You are in a large room, with a passage to the south, a passage to the
|
||||
west, and a wall of broken rock to the east. There is a large "Y2" on
|
||||
a rock in the room's center.
|
||||
|
||||
> w
|
||||
|
||||
You're at a low window overlooking a huge pit, which extends up out of
|
||||
sight. A floor is indistinctly visible over 50 feet below. Traces of
|
||||
white mist cover the floor of the pit, becoming thicker to the right.
|
||||
Marks in the dust around the window would seem to indicate that
|
||||
someone has been here recently. Directly across the pit from you and
|
||||
25 feet away there is a similar window looking into a lighted room. A
|
||||
shadowy figure can be seen there peering back at you.
|
||||
|
||||
The shadowy figure seems to be trying to attract your attention.
|
||||
|
||||
> e
|
||||
|
||||
You're at "Y2".
|
||||
|
||||
> s
|
||||
|
||||
You're in n/s passage above e/w passage.
|
||||
|
||||
> s
|
||||
|
||||
You're in Hall of Mt King.
|
||||
|
||||
> e
|
||||
|
||||
You're in Hall of Mists.
|
||||
|
||||
There is a little axe here.
|
||||
|
||||
Rough stone steps lead up the dome.
|
||||
|
||||
> take axe
|
||||
|
||||
OK
|
||||
|
||||
> listen
|
||||
|
||||
The wind whistles coldly past your ears.
|
||||
|
||||
> score
|
||||
|
||||
You have garnered 207 out of a possible 430 points, using 413 turns.
|
||||
|
||||
> inven
|
||||
|
||||
You are currently holding the following:
|
||||
Brass lantern
|
||||
Dwarf's axe
|
||||
|
||||
> waste 2443
|
||||
|
||||
Game limit is now 27
|
||||
|
||||
You're in Hall of Mists.
|
||||
|
||||
Rough stone steps lead up the dome.
|
||||
|
||||
> z
|
||||
|
||||
Your lamp is getting dim, and you're out of spare batteries. You'd
|
||||
best start wrapping this up.
|
||||
|
||||
OK
|
||||
|
||||
|
||||
You scored 207 out of a possible 430, using 416 turns.
|
||||
You scored 223 out of a possible 430, using 303 turns.
|
||||
|
||||
You may now consider yourself a "Seasoned Adventurer".
|
||||
|
||||
To achieve the next higher rating, you need 44 more points.
|
||||
To achieve the next higher rating, you need 28 more points.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Test many nonlethal failure conditions
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# See comments in this log
|
||||
n
|
||||
seed 1838473132
|
||||
|
@ -310,141 +312,5 @@ sw
|
|||
free bear
|
||||
take bear
|
||||
sw
|
||||
yes
|
||||
inven
|
||||
out
|
||||
take lamp
|
||||
light lamp
|
||||
in
|
||||
plugh
|
||||
s
|
||||
d
|
||||
bedquilt
|
||||
n
|
||||
sw
|
||||
up
|
||||
over
|
||||
feed bear
|
||||
attack bear
|
||||
# We'll need these when the game times out
|
||||
take coins
|
||||
take axe
|
||||
# Back to fissure to vanish the bridge and then test OVER
|
||||
sw
|
||||
d
|
||||
se
|
||||
se
|
||||
ne
|
||||
e
|
||||
up
|
||||
e
|
||||
up
|
||||
s
|
||||
up
|
||||
w
|
||||
take rod
|
||||
wave rod
|
||||
across
|
||||
# Next, buy batteries but don't take them.
|
||||
wave rod
|
||||
across
|
||||
w
|
||||
w
|
||||
w
|
||||
s
|
||||
s
|
||||
sw
|
||||
se
|
||||
s
|
||||
s
|
||||
drop coins
|
||||
look
|
||||
n
|
||||
z
|
||||
z
|
||||
z
|
||||
z
|
||||
# Battery warning happens here.
|
||||
s
|
||||
take batteries
|
||||
# We now have 2500 more turns of life. Into the maze...
|
||||
n
|
||||
# Show that trying to back up in the maze fails
|
||||
back
|
||||
n
|
||||
nw
|
||||
d
|
||||
# Out of maze. Drink where nothing is eligible.
|
||||
drink
|
||||
e
|
||||
e
|
||||
# PARALLEL1 coverage
|
||||
n
|
||||
# If we go to hall of mists we'll meet a killer dwarf with the drop on us
|
||||
#e
|
||||
#e
|
||||
w
|
||||
w
|
||||
n
|
||||
e
|
||||
d
|
||||
e
|
||||
u
|
||||
throw axe
|
||||
d
|
||||
n
|
||||
d
|
||||
# Coverage of LOC_SMALLPIT and LOC_SMALLPITBRINK
|
||||
e
|
||||
d
|
||||
listen
|
||||
u
|
||||
w
|
||||
# Coverage of LOC_THREEJUNCTION, LOC_WINDOW2, LOC_SECRET2, LOC_TOPSTALACTITE,
|
||||
# LOC_NECKBROKE. Only accessible via stalactite from big maze or by random
|
||||
# exit from Bedquilt.
|
||||
bedquilt
|
||||
z
|
||||
z
|
||||
z
|
||||
n
|
||||
n
|
||||
n
|
||||
n
|
||||
# In secret canyon
|
||||
w
|
||||
n
|
||||
w
|
||||
s
|
||||
# LOC_TOPSTALACTITE
|
||||
s
|
||||
n
|
||||
s
|
||||
n
|
||||
n
|
||||
n
|
||||
jump
|
||||
y
|
||||
# Reincarnation, cover LOC_SEWER
|
||||
out
|
||||
take lamp
|
||||
in
|
||||
downstream
|
||||
plugh
|
||||
on lamp
|
||||
# Cover WINDOW1
|
||||
w
|
||||
e
|
||||
# Retrieve axe
|
||||
s
|
||||
s
|
||||
e
|
||||
take axe
|
||||
listen
|
||||
# At Hall of Mists
|
||||
score
|
||||
inven
|
||||
# Timewarp forward to test exhaustion of replacement batteries
|
||||
waste 2443
|
||||
z
|
||||
# MISSING_BATTERIES is uttered
|
||||
quit
|
||||
|
|
|
@ -2930,7 +2930,7 @@ OK
|
|||
|
||||
> take bird
|
||||
|
||||
OK
|
||||
You are already carrying it!
|
||||
|
||||
> free bird
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Attempt to kill snake with bird in the endgame
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1838473132
|
||||
in
|
||||
|
|
|
@ -456,7 +456,7 @@ attack. He seems almost amused by your puny effort.
|
|||
|
||||
OK
|
||||
|
||||
|
||||
>
|
||||
You scored 61 out of a possible 430, using 81 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
## Verify that the bird is weightless in inventory
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Checks fix for GitLab issue #40
|
||||
#NOCOMPARE Bird was not weightless in cage in advent430 so this test is invalid.
|
||||
n
|
||||
#seed 687800971
|
||||
seed 976729036
|
||||
|
|
|
@ -984,7 +984,7 @@ There is a huge beanstalk growing out of the west pit up to the hole.
|
|||
You are at the bottom of the eastern pit in the Twopit Room. There is
|
||||
a small pool of oil in one corner of the pit.
|
||||
|
||||
> g oil
|
||||
> get oil
|
||||
|
||||
Your bottle is now full of oil.
|
||||
|
||||
|
@ -1351,7 +1351,7 @@ You're in Giant Room.
|
|||
|
||||
There is a large nest here, full of golden eggs!
|
||||
|
||||
> g
|
||||
> get
|
||||
|
||||
OK
|
||||
|
||||
|
@ -1479,7 +1479,7 @@ You're in Chamber of Boulders.
|
|||
|
||||
There are rare spices here!
|
||||
|
||||
|
||||
>
|
||||
You scored 119 out of a possible 430, using 238 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Coverage of LOC_BOULDERS2.short
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1838473132
|
||||
in
|
||||
|
@ -156,7 +158,7 @@ u
|
|||
drop appen
|
||||
e
|
||||
d
|
||||
g oil
|
||||
get oil
|
||||
u
|
||||
w
|
||||
d
|
||||
|
@ -220,7 +222,7 @@ w
|
|||
d
|
||||
climb
|
||||
w
|
||||
g
|
||||
get
|
||||
n
|
||||
take bottl
|
||||
n
|
||||
|
@ -239,4 +241,4 @@ fork
|
|||
ne
|
||||
e
|
||||
out
|
||||
e
|
||||
e
|
||||
|
|
|
@ -2934,14 +2934,14 @@ Huh?
|
|||
|
||||
Huh?
|
||||
|
||||
> unlock oyster
|
||||
|
||||
I advise you to put down the oyster before opening it. >WRENCH!<
|
||||
|
||||
> drop oyster
|
||||
|
||||
OK
|
||||
|
||||
> unlock oyster
|
||||
|
||||
You don't have anything strong enough to open the oyster.
|
||||
|
||||
> take oyster
|
||||
|
||||
OK
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Break the mirror in endgame and die
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1838473132
|
||||
in
|
||||
|
@ -467,14 +469,16 @@ e
|
|||
e
|
||||
cave
|
||||
e
|
||||
# Everything to here is from endgame428
|
||||
# Everything to here is from endgame428,
|
||||
# except we drop the oyster before trying
|
||||
# to unlock it rather than after.
|
||||
attack
|
||||
take oyster
|
||||
find oyster
|
||||
lock
|
||||
lock oyster
|
||||
unlock oyster
|
||||
drop oyster
|
||||
unlock oyster
|
||||
take oyster
|
||||
read oyster
|
||||
y
|
||||
|
@ -482,4 +486,3 @@ sw
|
|||
attack bird
|
||||
find bird
|
||||
break mirror
|
||||
|
||||
|
|
|
@ -51,10 +51,6 @@ has scrawled, "MAGIC WORD XYZZY".
|
|||
|
||||
A three foot black rod with a rusty star on an end lies nearby.
|
||||
|
||||
> eat grate
|
||||
|
||||
I see no grate here.
|
||||
|
||||
> w
|
||||
|
||||
You are in an awkward sloping east/west canyon.
|
||||
|
@ -75,8 +71,8 @@ You can catch the bird, but you cannot carry it.
|
|||
|
||||
The little bird is now dead. Its body disappears.
|
||||
|
||||
|
||||
You scored 32 out of a possible 430, using 10 turns.
|
||||
>
|
||||
You scored 32 out of a possible 430, using 9 turns.
|
||||
|
||||
You are obviously a rank amateur. Better luck next time.
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Try to carry bird without cage, then kill bird
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1071883378
|
||||
in
|
||||
|
@ -6,9 +8,8 @@ take lamp
|
|||
rub lamp
|
||||
xyzzy
|
||||
on
|
||||
eat grate
|
||||
w
|
||||
w
|
||||
take bird
|
||||
# test intransitive attack on bird
|
||||
attack
|
||||
attack
|
||||
|
|
|
@ -327,7 +327,7 @@ The bird eyes you suspiciously and flutters away. A moment later you
|
|||
feel something wet land on your head, but upon looking up you can see
|
||||
no sign of the culprit.
|
||||
|
||||
|
||||
>
|
||||
You scored 113 out of a possible 430, using 57 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Try to carry the bird after freeing it instead of listening
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1495951709
|
||||
attack
|
||||
|
|
|
@ -13,15 +13,15 @@ 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.
|
||||
|
||||
|
||||
>
|
||||
Now let's see you do it without suspending in mid-Adventure.
|
||||
|
||||
You scored 9031 out of a possible 430, using 0 turns.
|
||||
|
||||
'Adventuredom stands in awe -- you have now joined the ranks of the
|
||||
Adventuredom stands in awe -- you have now joined the ranks of the
|
||||
W O R L D C H A M P I O N A D V E N T U R E R S !
|
||||
It may interest you to know that the Dungeon-Master himself has, to
|
||||
my knowledge, never achieved this threshold in fewer than 330 turns.'
|
||||
my knowledge, never achieved this threshold in fewer than 330 turns.
|
||||
|
||||
To achieve the next higher rating would be a neat trick!
|
||||
Congratulations!!
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
## Resume from absurd save file with numdie = -900
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#NOCOMPARE Can't compare to advent430 due to version skew
|
||||
n
|
||||
resume
|
||||
cheat_numdie.adv
|
||||
|
|
|
@ -13,7 +13,7 @@ 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.
|
||||
|
||||
|
||||
>
|
||||
Now let's see you do it without suspending in mid-Adventure.
|
||||
|
||||
You scored 10031 out of a possible 430, using 0 turns.
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
## Resume from absurd save file with numdie = -1000
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# generating "off my scale" score threshold message
|
||||
#NOCOMPARE Can't compare to advent430 due to version skew
|
||||
n
|
||||
resume
|
||||
cheat_numdie1000.adv
|
||||
|
|
4
tests/compare
Executable file
4
tests/compare
Executable file
|
@ -0,0 +1,4 @@
|
|||
#! /bin/sh
|
||||
# Display diff for an individual test
|
||||
test=$1
|
||||
../advent <${test}.log | ./tapdiffer "${test}" "${test}.chk"
|
|
@ -1,4 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
"""
|
||||
This is the open-adventure dungeon text coverage report generator. It
|
||||
consumes a YAML description of the dungeon and determines whether the
|
||||
|
@ -7,12 +9,13 @@ various strings contained are present within the test check files.
|
|||
The default HTML output is appropriate for use with Gitlab CI.
|
||||
You can override it with a command-line argument.
|
||||
|
||||
The DANGLING list is for actions that should be considered always found
|
||||
even if the checkfile search doesn't find them. Typically this will because
|
||||
they emit a templated message that can't be regression-tested by equality.
|
||||
The DANGLING lists are for actions and messages that should be
|
||||
considered always found even if the checkfile search doesn't find them.
|
||||
Typically this will because an action emit a templated message that
|
||||
can't be regression-tested by equality.
|
||||
"""
|
||||
|
||||
# pylint: disable=consider-using-f-string
|
||||
# pylint: disable=consider-using-f-string,line-too-long,invalid-name,missing-function-docstring,redefined-outer-name
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -23,44 +26,48 @@ TEST_DIR = "."
|
|||
YAML_PATH = "../adventure.yaml"
|
||||
HTML_TEMPLATE_PATH = "../templates/coverage_dungeon.html.tpl"
|
||||
DEFAULT_HTML_OUTPUT_PATH = "../coverage/adventure.yaml.html"
|
||||
DANGLING = ["ACT_VERSION"]
|
||||
DANGLING_ACTIONS = ["ACT_VERSION"]
|
||||
DANGLING_MESSAGES = ["SAVERESUME_DISABLED"]
|
||||
|
||||
STDOUT_REPORT_CATEGORY = " {name:.<19}: {percent:5.1f}% covered ({covered} of {total})\n"
|
||||
STDOUT_REPORT_CATEGORY = (
|
||||
" {name:.<19}: {percent:5.1f}% covered ({covered} of {total})\n"
|
||||
)
|
||||
|
||||
HTML_SUMMARY_ROW = '''
|
||||
HTML_SUMMARY_ROW = """
|
||||
<tr>
|
||||
<td class="headerItem"><a href="#{name}">{name}:</a></td>
|
||||
<td class="headerCovTableEntry">{total}</td>
|
||||
<td class="headerCovTableEntry">{covered}</td>
|
||||
<td class="headerCovTableEntry">{percent:.1f}%</td>
|
||||
</tr>
|
||||
'''
|
||||
"""
|
||||
|
||||
HTML_CATEGORY_SECTION = '''
|
||||
HTML_CATEGORY_SECTION = """
|
||||
<tr id="{id}"></tr>
|
||||
{rows}
|
||||
<tr>
|
||||
<td> </td>
|
||||
</tr>
|
||||
'''
|
||||
"""
|
||||
|
||||
HTML_CATEGORY_HEADER = '''
|
||||
HTML_CATEGORY_HEADER = """
|
||||
<tr>
|
||||
<td class="tableHead" width="60%" colspan="{colspan}">{label}</td>
|
||||
{cells}
|
||||
</tr>
|
||||
'''
|
||||
"""
|
||||
|
||||
HTML_CATEGORY_HEADER_CELL = '<td class="tableHead" width="15%">{}</td>\n'
|
||||
|
||||
HTML_CATEGORY_COVERAGE_CELL = '<td class="{}"> </td>\n'
|
||||
|
||||
HTML_CATEGORY_ROW = '''
|
||||
HTML_CATEGORY_ROW = """
|
||||
<tr>
|
||||
<td class="coverFile" colspan="{colspan}">{id}</td>
|
||||
{cells}
|
||||
</tr>
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
def search(needle, haystack):
|
||||
# Search for needle in haystack, first escaping needle for regex, then
|
||||
|
@ -71,16 +78,19 @@ def search(needle, haystack):
|
|||
# if needle is empty, assume we're going to find an empty string
|
||||
return True
|
||||
|
||||
needle_san = re.escape(needle) \
|
||||
.replace("\\n", "\n") \
|
||||
.replace("\\t", "\t") \
|
||||
.replace("%S", ".*") \
|
||||
.replace("%s", ".*") \
|
||||
.replace("%d", ".*") \
|
||||
.replace("%V", ".*")
|
||||
needle_san = (
|
||||
re.escape(needle)
|
||||
.replace("\\n", "\n")
|
||||
.replace("\\t", "\t")
|
||||
.replace("%S", ".*")
|
||||
.replace("%s", ".*")
|
||||
.replace("%d", ".*")
|
||||
.replace("%V", ".*")
|
||||
)
|
||||
|
||||
return re.search(needle_san, haystack)
|
||||
|
||||
|
||||
def obj_coverage(objects, text, report):
|
||||
# objects have multiple descriptions based on state
|
||||
for _, objouter in enumerate(objects):
|
||||
|
@ -89,19 +99,20 @@ def obj_coverage(objects, text, report):
|
|||
for j, desc in enumerate(obj["descriptions"]):
|
||||
name = "{}[{}]".format(obj_name, j)
|
||||
if name not in report["messages"]:
|
||||
report["messages"][name] = {"covered" : False}
|
||||
report["messages"][name] = {"covered": False}
|
||||
report["total"] += 1
|
||||
if not report["messages"][name]["covered"] and search(desc, text):
|
||||
report["messages"][name]["covered"] = True
|
||||
report["covered"] += 1
|
||||
|
||||
|
||||
def loc_coverage(locations, text, report):
|
||||
# locations have a long and a short description, that each have to
|
||||
# be checked seperately
|
||||
# be checked separately
|
||||
for name, loc in locations:
|
||||
desc = loc["description"]
|
||||
if name not in report["messages"]:
|
||||
report["messages"][name] = {"long" : False, "short": False}
|
||||
report["messages"][name] = {"long": False, "short": False}
|
||||
report["total"] += 2
|
||||
if not report["messages"][name]["long"] and search(desc["long"], text):
|
||||
report["messages"][name]["long"] = True
|
||||
|
@ -110,6 +121,7 @@ def loc_coverage(locations, text, report):
|
|||
report["messages"][name]["short"] = True
|
||||
report["covered"] += 1
|
||||
|
||||
|
||||
def hint_coverage(obituaries, text, report):
|
||||
# hints have a "question" where the hint is offered, followed
|
||||
# by the actual hint if the player requests it
|
||||
|
@ -117,7 +129,7 @@ def hint_coverage(obituaries, text, report):
|
|||
hint = hintouter["hint"]
|
||||
name = hint["name"]
|
||||
if name not in report["messages"]:
|
||||
report["messages"][name] = {"question" : False, "hint": False}
|
||||
report["messages"][name] = {"question": False, "hint": False}
|
||||
report["total"] += 2
|
||||
if not report["messages"][name]["question"] and search(hint["question"], text):
|
||||
report["messages"][name]["question"] = True
|
||||
|
@ -126,61 +138,72 @@ def hint_coverage(obituaries, text, report):
|
|||
report["messages"][name]["hint"] = True
|
||||
report["covered"] += 1
|
||||
|
||||
|
||||
def obit_coverage(obituaries, text, report):
|
||||
# obituaries have a "query" where it asks the player for a resurrection,
|
||||
# followed by a snarky comment if the player says yes
|
||||
for name, obit in enumerate(obituaries):
|
||||
if name not in report["messages"]:
|
||||
report["messages"][name] = {"query" : False, "yes_response": False}
|
||||
report["messages"][name] = {"query": False, "yes_response": False}
|
||||
report["total"] += 2
|
||||
if not report["messages"][name]["query"] and search(obit["query"], text):
|
||||
report["messages"][name]["query"] = True
|
||||
report["covered"] += 1
|
||||
if not report["messages"][name]["yes_response"] and search(obit["yes_response"], text):
|
||||
if not report["messages"][name]["yes_response"] and search(
|
||||
obit["yes_response"], text
|
||||
):
|
||||
report["messages"][name]["yes_response"] = True
|
||||
report["covered"] += 1
|
||||
|
||||
|
||||
def threshold_coverage(classes, text, report):
|
||||
# works for class thresholds and turn threshold, which have a "message"
|
||||
# property
|
||||
for name, item in enumerate(classes):
|
||||
if name not in report["messages"]:
|
||||
report["messages"][name] = {"covered" : "False"}
|
||||
report["messages"][name] = {"covered": False}
|
||||
report["total"] += 1
|
||||
if not report["messages"][name]["covered"] and search(item["message"], text):
|
||||
report["messages"][name]["covered"] = True
|
||||
report["covered"] += 1
|
||||
|
||||
|
||||
def arb_coverage(arb_msgs, text, report):
|
||||
for name, message in arb_msgs:
|
||||
if name not in report["messages"]:
|
||||
report["messages"][name] = {"covered" : False}
|
||||
report["messages"][name] = {"covered": False}
|
||||
report["total"] += 1
|
||||
if not report["messages"][name]["covered"] and search(message, text):
|
||||
if not report["messages"][name]["covered"] and (
|
||||
search(message, text) or name in DANGLING_MESSAGES
|
||||
):
|
||||
report["messages"][name]["covered"] = True
|
||||
report["covered"] += 1
|
||||
|
||||
|
||||
def actions_coverage(items, text, report):
|
||||
# works for actions
|
||||
for name, item in items:
|
||||
if name not in report["messages"]:
|
||||
report["messages"][name] = {"covered" : False}
|
||||
report["messages"][name] = {"covered": False}
|
||||
report["total"] += 1
|
||||
if not report["messages"][name]["covered"] and (search(item["message"], text) or name in DANGLING):
|
||||
if not report["messages"][name]["covered"] and (
|
||||
search(item["message"], text) or name in DANGLING_ACTIONS
|
||||
):
|
||||
report["messages"][name]["covered"] = True
|
||||
report["covered"] += 1
|
||||
|
||||
|
||||
def coverage_report(db, check_file_contents):
|
||||
# Create report for each catagory, including total items, number of items
|
||||
# Create report for each category, including total items, number of items
|
||||
# covered, and a list of the covered messages
|
||||
report = {}
|
||||
for name in db.keys():
|
||||
# initialize each catagory
|
||||
report[name] = {
|
||||
"name" : name, # convenience for string formatting
|
||||
"total" : 0,
|
||||
"covered" : 0,
|
||||
"messages" : {}
|
||||
"name": name, # convenience for string formatting
|
||||
"total": 0,
|
||||
"covered": 0,
|
||||
"messages": {},
|
||||
}
|
||||
|
||||
# search for each message in every test check file
|
||||
|
@ -196,20 +219,21 @@ def coverage_report(db, check_file_contents):
|
|||
|
||||
return report
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# load DB
|
||||
try:
|
||||
with open(YAML_PATH, "r", encoding='ascii', errors='surrogateescape') as f:
|
||||
with open(YAML_PATH, "r", encoding="ascii", errors="surrogateescape") as f:
|
||||
db = yaml.safe_load(f)
|
||||
except IOError as e:
|
||||
print('ERROR: could not load %s (%s)' % (YAML_PATH, e.strerror))
|
||||
print("ERROR: could not load %s (%s)" % (YAML_PATH, e.strerror))
|
||||
sys.exit(-1)
|
||||
|
||||
# get contents of all the check files
|
||||
check_file_contents = []
|
||||
for filename in os.listdir(TEST_DIR):
|
||||
if filename.endswith(".chk"):
|
||||
with open(filename, "r", encoding='ascii', errors='surrogateescape') as f:
|
||||
with open(filename, "r", encoding="ascii", errors="surrogateescape") as f:
|
||||
check_file_contents.append(f.read())
|
||||
|
||||
# run coverage analysis report on dungeon database
|
||||
|
@ -232,14 +256,20 @@ if __name__ == "__main__":
|
|||
colspan = 10 - len(cat_keys)
|
||||
for key in cat_keys:
|
||||
headers_html += HTML_CATEGORY_HEADER_CELL.format(key)
|
||||
category_html = HTML_CATEGORY_HEADER.format(colspan=colspan, label=category["name"], cells=headers_html)
|
||||
category_html = HTML_CATEGORY_HEADER.format(
|
||||
colspan=colspan, label=category["name"], cells=headers_html
|
||||
)
|
||||
|
||||
# render message coverage row
|
||||
for message_id, covered in cat_messages:
|
||||
category_html_row = ""
|
||||
for key, value in covered.items():
|
||||
category_html_row += HTML_CATEGORY_COVERAGE_CELL.format("uncovered" if not value else "covered")
|
||||
category_html += HTML_CATEGORY_ROW.format(id=message_id,colspan=colspan, cells=category_html_row)
|
||||
category_html_row += HTML_CATEGORY_COVERAGE_CELL.format(
|
||||
"uncovered" if not value else "covered"
|
||||
)
|
||||
category_html += HTML_CATEGORY_ROW.format(
|
||||
id=message_id, colspan=colspan, cells=category_html_row
|
||||
)
|
||||
categories_html += HTML_CATEGORY_SECTION.format(id=name, rows=category_html)
|
||||
|
||||
# render category summaries
|
||||
|
@ -256,16 +286,22 @@ if __name__ == "__main__":
|
|||
|
||||
# render HTML report
|
||||
try:
|
||||
with open(HTML_TEMPLATE_PATH, "r", encoding='ascii', errors='surrogateescape') as f:
|
||||
with open(
|
||||
HTML_TEMPLATE_PATH, "r", encoding="ascii", errors="surrogateescape"
|
||||
) as f:
|
||||
# read in HTML template
|
||||
html_template = f.read()
|
||||
except IOError as e:
|
||||
print('ERROR: reading HTML report template failed ({})'.format(e.strerror))
|
||||
print("ERROR: reading HTML report template failed ({})".format(e.strerror))
|
||||
sys.exit(-1)
|
||||
|
||||
# parse template with report and write it out
|
||||
try:
|
||||
with open(html_output_path, "w", encoding='ascii', errors='surrogateescape') as f:
|
||||
f.write(html_template.format(categories=categories_html, summary=summary_html))
|
||||
with open(
|
||||
html_output_path, "w", encoding="ascii", errors="surrogateescape"
|
||||
) as f:
|
||||
f.write(
|
||||
html_template.format(categories=categories_html, summary=summary_html)
|
||||
)
|
||||
except IOError as e:
|
||||
print('ERROR: writing HTML report failed ({})'.format(e.strerror))
|
||||
print("ERROR: writing HTML report failed ({})".format(e.strerror))
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Jump into a pit and die, refuse reincarnation
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1495774850
|
||||
in
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Last-minute defeat, with lava. Also tests vase drop before pillow.
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Based on walkthrough at http://www.ecsoftwareconsulting.com/node/56
|
||||
n
|
||||
seed 1838473132
|
||||
|
|
|
@ -166,7 +166,7 @@ The dome is unclimbable.
|
|||
|
||||
You're in Hall of Mists.
|
||||
|
||||
|
||||
>
|
||||
You scored 63 out of a possible 430, using 24 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Take nugget and fail to climb to the dome
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1838473132
|
||||
in
|
||||
|
|
|
@ -246,7 +246,7 @@ There is a Persian rug spread out on the floor!
|
|||
|
||||
The blood-specked body of a huge green dead dragon lies to one side.
|
||||
|
||||
|
||||
>
|
||||
You scored 65 out of a possible 430, using 32 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Check that dead dragon actually moves its location (fuzzed)
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 18084731
|
||||
in
|
||||
|
|
|
@ -150,7 +150,7 @@ A huge green fierce snake bars the way!
|
|||
|
||||
There's nothing here it wants to eat (except perhaps you).
|
||||
|
||||
|
||||
>
|
||||
You scored 59 out of a possible 430, using 25 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Try to carry the bird after freeing it instead of listening
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1495951709
|
||||
in
|
||||
|
@ -25,4 +27,4 @@ take bird
|
|||
drop cage
|
||||
d
|
||||
d
|
||||
feed snake
|
||||
feed snake
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Speak a magic word at an inopportune time and drown.
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Based on walkthrough at http://www.ecsoftwareconsulting.com/node/56
|
||||
n
|
||||
seed 1838473132
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## In which the dwarf kills you
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1494912171
|
||||
in
|
||||
|
|
|
@ -58,3 +58,12 @@ 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
|
||||
to try to reincarnate you?
|
||||
|
||||
> n
|
||||
|
||||
OK
|
||||
|
||||
You scored 51 out of a possible 430, using 7 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
||||
To achieve the next higher rating, you need 70 more points.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Check that dwarf spawns in alternative location (fuzzed)
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 383847
|
||||
in
|
||||
|
@ -8,3 +10,4 @@ w
|
|||
w
|
||||
d
|
||||
d
|
||||
n
|
||||
|
|
|
@ -1167,7 +1167,7 @@ You're in Giant Room.
|
|||
|
||||
There is a large nest here, full of golden eggs!
|
||||
|
||||
> g egg
|
||||
> take egg
|
||||
|
||||
OK
|
||||
|
||||
|
@ -1223,7 +1223,7 @@ Wicker cage
|
|||
Black rod
|
||||
Small bottle
|
||||
|
||||
|
||||
>
|
||||
You scored 77 out of a possible 430, using 190 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Be done with Giant Room and eggs (fuzzed)
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1838473132
|
||||
in
|
||||
|
@ -184,11 +186,11 @@ w
|
|||
d
|
||||
climb
|
||||
w
|
||||
g egg
|
||||
take egg
|
||||
n
|
||||
fee
|
||||
fie
|
||||
foe
|
||||
foo
|
||||
look
|
||||
inven
|
||||
inven
|
||||
|
|
|
@ -399,7 +399,7 @@ the west wall is scrawled the inscription, "FEE FIE FOE FOO" [sic].
|
|||
|
||||
There is a large nest here, full of golden eggs!
|
||||
|
||||
> g
|
||||
> take
|
||||
|
||||
OK
|
||||
|
||||
|
@ -435,7 +435,7 @@ There is a large nest here, full of golden eggs!
|
|||
|
||||
The passage here is blocked by a recent cave-in.
|
||||
|
||||
|
||||
>
|
||||
You scored 67 out of a possible 430, using 66 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Vanishing eggs in Giant Room (fuzzed)
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1838473132
|
||||
in
|
||||
|
@ -59,7 +61,7 @@ d
|
|||
water plant
|
||||
climb
|
||||
w
|
||||
g
|
||||
take
|
||||
n
|
||||
fee
|
||||
fie
|
||||
|
@ -67,4 +69,4 @@ foe
|
|||
foo
|
||||
# go south, east to arrive at LOC_CAVEIN for coverage
|
||||
s
|
||||
e
|
||||
e
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## 428-point walkthrough
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Based on walkthrough at http://www.ecsoftwareconsulting.com/node/56
|
||||
n
|
||||
seed 1838473132
|
||||
|
|
|
@ -2435,11 +2435,12 @@ OK
|
|||
|
||||
You are currently holding the following:
|
||||
Wicker cage
|
||||
Little bird in cage
|
||||
Small bottle
|
||||
|
||||
> get bird
|
||||
|
||||
OK
|
||||
You are already carrying it!
|
||||
|
||||
> inven
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
### Check that water is unavailable in endgame
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Addresses GitLab issue #55: in endgame, some object starting states are incorrect
|
||||
#NOCOMPARE Bird was not weightless in cage in advent430, this test depends on that.
|
||||
no
|
||||
seed 11247848
|
||||
no
|
||||
|
|
|
@ -436,7 +436,7 @@ OK
|
|||
|
||||
OK
|
||||
|
||||
|
||||
>
|
||||
You scored 59 out of a possible 430, using 93 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Fail to get maze hint by being empty-handed (fuzzed)
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 25508795
|
||||
in
|
||||
|
|
|
@ -1638,7 +1638,7 @@ OK
|
|||
|
||||
OK
|
||||
|
||||
|
||||
>
|
||||
You scored 77 out of a possible 430, using 263 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
## Qualify for ogre hint but fail due to dwarves dead (fuzzed)
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#NOCOMPARE Fails due uninteresting difference in whitespace process.
|
||||
n
|
||||
seed 1838473132
|
||||
in
|
||||
|
|
|
@ -394,7 +394,7 @@ You are in a large chamber with passages to the west and north.
|
|||
|
||||
A formidable ogre bars the northern exit.
|
||||
|
||||
|
||||
>
|
||||
You scored 63 out of a possible 430, using 56 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Qualify for ogre hint but fail due to nearby dwarf (fuzzed)
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 25508795
|
||||
in
|
||||
|
|
|
@ -109,7 +109,7 @@ OK
|
|||
|
||||
OK
|
||||
|
||||
|
||||
>
|
||||
You scored 32 out of a possible 430, using 25 turns.
|
||||
|
||||
You are obviously a rank amateur. Better luck next time.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Fail getting wood hint by finding appendage (fuzzed)
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
n
|
||||
z
|
||||
|
@ -24,4 +26,4 @@ z
|
|||
z
|
||||
z
|
||||
z
|
||||
z
|
||||
z
|
||||
|
|
|
@ -57,7 +57,7 @@ You can't fill that.
|
|||
|
||||
Your bottle is already full.
|
||||
|
||||
|
||||
>
|
||||
You scored 32 out of a possible 430, using 10 turns.
|
||||
|
||||
You are obviously a rank amateur. Better luck next time.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Attempt to fill lamp, attempt to fill bottle with no source
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
in
|
||||
carry lamp
|
||||
|
|
|
@ -1463,7 +1463,7 @@ A small velvet pillow lies on the ground.
|
|||
|
||||
You can't be serious!
|
||||
|
||||
|
||||
>
|
||||
You scored 191 out of a possible 430, using 241 turns.
|
||||
|
||||
You may now consider yourself a "Seasoned Adventurer".
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Fill the vase
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# Arthur O'Dwyer <arthur.j.odwyer@gmail.com> writes:
|
||||
#
|
||||
# (4) Lastly, here's a test case for you! Go get the VASE; then get the
|
||||
|
|
|
@ -2039,8 +2039,6 @@ There is a Persian rug here, hovering in mid-air!
|
|||
A brilliant blue star sapphire is here!
|
||||
|
||||
>
|
||||
|
||||
|
||||
You scored 257 out of a possible 430, using 337 turns.
|
||||
|
||||
You have reached "Junior Master" status.
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
## Test fix for issue #51: rug flying is broken
|
||||
## Test fix for issue 51: rug flying is broken
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#NOCOMPARE Behavior differs due to a bug fix.
|
||||
n
|
||||
seed 1838473132
|
||||
in
|
||||
|
@ -340,4 +343,3 @@ drop emerald
|
|||
fly rug
|
||||
fly rug
|
||||
fly rug
|
||||
|
||||
|
|
|
@ -796,7 +796,7 @@ There is food here.
|
|||
|
||||
>
|
||||
|
||||
|
||||
>
|
||||
You scored 61 out of a possible 430, using 121 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Coverage of LOC_FOOTSLIP
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1838473132
|
||||
in
|
||||
|
|
|
@ -2145,7 +2145,7 @@ There is a ruby resting in a small cavity in the rock!
|
|||
|
||||
There is a Persian rug spread out on the floor!
|
||||
|
||||
|
||||
>
|
||||
You scored 271 out of a possible 430, using 365 turns.
|
||||
|
||||
You have reached "Junior Master" status.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
## Observe amber, ruby, sapphire after state change
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
n
|
||||
seed 1635997320
|
||||
in
|
||||
|
|
|
@ -2527,7 +2527,7 @@ best start wrapping this up.
|
|||
|
||||
OK
|
||||
|
||||
|
||||
>
|
||||
You scored 207 out of a possible 430, using 413 turns.
|
||||
|
||||
You may now consider yourself a "Seasoned Adventurer".
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
## Test many nonlethal failure conditions
|
||||
# SPDX-FileCopyrightText: Copyright Eric S. Raymond <esr@thyrsus.com>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
# This variant elicits the prompt to go back for batteries
|
||||
# See comments in this log
|
||||
#NOCOMPARE Relies on "waste"
|
||||
n
|
||||
seed 1838473132
|
||||
in
|
||||
|
|
|
@ -150,7 +150,7 @@ There is a way to explore that region without having to worry about
|
|||
falling into a pit. None of the objects available is immediately
|
||||
useful in discovering the secret.
|
||||
|
||||
|
||||
>
|
||||
You scored 54 out of a possible 430, using 27 turns.
|
||||
|
||||
Your score qualifies you as a novice class adventurer.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue