#!/bin/bash
//usr/bin/env stty -echo; sudo staprun -T 30 `stap -p4 "$0"`; stty echo; exit

// 2048.stp - written by NeoCat
// Inspired by https://github.com/gabrielecirulli/2048/
// Licensed under MIT license

// Join the numbers and get to the 2048 tile!
// HOW TO PLAY: Use your arrow keys to move the tiles. When two tiles with the same number touch, they merge into one!

global k, F, F_copy, clear, changed, score
global animate, moving, moved, spawning

function color(i) {
	if (i < 0)
		printf("\033[0m");
	else
		printf("\033[48;5;%dm\033[38;5;%dm",
		       i == 0 ? 96 :
		       i == 1 ? 95 :
		       i == 2 ? 252 :
		       i == 4 ? 225 :
		       i == 8 ? 218 :
		       i == 16 ? 136 :
		       i == 32 ? 202 :
		       i == 64 ? 196 :
		       i == 128 ? 214 :
		       i == 256 ? 178 :
		       i == 512 ? 178 : 136,
		       i < 8 ? 0 : 255)
}
function cmove(x,y) {
	printf("\033[%d;%dH", y, x)
}
function disp_number(n, j) {
	if (!n || j != 1)
		printf("      ")
	else if (n < 100)
		printf(" %3d  ", n)
	else
		printf(" %4d ", n)
}
function display() {
	cmove(0,0)
	frame = "\033[48;5;95m                                  \033[0m"
	printf("%s\n", frame)
	for (y = 0; y < 4; y++) {
		for (j = 0; j < 3; j++) {
			for (x = 0; x < 4; x++) {
				i = x + y*4
				color(1)
				printf("  ")
				color(F[i])
				disp_number(F[i], j)
			}
			color(1)
			printf("  ")
			color(-1)
			printf("\n")
		}
		printf("%s\n", frame)
	}
}

function spawn() {
	cnt = 0
	for (i = 0; i < 16; i++)
		if (!F[i])
			cnt++;
	if (cnt == 0) return -1
	r = get_cycles() % cnt
	cnt = 0
	for (i = 0; i < 16; i++) {
		if (!F[i])
			if (cnt++ == r) {
				F[i] = get_cycles()%20 ? 2 : 4
				return i
			}
	}
	return -1
}

function move(from, to) {
	if (!F[from])
		return 0
	if (!F[to]) {
		moving[from] = F[from]
		moved = 1
		F[to] = F[from]
		F[from] = 0
		return 1
	}
	if (F[from] == F[to] && !changed[from] && !changed[to]) {
		moving[from] = F[from]
		moved = 1
		F[to] += F[from]
		F[from] = 0
		changed[to] = 1
		score += F[to]
		if (F[to] == 2048) clear = 1
		return 1
	}
	return 0
}
function up() {
	updated = 0
	for (i = 4; i < 16; i++)
		updated = move(i, i-4) || updated
	return updated
}
function down() {
	updated = 0
	for (i = 11; i >= 0; i--)
		updated = move(i, i+4) || updated
	return updated
}
function left() {
	updated = 0
	for (i = 0; i < 16; i++)
		if (i % 4)
			updated = move(i, i-1) || updated
	return updated
}
function right() {
	updated = 0
	for (i = 15; i >= 0; i--)
		if (i % 4 != 3)
			updated = move(i, i+1) || updated
	return updated
}

probe begin {
	for (i = 0; i < 16; i++)
		F[i] = 0
	spawn()

	printf("\033[2J") // clear
	display()
}

probe timer.ms(30) {
	if (!animate) next
	if (animate > 3) {
		display()
		animate = 0
		next_move()
		next
	}
	if (k == 103) {
		dx = 0
		dy = -animate
	}
	if (k == 105) {
		dx = -animate*2-2
		dy = 0
	}
	if (k == 106) {
		dx = animate*2-2
		dy = 0
	}
	if (k == 108) {
		dx = 0
		dy = animate
	}
	for (y = 0; y < 4; y++) {
		for (x = 0; x < 4; x++) {
			i = x + y*4;
			if (!moving[i]) continue
			for (j = 0; j < 3; j++) {
				cmove(x*8+3+dx, y*4+2+j+dy)
				if (k == 105 || k == 106) {
					color(0)
					printf("  ")
				}
				color(moving[i])
				disp_number(moving[i], j)
				if (k == 105 || k == 106) {
					color(0)
					printf("  ")
				}
				color(-1)
			}
			if (dy) {
				cmove(x*8+3+dx, y*4+dy+(dy<0?5:1))
				color(0)
				printf("      ")
				color(-1)
			}
		}
	}
	animate++
}

function next_move() {
	delete moving
	if (spawning) {
		spawning = 0
		display()
		for (i = 0; i < 16; i++)
			F_copy[i] = F[i];
		if (!up() && !down() && !left() && !right())
			gameover = 1
		for (i = 0; i < 16; i++)
			F[i] = F_copy[i];
		cmove(0,18)
		printf("           Score: %d\n", score)
		if (clear) {
			printf("       !! Y O U   W I N !!      \n\n")
			printf("           Score: %d\n", score)
		}
		if (gameover) {
			printf("      !! G A M E O V E R !!      \n\n")
			printf("           Score: %d\n", score)
			exit()
		}
		return 0
	}
	if (k == 103)
		animate = up()
	else if (k == 105)
		animate = left()
	else if (k == 106)
		animate = right()
	else if (k == 108)
		animate = down()
	if (!animate && moved && !spawning) {
		animate = 9
		spawning = spawn()
		cmove(spawning%4*8+4, spawning/4*4+3)
		color(F[spawning])
		printf(" %d ", F[spawning])
	}
	return animate
}

probe kernel.function("kbd_event") {
	if ($value != 1) next
	key = $event_code
	if (!key || key == 4) next
	//cmove(0,19) printf("key = %d ", key)
	onkeydown(key)
}

/* RANDOM MOVE: run with option `stap -G rand=1 2048.stp' or
                                `staprun -T 30 /path/to/*.ko rand=1' */
global c, rand=0
probe timer.ms(500) {
	if (!rand) next
	dir = get_cycles()/256%4
	c[0] = 103 c[1] = 105 c[2] = 106 c[3] = 108
	key = c[dir]
	cmove(0,19) printf("key = %d ", key)
	onkeydown(key)
}

function onkeydown(key) {
	if (animate) {
		while (next_move());
		animate = 0
		display()
	}
	k = key
	delete changed
	moved = 0
	next_move()
}
