3#Author: Pieter van der Star (info@pietervanderstar.nl)
4#Modifications by: (unmodified)
5#This program is free software: you can redistribute it and/or modify
6#it under the terms of the GNU Affero General Public License as
7#published by the Free Software Foundation, either version 3 of the
8#License, or (at your option) any later version.
10#This program is distributed in the hope that it will be useful,
11#but WITHOUT ANY WARRANTY; without even the implied warranty of
12#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13#GNU Affero General Public License for more details.
15#You should have received a copy of the GNU Affero General Public License
16#along with this program. If not, see <https:
18#Feel free to use and modify as long as:
19# - This License stays intact.
20# - You give the original author(s) credit for their work
21# This includes the author(s) of modifications.
22# - The modifications are indicated clearly in the changelog
23# - The result must be free, in both monetary sense and personal sense
24# i.e. no account required, no personal data needs to be handed over.
25# - The software is provided as-is. Feature requests or bug reports may be
29#┏━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━┓
30#┃ 16 oct 2022 │ First release │ Pieter van der Star ┃
31#┗━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━┛
34#todo: x in center o in top right gives transpos error
35# so does x in center and o in bottom left a o in middle left
36# and mid right, top left vs bottom right for o are correct
38#TODO: add game status flag so it can tell as soon as it knows the game is won/lost/tied and also when it is inevetable so it can tell the player ie. "you cannot win anymore" "you lost"
40#This version of the game tic-tac-toe may not seem very good when you start out playing, but that is because it only knows the rules of the game, not the strategies involved. Neither does it calculate the best move by going through all the moves of the game. Well, that last part is not entirely true. It does kinda go through all the options. But it does this by playing. Each time you play the, program will learn if moves were good or bad. After a while it will stop using the bad ones and prefer the good ones, thus becoming a stronger player.
42#The program has a couple of elements.
43#The most important elements are the boards. The boards contain all valid states of the tic-tac-toe square. The mirrorings and rotations are ignored. This means that any board with an X in a corner and nothing else on the board is the same from the programs point of view. This means some mechanism is needed to help keep track of those transpositions. That is the job of the transposition elements. Together with some functions those elements make it possible to rotate and mirror the boards and put the user input on the correct square.
45#The progress of the learning is saved in a boards.txt file [note 1]. This allows curious people to monitor the learning progress, or save the intermediate stages if it becomes too smart to beat.
47#all variable names of the form b<integer> are reserved for boards
48#boards live in the global scope.
49#Since the current board needs to be passed around as well the variable name bCurrent is also reserved. As is the a temporary board bTemp.
51#The board element is quite complex and is stored as an array. In C it
52#would look something like this:
60# int* children_weights;
63#note 1: unless otherwise specified by a command line argument.
65#print the introduction
69 printf
" ###############################################\n";sleep 0.3;
70 printf
" #################################################\n";sleep 0.3;
71 printf
"## ##\n";sleep 0.3;
72 printf
"## Tic Tac Toe or naughts and crosses ##\n";sleep 0.3;
73 printf
"## ##\n";sleep 0.3;
74 printf
"## Created by Pieter van der Star ##\n";sleep 0.3;
75 printf
"## Inspired by Donald Mitchies: ##\n";sleep 0.3;
76 printf
"## M E N A C E ##\n";sleep 0.3;
77 printf
"## ##\n";sleep 0.3;
78 printf
"## Matchbox ##\n";sleep 0.3;
79 printf
"## Educable ##\n";sleep 0.5;
80 printf
"## Naughts ##\n";sleep 0.5;
81 printf
"## And ##\n";sleep 0.3;
82 printf
"## Crosses ##\n";sleep 0.3;
83 printf
"## Engine ##\n";sleep 0.5;
84 printf
"## ##\n";sleep 0.3;
85 printf
" #################################################\n";sleep 0.3;
86 printf
" ###############################################\n";sleep 0.3;
87 printf
" This game is released under the GNU AGPL 3.0 license\n";sleep 0.3;
88 printf
" https://www.gnu.org/licenses/agpl-3.0.html\n";sleep 0.3;
89 printf
" A link to Matt Parkers video on Menace\n";sleep 0.3;
90 printf
" which first introduced the idea to me:\n";sleep 0.3;
91 printf
" https://m.youtube.com/watch?v=R9c-_neaxeU\n";sleep 0.3;
92 printf
" I wrote most of the code based on that video\n";sleep 0.3;
93 printf
" But when it came to the weights (number of beads)\n";sleep 0.3;
94 printf
" I read the paper Experiments on the mechanization\n";sleep 0.3;
95 printf
" of game-learning by Donald Michie.\n";sleep 0.3;
99#A bunch of constants used for board propeties and game states
101#array offsets of board "object"
102declare -i -r C_NAME_OFFSET=0;
103declare -i -r C_CELLS_OFFSET=1;
104declare -i -r C_VALUE_OFFSET=10;
105declare -i -r C_NEXT_OFFSET=11;
107#some offsets need to be calculated, the follow after the constants
110#constants used to keep track of whos turn it is
111declare -i -r C_USER=1;
112declare -i -r C_COMPUTER=-1;
114#custom boolean values
115declare -i -r C_TRUE=1;
116declare -i -r C_FALSE=0;
118declare -i -r C_NUM_ALLBOARDS=19683;#922;#combinations=3^9 but a lot are pruned after first invalid is encountered (abs(numX-numO)>1)
120#make it easier to print the naughts and crosses
128#transposition indice matrices
129h0v0r0=( 0 1 2 3 4 5 6 7 8 0 );
130h0v0r1=( 2 5 8 1 4 7 0 3 6 0 );
131h0v0r2=( 8 7 6 5 4 3 2 1 0 0 );
132h0v0r3=( 6 3 0 7 4 1 8 5 2 0 );
134h0v1r0=( 6 7 8 3 4 5 0 1 2 0 );
135h0v1r1=( 8 5 2 7 4 1 6 3 0 0 );
136h0v1r2=( 2 1 0 5 4 3 8 7 6 0 );
137h0v1r3=( 0 3 6 1 4 7 2 5 8 0 );
139h1v0r0=( 2 1 0 5 4 3 8 7 6 0 );
140h1v0r1=( 0 3 6 1 4 7 2 5 8 0 );
141h1v0r2=( 6 7 8 3 4 5 0 1 2 0 );
142h1v0r3=( 8 5 2 7 4 1 6 3 0 0 );
144#calculates and sets the id of a given transposition
145#ars: transposition name
147#modifies transposition id
148function calculateTranspositionId {
152 p=( 11 13 17 19 23 29 31 37 41 )
154 while [
$i -lt 9 ];
do
164#calculates and sets the id of all transpositio ns
167#modifies transposition ids
168function calculateTranspositionIds {
170 while [ $h -le 1 ];
do
172 while [ $v -le 1 ];
do
173 if [[ $h -eq 1 && $v -eq 1 ]]; then
177 while [ $r -le 3 ];
do
178 calculateTranspositionId
"h$h""v$v""r$r";
187#directly call calculateTranspositionIds
188calculateTranspositionIds
190#transpose a transposition
191#args: transposition name, transposition name
193#sets global transposition
194function transposeTransposition {
195 local -i verbosity=0;
196 if [ $bTmp ]; then #
this function needs bTmp, so backup the current value
201 bTmp=( 0 0 0 0 0 0 0 0 0 0 );
203 while [
$i -lt 9 ];
do
209 calculateTranspositionId
"bTmp"
212 while [ $h -le 1 ];
do
214 while [ $v -le 1 ];
do
215 if [[ $h -eq 1 && $v -eq 1 ]]; then
219 while [ $r -le 3 ];
do
220 name=
"h${h}v${v}r${r}";
221 if [ $verbosity -gt 0 ]; then
226 if [ ${t[9]} -eq ${bTmp[9]} ]; then
227 transposition=
"$name";
236 transposition=
"h0v0r0"
238 if [ $bTempbu ]; then
245#arg2: transposition to apply
246#arg3: bool, print properties (like list of children)
247#arg4: bool, print coordinates (should it print column anc row indicators)
249 local -i verbosity=0;
251 printf
"Board %s does not exist\n" $1;
255 if [ $verbosity -ge 2 ]; then
258 if [ $# -gt 1 ]; then
259 local -r transposition=
"$2"
261 local -r transposition=
"h0v0r0"
264 local -n matrix=
"$transposition";
266 if [ $# -gt 2 ]; then
270 if [ $# -gt 3 ]; then
273 if [ $printProperties -eq
$C_TRUE ]; then
274 printf
"name: %s\n" ${board[$C_NAME_OFFSET]};
275 printf
"transposition: %s\n" $transposition
278 if [ $printCoordinates -eq
$C_TRUE ]; then
284 while [ $r -lt 3 ];
do
286 if [ $printCoordinates -eq
$C_TRUE ]; then
292 while [ $c -lt 3 ];
do
294 local -i pos=${matrix[$(((r*3+c)))]}
295 val=
"${board[$((C_CELLS_OFFSET+pos))]}";
298 if [ $c -lt 2 ]; then
305 if [ $r -lt 2 ]; then
306 printf
"\n ┠─┼─┼─┨\n";
310 printf
"\n ┗━┷━┷━┛\n";
312 if [ $printProperties -eq
$C_TRUE ]; then
313 printf
"value: %d\n" ${board[$((C_VALUE_OFFSET))]};
315 while [ $v -lt 2 ];
do
316 local -i nnext=${board[$((C_NEXT_OFFSET+v))]};
317 local -i firstChildOffset=${board[$C_NEXT_OFFSET]};
318 firstChildOffset=$((firstChildOffset*v+C_NEXT_OFFSET+2));
319 printf
"number of next boards for %s: %d\n" ${xo[$v]} $nnext;
320 printf
"firstChildOffset: %d\n" $firstChildOffset;
322 while [
$i -lt $nnext ];
do
327 printf
"%s(%d)," "${board[$((i+firstChildOffset))]}" "${board[$((i+firstChildOffset+nnext_x+nnext_o))]}";
338#todo check if already exists
341 board[$C_NAME_OFFSET]=
"$1"
344 while [ $r -lt 3 ];
do
346 while [ $c -lt 3 ];
do
347 board[$((C_CELLS_OFFSET+(r*3+c)))]=
" ";
352 board[$C_NEXT_OFFSET]=0;
353 board[$C_NEXT_OFFSET+1]=0;
356#helper function for is won
357#checks if one player has all boxes in the row
364 while [ $c -lt 3 ];
do
365 curVal=
"${board[$((C_CELLS_OFFSET+(3*r+c)))]}";
366 if [
"$curVal" ==
" " ]; then
369 if [[ $c -gt 0 &&
"$prevVal" !=
"$curVal" ]]; then
380#helper function for is won
381#checks if one player has all boxes in the column
382function isWonColumn {
388 while [ $r -lt 3 ];
do
389 curVal=
"${board[$((C_CELLS_OFFSET+(3*r+c)))]}";
390 if [
"$curVal" ==
" " ]; then
393 if [[ $r -gt 0 &&
"$prevVal" !=
"$curVal" ]]; then
403#helper function for is won
404#checks if one player has all boxes in the diagonal going right
405function isWonDiagonalR {
410 while [
$i -lt 3 ];
do
411 curVal=
"${board[$((C_CELLS_OFFSET+(3*i+i)))]}";
412 if [
"$curVal" ==
" " ]; then
415 if [[
$i -gt 0 &&
"$prevVal" !=
"$curVal" ]]; then
425#helper function for is won
426#checks if one player has all boxes in the diagonal going left
427function isWonDiagonalL {
432 while [
$i -lt 3 ];
do
433 curVal=
"${board[$((C_CELLS_OFFSET+(3*(i+1)-i)-1))]}";
434 if [
"$curVal" ==
" " ]; then
437 if [[
$i -gt 0 &&
"$prevVal" !=
"$curVal" ]]; then
448#checks if one player has won the game
450#returns boolean true if a player won, otherwise false
452 local -i verbosity=0;
457 while [ $r -lt 3 ];
do
458 isWonRow $boardName $r
460 if [ $verbosity -gt 0 ]; then
461 printf
"Found winning board(r%d)\n" $r;
470 while [ $c -lt 3 ];
do
471 isWonColumn $boardName $c
473 if [ $verbosity -gt 0 ]; then
474 printf
"Found winning board (c%d)\n" $c;
482 isWonDiagonalL $boardName
484 if [ $verbosity -gt 0 ]; then
485 printf
"Found winning board (DiagL)\n";
489 isWonDiagonalR $boardName
491 if [ $verbosity -gt 0 ]; then
492 printf
"Found winning board (DiagR)\n";
500#checks if the board is a valid board
501#the number of x and o should never differ more than 1
503#returns boolean, true if valid, otherwise false
511 while [ $r -lt 3 ];
do
513 while [ $c -lt 3 ];
do
514 val=
"${board[$((C_CELLS_OFFSET+(r*3+c)))]}"
515 if [
"$val" ==
"X" ]; then
517 elif [
"$val" ==
"O" ]; then
524local -i diff=$((nX-nO));
526if [[ -2 -lt $diff && $diff -lt 2 ]]; then
534#function to get the number of empty cells
536#returns numbere of empty cells
543 while [ $r -lt 3 ];
do
545 while [ $c -lt 3 ];
do
546 val=
"${board[$((C_CELLS_OFFSET+(r*3+c)))]}"
547 if [
"$val" ==
" " ]; then
548 nEmpty=$((nEmpty+1));
557#copy the values of one board to another
558#args: board name to copy from
559# board name to copy to
561function copyBoard { #only the values are copied
562 local -n originalBoard=$1;
563 #create the new board
565 if [ $verbosity -gt 0 ]; then
566 printf
"copying %s to %s\n" $1 $2
568 makeBoard
"$boardName"
569 local -n newBoard=
"$boardName"
572 while [ $r -lt 3 ];
do
574 while [ $c -lt 3 ];
do
575 newBoard[$((C_CELLS_OFFSET+(r*3+c)))]=
"${originalBoard[$((C_CELLS_OFFSET+(r*3+c)))]}"
582#calculate and set the value of a board
585 #sets the board value of the board object
586 function calculateBoardValue {
591 local -i boardValue=0;
593 posValueMatrix=( 17 19 17
597 while [ $r -lt 3 ];
do
599 while [ $c -lt 3 ];
do
600 val=
"${board[$((C_CELLS_OFFSET+(r*3+c)))]}"
606 boardValue=$((boardValue+val*${posValueMatrix[$((r*3+c))]}));
611 if [ $verbosity -gt 0 ]; then
612 printf
"value: %d\n" $boardValue;
614 board[$C_VALUE_OFFSET]=$boardValue;
617 #check if a given board exists
619 #returns bool, true if found, false if not.
620 #sets global dubplicateboard with the name of the board that is a match
621 #sets the global transposition
622 function doesBoardExist {
623 local -i verbosity=0;
624 if [ $verbosity -gt 0 ]; then
625 echo
"boardNumber: $boardNumber";
629 while [
$i -ne $boardNumber ];
do
631 if [ $verbosity -gt 0 ]; then
632 echo
"checking against b$i"
633 echo
"if [ ${board[$C_VALUE_OFFSET]} -eq ${b[$C_VALUE_OFFSET]} ]; then"
634 echo
"if [ ${board[$C_NAME_OFFSET]} != ${b[$C_NAME_OFFSET]} ]; then"
636 if [ ${board[$C_VALUE_OFFSET]} -eq ${b[$C_VALUE_OFFSET]} ]; then
637 if [
"${board[$C_NAME_OFFSET]}" !=
"${b[$C_NAME_OFFSET]}" ]; then
638 findTransposition
"$board" "$b"
640 if [ $verbosity -gt 0 ]; then
641 printf
"Found another board for %s: %s (%s)\n" "${board[$C_NAME_OFFSET]}" "${b[$C_NAME_OFFSET]}" "$transposition";
643 duplicateBoard=
"${b[$C_NAME_OFFSET]}";
653#given to boards figure out how to transpose one to get the other
654#arg1: board that anchors the transposition
655#arg2: board to get the transposition for
656#returns boolean, true if successful, false if boards are to different
657function findTransposition {
658 local -i verbosity=0;
659 if [ $verbosity -gt 1 ]; then
660 printf
"findTransposition: %s %s\n" "$1" "$2"
665 if [ $verbosity -gt 1 ]; then
669 local -i h; #horizontal mirroring
670 local -i v; #vertical mirroring
671 local -i r; #rotation
672 local -i i; #cell iterator
675 while [ $h -lt 2 ];
do
677 while [ $v -lt 2 ];
do
678 #double mirroring is same as no mirroring but rotated twice
679 if [[ $h -eq 1 && $v -eq 1 ]]; then
683 while [ $r -lt 4 ];
do
684 local -n matrix=
"h$h""v$v""r$r";
685 transposition=
"h$h""v$v""r$r"
686 if [ $verbosity -gt 1 ]; then
687 printf
"transpose: %s\n" $transposition
690 while [
$i -lt 9 ];
do
691 local -i m=${matrix[
$i]}
692 if [ $verbosity -gt 1 ]; then
693 printf
"%d |%s| != |%s| %d\n" $i "${a[$((C_CELLS_OFFSET+i))]}" "${b[$((C_CELLS_OFFSET+m))]}" $m
695 if [
"${a[$((C_CELLS_OFFSET+i))]}" !=
"${b[$((C_CELLS_OFFSET+m))]}" ]; then
700 if [
$i -eq 9 ]; then
713 local -n -r board=
"$1"
715 local -i nnext=${board[$((C_NEXT_OFFSET+v))]};
719function getFirstChildOffset {
720 local -n -r board=
"$1"
722 local -i firstChildOffset;
723 if [ $v -eq 0 ]; then
724 local -i firstChildOffset=$C_NEXT_OFFSET+2;
728 firstChildOffset=$((C_NEXT_OFFSET+nnextX+2));
730 return $firstChildOffset;
733function getChildWeight {
734 local -n -r board=
"$1"
743 getFirstChildOffset
"$1" "$v"
744 local firstChildOffset=$?
745 return ${board[$((firstChildOffset+nnext_x+nnext_o+index))]}
748function setChildWeight {
749 local -n -r board=
"$1"
752 local -i -r weight=$4
759 getFirstChildOffset
"$1" "$v"
760 local firstChildOffset=$?
761 board[$((firstChildOffset+nnext_x+nnext_o+index))]=$weight
764#add a child from the list of children
765#arg1: parent (to add the child to)
767#arg3: which child list to add to (X or O)
769function insertChild {
770 local -n -r parent=
"$1"
773 local -i -r weight=$4
774 local -i verbosity=0;
776 if [ $verbosity -gt 0 ]; then
777 printf
"insertChild.parent: %s\n" "${parent[$C_NAME_OFFSET]}"
778 printf
"insertChild.child: %s\n" $child
779 printf
"insertChild.v: %d\n" $v
780 printf
"insertChild.weight: %d\n" $weight
789 getFirstChildOffset
"$1" "$v"
790 local firstChildOffset=$?
793 #if for X then shift O to make room
794 #and always shift the weights
795 #TODO: maybe implement a smarter way, but that requires more
796 #handling on firstChildOffset and weight offset calculations
797 local -i weightInsertPoint=$((${#parent[@]}+1));
798 local -i childInsertPoint=$((${#parent[@]}-nnext_x-nnext_o));
799 if [ $v -eq 0 ]; then
800 weightInsertPoint=$((weightInsertPoint-nnext_o));
801 childInsertPoint=$((childInsertPoint-nnext_o));
804 if [ $verbosity -gt 0 ]; then
805 printf
"weightInsertPoint: %d\n" $weightInsertPoint
806 printf
"childInsertPoint: %d\n" $childInsertPoint
808 local -i i=$((${#parent[@]}+1));
810 while [
$i -gt $weightInsertPoint ];
do
811 parent[
$i]=${parent[$((i-2))]}; #2 so to make room
for the child and the weight
814 #now we have made room for the weight we can insert that
815 parent[$((i+0))]=$weight
818 while [
$i -gt $childInsertPoint ]; do
819 parent[
$i]=${parent[$((i-1))]};
825 local -i nnext=${parent[$((C_NEXT_OFFSET+v))]};
826 if [ $verbosity -gt 0 ]; then
827 printf
"insertChild.nnext[%d]: %d\n" $v $nnext
831 parent[$((C_NEXT_OFFSET+v))]=$nnext;
832 if [ $verbosity -gt 0 ]; then
833 printf
"insertChild.nnext[%d]: %d\n" $v $nnext
837#makes all children and other offspring from the given board
838#as the function runs recursive global progress variables are used
840#arg2: depth, used during debug
842#sets global variables used for tracking progress
843#sets global variables for the boards
844declare -i makeNextBoardsProgress=0;
845declare -i makeNextBoardsProgressTreshold=20;
846declare -i makeNextBoardsDepth=0;
847function makeNextBoards {
853 local -i verbosity=0;
854 if [[ $depth -gt $makeNextBoardsDepth && $makeNextBoardsDepth -ne 0 ]]; then
858 if [ $makeNextBoardsProgress -gt $makeNextBoardsProgressTreshold ]; then
859 makeNextBoardsProgressTreshold=$((makeNextBoardsProgressTreshold+(C_NUM_ALLBOARDS/1000)));
860 local -i progress=$((makeNextBoardsProgress*1000/$C_NUM_ALLBOARDS));
861 printf
"Making boards, progress: %3d.%01d%%\n" $((progress/10)) $((progress%10));
862 # printf "Making boards, progress: %3d.%01d%% #%9d\n" $((progress/10)) $((progress%10)) $makeNextBoardsProgress 1>&2
866 while [ $v -lt 2 ];
do
868 while [ $r -lt 3 ];
do
870 while [ $c -lt 3 ];
do
871 makeNextBoardsProgress=$((makeNextBoardsProgress+1));
872 val=
"${board[$((C_CELLS_OFFSET+(r*3+c)))]}"
873 if [
"$val" ==
" " ]; then
874 #create the new board
875 boardName=
"b$boardNumber";
876 if [ $verbosity -gt 0 ]; then
877 printf
"boardName: %s\n" $boardName;
879 boardNumber=$((boardNumber+1));
880 copyBoard
"$1" "$boardName";
881 local -n newBoard=$boardName;
882 newBoard[$((C_CELLS_OFFSET+(r*3+c)))]=${xo[$v]};
883 if [ $verbosity -gt 1 ]; then
884 printf
"set %d,%d to %s, v: %d\n" $r $c
"${xo[$v]}" $v;
887 if [ $verbosity -gt 3 ]; then
888 printf
"new board is\n"
894 if [ $verbosity -gt 1 ]; then
895 printf
"board no good, scrapping\n";
898 boardNumber=$((boardNumber-1));
899 if [ $verbosity -gt 1 ]; then
900 printf
"skipping to next symbol\n"
907 calculateBoardValue
"$boardName";
908 doesBoardExist
"$boardName";
911 if [ $exists -eq
$C_TRUE ]; then
912 #set the existing one as child, but add the transposition
913 insertChild
"$1" "$duplicateBoard$transposition" $v
914 if [ $verbosity -gt 1 ]; then
915 printf
"board exists, scrapping this version\n";
918 boardNumber=$((boardNumber-1));
919 boardName=
"$duplicateBoard$transposition"
924 if [ $verbosity -gt 1 ]; then
925 printf
"New board: %s\n" $boardName;
927 insertChild
"$1" "$boardName""h0v0r0" $v
930 makeNextBoards $boardName $((depth+1));
939 if [ $verbosity -gt 2 ]; then
940 printf
"makeNextBoards tried: %d boards\n" $makeNextBoardsProgress;
944#write the board information to a file
947function writeBoardsInfo {
948 if [ $# -gt 0 ]; then
953 if [ $verbosity -gt 1 ]; then
954 printf
"boardNumber: %d\n" $boardNumber
959 printf
"#Board config\n" >
$file
960 while [
$i -lt $boardNumber ];
do
962 local -n board=
"b$i";
965 printf
"%s=( %s" ${board[$C_NAME_OFFSET]} ${board[$C_NAME_OFFSET]} >>
$file
969 while [ $r -lt 3 ];
do
971 while [ $c -lt 3 ];
do
972 val=
"${board[$((C_CELLS_OFFSET+(r*3+c)))]}"
973 printf
" \"%s\"" "$val" >>
$file
980 printf
" %d" ${board[$((C_VALUE_OFFSET))]} >>
$file
982 #print number of boards
984 while [ $v -lt 2 ];
do
985 getNNext
"${board[0]}" $v
987 printf
" %d " $nnext >>
$file
991 #print the next boards
993 getFirstChildOffset
"${board[0]}" 0
995 while [ $j -lt ${#board[@]} ];
do
996 printf
" %s" "${board[$j]}" >>
$file
1001 printf
" );\n" >>
$file
1003 if [ $((i%(boardNumber/100))) -eq 0 ]; then
1004 local -i progress=$((i*1000/boardNumber));
1005 printf
"Writing boards info, progress: %3d.%01d%%\n" $((progress/10)) $((progress%10));
1009 printf
"boardNumber=%d;\n" $boardNumber >>
$file;
1010 printf
"gameCounter=%d;\n" $gameCounter >>
$file;
1011 printf
"\n" >>
$file;
1012 printf
"Writig boards info complete\n";
1015#figure out the depth of this board
1022 while [
$i -lt 9 ];
do
1023 if [
"${bTmp[$((C_CELLS_OFFSET+i))]}" !=
" " ]; then
1032#set the weight for each child.
1033#The weight is detemined by how far down the tree the board is.
1036#modifies global boards
1037function setStartingWeights {
1038 #michi used the following weights
1045 #but that gives us a problem with levels 0,2,4,6, the most simple solution is to double the weights and then fill in the in between integer values.
1046 #This gives us the below table
1057 #the downside of this approach is that training will take longer, the other option is to apply the same weight on two depths
1069 #this only leaves level 0, this will be getting the value 5
1071 weights=( 5 4 4 3 3 2 2 1 1 );#todo: what was the syntax
for a local array again?
1074 while [
$i -lt $boardNumber ];
do
1075 local boardName=
"b$i";
1077 while [ $v -le 1 ];
do
1078 #printf "v: %d\n" $v
1079 local -n bTmp=
"$boardName"
1080 getNNext
"$boardName" $v;
1082 local -i firstChildOffset=${bTmp[$C_NEXT_OFFSET]};
1084 while [ $c -lt $nnext ];
do
1085 local child=
"${bTmp[$((firstChildOffset+c))]}"
1086 getDepth
"$boardName"
1088 setChildWeight
"$boardName" $v $c ${weights[$depth]}
1093 if [ $((i%(boardNumber/100))) -eq 0 ]; then
1094 local -i progress=$((i*1000/boardNumber));
1095 printf
"Setting starting weights, progress: %3d.%01d%%\n" $((progress/10)) $((progress%10));
1102#performs the main game b
1103#arg1: file to read from
1106 if [ $# -gt 0 ]; then
1109 local file=
"boards.txt";
1111 if [ -f
"$file" ]; then
1112 printf
"Loading boards config\n";
1114 if [ $verbosity -gt 1 ]; then
1115 echo
"bn: $boardNumber"
1117 if [ $boardNumber -eq 0 ]; then
1118 printf
"Input file doesn't contain the required information\n";
1122 printf
"Currently the boards file is missing.\n";
1123 printf
"A new one will be created but this will take some time.\n";
1124 printf
"press q to exit, press any other key to create the file.\n";
1130 printf
"Creating boards config\n";
1132 boardNumber=$((boardNumber+1));
1134 calculateBoardValue
"b0";
1135 makeNextBoards
"b0";
1136 printf
"end of makeNextBoards\n";
1137 if [ $verbosity -gt 2 ]; then
1138 printf
"makeNextBoards tried: %d boards\n" $makeNextBoardsProgress;
1140 #add starting weights
1141 printf
"setting starting weights\n";
1143 printf
"writing config to file. This can take some time\n"
1144 writeBoardsInfo
"$board_src"
1149#check if a given board is a child of another board.
1150#arg1:board name to find
1151#arg2:board name of parent
1152#returns bool, found or not found
1154#sets global transposition to transposition of the child
1155#sets global index to the index of the child
1160 if [ $verbosity -gt 2 ]; then
1161 printf
"see if %s has child %s\n" $parent $find
1165 if [
"$3" ==
"O" ]; then
1169 #strip transposition
1170 if [ ${find: -2:1} ==
"r" ]; then
1171 find=${find:0:$((${#find}-6))};
1175 if [ $verbosity -gt 2 ]; then
1177 if [ $verbosity -gt 3 ]; then
1181 echo
"${tempBoard[@]}"
1183 local -n parentBoard=
"$parent"
1184 local -n findBoard=
"$find"
1185 getNNext
"$parent" "$v"
1187 getFirstChildOffset
"$parent" "$v"
1188 local -i firstChildOffset=$?;
1189 while [
$i -lt $nnext ];
do
1190 boardName=
"${parentBoard[$((firstChildOffset+i))]}"
1191 if [ $verbosity -ge 2 ]; then
1192 printf
"checking %s (%d/%d)\n" "$boardName" $((i+1)) $nnext
1194 #get transposition information
1195 if [ ${boardName: -2:1} ==
"r" ]; then
1196 transposition=${boardName:$((${#boardName}-6))};
1197 boardName=${boardName:0:$((${#boardName}-6))};
1199 local -n tempBoard=$boardName;
1200 if [ $verbosity -ge 4 ]; then
1203 if [ $verbosity -ge 2 ]; then
1204 printf
"Name: %s\n" ${findBoard[$C_NAME_OFFSET]}
1205 printf
"values: %d ?= %d\n" ${findBoard[$C_VALUE_OFFSET]} ${tempBoard[$C_VALUE_OFFSET]}
1207 if [ ${findBoard[$C_VALUE_OFFSET]} -eq ${tempBoard[$C_VALUE_OFFSET]} ]; then
1208 if [ $verbosity -ge 2 ]; then
1209 printf
"found board: %s at index %d\n" $boardName
$i;
1211 if [ $verbosity -gt 0 ]; then
1216 findTransposition
"$boardName" "findBoard";
1218 if [ $verbosity -ge 3 ]; then
1219 printf
"is a transposition of child\n"
1224 if [ $verbosity -ge 3 ]; then
1225 printf
"not the same board\n";
1229 if [ $verbosity -ge 3 ]; then
1230 printf
"no value match\n"
1238#get the indices of all the children with a given name
1239#arg1:board name to find
1240#arg2:board name of parent
1241#srg3: bool, when true ignore the transposition
1244#sets global VAR to array containing the indices of all
1245#the matching children
1246function findChildByName {
1249 local noTransposition=
"$3"
1251 if [
"$noTransposition" ==
"" ]; then
1252 noTransposition=
"$3"
1255 getFirstChildOffset
"$parent"
1258 getNNext
"$parent" 0
1259 local -i -r nnext_x=$?
1260 getNNext
"$parent" 1
1261 local -i -r nnext_o=$?
1262 local -i nnext=$((nnext_x+nnext_o));
1263 local -i lastChildIndex=$((i+nnext));
1264 local -n board=
"$parent"
1266 if [ $noTransposition -eq
$C_TRUE ]; then
1267 if [ ${find: -2:1} ==
"r" ]; then
1268 find=${find:0:$((${#find}-6))};
1272 while [
$i -lt $lastChildIndex ];
do
1273 local child=
"${board[i]}";
1274 if [ $noTransposition -eq
$C_TRUE ]; then
1275 local childName=${child:0:$((${#child}-6))};
1276 if [
"$find" ==
"$childName" ]; then
1280 if [
"$find" ==
"$child" ]; then
1287 local -n board=
"$parent";
1290#to keep track of all the moves made
1292#add a breadcrumb to the trace
1296function addBreadcrumb {
1298 local -i index=${breadcrumbs[0]};
1300 breadcrumbs[$index]=
"$board";
1301 breadcrumbs[0]=$index;
1304#change the weight of a board with a given delta
1305#arg1: board name of parent
1306#arg2: board name of child
1307#arg3: is the child an x or an o child
1308#arg4: the delta with which to change the weight
1309#TODO: use getChildWeight and setChildWeight instead
1310# better update setWeight to allow + and - prefixes
1312function changeWeight {
1313 local -r parent=
"$1";
1314 local -i -r childIndex=
"$2";
1316 local -i -r delta=$4;
1317 getNNext
"$parent" 0
1318 local -i -r nnext_x=$?
1319 getNNext
"$parent" 1
1320 local -i -r nnext_o=$?
1321 local -i -r nnext=$((nnext_x+nnext_o))
1322 local -n parentBoard=
"$parent";
1323 local -i curWeight=${parentBoard[$((childIndex+nnext))]};
1324 local -i newWeight=$((curWeight+(delta)));
1325 if [ $newWeight -le 0 ]; then
1327 elif [ $newWeight -ge 255 ]; then
1330 parentBoard[$((childIndex+nnext))]=$newWeight;
1334#when the game is done change the likelyhoods of a board
1336#arg1: symbol of the winner
1338#modifies all boards that were used during this match
1339function setNewWeights {
1341 local playingAs=
"$2"
1343 printf
"Learning from last match...\n"
1345 if [ $verbosity -ge 1 ]; then
1346 echo
"winner was: $1"
1349 if [ $verbosity -ge 2 ]; then
1350 if [
"$whoWon" ==
" " ]; then
1351 echo
"Was a tie, lightly strengthening both branches"
1353 echo
"Someone won, strengthening branch for the winner and weakening branch for the other";
1356 #winner is always the last player to do a move
1358 local -i isWinner=1;
1359 local -i i=${breadcrumbs[0]}
1360 printf
"%d moves made\n" $i
1361 #the first board is always b0, but that is not a move, so not stored as breadcrumb. Here we need it, so we replace the size field with the b0 board
1362 breadcrumbs[0]=
"b0h0v0r0"
1363 while [
$i -gt 0 ];
do
1364 local parent=
"${breadcrumbs[$((i-1))]}"
1365 #strip the transposition
1366 parent=${parent:0:$((${#parent}-6))};
1368 local child=
"${breadcrumbs[$i]}"
1370 if [ $verbosity -ge 2 ]; then
1371 printf
"processing board %s\n" $child
1372 printf
"parent: %s\n" "$parent"
1374 findChildByName
"$child" "$parent" $C_TRUE
1376 if [
"$whoWon" ==
" " ]; then
1379 if [ $isWinner -gt 0 ]; then
1384 isWinner=$((isWinner*-1));
1388 while [ $j -lt ${#VAR[@]} ];
do
1389 changeWeight
"$parent" ${VAR[$j]} $((i%2)) $delta
1397#sets global VAR with array filled with all the possible next moves and indexes
1398#move0, index0, move3, index3, etc
1399function getNextMoves {
1400 local -r boardName=
"$1";
1402 local -n -r board=
"$boardName";
1403 local -i verbosity=0;
1404 getNNext
"$boardName" $v
1407 if [ $nnext -eq 0 ]; then
1413 getFirstChildOffset
"$boardName" $v
1414 local -i firstChildOffset=$?
1416 while [ $index -lt $nnext ];
do
1417 getChildWeight
"$boardName" $v $index
1419 if [ $verbosity -gt 0 ]; then
1420 printf
"index %d, weight: %d\n" $index $weight;
1423 while [ $j -lt $weight ];
do
1424 if [ $verbosity -gt 0 ]; then
1425 #echo "moves: ${moves[@]}";
1426 echo
"moves: ${VAR[@]}";
1428 #moves[$nMoves]=${board[$((firstChildOffset+index))]};
1429 VAR[$nMoves]=${board[$((firstChildOffset+index))]};
1430 nMoves=$((nMoves+1))
1442#print help on the command line arguments
1445function printArgsHelp {
1446 printf
"Help for the tic tac toe game\n";
1447 printf
" Start the game and then press h for gameplay help\n"
1448 printf
" -r <file> read the board settings from specified file\n"; #
if not exist
try default or just quit?
1449 printf
" -w <file> write the board settings to specified file\n";
1450 printf
" if file is an empty string the boards are\n";
1451 printf
" not saved\n";
1452 printf
" -? user goes first(unsupported)\n";
1453 printf
" -? disable learning\n";
1454 printf
" -n numeric input mode\n"
1455 printf
" -N numpad numeric input mode\n"
1456 printf
" -v <value> set verbosity, each subfunction has its own\n";
1457 printf
" verbosity. Default value is 0 -> off.\n";
1458 printf
" -t test mode, do not run main. Used when sourceing\n";
1459 printf
" in testscript\n";
1460 printf
" -i skip the intro\n";
1461 printf
" -h print this help and exit. More information\n";
1462 printf
" available in the i -game help. Open that by\n"
1463 printf
" pressing h when asked for a move.\n"
1464 printf
" -? means flag not yet determined\n";
1467function printGameHelp {
1468 printf
"\n\nHow to play\n";
1469 printf
"The game is played on a grid with 9 squares. Both\n";
1470 printf
"players take turns in which they can occupy a\n";
1471 printf
"square by marking them with their symbol. One \n";
1472 printf
"player uses an X, the other a O. Only an empty\n";
1473 printf
"square can be occupied. The goal of the game is\n";
1474 printf
"to get three squares in a row. This can be\n";
1475 printf
"horizontally, vertically or diagonally. The first\n";
1476 printf
"player to align three of their symbols wins.\n";
1478 printf
"The controls of this game are simple. On the edges\n"
1479 printf
"of the board are letters and numbers\n";
1481 printf
"When it is your turn ou specify a column by typing\n";
1482 printf
"One of the column letters, followed by a row number\n"
1483 printf
"That will place your symbol on the board, or inform\n";
1484 printf
"you the move is invalid and you can try again. This \n";
1485 printf
"can be because there already is a symbol on that\n";
1486 printf
"cell, or the cell does not exist.\n"
1488 printf
"When in numeric input mode the coordinates won't be\n"
1489 printf
"printed on the edge of the board.\n"
1490 printf
"instead a single digit is used to identify the cells\n"
1491 printf
" ┏━┯━┯━┓\n";
1492 printf
" ┃1┃2┃3┃\n";
1493 printf
" ┠─┼─┼─┨\n";
1494 printf
" ┃4┃5┃6┃\n";
1495 printf
" ┠─┼─┼─┨\n";
1496 printf
" ┃7┃8┃9┃\n";
1497 printf
" ┗━┷━┷━┛\n";
1498 printf
"In numpad numeric mode the order is slightly different:\n"
1499 printf
" ┏━┯━┯━┓\n";
1500 printf
" ┃7┃8┃9┃\n";
1501 printf
" ┠─┼─┼─┨\n";
1502 printf
" ┃4┃5┃6┃\n";
1503 printf
" ┠─┼─┼─┨\n";
1504 printf
" ┃1┃2┃3┃\n";
1505 printf
" ┗━┷━┷━┛\n";
1507 printf
"Oh, and you quit by giving q as a move\n"
1508 printf
"Press enter key to return to the game\n"
1509 read dummy #dummy to wait on user key press
1513declare duplicateBoard=
"";
1514declare -i boardNumber=0;
1515declare -i gameCounter=0;
1516declare -a breadcrumbs;
1517declare -i childIndex;
1520 local -i verbosity=0;
1521 local board_src=
"boards.txt";
1522 local board_dst=
"boards.txt";
1523 local coordinateInputMode=
$C_TRUE;
1526 while getopts
":v:w:r:hnNti" flag;
do
1528 r) echo
"src: ${OPTARG}"; board_src=
"${OPTARG}";;
1529 w) echo
"dst: ${OPTARG}"; board_dst=
"${OPTARG}";;
1530 h) printArgsHelp;
return 0;;
1531 v) echo
"v: ${OPTARG} $1 $2"; verbosity=${OPTARG};;
1536 *) printf
"unknown argument %s\n" $flag;;#
return 1;;
1539 if [ $skipIntro -eq
$C_FALSE ]; then
1544 if [ $? -ne 0 ]; then
1552 #start playing the game
1553 while [ $quit -eq
$C_FALSE ];
do #loop
for multiple games
1554 local turnCounter=-1;
1555 local -i whosTurn=$C_USER;
1558 declare -n bCurrent=
"b0";
1559 local transposition=
"h0v0r0";
1561 printf
"%d Game(s) played\n" $gameCounter
1562 gameCounter=$((gameCounter+1))
1563 if [ $verbosity -ge 1 ]; then
1564 echo
"bCurrent: ${bCurrent[@]}";
1566 while [[ $quit -eq
$C_FALSE && $gameEnded -eq
$C_FALSE ]]; do
#loop for turns in game
1567 printf
"This turn(%d) is for: %s\n" $turnCounter ${xo[$((turnCounter%2))]}
1570 #now do stuff for the next turn
1571 turnCounter=$((turnCounter+1))
1572 local -i v=$(((turnCounter)%2))
1573 if [ $playSelf -ne
$C_TRUE ]; then
1574 whosTurn=$((whosTurn*-1));
1576 printf
"preparing for next move (%s)\n" ${xo[$v]};
1577 #check if moves left
1579 getNextMoves
"$bCurrent" $v
1581 printf
"no more moves for %s\n" ${xo[$v]};
1582 isWon
"${bCurrent[$C_NAME_OFFSET]}";
1584 printf
"%s won.\n" ${xo[$(((turnCounter-1)%2))]};
1585 whoWon=${xo[$(((turnCounter-1)%2))]}
1586 if [ $whosTurn -eq $C_COMPUTER ]; then #computer turn
1587 printf
"congratulations!\n"
1589 printf
"better luck next time.\n"
1593 else #check
if all squares are used
1596 if [ $depth -eq 9 ]; then
1597 printf
"The game ended in a tie.\n";
1604 nextMoves=(
"${VAR[@]}"); #TODO: make local array
1605 if [ $verbosity -ge 1 ]; then
1606 echo
"currentboard: ${bCurrent[@]}"
1607 echo
"next moves: ${VAR[@]}"
1608 echo
"next moves: ${nextMoves[@]}"
1611 if [ $whosTurn -eq $C_COMPUTER ]; then #computer turn
1612 printf --
"--COMPUTER--\n";
1613 #choose the next board, which is the next move
1614 local -i nnext=${#nextMoves[@]};
1615 if [ $nnext -le 0 ]; then
1616 printf
"An error occurred, something went wrong with the weights\n"
1617 printf
"board %s\n" "${bCurrent[@]}"
1618 printf
"writing boards to error file\n"
1619 board_dst=
"nonext_debug.txt"
1623 local -i nextBoardIndex=$((RANDOM%nnext));
1625 boardName=
"${nextMoves[$nextBoardIndex]}";
1626 if [ $verbosity -ge 1 ]; then
1627 printf
"nextBoardIndex: %d, board: %s, v: %d\n" $nextBoardIndex ${nextMoves[$nextBoardIndex]} $v;
1628 printf
"next board: %s\n" "$boardName";
1630 local prevTransposition=
"$transposition";
1631 #get transposition information
1632 if [ ${boardName: -2:1} ==
"r" ]; then
1633 transposition=${boardName:$((${#boardName}-6))};
1634 boardName=${boardName:0:$((${#boardName}-6))};
1637 printf --
"--USER--\n";
1638 if [ $verbosity -ge 2 ]; then
1639 printf
"transposition: %s\n" $transposition
1640 printf
"breadcrumbs: "
1641 printf
"%s-" "${breadcrumbs[@]}"
1645 while [ $isValid -eq
$C_FALSE ];
do
1646 printf
"Your move: ";
1651 if [
"$c" ==
"q" ]; then
1656 if [
"$c" ==
"h" ]; then
1661 if [ $coordinateInputMode -eq
$C_TRUE ]; then
1663 printf
"\n move given: %s%s\n" "$c" "$r";
1664 #regexp would be a great way to check validity
1665 #but not supported on dev terminal, so other way is needed
1667 while [
$i -lt ${#r[@]} ];
do
1671 printf
"that is not a valid move\nplease enter a valid move.\n";
1680 if [[ ! ( 0 -le $r && $r -le 3 ) ]]; then
1682 printf
"that is not a valid move.\n Please enter a valid move.\n";
1685 local -i pos=$((r*3));
1687 [aA]) pos=$((pos+0));;
1688 [bB]) pos=$((pos+1));;
1689 [cC]) pos=$((pos+2));;
1692 printf
"that is not a valid move.\n";
1696 printf
"\n move given: %s\n" "$c";
1697 #regexp would be a great way to check validity
1698 #but not supported on dev terminal, so other way is needed
1700 while [
$i -lt ${#c[@]} ];
do
1704 printf
"that is not a valid move\nplease enter a valid move: ";
1712 local -i pos=$((c-1)); #-1 to go from lowest 1 to lowest 0
1713 if [ $numpadMode -eq
$C_TRUE ]; then
1714 posLUT=( 6 7 8 3 4 5 0 1 2 )
1715 pos=${posLUT[$pos]};
1717 #the position is given as is on the numpad, the positions
1720 #apply the transposition to get the correct position
1721 #but in case it is invalid, save the current transposition
1722 local prevTransposition=
"$transposition";
1723 local -n matrix=
"$transposition";
1726 if [ $verbosity -ge 2 ]; then
1727 printf
"transposed position: %d\n" $pos
1729 if [
"${bCurrent[$((C_CELLS_OFFSET+pos))]}" !=
" " ]; then
1731 printf
"that is not a valid move, position already taken\n";
1732 if [ $verbosity -ge 2 ]; then
1733 echo
"${bCurrent[@]}";
1736 transposition=
"$prevTransposition";
1737 echo
"transposition=$prevTransposition";
1740 if [ $quit -eq
$C_TRUE ]; then
1743 if [ $verbosity -ge 1 ]; then
1744 printf
"%s%d %d\n" $c $r $pos;
1746 #now find the corresponding board name
1747 copyBoard
"bCurrent" "bTemp"
1748 bTemp[$((C_CELLS_OFFSET+pos))]=${xo[$v]};
1749 calculateBoardValue
"bTemp";
1751 if [ $verbosity -ge 1 ]; then
1754 local prevTransposition=
"$transposition";
1755 findChild
"${bTemp[$C_NAME_OFFSET]}" "bCurrent" "${xo[$v]}"
1758 printf
"Board user chose is: %s\n" $boardName;
1760 #crash, shouldn't happen if programmed correctly
1761 #just here so it is clear what is happening
1762 #and the program doesn't try to stumble on and
1763 #makes debugging a lot harder
1764 printf
"ERROR: no board found\n"
1768 if [ $verbosity -ge 1 ]; then
1770 printf
"The transposition of that board is: %s\n" $transposition
1773 #now apply the old transposition to this one
1775 if [ $verbosity -ge 2 ]; then
1776 printf
"transposition, current, old: %s, %s\n" "$transposition" "$prevTransposition";
1778 transposeTransposition
"$transposition" "$prevTransposition";
1780 if [ $verbosity -ge 1 ]; then
1781 printf
"the new board transposition is: %s\n" $transposition;
1783 declare -n bCurrent=
"$boardName";
1784 addBreadcrumb
"$boardName$transposition"
1786 if [ $quit -eq
$C_TRUE ]; then
1790 if [ $verbosity -ge 1 ]; then
1791 echo
"breadcr.: ${breadcrumbs[@]}";
1795 #"learn" from the last game
1796 setNewWeights
"$whoWon" "X"; #TODO: give second arg depending on playing X or O (
for learning)
1799 #TODO: ask if the user wants to play again
1802 #remember the learned boards
1803 if [[
"$board_dst" !=
"" && $haveLearned -eq
$C_TRUE ]]; then
1804 writeBoardsInfo
"$board_dst";
$i
Counter for the number of rounds already done.
const $C_FALSE
False value.
if(func_num_args() -le 0) if(func_num_args() -le 1) $file
Name of the file to use as trigger.