# An implementation of a Tetris-variant called Xixit in pure gnuplot

# set up plot area, title, instructions

fs = GPVAL_TERM eq "x11" ? 20 : GPVAL_TERM eq "qt" || GPVAL_TERM eq "windows" ? 15 : 12

reset
unset tics
unset key
set title "X I X I T" font ",".fs

set label 1 \
	"Keys:\n\nLeft/Right: Move\n\nDown: Drop\n\nA/S: Rotate\n\n".\
	"+/-: Speed\n\np: Pause/Unpause\n\nEsc: Quit" \
	at graph -0.5, 0.95 font ",".fs center

foo = rand(time(1))

xmin = ymin = 0
xmax = 8
ymax = 17

set xr [xmin-0.5:xmax+0.5]
set yr [ymin-0.5:ymax+0.5]
set size ratio -1

# shorthands used below
P = 1/4.
Q = sqrt(3)/4
R = 2/5.

# definition of "gems"
max_kinds = 5

objt_1 = "oi=%d; ox=%d; oy=%d; set obj oi circle at ox, oy radius 0.4 fc rgb '#bf0000' fs solid border lc rgb '#7f0000';"
objt_2 = "oi=%d; ox=%d; oy=%d; set obj oi rect center ox, oy size 0.8,0.8 fc rgb '#00bf00' fs solid border lc rgb '#007f00'"
objt_3 = "oi=%d; ox=%d; oy=%d; set obj oi poly from ox-Q, oy-P to ox, oy+Q to ox+Q, oy-P to ox-Q, oy-P fs solid border lc rgb '#0000bf' fc rgb '#00007f'"
objt_4 = "oi=%d; ox=%d; oy=%d; set obj oi poly from ox-Q, oy+P to ox, oy-Q to ox+Q, oy+P to ox-Q, oy+P fs solid border lc rgb '#afafaf' fc rgb '#5f5f5f'"
objt_5 = "oi=%d; ox=%d; oy=%d; set obj oi poly from ox-R, oy to ox, oy+R to ox+R, oy to ox, oy-R to ox-R, oy fs solid border lc rgb '#af00af' fc rgb '#6f006f'"

# initializing constants and variables
pause = 0.005
wait = 50

iter = 0

score = 0

level = 1

level_limit(level) = level**2 * 4

locked = 0
key_action = ""
paused = 0

alive = 1

do_redraw = 0

found = 0
check_for_more = 0
cascade_bonus = 1

right = { 1,  0}
left  = {-1,  0}
down  = { 0, -1}
up    = { 0,  1}

# For every point of the grid
# there is a block_x_y variable for storing which kind of gem
# occupies that block, and a del_x_y variable which will be set 
# by check_threes if that particular gem is to be eliminated 
# in the current iteration.
# Additionally, there is one bottom_x variable for each column,
# telling how high the stack of stationary gems is in that column.
# We have to put up with these because gnuplot doesn't have arrays.

block(i, j) = sprintf("block_%d_%d", int(i), int(j))
del(i, j)   = sprintf("del_%d_%d",   int(i), int(j))
bottom(i)   = sprintf("bottom_%d",   int(i))
do for [i=xmin:xmax] {
	do for [j=ymin:ymax] {
		eval block(i, j)." = 0"
		eval del(i, j).  " = 0"
	}
	eval bottom(i)." = 0" 
}


# hack
bottom_100 = 100

# keybindings
bind "Right"       "locked = 1; key_action = move_right"
bind "Left"        "locked = 1; key_action = move_left"
bind "Down"        "locked = 1; key_action = move_down"
bind "Up"          "foo = 1"                             # normally it would scroll the plot area up, 
                                                         # disable it here (dummy command required)
bind "a"           "locked = 1; key_action = rotate"     # rotate
bind "s"           "locked = 1; key_action = unrotate"   # unrotate
bind "p"           "paused = !paused"

bind "Escape"      "alive = 0"
bind "KP_Add"      "wait = wait > 1 ? wait - 1 : 1"
bind "KP_Subtract" "wait = wait + 1"

# "procedures" and "functions"
get_bot_id(x) = sprintf("%d",int(x<=xmax && x>=xmin ? x : 100))

move_right = \
	"eval 'bottom = bottom_'.get_bot_id(real(current+right));".\
	"current = real(current) < xmax && imag(current) > bottom ? current + right : current;".\
	"eval redraw"

move_left = \
	"eval 'bottom = bottom_'.get_bot_id(real(current+left));".\
	"current = real(current) > xmin && imag(current) > bottom ? current + left  : current;".\
	"eval redraw;"

move_down = "eval is_space_left; current = space_left ? current + down  : current; eval redraw"

rotate   = "temp = tc_0; tc_0 = tc_1; tc_1 = tc_2; tc_2 = temp; eval redraw"
unrotate = "temp = tc_0; tc_0 = tc_2; tc_2 = tc_1; tc_1 = temp; eval redraw"
redraw   = \
	"do_redraw = 1;".\
	"do for [i=0:2] {".\
		"eval 'tc=tc_'.i;".\
		"eval 'objt=objt_'.tc;".\
		"eval sprintf(objt, 10+i, real(current), imag(current)+i);".\
	"}"

