#!/bin/bash 

##############################################################################
#         Author: Erik Hamera alias Yokotashi                                #
#         contact: lhc (at) kanal (dot) ucw (dot) cz                         #
#                  lhc (at) chapadla (dot) cz                                #
#         License: GNU GPL                                                   # 
##############################################################################   
# Algorithms are suboptimal, but thinking time is much longer, than          #
# computing time ...                                                         #
#                                                                            # 
# Enjoy the game                                                             #
##############################################################################

#play_size_x=5;
#play_size_y=5;
play_size_x=6;
play_size_y=6;

# #----------------- 5x5 -----------------
# play=( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 );
# play_results=( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 );
# play_remove=( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 );
# play_tmp_col=( 0 0 0 0 0 )
# play_tmp_row=( 0 0 0 0 0 ) 
# #able to have non-square playground

#----------------- 6x6 -----------------
play=( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 );
play_results=( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 );
play_remove=( 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 );
play_tmp_col=( 0 0 0 0 0 0 )
play_tmp_row=( 0 0 0 0 0 0 ) 
#able to have non-square playground


cell_size_x=8; #without border
cell_add_y=1; #without border, adds lines up and down from number

# ------------------ algorithm -------------------
# any 3 identical number in row or column will generate 3-times bigger number in
# the middle and nothing on the sides
#
# if any number is in more than 1 triplet, it's used on every triplet
#
# if some number should disapper and be tripled together, it will be tripled
#
# therefore:
# 0. wait for key (w a s d, e, q, something for store the state and quit)
#    e = "don't move, just collect triplets", q = "quit"
# 1. move numbers to one of 4 edges (like in 2048 game) in play array
# 2. found all triplets and write tripled numbers to the play_results array and
#    removed numbers to the play_remove array (as 1s)
# 3. remove numbers from the play_remove array
# 4. insert numbers from the play_result (even if it have been removed before)
# 5. reset the play_results and the play_remove arrays to 0s
# 6. generate random 1 or 3
# 7. draw
# 8. wait for key
# ----------------- preinitialize ----------------

pls_x=$((play_size_x - 1))
pls_y=$((play_size_y - 1))

# ------------------ functions --------------------

spaces() {
	for a in `seq 1 "$1"`; do
		echo -n ' '
	done
}

number() {
	# $1 number to print
	# $cell_size_x desired size of that number
	#echo "$1"
	if [ "$1" = "0" ]; then
		spaces "$cell_size_x";
	else
		sp="$(echo "$cell_size_x-l($1+0.5)/l(10)"|bc -l|sed 's/\..*//')"
		sp_post="$(expr "$sp" / 2)"
		sp_pre=$((sp - sp_post))
		spaces "$sp_pre"
		echo -n "$1"
		spaces "$sp_post"
	fi
}

underline() {
	length=$((play_size_x * ( cell_size_x + 1 ) + 1 ));
        for a in `seq 1 "$length"`; do
                echo -n '-'
        done
}

draw_a_cell() {
	# $1 x position
	# $2 y position
	lin=$(($2 + $1 * play_size_x))
	echo "X $1 $2 $lin XX ${play[$lin]}"
	for a in `seq 1 "$cell_add_y"`; do
		spaces "$cell_size_x"
		echo '|'
	done
	number "${play[$lin]}";
	echo '|';
	for a in `seq 1 "$cell_add_y"`; do
                spaces "$cell_size_x"
                echo '|'
        done
}

draw_a_cell_line() {
	# $1 x position
	#lin=$(($2 + $1 * play_size_x))
	#echo "X $1 $2 $lin XX ${play[$lin]}"

	for a in `seq 1 "$cell_add_y"`; do
		echo -n '|'
		for a in `seq 1 "$play_size_x"`; do
			spaces "$cell_size_x"
			echo -n '|'
		done
	done
	echo

	echo -n '|'
	for x in `seq 0 "$pls_x"`; do
		lin=$((x + $1 * play_size_x))
		number "${play[$lin]}";
		echo -n '|';
	done
	echo

	for a in `seq 1 "$cell_add_y"`; do
		echo -n '|'
		for a in `seq 1 "$play_size_x"`; do
			spaces "$cell_size_x"
			echo -n '|'
		done
        done
	echo

	underline
	echo
}

draw() {
	#echo "$pls_x $pls_y"

	underline
	echo

	for y in `seq 0 "$pls_y"`; do
		draw_a_cell_line "$y"
	done
}

show_help() {
	echo
	echo "Keys w,a,s, or d moves all numbers to top, left, bottom, or right side."
	echo "Key e does nothing in thet step (and any other key too)."
	echo "Triplets are found, and one '1' or '3' number added at random place after"
	echo "each step."
	echo "Three identical numbers in the row or column are added to the middle cell."
	echo "You can get more if you use one or more numbers twice: 3333 -> _99_, or less"
	echo "if you use more triplets with common middle cell:   1      _"
	echo "                                                   111 -> _3_"
	echo "                                                    1      _"
	echo "Therefore all numbers you'll see will be powers of 3."
	echo
	echo "Pres q for quit. It will ask for confirm (type 'quit' and enter), because"
	echo "unexpected quit would be annoying."
}


