release winning entries of IOCCC28 under the year 2024

Congratulations to:

* 2024/anders - Prize in sound coding - layered music generator
* 2024/burton - Prize in pentagrammatology - wordle hard mode solver
* 2024/cable1 - Prize in bot talk - LLM inference engine
* 2024/cable2 - Prize in murky waters - salmon recipe
* 2024/carlini - Prize in perfect timing - Intel 4004 emulation
* 2024/codemeow - Prize in tray planting - bonsai generator
* 2024/endoh1 - Prize in patient pointillism - slow ray tracer in CPP
* 2024/endoh2 - Prize in solid body physics - rotating rigid body simulation
* 2024/ferguson1 - Prize in diabolical logistics - Oregon trail pact with the devil
* 2024/ferguson2 - Prize in yil-tas - Navajo code talker translator
* 2024/howe - Nice accent eh-ward - mini vi
* 2024/kramer - Prize in linguistic arithmetic - natural language numerical calculator
* 2024/kurdyukov1 - Prize in phased periodicity - phase of the Moon
* 2024/kurdyukov2 - Prize in art restoration - JPEG artifact removal
* 2024/kurdyukov3 - Prize in virtual quietus - gaming VM and doom
* 2024/kurdyukov4 - Prize in embiggening - encode without literals
* 2024/maffiodo - Prize in creative interpretation - Jav\*script interpreter
* 2024/mills - Prize in ℤ₃ - Infocom v3 story interpreter
* 2024/stedolan - Best one liner - icon from MD5
* 2024/straadt - Prize in imitative rebooting - OpenRISC CPU and Linux
* 2024/tmarrec - Prize in cyclonic coding - 3D tornado simulation
* 2024/tompng - Prize in quasi bijection - vortex encryptor decryptor
* 2024/weaver - Sur prize - rickroll

Ran `make update` to perform the above.
 	new file:   2024/burton/.entry.json
This commit is contained in:
Landon Curt Noll committed 2025-08-02 14:43:07 -07:00
1 parent 10512e6c79
commit 09750f8899
443 files changed
+182938 -231

No files matched your search

