User:Madewokherd/blanxxbot

From RabbitWiki

Jump to: navigation, search
Image:FluxxGameLogo.jpg Fluxx Blanxx

This page is about a great Fluxx Blanxx idea. Blanxx are blank cards, pre-printed and ready to use in your own customized Fluxx game! Fluxx is an ever changing game, and people have come up with a never ending stream of variations. These card ideas can be used with Official Fluxx Blanxx cards!

Listing of all Custom Fluxx Blanxx pages on this wiki.

The following is a python script used to generate the Fluxx Blanxx Listed by Author, Fluxx Blanxx Listed as Related Cards‎, and Fluxx Blanxx Listed by Deck‎ pages.

#!/usr/bin/env python

import codecs
import urllib2
import xml.sax
import xml.sax.handler

standard_decks = {
    'Fluxx 1.0': set([
        "Chocolate",
        "Cookies",
        "Death",
        "The Eye",
        "Love",
        "Milk",
        "Money",
        "Moon", # The Moon
        "Peace",
        "The Pyramid",
        "The Rocket",
        "The Sun",
        "Taxes",
        "Television",
        "Time",
        "Toast",
        "Bread", #Bread is not in this deck but Toast can substitute
        "Toaster", # The Toaster
        "War",
        "10 Cards in Hand",
        "5 Keepers",
        "The Appliances",
        "The Brain (no TV)",
        "Chocolate Cookies",
        "Chocolate Milk",
        "Death & Taxes",
        "Death by Chocolate",
        "Eye on the Pyramid",
        "Peace & Love",
        "Keeper Shortage",
        "Milk and Cookies",
        "Eye & the Brain",
        "Money (no Taxes)",
        "The Sun & the Moon)",
        "Peace (no War)",
        "Rocket to the Moon",
        "Time is Money",
        "Toast & the Toaster",
        "War = Death",
        "Discard and Draw",
        "Draw 2 and use 'em",
        "Draw, 3, Play 2 of them",
        "Everybody Gets One",
        "Exchange Keepers",
        "Lose a Turn",
        "Pilfer the Trash",
        "Rules Reset",
        "Scramble Keepers",
        "Security Breach",
        "Steal a Keeper",
        "Taxation!",
        "Trade Hands",
        "Trash a Keeper",
        "Trash a New Rule",
        "Trash Hand Limit",
        "Trash Keeper Limit",
        "Use What You Take",
        "Brain Bonus",
        "Draw 2",
        "Draw 3",
        "Draw 4",
        "Draw 5",
        "Eye Bonus",
        "Final Card Random",
        "Government Cover Up",
        "Hand Limit 0",
        "Hand Limit 1",
        "Hand Limit 2",
        "Hand Limit 3",
        "Keeper Limit 2",
        "Keeper Limit 3",
        "Play 2",
        "Play 3",
        "Play 4",
        "Play All",
        "Secret Data",
        "Tax Bonus",
        "Time Bonus",
        ]),
    'Fluxx 2.x': set([
        "The Brain",
        "Bread",
        "Chocolate",
        "Coffee",
        "Cookies",
        "Death",
        "Doughnuts",
        "The Eye",
        "Love",
        "Milk",
        "Money",
        "Moon", # The Moon
        "Peace",
        "The Pyramid",
        "The Rocket",
        "The Sun",
        "Taxes",
        "Television",
        "Time",
        "Toaster",
        "War",
        "10 Cards in Hand",
        "5 Keepers",
        "All You Need is Love",
        "The Appliances",
        "Brain (no TV)",
        "Chocolate Cookies",
        "Chocolate Milk",
        "Coffee & Doughnuts",
        "Death & Taxes",
        "Death by Chocolate",
        "The Great Seal",
        "Hippyism",
        "Milk and Cookies",
        "The Mind's Eye",
        "Money (no Taxes)",
        "The Sun & The Moon",
        "Peace (no War)",
        "Rocket to the Moon",
        "Time is Money",
        "Toast",
        "War and Death",
        "Discard and Draw",
        "Draw 2 and use 'em",
        "Draw, 3, Play 2 of them",
        "Everybody Gets One",
        "Exchange Keepers",
        "Pilfer the Trash",
        "Rules Reset",
        "Scramble Keepers",
        "Security Breach",
        "Steal a Keeper",
        "Take Another Turn",
        "Taxation!",
        "Trade Hands",
        "Trash a Keeper",
        "Trash a New Rule",
        "Trash Hand Limit",
        "Trash Keeper Limit",
        "Use What You Take",
        "Brain Bonus",
        "Draw 2",
        "Draw 3",
        "Draw 4",
        "Draw 5",
        "Final Card Random",
        "Government Cover Up",
        "Hand Limit 0",
        "Hand Limit 1",
        "Hand Limit 2",
        "Hand Limit 3",
        "Keeper Limit 2",
        "Keeper Limit 3",
        "Keeper Limit 4",
        "Money Bonus",
        "Play 2",
        "Play 3",
        "Play 4",
        "Play All",
        "Secret Data",
        "Tax Bonus",
        "Time Bonus",
        ]),
    'Fluxx 3.x': set([
        "The Brain",
        "Bread",
        "Chocolate",
        "Cookies",
        "Death",
        "Dreams",
        "Love",
        "Milk",
        "Money",
        "Moon",
        "Peace",
        "The Rocket",
        "Sleep",
        "The Sun",
        "Television", # The Television
        "Time",
        "Toaster", # The Toaster
        "War",
        "10 Cards in Hand",
        "5 Keepers",
        "All You Need is Love",
        "The Appliances",
        "Baked Goods",
        "Bed Time",
        "The Brain (no TV)",
        "Chocolate Cookies",
        "Chocolate Milk",
        "Death by Chocolate",
        "Dreamland",
        "Hearts and Minds",
        "Hippyism",
        "Milk and Cookies",
        "The Sun & the Moon",
        "Night & Day",
        "Peace (no War)",
        "Rocket Science",
        "Rocket to the Moon",
        "Squishy Chocolate",
        "Time is Money",
        "Toast",
        "War and Death",
        "Winning the Lottery",
        "Discard and Draw",
        "Draw 2 and use 'em",
        "Draw 3, Play 2 of them",
        "Empty the Trash",
        "Everybody Gets 1",
        "Exchange Keepers",
        #"Go Fish", #not in all versions
        #"I Need a Goal", #not in all versions
        #"Jackpot!", #not in all versions
        "Let's Do That Again",
        "Let's Simplify",
        "No Limits",
        "Rotate Hands",
        "Rules Reset",
        "Scramble Keepers",
        "Steal a Keeper",
        "Take Another Turn",
        "Taxation",
        "Trade Hands",
        "Trash a Keeper",
        "Trash a New Rule",
        "Use What You Take",
        "Draw 2",
        "Draw 3",
        "Draw 4",
        "Draw 5",
        "Play 2",
        "Play 3",
        "Play 4",
        "Play All",
        "Hand Limit 0",
        "Hand Limit 1",
        "Hand Limit 2",
        "Keeper Limit 2",
        "Keeper Limit 3",
        "Keeper Limit 4",
        "Double Agenda",
        "Reverse Order",
        "First Play Random",
        "No-Hand Bonus",
        "Poor Bonus",
        "Rich Bonus",
        "Inflation",
        "X = X + 1",
        ]),
    'Fluxx 4.0': set([
        "Rules Escalation",
        "The Brain",
        "Bread",
        "Chocolate",
        "Cookies",
        "The Cosmos",
        "Dreams",
        "The Eye",
        "Love",
        "Milk",
        "Money",
        "Moon", # The Moon
        "The Party", # The Party
        "Peace",
        "The Rocket",
        "Sleep",
        "The Sun",
        "Television",
        "Time",
        "Toaster", # The Toaster
        "Death",
        "Radioactive Potato",
        "Taxes",
        "War",
        "5 Keepers",
        "10 Cards in Hand",
        "All That is Certain",
        "All You Need is Love",
        "The Appliances",
        "Baked Goods",
        "Bed Time",
        "The Brain (no TV)", #The Brain (No TV)
        "Chocolate Cookies",
        "Chocolate Milk",
        "Death by Chocolate",
        "Dough",
        "Dreamland",
        "Hearts and Minds",
        "Hippyism",
        "Milk and Cookies",
        "The Mind's Eye",
        "The Sun & the Moon", #subst. Night & Day
        "Night & Day", #Night and Day
        "Party Snacks",
        "Peace (no War)",
        "Rocket Science",
        "Rocket to the Moon",
        "Squishy Chocolate",
        "Star Gazing",
        "Interstellar Spacecraft",
        "Time is Money",
        "Toast",
        "War and Death", #War = Death
        "Winning the Lottery",
        "Creeper Sweeper",
        "Discard and Draw", #Discard & Draw
        "Draw 2 and use 'em",
        "Draw 3, Play 2 of them", #Draw 3, play 2 of them
        "Everybody Gets 1",
        "Exchange Keepers",
        "Jackpot", #Jackpot!
        "Let's Do That Again", #Let's Do That Again
        "Let's Simplify",
        "Mix It All Up",
        "No Limits",
        "Rotate Hands",
        "Rules Reset",
        "Steal Something",
        "Take Another Turn",
        "Taxation", #Taxation!
        "Today's Special", #Today's Special!
        "Trade Hands",
        "Trash a New Rule",
        "It's Trash Day",
        "Trash Something",
        "Use What You Take",
        "No-Hand Bonus",
        "Poor Bonus",
        "Rich Bonus",
        "Double Agenda",
        "Draw 2",
        "Draw 3",
        "Draw 4",
        "Draw 5",
        "First Play Random",
        "Get On With It!",
        "Hand Limit 0",
        "Hand Limit 1",
        "Hand Limit 2",
        "Inflation",
        "X = X + 1", # subst. Inflation
        "Keeper Limit 2",
        "Keeper Limit 3",
        "Keeper Limit 4",
        "Party Bonus",
        "Play 2",
        "Play 3",
        "Play 4",
        "Play All",
        "Silver Lining",
        "You Also Need a Baked Potato",
        ]),
    'EcoFluxx': set([
        "Air",
        "Water",
        "Sunshine",
        "Dirt",
        "Mushrooms",
        "Seeds",
        "Leaves",
        "Flowers",
        "Trees",
        "Worms",
        "Spiders",
        "Insects",
        "Frogs",
        "Tadpoles",
        "Fish",
        "Snakes",
        "Birds",
        "Mice",
        "Rabbits",
        "Bears",
        "Poison",
        "Mud",
        "Clouds",
        "Rainbows",
        "Basking",
        "Earthworms",
        "Trees Clean the Air",
        "Night Music",
        "Photosynthesis",
        "Mighty Oaks from Tiny Acorns Grow",
        "Metamorphosis",
        "Decay",
        "Ferns",
        "Deciduous Trees in Winter",
        "Pollination",
        "Invertebrates",
        "Mammals",
        "Herpetology",
        "Mice Eat Seeds",
        "Snakes Eat Mice",
        "Insects Eat Leaves or Mushrooms",
        "Frogs Eat Insects",
        "Spiders Eat Insects",
        "Birds Eat Insects and Worms and Seeds",
        "Rabbits Eat Leaves",
        "Fish Eat Worms",
        "Bears Eat Fish",
        "Mass Migration",
        "Scavenger",
        "Pollution",
        "Population Crash",
        "Extinction",
        "Share the Wealth",
        "Draw 3, Play 2 of them",
        "Everybody Gets 1",
        "Trash a Keeper",
        "Exchange Keepers",
        "Steal a Keeper",
        "Trade Hands",
        "Use What You Take",
        "Rules Reset",
        "Taxation",
        "Trash a New Rule",
        "Discard & Draw",
        "Let's Do That Again!",
        "Draw 2",
        "Draw 3",
        "Draw 4",
        "Draw 5",
        "Play 2",
        "Play 3",
        "Play 4",
        "Play All",
        "Hand Limit 0",
        "Hand Limit 1",
        "Hand Limit 2",
        "Keeper Limit 1",
        "Keeper Limit 2",
        "Keeper Limit 3",
        "Keeper Limit 4",
        "Composting",
        "No-Hand Bonus",
        ]),
    'Family Fluxx': set([
        "The Cat",
        "The Dog",
        "The Mouse",
        "The House",
        "Tree", # The Tree
        "The Playground",
        "The Gift",
        "The Stick",
        "The Ball",
        "Cake",
        "Ice Cream",
        "Cheese",
        "Rain",
        "Playing Indoors",
        "Playing Outdoors",
        "Stick Ball",
        "Any 2 Animals",
        "Ice Cream Cake",
        "Recess",
        "Tree-house",
        "Mouse in the House",
        "Walk the Dog",
        "The Cheese Stands Alone",
        "Fetch!",
        "Mice Love Cheese",
        "House Cat",
        "Cheesecake",
        "Happy Birthday",
        "Ice Cream on a Stick",
        "Free Ice Cream!",
        "Choose a New Rule",
        "Share the Wealth",
        "Jackpot!",
        "Pass 1 Left",
        "Pass 1 Right",
        "Draw 2 and use 'em",
        "Everybody gets 1",
        "Trash a Keeper",
        "Exchange Keepers",
        "Steal a Keeper",
        "Trade Hands",
        "Use what you take",
        "Rules Reset ",
        "Draw 2",
        "Draw 3",
        "Play 2",
        "Play 3",
        "Play All",
        "Hand Limit 1",
        "Hand Limit 2",
        "Keeper Limit 3",
        "Keeper Limit 4",
        "Child Bonus",
        "Parent Bonus",
        "Grandparent Bonus",
        ]),
    'Zombie Fluxx': set([
        "Zombie Repellent",
        "Sonic Tranquilizer",
        "The Shotgun",
        "The Chainsaw",
        "The Shovel",
        "The Car",
        "Lumber",
        "Can of Gasoline",
        "The Baseball Bat",
        "A Friend (male)",
        "A Friend (female)",
        "Coffee",
        "Doughnuts", #Donuts
        "Sandwiches",
        "Brains",
        "Zombie",
        "Pair of Zombies",
        "Zombie Trio",
        "Trio of Zombies",
        "Zombie Quartet",
        "They Fear Fire",
        "Hit the Gas!",
        "Arsenal",
        '"Shotgun!"',
        "We're All All Right!",
        "Breakfast and Lunch",
        "Zombie Food",
        "What's in the Toolshed?",
        "Food & Gas",
        "Barricade the Windows",
        "I Alone Survived",
        "We Need Firewood",
        "Food for Thought",
        "Provisions",
        "Donuts with Coffee",
        "Getaway Driver",
        "Four, Three, Two, One",
        "Zombie Baseball Team",
        "Brain Baseball",
        "Brain Sandwiches",
        "\"I'll Hold 'em Off!\"",
        "Zombie Victory",
        "Draw 3, play 2 of them",
        "Draw 2 and use 'em",
        "Everybody Gets 1",
        "Trash a Keeper",
        "Exchange Keepers",
        "Steal a Keeper",
        "Trade Hands",
        "Use What You Take",
        "Rules Reset",
        "Taxation",
        "Trash a New Rule",
        "Discard & Draw",
        "Let's Do That Again!",
        "Let's Simplify",
        "Take Another Turn",
        "Zombie Jamboree",
        "No Zombies",
        "...and Stay Dead!",
        "Return of the Dead",
        "Hey, Over Here!",
        "Let's Shamble!",
        "Eaten by Zombies!",
        "Out of Ammo",
        "Draw 2",
        "Draw 3",
        "Draw 4",
        "Draw 5",
        "Play 2",
        "Play 3",
        "Play 4",
        "Play All",
        "Hand Limit 0",
        "Hand Limit 1",
        "Hand Limit 2",
        "Hand Limit 3",
        "Double Agenda",
        "No-Hand Bonus",
        "Groaning Required",
        "Zombie Season",
        "Eradication Bonus",
        "Adrenaline Bonus",
        "Weapon Bonus",
        "It's Not Working!",
        "Dead Friends",
        "Look, Over There!",
        "Zombies Ain't So Bad",
        ]),
    'Pirate Fluxx': set([
        "Captain's Hat",
        "Schooner",
        "Galleon",
        "Frigate",
        "Dinghy",
        "Sloop",
        "Treasure Map",
        "The Key",
        "Pearls",
        "Rubies",
        "Pieces of Eight",
        "Gold Doubloons",
        "Emeralds",
        "Diamonds",
        "Strongbox",
        "Oranges",
        "Limes",
        "Monkey",
        "Flintlock Pistol",
        "Royal Colors",
        "Jolly Roger",
        "Cannon",
        "Cutlass",
        "Keg",
        "Rum",
        "Tropical Island",
        "Parrot",
        "Gunpowder",
        "Scurvy",
        "Shackles",
        "Shipwreck",
        "King's Ransom",
        "Walk the Plank!",
        "Mutiny!",
        "Rough Seas",
        "Draw 2 and Use 'em",
        "Draw 3, play 2 of them",
        "Everybody Gets 1",
        "Discard & Draw",
        "Exchange Keepers",
        "Jackpot!",
        "Let's Do That Again!",
        "Rules Reset",
        "Share the Wealth",
        "Trash Something",
        "Trash a New Rule",
        "Trade Hands",
        "Use What You Take",
        "Steal a Keeper ",
        "Royal Navy Ship",
        "Yo Ho Ho & a Bottle of Rum",
        "Privateers",
        "Shallow Seas",
        "Key Lime Pie",
        "Free the Prisoner!",
        "Keg of Rum",
        "Fresh Fruit",
        "Sword & Pistol",
        "Gems",
        "Armada",
        "Treasure Chest",
        "Treasure Ship",
        "Gold and Silver",
        "X Marks the Spot",
        "Munitions",
        "Powder Monkey",
        "Land Ho!",
        "Powder Keg",
        "Marooned",
        "The Captain's Parrot",
        "Run Out the Guns!",
        "Unlock the Box",
        "Pirate Ship",
        "Pirate Pets ",
        "That Be Mine!",
        "Canceled Plans",
        "Avast! Halt!",
        "Veto!",
        "Long Live the Captain!",
        "Plunder!",
        "Talk Like a Pirate",
        "Swap Plays For Draws",
        "No-Hand Bonus",
        "Double Agenda",
        "Hand Limit 4",
        "Hand Limit 3",
        "Hand Limit 2",
        "Hand Limit 1",
        "Keeper Limit 2",
        "Keeper Limit 4",
        "Keeper Limit 3",
        "Draw 5",
        "Draw 4",
        "Draw 3",
        "Draw 2",
        "Play 2",
        "Play 3",
        "Play 4",
        "Play All",
        "Booty Keeper (Pirate Fluxx)",
        "Ship Keeper (Pirate Fluxx)",
        ]),
    'Christian Fluxx': set([
        'The Bible',
        'The Cross',
        'The Story of Jesus',
        'Church Time',
        'Jesus Loves You',
        'Cross Bonus',
        'Bible Verses',
        ]),
    'Jewish Fluxx': set([
        'Torah',
        'Candles',
        'Tradition',
        'Shabbat',
        'Torah Study',
        'Judaica Bonus',
        'Hebrew Knowledge',
        ]),
    '(promo cards)': set([
        'Andy Looney',
        'Time Vortex',
        ]),
    }

deck_names = ['Fluxx 1.0', 'Fluxx 2.x', 'Fluxx 3.x', 'Fluxx 4.0', 'EcoFluxx', 'Family Fluxx', 'Zombie Fluxx']

card_types = ['Rule', 'Keeper', 'Goal', 'Action', 'Ungoal', 'Creeper']

class WikiContentHandler(xml.sax.handler.ContentHandler):
    def startDocument(self):
        self.contents = {}
        self.title = ''
        self.content = []
        self.inpage = False
        self.inrev = False
    
    def endDocument(self):
        pass
    
    def startElement(self, name, attrs):
        if name == 'page':
            self.inpage = True
            self.title = attrs['title']
        elif name == 'rev' and self.inpage:
            self.inrev = True
            self.content[:] = ()
    
    def endElement(self, name):
        if name == 'page':
            self.inpage = False
            self.contents[self.title] = ''.join(self.content)
        elif name == 'rev' and self.inpage:
            self.inrev = False
    
    def characters(self, content):
        if self.inrev:
            self.content.append(content)
    
    def ignorableWhitespace(self, whitespace):
        if self.inrev:
            self.content.append(whitespace)

def get_wiki_contents(host, pages):
    url = 'http://%s/w/api.php?action=query&prop=revisions&titles=%s&rvprop=content&format=xml' % (host, '|'.join(pages))
    parser = xml.sax.make_parser()
    handler = WikiContentHandler()
    parser.setContentHandler(handler)
    parser.parse(urllib2.urlopen(url))
    return handler.contents

