1# CardGame.itcl
2#
3#       Creates a card game in a whiteboard canvas.
4#
5#
6# $Id: CardGame.itcl,v 1.14 2007-09-16 07:39:13 matben Exp $
7
8namespace eval CardGame {
9
10    variable infoScript [info script]
11}
12
13# This file may be sourced more than once; protect for this!
14# Is there a better way? Ugly!
15
16if {[itcl::find classes CardGame] == ""} {
17
18    class CardGame {
19
20	inherit CardStack BaseWBCanvas
21
22	# -----------------------
23	# Private static methods.
24	# -----------------------
25
26	private {
27	    proc Init {}
28	    proc Handler {w type cmd args}
29	    proc GetThisFromStackUtag {utag}
30	}
31
32	# ------------------
33	# Class constructor.
34	# ------------------
35
36	protected {
37	    constructor {w x y args} {$this BaseWBCanvas::constructor $w} {}
38	    destructor {}
39	}
40
41	# --------------
42	# Class methods.
43	# --------------
44
45	public {
46	    method Save {id args}
47	    method Delete {id}
48	}
49
50	# ----------------------
51	# Private class methods.
52	# ----------------------
53
54	protected {
55	    method DrawCardStack {x y args}
56	    method MakeMenus {}
57	    method PointPress {x y} {}
58	    method PointMotion {x y} {}
59	    method PointRelease {x y} {}
60	    method MoveInitStack {x y} {}
61	    method MoveMotionStack {x y} {}
62	    method MoveReleaseStack {x y} {}
63	    method MoveInitCard {x y} {}
64	    method MoveMotionCard {x y} {}
65	    method MoveReleaseCard {x y} {}
66	    method DeleteStack {} {}
67	    method DeleteCard {} {}
68	    method StackPopup {x y} {}
69	    method CardPopup {x y} {}
70	    method PopupRelease {}
71	    method NewCard {x y}
72	    method NewBlankCard {}
73	    method NewCardAutoPlace {}
74	    method ShowMe {}
75	    method ShowAll {}
76	    method Hide {}
77	    method MixStack {}
78	    method GetValue {name}
79	    method InvokeCardStack {args}
80	    method CheckIfEmptyStack {}
81	}
82
83	# ----------------------------
84	# Private static data members.
85	# ----------------------------
86
87	private common cardstackMenu
88	private common cardMenu
89	private common cardStatePopup
90	private common stickyDistance 20
91	private common autoPlaceOffset 6
92	private common idpopup
93	private common inited 0
94	private common sound 0
95
96	# ---------------------------
97	# Private class data members.
98	# ---------------------------
99
100	protected {
101	    variable idstack
102	    variable utagstack
103	    variable csObj
104	    variable move
105	    variable idcard
106	    variable currentCard
107	    variable played 0
108	    variable emptyPointStackBinds
109	    variable emptyInstBinds
110	}
111    }
112
113    body CardGame::constructor {w x y args} {
114
115	::Debug 4 "CardGame::constructor $this, args='$args'"
116
117	MakeMenus
118
119	# CardStack object as a canvas item.
120	set csObj [CardStack #auto]
121	$csObj MixCards
122
123	# Make actual canvas item(s).
124	eval {DrawCardStack $x $y} $args
125
126	# All special bindings for the card stack.
127	# %W will be substituted by the canvas widget path.
128	#
129	# IMPORTANT: we cannot access instance specific stuff,
130	# variables and such, when binding to tags shared by many objects!
131	set b1Stack        {%W bind cardstack <Button-1>}
132	set b1MotionStack  {%W bind cardstack <B1-Motion>}
133	set b1ReleaseStack {%W bind cardstack <ButtonRelease-1>}
134	set b1Card         {%W bind playcard <Button-1>}
135	set b1MotionCard   {%W bind playcard <B1-Motion>}
136	set b1ReleaseCard  {%W bind playcard <ButtonRelease-1>}
137
138	set b1StackInst         [list %W bind $idstack <Button-1>]
139	set b1MotionStackInst   [list %W bind $idstack <B1-Motion>]
140	set b1ReleaseStackInst  [list %W bind $idstack <ButtonRelease-1>]
141
142	switch -- [tk windowingsystem] {
143	    aqua {
144		set b1PopupStack {%W bind cardstack <Control-Button-1>}
145		set b1PopupCard  {%W bind playcard  <Control-Button-1>}
146		set b1PopupRel   {%W bind playcard  <Control-ButtonRelease-1>}
147		set b2PopupStack {%W bind cardstack <Button-2>}
148		set b2PopupCard  {%W bind playcard  <Button-2>}
149	    }
150	    default {
151		set b3PopupStack {%W bind cardstack <Button-3>}
152		set b3PopupCard  {%W bind playcard  <Button-3>}
153	    }
154	}
155
156	set classBindList [list \
157	  move    [list $b1Stack        [code $this MoveInitStack %x %y]] \
158	  move    [list $b1MotionStack  [code $this MoveMotionStack %x %y]] \
159	  move    [list $b1ReleaseStack [code $this MoveReleaseStack %x %y]] \
160	  move    [list $b1Card         [code $this MoveInitCard %x %y]] \
161	  move    [list $b1MotionCard   [code $this MoveMotionCard %x %y]] \
162	  move    [list $b1ReleaseCard  [code $this MoveReleaseCard %x %y]] \
163	  del     [list $b1Card         [code $this DeleteCard]] ]
164	set instBindList [list \
165	  point   [list $b1StackInst        [code $this PointPress %x %y]] \
166	  point   [list $b1MotionStackInst  [code $this PointMotion %x %y]] \
167	  point   [list $b1ReleaseStackInst [code $this PointRelease %x %y]] \
168	  del     [list $b1StackInst        [code $this DeleteStack]] ]
169
170	set emptyPointStackBinds [list \
171	  point   [list $b1StackInst        {}] \
172	  point   [list $b1MotionStackInst  {}] \
173	  point   [list $b1ReleaseStackInst {}]]
174	set emptyInstBinds [list \
175	  point   [list $b1StackInst        {}] \
176	  point   [list $b1MotionStackInst  {}] \
177	  point   [list $b1ReleaseStackInst {}] \
178	  del     [list $b1StackInst        {}]]
179
180	switch -- [tk windowingsystem] {
181	    aqua {
182		lappend classBindList \
183		  point   [list $b1PopupStack   [code $this StackPopup %X %Y]] \
184		  *       [list $b1PopupCard    [code $this CardPopup %X %Y]]  \
185		  *       [list $b1PopupRel     [code $this PopupRelease]]  \
186		  point   [list $b2PopupStack   [code $this StackPopup %X %Y]] \
187		  *       [list $b2PopupCard    [code $this CardPopup %X %Y]]
188	    }
189	    default {
190		lappend classBindList \
191		  point   [list $b3PopupStack   [code $this StackPopup %X %Y]] \
192		  *       [list $b3PopupCard    [code $this CardPopup %X %Y]]
193	    }
194	}
195
196	# The naming here is a bit confusing...
197	RegisterCanvasClassBinds CardGame $classBindList
198	RegisterCanvasInstBinds  CardGame:$this $instBindList
199
200	# Check if swash.wav sound available.
201	if {!$inited} {
202	    Init
203	}
204	bind $tkCanvas <Destroy> +[list delete object $this]
205    }
206
207    body CardGame::destructor {} {
208	# empty, so far.
209    }
210
211    body CardGame::Init {} {
212
213	# Check if swash.wav sound available.
214	# Eventually we need an application base class for things like this...
215	if {[component::exists Sounds]} {
216	    ::Sounds::Create swash [file join [GetThis soundsPath] swash.wav]
217	    set sound 1
218	}
219	::WB::RegisterHandler CARDGAME [code Handler]
220	set inited 1
221    }
222
223    body CardGame::Handler {w type cmd args} {
224
225	::Debug 4 "CardGame::Handler w=$w, type=$type, cmd=$cmd"
226
227	switch -- [lindex $cmd 1] {
228	    picked {
229
230		# The remote cardstack has picked this card. Remove from stack!
231		set utag [lindex $cmd 2]
232		set card [lindex $cmd 3]
233
234		# Need to backtrace from utag to actual object.
235		set obj [GetThisFromStackUtag $utag]
236		if {$obj != ""} {
237		    $obj InvokeCardStack DrawCard $card
238		    $obj CheckIfEmptyStack
239		}
240	    }
241	}
242    }
243
244    # GetThisFromStackUtag --
245    #
246    #   Static method to map from a stacks utag to the actual object.
247    #   Needed since utags are the only globally uniqe identifier here.
248
249    body CardGame::GetThisFromStackUtag {utag} {
250
251	set ansObj ""
252	foreach obj [itcl::find objects -class CardGame] {
253	    set tmputag [$obj GetValue utagstack]
254	    if {[string equal $utag $tmputag]} {
255		set ansObj $obj
256		break
257	    }
258	}
259	return $ansObj
260    }
261
262    body CardGame::GetValue {name} {
263	return [set $name]
264    }
265
266    body CardGame::InvokeCardStack {args} {
267	eval {$csObj} $args
268    }
269
270    body CardGame::DrawCardStack {x y args} {
271
272	array set argsArr $args
273	if {[info exists argsArr(-tags)]} {
274	    set utag $argsArr(-tags)
275	} else {
276	    set utag [NewUtag]
277	}
278	set utagstack $utag
279	set im [$csObj Image cardpile]
280
281	# Note that the object name $this is only defined locally!
282	# Use $utagstack for globally identfying the stack.
283	set idstack [$tkCanvas create image $x $y -image $im -anchor nw  \
284	  -tags [list image cardstack object:${this} $utag]]
285
286	foreach {key value} $args {
287	    switch -- $key {
288		-topcard {
289		    $csObj MakeTopmost $value
290		}
291		-cards {
292		    foreach cmd $value {
293			set id [eval {$tkCanvas} $cmd]
294			$tkCanvas addtag cardstackutag:${utagstack} withtag $id
295			$tkCanvas addtag [NewUtag] withtag $id
296		    }
297		}
298	    }
299	}
300    }
301
302    body CardGame::CheckIfEmptyStack {} {
303
304	if {[$csObj NumberOfCards] == 0} {
305	    RegisterCanvasInstBinds CardGame $emptyPointStackBinds
306	    ItemConfigure $idstack -image [$csObj Image black]
307	}
308    }
309
310    body CardGame::MakeMenus {} {
311
312	# Only a single set per canvas.
313	set cardstackMenu ${tkCanvas}.csmenu
314	if {![winfo exists $cardstackMenu]} {
315	    menu $cardstackMenu -tearoff 0
316	    if {0} {
317		$cardstackMenu add command -label [mc "Mix Stack"]  \
318		  -command [code $this MixStack]
319		$cardstackMenu add command -label [mc "New Card"]  \
320		  -command [code $this NewCardAutoPlace]
321	    }
322	    set cardMenu [menu ${tkCanvas}.camenu -tearoff 0]
323	    $cardMenu add radiobutton -label [mc "Show Me"]  \
324	      -command [code $this ShowMe] -variable [scope cardStatePopup] \
325	      -value half
326	    $cardMenu add radiobutton -label [mc "Show All"]  \
327	      -command [code $this ShowAll] -variable [scope cardStatePopup] \
328	      -value up
329	    $cardMenu add radiobutton -label [mc Hide]  \
330	      -command [code $this Hide] -variable [scope cardStatePopup] \
331	      -value back
332	}
333    }
334
335    body CardGame::PointPress {x y} {
336
337	# New anonymous card. True card created on button release.
338	NewBlankCard
339	set off 3
340	$tkCanvas move $idcard $off $off
341	set move(x) [$tkCanvas canvasx $x]
342	set move(y) [$tkCanvas canvasy $y]
343	set move(x0) $move(x)
344	set move(y0) $move(y)
345	set move(id) [$tkCanvas find withtag $idcard]
346
347	# Shadow to highlight that a new card has been created.
348	foreach {x1 y1 x2 y2} [$tkCanvas bbox $idcard] {
349	    incr x1 $off
350	    incr y1 $off
351	    incr x2 $off
352	    incr y2 $off
353	}
354	set move(idshadow) [$tkCanvas create rectangle $x1 $y1 $x2 $y2  \
355	  -outline {} -fill gray60]
356	$tkCanvas lower $move(idshadow) $idcard
357    }
358
359    body CardGame::PointMotion {x y} {
360
361	set x [$tkCanvas canvasx $x]
362	set y [$tkCanvas canvasy $y]
363	set dx [expr {$x - $move(x)}]
364	set dy [expr {$y - $move(y)}]
365	$tkCanvas move $move(id) $dx $dy
366	$tkCanvas move $move(idshadow) $dx $dy
367	set move(x) $x
368	set move(y) $y
369
370	if {$sound && !$played && \
371	  [expr {hypot($x - $move(x0), $y - $move(y0))}] > $stickyDistance} {
372	    ::Sounds::Play swash
373	    set played 1
374	}
375	CancelBox
376    }
377
378    body CardGame::PointRelease {x y} {
379
380	$tkCanvas delete $move(idshadow)
381	set x [$tkCanvas canvasx $x]
382	set y [$tkCanvas canvasy $y]
383	if {[expr {hypot($x - $move(x0), $y - $move(y0))}] < $stickyDistance} {
384	    $tkCanvas delete $idcard
385	} else {
386	    set coo [$tkCanvas coords $idcard]
387	    $tkCanvas delete $idcard
388
389	    # Pick a new card.
390	    eval {NewCard} $coo
391	    if {[$csObj NumberOfCards] == 0} {
392		ItemConfigure $idstack -image [$csObj Image black]
393	    }
394	    set imhalf [$csObj HalfImage $currentCard]
395	    $tkCanvas itemconfigure $idcard -image $imhalf
396	    set imback [$csObj Image back]
397	    set cmd [list create image [$tkCanvas coords $idcard] \
398	      -image $imback -anchor nw -tags [$tkCanvas gettags $idcard]]
399	    Command $cmd remote
400
401	    # We must tell the remote cardstack to remove this card from stack.
402	    GenCommand "CARDGAME: picked $utagstack $currentCard" remote
403	    CheckIfEmptyStack
404	}
405	set played 0
406    }
407
408    body CardGame::MoveInitStack {x y} {
409	InitMoveCurrent $x $y
410    }
411
412    body CardGame::MoveMotionStack {x y} {
413	DragMoveCurrent $x $y
414    }
415
416    body CardGame::MoveReleaseStack {x y} {
417	FinalMoveCurrent $x $y
418	set utag [GetUtag current]
419	Command [list raise $utag]
420    }
421
422    body CardGame::MoveInitCard {x y} {
423	InitMoveCurrent $x $y
424    }
425
426    body CardGame::MoveMotionCard {x y} {
427	DragMoveCurrent $x $y
428    }
429
430    body CardGame::MoveReleaseCard {x y} {
431
432	FinalMoveCurrent $x $y
433	set utag [GetUtag current]
434	Command [list raise $utag]
435    }
436
437    body CardGame::DeleteStack {} {
438
439	set cmdList [list [list delete $utagstack]]
440	DeregisterCanvasInstBinds CardGame:$this
441
442	# Delete all cards as well.
443	foreach id [$tkCanvas find withtag cardstackutag:${utagstack}] {
444	    lappend cmdList [list delete [GetUtag $id]]
445	}
446	CommandList $cmdList
447    }
448
449    body CardGame::DeleteCard {} {
450
451	# We could try having an undo command as well...
452	set utag [GetUtag current]
453	Command [list delete $utag]
454
455    }
456
457    body CardGame::StackPopup {x y} {
458
459	set idpopup [$tkCanvas find withtag current]
460	tk_popup $cardstackMenu [expr {int($x) - 10}] [expr {int($y) - 10}]
461    }
462
463    body CardGame::CardPopup {x y} {
464
465	set idpopup [$tkCanvas find withtag current]
466	set im [$tkCanvas itemcget $idpopup -image]
467	set state [$csObj CardSideFromImage $im]
468	set cardStatePopup $state
469	tk_popup $cardMenu [expr {int($x) - 10}] [expr {int($y) - 10}]
470    }
471
472    body CardGame::PopupRelease {} {
473	CancelBox
474    }
475
476    body CardGame::NewCard {x y} {
477
478	set card [$csObj PopAndMix]
479	set currentCard $card
480	if {$card != ""} {
481	    set im [$csObj Image back]
482
483	    # We use cardstackutag:.. to indicate which stack
484	    # a card comes from.
485	    set tags [list image playcard cardstackutag:${utagstack} \
486	      card:${card} [NewUtag]]
487	    set idcard [$tkCanvas create image $x $y -image $im -anchor nw \
488	      -tags $tags]
489	}
490    }
491
492    body CardGame::NewBlankCard {} {
493
494	foreach {x y} [$tkCanvas coords $idstack] break
495	set im [$csObj Image back]
496	set idcard [$tkCanvas create image $x $y -image $im -anchor nw]
497    }
498
499    body CardGame::NewCardAutoPlace {} {
500
501	# This one does not yet work properly!
502	NewCard
503	if {$idcard != ""} {
504	    foreach {xstack ystack} [$tkCanvas coords $idstack] break
505	    set x [expr {$xstack + $stickyDistance + 20}]
506	    set y $ystack
507	    set cmd [list create image $x $y \
508	      -image [$tkCanvas itemcget $idcard -image] -anchor nw \
509	      -tags [$tkCanvas gettags $idcard]]
510	    Command $cmd
511	}
512    }
513
514    body CardGame::MixStack {} {
515	# We do this when drawing cards
516	#$csObj MixCards
517    }
518
519    body CardGame::ShowMe {} {
520
521	CancelBox
522	set tags [$tkCanvas gettags $idpopup]
523	if {[regexp {card:([a-z0-9]{2})} $tags match card]} {
524	    $tkCanvas itemconfigure $idpopup -image [$csObj HalfImage $card]
525	}
526	set utag [GetUtag $idpopup]
527	Command [list raise $utag]
528    }
529
530    body CardGame::ShowAll {} {
531
532	CancelBox
533	set tags [$tkCanvas gettags $idpopup]
534	if {[regexp {card:([a-z0-9]{2})} $tags match card]} {
535	    ItemConfigure $idpopup -image [$csObj Image $card]
536	}
537	set utag [GetUtag $idpopup]
538	Command [list raise $utag]
539    }
540
541    body CardGame::Hide {} {
542
543	CancelBox
544	set tags [$tkCanvas gettags $idpopup]
545	if {[regexp {card:([a-z0-9]{2})} $tags match card]} {
546	    ItemConfigure $idpopup -image [$csObj Image back]
547	}
548    }
549
550    # CardGame::Save --
551    #
552    #       Returns a oneline import command. Used from app.
553    #
554    # Arguments:
555    #       id          item id or tag
556    #       args:
557    #           -basepath absolutePath    translate image -file to a relative path.
558    #           -uritype ( file | http )
559    #           -keeputag 0|1
560    #
561    # Results:
562    #       a single command line.
563
564    body CardGame::Save {id args} {
565
566	# Seemed to be the only way :-(
567	upvar [namespace current]::infoScript infoScript
568
569	set tags [$tkCanvas gettags $id]
570	if {[lsearch $tags cardstack] < 0} {
571	    return
572	}
573	array set argsArr {
574	    -uritype file
575	}
576	array set argsArr $args
577	set uriopts [eval {
578	    ::CanvasUtils::GetImportOptsURI $argsArr(-uritype) $infoScript
579	} $args]
580
581	set impcmd [concat "import" [$tkCanvas coords $id] $uriopts]
582	set im [$csObj Image cardpile]
583	lappend impcmd -mime application/x-itcl
584	lappend impcmd -width [image width $im] -height [image height $im]
585
586	# Get topmost card.
587	lappend impcmd -topcard [$csObj TopCard]
588	set allcards {}
589
590	# Find all cards.
591	set tagsearch playcard&&cardstackutag:${utagstack}
592	foreach idc [$tkCanvas find withtag $tagsearch] {
593	    set ctags [$tkCanvas gettags $idc]
594	    set savetags {image playcard}
595	    if {[regexp {card:([a-z0-9]{2})} $ctags match card]} {
596		lappend savetags card:${card}
597	    }
598	    if {[regexp {(state:([a-z]+))} $ctags match stag state]} {
599		lappend savetags state:${state}
600	    }
601	    set im [$tkCanvas itemcget $idc -image]
602	    set cardspec [concat {create image} [$tkCanvas coords $idc] \
603	      -anchor nw -tags [list $savetags] -image $im]
604	    lappend allcards $cardspec
605	}
606	lappend impcmd -cards $allcards
607	return $impcmd
608    }
609
610
611    body CardGame::Delete {id} {
612
613	switch -- $id $idstack {
614	    DeleteStack
615	} default {
616
617	    # never used it seems...
618	    set utag [GetUtag $id]
619	    return [list [list delete $utag] {}]
620	}
621    }
622}
623
624# We must instantiate ourself...
625
626eval {CardGame #auto $w $x $y} $args
627
628
629
630