+361
View File
@@ -0,0 +1,361 @@
{
"no_comment" : "mandatory comment: because comments were removed from the original JSON spec",
"entry_JSON_format_version" : "1.2 2024-09-25",
"award" : "Prize in pentagrammatology",
"year" : 2024,
"dir" : "burton",
"entry_id" : "2024_burton",
"title" : "wordle",
"abstract" : "wordle hard mode solver",
"author_set" : [
{
"author_handle" : "Dave_Burton"
}
],
"manifest" : [
{
"file_path" : "prog.c",
"inventory_order" : 20,
"OK_to_edit" : true,
"display_as" : "c",
"display_via_github" : true,
"entry_text" : "entry source code"
},
{
"file_path" : "Makefile",
"inventory_order" : 30,
"OK_to_edit" : true,
"display_as" : "makefile",
"display_via_github" : true,
"entry_text" : "entry Makefile"
},
{
"file_path" : "prog.orig.c",
"inventory_order" : 50,
"OK_to_edit" : false,
"display_as" : "c",
"display_via_github" : true,
"entry_text" : "original source code"
},
{
"file_path" : "deobfuscated.html",
"inventory_order" : 100,
"OK_to_edit" : false,
"display_as" : "markdown",
"display_via_github" : true,
"entry_text" : "about deobfuscation"
},
{
"file_path" : "dict",
"inventory_order" : 100,
"OK_to_edit" : true,
"display_as" : "text",
"display_via_github" : true,
"entry_text" : "5-letter wordle dictionary"
},
{
"file_path" : "extra.html",
"inventory_order" : 100,
"OK_to_edit" : false,
"display_as" : "markdown",
"display_via_github" : true,
"entry_text" : "description of the extra files"
},
{
"file_path" : "reveal.c",
"inventory_order" : 100,
"OK_to_edit" : true,
"display_as" : "c",
"display_via_github" : true,
"entry_text" : "reveal wordle hints"
},
{
"file_path" : "try.sh",
"inventory_order" : 100,
"OK_to_edit" : true,
"display_as" : "shellscript",
"display_via_github" : true,
"entry_text" : "script to try entry"
},
{
"file_path" : "deobfuscated.c",
"inventory_order" : 150,
"OK_to_edit" : true,
"display_as" : "c",
"display_via_github" : true,
"entry_text" : "deobfuscated source code"
},
{
"file_path" : "gen.c",
"inventory_order" : 150,
"OK_to_edit" : true,
"display_as" : "c",
"display_via_github" : true,
"entry_text" : "simulate wordle games"
},
{
"file_path" : "worst",
"inventory_order" : 150,
"OK_to_edit" : true,
"display_as" : "shellscript",
"display_via_github" : true,
"entry_text" : "determine worst target words"
},
{
"file_path" : "2024_01_18_wordle_stats.png",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "image",
"display_via_github" : true,
"entry_text" : "wordle stats"
},
{
"file_path" : "avg",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "shellscript",
"display_via_github" : true,
"entry_text" : "simple analysis script"
},
{
"file_path" : "comp.avg",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "shellscript",
"display_via_github" : true,
"entry_text" : "compute averages"
},
{
"file_path" : "dups.c",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "c",
"display_via_github" : true,
"entry_text" : "remove repeated letters"
},
{
"file_path" : "exhaust",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "shellscript",
"display_via_github" : true,
"entry_text" : "exhaustive test of all wordle words"
},
{
"file_path" : "extra_guesses",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "shellscript",
"display_via_github" : true,
"entry_text" : "allow extra wordle guesses"
},
{
"file_path" : "freq",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "shellscript",
"display_via_github" : true,
"entry_text" : "letter frequency in wordle words"
},
{
"file_path" : "mkresults",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "shellscript",
"display_via_github" : true,
"entry_text" : "report showing average word accuracy"
},
{
"file_path" : "rank",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "shellscript",
"display_via_github" : true,
"entry_text" : "how averages are calculated and missed words"
},
{
"file_path" : "run.c",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "c",
"display_via_github" : true,
"entry_text" : "automated testing tool"
},
{
"file_path" : "unob.c",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "c",
"display_via_github" : true,
"entry_text" : "unobfuscator tool source code"
},
{
"file_path" : "wavg",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "shellscript",
"display_via_github" : true,
"entry_text" : "weigh six scores"
},
{
"file_path" : "word.result",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "shellscript",
"display_via_github" : true,
"entry_text" : "produce results of a"
},
{
"file_path" : "wrank",
"inventory_order" : 200,
"OK_to_edit" : true,
"display_as" : "shellscript",
"display_via_github" : true,
"entry_text" : "weighted rank tool"
},
{
"file_path" : "2024_burton.tar.bz2",
"inventory_order" : 1415926535,
"OK_to_edit" : false,
"display_as" : "tbz2",
"display_via_github" : false,
"entry_text" : "download entry tarball"
},
{
"file_path" : ".entry.json",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "json",
"display_via_github" : true,
"entry_text" : "entry summary and manifest in JSON"
},
{
"file_path" : ".gitignore",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "gitignore",
"display_via_github" : true,
"entry_text" : "list of files that should not be committed under git"
},
{
"file_path" : ".path",
"inventory_order" : 4000000000,
"OK_to_edit" : false,
"display_as" : "path",
"display_via_github" : false,
"entry_text" : "directory path from top level directory"
},
{
"file_path" : "README.md",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "markdown",
"display_via_github" : true,
"entry_text" : "markdown source for this web page"
},
{
"file_path" : "correct.test.abode",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "text",
"display_via_github" : true,
"entry_text" : "correct test of the word abode"
},
{
"file_path" : "correct.test.jaunt",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "text",
"display_via_github" : true,
"entry_text" : "correct test of the word jaunt"
},
{
"file_path" : "correct.test.light",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "text",
"display_via_github" : true,
"entry_text" : "correct test of the word light"
},
{
"file_path" : "deobfuscated.md",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "markdown",
"display_via_github" : true,
"entry_text" : "markdown source for deobfuscated.html"
},
{
"file_path" : "extra.md",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "markdown",
"display_via_github" : true,
"entry_text" : "markdown source for extra.html"
},
{
"file_path" : "r.blast",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "text",
"display_via_github" : true,
"entry_text" : "analysis for the word blast"
},
{
"file_path" : "r.crate",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "text",
"display_via_github" : true,
"entry_text" : "analysis for the word crate"
},
{
"file_path" : "r.query",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "text",
"display_via_github" : true,
"entry_text" : "analysis for the word query"
},
{
"file_path" : "r.terns",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "text",
"display_via_github" : true,
"entry_text" : "analysis for the word terns"
},
{
"file_path" : "test.abode",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "text",
"display_via_github" : true,
"entry_text" : "analysis for the word"
},
{
"file_path" : "test.jaunt",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "text",
"display_via_github" : true,
"entry_text" : "analysis for the jaunt"
},
{
"file_path" : "test.light",
"inventory_order" : 4000000000,
"OK_to_edit" : true,
"display_as" : "text",
"display_via_github" : true,
"entry_text" : "analysis for the word light"
},
{
"file_path" : "index.html",
"inventory_order" : 4294967295,
"OK_to_edit" : false,
"display_as" : "html",
"display_via_github" : false,
"entry_text" : "this web page"
}
]
}
+7
View File
@@ -0,0 +1,7 @@
dups
fmt
fmt.c
gen
reveal
run
usdw
+1
View File
@@ -0,0 +1 @@
2024/burton
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.
+269
View File
@@ -0,0 +1,269 @@
#!/usr/bin/env make
#
# 2024/burton
#############################
# shell used by this Makefile
#############################
SHELL= bash
#######################
# common tool locations
#######################
include ../../var.mk
#####################
# C compiler settings
#####################
# Common C compiler warnings to silence
#
# Example: CSILENCE= -Wno-poison-system-directories
#
CSILENCE= -Wno-misleading-indentation -Wno-parentheses -Wno-unused-value
# Attempt to silence unknown warning options
#
CUNKNOWN= -Wno-unknown-warning-option
# Common C compiler warning flags
#
# NOTE: The addition of -pedantic to CWARN is a challenge that
# You may wish to avoid if it proves too problematic.
# There is NO penalty for removing -pedantic from CWARN.
#
CWARN= -Wall -Wextra -pedantic ${CSILENCE} ${CUNKNOWN}
# Compiler standard
#
CSTD= -std=gnu17
# Compiler bit architecture
#
# Example for 32-bitness: ARCH= -m32
# Example for 64-bitness: ARCH= -m64
#
# NOTE: Normally one should NOT specify a specific architecture.
#
ARCH=
# Defines that are needed to compile
#
# Example: -Dfoo -Dbar=baz
#
CDEFINE= -D_FORTIFY_SOURCE=0
# Include files that are needed to compile
#
# Example: CINCLUDE= -include stdio.h
#
CINCLUDE=
# Other flags to pass to the C compiler
#
# Example: COTHER= -fno-math-errno
#
COTHER=
# Optimization
#
# NOTE: Feel free to change the level of compiler optimization.
# The "-O3" is just a friendly default you might wish to try.
#
# Example: OPT= -O0 -g
#
OPT= -O3
# Default flags for ANSI C compilation
#
CFLAGS= ${CSTD} ${CWARN} ${ARCH} ${CDEFINE} ${CINCLUDE} ${COTHER} ${OPT}
# Libraries needed to build
#
# Example: LDFLAGS= -lncurses -lm
#
LDFLAGS=
# C compiler to use
#
# NOTE: The IOCCC Judges recommend you leave CC as just "cc"
# and NOT set it to clang, or gcc, or something else
# unless you have a STRONG reason to do so.
#
# Setting CC to something other than "cc" makes your
# code less portable to those who do not have your
# particular C compiler. **hint**
#
# If you want to test your code with a particular C compiler,
# use the make command line. For example:
#
# make all CC=clang
# make all CC=gcc
#
CC= cc
# Compiler add-ons or replacements for clang only
#
ifeq "$(findstring $(CLANG),${CC})" "$(CLANG)"
#
# NOTE: This code is only invoked when CC is "clang"
# such as when you use the make command line:
#
# make all CC=clang
#
CSILENCE+= -Wno-array-bounds-pointer-arithmetic -Wno-comma \
-Wno-declaration-after-statement -Wno-format-nonliteral \
-Wno-implicit-int-conversion -Wno-missing-prototypes \
-Wno-missing-variable-declarations -Wno-poison-system-directories \
-Wno-shadow -Wno-shorten-64-to-32 -Wno-vla -Wno-extra-semi-stmt \
-Wno-misleading-indentation -Wno-unsafe-buffer-usage
#
CWARN+= -Weverything
#
endif
# Specific add-ons or replacements for gcc only
#
ifeq "$(findstring $(GCC),${CC})" "$(GCC)"
#
# NOTE: This code is only invoked when CC is "gcc"
# such as when you use the make command line:
#
# make all CC=gcc
#
CSILENCE+=
#
CWARN+=
#
endif
###########################################
# Special Makefile variables for this entry
###########################################
ENTRY= prog
PROG= ${ENTRY}
#
OBJ= ${PROG}.o
TARGET= ${PROG} ${TOOLS}
#
ALT_OBJ=
ALT_TARGET=
# list any data files supplied with your submission
#
# Example: DATA= curds whey
#
DATA= dict test.abode test.light test.jaunt correct.test.abode \
correct.test.jaunt correct.test.light r.blast r.crate r.query r.terns
# utility scripts
#
SCRIPTS= avg comp.avg exhaust extra_guesses freq mkresults rank wavg word.result worst wrank
TOOLS= gen dups run reveal usdw
IOCCCSIZE= iocccsize
DICTIONARY= /usr/share/dict/words
#################
# build the entry
#################
all: data ${TARGET} ${SCRIPTS} ${TOOLS}
@${TRUE}
.PHONY: all alt data everything clean clobber
# how to compile
#
${PROG}: ${PROG}.c
${CC} ${CFLAGS} ${PROG}.c -o $@ ${LDFLAGS}
define stest
echo -n "$(2): ";
if ${CAT} $(2) | $(1) dict | ${DIFF} - correct.$(2); then echo pass; else echo fail; fi
endef
test: prog size
@ $(call stest,./prog,test.light,-lt)
@ $(call stest,./prog,test.abode,-lt)
@ $(call stest,./prog,test.jaunt,-eq)
size:
${IOCCCSIZE} prog.c
@# iocccsize-2019 -i < prog.c
@# iocccsize-2020 -i < prog.c > /dev/null
tfmt: fmt
@ $(call stest,./fmt,test.light,-lt)
@ $(call stest,./fmt,test.abode,-lt)
@ $(call stest,./fmt,test.jaunt,-eq)
fmt: fmt.c
${CC} ${CFLAGS} fmt.c -o $@ ${LDFLAGS}
fmt.c: prog.c
${RM} -f $@
${CLANG_FORMAT} -style=file < prog.c > fmt.c
dups: dups.c
${CC} ${CFLAGS} dups.c -o $@ ${LDFLAGS}
gen: gen.c
${CC} ${CFLAGS} gen.c -o $@ ${LDFLAGS}
run: run.c
${CC} ${CFLAGS} run.c -o $@ ${LDFLAGS}
reveal: reveal.c
${CC} ${CFLAGS} reveal.c -o $@ ${LDFLAGS}
usdw:
${RM} -f $@
${GREP} '^.....$$' ${DICTIONARY} | ${GREP} -v '[A-Z]' | ${GREP} -v beant > $@
spoiler: reveal fmt.c
@if test -x ${CLANG_FORMAT}; then ${CLANG_FORMAT} -style=file < prog.c | ./reveal; else ./reveal < fmt.c; fi
# alternative executable
#
alt: data ${ALT_TARGET}
@${TRUE}
${PROG}.alt: ${PROG}.alt.c
${CC} ${CFLAGS} ${PROG}.alt.c -o $@ ${LDFLAGS}
# data files
#
data: ${DATA}
@${TRUE}
# both all and alt
#
everything: all alt
@${TRUE}
###############
# utility rules
###############
#
clean:
${RM} -f ${OBJ} ${ALT_OBJ}
clobber: clean
${RM} -f ${TARGET} ${ALT_TARGET}
${RM} -rf *.dSYM
${RM} -f fmt.c fmt
######################################
# optional include of 1337 hacker rulz
######################################
-include 1337.mk ../1337.mk ../../1337.mk
+546
View File
@@ -0,0 +1,546 @@
## To build:
``` <!---sh-->
make all
```
## To use:
``` <!---sh-->
./prog dict
```
## Try:
``` <!---sh-->
./try.sh
```
## Judges' remarks:
We were impressed with the algorithm, but one question remains: is there a
strategy which reduces the probability of a loss even further, at the cost of
requiring a higher average number of guesses?
## Author's remarks:
This program is an assistant for solving hard-mode [Wordle(tm)][1]
(you must use any revealed hints in all subsequent guesses)
accurately/efficiently - in the fewest guesses.
The program expects lower case input in two words: `guess color`,
followed by newline.
- `guess` is the 5 letter word you guessed,
- `color` is 5 letters from the set [g,y,b]
that correspond to the colors shown in Wordle: g=green, y=yellow, b=black.
[1]: https://www.nytimes.com/games/wordle/index.html
Start the program with a single argument, the name of a dictionary of words.
`/usr/share/dict/words` is a perfectly cromulent dictionary,
but the included file `dict` contains the 2315 target words from the original
Wordle game and consistently provides the fewest guesses.
As an example session, Wordle 226 has the target word `light`.
The recommended first guess (for all Wordle puzzles) is `blast`, `terns`,
or `crate` (see the [extra.html](extra.html) that accompanies this entry for details).
Enter `terns` in game 226 of Wordle, and click the enter button.
The letters will turn yellow, black, black, black, black.
This assistant prompts "? ", and the input expected is the guessed word
followed by the first letters of the color revealed for that guess:
``` <!---sh-->
$ prog dict
? terns ybbbb
loath
? loath gbbyy
light
$
```
The program responds with the recommended next guess. Enter
this word `loath` into the game, which then reveals the colors
green, black, black, yellow, yellow. Typing `loath gbbyy` into
this assistant shows the solution is `light`, and exits.
Type this word into the game, click enter, and viola!
Solved in 3 guesses: terns > loath > light.
This program averages better than 4 guesses on the 1566 starting words
without duplicate letters created by `./dups -n < dict`,
ranging from 3.587 to 3.997, with an overall average of 3.748 guesses.
This is excellent performance, and close to optimal.
See this YouTube presentation on [solving Wordle using information theory][2],
which shows a hard mode average of 3.562 when using `crane`
(time offset 29:58). There is a small correction video changing
[`crane` to `salet`, `trace` or `crate`][3].
NB: These videos are NOT spoilers for this algorithm.
[2]: https://www.youtube.com/watch?v=v68zYyaEmEA
[3]: https://www.youtube.com/watch?v=fRed0Xmc2Wg
`prog` does NOT require you to use its offered guess.
If you think you have a better guess than offered,
simply type your guess and the Wordle/`gen` responses into `prog`;
it will internally re-adjust using your input instead of its guess,
and produce what it thinks is the best next guess after your input.
### A Feature
Caution: When there are duplicate letters in the guess word,
and one of them is marked black and the other non-black,
this program MUST have the black letter marked *yellow*.
The example session (Wordle 228) below demonstrates this:
```
# do NOT do this
$ prog dict
? terns bbbby
shoal
? shoal gggbb
shook
? shook gggbg <<-- this is what Wordle shows
xyzzy <<-- no candidate words match
# do THIS instead
$ prog dict
? terns bbbby
shoal
? shoal gggbb
shook
? shook gggyg <<-- not gggbg as shown in the game
shock
? shock ggggg
shock
```
In the guess word `shook`, there are 2 o's, but only one shown valid in
the puzzle. If you mark the leftmost o as black (as according to the
Wordle game), this program cannot find the winning word (`shock`),
producing the error word `xyzzy`.
The included simulator `gen` produces the required transformation of b -> y.
The extra program `run` connects `gen` and `prog`, allowing for quick tests.
```
make tools
./run dict terns shock # test starting guess `terns` against target `shock`
```
Using `run` allows one to quickly and easily experiment with different
starting words for different target words, seeing how the guesses progress.
(A late tweak to the program improved its accuracy, and in the above example,
the progression is now terns > shoal > shock. However, the caution remains
for any duplicate letter guesses.)
### Failure mode
The following 4 words from the included file `dict` are not guessed correctly
within 6 guesses when using the recommended (via exhaustive testing)
starting guess of `blast`. Since this program is deterministic, these
four words always require 7 guesses when starting with `blast`, yielding
a failure rate of 4/2315, or a success rate of 99.8%.
```
foyer jaunt shave wound
```
Using the ancillary programs `run`, `exhaust`, and `mkresults`,
the starting word `blast` yields the fewest misses at these four words,
with an accuracy/efficiency of 3.67. See [extra.html](extra.html)
for more detail.
This program also has a benefit the game Wordle does not:
Wordle allows several thousand extra words to use in guesses not in `dict`.
This program does not restrict _any_ words, so one could enter any
combination of 5 letters as input, except these are unlikely to be accepted
by the Wordle game, and this program selects its words from the much more
limited set that is present in `dict`. Using an expanded dictionary like
`/usr/share/dict/words` provides a richer set of guesses, but generally
increases the average number of guesses and the failure rate.
### Interesting questions
- How are candidate words chosen?
- How does the array of numbers relate to finding a word?
- It is interesting to see the final stages of picking a word.
You might be interested to add observability? (Hint: `make tfmt`)
Although `clang-format` is unlikely to help you understand the code,
`printf`'s will help you understand how the "tables" are used:
"Show me your flowcharts and conceal your tables,
and I shall continue to by mystified;
show me your tables and I won't usually need your flowcharts...."
-- Fred Brooks
- There is a plausible "tunable", not exposed. It selects when to
discard certain words. What is this value, and how important is this
value in optimizing results?
- There is a debatable "bug" in the code, but it actually _improves_ the
results. Where and why?
Hint: the limit `for` a loop. ;-)
- This solver/assistant is pretty interesting to play with, experiment with.
Can you improve it?
### Did you notice?
- Wordle is spelled in the code several different ways, one is obscure.
If you need help, try this command (which requires `clang-format`):
```
make spoiler
```
- My second favorite language is spelled out thrice in the source code.
- Variables are mostly generic a,b,c and i,j,k,l and reused in different
scopes. Have fun!
- The Sierpinski[4] number? What in the world is a Sierpinski number?
[4]: https://planetmath.org/sierpinskinumber
### Time Passages (Apologies to Al Stewart)
This code was written in mid January 2022, before the NYT purchased Wordle[tm].
I have since used this assistant to play the game for more than a year,
confirming my local experiments.
![wordle stats](./2024_01_18_wordle_stats.png)
The dictionary changed in 2023! In late March 2023, I encountered the first
word not in `dict`: `guano`. In a bit of a surprise, I tried:
```
blast bbgbb
reach bbgbb
piano bbggg
xyzzy
```
What? The only word in `dict` ending in `ano` is `piano`. So I tried again
using `/usr/share/dict/words`:
```
blast bbgbb
crane bbggb
guano ggggg
```
Blending the two, the puzzle was guessed in 4 tries total:
```
blast > reach > piano > guano
```
So caveat emptor: while `dict` used to be authoritative, we know some words
were deemed offensive and removed. More problematic, some words were added,
although I am presently unaware of which words. The existence proof is the
above mentioned `guano`. There are almost certainly other words missing.
With `guano` added to `dict`, it is guessed in 3 using `blast` as the
starting word:
```
blast bbgbb
reach bbgbb
guano ggggg
```
NB: `dict` has not been changed. `gauno` was NOT added to `dict`.
Adding this word changes the statistics, and thus requires a change
to all this material. It has a *slightly* negative impact, except
for it finds the word `guano`. Essentially, as you add new words
to `dict`, the permutations expand, and the scores decrease. This is
why `/usr/share/dict/words` scores worse than `dict` - and loses more often.
April 9, 2023, this problem repeated with the target word `snafu`.
That seems apropos.
```
$ ./prog dict
blast bbgyb
share gbgbb
spank gbgyb
xyzzy
$ ./prog /usr/share/dict/words
blast bbgyb
share gbgbb
spank gbgyb
snafu ggggg
```
And on June 9, 2023, again. This time, it cost nothing, and won on the
second guess.
```
$ ./prog dict
blast gyygb
xyzzy
$ ./prog /usr/share/dict/words
blast gyygb
balsa ggggg
```
June 19, 2023, another one wasn't working, so time for a better dictionary:
```
$ ./prog dict
blast bbybb
ramen bgbbb
vapid bgbbb
wacky bgbyb
xyzzy
# a copy of /usr/share/dict/words with five letters and no capitals:
grep '^.....$' /usr/share/dict/words | grep -v '[A-Z]' > ./usdw
$ ./prog usdw
blast bbybb
redan bbbyb
campo bgbbg
wahoo bgbgg
kazoo ggggg
```
As of June 19, 2023, it is known that at least these words have been added
(more are likely). Using `./usdw` as above, `prog` guesses these words in:
```
guano 3
snafu 4
balsa 2
kazoo 5
```
See the [extra.html](extra.html) for more details on the program `run`:
```
for n in guano snafu balsa kazoo; do
./run usdw blast $n;
done | grep good
```
As of April 22, 2024, add the word `laser` in 3 guesses.
```
$ ./prog usdw
blast byyyb
solea ybygy
laser ggggg
```
And May 8, 2024 brings on `pious` in 4 guesses (after the failure with `dict`):
```
$ prog dict
blast bbbyb
siren ygbbb
ficus bgbgg
xyzzy
$ prog usdw
? blast bbbyb
serio
? siren ygbbb
pisco
? ficus bgbgg
pious
? pious ggggg
```
Notice here that the suggested word was NOT used, instead the original
guesses were used, and it found the correct answer in 4 guesses.
However, doing
```
./run usdw blast pious
```
gets the answer correct in 3 guesses: blast > serio > pious.
And Sep 17, 2024 brings us the word `beaut`.
```
$ prog dict
blast gbgbg
xyzzy
$ prog usdw
blast gbgbgb
beant -- not allowed, so removed beant from usdw
brant gbgbg
beaut ggggg
```
Three guesses using an alternate dictionary and having to remove `beant`.
This word isn't even a word I can find in Webster's. I hope your version
of `/usr/share/dict/words` has more cromulent words.
Oct 9, 2024 brings the word `mommy`.
```
$ prog dict
blast bbbbb
drone bbybb
pouch bgbbb
foggy bgbbg
$ prog usdw
blast bbbbb
drone bbybb
pouch bgbbb
foggy bgbbg
mommy ggggg
```
Fortunately, the program is good enough that failing with one
dictionary still allows you to switch dictionaries and win.
Nov 13, 2024 brings `primp`.
```
$ ./prog dict
blast bbbbb
drone bgbbb
crimp bgggg
xyzzy
$ ./prog usdw
blast bbbbb
drone bgbbb
crimp bgggg
primp ggggg
```
Jan 20, 2025 brings the word `squid`.
```
$ ./prog dict
blast bbbyb
siren gybbb
skimp gbybb
sushi <<-- last plausible word
$ ./prog usdw
blast bbbyb
siren gybbb
skimp gbybb
sushi sgbby
squid ggggg
```
As of Jan 20, 2025, the ten known words not in `dict` are
(scores are using `usdw`, not `dict`, which lacks these words):
```
guano 4
snafu 5
balsa 3
kazoo 4
laser 3
pious 4
beaut 3
mommy 4
primp 5
squid 4
```
Feb 18, 2025 adds a word not in `dict` nor `usdw`. "indie".
```
$ ./prog dict
blast bbbbb
drone ybbyg
undue bggbg
xyzzy
$ ./prog usdw
blast bbbbb
drone ybbyg
undue bggbg
xyzzy
I grep'd the dictionaries. No plausbile hits.
So I solved it the old fashioned way: I guessed.
blast bbbbb
drone ybbyg
undue bggbg
indie ggggg
This solved it in 4, but the program could not win.
So add to the summary list above:
indie 4
```
### Compilation
Compiles cleanly with `-Wall -Wpedantic -Wextra`,
on MACOS M2 Clang/GCC 14 and Linux X86 Gcc 8.5.
If you are using `-Weverything`,
which IMHO is exceptionally noisy about non-problems,
and given the requirements of obfuscation and layout,
there are a few `-Wno-` flags required to achieve a
quiet compilation:
```
-Wno-poison-system-directories \
-Wno-missing-variable-declarations \
-Wno-shadow \
-Wno-vla \
-Wno-missing-prototypes \
-Wno-shorten-64-to-32 \
-Wno-format-nonliteral \
-Wno-implicit-int-conversion \
-Wno-declaration-after-statement \
-Wno-comma \
-Wno-array-bounds-pointer-arithmetic \
-Wno-sign-conversion
```
The code uses variable length arrays, omitted the keyword `static`
for functions and global variables, and collide `size_t` with `int`
for return types. There are no pedantic prototypes for functions.
There is no misuse of the comma operator, but try getting the compiler
to ignore it. The code has been run on MacOS M2 and Linux x86 with
no failures observed. And as documented in [deobfuscated.html](deobfuscated.html),
pointer bounds are intentionally abused for obfuscation, but the
code is correct as documented.
This is obfuscated C, so yes, variables are intentionally shadowed.
`printf()` is also abused, passing in non-literals as the format.
Perhaps there is some platform out there that won't work correctly
due to the omissions noted above, but I am unaware of these.
<!--
Copyright © 1984-2025 by Landon Curt Noll and Leonid A. Broukhis. All Rights Reserved.
You are free to share and adapt this file under the terms of this license:
Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
For more information, see:
https://creativecommons.org/licenses/by-sa/4.0/
-->
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
awk '
/^guess1/ {if (w == "") w = $2 }
/good:/ {n++; t+=$5 }
/lose:/ {n++; L++ }
END {printf "%s %.06f %d\n",w,t/n,L }' $1
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
a=$1
b=$2
if test $# -ne 2; then echo "usage: $0 result.a result.b" >&2; exit 1; fi
join $a $b | awk '
{
davg=$4-$2
dloss=$5-$3
print $0, davg, dloss
}
' | tabby
+3
View File
@@ -0,0 +1,3 @@
? amber
? adobe
? abode
+6
View File
@@ -0,0 +1,6 @@
? daunt
? haunt
? gaunt
? vaunt
? taunt
? jaunt
+2
View File
@@ -0,0 +1,2 @@
? loath
? light
+165
View File
@@ -0,0 +1,165 @@
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
typedef void q;
typedef int _;
typedef char p;
struct {
p a[257], b[9][5], c[5], d[5];
_ e, f;
} i;
p t[78557][5];
_ s, a[] = {
9, 9, 9, 10, 13, 18, 27, 39, 55, 1, 18, 2, 1, 1,
35, 2, 19, 18, 1, 52, 36, 35, 20, 3, 2, 19, 18, 18,
69, 2, 53, 1, 52, 37, 3, 20, 36, 19, 35, 18, 52, 86,
2, 53, 70, 1, 69, 38, /* 36 */ 4, 21, 3, 37, 19,
36, 35, 25, 10, 17, 15, 26, 9, 11, 12, 23, 2, 6, 18,
13, 20, 22, 14, 1, 24, 19, 21, 16, 5, 7, 4, 8, 3
} , u = ' ';
_ W(p *a, p *b) {
for (p *c = a + 5; a < c; )
*a++ = *b++;
return 0;
}
_ O(p *a, p b) {
p *c = a + 5;
for (; a < c && *a - b; )
++a;
return a < c ? 5 - (a - c) : 0;
}
_ R(p *a, ...) {
va_list b;
va_start(b, a);
vprintf(a, b);
exit(1);
}
q D(p *a) {
if (s == sizeof t / 5)
R("limit %d\n", s);
p *b = s[t], i, j;
for (i = j = 0; i < 7 && !j; ++i)
if (!(j = *a == 10 ? i : 0))
*b++ = u | *a++;
j - 5 || ++s;
}
q L(p *a, _ b) {
for (_ l = 0, o = 0, k; l < s; o = 0) {
for (_ j = 0; !o && j < 5 && (k = t[l][j]); ++j)
i.c[j] && k - i.c[j] && ++o;
for (_ j = 0; !o && j < 5 && (k = t[l][j]); ++j)
i.a[k] && ++o;
for (_ j = 0; !o && j < 5 && (k = t[l][j]); ++j)
i.d[j] && !O(t[l], i.d[j]) && ++o;
for (_ f = i.e; f-- && !o; )
for (_ j = 0; !o && j < 5 && (k = t[l][j]); ++j)
i.b[f][j] == k && ++o;
if (!o) {
for (_ j = 0; j < 5; ++j)
o += t[l][j] == a[j];
o = o == 5 ? b != 5 : 0;
}
o ? W(t[l], t[--s]) : ++l;
}
}
_ e(_ W, _ O, _ R, _ D, _ *L, _ *E) {
for (; O < D; )
L[R++] = E[O++ + D * W];
return R;
}
q S(_ O, _ *l) {
for (_ *s = &a[a[O]], *e = a[-~O] + s - a[O]; s < e; ) {
_ i = *s++,
j = i & 15,
k = l[i >>= 4] < l[j] ? k = l[i], l[i] = l[j], l[j] = k : i;
}
}
_ o(_ W, _ O, _ R, _ D, _ *L, _ *E) {
_ o = 0, w = 0;
for (; o < D & w < D; )
L[R++] = E[o + D * W] >= E[w + D * O]
? E[o++ + D * W]
: E[w++ + D * O];
return e(O, w, e(W, o, R, D, L, E), D, L, E);
}
q l(_ n, _ *f) {
for (_ *e = f + 26, i, j; f < e && *f > u; f += i) {
for (i = 1, j = *f / u; i < n && f[i] >> 5 == j; )
++i;
for (j = 0; j < i; ++j)
f[j] &= 31,
f[j] |= a[f[j] + a[~-9]] << 5;
S(i, f);
}
}
q v(_ a, _ *w, _ *k) {
o(2, 3, o(0, 1, 0, a, w, k), a, w, k);
o(0, 1, 0, a + a, k, w);
l(a, k);
}
p *E(p *d) {
_ l, j, k = u, b[u], c[k];
p *e[97][5] = { 0 } ;
for (l = 0; l < k; ++l)
c[l] = l;
for (l = 0; l < s; ++l)
for (_ j = 0; j < 5; ++j)
c[t[l][j] - 97] += k;
for (l = j = 7; j < 22; j += l)
S(l, c + j - l);
v(l, b, c);
for (l = 0; l < k; ++l)
b[c[l] & ~-k] = l;
for (k *= 3, l = 0; l < s; ++l) {
_ i = 0;
for (_ j = 0; j < 5; ++j)
i += b[t[l][j] - 97];
if (i < k)
for (_ j = 0; j < 5; ++j)
e[i][j] || (e[i][j] = t[l], j = 4);
}
for (l = 0; l < k; ++l)
for (p *b = 0, k = j = 0; j < 5 && (b = e[l][j]); k = !++j) {
p c[32] = { 0 } ;
d = *d - 120 ? d : b;
if (s > 2)
for (_ j = 0; j < 5; ++j)
k += ++c[b[j] - 97] > 1;
if (!k)
return b;
}
return d;
}
p *r(p *b) {
_ k, l;
for (_ j = k = l = 0; j < 5; ++j) {
signed char n = b[j], o = b[j - ~5] + ~0x66;
(o ? i.b[i.e] : i.c)[j] = n;
!o ? i.a[n] = !++k : O(i.d, n) || (*(o < 0 ? &i.a[n] : &i.d[i.f++]) = l = n);
}
i.e += !!l;
L(b, k);
return E("xyzzy");
}
_ main(_ n, char **v) {
p *j = i.c - 97, *l = j - 11;
n - 2 ? R("usage: %s dict\n", *v) : 5;
FILE *k = fopen(*++v, "r");
if (!k)
return perror(*v), 1;
while (fgets(j, u ^ n, k))
D(j);
fclose(k);
setbuf(stdout, 0);
while (~-s) {
printf("? ");
while (~(n = getchar()) && n - 10 && l < j)
*l++ = n;
l < j && R("input: guess color\n");
printf("%.5s\n", r(l = j + ~10));
}
return 0;
}
+814
View File
@@ -0,0 +1,814 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<!-- START: two lines up starts content from: inc/top.default.html -->
<!-- END: this line ends content from: inc/top.default.html -->
<!-- START: this line starts content from: inc/head.default.html -->
<head>
<link rel="stylesheet" href="../../ioccc.css">
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap" rel="stylesheet">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>The International Obfuscated C Code Contest</title>
<link rel="icon" type="image/x-icon" href="../../favicon.ico">
<meta name="description" content="deobfuscated.md for 2024/burton">
<meta name="keywords" content="IOCCC, 2024/burton, deobfuscated.md">
</head>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<!-- !!! DO NOT MODIFY THIS FILE - This file is generated by a tool !!! -->
<!-- !!! DO NOT MODIFY THIS FILE - This file is generated by a tool !!! -->
<!-- !!! DO NOT MODIFY THIS FILE - This file is generated by a tool !!! -->
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<!-- END: this line ends content from: inc/head.default.html -->
<!-- -->
<!-- This web page was formed via the tool: bin/gen-other-html.sh -->
<!-- The content of main section of this web page came from: 2024/burton/deobfuscated.md -->
<!-- -->
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<!-- !!! Do not modify this web page, instead modify the file: 2024/burton/deobfuscated.md !!! -->
<!-- !!! Do not modify this web page, instead modify the file: 2024/burton/deobfuscated.md !!! -->
<!-- !!! Do not modify this web page, instead modify the file: 2024/burton/deobfuscated.md !!! -->
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<!-- Markdown content was converted into HTML via the tool: bin/md2html.sh -->
<!-- START: this line starts content from: inc/body.default.html -->
<body>
<!-- END: this line ends content from: inc/body.default.html -->
<!-- START: this line starts content from: inc/topbar.default.html -->
<div class="theader">
<nav class="topbar">
<div class="container">
<div class="logo">
<a href="../../index.html" class="logo-link">
IOCCC
</a>
</div>
<div class="topbar-items">
<div class="item">
<span class="item-header">
Entries
</span>
<div class="sub-item">
<div class="outfit-font">
<a href="../../years.html" class="sub-item-link">
Winning entries
</a>
</div>
<div class="outfit-font">
<a href="../../authors.html" class="sub-item-link">
Winning authors
</a>
</div>
<div class="outfit-font">
<a href="../../location.html" class="sub-item-link">
Location of authors
</a>
</div>
<div class="outfit-font">
<a href="../../bugs.html" class="sub-item-link">
Bugs and (mis)features
</a>
</div>
<div class="outfit-font">
<a href="../../faq.html#fix_an_entry" class="sub-item-link">
Fixing entries
</a>
</div>
<div class="outfit-font">
<a href="../../faq.html#fix_author" class="sub-item-link">
Updating author info
</a>
</div>
</div>
</div>
<div class="item">
<span class="item-header">
Status
</span>
<div class="sub-item">
<div class="outfit-font">
<a href="../../news.html" class="sub-item-link">
News
</a>
</div>
<div class="outfit-font">
<a href="../../status.html" class="sub-item-link">
Contest status
</a>
</div>
<div class="outfit-font">
<a href="../../next/rules.html" class="sub-item-link">
IOCCC rules
</a>
</div>
<div class="outfit-font">
<a href="../../next/guidelines.html" class="sub-item-link">
IOCCC guidelines
</a>
</div>
<div class="outfit-font">
<a href="../../markdown.html" class="sub-item-link">
Markdown guidelines
</a>
</div>
<div class="outfit-font">
<a href="../../SECURITY.html" class="sub-item-link">
Security policy
</a>
</div>
</div>
</div>
<div class="item">
<span class="item-header">
FAQ
</span>
<div class="sub-item">
<div class="outfit-font">
<a href="../../faq.html" class="sub-item-link">
Frequently asked questions
</a>
</div>
<div class="outfit-font">
<a href="../../quick-start.html#enter" class="sub-item-link">
Enter the IOCCC
</a>
</div>
<div class="outfit-font">
<a href="../../faq.html#compiling" class="sub-item-link">
Compiling entries
</a>
</div>
<div class="outfit-font">
<a href="../../faq.html#running_entries" class="sub-item-link">
Running entries
</a>
</div>
<div class="outfit-font">
<a href="../../faq.html#help" class="sub-item-link">
How to help
</a>
</div>
</div>
</div>
<div class="item">
<span class="item-header">
About
</span>
<div class="sub-item">
<div class="outfit-font">
<a href="../../index.html" class="sub-item-link">
Home page
</a>
</div>
<div class="outfit-font">
<a href="../../social.html" class="sub-item-link">
IOCCC social media
</a>
</div>
<div class="outfit-font">
<a href="../../about.html" class="sub-item-link">
About the IOCCC
</a>
</div>
<div class="outfit-font">
<a href="../../judges.html" class="sub-item-link">
The judges
</a>
</div>
<div class="outfit-font">
<a href="../../thanks-for-help.html" class="sub-item-link">
Thanks for the help
</a>
</div>
<div class="outfit-font">
<a href="../../contact.html" class="sub-item-link">
Contact us
</a>
</div>
</div>
</div>
</div>
</div>
</nav>
<div class="header-mobile-menu">
<noscript>
<a href="../../nojs-menu.html" class="topbar-js-label">
Please Enable JavaScript
</a>
</noscript>
<button id="header-open-menu-button" class="topbar-mobile-menu">
<img
src="../../png/hamburger-icon-open.png"
alt="hamburger style menu icon - open state"
width=48
height=48>
</button>
<button id="header-close-menu-button" class="hide-content">
<img
src="../../png/hamburger-icon-closed.png"
alt="hamburger style menu icon - closed state"
width=48
height=48>
</button>
<div id="mobile-menu-panel" class="hide-content">
<div class="mobile-menu-container">
<div class="mobile-menu-wrapper">
<div class="mobile-menu-item">
Entries
</div>
<div class="mobile-submenu-wrapper">
<a class="mobile-submenu-item" href="../../years.html">
Winning entries
</a>
<a class="mobile-submenu-item" href="../../authors.html">
Winning authors
</a>
<a class="mobile-submenu-item" href="../../location.html">
Location of authors
</a>
<a class="mobile-submenu-item" href="../../bugs.html">
Bugs and (mis)features
</a>
<a class="mobile-submenu-item" href="../../faq.html#fix_an_entry">
Fixing entries
</a>
<a class="mobile-submenu-item" href="../../faq.html#fix_author">
Updating author info
</a>
<a class="mobile-submenu-item" href="../../thanks-for-help.html">
Thanks for the help
</a>
</div>
</div>
<div class="mobile-menu-wrapper">
<div class="mobile-menu-item">
Status
</div>
<div class="mobile-submenu-wrapper">
<a class="mobile-submenu-item" href="../../news.html">
News
</a>
<a class="mobile-submenu-item" href="../../status.html">
Contest status
</a>
<a class="mobile-submenu-item" href="../../next/rules.html">
IOCCC rules
</a>
<a class="mobile-submenu-item" href="../../next/guidelines.html">
IOCCC guidelines
</a>
<a class="mobile-submenu-item" href="../../markdown.html">
Markdown guidelines
</a>
<a class="mobile-submenu-item" href="../../SECURITY.html">
Security policy
</a>
</div>
</div>
<div class="mobile-menu-wrapper">
<div class="mobile-menu-item">
FAQ
</div>
<div class="mobile-submenu-wrapper">
<a class="mobile-submenu-item" href="../../faq.html">
Frequently asked questions
</a>
<a class="mobile-submenu-item" href="../../quick-start.html#enter">
Enter the IOCCC
</a>
<a class="mobile-submenu-item" href="../../faq.html#compiling">
Compiling entries
</a>
<a class="mobile-submenu-item" href="../../faq.html#running_entries">
Running entries
</a>
<a class="mobile-submenu-item" href="../../faq.html#help">
How to help
</a>
</div>
</div>
<div class="mobile-menu-wrapper">
<div class="mobile-menu-item">
About
</div>
<div class="mobile-submenu-wrapper">
<a class="mobile-submenu-item" href="../../index.html">
Home page
</a>
<a class="mobile-submenu-item" href="../../social.html">
IOCCC social media
</a>
<a class="mobile-submenu-item" href="../../about.html">
About the IOCCC
</a>
<a class="mobile-submenu-item" href="../../judges.html">
The judges
</a>
<a class="mobile-submenu-item" href="../../contact.html">
Contact us
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
var headerOpenMenuButton = document.getElementById("header-open-menu-button");
var headerCloseMenuButton = document.getElementById("header-close-menu-button");
var mobileMenuPanel = document.getElementById("mobile-menu-panel");
headerOpenMenuButton.addEventListener("click", () => {
headerOpenMenuButton.classList.remove("topbar-mobile-menu");
headerOpenMenuButton.classList.add("hide-content");
headerCloseMenuButton.classList.remove("hide-content");
headerCloseMenuButton.classList.add("topbar-mobile-menu");
mobileMenuPanel.classList.remove("hide-content");
mobileMenuPanel.classList.add("topbar-mobile-panel");
});
headerCloseMenuButton.addEventListener("click", () => {
headerCloseMenuButton.classList.remove("topbar-mobile-menu");
headerCloseMenuButton.classList.add("hide-content");
mobileMenuPanel.classList.add("hide-content");
mobileMenuPanel.classList.remove("topbar-mobile-panel");
headerOpenMenuButton.classList.add("topbar-mobile-menu");
headerOpenMenuButton.classList.remove("hide-content");
});
</script>
<!-- END: this line ends content from: inc/topbar.default.html -->
<!-- START: this line starts content from: inc/header.default.html -->
<div class="header">
<a href="../../2011/zucker/index.html">
<img src="../../png/ioccc.png"
alt="IOCCC image by Matt Zucker"
width=300
height=110>
</a>
<h1>The International Obfuscated C Code Contest</h1>
<h2>deobfuscated.html for 2024/burton</h2>
</div>
<!-- END: this line ends content from: inc/header.default.html -->
<!-- START: this line starts content from: inc/navbar.up2index.html -->
<div class="navbar">
<a class="Left" href="index.html">&uarr; jump to 2024/burton/index.html &uarr;</a>
<a class="Right" href="https://validator.w3.org/nu/?doc=https%3A%2F%2Fwww.ioccc.org%2F2024%2Fburton%2Fdeobfuscated.html">&#x2713;</a>
</div>
<!-- END: this line ends content from: inc/navbar.up2index.html -->
<!-- START: this line starts content from: inc/before-content.default.html -->
<div class="content" id="content">
<!-- END: this line ends content from: inc/before-content.default.html -->
<!-- START: this line starts content for HTML phase 21 by: bin/pandoc-wrapper.sh via bin/md2html.sh -->
<!-- BEFORE: 1st line of markdown file: 2024/burton/deobfuscated.md -->
<h1 id="do-not-read-this-if-you-want-to-figure-it-out-yourself">Do not read this if you want to figure it out yourself!</h1>
<p>The file <code>deobfuscated.c</code> is simply a pretty-printed version of
<code>prog.c</code> with the macros expanded. It is hopefully just as
difficult to understand as the original presentation:
obfuscation is not just presentation, after all.</p>
<p>If you read further, the remainder talks about how the code
works. If you want to figure it out yourself, stop reading here
and do look at <code>deobfuscated.c</code>.</p>
<h2 id="read-dictionary">Read Dictionary</h2>
<p>The program begins by reading junk ;-) from the dictionary and processing
via <code>D()</code>. The words are stored in <code>t[][]</code>, counted by <code>s</code> and <code>D()</code> ensures
this never has buffer overflow. Notice the swap of index and array in <code>s[t]</code>.
It also ensures only 5 letter words are selected from the dictionary,
and folds uppercase to lowercase. Each call of <code>D()</code> handles one word.</p>
<p>NB: if the dictionary has, for instance, “ARRAY” and “array” and “Array”,
there will be 3 copies of “array” stored in <code>t[][]</code>.
This will degrade performance and may cause a malfunction of the algorithm,
and definitely if the target word is “array”. Thus the <code>makefile</code> builds
<code>usdw</code> with capital letters removed (avoiding this problem completely).
However, if there are duplicate words in the dictionary, use another.</p>
<h2 id="user-input">User Input</h2>
<p>After the dictionary is read, it is time to prompt the user for the
input guess. This happens in <code>main()</code>. The input buffer is the upper
half of <code>i.a</code>, which is never used (ASCII lowercase letters only).
The newline is ignored by <code>n - 10</code>, at most 11 characters (<code>l &lt; j</code>)
can be input, and EOF terminates the input loop.</p>
<p>Here is the explanation of how <code>i.c - 97</code> works to get an input buffer.
Assume <code>struct i</code> has the address 1000, then <code>i.a</code> begins at 1000.
<code>i.b</code> begins at 1257. <code>i.c</code> begins at 9*5 = 45 + 1257 = 1302.
1302 - 97 = 1205. Therefore, the <code>i.c - 97</code> sets the input buffer at
<code>&amp;i.a[205]</code>, ASCII lowercase input ends at <code>i.a[123]</code>, and there are
53 characters before <code>i.b</code> is overwritten - plenty of room for the user IO.
Why 97? The expression <code>x - 97</code> is used several times in the code,
leading to some confusion on the reason for the value.</p>
<p>Note that this abuse is what draws a compiler complaint, requiring:</p>
<pre><code> -Wno-array-bounds-pointer-arithmetic</code></pre>
<p>It is safe. It works. There is no problem here, but the compiler
does not know this.</p>
<h2 id="process-user-input">Process User Input</h2>
<p>Once the user input is received, the program calls <code>r()</code>, which populates
<code>struct i</code> with the guesses. This serves to set the patterns known
and impossible, as well as letters used and required (remember, this is
hard mode Wordle, which requires you to use all green letters in the
same place for all subsequent guesses). <code>r()</code> returns the next word
to guess via <code>E()</code> (aka choose() - see below).</p>
<p>Within <code>r()</code>, it is important to understand the ASCII values for g, y and b.</p>
<pre><code> b 98
g 103
y 121</code></pre>
<p>Within <code>r()</code>, <code>n</code> is the guessed letter, and <code>o</code> is color - 103.
If <code>o</code> is greater than zero (121-103), this guess is correct, but
incorrect location (yellow). If it is less than zero (98-103),
this guess is not possible (black). If it is zero (103-103),
it is the correct letter in the correct location (green).</p>
<p><code>struct i</code> can be re-written this way:</p>
<pre><code> struct {
char black[256]; // wrong letters guessed so far
char words[9][5]; // words used in guesses so far
char green[5]; // correct letters (must reuse these)
char yellow[5]; // incorrect letters (impossible positions)
int num_guesses;
int num_yellow;
} i;</code></pre>
<p>And <code>r()</code> can be rewritten:</p>
<pre><code> char *r(char *input) {
int num_green = 0, l = 0;
for (int j = 0; j &lt; 5; ++j) {
char n = input[j];
signed char o = input[j + 6] - 103; // j - ~5 = j - -6 == j + 6
// if this is a correct guess
if (o == 0) {
i.green[j] = n;
i.black[n] = 0; // remove this from black
++num_green;
} else {
i.words[i.num_guesses][j] = n;
// if not already a yellow letter
if (memchr(i.yellow, n, 5) == NULL) {
l = n;
if (o &lt; 0) i.black[n] = n; // black, impossible
else i.yellow[i.num_yellow++] = n;
}
}
}
// l is 0 if all letters correct, else l = n
i.num_guesses += l &gt; 0 ? 1 : 0;
filter(input, num_green);
return choose(&quot;xyzzy&quot;);
}</code></pre>
<h2 id="filter">Filter</h2>
<p>The next step is to remove the impossible words from the in-memory dictionary.
This is handled by <code>L()</code>. <code>L()</code> iterates over all the remaining words in the
dictionary. <code>L()</code> runs a number of filters, and if the word is plausible,
it is skipped, and if impossible, the current word is replaced from the end
of the list. This prevents holes from appearing in the dictionary, and keeps
the count accurate. The important action is carried out in the expression
<code>o ? W(t[l], t[--s]) : ++l</code>. That is, if <code>o</code> is true, the word is replaced
by the end of the list, else the word is skipped. Note in particular the
expressions <code>--s</code> and <code>++l</code> (decrement and replace, or skip, respectively).</p>
<p>Said another way, think of <code>W()</code> as an analog of <code>strncpy()</code>. Thus, if <code>o</code>
is true, then <code>strncpy(t[l], t[--s], 5)</code>, which overwrites the current word
with the end of the list, and decrements the number of entries. Note the
end of the list is not <code>t[s]</code>, but <code>t[s-1]</code>, thus the pre-decrement achieves
both the desired index and decrements the count.</p>
<h2 id="word-selection">Word Selection</h2>
<p>After the words are filtered, then comes the hard part: selecting which word
to guess next! This is handled by <code>E()</code>, by far the most interesting routine
in the code.</p>
<p>The loops in order:</p>
<ul>
<li>init <code>c[]</code> with ordinal values (a-z plus 6 slop) in the lower 5 bits</li>
<li>in the higher order bits, count the frequency of all remaining letters</li>
<li>sort by the frequency of letters in 3 sets of 7 (21 not 26!)</li>
<li>merge the separate sorts into one via <code>v()</code> (see below on merging)</li>
<li>for letters with same freq, re-sort by English frequency values <code>l()</code></li>
<li>using the sorted list, put the corresponding ordinals into <code>b[]</code></li>
<li>for each word, if scores below 97, put into <code>e[][]</code> (low score wins)</li>
<li>allow up to 5 duplicate scores, only keep first 5 words with same score</li>
<li>search the list for the lowest scoring word (best match)</li>
<li>if more than 2 words remain, find first word without duplicate letters</li>
<li>return whatever we found, if anything</li>
</ul>
<p>The weakness in this scheme is at least two-fold: 1) if there are more than 5
words with the same score, words that come later are not selected, and 2) all
words with the same score are considered the same, without regard to letter
order or letter frequencies. I have run no experiments here,
but I think this is an area for minor improvement.</p>
<h2 id="network-sort">Network Sort</h2>
<p>Network sort<a href="https://en.wikipedia.org/wiki/Sorting_network">1</a> is a simple, compact, constant time sort with minimal compares.</p>
<p>It is obfuscated in a number of ways.
The routine <code>S()</code> looks into the array <code>a[]</code> and determines the number of
elements to sort via the differences in the initial values of <code>a[]</code>.
Consider the possible number of values given in <code>O</code> from 0 (zero) to 7:
if <code>O</code> is 0 or 1, no sort is necessary (an empty list cannot be sorted,
and a list of one does not need to be sorted):</p>
<pre><code> O+1 O
a[1] - a[0] = 9 - 9 = 0
a[2] - a[1] = 9 - 9 = 0
a[3] - a[2] = 10 - 9 = 1</code></pre>
<p>If <code>O</code> is 2, then only one compare and swap is necessary. Why is this?
Consider how many compares are necessary to determine the proper order of
variables A and B: one test and one possible swap are all that is needed.
If A &gt; B, swap. If A &lt;= B, then no swap and the list is already ordered.</p>
<p>Where is this calculation done? Address arithmetic FTW!
<code>e = a[-~O] + s - a[O]</code>. <code>-~O</code> is the identity <code>O+1</code>.
So this expression restated is <code>e = s + a[O+1] - a[O]</code>.
If you look at the first numbers in <code>a[]</code> and their offsets (table 1):</p>
<pre><code> table 1
offsets: 0 1 2 3 4 5 6 7 8
values : 9 9 9 10 13 18 27 39 55</code></pre>
<p>You can see the expression selects for O in 0..7 (table 2):</p>
<pre><code> // table 2
a[0+1] - a[0] = 9 - 9 = 0 // 0 selects no compares
a[1+1] - a[1] = 9 - 9 = 0 // 1 selects no compares
a[2+1] - a[2] = 10 - 9 = 1 // 2 selects 1 compare
a[3+1] - a[3] = 13 - 10 = 3 // 3 selects 3 compares
a[4+1] - a[4] = 18 - 13 = 5 // 4 selects 5 compares
a[5+1] - a[5] = 27 - 18 = 9 // 5 selects 9 compares
a[6+1] - a[6] = 39 - 27 = 12 // 6 selects 12 compares
a[7+1] - a[7] = 55 - 39 = 16 // 7 selects 16 compares</code></pre>
<p>What are we comparing? <code>s</code> ranges from <code>&amp;a[a[O]]</code> to <code>&amp;a[a[O+1]]</code>.</p>
<pre><code> O expr
0 9 - 9 = 0: no compares
1 9 - 9 = 0: no compares
2 10 - 9 = 1: &amp;a[a[9]] + 1 or the set { 1 }
3 13 - 10 = 3: &amp;a[a[10]] + 3 or the set { 18, 2, 1 }
4 18 - 13 = 5: &amp;a[a[13]] + 5 or the set { 1, 35, 2, 19, 18 }
...
7 55 - 39 = 16: &amp;a[a[39]] + 16 or the set
{ 18, 52, 86, 2, 53, 70, 1, 69,
38, 4, 21, 3, 37, 19, 36, 35 }</code></pre>
<p>So what do these numbers mean for sorting? Translate them to hex:</p>
<pre><code> { 0x12, 0x34, 0x56, 0x02, 0x35, 0x46, 0x01, 0x45,
0x26, 0x04, 0x15, 0x03, 0x25, 0x13, 0x24, 0x23 }</code></pre>
<p>This compact arrangement can be split into high and low nibbles:</p>
<pre><code> { (1,2), (3,4), (5,6), (0,2), (3,5), ... }</code></pre>
<p>which means compare and maybe swap (keeping order low to high):</p>
<pre><code> if (s[1] &gt; s[2]) swap();
if (s[3] &gt; s[4]) swap();
if (s[5] &gt; s[6]) swap();
if (s[0] &gt; s[2]) swap();
if (s[3] &gt; s[5]) swap();
....</code></pre>
<p>For instance, when sorting 7 elements, start from <code>a[a[7]]</code> (39) until
<code>a[a[8]]</code> (55), or 16 elements starting at <code>a[39]</code>.</p>
<h2 id="the-array-a-explained-finally">The Array a[] Explained, Finally</h2>
<p>The first set of 9 numbers are both the lengths AND the start and end indexes
into the array <code>a[]</code>, as explained above in the Network Sort. The second set
of numbers runs from indexes 9 through 54, and are the Network Sort comparison
values. At index 55 begins the third part of the array. The final 26 values
encode the inverse frequency of letters in the English language. The first
value is 25 for the letter ‘a’. If you count, then 26 is for the letter ‘e’,
the most common letter in the English language
(or at least for the dictionary used).</p>
<p>This final set of letters is used in the routine <code>l()</code>. Notice the expression:
<code>a[f[j] + a[~-9]]</code>; <code>a[~-9]</code> is an obscure way of writing <code>a[8]</code>, which is the
value 55. So <code>a[f[j]]</code> is an index into this frequency array, offset by 55.
Why is there a <code>&lt;&lt; 5</code>? Again, the letter offsets are in the lowest 5 bits,
and the counts of the letters is in the upper bits.</p>
<p>The reason for putting the counts in the upper bits is simple: it makes the
value larger for larger counts. Thus, sorting based upon frequency is natural.</p>
<h2 id="merge-sort">Merge Sort</h2>
<p>The network sort as implemented handles up to 7 letters in 3 calls.
26 / 7 = 3 r 5. Thus, we need to merge up to 4 sets of 7 letters.
This merge is handled by <code>v()</code>, which calls <code>o()</code> 3 times, once recursively.
This is followed by a rearrangement handled in <code>l()</code>. This last step finds
all the letters with the same frequency, then sorts them according to their
English frequency.</p>
<p>Back to the merge sort, which is really handled in <code>o()</code>. This is rather
difficult to follow. The first call to <code>o()</code> is within the argument list
to the second call. The first call has arguments
W=0, O=1, R=0, D=a, L=w, E=k; and <code>v()</code> was called with <code>v(l=7, w=b, k=c)</code>.
This first merge pass copies to: <code>L[R]</code> (<code>b[0]</code>) and selects from either
<code>c[o]</code> or <code>c[w]</code> depending upon which is greater or equal. That is, it merges
two 7 letter arrays into a single 14 element array twice, then it merges two 14
element arrays into a single 28 element array. Note on the third call
to <code>o()</code>, the last two arguments are swapped, which puts the final sort
into <code>c[]</code>.</p>
<p>Below is a hopefully easier way to read the merge sort code:</p>
<pre><code> // merge residual
int e(int mult_o, int start, int to, int off, int *a1, int *a2)
{
for (; start &lt; off; )
a1[to++] = a2[start++ + off * mult_o];
return to;
}
// merge sort
int o(int mult_o, int mult_w, int to, int len, int *a1, int *a2)
{
// first call: mult_o = 0, mult_w = 1, to = 0, len = 7, a1=b, a2=c
// second: mult_o = 2, mult_w = 3, to = N, len = 7, a1=b, a2=c
// third: mult_o = 0, mult_w = 1, to = 0, len = 14, a1=c, a2=b
int o = 0, w = 0;
for (; o &lt; len &amp;&amp; w &lt; len; ) {
// first call: to=0, len=7, mult_o=0, mult_w=1
// a1[0++] = a2[0 + 7*0] &gt;= a2[0 + 7*1]
// ? a2[0 + 7*0] : a2[0 + 7*1]
// a1[0++] = a2[0] &gt; a2[7] ? a[0++] : a2[7++]
// second call: to=14, len=7, mult_o=2, mult_w=3
// a1[14++] = a2[0 + 7*2] &gt;= a2[0 + 7*3]
// ? a2[0 + 7*2] : a2[0 + 7*3]
// a1[14++] = a2[14] &gt; a2[21] ? a2[14++] : a2[21++]
// third call: to=0, len=14, mult_o=0, mult_w=1
// a1[0++] = a2[0 + 14*0] &gt;= a2[0 + 14*1]
// ? a2[0 + 14*0] : a2[0 + 14*1];
// a1[0++] = a[0] &gt;= a2[14] ? a2[0++] : a2[14++]
a1[to++] = a2[o + len * mult_o] &gt;= a2[w + len * mult_w]
? a2[o++ + len * mult_o]
: a2[w++ + len * mult_w]
}
int n = e(mult_o, o, to, len, a1, a2);
return e(mult_w, w, n, len, a1, a2);
}</code></pre>
<p>Isn’t that as clear as mud? It is far easier to understand the flow when
you add <code>printf()</code> statements to follow the logic. Included is the file
<code>deobfuscated.c</code> for you to start with.</p>
<h2 id="why-name-the-functions-as-they-are">Why name the functions as they are?</h2>
<p>Run <code>make spoiler</code>.</p>
<h2 id="bug-explained">Bug Explained</h2>
<p>If you followed the above explanation, you will notice that only the first
21 of 26 letters are sorted (ignoring { v, w, x, y, z }. But 28 of 26 letters
are merge sorted. It was observed the program performs worse if all 26 letters
are sorted (which tends to over-favor <code>y</code>).
But some words do have <code>y</code> in them, which is why the merge sort takes 28
letters (including the ASCII values <code>{</code> and <code>|</code>), so all letters are still
selected when appropriate. The merge sort puts the { v, w, x, y, z }
letters into the proper frequency set, eventually.</p>
<p>If you change the loop value <code>22</code> to <code>32</code>, then all letters are sorted,
the program will work correctly, but it will change the performance
of the algorithm to be slightly worse than the original.</p>
<p>If you change any values in the array <code>a[]</code>,
the program will no longer work correctly.</p>
<p>Finally, what is the significance of the array size 78557?
<code>usdw</code> only needs 8506. Why was such a large value used?
<!-- AFTER: last line of markdown file: 2024/burton/deobfuscated.md --></p>
<!-- END: this line ends content for HTML phase 21 by: bin/pandoc-wrapper.sh via bin/md2html.sh -->
<!-- START: this line starts content from: inc/after-content.default.html -->
</div>
<!-- END: this line ends content from: inc/after-content.default.html -->
<!-- START: this line starts content from: inc/footer.default.html -->
<!--
Copyright © 1984-2025 by Landon Curt Noll. All Rights Reserved.
You are free to share and adapt this file under the terms of this license:
Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
For more information, see:
https://creativecommons.org/licenses/by-sa/4.0/
-->
<div class="footer">
<div id="copyright"><h3>Copyright &copy; 1984-2025 by Landon Curt Noll:
<a href="https://creativecommons.org/faq/#what-does-some-rights-reserved-mean"
target="_blank"
rel="license noopener noreferrer">Some Rights Reserved</a></h3>
<p>
This work is <b>licensed by Landon Curt Noll</b> under
<b><a href="https://creativecommons.org/licenses/by-sa/4.0/?ref=chooser-v1"
target="_blank"
rel="license noopener noreferrer"
style="display:inline-block;">CC BY-SA 4.0</a></b>.
<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;"
src="../../png/cc.png"
alt="cc inside circle symbol">
<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;"
src="../../png/by.png"
alt="person inside circle symbol">
<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;"
src="../../png/sa.png"
alt="arrow looping back on itself inside circle symbol"><br>
You should <b>carefully review</b> the
<b><a href="https://creativecommons.org/licenses/by-sa/4.0/legalcode.en"
target="_blank"
rel="license noopener noreferrer">CC BY-SA 4.0 LEGAL CODE</a></b>
before using the licensed material.<br>
You may wish to review the
<b><a href="../../license.html"
target="_blank"
rel="license noopener noreferrer">highlights of some of the key features and terms</a></b>
of <b>CC BY-SA 4.0</b>.<br>
</p>
</div>
<div id="coda"><h3>Coda</h3>
<p>
<a href="https://validator.w3.org/nu/?doc=https%3A%2F%2Fwww.ioccc.org%2F2024%2Fburton%2Fdeobfuscated.html" rel="nofollow">Nu HTML check this web page</a><br>
<a href="#top">Jump to top</a> <a href="#content">Jump to Content</a>
</p>
</div>
</div>
<!-- END: this line ends content from: inc/footer.default.html -->
<!-- START: this line starts content from: inc/bottom.default.html -->
</body>
</html>
<!-- END: this line ends content from: inc/bottom.default.html -->
+382
View File
@@ -0,0 +1,382 @@
# Do not read this if you want to figure it out yourself! #
The file `deobfuscated.c` is simply a pretty-printed version of
`prog.c` with the macros expanded. It is hopefully just as
difficult to understand as the original presentation:
obfuscation is not just presentation, after all.
If you read further, the remainder talks about how the code
works. If you want to figure it out yourself, stop reading here
and do look at `deobfuscated.c`.
## Read Dictionary ##
The program begins by reading junk ;-) from the dictionary and processing
via `D()`. The words are stored in `t[][]`, counted by `s` and `D()` ensures
this never has buffer overflow. Notice the swap of index and array in `s[t]`.
It also ensures only 5 letter words are selected from the dictionary,
and folds uppercase to lowercase. Each call of `D()` handles one word.
NB: if the dictionary has, for instance, "ARRAY" and "array" and "Array",
there will be 3 copies of "array" stored in `t[][]`.
This will degrade performance and may cause a malfunction of the algorithm,
and definitely if the target word is "array". Thus the `makefile` builds
`usdw` with capital letters removed (avoiding this problem completely).
However, if there are duplicate words in the dictionary, use another.
## User Input ##
After the dictionary is read, it is time to prompt the user for the
input guess. This happens in `main()`. The input buffer is the upper
half of `i.a`, which is never used (ASCII lowercase letters only).
The newline is ignored by `n - 10`, at most 11 characters (`l < j`)
can be input, and EOF terminates the input loop.
Here is the explanation of how `i.c - 97` works to get an input buffer.
Assume `struct i` has the address 1000, then `i.a` begins at 1000.
`i.b` begins at 1257. `i.c` begins at 9*5 = 45 + 1257 = 1302.
1302 - 97 = 1205. Therefore, the `i.c - 97` sets the input buffer at
`&i.a[205]`, ASCII lowercase input ends at `i.a[123]`, and there are
53 characters before `i.b` is overwritten - plenty of room for the user IO.
Why 97? The expression `x - 97` is used several times in the code,
leading to some confusion on the reason for the value.
Note that this abuse is what draws a compiler complaint, requiring:
```
-Wno-array-bounds-pointer-arithmetic
```
It is safe. It works. There is no problem here, but the compiler
does not know this.
## Process User Input ##
Once the user input is received, the program calls `r()`, which populates
`struct i` with the guesses. This serves to set the patterns known
and impossible, as well as letters used and required (remember, this is
hard mode Wordle, which requires you to use all green letters in the
same place for all subsequent guesses). `r()` returns the next word
to guess via `E()` (aka choose() - see below).
Within `r()`, it is important to understand the ASCII values for g, y and b.
```
b 98
g 103
y 121
```
Within `r()`, `n` is the guessed letter, and `o` is color - 103.
If `o` is greater than zero (121-103), this guess is correct, but
incorrect location (yellow). If it is less than zero (98-103),
this guess is not possible (black). If it is zero (103-103),
it is the correct letter in the correct location (green).
`struct i` can be re-written this way:
``` <!---c-->
struct {
char black[256]; // wrong letters guessed so far
char words[9][5]; // words used in guesses so far
char green[5]; // correct letters (must reuse these)
char yellow[5]; // incorrect letters (impossible positions)
int num_guesses;
int num_yellow;
} i;
```
And `r()` can be rewritten:
``` <!---c-->
char *r(char *input) {
int num_green = 0, l = 0;
for (int j = 0; j < 5; ++j) {
char n = input[j];
signed char o = input[j + 6] - 103; // j - ~5 = j - -6 == j + 6
// if this is a correct guess
if (o == 0) {
i.green[j] = n;
i.black[n] = 0; // remove this from black
++num_green;
} else {
i.words[i.num_guesses][j] = n;
// if not already a yellow letter
if (memchr(i.yellow, n, 5) == NULL) {
l = n;
if (o < 0) i.black[n] = n; // black, impossible
else i.yellow[i.num_yellow++] = n;
}
}
}
// l is 0 if all letters correct, else l = n
i.num_guesses += l > 0 ? 1 : 0;
filter(input, num_green);
return choose("xyzzy");
}
```
## Filter ##
The next step is to remove the impossible words from the in-memory dictionary.
This is handled by `L()`. `L()` iterates over all the remaining words in the
dictionary. `L()` runs a number of filters, and if the word is plausible,
it is skipped, and if impossible, the current word is replaced from the end
of the list. This prevents holes from appearing in the dictionary, and keeps
the count accurate. The important action is carried out in the expression
`o ? W(t[l], t[--s]) : ++l`. That is, if `o` is true, the word is replaced
by the end of the list, else the word is skipped. Note in particular the
expressions `--s` and `++l` (decrement and replace, or skip, respectively).
Said another way, think of `W()` as an analog of `strncpy()`. Thus, if `o`
is true, then `strncpy(t[l], t[--s], 5)`, which overwrites the current word
with the end of the list, and decrements the number of entries. Note the
end of the list is not `t[s]`, but `t[s-1]`, thus the pre-decrement achieves
both the desired index and decrements the count.
## Word Selection ##
After the words are filtered, then comes the hard part: selecting which word
to guess next! This is handled by `E()`, by far the most interesting routine
in the code.
The loops in order:
- init `c[]` with ordinal values (a-z plus 6 slop) in the lower 5 bits
- in the higher order bits, count the frequency of all remaining letters
- sort by the frequency of letters in 3 sets of 7 (21 not 26!)
- merge the separate sorts into one via `v()` (see below on merging)
- for letters with same freq, re-sort by English frequency values `l()`
- using the sorted list, put the corresponding ordinals into `b[]`
- for each word, if scores below 97, put into `e[][]` (low score wins)
- allow up to 5 duplicate scores, only keep first 5 words with same score
- search the list for the lowest scoring word (best match)
- if more than 2 words remain, find first word without duplicate letters
- return whatever we found, if anything
The weakness in this scheme is at least two-fold: 1) if there are more than 5
words with the same score, words that come later are not selected, and 2) all
words with the same score are considered the same, without regard to letter
order or letter frequencies. I have run no experiments here,
but I think this is an area for minor improvement.
## Network Sort ##
Network sort[1] is a simple, compact, constant time sort with minimal compares.
It is obfuscated in a number of ways.
The routine `S()` looks into the array `a[]` and determines the number of
elements to sort via the differences in the initial values of `a[]`.
Consider the possible number of values given in `O` from 0 (zero) to 7:
if `O` is 0 or 1, no sort is necessary (an empty list cannot be sorted,
and a list of one does not need to be sorted):
```
O+1 O
a[1] - a[0] = 9 - 9 = 0
a[2] - a[1] = 9 - 9 = 0
a[3] - a[2] = 10 - 9 = 1
```
If `O` is 2, then only one compare and swap is necessary. Why is this?
Consider how many compares are necessary to determine the proper order of
variables A and B: one test and one possible swap are all that is needed.
If A > B, swap. If A <= B, then no swap and the list is already ordered.
Where is this calculation done? Address arithmetic FTW!
`e = a[-~O] + s - a[O]`. `-~O` is the identity `O+1`.
So this expression restated is `e = s + a[O+1] - a[O]`.
If you look at the first numbers in `a[]` and their offsets (table 1):
```
table 1
offsets: 0 1 2 3 4 5 6 7 8
values : 9 9 9 10 13 18 27 39 55
```
You can see the expression selects for O in 0..7 (table 2):
``` <!---c-->
// table 2
a[0+1] - a[0] = 9 - 9 = 0 // 0 selects no compares
a[1+1] - a[1] = 9 - 9 = 0 // 1 selects no compares
a[2+1] - a[2] = 10 - 9 = 1 // 2 selects 1 compare
a[3+1] - a[3] = 13 - 10 = 3 // 3 selects 3 compares
a[4+1] - a[4] = 18 - 13 = 5 // 4 selects 5 compares
a[5+1] - a[5] = 27 - 18 = 9 // 5 selects 9 compares
a[6+1] - a[6] = 39 - 27 = 12 // 6 selects 12 compares
a[7+1] - a[7] = 55 - 39 = 16 // 7 selects 16 compares
```
What are we comparing? `s` ranges from `&a[a[O]]` to `&a[a[O+1]]`.
```
O expr
0 9 - 9 = 0: no compares
1 9 - 9 = 0: no compares
2 10 - 9 = 1: &a[a[9]] + 1 or the set { 1 }
3 13 - 10 = 3: &a[a[10]] + 3 or the set { 18, 2, 1 }
4 18 - 13 = 5: &a[a[13]] + 5 or the set { 1, 35, 2, 19, 18 }
...
7 55 - 39 = 16: &a[a[39]] + 16 or the set
{ 18, 52, 86, 2, 53, 70, 1, 69,
38, 4, 21, 3, 37, 19, 36, 35 }
```
So what do these numbers mean for sorting? Translate them to hex:
``` <!---c-->
{ 0x12, 0x34, 0x56, 0x02, 0x35, 0x46, 0x01, 0x45,
0x26, 0x04, 0x15, 0x03, 0x25, 0x13, 0x24, 0x23 }
```
This compact arrangement can be split into high and low nibbles:
```
{ (1,2), (3,4), (5,6), (0,2), (3,5), ... }
```
which means compare and maybe swap (keeping order low to high):
``` <!---c-->
if (s[1] > s[2]) swap();
if (s[3] > s[4]) swap();
if (s[5] > s[6]) swap();
if (s[0] > s[2]) swap();
if (s[3] > s[5]) swap();
....
```
For instance, when sorting 7 elements, start from `a[a[7]]` (39) until
`a[a[8]]` (55), or 16 elements starting at `a[39]`.
[1]: https://en.wikipedia.org/wiki/Sorting_network
## The Array a[] Explained, Finally ##
The first set of 9 numbers are both the lengths AND the start and end indexes
into the array `a[]`, as explained above in the Network Sort. The second set
of numbers runs from indexes 9 through 54, and are the Network Sort comparison
values. At index 55 begins the third part of the array. The final 26 values
encode the inverse frequency of letters in the English language. The first
value is 25 for the letter 'a'. If you count, then 26 is for the letter 'e',
the most common letter in the English language
(or at least for the dictionary used).
This final set of letters is used in the routine `l()`. Notice the expression:
`a[f[j] + a[~-9]]`; `a[~-9]` is an obscure way of writing `a[8]`, which is the
value 55. So `a[f[j]]` is an index into this frequency array, offset by 55.
Why is there a `<< 5`? Again, the letter offsets are in the lowest 5 bits,
and the counts of the letters is in the upper bits.
The reason for putting the counts in the upper bits is simple: it makes the
value larger for larger counts. Thus, sorting based upon frequency is natural.
## Merge Sort ##
The network sort as implemented handles up to 7 letters in 3 calls.
26 / 7 = 3 r 5. Thus, we need to merge up to 4 sets of 7 letters.
This merge is handled by `v()`, which calls `o()` 3 times, once recursively.
This is followed by a rearrangement handled in `l()`. This last step finds
all the letters with the same frequency, then sorts them according to their
English frequency.
Back to the merge sort, which is really handled in `o()`. This is rather
difficult to follow. The first call to `o()` is within the argument list
to the second call. The first call has arguments
W=0, O=1, R=0, D=a, L=w, E=k; and `v()` was called with `v(l=7, w=b, k=c)`.
This first merge pass copies to: `L[R]` (`b[0]`) and selects from either
`c[o]` or `c[w]` depending upon which is greater or equal. That is, it merges
two 7 letter arrays into a single 14 element array twice, then it merges two 14
element arrays into a single 28 element array. Note on the third call
to `o()`, the last two arguments are swapped, which puts the final sort
into `c[]`.
Below is a hopefully easier way to read the merge sort code:
``` <!---c-->
// merge residual
int e(int mult_o, int start, int to, int off, int *a1, int *a2)
{
for (; start < off; )
a1[to++] = a2[start++ + off * mult_o];
return to;
}
// merge sort
int o(int mult_o, int mult_w, int to, int len, int *a1, int *a2)
{
// first call: mult_o = 0, mult_w = 1, to = 0, len = 7, a1=b, a2=c
// second: mult_o = 2, mult_w = 3, to = N, len = 7, a1=b, a2=c
// third: mult_o = 0, mult_w = 1, to = 0, len = 14, a1=c, a2=b
int o = 0, w = 0;
for (; o < len && w < len; ) {
// first call: to=0, len=7, mult_o=0, mult_w=1
// a1[0++] = a2[0 + 7*0] >= a2[0 + 7*1]
// ? a2[0 + 7*0] : a2[0 + 7*1]
// a1[0++] = a2[0] > a2[7] ? a[0++] : a2[7++]
// second call: to=14, len=7, mult_o=2, mult_w=3
// a1[14++] = a2[0 + 7*2] >= a2[0 + 7*3]
// ? a2[0 + 7*2] : a2[0 + 7*3]
// a1[14++] = a2[14] > a2[21] ? a2[14++] : a2[21++]
// third call: to=0, len=14, mult_o=0, mult_w=1
// a1[0++] = a2[0 + 14*0] >= a2[0 + 14*1]
// ? a2[0 + 14*0] : a2[0 + 14*1];
// a1[0++] = a[0] >= a2[14] ? a2[0++] : a2[14++]
a1[to++] = a2[o + len * mult_o] >= a2[w + len * mult_w]
? a2[o++ + len * mult_o]
: a2[w++ + len * mult_w]
}
int n = e(mult_o, o, to, len, a1, a2);
return e(mult_w, w, n, len, a1, a2);
}
```
Isn't that as clear as mud? It is far easier to understand the flow when
you add `printf()` statements to follow the logic. Included is the file
`deobfuscated.c` for you to start with.
## Why name the functions as they are? ##
Run `make spoiler`.
## Bug Explained ##
If you followed the above explanation, you will notice that only the first
21 of 26 letters are sorted (ignoring { v, w, x, y, z }. But 28 of 26 letters
are merge sorted. It was observed the program performs worse if all 26 letters
are sorted (which tends to over-favor `y`).
But some words do have `y` in them, which is why the merge sort takes 28
letters (including the ASCII values `{` and `|`), so all letters are still
selected when appropriate. The merge sort puts the { v, w, x, y, z }
letters into the proper frequency set, eventually.
If you change the loop value `22` to `32`, then all letters are sorted,
the program will work correctly, but it will change the performance
of the algorithm to be slightly worse than the original.
If you change any values in the array `a[]`,
the program will no longer work correctly.
Finally, what is the significance of the array size 78557?
`usdw` only needs 8506. Why was such a large value used?
+2315
View File
File diff suppressed because it is too large. Load diff
+29
View File
@@ -0,0 +1,29 @@
/*
* usage: dups [-n] [len]
*/
#include <stdio.h>
#include <stdlib.h>
int main(int ac, char **av)
{
char buf[32];
int no = ac > 1 && av[1][0] == '-' && av[1][1] == 'n' && ++av && --ac;
int n = ac > 1 ? atoi(av[1]) : 5;
while (fgets(buf, sizeof buf, stdin)) {
char d[256] = {0};
int skip = 0;
if (buf[n] != '\n')
continue;
for (int i = 0; i < n; ++i) {
int c = buf[i] |= 32;
if (++d[c] > 1)
++skip;
}
if (!!skip != no)
printf("%s", buf);
}
return 0;
}
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# amount of parallelism
p=8
dir=$1
: ${dir:=results}
mkdir -p $dir
start_words=$2
: ${start_words:=start_words}
dict=$3
: ${dict:=dict}
rm -fr ./tmp
mkdir -p tmp
# split dict into smaller chucks for some parallelism
n=1
cat $start_words | ./dups -n | while read a; do
echo $a >> tmp/$n
((n+=1))
test $n -gt $p && n=1
done
# run the experiment (expensive)
n=1
while test $n -le $p; do
./word.result $dir $dict < tmp/$n 2>/dev/null &
((n+=1))
done
rm -fr ./tmp
wait
+887
View File
@@ -0,0 +1,887 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<!-- START: two lines up starts content from: inc/top.default.html -->
<!-- END: this line ends content from: inc/top.default.html -->
<!-- START: this line starts content from: inc/head.default.html -->
<head>
<link rel="stylesheet" href="../../ioccc.css">
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap" rel="stylesheet">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>The International Obfuscated C Code Contest</title>
<link rel="icon" type="image/x-icon" href="../../favicon.ico">
<meta name="description" content="extra.md for 2024/burton">
<meta name="keywords" content="IOCCC, 2024/burton, extra.md">
</head>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<!-- !!! DO NOT MODIFY THIS FILE - This file is generated by a tool !!! -->
<!-- !!! DO NOT MODIFY THIS FILE - This file is generated by a tool !!! -->
<!-- !!! DO NOT MODIFY THIS FILE - This file is generated by a tool !!! -->
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<!-- END: this line ends content from: inc/head.default.html -->
<!-- -->
<!-- This web page was formed via the tool: bin/gen-other-html.sh -->
<!-- The content of main section of this web page came from: 2024/burton/extra.md -->
<!-- -->
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<!-- !!! Do not modify this web page, instead modify the file: 2024/burton/extra.md !!! -->
<!-- !!! Do not modify this web page, instead modify the file: 2024/burton/extra.md !!! -->
<!-- !!! Do not modify this web page, instead modify the file: 2024/burton/extra.md !!! -->
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<!-- Markdown content was converted into HTML via the tool: bin/md2html.sh -->
<!-- START: this line starts content from: inc/body.default.html -->
<body>
<!-- END: this line ends content from: inc/body.default.html -->
<!-- START: this line starts content from: inc/topbar.default.html -->
<div class="theader">
<nav class="topbar">
<div class="container">
<div class="logo">
<a href="../../index.html" class="logo-link">
IOCCC
</a>
</div>
<div class="topbar-items">
<div class="item">
<span class="item-header">
Entries
</span>
<div class="sub-item">
<div class="outfit-font">
<a href="../../years.html" class="sub-item-link">
Winning entries
</a>
</div>
<div class="outfit-font">
<a href="../../authors.html" class="sub-item-link">
Winning authors
</a>
</div>
<div class="outfit-font">
<a href="../../location.html" class="sub-item-link">
Location of authors
</a>
</div>
<div class="outfit-font">
<a href="../../bugs.html" class="sub-item-link">
Bugs and (mis)features
</a>
</div>
<div class="outfit-font">
<a href="../../faq.html#fix_an_entry" class="sub-item-link">
Fixing entries
</a>
</div>
<div class="outfit-font">
<a href="../../faq.html#fix_author" class="sub-item-link">
Updating author info
</a>
</div>
</div>
</div>
<div class="item">
<span class="item-header">
Status
</span>
<div class="sub-item">
<div class="outfit-font">
<a href="../../news.html" class="sub-item-link">
News
</a>
</div>
<div class="outfit-font">
<a href="../../status.html" class="sub-item-link">
Contest status
</a>
</div>
<div class="outfit-font">
<a href="../../next/rules.html" class="sub-item-link">
IOCCC rules
</a>
</div>
<div class="outfit-font">
<a href="../../next/guidelines.html" class="sub-item-link">
IOCCC guidelines
</a>
</div>
<div class="outfit-font">
<a href="../../markdown.html" class="sub-item-link">
Markdown guidelines
</a>
</div>
<div class="outfit-font">
<a href="../../SECURITY.html" class="sub-item-link">
Security policy
</a>
</div>
</div>
</div>
<div class="item">
<span class="item-header">
FAQ
</span>
<div class="sub-item">
<div class="outfit-font">
<a href="../../faq.html" class="sub-item-link">
Frequently asked questions
</a>
</div>
<div class="outfit-font">
<a href="../../quick-start.html#enter" class="sub-item-link">
Enter the IOCCC
</a>
</div>
<div class="outfit-font">
<a href="../../faq.html#compiling" class="sub-item-link">
Compiling entries
</a>
</div>
<div class="outfit-font">
<a href="../../faq.html#running_entries" class="sub-item-link">
Running entries
</a>
</div>
<div class="outfit-font">
<a href="../../faq.html#help" class="sub-item-link">
How to help
</a>
</div>
</div>
</div>
<div class="item">
<span class="item-header">
About
</span>
<div class="sub-item">
<div class="outfit-font">
<a href="../../index.html" class="sub-item-link">
Home page
</a>
</div>
<div class="outfit-font">
<a href="../../social.html" class="sub-item-link">
IOCCC social media
</a>
</div>
<div class="outfit-font">
<a href="../../about.html" class="sub-item-link">
About the IOCCC
</a>
</div>
<div class="outfit-font">
<a href="../../judges.html" class="sub-item-link">
The judges
</a>
</div>
<div class="outfit-font">
<a href="../../thanks-for-help.html" class="sub-item-link">
Thanks for the help
</a>
</div>
<div class="outfit-font">
<a href="../../contact.html" class="sub-item-link">
Contact us
</a>
</div>
</div>
</div>
</div>
</div>
</nav>
<div class="header-mobile-menu">
<noscript>
<a href="../../nojs-menu.html" class="topbar-js-label">
Please Enable JavaScript
</a>
</noscript>
<button id="header-open-menu-button" class="topbar-mobile-menu">
<img
src="../../png/hamburger-icon-open.png"
alt="hamburger style menu icon - open state"
width=48
height=48>
</button>
<button id="header-close-menu-button" class="hide-content">
<img
src="../../png/hamburger-icon-closed.png"
alt="hamburger style menu icon - closed state"
width=48
height=48>
</button>
<div id="mobile-menu-panel" class="hide-content">
<div class="mobile-menu-container">
<div class="mobile-menu-wrapper">
<div class="mobile-menu-item">
Entries
</div>
<div class="mobile-submenu-wrapper">
<a class="mobile-submenu-item" href="../../years.html">
Winning entries
</a>
<a class="mobile-submenu-item" href="../../authors.html">
Winning authors
</a>
<a class="mobile-submenu-item" href="../../location.html">
Location of authors
</a>
<a class="mobile-submenu-item" href="../../bugs.html">
Bugs and (mis)features
</a>
<a class="mobile-submenu-item" href="../../faq.html#fix_an_entry">
Fixing entries
</a>
<a class="mobile-submenu-item" href="../../faq.html#fix_author">
Updating author info
</a>
<a class="mobile-submenu-item" href="../../thanks-for-help.html">
Thanks for the help
</a>
</div>
</div>
<div class="mobile-menu-wrapper">
<div class="mobile-menu-item">
Status
</div>
<div class="mobile-submenu-wrapper">
<a class="mobile-submenu-item" href="../../news.html">
News
</a>
<a class="mobile-submenu-item" href="../../status.html">
Contest status
</a>
<a class="mobile-submenu-item" href="../../next/rules.html">
IOCCC rules
</a>
<a class="mobile-submenu-item" href="../../next/guidelines.html">
IOCCC guidelines
</a>
<a class="mobile-submenu-item" href="../../markdown.html">
Markdown guidelines
</a>
<a class="mobile-submenu-item" href="../../SECURITY.html">
Security policy
</a>
</div>
</div>
<div class="mobile-menu-wrapper">
<div class="mobile-menu-item">
FAQ
</div>
<div class="mobile-submenu-wrapper">
<a class="mobile-submenu-item" href="../../faq.html">
Frequently asked questions
</a>
<a class="mobile-submenu-item" href="../../quick-start.html#enter">
Enter the IOCCC
</a>
<a class="mobile-submenu-item" href="../../faq.html#compiling">
Compiling entries
</a>
<a class="mobile-submenu-item" href="../../faq.html#running_entries">
Running entries
</a>
<a class="mobile-submenu-item" href="../../faq.html#help">
How to help
</a>
</div>
</div>
<div class="mobile-menu-wrapper">
<div class="mobile-menu-item">
About
</div>
<div class="mobile-submenu-wrapper">
<a class="mobile-submenu-item" href="../../index.html">
Home page
</a>
<a class="mobile-submenu-item" href="../../social.html">
IOCCC social media
</a>
<a class="mobile-submenu-item" href="../../about.html">
About the IOCCC
</a>
<a class="mobile-submenu-item" href="../../judges.html">
The judges
</a>
<a class="mobile-submenu-item" href="../../contact.html">
Contact us
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
var headerOpenMenuButton = document.getElementById("header-open-menu-button");
var headerCloseMenuButton = document.getElementById("header-close-menu-button");
var mobileMenuPanel = document.getElementById("mobile-menu-panel");
headerOpenMenuButton.addEventListener("click", () => {
headerOpenMenuButton.classList.remove("topbar-mobile-menu");
headerOpenMenuButton.classList.add("hide-content");
headerCloseMenuButton.classList.remove("hide-content");
headerCloseMenuButton.classList.add("topbar-mobile-menu");
mobileMenuPanel.classList.remove("hide-content");
mobileMenuPanel.classList.add("topbar-mobile-panel");
});
headerCloseMenuButton.addEventListener("click", () => {
headerCloseMenuButton.classList.remove("topbar-mobile-menu");
headerCloseMenuButton.classList.add("hide-content");
mobileMenuPanel.classList.add("hide-content");
mobileMenuPanel.classList.remove("topbar-mobile-panel");
headerOpenMenuButton.classList.add("topbar-mobile-menu");
headerOpenMenuButton.classList.remove("hide-content");
});
</script>
<!-- END: this line ends content from: inc/topbar.default.html -->
<!-- START: this line starts content from: inc/header.default.html -->
<div class="header">
<a href="../../2011/zucker/index.html">
<img src="../../png/ioccc.png"
alt="IOCCC image by Matt Zucker"
width=300
height=110>
</a>
<h1>The International Obfuscated C Code Contest</h1>
<h2>extra.html for 2024/burton</h2>
</div>
<!-- END: this line ends content from: inc/header.default.html -->
<!-- START: this line starts content from: inc/navbar.up2index.html -->
<div class="navbar">
<a class="Left" href="index.html">&uarr; jump to 2024/burton/index.html &uarr;</a>
<a class="Right" href="https://validator.w3.org/nu/?doc=https%3A%2F%2Fwww.ioccc.org%2F2024%2Fburton%2Fextra.html">&#x2713;</a>
</div>
<!-- END: this line ends content from: inc/navbar.up2index.html -->
<!-- START: this line starts content from: inc/before-content.default.html -->
<div class="content" id="content">
<!-- END: this line ends content from: inc/before-content.default.html -->
<!-- START: this line starts content for HTML phase 21 by: bin/pandoc-wrapper.sh via bin/md2html.sh -->
<!-- BEFORE: 1st line of markdown file: 2024/burton/extra.md -->
<h1 id="extra">extra</h1>
<p>This readme provides a description of the extra files provided, how to use them,
analysis of their results, and a deeper investigation on the failed guesses.</p>
<p>The recommendation is to do:</p>
<pre><code> make tools
./run dict blast swept # exhaust runs this command a LOT!
./run dict crate swept # show the diff between starting words
mkdir results # create the results directory
./exhaust # exhaustive test of all words (~1 hour)
echo terns | ./word.result # add the word &quot;terns&quot; to results
./mkresults &gt; results.avg # proc exhaust into a single, small file</code></pre>
<p>The files created as output from <code>exhaust</code> allow one to view, for each
starting word, how each of the solutions resolve, including the misses.
<code>mkresults</code> outputs a 1566 line report showing the starting word, its
average accuracy, and the number of missed words. To show the number
of guesses and the actual missed words, run <code>rank</code> on the desired word:</p>
<pre><code> ./rank results/blast
1 1 # blast guessed itself
2 112 # 112 words guessed in 2 attempts
3 867 # 867 words guessed in 3 attempts
4 1019 # ...
5 271
6 41
foyer jaunt shave wound
./avg results/blast
blast 3.673002 4
grep blast results.avg
blast 3.673002 4</code></pre>
<p>The C programs allow for (automated) testing:</p>
<pre><code> gen.c - simulate wordle game by generating targets and scoring guesses
./gen dict [target]
dups.c - remove words with(out) repeated letters
(best starting words have no repeats)
cat dict | wc -l # all the words
cat dict | ./dups | wc -l # words with repeated letters
cat dict | ./dups -n | wc -l # words without repeated letters
run.c - `gen` and `prog` connected via pipes; allows automated testing
./run dict [start [target]]
start - the first guess
target - force the target to be the named word</code></pre>
<h2 id="using-more-guesses">Using More Guesses</h2>
<p>If you wish to see the failed words in action, and if more guesses would
work, try:</p>
<pre><code> ./run dict blast jaunt | grep ^gen
gen: target: jaunt
gen: blast bbybg
gen: daunt bgggg
gen: haunt bgggg
gen: gaunt bgggg
gen: vaunt bgggg
gen: taunt ygggg
gen result end=0: n 6 lose: jaunt
GUESSES=7 ./run dict blast jaunt | grep ^gen
gen: target: jaunt
gen: blast bbybg
gen: daunt bgggg
gen: haunt bgggg
gen: gaunt bgggg
gen: vaunt bgggg
gen: taunt ygggg
gen: jaunt ggggg
gen result end=2: y 7 good: jaunt</code></pre>
<p>Using <code>GUESSES=9</code> and <code>exhaust</code>, one gets a metric that scores words without
misses, allowing a better comparison on the average score. Note, however,
that the game doesn’t bend this way, and anything more than 6 guesses is a
loss. Take this metric with a grain of salt. Also, 9 is the limit of
guesses; the program will not work properly - if at all - with more than
9 guesses.</p>
<p>Ranking by fewest missed with GUESSES=6, the order is:</p>
<pre><code> blast palsy climb flash lapse</code></pre>
<p>Ranking by average with GUESSES=6, the order is:</p>
<pre><code> crate slate trace crane least</code></pre>
<p>Ranking by average with GUESSES=9, the order is:</p>
<pre><code> slate trace crate train least</code></pre>
<p>To reproduce these results:</p>
<pre><code> GUESSES=9 ./exhaust results.9
./mkresults results.9 &gt; results.9.avg</code></pre>
<h2 id="simple-analysis">Simple Analysis</h2>
<p>The 1566 raw files produced by <code>exhaust</code> are ~370KB each (total ~562MB),
far too large to submit, so included are the compressed results files for
four starting words, and the summary file <code>results.avg</code>.</p>
<pre><code> blast fewest missed
crate best average
query most missing
terns example used in remarks</code></pre>
<p>The program <code>rank</code> allows one to see how the averages are calculated, as well
as the actual missed words. Comparing the results for <code>blast</code> and <code>terns</code>:</p>
<pre><code> ./rank r.terns | awk &#39;NF==2&#39; &gt; a
./rank r.blast | awk &#39;NF==2&#39; &gt; b
join a b | column -t
guesses terns blast
-------- ----- -----
2 110 112
3 909 867
4 960 1019
5 265 271
6 53 41
F 18 4 (failed puzzles)</code></pre>
<p>In general, the program <code>avg</code> is easier to use, but conveys less information:</p>
<pre><code> ./avg r.terns; ./avg r.blast
terns 3.641469 18
blast 3.673002 4</code></pre>
<p>You have to decide if you prefer a marginally lower average, but slightly
higher chance of losing, versus a marginally higher average and lower chance
of losing. This is developed further in the following section.</p>
<p>It is interesting that the letter order makes a noticeable difference:</p>
<pre><code> ./avg r.terns; ./avg results/stern
terns 3.641469 18 # lower average and fewer misses
stern 3.658315 20</code></pre>
<p>For completeness, using <code>GUESSES=9</code> and the full starting dictionary from
Wordle(tm), here are the results for the “best” starting words: <code>salet</code>,
<code>slate</code>, and <code>blast</code>.</p>
<pre><code> guesses salet slate blast
------- ----- ----- -----
1 0 1 1
2 148 146 112
3 932 939 867
4 925 919 1019
5 240 235 271
6 56 57 41
--- ----- ----- -----
7 11 13 4
8 2 4 0
9 1 1 0
--- ----- ----- -----
avg 3.641 3.644 3.685</code></pre>
<p>Obviously, <code>salet</code> produces the best results, excepting the losses (14).
Just re-arranging the letters into <code>slate</code> produces almost the same,
but there are more losses (18). If attempting to minimize losses,
then <code>blast</code> is the optimal starting guess, since it never takes more
than 7 guesses and has the fewest losses (4) of any starting word in the
combined full Wordle(tm) dictionary.</p>
<p>NB: The averages for <code>GUESSES=9</code> are larger than the default,
because the misses are essentially folded into the average.
The averages are more accurately compared when using the higher limit,
although minimizing losses remains the priority (who likes to lose?).</p>
<h2 id="seek-these">Seek These</h2>
<p>The starting words with the fewest misses with <code>dict</code>:</p>
<pre><code> sort -k3n results.avg | sed 18q | awk &#39;{print $1, $3}&#39; | column -t | pr -t -3
blast 4 ralph 7 crawl 8
palsy 6 ranch 7 crumb 8
climb 7 shawl 7 grasp 8
flash 7 tramp 7 lymph 8
lapse 7 clash 8 spark 8
prowl 7 clasp 8 swamp 8
The starting words with the best accuracy:
sort -k2n results.avg | sed 18q | pr -t -3
crate 3.586609 20 irate 3.611231 31 trice 3.617279 15
slate 3.586609 18 slice 3.612095 22 cater 3.620302 26
trace 3.588769 18 stale 3.612959 19 satin 3.621598 19
crane 3.595248 26 train 3.613391 14 snail 3.621598 19
least 3.599568 19 trade 3.613823 20 stare 3.621598 19
plate 3.606479 22 leant 3.616415 18 dealt 3.622462 15
Note the word with fewest misses, `blast`, is relatively far
down the list in accuracy at 178th best:
sort -k2n results.avg | pr -t -n | grep blast
178 blast 3.673002 4</code></pre>
<p>Versus starting words with fewest misses:</p>
<pre><code> egrep &#39;terns|lapse|blast|climb|crate&#39; results.avg
blast 3.673002 4
climb 3.772354 7
crate 3.586609 20
lapse 3.664795 7
terns 3.641469 18
weighted avg
# blast crate blast crate improvement
- ----- ----- --------------- -----------
1 1 1 1.0000 1.0000 0.0000
2 112 147 1.9912 1.9932 -0.0020
3 867 952 2.8837 2.8645 -0.0192
4 1019 884 3.4527 3.3951 -0.0576
5 271 250 3.6374 3.5775 -0.0599
6 41 61 3.6794 3.6429 -0.0365
F 4 20 -16 +16 (0.26% blast vs 1.28% crate)</code></pre>
<p>Notice the difference in the average and weighted average. The program
<code>avg</code> simply takes the total guesses as a percentage of the total words.
The program <code>wavg</code> takes each of the six scores and weights them by the
score. I wrote <code>wavg</code> later to track my progress playing the online game,
and it is a more accurate score IMHO, but both are useful for comparisons.</p>
<pre><code> $ ./avg results/blast
blast 3.673002 4
$ ./rank results/blast
1 1
2 112
3 867
4 1019
5 271
6 41
foyer jaunt shave wound
$ ./wavg 1 112 867 1019 271 41
3.6794</code></pre>
<p>The program <code>wrank</code> (weighted rank) makes this latter calculation much easier.
It takes the output from <code>rank</code> and invokes <code>wavg</code> with these numbers,
saving a lot of typing and errors.</p>
<pre><code> ./wrank blast
blast 3.6794</code></pre>
<p>In words, <code>crate</code> as a first guess gets more correct answers in 2 and 3
guesses, reducing its cost. It looks to be a stronger candidate, but it
misses 16 more words (therefore losing the game more often). Your task
is to determine your risk profile, and decide among the variety of possible
starting points, then let <code>prog</code> recommend your next guess.</p>
<p>A somewhat laborious calculation provides the answer to which starting word
has the most answers in two words and three words:</p>
<pre><code> # two words
for n in results/*; do
rank $n | awk -v word=$n &#39;/^2/ {print word,$2}&#39;;
done | sort -k2nr | sed 3q
trace 149
crate 147
slate 146
# three words
for n in results/*; do
rank $n | awk -v word=$n &#39;/^3/ {print word,$2}&#39;;
done | sort -k2nr | sed 4q
crate 952
train 944
trace 939
slate 936
# versus the averages
sort -k2n -k3n results.avg | sed 3q
slate 3.586609 18
crate 3.586609 20
trace 3.588769 18</code></pre>
<h2 id="avoid-these">Avoid These</h2>
<p>The starting words with the worst accuracy:</p>
<pre><code> sort -k2nr results.avg | sed 18q | pr -t -3
jumpy 3.997408 21 juicy 3.954212 39 squib 3.927862 28
jumbo 3.996976 25 quake 3.952052 38 quark 3.926566 30
buxom 3.995248 23 jerky 3.950756 31 quoth 3.920950 33
quick 3.987041 36 equip 3.939957 22 gawky 3.918790 22
quack 3.974514 23 quasi 3.938661 32 unzip 3.915335 24
quirk 3.964147 40 epoxy 3.934773 27 judge 3.914903 33</code></pre>
<p>The starting words with the most failures (missed words):</p>
<pre><code> sort -k3nr results.avg | sed 18q | pr -t -3
query 3.893305 43 juicy 3.954212 39 enjoy 3.890713 37
quite 3.835421 42 quota 3.881641 39 gauze 3.878186 37
yield 3.740821 41 quake 3.952052 38 oxide 3.888121 37
juice 3.847084 40 quiet 3.857451 38 fiery 3.772786 36
quirk 3.964147 40 quote 3.829374 38 quail 3.893737 36
axion 3.796544 39 unify 3.862203 38 quick 3.987041 36</code></pre>
<p>The worst target words, for any starting word (N out of 1566):</p>
<pre><code> ./worst
wound 1340 jaunt 1254 hatch 1157 foyer 1109 waste 1057
found 997 shave 945 wight 943 willy 790 catch 779
boxer 762 jolly 751 graze 626 rower 611 fatty 598
roger 568 taunt 520 watch 497 witty 483 wooer 451
tight 449 bound 442 swore 415 taste 405 mower 378
hover 374 bunch 371 shake 358 racer 348 rarer 294
paper 289 water 285 stave 271 ferry 250 fever 242
fully 241 paddy 239 fight 225 parer 219 taffy 207
frown 196 joker 195 filly 188 batty 179 lowly 179
staff 176 jazzy 174 piper 152 vying 152 swear 151</code></pre>
<h2 id="spoiler-deep-dive-on-near-misses-spoiler">SPOILER Deep Dive on Near Misses SPOILER</h2>
<p>Dissecting the failed targets when starting with <code>blast</code>. Recall <code>blast</code>
only misses 4 words out of all 2315:</p>
<pre><code> foyer jaunt shave wound</code></pre>
<p>These four words have common sub-patterns, which in hard-mode is difficult
to disambiguate. Using <code>run dict blast $word</code>, here are the grep sub-patterns
and counts for these four words as revealed in the output:</p>
<pre><code> foyer 25 grep &#39;^.o.er&#39; dict | wc -l
jaunt 6 grep &#39;^.aunt&#39; dict | wc -l
shave 7 grep &#39;^sha.e&#39; dict | wc -l
wound 8 grep &#39;^.ound&#39; dict | wc -l</code></pre>
<p>Looking at the patterns, the program is making guesses that just happen to
select the correct word last in its choices. In all cases, on the seventh
guess, the program has the correct answer using <code>blast</code> as the starting guess.
It is easy to do an exhaustive look for only four words, but as the count
goes up, so does the work.</p>
<p>Several variations were attempted, including randomization of the few results
remaining. Besides being non-deterministic (this time it wins, next time it
loses), better results were obtained with other changes to the selection
algorithm. Some small insight into the algorithm used may be seen by the
<code>grep</code> command lines, although there are additional insights used within
the program that improve significantly upon these results.</p>
<pre><code> ./run dict blast foyer | grep ^gen
gen: target: foyer 2315
gen: blast bbbbb 429 grep -v &#39;[blast]&#39; dict | wc -l
gen: drone byyby 27 grep r dict | grep o | grep e | grep -v &#39;[blastdn]&#39; | wc -l
gen: cower bgbgg 8 grep &#39;^.o.er&#39; dict | grep -v &#39;[blastdncw]&#39;
foyer homer hover joker
mover poker roger rover
gen: mover bgbgg 4 grep &#39;.o.er&#39; dict | grep -v &#39;[blastdncwmv]&#39;
foyer joker poker roger
gen: poker bgbgg rm poker joker (foyer roger)
gen: roger ygbgg rm roger (foyer)
gen result end=0: n 6 lose: foyer
---------------
./run dict blast jaunt | grep ^gen
gen: target: jaunt 2315
gen: blast bbybg 46 grep &#39;t$&#39; dict | grep a | grep -v &#39;[bls]&#39; | wc -l
gen: daunt bgggg 5 grep &#39;^.aunt$&#39; dict | grep -v &#39;[blsd]&#39;
gaunt haunt jaunt taunt
vaunt
gen: haunt bgggg rm haunt (gaunt, jaunt, taunt, vaunt)
gen: gaunt bgggg rm guant (jaunt, taunt, vaunt)
gen: vaunt bgggg rm vaunt (jaunt, taunt)
gen: taunt ygggg rm taunt (jaunt)
gen result end=0: n 6 lose: jaunt
---------------
./run dict blast shave | grep ^gen
gen: target: shake 2315
gen: blast bbgyb 52 grep &#39;^..a&#39; dict | grep s | grep -v &#39;[blt]&#39; | wc -l
gen: share gggbg 5 grep &#39;^sha.e&#39; dict | grep -v &#39;[bltr]&#39;
shade shake shame shape
shave
gen: shade gggbg rm shade (shake shame shape shave)
gen: shape gggbg rm shape (shake shame shave)
gen: shame gggbg rm shame (shake shave)
gen: shake gggbg rm shake (shave)
gen result end=0: n 6 lose: shave
---------------
./run dict blast wound | grep ^gen
gen: target: wound 2315
gen: blast bbbbb 429 grep -v &#39;[blast]&#39; dict | wc -l
gen: drone ybygb 7 grep &#39;n.$&#39; dict | grep d | grep o | grep -v &#39;[blastre]&#39; | wc -l
gen: pound bgggg 4 grep &#39;^.ound&#39; dict | grep -v &#39;[blastreh]&#39;
found hound mound wound
gen: mound bgggg rm mound (found, hound, wound)
gen: hound bgggg rm hound (found, wound)
gen: found bgggg rm found (wound)
gen result end=0: n 6 lose: wound</code></pre>
<!-- AFTER: last line of markdown file: 2024/burton/extra.md -->
<!-- END: this line ends content for HTML phase 21 by: bin/pandoc-wrapper.sh via bin/md2html.sh -->
<!-- START: this line starts content from: inc/after-content.default.html -->
</div>
<!-- END: this line ends content from: inc/after-content.default.html -->
<!-- START: this line starts content from: inc/footer.default.html -->
<!--
Copyright © 1984-2025 by Landon Curt Noll. All Rights Reserved.
You are free to share and adapt this file under the terms of this license:
Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
For more information, see:
https://creativecommons.org/licenses/by-sa/4.0/
-->
<div class="footer">
<div id="copyright"><h3>Copyright &copy; 1984-2025 by Landon Curt Noll:
<a href="https://creativecommons.org/faq/#what-does-some-rights-reserved-mean"
target="_blank"
rel="license noopener noreferrer">Some Rights Reserved</a></h3>
<p>
This work is <b>licensed by Landon Curt Noll</b> under
<b><a href="https://creativecommons.org/licenses/by-sa/4.0/?ref=chooser-v1"
target="_blank"
rel="license noopener noreferrer"
style="display:inline-block;">CC BY-SA 4.0</a></b>.
<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;"
src="../../png/cc.png"
alt="cc inside circle symbol">
<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;"
src="../../png/by.png"
alt="person inside circle symbol">
<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;"
src="../../png/sa.png"
alt="arrow looping back on itself inside circle symbol"><br>
You should <b>carefully review</b> the
<b><a href="https://creativecommons.org/licenses/by-sa/4.0/legalcode.en"
target="_blank"
rel="license noopener noreferrer">CC BY-SA 4.0 LEGAL CODE</a></b>
before using the licensed material.<br>
You may wish to review the
<b><a href="../../license.html"
target="_blank"
rel="license noopener noreferrer">highlights of some of the key features and terms</a></b>
of <b>CC BY-SA 4.0</b>.<br>
</p>
</div>
<div id="coda"><h3>Coda</h3>
<p>
<a href="https://validator.w3.org/nu/?doc=https%3A%2F%2Fwww.ioccc.org%2F2024%2Fburton%2Fextra.html" rel="nofollow">Nu HTML check this web page</a><br>
<a href="#top">Jump to top</a> <a href="#content">Jump to Content</a>
</p>
</div>
</div>
<!-- END: this line ends content from: inc/footer.default.html -->
<!-- START: this line starts content from: inc/bottom.default.html -->
</body>
</html>
<!-- END: this line ends content from: inc/bottom.default.html -->
+471
View File
@@ -0,0 +1,471 @@
# extra
This readme provides a description of the extra files provided, how to use them,
analysis of their results, and a deeper investigation on the failed guesses.
The recommendation is to do:
``` <!---sh-->
make tools
./run dict blast swept # exhaust runs this command a LOT!
./run dict crate swept # show the diff between starting words
mkdir results # create the results directory
./exhaust # exhaustive test of all words (~1 hour)
echo terns | ./word.result # add the word "terns" to results
./mkresults > results.avg # proc exhaust into a single, small file
```
The files created as output from `exhaust` allow one to view, for each
starting word, how each of the solutions resolve, including the misses.
`mkresults` outputs a 1566 line report showing the starting word, its
average accuracy, and the number of missed words. To show the number
of guesses and the actual missed words, run `rank` on the desired word:
``` <!---sh-->
./rank results/blast
1 1 # blast guessed itself
2 112 # 112 words guessed in 2 attempts
3 867 # 867 words guessed in 3 attempts
4 1019 # ...
5 271
6 41
foyer jaunt shave wound
./avg results/blast
blast 3.673002 4
grep blast results.avg
blast 3.673002 4
```
The C programs allow for (automated) testing:
```
gen.c - simulate wordle game by generating targets and scoring guesses
./gen dict [target]
dups.c - remove words with(out) repeated letters
(best starting words have no repeats)
cat dict | wc -l # all the words
cat dict | ./dups | wc -l # words with repeated letters
cat dict | ./dups -n | wc -l # words without repeated letters
run.c - `gen` and `prog` connected via pipes; allows automated testing
./run dict [start [target]]
start - the first guess
target - force the target to be the named word
```
## Using More Guesses
If you wish to see the failed words in action, and if more guesses would
work, try:
```
./run dict blast jaunt | grep ^gen
gen: target: jaunt
gen: blast bbybg
gen: daunt bgggg
gen: haunt bgggg
gen: gaunt bgggg
gen: vaunt bgggg
gen: taunt ygggg
gen result end=0: n 6 lose: jaunt
GUESSES=7 ./run dict blast jaunt | grep ^gen
gen: target: jaunt
gen: blast bbybg
gen: daunt bgggg
gen: haunt bgggg
gen: gaunt bgggg
gen: vaunt bgggg
gen: taunt ygggg
gen: jaunt ggggg
gen result end=2: y 7 good: jaunt
```
Using `GUESSES=9` and `exhaust`, one gets a metric that scores words without
misses, allowing a better comparison on the average score. Note, however,
that the game doesn't bend this way, and anything more than 6 guesses is a
loss. Take this metric with a grain of salt. Also, 9 is the limit of
guesses; the program will not work properly - if at all - with more than
9 guesses.
Ranking by fewest missed with GUESSES=6, the order is:
```
blast palsy climb flash lapse
```
Ranking by average with GUESSES=6, the order is:
```
crate slate trace crane least
```
Ranking by average with GUESSES=9, the order is:
```
slate trace crate train least
```
To reproduce these results:
```
GUESSES=9 ./exhaust results.9
./mkresults results.9 > results.9.avg
```
## Simple Analysis
The 1566 raw files produced by `exhaust` are ~370KB each (total ~562MB),
far too large to submit, so included are the compressed results files for
four starting words, and the summary file `results.avg`.
```
blast fewest missed
crate best average
query most missing
terns example used in remarks
```
The program `rank` allows one to see how the averages are calculated, as well
as the actual missed words. Comparing the results for `blast` and `terns`:
```
./rank r.terns | awk 'NF==2' > a
./rank r.blast | awk 'NF==2' > b
join a b | column -t
guesses terns blast
-------- ----- -----
2 110 112
3 909 867
4 960 1019
5 265 271
6 53 41
F 18 4 (failed puzzles)
```
In general, the program `avg` is easier to use, but conveys less information:
```
./avg r.terns; ./avg r.blast
terns 3.641469 18
blast 3.673002 4
```
You have to decide if you prefer a marginally lower average, but slightly
higher chance of losing, versus a marginally higher average and lower chance
of losing. This is developed further in the following section.
It is interesting that the letter order makes a noticeable difference:
```
./avg r.terns; ./avg results/stern
terns 3.641469 18 # lower average and fewer misses
stern 3.658315 20
```
For completeness, using `GUESSES=9` and the full starting dictionary from
Wordle(tm), here are the results for the "best" starting words: `salet`,
`slate`, and `blast`.
```
guesses salet slate blast
------- ----- ----- -----
1 0 1 1
2 148 146 112
3 932 939 867
4 925 919 1019
5 240 235 271
6 56 57 41
--- ----- ----- -----
7 11 13 4
8 2 4 0
9 1 1 0
--- ----- ----- -----
avg 3.641 3.644 3.685
```
Obviously, `salet` produces the best results, excepting the losses (14).
Just re-arranging the letters into `slate` produces almost the same,
but there are more losses (18). If attempting to minimize losses,
then `blast` is the optimal starting guess, since it never takes more
than 7 guesses and has the fewest losses (4) of any starting word in the
combined full Wordle(tm) dictionary.
NB: The averages for `GUESSES=9` are larger than the default,
because the misses are essentially folded into the average.
The averages are more accurately compared when using the higher limit,
although minimizing losses remains the priority (who likes to lose?).
## Seek These
The starting words with the fewest misses with `dict`:
```
sort -k3n results.avg | sed 18q | awk '{print $1, $3}' | column -t | pr -t -3
blast 4 ralph 7 crawl 8
palsy 6 ranch 7 crumb 8
climb 7 shawl 7 grasp 8
flash 7 tramp 7 lymph 8
lapse 7 clash 8 spark 8
prowl 7 clasp 8 swamp 8
The starting words with the best accuracy:
sort -k2n results.avg | sed 18q | pr -t -3
crate 3.586609 20 irate 3.611231 31 trice 3.617279 15
slate 3.586609 18 slice 3.612095 22 cater 3.620302 26
trace 3.588769 18 stale 3.612959 19 satin 3.621598 19
crane 3.595248 26 train 3.613391 14 snail 3.621598 19
least 3.599568 19 trade 3.613823 20 stare 3.621598 19
plate 3.606479 22 leant 3.616415 18 dealt 3.622462 15
Note the word with fewest misses, `blast`, is relatively far
down the list in accuracy at 178th best:
sort -k2n results.avg | pr -t -n | grep blast
178 blast 3.673002 4
```
Versus starting words with fewest misses:
```
egrep 'terns|lapse|blast|climb|crate' results.avg
blast 3.673002 4
climb 3.772354 7
crate 3.586609 20
lapse 3.664795 7
terns 3.641469 18
weighted avg
# blast crate blast crate improvement
- ----- ----- --------------- -----------
1 1 1 1.0000 1.0000 0.0000
2 112 147 1.9912 1.9932 -0.0020
3 867 952 2.8837 2.8645 -0.0192
4 1019 884 3.4527 3.3951 -0.0576
5 271 250 3.6374 3.5775 -0.0599
6 41 61 3.6794 3.6429 -0.0365
F 4 20 -16 +16 (0.26% blast vs 1.28% crate)
```
Notice the difference in the average and weighted average. The program
`avg` simply takes the total guesses as a percentage of the total words.
The program `wavg` takes each of the six scores and weights them by the
score. I wrote `wavg` later to track my progress playing the online game,
and it is a more accurate score IMHO, but both are useful for comparisons.
``` <!---sh-->
$ ./avg results/blast
blast 3.673002 4
$ ./rank results/blast
1 1
2 112
3 867
4 1019
5 271
6 41
foyer jaunt shave wound
$ ./wavg 1 112 867 1019 271 41
3.6794
```
The program `wrank` (weighted rank) makes this latter calculation much easier.
It takes the output from `rank` and invokes `wavg` with these numbers,
saving a lot of typing and errors.
``` <!---sh-->
./wrank blast
blast 3.6794
```
In words, `crate` as a first guess gets more correct answers in 2 and 3
guesses, reducing its cost. It looks to be a stronger candidate, but it
misses 16 more words (therefore losing the game more often). Your task
is to determine your risk profile, and decide among the variety of possible
starting points, then let `prog` recommend your next guess.
A somewhat laborious calculation provides the answer to which starting word
has the most answers in two words and three words:
```
# two words
for n in results/*; do
rank $n | awk -v word=$n '/^2/ {print word,$2}';
done | sort -k2nr | sed 3q
trace 149
crate 147
slate 146
# three words
for n in results/*; do
rank $n | awk -v word=$n '/^3/ {print word,$2}';
done | sort -k2nr | sed 4q
crate 952
train 944
trace 939
slate 936
# versus the averages
sort -k2n -k3n results.avg | sed 3q
slate 3.586609 18
crate 3.586609 20
trace 3.588769 18
```
## Avoid These
The starting words with the worst accuracy:
```
sort -k2nr results.avg | sed 18q | pr -t -3
jumpy 3.997408 21 juicy 3.954212 39 squib 3.927862 28
jumbo 3.996976 25 quake 3.952052 38 quark 3.926566 30
buxom 3.995248 23 jerky 3.950756 31 quoth 3.920950 33
quick 3.987041 36 equip 3.939957 22 gawky 3.918790 22
quack 3.974514 23 quasi 3.938661 32 unzip 3.915335 24
quirk 3.964147 40 epoxy 3.934773 27 judge 3.914903 33
```
The starting words with the most failures (missed words):
```
sort -k3nr results.avg | sed 18q | pr -t -3
query 3.893305 43 juicy 3.954212 39 enjoy 3.890713 37
quite 3.835421 42 quota 3.881641 39 gauze 3.878186 37
yield 3.740821 41 quake 3.952052 38 oxide 3.888121 37
juice 3.847084 40 quiet 3.857451 38 fiery 3.772786 36
quirk 3.964147 40 quote 3.829374 38 quail 3.893737 36
axion 3.796544 39 unify 3.862203 38 quick 3.987041 36
```
The worst target words, for any starting word (N out of 1566):
```
./worst
wound 1340 jaunt 1254 hatch 1157 foyer 1109 waste 1057
found 997 shave 945 wight 943 willy 790 catch 779
boxer 762 jolly 751 graze 626 rower 611 fatty 598
roger 568 taunt 520 watch 497 witty 483 wooer 451
tight 449 bound 442 swore 415 taste 405 mower 378
hover 374 bunch 371 shake 358 racer 348 rarer 294
paper 289 water 285 stave 271 ferry 250 fever 242
fully 241 paddy 239 fight 225 parer 219 taffy 207
frown 196 joker 195 filly 188 batty 179 lowly 179
staff 176 jazzy 174 piper 152 vying 152 swear 151
```
## SPOILER Deep Dive on Near Misses SPOILER
Dissecting the failed targets when starting with `blast`. Recall `blast`
only misses 4 words out of all 2315:
```
foyer jaunt shave wound
```
These four words have common sub-patterns, which in hard-mode is difficult
to disambiguate. Using `run dict blast $word`, here are the grep sub-patterns
and counts for these four words as revealed in the output:
```
foyer 25 grep '^.o.er' dict | wc -l
jaunt 6 grep '^.aunt' dict | wc -l
shave 7 grep '^sha.e' dict | wc -l
wound 8 grep '^.ound' dict | wc -l
```
Looking at the patterns, the program is making guesses that just happen to
select the correct word last in its choices. In all cases, on the seventh
guess, the program has the correct answer using `blast` as the starting guess.
It is easy to do an exhaustive look for only four words, but as the count
goes up, so does the work.
Several variations were attempted, including randomization of the few results
remaining. Besides being non-deterministic (this time it wins, next time it
loses), better results were obtained with other changes to the selection
algorithm. Some small insight into the algorithm used may be seen by the
`grep` command lines, although there are additional insights used within
the program that improve significantly upon these results.
```
./run dict blast foyer | grep ^gen
gen: target: foyer 2315
gen: blast bbbbb 429 grep -v '[blast]' dict | wc -l
gen: drone byyby 27 grep r dict | grep o | grep e | grep -v '[blastdn]' | wc -l
gen: cower bgbgg 8 grep '^.o.er' dict | grep -v '[blastdncw]'
foyer homer hover joker
mover poker roger rover
gen: mover bgbgg 4 grep '.o.er' dict | grep -v '[blastdncwmv]'
foyer joker poker roger
gen: poker bgbgg rm poker joker (foyer roger)
gen: roger ygbgg rm roger (foyer)
gen result end=0: n 6 lose: foyer
---------------
./run dict blast jaunt | grep ^gen
gen: target: jaunt 2315
gen: blast bbybg 46 grep 't$' dict | grep a | grep -v '[bls]' | wc -l
gen: daunt bgggg 5 grep '^.aunt$' dict | grep -v '[blsd]'
gaunt haunt jaunt taunt
vaunt
gen: haunt bgggg rm haunt (gaunt, jaunt, taunt, vaunt)
gen: gaunt bgggg rm guant (jaunt, taunt, vaunt)
gen: vaunt bgggg rm vaunt (jaunt, taunt)
gen: taunt ygggg rm taunt (jaunt)
gen result end=0: n 6 lose: jaunt
---------------
./run dict blast shave | grep ^gen
gen: target: shake 2315
gen: blast bbgyb 52 grep '^..a' dict | grep s | grep -v '[blt]' | wc -l
gen: share gggbg 5 grep '^sha.e' dict | grep -v '[bltr]'
shade shake shame shape
shave
gen: shade gggbg rm shade (shake shame shape shave)
gen: shape gggbg rm shape (shake shame shave)
gen: shame gggbg rm shame (shake shave)
gen: shake gggbg rm shake (shave)
gen result end=0: n 6 lose: shave
---------------
./run dict blast wound | grep ^gen
gen: target: wound 2315
gen: blast bbbbb 429 grep -v '[blast]' dict | wc -l
gen: drone ybygb 7 grep 'n.$' dict | grep d | grep o | grep -v '[blastre]' | wc -l
gen: pound bgggg 4 grep '^.ound' dict | grep -v '[blastreh]'
found hound mound wound
gen: mound bgggg rm mound (found, hound, wound)
gen: hound bgggg rm hound (found, wound)
gen: found bgggg rm found (wound)
gen result end=0: n 6 lose: wound
```
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
w=$1; : ${w:?echo "usage: $0 target_word" >&2; exit 1}
rank results/$w |
sed -n 7,\$p |
tr '\t' '\n' |
awk '$1 != "" {print $1}' |
sort -u |
while read a; do
GUESSES=9 run dict $w $a
done |
grep result
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
dict=$1
: ${dict:=dict}
echo first
seq a z | while read a; do /bin/echo -n "$a: "; grep ^$a $dict | wc -l; done | sort -k2nr
echo second
seq a z | while read a; do /bin/echo -n "$a: "; grep ^.$a $dict | wc -l; done | sort -k2nr
echo third
seq a z | while read a; do /bin/echo -n "$a: "; grep ^..$a $dict | wc -l; done | sort -k2nr
echo fourth
seq a z | while read a; do /bin/echo -n "$a: "; grep ^...$a $dict | wc -l; done | sort -k2nr
echo last
seq a z | while read a; do /bin/echo -n "$a: "; grep ^....$a $dict | wc -l; done | sort -k2nr
+86
View File
@@ -0,0 +1,86 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
/*
* Wordle generator - text mode
* This program works in coordination with the obfuscated code
* to allow one to play more rapidly (e.g. test the algorithm)
* or just to play along in text mode.
*
* This version is a pale imitation of the Real Wordle(tm).
* If you want to get the real experience, please visit:
*
* https://www.nytimes.com/games/wordle/index.html
*/
static int
check(char *w, char *target)
{
printf("%.5s ", w);
for (int i = 0; i < 5; ++i) {
if (w[i] == target[i])
putchar('g');
else if (memchr(target, w[i], 5))
putchar('y');
else
putchar('b');
}
putchar('\n');
fflush(stdout);
return memcmp(w, target, 5) == 0;
}
int
main(int ac, char **av)
{
char words[78557][5];
char buf[100];
int cnt = 0, win = 0;
int nguesses = 6;
if (ac < 2) {
fprintf(stderr, "usage: %s dict [target]\n", av[0]);
return 1;
}
if (getenv("GUESSES")) {
nguesses = atoi(getenv("GUESSES"));
// fprintf(stderr, "using %d guesses\n", nguesses);
}
FILE *fp = fopen(av[1], "r");
if (!fp) {
perror(av[1]);
exit(1);
}
while (fgets(buf, sizeof buf, fp)) {
if (buf[5] != '\n')
continue;
memcpy(words[cnt++], buf, 5);
}
fclose(fp);
srandom(time(0) ^ getpid());
char *target = ac == 3 ? av[2] : words[random() % cnt];
getenv("CHEAT") && printf("target: %.5s\n", target);
int i;
for (i = 0; i < nguesses; ) {
printf("? "); fflush(stdout);
if (!fgets(buf, sizeof buf, stdin))
break;
if (buf[5] != '\n') {
printf("you must enter 5 letters\n");
continue;
}
++i;
if ((win = check(buf, target)))
break;
}
printf("%c %d %s: %.5s\n", win ? 'y' : 'n', i, win ? "good" : "lose", target);
return 0;
}
File diff suppressed because it is too large. Load diff
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
dir=$1
: ${dir:=results}
for name in $dir/*; do
./avg $name
done | sed "s#$dir/##" | tr : ' '
+64
View File
@@ -0,0 +1,64 @@
#include <stdarg.h>
#include <stdlib.h>
# include <stdio.h>
#define J(J) for (\
_ j=0; j<5; ++j) J;
#define/*IOCCC*/I \
\
\
\
_ W,_ O,_ R,_ D,_*L,_*E
#define A(A )for(_ j=0; !o&&j<5&&(k = t[l][j]); ++j)A&&++o;
typedef/* ; ; */void q; typedef int _ ; typedef char/* */p;
struct{ p a [257],b[9][ 5],c[5],d[5 ]; _ e,f; } i;p t[78557
][5]; _ s,a []={ 9,9,9, 10,13,18,27 ,39,55,1,18 ,2,1,1,35,2
,19,18,1,52 ,36,35,20,3 ,2,19,18,18 ,69,2,53,1, 52,37,3,20,
36,19,35,18 ,52,86,2,53 ,70,1,69,38 ,4,21,3,37, 19,36,35,25
,10,+17,15, 26,9,11,12, 23,2,06,18, 13,20,22,14 ,1,24,19,21
,16,5,7,4,8 ,3},u=' ';_ W(p*a,p*b){ for(p*c=a+5 ;a<c;)*a++=
*b++;return 0; } _ O(p* a,p b){ p*c =a+5; for(; a<c&&*a-b;)
++a; return a<c?5-(a-c) :0; } _ R(p *a, ... ) { va_list b ;
va_start(b, a); vprintf (a,b); exit (1); } q D( p*a){ if (s
==sizeof t/ 5)R("limit" " %d\n",s); p*b=s[t],i, j; for(i=j=
0; i<7&&!j; ++i)if(!(j= *a==10?i:0) )*b++=u|* a ++; j-5||++
s; } q L(p* a,_ b){ for (_ l=0,o=0, k; l<s; o=0 ){ A(i.c[j]
&&k-i.c[j]) A(i.a[k])A( i.d[j]&&!O( t[l],i.d[j] ))for(_ f=i
.e; f--&&!o ; )A(i.b[f] [j]==k)if(! o){ J(o+=t[ l][j]==a[j]
)o=o==5?b!= 5:0; } o?W( t[l],t[--s] ) :++l; } } _ e(I){ for
(; O<D; )L[ R++]=E[O+++ D*W];return R; } q S( _ O,_*l){ for
(_*s=&a[a[O ]],*e=a[-~O ]+s-a[O]; s <e; ){ _ i= *s++,j=i&15
,k=l[i>>=4] <l[+j]?k=l[ i],l[i]=l[j ],l[j]=k:i; } } _ o(I){
_ o=0, w=0; for(; o<D&w <D; )L[R++] =E[o+D*W]>= E[w+D*O]?E[
o+++D*W]:E[ w +++ D*O]; return e(O, w,e(W,o,R,D ,L,E),D,L,E
); } q l( _ n,_*f){ for (_* e=f+26, i,j; f<e&&* f>u; f+= i)
{ for(i=1,j =*f/u; i <n &&f[i]>>5== j; )++i;for (j=0; j <i;
++j)f[j] &= 31,f[j]|=a[ f[j]+a[~-9] ]<<5; S(i,f ); } } q v(
_ a,_*w,_*k ){ o(2,3,o( 0,1,0,a,w,k ),a,w,k); o (0,1,0,a+a,
k,w); l(a,k ); } p*E(p* d){ _ l,j,k =u,b[u] ,c[ k]; p*e[97]
[5 ]={ 0} ; for(l=0; l< k; ++l)c[l] =l; for(l=0 ; l<s; ++l)
J(c[t[l][j] -97]+=k)for (l= j=7; j< 22; j+=l)S( l,c+j-l); v
(l,b,c);for (l= 0; l<k; ++l)b[c[l]& ~-k]=l; for (k*=3,l= 0;
l<s; ++l) { _ i=0; J( i +=b[t[l][j] -97])if(i<k )J(e[i][ j]
||(e[i][j]= t[l],j=4))} for(l=0; l< k; ++l)for( p*b=0,k=j=0
; j<5&&(b=e [l][j]); k= !++j){ p c[ 32]={0} ; d =*d-120?d:b
; if(s>2)J( k+=++c[b[j] -97]>1); if (!k) return b; } return
d; } p*r(p* b){ _ k, l; for(_ j=k=l =0; j<5; ++ j) { signed
char n=b[j] ,o=b[j-~5]+ ~0x66; (o?i .b[i.e]:i.c )[j]=n; !o?
i.a[n]=!++k :O(i.d,n)|| (*(o<0?&i.a [n]:&i.d[i. f++])=l=n);
} i.e += !! l; L(b, k); return E("" "xyzzy"); } _ main(_ n,
char**v){ p *j=i .c-97, *l=j-11; n- 2?R("usage" ": %s dict"
"\n",* v):5 ; ; FILE*k= fopen(*++v, "r"); if(!k ) return //
perror(*v), 1; ; while( fgets(j, u^ n,k)) D(j); fclose (k);
; ; setbuf( stdout, 0); while(~-s){ printf("? " ); while(~(
n=getchar() )&&n-10&&l< j)*l++=n; l <j&&R("inp" "ut: guess"
" color\n") ;printf("%" ".5s\n",r(l =j+~10)); } return 0; }
+64
View File
@@ -0,0 +1,64 @@
#include <stdarg.h>
#include <stdlib.h>
# include <stdio.h>
#define J(J) for (\
_ j=0; j<5; ++j) J;
#define/*IOCCC*/I \
\
\
\
_ W,_ O,_ R,_ D,_*L,_*E
#define A(A )for(_ j=0; !o&&j<5&&(k = t[l][j]); ++j)A&&++o;
typedef/* ; ; */void q; typedef int _ ; typedef char/* */p;
struct{ p a [257],b[9][ 5],c[5],d[5 ]; _ e,f; } i;p t[78557
][5]; _ s,a []={ 9,9,9, 10,13,18,27 ,39,55,1,18 ,2,1,1,35,2
,19,18,1,52 ,36,35,20,3 ,2,19,18,18 ,69,2,53,1, 52,37,3,20,
36,19,35,18 ,52,86,2,53 ,70,1,69,38 ,4,21,3,37, 19,36,35,25
,10,+17,15, 26,9,11,12, 23,2,06,18, 13,20,22,14 ,1,24,19,21
,16,5,7,4,8 ,3},u=' ';_ W(p*a,p*b){ for(p*c=a+5 ;a<c;)*a++=
*b++;return 0; } _ O(p* a,p b){ p*c =a+5; for(; a<c&&*a-b;)
++a; return a<c?5-(a-c) :0; } _ R(p *a, ... ) { va_list b ;
va_start(b, a); vprintf (a,b); exit (1); } q D( p*a){ if (s
==sizeof t/ 5)R("limit" " %d\n",s); p*b=s[t],i, j; for(i=j=
0; i<7&&!j; ++i)if(!(j= *a==10?i:0) )*b++=u|* a ++; j-5||++
s; } q L(p* a,_ b){ for (_ l=0,o=0, k; l<s; o=0 ){ A(i.c[j]
&&k-i.c[j]) A(i.a[k])A( i.d[j]&&!O( t[l],i.d[j] ))for(_ f=i
.e; f--&&!o ; )A(i.b[f] [j]==k)if(! o){ J(o+=t[ l][j]==a[j]
)o=o==5?b!= 5:0; } o?W( t[l],t[--s] ) :++l; } } _ e(I){ for
(; O<D; )L[ R++]=E[O+++ D*W];return R; } q S( _ O,_*l){ for
(_*s=&a[a[O ]],*e=a[-~O ]+s-a[O]; s <e; ){ _ i= *s++,j=i&15
,k=l[i>>=4] <l[+j]?k=l[ i],l[i]=l[j ],l[j]=k:i; } } _ o(I){
_ o=0, w=0; for(; o<D&w <D; )L[R++] =E[o+D*W]>= E[w+D*O]?E[
o+++D*W]:E[ w +++ D*O]; return e(O, w,e(W,o,R,D ,L,E),D,L,E
); } q l( _ n,_*f){ for (_* e=f+26, i,j; f<e&&* f>u; f+= i)
{ for(i=1,j =*f/u; i <n &&f[i]>>5== j; )++i;for (j=0; j <i;
++j)f[j] &= 31,f[j]|=a[ f[j]+a[~-9] ]<<5; S(i,f ); } } q v(
_ a,_*w,_*k ){ o(2,3,o( 0,1,0,a,w,k ),a,w,k); o (0,1,0,a+a,
k,w); l(a,k ); } p*E(p* d){ _ l,j,k =u,b[u] ,c[ k]; p*e[97]
[5 ]={ 0} ; for(l=0; l< k; ++l)c[l] =l; for(l=0 ; l<s; ++l)
J(c[t[l][j] -97]+=k)for (l= j=7; j< 22; j+=l)S( l,c+j-l); v
(l,b,c);for (l= 0; l<k; ++l)b[c[l]& ~-k]=l; for (k*=3,l= 0;
l<s; ++l) { _ i=0; J( i +=b[t[l][j] -97])if(i<k )J(e[i][ j]
||(e[i][j]= t[l],j=4))} for(l=0; l< k; ++l)for( p*b=0,k=j=0
; j<5&&(b=e [l][j]); k= !++j){ p c[ 32]={0} ; d =*d-120?d:b
; if(s>2)J( k+=++c[b[j] -97]>1); if (!k) return b; } return
d; } p*r(p* b){ _ k, l; for(_ j=k=l =0; j<5; ++ j) { signed
char n=b[j] ,o=b[j-~5]+ ~0x66; (o?i .b[i.e]:i.c )[j]=n; !o?
i.a[n]=!++k :O(i.d,n)|| (*(o<0?&i.a [n]:&i.d[i. f++])=l=n);
} i.e += !! l; L(b, k); return E("" "xyzzy"); } _ main(_ n,
char**v){ p *j=i .c-97, *l=j-11; n- 2?R("usage" ": %s dict"
"\n",* v):5 ; ; FILE*k= fopen(*++v, "r"); if(!k ) return //
perror(*v), 1; ; while( fgets(j, u^ n,k)) D(j); fclose (k);
; ; setbuf( stdout, 0); while(~-s){ printf("? " ); while(~(
n=getchar() )&&n-10&&l< j)*l++=n; l <j&&R("inp" "ut: guess"
" color\n") ;printf("%" ".5s\n",r(l =j+~10)); } return 0; }
+23999
View File
File diff suppressed because it is too large. Load diff
+23783
View File
File diff suppressed because it is too large. Load diff
+25469
View File
File diff suppressed because it is too large. Load diff
+24014
View File
File diff suppressed because it is too large. Load diff
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
: ${dir:=results}
word=$1
echo $word | grep -q /
if test $? -eq 1; then word=$dir/$word; fi
grep good: $word | awk '{n[$5]++}END{for(i=1; i<7; ++i)print i,n[i]}' | sort -k1n
grep lose: $word | awk '{print $NF}' | pr -t -a -5
+26
View File
@@ -0,0 +1,26 @@
/* clang-format --style=LLVM < prog.c | reveal */
#include <stdio.h>
#include <ctype.h>
#include <string.h>
int main(void)
{
int start = 0, len;
char buf[128];
while (fgets(buf, sizeof buf, stdin)) {
len = strlen(buf);
if (len > 3 && !isspace(buf[0])) {
char c = buf[2];
if (c == 'i') start = 1;
if (c == 'm') start = 0;
if (!start) continue;
if (buf[2] == '*') c = buf[3];
if (isalpha(c)) putchar(c);
} else if (start && len > 6 && buf[6] == ']')
putchar(buf[4]);
}
putchar('\n');
return 0;
}
+186
View File
@@ -0,0 +1,186 @@
/*
* run - connect gen and prog via pipes
*
* one-shot: run dict
* specific: run dict $start $target
* automate: cat dict | while read a; do run dict $start $a; done
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
static int DB;
static int
error(char *who)
{
perror(who);
kill(0, SIGKILL);
exit(1);
}
static int
xread(int fd, char *buf, int len)
{
int remain = len;
int t = 0;
if (DB) { printf("xread %d:", len); fflush(stdout); }
do {
int n = read(fd, buf + t, remain);
if (n < 0) error("read");
if (n == 0) return t;
if (n <= remain) {
remain -= n;
t += n;
}
} while (t != len);
buf[t] = 0;
if (DB) {
printf(" %s", buf);
if (buf[t-1] != '\n') printf("\n");
fflush(stdout);
}
return t;
}
static void
spawn(int r[2], int w[2], char **ev, char *path, char *name, char *dict, char *opt)
{
pipe(r) && error("pipe r");
pipe(w) && error("pipe w");
pid_t pid = fork();
if (pid == -1)
error("fork");
if (pid == 0) {
char *v[4] = { name, dict, opt, 0 };
dup2(r[0], 0) < 0 && error("dup2 r[0]");
dup2(w[1], 1) < 0 && error("dup2 w[1]");
close(r[1]);
close(w[0]);
execve(path, v, ev);
error(path);
}
close(r[0]);
close(w[1]);
}
int
main(int ac, char **av, char **ev)
{
int pgr[2], pgw[2]; // gen r/w
int ppr[2], ppw[2]; // prog r/w
static char guess[20], report[20];
static char xyzzy[] = "xyzzy";
char *cp, *prog = "./prog";
int n, end = 0;
int nguesses = 6;
if (ac < 2) {
fprintf(stderr, "usage: %s dict [start [target]]\n", av[0]);
return 1;
}
DB = !!getenv("DEBUG");
if ((cp = getenv("GUESSES")))
nguesses = atoi(cp);
if ((cp = getenv("PROG")))
prog = cp;
spawn(pgr, pgw, ev, "./gen", "gen", av[1], ac==4 ? av[3] : 0);
spawn(ppr, ppw, ev, prog, "prog", av[1], 0);
// wait for gen to be ready; read the prompt
char buf[20];
xread(pgw[0], buf, 2);
if (buf[0] != '?') {
xread(pgw[0], buf+2, 12); // target: guess
printf("gen: %s", buf);
xread(pgw[0], buf, 2);
}
// wait for prog prompt
xread(ppw[0], buf, 2);
memcpy(guess, ac>2 ? av[2] : "blast", 5);
guess[5] = '\n'; // mimic reading from stdin
for (int i = 0; i < nguesses && end != 1; ++i) {
printf("guess%d: %s", i+1, guess);
n = write(pgr[1], guess, 6);
if (n < 0) error("write pgw[1]");
n = xread(pgw[0], report, 12);
if (n != 12) {
if (n < 0) error("read pgr[0]");
printf("bad read from pgr[0]: %d\n", n);
return 2;
}
printf("gen: %s", report);
xread(pgw[0], guess, 2); // read the prompt
if (guess[0] != '?') {
xread(pgw[0], guess+2, 14);
printf("gen result end=%d: %s", end, guess);
if (end == 2) {
close(pgr[1]);
close(ppr[1]);
return 0;
}
close(pgw[0]);
close(pgr[1]);
end = 1;
}
if (end == 2) {
// cannot write to prog, it exited on winner, but gen disagrees
printf("ERROR: prog reported win, gen does not\n");
close(pgr[1]);
close(ppr[1]);
return 1;
}
n = write(ppr[1], report, n);
if (n < 0) error("ppw[1]");
n = xread(ppw[0], guess, 6);
if (n != 6) {
if (n < 0) error("read ppr");
printf("bad read from pipe_prog: %d {%.*s}\n", n, n, guess);
return 3;
}
if (strncmp(guess, xyzzy, sizeof(xyzzy)-1) == 0) {
printf("ERROR: desired word not in this dictionary\n");
close(pgr[1]);
close(ppr[1]);
return 1;
}
// might not prompt, just exit
if (xread(ppw[0], report, 2) == 0) { // read the prompt
printf("prog: EOF\n");
if (end == 1) {
close(pgr[1]);
close(ppr[1]);
return 0;
}
end = 2;
close(ppr[1]);
close(ppw[0]);
}
}
}
+5
View File
@@ -0,0 +1,5 @@
blast ybybb
amber gbyyb
adobe gygyg
abode ggggg
+6
View File
@@ -0,0 +1,6 @@
blast bbybg
daunt bgggg
haunt bgggg
gaunt bgggg
vaunt bgggg
taunt ygggg
+3
View File
@@ -0,0 +1,3 @@
terns ybbbb
loath gbbyy
light ggggg
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
#
# try.sh - demonstrate 2024/burton
# make sure CC is set so that when we do make CC="$CC" it isn't empty. Doing it
# this way allows us to have the user specify a different compiler in an easy way.
#
[[ -z "$CC" ]] && CC="cc"
# make sure the program has been built
#
make CC="$CC" all >/dev/null || exit 1
# clear the screen
#
clear
# try the program
#
read -r -n 1 -p "Press any key to run: ./prog: "
echo 1>&2
echo 1>&2
cat test.light 1>&2
echo 1>&2
echo '=-=-=' 1>&2
echo 1>&2
./prog dict < test.light
echo 1>&2
+165
View File
@@ -0,0 +1,165 @@
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
typedef void q;
typedef int _;
typedef char p;
struct {
p a[256], b[9][5], c[5], d[5];
_ e, f;
} i;
p t[78557][5];
_ s, a[] = {
9, 9, 9, 10, 13, 18, 27, 39, 55, 1, 18, 2, 1, 1,
35, 2, 19, 18, 1, 52, 36, 35, 20, 3, 2, 19, 18, 18,
69, 2, 53, 1, 52, 37, 3, 20, 36, 19, 35, 18, 52, 86,
2, 53, 70, 1, 69, 38, /* 36 */ 4, 21, 3, 37, 19,
36, 35, 25, 10, 17, 15, 26, 9, 11, 12, 23, 2, 6, 18,
13, 20, 22, 14, 1, 24, 19, 21, 16, 5, 7, 4, 8, 3
} , u = ' ';
_ W(p *a, p *b) {
for (p *c = a + 5; a < c; )
*a++ = *b++;
return 0;
}
_ O(p *a, p b) {
p *c = a + 5;
for (; a < c && *a - b; )
++a;
return a < c ? 5 - (a - c) : 0;
}
_ R(p *a, ...) {
va_list b;
va_start(b, a);
vprintf(a, b);
exit(1);
}
q D(p *a) {
if (s == sizeof t / 5)
R("limit %d\n", s);
p *b = s[t], i, j;
for (i = j = 0; i < 7 && !j; ++i)
if (!(j = *a == 10 ? i : 0))
*b++ = u | *a++;
j - 5 || ++s;
}
q L(p *a, _ b) {
for (_ l = 0, o = 0, k; l < s; o = 0) {
for (_ j = 0; !o && j < 5 && (k = t[l][j]); ++j)
i.c[j] && k - i.c[j] && ++o;
for (_ j = 0; !o && j < 5 && (k = t[l][j]); ++j)
i.a[k] && ++o;
for (_ j = 0; !o && j < 5 && (k = t[l][j]); ++j)
i.d[j] && !O(t[l], i.d[j]) && ++o;
for (_ f = i.e; f-- && !o; )
for (_ j = 0; !o && j < 5 && (k = t[l][j]); ++j)
i.b[f][j] == k && ++o;
if (!o) {
for (_ j = 0; j < 5; ++j)
o += t[l][j] == a[j];
o = o == 5 ? b != 5 : 0;
}
o ? W(t[l], t[--s]) : ++l;
}
}
_ e(_ W, _ O, _ R, _ D, _ *L, _ *E) {
for (; O < D; )
L[R++] = E[O++ + D * W];
return R;
}
q S(_ O, _ *l) {
for (_ *s = &a[a[O]], *e = a[-~O] + s - a[O]; s < e; ) {
_ i = *s++,
j = i & 15,
k = l[i >>= 4] < l[j] ? k = l[i], l[i] = l[j], l[j] = k : i;
}
}
_ o(_ W, _ O, _ R, _ D, _ *L, _ *E) {
_ o = 0, w = 0;
for (; o < D & w < D; )
L[R++] = E[o + D * W] >= E[w + D * O]
? E[o++ + D * W]
: E[w++ + D * O];
return e(O, w, e(W, o, R, D, L, E), D, L, E);
}
q l(_ n, _ *f) {
for (_ *e = f + 26, i, j; f < e && *f > u; f += i) {
for (i = 1, j = *f / u; i < n && f[i] >> 5 == j; )
++i;
for (j = 0; j < i; ++j)
f[j] &= 31,
f[j] |= a[f[j] + a[~-9]] << 5;
S(i, f);
}
}
q v(_ a, _ *w, _ *k) {
o(2, 3, o(0, 1, 0, a, w, k), a, w, k);
o(0, 1, 0, a + a, k, w);
l(a, k);
}
p *E(p *d) {
_ l, j, k = u, b[u], c[k];
p *e[97][5] = { 0 } ;
for (l = 0; l < k; ++l)
c[l] = l;
for (l = 0; l < s; ++l)
for (_ j = 0; j < 5; ++j)
c[t[l][j] - 97] += k;
for (l = j = 7; j < 22; j += l)
S(l, c + j - l);
v(l, b, c);
for (l = 0; l < k; ++l)
b[c[l] & ~-k] = l;
for (k *= 3, l = 0; l < s; ++l) {
_ i = 0;
for (_ j = 0; j < 5; ++j)
i += b[t[l][j] - 97];
if (i < k)
for (_ j = 0; j < 5; ++j)
e[i][j] || (e[i][j] = t[l], j = 4);
}
for (l = 0; l < k; ++l)
for (p *b = 0, k = j = 0; j < 5 && (b = e[l][j]); k = !++j) {
p c[32] = { 0 } ;
d = *d - 120 ? d : b;
if (s > 2)
for (_ j = 0; j < 5; ++j)
k += ++c[b[j] - 97] > 1;
if (!k)
return b;
}
return d;
}
p *r(p *b) {
_ k, l;
for (_ j = k = l = 0; j < 5; ++j) {
signed char n = b[j], o = b[j - ~5] + ~0x66;
(o ? i.b[i.e] : i.c)[j] = n;
!o ? i.a[n] = !++k : O(i.d, n) || (*(o < 0 ? &i.a[n] : &i.d[i.f++]) = l = n);
}
i.e += !!l;
L(b, k);
return E("xyzzy");
}
_ main(_ n, char **v) {
p *j = i.c - 97, *l = j - 11;
n - 2 ? R("usage: %s dict\n", *v) : 5;
FILE *k = fopen(*++v, "r");
if (!k)
return perror(*v), 1;
while (fgets(j, u ^ n, k))
D(j);
fclose(k);
setbuf(stdout, 0);
while (~-s) {
printf("? ");
while (~(n = getchar()) && n - 10 && l < j)
*l++ = n;
l < j && R("input: guess color\n");
printf("%.5s\n", r(l = j + ~10));
}
return 0;
}
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
test $# -eq 6 || { echo must have all six scores; exit 1; }
echo "($1*1 + $2*2 + $3*3 + $4*4 + $5*5 + $6*6) / ($1+$2+$3+$4+$5+$6)" | bc -l | awk '{printf "%.04f\n", $1 }'
+20
View File
@@ -0,0 +1,20 @@
#!/bin/sh
# usage: echo terns | word.result
# usage: cat dict | word.result
dir=$1
: ${dir:=results}
d=$2
: ${d:=dict}
# for each word as a starting guess
# run the sim for each target word
while read w; do
echo $w;
cat dict | while read t; do
./run $d $w $t
done > $dir/$w
done
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
dir=$1
: ${dir:=results}
lines=$2
: ${lines:=10}
for name in $dir/*; do
grep lose: $name | awk '{print $NF}';
done |
awk '{a[$1]++}END{for(i in a)print i,a[i]}' |
sort -k2nr |
pr -t -a -5 |
sed ${lines}q
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
a=$(./rank results/$1 | awk 'NR<7 {print $2}')
/bin/echo -n "$1 "
./wavg $a