YU-GI-OH! POWER OF XLINQ [C#, XLINQ, XML]
WRITTEN BY ALLEN LEE
0. TABLE OF CONTENT
- 1. YU-GI-OH! POWER OF XLINQ.
- 2. GAME CARDS.
- 3. <CARDS>...</CARDS>
- 4. CLASS CARD { ... }
- 5. GIVE ME WHAT I WANT!
- 6. SHOW ME THE RESULTS.
- 7. REFERENCES.
1. YU-GI-OH! POWER OF XLINQ.
如果你玩过游戏王的决斗怪兽游戏,你会发现一个好的卡片信息管理器能使你装备卡片组的工作事半功倍。例如,Yu-Gi-Oh! Power of Chaos 系列游戏就内置了一个简单的卡片信息管理器。
现在,我需要一个更强大的卡片信息管理器,代号为 Positron,它从各个方面对卡片信息进行筛选和排序,并协助我寻找合适的卡片装备到我的卡片组中。
2. GAME CARDS.
要管理好这些卡片,我们得首先了解这些卡片是如何分类的,以及它们包含了哪些信息。根据官方的说明,卡片的概要分类如下:
- Monster Cards(怪兽卡)
- A. Normal Monster Cards(普通怪兽卡)
- B. Fusion Monster Cards(融合怪兽卡)
- C. Ritual Monster Cards(仪式怪兽卡)
- D. Effect Monster Cards(效果怪兽卡)
- Spell Cards(魔法卡)
- A. Normal Spell Cards(普通魔法卡)
- B. Continuous Spell Cards(永续魔法卡)
- C. Equip Spell Cards(装备魔法卡)
- D. Field Spell Cards(场地魔法卡)
- E. Quick-Play Spell Cards(速攻魔法卡)
- F. Ritual Spell Cards(仪式魔法卡)
- Trap Cards(陷阱卡)
- A. Normal Trap Cards(普通陷阱卡)
- B. Counter Trap Cards(反击陷阱卡)
- C. Continuous Trap Cards(永续陷阱卡)
现在我们来细看每一种卡片上所包含的信息。所有的怪兽卡都包含了以下信息:
- Monster Name(怪兽名字)
- Attribute(属性)
- Level(等级)
- Type(类别)
- Attack Points (ATK)(攻击力)
- Defense Points (DEF)(防守力)
- Card Description(卡片描述)
并由 Attribute 和 Type 来划分类别:
Attribute 枚举:
Earth(地属性)、Water(水属性)、Fire(炎属性)、Wind(风属性)、Light(光属性)、Dark(闇属性)
Type 枚举:
Type:Dragon(龙族)、Spellcaster(魔法使族)、Zombie(不死族)、Warrior(战士族)、Beast-Warrior(兽战士族)、Beast(兽族)、Winged Beast(鸟兽族)、Fiend(恶魔族)、Fairy(天使族)、Insect(昆虫族)、Dinosaur(恐龙族)、Reptile(爬虫族)、Fish(鱼族)、Sea Serpent(海龙族)、Machine(机械族)、Thunder(雷族)、Aqua(水族)、Pyro(炎族)、Rock(岩石族)、Plant(植物族)
上图所展示的是一张效果怪兽卡,它包含如下信息:
- Monster Name(怪兽名字):Deepsea Warrior
- Attribute(属性):Water
- Level(等级):5
- Type(类别):Warrior
- Attack Points (ATK)(攻击力):1600
- Defense Points (DEF)(防守力):1800
- Card Description(卡片描述):When "Umi" is face-up on the field, this card is unaffected by any Spell Cards.
其中 Fusion Monster Cards 的 Card Description 上列出了融合所需的怪兽(Fusion-Material Monsters);Effect Monster Cards 的 Card Description 上描述了该效果怪兽的效果。
而 Spell Cards 和 Trap Cards 就比较简单,仅包含了:
- Name(魔法卡或者陷阱卡的名字)
- Category(魔法卡或者陷阱卡的类型)
- Card Description(卡片描述)
3. <CARDS>...</CARDS>
接下来,我要为这些卡片信息建立 XML 表示模型。首先是怪兽卡的 XML 表示模型:
<
monstercard
img
=""
category
=""
name
=""
attribute
=""
level
=""
type
=""
atk
=""
def
=""
>
</
monstercard
>
其中,
- img:指示该怪兽卡的图片位置。
- category:指示该怪兽卡的类别。允许的值为 Normal、Effect、Fusion 或者 Ritual,分别表示普通怪兽、效果怪兽、融合怪兽或者仪式怪兽。虽然融合怪兽或者仪式怪兽也可以带有效果,但在正式的卡片归类里,这些怪兽将归入融合怪兽或者仪式怪兽类别。例如 Thousand-Eye Restrict 就既是融合怪兽也是效果怪兽,却被归类到融合怪兽里。
- name:指示该怪兽卡的名字。
- attribute:指示该怪兽的属性,允许的值如上面的 Attribute 枚举所示。
- type:指示该怪兽的类型,允许的值如上面的 type 枚举所示。
- level:指示该怪兽的等级,对应着卡片右上方的星星数目,其值为整数类型。
- atk:指示怪兽的攻击力,其值为整数类型。
- def:指示怪兽的防守力,其值为整数类型。
- 最后,mostercard 元素的内容则为卡片描述内容。
类似地,魔法卡的表示为:
<
spellcard
img
=""
category
=""
name
=""
>
</
spellcard
>
其中,
- img:指示该魔法卡的图片位置。
- category:指示该魔法卡的类别。允许的值为 Normal、Continuous、Equip、Field、QuickPlay 或者 Ritual,分别表示普通魔法卡、永续魔法卡、装备魔法卡、场地魔法卡、速攻魔法卡或者仪式魔法卡。
- name:指示该魔法卡的名字。
陷阱卡的表示为:
<
trapcard
img
=""
category
=""
name
=""
>
</
trapcard
>
其中,
- img:指示该陷阱卡的图片位置。
- category:指示该陷阱卡的类别。允许的值为 Normal、Counter 或者 Continuous,分别表示普通陷阱卡、反击陷阱卡或者永续陷阱卡。
- name:指示该陷阱卡的名字。
以下是一份示范代码:
sample.xml
<?xml version="1.0" encoding="utf-8" ?>
<cards>
<monstercards>
<monstercard img="Monster Cards\Barrel Dragon.jpg" category="Effect" name="Barrel Dragon" attribute="Dark" level="7" type="Machine" atk="2600" def="2200">Toss a coin 3 times. If 2 out of 3 results are Heads, destroy 1 monster on your opponent's side of the field. This card's effect can only be used during your own turn, once per turn.</monstercard>
<monstercard img="Monster Cards\Blue-Eyes White Dragon.jpg" category="Normal" name="Blue-Eyes White Dragon" attribute="Light" level="8" type="Dragon" atk="3000" def="2500">This legendary dragon is a powerful engine of destruction. Virtually invincible, very few have faced this awesome creature and lived to tell the tale.</monstercard>
<monstercard img="Monster Cards\Thousand-Eye Restrict.jpg" category="Fusion" name="Thousand-Eye Restrict" attribute="Dark" level="1" type="Spellcaster" atk="0" def="0">"Relinquished" + "Thousand-Eyes Idol". As long as this card remains face-up on the field, other monsters cannot change their positions or attack. This monster can take on the ATK and DEF of 1 opponent's monster on the field (a face-down monster results in an ATK and DEF of 0). Treat the selected monster as an Equip Spell Card and use it to equip "Thousand-Eyes Restrict". You may use this effect only once per turn and can equip "Thousand-Eyes Restrict" with only 1 monster at a time.</monstercard>
<monstercard img="Monster Cards\Magician of Black Chaos.jpg" category="Ritual" name="Magician of Black Chaos" attribute="Dark" level="8" type="Spellcaster" atk="2800" def="2600">This monster can only be Ritual Sommoned with the Ritual Spell Card, "Dark Magic Ritual". You must also offer monsters whose total Level Stars equal 8 or more as a Tribute from the field or your hand.</monstercard>
</monstercards>
<spellcards>
<spellcard img="Spell Cards\Dark Hole.jpg" category="Normal" name="Dark Hole">Destroys all monsters on the field.</spellcard>
</spellcards>
<trapcards>
<trapcard img="Trap Cards\White Hole.jpg" category="Normal" name="White Hole">When your opponent plays "Dark Hole", the monsters on your side of the field are not destroyed.</trapcard>
</trapcards>
</cards>
4. CLASS CARD { ... }
首先,我定义一个 Card 的抽象了,里面有一个 ToXElement 的抽象方法:
//
Code #01
abstract
class
Card
{
public abstract XElement ToXElement();
// Other code omitted
}
接下来,我们来看看 MonsterCard 的 ToXElement 实现和其中一个构造函数:
//
Code #02
class
MonsterCard : Card
{
public MonsterCard(XElement info)
{
m_ImageLocation = (string)info.Attribute("img");
m_Name = (string)info.Attribute("name");
m_Category = (MonsterCardCategory)Enum.Parse(typeof(MonsterCardCategory), (string)info.Attribute("category"), false);
m_Attribute = (MonsterAttribute)Enum.Parse(typeof(MonsterAttribute), (string)info.Attribute("attribute"), false);
m_Type = (MonsterType)Enum.Parse(typeof(MonsterAttribute), (string)info.Attribute("type"), false);
m_Level = Int32.Parse((string)info.Attribute("level"));
m_Atk = Int32.Parse((string)info.Attribute("atk"));
m_Def = Int32.Parse((string)info.Attribute("def"));
m_Description = (string)info.Value;
}
public override XElement ToXElement()
{
return new XElement("monstercard",
new XAttribute("img", m_ImageLocation),
new XAttribute("category", m_Category.ToString()),
new XAttribute("name", m_Name),
new XAttribute("attribute", m_Attribute.ToString()),
new XAttribute("level", m_Level.ToString()),
new XAttribute("type", m_Type.ToString()),
new XAttribute("atk", m_Atk.ToString()),
new XAttribute("def", m_Def.ToString()),
m_Description);
}
// Other code omitted
}
在 ToXElement 中,我们使用了 Functional Construction 方式来创建 XML,它调用了 XElement 的
//
Code #03
public
XElement(XName name,
params
object
[] contents)
XElement 懂得处理所有类型的输入参数,以下摘自 XLinq Overview:
- A string, which is added as text content.
- An XElement, which is added as a child element.
- An XAttribute, which is added as an attribute.
- An XProcessingInstruction, XComment, or XCData, which is added as child content.
- An IEnumerable, which is enumerated, and these rules are applied recursively.
- Anything else, ToString() is called and the result is added as text content.
- null, which is ignored.
从这里,我们可以看出,XLing 使得无论读取或者创建 XML 都变得比以前更为便捷了。
5. GIVE ME WHAT I WANT!
现在我们来看这张卡:
这张卡的效果是禁止场上所有等级4或以上的怪兽攻击,于是,我希望能够找到等级3或者以下,并具有能直接攻击对手的效果的怪兽:
//
Code #04
XElement cards
=
XElement.Load(
@"
E:\My Documents\Visual Studio\Projects\LINQLab\Sample.xml
"
);
var wanted
=
from c
in
cards.Element(
"
monstercards
"
).Elements(
"
monstercard
"
)
where (Int32.Parse((
string
)c.Attribute(
"
level
"
))
<
4
)
&&
((
string
)c.Attribute(
"
category
"
)
==
"
Effect
"
)
orderby Int32.Parse((
string
)c.Attribute(
"
atk
"
))
select c;
foreach
(var c
in
wanted)
{
Console.WriteLine((string)c.Attribute("name"));
Console.WriteLine("[{0}]", (string)c.Attribute("type"));
Console.WriteLine(c.Value);
Console.WriteLine("ATK/ {0} DEF/ {1}", (string)c.Attribute("atk"), (string)c.Attribute("def"));
}
//
Output:
//
//
Jinzo #7
//
[Machine]
//
This monster may attack your opponent's Life Points directly.
//
ATK/ 500 DEF/ 400
//
//
Man-Eater Bug
//
[Insect]
//
FLIP: Destroys 1 monster on the field (regardless of position).
//
ATK/ 450 DEF/ 600
由于 Positron 没有足够的能力分析卡片上的描述,所以我只好把第二个条件改为“具有效果的怪兽”,让 Positron 做第一轮筛选。如果筛选的结果显示出多个选择,那么毫无疑问我会选择攻击力高的,所以我让 Positron 根据怪兽的攻击力进行排序。从筛选结果中,我们可以看到 Jinzo #7 就是我要找的怪兽了。
当然,如果你已经把 XML 文件的数据映射到一组 MonsterCard 类(例如,List<MonsterCard> cards),你也可以这样:
//
Code #05
var wanted
=
from c
in
cards
where (c.Level
<
4
)
&&
(c.Category
==
MonsterCardCategory.Effect)
orderby c.Atk
select c;
foreach
(var c
in
wanted)
{
Console.WriteLine(c.Name);
Console.WriteLine("[{0}]", c.Type.ToString());
Console.WriteLine(c.Description);
Console.WriteLine("ATK/ {0} DEF/ {1}", c.Atk.ToString(), c.Def.ToString());
}
6. SHOW ME THE RESULTS.
现在假设我已经筛选了一批后备卡片,并储存在一个 XElement 中,我希望把结果以 HTML 方式展示出来,那我有该如何处理呢?答案是用 Functional Construction 方式,把 HTML 当作 XML 来处理。
假设我需要把上面那份 sample.xml 转换为如下的 HTML 文件:
sample.html
<html>
<head>
<title>Card List</title>
</head>
<body>
<h2>
<font color="#0000FF">Monster Cards</font>
</h2>
<p>
<img src="Monster Cards\Barrel Dragon.png" />
</p>
<p>Barrel Dragon Machine</p>
<p>Toss a coin 3 times. If 2 out of 3 results are Heads, destroy 1 monster on your opponent's side of the field. This card's effect can only be used during your own turn, once per turn.</p>
<p>ATK/ 2600 DEF/ 2200</p>
<p>
<img src="Monster Cards\Blue-Eyes White Dragon.png" />
</p>
<p>Blue-Eyes White Dragon Dragon</p>
<p>This legendary dragon is a powerful engine of destruction. Virtually invincible, very few have faced this awesome creature and lived to tell the tale.</p>
<p>ATK/ 3000 DEF/ 2500</p>
<p>
<img src="Monster Cards\Thousand-Eye Restrict.png" />
</p>
<p>Thousand-Eye Restrict Spellcaster</p>
<p>"Relinquished" + "Thousand-Eyes Idol". As long as this card remains face-up on the field, other monsters cannot change their positions or attack. This monster can take on the ATK and DEF of 1 opponent's monster on the field (a face-down monster results in an ATK and DEF of 0). Treat the selected monster as an Equip Spell Card and use it to equip "Thousand-Eyes Restrict". You may use this effect only once per turn and can equip "Thousand-Eyes Restrict" with only 1 monster at a time.</p>
<p>ATK/ 0 DEF/ 0</p>
<p>
<img src="Monster Cards\Magician of Black Chaos.png" />
</p>
<p>Magician of Black Chaos Spellcaster</p>
<p>This monster can only be Ritual Sommoned with the Ritual Spell Card, "Dark Magic Ritual". You must also offer monsters whose total Level Stars equal 8 or more as a Tribute from the field or your hand.</p>
<p>ATK/ 2800 DEF/ 2600</p>
<h2>
<font color="#0000FF">Spell Cards</font>
</h2>
<p>
<img src="Spell Cards\Dark Hole.png" />
</p>
<p>Dark Hole</p>
<p>Destroys all monsters on the field.</p>
<h2>
<font color="#0000FF">Trap Cards</font>
</h2>
<p>
<img src="Trap Cards\White Hole.png" />
</p>
<p>White Hole</p>
<p>When your opponent plays "Dark Hole", the monsters on your side of the field are not destroyed.</p>
</body>
</html>
首先,我们要设置好基本的“外围”元素,例如 html、head、title 和 body 等:
//
Code #06
static
XElement ExtractCards(XElement src)
{
return new XElement("html",
new XElement("head",
new XElement("title", "Card List")),
new XElement("body",
ExtractMonsterCards(src),
ExtractSpellCards(src),
ExtractTrapCards(src)));
}
然后对 src 分别提取怪兽卡、魔法卡和陷阱卡的数据并进行格式化转换:
//
Code #07
static
object
[] ExtractMonsterCards(XElement cards)
{
return new object[] {
ExtractTitle("Monster Cards"),
from m in cards.Element("monstercards").Elements("monstercard")
select new object[] {
ExtractImage(m),
new XElement("p", String.Format("{0} {1}", (string)m.Attribute("name"), (string)m.Attribute("type"))),
new XElement("p", m.Value),
new XElement("p", String.Format("ATK/ {0} DEF/ {1}", (string)m.Attribute("atk"), (string)m.Attribute("def")))
}
};
}
static
object
[] ExtractSpellCards(XElement cards)
{
return new object[] {
ExtractTitle("Spell Cards"),
from s in cards.Element("spellcards").Elements("spellcard")
select new object[] {
ExtractImage(s),
new XElement("p", (string)s.Attribute("name")),
new XElement("p", s.Value)
}
};
}
static
object
[] ExtractTrapCards(XElement cards)
{
return new object[] {
ExtractTitle("Trap Cards"),
from t in cards.Element("trapcards").Elements("trapcard")
select new object[] {
ExtractImage(t),
new XElement("p", (string)t.Attribute("name")),
new XElement("p", t.Value)
}
};
}
由于卡片的图片、名字和描述等内容是并行排列的,所以我向 select 的投射了一个 object[]。
这里,我为三大卡类设定了标题:
//
Code #08
static
XElement ExtractTitle(
string
title)
{
return new XElement("h2",
new XElement("font", new XAttribute("color", "#0000FF"), title));
}
而卡片的图片信息转换则为:
//
Code #09
static
XElement ExtractImage(XElement card)
{
return new XElement("p",
new XElement("img", new XAttribute("src", (string)card.Attribute("img"))));
}
最后,我们在 Main 中输出转换后的结果:
//
Code #10
XElement src
=
XElement.Load(
@"
E:\My Documents\Visual Studio\Projects\LINQLab\Sample.xml
"
);
XElement dtn
=
ExtractCards(src);
XmlWriterSettings settings
=
new
XmlWriterSettings();
settings.OmitXmlDeclaration
=
true
;
settings.Indent
=
true
;
XmlWriter writer
=
XmlWriter.Create(
@"
E:\My Documents\Visual Studio\Projects\LINQLab\Sample.html
"
, settings);
dtn.Save(writer);
writer.Close();
注意,请务必把 XmlWriterSettings.OmitXmlDeclaration 设为 true,这样,输出结果才是“真正”的 HTML。
7. REFERENCES.