从配置文件说到逻辑式编程

2016-08-06  本文已影响0人  golden_age

在游戏制作里,一般会存在大量的配置文件,比如说地图的配置,NPC的配置,技能的配置,任务活动的配置,
这些配置经常是以 二维表的形式存在,我们的策划喜欢强大的表格处理工具excel

这种配置,可以算得上是小型数据库,我曾见过超过10M的配置数据,

面对这样的配置类型,我们有查询的需求,比如说:

如果
我们面对非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)里,只有三类表达式,

我们都学过一些基础逻辑, 一个有名的例子(三段论):
一切人都会死,
苏格拉底是人,
所以苏格拉底会死,

写成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

可以看到,这样小的系统通过查询的方式,确实证明了 ”苏格拉底必死"这一命题。

上一篇 下一篇

猜你喜欢

热点阅读