您必须已经从标题中猜到,今天的文章将关注软件源代码中的错误。但不仅如此。如果您不仅对C ++感兴趣并且在阅读其他开发人员代码中的错误,还会挖掘不寻常的视频游戏并想知道“roguelikes”是什么以及如何玩它们,那么欢迎继续阅读!


在寻找不寻常的游戏时,我偶然发现了大灾变黑暗日子,由于它的图形基于黑色背景上排列的各种颜色的ASCII字符,因此在其他游戏中脱颖而出。

令人惊讶的是,这个和其他类似游戏让你感到惊讶的是它们内置了多少功能。例如,特别是在大灾变中,由于可用的数十个参数,特征和初始场景,你甚至无法创建一个没有谷歌某些指南的冲动的角色,更不用说整个游戏中发生的事件的多种变化。

由于它是一个开源代码的游戏,而且是一个用C ++编写的游戏,我们无法使用我们的静态代码分析器PVS-Studio进行检查,我正在积极参与其中。该项目的代码令人惊讶的高质量,但它仍然有一些小缺陷,其中一些我将在本文中讨论。

PVS-Studio已经检查了很多游戏。您可以在我们的文章“ 视频游戏开发中的静态分析:十大软件错误 ”中找到一些示例。

逻辑

例1:

此示例显示了经典的复制粘贴错误。

V501 “||”的左侧和右侧有相同的子表达式 operator:rng(2,7)<abs(z)|| rng(2,7)<abs(z)overmap.cpp 1503

bool overmap::generate_sub( const int z )
{....
if( rng( 2, 7 ) < abs( z ) || rng( 2, 7 ) < abs( z ) )
{....}....
}

检查相同的条件两次。程序员复制了表达式但忘记修改副本。我不确定这是否是一个严重的错误,但事实是检查不能正常工作。

另一个类似错误:

V501’&&‘运算符的左侧和右侧有相同的子表达式’one_in(100000 / to_turns (dur))’。player_hardcoded_effects.cpp 547

例2:

V728可以简化过度检查。’(A && B)|| (!A &&!B)'表达式等同于’bool(A)== bool(B)'表达式。inventory_ui.cpp 199

bool inventory_selector_preset::sort_compare( .... ) const
{....const bool left_fav  = g->u.inv.assigned.count( lhs.location->invlet );const bool right_fav = g->u.inv.assigned.count( rhs.location->invlet );if( ( left_fav && right_fav ) || ( !left_fav && !right_fav ) ) {return ....} ....
}

这种情况在逻辑上是正确的,但它过于复杂。编写此代码的人应该对将要维护它的同事程序员表示同情。它可以用更简单的形式重写:if(left_fav == right_fav)。

另一个类似错误:

V728可以简化过度检查。’(A &&!B)|| (!A && B)'表达式等同于’bool(A)!= bool(B)'表达式。iuse_actor.cpp 2653
题外话题
我惊讶地发现今天以“roguelikes”为名的游戏只是roguelike游戏旧类型的温和代表。这一切都始于1980年的狂热游戏Rogue,它激发了许多学生和程序员用类似元素创建自己的游戏。我想很多影响力也来自桌面游戏DnD社区及其变化。

微优化

例3:

该组的警告指向可能优化而非错误的点。

V801性能下降。最好将第二个函数参数重新定义为引用。考虑将’const … type’替换为’const …&type’。map.cpp 4644

template <typename Stack>
std::list<item> use_amount_stack( Stack stack, const itype_id type )
{std::list<item> ret;for( auto a = stack.begin(); a != stack.end() && quantity > 0; ) {if( a->use_amount( type, ret ) ) {a = stack.erase( a );} else {++a;}}return ret;
}

在这段代码中,itype_id实际上是一个伪装的std :: string。由于参数无论如何都是作为常量传递的,这意味着它是不可变的,简单地传递对变量的引用将有助于通过避免复制操作来增强性能并节省计算资源。即使字符串不太长,每次都没有充分理由复制它是一个坏主意 - 更是如此,因为这个函数被各种调用者调用,反过来,它也从外部获取类型并具有复制它。

