在游戏制作里,一般会存在大量的配置文件,比如说地图的配置,NPC的配置,技能的配置,任务活动的配置,
这些配置经常是以 二维表的形式存在,我们的策划喜欢强大的表格处理工具excel
这种配置,可以算得上是小型数据库,我曾见过超过10M的配置数据,
面对这样的配置类型,我们有查询的需求,比如说:
- 查询当前玩家所在地图所有可用的跳转点
- 查询玩家当前地图上所有可交互的NPC,将它们生成在玩家附近。
- 满足当前条件的所有可接任务的集合。
- 获取当前条件下玩家可学习的技能集合。
如果
我们面对非SQL系统的数据源,
我们需要在当前的编程环境下,方便地表达查询,
c#的解决办法, linq, (其实我们可以在很多语言里实现类似的技术, 不过这是另外一个题目了,stream API)
c# 对于任何数据类型,如果实现了IEnumerable 接口,即视为可应用linq查询的数据源。
例: 查询当前玩家所在地图所有可用的跳转点
(假设可用跳转点条件为,当前地图,等级要求,完成任务ID)
class Potal
{
int mapID;
int level;
int taskID;
...
};
class Potals : IEnumerable<Potal>
{
...
}
...
var validPotals = from potal in potals
where potal.mapID == player.map.ID &&
potal.level <= player.level &&
player.taskDone.Contain(potal.taskID);
对于不熟linq语法的读者,我写成另一种形式
var validPotals = potals.Where(potal =>
{
return
potal.mapID == player.map.ID &&
potal.level <= player.level &&
player.taskDone.Contain(potal.taskID);
});
翻译成最平凡的语法是,
List<Potal> validPotals1 = new List<Potal>();
foreach (var potal in potals)
{
if(potal.mapID == player.map.ID &&
potal.level <= player.level &&
player.taskDone.Contain(potal.taskID))
{
validPotals1.Add(potal);
}
}
在这样简单的例子里, linq并不能表达它优势,但毕竟, linq给我们带来了强大的表达能力,我们初步实现了,“告诉计算机我们需要什么,而不是怎样去做",
在计算机科学里,这样的编程范式叫做”逻辑式编程“,逻辑式编程能做的远不止数据库查询(相对于大家对SQL这种特定语言的印象)。
逻辑式编程语言(prolog)里,只有三类表达式,
- 事实(Facts), 它是数据集,可以理解为各种数据源,数据表,
- 规则(Rules): 对应的是 合一规则, 可以理解成数据源的约束,
- 查询(Queries): 问题求解
我们都学过一些基础逻辑, 一个有名的例子(三段论):
一切人都会死,
苏格拉底是人,
所以苏格拉底会死,
写成prolog程序
human(Socrates)//事实:
motal(X) :- human(X)//规则:
?- motal(Socrates)//查询
>> yes
我们推导出 苏格拉底 确实会死,
好吧,我们强来,在c# 表达如此逻辑
class Mortal { }
class Human : Mortal { }//规则,人类是会死生物
static void Main(string[] args)
{
var Socrates = new Human(); //事实
if(Socrates is Mortal) //查询
{
Console.WriteLine("yes!");
}
}
(好像我们发现了点什么...关于类型和逻辑的关系是个艰深的话题 )
以下,我做一个极为简单的逻辑系统,在这系统里完成以上的逻辑式查询, 为了易于表达,测试里我们只考虑一元的情况(arity), 比如说Query1(将只查询一元属性的Fact)
//lxf0525@gmail.com
class KnowlegeBase
{
class Entity
{
public string name = "";
}
class Atom : Entity { }//原子enity
class Variable : Entity { }//变量 enity, 将作为合一的名称
class Fact //事实
{
public static List<Fact> facts = new List<Fact>();
//加入一个Fact(一元)
public static Fact newFact1(string tag, string atomName)
{
Fact f = new Fact();
f.tag = tag;
f.ents.Add(new Atom() { name = atomName });
facts.Add(f);
return f;
}
public string tag = "";
public List<Entity> ents = new List<Entity>();
public Atom getFirstAtom()
{
return ents.First() as Atom;
}
public static IEnumerable<Fact> getFact(string tag)
{
return facts.Where(f => f.tag == tag);
}
}
class Rule//规则
{
public Fact implication;
public List<Fact> prerequisite = new List<Fact>();
public static List<Rule> Rules = new List<Rule>();
public static Rule newRule1(string tagImplication, string tagPrerequisite)
{
Fact f = new Fact();
f.tag = tagImplication;
f.ents = new List<Entity>() { new Variable() { name = "X"}};
Fact f1 = new Fact();
f1.tag = tagPrerequisite;
f1.ents = new List<Entity>() { new Variable() { name = "X" } };
Rule r = new Rule();
r.implication = f;
r.prerequisite.Add(f1);
Rules.Add(r);
return r;
}
public static IEnumerable<Rule> getRule(string implicationTag)
{
return Rules.Where(r => r.implication.tag == implicationTag);
}
}
class Query //查询
{
//1元Fact的查询
public static IEnumerable<Atom> Query1(string factTag, string atomName)
{
//基本事实的查询
var facts = Fact.getFact(factTag).Where(f => f.getFirstAtom().name == atomName);
foreach(var f in facts)
{
yield return f.getFirstAtom();
}
//规则查询,
foreach(var r in Rule.getRule(factTag))
{
foreach(var f in r.prerequisite)
{
foreach(var a in Query1(f.tag, atomName) )
{
yield return a;
}
}
}
}
}
static void Main(string[] args)
{
Fact.newFact1("Human", "Socrates");
Rule.newRule1("Mortal", "Human");
Console.WriteLine(Query.Query1("Mortal", "Socrates").Count() > 0? yes : no);
}
}
////
// >> yes
可以看到,这样小的系统通过查询的方式,确实证明了 ”苏格拉底必死"这一命题。