Skip to content

Instantly share code, notes, and snippets.

@Berlioz
Created March 28, 2013 21:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Berlioz/5267024 to your computer and use it in GitHub Desktop.
Save Berlioz/5267024 to your computer and use it in GitHub Desktop.
Cockatrice support for magic card generator
#!/usr/bin/ruby
# usage: transformer.rb 20 >> cards.xml, where the integer is the number of pages to hit
# dependency: nokogiri, any version written after Hitler was dead
require 'open-uri'
require 'nokogiri'
GENERATOR_URI = "http://www.toothycat.net/wiki/bnf.pl?page=AlexChurchill/MagicCardGenerator"
class Card
COLORMAP = {'red' => 'R', 'black' => 'B', 'blue' => 'U', 'white' => 'W', 'green' => 'G'}
# given a list of lines from the generator website all corresponding to the
# same card, instantiate a Card object with all the data stored in the right
# places
def initialize(lines)
# artifacts and lands
if lines.first[:type] == :short_typebar
@type = lines.first[:text].strip
@colors = []
else
# split the first line into card type and other metadata
text = lines.first[:text]
typebar, metadata = text.scan(/(.*?)\s+\((.*?)\)/).first
# for whatever reason the generator splits creature types with two spaces
@type = typebar.gsub(" ", " ").strip
# split metadata into power/toughness and colors
@pt = metadata.scan(/\d+\/\d+/).first
@colors = metadata.split(",").last.strip.split(" and ")
end
text_lines = []
lines[1..-1].each do |line|
text = line[:text]
if line[:type] == :flavor_text
@flavor = text
elsif line[:type] == :cost
@cost = text.scan(/\d+/)
else
# it's rules text of some sort. Probably.
text_lines << pretty(text)
end
end
@text = text_lines.join("\n")
end
# Handle wrapping the tap and mana cost symbols in {}s
# Handle uppercasing the first letter of the line
# TODO: fix the case of a mana cost containing only colorless mana
# (currently disabled because of confusion with stuff like deal 3 damage)
def pretty(text)
text[0] = text[0].capitalize
words = text.split(" ")
new_words = []
words.each do |word|
if /\A\d{0,2}(W|U|R|B|G|T)*:?,?.?\z/.match(word)
numeric = Integer(word) rescue nil
if numeric
new_words << word
else
new_words << word.gsub(/(\d+|W|U|R|B|G|T)/,'{\1}')
end
else
new_words << word
end
end
new_words.join(" ")
end
# convert this card to an xml node formatted like cockatrice's cards.xml
def to_xmlnode(doc, id)
node = doc.create_element("card")
# create subnodes
node.add_child(doc.create_element("name", "GEN#{id}"))
node.add_child(doc.create_element("set", "GEN"))
@colors.each do |color|
node.add_child(doc.create_element("color", COLORMAP[color]))
end
node.add_child(doc.create_element("manacost", @cost))
node.add_child(doc.create_element("type", @type))
node.add_child(doc.create_element("pt", @pt)) if @pt
node.add_child(doc.create_element("tablerow", 0))
node.add_child(doc.create_element("text", @text))
node
end
end
# returns a file object containing one pass of the generator (3 cards)
def get_blob
open(GENERATOR_URI)
end
def extract_content(element)
if element.is_a? Nokogiri::XML::Text
element.text
else
child_texts = element.children.map{|c| extract_content(c)}.compact
child_texts.length == 0 ? nil : child_texts.join("\n")
end
end
def guess_linetype(element, text)
if element.name == "i"
if text[0..1] == "--"
:flavor_text
elsif /Converted mana cost: \d+/.match(text)
:cost
elsif (text[0] == "(" && text[-1] == ")")
:reminder_text
elsif /(.*?)\s+\((.*?)\)/.match(text)
:long_typebar
elsif ["Land", "Artifact", "Artifact - Equipment"].include?(text.strip)
:short_typebar
elsif
:keyword_or_unknown
end
elsif text.include?("- - - - -")
:flip_delimiter
else
:rules_text
end
end
def lex(blob)
page = Nokogiri::HTML(blob)
tokens = []
# try to pair each line up with our best guess for its type
page.xpath("//body").children.each do |element|
text = extract_content(element)
next if (text.nil? || text == "\n" || text.include?("AlexChurchill"))
tokens << {:type => guess_linetype(element, text), :text => text}
end
# do another walk to resolve planeswalker names and flip cards
tokens.each_with_index do |token, i|
if token[:type] == :full_typebar && token[:text].include?("Planeswalker")
tokens[i - 1][:type] = :planeswalker_name
elsif token[:type] == :flip_delimiter
tokens[i + 1][:type] = :rules_text
end
end
end
def parse(tokens)
current_card = []
created_cards = []
tokens.each do |token|
next if token[:type] == :planeswalker_name
if [:long_typebar, :short_typebar].include?(token[:type])
unless current_card.empty?
created_cards << Card.new(current_card)
current_card = []
end
end
current_card << token
end
created_cards << Card.new(current_card)
created_cards
end
def create_xml_doc(all_cards)
doc = Nokogiri::XML::Document.new
doc.root = doc.create_element("cockatrice_carddatabase", :version => "2")
# init set data
sets = doc.create_element("sets")
set = doc.create_element("set")
setname = doc.create_element("name", "GEN")
setlongname = doc.create_element("longname", "BNF Generated Cards")
set.add_child(setname)
set.add_child(setlongname)
sets.add_child(set)
doc.root.add_child(sets)
# init container for cards
cards = doc.create_element("cards")
# fill the container
id = 0
all_cards.each do |card|
new_node = card.to_xmlnode(doc, id)
id += 1
cards.add_child(new_node)
end
doc.root.add_child(cards)
doc
end
def download_set(pages)
set = []
pages.times do
set << parse(lex(get_blob))
sleep(1)
end
set.flatten
end
pages = ARGV[0] ? ARGV[0].to_i : 90
set = download_set(pages)
doc = create_xml_doc(set)
puts doc.to_s
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment