#!/usr/local/bin/cz --
use b
#use X11/cursorfont.h

# def debug warn
def debug void

def N 52
sprite background_sprite, card_back_sprite, show_hint_sprite, card_sprites[N], stack_base_sprites[4]
int card_w, card_h
int show_hint_x, show_hint_y
def gap 15
def col_w (w-gap)/8
def row_h 18 # 13 25
num plot_slow_new_game = 0 # 0.005 # 0.01
num plot_slow_stack = 0.05
num plot_slow_stack_all = 0.01
int moves
num intro_fps = 100
num hint_fps = 50
num hint_secs = 1
num hint_wait = 0.5
boolean fast = 0
int unstacked
int uncleared
boolean auto_pilot = 0
int numbered_game = -1

def rank_chars "_A23456789TJQK"
def suit_chars "SHDC"

struct card_state:
	int n
	int col
	int row
	boolean face_up

card_state card[N]
vec cols[8]
int stacks[4]

Main:
	load_sprites()
	space(background_sprite.width, background_sprite.height)
	vid_init()
#	XDefineCursor(display, window, XCreateFontCursor(display, XC_crosshair))
#	intro()
	init()
	if args:
		numbered_game = atoi(arg[0])
		seed(1)
		--numbered_game
		repeat(numbered_game):
			shuffle()
	repeat:
		new_game()
		event_loop()

load_sprites:
	char file[32] = ""
	cstr cards_dir = path_cat(program_dir, "cards")
	cd_block(cards_dir):
		for(i, 0, N):
			int suit = (i+1) % 4  # CSHD -> SHDC
			int rank = i / 4
			rank = rank == 0 ? 0 : 13-rank # A K Q J 10 ... -> A 2 3 4 5 ...
			int card = rank * 4 + suit
			snprintf(file, sizeof(file), "%02d.png", card+1)
			sprite_load_png(&card_sprites[i], file)
		for(i, 0, 4):
			int suit = (i+1) % 4  # CSHD -> SHDC!
			snprintf(file, sizeof(file), "stack%02d.png", suit+1)
			sprite_load_png(&stack_base_sprites[i], file)
		sprite_load_png(&card_back_sprite, "b02fv.png")
		sprite_load_png(&background_sprite, "greenbackground.png")
		sprite_load_png(&show_hint_sprite, "showhint.png")
	card_w = card_sprites[0].width ; card_h = card_sprites[0].height
	Free(cards_dir)

init:
	for(i, 0, 8):
		init(&cols[i], vec, int, 16)
	init(cards_in_motion, vec, int, 16)
	show_hint_x = w-gap-show_hint_sprite.width ; show_hint_y = h-gap-show_hint_sprite.height
	NEW(hints, vec, hint, 32)

boolean stop_intro = 0

intro:
	int packs=1
	pointn2 card_pos[N*packs], card_vel[N*packs]

	for(i, 0, N*packs):
		card_pos[i] = (pointn2){{Rand(0, w-card_w), Rand(0, h-card_h)}}
		card_vel[i] = (pointn2){{Rand(-5, 5), Rand(-5, 5)}}

	key_handler_default = mouse_handler_default = thunk(intro_stop)

	while !stop_intro:
		sprite_put(screen, &background_sprite, 0, 0)
		back(i, N*packs):   # high cards on top
			pointn2 *p = &card_pos[i], *v = &card_vel[i]
			sprite_put(screen, &card_sprites[i%N], p->x[0], p->x[1])
			each(a, 0, 1):
				p->x[a] += v->x[a]
			bounce(p->x[0], v->x[0], w-card_w, >, 0.8)
			bounce(p->x[0], v->x[0], 0, <, 0.8)
			bounce(p->x[1], v->x[1], h-card_h, >, 0.8)
			bounce(p->x[1], v->x[1], 0, <, 0.8)
#				v->x[1] += 0.1
		Paint()
		csleep(1.0/intro_fps)

void *intro_stop(void *obj, void *a0, void *event):
	use(obj, a0, event)
	stop_intro = 1
	return thunk_yes

