1#!/usr/local/bin/wish8.6 -f
2
3######################################################################
4#
5# table.tcl
6#
7# Copyright (C) 1993,1994 by John Heidemann <johnh@ficus.cs.ucla.edu>
8# All rights reserved.  See the main klondike file for a full copyright
9# notice.
10#
11# $Id: table.tcl,v 2.16 1997/03/26 17:47:29 johnh Exp $
12#
13######################################################################
14
15#
16# generic card table routines
17#
18set rcsid(table.tcl) {$Id: table.tcl,v 2.16 1997/03/26 17:47:29 johnh Exp $}
19
20
21proc mkTableDefaults {} {
22	global table tk_version
23
24	if {$tk_version < 4.0} {
25		set table(colormodel) [tk colormodel .]
26	} else {
27		set table(colormodel) "color"
28		if {[winfo depth .] < 8} {
29			set table(colormodel) monochrome
30		}
31	}
32	set table(font) -*-Helvetica-Medium-R-*-140-*
33	if { $table(colormodel) == "monochrome" } {
34		set table(fg) Black
35		set table(bg) White
36	} else {
37		set table(fg) Black
38		set table(bg) #ffe4c4
39	}
40
41	set table(font) -*-Helvetica-Medium-R-*-140-*
42
43	set table(cardWidth) 54
44	set table(cardHeight) 69
45	# If necessary
46	# we will trim these values a little shorter in dontspace
47	# to make the window fit a 640x480 display.
48	set table(gutter) 12
49	set table(stackedCardOffset) 25
50}
51
52proc tableBell {} {
53	global tk_version
54	if {$tk_version < 4.0} {
55		puts -nonewline "\a"
56	} else {
57		bell
58	}
59
60}
61
62proc mkCardDefaults {} {
63	global table
64
65	# cards
66	# (see reface card for card colors)
67
68	set table(cardWidth) 52
69	set table(cardHeight) 67
70	# cardSpace -- reasonable amount of space between card bitmaps
71	set table(cardSpace) 8
72	# cardOverlap -- required overlap when dropping cards
73	set table(cardOverlap) 4
74	# padValue -- a good value for random padding (around text)
75	set table(padValue) 10
76
77	# coords of a place off the screen
78	set table(hiddenX) -1000
79	set table(hiddenY) -1000
80}
81
82#
83# init stuff
84#
85# table(preActionProc) = called before any action (used to start game)
86#
87proc mkTable {width height preActionProc} {
88	global table tk_version
89
90	set table(width) $width
91	set table(height) $height
92	set table(preActionProc) $preActionProc
93	set table(id) ".c"
94
95	mkCardDefaults
96
97	# random constants
98	set table(values) "a 2 3 4 5 6 7 8 9 t j q k"
99	set table(suits) "c d h s"
100	set table(cvalues) "xa23456789tjqkx"
101	set table(csuits) "xcdhsx"
102	set table(otherColorSuits,c) "dh"
103	set table(otherColorSuits,d) "cs"
104	set table(otherColorSuits,h) "cs"
105	set table(otherColorSuits,s) "dh"
106
107	canvas $table(id) -relief raised \
108		-width $table(width) -height $table(height) \
109		-background $table(bg)
110	if {$tk_version >= 4.0} {
111		focus $table(id)
112		$table(id) configure -takefocus 1
113	}
114
115	return $table(id)
116}
117
118
119#
120# card bitmap backgrounds
121#
122proc setBackBitmap {} {
123	global table
124	if { [info exists table(backFace)] } {
125		set oldBackFace $table(backFace)
126	} else {
127		set oldBackFace "xxx"
128	}
129	set table(backFace) "back_$table(backChoice)"
130	#
131	# Fix any cards with the old back.
132	#
133	if {[catch {$table(id) configure}] == 0} {
134		foreach i [$table(id) find withtag card] {
135			set itemBitmap [lindex [$table(id) itemconfigure $i -bitmap] 4]
136			if { [regexp $oldBackFace $itemBitmap] } {
137				refaceItem $i $table(id) $table(backFace)
138			}
139		}
140	}
141}
142
143proc chooseCardBackground {} {
144	global table
145
146	#
147	# get our choices
148	#
149	set choices ""
150	# Note:  wish-4.1 under linux seems to have a bug
151	# with the glob failing.  If so, take our
152	# known good cases.
153	if [catch {
154		set possibleChoices [glob [string trimleft "$table(bitmapdir)/c_back_*.xbm" "@"]]
155	}] {
156		set possibleChoices "c_back_crane.xbm c_back_pagoda.xbm c_back_plain.xbm"
157	}
158	foreach i $possibleChoices {
159		regexp {c_back_(.*)\.xbm$} $i trash token
160		lappend choices $token
161	}
162	if { $choices == "" } {
163		return -errorinfo "No background bitmap found."
164	}
165	set table(backChoices) $choices
166
167	#
168	# randomly pick one
169	#
170	set table(backChoice) [lindex $choices [random [llength $choices]]]
171	setBackBitmap
172}
173chooseCardBackground
174
175
176#
177# table stuff
178#
179
180
181proc refaceItem {itemId w face} {
182	global table items
183	# puts "refaceItem $itemId $w $face"
184	switch -glob $face {
185		[a23456789tjqk][cs] {
186			set items($itemId,normFg) Black
187			set items($itemId,normBg) White
188			if { $table(colormodel) == "monochrome" } {
189				set items($itemId,highFg) White
190				set items($itemId,highBg) Black
191			} else {
192				set items($itemId,highFg) Black
193				set items($itemId,highBg) Gray70
194			}
195		}
196		[a23456789tjqk][dh] {
197			set items($itemId,normFg) Red
198			set items($itemId,normBg) White
199			if { $table(colormodel) == "monochrome" } {
200				set items($itemId,highFg) White
201				set items($itemId,highBg) Black
202			} else {
203				set items($itemId,highFg) "#a00000"
204				set items($itemId,highBg) Gray70
205			}
206		}
207		back_*		    {
208			set items($itemId,normFg) Black
209			set items($itemId,normBg) White
210			if { $table(colormodel) == "monochrome" } {
211				set items($itemId,highFg) White
212				set items($itemId,highBg) Black
213			} else {
214				set items($itemId,highFg) Black
215				set items($itemId,highBg) Gray70
216			}
217		}
218		space		    -
219		warnspace	    {
220			set items($itemId,normFg) $table(fg)
221			set items($itemId,normBg) $table(bg)
222			if { $table(colormodel) == "monochrome" } {
223				set items($itemId,highFg) $table(bg)
224				set items($itemId,highBg) $table(fg)
225			} else {
226				set items($itemId,highFg) Black
227				set items($itemId,highBg) Gray70
228			}
229		}
230		default		    { puts "refaceItem: unkown face $face\n" }
231	}
232	$w itemconfigure $itemId \
233		-bitmap "$table(bitmapdir)/c_$face.xbm" \
234		-foreground $items($itemId,normFg) \
235		-background $items($itemId,normBg)
236
237}
238
239proc createItemBitmap {x y face} {
240	global table
241	set c $table(id)
242	set itemId [ $c create bitmap $x $y -anchor nw]
243	refaceItem $itemId $c $face
244	# Remember the cards so we can change bitmaps as required.
245	$c addtag card withtag $itemId
246	# $c addtag debug withtag $itemId
247	return $itemId
248}
249
250
251#
252# null procs
253#
254proc recursiveFindFriendsProc {itemId w x y closure} {
255	global items
256	if { $closure == {} } {
257		return {}
258	} else {
259		return [linsert [recursiveFindFriendsProc $closure $w $x $y $items($closure,dragFindFriendsClosure)] 0 $closure]
260	}
261}
262proc defaultDragTargetAcceptProc {w target src srcFriends} { return 1 }
263
264proc defaultClickProc {item w x y closure} {}
265
266#
267# dropableCard
268#
269proc defaultDragTargetEnterProc {item w x y targetId} {
270	global items
271	$w itemconfig $targetId \
272		-foreground $items($targetId,highFg) \
273		-background $items($targetId,highBg)
274	}
275proc defaultDragTargetLeaveProc {item w x y targetId} {
276	global items
277	$w itemconfig $targetId \
278		-foreground $items($targetId,normFg) \
279		-background $items($targetId,normBg)
280}
281
282proc whereDroppedDragProc {itemId w x y src target} {
283}
284
285proc originalPlaceDragProc {itemId w x y src target} {
286	global table
287	# debugLog "originalPlaceDragProc $itemId $w $x $y $src $target"
288	if { $target == "" } {
289		#
290		# Put the card back where it started.
291		# This is a little trickey since we could be dragging a
292		# stack, so we compute the relative distance and
293		# move selected.
294		#
295		set oldCoords [$w coords $itemId]
296		moveAllRelatively $w $src \
297			[lindex $oldCoords 0] [lindex $oldCoords 1] \
298			$table(dragInitialX) $table(dragInitialY)
299	} else {
300		error "defaultDragAbortProc: called with target $target"
301	}
302}
303
304proc onCardPlaceDragProc {itemId w x y src target} {
305	global table items
306	if { $target != {} } {
307		# unhiligth other card by calling leave proc
308		defaultDragTargetLeaveProc $itemId $w $x $y $target
309		moveCardOnCard $w $itemId $target selected
310	} else {
311		error "defaultDragAbortProc: called with target $target"
312	}
313}
314
315proc samePlaceChildOffsetProc {} {
316	return [list 0 0]
317}
318proc offsetChildOffsetProc {} {
319	global table
320	return [list 0 $table(stackedCardOffset)]
321}
322
323
324
325#
326# map from id's to cards
327#
328proc memorizeCard {id vs} {
329	global cards
330	set cards($vs) $id
331}
332
333proc rememberCard {vs} {
334	global cards
335	return $cards($vs)
336}
337
338
339#
340# deck stuff
341#
342
343proc getCard {w id param} {
344	global items
345	return $items($id,[string trim $param "-"])
346}
347
348proc setCard {w id args} {
349	global items
350	# puts "$id: $args"
351	while { [llength $args] } {
352		set a [lindex $args 0]
353		set args [lreplace $args 0 0]
354		if { $a != "-default" } {
355			if { [llength $args] == 0 } {
356				error "setCard: argument $a without parameter"
357			}
358			set b [lindex $args 0]
359			set args [lreplace $args 0 0]
360
361			switch -exact -- $a {
362			"-atag" {
363				$w addtag $b withtag $id
364				# Make sure that double clicking is allowed
365				# before clicking for dragging.
366				set tags [lindex [$w itemconf $id -tags] 4]
367				set doubleI [lsearch -exact $tags "doubleClickableCard"]
368				set dragableI [lsearch -exact $tags "dragableCard"]
369				if { $dragableI < $doubleI } {
370					# Redo dragableCard tag to make it last.
371					$w dtag $id dragableCard
372					$w addtag dragableCard withtag $id
373				}
374			}
375			"-dtag" { $w dtag $id $b }
376			default {
377				set a [string trim $a "-"]
378				set items($id,$a) $b
379				}
380			}
381			switch  -exact -- $a {
382			"side" {
383				if { $b == "back" } {
384					set face $table(backFace)
385				} else {
386					set face $items($id,subtype)
387				}
388				refaceItem $id $w $face
389				}
390			"subtype" {
391				memorizeCard $id $b
392				}
393			}
394		} else { # -default
395			# kill tags
396			$w itemconf $id -tags card
397
398			set items($id,type) card
399					# values: card, place
400			if { [info exists items($id,subtype)] == 0 } {
401				set items($id,subtype) ""
402					# for cards: value/suit
403			}
404			set items($id,location) hidden
405					# values: hidden, deck, pile,
406					# tableau, foundation
407			set items($id,sublocation) ""
408					# values
409			set items($id,side) back
410					# values: face, back
411			set items($id,parent) {}
412			set items($id,child) {}
413
414			# items(id,normFg), items(id,normBg)
415			# items(id,highFg), items(id,highBg)
416
417			set items($id,childOffsetProc) samePlaceChildOffsetProc
418					# returns xy list of where
419					# a child should be placed
420
421			set items($id,dragableCardPress) defaultDragableCardPress
422			set items($id,dragableCardMove) defaultDragableCardMove
423			set items($id,dragableCardRelease) defaultDragableCardRelease
424			set items($id,dragFindFriendsProc) recursiveFindFriendsProc
425			set items($id,dragFindFriendsClosure) {}
426					# return a list of friends to be drug
427			set items($id,dragTargetEnterProc) \
428						defaultDragTargetEnterProc
429					# when dropAccepting, called if a
430					# valid target is over us
431			set items($id,dragTargetLeaveProc) \
432						defaultDragTargetLeaveProc
433					# when dropAccepting, called if a
434					# valid target was over us but left
435			set items($id,dragTargetAcceptGlob) ""
436					# globbing of cards that we take
437			set items($id,dragTargetAcceptProc) \
438						defaultDragTargetAcceptProc
439					# second check after Accepts
440			set items($id,dragCommitProc) onCardPlaceDragProc
441					# Called when drag is released.
442
443			set items($id,clickProc) defaultClickProc
444					# Called when clicked on.
445			set items($id,clickClosure) {}
446					# passed to clickProc
447			set items($id,doubleClickProc) defaultClickProc
448					# Called when clicked on.
449			set items($id,doubleClickClosure) {}
450					# passed to doubleClickProc
451
452			set items($id,orphanChildProc) error
453			set items($id,orphanChildClosure) {}
454
455			set items($id,adoptChildProc) error
456			set items($id,adoptChildClosure) {}
457		}
458	}
459}
460
461proc tableRegisterDumbItem {id} {
462	global items
463	set items($id,dragTargetAcceptGlob) {}
464}
465
466proc mkDeck {} {
467	global table items deck
468
469	#
470	# Create each new card on the deck
471	# and add it to items.
472	# Initially cards are instantiated off-screen.
473	#
474	#
475	# Additionally, cards ids are listed in the deck list.
476	#
477
478	foreach v $table(values) {
479		foreach s $table(suits) {
480			# create the card
481			set id [createItemBitmap $table(hiddenX) $table(hiddenY) $table(backFace)]
482			lappend deck $id
483			setCard $table(id) $id -default -type card -subtype $v$s
484		}
485	}
486}
487
488
489
490proc shuffleDeck {} {
491	global deck
492	# Put the deck in a cannonical order so we
493	# can regenerate it with the same sequenece
494	# of random numbers.
495	set oldCards [lsort -integer $deck]
496	set newCards ""
497	while { [llength $oldCards] > 0 } {
498		# tclX
499		set i [random [llength $oldCards]]
500		lappend newCards [lindex $oldCards $i]
501		set oldCards [lreplace $oldCards $i $i]
502	}
503	set deck $newCards
504}
505
506
507
508
509#
510# bindings
511#
512proc mkBindings {w} {
513	global table items
514
515	#
516	# card clicking
517	#
518	$w bind clickableCard <ButtonRelease-1> {
519		global table
520
521		$table(preActionProc) click
522
523		set itemId [%W find withtag current]
524		# NEEDSWORK: Check to make sure release is still on card.
525		$items($itemId,clickProc) $itemId %W %x %y $items($itemId,clickClosure)
526	}
527	$w bind clickableCard <Enter> { enterHilightCard [%W find withtag current] %W %x %y }
528	$w bind clickableCard <Leave> { leaveUnhilightCard [%W find withtag current] %W %x %y }
529
530	#
531	# double clicking
532	#
533	$w bind doubleClickableCard <Double-ButtonRelease-1> {
534		global table
535
536		$table(preActionProc) doubleClick
537		set table(dragging) 0
538
539		set itemId [%W find withtag current]
540		# NEEDSWORK: Check to make sure release is still on card.
541		$items($itemId,doubleClickProc) $itemId %W %x %y $items($itemId,clickClosure)
542	}
543	# johnh: xxx
544	$w bind doubleClickableCard <ButtonRelease-3> {
545		global table
546
547		$table(preActionProc) doubleClick
548
549		set itemId [%W find withtag current]
550		# NEEDSWORK: Check to make sure release is still on card.
551		$items($itemId,doubleClickProc) $itemId %W %x %y $items($itemId,clickClosure)
552	}
553	$w bind doubleClickableCard <Enter> { enterHilightCard [%W find withtag current] %W %x %y }
554	$w bind doubleClickableCard <Leave> { leaveUnhilightCard [%W find withtag current] %W %x %y }
555
556	#
557	# dragableCard
558	#
559	$w bind dragableCard <ButtonPress-1> {
560		set id [%W find withtag current]
561		$items($id,dragableCardPress) $id %W %x %y
562	}
563	$w bind dragableCard <B1-Motion> {
564		set id [%W find withtag current]
565		$items($id,dragableCardMove) $id %W %x %y
566	}
567	$w bind dragableCard <ButtonRelease-1> {
568		set id [%W find withtag current]
569		$items($id,dragableCardRelease) $id %W %x %y
570	}
571	$w bind dragableCard <Enter> { enterHilightCard [%W find withtag current] %W %x %y }
572	$w bind dragableCard <Leave> { leaveUnhilightCard [%W find withtag current] %W %x %y }
573
574	#
575	# outlineableCard
576	#
577	$w bind outlineableCard <Enter> { enterHilightCard [%W find withtag current] %W %x %y }
578	$w bind outlineableCard <Leave> { leaveUnhilightCard [%W find withtag current] %W %x %y }
579
580	# this is for debugging
581	$w bind card <ButtonPress-2> { debugCard [%W find withtag current] %W %x %y}
582
583	#
584	# untouched stuff
585	#
586	$w bind untouchedCard <ButtonPress-1> { beginGame current %W %x %y }
587	#
588	$w bind pauseItems <ButtonRelease-1> { unpauseGame }
589
590	#
591	# Keyboard events
592	#
593	bind $w <Control-KeyPress> { # do nothing }
594	bind $w <Meta-KeyPress> { # do nothing }
595#	bind $w <KeyPress> { keyPress "%A" %W }
596	resetKeyState
597}
598
599proc debugCard {id w x y } {
600	global items
601	puts -nonewline "dubugCard $id: "
602	foreach i {location sublocation parent child} {
603		puts -nonewline "$i=$items($id,$i) "
604	}
605	puts ""
606}
607
608
609#
610# keyPress
611#
612# Keypresses are managed by modes
613#	submode: any-(special,suit,value)->(any,value,suit)
614#		value-(suit)->any
615#		suit-(value)->any
616#	source---target
617#
618#
619proc keyPress {asc w} {
620	global table
621
622	if { "$asc" == "" } { return }
623	set asc [string tolower $asc]
624	switch -exact -- $table(keySubState) {
625	"any" {
626		switch -glob -- "$asc" {
627		[cdhs] {
628			set table(keySuit) $asc
629			set newSubState suit
630		}
631		[a23456789tjqk] {
632			set table(keyValue) $asc
633			set newSubState value
634		}
635		[\ ] {
636			set newSubState triple
637		}
638		[\n] {
639			set newSubState double
640		}
641		default {
642			set newSubState error
643		}
644		}
645	}
646	"value" {
647		switch -glob -- $asc {
648		[cdhs] {
649			set table(keySuit) $asc
650			set newSubState complete
651		}
652		[\b] {
653			set newSubState any
654		}
655		default {
656			set newSubState error
657		}
658		}
659	}
660	"suit" {
661		switch -glob -- $asc {
662		[a23456789tjqk] {
663			set table(keyValue) $asc
664			set newSubState complete
665		}
666		[\b] {
667			set newSubState any
668		}
669		default {
670			set newSubState error
671		}
672		}
673	}
674	}
675	#
676	# Handle larger state transitions.
677	#
678	switch -exact -- $table(keyState) {
679	"any" {
680		switch -exact -- $newSubState {
681			"double" {
682				set newState any
683				set newSubState any
684			}
685			"triple" {
686				set newState any
687				set newSubState any
688			}
689			"complete" {
690				set newState card
691				set newSubState any
692			}
693			"error" {
694				set newState error
695				set newSubState any
696			}
697			default {
698				set newState $table(keyState)
699			}
700		}
701	}
702	"card" {
703		switch -exact -- $newSubState {
704			"double" {
705				set newState double
706				set newSubState any
707			}
708			"triple" {
709				set newState triple
710				set newSubState any
711			}
712			"complete" {
713				set newState move
714				set newSubState any
715			}
716			"error" {
717				set newState error
718				set newSubState any
719			}
720			default {
721				set newState $table(keyState)
722			}
723		}
724	}
725	default {
726		set newState any
727		set newSubState any
728	}
729	}
730	#
731	# Take the action.
732	#
733	if { $table(keyState) == "card" && $newState != "card"} {
734		# Unhilight the card.
735		unhilightCard $table(keyFirstId) $w "" ""
736	}
737	switch -exact -- $newState {
738	"double" {
739		set newState any
740	}
741	"triple" {
742		set newState any
743	}
744	"card" {
745		# Check to make sure it's a card with actions.
746		set id [rememberCard "$table(keyValue)$table(keySuit)"]
747		set table(keyFirstId) $id
748		set goodCard 0
749		foreach i [lindex [$w itemconf $id -tags] 4] {
750			switch -exact -- $i {
751			"clickableCard" -
752			"doubleClickableCard" -
753			"dragableCard" {
754				set goodCard 1
755				break
756			}
757			}
758		}
759		if { $goodCard } {
760			# Hilight the selected card.
761			hilightCard $table(keyFirstId) $w "" ""
762		} else {
763			set newState any
764		}
765	}
766	"move" {
767#		if { [cardHasTag $w $table(keyFirstId) dragableCard] } {
768#			set newId [rememberCard "$table(keyValue)$table(keySuit)"]
769#		}
770		set newState any
771	}
772	"error" {
773		tableBell
774		set newState any
775	}
776	}
777	#
778	# Commit the action.
779	#
780	set table(keyState) $newState
781	set table(keySubState) $newSubState
782}
783
784proc resetKeyState {} {
785	global table
786
787	set table(keySubState) any
788	set table(keyState) any
789	set table(keySuit) "x"
790	set table(keyValue) "x"
791	set table(keyFirstId) "x"
792}
793
794
795#
796# outlineableCard
797#
798proc enterHilightCard {itemId w x y} {
799	global game
800	# Avoid hilighting cards that will be obscured by other cards.
801	if { $game(status) == "dealing" } { return }
802	hilightCard $itemId $w $x $y
803}
804proc leaveUnhilightCard {itemId w x y} {
805	global table
806
807	if { $table(keyFirstId) == $itemId } { return }
808	# A bug was happening where sometimes we'd get a null itemId
809	# when redealing the tableau.  It's not consistently repeatable.
810	# Avoid the problem
811	if { $itemId == "" } { return }
812	unhilightCard $itemId $w $x $y
813}
814
815proc hilightCard {itemId w x y} {
816	global table items
817	# puts "hilightCard $itemId $w $x $y"
818	if { [llength $itemId] != 1 } { error "hilightCard: called with list of items." }
819	$table(preActionProc) outlineEnter
820	$w itemconfig $itemId \
821		-foreground $items($itemId,highFg) \
822		-background $items($itemId,highBg)
823}
824proc unhilightCard {itemId w x y} {
825	global table items
826
827	if { [llength $itemId] < 1 } { error "unhilightCard: called with empty items." }
828	if { [llength $itemId] > 1 } { error "unhilightCard: called with list of items." }
829	$table(preActionProc) outlineLeave
830	$w itemconfig $itemId \
831		-foreground $items($itemId,normFg) \
832		-background $items($itemId,normBg)
833}
834proc checkHilighting {w x y} {
835	# Items come back to front, so reverse them.
836	set ids [lreverse [$w find overlapping $x $y $x $y]]
837	foreach id $ids {
838		set tags [lindex [$w itemconfig $id -tags] 4]
839		if { ([lsearch -exact $tags outlineableCard] != -1) ||
840			([lsearch -exact $tags dragableCard] != -1)} {
841			hilightCard $id $w $x $y
842			return
843		}
844	}
845}
846
847#
848# dragableCard
849#
850proc defaultDragableCardPress {itemId w x y} {
851	global table items
852
853	$table(preActionProc) dragPress
854	set table(dragging) 1
855
856	unhilightCard $itemId $w $x $y
857
858	$w dtag selected
859	$w addtag selected withtag $itemId
860	set friends [$items($itemId,dragFindFriendsProc) $itemId $w $x $y $items($itemId,dragFindFriendsClosure)]
861	set table(dragFriends) $friends
862	# NEEDSWORK: Tk3.2 bug.  We shouldn't have to loop here, but
863	# it seems that "$w addtag selected withtag $friends"
864	# just adds one of the list.
865	foreach i $friends {
866		$w addtag selected withtag $i
867	}
868	$w raise selected
869	set table(dragLastX) $x
870	set table(dragLastY) $y
871	set table(dragLastHit) {}
872	set startXY [lrange [$w bbox $itemId] 0 1]
873	set table(dragInitialX) [lindex $startXY 0]
874	set table(dragInitialY) [lindex $startXY 1]
875}
876proc defaultDragableCardMove {itemId w x y} {
877	global table items
878	# debugLog "defaultDragableCardMove $itemId $w $x $y"
879	$w move selected [expr $x-$table(dragLastX)] [expr $y-$table(dragLastY)]
880	set table(dragLastX) $x
881	set table(dragLastY) $y
882
883	set hit [checkForDropableHit $itemId $w]
884	if { $hit != $table(dragLastHit) } {
885		if { $table(dragLastHit) != {} } {
886			$items($table(dragLastHit),dragTargetLeaveProc) $itemId $w $x $y $table(dragLastHit)
887		}
888		if { $hit != {} } {
889			$items($hit,dragTargetEnterProc) $itemId $w $x $y $hit
890		}
891		set table(dragLastHit) $hit
892	}
893}
894
895proc checkForDropableHit { itemId w } {
896	global table items
897
898	#
899	# Check for hit over possible dropableCard.
900	#
901	set bbox [$w bbox $itemId]
902	set bbox_t [expr [lindex $bbox 0]+$table(cardOverlap)]
903	set bbox_l [expr [lindex $bbox 1]+$table(cardOverlap)]
904	set bbox_b [expr [lindex $bbox 2]-$table(cardOverlap)]
905	set bbox_r [expr [lindex $bbox 3]-$table(cardOverlap)]
906	set hits [$w find overlapping $bbox_t $bbox_l $bbox_b $bbox_r]
907	#
908	# Go through the list of hits
909	# (in reverse order---we assume the list is sorted back-to-front).
910	# Quit if we get a good hit.
911	#
912	foreach hit [lreverse $hits] {
913		#
914		# Now check to see if we're over a dropableCard.
915		# (Sigh, there doesn't seem any way to query the tags
916		# of an object.)
917		#
918		if { ([string match $items($hit,dragTargetAcceptGlob) $items($itemId,subtype)] == 0) } { continue }
919		if { [$items($hit,dragTargetAcceptProc) $w $hit $itemId $table(dragFriends)] } {
920			# puts stderr "$itemId: $items($itemId,cardVS),$table(selectedCount) matches $hit: $table($hit,dropAccepts) of $hits"
921			return $hit;
922		}
923	}
924	return {}
925}
926
927proc defaultDragableCardRelease {itemId w x y} {
928	global items table
929	# debugLog "defaultDragableCardRelease $itemId $w $x $y"
930	# Dropping the card somewhere?
931	if { $table(dragLastHit) != {} } {
932		# yes
933		$items($table(dragLastHit),dragTargetLeaveProc) $itemId $w $x $y $table(dragLastHit)
934		playCardOnCard $w $itemId $table(dragFriends) $table(dragLastHit)
935	} elseif { $table(dragging) != 0 } {
936		# no, send it home
937		originalPlaceDragProc $itemId $w $x $y selected {}
938	}
939	# Redo the hilighting.
940	checkHilighting $w $x $y
941}
942
943
944#
945# playCardOnCard
946#
947proc playCardOnCard {w top topFriends newBottom} {
948	global items
949	#
950	# debugLog "playCardOnCard $w $top $topFriends $newBottom"
951	set oldBottom $items($top,parent)
952	if { $oldBottom != {} } {
953		$items($oldBottom,orphanChildProc) $w $oldBottom $top $topFriends $items($oldBottom,orphanChildClosure)
954	}
955	# 2. Connect top to bottom.
956	$items($newBottom,adoptChildProc) $w $newBottom $top $topFriends $items($newBottom,adoptChildClosure)
957}
958
959#
960# Unconditional position card on another card.
961#
962proc moveCardOnCard {w top bottom args} {
963	global items
964
965	# debugLog "moveCardOnCard $w $top $bottom $args"
966	if {[llength $args] == 0} {
967		set topTag $top
968	} else {
969		set topTag [lindex $args 0]
970	}
971
972	set oldCoords [$w coords $top]
973	set bottomCoords [$w bbox $bottom]
974	set bottomOffset [$items($bottom,childOffsetProc)]
975	moveAllRelatively $w $topTag \
976		[lindex $oldCoords 0] [lindex $oldCoords 1] \
977		[expr [lindex $bottomCoords 0]+[lindex $bottomOffset 0]] \
978		[expr [lindex $bottomCoords 1]+[lindex $bottomOffset 1]]
979
980	$w raise $top
981}
982
983
984
985proc moveAllRelatively { w items oldX oldY newX newY } {
986	# debugLog "moveAllRelatively $w $items $oldX $oldY $newX $newY"
987	$w move $items [expr $newX-$oldX] [expr $newY-$oldY]
988}
989
990
991proc figureNextValue {oldValue inc} {
992	global table
993	return [string index $table(cvalues) \
994			[expr [string first $oldValue $table(cvalues)]+$inc]]
995}
996
997proc globNextLowerOtherColor {vs} {
998	global table
999
1000	set v [string index $vs 0]
1001	set s [string index $vs 1]
1002
1003	return "[figureNextValue $v -1]\[$table(otherColorSuits,$s)\]"
1004}
1005
1006proc lreverse {lin} {
1007	set lout {}
1008	for {set i [expr [llength $lin]-1]} { $i >= 0 } {incr i -1} {
1009		lappend lout [lindex $lin $i]
1010	}
1011	return $lout
1012}
1013
1014proc obscureTable {} {
1015	global table
1016	set w $table(id)
1017	$w create rectangle 0 0 $table(width) $table(height) \
1018		-fill $table(bg) -tag pauseItems
1019	$w create text [expr $table(width)/2] [expr $table(height)/2] \
1020			-anchor center -fill $table(fg) \
1021			-text "Game paused.\nClick to continue." \
1022			-tag pauseItems
1023}
1024
1025proc unobscureTable {} {
1026	global table
1027	$table(id) delete pauseItems
1028}
1029