author_accounts = {}

def get_author(line):
    # FIXME: hack
    title = None
    for x in ['Backwards', 'Reverse', 'Reverse Pivot', 'Russian Roulette']:
        if x in line:
            title = x
    if 'submitted by: ' in line:
        dummy, author = line.split('submitted by: ')
        if '[[User:' in author: # [[User:username|Author]] Extra Information
            author, dummy = author.split(']]', 1)
            dummy, author = author.split('[[User:', 1)
            if '|' in author:
                account, author = author.split('|', 1)
            else:
                account = author
            author_accounts[author] = account
        if ' (' in author: # Author (Extra Information)
            author, dummy = author.split(' (', 1)
        if author.endswith('"') and ' "' in author: # Ross Andrews "not one of my favorites" 
            author, dummy = author.split(' "', 1)
        if ' via ' in author: # Mark "Daigohji" Mascaro via Neil Raynar
            author, dummy = author.split(' via ', 1)
        if ' who ' in author: # Hal Haag who borrowed it from June Swords
            author, dummy = author.split(' who ', 1)
        if author.endswith(' ???'): # Author ???
            author = author[:-4]
        if author == 'unknown': author = 'Unknown'
        return author, title
    elif 'Author: ' in line:
        dummy, author = line.split('Author: ')
        author = author.rstrip('-> \t')
        return author, title
    return None, None