new_game:
	moves = 0
	shuffle()
	deal()
	control_default()
	plot_cards(plot_slow_new_game)
	control_play()
	auto_pilot = 0
	if numbered_game >= 0
		++numbered_game
		debug("game number %d", numbered_game)
	cc_col = 0; cc_row = -1
	change_card_next()

change_card_next()
	++cc_row
	card_state *C
	repeat
		if cc_row >= veclen(&cols[cc_col])
			cc_row = 0 ; ++cc_col
			if cc_col == 8
				cc_col = 0
		else
			C = &card[*(int*)v(&cols[cc_col], cc_row)]
			if C->face_up == 0
				++cc_row
			 else
				break
	int n = C->n
	cc_rank = rank(n) ; cc_suit = suit(n)
	cc_step = 2

void *exit_game(void *obj, void *a0, void *event):
	use(obj, a0, event)
	if moving_cards:
		move_cards_abort()
	new_game()
	return thunk_yes

void *toggle_auto_pilot(void *obj, void *a0, void *event):
	use(obj, a0, event)
	auto_pilot = !auto_pilot
	while auto_pilot && unstacked:
		show_hint(1)
		handle_events(0)
	auto_pilot = 0
	return thunk_yes

shuffle:
	for(c, 0, N):
		card_state *C = &card[c]
		C->n = c
	for(c, 0, N):
		int c2 = randi(c, N)
		card_state *C = &card[c], *C2 = &card[c2]
		swap(C->n, C2->n)

deal:
	uncleared = 0
	unstacked = N
	for(i, 0, 8):
		vecclr(&cols[i])
	int col = 0, row = 0
	for(c, 0, N):
		card_state *C = &card[c]
		vec_push(&cols[col], c)
		C->col = col
		C->row = row
		C->face_up = veclen(&cols[col]) > col
		if !C->face_up:
			++uncleared
		++col
		if col == 7
			++row
			col = 1
			while veclen(&cols[col]) == col+5
				++col
	for(i, 0, 4):
		stacks[i] = 0
	cards_moved_hook()

def plot_cards():
	plot_cards(0)
plot_cards(num plot_slow):
	sprite_put(screen, &background_sprite, 0, 0)
	sprite_put_transl(screen, &show_hint_sprite, show_hint_x, show_hint_y)
	for(suit, 0, 4):
		pointi2 p = card_point(7, suit)
		if stacks[suit]:
			sprite_put(screen, &card_sprites[card_n(suit, stacks[suit])], p.x[0], p.x[1])
		 else:
			sprite_put_transp(screen, &stack_base_sprites[suit], p.x[0], p.x[1])
	int row = 0
	int some
	do:
		some = 0
		for(col, 0, 8):
			if veclen(&cols[col]) > row
				some = 1
				int c = *(int*)v(&cols[col], row)
				card_state *C = &card[c]
				pointi2 p = card_point(C->col, C->row)
				sprite *s = C->face_up ? &card_sprites[C->n] : &card_back_sprite
				sprite_put(screen, s, p.x[0], p.x[1])
				if plot_slow && !fast
					Paint()
					csleep(plot_slow)
		++row
	 while some
	if moving_cards
		int x = move_card_drag_offset.x[0]+move_card_drag.x[0]
		int y = move_card_drag_offset.x[1]+move_card_drag.x[1]
		for_vec(i, cards_in_motion, int):
			card_state *C = &card[*i]
			sprite_put(screen, &card_sprites[C->n], x, y)
			y += row_h
	Paint()

int suit(int n):
	return n % 4

int rank(int n):
	return n / 4 + 1

int card_n(int suit, int rank):
	return (rank - 1)*4 + suit

int card_color(int n):
	int s = suit(n)
	return among(s, 1, 2)

struct card_name:
	char s[3]

def name(n) get_card_name(n).s
card_name get_card_name(int n):
	return (card_name){{rank_chars[rank(n)], suit_chars[suit(n)], '\0'}}

pointi2 card_point(int col, int row):
	pointi2 pos
	pos.x[0] = gap + col * col_w
	pos.x[1] = gap + row * (col < 7 ? row_h : card_h + gap)
	return pos

