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