2##! Author: Pieter van der Star (info@pietervanderstar.nl)
3##! 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:
20#┏━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━┓
21#┃ 13 feb 2021 │ First header information in place │ Pieter van der Star ┃
22#┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨
23#┃ 8 jun 2021 │ Ready for release when a license is │ Pieter van der Star ┃
25#┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨
26#┃ 26 mch 2022 │ First release │ Pieter van der Star ┃
27#┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨
28#┃ 24 feb 2024 │ Changed documentation comments to be │ Pieter van der Star ┃
29#┃ │ compatible with my doxygen-bash-filter. │ ┃
30#┗━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━┛
33##! @brief This script allows someone to play the game patience.
36#The cards are numbered such that the card number:
37# modulo 2 gives the color (0= black, 1=red)
38# modulo 4 gives the suit (0=spades, 1=hearts, 2=clubs, 3=diamonds)
39# devide by 4 gives the value-1 (ace is 0, not one, 2 is one, not two etc)
43#TODO: add info on scoring in help
44#TODO: remove commented out debug code
46#FEATURE: allow switching number of open cards in-game
47#FEATURE: add bouncing cards animation when won
49#when debugging it is usefull to always run the same shuffled deck
50#uncomment the random assignment to fix the game, changing the number
51#will change the game. If unassigned, it will be random each run.
54#constants for boolean values
55##! Bool is not initialised.
56declare -r -i C_UNINIT=2;
58declare -r -i C_TRUE=1;
60declare -r -i C_FALSE=0;
63#constants and functions for return value checking
64##! @defgroup patience_return_values Return values used in patience.sh.
65##! @brief List of return values used in this script.
67#error values should always be larger than 60 as 0-60
69##! Return value for success.
70##! @todo Shouldn´t this be 0?? if not add note.
71declare -r -i C_SUCCESS=255;
72##! Indicates the selected column to take a card from is emtpy.
73declare -r -i C_EMPTY_COLUMN=254;
74##! Indicates the given move is not a valid move.
75declare -r -i C_MOVE_INVALID=253;
76##! Indicates the card asked for is not available for a move.
77declare -r -i C_CARD_NOT_FOUND=252;
78##! Indicates the given column does not exist.
79declare -r -i C_COLUMN_NOT_EXISTS=251;
80##! Indicates the argument given does not exist.
81declare -r -i C_INVALID_ARGUMENT=250;
82##! Indicates the card given is empty.
83declare -r -i C_NULL_CARD=249;
84##! The request to turn a blind card cannot be fulfilled as there are no blind cards.
85declare -r -i C_NO_BLINDS=248;
86##! The card stack is too small to give the requested cards.
87declare -r -i C_NOT_ENOUGH_CARDS=247;
88##! The given name is not a valid card name.
89declare -r -i C_INCORRECT_CARD_NAME=246;
90##! The size of the terminal is not large enough to show the patience board.
91declare -r -i C_TERMINAL_SIZE_ERROR=245;
92##! Returned prematurely for debug purposes.
93declare -r -i C_DEBUG_STOP=70;
95##! @brief Prints a descriptive error message based on the given error code.
96##! @param $arg1 The error code to give the description for.
98 local -i errorCode=$1;
102 $C_MOVE_INVALID ) printf
"Move is not allowed";; #why should be output by the
function returning
this value
107 $C_NO_BLINDS ) printf
"There are no blind cards in this column";;
108 $C_DEBUG_STOP ) printf
"Stopped execution for debug purposes";;
111 * ) printf
"Unknown error: %d" $errorCode;;
117### Some global vars and functions for formatting ###
118##! Indicates if terminal colors should be used.
120##! @brief Sets the terminal text color to black.
122##! @brief Sets the terminal text color to red.
124##! @brief Sets the terminal text color to green.
126##! @brief Sets the terminal text color to brown.
128##! @brief Sets the terminal text color to blue.
130##! @brief Sets the terminal text color to purple.
132##! @brief Sets the terminal text color to cyan.
134##! @brief Sets the terminal text color to gray.
137##! @brief Sets the terminal background color to black.
139##! @brief Sets the terminal background color to red.
141##! @brief Sets the terminal background color to green.
143##! @brief Sets the terminal background color to brown.
145##! @brief Sets the terminal background color to blue.
147##! @brief Sets the terminal background color to purple.
149##! @brief Sets the terminal background color to cyan.
151##! @brief Sets the terminal background color to gray.
154##! @brief Sets the terminal to print bold text.
156##! @brief Sets the terminal to print underlined text.
158##! @brief Sets the terminal to reset the colors to the default.
160##! @brief Sets the background of the board patience is played on.
163### Debug functions ###
164##! Print the cards in the deck for debug purposes.
168 while [
$i -lt ${#deck[@]} ];
do
174 while [
$i -lt ${#deckFront[@]} ];
do
180 while [
$i -lt ${#deckOpen[@]} ];
do
187### deck manipulation functions ###
188#The deck is split in three parts, the closed stack (closed), the open cards for the currrent hand (front)
189# and the open cards from previous hands (open).
191##! Remove cards from the open part of the deck at given index and fill the gap.
192##! @param $arg1 Index of the card to be removed.
194 local -i cardIndex=$1;
195 local -A cards=${#deckOpen[@]};
199 while [
$i -lt $cards ];
do
200 if [
$i -eq $cardIndex ]; then
203 deckOpen[
$i]=${deckOpen[$j]};
207 unset deckOpen[$cards];
210##! @brief Remove cards from the front of the deck.
211##! @param $arg1 Index of the card to be removed.
212##! @param $arg2 should it backfill from deckOpen.
213##! @returns Error code according to @ref patience_return_values.
215##! Remove cards from the open part of the deck at given index
216##! and fill the gap. If backfill is set take one card from the
217##! open hand of the deck and move it to the front.
219 local -i cardIndex=$1;
220 cardIndex=$((cardIndex));
221 if [ $# -gt 1 ]; then
222 local -i -r backfillIfEmpty=$2;
224 local -i -r backfillIfEmpty=
$C_FALSE;
226 local -A cards=${#deckFront[@]}
230 while [
$i -lt $cards ];
do
231 if [
$i -eq $cardIndex ]; then
234 deckFront[
$i]=${deckFront[$j]}
238 unset deckFront[$cards]
239 if [[ ${#deckFront[@]} -eq 0 && $backfillIfEmpty -eq
$C_TRUE ]]; then
240 if [ ${#deckOpen[@]} -ne 0 ]; then
241 deckFront[0]=${deckOpen[$((${#deckOpen[@]}-1))]};
242 unset deckOpen[$((${#deckOpen[@]}-1))]
248##! Remove cards from the closed part of the deck at given index and fill the gap.
249##! @param $arg1 index of the card to be removed.
251 local -i cardIndex=$1
252 local -A cards=${#deck[@]}
256 while [
$i -lt $cards ];
do
257 if [
$i -eq $cardIndex ]; then
267##! Reindex the deck, i.e. remove gaps in array.
271 while [ $j -lt ${#deck[@]} ];
do
272 if [
"${deck[$i]}" ==
"" ]; then
273 while [
"${deck[$j]}" ==
"" ];
do
277 deck[
$i]=${deck[$j]};
281 while [
$i -lt $j ];
do
287##! Move the cards in the deck to a random new position.
289 local -A shuffledDeck
290 while [ ${#deck[@]} -gt 0 ];
do
291 local -i index=$(($RANDOM%${#deck[@]}))
292 if [
"${deck[$index]}" ==
"" ]; then
295 shuffledDeck[${#shuffledDeck[@]}]=${deck[$index]};
300 while [
$i -lt ${#shuffledDeck[@]} ];
do
301 deck[
$i]=${shuffledDeck[
$i]};
306##! Fill the deck with all the cards.
309 while [
$i -lt 52 ];
do
315##! Move cards from the front to the open deck and then nFront from the closed to the front.
316##! @param $arg1 how many cards should be in a hand.
317##! @post Sets the global variable nDeckIterations.
319 local -i -r nFront=$1;
322 local -A cards=${#deckFront[@]};
323 while [
$i -le $cards ];
do
324 deckOpen[${#deckOpen[@]}]=${deckFront[$((cards-i))]};
329 #check if we need to flip the deck
330 if [ ${#deck[@]} -eq 0 ]; then
331 #move from open to deck
332 while [ ${#deckOpen[@]} -ne 0 ];
do
333 deck[${#deck[@]}]=${deckOpen[0]};
340 if [ $nFront -gt ${#deck[@]} ]; then
343 while [
$i -gt 0 ];
do
345 deckFront[
$i]=${deck[0]};
348 #check if we need to flip the deck in the next move
349 if [ ${#deck[@]} -eq 0 ]; then
350 nDeckIterations=$((nDeckIterations+1));
354### User interface outputs ###
356##! Print the suit stacks as a string, if empty print the suit symbol.
358 local symbols=(
"♠" "♥" "♣" "♦")
360 while [
$i -lt 4 ];
do
361 if [ $((i%2)) -eq 0 ]; then
366 if [ ${suitStack[
$i]} -eq -1 ]; then
367 printf
"%s " ${symbols[
$i]}
369 local -i value=${suitStack[
$i]}
370 local -i card=$((value*4+i))
378##! Print the deck, in two parts, first the closed, then the front.
379##! @param $arg1 end of deck iterations.
381 local -i endDeckIterations=$1
382 local -i inDeck=${#deck[@]} #number of cards in the (closed) deck
383 local -i inDeckFront=${#deckFront[@]} #number of cards in the front deck
384 if [ $endDeckIterations -eq
$C_TRUE ]; then
386 elif [ $inDeck -gt 0 ]; then
393 while [
$i -lt $inDeckFront ];
do
395 if [ $? -eq 0 ]; then
404 #fill the space set for the deck (5 is the space used by the highest
406 while [
$i -lt 5 ];
do
413##! @param $arg1 (optional) has player won the game.
414##! @param $arg2 (optional) end of deck iterations.
418 if [ $# -lt 1 ]; then local -i isWon=
$C_FALSE;
else local -i isWon=$1; fi
419 if [ $# -lt 2 ]; then local -i endDeckIterations=
$C_FALSE;
else local -i endDeckIterations=$2; fi
423 printf
"%s" "$marginTop"
424 printf
"%s Patience\n" "$marginLeft";
426 printBoardContent "┃%s │%s┃" "$(printDeck $endDeckIterations)" "$(printSuitsStack)"
432 while [ $r -lt 19 ];
do
433 if [ $isWon -eq
$C_TRUE ]; then
434 if [[ 2 -le $r && $r -le 14 ]]; then
435 if [ $r -eq 2 ]; then line=
" __ ______ _ _ ";
436 elif [ $r -eq 3 ]; then line=
" \\ \\ / / __ \\| | | | ";
437 elif [ $r -eq 4 ]; then line=
" \\ \\_/ / | | | | | | ";
438 elif [ $r -eq 5 ]; then line=
" \\ /| | | | | | | ";
439 elif [ $r -eq 6 ]; then line=
" | | | |__| | |__| | ";
440 elif [ $r -eq 7 ]; then line=
" |_| \\____/ \\____/ ";
441 elif [ $r -eq 8 ]; then line=
" __ ______ _ _ ";
442 elif [ $r -eq 9 ]; then line=
" \\ \\ / / __ \\| \\ | | ";
443 elif [ $r -eq 10 ]; then line=
" \\ \\ /\\ / / | | | \\| | ";
444 elif [ $r -eq 11 ]; then line=
" \\ \\/ \\/ /| | | | . \` | ";
445 elif [ $r -eq 12 ]; then line=
" \\ /\\ / | |__| | |\\ | ";
446 elif [ $r -eq 13 ]; then line=
" \\/ \\/ \\____/|_| \\_| ";
447 elif [ $r -eq 14 ]; then line=
" ";
449 printBoardContent "┃%s%s%s%s%s┃" "$(terminalBrownBackground)" "$(terminalGreenForeground)" "$line" "$(setBoardBackground)" "$(terminalGreyForeground)";
453 #build the actual board
456 while [ $c -lt 7 ];
do
457 line+=
"$(terminalGreyForeground)";
458 if [ $r -lt ${blindStacks[$c,0]} ]; then
459 line+=
"$(printCard ${blindStacks[$c,$((r+1))]} 0)";
460 elif [[ $r -eq 0 && ${openStacks[$c,0]} -eq -1 ]]; then
462 elif [ $r -lt $((${openStacks[$c,0]}+${blindStacks[$c,0]})) ]; then
463 getCardColor ${openStacks[$c,$((r-${blindStacks[$c,0]}+1))]}
465 if [ $color -eq 0 ]; then
466 line+=
"$(terminalBlackForeground)";
468 line+=
"$(terminalRedForeground)";
470 line+=
"$(printCard ${openStacks[$c,$((r-${blindStacks[$c,0]}+1))]} )";
495##! @brief Helper function to print the board. It handles the coloring of the board.
496##! arguments same as args to printf.
498 printf
"%s" "$marginLeft";
setBoardBackground; printf
"$@";terminalBlueBackground;printf "\n";
501##! @brief Helper function to print the board. It handles the coloring of the board.
506 printf
"%s-%03d%s" "$(terminalRedForeground)" $((-1*score))
"$(terminalGreyForeground)";
510##! @brief Print the help function and handles help navigation through the help.
511##! @param $arg1 (optional) pagenumber to show.
512##! @returns Error code according to @ref patience_return_values.
516 if [ $# -eq 0 ]; then
521 local -r -i nPages=4;
525 printBoardContent "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓";
527 1)
printBoardContent "┃ Hi, this game uses text input for the moves. These commands ┃";
528 printBoardContent "┃ are processed during typing. There is no need to press the ┃";
531 printBoardContent "┃ Cards are are selected by a shorthand: first give a number ┃";
532 printBoardContent "┃ or letter for the value, then a letter to select the suit ┃";
538 printBoardContent "┃ %sCommands%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)";
542 printBoardContent "┃ \"<card> <card>\" moves the first card on top of the second ┃";
545 printBoardContent "┃ \"<card> c<number>\" moves the card to the corresponding ┃";
551 printBoardContent "┃ \" \" or [enter] move all possible cards to the suitstacks ┃";
552 printBoardContent "┃ \"m\" recalculate the margins (use after resizing the terminal) ┃";
557 printBoardContent "┃ %sCommand line options%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)";
558 printBoardContent "┃ -o <number> Number of cards to show when taking from deck ┃";
561 printBoardContent "┃ -i <number> Number of iterations for the deck (0=no limit) ┃";
564 printBoardContent "┃ %sCard selection%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)";
565 printBoardContent "┃ The following table shows the cards and corresponding command:┃";
574 printBoardContent "┃ First enter the column, and then the row. i.e. \"as\" = 🂡 ┃";
576 printBoardContent "┃ %sOther symbols%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)";
577 printBoardContent "┃ There are two other cards that may appear during gameplay: ┃";
581 printBoardContent "┃ It will restart with the deck unless a limit has been set, ┃";
583 printBoardContent "┃ ⓝ column numbers, usefull when using he c<number> command ┃";
587 printBoardContent "┃ %sExamples%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)";
588 printBoardContent "┃ 1: To put 🂻 on top of 🃝 the following command needs to be ┃";
589 printBoardContent "┃ given: \"jh\" followed by \"qc\". The first cards' value is ┃";
594 printBoardContent "┃ needs to be given: \"ah\" followed by \"s\". \"s\" indicates the ┃";
599 printBoardContent "┃ %sScoring%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)";
600 printBoardContent "┃ At each new game 100 points are substracted from the count. ┃";
601 printBoardContent "┃ You gain points by removing cards from the deck (1pt/card) ┃";
603 printBoardContent "┃ so moving an ace to the suitstacks give 1 point, while a king ┃";
617 printBoardContent "┃ %sLicense%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)";
618 printBoardContent "┃This program is distributed in the hope that it will be useful,┃";
619 printBoardContent "┃but WITHOUT ANY WARRANTY; without even the implied warranty of ┃";
620 printBoardContent "┃MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ┃";
623 printBoardContent "┃You should have received a copy of the GNU Affero General ┃";
631 printBoardContent "┃ - The modifications are indicated clearly in the changelog ┃";
636 printBoardContent "┃ - The software is provided as-is. Feature requests or bug ┃";
648 printBoardContent "┃ press n for next page, p for previous or q to quit help %2d/%d ┃" $page 4;
649 printBoardContent "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛";
653 while [[
"$char" !=
"n" &&
"$char" !=
"q" ]];
do
656 "n")
if [ $page -ne $nPages ]; then
printHelp $((page+1));
else char=
""; fi;;
657 "p")
if [ $page -ne 1 ]; then
printHelp $((page-1));
else char=
""; fi;;
664##! Output a card character.
665##! @param $arg1 cardnumber of card to print.
666##! @param $arg2 (optional) print the card closed.
667##! @returns Error code according to @ref patience_return_values.
670 if [
"$card" ==
"" ]; then
675 if [ $# -ge 2 ]; then local open=$2;
676 else local open=1; fi
677 if [ $card -eq -1 ]; then
683 cards=(
"🂡" "🂱" "🃑" "🃁" #A
696 "🂬" "🂼" "🃜" "🃌" #C here
for completeness of the unicode deck (
for future French version?)
699 if [ $((open)) -eq 1 ]; then
700 printf
"%s " ${cards[$card]};
702 printf
"%s " $cardBack;
707##! Print the credits of the game and perform a short "animation".
710 while [
$i -le 4 ];
do
712 printf
"%s" "$marginTop";
713 printf
"\n\n\n\n\n\n\n\n";
714 printf
"%s Patience by\n" "$marginLeft";
715 printf
"%s Pieter van der Star\n" "$marginLeft";
718 printf
"%s " "$marginLeft";
719 if [
$i -gt 0 ]; then printf
"\e[30m♠ ";
else printf
"\e[31m♤ "; fi;
720 if [
$i -gt 1 ]; then printf
"\e[31m♥ ";
else printf
"\e[30m♡ "; fi;
721 if [
$i -gt 2 ]; then printf
"\e[30m♣ ";
else printf
"\e[31m♧ "; fi;
722 if [
$i -gt 3 ]; then printf
"\e[31m♦ ";
else printf
"\e[30m♢ "; fi;
733##! Check if the game is won.
734##! @returns Boolean true if game won, false if not (yet).
737 while [
$i -lt 4 ];
do
738 if [ ${suitStack[
$i]} -ne 12 ]; then
746##! Deal the blind cards on the board.
750 while [ $c -lt 7 ];
do
751 blindStacks[$c,0]=$((c+1))
753 while [ $r -le $((c+1)) ];
do
754 blindStacks[$c,$r]=${deck[0]}
755# printCard ${deck[0]};
764##! Fill any open columns on the board.
765##! @returns Error code according to @ref patience_return_values.
768 while [ $c -lt 7 ];
do
769 local nOpen=${openStacks[$c,0]}
770 if [ $nOpen -eq 0 ]; then
771 ##printf "turnBlind c: %d\n" $c
783##! Turn a blind card from a given column.
784##! @param $arg1 column to turn the frontmost blind card.
785##! @returns Error code according to @ref patience_return_values.
788 local nRows=${blindStacks[$c,0]}
789 if [ $nRows -eq 0 ]; then
793 openStacks[$c,1]=${blindStacks[$c,$nRows]}
794 unset blindStacks[$c,$nRows]
795 blindStacks[$c,0]=$((nRows-1))
799##! Initialise the suitstacks
807##! Get the card value at the bottom of the given column
808##! @param $arg1 column
809##! @returns Error code according to @ref patience_return_values.
812 if [[ $c -lt 0 || $c -gt 6 ]]; then
813 printf
"getBottomCard: Incorrect column given %d" $c 1>&2;
815 elif [ ${openStacks[$c,0]} -eq 0 ]; then
818 return ${openStacks[$c,${openStacks[$c,0]}]};
823##! Get the card value at the top of the given column.
824##! @param $arg1 column.
825##! @returns Error code according to @ref patience_return_values.
828 if [[ $c -lt 0 || $c -gt 6 ]]; then
829 printf
"getTopCard: Incorrect column given %d" $c 1>&2;
831 elif [ ${openStacks[$c,0]} -eq 0 ]; then
834 return ${openStacks[$c,1]};
839##! Move a card from the deck to the corresponding suitstack.
840##! @returns Error code according to @ref patience_return_values.
842 local card=${deckFront[0]}
848 local stackValue=${suitStack[$suit]}
849 if [ $((stackValue+1)) -ne $value ]; then
850 user_msg1=
"Invalid move, cannot put %d" $value
851 user_msg2=
"on %d." $stackValue
856 suitStack[$suit]=$value
857 score=$((score+value+1));
858 printf
"score: %d\n" $score
864##! Remove a card from one of the open stacks on the board.
865##! @param $arg1 column index.
866##! @param $arg2 number of cards.
867##! @returns Error code according to @ref patience_return_values.
871 local rows=${openStacks[$c,0]};
872 if [ $n -gt $rows ]; then
873 printf
"ERROR not enough cards on stack to remove %d cards" $n 1>&2;
877 while [ $r -gt 1 ];
do
878 unset openStacks[$c,$r]
881 openStacks[$c,0]=$((rows-n));
885##! Remove a card from the suitstack.
886##! @param $arg1 the suit to remove the card from.
887##! @returns Error code according to @ref patience_return_values.
890 if [ ${suitStack[$s]} -lt 0 ]; then
891 return $C_EMTPY_SUITSTACK; #no cards on suitstack
893 suitStack[$s]=$((${suitStack[$s]}-1))
897##! Initialise the open stacks on the board.
900 while [ $c -lt 7 ];
do
906##! Search the deck, board and suitstacks for the given card.
907##! @param $arg1 Cardnumber of the card to find.
908##! @post updates inherited:
909##! $fromFromDeck to indicate if the card is the first card on the deck.
910##! $fromFromSuit to indicate if the card is the top of the suit stack.
911##! $inColumn to indicate which column the card is in.
912##! @returns Error code according to @ref patience_return_values.
917 #first look on the deck
918 if [[ ${#deckFront[@]} -gt 0 && ${deckFront[0]} -eq $card ]]; then
922 #then look on the suitstacks
927 if [ ${suitStack[$suit]} -eq $value ]; then
931 #then look on the board
933 while [ $inColumn -lt 7 ];
do
935 inRow=${openStacks[$inColumn,0]};
936 while [ $inRow -gt 0 ];
do
937 local checkingCard=${openStacks[$inColumn,$inRow]};
938 if [ $checkingCard -eq $card ]; then
944 inColumn=$((inColumn+1))
949##! Get the value of the card based on the cardnumber.
950##! @param $arg1 Cardnumber.
951##! @returns Card value.
957##! Get the suit of the card based on the cardnumber.
958##! @param $arg1 Cardnumber.
959##! @returns Number indicating card suit.
965##! Get the color of the card based on the cardnumber.
966##! @param $arg1 Cardnumber.
967##! @returns Number indicating card value.
973##! Check if the move is a valid move on the board.
974##! @param $arg1 Cardnumber of card to move.
975##! @param $arg2 cardnumber of card to move the arg1 card on top of.
976##! @returns Error code according to @ref patience_return_values.
991 if [ $fromValue -eq 12 ]; then #a king is an exception to the rule
992 if [ $toCard -ne
$C_EMPTY_COLUMN ]; then #and may only be moved to non-open column
993 user_msg1=
"A king can only be moved";
997 elif [ $fromColor -eq $toColor ]; then
998 user_msg1=
"A king can only be moved";
1000 elif [ $fromValue -ne $((toValue-1)) ]; then
1001 user_msg1=
"the cards can only stack onto";
1002 user_msg2=
"another in value order K,Q,J,10...2,1,A";
return $C_MOVE_INVALID;
1008##! Check if the move is a valid move to the suitstacks.
1009##! @param $arg1 cardnumber of card to move.
1010##! @returns Error code according to @ref patience_return_values.
1015 local topValue=${suitStack[$suit]}
1016 if [ $((topValue+1)) -ne $value ]; then
1017 user_msg1=
"the cards can only stack onto";
1018 user_msg2=
"another in the order A,1,2...10,J,Q,K";
return $C_MOVE_INVALID;
1023##! Move card from A to B.
1024##! @param $arg1 card to move.
1025##! @param $arg2 location to move to.
1026##! @returns Error code according to @ref patience_return_values.
1028 #if [ $# -ge 3 ]; then
1029 # printf "ENABLING DEBUG"
1032 local -i fromCard=$1;
1035 user_msg1=
"Card(s) to move not found";
1038 local fromColumn=$inColumn;
1039 local fromRow=$inRow;
1044 local toColumn=${to:1:1};
1048 #is it a valid move?
1056 #getCardValue $toCard
1059 if [ $fromDeck -eq
$C_TRUE ]; then
1060 #update the rowcount
1061 openStacks[$toColumn,0]=$((${openStacks[$toColumn,0]}+1));
1063 openStacks[$toColumn,${openStacks[$toColumn,0]}]=${deckFront[0]};
1064 #remove from old position
1067 elif [ $fromSuit -eq
$C_TRUE ]; then
1070 #update the rowcount
1071 openStacks[$toColumn,0]=$((${openStacks[$toColumn,0]}+1));
1073 openStacks[$toColumn,${openStacks[$toColumn,0]}]=$fromCard;
1074 #remove from old position
1077 #to same location is a valid "move", but nothing needs to be done
1078 if [ $toColumn -eq $fromColumn ]; then
1082 while [ $r -le ${openStacks[$fromColumn,0]} ];
do
1083 #update the rowcount
1084 openStacks[$toColumn,0]=$((${openStacks[$toColumn,0]}+1));
1086 openStacks[$toColumn,${openStacks[$toColumn,0]}]=${openStacks[$fromColumn,$r]};
1087 #remove from old position
1088 unset openStacks[$fromColumn,$r];
1091 #update the rowcounts
1092 openStacks[$fromColumn,0]=$((fromRow-1));
1095 "s") #is the destination on the suitstacks?
1096 if [ $fromSuit -eq
$C_TRUE ]; then
1097 user_msg1=
"Card is already on the";
1098 user_msg2=
"suitstacks";
1101 #is the card the bottom card
1103 user_msg1=
"only the bottom card, or the one from";
1104 user_msg2=
"the deck can be put on the suitstacks";
1107 #determine the value and suit
1118 suitStack[$suit]=$value
1119 if [ $fromDeck -eq
$C_TRUE ]; then
1120 ##printf "fromdeck\n";
1124 #printErrorDescription $ret;
1126 ##printf "not fromdeck\n";
1129 #printErrorDescription $ret;
1131 score=$((score+value+1));
1132 if [ $ret -ne
$C_SUCCESS ]; then user_msg1=
"ERROR: something went wrong when removing"; user_msg2=
"card from the open stack";
set +x;
return $ret;fi
1140 local fromFromDeck=$fromDeck;
1141 local fromFromSuit=$fromSuit;
1146 #to same location is a valid "move", but nothing needs to be done
1147 #but that is only the case when from is not on the deck or suitstacks
1148 if [[ $fromFromDeck -ne
$C_TRUE && $fromFromSuit -ne
$C_TRUE ]]; then
1149 if [ $inColumn -eq $fromColumn ]; then
1150 user_msg1=
"to same location";
1156 *) user_msg1=
"ERROR: unknown destination \"$2\"";
return $C_MOVE_INVALID;
1162##! Try to put all available cards on the suitstacks.
1165 while [ $doLoop -eq
$C_TRUE ];
do
1166 doLoop=
$C_FALSE; #
set back to
true if a card was moved so we can run again
1168 ##printf "AM: checking deck\n";
1169 if [ ${#deckFront[@]} -ne 0 ]; then
1173 ##printf "Moved %d (deck) " $card;printCard $card;printf "\n";
1178 #iterate over all columns
1180 while [ $c -lt 7 ];
do
1181 ##printf "AM: checking column %d\n" $c
1182 #try moving the bottom card to the stack
1189 ##printf "Moved %d " $card;printCard $card;printf "\n";
1199 user_msg1=
"Automove to suitstacks"
1200 user_msg2=
"in progress";
1204 user_msg1=
"Automove to suitstacks"
1205 user_msg2=
"finished";
1206 ##printf "AM: return\n";
1209##! Convert a human card name to a cardnumber.
1210##! @param $arg1 abbrieviated human name for the card.
1211##! @returns Card number.
1214 local length=${#name}
1215 local suit=${name:$((length-1)):1}
1216 local value=${name:0:$((length-1))}
1228 "10"|[1-9]) value=$((value-1));;
1232 local card=$((value*4+suit));
1236##! Convert cardnumber to a human readable card name.
1237##! @param $arg1 card number.
1238##! @returns Error code according to @ref patience_return_values.
1240##! Prints card name, for use in $(cardToName n)
1251 10|[1-9]) printf
"%d" $value;;
1268##! Calculate the margin so the board ends up in the middle of the terminal.
1269##! @param $arg1 terminal width.
1270##! @param $arg2 width of the content (board is different to help)
1271##! @post Sets global marginLeft.
1273 local -i terminalWidth=$1;
1274 local -i contentWidth=$2;
1275 local -i margins=$((terminalWidth-contentWidth));
1276 local -i margin=$((margins/2));
1278 while [ $margin -gt 0 ];
do
1280 margin=$((margin-1));
1285##! Calculate the margin so the board ends up in the middle of the terminal.
1286##! @param $arg1 terminal height.
1287##! @param $arg2 height of the content (board is different to help)
1288##! @post Sets global marginTop.
1290 local -i terminalHeight=$1;
1291 local -i contentHeight=$2;
1292 local -i margins=$((terminalHeight-contentHeight));
1293 local -i margin=$((margins/2));
1295 while [ $margin -gt 0 ];
do
1296 #sadly \n does not work, so then use a hard return in the string
1299 margin=$((margin-1));
1306##! The deck of cards.
1308##! The blind cards of each column.
1309declare -A blindStacks
1310##! The open cards of each column.
1311declare -A openStacks
1312##! The open cards on the unplayed deck.
1314##! The card on the front of the deck.
1316##! Stacks of cards having been sorted by suit.
1318##! Text for the user message, first row.
1319declare user_msg1=
"Welcome"
1320##! Text for the user message, second row.
1321declare user_msg2=
" press h for help"
1322##! How much margin to keep to the left of the terminal to center the board.
1323declare marginLeft=
"";
1324##! How much margin to keep to the top of the terminal to center the board.
1325declare marginTop=
"";
1326#declare -i blindRows=0;
1327##! How many times has the deck been rotated (viewed all cards and started over).
1328declare -i nDeckIterations=0;
1332##! Initialise the game.
1345 score=$((score-100));
1348##! Cleanup remnants of the old game before starting a new.
1350 while [ ${#deckOpen[@]} -gt 0 ];
do
1351 unset deckOpen[$((${#deckOpen[@]}-1))];
1353 while [ ${#deckFront} -gt 0 ];
do
1354 unset deckFront[$((${#deckFront[@]}-1))];
1358##! Ask user for confirmation.
1359##! @param $arg1 Description of the action to perform.
1360##! @returns Bool, user confirmed.
1364 printf
"%sAre you sure you want to\n" "$marginLeft";
1365 printf
"%s%24s? (y/n)\n" "$marginLeft" "$1";
1367 while [[
"$char" !=
"n" &&
"$char" !=
"q" ]];
do
1375##! Check if the terminal has the correct size to display the board.
1376##! @param $arg1 Width of the terminal (in characters).
1377##! @param $arg2 Width of the terminal (in characters).
1381 if [[ $width -lt 65 || $height -lt 35 ]]; then
1382 printf
"Please adjust the terminal to at least 65x35\n";
1383 printf
"The game requires at least 65 columns and has a height of 35 lines\n";
1384 printf
"The terminal currently has %d columns and has a height of %d lines\n" $width $height;
1391 #before anything else, check the terminal width and height
1392 #>= the minimum required by the game
1393 local -i width=$(tput cols);
1394 local -i height=$(tput lines);
1400 #main variables that can be overwritten by command line arguments
1401 local -i nOpen=3; #3 is
default, can be
set to anything between 1 and 5;
1402 local -i maxDeckIterations=0;#no limit by
default, can be any value between 0 (no limit) and 255
1403 while getopts
"ho:ci:s" arg;
do
1405 h) printf
"For help press h after start\n";sleep 5;;
1406 o) nOpen=${OPTARG};;
1407 c) useColoredTerminal=0;;
1408 i) maxDeckIterations=${OPTARG};;
1410 *) printf
"";exit 0;;
1415 #validate user input
1416 if [[ 0 -gt $nOpen || $nOpen -gt 5 ]]; then
1417 printf
"Invalid value for argumment o\n";
1418 printf
"\tnumber of open cards must be\n";
1419 printf
"0<number of open cards ≤ 5.\n";
1420 printf
"Value given: %d\n" $nOpen;
1424 if [[ 0 -gt $maxDeckIterations || $maxDeckIterations -gt 255 ]]; then
1425 printf
"Invalid value for argumment i\n";
1426 printf
"\tnumber of iterations must be\n";
1427 printf
"0<number of iterations ≤ 255.\n";
1428 printf
"Value given: %d\n" $maxDeckIterations;
1440 local -i endDeckIterations=
$C_FALSE;
1443 #handle user input in the main loop
1448 while [ $endGame -eq
$C_FALSE ];
do
1449 if [ $gotCommand -eq
$C_TRUE ]; then
1457 printf
"%sCommand: %s" "$marginLeft" "$state"
1458 while [ $gotCommand -eq
$C_FALSE ];
do
1461 shopt -s extglob # turn on extended globbing
1463 [a]|[0-9]|
"10"|[jqk] ) ;;
1464 [a0-9jqk][shcd]|
"10"[shcd])
if [ $won -eq
$C_TRUE ]; then
1466 elif [ $cardF -lt 0 ]; then
1478 "c" )
if [ $won -eq
$C_TRUE ]; then
1482 "c"[0-6] )
if [ $won -eq
$C_TRUE ]; then
1484 elif [ $cardF -lt 0 ]; then
1490 user_msg1=
"Column is empty or is";
1491 user_msg2=
"not a valid column";
1498 "s" )
if [ $won -eq
$C_TRUE ]; then
1500 elif [ $cardF -lt 0 ]; then
1501 user_msg1=
"Can only use s when already";
1502 user_msg2=
"selected a card to move";
1510 "d" )
if [ $won -eq
$C_TRUE ]; then
1513 if [ $endDeckIterations -eq
$C_FALSE ]; then
1516 if [[
$nDeckIterations -ge $maxDeckIterations && $maxDeckIterations -ne 0 ]]; then
1522 *
"r" ) user_msg1=
"resetting command"; cardF=-1;gotCommand=
$C_TRUE;;
1528 height=$(tput lines);
1534 user_msg1=
"recalculated margins";
1541 "?" ) printf
"\nCurrent command: \"%s\"\nCurrent cardF: %s\n" $state
"$(printCard $cardF)";gotCommand=
$C_TRUE;;
1552 * ) user_msg1=
"Invalid command \"$state\",";
1553 user_msg2=
"press h for help";
1570#call the main function and pass all script arguments to it
const $C_NO_BLINDS
The request to turn a blind card cannot be fulfilled as there are no blind cards.
const $C_EMPTY_COLUMN
Indicates the selected column to take a card from is emtpy.
const $C_MOVE_INVALID
Indicates the given move is not a valid move.
printErrorDescription($arg1)
Prints a descriptive error message based on the given error code.
const $C_NOT_ENOUGH_CARDS
The card stack is too small to give the requested cards.
const $C_INVALID_ARGUMENT
Indicates the argument given does not exist.
const $C_DEBUG_STOP
Returned prematurely for debug purposes.
const $C_NULL_CARD
Indicates the card given is empty.
const $C_COLUMN_NOT_EXISTS
Indicates the given column does not exist.
const $C_CARD_NOT_FOUND
Indicates the card asked for is not available for a move.
const $C_TERMINAL_SIZE_ERROR
The size of the terminal is not large enough to show the patience board.
const $C_INCORRECT_CARD_NAME
The given name is not a valid card name.
$i
Counter for the number of rounds already done.
removeFromOpenStacks($arg1, $arg2)
dealBlinds()
Deal the blind cards on the board.
terminalPurpleForeground()
Sets the terminal text color to purple.
printCredits()
Print the credits of the game and perform a short "animation".
printHelp($arg1)
Print the help function and handles help navigation through the help.
reindexDeck()
Reindex the deck, i.e. remove gaps in array.
terminalBlackBackground()
Sets the terminal background color to black.
debugPrintDeck()
Print the cards in the deck for debug purposes.
terminalCyanForeground()
Sets the terminal text color to cyan.
populateDeck()
Fill the deck with all the cards.
terminalUnderline()
Sets the terminal to print underlined text.
$deckFront
The card on the front of the deck.
$blindStacks
The blind cards of each column.
removeFromDeckOpen($arg1)
terminalGreenBackground()
Sets the terminal background color to green.
init()
Initialise the game.
$user_msg1
Text for the user message, first row.
$deckOpen
The open cards on the unplayed deck.
checkTerminalSize($arg1, $arg2)
terminalBlueForeground()
Sets the terminal text color to blue.
isValidBoardMove($arg1, $arg2)
const $C_UNINIT
Bool is not initialised.
terminalPurpleBackground()
Sets the terminal background color to purple.
$marginTop
How much margin to keep to the top of the terminal to center the board.
terminalBold()
Sets the terminal to print bold text.
shuffleDeck()
Move the cards in the deck to a random new position.
$suitStack
Stacks of cards having been sorted by suit.
printScore()
Helper function to print the board. It handles the coloring of the board.
$openStacks
The open cards of each column.
terminalBlackForeground()
Sets the terminal text color to black.
calculateTopMargin($arg1, $arg2)
$useColoredTerminal
Indicates if terminal colors should be used.
$user_msg2
Text for the user message, second row.
terminalClearColors()
Sets the terminal to reset the colors to the default.
terminalBrownBackground()
Sets the terminal background color to brown.
printSuitsStack()
Print the suit stacks as a string, if empty print the suit symbol.
initSuitStack()
Initialise the suitstacks.
setBoardBackground()
Sets the background of the board patience is played on.
terminalBrownForeground()
Sets the terminal text color to brown.
$nDeckIterations
How many times has the deck been rotated (viewed all cards and started over).
const $C_FALSE
False value.
terminalRedBackground()
Sets the terminal background color to red.
terminalGreenForeground()
Sets the terminal text color to green.
terminalBlueBackground()
Sets the terminal background color to blue.
printBoardContent()
Helper function to print the board. It handles the coloring of the board. arguments same as args to p...
terminalGreyBackground()
Sets the terminal background color to gray.
initOpen()
Initialise the open stacks on the board.
autoMoveToSuitstacks()
Try to put all available cards on the suitstacks.
removeFromDeckFront($arg1, $arg2)
Remove cards from the front of the deck.
terminalRedForeground()
Sets the terminal text color to red.
terminalGreyForeground()
Sets the terminal text color to gray.
$marginLeft
How much margin to keep to the left of the terminal to center the board.
terminalCyanBackground()
Sets the terminal background color to cyan.
cleanup()
Cleanup remnants of the old game before starting a new.
calculateLeftMargin($arg1, $arg2)