move() {
	# $1 direction - wasd ~ up, left, down, right
	if [ "$1" = "a" ]; then
		for line in `seq 0 "$pls_y"`; do
			col=0;
			for x in `seq 0 "$pls_x"`; do
				lin=$((x + line * play_size_x))
				if [ "${play[$lin]}" != "0" ]; then
					play_tmp_row[$col]="${play[$lin]}"
					col=$((col + 1))
				fi
			done
			for x in `seq 0 "$pls_x"`; do
				lin=$((x + line * play_size_x))
				play[$lin]=${play_tmp_row[$x]}
				play_tmp_row[$x]=0
			done
		done		
	fi

	if [ "$1" = "d" ]; then
		for line in `seq 0 "$pls_y"`; do
			col="$pls_x";
			for x in `seq "$pls_x" -1 0`; do
				lin=$((x + line * play_size_x))
				if [ "${play[$lin]}" != "0" ]; then
					play_tmp_row[$col]="${play[$lin]}"
					col=$((col - 1))
				fi
			done
			for x in `seq "$pls_x" -1 0`; do
				lin=$((x + line * play_size_x))
				play[$lin]=${play_tmp_row[$x]}
				play_tmp_row[$x]=0
			done
		done		
	fi

	if [ "$1" = "w" ]; then
		for column in `seq 0 "$pls_x"`; do
			row=0;
			for y in `seq 0 "$pls_y"`; do
				lin=$((column + y * play_size_x))
				if [ "${play[$lin]}" != "0" ]; then
					play_tmp_col[$row]="${play[$lin]}"
					row=$((row + 1))
				fi
			done
			for y in `seq 0 "$pls_y"`; do
				lin=$((column + y * play_size_x))
				play[$lin]=${play_tmp_col[$y]}
				play_tmp_col[$y]=0
			done
		done		
	fi

	if [ "$1" = "s" ]; then
		for column in `seq 0 "$pls_x"`; do
			row="$pls_y";
			for y in `seq "$pls_y" -1 0`; do
				lin=$((column + y * play_size_x))
				if [ "${play[$lin]}" != "0" ]; then
					play_tmp_col[$row]="${play[$lin]}"
					row=$((row - 1))
				fi
			done
			for y in `seq "$pls_y" -1 0`; do
				lin=$((column + y * play_size_x))
				play[$lin]=${play_tmp_col[$y]}
				play_tmp_col[$y]=0
			done
		done		
	fi

	if [ "$1" = "q" ]; then
		echo
		echo "If you want to quit, type "quit" and enter."
		read input
		if [ "$input" = "quit" ]; then
			exit;
		fi
	fi

	if [ "$1" = "h" ]; then
		echo
		show_help
		echo
		echo "You can continue with playing"
		draw;
		read -n1 input;	
	fi

} 

read_play() {
	# $1 x
	# $2 y
	# $pls_x - maximum in x
	# $pls_y - maximum in y
	# access over boundaries returns 0
	
	if [ "$1" -lt "0" ]; then
		#echo ">>$1<< lt1" >/dev/stderr
		return 0;
	fi
	if [ "$2" -lt "0" ]; then
		#echo ">>$2<< lt2" >/dev/stderr
		return 0;
	fi
	if [ "$1" -gt "$pls_x" ]; then
		#echo ">>$1<< gt1" >/dev/stderr
		return 0;
	fi
	if [ "$2" -gt "$pls_y" ]; then
		#echo ">>$2<< gt2" >/dev/stderr
		return 0;
	fi
	lin=$(($1 + $2 * play_size_x))
	#echo ">>$1<< >>$2<< >>$lin<<" >/dev/stderr
	#echo "RT:${play[$lin]}" >/dev/stderr
	#return "${play[$lin]}"
	echo "${play[$lin]}"
}

read_play_up(){
		x=$1
		y=$(($2 - 1 ))
		read_play "$x" "$y"
}

read_play_down(){
		x=$1
		y=$(($2 + 1 ))
		read_play "$x" "$y"
}

read_play_left(){
		x=$(($1 - 1 ))
		y=$2
		read_play "$x" "$y"
}

read_play_right(){
		x=$(($1 + 1 ))
		y=$2
		read_play "$x" "$y"
}

write_playresults(){
	# $1 x
	# $2 y
	# $3 value
	# $pls_x - maximum in x
	# $pls_y - maximum in y
	# access over boundaries is ignored
	
	if [ "$1" -lt "0" ]; then
		#echo ">>$1<< lt1" >/dev/stderr
		return ;
	fi
	if [ "$2" -lt "0" ]; then
		#echo ">>$2<< lt2" >/dev/stderr
		return ;
	fi
	if [ "$1" -gt "$pls_x" ]; then
		#echo ">>$1<< gt1" >/dev/stderr
		return ;
	fi
	if [ "$2" -gt "$pls_y" ]; then
		#echo ">>$2<< gt2" >/dev/stderr
		return ;
	fi
	lin=$(($1 + $2 * play_size_x))
	#echo ">>$1<< >>$2<< >>$lin<<" >/dev/stderr
	#echo "RT:${play[$lin]}" >/dev/stderr
	#return "${play[$lin]}"
	play_results[$lin]="$3"
}