int which_card(int *col, int *row, int x, int y):
	*col = which_col(x)
	*row = which_row(y)
	if *col < 0 || *row < 0:
		return -1
	int n = veclen(&cols[*col])
	if *row >= n:
		*row = n-1
		if y >= card_point(*col, *row).x[1]+card_h:
			return -1
	return 0

int which_col(int x):
	x -= gap
	if x<0:
		return -1
	int col = x / col_w
	int rem = x % col_w
	if rem >= card_w:
		return -1
	return col

int which_row(int y):
	y -= gap
	if y<0:
		return -1
	int row = y / row_h
	return row

control_play:
	key_handler("x", KeyPress) = thunk(exit_game)
	key_handler("p", KeyPress) = thunk(toggle_auto_pilot)
	key_handler("w", KeyPress) = thunk(show_hint, NULL, i2p(0))
	key_handler("m", KeyPress) = thunk(show_hint, NULL, i2p(1))
	key_handler("Down", KeyPress) = thunk(speed, NULL, i2p(125))
	key_handler("Up", KeyPress) = thunk(speed, NULL, i2p(80))
	key_handler("f", KeyPress) = thunk(toggle_fast)
	mouse_handler(1, ButtonPress) = thunk(move_cards_start)
	mouse_handler(1, MotionNotify) = thunk(move_cards_drag)
	mouse_handler(1, ButtonRelease) = thunk(move_cards_stop)
	mouse_handler(2, ButtonPress) = thunk(change_card)
	mouse_handler(3, ButtonPress) = thunk(stack_cards)
	for_cstr(p, rank_chars suit_chars)
		char s[2]
		s[0] = *p; s[1] = '\0'
		key_handler(s, KeyPress) = thunk(change_card)

void *key_input(void *obj, void *a0, void *event):
	use(obj, a0)
	gr_event *e = event
	key_event_debug("key input ignored: %s: %s", e)
	return thunk_yes

boolean moving_cards = 0
int move_card_from_col, move_card_from_row
pointi2 move_card_drag_offset, move_card_drag
vec struct__cards_in_motion, *cards_in_motion = &struct__cards_in_motion

void *move_cards_start(void *obj, void *a0, void *event):
	change_card(obj, a0, event)
	gr_event *e = event
	int col, row
	if which_card(&col, &row, e->x, e->y) == 0:
		int c = *(int*)v(&cols[col], row)
		if card[c].face_up:
			move_start(col, row)
			pointi2 p = card_point(col, row)
			move_card_drag_offset = (pointi2){{p.x[0] - e->x, p.x[1] - e->y}}
			move_card_drag = (pointi2){{e->x, e->y}}
			plot_cards()
#			debug("move cards start %d %d", col, row)
	 eif !pix_a(sprite_at(&show_hint_sprite, e->x-show_hint_x, e->y-show_hint_y))
		show_hint(0)
	return thunk_yes

move_start(int col, int row)
	if moving_cards:
		move_cards_abort()
		plot_cards()
	moving_cards = 1
	move_card_from_col = col ; move_card_from_row = row
	int n_cards = veclen(&cols[col]) - row
	vec_append(cards_in_motion, v(&cols[col], row), n_cards)
	vec_set_size(&cols[col], row)

void *move_cards_drag(void *obj, void *a0, void *event):
	use(obj, a0)
	gr_event *e = event
	if moving_cards:
#		debug("move cards drag")
		move_card_drag = (pointi2){{e->x, e->y}}
		plot_cards()
	return thunk_yes

void *move_cards_stop(void *obj, void *a0, void *event):
	use(obj, a0)
	gr_event *e = event
	if moving_cards:
		int col = which_col(e->x)
		int card_floating = *(int*)vec0(cards_in_motion)
		int from_col = move_card_from_col
		if col == 7 && veclen(cards_in_motion) == 1 && can_stack(card_floating):
			move_cards_abort()
			stack_card(from_col, veclen(&cols[from_col])-1)
		 eif tween(col, 0, 6) && cards_can_land(card_floating, col):
			move_cards(col)
		 else:
			move_cards_abort()
		plot_cards()
#		debug("move cards stop %d", col)
	return thunk_yes

