Linux: 1 to 15 Number Puzzle Game Using Shell Script

Linux: 1 to 15 Number Puzzle Game Using Shell Script

Here I am sharing a 1 to 15 number puzzle game using a shell script for Linux/Unix systems. You can see the game in the featured GIF image of this article to have an idea about the game.

1 to 15 Number Puzzle Game Using Shell Script

To start the game press Enter and the numbers will shuffle in the board. Now you have to solve the puzzle by making the sequence from 1 to 15 using the arrow keys. Press an up-arrow key to move the digit up on the blank space and similarly down, left and right arrow keys will work. Press q to exit the game. Create the following script and give any name, for example, numpuzzle.sh.

numpuzzle.sh

#!/bin/bash

scriptname=${0##*/}

board=( {1..15} "" )         ## The basic board array
target=( "${board[@]}" )     ## A copy for comparison (the target)
empty=15                     ## The empty square
last=0                       ## The last move made
A=0 B=1 C=2 D=3              ## Indices into array of possible moves
topleft='\e[0;0H'            ## Move cursor to top left corner of window
nocursor='\e[?25l'           ## Make cursor invisible
normal=\e[0m\e[?12l\e[?25h   ## Resume normal operation
## Board layout is a printf format string
## At its most basic, it could be a simple:
fmt="$nocursor$topleft
     %2s  %2s   %2s  %2s
     %2s  %2s   %2s  %2s
     %2s  %2s   %2s  %2s
     %2s  %2s   %2s  %2s
"

## I prefer this ASCII board
fmt="\e[?25l\e[0;0H\n
\t+----+----+----+----+
\t|    |    |    |    |
\t| %2s | %2s | %2s | %2s |
\t|    |    |    |    |
\t+----+----+----+----+
\t|    |    |    |    |
\t| %2s | %2s | %2s | %2s |
\t|    |    |    |    |
\t+----+----+----+----+
\t|    |    |    |    |
\t| %2s | %2s | %2s | %2s |
\t|    |    |    |    |
\t+----+----+----+----+
\t|    |    |    |    |
\t| %2s | %2s | %2s | %2s |
\t|    |    |    |    |
\t+----+----+----+----+\n\n"

print_board() #@ What the name says
{
  printf "$fmt" "${board[@]}"
}

borders() #@ List squares bordering on the empty square
{
  ## Calculate x/y co-ordinates of the empty square
  local x=$(( ${empty:=0} % 4 )) y=$(( $empty / 4 ))
  ## The array, bordering, has 4 elements, corresponding to the 4 directions
  ## If a move in any direction would be off the board, that element is empty
  ##
  unset bordering      ## clear array before setting it
  [ $y -lt 3 ] && bordering[$A]=$(( $empty + 4 ))
  [ $y -gt 0 ] && bordering[$B]=$(( $empty - 4 ))
  [ $x -gt 0 ] && bordering[$C]=$(( $empty - 1 ))
  [ $x -lt 3 ] && bordering[$D]=$(( $empty + 1 ))
}

check() #@ Check whether puzzle has been solved
{
  ## Compare current board with target
  if [ "${board[*]}" = "${target[*]}" ]
  then
     ## Puzzle is completed, print message and exit
     print_board
     printf "\a\tCompleted in %d moves\n\n" "$moves"
     exit
  fi
}

move() #@ Move the square in $1
{
  movelist="$empty $movelist"    ## add current empty square to the move list
  moves=$(( $moves + 1 ))        ## increment move counter
  board[$empty]=${board[$1]}     ## put $1 into the current empty square
  board[$1]=""                   ## remove number from new empty square
  last=$empty                    ## .... and put it in old empty square
  empty=$1                       ## set new value for empty-square pointer
}

random_move() #@ Move one of the squares in the arguments
{
  ## The arguments to random_move are the squares that can be moved
  ## (as generated by the borders function)
  local sq
  while :
  do
     sq=$(( $RANDOM % $# + 1 ))
     sq=${!sq}
     [ $sq -ne ${last:-666} ] &&   ## do not undo last move
        break
  done
  move "$sq"
}
shuffle() 
{
  local n=0 max=$(( $RANDOM % 100 + 150 ))    ## number of moves to make
  while [ $(( n += 1 )) -lt $max ]
  do
     borders                                  ## generate list of possible moves
     random_move "${bordering[@]}"            ## move to one of them at random
  done
}

trap 'printf "$normal"' EXIT                  ## return terminal to normal state on exit

clear
print_board
echo
printf "
  Use the cursor keys to move the tiles around.
  The game is finished when you return to the
  position shown above.
  Try to complete the puzzle in as few moves
  as possible.
         Press \e[1mENTER\e[0m to continue
"

shuffle                                      ## randomize board
moves=0                                      ## reset move counter
read -s                                      ## wait for user
clear                                        ## clear the screen

while :
do
   borders
   print_board
   printf "\t   %d move" "$moves"
   [ $moves -ne 1 ] && printf "s"
   check

   ## read a single character without waiting for <ENTER>
   read -sn1 -p $'        \e[K' key

   ## The cursor keys generate three characters: ESC, [ and A, B, C, or D;
   ## this loop will run three times for each press of a cursor key
   ## but will not do anything until it receives a letter
   ## from the cursor key (or entered directly with A etc.), or a 'q' to exit
   case $key in
     A) [ -n "${bordering[$A]}" ] && move "${bordering[$A]}" ;;
     B) [ -n "${bordering[$B]}" ] && move "${bordering[$B]}" ;;
     C) [ -n "${bordering[$C]}" ] && move "${bordering[$C]}" ;;
     D) [ -n "${bordering[$D]}" ] && move "${bordering[$D]}" ;;
     q) echo; break ;;
   esac
done

Make the file executable

chmod +x numpuzzle.sh

Test

./numpuzzle.sh

The output would be the same as shown in the featured image of this article.

See also: