Set Solver

The script solves the card game Set. You can find a complete package with documentation of this here.

#!/usr/bin/python

import sys, os
from optparse import OptionParser

# Loads a list of cards from a file
# File should be tab delineated and have one property in each field
def loadFile(filename):
    Cards = list()
    f = file(filename, 'r')

    for line in f:
        line = line.strip();

        # skip over comment lines
        if line[0] == "#":
            continue

        fields = line.split();

        # Don't load a corrupt line that doesn't have four fields
        if len(fields) != 4:
            print "This line is corrupt: %s" % line
            continue

        # Create the card and add it to the list
        c = Card(fields[0], fields[1], fields[2], fields[3])
        Cards.append(c)
    f.close()
    return Cards

# Tests whether three cards are a set or not
def isSet(CardA, CardB, CardC):
    # each property must be all the same or all different
    colorsASet = isPropertyASet( CardA.color, CardB.color, CardC.color )
    numbersASet = isPropertyASet( CardA.number, CardB.number, CardC.number )
    shapeASet = isPropertyASet( CardA.shape, CardB.shape, CardC.shape )
    fillASet = isPropertyASet( CardA.fill, CardB.fill, CardC.fill )

    # all properties must be a valid set
    return colorsASet & numbersASet & shapeASet & fillASet

# Tests a set of properties to test and see if that property's
# conditions are met
# Returns true if they are all the same or all different, false otherwise
def isPropertyASet(propA, propB, propC):
    # all properties are equal
    if (propA == propB) & (propB == propC):
        return True
    # or all properties are different
    if (propA != propB) & (propB != propC) & (propA != propC):
        return True
    return False

# Card class, contains the properties of each card
class Card:
    # Initializes all card properties
    def __init__(self, color, number, shape, fill):
        self.color = color
        self.number = number
        self.shape = shape
        self.fill = fill

    # Returns this card in string form
    def __str__(self):
        return "%s, %s, %s, %s" % (self.color, self.number, self.shape, self.fill)

    # Determines if a card is equal to another card
    def __eq__(A, B):
        return A.color == B.color & A.number == B.number & A.shape == B.shape & A.fill == B.fill

if __name__ == '__main__':
    parser = OptionParser()
    parser.add_option("-i", "--infile", dest="infile",
        help="The input file", metavar="INFILE")

    (options, args) = parser.parse_args()

    if options.infile:
        InFile = options.infile
    else:
        print >> sys.stderr, "Error: no input file to load!"
        parser.print_help()
        sys.exit(0)

    # Load and list all cards
    Cards = loadFile( InFile )

    print "Cards:"

    for i in range(0, len(Cards)):
        print "\tCard %s: %s" % ( i, Cards[i] )

    Matches = list()

    # upper triangular for loop pairing up cards
    for CardA in range(0, len(Cards)-2):
        for CardB in range(CardA+1, len(Cards)-1):
            for CardC in range(CardB+1, len(Cards)):
                if isSet(Cards[CardA], Cards[CardB], Cards[CardC]):
                    Matches.append("%s-%s-%s" %  (CardA, CardB, CardC))

    # print all sets
    print "Sets:"
    for match in Matches:
        print "\t%s" % match

Example card file:

# ANSWERS: 0-3-10, 1-6-9, 2-3-8, 2-10-11, 4-5-8, 6-7-10
Red	2	Diamond	Full
Green	1	Diamond	Empty
Green	3	Squiggle	Full
Green	3	Squiggle	Lines
Purple	1	Oval	Empty
Red	2	Diamond	Empty
Green	3	Diamond	Full
Red	3	Squiggle	Lines
Green	3	Squiggle	Empty
Green	2	Diamond	Lines
Purple	3	Oval	Empty
Red	3	Diamond	Lines
XHTML 1.1 CSS 2 Sec 508