#!/bin/bash ##! Author: Pieter van der Star (info@pietervanderstar.nl) ##! Modifications by: (unmodified) ##! ##! This program is free software: you can redistribute it and/or modify ##! it under the terms of the GNU Affero General Public License as ##! published by the Free Software Foundation, either version 3 of the ##! License, or (at your option) any later version. ##! ##! This program is distributed in the hope that it will be useful, ##! but WITHOUT ANY WARRANTY; without even the implied warranty of ##! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ##! GNU Affero General Public License for more details. ##! ##! You should have received a copy of the GNU Affero General Public License ##! along with this program. If not, see <https://www.gnu.org/licenses/>. #
#Changelog: #┏━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━┓ #┃ 13 feb 2021 │ First header information in place │ Pieter van der Star ┃ #┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨ #┃ 8 jun 2021 │ Ready for release when a license is │ Pieter van der Star ┃ #┃ │ chosen │ ┃ #┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨ #┃ 26 mch 2022 │ First release │ Pieter van der Star ┃ #┠───────────────┼──────────────────────────────────────────┼──────────────────────────┨ #┃ 24 feb 2024 │ Changed documentation comments to be │ Pieter van der Star ┃ #┃ │ compatible with my doxygen-bash-filter. │ ┃ #┗━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━┛
##! @file ##! @brief This script allows someone to play the game patience.
#Cards #The cards are numbered such that the card number: # modulo 2 gives the color (0= black, 1=red) # modulo 4 gives the suit (0=spades, 1=hearts, 2=clubs, 3=diamonds) # devide by 4 gives the value-1 (ace is 0, not one, 2 is one, not two etc) # -1 is no card #
#TODO: add info on scoring in help #TODO: remove commented out debug code
#FEATURE: allow switching number of open cards in-game #FEATURE: add bouncing cards animation when won
#when debugging it is usefull to always run the same shuffled deck #uncomment the random assignment to fix the game, changing the number #will change the game. If unassigned, it will be random each run. #RANDOM=101;
#constants for boolean values ##! Bool is not initialised. declare -r -i C_UNINIT=2; ##! True value. declare -r -i C_TRUE=1; ##! False value. declare -r -i C_FALSE=0;
#constants and functions for return value checking ##! @defgroup patience_return_values Return values used in patience.sh. ##! @brief List of return values used in this script. ##! @{ #error values should always be larger than 60 as 0-60 #are valid cards. ##! Return value for success. ##! @todo Shouldn´t this be 0?? if not add note. declare -r -i C_SUCCESS=255; ##! Indicates the selected column to take a card from is emtpy. declare -r -i C_EMPTY_COLUMN=254; ##! Indicates the given move is not a valid move. declare -r -i C_MOVE_INVALID=253; ##! Indicates the card asked for is not available for a move. declare -r -i C_CARD_NOT_FOUND=252; ##! Indicates the given column does not exist. declare -r -i C_COLUMN_NOT_EXISTS=251; ##! Indicates the argument given does not exist. declare -r -i C_INVALID_ARGUMENT=250; ##! Indicates the card given is empty. declare -r -i C_NULL_CARD=249; ##! The request to turn a blind card cannot be fulfilled as there are no blind cards. declare -r -i C_NO_BLINDS=248; ##! The card stack is too small to give the requested cards. declare -r -i C_NOT_ENOUGH_CARDS=247; ##! The given name is not a valid card name. declare -r -i C_INCORRECT_CARD_NAME=246; ##! The size of the terminal is not large enough to show the patience board. declare -r -i C_TERMINAL_SIZE_ERROR=245; ##! Returned prematurely for debug purposes. declare -r -i C_DEBUG_STOP=70;
##! @brief Prints a descriptive error message based on the given error code. ##! @param $arg1 The error code to give the description for. function printErrorDescription { local -i errorCode=$1;
case $errorCode in
$C_SUCCESS ) printf"Successful";;
$C_EMPTY_COLUMN ) printf"There are no cards in the given column";;
$C_MOVE_INVALID ) printf"Move is not allowed";; #why should be output by the function returning this value
$C_CARD_NOT_FOUND ) printf"The requested card cannot be found";;
$C_COLUMN_NOT_EXISTS ) printf"The given column does not exist";;
$C_INVALID_ARGUMENT ) printf"Has an invalid argument";;
$C_NULL_CARD ) printf"The card given is empty";;
$C_NO_BLINDS ) printf"There are no blind cards in this column";;
$C_DEBUG_STOP ) printf"Stopped execution for debug purposes";;
$C_NOT_ENOUGH_CARDS ) printf"Not enough cards on stack to remove requested amount";;
$C_INCORRECT_CARD_NAME ) printf"The supplied string is not a card name";;
* ) printf"Unknown error: %d" $errorCode;;
esac;
} ##! @}
### Some global vars and functions for formatting ### ##! Indicates if terminal colors should be used. useColoredTerminal=$C_TRUE ##! @brief Sets the terminal text color to black. function terminalBlackForeground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[30m"; fi;} ##! @brief Sets the terminal text color to red. function terminalRedForeground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[31m"; fi;} ##! @brief Sets the terminal text color to green. function terminalGreenForeground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[32m"; fi;} ##! @brief Sets the terminal text color to brown. function terminalBrownForeground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[33m"; fi;} ##! @brief Sets the terminal text color to blue. function terminalBlueForeground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[34m"; fi;} ##! @brief Sets the terminal text color to purple. function terminalPurpleForeground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[35m"; fi;} ##! @brief Sets the terminal text color to cyan. function terminalCyanForeground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[36m"; fi;} ##! @brief Sets the terminal text color to gray. function terminalGreyForeground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[37m"; fi;}
##! @brief Sets the terminal background color to black. function terminalBlackBackground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[40m"; fi;} ##! @brief Sets the terminal background color to red. function terminalRedBackground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[41m"; fi;} ##! @brief Sets the terminal background color to green. function terminalGreenBackground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[42m"; fi;} ##! @brief Sets the terminal background color to brown. function terminalBrownBackground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[43m"; fi;} ##! @brief Sets the terminal background color to blue. function terminalBlueBackground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[44m"; fi;} ##! @brief Sets the terminal background color to purple. function terminalPurpleBackground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[45m"; fi;} ##! @brief Sets the terminal background color to cyan. function terminalCyanBackground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[46m"; fi;} ##! @brief Sets the terminal background color to gray. function terminalGreyBackground { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[47m"; fi;}
##! @brief Sets the terminal to print bold text. function terminalBold { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[1m"; fi;} ##! @brief Sets the terminal to print underlined text. function terminalUnderline { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[4m"; fi;} ##! @brief Sets the terminal to reset the colors to the default. function terminalClearColors { if [ $useColoredTerminal -eq $C_TRUE ]; thenprintf"\e[m"; fi;} ##! @brief Sets the background of the board patience is played on. function setBoardBackground { terminalGreenBackground;}
### Debug functions ### ##! Print the cards in the deck for debug purposes. function debugPrintDeck { local -i i=0; printf"closed:\n" while [ $i -lt ${#deck[@]} ]; do
printCard ${deck[$i]}; i=$((i+1)); done i=0; printf"\nfront:\n" while [ $i -lt ${#deckFront[@]} ]; do
printCard ${deckFront[$i]}; i=$((i+1)); done i=0; printf"\nopen:\n" while [ $i -lt ${#deckOpen[@]} ]; do
printCard ${deckOpen[$i]}; i=$((i+1)); done printf"\n";
}
### deck manipulation functions ### #The deck is split in three parts, the closed stack (closed), the open cards for the currrent hand (front) # and the open cards from previous hands (open).
##! Remove cards from the open part of the deck at given index and fill the gap. ##! @param $arg1 Index of the card to be removed. function removeFromDeckOpen { local -i cardIndex=$1; local -A cards=${#deckOpen[@]}; cards=$((cards-1)); local -i i=0; local -i j=0; while [ $i -lt $cards ]; do if [ $i -eq $cardIndex ]; then j=$((j+1)); fi deckOpen[$i]=${deckOpen[$j]}; i=$((i+1)); j=$((j+1)); done unset deckOpen[$cards];
}
##! @brief Remove cards from the front of the deck. ##! @param $arg1 Index of the card to be removed. ##! @param $arg2 should it backfill from deckOpen. ##! @returns Error code according to @ref patience_return_values. ##! ##! Remove cards from the open part of the deck at given index ##! and fill the gap. If backfill is set take one card from the ##! open hand of the deck and move it to the front. function removeFromDeckFront { local -i cardIndex=$1; cardIndex=$((cardIndex)); if [ $# -gt 1 ]; then local -i -r backfillIfEmpty=$2; else local -i -r backfillIfEmpty=$C_FALSE; fi local -A cards=${#deckFront[@]} cards=$((cards-1)) local -i i=0; local -i j=0; while [ $i -lt $cards ]; do if [ $i -eq $cardIndex ]; then j=$((j+1)) fi deckFront[$i]=${deckFront[$j]} i=$((i+1)) j=$((j+1)) done unset deckFront[$cards] if [[ ${#deckFront[@]} -eq 0 && $backfillIfEmpty -eq $C_TRUE ]]; then if [ ${#deckOpen[@]} -ne 0 ]; then deckFront[0]=${deckOpen[$((${#deckOpen[@]}-1))]}; unset deckOpen[$((${#deckOpen[@]}-1))] fi fi return $C_SUCCESS;
}
##! Remove cards from the closed part of the deck at given index and fill the gap. ##! @param $arg1 index of the card to be removed. function removeFromDeck { local -i cardIndex=$1 local -A cards=${#deck[@]} cards=$((cards-1)) local -i i=0; local -i j=0; while [ $i -lt $cards ]; do if [ $i -eq $cardIndex ]; then j=$((j+1)) fi deck[$i]=${deck[$j]} i=$((i+1)) j=$((j+1)) done unset deck[$cards]
}
##! Reindex the deck, i.e. remove gaps in array. function reindexDeck { local -i i=0; local -i j=0; while [ $j -lt ${#deck[@]} ]; do if [ "${deck[$i]}"=="" ]; then while [ "${deck[$j]}"=="" ]; do j=$((j+1)); done fi deck[$i]=${deck[$j]}; i=$((i+1)); j=$((j+1)); done while [ $i -lt $j ]; do unset deck[$i]; i=$((i+1)); done
}
##! Move the cards in the deck to a random new position. function shuffleDeck { local -A shuffledDeck while [ ${#deck[@]} -gt 0 ]; do local -i index=$(($RANDOM%${#deck[@]})) if [ "${deck[$index]}"=="" ]; then continue; fi shuffledDeck[${#shuffledDeck[@]}]=${deck[$index]}; unset deck[$index];
reindexDeck; done local -i i=0; while [ $i -lt ${#shuffledDeck[@]} ]; do deck[$i]=${shuffledDeck[$i]}; i=$((i+1)); done
}
##! Fill the deck with all the cards. function populateDeck { local -i i=0 while [ $i -lt 52 ]; do deck[$i]=$i; i=$((i+1)) done
}
##! Move cards from the front to the open deck and then nFront from the closed to the front. ##! @param $arg1 how many cards should be in a hand. ##! @post Sets the global variable nDeckIterations. function rotateDeck { local -i -r nFront=$1; #move front to open local -i i=1; local -A cards=${#deckFront[@]}; while [ $i -le $cards ]; do deckOpen[${#deckOpen[@]}]=${deckFront[$((cards-i))]};
removeFromDeckFront $cards; i=$((i+1)); done
#check if we need to flip the deck if [ ${#deck[@]} -eq 0 ]; then #move from open to deck while [ ${#deckOpen[@]} -ne 0 ]; do deck[${#deck[@]}]=${deckOpen[0]};
removeFromDeckOpen 0; done fi
#deal a new set i=$nFront; if [ $nFront -gt ${#deck[@]} ]; then i=${#deck[@]}; fi while [ $i -gt 0 ]; do i=$((i-1)); deckFront[$i]=${deck[0]};
removeFromDeck 0; done #check if we need to flip the deck in the next move if [ ${#deck[@]} -eq 0 ]; then nDeckIterations=$((nDeckIterations+1)); fi;
}
### User interface outputs ###
##! Print the suit stacks as a string, if empty print the suit symbol. function printSuitsStack { localsymbols=("♠""♥""♣""♦") local -i i=0; while [ $i -lt 4 ]; do if [ $((i%2)) -eq 0 ]; then
terminalBlackForeground else
terminalRedForeground fi if [ ${suitStack[$i]} -eq -1 ]; then printf"%s " ${symbols[$i]} else local -i value=${suitStack[$i]} local -i card=$((value*4+i))
printCard $card; fi i=$((i+1)) done
terminalGreyForeground
}
##! Print the deck, in two parts, first the closed, then the front. ##! @param $arg1 end of deck iterations. function printDeck { local -i endDeckIterations=$1 local -i inDeck=${#deck[@]} #number of cards in the (closed) deck local -i inDeckFront=${#deckFront[@]} #number of cards in the front deck if [ $endDeckIterations -eq $C_TRUE ]; then printf" ⃠ "; elif [ $inDeck -gt 0 ]; then
printCard 0 0; else printf"╳ "; fi
local -i i=0; while [ $i -lt $inDeckFront ]; do
getCardColor ${deckFront[$i]} if [ $? -eq 0 ]; then
terminalBlackForeground else
terminalRedForeground fi
printCard ${deckFront[$i]} i=$((i+1)) done
terminalGreyForeground #fill the space set for the deck (5 is the space used by the highest #nOpen value allowed while [ $i -lt 5 ]; do printf" "; i=$((i+1)); done
}
##! Print the board. ##! @param $arg1 (optional) has player won the game. ##! @param $arg2 (optional) end of deck iterations. function printBoard {
terminalBlueBackground;
clear; if [ $# -lt 1 ]; thenlocal -i isWon=$C_FALSE; elselocal -i isWon=$1; fi if [ $# -lt 2 ]; thenlocal -i endDeckIterations=$C_FALSE; elselocal -i endDeckIterations=$2; fi
terminalBlueBackground;
terminalGreyForeground; printf"%s""$marginTop" printf"%s Patience\n""$marginLeft";
printBoardContent "┏━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━┓";
printBoardContent "┃%s │%s┃""$(printDeck $endDeckIterations)""$(printSuitsStack)"
printBoardContent "┃ ╰────────┨";
printBoardContent "┃ ⓪ ① ② ③ ④ ⑤ ⑥ ┃";
local -i r=0; while [ $r -lt 19 ]; do if [ $isWon -eq $C_TRUE ]; then if [[ 2 -le $r && $r -le 14 ]]; then if [ $r -eq 2 ]; then line=" __ ______ _ _ "; elif [ $r -eq 3 ]; then line=" \\ \\ / / __ \\| | | | "; elif [ $r -eq 4 ]; then line=" \\ \\_/ / | | | | | | "; elif [ $r -eq 5 ]; then line=" \\ /| | | | | | | "; elif [ $r -eq 6 ]; then line=" | | | |__| | |__| | "; elif [ $r -eq 7 ]; then line=" |_| \\____/ \\____/ "; elif [ $r -eq 8 ]; then line=" __ ______ _ _ "; elif [ $r -eq 9 ]; then line=" \\ \\ / / __ \\| \\ | | "; elif [ $r -eq 10 ]; then line=" \\ \\ /\\ / / | | | \\| | "; elif [ $r -eq 11 ]; then line=" \\ \\/ \\/ /| | | | . \` | "; elif [ $r -eq 12 ]; then line=" \\ /\\ / | |__| | |\\ | "; elif [ $r -eq 13 ]; then line=" \\/ \\/ \\____/|_| \\_| "; elif [ $r -eq 14 ]; then line=" "; fi
printBoardContent "┃%s%s%s%s%s┃""$(terminalBrownBackground)""$(terminalGreenForeground)""$line""$(setBoardBackground)""$(terminalGreyForeground)"; r=$((r+1));continue; fi fi #build the actual board local line=""; local -i c=0; while [ $c -lt 7 ]; do
line+="$(terminalGreyForeground)"; if [ $r -lt ${blindStacks[$c,0]} ]; then
line+="$(printCard ${blindStacks[$c,$((r+1))]} 0)"; elif [[ $r -eq 0 && ${openStacks[$c,0]} -eq -1 ]]; then line+=$(terminalGreyForeground;printf"🃟 "); elif [ $r -lt $((${openStacks[$c,0]}+${blindStacks[$c,0]})) ]; then
getCardColor ${openStacks[$c,$((r-${blindStacks[$c,0]}+1))]} local -i color=$? if [ $color -eq 0 ]; then
line+="$(terminalBlackForeground)"; else
line+="$(terminalRedForeground)"; fi
line+="$(printCard ${openStacks[$c,$((r-${blindStacks[$c,0]}+1))]} )"; else
line+=" "; fi c=$((c+1)); done line+=$(terminalGreyForeground);
printBoardContent "┃ %s ┃""$line"; r=$((r+1)); done
##! @brief Helper function to print the board. It handles the coloring of the board. ##! arguments same as args to printf. function printBoardContent { printf"%s""$marginLeft";setBoardBackground; printf"$@";terminalBlueBackground;printf "\n";
}
##! @brief Helper function to print the board. It handles the coloring of the board. function printScore { if [ $score -ge 0 ]; then printf" %03d" $score; else printf"%s-%03d%s""$(terminalRedForeground)" $((-1*score)) "$(terminalGreyForeground)"; fi;
}
##! @brief Print the help function and handles help navigation through the help. ##! @param $arg1 (optional) pagenumber to show. ##! @returns Error code according to @ref patience_return_values. function printHelp {
terminalBlueBackground;
clear; if [ $# -eq 0 ]; then local -i page=1 else local -i page=$1 fi local -r -i nPages=4;
terminalGreyForeground
calculateLeftMargin $width 65; printf"\n";
printBoardContent "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓";
case $page in
1) printBoardContent "┃ Hi, this game uses text input for the moves. These commands ┃";
printBoardContent "┃ are processed during typing. There is no need to press the ┃";
printBoardContent "┃ enter key afterwards. ┃";
printBoardContent "┃ ┃";
printBoardContent "┃ Cards are are selected by a shorthand: first give a number ┃";
printBoardContent "┃ or letter for the value, then a letter to select the suit ┃";
printBoardContent "┃ of the card you want. ┃";
printBoardContent "┃ ┃";
printBoardContent "┃ Have fun playing, ┃";
printBoardContent "┃ Pieter van der Star ┃";
printBoardContent "┃ ┃";
printBoardContent "┃ %sCommands%s ┃""$(terminalBold)$(terminalUnderline)""$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)";
printBoardContent "┃ To move the cards the following commands are available: ┃";
printBoardContent "┃ \"h\" enters this help ┃"; printBoardContent "┃ \"e\" exits the game (doesn't work in this help) ┃"; printBoardContent "┃ \"<card> <card>\" moves the first card on top of the second ┃"; printBoardContent "┃ \"<card> s\" moves the card to the corresponding ┃"; printBoardContent "┃ suitstack ┃"; printBoardContent "┃ \"<card> c<number>\" moves the card to the corresponding ┃"; printBoardContent "┃ column ┃"; printBoardContent "┃ \"d\" shows new cards from the deck ┃"; printBoardContent "┃ \"r\" if a command is not yet complete, this clears the ┃"; printBoardContent "┃ command ┃"; printBoardContent "┃ \"n\" starts a new game ┃"; printBoardContent "┃ \" \" or [enter] move all possible cards to the suitstacks ┃"; printBoardContent "┃ \"m\" recalculate the margins (use after resizing the terminal) ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; ;; 2) printBoardContent "┃ %sCommand line options%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)"; printBoardContent "┃ -o <number> Number of cards to show when taking from deck ┃"; printBoardContent "┃ -c Do not use colored terminal (Default is ┃"; printBoardContent "┃ colored) ┃"; printBoardContent "┃ -i <number> Number of iterations for the deck (0=no limit) ┃"; printBoardContent "┃ -s Source only, do not run the program. ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ %sCard selection%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)"; printBoardContent "┃ The following table shows the cards and corresponding command:┃"; printBoardContent "┃ ┌──╥──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ ┃"; printBoardContent "┃ │ ║a │2 │3 │4 │5 │6 │7 │8 │9 │10│j │q │k │ ┃"; printBoardContent "┃ ╞══╬══╪══╪══╪══╪══╪══╪══╪══╪══╪══╪══╪══╪══╡ ┃"; printBoardContent "┃ │s ║🂡 │🂢 │🂣 │🂤 │🂥 │🂦 │🂧 │🂨 │🂩 │🂪 │🂫 │🂭 │🂮 │ ┃"; printBoardContent "┃ │h ║🂱 │🂲 │🂳 │🂴 │🂵 │🂶 │🂷 │🂸 │🂹 │🂺 │🂻 │🂽 │🂾 │ ┃"; printBoardContent "┃ │d ║🃁 │🃂 │🃃 │🃄 │🃅 │🃆 │🃇 │🃈 │🃉 │🃊 │🃋 │🃍 │🃎 │ ┃"; printBoardContent "┃ │c ║🃑 │🃒 │🃓 │🃔 │🃕 │🃖 │🃗 │🃘 │🃙 │🃚 │🃛 │🃝 │🃞 │ ┃"; printBoardContent "┃ └──╨──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘ ┃"; printBoardContent "┃ First enter the column, and then the row. i.e. \"as\" = 🂡 ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ %sOther symbols%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)"; printBoardContent "┃ There are two other cards that may appear during gameplay: ┃"; printBoardContent "┃ 🂠 which is just the back of a card. ┃"; printBoardContent "┃ 🃟 which signifies an empty place. ┃"; printBoardContent "┃ ╳ which signifies the end of the deck has been reached. ┃"; printBoardContent "┃ It will restart with the deck unless a limit has been set, ┃"; printBoardContent "┃ in which case ⃠ will be shown. ┃"; printBoardContent "┃ ⓝ column numbers, usefull when using he c<number> command ┃"; printBoardContent "┃ ┃"; ;; 3) printBoardContent "┃ %sExamples%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)"; printBoardContent "┃ 1: To put 🂻 on top of 🃝 the following command needs to be ┃"; printBoardContent "┃ given: \"jh\" followed by \"qc\". The first cards' value is ┃"; printBoardContent "┃ jack, the suit is hearts, the second cards' value ┃"; printBoardContent "┃ is queen and the suit is clubs. ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ 2: To put the 🂱 on the suit stack the following command ┃"; printBoardContent "┃ needs to be given: \"ah\" followed by \"s\". \"s\" indicates the ┃"; printBoardContent "┃ card should be moved to the suitstack. ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ %sScoring%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)"; printBoardContent "┃ At each new game 100 points are substracted from the count. ┃"; printBoardContent "┃ You gain points by removing cards from the deck (1pt/card) ┃"; printBoardContent "┃ And by moving cards to the suitstacks (card value) ┃"; printBoardContent "┃ so moving an ace to the suitstacks give 1 point, while a king ┃"; printBoardContent "┃ will add 13 points to your score ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; ;; 4) printBoardContent "┃ %sLicense%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)"; printBoardContent "┃This program is distributed in the hope that it will be useful,┃"; printBoardContent "┃but WITHOUT ANY WARRANTY; without even the implied warranty of ┃"; printBoardContent "┃MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ┃"; printBoardContent "┃GNU Affero General Public License for more details. ┃"; printBoardContent "┃ ┃"; printBoardContent "┃You should have received a copy of the GNU Affero General ┃"; printBoardContent "┃Public License along with this program. ┃"; printBoardContent "┃ If not, see <https://www.gnu.org/licenses/ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ Feel free to use and modify as long as: ┃"; printBoardContent "┃ - This License stays intact. ┃"; printBoardContent "┃ - You give the original author credit for their work ┃"; printBoardContent "┃ - The modifications are indicated clearly in the changelog ┃"; printBoardContent "┃ - The result must be free, in both monetary sense and ┃"; printBoardContent "┃ personal sense ┃"; printBoardContent "┃ i.e. no account required, no personal data needs to be ┃"; printBoardContent "┃ handed over. ┃"; printBoardContent "┃ - The software is provided as-is. Feature requests or bug ┃"; printBoardContent "┃ reports may be ignored. ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ ┃"; printBoardContent "┃ End of help, press q to quit help ┃"; ;; esac printBoardContent "┃ ┃"; printBoardContent "┃ press n for next page, p for previous or q to quit help %2d/%d ┃" $page 4; printBoardContent "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛"; terminalClearColors; printf "\n"; local char=""; while [[ "$char" != "n" && "$char" != "q" ]]; do read -n 1 char
case $char in "n") if [ $page -ne $nPages ]; then printHelp $((page+1)); else char=""; fi;; "p") if [ $page -ne 1 ]; then printHelp $((page-1)); else char=""; fi;; "q") calculateLeftMargin $width 31; return $C_SUCCESS;;
esac done return $C_SUCCESS;
}
##! Output a card character. ##! @param $arg1 cardnumber of card to print. ##! @param $arg2 (optional) print the card closed. ##! @returns Error code according to @ref patience_return_values. function printCard { localcard=$1 if [ "$card"=="" ]; then printf"X "; return $C_NULL_CARD; fi card=$((card)) if [ $# -ge 2 ]; thenlocalopen=$2; elselocalopen=1; fi if [ $card -eq -1 ]; then printf" " return $C_SUCCESS; fi
local -a cards cards=("🂡""🂱""🃑""🃁"#A "🂢""🂲""🃒""🃂"#2 "🂣""🂳""🃓""🃃"#3 "🂤""🂴""🃔""🃄"#4 "🂥""🂵""🃕""🃅"#5 "🂦""🂶""🃖""🃆"#6 "🂧""🂷""🃗""🃇"#7 "🂨""🂸""🃘""🃈"#8 "🂩""🂹""🃙""🃉"#9 "🂪""🂺""🃚""🃊"#10 "🂫""🂻""🃛""🃋"#J "🂭""🂽""🃝""🃍"#Q "🂮""🂾""🃞""🃎"#K "🂬""🂼""🃜""🃌"#C here for completeness of the unicode deck (for future French version?)
); local cardBack="🂠"; if [ $((open)) -eq 1 ]; then printf"%s " ${cards[$card]}; else printf"%s " $cardBack; fi return $C_SUCCESS;
}
##! Print the credits of the game and perform a short "animation". function printCredits { locali=0; while [ $i -le 4 ]; do
clear; printf"%s""$marginTop"; printf"\n\n\n\n\n\n\n\n"; printf"%s Patience by\n""$marginLeft"; printf"%s Pieter van der Star\n""$marginLeft"; printf"\n";
terminalBrownForeground; printf"%s ""$marginLeft"; if [ $i -gt 0 ]; thenprintf"\e[30m♠ ";elseprintf"\e[31m♤ "; fi; if [ $i -gt 1 ]; thenprintf"\e[31m♥ ";elseprintf"\e[30m♡ "; fi; if [ $i -gt 2 ]; thenprintf"\e[30m♣ ";elseprintf"\e[31m♧ "; fi; if [ $i -gt 3 ]; thenprintf"\e[31m♦ ";elseprintf"\e[30m♢ "; fi; printf"\n";
terminalGreenForeground; local -i j=0;
##! Check if the game is won. ##! @returns Boolean true if game won, false if not (yet). function isGameWon { locali=0; while [ $i -lt 4 ]; do if [ ${suitStack[$i]} -ne 12 ]; then return $C_FALSE; fi i=$((i+1)); done; return $C_TRUE;
}
##! Deal the blind cards on the board. function dealBlinds { localc=0 # printf "Took: "; while [ $c -lt 7 ]; do blindStacks[$c,0]=$((c+1)) localr=1; while [ $r -le $((c+1)) ]; do blindStacks[$c,$r]=${deck[0]} # printCard ${deck[0]};
removeFromDeck 0 r=$((r+1)); done c=$((c+1)) done # printf "\n"
}
##! Fill any open columns on the board. ##! @returns Error code according to @ref patience_return_values. function fillOpen { localc=0 while [ $c -lt 7 ]; do localnOpen=${openStacks[$c,0]} if [ $nOpen -eq 0 ]; then ##printf "turnBlind c: %d\n" $c
turnBlind $c localret=$? if [[ $ret -ne $C_SUCCESS && $ret -ne $C_NO_BLINDS ]]; then return $ret; fi fi c=$((c+1)) done return $ret;
}
##! Turn a blind card from a given column. ##! @param $arg1 column to turn the frontmost blind card. ##! @returns Error code according to @ref patience_return_values. function turnBlind { localc=$1 localnRows=${blindStacks[$c,0]} if [ $nRows -eq 0 ]; then return $C_NO_BLINDS; fi openStacks[$c,0]=1 openStacks[$c,1]=${blindStacks[$c,$nRows]} unset blindStacks[$c,$nRows] blindStacks[$c,0]=$((nRows-1)) return $C_SUCCESS;
}
##! Initialise the suitstacks function initSuitStack { suitStack[0]=-1; suitStack[1]=-1; suitStack[2]=-1; suitStack[3]=-1;
}
##! Get the card value at the bottom of the given column ##! @param $arg1 column ##! @returns Error code according to @ref patience_return_values. function getBottomCard { localc=$1 if [[ $c -lt 0 || $c -gt 6 ]]; then printf"getBottomCard: Incorrect column given %d" $c 1>&2; return $C_COLUMN_NOT_EXISTS; elif [ ${openStacks[$c,0]} -eq 0 ]; then return $C_EMPTY_COLUMN; else return ${openStacks[$c,${openStacks[$c,0]}]}; fi return $C_SUCCESS;
}
##! Get the card value at the top of the given column. ##! @param $arg1 column. ##! @returns Error code according to @ref patience_return_values. function getTopCard { localc=$1 if [[ $c -lt 0 || $c -gt 6 ]]; then printf"getTopCard: Incorrect column given %d" $c 1>&2; return $C_COLUMN_NOT_EXISTS; elif [ ${openStacks[$c,0]} -eq 0 ]; then return $C_EMPTY_COLUMN; else return ${openStacks[$c,1]}; fi return $C_SUCCESS;
}
##! Move a card from the deck to the corresponding suitstack. ##! @returns Error code according to @ref patience_return_values. function moveDeckToSuitStack { localcard=${deckFront[0]}
getCardSuit $card localsuit=$?
getCardValue $card localvalue=$?
localstackValue=${suitStack[$suit]} if [ $((stackValue+1)) -ne $value ]; then
user_msg1="Invalid move, cannot put %d" $value
user_msg2="on %d." $stackValue printf"TEST";
sleep 10; return $C_MOVE_INVALID; else suitStack[$suit]=$value score=$((score+value+1)); printf"score: %d\n" $score fi
removeFromDeckFront 0 1 return $?;
}
##! Remove a card from one of the open stacks on the board. ##! @param $arg1 column index. ##! @param $arg2 number of cards. ##! @returns Error code according to @ref patience_return_values. function removeFromOpenStacks { localc=$1; localn=$2; localrows=${openStacks[$c,0]}; if [ $n -gt $rows ]; then printf"ERROR not enough cards on stack to remove %d cards" $n 1>&2; return $C_NOT_ENOUGH_CARDS; fi localr=$n; while [ $r -gt 1 ]; do unset openStacks[$c,$r] r=$((r-1)) done openStacks[$c,0]=$((rows-n)); return $C_SUCCESS;
}
##! Remove a card from the suitstack. ##! @param $arg1 the suit to remove the card from. ##! @returns Error code according to @ref patience_return_values. function removeFromSuit { locals=$1; if [ ${suitStack[$s]} -lt 0 ]; then return $C_EMTPY_SUITSTACK; #no cards on suitstack fi suitStack[$s]=$((${suitStack[$s]}-1)) return $C_SUCCESS;
}
##! Initialise the open stacks on the board. function initOpen { localc=0 while [ $c -lt 7 ]; do openStacks[$c,0]=0 c=$((c+1)) done
}
##! Search the deck, board and suitstacks for the given card. ##! @param $arg1 Cardnumber of the card to find. ##! @post updates inherited: ##! $fromFromDeck to indicate if the card is the first card on the deck. ##! $fromFromSuit to indicate if the card is the top of the suit stack. ##! $inColumn to indicate which column the card is in. ##! @returns Error code according to @ref patience_return_values. function findCard { localcard=$1; fromDeck=$C_FALSE; fromSuit=$C_FALSE; #first look on the deck if [[ ${#deckFront[@]} -gt 0 && ${deckFront[0]} -eq $card ]]; then fromDeck=$C_TRUE; return $C_SUCCESS; fi #then look on the suitstacks
getCardValue $card; localvalue=$?;
getCardSuit $card; localsuit=$?; if [ ${suitStack[$suit]} -eq $value ]; then fromSuit=$C_TRUE; return $C_SUCCESS; fi #then look on the board inColumn=0; while [ $inColumn -lt 7 ]; do isBottomCard=$C_TRUE inRow=${openStacks[$inColumn,0]}; while [ $inRow -gt 0 ]; do localcheckingCard=${openStacks[$inColumn,$inRow]}; if [ $checkingCard -eq $card ]; then return $C_SUCCESS; fi isBottomCard=$C_FALSE inRow=$((inRow-1)) done inColumn=$((inColumn+1)) done return $C_CARD_NOT_FOUND;
}
##! Get the value of the card based on the cardnumber. ##! @param $arg1 Cardnumber. ##! @returns Card value. function getCardValue { card=$1 return $((card/4))
}
##! Get the suit of the card based on the cardnumber. ##! @param $arg1 Cardnumber. ##! @returns Number indicating card suit. function getCardSuit { card=$1 return $((card%4));
}
##! Get the color of the card based on the cardnumber. ##! @param $arg1 Cardnumber. ##! @returns Number indicating card value. function getCardColor { card=$1 return $((card%2));
}
##! Check if the move is a valid move on the board. ##! @param $arg1 Cardnumber of card to move. ##! @param $arg2 cardnumber of card to move the arg1 card on top of. ##! @returns Error code according to @ref patience_return_values. function isValidBoardMove { localfromCard=$1; localtoCard=$2;
if [ $fromValue -eq 12 ]; then#a king is an exception to the rule if [ $toCard -ne $C_EMPTY_COLUMN ]; then#and may only be moved to non-open column
user_msg1="A king can only be moved";
user_msg2="to an empty column.";return $C_MOVE_INVALID; fi return $C_SUCCESS; elif [ $fromColor -eq $toColor ]; then
user_msg1="A king can only be moved";
user_msg2="on op of a card with another color";return $C_MOVE_INVALID; elif [ $fromValue -ne $((toValue-1)) ]; then
user_msg1="the cards can only stack onto";
user_msg2="another in value order K,Q,J,10...2,1,A";return $C_MOVE_INVALID; printf"Invalid move: \n";return $C_MOVE_INVALID; fi return $C_SUCCESS;
}
##! Check if the move is a valid move to the suitstacks. ##! @param $arg1 cardnumber of card to move. ##! @returns Error code according to @ref patience_return_values. function isValidSuitMove { localcard=$1;
getCardSuit $card localsuit=$? localtopValue=${suitStack[$suit]} if [ $((topValue+1)) -ne $value ]; then
user_msg1="the cards can only stack onto";
user_msg2="another in the order A,1,2...10,J,Q,K";return $C_MOVE_INVALID; fi return $C_SUCCESS;
}
##! Move card from A to B. ##! @param $arg1 card to move. ##! @param $arg2 location to move to. ##! @returns Error code according to @ref patience_return_values. function moveCards { #if [ $# -ge 3 ]; then # printf "ENABLING DEBUG" # set -x; #fi local -i fromCard=$1;
findCard $fromCard; if [ $? -ne $C_SUCCESS ]; then
user_msg1="Card(s) to move not found"; set +x; return $C_CARD_NOT_FOUND; fi localfromColumn=$inColumn; localfromRow=$inRow;
localto=$2
case $to in "c"[0-6]) localtoColumn=${to:1:1};
getBottomCard $toColumn localtoCard=$?
#is it a valid move?
isValidBoardMove $fromCard $toCard if [ $? -ne $C_SUCCESS ]; then set +x; return $C_MOVE_INVALID; fi
if [ $fromDeck -eq $C_TRUE ]; then #update the rowcount openStacks[$toColumn,0]=$((${openStacks[$toColumn,0]}+1)); #copy the card openStacks[$toColumn,${openStacks[$toColumn,0]}]=${deckFront[0]}; #remove from old position
removeFromDeckFront 0 1; score=$((score+1)); elif [ $fromSuit -eq $C_TRUE ]; then
getCardSuit $fromCard; localsuit=$?; #update the rowcount openStacks[$toColumn,0]=$((${openStacks[$toColumn,0]}+1)); #copy the card openStacks[$toColumn,${openStacks[$toColumn,0]}]=$fromCard; #remove from old position
removeFromSuit $suit; else #to same location is a valid "move", but nothing needs to be done if [ $toColumn -eq $fromColumn ]; then set +x; return $C_SUCCESS; fi localr=$fromRow; while [ $r -le ${openStacks[$fromColumn,0]} ]; do #update the rowcount openStacks[$toColumn,0]=$((${openStacks[$toColumn,0]}+1)); #copy the card openStacks[$toColumn,${openStacks[$toColumn,0]}]=${openStacks[$fromColumn,$r]}; #remove from old position unset openStacks[$fromColumn,$r]; r=$((r+1)) done #update the rowcounts openStacks[$fromColumn,0]=$((fromRow-1)); fi
;; "s") #is the destination on the suitstacks? if [ $fromSuit -eq $C_TRUE ]; then
user_msg1="Card is already on the";
user_msg2="suitstacks"; set +x; return $C_SUCCESS; fi #is the card the bottom card if [[ $fromDeck -eq $C_FALSE && $isBottomCard -eq $C_FALSE ]]; then
user_msg1="only the bottom card, or the one from";
user_msg2="the deck can be put on the suitstacks"; set +x; return $C_MOVE_INVALID; fi #determine the value and suit
getCardValue $fromCard localvalue=$?
getCardSuit $fromCard localsuit=$?
isValidSuitMove $fromCard; if [ $? -ne $C_SUCCESS ]; then set +x; return $C_MOVE_INVALID; fi
#move the card suitStack[$suit]=$value if [ $fromDeck -eq $C_TRUE ]; then ##printf "fromdeck\n";
removeFromDeckFront 0 1; score=$((score+1)); #local ret=$?; #printErrorDescription $ret; else ##printf "not fromdeck\n";
removeFromOpenStacks $inColumn 1; #local ret=$?; #printErrorDescription $ret; fi score=$((score+value+1)); 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
set +x; return $C_SUCCESS;
;;
[0-9]|[0-9][0-9]) toCard=$2; toCard=$((toCard));
findCard $toCard; if [ $? -ne $C_SUCCESS ]; then user_msg1="Card(s) to move to not found";return $C_CARD_NOT_FOUND;fi
#to same location is a valid "move", but nothing needs to be done #but that is only the case when from is not on the deck or suitstacks if [[ $fromFromDeck -ne $C_TRUE && $fromFromSuit -ne $C_TRUE ]]; then if [ $inColumn -eq $fromColumn ]; then
user_msg1="to same location"; return $C_SUCCESS; fi fi
moveCards $fromCard "c$inColumn";
;;
*) user_msg1="ERROR: unknown destination \"$2\"";return $C_MOVE_INVALID;
esac set +x return $C_SUCCESS;
}
##! Try to put all available cards on the suitstacks. function autoMoveToSuitstacks { localdoLoop=$C_TRUE; while [ $doLoop -eq $C_TRUE ]; do doLoop=$C_FALSE; #set back to true if a card was moved so we can run again #check the deck ##printf "AM: checking deck\n"; if [ ${#deckFront[@]} -ne 0 ]; then
moveCards ${deckFront[0]} "s"; ret=$?; if [ $ret -eq $C_SUCCESS ]; then ##printf "Moved %d (deck) " $card;printCard $card;printf "\n"; doLoop=$C_TRUE;
sleep 1; fi; fi; #iterate over all columns localc=0; while [ $c -lt 7 ]; do ##printf "AM: checking column %d\n" $c #try moving the bottom card to the stack
getBottomCard $c; card=$?; if [[ $card -ne $C_NULL_CARD && $card -ne $C_EMPTY_COLUMN ]]; then
moveCards $card "s" 0; ret=$? if [ $ret -eq $C_SUCCESS ]; then ##printf "Moved %d " $card;printCard $card;printf "\n"; doLoop=$C_TRUE;
sleep 1; fi; fi
c=$((c+1)); done;
fillOpen; if [ $? -ne $C_NO_BLINDS ]; then
user_msg1="Automove to suitstacks"
user_msg2="in progress"; fi
done;
user_msg1="Automove to suitstacks"
user_msg2="finished"; ##printf "AM: return\n";
}
##! Convert a human card name to a cardnumber. ##! @param $arg1 abbrieviated human name for the card. ##! @returns Card number. function nameToCard { localname=$1 locallength=${#name} localsuit=${name:$((length-1)):1} localvalue=${name:0:$((length-1))}
case $suit in "s") suit=0;; "h") suit=1;; "c") suit=2;; "d") suit=3;;
*) user_msg1="ERROR, Invalid suit %s" $suit; return $C_INCORRECT_CARD_NAME;;
esac
case $value in "k") value=12;; "q") value=11;; "j") value=10;; "10"|[1-9]) value=$((value-1));; "a") value=0;;
*) user_msg1="ERROR, Invalid value %s" $value;return $C_INCORRECT_CARD_NAME;;
esac localcard=$((value*4+suit)); return $card;
}
##! Convert cardnumber to a human readable card name. ##! @param $arg1 card number. ##! @returns Error code according to @ref patience_return_values. ##! ##! Prints card name, for use in $(cardToName n) function cardToName { local -i card=$1 local -i value;
getCardValue $card value=$?; value=$((value+1));
case $value in
13) printf"k";;
12) printf"q";;
11) printf"j";;
10|[1-9]) printf"%d" $value;;
0) printf"a";;
*) return $C_INVALID_ARGUMENT;;
esac local -i suit;
getCardSuit $card; suit=$?;
case $suit in
0) printf"s";;
1) printf"h";;
2) printf"c";;
3) printf"d";;
*) return $C_INVALID_ARGUMENT;;
esac return $C_SUCCESS;
}
##! Calculate the margin so the board ends up in the middle of the terminal. ##! @param $arg1 terminal width. ##! @param $arg2 width of the content (board is different to help) ##! @post Sets global marginLeft. function calculateLeftMargin { local -i terminalWidth=$1; local -i contentWidth=$2; local -i margins=$((terminalWidth-contentWidth)); local -i margin=$((margins/2));
marginLeft=""; while [ $margin -gt 0 ]; do
marginLeft+=" "; margin=$((margin-1)); done
}
##! Calculate the margin so the board ends up in the middle of the terminal. ##! @param $arg1 terminal height. ##! @param $arg2 height of the content (board is different to help) ##! @post Sets global marginTop. function calculateTopMargin { local -i terminalHeight=$1; local -i contentHeight=$2; local -i margins=$((terminalHeight-contentHeight)); local -i margin=$((margins/2));
marginTop=""; while [ $margin -gt 0 ]; do #sadly \n does not work, so then use a hard return in the string
marginTop+=""; margin=$((margin-1)); done
}
#global vars #declare -A board ##! The deck of cards. declare -A deck ##! The blind cards of each column. declare -A blindStacks ##! The open cards of each column. declare -A openStacks ##! The open cards on the unplayed deck. declare -A deckOpen ##! The card on the front of the deck. declare -A deckFront ##! Stacks of cards having been sorted by suit. declare -A suitStack ##! Text for the user message, first row. declare user_msg1="Welcome" ##! Text for the user message, second row. declare user_msg2=" press h for help" ##! How much margin to keep to the left of the terminal to center the board. declare marginLeft=""; ##! How much margin to keep to the top of the terminal to center the board. declare marginTop=""; #declare -i blindRows=0; ##! How many times has the deck been rotated (viewed all cards and started over). declare -i nDeckIterations=0; ##! Current score. declare -i score=0;
##! Cleanup remnants of the old game before starting a new. function cleanup { while [ ${#deckOpen[@]} -gt 0 ]; do unset deckOpen[$((${#deckOpen[@]}-1))]; done while [ ${#deckFront} -gt 0 ]; do unset deckFront[$((${#deckFront[@]}-1))]; done
}
##! Ask user for confirmation. ##! @param $arg1 Description of the action to perform. ##! @returns Bool, user confirmed. function confirmAction {
clear;
printBoard; printf"%sAre you sure you want to\n""$marginLeft"; printf"%s%24s? (y/n)\n""$marginLeft""$1"; local char=""; while [[ "$char" != "n" && "$char" != "q" ]]; do read -n 1 char
case $char in "y") return $C_TRUE;; "n") return $C_FALSE;;
esac done
} ##! Check if the terminal has the correct size to display the board. ##! @param $arg1 Width of the terminal (in characters). ##! @param $arg2 Width of the terminal (in characters). function checkTerminalSize { local -i width=$1; local -i height=$2; if [[ $width -lt 65 || $height -lt 35 ]]; then printf"Please adjust the terminal to at least 65x35\n"; printf"The game requires at least 65 columns and has a height of 35 lines\n"; printf"The terminal currently has %d columns and has a height of %d lines\n" $width $height; return $C_TERMINAL_SIZE_ERROR; fi
}
##! Main function. function main { #before anything else, check the terminal width and height #>= the minimum required by the game local -i width=$(tput cols); local -i height=$(tput lines);
checkTerminalSize $width $height; if [ $? -eq $C_TERMINAL_SIZE_ERROR ]; then return 1; fi
#main variables that can be overwritten by command line arguments local -i nOpen=3; #3 is default, can be set to anything between 1 and 5; local -i maxDeckIterations=0;#no limit by default, can be any value between 0 (no limit) and 255 whilegetopts"ho:ci:s" arg; do
case $arg in
h) printf"For help press h after start\n";sleep 5;;
o) nOpen=${OPTARG};;
c) useColoredTerminal=0;;
i) maxDeckIterations=${OPTARG};;
s) return $C_SUCCESS;;
*) printf"";exit 0;;
esac done
#validate user input if [[ 0 -gt $nOpen || $nOpen -gt 5 ]]; then printf"Invalid value for argumment o\n"; printf"\tnumber of open cards must be\n"; printf"0<number of open cards ≤ 5.\n"; printf"Value given: %d\n" $nOpen; return $C_INVALID_ARGUMENT; fi
if [[ 0 -gt $maxDeckIterations || $maxDeckIterations -gt 255 ]]; then printf"Invalid value for argumment i\n"; printf"\tnumber of iterations must be\n"; printf"0<number of iterations ≤ 255.\n"; printf"Value given: %d\n" $maxDeckIterations; return $C_INVALID_ARGUMENT; fi
#handle user input in the main loop local state="" local -i endGame=$C_FALSE local -i gotCommand=$C_TRUE local -i won=$C_FALSE while [ $endGame -eq $C_FALSE ]; do if [ $gotCommand -eq $C_TRUE ]; then
fillOpen;
printBoard $won $endDeckIterations fi gotCommand=$C_FALSE
state="" local -i cardF=-1; local -i cardT=-1; printf"%sCommand: %s""$marginLeft""$state" while [ $gotCommand -eq $C_FALSE ]; do read -n 1 char
state="$state$char" shopt -s extglob # turn on extended globbing
case "$state" in
[a]|[0-9]|"10"|[jqk] ) ;;
[a0-9jqk][shcd]|"10"[shcd]) if [ $won -eq $C_TRUE ]; then
state=""; elif [ $cardF -lt 0 ]; then
nameToCard $state; cardF=$?; printf" ";
state="" else
nameToCard $state; cardT=$?; gotCommand=$C_TRUE;
moveCards $cardF $cardT fi
;; "c" ) if [ $won -eq $C_TRUE ]; then
state=""; fi
;; "c"[0-6] ) if [ $won -eq $C_TRUE ]; then
state=""; elif [ $cardF -lt 0 ]; then
getTopCard ${state:1:1} cardF=$?; printf" ";
state="" if [[ $cardF -eq $C_COLUMN_NOT_EXISTS || $cardF -eq $C_EMPTY_COLUMN ]]; then
user_msg1="Column is empty or is";
user_msg2="not a valid column"; fi else
moveCards $cardF $state gotCommand=$C_TRUE; fi
;; "s" ) if [ $won -eq $C_TRUE ]; then
state=""; elif [ $cardF -lt 0 ]; then
user_msg1="Can only use s when already";
user_msg2="selected a card to move"; gotCommand=$C_TRUE;
state="" else
moveCards $cardF "s"; gotCommand=$C_TRUE; fi
;; "d" ) if [ $won -eq $C_TRUE ]; then
state=""; else if [ $endDeckIterations -eq $C_FALSE ]; then
rotateDeck $nOpen; fi; if [[ $nDeckIterations -ge $maxDeckIterations && $maxDeckIterations -ne 0 ]]; then endDeckIterations=$C_TRUE; fi; gotCommand=$C_TRUE; fi
;;
*"r" ) user_msg1="resetting command"; cardF=-1;gotCommand=$C_TRUE;; "" ) autoMoveToSuitstacks;printf"\n";gotCommand=$C_TRUE;; "e" ) confirmAction "exit the game"; endGame=$?;gotCommand=$C_TRUE;; "m" ) local -i ok=$C_FALSE; while [ $ok -ne $C_TRUE ]; do width=$(tput cols); height=$(tput lines);
clear;
checkTerminalSize $width $height; if [ $? -ne $C_TERMINAL_SIZE_ERROR ]; then
calculateLeftMargin $width 31;
calculateTopMargin $height 33;
user_msg1="recalculated margins"; gotCommand=$C_TRUE; ok=$C_TRUE; fi
sleep 1; done
;; "?" ) printf"\nCurrent command: \"%s\"\nCurrent cardF: %s\n" $state "$(printCard $cardF)";gotCommand=$C_TRUE;; "n" ) confirmAction "start a new game"; if [ $? -eq $C_TRUE ]; then
cleanup;
init; endDeckIterations=$C_FALSE; nDeckIterations=0; fi; gotCommand=$C_TRUE;
state="";; "h" ) printHelp;clear;gotCommand=$C_TRUE;;
* ) user_msg1="Invalid command \"$state\","; user_msg2="press h for help"; gotCommand=$C_TRUE; state=""; ;; esac isGameWon; won=$?; if [ $won -eq $C_TRUE ]; then printBoard $won $endDeckIterations; fi done done terminalClearColors; clear;}#call the main function and pass all script arguments to it(main