write_playremove(){
	# $1 x
	# $2 y
	# $3 value
	# $pls_x - maximum in x
	# $pls_y - maximum in y
	# access over boundaries is ignored
	
	if [ "$1" -lt "0" ]; then
		#echo ">>$1<< lt1" >/dev/stderr
		return ;
	fi
	if [ "$2" -lt "0" ]; then
		#echo ">>$2<< lt2" >/dev/stderr
		return ;
	fi
	if [ "$1" -gt "$pls_x" ]; then
		#echo ">>$1<< gt1" >/dev/stderr
		return ;
	fi
	if [ "$2" -gt "$pls_y" ]; then
		#echo ">>$2<< gt2" >/dev/stderr
		return ;
	fi
	lin=$(($1 + $2 * play_size_x))
	#echo ">>$1<< >>$2<< >>$lin<<" >/dev/stderr
	#echo "RT:${play[$lin]}" >/dev/stderr
	#return "${play[$lin]}"
	play_remove[$lin]="$3"
}

write_playremove_horizontal(){
	# $1 x
	# $2 y
	# $pls_x - maximum in x
	# $pls_y - maximum in y
	
	write_playremove "$(($1 - 1))" "$2" "1" 
	write_playremove "$(($1 + 1))" "$2" "1" 

}

write_playremove_vertical(){
	# $1 x
	# $2 y
	# $pls_x - maximum in x
	# $pls_y - maximum in y
	
	write_playremove "$1" "$(($2 - 1))" "1" 
	write_playremove "$1" "$(($2 + 1))" "1" 

}

reset_playremove(){
	max="$((play_size_x * play_size_y - 1))";
	for i in `seq 0 "$max"`; do
		play_remove[$i]="0";
	done
}

reset_playresults(){
	max="$((play_size_x * play_size_y - 1))";
	for i in `seq 0 "$max"`; do
		play_results[$i]="0";
	done
}

found_triplets() {
	for y in `seq 0 "$pls_y"`; do
		for x in `seq 0 "$pls_x"`; do
			this_point="$(read_play "$x" "$y")"
			#echo "THIS POINT: $this_point"
			#echo "THIS POINT DIRECT: $(read_play "$x" "$y")"
			if [ "$this_point" != "0" ]; then
				#see around this point
				if [ "$(read_play_up "$x" "$y")" = "$this_point" -a "$(read_play_down "$x" "$y")" = "$this_point" ]; then
					#echo "triplet | at $x $y"
					write_playresults "$x" "$y" "$((this_point * 3))"
					write_playremove_vertical "$x" "$y"
				fi
				if [ "$(read_play_left "$x" "$y")" = "$this_point" -a "$(read_play_right "$x" "$y")" = "$this_point" ]; then
					#echo "triplet - at $x $y"
					write_playresults "$x" "$y" "$((this_point * 3))"
					write_playremove_horizontal "$x" "$y"
				fi
			fi
		done
	done
}

project_playremove(){
	max="$((play_size_x * play_size_y - 1))";
	for i in `seq 0 "$max"`; do
		if [ "${play_remove[$i]}" != "0" ]; then
			play[$i]="0"
		fi
	done
}

project_playresults(){
	max="$((play_size_x * play_size_y - 1))";
	for i in `seq 0 "$max"`; do
		if [ "${play_results[$i]}" != "0" ]; then
			play[$i]="${play_results[$i]}"
		fi
	done
}

add_random_number(){
	countfree=0
	max="$((play_size_x * play_size_y - 1))";
	for i in `seq 0 "$max"`; do
		if [ "${play[$i]}" = "0" ]; then
			countfree="$((countfree + 1))"
		fi
	done
	if [ "$countfree" = "0" ]; then
		echo "I have bad news for you. You have lost, therefore yo're going to divide by 0."
	fi
	add_to="$(( RANDOM % countfree + 1 ))"
	which_number="$(( ( RANDOM % 2 ) * 2 + 1 ))"
	#echo -n "adding to random: $add_to number: $which_number position:"
	countfree=0
	for i in `seq 0 "$max"`; do
		if [ "${play[$i]}" = "0" ]; then
			countfree="$((countfree + 1))"
			if [ "$countfree" = "$add_to" ]; then
				play[$i]="$which_number"
				#echo " $i"
			fi
		fi
	done
}

# --------------- MAIN ------------------
echo "Welcome in 2187."
echo "For help run 'game2187 -h' or press 'h' during game."
echo "For Lovecraftian abominations on t-shirts see http://chapadla.cz."

if [ "x$1" = "x-h" ]; then
	show_help
	exit
fi

add_random_number;
draw;
while read -n1 input; do
	move "$input";
		#draw;
	found_triplets;
		#for a in `seq 0 24`; do echo -n "${play_results[$a]} "; done;echo
		#for a in `seq 0 24`; do echo -n "${play_remove[$a]} "; done;echo

	project_playremove;
	project_playresults;
	reset_playremove;
	reset_playresults;
	add_random_number;
	echo; echo;
	draw;
done 