def remove_parens(text):
    while '(' in text:
        text, rest = text.split('(', 1)
        rest = remove_parens(rest)
        if ')' in rest:
            dummy, rest = rest.split(')', 1)
            text = '%s%s' % (text, rest)
    return text.strip()

def get_blanxx_cards():
    result = {}
    print 'Getting wiki contents..'
    pages = get_wiki_contents('rabbits.continuation.org', ('Fluxx_Blanxx_%ss' % card_type for card_type in card_types))
    
    dups = set()
    
    print 'Parsing wiki contents..'
    for page in pages:
        card_type = page[13:-1]
        title = None
        text = None
        rawtext = None
        anchor = None
        author = None
        for line in pages[page].splitlines():
            line = line.strip()
            if line.startswith('*') and "'''" in line:
                #add the previous entry if one exists
                if title:
                    if author is None:
                        author = 'Unknown'
                        print "WARNING: Author of %s (%s) unknown" % (title, card_type)
                    result[title] = [card_type, anchor, text, author, rawtext]
                    title = text = rawtext = anchor = author = None
                dummy, title, rest = line.split("'''", 2)
                dummy, anchor, rest = rest.split('"', 2)
                if ' - ' in rest:
                    dummy, rawtext = rest.split(' - ', 1)
                elif '</span>' in rest:
                    dummy, rawtext = rest.split('</span>', 1)
                else:
                    rawtext = ''
                # FIXME: ignoring explanatory text
                text = remove_parens(rawtext)
                if title.endswith(', The'):
                    title = 'The %s' % title[:-5]
                if title in result:
                    print 'WARNING: There are multiple cards named %s' % title
                    # FIXME: this is a shameful hack; I would argue, though, 
                    #  that two different cards should never have the same name,
                    #  Toast notwithstanding
                    while title in result:
                        title = '%s ' % title
            else:
                new_author, new_title = get_author(line)
                if new_title is not None:
                    # FIXME: hack for Backwards, Reverse, or Reverse Pivot
                    if new_author is not None:
                        if author is not None:
                            result[title] = [card_type, anchor, text, author, rawtext]
                        title = new_title
                        author = new_author
                        anchor = title.replace(' ', '_')
                elif new_author is not None:
                    if author is not None:
                        author = '%s and %s' % (author, new_author)
                    else:
                        author = new_author
        if title:
            if author is None:
                author = 'Unknown'
                print "WARNING: Author of %s (%s) unknown" % (title, card_type)
            result[title] = [card_type, anchor, text, author, rawtext]
    
    return result

blanxx_cards = get_blanxx_cards()

def card_link(title):
    card_type, anchor, text, author, rawtext = blanxx_cards[title]
    title = title.replace(',', '')
    title = title.rstrip(' ')
    return '[[Fluxx_Blanxx_%ss#%s|%s]]' % (card_type, anchor, title)

#for sorting
def card_key(title):
    if title.startswith('The '):
        title = title[4:]
    return title.lower()

def printed_type(type):
    if type == 'Rule':
        return 'New Rule'
    else:
        return type

def write_card_list(f, cards):
    def format_sublist(seq):
        l = list(seq)
        if len(l) > 1:
            return '\n:* '+'\n:* '.join(l)
        else:
            return l[0]
    bytype = {}
    for card in cards:
        type = blanxx_cards[card][0]
        if type not in bytype:
            bytype[type] = []
        bytype[type].append(card)
    for type in card_types:
        if type in bytype:
            f.write("* '''%s''': %s\n" % (printed_type(type), format_sublist(card_link(x) for x in sorted(bytype[type], key=card_key))))

#generate a list of cards by author
print "Generating byauthor.txt"
def do_byauthor():
    authors = {}
    for title in blanxx_cards:
        card_type, anchor, text, author, rawtext = blanxx_cards[title]
        while ' and ' in author:
            # special case: First Author and Second Author
            author, second_author = author.rsplit(' and ', 1)
            if second_author not in authors:
                authors[second_author] = []
            authors[second_author].append(title)
        if author not in authors:
            authors[author] = []
        authors[author].append(title)
    author_names = list(authors)
    #sort authors by last name
    suffixes = set(['iii', 'ii', 'sr.'])
    def lastname(string):
        string = string.lower()
        while ' ' in string:
            string, lastname = string.rsplit(' ', 1)
            if lastname in suffixes:
                continue
            return lastname
        return string
    author_names.sort(key=lastname)
    #create file
    f = codecs.open('byauthor.txt', 'w', 'utf8')
    for author in author_names:
        if author in author_accounts:
            f.write('== [[User:%s|%s]] ==\n' % (author_accounts[author], author))
        else:
            f.write('== %s ==\n' % author)
        write_card_list(f, authors[author])
        f.write('\n\n')
    f.close()