move_cards_abort():
	move_cards(move_card_from_col)

boolean cards_can_land(int card_floating, int col):
	vec *column = &cols[col]
	if !veclen(column):
		return rank(card[card_floating].n) == 13  # king
	int card_base = *(int*)vec_top(column)
	return card_can_go_on_card(card[card_floating].n, card[card_base].n)

boolean card_can_go_on_card(int floating, int base):
	return card_color(floating) != card_color(base) &&
	 rank(floating) == rank(base) - 1

move_cards(int col):
	vec *column = &cols[col]
	int n = veclen(column)
	for_vec(i, cards_in_motion, int):
		card[*i].col = col
		card[*i].row = n++
	vec_append(column, cards_in_motion)
	vecclr(cards_in_motion)
	moving_cards = 0
	if col != move_card_from_col
		++moves
		cards_moved_hook()
		show_top_card(move_card_from_col)

show_top_card(int col):
	vec *column = &cols[col]
	int n = veclen(column)
	if n:
		card_state *C = &card[*(int*)vec_top(column)]
		if !C->face_up:
			C->face_up = 1
			--uncleared
		cc_step = 2
		cc_col = col; cc_row = n-1
		cc_rank = rank(C->n); cc_suit = suit(C->n)
		debug("revealed card %c%c", rank_chars[cc_rank], suit_chars[cc_suit])

void *stack_cards(void *obj, void *a0, void *event):
	use(obj, a0)
	gr_event *e = event
	int col, row
	int stacked = 0
	if which_card(&col, &row, e->x, e->y) == 0:
		vec *column = &cols[col]
		int c = *(int*)v(column, row)
		int n = card[c].n
		if rank(n) == 13 && (row != veclen(column)-1 || !can_stack(c)):
			stacked += stack_all_cards()
		 else:
			paint_handle_events = 0
			back(row2, veclen(column), row):
				if !stack_card(col, row2):
					break
				++stacked
				plot_cards()
				csleep(plot_slow_stack)
			paint_handle_events = 1
	#		debug("stack cards %d %d", col, row)
	 eif !pix_a(sprite_at(&show_hint_sprite, e->x-show_hint_x, e->y-show_hint_y)):
		show_hint(1)
	if stacked == 0
		change_card(obj, a0, event)
	return thunk_yes

int stack_card(int col, int row):
	vec *column = &cols[col]
	int c = *(int*)v(column, row)
	int n = card[c].n
	if !can_stack(c):
		return 0
	 else:
		++stacks[suit(n)]
		--unstacked
		card[c].col = 7
		card[c].row = suit(n)
		vec_pop(column)
		cards_moved_hook()
		show_top_card(col)
		return 1

int stack_all_cards()
	int done = 0
	int stacked = 0
	paint_handle_events = 0
	while !done:
		done = 1
		for(col, 0, 7):
			vec *column = &cols[col]
			int row = veclen(column)-1
			if row >= 0:
				if stack_card(col, row):
					++stacked
					done = 0
					plot_cards()
					csleep(plot_slow_stack_all)
	paint_handle_events = 1
	return stacked

boolean can_stack(int c):
	int n = card[c].n
	return card[c].face_up && stacks[suit(n)] + 1 == rank(n)

#int which_stack(int x, int y):
#	use(x, y)
#	# TODO
#	return -1

int show_hint_ix = 0

struct hint
	int col, row
	int to_col # 7 means stack it

vec struct__hints, *hints = &struct__hints
boolean need_calc_hints = 1
int hint_ix = 0

cards_moved_hook():
	vecclr(hints)
	need_calc_hints = 1
	hint_ix = 0
	change_card_reset()

def show_hint(doit):
	show_hint(NULL, i2p(doit), NULL)
void *show_hint(void *obj, void *a0, void *a1):
	use(obj, a1)
	boolean doit = p2i(a0)
	# cycles through possible moves
	if need_calc_hints:
		calc_hints()
		need_calc_hints = 0
	 eif doit:
		--hint_ix
		if hint_ix < 0:
			hint_ix += veclen(hints)
	if !veclen(hints):
		debug("there are no moves!")  # TODO
		auto_pilot = 0
	 else:
		hint *h = v(hints, hint_ix)
		suggest_move(h->col, h->row, h->to_col, doit)
		if ++hint_ix >= veclen(hints):
			hint_ix = 0
	return thunk_yes