类似的问题:

  • V801性能下降。最好将第三个函数参数重新定义为引用。考虑用’const …&evt_filter’替换’const …evt_filter’。input.cpp 691
  • V801性能下降。最好将第五个函数参数重新定义为引用。考虑将’const color’替换为’const …&color’。output.h 207
  • 分析仪共发出32种此类警告。
例4:

V813性能下降。'str’参数应该可以呈现为常量引用。catacharset.cpp 256

std::string base64_encode( std::string str )
{if( str.length() > 0 && str[0] == '#' ) {return str;}int input_length = str.length();std::string encoded_data( output_length, '\0' );....for( int i = 0, j = 0; i < input_length; ) {....}for( int i = 0; i < mod_table[input_length % 3]; i++ ) {encoded_data[output_length - 1 - i] = '=';}return "#" + encoded_data;
}

虽然参数是非常数,但它在函数体中不会以任何方式改变。因此,为了优化,更好的解决方案是通过常量引用传递它,而不是强制编译器创建本地副本。

这个警告也没有出现; 此类型的警告总数为26。


类似的问题:

  • V813性能下降。'message’参数应该可以呈现为常量引用。json.cpp 1452
  • V813性能下降。's’参数应该可以作为常量引用呈现。catacharset.cpp 218 等等…

题外话题II

一些经典的roguelike游戏仍在积极开发中。如果您查看Cataclysm DDA或NetHack的GitHub存储库,您会看到每天都会提交更改。NetHack实际上是最古老的游戏,它仍在开发中:它于1987年7月发布,最后一个版本可追溯到2018年。

矮人要塞是该流派中最受欢迎的游戏之一。开发始于2002年,第一个版本于2006年发布。它的座右铭“失去乐趣”反映了这个游戏不可能获胜的事实。2007年,Dwarf Fortress通过每年在ASCII GAMES网站上举行的投票获得“年度最佳Roguelike游戏”。


顺便说一句,粉丝们可能很高兴知道Dwarf Fortress将使用由两位经验丰富的模组添加的增强型32位图形来加入Steam。高级版还将获得额外的音乐曲目和Steam Workshop支持。如果愿意,付费副本的所有者将能够切换到旧的ASCII图形。更多。

覆盖赋值运算符

例5,6:

这是一些有趣的警告。

V690的“JSONObject的”类实现了拷贝构造函数,但缺乏“=”运算符。使用这样的课程是危险的。json.h 647

class JsonObject
{private:....JsonIn *jsin;....public:JsonObject( JsonIn &jsin );JsonObject( const JsonObject &jsobj );JsonObject() : positions(), start( 0 ), end( 0 ), jsin( NULL ) {}~JsonObject() {finish();}void finish(); // moves the stream to the end of the object....void JsonObject::finish(){....}....
}

此类具有复制构造函数和析构函数,但不会覆盖赋值运算符。问题是自动生成的赋值运算符只能将指针赋给JsonIn。因此,类JsonObject的两个对象都将指向同一个JsonIn。我不能确定当前版本是否会出现这种情况,但有一天肯定会陷入这个陷阱。

下一课有类似的问题。

V690的“JsonArray”类实现了拷贝构造函数,但缺乏“=”运算符。使用这样的课程是危险的。json.h 820

class JsonArray
{private:....JsonIn *jsin;....public:JsonArray( JsonIn &jsin );JsonArray( const JsonArray &jsarr );JsonArray() : positions(), ...., jsin( NULL ) {};~JsonArray() {finish();}void finish(); // move the stream position to the end of the arrayvoid JsonArray::finish(){....}
}

在“ 大二法则 ”一文中详细解释了在一个复杂的类中没有覆盖赋值运算符的危险。

例7,8:

这两个也处理赋值运算符覆盖,但这次是它的具体实现。

V794应该保护赋值运算符不受’this ==&other’的影响。