do_byauthor()

#FIXME: manual kludges
blanxx_cards['RPS'][2] = """You win if:
- You have Rock, someone else has Scissors, no one has Paper
- You have Scissors, someone else has Paper, no one has Rock
- You have Paper, someone else has Rock, no one has Scissors"""
blanxx_cards['Nanofiction Story Title'][2] = "There could be goals which put together two Nanofiction Keepers (Nanofiction Introductors and Nanofiction Concluders) in such a way that a story is created."

#generate database of mentioned cards
standard_cards = set()
all_cards = set()
mentions = {}
mentioned_by = {}
print "Generating card links"
def get_card_desc(card):
    if card == '1':
        return 'The Number One' # 1 is referred to as The Number One in the text of other cards
    elif card == 'Mars, the Red Planet':
        return 'Mars'
    elif card in ['Choose a New Rule', '5 Keepers', 'Trash a New Rule']:
        return None
    elif card.endswith(' '):
        return None
    elif card.startswith('The '):
        return card[4:]
    else:
        return card
def do_related_lists():
    all_cards.update(blanxx_cards)
    for deck in standard_decks:
        standard_cards.update(standard_decks[deck])
    all_cards.update(standard_cards)
    
    for card1 in all_cards:
        card1_desc = get_card_desc(card1)
        if card1_desc is None:
            continue
        card1_desc = remove_parens(card1_desc)
        for card2 in blanxx_cards:
            if card1 == card2:
                continue
            card_type, anchor, text, author, rawtext = blanxx_cards[card2]
            if card1_desc in text:
                try:
                    mentioned_by[card1].append(card2)
                except KeyError:
                    mentioned_by[card1] = [card2]
                try:
                    mentions[card2].append(card1)
                except KeyError:
                    mentions[card2] = [card1]
    # fix cases where a card's name contains another card's name
    for card in mentions:
        for mentioned_card1 in mentions[card][:]:
            mentioned_card1_desc = get_card_desc(mentioned_card1)
            if mentioned_card1_desc is None:
                continue
            for mentioned_card2 in mentions[card][:]+[card]:
                if mentioned_card1 == mentioned_card2 or mentioned_card1 not in mentions[card]:
                    continue
                mentioned_card2_desc = get_card_desc(mentioned_card2)
                if mentioned_card2_desc is None:
                    continue
                if mentioned_card1_desc == mentioned_card2_desc and mentioned_card2 != card:
                    continue
                if '(' in mentioned_card1_desc:
                    if remove_parens(mentioned_card1_desc) in mentioned_card2_desc and mentioned_card1_desc not in blanxx_cards[card][4].replace(mentioned_card2_desc, ''):
                        mentions[card].remove(mentioned_card1)
                        mentioned_by[mentioned_card1].remove(card)
                        if len(mentioned_by[mentioned_card1]) == 0:
                            del mentioned_by[mentioned_card1]
                            continue
                if '(' in mentioned_card2_desc and remove_parens(mentioned_card2_desc) in mentioned_card1_desc:
                    continue
                if mentioned_card1_desc in mentioned_card2_desc and remove_parens(mentioned_card1_desc) not in blanxx_cards[card][2].replace(remove_parens(mentioned_card2_desc), ''):
                    mentions[card].remove(mentioned_card1)
                    mentioned_by[mentioned_card1].remove(card)
                    if len(mentioned_by[mentioned_card1]) == 0:
                        del mentioned_by[mentioned_card1]