calc_hints():
	for(col, 0, 7):
		vec *column = &cols[col]
		for(row, 0, veclen(column)):
			int c = *(int*)v(column, row)
			if !card[c].face_up:
				continue
			int n = card[c].n
			boolean king_at_base = rank(n) == 13 && row == 0
			if king_at_base:
				continue
			for(to_col, 0, 7):
				if to_col == col:
					continue
				if cards_can_land(c, to_col):
					vec_push(hints, (hint){col, row, to_col})
	for(col, 0, 7):
		vec *column = &cols[col]
		int row = veclen(column)-1
		if row >= 0:
			int c = *(int*)v(column, row)
			if can_stack(c):
				vec_push(hints, (hint){col, row, 7})
	if veclen(hints):
		choose_best_hint()

choose_best_hint():
	hint *best_hint = NULL
	best_hint = choose_best_hint_stack_lowest()
	if uncleared:
		if !best_hint:
			best_hint = choose_best_clearing_move(0)
		if !best_hint:
			best_hint = choose_best_clearing_move(1)
		if !best_hint:
			debug("there is no way to clear more cards")  # TODO
	if !best_hint:
		best_hint = choose_best_stacking_move(0)
	if !best_hint:
		best_hint = choose_non_trivial_move()
	if !best_hint:
		best_hint = choose_best_stacking_move(1)
	if !best_hint:
		debug("only trivial moves left!")
#		best_hint = v(hints, randi(veclen(hints)))
		auto_pilot = 0
	if best_hint:
		let(tmp, *best_hint)
		vec_cut(hints, best_hint-(hint*)vec0(hints))
		vec_unshift(hints, &tmp)
		debug("found a best hint")

int next_to_stack()
	int min_stack = 13
	for(i, 0, 4):
		if stacks[i] < min_stack:
			min_stack = stacks[i]
	return min_stack+1

hint *choose_best_hint_stack_lowest():
	debug("choose_best_hint_stack_lowest")
	int next = next_to_stack()
	for_vec(h, hints, hint):
		if h->to_col == 7:
			vec *column = &cols[h->col]
			int c = *(int*)v(column, h->row)
			if rank(card[c].n) == next:
				return h
	return NULL

boolean stopped_for_depth

hint *choose_best_clearing_move(boolean may_stack):
	debug("choose_best_clearing_move %d", may_stack)
	for(col, 0, 7):
		debug("trying col %d", col)
		int depth = 1
		repeat:
			char already[N]
			bzero(already, sizeof(already))
			debug("trying depth %d", depth)
			stopped_for_depth = 0
			vec *column = &cols[col]
			back(row, veclen(column), 0):
				int c = *(int*)v(column, row)
				if !card[c].face_up:
					hint *h = try_move(col, row+1, may_stack, already, depth):
					if h:
						return h
					break
			debug("stopped_for_depth %d", stopped_for_depth)
			if !stopped_for_depth:
				break
			++depth
	return NULL

hint *choose_best_stacking_move(boolean stack_any)
	debug("choose_best_stacking_move %d", stack_any)
	int depth = 1
	int next = next_to_stack()
	repeat:
		char already[N]
		bzero(already, sizeof(already))
		stopped_for_depth = 0
		for(col, 0, 7):
			vec *column = &cols[col]
			int l = veclen(column)
			for(row, 0, l):
				int c = *(int*)v(column, row)
				int n = card[c].n
				int r = rank(n)
				if can_stack(c) && (stack_any || r == next):
					if row == l-1:
						return find_hint(col, row, 7)
					 else:
						hint *h = try_move(col, row+1, 1, already, depth)
						if h:
							return h
		if !stopped_for_depth:
			break
		++depth
	return NULL

hint *choose_non_trivial_move()
	debug("choose_non_trivial_move")
	for_vec(h, hints, hint):
		vec *column = &cols[h->col]
		if h->to_col == 7:
			continue
