代码之家  ›  专栏  ›  技术社区  ›  James Black

寻找好的服务器端语言,允许玩家上传可以执行的代码

  •  6
  • James Black  · 技术社区  · 15 年前

    我有一个想法,我想写一个程序,但哪种语言最好是我的问题。

    如果我有一个赛车游戏,我想允许用户提交新的交互式3D赛车赛道(想想在速度赛车电影中找到的赛道)、车辆和他们的自动驾驶汽车的代码,那么他们会为他们的汽车创建人工智能,使汽车能够确定如何处理危险。

    所以,我需要一种运行速度很快的语言,作为服务器提供的所有可能种族及其各种状态的世界地图的一部分。

    例如,我很好奇这是否是在scala中创建DSL的一个很好的理由?

    我不想重新启动一个应用程序来加载新的DLL或JAR文件,所以许多编译语言会是一个问题。

    我对Linux或Windows开放,对语言、大多数脚本语言、F、scala、erlang或我能用的大多数oop都开放。

    用户将能够监控他们的车辆运行情况,如果他们为该车辆上传了多个人工智能,当遇到某些障碍时,他们应该能够根据需要交换一个人工智能程序与另一个人工智能程序。

    更新:到目前为止,解决方案是使用V8和Lua的JavaScript。

    我很好奇这是否是DSL的一个好用途,实际上是3个独立的。1用于创建赛道,另一个用于控制赛车,第三个用于创建新车。

    如果是这样的话,哈斯克尔、F或斯卡拉会成为这方面的好选择吗?

    更新: 让不同的部分以不同的语言结束是否有意义?例如,如果Erlang用于控制汽车,Lua用于汽车本身,也用于动画赛车场?

    5 回复  |  直到 8 年前
        1
  •  31
  •   igouy peenut    8 年前

    你的情况听起来像是Lua的好人选。

    • 你需要沙盒:这是 easy to do in Lua . 您只需通过覆盖或删除 os.execute 例如,命令,用户无法再访问该函数。
    • 你想要快点:看看 Lua benchmarks against other languages .
    • 令人难以置信的是,你需要与另一种语言进行互操作。Lua是非常容易(IMO)嵌入在C或C++,至少。我还没用过 LuaInterface 但这就是C键。
    • Lua具有一阶函数,因此可以很容易地动态交换函数。
    • 卢阿 supports OOP to some extent 使用元表。
    • Lua的主要数据结构是 table (关联数组)非常适合于稀疏的数据结构,如与世界地图集成。
    • Lua有非常规则的语法。分号或缩进没有什么有趣的技巧,所以当你的用户学习你的语言时,这就少了一件事了——更不用说,使用一种有良好文档记录的语言会带走你自己在文档记录方面必须做的一些工作。

    另外,正如@elviejo在评论中指出的,lua已经在许多游戏中用作脚本语言。如果没有别的,那么以您描述的方式使用Lua肯定有一些先例。而且,正如@gmonc提到的,您的用户可能已经在另一个游戏中使用了lua。


    到目前为止 怎样 要与Lua集成:通常,您的用户只需上传一个Lua脚本文件。为了大大简化,您可以为用户提供可用的功能,例如 TurnLeft , TurnRight , Go Stop . 然后,用户会上传一个脚本
    Actions = {} -- empty table, but you might want to provide default functions 
    function Actions.Cone()
        TurnLeft()
    end
    
    function Actions.Wall()
        Stop()
        TurnRight()
        TurnRight()
        Go()
    end
    

    然后在服务器端,您可以用 Go() . 然后,当他们的车到达圆锥体时,你就称他们为 Actions.Cone() 功能;墙通向 Actions.Wall() 函数等。此时,您(希望)已经对Lua环境进行了沙盒处理,因此您可以简单地执行它们的脚本,而不必考虑错误检查——如果它们的脚本导致了错误,那么您没有理由不能将错误直接传递给用户。如果有的话 不是 任何错误, lua_State 在服务器的代码中应该包含他们的汽车的最终状态。


    较好的例子

    这里有一个独立的C文件,它从stdin中获取一个lua脚本,并按照我上面解释的方式运行它。游戏是你会遇到地面,栅栏,或树枝,你必须分别跑,跳,或鸭通过。您可以通过stdin输入一个lua脚本来决定如何反应。源代码有点长,但希望它容易理解(除了需要一段时间才能习惯的LuaAPI)。这是我在过去30分钟里的原创作品,希望它能帮助:

    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include "lua.h"
    #include "lauxlib.h"
    #include "lualib.h"
    
    #define FAIL 0
    #define SUCCESS 1
    
    /* Possible states for the player */
    enum STATE {
        RUNNING,
        JUMPING,
        DUCKING
    };
    
    /* Possible obstacles */
    enum OBSTACLE {
        GROUND,
        FENCE,
        BRANCH
    };
    
    /* Using global vars here for brevity */
    enum STATE playerstate = RUNNING;
    enum OBSTACLE currentobstacle = GROUND;
    
    /* Functions to be bound to Lua */
    int Duck(lua_State *L)
    {
        playerstate = DUCKING;
        return 0; /* no return values to Lua */
    }
    
    int Run(lua_State *L)
    {
        playerstate = RUNNING;
        return 0;
    }
    
    int Jump(lua_State *L)
    {
        playerstate = JUMPING;
        return 0;
    }
    
    /* Check if player can pass obstacle, offer feedback */
    int CanPassObstacle()
    {
        if ( (playerstate == RUNNING && currentobstacle == GROUND) )
        {
            printf("Successful run!\n");
            return SUCCESS;
        }
        if (playerstate == JUMPING && currentobstacle == FENCE)
        {
            printf("Successful jump!\n");
            return SUCCESS;
        }
        if (playerstate == DUCKING && currentobstacle == BRANCH)
        {
            printf("Successful duck!\n");
            return SUCCESS;
        }
        printf("Wrong move!\n");
        return FAIL;
    }
    
    /* Pick a random obstacle */
    enum OBSTACLE GetNewObstacle()
    {
        int i = rand() % 3;
        if (i == 0) { return GROUND; }
        if (i == 1) { return FENCE; }
        else { return BRANCH; }
    }
    
    /* Execute appropriate function defined in Lua for the next obstacle */
    int HandleObstacle(lua_State *L)
    {
        /* Get the table named Actions */
        lua_getglobal(L, "Actions");
        if (!lua_istable(L, -1)) {return FAIL;}
        currentobstacle = GetNewObstacle();
    
        /* Decide which user function to call */
        if (currentobstacle == GROUND)
        {
            lua_getfield(L, -1, "Ground");
        }
        else if (currentobstacle == FENCE)
        {
            lua_getfield(L, -1, "Fence");
        }
        else if (currentobstacle == BRANCH)
        {
            lua_getfield(L, -1, "Branch");
        }
    
        if (lua_isfunction(L, -1))
        {
            lua_call(L, 0, 0); /* 0 args, 0 results */
            return CanPassObstacle();
        }
        return FAIL;
    }
    
    int main()
    {
        int i, res;
        srand(time(NULL));
        lua_State *L = lua_open();
    
        /* Bind the C functions to Lua functions */
        lua_pushcfunction(L, &Duck);
        lua_setglobal(L, "Duck");
    
        lua_pushcfunction(L, &Run);
        lua_setglobal(L, "Run");
    
        lua_pushcfunction(L, &Jump);
        lua_setglobal(L, "Jump");
    
        /* execute script from stdin */
        res = luaL_dofile(L, NULL); 
        if (res)
        {
            printf("Lua script error: %s\n", lua_tostring(L, -1));
            return 1;
        }
    
        for (i = 0 ; i < 5 ; i++)
        {
            if (HandleObstacle(L) == FAIL)
            {
                printf("You failed!\n");
                return 0;
            }
        }
    
        printf("You passed!\n");
    
        return 0;
    }
    

    在GCC上使用 gcc runner.c -o runner -llua5.1 -I/usr/include/lua5.1 .

    几乎每次成功通过的唯一Lua脚本是:

    Actions = {}
    
    function Actions.Ground() Run() end
    function Actions.Fence() Jump() end
    function Actions.Branch() Duck() end
    

    也可以写为

    Actions = {}
    Actions.Ground = Run
    Actions.Fence = Jump
    Actions.Branch = Duck
    

    使用好的脚本,您将看到如下输出:

    Successful duck!
    Successful run!
    Successful jump!
    Successful jump!
    Successful duck!
    You passed!
    

    如果用户尝试恶意操作,程序只会提供一个错误:

    $ echo "Actions = {} function Actions.Ground() os.execute('rm -rf /') end" | ./runner 
    PANIC: unprotected error in call to Lua API (stdin:1: attempt to index global 'os' (a nil value))
    

    如果移动脚本不正确,用户将看到执行了错误的移动:

    $ echo "Actions = {} Actions.Ground = Jump; Actions.Fence = Duck; Actions.Branch = Run" | ./runner 
    Wrong move!
    You failed!
    
        2
  •  10
  •   Daniel A. White    15 年前

    为什么不使用javascript或ecmascript?谷歌 V8 这是一种非常好的沙盒方法。我记得这很容易。当然,您必须为它编写一些绑定。

        3
  •  2
  •   Clay Fowler    15 年前

    我建议使用dot net有以下几个原因:

    玩家可以选择他们在C、Ironpython、VB.NET、Boo等语言中实现他们的解决方案,但你的运行时并不在意——它只是动态地将点网程序集加载到沙盒中。但是这给了你的玩家一个他们自己喜欢的语言选择。这鼓励玩家享受体验,而不是一些玩家决定不参与,因为他们只是不喜欢你选择的单一语言。您的总体框架可能是C,但玩家的代码可以是任何点网语言。

    在点网中,沙盒和动态加载是非常成熟的。您可以将播放机程序集加载到以部分信任方式运行的自己的沙盒AppDomain中。您不必重新启动容器进程来加载和卸载这些播放机AppDomain。

    鼓励玩家“玩”这个游戏,因为语言(无论他们选择哪种点网语言)不仅对游戏脚本有用,而且可以在行业中获得真正的职业生涯。在“C”中找工作会得到 许多 例如,点击率比“Lua”或“Haskell”要高。因此,游戏不仅有趣,尤其是对年轻玩家来说,实际上是帮助他们学习真正有用的、有市场的技能,这些技能可以在以后为他们赚钱。这是对参加这场比赛的极大鼓励。

    执行速度极快。与Lua等其他选择不同的是,这是以出色的性能而闻名的编译代码,即使在实时游戏中也是如此。(例如,请参见Unity3D)。

    玩家可以在Mac和Linux上使用MonoDevelopment,也可以从微软免费使用Visual Studio Express,或者可以使用好的ol'记事本和命令行。与其他选择的不同之处在于,如果玩家选择使用成熟、现代的IDE,那么它们是可用的。

    对于问题的人工智能部分来说,DSL似乎不是一个好主意,因为对车辆实施人工智能需要玩家进行大量创造性的问题解决。对于DSL,您将它们锁定在您在考虑问题时定义问题的方式中。拥有一个完整平台的智能玩家,比如网络(或者上面提到的其他选择),可能对一些你从未预见到的人工智能问题有着全新的创新解决方案。有了合适的工具,这些玩家可以实施疯狂的学习程序或小型神经网络,或者谁知道如何实现他们的人工智能。但是,如果将它们锁定到一个简化的DSL中,不同玩家的人工智能实现可能没有太多变化(因为他们可用的思想表达集要小得多)。

    对于问题的其他部分,比如定义轨迹,DSL可能会很好。不过,我还是倾向于使用像boo这样简单的点网语言,这样您就可以为整个项目拥有一个统一的技术堆栈。

        4
  •  1
  •   Test    15 年前

    我以前在MMO中做过,你知道,NPC响应脚本使用Python,而它在C++的框架中,说任何NPC相关的动作都会触发框架运行一个Python脚本(当然是一个C Python接口,而不是一个shell调用,比如“Python/Script /Run.Py”)。脚本是可替换的运行时,虽然需要玩家或游戏管理员发出命令进行刷新,但无论如何,游戏服务器程序不需要重新启动。

    实际上,我忘记了,对于一个新的脚本运行时来说,“通过发出一个命令进行刷新”是否是必需的……2年前……但是我认为它适合您。

        5
  •  1
  •   jldupont    15 年前

    考虑二郎:

    • 您需要sandboxing/dsl:您可以编写“解析器生成器”来清除对关键/脆弱系统调用的访问。使用此功能可以“轻松”增强股票编译器。

    • 您需要细粒度的调度:您需要 some control 此外,还提供了在单独的模拟器中运行每个“用户”。也许你能做得更好,但我得多挖点。记住日程安排是O(1):-)

    • 你需要在你的“玩家”之间进行资源分配(我想如果我理解正确的话):Erlang没有共享状态,所以这从一开始就很有帮助。你可以很容易地制作一些监视玩家资源消耗的监听器等。也可以看到上面的链接(很多旋钮控制模拟器)。

    • 您需要代码热交换:Erlang是从一开始就为此设计的

    • 您需要扩展:Erlang可以很好地扩展SMP,因为它是基于消息传递和无缝的机器间通信,所以您可以水平扩展。

    • 您可以使用C驱动程序优化关键路径

    • 集成了“主管”功能,可优雅地重新启动“用户”

    Ulf Wiger on Concurrency