mattack_common.h 49class StringRef {public:....private:friend struct StringRefTestAccess;char const* m_start;size_type m_size;char* m_data = nullptr;....
auto operator = ( StringRef const &other ) noexcept -> StringRef& {delete[] m_data;m_data = nullptr;m_start = other.m_start;m_size = other.m_size;return *this;
}

这种实施方式无法防止潜在的自我分配,这是不安全的做法。也就是说,将此引用传递给此运算符可能会导致内存泄漏。

这是一个类似的不正确覆盖赋值运算符的例子,具有特殊的副作用:

V794应该保护赋值运算符不受’this ==&rhs’的影响。player_activity.cpp 38

player_activity &player_activity::operator=( const player_activity &rhs )
{type = rhs.type;....targets.clear();targets.reserve( rhs.targets.size() );std::transform( rhs.targets.begin(),rhs.targets.end(),std::back_inserter( targets ),[]( const item_location & e ) {return e.clone();} );return *this;
}

此代码也没有检查自我赋值,此外,它还有一个要填充的向量。通过赋值运算符的这种实现,将对象分配给自身将导致目标字段中的向量加倍,其中一些元素被破坏。但是,变换前面是clear,它将清除对象的向量,从而导致数据丢失。

题外话题III

2008年,roguelikes甚至得到了一个正式的定义,名为“柏林解释”。据此,所有这些游戏都有以下几个要素:

  • 随机生成的世界,增加了可重玩性;
  • Permadeath:如果你的角色死了,他们会永远死去,他们所有的物品都会丢失;
  • 回合制游戏:任何改变都只与玩家的行为一起发生; 时间流程暂停,直到玩家执行某个动作;
  • 生存:资源不足。

最后,roguelikes最重要的特征主要集中在探索世界,寻找物品的新用途和地牢爬行。

这是Cataclysm DDA中常见的情况,因为你的角色最终被冷冻到骨头,饥饿,口渴,最重要的是,两条腿被六条触手取代。

重要的细节

例9:

V1028可能溢出。考虑将’start + larger’运算符的操作数转换为’size_t’类型,而不是结果。worldfactory.cpp 638

void worldfactory::draw_mod_list( int &start, .... )
{....int larger = ....;unsigned int iNum = ....;  ....for( .... ){if(   iNum >= static_cast<size_t>( start )&& iNum < static_cast<size_t>( start + larger ) ){....}....}
....
}

看起来程序员想要采取预防措施来防止溢出。但是,提升总和的类型不会产生任何差别,因为溢出将在此之前发生,在添加值的步骤中,促销将在无意义的值上完成。为避免这种情况,只应将其中一个参数强制转换为更宽的类型:(static_cast <size_t>(start)+ greater)。

例10:

V530需要使用函数’size’的返回值。worldfactory.cpp 1340

bool worldfactory::world_need_lua_build( std::string world_name )
{
#ifndef LUA
....
#endif// Prevent unused var error when LUA and RELEASE enabled.world_name.size();return false;
}

对于这样的案例,有一个技巧。如果最终得到一个未使用的变量并且想要禁止编译器警告,只需写入(void)world_name而不是调用该变量上的方法。

例11:

V812性能下降。无效地使用’计数’功能。可以通过调用’find’函数来替换它。player.cpp 9600

bool player::read( int inventory_position, const bool continuous )
{....player_activity activity;if(   !continuous|| !std::all_of( learners.begin(),learners.end(), [&]( std::pair<npc *, std::string> elem ){return std::count( activity.values.begin(),activity.values.end(), elem.first->getID() ) != 0;} ){....}....
}

计数与零比较的事实表明程序员想要找出活动是否包含至少一个必需元素。但是count必须遍历整个容器,因为它会计算所有元素的出现次数。使用find可以更快地完成工作,一旦找到第一个匹配项就会停止。

示例12:如果您知道有关char类型的一个棘手细节,则很容易找到此错误。

不应将V739 EOF与’char’类型的值进行比较。'ch’应该是’int’类型。json.cpp 762

void JsonIn::skip_separator()
{signed char ch;....if (ch == ',') {if( ate_separator ) {....}....} else if (ch == EOF) {....
}


除非您知道EOF定义为-1 ,否则这是您不会轻易发现的错误之一。因此,在将其与signed char类型的变量进行比较时,几乎在所有情况下条件的计算结果为false。唯一的例外是代码为0xFF(255)的字符。在比较中使用时,它将变为-1,从而使条件成立。

例13:

这个小虫子有一天可能会成为关键。毕竟,有很好的理由,它在CWE列表中被发现为CWE-834。请注意,该项目已触发此警告五次。

V663无限循环是可能的。'cin.eof()'条件不足以打破循环。考虑将’cin.fail()'函数调用添加到条件表达式。action.cpp 46

void parse_keymap( std::istream &keymap_txt, .... ){while( !keymap_txt.eof() ) {....}
}

正如警告所说,从文件读取时检查EOF是不够的 - 您还必须通过调用cin.fail()来检查输入失败。让我们修复代码以使其更安全:

while( !keymap_txt.eof() )
{if(keymap_txt.fail()){keymap_txt.clear();keymap_txt.ignore(numeric_limits<streamsize>::max(),'\n');break;}....
}

该 目的 的keymap_txt.clear()是之后发生读取错误,让你可以阅读文本的其余部分以清除流的错误状态(标志)。使用参数numeric_limits < streamsize > :: max()和换行符调用keymap_txt.ignore可以跳过字符串的剩余部分。

有一种更简单的方法可以停止阅读:

while( !keymap_txt )
{....
}

放入逻辑上下文时,流将自身转换为等于true的值,直到达到EOF。

题外话四

我们这个时代最受欢迎的roguelike相关游戏结合了原始roguelikes和其他类型的元素,如平台游戏,策略等。这种游戏被称为“roguelike-like”或“roguelite”。其中包括“ 不要饿死”,“艾萨克的束缚”,“ FTL:比光更快”,“ 最黑暗的地下城”,甚至“ 暗黑破坏神”等着名作品。

然而,roguelike和roguelite之间的区别有时可能非常小,以至于你无法确定游戏属于哪个类别。有些人认为矮人堡垒不是严格意义上的roguelike,而其他人认为暗黑破坏神是经典roguelike游戏。

结论

尽管该项目通常被证明是高质量的,只有少数严重的缺陷,但并不意味着没有静态分析就能做到。静态分析的力量是经常使用的,而不是像我们为普及那样进行的一次性检查。如果经常使用,静态分析仪可以帮助您在最早的开发阶段检测错误,从而使它们更便于修复。示例计算。


游戏仍然在不断发展,有一个活跃的modder社区正在努力。顺便说一句,它已被移植到多个平台,包括iOS和Android。所以,如果您有兴趣,请试一试!

作者:Victoria Khanieva
原文链接:https://www.viva64.com/en/b/0628/

大灾变黑暗日子:静态分析和Roguelike游戏相关推荐

  1. 推荐率91.3%,这款Roguelike游戏分享了和玩家“相处”的秘诀

    编者按:你想做的游戏和用户想玩的游戏,是一样的吗?如果出现了不同,该怎么办?<元能失控>的研发团队就遇到了这样的问题,一起来看看他们是怎么做的吧. 2018年的时候,<元能失控> ...

  2. 腾讯NExT Studios万字解读:我们是怎样将一款偏硬核的Roguelike游戏大众化的?

    过去一年,不论是国内还是海外,都诞生了不少优秀的Roguelike游戏.但其中,由腾讯NExT Studios研发的<不思议的皇冠>是比较特殊的一个. 这种特殊首先源自游戏本身的玩法机制. ...

  3. Roguelike 游戏中的计算哲学

    撰稿人:Authing 汪智勇 计算哲学并不只存在于严肃的商业业务中,在很多游戏中也能感知相通之处. 作为"开发者友好"的 Authing,今天我们聊聊许多开发者都会感兴趣的话题- ...

  4. Roguelike游戏成破解重灾区,如何破局?

    如果有一款游戏,会让玩家一遍又一遍的去体验,那会是什么? 上世纪八十年代,一款叫<Rogue>的游戏横空出世,其独特的随机性玩法引领了一众游戏的效仿,被权威杂志"PC WORLD ...

  5. 怪兽地牢---(有趣的探索型Roguelike游戏)

    <怪兽地牢>是一款非常有趣的探索型Roguelike游戏,每一次进入游戏都是不同的体验.游戏中,你将扮演多名冒险家进行地牢探险,你需要打败各种各样的怪物,成为最厉害的探险家,解密各种地牢谜 ...

  6. 使用GCP开发带有强化学习功能的Roguelike游戏

    强化学习(RL)的许多应用都是专门针对将人工从训练循环中脱离而设计的. 例如,OpenAI Gym [1]提供了一个训练RL模型以充当Atari游戏中的玩家的框架,许多问扎根都描述了将RL用于机器人技 ...

  7. Roguelike游戏的视野算法

    简介 实现Roguelike的一项任务是弄清楚如何计算玩家或怪物的可见区域.现有的算法很多,但是都存在缺陷,因此我着手开发一种新的算法,该算法对我来说是快速,准确和美观的.尽管我没有创建理想的算法,但 ...

  8. 了解一下Roguelike游戏和Roguelite游戏

    Roguelike和Roguelite特点 rogulike不含永久升级,没有数值刺激,每一局的开局(游戏难度.人物属性)都是相同的.只有在不断的失败中提高自身技巧才能提高通关机率 roguelite ...

  9. 用计算机弹出黎明的黑暗,STEAM打开黎明杀机启动游戏后弹出计算机丢失msvcp140period;dllperiod; | 手游网游页游攻略大全...

    发布时间:2015-12-23 恐怖黎明游戏进不去怎么办 丢失msvcp140.dll错误怎么办.有玩家在进入恐怖黎明游戏的时候出现系统报错,提示msvcp140.dll错误,导致无法进行游戏,下面9 ...

  10. 黑暗爆炸 #1059. [ZJOI2007]矩阵游戏

    传送门 思路:这道题能用二分图我是完全没有想到的,后来去补了下最大二分图的最大匹配,然后看题解才勉强推了这样的思路 一行的每个元素看成二分图的一边,一列的元素看成二分图的另一边. 如果某个点是1那么就 ...

最新文章

  1. 十步优化SQL Server中的数据访问
  2. python 归一化 标准化
  3. newman的键盘不错
  4. 计算机专业和学历的关系!!重要!!
  5. vant显示日期格式_Vue+Vant ui实现日期时间选择
  6. C#使用多态求方形面积周长和圆的面积周长
  7. bitset与取数凑数类问题
  8. vue中v-if指令的使用之Vue知识点归纳(六)
  9. 相机标定(1)内\外参矩阵和畸变矩阵
  10. 大数据学习——关于hive中的各种join
  11. ffmpeg学习日记5-使用ffmpeg进行h264解码
  12. 从零开始学习 JD Chain(二)- JD Chain 区块链浏览器
  13. mysql 联合主键 null_提问关于 mysql得联合主键和复合主键的问题
  14. java短链接_Java 网址短链接服务原理及解决方案
  15. 华为5g鸿蒙折叠,华为再次亮剑!5G新旗舰已经确认,折叠屏+升级到鸿蒙2.0,价格过万...
  16. Wifi无法自动连接的问题
  17. Java 知识点整理-7.StringBuffer类+冒泡排序+选择排序+二分法+Arrays类+基本数据类型的包装类
  18. 纵观客户服务渠道变化,引领在线客服系统新方向
  19. 笔记本电脑硬件软件全升级:内存条-固态硬盘-重装系统
  20. c语言中void *的使用

热门文章

  1. no.8 python 和 Linux (笔记)
  2. 如何理解邮件中的“CC、PS、FYI”等英文缩写?
  3. MPI(Massage Passing Interface)
  4. 用计算机找到自己的另一半,生辰八字算婚期计算器 免费算个人感情婚姻的另一半...
  5. 状态模式,懂你的另一半
  6. ps 图片处理技法 怎样使照片看起来更加清晰
  7. 如何彻底卸载电脑中的垃圾软件
  8. oracle 12C 静默安装
  9. 小谈startup类ConfigureServices方法的作用
  10. PMPtiku项目管理第六版项目的复杂性