do_related_lists()

#FIXME: modifying database with manual kludges
mentioned_by['Cookies'].remove("The Singer's Keeper")
mentioned_by['The Sun'].remove("The Singer's Keeper")
del mentions["The Singer's Keeper"]
del mentioned_by['Scramble Keepers']
del mentions["Scramble Hands"]
del mentioned_by['Time Vortex']
del mentioned_by['Scramble Hands']
del mentions['Communist Manifesto']
del mentioned_by['Cat Ass Trophy']
del mentions['Tornado']

#generate by-card lists
print "Generating list of related cards"
def do_bycard():
    # group mentioned cards by type
    bytype = {}
    f = codecs.open('bycard.txt', 'w', 'utf8')
    for card in mentioned_by:
        if card in standard_cards:
            type = 'Standard'
        else:
            type = blanxx_cards[card][0]
        try:
            bytype[type].append(card)
        except KeyError:
            bytype[type] = [card]
    for type in ['Standard']+card_types:
        if type in bytype:
            f.write('== %s ==\n' % printed_type(type))
            for card in sorted(bytype[type], key=card_key):
                if type == 'Standard':
                    f.write('=== %s ===\n' % card)
                else:
                    f.write('=== %s ===\n' % card_link(card))
                write_card_list(f, mentioned_by[card])
                f.write('\n\n')
    f.close()
