random bash scripts
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

374 lines
9.1 KiB

  1. #!/usr/bin/env bash
  2. set -e
  3. #important variables
  4. declare -ia board # array that keeps track of game status
  5. declare -i pieces # number of pieces present on board
  6. declare -i score=0 # score variable
  7. declare -i flag_skip # flag that prevents doing more than one operation on
  8. # single field in one step
  9. declare -i moves # stores number of possible moves to determine if player lost
  10. # the game
  11. declare ESC=$'\e' # escape byte
  12. declare header="Bash 2048 v1.1 (https://github.com/mydzor/bash2048)"
  13. start_time=$(date +%s)
  14. declare -i start_time
  15. #default config
  16. declare -i board_size=4
  17. declare -i target=2048
  18. declare -i reload_flag=0
  19. declare config_dir="$HOME/.bash2048"
  20. #for colorizing numbers
  21. declare -a colors
  22. colors[2]=33 # yellow text
  23. colors[4]=32 # green text
  24. colors[8]=34 # blue text
  25. colors[16]=36 # cyan text
  26. colors[32]=35 # purple text
  27. colors[64]="33m\\033[7" # yellow background
  28. colors[128]="32m\\033[7" # green background
  29. colors[256]="34m\\033[7" # blue background
  30. colors[512]="36m\\033[7" # cyan background
  31. colors[1024]="35m\\033[7" # purple background
  32. colors[2048]="31m\\033[7" # red background (won with default target)
  33. exec 3>/dev/null # no logging by default
  34. trap "end_game 0 1" INT #handle INT signal
  35. #simplified replacement of seq command
  36. function _seq() {
  37. local cur=1
  38. local max
  39. local inc=1
  40. case $# in
  41. 1) ((max = "$1")) ;;
  42. 2)
  43. ((cur = "$1"))
  44. ((max = "$2"))
  45. ;;
  46. 3)
  47. ((cur = "$1"))
  48. ((inc = "$2"))
  49. ((max = "$3"))
  50. ;;
  51. esac
  52. while test "$max" -ge "$cur"; do
  53. printf "%i " "$cur"
  54. ((cur += inc))
  55. done
  56. }
  57. # print currect status of the game, last added pieces are marked red
  58. function print_board() {
  59. clear
  60. printf "%s pieces=%s target=%s score=%s\\n" "$header" "$pieces" "$target" "$score"
  61. printf "Board status:\\n" >&3
  62. printf "\\n"
  63. printf '/------'
  64. for l in "$(_seq 1 "$index_max")"; do
  65. printf '+------'
  66. done
  67. printf '\\\n'
  68. for l in "$(_seq 0 "$index_max")"; do
  69. printf '|'
  70. for m in "$(_seq 0 "$index_max")"; do
  71. if (("${board[l * $board_size + m]}")); then
  72. if ( (last_added=(l*board_size+m)) | (first_round=(l*board_size+m))); then
  73. printf '\033[1m\033[31m %4d \033[0m|' "${board[l * $board_size + m]}"
  74. else
  75. printf "\\033[1m\\033[${colors[${board[l * $board_size + m]}]}m %4d\\033[0m |" "${board[l * $board_size + m]}"
  76. fi
  77. printf " %4d |" "${board[l * $board_size + m]}" >&3
  78. else
  79. printf ' |'
  80. printf ' |' >&3
  81. fi
  82. done
  83. ((l == index_max)) || {
  84. printf '\n|------'
  85. for j in "$(_seq 1 "$index_max")"; do
  86. printf '+------'
  87. done
  88. printf '|\n'
  89. printf '\n' >&3
  90. }
  91. done
  92. printf '\n\\------'
  93. for l in "$(_seq 1 "$index_max")"; do
  94. printf '+------'
  95. done
  96. printf '/\n'
  97. }
  98. # Generate new piece on the board
  99. # inputs:
  100. # $board - original state of the game board
  101. # $pieces - original number of pieces
  102. # outputs:
  103. # $board - new state of the game board
  104. # $pieces - new number of pieces
  105. function generate_piece() {
  106. while true; do
  107. ((pos = RANDOM % fields_total))
  108. ((board[pos])) || {
  109. ((value = RANDOM % 10 ? 2 : 4))
  110. board["$pos]=$value"
  111. last_added=$pos
  112. printf "Generated new piece with value %i at position [%i]\\n" "$value" "$pos" >&3
  113. break
  114. }
  115. done
  116. ((pieces++))
  117. }
  118. # perform push operation between two pieces
  119. # inputs:
  120. # $1 - push position, for horizontal push this is row, for vertical column
  121. # $2 - recipient piece, this will hold result if moving or joining
  122. # $3 - originator piece, after moving or joining this will be left empty
  123. # $4 - direction of push, can be either "up", "down", "left" or "right"
  124. # $5 - if anything is passed, do not perform the push, only update number
  125. # of valid moves
  126. # $board - original state of the game board
  127. # outputs:
  128. # $change - indicates if the board was changed this round
  129. # $flag_skip - indicates that recipient piece cannot be modified further
  130. # $board - new state of the game board
  131. function push_pieces() {
  132. case $4 in
  133. "up")
  134. ((first = "$2" * board_size + "$1"))
  135. ((second = ("$2" + "$3") * board_size + "$1"))
  136. ;;
  137. "down")
  138. ((first = (index_max - "$2") * board_size + "$1"))
  139. ((second = (index_max - "$2" - "$3") * board_size + "$1"))
  140. ;;
  141. "left")
  142. ((first = "$1" * board_size + "$2"))
  143. ((second = "$1" * board_size + ("$2" + "$3")))
  144. ;;
  145. "right")
  146. ((first = "$1" * board_size + (index_max - "$2")))
  147. ((second = "$1" * board_size + (index_max - "$2" - "$3")))
  148. ;;
  149. esac
  150. (("${board[$first]}")) || {
  151. (("${board[$second]}")) && {
  152. if test -z "$5"; then
  153. board["$first]=${board[$second]}"
  154. ((board["$second"] = 0))
  155. ((change = 1))
  156. printf "move piece with value %s from [%s] to [%s]\\n" "${board[$first]}" "$second" "$first" >&3
  157. else
  158. ((moves++))
  159. fi
  160. return
  161. }
  162. return
  163. }
  164. (("${board[$second]}")) && ((flag_skip = 1))
  165. if (("${board[$first]}" == board[second])); then
  166. if test -z "$5"; then
  167. ((board[first] *= 2))
  168. ((board[first] == target)) && end_game 1
  169. ((board[second] = 0))
  170. ((pieces -= 1))
  171. ((change = 1))
  172. ((score += "${board[$first]}"))
  173. printf "joined piece from [%s] with [%s], new value=%s\\n" "$second" "$first" "${board[$first]}" >&3
  174. else
  175. ((moves++))
  176. fi
  177. fi
  178. }
  179. function apply_push() {
  180. printf "\\n\\ninput: %s key\\n" "$1" >&3
  181. for i in "$(_seq 0 "$index_max")"; do
  182. for j in "$(_seq 0 "$index_max")"; do
  183. flag_skip=0
  184. ((increment_max = index_max - j))
  185. for k in "$(_seq 1 "$increment_max")"; do
  186. ((flag_skip)) && break
  187. push_pieces "$i" "$j" "$k" "$1" "$2"
  188. done
  189. done
  190. done
  191. }
  192. function check_moves() {
  193. ((moves = 0))
  194. apply_push up fake
  195. apply_push down fake
  196. apply_push left fake
  197. apply_push right fake
  198. }
  199. function key_react() {
  200. ((change = 0))
  201. read -d -r '' -sn 1
  202. if test "$REPLY" = "$ESC"; then
  203. read -r -d '' -sn 1 -t1
  204. test "$REPLY" = "[" && {
  205. read -r -d '' -sn 1 -t1
  206. case $REPLY in
  207. A) apply_push up ;;
  208. B) apply_push down ;;
  209. C) apply_push right ;;
  210. D) apply_push left ;;
  211. esac
  212. }
  213. else
  214. case $REPLY in
  215. k) apply_push up ;;
  216. j) apply_push down ;;
  217. l) apply_push right ;;
  218. h) apply_push left ;;
  219. esac
  220. fi
  221. }
  222. function save_game() {
  223. rm -rf "$config_dir"
  224. mkdir "$config_dir"
  225. echo "${board[@]}" >"$config_dir/board"
  226. echo "$board_size" >"$config_dir/board_size"
  227. echo "$pieces" >"$config_dir/pieces"
  228. echo "$target" >"$config_dir/target"
  229. # echo "$log_file" > "$config_dir/log_file"
  230. echo "$score" >"$config_dir/score"
  231. echo "${first_round[0]}" >"$config_dir/first_round"
  232. }
  233. function reload_game() {
  234. printf "Loading saved game...\\n" >&3
  235. if test ! -d "$config_dir"; then
  236. return
  237. fi
  238. board=$(cat "$config_dir/board")
  239. board_size=("$(cat "$config_dir/board_size")")
  240. board=("$(cat "$config_dir/board")")
  241. pieces=("$(cat "$config_dir/pieces")")
  242. first_round=("$(cat "$config_dir/first_round")")
  243. target=("$(cat "$config_dir/target")")
  244. score=("$(cat "$config_dir/score")")
  245. fields_total=$((board_size * board_size))
  246. index_max=$((board_size - 1))
  247. }
  248. function end_game() {
  249. # count game duration
  250. end_time=$(date +%s)
  251. ((total_time = end_time - start_time))
  252. print_board
  253. printf "Your score: %s\\n" "${score[0]}"
  254. printf "This game lasted "
  255. if date --version >/dev/null 2>&1; then
  256. date -u -d @"$total_time" +%T
  257. else
  258. date -u -r "$total_time" +%T
  259. fi
  260. stty echo
  261. (("$1")) && {
  262. printf "Congratulations you have achieved %s\\n" "${target[0]}"
  263. exit 0
  264. }
  265. ( (test -z "$2")) && {
  266. read -n1 -p -r "Do you want to overwrite saved game? [y|N]: "
  267. test "$REPLY" = "Y" || test "$REPLY" = "y" && {
  268. save_game
  269. printf "\\nGame saved. Use -r option next to load this game.\\n"
  270. exit 0
  271. }
  272. test "$REPLY" = "" && {
  273. printf "\\nGame not saved.\\n"
  274. exit 0
  275. }
  276. }
  277. printf "\\nYou have lost, better luck next time.\\033[0m\\n"
  278. exit 0
  279. }
  280. function help() {
  281. cat <<END_HELP
  282. Usage: $1 [-b INTEGER] [-t INTEGER] [-l FILE] [-r] [-h]
  283. -b specify game board size (sizes 3-9 allowed)
  284. -t specify target score to win (needs to be power of 2)
  285. -l log debug info into specified file
  286. -r reload the previous game
  287. -h this help
  288. END_HELP
  289. }
  290. #parse commandline options
  291. while getopts "b:t:l:rh" opt; do
  292. case $opt in
  293. b)
  294. board_size="$OPTARG"
  295. (
  296. (board_size >= 3) &
  297. (board_size <= 9)
  298. ) || {
  299. printf "Invalid board size, please choose size between 3 and 9\\n"
  300. exit -1
  301. }
  302. ;;
  303. t)
  304. target="$OPTARG"
  305. if printf "obase=2;%s\\n" "${target[0]}" | bc | grep -e '^1[^1]*$'; then
  306. printf "Invalid target, has to be power of two\\n"
  307. exit -1
  308. fi
  309. ;;
  310. r) reload_flag="1" ;;
  311. h)
  312. help "$0"
  313. exit 0
  314. ;;
  315. l) exec 3>"$OPTARG" ;;
  316. \?)
  317. printf "Invalid option: -%s, try $0 -h\\n" "$opt" >&2
  318. exit 1
  319. ;;
  320. :)
  321. printf "Option -%s requires an argument, try $0 -h\\n" "$opt" >&2
  322. exit 1
  323. ;;
  324. esac
  325. done
  326. #init board
  327. ((fields_total = board_size * board_size))
  328. ((index_max = board_size - 1))
  329. for i in "$(_seq 0 "$fields_total")"; do board["$i]=0"; done
  330. ((pieces = 0))
  331. generate_piece
  332. first_round=${last_added[0]}
  333. generate_piece
  334. #load saved game if flag is set
  335. if test "$reload_flag" = "1"; then
  336. reload_game
  337. fi
  338. while true; do
  339. print_board
  340. key_react
  341. ((change)) && generate_piece
  342. first_round=-1
  343. ((pieces == fields_total)) && {
  344. check_moves
  345. ((moves == 0)) && end_game 0 #lose the game
  346. }
  347. done