#			fault("choose_non_trivial_move called but direct stacking move available")
		vec *to_column = &cols[h->to_col]
		int l_to = veclen(to_column)
		if !l_to:
			debug("chose non-trivial move for king")
			return h
		if h->row == 0:
			return h
		int c_up = *(int*)v(column, h->row-1)
		int n_up = card[c_up].n
		int c_up_to = *(int*)v(to_column, l_to-1)
		int n_up_to = card[c_up_to].n
		if rank(n_up) != rank(n_up_to) || card_color(n_up) != card_color(n_up_to):
			debug("chose non-trivial move")
			return h
	return NULL

hint *try_move(int col, int row, int may_stack, char *already, int depth):
	if depth == 0:
		stopped_for_depth = 1
		return NULL
	# try_move - how can we move this card?
	# * keep a record of each card we try to unbury, and don't loop
	# * if we fail to unbury a card, record that fact, to shortcut future searches
	vec *column = &cols[col]

	# 0. we can stack the cards above it - do it
	if may_stack && can_stack_all_from(col, row):
		return find_hint(col, veclen(column)-1, 7)
	int c = *(int*)v(column, row)
	int n = card[c].n
	if already[c]:
		return NULL
	already[c] = 1
	debug("try_move %d %d %s", col, row, name(n))

	# 1. we can already move it to a column (possibly if we stack some cards first) - do it
	# 2. we can find a card buried in another column to move it to - try to unbury any such cards recursively
	# 3. we can find a card in this column to move it to - split this column by trying to unbury each card between them
	for(to_col, 0, 7):
		vec *to_column = &cols[to_col]
		int l = veclen(to_column)
		back(to_row, l+1, 0):
			if to_col == col && to_row == row:
				continue
			if to_row > 0 && to_row < l && !card[*(int*)v(to_column, to_row-1)].face_up:
				break
			if to_row == 0 && to_row < l && !card[*(int*)v(to_column, to_row)].face_up:
				break
			if might_move(n, row, to_col, to_row):
				if to_col == col && to_row > row:
					continue  # this doesn't work right yet.
					debug("might move to higher on same column. ohoh!")
					for(r1, row+1, to_row):
						debug("trying to split at %d", r1)
	#					Fprintf(stderr, " ")
						hint *h = try_move(col, r1, may_stack, already, depth-1)
						if h:
							return h
				 eif to_row == l:
					return find_hint(col, row, to_col)
				 eif may_stack && can_stack_all_from(to_col, to_row):
					return find_hint(to_col, l-1, 7)
				 else:
	#				Fprintf(stderr, " ")
					hint *h = try_move(to_col, to_row, may_stack, already, depth-1)
					if h:
						return h

	# Fprintf(stderr, ".")
	return NULL

boolean might_move(int n, int row, int to_col, int to_row):
	if rank(n) == 13:
		return to_row == 0 && row != 0
	 eif to_row > 0:
		vec *to_column = &cols[to_col]
		int ct = *(int*)v(to_column, to_row-1)
		int nt = card[ct].n
		return rank(nt) == rank(n)+1 && card_color(nt) != card_color(n)
	return 0

hint *find_hint(int col, int row, int to_col):
	for_vec(h, hints, hint):
		if h->col == col && h->row == row && h->to_col == to_col:
			return h
	error("FIND_HINT FAILED %d %d %d", col, row, to_col)
#	kill(getpid(), SIGSTOP)
	return NULL

boolean can_stack_all_from(int col, int row1):
	vec *column = &cols[col]
	int test_stacks[4]
	for(i, 0, 4):
		test_stacks[i] = stacks[i]
	back(row, veclen(column), row1):
		int c = *(int*)v(column, row)
		int n = card[c].n
		if rank(n) != test_stacks[suit(n)] + 1:
			return 0
		++test_stacks[suit(n)]
	return 1

