Pieters bash scripts
Documentation for the bash scripts I have published
Loading...
Searching...
No Matches
tictactoe.sh
1#!/bin/bash
2
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.
9#
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.
14#
15#You should have received a copy of the GNU Affero General Public License
16#along with this program. If not, see <https://www.gnu.org/licenses/
17#
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
26# ignored.
27#
28#Changelog:
29#┏━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━┓
30#┃ 16 oct 2022 │ First release │ Pieter van der Star ┃
31#┗━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━┛
32
33
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
37#TODO: add error codes
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"
39
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.
41
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.
44
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.
46
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.
50
51#The board element is quite complex and is stored as an array. In C it
52#would look something like this:
53#struct board{
54# char* name;
55# int[3][3] cells;
56# int board_value;
57# int next_boards_x;
58# int next_boards_o;
59# char** children;
60# int* children_weights;
61#}
62
63#note 1: unless otherwise specified by a command line argument.
64
65#print the introduction
66#args: none
67#returns nothing
68function printIntro {
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;
96 sleep 2;
97}
98
99#A bunch of constants used for board propeties and game states
100
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;
106
107#some offsets need to be calculated, the follow after the constants
108
109
110#constants used to keep track of whos turn it is
111declare -i -r C_USER=1;
112declare -i -r C_COMPUTER=-1;
113
114#custom boolean values
115declare -i -r C_TRUE=1;
116declare -i -r C_FALSE=0;
117
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)
119
120#make it easier to print the naughts and crosses
121xo=( "X" "O" );
122
123
124# 0 1 2
125# 3 4 5
126# 6 7 8
127
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 );
133
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 );
138
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 );
143
144#calculates and sets the id of a given transposition
145#ars: transposition name
146#returns nothing
147#modifies transposition id
148function calculateTranspositionId {
149 local -n t="$1"
150 local -i i=0
151 local -a p
152 p=( 11 13 17 19 23 29 31 37 41 )
153 local -i id=0;
154 while [ $i -lt 9 ]; do
155 pos=${t[i]}
156 prime=${p[$i]}
157 id=$((id+pos*prime))
158 i=$((i+1))
159 done
160 t[9]=$id
161}
162
163
164#calculates and sets the id of all transpositio ns
165#ars: nothing
166#returns nothing
167#modifies transposition ids
168function calculateTranspositionIds {
169 local -i h=0;
170 while [ $h -le 1 ]; do
171 local -i v=0;
172 while [ $v -le 1 ]; do
173 if [[ $h -eq 1 && $v -eq 1 ]]; then
174 break;
175 fi
176 local -i r=0;
177 while [ $r -le 3 ]; do
178 calculateTranspositionId "h$h""v$v""r$r";
179 r=$((r+1))
180 done
181 v=$((v+1))
182 done
183 h=$((h+1))
184 done
185}
186
187#directly call calculateTranspositionIds
188calculateTranspositionIds
189
190#transpose a transposition
191#args: transposition name, transposition name
192#returns nothing
193#sets global transposition
194function transposeTransposition {
195 local -i verbosity=0;
196 if [ $bTmp ]; then #this function needs bTmp, so backup the current value
197 local bTempbu=$bTmp;
198 fi
199 local -n a="$1";
200 local -n b="$2";
201 bTmp=( 0 0 0 0 0 0 0 0 0 0 );
202 local -i i=0;
203 while [ $i -lt 9 ]; do
204 local -i pos=${b[i]}
205 bTmp[i]=${a[pos]}
206 i=$((i+1))
207 done
208
209 calculateTranspositionId "bTmp"
210 #find the same id
211 local -i h=0;
212 while [ $h -le 1 ]; do
213 local -i v=0;
214 while [ $v -le 1 ]; do
215 if [[ $h -eq 1 && $v -eq 1 ]]; then
216 break;
217 fi
218 local -i r=0;
219 while [ $r -le 3 ]; do
220 name="h${h}v${v}r${r}";
221 if [ $verbosity -gt 0 ]; then
222 echo "chk $name"
223 fi
224 local -n t="$name"
225
226 if [ ${t[9]} -eq ${bTmp[9]} ]; then
227 transposition="$name";
228 return
229 fi
230 r=$((r+1))
231 done
232 v=$((v+1))
233 done
234 h=$((h+1))
235 done
236 transposition="h0v0r0"
237 unset "bTmp"
238 if [ $bTempbu ]; then
239 bTmp=$bTempbu;
240 fi
241}
242
243#prints the board
244#arg1: board name,
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)
248function printBoard {
249 local -i verbosity=0;
250 if [ ! -v $1 ]; then
251 printf "Board %s does not exist\n" $1;
252 return 0;
253 fi
254 local -n board="$1"
255 if [ $verbosity -ge 2 ]; then
256 echo "${board[@]}"
257 fi
258 if [ $# -gt 1 ]; then
259 local -r transposition="$2"
260 else
261 local -r transposition="h0v0r0"
262 fi
263
264 local -n matrix="$transposition";
265 printProperties=$C_FALSE;
266 if [ $# -gt 2 ]; then
267 printProperties=$3;
268 fi
269 printCoordinates=$C_FALSE
270 if [ $# -gt 3 ]; then
271 printCoordinates=$4;
272 fi
273 if [ $printProperties -eq $C_TRUE ]; then
274 printf "name: %s\n" ${board[$C_NAME_OFFSET]};
275 printf "transposition: %s\n" $transposition
276 printf "cells:\n";
277 fi
278 if [ $printCoordinates -eq $C_TRUE ]; then
279 printf " A B C\n";
280 fi
281 printf " ┏━┯━┯━┓\n";
282 local -i r=0;
283 local -i c;
284 while [ $r -lt 3 ]; do
285 c=0;
286 if [ $printCoordinates -eq $C_TRUE ]; then
287 printf "%d" $r;
288 else
289 printf " ";
290 fi
291 printf "┃";
292 while [ $c -lt 3 ]; do
293
294 local -i pos=${matrix[$(((r*3+c)))]}
295 val="${board[$((C_CELLS_OFFSET+pos))]}";
296 printf "%s" "$val";
297
298 if [ $c -lt 2 ]; then
299 printf "│";
300 fi
301
302 c=$((c+1))
303 done
304 printf "┃";
305 if [ $r -lt 2 ]; then
306 printf "\n ┠─┼─┼─┨\n";
307 fi
308 r=$((r+1))
309 done
310 printf "\n ┗━┷━┷━┛\n";
311
312 if [ $printProperties -eq $C_TRUE ]; then
313 printf "value: %d\n" ${board[$((C_VALUE_OFFSET))]};
314 local -i v=0;
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;
321 local -i i=0;
322 while [ $i -lt $nnext ]; do
323 getNNext "$1" 0
324 local -i nnext_x=$?
325 getNNext "$1" 1
326 local -i nnext_o=$?
327 printf "%s(%d)," "${board[$((i+firstChildOffset))]}" "${board[$((i+firstChildOffset+nnext_x+nnext_o))]}";
328 i=$((i+1));
329 done
330 printf "\n";
331 v=$((v+1));
332 done
333 fi
334 }
335
336#makes a board
337#args: board name
338#todo check if already exists
339function makeBoard {
340 local -n board=$1
341 board[$C_NAME_OFFSET]="$1"
342 local -i r=0;
343 local -i c=0;
344 while [ $r -lt 3 ]; do
345 c=0;
346 while [ $c -lt 3 ]; do
347 board[$((C_CELLS_OFFSET+(r*3+c)))]=" ";
348 c=$((c+1))
349 done
350 r=$((r+1))
351 done
352 board[$C_NEXT_OFFSET]=0;
353 board[$C_NEXT_OFFSET+1]=0;
354}
355
356#helper function for is won
357#checks if one player has all boxes in the row
358function isWonRow {
359 local -n board=$1;
360 local -i r=$2;
361 local -i c=0;
362 local curVal;
363 local prevVal;
364 while [ $c -lt 3 ]; do
365 curVal="${board[$((C_CELLS_OFFSET+(3*r+c)))]}";
366 if [ "$curVal" == " " ]; then
367 return $C_FALSE;
368 else
369 if [[ $c -gt 0 && "$prevVal" != "$curVal" ]]; then
370 return $C_FALSE;
371 fi;
372 fi
373 prevVal="$curVal";
374 c=$((c+1));
375 done
376 sleep 5
377 return $C_TRUE;
378}
379
380#helper function for is won
381#checks if one player has all boxes in the column
382function isWonColumn {
383 local -n board=$1;
384 local -i r=0;
385 local -i c=$2;
386 local curVal;
387 local prevVal;
388 while [ $r -lt 3 ]; do
389 curVal="${board[$((C_CELLS_OFFSET+(3*r+c)))]}";
390 if [ "$curVal" == " " ]; then
391 return $C_FALSE;
392 else
393 if [[ $r -gt 0 && "$prevVal" != "$curVal" ]]; then
394 return $C_FALSE;
395 fi;
396 fi
397 prevVal="$curVal";
398 r=$((r+1));
399 done
400 return $C_TRUE;
401}
402
403#helper function for is won
404#checks if one player has all boxes in the diagonal going right
405function isWonDiagonalR {
406 local -n board=$1;
407 local -i i=0;
408 local curVal;
409 local prevVal;
410 while [ $i -lt 3 ]; do
411 curVal="${board[$((C_CELLS_OFFSET+(3*i+i)))]}";
412 if [ "$curVal" == " " ]; then
413 return $C_FALSE;
414 else
415 if [[ $i -gt 0 && "$prevVal" != "$curVal" ]]; then
416 return $C_FALSE;
417 fi;
418 fi
419 prevVal="$curVal";
420 i=$((i+1));
421 done
422 return $C_TRUE;
423}
424
425#helper function for is won
426#checks if one player has all boxes in the diagonal going left
427function isWonDiagonalL {
428 local -n board=$1;
429 local -i i=0;
430 local curVal;
431 local prevVal;
432 while [ $i -lt 3 ]; do
433 curVal="${board[$((C_CELLS_OFFSET+(3*(i+1)-i)-1))]}";
434 if [ "$curVal" == " " ]; then
435 return $C_FALSE;
436 else
437 if [[ $i -gt 0 && "$prevVal" != "$curVal" ]]; then
438 return $C_FALSE;
439 fi;
440 fi
441 prevVal="$curVal";
442 i=$((i+1));
443 done
444 return $C_TRUE;
445}
446
447
448#checks if one player has won the game
449#args: board name
450#returns boolean true if a player won, otherwise false
451function isWon {
452 local -i verbosity=0;
453 local boardName=$1
454 local -i r=0;
455 local prevVal;
456 #check rows
457 while [ $r -lt 3 ]; do
458 isWonRow $boardName $r
459 if [ $? -eq $C_TRUE ]; then
460 if [ $verbosity -gt 0 ]; then
461 printf "Found winning board(r%d)\n" $r;
462 fi
463 return $C_TRUE;
464 fi
465 r=$((r+1));
466 done
467
468 #check columns
469 local -i c=0;
470 while [ $c -lt 3 ]; do
471 isWonColumn $boardName $c
472 if [ $? -eq $C_TRUE ]; then
473 if [ $verbosity -gt 0 ]; then
474 printf "Found winning board (c%d)\n" $c;
475 fi
476 return $C_TRUE;
477 fi
478 c=$((c+1));
479 done
480
481 #check diagonals
482 isWonDiagonalL $boardName
483 if [ $? -eq $C_TRUE ]; then
484 if [ $verbosity -gt 0 ]; then
485 printf "Found winning board (DiagL)\n";
486 fi
487 return $C_TRUE;
488 fi
489 isWonDiagonalR $boardName
490 if [ $? -eq $C_TRUE ]; then
491 if [ $verbosity -gt 0 ]; then
492 printf "Found winning board (DiagR)\n";
493 fi
494 return $C_TRUE;
495 fi
496
497 return $C_FALSE;
498}
499
500#checks if the board is a valid board
501#the number of x and o should never differ more than 1
502#args: board name
503#returns boolean, true if valid, otherwise false
504function isValid {
505 local -n board=$1
506 local -i nX=0;
507 local -i nO=0;
508
509 local -i r=0;
510 local -i c=0;
511 while [ $r -lt 3 ]; do
512 c=0;
513 while [ $c -lt 3 ]; do
514 val="${board[$((C_CELLS_OFFSET+(r*3+c)))]}"
515 if [ "$val" == "X" ]; then
516 nX=$((nX+1));
517 elif [ "$val" == "O" ]; then
518 nO=$((nO+1));
519 fi
520 c=$((c+1))
521 done
522 r=$((r+1))
523 done
524local -i diff=$((nX-nO));
525
526if [[ -2 -lt $diff && $diff -lt 2 ]]; then
527 return $C_TRUE;
528 else
529 return $C_FALSE;
530 fi
531}
532
533
534#function to get the number of empty cells
535#args: board name
536#returns numbere of empty cells
537function getNEmpty {
538 local -n board=$1
539 local -i nEmpty=0;
540
541 local -i r=0;
542 local -i c=0;
543 while [ $r -lt 3 ]; do
544 c=0;
545 while [ $c -lt 3 ]; do
546 val="${board[$((C_CELLS_OFFSET+(r*3+c)))]}"
547 if [ "$val" == " " ]; then
548 nEmpty=$((nEmpty+1));
549 fi
550 c=$((c+1))
551 done
552 r=$((r+1))
553 done
554 return $nEmpty;
555}
556
557#copy the values of one board to another
558#args: board name to copy from
559# board name to copy to
560#returns nothing
561function copyBoard { #only the values are copied
562 local -n originalBoard=$1;
563 #create the new board
564 boardName=$2
565 if [ $verbosity -gt 0 ]; then
566 printf "copying %s to %s\n" $1 $2
567 fi
568 makeBoard "$boardName"
569 local -n newBoard="$boardName"
570 local -i r=0;#row
571 local -i c=0;#column
572 while [ $r -lt 3 ]; do
573 c=0;
574 while [ $c -lt 3 ]; do
575 newBoard[$((C_CELLS_OFFSET+(r*3+c)))]="${originalBoard[$((C_CELLS_OFFSET+(r*3+c)))]}"
576 c=$((c+1))
577 done
578 r=$((r+1))
579 done
580}
581
582#calculate and set the value of a board
583#args: bosrd name
584 #returns nothing
585 #sets the board value of the board object
586 function calculateBoardValue {
587 local -n board=$1;
588 local -i r=0;#row
589 local -i c;#column
590 local -i v;#value
591 local -i boardValue=0;
592
593 posValueMatrix=( 17 19 17
594 19 23 19
595 17 19 17 );
596
597 while [ $r -lt 3 ]; do
598 c=0;
599 while [ $c -lt 3 ]; do
600 val="${board[$((C_CELLS_OFFSET+(r*3+c)))]}"
601 case "$val" in
602 " ") val=0;;
603 "X") val=7;;
604 "O") val=13;;
605 esac
606 boardValue=$((boardValue+val*${posValueMatrix[$((r*3+c))]}));
607 c=$((c+1));
608 done
609 r=$((r+1));
610 done
611 if [ $verbosity -gt 0 ]; then
612 printf "value: %d\n" $boardValue;
613fi
614 board[$C_VALUE_OFFSET]=$boardValue;
615 }
616
617 #check if a given board exists
618 #arg1: board to find
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";
626 fi
627 local -n board="$1";
628 local -i i=0;
629 while [ $i -ne $boardNumber ]; do
630 local -n b="b$i";
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"
635 fi
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"
639 if [ $? -eq $C_TRUE ]; then
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";
642 fi
643 duplicateBoard="${b[$C_NAME_OFFSET]}";
644 return $C_TRUE;
645 fi
646 fi
647 fi
648 i=$((i+1));
649 done
650 return $C_FALSE;
651 }
652
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"
661 fi
662
663 local -n a=$1;
664 local -n b=$2;
665 if [ $verbosity -gt 1 ]; then
666 printBoard "a"
667 printBoard "b"
668 fi
669 local -i h; #horizontal mirroring
670 local -i v; #vertical mirroring
671 local -i r; #rotation
672 local -i i; #cell iterator
673
674 h=0;
675 while [ $h -lt 2 ]; do
676 v=0;
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
680 return $C_FALSE;
681 fi
682 r=0;
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
688 fi
689 i=0;
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
694 fi
695 if [ "${a[$((C_CELLS_OFFSET+i))]}" != "${b[$((C_CELLS_OFFSET+m))]}" ]; then
696 break;
697 fi
698 i=$((i+1));
699 done
700 if [ $i -eq 9 ]; then
701 return $C_TRUE
702 fi
703 r=$((r+1));
704 done
705 v=$((v+1));
706 done
707 h=$((h+1));
708 done
709 return $C_FALSE;
710}
711
712function getNNext {
713 local -n -r board="$1"
714 local -i -r v=$2
715 local -i nnext=${board[$((C_NEXT_OFFSET+v))]};
716 return $nnext;
717}
718
719function getFirstChildOffset {
720 local -n -r board="$1"
721 local -i -r v=$2
722 local -i firstChildOffset;
723 if [ $v -eq 0 ]; then
724 local -i firstChildOffset=$C_NEXT_OFFSET+2;
725 else
726 getNNext "$board" 0
727 local -i nnextX=$?
728 firstChildOffset=$((C_NEXT_OFFSET+nnextX+2));
729 fi
730 return $firstChildOffset;
731}
732
733function getChildWeight {
734 local -n -r board="$1"
735 local -i -r v=$2
736 local -i -r index=$3
737
738 getNNext "$1" 0
739 local -i nnext_x=$?
740 getNNext "$1" 1
741 local -i nnext_o=$?
742
743 getFirstChildOffset "$1" "$v"
744 local firstChildOffset=$?
745 return ${board[$((firstChildOffset+nnext_x+nnext_o+index))]}
746}
747
748function setChildWeight {
749 local -n -r board="$1"
750 local -i -r v=$2
751 local -i -r index=$3
752 local -i -r weight=$4
753
754 getNNext "$1" 0
755 local -i nnext_x=$?
756 getNNext "$1" 1
757 local -i nnext_o=$?
758
759 getFirstChildOffset "$1" "$v"
760 local firstChildOffset=$?
761 board[$((firstChildOffset+nnext_x+nnext_o+index))]=$weight
762}
763
764#add a child from the list of children
765#arg1: parent (to add the child to)
766#arg2: child to add
767#arg3: which child list to add to (X or O)
768#returns: nothing
769function insertChild {
770 local -n -r parent="$1"
771 local -r child="$2"
772 local -i -r v=$3
773 local -i -r weight=$4
774 local -i verbosity=0;
775
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
781 echo "${parent[@]}"
782 fi
783
784 getNNext "$1" 0
785 local -i nnext_x=$?
786 getNNext "$1" 1
787 local -i nnext_o=$?
788
789 getFirstChildOffset "$1" "$v"
790 local firstChildOffset=$?
791
792
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));
802 fi
803
804 if [ $verbosity -gt 0 ]; then
805 printf "weightInsertPoint: %d\n" $weightInsertPoint
806 printf "childInsertPoint: %d\n" $childInsertPoint
807 fi
808 local -i i=$((${#parent[@]}+1));
809
810 while [ $i -gt $weightInsertPoint ]; do
811 parent[$i]=${parent[$((i-2))]}; #2 so to make room for the child and the weight
812 i=$((i-1))
813 done
814 #now we have made room for the weight we can insert that
815 parent[$((i+0))]=$weight
816 i=$((i-1))
817
818 while [ $i -gt $childInsertPoint ]; do
819 parent[$i]=${parent[$((i-1))]};
820 i=$((i-1))
821 done
822
823 parent[$i]="$child";
824 #update nnext
825 local -i nnext=${parent[$((C_NEXT_OFFSET+v))]};
826 if [ $verbosity -gt 0 ]; then
827 printf "insertChild.nnext[%d]: %d\n" $v $nnext
828 fi
829 nnext=$((nnext+1));
830
831 parent[$((C_NEXT_OFFSET+v))]=$nnext;
832 if [ $verbosity -gt 0 ]; then
833 printf "insertChild.nnext[%d]: %d\n" $v $nnext
834 fi
835}
836
837#makes all children and other offspring from the given board
838#as the function runs recursive global progress variables are used
839#arg1: parent board
840#arg2: depth, used during debug
841#returns nothing
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 {
848 local -n board=$1;
849 local -i depth=$2;
850 local -i r;#row
851 local -i c;#column
852 local -i v;#value
853 local -i verbosity=0;
854 if [[ $depth -gt $makeNextBoardsDepth && $makeNextBoardsDepth -ne 0 ]]; then
855 return 0;
856 fi
857
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
863 fi
864
865 v=0;
866 while [ $v -lt 2 ]; do
867 r=0;
868 while [ $r -lt 3 ]; do
869 c=0;
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;
878 fi
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;
885 fi
886
887 if [ $verbosity -gt 3 ]; then
888 printf "new board is\n"
889 printBoard $boardName;
890 fi
891 isValid $boardName;
892 local -i valid=$?;
893 if [ $valid -eq $C_FALSE ]; then
894 if [ $verbosity -gt 1 ]; then
895 printf "board no good, scrapping\n";
896 fi
897 unset $boardName;
898 boardNumber=$((boardNumber-1));
899 if [ $verbosity -gt 1 ]; then
900 printf "skipping to next symbol\n"
901 fi
902 c=100;
903 r=100;
904 continue;
905 fi
906
907 calculateBoardValue "$boardName";
908 doesBoardExist "$boardName";
909 local -i exists=$?;
910
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";
916 fi
917 unset $boardName;
918 boardNumber=$((boardNumber-1));
919 boardName="$duplicateBoard$transposition"
920 c=$((c+1));
921 continue;
922 fi
923 #not exist and valid
924 if [ $verbosity -gt 1 ]; then
925 printf "New board: %s\n" $boardName;
926 fi
927 insertChild "$1" "$boardName""h0v0r0" $v
928 isWon $boardName;
929 if [[ $? -eq $C_FALSE && $exists -eq $C_FALSE ]]; then
930 makeNextBoards $boardName $((depth+1));
931 fi
932 fi
933 c=$((c+1));
934 done
935 r=$((r+1));
936 done
937 v=$((v+1));
938 done
939 if [ $verbosity -gt 2 ]; then
940 printf "makeNextBoards tried: %d boards\n" $makeNextBoardsProgress;
941 fi
942}
943
944#write the board information to a file
945#args: filename
946#returns: nothing
947function writeBoardsInfo {
948 if [ $# -gt 0 ]; then
949 file=$1
950 else
951 file="boards.txt";
952 fi
953 if [ $verbosity -gt 1 ]; then
954 printf "boardNumber: %d\n" $boardNumber
955 fi
956 local -i i=0;
957 #truncate file
958
959 printf "#Board config\n" > $file
960 while [ $i -lt $boardNumber ]; do
961 local -i j=0;
962 local -n board="b$i";
963
964 #print name
965 printf "%s=( %s" ${board[$C_NAME_OFFSET]} ${board[$C_NAME_OFFSET]} >> $file
966
967 #print cells
968 local -i r=0;
969 while [ $r -lt 3 ]; do
970 c=0;
971 while [ $c -lt 3 ]; do
972 val="${board[$((C_CELLS_OFFSET+(r*3+c)))]}"
973 printf " \"%s\"" "$val" >> $file
974 c=$((c+1));
975 done
976 r=$((r+1));
977 done
978
979 #print board value
980 printf " %d" ${board[$((C_VALUE_OFFSET))]} >> $file
981
982 #print number of boards
983 local -i v=0;
984 while [ $v -lt 2 ]; do
985 getNNext "${board[0]}" $v
986 local -i nnext=$?;
987 printf " %d " $nnext >> $file
988 v=$((v+1));
989 done
990
991 #print the next boards
992 local -i j=0;
993 getFirstChildOffset "${board[0]}" 0
994 j=$?
995 while [ $j -lt ${#board[@]} ]; do
996 printf " %s" "${board[$j]}" >> $file
997 j=$((j+1));
998 done
999
1000 #close the array
1001 printf " );\n" >> $file
1002
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));
1006 fi
1007 i=$((i+1));
1008 done
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";
1013}
1014
1015#figure out the depth of this board
1016#arg1: boardname
1017#returns depth
1018function getDepth {
1019 local -n bTmp="$1";
1020 local -i i=0;
1021 local -i depth=0;
1022 while [ $i -lt 9 ]; do
1023 if [ "${bTmp[$((C_CELLS_OFFSET+i))]}" != " " ]; then
1024 depth=$((depth+1));
1025 fi
1026 i=$((i+1));
1027 done
1028 return $depth;
1029}
1030
1031
1032#set the weight for each child.
1033#The weight is detemined by how far down the tree the board is.
1034#no argumens
1035#returns nothing
1036#modifies global boards
1037function setStartingWeights {
1038 #michi used the following weights
1039 #|depth|weights|
1040 #+-----+-------+
1041 #| 1 | 4 |
1042 #| 3 | 3 |
1043 #| 5 | 2 |
1044 #| 7 | 1 |
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
1047 #|depth|weights|
1048 #+-----+-------+
1049 #| 1 | 8 |
1050 #| 2 | 7 |
1051 #| 3 | 6 |
1052 #| 4 | 5 |
1053 #| 5 | 4 |
1054 #| 6 | 3 |
1055 #| 7 | 2 |
1056 #| 8 | 1 |
1057 #the downside of this approach is that training will take longer, the other option is to apply the same weight on two depths
1058 #|depth|weights|
1059 #+-----+-------+
1060 #| 1 | 4 |
1061 #| 2 | 4 |
1062 #| 3 | 3 |
1063 #| 4 | 3 |
1064 #| 5 | 3 |
1065 #| 6 | 2 |
1066 #| 7 | 1 |
1067 #| 8 | 1 |
1068
1069 #this only leaves level 0, this will be getting the value 5
1070
1071 weights=( 5 4 4 3 3 2 2 1 1 );#todo: what was the syntax for a local array again?
1072
1073 local i=0;
1074 while [ $i -lt $boardNumber ]; do
1075 local boardName="b$i";
1076 local -i v=0;
1077 while [ $v -le 1 ]; do
1078 #printf "v: %d\n" $v
1079 local -n bTmp="$boardName"
1080 getNNext "$boardName" $v;
1081 local -i nnext=$?;
1082 local -i firstChildOffset=${bTmp[$C_NEXT_OFFSET]};
1083 local -i c=0;
1084 while [ $c -lt $nnext ]; do
1085 local child="${bTmp[$((firstChildOffset+c))]}"
1086 getDepth "$boardName"
1087 local -i depth=$?
1088 setChildWeight "$boardName" $v $c ${weights[$depth]}
1089 c=$((c+1));
1090 done
1091 v=$((v+1));
1092 done
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));
1096 fi
1097 i=$((i+1));
1098 done
1099}
1100
1101
1102#performs the main game b
1103#arg1: file to read from
1104#returns nothing
1105function setup {
1106 if [ $# -gt 0 ]; then
1107 local file="$1"
1108 else
1109 local file="boards.txt";
1110 fi
1111 if [ -f "$file" ]; then
1112 printf "Loading boards config\n";
1113 source "$file"
1114 if [ $verbosity -gt 1 ]; then
1115 echo "bn: $boardNumber"
1116 fi
1117 if [ $boardNumber -eq 0 ]; then
1118 printf "Input file doesn't contain the required information\n";
1119 return 1;
1120 fi
1121 else
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";
1125 read -n 1 ans
1126 case $ans in
1127 [Qq]*) return 1;;
1128 esac
1129
1130 printf "Creating boards config\n";
1131 makeBoard "b0"
1132 boardNumber=$((boardNumber+1));
1133 #printBoard "b0";
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;
1139 fi
1140 #add starting weights
1141 printf "setting starting weights\n";
1142 setStartingWeights
1143 printf "writing config to file. This can take some time\n"
1144 writeBoardsInfo "$board_src"
1145 fi
1146 return 0;
1147}
1148
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
1153
1154#sets global transposition to transposition of the child
1155#sets global index to the index of the child
1156function findChild {
1157 local verbosity=0;
1158 local find="$1"
1159 local parent="$2"
1160 if [ $verbosity -gt 2 ]; then
1161 printf "see if %s has child %s\n" $parent $find
1162 fi
1163 local -i i=0;
1164 local -i v=0;
1165 if [ "$3" == "O" ]; then
1166 v=1
1167 fi
1168
1169 #strip transposition
1170 if [ ${find: -2:1} == "r" ]; then
1171 find=${find:0:$((${#find}-6))};
1172 fi
1173
1174
1175 if [ $verbosity -gt 2 ]; then
1176 printf "v: %d\n" $v
1177 if [ $verbosity -gt 3 ]; then
1178 printBoard "$parent" "h0v0r0" 1 1
1179 printBoard "$find" "h0v0r0" 1 1
1180 fi
1181 echo "${tempBoard[@]}"
1182 fi
1183 local -n parentBoard="$parent"
1184 local -n findBoard="$find"
1185 getNNext "$parent" "$v"
1186 local -i nnext=$?;
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
1193 fi
1194 #get transposition information
1195 if [ ${boardName: -2:1} == "r" ]; then
1196 transposition=${boardName:$((${#boardName}-6))};
1197 boardName=${boardName:0:$((${#boardName}-6))};
1198 fi
1199 local -n tempBoard=$boardName;
1200 if [ $verbosity -ge 4 ]; then
1201 printBoard "$boardName" "h0v0r0" 1 1
1202 fi
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]}
1206 fi
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;
1210 fi
1211 if [ $verbosity -gt 0 ]; then
1212 printBoard "$find" "h0v0r0" 1 1
1213 printBoard "$boardName" "h0v0r0" 1 1
1214 printf "######\n"
1215 fi
1216 findTransposition "$boardName" "findBoard";
1217 if [ $? -eq $C_TRUE ]; then
1218 if [ $verbosity -ge 3 ]; then
1219 printf "is a transposition of child\n"
1220 fi
1221 childIndex=$i;
1222 return $C_TRUE
1223 else
1224 if [ $verbosity -ge 3 ]; then
1225 printf "not the same board\n";
1226 fi
1227 fi
1228 else
1229 if [ $verbosity -ge 3 ]; then
1230 printf "no value match\n"
1231 fi
1232 fi
1233 i=$((i+1));
1234 done
1235 return $C_FALSE
1236}
1237
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
1242#returns nothing
1243
1244#sets global VAR to array containing the indices of all
1245#the matching children
1246function findChildByName {
1247 local find="$1"
1248 local parent="$2"
1249 local noTransposition="$3"
1250 unset VAR
1251 if [ "$noTransposition" == "" ]; then
1252 noTransposition="$3"
1253 fi
1254
1255 getFirstChildOffset "$parent"
1256 local -i i=$?
1257
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"
1265
1266 if [ $noTransposition -eq $C_TRUE ]; then
1267 if [ ${find: -2:1} == "r" ]; then
1268 find=${find:0:$((${#find}-6))};
1269 fi
1270 fi
1271
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
1277 VAR[${#VAR[@]}]=$i;
1278 fi
1279 else
1280 if [ "$find" == "$child" ]; then
1281 VAR[0]=$i;
1282 fi
1283 fi
1284 i=$((i+1));
1285 done
1286
1287 local -n board="$parent";
1288}
1289
1290#to keep track of all the moves made
1291#leave breadcrumbs
1292#add a breadcrumb to the trace
1293#arg1: board to add
1294#returns: nothing
1295#sets: breadcrumbs
1296function addBreadcrumb {
1297 local board="$1";
1298 local -i index=${breadcrumbs[0]};
1299 index=$((index+1));
1300 breadcrumbs[$index]="$board";
1301 breadcrumbs[0]=$index;
1302}
1303
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
1311# as modifiers
1312function changeWeight {
1313 local -r parent="$1";
1314 local -i -r childIndex="$2";
1315 local -i -r v=$3;
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
1326 newWeight=0;
1327 elif [ $newWeight -ge 255 ]; then
1328 newWeight=255;
1329 fi
1330 parentBoard[$((childIndex+nnext))]=$newWeight;
1331
1332}
1333
1334#when the game is done change the likelyhoods of a board
1335#being chosen
1336#arg1: symbol of the winner
1337#returns nothing
1338#modifies all boards that were used during this match
1339function setNewWeights {
1340 local whoWon="$1"
1341 local playingAs="$2"
1342 local verbosity=0;
1343 printf "Learning from last match...\n"
1344
1345 if [ $verbosity -ge 1 ]; then
1346 echo "winner was: $1"
1347 fi
1348
1349 if [ $verbosity -ge 2 ]; then
1350 if [ "$whoWon" == " " ]; then
1351 echo "Was a tie, lightly strengthening both branches"
1352 else
1353 echo "Someone won, strengthening branch for the winner and weakening branch for the other";
1354 fi
1355 fi
1356 #winner is always the last player to do a move
1357 #working backwards
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))};
1367
1368 local child="${breadcrumbs[$i]}"
1369
1370 if [ $verbosity -ge 2 ]; then
1371 printf "processing board %s\n" $child
1372 printf "parent: %s\n" "$parent"
1373 fi
1374 findChildByName "$child" "$parent" $C_TRUE
1375 local -i delta=0
1376 if [ "$whoWon" == " " ]; then
1377 delta=1
1378 else
1379 if [ $isWinner -gt 0 ]; then
1380 delta=3;
1381 else
1382 delta=-1;
1383 fi
1384 isWinner=$((isWinner*-1));
1385 fi
1386
1387 local -i j=0;
1388 while [ $j -lt ${#VAR[@]} ]; do
1389 changeWeight "$parent" ${VAR[$j]} $((i%2)) $delta
1390 j=$((j+1))
1391 done
1392
1393 i=$((i-1));
1394 done
1395}
1396
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";
1401 local -i -r v=$2;
1402 local -n -r board="$boardName";
1403 local -i verbosity=0;
1404 getNNext "$boardName" $v
1405 local -i nnext=$?;
1406 unset VAR
1407 if [ $nnext -eq 0 ]; then
1408 return $C_FALSE;
1409 fi
1410 local -i nMoves=0;
1411 local -i index=0;
1412 #local -a moves
1413 getFirstChildOffset "$boardName" $v
1414 local -i firstChildOffset=$?
1415
1416 while [ $index -lt $nnext ]; do
1417 getChildWeight "$boardName" $v $index
1418 local -i weight=$?
1419 if [ $verbosity -gt 0 ]; then
1420 printf "index %d, weight: %d\n" $index $weight;
1421 fi
1422 local -i j=0;
1423 while [ $j -lt $weight ]; do
1424 if [ $verbosity -gt 0 ]; then
1425 #echo "moves: ${moves[@]}";
1426 echo "moves: ${VAR[@]}";
1427 fi
1428 #moves[$nMoves]=${board[$((firstChildOffset+index))]};
1429 VAR[$nMoves]=${board[$((firstChildOffset+index))]};
1430 nMoves=$((nMoves+1))
1431 j=$((j+1))
1432 done
1433
1434
1435 index=$((index+1))
1436 done
1437 #VAR=${moves[@]};
1438 return $C_TRUE;
1439
1440}
1441
1442#print help on the command line arguments
1443#args: none
1444#returns nothing
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";
1465}
1466
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";
1477 printf "\n";
1478 printf "The controls of this game are simple. On the edges\n"
1479 printf "of the board are letters and numbers\n";
1480 printBoard b0 "h0v0r0" $C_FALSE $C_TRUE
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"
1487 printf "\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";
1506 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
1510}
1511
1512#some global vars
1513declare duplicateBoard="";
1514declare -i boardNumber=0;
1515declare -i gameCounter=0;
1516declare -a breadcrumbs;
1517declare -i childIndex;
1518
1519function main {
1520 local -i verbosity=0;
1521 local board_src="boards.txt";
1522 local board_dst="boards.txt";
1523 local coordinateInputMode=$C_TRUE;
1524 local -i numpadMode=$C_FALSE;
1525 local -i skipIntro=$C_FALSE;
1526 while getopts ":v:w:r:hnNti" flag; do
1527 case ${flag} in
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};;
1532 n) coordinateInputMode=$C_FALSE;;
1533 N) coordinateInputMode=$C_FALSE;numpadMode=$C_TRUE;;
1534 t) return 0;;
1535 i) skipIntro=$C_TRUE;;
1536 *) printf "unknown argument %s\n" $flag;;# return 1;;
1537 esac
1538 done
1539 if [ $skipIntro -eq $C_FALSE ]; then
1540 printIntro
1541 fi
1542
1543 setup "$board_src"
1544 if [ $? -ne 0 ]; then
1545 return 1;
1546 fi;
1547
1548 local -i playSelf=$C_FALSE;
1549 local quit=$C_FALSE
1550 local -i haveLearned=$C_FALSE;
1551
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;
1556 local whoWon=" ";
1557 local gameEnded=$C_FALSE;
1558 declare -n bCurrent="b0";
1559 local transposition="h0v0r0";
1560 unset breadcrumbs
1561 printf "%d Game(s) played\n" $gameCounter
1562 gameCounter=$((gameCounter+1))
1563 if [ $verbosity -ge 1 ]; then
1564 echo "bCurrent: ${bCurrent[@]}";
1565 fi
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))]}
1568 printBoard ${bCurrent[$C_NAME_OFFSET]} $transposition $C_FALSE $coordinateInputMode;
1569
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));
1575 fi
1576 printf "preparing for next move (%s)\n" ${xo[$v]};
1577 #check if moves left
1578
1579 getNextMoves "$bCurrent" $v
1580 if [ $? -ne $C_TRUE ]; then
1581 printf "no more moves for %s\n" ${xo[$v]};
1582 isWon "${bCurrent[$C_NAME_OFFSET]}";
1583 if [ $? -eq $C_TRUE ]; then
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"
1588 else
1589 printf "better luck next time.\n"
1590 fi
1591 gameEnded=$C_TRUE;
1592 break;
1593 else #check if all squares are used
1594 getDepth "bCurrent"
1595 local depth=$?
1596 if [ $depth -eq 9 ]; then
1597 printf "The game ended in a tie.\n";
1598 gameEnded=$C_TRUE;
1599 fi
1600 printBoard "bCurrent" "$transposition" $C_FALSE $coordinateInputMode;
1601 break;
1602 fi
1603 fi
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[@]}"
1609 fi
1610
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"
1620 quit=$C_TRUE
1621 break;
1622 fi
1623 local -i nextBoardIndex=$((RANDOM%nnext));
1624
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";
1629 fi
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))};
1635 fi
1636 else #user move
1637 printf -- "--USER--\n";
1638 if [ $verbosity -ge 2 ]; then
1639 printf "transposition: %s\n" $transposition
1640 printf "breadcrumbs: "
1641 printf "%s-" "${breadcrumbs[@]}"
1642 printf "\n"
1643 fi
1644 local -i isValid=$C_FALSE;
1645 while [ $isValid -eq $C_FALSE ]; do
1646 printf "Your move: ";
1647 local c;
1648 local r;
1649 isValid=$C_TRUE;
1650 read -n 1 c;
1651 if [ "$c" == "q" ]; then
1652 quit=$C_TRUE;
1653 printf "\n";
1654 break;
1655 fi
1656 if [ "$c" == "h" ]; then
1657 printGameHelp
1658 isValid=$C_FALSE
1659 continue;
1660 fi
1661 if [ $coordinateInputMode -eq $C_TRUE ]; then
1662 read -n 1 r
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
1666 local -i i=0;
1667 while [ $i -lt ${#r[@]} ]; do
1668 case "${r[$i]}" in
1669 [0-9]) ;;
1670 *) isValid=$C_FALSE;
1671 printf "that is not a valid move\nplease enter a valid move.\n";
1672 break;
1673 esac
1674 i=$((i+1));
1675 done
1676 if [ $isValid -eq $C_FALSE ]; then
1677 continue;
1678 fi
1679
1680 if [[ ! ( 0 -le $r && $r -le 3 ) ]]; then
1681 isValid=$C_FALSE;
1682 printf "that is not a valid move.\n Please enter a valid move.\n";
1683 continue;
1684 fi
1685 local -i pos=$((r*3));
1686 case $c in
1687 [aA]) pos=$((pos+0));;
1688 [bB]) pos=$((pos+1));;
1689 [cC]) pos=$((pos+2));;
1690 [qQ]) exit;;
1691 *) isValid=$C_FALSE;
1692 printf "that is not a valid move.\n";
1693 continue;;
1694 esac
1695 else
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
1699 local -i i=0;
1700 while [ $i -lt ${#c[@]} ]; do
1701 case "${c[$i]}" in
1702 [1-9]) ;;
1703 *) isValid=$C_FALSE;
1704 printf "that is not a valid move\nplease enter a valid move: ";
1705 break;
1706 esac
1707 i=$((i+1));
1708 done
1709 if [ $isValid -eq $C_FALSE ]; then
1710 continue;
1711 fi
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]};
1716 fi
1717 #the position is given as is on the numpad, the positions
1718 fi
1719
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";
1724 pos=${matrix[pos]}
1725
1726 if [ $verbosity -ge 2 ]; then
1727 printf "transposed position: %d\n" $pos
1728 fi
1729 if [ "${bCurrent[$((C_CELLS_OFFSET+pos))]}" != " " ]; then
1730 isValid=$C_FALSE;
1731 printf "that is not a valid move, position already taken\n";
1732 if [ $verbosity -ge 2 ]; then
1733 echo "${bCurrent[@]}";
1734 fi
1735 printBoard "bCurrent" "$prevTransposition" $C_FALSE $coordinateInputMode
1736 transposition="$prevTransposition";
1737 echo "transposition=$prevTransposition";
1738 fi
1739 done
1740 if [ $quit -eq $C_TRUE ]; then
1741 break;
1742 fi
1743 if [ $verbosity -ge 1 ]; then
1744 printf "%s%d %d\n" $c $r $pos;
1745 fi
1746 #now find the corresponding board name
1747 copyBoard "bCurrent" "bTemp"
1748 bTemp[$((C_CELLS_OFFSET+pos))]=${xo[$v]};
1749 calculateBoardValue "bTemp";
1750
1751 if [ $verbosity -ge 1 ]; then
1752 printBoard "bTemp" $transposition $C_TRUE;
1753 fi
1754 local prevTransposition="$transposition";
1755 findChild "${bTemp[$C_NAME_OFFSET]}" "bCurrent" "${xo[$v]}"
1756
1757 if [ $? -eq $C_TRUE ]; then
1758 printf "Board user chose is: %s\n" $boardName;
1759 else
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"
1765 exit
1766 fi
1767
1768 if [ $verbosity -ge 1 ]; then
1769 printBoard $boardName
1770 printf "The transposition of that board is: %s\n" $transposition
1771 fi
1772 fi
1773 #now apply the old transposition to this one
1774
1775 if [ $verbosity -ge 2 ]; then
1776 printf "transposition, current, old: %s, %s\n" "$transposition" "$prevTransposition";
1777 fi
1778 transposeTransposition "$transposition" "$prevTransposition";
1779
1780 if [ $verbosity -ge 1 ]; then
1781 printf "the new board transposition is: %s\n" $transposition;
1782 fi
1783 declare -n bCurrent="$boardName";
1784 addBreadcrumb "$boardName$transposition"
1785 done
1786 if [ $quit -eq $C_TRUE ]; then
1787 printf "quitting\n"
1788 break;
1789 fi
1790 if [ $verbosity -ge 1 ]; then
1791 echo "breadcr.: ${breadcrumbs[@]}";
1792 fi
1793 printf "\n";
1794 sleep 0.5;
1795 #"learn" from the last game
1796 setNewWeights "$whoWon" "X"; #TODO: give second arg depending on playing X or O (for learning)
1797 haveLearned=$C_TRUE;
1798 sleep 1;
1799 #TODO: ask if the user wants to play again
1800
1801 done
1802 #remember the learned boards
1803 if [[ "$board_dst" != "" && $haveLearned -eq $C_TRUE ]]; then
1804 writeBoardsInfo "$board_dst";
1805 fi
1806
1807 return 0;
1808}
1809echo "$@";
1810main "$@";
$i
Counter for the number of rounds already done.
printBoard($arg1, $arg2)
Definition patience.sh:414
main()
Main function.
Definition patience.sh:1382
const $C_FALSE
False value.
Definition patience.sh:60
const $C_TRUE
True value.
Definition patience.sh:58
if(func_num_args() -le 0) if(func_num_args() -le 1) $file
Name of the file to use as trigger.