Fixes in 1991/westley + alt code

The alt code is based on the author's remarks where the computer will
always win (supposedly :-) :-) ). It involves the new script ttt.alt.sh
which is copied to ttt.alt with make. This script has changes from
ttt.sh so that it doesn't stomp over the files that ttt.sh makes.

The Makefile clobber rule had a typo fix as well which prevented a file
from being removed (a file specified twice instead).

The README.md file has been typo and format fixed.

The .gitignore file has been updated.
This commit is contained in:
Cody Boone Ferguson committed 2023-11-06 09:25:12 -08:00
1 parent e3322d8d89
commit f9169eca06
6 files changed
+368 -51

No files matched your search

+6
View File
@@ -1,11 +1,17 @@
merlyn
merlyn.c
merlyn.alt.c
nextmove
nextmove.c
nextmove.alt.c
ttt
ttt.alt
ttt_game
ttt_game.c
ttt_game.alt.c
westley
westley.alt
ttt.alt_old.c
ttt_old.c
westley.orig
prog.orig
+59 -3
View File
@@ -117,8 +117,8 @@ OBJ= ${PROG}.o
DATA=
TARGET= ${PROG} ttt
#
ALT_OBJ=
ALT_TARGET=
ALT_OBJ= ${PROG}.alt.o
ALT_TARGET= ${PROG}.alt ttt.alt
#################
@@ -190,6 +190,60 @@ ttt_game.c: westley.c
${CP} -f westley.c ttt_game.c
${CHMOD} +w ttt_game.c
# alt version
#
# the hint file gives nextmove.alt.c as an example, provide a build rule for it
#
nextmove.alt: nextmove.alt.c ttt_alt
${CC} ${CFLAGS} -DP=putchar -DN="P(10)" -DQ="P(34)" -DF="S(&X)" \
-DJ=z[4] -DK=J[14] \
-DH="w[]={146,7,292,73,448,56,84,273,325,297,108,324,22,40,48,40,17,325}" \
nextmove.alt.c -o nextmove.alt
# the ttt_alt script has been added by the judges to make playing westley.alt easy
#
ttt_alt: ttt_alt.sh
${RM} -f ttt_alt
${CP} -f ttt_alt.sh ttt_alt
${CHMOD} +x ttt_alt
# used by the ttt_alt shell script when playing westley.alt
#
# The ttt_alt script controls what goes into merlyn.alt.c, and how it is used.
# The merlyn.alt program and source represent the best (hopefully) state that
# the program has evolved into thus far. The ttt_alt_game program and source
# represent the current state of the game.
#
merlyn.alt: merlyn.alt.c ttt_alt
@${CC} ${CFLAGS} -DP=putchar -DN="P(10)" -DQ="P(34)" -DF="S(&X)" \
-DJ=z[4] -DK=J[14] \
-DH="w[]={146,7,292,73,448,56,84,273,325,297,108,324,22,40,48,40,17,325}" \
merlyn.alt.c -o merlyn.alt
ttt_alt_game: ttt_alt_game.c ttt_alt
@${CC} ${CFLAGS} -DP=putchar -DN="P(10)" -DQ="P(34)" -DF="S(&X)" \
-DJ=z[4] -DK=J[14] \
-DH="w[]={146,7,292,73,448,56,84,273,325,297,108,324,22,40,48,40,17,325}" \
ttt_alt_game.c -o ttt_alt_game
# The upper level makefile wants to make these source files if they don't
# already exist.
#
merlyn.alt.c: westley.alt.c
${RM} -f merlyn.alt.c
${CP} -f westley.alt.c merlyn.alt.c
${CHMOD} +w merlyn.alt.c
nextmove.alt.c: westley.alt.c
${RM} -f nextmove.alt.c
${CP} -f westley.alt.c nextmove.alt.c
${CHMOD} +w nextmove.alt.c
ttt_alt_game.c: westley.alt.c
${RM} -f ttt_alt_game.c
${CP} -f westley.alt.c ttt_alt_game.c
${CHMOD} +w ttt_alt_game.c
# data files
#
data: ${DATA}
@@ -256,8 +310,10 @@ clean:
clobber: clean
${RM} -f ${TARGET} ${ALT_TARGET}
${RM} -f merlyn merlyn.c nextmove.c nextmove.c
${RM} -f merlyn merlyn.c nextmove nextmove.c
${RM} -f merlyn.alt merlyn.alt.c nextmove.alt nextmove.alt.c
${RM} -f ttt_game ttt_game.c ttt_old.c ttt_new.c nextmove
${RM} -f ttt.alt_game ttt.alt_game.c ttt.alt_old.c ttt_.altnew.c nextmove.alt
@-if [ -e sandwich ]; then \
${RM} -f sandwich; \
echo 'ate sandwich'; \
+71 -48
View File
@@ -4,6 +4,8 @@
make all
```
There is an [alternate version](#alternate-code) that is supposed to win all the
time.
### Bugs and (Mis)features:
@@ -18,7 +20,11 @@ For more detailed information see [1991 westley in bugs.md](/bugs.md#1991-westle
## To use:
Make and run as follows:
NOTE: we have provided a script to perform the below procedure which we include
for the interested. See the [try](#try) section if you just want to play.
Make and then run as follows:
```sh
@@ -34,7 +40,7 @@ a move on a tic-tac-toe board:
7 8 9
```
If you omit `move_location`, then the computer moves first. For your\
If you omit `move_location`, then the computer moves first. For your
next move, recompile `nextmove.c` and play it again:
```sh
@@ -45,7 +51,8 @@ make nextmove
### Try:
We have provided the shell script, [ttt](ttt.sh), to make it easier to play the game:
We have provided the shell script, [ttt](ttt.sh), to make it easier to play the
game:
```sh
make ttt
@@ -54,12 +61,12 @@ make ttt
```
The shell script determines when the game is over, and automatically\
replaces `merlyn.c` (copied from [westley.c](westley.c) with an improved version after a loss. The [ttt](ttt.sh) script\
has two other modes:
The shell script determines when the game is over, and automatically replaces
`merlyn.c` (copied from [westley.c](westley.c) with an improved version after a
loss). The [ttt](ttt.sh) script has two other modes:
```sh
./ttt quitgame # cancel any game in process, revert to merlyn.c
./ttt quitgame # cancel any game in progress, revert to merlyn.c
./ttt clobber # remove all game files, revert to original source
```
@@ -109,6 +116,24 @@ Redirects stderr to `/dev/null` by default. If you need errors or warnings use
`-e`.
## Alternate code:
This version is based on the author's remarks. It is not supposed to lose but it
can lose if you cheat. That's supposed to happen. Can you win any other time?
### Alternate build:
```sh
make alt
```
### Alternate use:
Rather than use `ttt` try the script `ttt.alt` instead.
## Judges' remarks:
Can you figure out why the board looks different than the
@@ -153,12 +178,11 @@ The program, when run, reproduces itself with both the player's
move and the computer's move added. Recompile THIS program (using
the same compile line) and repeat until the game is finished.
If the computer wins, the "straight face" in the upper right-hand
corner ":-|" will change into a happy face ":-)". If it is a draw,
the face does not change. If the computer loses, the board is
blanked and the face changes into a sad face ":-(". If this
happens, the blank board should replace the original program; the
computer changes its play and will continue to do so until it no
If the computer wins, the "straight face" in the upper right-hand corner, ":-|",
will change into a happy face, ":-)". If it is a draw, the face does not
change. If the computer loses, the board is blanked and the face changes into a
sad face, ":-(". If this happens, the blank board should replace the original
program; the computer changes its play and will continue to do so until it no
longer loses games.
If you want a program that never loses, simply replace the string
@@ -166,47 +190,47 @@ If you want a program that never loses, simply replace the string
### How it works
The vertical "bars" of the grid are identical functions, with the
name of the function as a(), b(), or c(). After each function is
called, the global variables X and O contain bitmasks of where all
the Xs and Os are placed. The statement:
The vertical "bars" of the grid are identical functions, with the name of the
function as `a()`, `b()`, or `c()`. After each function is called, the global
variables `X` and `O` contain bitmasks of where all the `X`s and `O`s are placed.
The statement:
```c
X=- <char> -1;
``
```
evaluates to 1, -1, or -2 when X is 1 and <char> is one of "X",
"0", or " ". The function S(&X) sets the appropriate bits in
(global var) X and O and resets the value of &X to 1. This is how
the current position is computed.
evaluates to `1`, `-1`, or `-2` when `X` is `1` and `<char>` is one of `"X"`,
`"0"`, or `" "`. The function `S(&X)` sets the appropriate bits in (global
vars) `X` and `O` and resets the value of `&X` to `1`. This is how the current
position is computed.
The player's move is OR'd into the X mask, and (if it is the
opening move) the strategy string's first character is set to the
player's first move (or "5" if the computer moves first).
The player's move is bitwise ORed into the `X` mask, and (if it is the opening
move) the strategy string's first character is set to the player's first move
(or `5` if the computer moves first).
The evalution function y(M) checks if the mask has a winning
pattern (any of the first eight bitmasks of the w[N array), and
sets the variable q to 0 if it does.
The evaluation function `y(M)` checks if the mask has a winning pattern (any of
the first eight bitmasks of the `w[N]` array), and sets the variable `q` to `0`
if it does.
The player's move is evaluated to see if the human has won. The
smiley is set to :-) or :-( depending on this. Also, if the human
has won, the digit in the strategy string is decremented to change
the computer's play for next game.
The player's move is evaluated to see if the human has won. The smiley is set
to ":-)" or ":-(" depending on this. Also, if the human has won, the digit in
the strategy string is decremented to change the computer's play for the next
round.
If the human has NOT won, the e(N,M) function evaluates if ANY move
not in mask N can produce a win in mask M. The first evaluation
e(X,O) checks if O can make a legal winning move. If so, the
If the human has NOT won, the `e(N,M)` function evaluates if ANY move
not in mask `N` can produce a win in mask `M`. The first evaluation
`e(X,O)` checks if `O` can make a legal winning move. If so, the
computer has a winning move and makes it. If not, the smiley is
set to :-|, and a non-winning move is generated.
set to ":-|", and a non-winning move is generated.
A non-winning move first checks if X can win in one move; if so,
A non-winning move first checks if `X` can win in one move; if so,
this move is selected, blocking the win. Otherwise, a move is
generated by looking down the list of "move templates". The first
move template that generates a move that is NOT already occupied by
an X or O is returned.
an `X` or `O` is returned.
The "move templates" are static tic-tac-toe positions that generate
possible moves by making the e() function return "winning" moves,
possible moves by making the `e()` function return "winning" moves,
e.g. the template:
```
@@ -215,14 +239,13 @@ O , O (this is the value 40; 2^3+2^5)
. . . ("O"s are 1 bits, "." and "," are 0 bits)
```
...will make the e() function return a center move, as
this is the only move that produces three in a row. Notice that
the computer does not consider which cells contain Xs and Os when
making a template move, only which cells are empty and not-empty.
This makes for unusual play.
...will make the `e()` function return a center move, as this is the only move
that produces three in a row. Notice that the computer does not consider which
cells contain `X`s and `O`s when making a template move, only which cells are
empty and not-empty. This makes for unusual play.
The templates are carefully chosen to eventually block all traps
that X can try, by just blundering in the way.
The templates are carefully chosen to eventually block all traps that `X` can
try, by just blundering in the way.
Also note that the winning & strategy templates are part of the
compile line, making it possible to change the rules of the game by
@@ -272,10 +295,10 @@ O , O
O , O
```
A digit of "9" means start with the first template, "8" starts at
A digit of `9` means start with the first template, `8` starts at
the second, etc. The templates are scanned until a legal move is
found. A template is scanned from bottom to top, right to left
(i.e. move 9 is tested first, then 8, down to move 1).
(i.e. move `9` is tested first, then `8`, down to move `1`).
## Copyright and CC BY-SA 4.0 License:
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env bash
#
# play tic-tac-toe
# parse args
#
export USAGE="usage: $0 [-h] [-e] <command>
-h show this help and exit
-e show compiler stdout
commands:
./ttt.alt [move_digit] play a place
./ttt.alt quitgame quit the game
./ttt.alt clobber clean the state of the game
"
export E_FLAG=
while getopts :he flag; do
case "$flag" in
h) echo "$USAGE" 1>&2
exit 2
;;
e) E_FLAG="1"
;;
\?) echo "$0: ERROR: invalid option: -$OPTARG" 1>&2
exit 3
;;
:) echo "$0: ERROR: option -$OPTARG requires an argument" 1>&2
exit 3
;;
*)
;;
esac
done
# check args
#
shift $(( OPTIND - 1 ));
if [[ $# -gt 1 ]]; then
echo "$USAGE" 1>&2
exit 1
elif [[ $# -eq 1 ]]; then
move="$1"
else
move=
fi
# catch the clobber case
#
if [[ clobber = "$move" ]]; then
echo "ttt.alt: rm -f ttt.alt_game ttt.alt_game.o ttt.alt_game.c ttt.alt_old.c ttt.alt_new.c" 1>&2
rm -f ttt.alt_game ttt.alt_game.o ttt.alt_game.c ttt.alt_old.c ttt.alt_new.c
echo "ttt.alt: rm -f merlyn.alt.c merlyn.o merlyn" 1>&2
rm -f merlyn.alt.c merlyn.o merlyn
echo "ttt.alt: removed all but the original source" 1>&2
exit 0
fi
# catch the quitgame case
#
if [[ quitgame = "$move" ]]; then
echo "ttt.alt: rm -f ttt.alt_game ttt.alt_game.o ttt.alt_game.c ttt.alt_old.c ttt.alt_new.c" 1>&2
rm -f ttt.alt_game ttt.alt_game.o ttt.alt_game.c ttt.alt_old.c ttt.alt_new.c
echo "ttt.alt: current game removed, perhaps you were going to lose? :-)" 1>&2
exit 0
fi
# watch for bad args
#
if [[ $# -eq 1 ]]; then
case "$move" in
1|2|3|4|5|6|7|8|9) ;;
*) echo "ttt.alt: bad argument" 1>&2
echo "usage: ./ttt.alt [move_digit]" 1>&2
echo " ./ttt.alt quitgame" 1>&2
echo " ./ttt.alt clobber" 1>&2
exit 2 ;;
esac
fi
# be sure that we have the merlyn.alt.c source
#
if [[ ! -f merlyn.alt.c ]]; then
if [[ -f westley.alt.c ]]; then
echo "ttt.alt: No merlyn.alt.c around these parts, using a copy of westley.alt.c" 1>&2
cp westley.alt.c merlyn.alt.c
chmod +w merlyn.alt.c
else
echo "ttt.alt: Ouch! merlyn.alt.c and no westley.alt.c are gone!" 1>&2
exit 3
fi
# check to see if the copy worked
if [[ ! -f merlyn.alt.c ]]; then
echo "ttt.alt: Darn, couldn't copy westley.alt.c to merlyn.alt.c" 1>&2
exit 4
fi
fi
# be sure we have the merlyn program
#
if [[ ! -f merlyn ]]; then
export status=0
if [[ -n $E_FLAG ]]; then
echo "ttt.alt: make merlyn" 1>&2
make merlyn
status=$?
else
make merlyn 2>/dev/null
status=$?
fi
if [[ "$status" -ne 0 ]]; then
echo "ttt.alt: something is wrong, fix merlyn.alt.c and build it yourself!" 1>&2
echo " or maybe type: ./ttt.alt clobber" 1>&2
exit 5
fi
rm -f ttt.alt_game.c
fi
# If we don't have a ttt.alt_game.c, or we cannot write it, make a new ttt.alt_game.c
#
if [[ ! -w ttt.alt_game.c ]]; then
rm -f ttt.alt_game.c
if [[ -z "$move" ]]; then
./merlyn | tee ttt.alt_game.c
else
./merlyn "$move" | tee ttt.alt_game.c
fi
chmod +w ttt.alt_game.c
# If we have a ttt.alt_game.c, then assume that we are in the process of playing
#
else
rm -f ttt.alt_game ttt.alt_new.c
export status=0
if [[ -n $E_FLAG ]]; then
echo "ttt.alt: make ttt.alt_game" 1>&2
make ttt.alt_game
status=$?
else
make ttt.alt_game 2>/dev/null
status=$?
fi
if [[ "$status" -ne 0 ]]; then
echo "ttt.alt: something is wrong, fix ttt.alt_game.c and build it yourself!" 1>&2
echo " or maybe type: ./ttt.alt quitgame" 1>&2
exit 6
fi
if [[ -z "$move" ]]; then
./ttt.alt_game | tee ttt.alt_new.c
else
./ttt.alt_game "$move" | tee ttt.alt_new.c
fi
chmod +w ttt.alt_new.c
if [[ -s ttt.alt_new.c ]]; then
mv ttt.alt_game.c ttt.alt_old.c
mv ttt.alt_new.c ttt.alt_game.c
# Check for win, lose, or draw
# For a win or draw, copy merlyn.alt.c to ttt.alt_game.c
# For a loss, copy ttt.alt_game.c to merlyn.alt.c, as it is now better
# Also remove the executable for the file replaced
# For anything else, game in progress, do not do anything
# SC2006 (style): Use $(...) notation instead of legacy backticks `...`.
# shellcheck disable=SC2006
if [[ -n "`grep ':-(' ttt.alt_game.c`" ]]; then
echo
echo "ttt.alt: I lose...I won't make THAT mistake again!"
chmod +w merlyn.alt.c
mv ttt.alt_game.c merlyn.alt.c
rm -f merlyn ttt.alt_game
else
# SC2006 (style): Use $(...) notation instead of legacy backticks `...`.
# shellcheck disable=SC2006
if [[ -n "`grep ':-)' ttt.alt_game.c`" ]]; then
echo
echo "ttt.alt: I win!"
cp merlyn.alt.c ttt.alt_game.c
rm -f ttt.alt_game
else
# SC2006 (style): Use $(...) notation instead of legacy backticks `...`.
# shellcheck disable=SC2006
if [[ -z "`grep 'X=-. ' ttt.alt_game.c`" && -z "`grep '^. ' ttt.alt_game.c`" ]]; then
echo
echo "ttt.alt: It's a draw - play again?"
cp merlyn.alt.c ttt.alt_game.c
rm -f ttt.alt_game
fi
fi
fi
else
echo "ttt.alt: something is wrong, maybe ttt.alt_old.c will help" 1>&2
echo " or maybe type: ./ttt.alt quitgame" 1>&2
echo " or maybe type: ./ttt.alt clobber" 1>&2
exit 7
fi
fi
+26
View File
@@ -0,0 +1,26 @@
a(X){/*/X=- a(X){/*/X=-
-1;F;X=- -1;F;X=-
-1;F;}/*/ -1;F;}/*/
char z[][100]={"char z[][100]={","a(X){/*/X=-","-1;F;X=-","-1;F;}/*/","9883857753 :-|",
"int q,i,j,k,X,O=0,H;S(x)int*x;{X+=X;O+=O;*x+1?*x+2||X++:O++;*x=1;}L(n){for(*",
"z[i=1]=n+97;i<4;i++)M(256),s(i),M(128),s(i),M(64),N;X*=8;O*=8;}s(R){char*r=z",
"[R];for(q&&Q;*r;)P(*r++);q&&(Q,P(44));}M(m){P(9);i-2||P(X&m?88:O&m?48:32);P(",
"9);}y(A){for(j=8;j;)~A&w[--j]||(q=0);}e(W,Z){for(i-=i*q;i<9&&q;)y(W|(1<<i++&",
"~Z));}R(){for(k=J[*J-48]-40;k;)e(w[k--],X|O);}main(u,v)char**v;{a(q=1);b(1);",
"c(1);*J=--u?O?*J:*v[1]:53;X|=u<<57-*v[u];y(X);K=40+q;q?e(O,X),q&&(K='|'),e(X",
",O),R(),O|=1<<--i:J[*J-48+(X=O=0)]--;L(q=0);for(s(i=0);q=i<12;)s(i++),i>4&&N",
";s(q=12);P(48);P('}');P(59);N;q=0;L(1);for(i=5;i<13;)s(i++),N;L(2);}",0};
b(X){/*/X=- b(X){/*/X=-
-1;F;X=- -1;F;X=-
-1;F;}/*/ -1;F;}/*/
int q,i,j,k,X,O=0,H;S(x)int*x;{X+=X;O+=O;*x+1?*x+2||X++:O++;*x=1;}L(n){for(*
z[i=1]=n+97;i<4;i++)M(256),s(i),M(128),s(i),M(64),N;X*=8;O*=8;}s(R){char*r=z
[R];for(q&&Q;*r;)P(*r++);q&&(Q,P(44));}M(m){P(9);i-2||P(X&m?88:O&m?48:32);P(
9);}y(A){for(j=8;j;)~A&w[--j]||(q=0);}e(W,Z){for(i-=i*q;i<9&&q;)y(W|(1<<i++&
~Z));}R(){for(k=J[*J-48]-40;k;)e(w[k--],X|O);}main(u,v)char**v;{a(q=1);b(1);
c(1);*J=--u?O?*J:*v[1]:53;X|=u<<57-*v[u];y(X);K=40+q;q?e(O,X),q&&(K='|'),e(X
,O),R(),O|=1<<--i:J[*J-48+(X=O=0)]--;L(q=0);for(s(i=0);q=i<12;)s(i++),i>4&&N
;s(q=12);P(48);P('}');P(59);N;q=0;L(1);for(i=5;i<13;)s(i++),N;L(2);}
c(X){/*/X=- c(X){/*/X=-
-1;F;X=- -1;F;X=-
-1;F;}/*/ -1;F;}/*/
+6
View File
@@ -1282,6 +1282,12 @@ also improved it so that warnings/errors/about to compile messages are not shown
unless the `-e` option is used. This is because the errors being shown kind of
ruins the experience. Finally he made it pass ShellCheck.
Cody also added the alt version which is based on the author's remarks, a
version that supposedly (:-) ) always wins.
Cody also fixed the make clobber rule where a file was left lying about when it
should have been removed.
## [1992/adrian](1992/adrian/adrian.c) ([README.md](1992/adrian/README.md]))