suggest_move(int col, int row, int to_col, boolean doit):
	vec *column = &cols[col]
	int c = *(int*)v(column, row)
	int n = card[c].n
	debug("hint: move %s col %d row %d to col %d", name(n), col, row, to_col)
	move_start(col, row)
	move_card_drag_offset = (pointi2){{0, 0}}
	pointi2 p0 = card_point(col, row)
	pointi2 p1
	if to_col < 7:
		p1 = card_point(to_col, veclen(&cols[to_col]))
	 else
		p1 = card_point(to_col, suit(n))
	int x0 = p0.x[0], y0 = p0.x[1]
	int x1 = p1.x[0], y1 = p1.x[1]
	paint_handle_events = 0
	int hint_frames
	hint_frames = fast ? 2 : hint_fps*hint_secs
	if doit:
		hint_frames /= 2
	num range = doit ? 180 : 360
	if !doit:
		x1 += gap / 2
	for(i, 0, hint_frames+1):
		num n = i/(num)hint_frames
		n = - spow(Cos(range*n), 0.8) / 2 + 0.5
		move_card_drag = (pointi2){{(x1-x0)*n + x0, (y1-y0)*n + y0}}
		plot_cards()
		csleep(1.0/hint_fps)
		if !doit && i == hint_frames/2:
			csleep(hint_wait)
	if doit:
		if to_col == 7:
			move_cards_abort()
			stack_card(col, row)
		 else:
			move_cards(to_col)
	 else:
		move_cards_abort()
	plot_cards()
	paint_handle_events = 1

void *speed(void *obj, void *a0, void *a1):
	use(obj, a0, a1)
	num speed_fac = p2i(a0)/100.0
	plot_slow_new_game *= speed_fac
	plot_slow_stack *= speed_fac
	plot_slow_stack_all *= speed_fac
	hint_secs *= speed_fac
	hint_wait *= speed_fac
	return thunk_yes

void *toggle_fast(void *obj, void *a0, void *a1):
	use(obj, a0, a1)
	fast = !fast
	return thunk_yes

def change_card_reset()
	cc_step = 1

static int cc_step = 1, cc_col, cc_row, cc_rank, cc_suit
void *change_card(void *obj, void *a0, void *event):
	use(obj, a0)
	gr_event *e = event
	int to_col, to_row
	char *p
	debug("change_card %d", cc_step, e->which)

	char k = 0
	if e->type == KeyPress
		k = toupper((unsigned char)key_string(e->which)[0])
		debug("  code %d, k = %d %c", e->which, k, k)

	if e->type == ButtonPress
		cc_step = 1
		if which_card(&cc_col, &cc_row, e->x, e->y) == 0
			int c = *(int*)v(&cols[cc_col], cc_row)
			card_state *C = &card[c]
			if C->face_up
				++cc_step
	 eif cc_step == 2 && e->type == KeyPress && (p = strchr(rank_chars, k))
		cc_rank = p - rank_chars
		++cc_step
	 eif cc_step == 3 && e->type == KeyPress && (p = strchr(suit_chars, k))
		cc_suit = p - suit_chars
		debug("  changing to %c%c", rank_chars[cc_rank], suit_chars[cc_suit])
		int result = find_card(&to_col, &to_row, cc_suit, cc_rank)
		if result == -1
			debug("  target is stacked already, won't swap")
		 else
			swap_cards(cc_col, cc_row, to_col, to_row)
			plot_cards()
			change_card_next()
	 eif cc_step == 3
		cc_step = 2
	return thunk_yes

int find_card(int *col_, int *row_, int s, int r)
	# returns 0 or 1 if found, for facedown/faceup
	# returns -1 for not found (stacked already)
	for(col, 0, 7):
		vec *column = &cols[col]
		for(row, 0, veclen(column)):
			int c = *(int*)v(column, row)
			card_state *C = &card[c]
			int n = card[c].n
			if s == suit(n) && r == rank(n)
				*col_ = col ; *row_ = row
				return C->face_up
	return -1

swap_cards(int col1, int row1, int col2, int row2)
	use(col1, row1, col2, row2)
	vec *column1 = &cols[col1]
	vec *column2 = &cols[col2]
	int c1 = *(int*)v(column1, row1)
	int c2 = *(int*)v(column2, row2)
	card_state *C1 = &card[c1], *C2 = &card[c2]
	swap(C1->n, C2->n)

# LIBB
