Saboteur Card Path combinations

by Glidias
Total Number of possible unique Saboteur Card Path combinations for the route pathing.

http://en.wikipedia.org/wiki/Saboteur_%28card_game%29

Of course, the actual game might not use everything. But this is good for identifying all possible combinations and their assosiated unique ids (and implied valid paths).

I even manually counted all the combinations, excluding multi-level crossroads. Have I missed anything?
♥0 | Line 169 | Modified 2013-07-25 23:05:49 | MIT License
play

ActionScript3 source code

/**
 * Copyright Glidias ( http://wonderfl.net/user/Glidias )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/8kFa
 */

package 
{
    import com.bit101.components.Label;
    import com.bit101.components.NumericStepper;
    import com.bit101.components.TextArea;
    import com.bit101.components.VBox;
    import flash.display.DisplayObject;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.utils.Dictionary;
    /**
     * Counting number of cardgame path combinations for Saboteur version 1. 
     * I'll do up a  3d version preview soon as I have a 3d model prepared!
     * @author Glenn Ko
     */
    public class SaboteurJetty extends Sprite
    {
        // standard mask values  (fill regions paths)
        public static const EAST:uint = (1 << 0);  
        public static const NORTH:uint = (1 << 1);
        public static const WEST:uint = (1 << 2);
        public static const SOUTH:uint = (1 << 3);
        
        public static const NORTHEAST:uint = (1 << 0);
        public static const NORTHWEST:uint = (1 << 1);
        public static const SOUTHWEST:uint = (1 << 2);
        public static const SOUTHEAST:uint = (1 << 3);

        public static const NORTH_EAST:uint = (NORTH | EAST);
        public static const NORTH_WEST:uint = (NORTH | WEST);
        public static const SOUTH_WEST:uint = (SOUTH | WEST);
        public static const SOUTH_EAST:uint = (SOUTH | EAST);
        
        public static const ARC_VERTICAL:uint = (1 << 0);
        public static const ARC_HORIZONTAL:uint = (1 << 1);
        public static const ARC_NORTH_EAST:uint = (1 << 2);
        public static const ARC_NORTH_WEST:uint = (1 << 3);
        public static const ARC_SOUTH_WEST:uint = (1 << 4);
        public static const ARC_SOUTH_EAST:uint = (1 << 5);
        
        public static const ARC_MASK:uint =  ~15;  
        static public const ARC_SHIFT:uint = 4;
    
        // predicted: 2^5 (standard 90deg east,north,west,south,center mask) = 32
        // + 8 (diagonal steer bendey cards, 2 steering mirrors and 6 with-orphan portions included)
        // + 6 (vertical flip cases of diagonal steer bendey cards above 8-2=6, since 2 steering mirrors can't flip vertically) +
        // + 6 ( horizontal and vertical striaght road with stray bit at side) 
                // TODO: Must provide visual support for vertical straight road gaps to horizontal neighbors
        // + 4 (t junction with orphan edge bit) 
        // -1  (empty standard case) 
        // -1 (standard center cell only filled ) = 54
        // -4  (standard single edge with center filled) (merely deeper end, achieves same purpose)
        // = 50!
        
        private var combinations:Vector.<uint> = new Vector.<uint>();
        
        public function SaboteurJetty() 
        {
            
            collectNumCombinations(); 
        
            addChild(_previewJetty = new PreviewJetty());
            createStepper();
        
            _previewJetty.x = 10;
            _previewJetty.y = 10;
        
        }
        
        private function createStepper():void 
        {
            var stepper:NumericStepper;
            
            
            var vLayout:VBox = new VBox(this, 128);
            vLayout.x = 128;
            
            var label:Label = new Label(vLayout, 0, 0, (combinations.length) + " found.");
            stepper = new NumericStepper(vLayout, 0, 0, onStep);
            stepper.minimum = 0;
            stepper.maximum = combinations.length - 1;
            
            stepper.value = 0;
            
        
            debugField = new TextArea(vLayout);
            
                onStep(null, stepper);
            
        }
        
        private function onStep(e:Event, stepper:NumericStepper=null):void 
        {

            
            var stepper:NumericStepper =  e ? (e.currentTarget as NumericStepper) : stepper;
            var value:uint = combinations[ stepper.value  ];
            
            var debugStr:String = "";
            debugStr += (value & EAST) ? "E " : "";
            debugStr += (value & NORTH) ? "N " : "";
            debugStr += (value & WEST) ? "W " : "";
            debugStr += (value & SOUTH) ? "S " : "";
            
            var arcValue:uint = (value & ARC_MASK) >> ARC_SHIFT;
            debugStr += (arcValue & ARC_VERTICAL) ? "vert " : "";
            debugStr += (arcValue & ARC_HORIZONTAL) ? "horiz " : "";
            debugStr += (arcValue & ARC_NORTH_EAST) ? "ne " : "";
            debugStr += (arcValue & ARC_NORTH_WEST) ? "nw " : "";
            debugStr += (arcValue & ARC_SOUTH_WEST) ? "sw " : "";
            debugStr += (arcValue & ARC_SOUTH_EAST) ? "se " : "";
            
            debugField.text = debugStr;
        
            var n:int = _previewJetty.numChildren;
            while (--n > -1) {
                var c:DisplayObject = _previewJetty.getChildAt(n);
                c.visible = visJetty(value, c.name, stepper.value);
            }
        }
        

        
        private var arcEdgeDict:Dictionary = new Dictionary();
        private var _previewJetty:Sprite;
        
        private function getArcConnectionMaskValues2():Vector.<uint> {   // 12 hardcoded arc combinations
            var vec:Vector.<uint> = new <uint>[   // setup lookup table of 6 possible connections to check
            //    0,
                (ARC_VERTICAL),
                (ARC_HORIZONTAL),
                (ARC_VERTICAL | ARC_HORIZONTAL),
                (ARC_NORTH_WEST | ARC_SOUTH_WEST),
                (ARC_NORTH_EAST | ARC_SOUTH_EAST),
                (ARC_NORTH_WEST | ARC_NORTH_EAST),   // 5 - start use 90 degree junction
                (ARC_SOUTH_WEST | ARC_SOUTH_EAST),
                (ARC_NORTH_EAST),
                (ARC_NORTH_WEST),
                (ARC_SOUTH_WEST),
                (ARC_SOUTH_EAST),   // 10 - end use 90 degree junction
                (ARC_NORTH_WEST | ARC_SOUTH_EAST),
                (ARC_SOUTH_WEST | ARC_NORTH_EAST )
            ];
            var result:uint;
            var len:int = vec.length;
            for (var i:int = 0; i < len; i++) {
                var value:uint = vec[i];
                result = 0;
                
                result |= (value & ARC_VERTICAL) ? (NORTH | SOUTH) : 0;
                result |= (value & ARC_HORIZONTAL) ? (WEST | EAST) : 0;
                result |= (value & ARC_NORTH_EAST) ? (NORTH | EAST) : 0;
                result |= (value & ARC_NORTH_WEST) ? (NORTH | WEST) : 0;
                result |= (value & ARC_SOUTH_WEST) ? (SOUTH | WEST) : 0;
                result |= (value & ARC_SOUTH_EAST) ? (SOUTH | EAST) : 0;
                
                arcEdgeDict[value] = result;
            }
            
            

            //throw new Error(vec);
            return vec;
        }
        
        
        //private var arcValueList:Array = [];  // for debuggign
        private var debugField:TextArea;
        
        private function collectNumCombinations():void 
        {
            var dict:Dictionary = new Dictionary();
            var vec:Vector.<uint> = getArcConnectionMaskValues2();
            
        
            var key:uint;
            var count:uint = 0;
            
            for (var i:uint = 1; i < 16; i++) {   // go through activatable east,north,west,south edge states
                for (var a:uint = 0; a < vec.length; a++) {   // go through all activable arc combinations
                    var arcValue:uint = vec[a];
                    var mask:uint = arcEdgeDict[arcValue];
                    if ( (i  & mask) != 0 && (i & mask) === mask ) {  // case with valid connectable arc combination
                    
                        key =  (arcValue << ARC_SHIFT) | i;
                        //if (key == 0) throw new Error("WRONG1");
                        if (dict[key] == null) {
                            dict[ key] = count++;
                            combinations.push(key);
                        //    arcValueList.push(arcValue);
                        }
                    }
                    
                    // case without any connecting arc
                    ///*
                    key = i;
                    //if (key == 0) throw new Error("WRONG2");
                    if (dict[key] == null) {
                        dict[ key] = count++;
                        combinations.push(key);
                    //    arcValueList.push(0);
                    }
                //    */
                }
            }
        }
        
        
        
        
        private function visJetty(value:uint, groupName:String, index:int):Boolean {

        
            var arcValue:uint = (value & ARC_MASK) >> ARC_SHIFT;
            //if (arcValue != arcValueList[index]) throw new Error("MISMATCH!:"+arcValue + ", "+arcValueList[index]);
    
            var edgeValue:uint = value & ~ARC_MASK;
            var top90Deg:Boolean = arcValue === (ARC_NORTH_WEST | ARC_NORTH_EAST)  || (arcValue === (ARC_NORTH_EAST) && edgeValue===NORTH_EAST) || (arcValue === (ARC_NORTH_WEST) && edgeValue === NORTH_WEST);
            var bottom90Deg:Boolean = arcValue === (ARC_SOUTH_EAST | ARC_SOUTH_WEST)  || (arcValue === (ARC_SOUTH_EAST) && edgeValue === SOUTH_EAST) || (arcValue === (ARC_SOUTH_WEST) && edgeValue===SOUTH_WEST);
            
        //    /*
            switch (groupName) {
                case "side0":
                case "side0_posts":
                    return  ( value & EAST )!=0;
                case "side1":
                case "side1_posts":
                    return  ( value & NORTH )!=0; 
                case "side2":
                case "side2_posts":
                    return  ( value & WEST )!=0;
                case "side3":
                case "side3_posts":
                    return ( value & SOUTH )!=0;
                
                case "center":
                    return ((arcValue & (ARC_VERTICAL | ARC_HORIZONTAL))!=0) || top90Deg || bottom90Deg;
                case "center_top":
                     return  (arcValue & ARC_VERTICAL)!=0 || top90Deg; // has cut thru or T junction from bottom
                case "center_bottom":
                    return  (arcValue & ARC_VERTICAL)!=0 || bottom90Deg;   // has cut thru or T junction from bottom
            
                case "corner0_turn":  
                    return (arcValue & ARC_NORTH_EAST)!=0  &&  !top90Deg;  // and doesn't  have T junction from top
                case "corner1_turn":
                    return (arcValue & ARC_NORTH_WEST)!=0  &&  !top90Deg;
                case "corner2_turn":
                    return (arcValue & ARC_SOUTH_WEST)!=0 &&  !bottom90Deg;  // and doesn't  have T junction from bottom
                case "corner3_turn":
                    return (arcValue & ARC_SOUTH_EAST)!=0 &&  !bottom90Deg; 
        
                ///*      
                case "corner0_railing":
                    return (arcValue & ARC_NORTH_EAST)!=0  && top90Deg;
                case "corner1_railing":
                    return (arcValue & ARC_NORTH_WEST)!=0  && top90Deg;
                case "corner2_railing":
                    return (arcValue & ARC_SOUTH_WEST)!=0 &&  bottom90Deg;
                case "corner3_railing":
                    return (arcValue & ARC_SOUTH_EAST)!=0 && bottom90Deg; 
                //*/
                
                default: return false;
            }
            //    */
            ///return false;
        }
        
    }
}
import flash.display.Shape;
import flash.display.Sprite;

class PreviewJetty extends Sprite {
    public function PreviewJetty() {
        graphics.beginFill(0x000000);
        graphics.drawRect(0, 0, 3 * 32, 5 * 32);
        
        
        createShape("side0", 2, 2);
        createShape("side1", 1, 0);
        createShape("side2", 0, 2);
        createShape("side3", 1, 4);
        
        createShape("center", 1, 2);
        createShape("center_top", 1, 1);
        createShape("center_bottom", 1, 3);
        
        createShape("corner0_turn", 4, 3, true);
        createShape("corner1_turn", 1, 3, true);
        createShape("corner2_turn", 1, 6, true);
        createShape("corner3_turn", 4, 6, true);
    }
    
    private function createShape(name:String, x:int, y:int, halfSize:Boolean=false):void {
        var gridSize:Number = halfSize ? 16 : 32;
        var shape:Shape = new Shape();
        shape.name = name;
        shape.graphics.lineStyle(1, 0xFFFFFF);
        shape.graphics.beginFill(0xFF0000);
        shape.graphics.drawRect(x * gridSize, y * gridSize, gridSize, gridSize);
        
        addChild(shape);
    }
    
    
}