do_bycard()

#generate by-deck lists
print "Generating list of cards by deck"
def do_bydeck():
    f = codecs.open('bydeck.txt', 'w', 'utf8')
    
    # cards playable in any fluxx deck
    f.write('== Any deck ==\n')
    card_list = []
    for card in blanxx_cards:
        if card.endswith(' '):
            continue
        if blanxx_cards[card][2] == '': #skip cards with no text
            continue
        if card not in mentions:
            card_list.append(card)
    write_card_list(f, card_list)
    
    # cards playable in any Fluxx X.Y deck
    original_card_list = []
    original_deck_list = standard_decks['Fluxx 1.0']&standard_decks['Fluxx 2.x']&standard_decks['Fluxx 3.x']&standard_decks['Fluxx 4.0']
    for card in mentions:
        if card.endswith(' '):
            continue
        if card in original_deck_list:
            continue
        for mentioned_card in mentions[card]:
            if mentioned_card not in original_deck_list:
                break
        else:
            original_card_list.append(card)
    f.write('== Any regular deck ==\n')
    write_card_list(f, original_card_list)
    
    # cards playable in particular decks
    for deck in deck_names:
        card_list = []
        for card in mentions:
            if card.endswith(' '):
                continue
            if card in standard_decks[deck]:
                continue
            if card in original_card_list:
                continue
            for mentioned_card in mentions[card]:
                if mentioned_card not in standard_decks[deck]:
                    break
            else:
                card_list.append(card)
        if len(card_list) == 0:
            continue
        f.write('== %s ==\n' % deck)
        write_card_list(f, card_list)
    f.close()
do_bydeck()
Personal tools