-- 作者:wanghai00
-- 发布时间:4/13/2006 4:09:00 PM
--
下面为用到的js:dragsvg.js /* Draggable SVG Library Copyright(c) 2006, Jeff Schiller, CodeDread http://blog.codedread.com/ Location ======== The official location of this library is: http://www.codedread.com/dragsvg.js Please check back regularly for updates. Description =========== This script allows you to enable SVG entities to be draggable via the mouse. Instructions are: - Include xmlns:drag="http://www.codedread.com/dragsvg" in your svg element - Add drag:enable="true" to any SVG elements you want to be able to drag - call initializeDraggableElements() (typically in the document's onload event) to enable dragging for all elements with drag:enable="true" and/or - call enableDrag(ele)/disableDrag(ele) to enable/disable dragging for an individual element - use addEventListener(DRAG_EVENT, callback), where DRAG_EVENT is one of "dragstart", "dragmove", "dragdrop". The callback receives a DragEvent (see constructor below). To allow for a SVG entity to be dragged, it must include the drag:enable="true" (for instance: <circle drag:enable="true" .../>) and have no more than a simple translation (this function does not handle rotations, skews or scales. If your drawing's root element includes rotations/skews/scales, then wrap it in a draggable <g> element that includes the translation. For instance: <svg .... xmlns:drag="http://www.codedread.com/dragsvg" onload="initializeDraggableElements()"> <g id="icon_wrapper" drag:enable="true" transform="translate(100,300)"> ... your drawing here ... </g> <circle id="spot" cx="400" cy="300" r="50" fill="blue" drag:enable="true"/> </svg> Tested Configurations ===================== + tested and works in IE6 + ASV (3 and 6) + tested and works in Firefox 1.5 (native SVG) - tested and DOES NOT work in Opera 9 TP1 (I hope Opera 9 final will fix this) History ======= Version Date Notes ---------------------------------------------------------------------------- 1.0 2006-01-05 Initial version. 1.1 2006-01-18 a) Upon enabling any entities for dragging, event listeners are attached to the <svg> node for mouseUp and mouseMove b) Provided a function to register callbacks for drag events. 1.2 2006-01-25 Several minor fixes, including a rewrite of the getScreenTCM() function by Johan Sundstrom (http://ecmanaut.blogspot.com/) Thanks To ========= - Antoine Quint for his 3-year old article on dragging in SVG - Holger Will for his getScreenCTM() function which I subsequently tweaked to support percentages at the <svg> element level: http://groups.yahoo.com/group/svg-developers/message/50789 - Bjoern Hoehrmann for pointing out on the Freenode #svg IRC channel that getElementsByTagName() returns a NodeList (not an Array) thereby helping me keep my sanity - Johan Sundstrom for rewriting the getScreenTCM() using much better JavaScript Disclaimer and License ====================== This script is free to use and/or modify, but please provide credit and (where applicable) a link to http://www.codedread.com/ would be nice. TO DO: ====== 1) Make my parsing of transform string more reliable (and be able to handle rotations, scalings, skewings) 2) Allow for a drag:constraintXXXX attribute which defines a rectangle that limits the values of the transform attribute when being dragged. These values are "live", so they must be checked every time the mouse moves when being dragged. If the element is attempted to be dragged outside the bounds the mouse cursor position must be changed (i.e. stopped from movement) - can preventDefault() do this? Contact ======= Email comments/suggestions/bug reports to jeff at codedread dot com. */ var SVGNS = "http://www.w3.org/2000/svg"; var DRAGNS = "http://www.codedread.com/dragsvg"; var DRAGSTART = "dragstart"; var DRAGMOVE = "dragmove"; var DRAGDROP = "dragdrop"; function DragEvent(_mouseevt, _clientx, _clienty, _userx, _usery, _dragEnt, _mouseoffsetx, _mouseoffsety) { this.mouseEvt = _mouseevt; this.clientx = _clientx; this.clienty = _clienty; this.userx = _userx; this.usery = _usery; this.dragEnt = _dragEnt; this.mouseoffsetx = _mouseoffsetx; this.mouseoffsety = _mouseoffsety; } function inspect(obj) { var str = new Array(); var element = null; for(element in obj) { str[str.length] = element; } str.sort(); alert(obj + ":" + str.join(' ')); } var draggingElement = null; var nMouseOffsetX = 0; var nMouseOffsetY = 0; var screenCTMInv = null; var dragStartCallbacks = new Array(); var dragMoveCallbacks = new Array(); var dragDropCallbacks = new Array(); function addDragEventListener(dragEventStr, callbackFunc) { if(callbackFunc == null) { return; } var arr = null; if(dragEventStr == DRAGSTART) { arr = dragStartCallbacks; } else if(dragEventStr == DRAGMOVE) { arr = dragMoveCallbacks; } else if(dragEventStr == DRAGDROP) { arr = dragDropCallbacks; } else { return; } for(var func = 0; func < arr.length; ++func) { if(arr[func] == callbackFunc) { return; } } arr[arr.length] = callbackFunc; } function removeDragEventListener(dragEventStr, callbackFunc) { if(callbackFunc == null) { return; } var arr = null; if(dragEventStr == DRAGSTART) { arr = dragStartCallbacks; } else if(dragEventStr == DRAGMOVE) { arr = dragMoveCallbacks; } else if(dragEventStr == DRAGDROP) { arr = dragDropCallbacks; } else { return; } for(var func = 0; func < arr.length; ++func) { if(arr[func] == callbackFunc) { delete arr[func]; return; } } } function mouseDown(evt) { var target = evt.currentTarget; draggingElement = target; if(target) { var p = document.documentElement.createSVGPoint(); // p now contains the mouse position in browser client area in pixels p.x = evt.clientX; p.y = evt.clientY; screenCTMInv = getScreenCTM().inverse(); // p now contains the mouse position in SVG user coords p = p.matrixTransform(screenCTMInv); // nMouseOffsetX keeps track of how far the mouse dragged // since the last movement nMouseOffsetX = p.x - parseFloat(target.getAttributeNS(DRAGNS, "x")); nMouseOffsetY = p.y - parseFloat(target.getAttributeNS(DRAGNS, "y")); if(dragStartCallbacks.length) { var dragEvt = new DragEvent(evt, evt.clientX, evt.clientY, p.x, p.y, draggingElement, nMouseOffsetX, nMouseOffsetY); for(var func = 0; func < dragStartCallbacks.length; ++func) { if(dragStartCallbacks[func]) { dragStartCallbacks[func](dragEvt); break; } } } } evt.stopPropagation(); } function mouseUp(evt) { if(draggingElement) { if(dragDropCallbacks.length) { var p = document.documentElement.createSVGPoint(); p.x = evt.clientX; p.y = evt.clientY; screenCTMInv = getScreenCTM().inverse(); p = p.matrixTransform(screenCTMInv); // p.x -= nMouseOffsetX; // p.y -= nMouseOffsetY; var dragEvt = new DragEvent(evt, evt.clientX, evt.clientY, p.x, p.y, draggingElement, nMouseOffsetX, nMouseOffsetY); for(var func = 0; func < dragDropCallbacks.length; ++func) { if(dragDropCallbacks[func]) { dragDropCallbacks[func](dragEvt); break; } } } draggingElement = null; nMouseOffsetX = 0; nMouseOffsetY = 0; } } function mouseMove(evt) { if(draggingElement) { var p = document.documentElement.createSVGPoint(); p.x = evt.clientX; p.y = evt.clientY; p = p.matrixTransform(screenCTMInv); p.x -= nMouseOffsetX; p.y -= nMouseOffsetY; // at this point, we need to check if the drag is constrained // (look for drag:constrainLeft, constrainTop, etc on the draggingElement) var left = draggingElement.getAttributeNS(DRAGNS, "constraintLeft"); var top = draggingElement.getAttributeNS(DRAGNS, "constraintTop"); var right = draggingElement.getAttributeNS(DRAGNS, "constraintRight"); var bottom = draggingElement.getAttributeNS(DRAGNS, "constraintBottom"); if(left && p.x < left) { p.x = left; } else if(right && p.x > right) { p.x = right; } if(top && p.y < top) { p.y = top; } else if(bottom && p.y > bottom) { p.y = bottom; } draggingElement.setAttributeNS(DRAGNS, "x", p.x); draggingElement.setAttributeNS(DRAGNS, "y", p.y); draggingElement.setAttributeNS(null, "transform", "translate(" + p.x + "," + p.y + ")"); if(dragMoveCallbacks.length) { var dragEvt = new DragEvent(evt, evt.clientX, evt.clientY, p.x, p.y, draggingElement, nMouseOffsetX, nMouseOffsetY); for(var func = 0; func < dragMoveCallbacks.length; ++func) { if(dragMoveCallbacks[func]) { dragMoveCallbacks[func](dragEvt); break; } } } } } var bEnableSVGListeners = true; function enableDrag(ele) { if(ele) { if(bEnableSVGListeners) { // add event listeners to the <svg> node for mouse up and mouse move document.documentElement.addEventListener("mouseup", mouseUp, false); document.documentElement.addEventListener("mousemove", mouseMove, false); bEnableSVGListeners = false; } ele.setAttributeNS(DRAGNS, "enable", true); // add event listener ele.addEventListener("mousedown", mouseDown, false); // find transform attribute, extract drag:x/drag:y // The SVG 1.1 DOM provides facilities to get a transform list for // any transformable entity, unfortunately, ASV does not support this // interface, so we're stuck with parsing the "transform" attribute. // // Incidentally, this was absolutely the biggest pain...the transform // attribute could be in any of the following forms: // transform="translate(100)" (ty is zero here) // transform="translate( 100 )" (ty is zero here) // transform="translate(100,200)" // transform="translate(100 200)" // transform="translate( 100,200)" // transform="translate(100,200 )" // transform="translate(100 , 200)" // transform="translate( 100 , 200 ) " // etc... var tx = 0; var ty = 0; // First, I split the transform string by the parentheses, // the second word is the stuff between the parantheses, which is // my x (and possibly y) coordinate var xformstr = ele.getAttributeNS(null, "transform"); if(xformstr && xformstr.length > 0) { xformstr = xformstr.split(/[\(\)]/)[1]; // parsing this string for a float will always give me the x coordinate tx = parseFloat(xformstr); if(isNaN(tx)) { tx = 0; } // but we play it safe anyway // convert tx into a string on-the-fly to get its length, find the occurrence // of the tx string in the original xformstr, get the substring following // that and parse it into a float for the y-coord ty = parseFloat(xformstr.substr(xformstr.indexOf(tx)+(""+tx).length+1)); if(isNaN(ty)) { ty = 0; } // if it's NaN, then it defaults to zero } else { ele.setAttributeNS(null, "transform", "translate(0,0)"); } // now set the DOM dragx/dragy attributes ele.setAttributeNS(DRAGNS, "x", tx); ele.setAttributeNS(DRAGNS, "y", ty); // copy old cursor value (if present) ele.setAttributeNS(DRAGNS, "oldcursor", ele.getAttributeNS(null, "cursor")); ele.setAttributeNS(null, "cursor", "move"); } } function disableDrag(ele) { if(ele) { ele.setAttributeNS(DRAGNS, "enable", false); ele.removeEventListener("mousedown", mouseDown, false); // restore old cursor ele.setAttributeNS(null, "cursor", ele.getAttributeNS(DRAGNS, "oldcursor")); } } // This method scans through all SVG entities, looks for the class attribute and // determines if the SVG entity is included in the "draggable" class. If so, it // adds a mousedown event listener and then extracts the initial translation to // establish the drag:x/drag:y attributes which we'll use as shortcuts upon dragging. function initializeDraggableElements() { var allChildren = document.documentElement.getElementsByTagNameNS(SVGNS, "*"); for(var child = 0; child < allChildren.length; ++child) { // Adobe doesn't allow array access var ele = allChildren.item(child); // if ele has drag:enable="true", then set it up var bFoundDraggable = (ele.getAttributeNS(DRAGNS, "enable") == "true"); if(bFoundDraggable) { enableDrag(ele); } // if draggable } // for(var child... } // end initializeDraggableElements() function getScreenCTM(){ // now we find the screen CTM of the document SVG element var root = document.documentElement; var sCTM = root.createSVGMatrix(); var tr = root.createSVGMatrix(); var par = root.getAttributeNS(null, "preserveAspectRatio"); if (!par) { par="xMidYMid meet"; } //setting to default value parX = par.substring(0,4); //xMin;xMid;xMax parY = par.substring(4,8); //YMin;YMid;YMax; ma = par.split(' '); mos = ma[1]; //meet;slice //get dimensions of the viewport sCTM.a = 1; sCTM.d = 1; sCTM.e = 0; sCTM.f = 0; w = root.getAttribute('width') || '100%'; // w = innerWidth; h = root.getAttribute('height')|| '100%'; // h = innerHeight; if( w.indexOf('%')+1 ) w = (parseFloat(w) / 100.0) * innerWidth; if( h.indexOf('%')+1 ) h = (parseFloat(h) / 100.0) * innerHeight; // get the ViewBox var vb = (root.getAttribute('viewBox') || '0 0 '+w+' '+h).split(' '); //-------------------------------------------------------------------------- //create a matrix with current user transformation tr.a = root.currentScale; tr.d = root.currentScale; tr.e = root.currentTranslate.x; tr.f = root.currentTranslate.y; // scale factors sx = w/vb[2]; sy = h/vb[3]; // meetOrSlice if(mos=="slice") { s = (sx>sy ? sx : sy); } else { s = (sx<sy ? sx : sy); } //preserveAspectRatio="none" if (par=="none") { sCTM.a = sx; //scaleX sCTM.d = sy; //scaleY sCTM.e = -vb[0]*sx; //translateX sCTM.f = -vb[0]*sy; //translateY sCTM = tr.multiply(sCTM);//taking user transformations into account } else { sCTM.a = s; //scaleX sCTM.d = s; //scaleY //------------------------------------------------------- switch(parX){ case 'xMid': sCTM.e = ((w-vb[2]*s)/2) - vb[0]*s; //translateX break; case 'xMin': sCTM.e = - vb[0]*s; //translateX break; case 'xMax': sCTM.e = (w-vb[2]*s)- vb[0]*s; //translateX break; } //------------------------------------------------------------ switch(parY){ case 'YMid': sCTM.f = (h-vb[3]*s)/2 - vb[1]*s; //translateY break; case 'YMin': sCTM.f = - vb[1]*s; //translateY break; case 'YMax': sCTM.f = (h-vb[3]*s) - vb[1]*s; //translateY break; } sCTM = tr.multiply(sCTM); //taking user transformations into acount } // else return sCTM; } solitaire.js // Copyright (c) 2006, Jeff Schiller // http://www.codedread.com/ /* Thanks To: - David Bellot for his fantastic artwork */ var SVGNS = "http://www.w3.org/2000/svg"; var XLINKNS = "http://www.w3.org/1999/xlink"; var DRAGNS = "http://www.codedread.com/dragsvg"; var eNOSUIT = 0; var eHEART = 1; var eDIAMOND = 2; var eCLUB = 3; var eSPADE = 4; var eSUITMIN = 1; var eSUITMAX = 4; var gSuitNames = [ "UNKNOWN SUIT", "heart", "diamond", "club", "spade" ]; var gCardNames = [ "UNKNOWN CARD", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "jack", "queen", "king", "joker" ]; var gBackName = "#card_back"; var eJOKER = 0; var eJACK = 11; var eQUEEN = 12; var eKING = 13; var eCARDS_PER_SUIT = 13; function Card(inCardNum) { inCardNum = parseInt(inCardNum); if(inCardNum < 0 || inCardNum > 53) { alert("Card Number is invalid (0-53, actual value = " + inCardNum + ")"); return; } this.cardNum = inCardNum; this.bFaceUp = false; // get SVG element name var val = this.getValue(); if(val == eJOKER) { this.elemName = "#" + ( this.isRed() ? "red" : "black" ); this.elemName += "_joker"; } else { // TO DO: Once new artwork is in, restore this // this.elemName = "#" + val; // var suit = this.getSuit(); // if(suit != "eNOSUIT") { // this.elemName += ("_" + gSuitNames[suit]); // } // else { // alert("Invalid card, card num = " + this.cardNum); // } this.elemName = "#my_"; var suit = this.getSuit(); if(suit != "eNOSUIT") { this.elemName += (gSuitNames[suit] + "_" + gCardNames[val]); } else { alert("Invalid card, card num = " + this.cardNum); } } } Card.prototype.isJoker = function() { return ((this.cardNum < 1) || (this.cardNum > 52)); } Card.prototype.getValue = function() { if(this.isJoker()) return eJOKER; return ((this.cardNum-1)%eCARDS_PER_SUIT)+1;} Card.prototype.getSuit = function() { var suit = Math.floor( (this.cardNum-1) / eCARDS_PER_SUIT)+1; if(suit < eSUITMIN || suit > eSUITMAX) suit = eNOSUIT; return suit; } Card.prototype.isRed = function() { var suit = this.getSuit(); if(suit == eNOSUIT) { return (this.cardNum == 0); } return (suit == eHEART || suit == eDIAMOND); } Card.prototype.isFace = function() { var val = this.getValue(); return ( (val >= eJACK && val <= eKING) || this.isJoker() ); } Card.prototype.isFaceUp = function() { return this.bFaceUp; } Card.prototype.toString = function() { return gCardNames[this.getValue()] + " of " + gSuitNames[this.getSuit()] + "s"; } Card.prototype.flip = function() { this.bFaceUp = !this.bFaceUp; } // Model the solitaire game aspects here // BEGIN SOLITAIRE MODEL var CARD_VERT_OFFSET = 50; // A wrapper class for a tableau pile of cards that has the ability to display // as SVG and can display them as fanned vertically. function TableauPile(g) { if(g == null) { alert("TableauPile was initialized with a null <g> element"); } this.rootElem = g; this.elem = g; this.cards = new Array(); } TableauPile.prototype.pop = function() { if(this.cards.length < 1) { return; } var card = this.cards.pop(); // update DOM // if(card && ( card.isFaceUp() || this.cards.length < 1 )) { var removeMe = this.elem; this.elem = this.elem.parentNode; this.elem.removeChild(removeMe); // } // If the top card exposed is face down, allow it to be // clicked to flip it var newTop = this.top(); if(newTop && !newTop.isFaceUp()) { this.elem.addEventListener("click", flipTopTableauCard, false); } return card; } function flipTopTableauCard(evt) { var target = evt.currentTarget; var id = target.getAttributeNS(null, "id"); var tableau = parseInt(id.substr(id.indexOf("_")-1, 1)) - 1; var card = gTableau[tableau].pop(); card.flip(); gTableau[tableau].push(card); gTableau[tableau].elem.removeEventListener("click", flipTopTableauCard, false); } TableauPile.prototype.push = function(card) { var oldSize = this.cards.length; // if the currently top card is face down, disable flipping var oldtop = this.top(); if(oldtop && !oldtop.isFaceUp()) { this.elem.removeEventListener("click", flipTopTableauCard, false); } this.cards.push(card); // update DOM var newg = document.createElementNS(SVGNS, "g"); var gid = this.rootElem.getAttributeNS(null, "id") + "_" + oldSize; newg.setAttributeNS(null, "id", gid); newg.setAttributeNS(null, "transform", "translate(0,"+CARD_VERT_OFFSET+")"); var use = document.createElementNS(SVGNS, "use"); if(card.isFaceUp()) { use.setAttributeNS(XLINKNS, "href", card.elemName); } else { use.setAttributeNS(XLINKNS, "href", gBackName); } newg.appendChild(use); this.elem.appendChild(newg); this.elem = newg; if(card.isFaceUp()) { // alert("Enabling drag on " + card.toString() + " in TableauPile.push()"); enableDrag(document.getElementById(gid)); } } TableauPile.prototype.top = function() { // could have used at(length-1) here return (this.cards.length > 0) ? this.cards[this.cards.length-1] : null; } TableauPile.prototype.bottom = function() { // could have used at(0) here return (this.cards.length > 0) ? this.cards[0] : null; } TableauPile.prototype.size = function() { return this.cards.length; } TableauPile.prototype.clear = function() { if(this.cards.length < 1) { return; } // pop all the cards directly (i.e. not using TableauPile.pop()) while(this.cards.length > 0) { this.cards.pop(); } while(this.elem.hasChildNodes()) { this.elem.removeChild(this.elem.firstChild); } } TableauPile.prototype.at = function(ind) { var index = parseInt(ind); return (index >= 0 && index < this.cards.length) ? this.cards[index] : null; } function CardStack(g,_name) { if(g == null) { alert("CardStack was initialized with a null <g> element"); } this.elem = g; this.name = _name; this.cards = new Array(); } CardStack.prototype.pop = function() { if(this.cards.length < 1) { return; } var card = this.cards.pop(); // if we are face up or the stack wasn't empty but now is if(card.isFaceUp() || this.cards.length < 1) { // update DOM // clear stack DOM element this.elem.removeChild(this.elem.lastChild); if(this.elem.hasChildNodes()) { // alert("Enabling drag on " + this.top().toString() + " in CardStack.pop()"); enableDrag(this.elem.lastChild); } } return card; } CardStack.prototype.push = function(card,allowDrag) { var oldLength = this.cards.length; // if we had a face-up card, disable drag on it and make it the second card if(this.cards.length > 0 && this.top().isFaceUp()) { disableDrag(this.elem.lastChild); } this.cards.push(card); // update DOM // if we are face up or the stack was empty but now is not if(card.isFaceUp() || oldLength < 1) { var use = document.createElementNS(SVGNS, "use"); if(card.isFaceUp()) { use.setAttributeNS(XLINKNS, "href", card.elemName); if(allowDrag) { use.setAttributeNS(null, "id", "waste_" + oldLength); } } else { use.setAttributeNS(XLINKNS, "href", gBackName); } this.elem.appendChild(use); if(card.isFaceUp() && allowDrag) { // alert("enabled drag on " + card.toString() + " in CardStack.push()"); enableDrag(this.elem.lastChild); } } } CardStack.prototype.top = function() { // could have used at(length-1) here return (this.cards.length > 0) ? this.cards[this.cards.length-1] : null; } CardStack.prototype.bottom = function() { // could have used at(0) here return (this.cards.length > 0) ? this.cards[0] : null; } CardStack.prototype.size = function() { return this.cards.length; } CardStack.prototype.clear = function() { if(this.cards.length < 1) { return; } while(this.cards.length > 0) { this.cards.pop(); } var length = this.cards.length; // pop all the cards directly for(var loop = 0; loop < length; ++loop) { this.cards.pop(); } // update DOM while(this.elem.hasChildNodes()) { this.elem.removeChild(this.elem.firstChild); } } CardStack.prototype.at = function(ind) { var index = parseInt(ind); return (index >= 0 && index < this.cards.length) ? this.cards[index] : null; } CardStack.prototype.shuffle = function() { if(this.cards.length < 1) { return; } status("Shuffling Cards..."); // shuffle 10 times arbitrarily for(var iter = 0; iter < 10; ++iter) { for(var cardNum = 0; cardNum < this.cards.length; ++cardNum) { var swapper = Math.floor(Math.random()*this.cards.length); var tempCard = this.cards[cardNum]; this.cards[cardNum] = this.cards[swapper]; this.cards[swapper] = tempCard; } } if(this.top().isFaceUp()) { // TO DO: update DOM alert("Fatal Error! Tried to shuffle a face up deck"); } status("Ready..."); } function status(str) { // TO DO: Implement in SVG and here } // Solitaire Game Logic begins here // this holds the unique 52 cards gAllCards = new Array(52); for(var loop = 0; loop < gAllCards.length; ++loop) { gAllCards[loop] = new Card(loop+1); } gTableau = new Array(); gFoundation = new Array(); gDeck = null; gWaste = null; gDragPile = null; function startSolitaire() { var loop = 0; // reset all decks gWaste = new CardStack(document.getElementById("waste"), "waste"); // set the deck to contain all the cards and shuffle them gDeck = new CardStack(document.getElementById("deck"), "deck"); for(loop = 0; loop < gAllCards.length; ++loop) { gDeck.push(gAllCards[loop], false); } gDeck.shuffle(); for(loop = 0; loop < 7; ++loop) { gTableau[loop] = new TableauPile(document.getElementById("tableau" + (loop+1))); } for(loop = eSUITMIN; loop <= eSUITMAX; ++loop) { gFoundation[loop] = new CardStack(document.getElementById("foundation_"+gSuitNames[loop])); } // now deal the deck to the tableau for(loop = 0; loop < 7; ++loop) { // top card is always face up var card = gDeck.pop(); card.flip(); gTableau[loop].push(card); for(var tableauNum = loop+1; tableauNum < 7; ++tableauNum) { var card = gDeck.pop(); gTableau[tableauNum].push(card); } } // enable draw action on the deck gDeck.elem.addEventListener("click", drawCard, false); } function drawCard() { var card = null; if(gDeck.size() > 0) { card = gDeck.pop(); if(card == null) { alert('huh?'); } card.flip(); // var wasteWasEmpty = (gWaste.size() == 0); gWaste.push(card,true); // if deck is empty, turn off drawCard capability if(gDeck.size() <= 0) { // alert("here"); gDeck.elem.removeEventListener("click", drawCard, false); document.getElementById("deckoutline").addEventListener("click", flipDeck, false); // alert("hi there"); } } return card; } function flipDeck() { var numCards = gWaste.size(); for(var loop = 0; loop < numCards; ++loop) { var card = gWaste.pop(); card.flip(); gDeck.push(card,false); } document.getElementById("deckoutline").removeEventListener("click", flipDeck, false); gDeck.elem.addEventListener("click", drawCard, false); } // This method returns false if the move failed (in which case it is the caller's // responsibility to re-own the card objects) or true if the cards were // successfully moved to the tableau pile (in which case the caller should // relinquish all responsibility of the card objects) // cardPile is a JavaScript array of Card objects here function moveCardsToTableau(pile, tableauNum) { var tabNum = parseInt(tableauNum); if(tabNum < 0 || tabNum >= 7) { return false; } var tableau = gTableau[tabNum]; if(pile.length < 1) { return false; } var bResult = false; // if tableau is empty and bottom card in pile is a king OR // if tableau top card is face up, is one higher in value and is the opposite color // of the bottom card in pile if( (tableau.size() == 0 && pile[0].getValue() == eKING) || (tableau.size() > 0 && tableau.top().isFaceUp() && tableau.top().getValue() == pile[0].getValue()+1 && tableau.top().isRed() != pile[0].isRed()) ) { for(var loop = 0; loop < pile.length; ++loop) { tableau.push(pile[loop]); } bResult = true; } return bResult; } // This method returns false if the move failed (in which case it is the caller's // responsibility to re-own the card object) or true if the card was successfully // moved to the foundation pile (in which case the caller should relinquish all // responsibility of the card object) function moveCardToFoundation(card, foundationNum) { var foundNum = parseInt(foundationNum); if(foundNum < 1 || foundNum > 4) { return false; } if(card == null || !card.isFaceUp()) { return false; } var bResult = false; // card must be of the proper suit if(card.getSuit() == foundNum) { // if foundation is empty and the card is an ace OR // if foundation top card is one lower in value if( (gFoundation[foundNum].size() == 0 && card.getValue() == 1) || (gFoundation[foundNum].size() > 0 && gFoundation[foundNum].top().getValue() == (card.getValue()-1) ) ) { gFoundation[foundNum].push(card,false); bResult = true; } } return bResult; } function dumpPile(pile) { var str = ""; for(var loop = 0; loop < 13; ++loop) { if(pile.at(loop*4)) str += pile.at(loop*4).toString() + ", "; if(pile.at(loop*4+1)) str += pile.at(loop*4+1).toString() + ", "; if(pile.at(loop*4+2)) str += pile.at(loop*4+2).toString() + ", "; if(pile.at(loop*4+3)) str += pile.at(loop*4+3).toString() + ",\n"; } alert(str); } // waste = -1 // tableau = 0...7 // This makes sure the appropriate pile of cards is moved to the // bottom of "CardContainer" to ensure the drag is over top of // all other cards function dragStart(evt) { var container = document.getElementById("CardContainer"); if(!evt.dragEnt) { alert("dragStart(null)"); } var id = evt.dragEnt.getAttributeNS(null, "id"); var letter = id.indexOf("_"); if(letter != -1 && container) { if(eleToTop = document.getElementById(id.substr(0,letter))) { container.appendChild( eleToTop.parentNode.removeChild(eleToTop) ); } } // NOTE that the dragged element is still a child of the parent at this point. // Only the parent's z-index has changed, really. } // now determine where the dragged element has been dropped function dragStop(evt) { // determine what logical elements (i.e. Cards) were being dragged and // determine the drop point from a physical to a logical perspective var cardPile = new Array(); if(!evt.dragEnt) { alert("dragStop(null)"); } var id = evt.dragEnt.getAttributeNS(null, "id"); var pile = (id.substr(0,id.indexOf("_"))); var dropx = 0; var dropy = 0; var fromPile = null; var fromTableauNum = -1; var bMoveItBack = true; if(pile == "waste") { fromPile = gWaste; cardPile[0] = gWaste.top(); dropx = 1850; dropy = 120; } else { var tabNum = parseInt(pile.charAt(pile.length-1)) - 1; var cardNum = parseInt(id.substr(id.indexOf("_")+1)); fromPile = gTableau[tabNum]; fromTableauNum = tabNum; for(var loop = cardNum; loop < fromPile.size(); ++loop) { cardPile[cardPile.length] = fromPile.at(loop); } dropx = 120 + tabNum*200; dropy = 120 + (fromPile.size()-1)*CARD_VERT_OFFSET; // every card has a transform of translate(0,CARD_VERT_OFFSET) dropy -= CARD_VERT_OFFSET; } dropx += parseFloat(evt.dragEnt.getAttributeNS(DRAGNS, "x")); dropy += parseFloat(evt.dragEnt.getAttributeNS(DRAGNS, "y")); // map the physical dropx/dropy to a logical element var dropTarget = -1; var tabx = (dropx - 120)/200; for(var tab = 0; tab < 7; ++tab) { if( (tabx+0.3) >= tab && (tabx-0.3) <= tab && tab != fromTableauNum) { dropTarget = tab; break; } } // for each tableau // determine if the move is allowed if(dropTarget != -1) { if(moveCardsToTableau(cardPile, dropTarget)) { for(var loop = 0; loop < cardPile.length; ++loop) { fromPile.pop(); bMoveItBack = false; } } } // if it wasn't a tableau, check the foundations (must only be 1 card) else if(cardPile.length == 1) { // - Diamond = (1650,595) // - Heart = (1850,595) // - Club = (1650,870) // - Spade = (1850,870) // only check if it wasn't any of the tableaus var XTOL = 65; var YTOL = 65; if( checkPointTol(dropx, dropy, 1650, 595, XTOL, YTOL) ) { dropTarget = eDIAMOND; } else if( checkPointTol(dropx, dropy, 1850, 595, XTOL, YTOL) ) { dropTarget = eHEART; } else if( checkPointTol(dropx, dropy, 1650, 870, XTOL, YTOL) ) { dropTarget = eCLUB; } else if( checkPointTol(dropx, dropy, 1850, 870, XTOL, YTOL) ) { dropTarget = eSPADE; } // determine if the move is allowed if(dropTarget != -1) { if(moveCardToFoundation(cardPile[0], dropTarget) && fromPile != null) { fromPile.pop(); bMoveItBack = false; } } } if(bMoveItBack) { for(var loop = 0; loop < cardPile.length; ++loop) { fromPile.pop(); } for(var loop = 0; loop < cardPile.length; ++loop) { if(fromPile == gWaste) { fromPile.push(cardPile[loop],true); } else { fromPile.push(cardPile[loop]); } } } } function checkPointTol(actualx, actualy, targetx, targety, tolx, toly) { return ( (actualx+tolx) >= targetx && (actualx-tolx) <= targetx && (actualy+toly) >= targety && (actualy-toly) <= targety ); } function initGame() { addDragEventListener("dragstart", dragStart); addDragEventListener("dragdrop", dragStop); startSolitaire(); // dumpDeck(); } // END SOLITAIRE MODEL function init() { setTimeout("initGame()", 50); } /* // pops all cards from the indicated card down from the pile and returns a new TableauPile TableauPile.prototype.popSubPile = function(ind) { var index = parseInt(ind); if(index >= 0 && index < cards.length) { var length = this.cards.length - index; var sub = new TableauPile(); for(var loop = 0; loop < length; ++loop) { sub.push(this.at(loop+index)); } // now remove them from this pile for(var loop = 0; loop < length; ++loop) { this.pop(); } return sub; } return null; } // appends a pile to this pile and pops them all from subPile TableauPile.prototype.pushSubPile = function(subPile) { for(var loop = 0; loop < subPile.size(); ++loop) { this.push(subPile.at(loop)); } subPile.clear(); } CardStack.prototype.transfer = function(oldDeck, bFlip) { if(this.cards.length > 0) { alert("Fatal error! CardStack.transfer() called while not empty"); return; } this.cards.clear(); this.cards = oldDeck.cards; oldDeck.clear(); if(bFlip) { this.flip(); } // TO DO: update DOM } CardStack.prototype.flip = function() { if(this.cards.length < 1) { return; } // reverse the deck this.cards = this.cards.reverse(); // then flip all cards for(var card = 0; card < this.cards.length; ++card) { this.cards[card].flip(); } } */ 演示地址:http://www.codedread.com/solitaire.php
|