Pieters bash scripts
Documentation for the bash scripts I have published
Loading...
Searching...
No Matches
patience.sh
Go to the documentation of this file.
1#!/bin/bash
2##! Author: Pieter van der Star (info@pietervanderstar.nl)
3##! Modifications by: (unmodified)
4##!
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
19#Changelog:
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 ┃
24#┃ │ chosen │ ┃
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#┗━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━┛
31
32##! @file
33##! @brief This script allows someone to play the game patience.
34
35#Cards
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)
40# -1 is no card
41#
42
43#TODO: add info on scoring in help
44#TODO: remove commented out debug code
45
46#FEATURE: allow switching number of open cards in-game
47#FEATURE: add bouncing cards animation when won
48
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.
52#RANDOM=101;
53
54#constants for boolean values
55##! Bool is not initialised.
56declare -r -i C_UNINIT=2;
57##! True value.
58declare -r -i C_TRUE=1;
59##! False value.
60declare -r -i C_FALSE=0;
61
62
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.
66##! @{
67#error values should always be larger than 60 as 0-60
68#are valid cards.
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;
94
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;
99 case $errorCode in
100 $C_SUCCESS ) printf "Successful";;
101 $C_EMPTY_COLUMN ) printf "There are no cards in the given column";;
102 $C_MOVE_INVALID ) printf "Move is not allowed";; #why should be output by the function returning this value
103 $C_CARD_NOT_FOUND ) printf "The requested card cannot be found";;
104 $C_COLUMN_NOT_EXISTS ) printf "The given column does not exist";;
105 $C_INVALID_ARGUMENT ) printf "Has an invalid argument";;
106 $C_NULL_CARD ) printf "The card given is empty";;
107 $C_NO_BLINDS ) printf "There are no blind cards in this column";;
108 $C_DEBUG_STOP ) printf "Stopped execution for debug purposes";;
109 $C_NOT_ENOUGH_CARDS ) printf "Not enough cards on stack to remove requested amount";;
110 $C_INCORRECT_CARD_NAME ) printf "The supplied string is not a card name";;
111 * ) printf "Unknown error: %d" $errorCode;;
112 esac;
113}
114##! @}
115
116
117### Some global vars and functions for formatting ###
118##! Indicates if terminal colors should be used.
119useColoredTerminal=$C_TRUE
120##! @brief Sets the terminal text color to black.
121function terminalBlackForeground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[30m"; fi;}
122##! @brief Sets the terminal text color to red.
123function terminalRedForeground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[31m"; fi;}
124##! @brief Sets the terminal text color to green.
125function terminalGreenForeground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[32m"; fi;}
126##! @brief Sets the terminal text color to brown.
127function terminalBrownForeground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[33m"; fi;}
128##! @brief Sets the terminal text color to blue.
129function terminalBlueForeground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[34m"; fi;}
130##! @brief Sets the terminal text color to purple.
131function terminalPurpleForeground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[35m"; fi;}
132##! @brief Sets the terminal text color to cyan.
133function terminalCyanForeground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[36m"; fi;}
134##! @brief Sets the terminal text color to gray.
135function terminalGreyForeground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[37m"; fi;}
136
137##! @brief Sets the terminal background color to black.
138function terminalBlackBackground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[40m"; fi;}
139##! @brief Sets the terminal background color to red.
140function terminalRedBackground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[41m"; fi;}
141##! @brief Sets the terminal background color to green.
142function terminalGreenBackground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[42m"; fi;}
143##! @brief Sets the terminal background color to brown.
144function terminalBrownBackground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[43m"; fi;}
145##! @brief Sets the terminal background color to blue.
146function terminalBlueBackground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[44m"; fi;}
147##! @brief Sets the terminal background color to purple.
148function terminalPurpleBackground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[45m"; fi;}
149##! @brief Sets the terminal background color to cyan.
150function terminalCyanBackground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[46m"; fi;}
151##! @brief Sets the terminal background color to gray.
152function terminalGreyBackground { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[47m"; fi;}
153
154##! @brief Sets the terminal to print bold text.
155function terminalBold { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[1m"; fi;}
156##! @brief Sets the terminal to print underlined text.
157function terminalUnderline { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[4m"; fi;}
158##! @brief Sets the terminal to reset the colors to the default.
159function terminalClearColors { if [ $useColoredTerminal -eq $C_TRUE ]; then printf "\e[m"; fi;}
160##! @brief Sets the background of the board patience is played on.
162
163### Debug functions ###
164##! Print the cards in the deck for debug purposes.
166 local -i i=0;
167 printf "closed:\n"
168 while [ $i -lt ${#deck[@]} ]; do
169 printCard ${deck[$i]};
170 i=$((i+1));
171 done
172 i=0;
173 printf "\nfront:\n"
174 while [ $i -lt ${#deckFront[@]} ]; do
175 printCard ${deckFront[$i]};
176 i=$((i+1));
177 done
178 i=0;
179 printf "\nopen:\n"
180 while [ $i -lt ${#deckOpen[@]} ]; do
181 printCard ${deckOpen[$i]};
182 i=$((i+1));
183 done
184 printf "\n";
185}
186
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).
190
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[@]};
196 cards=$((cards-1));
197 local -i i=0;
198 local -i j=0;
199 while [ $i -lt $cards ]; do
200 if [ $i -eq $cardIndex ]; then
201 j=$((j+1));
202 fi
203 deckOpen[$i]=${deckOpen[$j]};
204 i=$((i+1));
205 j=$((j+1));
206 done
207 unset deckOpen[$cards];
208}
209
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.
214##!
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;
223 else
224 local -i -r backfillIfEmpty=$C_FALSE;
225 fi
226 local -A cards=${#deckFront[@]}
227 cards=$((cards-1))
228 local -i i=0;
229 local -i j=0;
230 while [ $i -lt $cards ]; do
231 if [ $i -eq $cardIndex ]; then
232 j=$((j+1))
233 fi
234 deckFront[$i]=${deckFront[$j]}
235 i=$((i+1))
236 j=$((j+1))
237 done
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))]
243 fi
244 fi
245 return $C_SUCCESS;
246}
247
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[@]}
253 cards=$((cards-1))
254 local -i i=0;
255 local -i j=0;
256 while [ $i -lt $cards ]; do
257 if [ $i -eq $cardIndex ]; then
258 j=$((j+1))
259 fi
260 deck[$i]=${deck[$j]}
261 i=$((i+1))
262 j=$((j+1))
263 done
264 unset deck[$cards]
265}
266
267##! Reindex the deck, i.e. remove gaps in array.
268function reindexDeck {
269 local -i i=0;
270 local -i j=0;
271 while [ $j -lt ${#deck[@]} ]; do
272 if [ "${deck[$i]}" == "" ]; then
273 while [ "${deck[$j]}" == "" ]; do
274 j=$((j+1));
275 done
276 fi
277 deck[$i]=${deck[$j]};
278 i=$((i+1));
279 j=$((j+1));
280 done
281 while [ $i -lt $j ]; do
282 unset deck[$i];
283 i=$((i+1));
284 done
285}
286
287##! Move the cards in the deck to a random new position.
288function shuffleDeck {
289 local -A shuffledDeck
290 while [ ${#deck[@]} -gt 0 ]; do
291 local -i index=$(($RANDOM%${#deck[@]}))
292 if [ "${deck[$index]}" == "" ]; then
293 continue;
294 fi
295 shuffledDeck[${#shuffledDeck[@]}]=${deck[$index]};
296 unset deck[$index];
298 done
299 local -i i=0;
300 while [ $i -lt ${#shuffledDeck[@]} ]; do
301 deck[$i]=${shuffledDeck[$i]};
302 i=$((i+1));
303 done
304}
305
306##! Fill the deck with all the cards.
307function populateDeck {
308 local -i i=0
309 while [ $i -lt 52 ]; do
310 deck[$i]=$i;
311 i=$((i+1))
312 done
313}
314
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.
318function rotateDeck {
319 local -i -r nFront=$1;
320 #move front to open
321 local -i i=1;
322 local -A cards=${#deckFront[@]};
323 while [ $i -le $cards ]; do
324 deckOpen[${#deckOpen[@]}]=${deckFront[$((cards-i))]};
325 removeFromDeckFront $cards;
326 i=$((i+1));
327 done
328
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]};
335 done
336 fi
337
338 #deal a new set
339 i=$nFront;
340 if [ $nFront -gt ${#deck[@]} ]; then
341 i=${#deck[@]};
342 fi
343 while [ $i -gt 0 ]; do
344 i=$((i-1));
345 deckFront[$i]=${deck[0]};
347 done
348 #check if we need to flip the deck in the next move
349 if [ ${#deck[@]} -eq 0 ]; then
350 nDeckIterations=$((nDeckIterations+1));
351 fi;
352}
353
354### User interface outputs ###
355
356##! Print the suit stacks as a string, if empty print the suit symbol.
357function printSuitsStack {
358 local symbols=("♠" "♥" "♣" "♦")
359 local -i i=0;
360 while [ $i -lt 4 ]; do
361 if [ $((i%2)) -eq 0 ]; then
363 else
365 fi
366 if [ ${suitStack[$i]} -eq -1 ]; then
367 printf "%s " ${symbols[$i]}
368 else
369 local -i value=${suitStack[$i]}
370 local -i card=$((value*4+i))
371 printCard $card;
372 fi
373 i=$((i+1))
374 done
376}
377
378##! Print the deck, in two parts, first the closed, then the front.
379##! @param $arg1 end of deck iterations.
380function printDeck {
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
385 printf " ⃠ ";
386 elif [ $inDeck -gt 0 ]; then
387 printCard 0 0;
388 else
389 printf "╳ ";
390 fi
391
392 local -i i=0;
393 while [ $i -lt $inDeckFront ]; do
394 getCardColor ${deckFront[$i]}
395 if [ $? -eq 0 ]; then
397 else
399 fi
400 printCard ${deckFront[$i]}
401 i=$((i+1))
402 done
404 #fill the space set for the deck (5 is the space used by the highest
405 #nOpen value allowed
406 while [ $i -lt 5 ]; do
407 printf " ";
408 i=$((i+1));
409 done
410}
411
412##! Print the board.
413##! @param $arg1 (optional) has player won the game.
414##! @param $arg2 (optional) end of deck iterations.
415function printBoard {
417 clear;
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
420
423 printf "%s" "$marginTop"
424 printf "%s Patience\n" "$marginLeft";
425 printBoardContent "┏━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━┓";
426 printBoardContent "┃%s │%s┃" "$(printDeck $endDeckIterations)" "$(printSuitsStack)"
427 printBoardContent "┃ ╰────────┨";
428 printBoardContent "┃ ⓪ ① ② ③ ④ ⑤ ⑥ ┃";
429
430
431 local -i r=0;
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=" ";
448 fi
449 printBoardContent "┃%s%s%s%s%s┃" "$(terminalBrownBackground)" "$(terminalGreenForeground)" "$line" "$(setBoardBackground)" "$(terminalGreyForeground)";
450 r=$((r+1));continue;
451 fi
452 fi
453 #build the actual board
454 local line="";
455 local -i c=0;
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
461 line+=$(terminalGreyForeground;printf "🃟 ");
462 elif [ $r -lt $((${openStacks[$c,0]}+${blindStacks[$c,0]})) ]; then
463 getCardColor ${openStacks[$c,$((r-${blindStacks[$c,0]}+1))]}
464 local -i color=$?
465 if [ $color -eq 0 ]; then
466 line+="$(terminalBlackForeground)";
467 else
468 line+="$(terminalRedForeground)";
469 fi
470 line+="$(printCard ${openStacks[$c,$((r-${blindStacks[$c,0]}+1))]} )";
471 else
472 line+=" ";
473 fi
474 c=$((c+1));
475 done
476 line+=$(terminalGreyForeground);
477 printBoardContent "┃ %s ┃" "$line";
478 r=$((r+1));
479 done
480
481 printBoardContent "┃ ┃";
482 printBoardContent "┃ ⓪ ① ② ③ ④ ⑤ ⑥ ┃";
483 printBoardContent "┃ ┃";
484 printBoardContent "┠─────────────────────────────┨";
485 printBoardContent "┃ %-27s ┃" "$user_msg1";
486 printBoardContent "┃ %027s ┃" "$user_msg2";
487 printBoardContent "┃ %s ┃" "$(printScore)";
488 printBoardContent "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛";
491 user_msg1="";
492 user_msg2="";
493}
494
495##! @brief Helper function to print the board. It handles the coloring of the board.
496##! arguments same as args to printf.
497function printBoardContent {
498 printf "%s" "$marginLeft";setBoardBackground; printf "$@";terminalBlueBackground;printf "\n";
499}
500
501##! @brief Helper function to print the board. It handles the coloring of the board.
502function printScore {
503 if [ $score -ge 0 ]; then
504 printf " %03d" $score;
505 else
506 printf "%s-%03d%s" "$(terminalRedForeground)" $((-1*score)) "$(terminalGreyForeground)";
507 fi;
508}
509
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.
513function printHelp {
515 clear;
516 if [ $# -eq 0 ]; then
517 local -i page=1
518 else
519 local -i page=$1
520 fi
521 local -r -i nPages=4;
523 calculateLeftMargin $width 65;
524 printf "\n";
525 printBoardContent "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓";
526 case $page in
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 ┃";
529 printBoardContent "┃ enter key afterwards. ┃";
530 printBoardContent "┃ ┃";
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 ┃";
533 printBoardContent "┃ of the card you want. ┃";
534 printBoardContent "┃ ┃";
535 printBoardContent "┃ Have fun playing, ┃";
536 printBoardContent "┃ Pieter van der Star ┃";
537 printBoardContent "┃ ┃";
538 printBoardContent "┃ %sCommands%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)";
539 printBoardContent "┃ To move the cards the following commands are available: ┃";
540 printBoardContent "┃ \"h\" enters this help ┃";
541 printBoardContent "┃ \"e\" exits the game (doesn't work in this help) ┃";
542 printBoardContent "┃ \"<card> <card>\" moves the first card on top of the second ┃";
543 printBoardContent "┃ \"<card> s\" moves the card to the corresponding ┃";
544 printBoardContent "┃ suitstack ┃";
545 printBoardContent "┃ \"<card> c<number>\" moves the card to the corresponding ┃";
546 printBoardContent "┃ column ┃";
547 printBoardContent "┃ \"d\" shows new cards from the deck ┃";
548 printBoardContent "┃ \"r\" if a command is not yet complete, this clears the ┃";
549 printBoardContent "┃ command ┃";
550 printBoardContent "┃ \"n\" starts a new game ┃";
551 printBoardContent "┃ \" \" or [enter] move all possible cards to the suitstacks ┃";
552 printBoardContent "┃ \"m\" recalculate the margins (use after resizing the terminal) ┃";
553 printBoardContent "┃ ┃";
554 printBoardContent "┃ ┃";
555 ;;
556 2)
557 printBoardContent "┃ %sCommand line options%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)";
558 printBoardContent "┃ -o <number> Number of cards to show when taking from deck ┃";
559 printBoardContent "┃ -c Do not use colored terminal (Default is ┃";
560 printBoardContent "┃ colored) ┃";
561 printBoardContent "┃ -i <number> Number of iterations for the deck (0=no limit) ┃";
562 printBoardContent "┃ -s Source only, do not run the program. ┃";
563 printBoardContent "┃ ┃";
564 printBoardContent "┃ %sCard selection%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)";
565 printBoardContent "┃ The following table shows the cards and corresponding command:┃";
566 printBoardContent "┃ ┌──╥──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ ┃";
567 printBoardContent "┃ │ ║a │2 │3 │4 │5 │6 │7 │8 │9 │10│j │q │k │ ┃";
568 printBoardContent "┃ ╞══╬══╪══╪══╪══╪══╪══╪══╪══╪══╪══╪══╪══╪══╡ ┃";
569 printBoardContent "┃ │s ║🂡 │🂢 │🂣 │🂤 │🂥 │🂦 │🂧 │🂨 │🂩 │🂪 │🂫 │🂭 │🂮 │ ┃";
570 printBoardContent "┃ │h ║🂱 │🂲 │🂳 │🂴 │🂵 │🂶 │🂷 │🂸 │🂹 │🂺 │🂻 │🂽 │🂾 │ ┃";
571 printBoardContent "┃ │d ║🃁 │🃂 │🃃 │🃄 │🃅 │🃆 │🃇 │🃈 │🃉 │🃊 │🃋 │🃍 │🃎 │ ┃";
572 printBoardContent "┃ │c ║🃑 │🃒 │🃓 │🃔 │🃕 │🃖 │🃗 │🃘 │🃙 │🃚 │🃛 │🃝 │🃞 │ ┃";
573 printBoardContent "┃ └──╨──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘ ┃";
574 printBoardContent "┃ First enter the column, and then the row. i.e. \"as\" = 🂡 ┃";
575 printBoardContent "┃ ┃";
576 printBoardContent "┃ %sOther symbols%s ┃" "$(terminalBold)$(terminalUnderline)" "$(terminalClearColors)$(terminalGreyForeground)$(setBoardBackground)";
577 printBoardContent "┃ There are two other cards that may appear during gameplay: ┃";
578 printBoardContent "┃ 🂠 which is just the back of a card. ┃";
579 printBoardContent "┃ 🃟 which signifies an empty place. ┃";
580 printBoardContent "┃ ╳ which signifies the end of the deck has been reached. ┃";
581 printBoardContent "┃ It will restart with the deck unless a limit has been set, ┃";
582 printBoardContent "┃ in which case ⃠ will be shown. ┃";
583 printBoardContent "┃ ⓝ column numbers, usefull when using he c<number> command ┃";
584 printBoardContent "┃ ┃";
585 ;;
586 3)
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 ┃";
590 printBoardContent "┃ jack, the suit is hearts, the second cards' value ┃";
591 printBoardContent "┃ is queen and the suit is clubs. ┃";
592 printBoardContent "┃ ┃";
593 printBoardContent "┃ 2: To put the 🂱 on the suit stack the following command ┃";
594 printBoardContent "┃ needs to be given: \"ah\" followed by \"s\". \"s\" indicates the ┃";
595 printBoardContent "┃ card should be moved to the suitstack. ┃";
596 printBoardContent "┃ ┃";
597 printBoardContent "┃ ┃";
598 printBoardContent "┃ ┃";
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) ┃";
602 printBoardContent "┃ And by moving cards to the suitstacks (card value) ┃";
603 printBoardContent "┃ so moving an ace to the suitstacks give 1 point, while a king ┃";
604 printBoardContent "┃ will add 13 points to your score ┃";
605 printBoardContent "┃ ┃";
606 printBoardContent "┃ ┃";
607 printBoardContent "┃ ┃";
608 printBoardContent "┃ ┃";
609 printBoardContent "┃ ┃";
610 printBoardContent "┃ ┃";
611 printBoardContent "┃ ┃";
612 printBoardContent "┃ ┃";
613 printBoardContent "┃ ┃";
614 printBoardContent "┃ ┃";
615 ;;
616 4)
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 ┃";
621 printBoardContent "┃GNU Affero General Public License for more details. ┃";
622 printBoardContent "┃ ┃";
623 printBoardContent "┃You should have received a copy of the GNU Affero General ┃";
624 printBoardContent "┃Public License along with this program. ┃";
625 printBoardContent "┃ If not, see <https://www.gnu.org/licenses/ ┃";
626 printBoardContent "┃ ┃";
627 printBoardContent "┃ ┃";
628 printBoardContent "┃ Feel free to use and modify as long as: ┃";
629 printBoardContent "┃ - This License stays intact. ┃";
630 printBoardContent "┃ - You give the original author credit for their work ┃";
631 printBoardContent "┃ - The modifications are indicated clearly in the changelog ┃";
632 printBoardContent "┃ - The result must be free, in both monetary sense and ┃";
633 printBoardContent "┃ personal sense ┃";
634 printBoardContent "┃ i.e. no account required, no personal data needs to be ┃";
635 printBoardContent "┃ handed over. ┃";
636 printBoardContent "┃ - The software is provided as-is. Feature requests or bug ┃";
637 printBoardContent "┃ reports may be ignored. ┃";
638 printBoardContent "┃ ┃";
639 printBoardContent "┃ ┃";
640 printBoardContent "┃ ┃";
641 printBoardContent "┃ ┃";
642 printBoardContent "┃ ┃";
643 printBoardContent "┃ ┃";
644 printBoardContent "┃ End of help, press q to quit help ┃";
645 ;;
646 esac
647 printBoardContent "┃ ┃";
648 printBoardContent "┃ press n for next page, p for previous or q to quit help %2d/%d ┃" $page 4;
649 printBoardContent "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛";
651 printf "\n";
652 local char="";
653 while [[ "$char" != "n" && "$char" != "q" ]]; do
654 read -n 1 char
655 case $char in
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;;
658 "q") calculateLeftMargin $width 31; return $C_SUCCESS;;
659 esac
660 done
661 return $C_SUCCESS;
662}
663
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.
668function printCard {
669 local card=$1
670 if [ "$card" == "" ]; then
671 printf "X ";
672 return $C_NULL_CARD;
673 fi
674 card=$((card))
675 if [ $# -ge 2 ]; then local open=$2;
676 else local open=1; fi
677 if [ $card -eq -1 ]; then
678 printf " "
679 return $C_SUCCESS;
680 fi
681
682 local -a cards
683 cards=("🂡" "🂱" "🃑" "🃁" #A
684 "🂢" "🂲" "🃒" "🃂" #2
685 "🂣" "🂳" "🃓" "🃃" #3
686 "🂤" "🂴" "🃔" "🃄" #4
687 "🂥" "🂵" "🃕" "🃅" #5
688 "🂦" "🂶" "🃖" "🃆" #6
689 "🂧" "🂷" "🃗" "🃇" #7
690 "🂨" "🂸" "🃘" "🃈" #8
691 "🂩" "🂹" "🃙" "🃉" #9
692 "🂪" "🂺" "🃚" "🃊" #10
693 "🂫" "🂻" "🃛" "🃋" #J
694 "🂭" "🂽" "🃝" "🃍" #Q
695 "🂮" "🂾" "🃞" "🃎" #K
696 "🂬" "🂼" "🃜" "🃌" #C here for completeness of the unicode deck (for future French version?)
697 );
698 local cardBack="🂠";
699 if [ $((open)) -eq 1 ]; then
700 printf "%s " ${cards[$card]};
701 else
702 printf "%s " $cardBack;
703 fi
704 return $C_SUCCESS;
705}
707##! Print the credits of the game and perform a short "animation".
708function printCredits {
709 local i=0;
710 while [ $i -le 4 ]; do
711 clear;
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";
716 printf "\n";
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;
723 printf "\n";
725 local -i j=0;
726
728 i=$((i+1));
729 sleep 0.5;
730 done;
731}
732
733##! Check if the game is won.
734##! @returns Boolean true if game won, false if not (yet).
735function isGameWon {
736 local i=0;
737 while [ $i -lt 4 ]; do
738 if [ ${suitStack[$i]} -ne 12 ]; then
739 return $C_FALSE;
740 fi
741 i=$((i+1));
742 done;
743 return $C_TRUE;
744}
746##! Deal the blind cards on the board.
747function dealBlinds {
748 local c=0
749# printf "Took: ";
750 while [ $c -lt 7 ]; do
751 blindStacks[$c,0]=$((c+1))
752 local r=1;
753 while [ $r -le $((c+1)) ]; do
754 blindStacks[$c,$r]=${deck[0]}
755# printCard ${deck[0]};
757 r=$((r+1));
758 done
759 c=$((c+1))
760 done
761# printf "\n"
762}
763
764##! Fill any open columns on the board.
765##! @returns Error code according to @ref patience_return_values.
766function fillOpen {
767 local c=0
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
772 turnBlind $c
773 local ret=$?
774 if [[ $ret -ne $C_SUCCESS && $ret -ne $C_NO_BLINDS ]]; then
775 return $ret;
776 fi
777 fi
778 c=$((c+1))
779 done
780 return $ret;
781}
782
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.
786function turnBlind {
787 local c=$1
788 local nRows=${blindStacks[$c,0]}
789 if [ $nRows -eq 0 ]; then
790 return $C_NO_BLINDS;
791 fi
792 openStacks[$c,0]=1
793 openStacks[$c,1]=${blindStacks[$c,$nRows]}
794 unset blindStacks[$c,$nRows]
795 blindStacks[$c,0]=$((nRows-1))
796 return $C_SUCCESS;
797}
799##! Initialise the suitstacks
800function initSuitStack {
801 suitStack[0]=-1;
802 suitStack[1]=-1;
803 suitStack[2]=-1;
804 suitStack[3]=-1;
805}
806
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.
810function getBottomCard {
811 local c=$1
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
816 return $C_EMPTY_COLUMN;
817 else
818 return ${openStacks[$c,${openStacks[$c,0]}]};
819 fi
820 return $C_SUCCESS;
821}
822
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.
826function getTopCard {
827 local c=$1
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
832 return $C_EMPTY_COLUMN;
833 else
834 return ${openStacks[$c,1]};
835 fi
836 return $C_SUCCESS;
837}
838
839##! Move a card from the deck to the corresponding suitstack.
840##! @returns Error code according to @ref patience_return_values.
841function moveDeckToSuitStack {
842 local card=${deckFront[0]}
843 getCardSuit $card
844 local suit=$?
845 getCardValue $card
846 local value=$?
847
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
852 printf "TEST";
853 sleep 10;
854 return $C_MOVE_INVALID;
855 else
856 suitStack[$suit]=$value
857 score=$((score+value+1));
858 printf "score: %d\n" $score
859 fi
861 return $?;
862}
863
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.
868function removeFromOpenStacks {
869 local c=$1;
870 local n=$2;
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;
874 return $C_NOT_ENOUGH_CARDS;
875 fi
876 local r=$n;
877 while [ $r -gt 1 ]; do
878 unset openStacks[$c,$r]
879 r=$((r-1))
880 done
881 openStacks[$c,0]=$((rows-n));
882 return $C_SUCCESS;
883}
884
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.
888function removeFromSuit {
889 local s=$1;
890 if [ ${suitStack[$s]} -lt 0 ]; then
891 return $C_EMTPY_SUITSTACK; #no cards on suitstack
892 fi
893 suitStack[$s]=$((${suitStack[$s]}-1))
894 return $C_SUCCESS;
895}
897##! Initialise the open stacks on the board.
898function initOpen {
899 local c=0
900 while [ $c -lt 7 ]; do
901 openStacks[$c,0]=0
902 c=$((c+1))
903 done
904}
905
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.
913function findCard {
914 local card=$1;
915 fromDeck=$C_FALSE;
916 fromSuit=$C_FALSE;
917 #first look on the deck
918 if [[ ${#deckFront[@]} -gt 0 && ${deckFront[0]} -eq $card ]]; then
919 fromDeck=$C_TRUE;
920 return $C_SUCCESS;
921 fi
922 #then look on the suitstacks
923 getCardValue $card;
924 local value=$?;
925 getCardSuit $card;
926 local suit=$?;
927 if [ ${suitStack[$suit]} -eq $value ]; then
928 fromSuit=$C_TRUE;
929 return $C_SUCCESS;
930 fi
931 #then look on the board
932 inColumn=0;
933 while [ $inColumn -lt 7 ]; do
934 isBottomCard=$C_TRUE
935 inRow=${openStacks[$inColumn,0]};
936 while [ $inRow -gt 0 ]; do
937 local checkingCard=${openStacks[$inColumn,$inRow]};
938 if [ $checkingCard -eq $card ]; then
939 return $C_SUCCESS;
940 fi
941 isBottomCard=$C_FALSE
942 inRow=$((inRow-1))
943 done
944 inColumn=$((inColumn+1))
945 done
946 return $C_CARD_NOT_FOUND;
947}
948
949##! Get the value of the card based on the cardnumber.
950##! @param $arg1 Cardnumber.
951##! @returns Card value.
952function getCardValue {
953 card=$1
954 return $((card/4))
955}
956
957##! Get the suit of the card based on the cardnumber.
958##! @param $arg1 Cardnumber.
959##! @returns Number indicating card suit.
960function getCardSuit {
961 card=$1
962 return $((card%4));
963}
964
965##! Get the color of the card based on the cardnumber.
966##! @param $arg1 Cardnumber.
967##! @returns Number indicating card value.
968function getCardColor {
969 card=$1
970 return $((card%2));
971}
972
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.
977function isValidBoardMove {
978 local fromCard=$1;
979 local toCard=$2;
980
981 getCardValue $fromCard;
982 local fromValue=$?;
983 getCardColor $fromCard;
984 local fromColor=$?;
985
986 getCardValue $toCard;
987 local toValue=$?;
988 getCardColor $toCard;
989 local toColor=$?;
990
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";
994 user_msg2="to an empty column.";return $C_MOVE_INVALID;
995 fi
996 return $C_SUCCESS;
997 elif [ $fromColor -eq $toColor ]; then
998 user_msg1="A king can only be moved";
999 user_msg2="on op of a card with another color";return $C_MOVE_INVALID;
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;
1003 printf "Invalid move: \n";return $C_MOVE_INVALID;
1004 fi
1005 return $C_SUCCESS;
1006}
1007
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.
1011function isValidSuitMove {
1012 local card=$1;
1013 getCardSuit $card
1014 local suit=$?
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;
1019 fi
1020 return $C_SUCCESS;
1021}
1022
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.
1027function moveCards {
1028 #if [ $# -ge 3 ]; then
1029 # printf "ENABLING DEBUG"
1030 # set -x;
1031 #fi
1032 local -i fromCard=$1;
1033 findCard $fromCard;
1034 if [ $? -ne $C_SUCCESS ]; then
1035 user_msg1="Card(s) to move not found";
1036 set +x; return $C_CARD_NOT_FOUND;
1037 fi
1038 local fromColumn=$inColumn;
1039 local fromRow=$inRow;
1040
1041 local to=$2
1042 case $to in
1043 "c"[0-6])
1044 local toColumn=${to:1:1};
1045 getBottomCard $toColumn
1046 local toCard=$?
1047
1048 #is it a valid move?
1049 isValidBoardMove $fromCard $toCard
1050 if [ $? -ne $C_SUCCESS ]; then
1051 set +x; return $C_MOVE_INVALID;
1052 fi
1053
1054 getCardValue $fromCard
1055 local fromValue=$?
1056 #getCardValue $toCard
1057 #local toValue=$?
1058
1059 if [ $fromDeck -eq $C_TRUE ]; then
1060 #update the rowcount
1061 openStacks[$toColumn,0]=$((${openStacks[$toColumn,0]}+1));
1062 #copy the card
1063 openStacks[$toColumn,${openStacks[$toColumn,0]}]=${deckFront[0]};
1064 #remove from old position
1066 score=$((score+1));
1067 elif [ $fromSuit -eq $C_TRUE ]; then
1068 getCardSuit $fromCard;
1069 local suit=$?;
1070 #update the rowcount
1071 openStacks[$toColumn,0]=$((${openStacks[$toColumn,0]}+1));
1072 #copy the card
1073 openStacks[$toColumn,${openStacks[$toColumn,0]}]=$fromCard;
1074 #remove from old position
1075 removeFromSuit $suit;
1076 else
1077 #to same location is a valid "move", but nothing needs to be done
1078 if [ $toColumn -eq $fromColumn ]; then
1079 set +x; return $C_SUCCESS;
1080 fi
1081 local r=$fromRow;
1082 while [ $r -le ${openStacks[$fromColumn,0]} ]; do
1083 #update the rowcount
1084 openStacks[$toColumn,0]=$((${openStacks[$toColumn,0]}+1));
1085 #copy the card
1086 openStacks[$toColumn,${openStacks[$toColumn,0]}]=${openStacks[$fromColumn,$r]};
1087 #remove from old position
1088 unset openStacks[$fromColumn,$r];
1089 r=$((r+1))
1090 done
1091 #update the rowcounts
1092 openStacks[$fromColumn,0]=$((fromRow-1));
1093 fi
1094 ;;
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";
1099 set +x; return $C_SUCCESS;
1100 fi
1101 #is the card the bottom card
1102 if [[ $fromDeck -eq $C_FALSE && $isBottomCard -eq $C_FALSE ]]; then
1103 user_msg1="only the bottom card, or the one from";
1104 user_msg2="the deck can be put on the suitstacks";
1105 set +x; return $C_MOVE_INVALID;
1106 fi
1107 #determine the value and suit
1108 getCardValue $fromCard
1109 local value=$?
1110 getCardSuit $fromCard
1111 local suit=$?
1112 isValidSuitMove $fromCard;
1113 if [ $? -ne $C_SUCCESS ]; then
1114 set +x; return $C_MOVE_INVALID;
1115 fi
1116
1117 #move the card
1118 suitStack[$suit]=$value
1119 if [ $fromDeck -eq $C_TRUE ]; then
1120 ##printf "fromdeck\n";
1122 score=$((score+1));
1123 #local ret=$?;
1124 #printErrorDescription $ret;
1125 else
1126 ##printf "not fromdeck\n";
1127 removeFromOpenStacks $inColumn 1;
1128 #local ret=$?;
1129 #printErrorDescription $ret;
1130 fi
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
1133
1134 set +x; return $C_SUCCESS;
1135 ;;
1136 [0-9]|[0-9][0-9])
1137 toCard=$2;
1138 toCard=$((toCard));
1139
1140 local fromFromDeck=$fromDeck;
1141 local fromFromSuit=$fromSuit;
1142
1143 findCard $toCard;
1144 if [ $? -ne $C_SUCCESS ]; then user_msg1="Card(s) to move to not found";return $C_CARD_NOT_FOUND;fi
1145
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";
1151 return $C_SUCCESS;
1152 fi
1153 fi
1154 moveCards $fromCard "c$inColumn";
1155 ;;
1156 *) user_msg1="ERROR: unknown destination \"$2\"";return $C_MOVE_INVALID;
1157 esac
1158 set +x
1159 return $C_SUCCESS;
1160}
1162##! Try to put all available cards on the suitstacks.
1163function autoMoveToSuitstacks {
1164 local doLoop=$C_TRUE;
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
1167 #check the deck
1168 ##printf "AM: checking deck\n";
1169 if [ ${#deckFront[@]} -ne 0 ]; then
1170 moveCards ${deckFront[0]} "s";
1171 ret=$?;
1172 if [ $ret -eq $C_SUCCESS ]; then
1173 ##printf "Moved %d (deck) " $card;printCard $card;printf "\n";
1174 doLoop=$C_TRUE;
1175 sleep 1;
1176 fi;
1177 fi;
1178 #iterate over all columns
1179 local c=0;
1180 while [ $c -lt 7 ]; do
1181 ##printf "AM: checking column %d\n" $c
1182 #try moving the bottom card to the stack
1183 getBottomCard $c;
1184 card=$?;
1185 if [[ $card -ne $C_NULL_CARD && $card -ne $C_EMPTY_COLUMN ]]; then
1186 moveCards $card "s" 0;
1187 ret=$?
1188 if [ $ret -eq $C_SUCCESS ]; then
1189 ##printf "Moved %d " $card;printCard $card;printf "\n";
1190 doLoop=$C_TRUE;
1191 sleep 1;
1192 fi;
1193 fi
1194
1195 c=$((c+1));
1196 done;
1197 fillOpen;
1198 if [ $? -ne $C_NO_BLINDS ]; then
1199 user_msg1="Automove to suitstacks"
1200 user_msg2="in progress";
1201 fi
1202
1203 done;
1204 user_msg1="Automove to suitstacks"
1205 user_msg2="finished";
1206 ##printf "AM: return\n";
1207}
1208
1209##! Convert a human card name to a cardnumber.
1210##! @param $arg1 abbrieviated human name for the card.
1211##! @returns Card number.
1212function nameToCard {
1213 local name=$1
1214 local length=${#name}
1215 local suit=${name:$((length-1)):1}
1216 local value=${name:0:$((length-1))}
1217 case $suit in
1218 "s") suit=0;;
1219 "h") suit=1;;
1220 "c") suit=2;;
1221 "d") suit=3;;
1222 *) user_msg1="ERROR, Invalid suit %s" $suit; return $C_INCORRECT_CARD_NAME;;
1223 esac
1224 case $value in
1225 "k") value=12;;
1226 "q") value=11;;
1227 "j") value=10;;
1228 "10"|[1-9]) value=$((value-1));;
1229 "a") value=0;;
1230 *) user_msg1="ERROR, Invalid value %s" $value;return $C_INCORRECT_CARD_NAME;;
1231 esac
1232 local card=$((value*4+suit));
1233 return $card;
1234}
1235
1236##! Convert cardnumber to a human readable card name.
1237##! @param $arg1 card number.
1238##! @returns Error code according to @ref patience_return_values.
1239##!
1240##! Prints card name, for use in $(cardToName n)
1241function cardToName {
1242 local -i card=$1
1243 local -i value;
1244 getCardValue $card
1245 value=$?;
1246 value=$((value+1));
1247 case $value in
1248 13) printf "k";;
1249 12) printf "q";;
1250 11) printf "j";;
1251 10|[1-9]) printf "%d" $value;;
1252 0) printf "a";;
1253 *) return $C_INVALID_ARGUMENT;;
1254 esac
1255 local -i suit;
1256 getCardSuit $card;
1257 suit=$?;
1258 case $suit in
1259 0) printf "s";;
1260 1) printf "h";;
1261 2) printf "c";;
1262 3) printf "d";;
1263 *) return $C_INVALID_ARGUMENT;;
1264 esac
1265 return $C_SUCCESS;
1266}
1267
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.
1272function calculateLeftMargin {
1273 local -i terminalWidth=$1;
1274 local -i contentWidth=$2;
1275 local -i margins=$((terminalWidth-contentWidth));
1276 local -i margin=$((margins/2));
1277 marginLeft="";
1278 while [ $margin -gt 0 ]; do
1279 marginLeft+=" ";
1280 margin=$((margin-1));
1281 done
1282}
1283
1284
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.
1289function calculateTopMargin {
1290 local -i terminalHeight=$1;
1291 local -i contentHeight=$2;
1292 local -i margins=$((terminalHeight-contentHeight));
1293 local -i margin=$((margins/2));
1294 marginTop="";
1295 while [ $margin -gt 0 ]; do
1296 #sadly \n does not work, so then use a hard return in the string
1297 marginTop+="
1298";
1299 margin=$((margin-1));
1300 done
1301}
1302
1303
1304#global vars
1305#declare -A board
1306##! The deck of cards.
1307declare -A deck
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.
1313declare -A deckOpen
1314##! The card on the front of the deck.
1315declare -A deckFront
1316##! Stacks of cards having been sorted by suit.
1317declare -A suitStack
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;
1329##! Current score.
1330declare -i score=0;
1331
1332##! Initialise the game.
1333function init {
1334# initBoard;
1336# debugPrintDeck
1338# debugPrintDeck
1339 dealBlinds;
1340# debugPrintDeck
1341# printBoard
1342 #exit;
1343 initOpen;
1345 score=$((score-100));
1346}
1347
1348##! Cleanup remnants of the old game before starting a new.
1349function cleanup {
1350 while [ ${#deckOpen[@]} -gt 0 ]; do
1351 unset deckOpen[$((${#deckOpen[@]}-1))];
1352 done
1353 while [ ${#deckFront} -gt 0 ]; do
1354 unset deckFront[$((${#deckFront[@]}-1))];
1355 done
1356}
1357
1358##! Ask user for confirmation.
1359##! @param $arg1 Description of the action to perform.
1360##! @returns Bool, user confirmed.
1361function confirmAction {
1362 clear;
1363 printBoard;
1364 printf "%sAre you sure you want to\n" "$marginLeft";
1365 printf "%s%24s? (y/n)\n" "$marginLeft" "$1";
1366 local char="";
1367 while [[ "$char" != "n" && "$char" != "q" ]]; do
1368 read -n 1 char
1369 case $char in
1370 "y") return $C_TRUE;;
1371 "n") return $C_FALSE;;
1372 esac
1373 done
1374}
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).
1378function checkTerminalSize {
1379 local -i width=$1;
1380 local -i height=$2;
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;
1386 fi
1387}
1388
1389##! Main function.
1390function main {
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);
1395 checkTerminalSize $width $height;
1396 if [ $? -eq $C_TERMINAL_SIZE_ERROR ]; then
1397 return 1;
1398 fi
1399
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
1404 case $arg in
1405 h) printf "For help press h after start\n";sleep 5;;
1406 o) nOpen=${OPTARG};;
1407 c) useColoredTerminal=0;;
1408 i) maxDeckIterations=${OPTARG};;
1409 s) return $C_SUCCESS;;
1410 *) printf "";exit 0;;
1411 esac
1412 done
1413
1414
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;
1421 return $C_INVALID_ARGUMENT;
1422 fi
1423
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;
1429 return $C_INVALID_ARGUMENT;
1430 fi
1431
1433 clear;
1434
1435 calculateLeftMargin $width 31
1436 calculateTopMargin $height 33
1437 #printCredits
1438
1439
1440 local -i endDeckIterations=$C_FALSE;
1441 init
1442
1443 #handle user input in the main loop
1444 local state=""
1445 local -i endGame=$C_FALSE
1446 local -i gotCommand=$C_TRUE
1447 local -i won=$C_FALSE
1448 while [ $endGame -eq $C_FALSE ]; do
1449 if [ $gotCommand -eq $C_TRUE ]; then
1450 fillOpen;
1451 printBoard $won $endDeckIterations
1452 fi
1453 gotCommand=$C_FALSE
1454 state=""
1455 local -i cardF=-1;
1456 local -i cardT=-1;
1457 printf "%sCommand: %s" "$marginLeft" "$state"
1458 while [ $gotCommand -eq $C_FALSE ]; do
1459 read -n 1 char
1460 state="$state$char"
1461 shopt -s extglob # turn on extended globbing
1462 case "$state" in
1463 [a]|[0-9]|"10"|[jqk] ) ;;
1464 [a0-9jqk][shcd]|"10"[shcd]) if [ $won -eq $C_TRUE ]; then
1465 state="";
1466 elif [ $cardF -lt 0 ]; then
1467 nameToCard $state;
1468 cardF=$?;
1469 printf " ";
1470 state=""
1471 else
1472 nameToCard $state;
1473 cardT=$?;
1474 gotCommand=$C_TRUE;
1475 moveCards $cardF $cardT
1476 fi
1477 ;;
1478 "c" ) if [ $won -eq $C_TRUE ]; then
1479 state="";
1480 fi
1481 ;;
1482 "c"[0-6] ) if [ $won -eq $C_TRUE ]; then
1483 state="";
1484 elif [ $cardF -lt 0 ]; then
1485 getTopCard ${state:1:1}
1486 cardF=$?;
1487 printf " ";
1488 state=""
1489 if [[ $cardF -eq $C_COLUMN_NOT_EXISTS || $cardF -eq $C_EMPTY_COLUMN ]]; then
1490 user_msg1="Column is empty or is";
1491 user_msg2="not a valid column";
1492 fi
1493 else
1494 moveCards $cardF $state
1495 gotCommand=$C_TRUE;
1496 fi
1497 ;;
1498 "s" ) if [ $won -eq $C_TRUE ]; then
1499 state="";
1500 elif [ $cardF -lt 0 ]; then
1501 user_msg1="Can only use s when already";
1502 user_msg2="selected a card to move";
1503 gotCommand=$C_TRUE;
1504 state=""
1505 else
1506 moveCards $cardF "s";
1507 gotCommand=$C_TRUE;
1508 fi
1509 ;;
1510 "d" ) if [ $won -eq $C_TRUE ]; then
1511 state="";
1512 else
1513 if [ $endDeckIterations -eq $C_FALSE ]; then
1514 rotateDeck $nOpen;
1515 fi;
1516 if [[ $nDeckIterations -ge $maxDeckIterations && $maxDeckIterations -ne 0 ]]; then
1517 endDeckIterations=$C_TRUE;
1518 fi;
1519 gotCommand=$C_TRUE;
1520 fi
1521 ;;
1522 *"r" ) user_msg1="resetting command"; cardF=-1;gotCommand=$C_TRUE;;
1523 "" ) autoMoveToSuitstacks;printf "\n";gotCommand=$C_TRUE;;
1524 "e" ) confirmAction "exit the game"; endGame=$?;gotCommand=$C_TRUE;;
1525 "m" ) local -i ok=$C_FALSE;
1526 while [ $ok -ne $C_TRUE ]; do
1527 width=$(tput cols);
1528 height=$(tput lines);
1529 clear;
1530 checkTerminalSize $width $height;
1531 if [ $? -ne $C_TERMINAL_SIZE_ERROR ]; then
1532 calculateLeftMargin $width 31;
1533 calculateTopMargin $height 33;
1534 user_msg1="recalculated margins";
1535 gotCommand=$C_TRUE;
1536 ok=$C_TRUE;
1537 fi
1538 sleep 1;
1539 done
1540 ;;
1541 "?" ) printf "\nCurrent command: \"%s\"\nCurrent cardF: %s\n" $state "$(printCard $cardF)";gotCommand=$C_TRUE;;
1542 "n" ) confirmAction "start a new game";
1543 if [ $? -eq $C_TRUE ]; then
1544 cleanup;
1545 init;
1546 endDeckIterations=$C_FALSE;
1547 nDeckIterations=0;
1548 fi;
1549 gotCommand=$C_TRUE;
1550 state="";;
1551 "h" ) printHelp;clear;gotCommand=$C_TRUE;;
1552 * ) user_msg1="Invalid command \"$state\",";
1553 user_msg2="press h for help";
1554 gotCommand=$C_TRUE;
1555 state="";
1556 ;;
1557 esac
1558 isGameWon;
1559 won=$?;
1560 if [ $won -eq $C_TRUE ]; then
1561 printBoard $won $endDeckIterations;
1562 fi
1563 done
1564 done
1565
1567 clear;
1568}
1569
1570#call the main function and pass all script arguments to it
1571(main "$@")
const $C_NO_BLINDS
The request to turn a blind card cannot be fulfilled as there are no blind cards.
Definition patience.sh:85
const $C_EMPTY_COLUMN
Indicates the selected column to take a card from is emtpy.
Definition patience.sh:73
const $C_MOVE_INVALID
Indicates the given move is not a valid move.
Definition patience.sh:75
printErrorDescription($arg1)
Prints a descriptive error message based on the given error code.
Definition patience.sh:97
const $C_NOT_ENOUGH_CARDS
The card stack is too small to give the requested cards.
Definition patience.sh:87
const $C_INVALID_ARGUMENT
Indicates the argument given does not exist.
Definition patience.sh:81
const $C_DEBUG_STOP
Returned prematurely for debug purposes.
Definition patience.sh:93
const $C_NULL_CARD
Indicates the card given is empty.
Definition patience.sh:83
const $C_COLUMN_NOT_EXISTS
Indicates the given column does not exist.
Definition patience.sh:79
const $C_CARD_NOT_FOUND
Indicates the card asked for is not available for a move.
Definition patience.sh:77
const $C_TERMINAL_SIZE_ERROR
The size of the terminal is not large enough to show the patience board.
Definition patience.sh:91
const $C_SUCCESS
Definition patience.sh:71
const $C_INCORRECT_CARD_NAME
The given name is not a valid card name.
Definition patience.sh:89
$i
Counter for the number of rounds already done.
removeFromOpenStacks($arg1, $arg2)
Definition patience.sh:866
dealBlinds()
Deal the blind cards on the board.
Definition patience.sh:745
printBoard($arg1, $arg2)
Definition patience.sh:414
terminalPurpleForeground()
Sets the terminal text color to purple.
Definition patience.sh:131
printCredits()
Print the credits of the game and perform a short "animation".
Definition patience.sh:706
rotateDeck($arg1)
Definition patience.sh:317
printHelp($arg1)
Print the help function and handles help navigation through the help.
Definition patience.sh:512
getBottomCard($arg1)
Definition patience.sh:808
reindexDeck()
Reindex the deck, i.e. remove gaps in array.
Definition patience.sh:268
moveDeckToSuitStack()
Definition patience.sh:839
terminalBlackBackground()
Sets the terminal background color to black.
Definition patience.sh:138
debugPrintDeck()
Print the cards in the deck for debug purposes.
Definition patience.sh:165
terminalCyanForeground()
Sets the terminal text color to cyan.
Definition patience.sh:133
fillOpen()
Definition patience.sh:764
removeFromDeck($arg1)
Definition patience.sh:250
populateDeck()
Fill the deck with all the cards.
Definition patience.sh:306
isValidSuitMove($arg1)
Definition patience.sh:1009
terminalUnderline()
Sets the terminal to print underlined text.
Definition patience.sh:157
cardToName($arg1)
Definition patience.sh:1239
$deckFront
The card on the front of the deck.
Definition patience.sh:1309
$blindStacks
The blind cards of each column.
Definition patience.sh:1306
removeFromDeckOpen($arg1)
Definition patience.sh:193
terminalGreenBackground()
Sets the terminal background color to green.
Definition patience.sh:142
init()
Initialise the game.
Definition patience.sh:1325
$user_msg1
Text for the user message, first row.
Definition patience.sh:1311
$deckOpen
The open cards on the unplayed deck.
Definition patience.sh:1308
checkTerminalSize($arg1, $arg2)
Definition patience.sh:1370
main()
Main function.
Definition patience.sh:1382
moveCards($arg1, $arg2)
Definition patience.sh:1025
terminalBlueForeground()
Sets the terminal text color to blue.
Definition patience.sh:129
getTopCard($arg1)
Definition patience.sh:824
removeFromSuit($arg1)
Definition patience.sh:886
isValidBoardMove($arg1, $arg2)
Definition patience.sh:975
const $C_UNINIT
Bool is not initialised.
Definition patience.sh:56
terminalPurpleBackground()
Sets the terminal background color to purple.
Definition patience.sh:148
$marginTop
How much margin to keep to the top of the terminal to center the board.
Definition patience.sh:1317
$deck
The deck of cards.
Definition patience.sh:1305
isGameWon()
Definition patience.sh:733
terminalBold()
Sets the terminal to print bold text.
Definition patience.sh:155
turnBlind($arg1)
Definition patience.sh:784
nameToCard($arg1)
Definition patience.sh:1210
shuffleDeck()
Move the cards in the deck to a random new position.
Definition patience.sh:288
$suitStack
Stacks of cards having been sorted by suit.
Definition patience.sh:1310
printScore()
Helper function to print the board. It handles the coloring of the board.
Definition patience.sh:501
$openStacks
The open cards of each column.
Definition patience.sh:1307
terminalBlackForeground()
Sets the terminal text color to black.
Definition patience.sh:121
calculateTopMargin($arg1, $arg2)
Definition patience.sh:1287
$useColoredTerminal
Indicates if terminal colors should be used.
Definition patience.sh:119
$user_msg2
Text for the user message, second row.
Definition patience.sh:1313
terminalClearColors()
Sets the terminal to reset the colors to the default.
Definition patience.sh:159
terminalBrownBackground()
Sets the terminal background color to brown.
Definition patience.sh:144
$score
Current score.
Definition patience.sh:1322
printSuitsStack()
Print the suit stacks as a string, if empty print the suit symbol.
Definition patience.sh:356
printDeck($arg1)
Definition patience.sh:379
getCardColor($arg1)
Definition patience.sh:966
printCard($arg1, $arg2)
Definition patience.sh:667
initSuitStack()
Initialise the suitstacks.
Definition patience.sh:798
setBoardBackground()
Sets the background of the board patience is played on.
Definition patience.sh:161
terminalBrownForeground()
Sets the terminal text color to brown.
Definition patience.sh:127
$nDeckIterations
How many times has the deck been rotated (viewed all cards and started over).
Definition patience.sh:1320
const $C_FALSE
False value.
Definition patience.sh:60
terminalRedBackground()
Sets the terminal background color to red.
Definition patience.sh:140
terminalGreenForeground()
Sets the terminal text color to green.
Definition patience.sh:125
terminalBlueBackground()
Sets the terminal background color to blue.
Definition patience.sh:146
printBoardContent()
Helper function to print the board. It handles the coloring of the board. arguments same as args to p...
Definition patience.sh:496
terminalGreyBackground()
Sets the terminal background color to gray.
Definition patience.sh:152
initOpen()
Initialise the open stacks on the board.
Definition patience.sh:896
confirmAction($arg1)
Definition patience.sh:1353
autoMoveToSuitstacks()
Try to put all available cards on the suitstacks.
Definition patience.sh:1161
removeFromDeckFront($arg1, $arg2)
Remove cards from the front of the deck.
Definition patience.sh:218
findCard($arg1)
Definition patience.sh:911
getCardValue($arg1)
Definition patience.sh:950
terminalRedForeground()
Sets the terminal text color to red.
Definition patience.sh:123
terminalGreyForeground()
Sets the terminal text color to gray.
Definition patience.sh:135
$marginLeft
How much margin to keep to the left of the terminal to center the board.
Definition patience.sh:1315
getCardSuit($arg1)
Definition patience.sh:958
const $C_TRUE
True value.
Definition patience.sh:58
terminalCyanBackground()
Sets the terminal background color to cyan.
Definition patience.sh:150
cleanup()
Cleanup remnants of the old game before starting a new.
Definition patience.sh:1341
calculateLeftMargin($arg1, $arg2)
Definition patience.sh:1270