is_space_left = "eval 'bottom = '.bottom(real(current)); space_left = imag(current) > bottom"

make_current_permanent = \
	"do for [i=0:2] {".\
		"eval 'tc=tc_'.i;".\
		"eval 'objt=objt_'.tc;".\
		"ix = int(real(current));".\
		"iy = int(imag(current)+i);".\
		"eval sprintf(objt, 1000+100*ix+iy, ix, iy);".\
		"eval block(ix, iy).' = tc';".\
		"eval bottom(ix).' = '.bottom(ix).' + 1';".\
	"}"


init_next = \
	"current = int(xmax/2) + (ymax-2) * {0,1};".\
	"tc_0 = int(1+rand(0)*max_kinds);".\
	"tc_1 = int(1+rand(0)*max_kinds);".\
	"tc_2 = int(1+rand(0)*max_kinds);"

check_threes_single = \
	"xc = real(check_dir); yc = imag(check_dir);".\
	"xmin_ = xc>0 ? xmin : xmin - xc*2;".\
	"xmax_ = xc<0 ? xmax : xmax - xc*2;".\
	"ymin_ = yc>0 ? ymin : ymin - yc*2;".\
	"ymax_ = yc<0 ? ymax : ymax - yc*2;".\
	"do for [i=xmin_:xmax_] {".\
		"do for [j=ymin_:ymax_] {".\
			"eval 'is_equal =   '.block(i, j).' ".\
							"&& '.block(i, j).' == '.block(i+xc, j+yc).' ".\
							"&& '.block(i, j).' == '.block(i+xc*2, j+yc*2);".\
			"if (is_equal) {".\
				"eval del(i,      j     ).' = 1';".\
				"eval del(i+xc,   j+yc  ).' = 1';".\
				"eval del(i+xc*2, j+yc*2).' = 1';".\
				"score = score + 3 * cascade_bonus;".\
				"eval level_up;".\
				"found = 1;".\
			"}".\
		"}".\
	"}"

check_threes = \
	"check_dir = right     ; eval check_threes_single;".\
	"check_dir = right + up; eval check_threes_single;".\
	"check_dir =         up; eval check_threes_single;".\
	"check_dir = left  + up; eval check_threes_single;"

delete_threes = \
	"do for [i=xmin:xmax] {".\
		"eval 'bottom = '.bottom(i);".\
		"j = ymin;".\
		"while (j < bottom) {".\
			"eval 'del = '.del(i, j);".\
			"if (del) {".\
				"do for [k=j:bottom-1] {".\
					"eval block(i, k).' = '.block(i, k+1);".\
					"eval   del(i, k).' = '.  del(i, k+1);".\
					"eval 'block = '.block(i, k);".\
					"if (block) {".\
						"eval 'objt=objt_'.block;".\
						"eval sprintf(objt, 1000+100*i+k, i, k);".\
					"}".\
				"}".\
				"eval 'unset obj 1000+100*'.i.'+'.(bottom-1);".\
				"eval block(i, bottom-1).' = 0';".\
				"eval   del(i, bottom-1).' = 0';".\
				"eval bottom(i).' = '.bottom(i).' - 1';".\
				"bottom = bottom - 1;".\
			"} else {".\
				"j = j + 1;".\
			"}".\
		"}".\
	"}"

level_up = \
	"if (score > level_limit(level) && wait > 1) {".\
		"wait = wait - 1;".\
		"level = level + 1;".\
	"}" 
	
game_over = \
	"set label 2 'G A M E   O V E R !' at xmax/2, ymax/2 font ','.fs center;".\
	"alive = 0"



show_score(sc, l) = \
	sprintf("set label 3 'LEVEL:\n\n%5d\n\nSCORE:\n\n%5d' at graph 1.05, 0.95 font ','.fs", l, sc)

# set up initial state
eval show_score(score, level)
eval init_next

eval redraw

# finally show the initial state
plot -1

# now the main loop
while (alive) {
	pause pause
	if (!paused) {
		if (locked) {
			eval key_action
			locked = 0
		}

		if (check_for_more) {
			eval check_threes
			if (found) {
				pause pause*10
				eval delete_threes
				eval show_score(score, level)
				check_for_more = 1
				cascade_bonus = cascade_bonus * 2
				found = 0
			} else {
				check_for_more = 0
				cascade_bonus = 1
			}
		} else {
			if (iter >= wait) {
				iter = 0
				# gravity
				eval move_down
				if (!space_left) {
					eval make_current_permanent
					eval init_next
					eval is_space_left
					if (!space_left) {
						eval game_over
					}
					eval check_threes
					if (found) {
						eval delete_threes
						eval show_score(score, level)
						check_for_more = 1
						cascade_bonus = cascade_bonus * 2
						found = 0
					}
				}
				do_redraw = 1
			}
		}

		iter = iter + 1
		do_redraw = 1

		# and finally show the new state if needed
		if (do_redraw) {
			eval redraw
			do_redraw = 0
			replot
		}
	}
}

# clean up
undef block*
undef del*
undef bottom*
reset bind
reset
