在 WSL2 中构建 Swift (Linux)

Swift 生态系统正在 Apple 平台之外积极开发,如今在 Windows 下使用 Windows Subsystem for Linux (WSL2) 进行编写已经相当方便。值得考虑的是,对于 Linux/WSL 下的程序集,可以使用 Swift 的轻量级版本 – 无需专有的 Apple 框架(例如 SwiftUI、UIKit、AppKit、CoreData、CoreML、ARKit、SpriteKit 和其他 iOS/macOS 特定的库),但对于控制台实用程序和后端来说,这已经足够了。在这篇文章中,我们将逐步介绍准备环境并从 WSL2 中的源代码构建 Swift 编译器的过程(以 Ubuntu/Debian 为例)。

我们更新软件包列表和系统本身:

sudo apt update && sudo apt upgrade -y

安装构建所需的依赖项:

sudo apt install -y \
  git cmake ninja-build clang python3 python3-pip \
  libicu-dev libxml2-dev libcurl4-openssl-dev \
  libedit-dev libsqlite3-dev swig libncurses5-dev \
  pkg-config tzdata rsync

安装编译器和链接器(LLVM 和 LLD):

sudo apt install -y llvm lld

克隆包含所有依赖项的 Swift 存储库:

git clone https://github.com/apple/swift.git
cd swift
utils/update-checkout --clone

用 swiftc 安装 `swiftly` 和现成的 swift

curl -O https://download.swift.org/swiftly/linux/swiftly-$(uname -m).tar.gz && \
tar zxf swiftly-$(uname -m).tar.gz && \
./swiftly init --quiet-shell-followup && \
. "${SWIFTLY_HOME_DIR:-$HOME/.local/share/swiftly}/env.sh" && \
hash -r

让我们开始构建(这将需要很长时间):

utils/build-script \
  --release-debuginfo \
  --swift-darwin-supported-archs="x86_64" \
  --llvm-targets-to-build="X86" \
  --skip-build-benchmarks \
  --skip-test-cmark \
  --skip-test-swift \
  --skip-ios \
  --skip-tvos \
  --skip-watchos \
  --skip-build-libdispatch=false \
  --skip-build-cmark=false \
  --skip-build-foundation \
  --skip-build-lldb \
  --skip-build-xctest \
  --skip-test-swift

构建完成后,将编译器的路径添加到PATH(指定构建文件夹的路径):

export PATH=/root/Sources/3rdparty/build/Ninja-RelWithDebInfoAssert/swift-linux-x86_64/bin/swiftc:$PATH

我们检查已安装的 Swift 版本是否正常工作:

swift --version

创建一个测试文件并运行它:

echo "print(\"Hello, World!\")" > hello.swift
swift hello.swift

您还可以编译二进制文件并运行它:

swiftc hello.swift
./hello

来源

模式解释器实践

上一篇文章中,我们研究了解释器模式的理论,了解了 AST 树是什么以及如何抽象终结符和非终结符表达式。这次,让我们抛开理论,看看如何将这种模式应用到我们每天都在使用的严肃商业项目中!

剧透:您现在可能正在使用解释器模式,只需在浏览器中阅读此文本即可!

业界使用此模式的最引人注目、也许也是最重要的示例之一是 JavaScript。这种语言最初是“在膝盖上”创建的,如今,由于解释的概念,它可以在数十亿台设备上运行。

改变互联网的 10 天

JavaScript 的历史充满了传奇。 1995 年,Brendan Eich 在 Netscape Communications 工作时,接到的任务是创建一种可以直接在浏览器 (Netscape Navigator) 中运行的简单脚本语言,以使网页具有交互性。管理层想要一种类似于当时超级流行的 Java 语法的东西,但不是针对专业工程师,而是针对网页设计师。

Eich 仅用了10 天来编写该语言的第一个原型,该语言当时被称为 Mocha(然后是 LiveScript,出于营销原因才称为 JavaScript)。这种热潮并非偶然:微软紧随其后,同时也在积极准备自己的脚本语言 VBScript,以便嵌入 Internet Explorer 浏览器中。 Netscape 迫切需要发布回应,以免在迫在眉睫的浏览器战争中落败。

根本没有时间将复杂的编译器编写成机器代码。对于 Eich 来说,显而易见且最快的解决方案是经典解释器的架构。

第一个解释器(SpiderMonkey)的工作方式如下:

  1. 它从页面读取脚本的文本源代码。
  2. 词法分析器将文本分解为标记。
  3. 解析器构建了一个抽象语法树(AST)。就解释器模式而言,这棵树由终端表达式(字符串,数字如42)和非终端(函数调用,语句如If、While)组成。
  4. 然后虚拟机一步步“遍历”这棵树,在每个节点执行嵌入其中的指令(调用类似于 Interpret() 的方法)。

上下文和对象

还记得在经典实现中我们必须传递给 Interpret(Context context) 方法的 Context 对象吗?解释器需要它来存储当前的内存状态。

就 JavaScript 而言,顶层上下文的角色由全局对象(例如浏览器中的窗口)扮演。例如,当您的 AST 节点尝试通过 document.write(“Hello”) 将文本写入屏幕时,解释器会访问其上下文(文档对象)并调用所需的内部浏览器 API。

多亏了解释器,JavaScript 才能够如此轻松地与 DOM(文档对象模型)进行交互 – 这些都只是上下文中可由树节点访问的对象。

解释器的演变:JIT 编译

从历史上看,浏览器中的 JS 长期以来一直是“纯粹”的解释器。而这有一个很大的缺点——速度慢。每次执行脚本时解析树并缓慢遍历每个节点会减慢复杂的 Web 应用程序的速度。

随着 2008 年 Google V8 引擎(内置于 Chrome 中)的出现,一场革命发生了。工程师们意识到,对于现代网络来说,一个解释器是不够的。引擎变得更加复杂:它仍然构建 AST 树,但现在使用 JIT(即时)编译

现代 JS 引擎(V8、SpiderMonkey)的工作方式就像一个复杂的管道:

  1. 快速而愚蠢的基本解释器立即开始执行您的 JS 代码,甚至无需等待它编译(经典模式在这里仍然有效)。
  2. 同时,引擎会监控代码的“热门”部分(被调用数千次的循环或函数)。
  3. 这些部分由 JIT 编译器直接编译为优化的机器代码,绕过缓慢的解释器。

正是解释器的即时启动和编译的计算能力的结合让 JavaScript 占领了世界,成为服务器 (Node.js) 和移动应用程序 (React Native) 的语言。

游戏行业的翻译

尽管 C++ 在重型计算领域占据主导地位,但解释器模式仍然是游戏开发中用于创建游戏逻辑的行业标准。为了什么?这样游戏设计师就可以制作游戏,而无需“放弃”引擎或需要不断重新编译引擎的风险。

一个很好的历史例子是UnrealScript – 虚幻竞技场和战争机器游戏的逻辑是在虚幻引擎1、2和3中编写的语言。文本被编译成紧凑的抽象机器字节码,然后由引擎的虚拟机逐步(解释)。

可视化图形脚本(蓝图)

如今,文本已被可视化编程所取代 – 虚幻引擎 4 和 5 中的蓝图系统。

如果您曾经在虚幻引擎中打开过蓝图,您就会看到很多通过电线连接的节点。从架构上来说,整个蓝图图是绘制在屏幕上的巨大抽象语法树 (AST)

  1. 终端表达式:常量节点。例如,仅存储数字 42 或字符串的节点。它们在解释时返回特定值。
  2. 非终止表达式:计算节点(添加)或流控制节点(分支)。它们具有参数输入,解释器首先对其进行递归计算,然后将结果作为输出引脚生成。

而上下文在这里的作用是由特定游戏对象(Actor)实例的记忆来扮演的。解释器机器安全地“行走”通过该图,请求数据并执行转换。

解释器还用在什么地方?

解释器模式几乎可以在任何需要执行动态指令的复杂系统中找到。以下只是商业软件中的一些示例:

  • 解释型编程语言(Python、Ruby、PHP)。它们的整个运行时基于经典模式。例如,CPython 参考实现首先将您的 .py 脚本解析为 AST,将其编译为字节码,然后一个巨大的虚拟机(计算循环)逐步解释该字节码。
  • Java 虚拟机 (JVM)。 最初,Java 代码不是编译成机器指令,而是编译成字节码。当您运行应用程序时,JVM 充当解释器(尽管使用积极的 JIT 编译,就像在 V8 中一样)。
  • 数据库和 SQL 当您在 PostgreSQL 或 MySQL 中发出 SQL 查询(SELECT * FROM users)时,数据库引擎充当解释器。它执行词法分析,构建 AST 查询树,生成执行计划,然后通过迭代表的行来逐字“解释”该计划。
  • 正则表达式 (RegEx)。任何正则表达式引擎都会在内部将字符串模式(例如 ^\d{3}-\d{2}$)解析为状态图 (NFA/DFA 自动机),然后内部解释器会通过该状态图,将每个输入字符与该图的顶点进行匹配。
  • Unity Shader Graph / 虚幻材质编辑器 – 将视觉节点解释为模块化着色器代码 (GLSL/HLSL)。
  • Blender 几何节点 – 解释数学和几何运算以按程序实时生成 3D 模型。

总计

解释器模式早已超出了“编写自己的计算器”的范围。这是最有力的行业标准。从每天在浏览器幕后执行数十亿字节代码的 JavaScript 引擎,到无需 C++ 知识即可构建复杂逻辑的游戏设计者,解释器仍然是现代 IT 开发中最重要的架构概念之一。

实际上没有福尔马林的框图

框图是一种视觉工具,有助于将复杂的算法转变为可理解且结构化的动作序列。从编程到业务流程管理,它们是一种可视化,分析和优化最复杂系统的通用语言。

想象一下一张地图,而不是道路是逻辑,而不是城市 – 行动。这是一个框图 – 在最令人困惑的过程中导航的必不可少的工具。

示例1:简化的游戏启动方案
要了解工作原理,让我们提出一个简单的游戏发布计划。

当一切都没有失败时,该方案显示了完美的脚本。但是在现实生活中,一切都更加复杂。

示例2:扩展的方案用于使用数据加载开始游戏
现代游戏通常需要互联网连接才能下载用户数据,保存或设置。让我们将这些步骤添加到我们的计划中。

该方案已经更现实,但是如果出现问题,会发生什么?

怎么样:一款“破产”的游戏,失去了互联网

在项目开始时,开发人员无法考虑所有可能的情况。例如,他们专注于游戏的主要逻辑,并且不认为如果玩家具有互联网连接会发生什么。

在这种情况下,其代码的框图看起来像这样:

在这种情况下,该游戏在等待数据的阶段冻结了,而不是由于缺乏连接而无法收到的数据,而不是发出错误或关闭。这导致了“黑屏”并冻结了应用程序。

如何变成:用户投诉纠正

在许多用户对徘徊的投诉之后,开发人员团队意识到我们需要纠正错误。他们通过添加错误处理单元来更改代码,该单元允许应用程序正确响应缺乏连接。

这就是校正的框图的外观,其中两个方案都被考虑:

借助这种方法,游戏现在正确地通知了用户有关问题的信息,在某些情况下,它甚至可以进入离线模式,从而使您可以继续游戏。这是一个很好的例子,说明了为什么框图如此重要:他们使开发人员不仅考虑了理想的执行方式,而且还考虑了所有可能的失败,从而使最终产品更加稳定和可靠。

不确定的行为

悬挂和错误只是该程序不可预测行为的一个例子。在编程中,存在一个不确定行为(不确定的行为)的概念 – 这是语言标准无法描述程序在某种情况下应如何行为的情况。

这可能会导致任何事情:从撤回的随机“垃圾”到程序的失败甚至严重的安全漏洞。例如,使用内存时,通常会发生不确定的行为。

语言C的一个示例:

想象一下,开发人员将线路复制到缓冲区中,但忘了添加到零符号(`\ 0`)的端,该符号标记了线路的末端。

这就是代码的样子:

#include 

int main() {
char buffer[5];
char* my_string = "hello";

memcpy(buffer, my_string, 5);

printf("%s\n", buffer);
return 0;
}

预期结果:“你好”
真正的结果是不可预测的。

为什么会发生这种情况?使用指定符的“ printf”函数期望该行以零符号结束。如果他不是,她将继续阅读突出显示的缓冲区之外的记忆。

这是此过程的框图,并具有两个可能的结果:

这是一个明显的例子,说明了框图如此重要的原因:它们使开发人员不仅考虑了理想的执行方式,而且还考虑了所有可能的失败,包括如此低级的问题,使最终产品更加稳定和可靠。

Pixel Perfect:在声明性时代的神话还是现实?

在界面开发的世界中,有一个共同的概念 – “小屋中的像素完美” 。它意味着设计机对最小像素的最准确再现。很长一段时间以来,它一直是黄金标准,尤其是在经典的网页设计时代。但是,随着声明性英里的到来和各种设备的快速增长,“ Pixel Perfect”的原理变得越来越短暂。让我们尝试找出原因。

帝国wysiwyg vs.声明守则:有什么区别?

传统上,许多接口,尤其是桌面,是使用命令式方法或编辑器的Wysiwyg(您看到的)创建的。在这样的工具中,设计师或开发人员直接用元素来操纵,将它们放在画布上,以准确的像素。它类似于使用图形编辑器 – 您会看到元素的外观,并且肯定可以将其定位。在这种情况下,“ Pixel Perfect”的成就是一个非常真实的目标。

但是,现代发展越来越基于声明性里程。这意味着您不告诉计算机“在此处放这个按钮”,而是要描述您想要获得的内容。例如,您没有指示元素的特定坐标,而是描述其属性:“此按钮应为红色,从各个方面有16px的凹痕,并且位于容器的中心。”像React,Vue,Swiftui或JetPack一样,只需使用此原理即可。

为什么“ Pixel Perfect”不适合许多设备的声明英里

想象一下,您创建了一个在iPhone 15 Pro Max,Samsung Galaxy Fold,iPad Pro和4K分辨率上看起来同样不错的应用程序。这些设备中的每一个都有不同的屏幕分辨率,像素密度,派对和物理大小。

当您使用声明性方法时,系统本身会考虑其所有参数在特定设备上显示您所描述的接口。您设置规则和依赖性,而不是苛刻的坐标。

* 适应性和响应能力:声明英里的主要目标是创建自适应和响应式接口。这意味着您的接口应自动适应屏幕的大小和方向,而不会破坏和保持可读性。如果我们试图在每个设备上“像素完美”,则必须为同一界面创建无数的选项,这将完全阐明声明方法的优势。
* 像素密度(DPI/PPI):设备具有不同的像素密度。如果您不考虑缩放率,则具有高密度的设备上的100个“虚拟”像素的尺寸将比低密度设备上的元素小得多。声明的框架是通过物理像素抽象的,与逻辑单元一起工作。
* 动态内容:现代应用程序中的内容通常是动态的 – 其体积和结构可能会有所不同。如果我们用力地将像素破烂,文本或图像的任何更改都会导致布局的“崩溃”。
* 各种平台:除了各种设备之外,还有不同的操作系统(iOS,Android,Web,桌面)。每个平台都有自己的设计,标准控件和字体。尝试在所有平台上制作绝对相同的Pixel完美界面的尝试将导致不自然的类型和差的用户体验。

旧方法没有消失,而是进化了

重要的是要了解,接口的方法不是“命令”和“声明性”之间的二元选择。从历史上看,对于每个平台,都有自己的工具和方法来创建接口。

* 本机接口文件: iOS是xib/故事板,用于android-xml标记文件。这些文件是一个完美的Wysiwyg布局,然后像编辑器一样在广播中显示。这种方法并没有在任何地方消失,它继续发展,并与现代宣言框架集成。例如,苹果和Jetpack的Swiftui在Android中构成了纯粹的声明代码的道路,但同时保留了使用经典布局的机会。
* 混合解决方案:经常在实际项目中,使用多种方法。例如,可以声明地实现应用程序的基本结构,并且对于需要准确的元素定位,可以使用较低的,命令式方法或开发的本机组件,以考虑到平台的细节。

从整体到适应性:设备的演变如何形成声明英里

在过去的几十年中,数字界面的世界发生了巨大变化。从具有固定许可证的固定计算机来看,我们来到了各种用户设备的指数增长的时代。今天,我们的应用程序应该同样擅长:

* 智能手机的所有形式和屏幕尺寸的大小。
* 平板电脑具有独特的方向模式和一个分开的屏幕。
* 笔记本电脑和台式机具有各种监视器的许可。
* 电视和媒体中心,远程控制。值得注意的是,即使对于电视,它的备注也很简单,因为 Apple TV Remote 具有最少的按钮,反之亦然,但具有许多功能,对接口的现代要求也不应需要对这些输入功能进行特定的适应。该接口应“好像自身”工作,而不必对与特定遥控器进行交互的“如何”相互作用。
* 智能手表和可穿戴设备带有简约屏幕。
* 虚拟现实头盔(VR),需要一种全新的空间接口方法。
* 增强现实设备(AR),应用有关现实世界的信息。
* 汽车信息和娱乐系统
*甚至是家用电器:从带有感官屏幕和带有交互式显示器的洗衣机到智能烤箱和智能房屋系统的冰箱。

这些设备中的每一个都有其独特的功能:物理维度,派对比率,像素密度,输入方法(触摸屏,鼠标,控制器,手势,声乐命令),以及用户环境的微妙之处。例如,VR Shlesh需要深层沉浸,并且在旅途中进行了智能手机的快速和直观的工作,而冰箱界面应像快速导航一样简单而大。

经典方法:支撑各个接口的负担

在台式机和第一个移动设备的主导时代,通常的业务是对单个接口文件的创建和支持,甚至是每个平台的完全独立的接口代码。

* ios 下的开发通常需要在Xcode中使用故事板或XIB文件,在Objective-C或Swift上编写代码。
*对于 android ,创建了XML标记文件和Java或Kotlin上的代码。
* Web接口打开了HTML/CSS/JavaScript。
*对于 c ++应用程序在各种桌面平台上,使用了它们的特定框架和工具:
*在 Windows 中,这些是MFC(Microsoft Foundation类),带有手动绘制元素的Win32 API或使用资源文件用于对话框Windows和Control Elements。
*可可(Objective-C/Swift)或用于直接控制图形界面的旧碳API。
*在 linux/unix样系统中,经常使用诸如GTK+或QT之类的库,这些库通常通过类似XML的标记文件(例如,QT Designer中的.UI文件)或直接的软件创建元素来提供其用于创建接口的小部件和机制。

这种方法可确保对每个平台的最大控制,从而使您可以考虑其所有特定功能和本机元素。但是,他有一个巨大的缺点:重复的努力和支持的巨大成本。设计或功能的最小变化需要引入几个独立代码库的权利。对于开发人员团队来说,这变成了真正的噩梦,减慢了新功能的输出并增加了错误的可能性。

声明英里:多样性的单一语言

正是为了回应这种快速的并发症,声明性的里程似乎是主要的范式。像 React,Vue,Swiftui,JetPack构成之类的Framws不仅是编写代码的新方法,而且是思维的根本转变。

声明性方法的主要思想:我们没有说“如何”绘制每个元素(势在必行),而是描述了“我们想看到的(声明性)。我们设置了接口的属性和条件,框架决定如何最好地在特定设备上显示它。

由于以下主要优势,这可能是可能的:

1。从平台的详细信息中抽象:声明的fraimvorki专门设计为忘记每个平台的低级别详细信息。开发人员使用单个传输的代码以更高的抽象级别描述了组件及其关系。
2。自动适应性和响应能力: freimvorki负责自动缩放,将元素的布局和适应性更改为不同尺寸的屏幕,像素密度和输入方法。这是通过使用灵活布局系统(例如Flexbox或Grid)以及类似于“逻辑像素”或“ DP”的概念来实现的。
3。用户体验的一致性:尽管存在外部差异,但声明的方法使您可以在整个设备家族中维护行为和交互的单一逻辑。这简化了测试过程,并提供了更可预测的用户体验。
4。开发和降低成本的加速:具有能够在许多平台上工作的相同代码,大大减少了发展和支持的时间和成本。团队可以专注于功能和设计,而不是重复重写相同的接口。
5。未来的准备就绪:从当前设备的细节中抽象的能力使声明代码更具对新型设备和形式因素的出现的抵抗力。可以更新Freimvorki以支持新技术,并且您已经编写的代码将获得此支持相对无缝。

结论

声明的英里不仅是一种时尚趋势,而且是由用户设备快速开发(包括物联网(IoT)和智能家用电器)引起的必要进化步骤。它允许开发人员和设计师创建复杂,自适应和统一的接口,而不会淹没每个平台的无尽特定实现。从对每个像素的命令控制到所需状态的声明性描述的过渡是一种认识,即在未来的世界中,无论显示哪种屏幕,都应灵活,传递和直观

程序员,设计师和用户需要学习如何生活在这个新世界。 Pixter Perfect的额外细节,设计为特定设备或分辨率,导致不必要的时间成本进行开发和支持。此外,这种刺激性的布局可能根本无法在具有非标准界面的设备上使用,例如有限的输入电视,VR和AR Shifts以及未来的其他设备,我们仍然不知道今天。灵活性和适应性 – 这些是创建现代世界中成功接口的关键。

Vibe核心技巧:为什么LLM仍然无法与固体,干燥和干净一起使用

随着大型语言模型(LLM)的开发,例如ChatGpt,越来越多的开发人员使用它们来生成代码,设计体系结构和加速集成。但是,通过实用的应用,它变得明显:建筑的经典原理 – 固体,干燥,清洁 – 与LLM Codgendation的特殊性相同。

这并不意味着原理已经过时了 – 相反,它们与手动开发完美合作。但是使用LLM,必须适应该方法。

为什么LLM无法应对建筑原理

封装

不合时间需要了解系统部分之间的界限,对开发人员意图的知识以及严格的访问限制。 LLM通常会简化结构,无缘无故地公开字段或重复实现。这使得代码更容易受到错误的影响,并违反了架构边界。

摘要和接口

设计模式,例如抽象工厂或策略,需要对系统的整体视图并了解其动态。模型可以在没有明确目的的情况下创建一个界面,而无需确保其实现或违反层之间的连接。结果是过量或非功能架构。

干(托管重复自己)

LLM不要寻求最大程度地减少重复代码 – 相反,与制作一般逻辑相比,他们更容易复制块。尽管他们可以根据要求提供重构,但默认模型倾向于生成“自给自足的”片段,即使它导致冗余。

干净的体系结构

清洁意味着严格的层次结构,远离框架,定向依赖性以及层之间的最小连接性。这样的结构的产生需要对系统的全球理解,而llm则以单词概率而不是建筑完整性的含义来起作用。因此,该代码混合在一起,违反了依赖的方向和简化的划分。

使用LLM 时效果更好

湿而不是干
在与LLM合作时,湿(写两次)方法更为实用。代码的重复不需要保留模型中的上下文,这意味着结果是可以预测的,并且更易于正确纠正。它还减少了非明显连接和错误的可能性。

此外,重复有助于补偿模型的短记忆:如果在多个地方发现了某些逻辑片段,LLM更有可能将其考虑到进一步的生成中。这简化了伴奏,并增加了对“忘记”的抵制。

简单结构而不是封装

避免复杂的封装并依靠代码部分之间的数据直接传输,您可以大大简化生成和调试。通过快速的迭代开发或MVP的创建,尤其如此。

简化体系结构

该项目的简单,平坦的结构具有最少的依赖性和抽象,从而在生成过程中产生了更稳定的结果。该模型可以更轻松地调整这样的代码,并且较少违反了组件之间的预期连接。

SDK集成 – 手动可靠

大多数语言模型接受过过时的文档版本培训。因此,当生成用于安装SDK的指令时,通常会出现错误:过时的命令,无关参数或链接到无法访问的资源。实践显示:最好使用官方文档和手动调整,而LLM则是辅助角色 – 例如,生成模板代码或配置的改编。

为什么原理仍然有效 – 但是手动开发

重要的是要了解,固体,干燥和清洁的困难与LLM有关的CODHENELISERALION。当开发人员手动编写代码时,这些原则继续证明其价值:它们降低了连接性,简化支持,提高项目的可读性和灵活性。

这是由于人类思维容易概括的事实。我们正在寻找模式,将重复的逻辑带入单个实体,创建模式。可能,这种行为具有进化的根源:减少信息量可节省认知资源。

LLM的行为不同:他们没有从数据量中体验到负载,也不会努力储蓄。相反,与构建和维护复杂的抽象相比,他们更容易使用重复的,分散的信息。这就是为什么他们更容易在没有封装的情况下应对代码,重复结构和最小的架构严重性。

结论

大型语言模型是开发中的有用工具,尤其是在早期或创建辅助代码时。但是,重要的是要适应它们的方法:为了简化体系结构,限制抽象,避免复杂的依赖关系,并且在配置SDK时不依赖它们。

固体,干燥和清洁的原则仍然是相关的,但它们在人的手中赋予了最佳效果。使用LLM时,使用简化的实用风格是合理的,该样式使您可以获取可靠且易于理解的代码,该代码易于手动完成。 LLM忘记的地方 – 复制代码可以帮助他记住。

将 Surreal Engine C++ 移植到 WebAssembly

在这篇文章中,我将描述如何将 Surreal Engine 游戏引擎移植到 WebAssembly。

超现实引擎–一个游戏引擎,实现了Unreal Engine 1的大部分功能,著名的游戏都使用这个引擎–虚幻竞技场 99、虚幻、杀出重围、不朽。它指的是主要在单线程执行环境中工作的经典引擎。

最初,我有一个想法,那就是承担一个我无法在任何合理时间内完成的项目,从而向我的 Twitch 粉丝表明,有些项目甚至是我也无法完成。在我的第一次直播中,我突然意识到使用 Emscripten 将 Surreal Engine C++ 移植到 WebAssembly 的任务是可行的。

Surreal Engine Emscripten Demo

一个月后,我可以在 WebAssembly 上演示我的前叉和引擎组装:
https://demensdeum.com/demos/SurrealEngine/

与原始版本一样,控制是使用键盘箭头进行的。接下来,我计划将其调整为移动控制 (tachi),添加正确的光照和 Unreal Tournament 99 渲染的其他图形功能。

从哪里开始?

我想说的第一件事是,任何项目都可以使用 Emscripten 从 C++ 移植到 WebAssembly,唯一的问题是功能有多完整。选择一个库端口已经可用于 Emscripten 的项目;对于 Surreal Engine,你非常幸运,因为该引擎使用 SDL 2、OpenAL 库。它们都被移植到 Emscripten。不过,Vulkan 用作图形 API,目前无法用于 HTML5,实现 WebGPU 的工作正在进行中,但也处于草案阶段,也未知从 Vulkan 到 WebGPU 的进一步移植有多简单,完全标准化后。因此,我必须为 Surreal Engine 编写自己的基本 OpenGL-ES / WebGL 渲染器。

构建项目

在 Surreal Engine 中构建系统 – CMake,这也简化了移植,因为Emscripten 提供其原生构建器“ emcmake,emmake。
Surreal Engine 移植基于我最新的 WebGL/OpenGL ES 和 C++ 游戏代码,称为 Death-Mask,因此开发更加简单,我随身携带了所有必要的构建标志和代码示例。

CMakeLists.txt 中最重要的一点是 Emscripten 的构建标志,下面是项目文件中的示例:


-s MAX_WEBGL_VERSION=2 \

-s EXCEPTION_DEBUG \

-fexceptions \

--preload-file UnrealTournament/ \

--preload-file SurrealEngine.pk3 \

--bind \

--use-preload-plugins \

-Wall \

-Wextra \

-Werror=return-type \

-s USE_SDL=2 \

-s ASSERTIONS=1 \

-w \

-g4 \

-s DISABLE_EXCEPTION_CATCHING=0 \

-O3 \

--no-heap-copy \

-s ALLOW_MEMORY_GROWTH=1 \

-s EXIT_RUNTIME=1")

构建脚本本身:


emmake make -j 16

cp SurrealEngine.data /srv/http/SurrealEngine/SurrealEngine.data

cp SurrealEngine.js /srv/http/SurrealEngine/SurrealEngine.js

cp SurrealEngine.wasm /srv/http/SurrealEngine/SurrealEngine.wasm

cp ../buildScripts/Emscripten/index.html /srv/http/SurrealEngine/index.html

cp ../buildScripts/Emscripten/background.png /srv/http/SurrealEngine/background.png

接下来我们将准备索引.html ,其中包括项目文件系统预加载器。为了上传到网络,我使用了 Unreal Tournament Demo 版本 338。正如您从 CMake 文件中看到的,解压后的游戏文件夹已添加到构建目录中,并作为 Emscripten 的预加载文件进行链接。

主要代码更改

那就需要改变游戏的游戏循环,不能无限循环,这样会导致浏览器卡住,而是需要使用emscripten_set_main_loop,我在2017年的笔记中写过这个功能< a href="https://demensdeum.com/blog/ru/2017/03/29/porting-sdl-c-game-to-html5-emscripten/" rel="noopener" target="_blank">将 SDL C++ 游戏移植到 HTML5 (Emscripten)”
我们将退出 while 循环的代码更改为 if,然后在全局范围内显示包含游戏循环的游戏引擎的主类,并编写一个全局函数,该函数将从全局对象中调用游戏循环步骤:


#include <emscripten.h>

Engine *EMSCRIPTEN_GLOBAL_GAME_ENGINE = nullptr;

void emscripten_game_loop_step() {

	EMSCRIPTEN_GLOBAL_GAME_ENGINE->Run();

}

#endif

之后,您需要确保应用程序中没有后台线程;如果有,则准备将其重写为单线程执行,或者使用 Emscripten 中的 phtread 库。
Surreal Engine中的后台线程用于播放音乐,数据来自主引擎线程,有关当前曲目、是否需要播放音乐或是否缺少音乐,然后后台线程通过互斥体接收新状态并开始播放新音乐,或暂停。背景流还用于在播放期间缓冲音乐。
我尝试使用 pthread 为 Emscripten 构建 Surreal Engine,但没有成功,因为 SDL2 和 OpenAL 端口是在没有 pthread 支持的情况下构建的,而且我不想为了音乐而重建它们。因此,我使用循环将背景音乐流的功能转移到单线程执行。通过从 C++ 代码中删除 pthread 调用,我将缓冲和音乐播放移至主线程,这样就不会出现延迟,我将缓冲区增加了几秒钟。

接下来我会描述图形和声音的具体实现。

不支持 Vulkan!

是的,HTML5 不支持 Vulkan,尽管所有营销手册都将跨平台和广泛的平台支持作为 Vulkan 的主要优势。因此,我必须为简化的 OpenGL 类型编写自己的基本图形渲染器 – ES,它用在移动设备上,有时它不包含现代OpenGL的时尚功能,但它很好地移植到WebGL,这正是Emscripten所实现的。写了基本的tile渲染,bsp渲染,最简单的GUI显示,渲染模型+贴图,两周就完成了。这也许是该项目中最困难的部分。实现 Surreal Engine 渲染的全部功能还有很多工作要做,因此欢迎读者以代码和拉取请求的形式提供任何帮助。

支持 OpenAL!

幸运的是,Surreal Engine 使用 OpenAL 进行音频输出。在 OpenAL 中编写了一个简单的 hello world 并使用 Emscripten 在 WebAssembly 中将其组装后,我清楚地意识到一切是多么简单,然后我开始移植声音。
经过几个小时的调试,很明显 Emscripten 的 OpenAL 实现存在几个错误,例如,在初始化读取单通道数时,该方法返回无限数,并且在尝试初始化无限大小的向量后,C++崩溃并出现异常向量::length_error。
我们通过将单声道数量硬编码为 2048 来解决这个问题:


		alcGetIntegerv(alDevice, ALC_STEREO_SOURCES, 1, &stereoSources);



#if __EMSCRIPTEN__

		monoSources = 2048; // for some reason Emscripten's OpenAL gives infinite monoSources count, bug?

#endif



有网络吗?

Surreal Engine目前不支持在线游戏,支持与机器人一起玩,但我们需要有人为这些机器人编写AI。理论上,您可以使用Websockets在WebAssembly/Emscripten上实现网络游戏。

结论

总之,我想说,由于使用了 Emscripten 移植的库,以及我过去在 WebAssembly 中使用 C++ 实现游戏的经验,Surreal Engine 的移植非常顺利在 Emscripten 上。以下是有关该主题的知识来源和存储库的链接。
M-M-M-杀死怪物!

此外,如果您想帮助该项目,最好使用 WebGL/OpenGL ES 渲染代码,请在 Telegram 中给我写信:
https://t.me/demenscave

链接

https://demensdeum.com/demos/Sur​​realEngine/
https://github.com/demensdeum/SurrealEngine-Emscripten

https://github.com/dpjudas/SurrealEngine

在 macOS 上打开 USB 键盘背光

我最近买了一个非常便宜的 Getorix GK-45X USB 键盘,带有 RGB 背光。将其连接到配备 M1 处理器的 MacBook Pro 后,很明显 RGB 背光不起作用。即使按下神奇组合 Fn + Scroll Lock 也无法打开背光;只是 MacBook 屏幕的背光亮度发生了变化。
这个问题有几种解决方案,分别是OpenRGB(不起作用)、HID LED Test(不起作用)。只有 kvmswitch 实用程序有效:
https://github.com/stoutput/OSX-KVM

您需要从 GitHub 下载它并允许它从系统设置的安全面板中的终端运行。
据我从描述中了解到,启动该实用程序后,它会发送 Fn + Scroll Lock 按键,从而打开/关闭键盘上的背光。

树排序

树排序——使用二叉搜索树进行排序。时间复杂度– O(n²)。在这样的树中,左边的每个节点都有小于该节点的数字,右边的每个节点都有大于该节点的数字,当从根开始并从左到右打印值时,我们得到一个排序的数字列表。令人惊讶吧?

考虑二叉搜索树图:

Derrick Coetzee(公共领域)

尝试手动读取从左下角倒数第二个左侧节点开始的数字,对于左侧的每个节点 – 右侧的一个节点。

它看起来像这样:

  1. 左下角倒数第二个节点 – 3.
  2. 它有一个左分支 – 1.
  3. 取这个数字(1)
  4. 接下来我们采用顶点 3 (1, 3)
  5. 右侧是分支 6,但它包含分支。因此,我们以同样的方式阅读它。
  6. 节点 6 编号 4 的左分支 (1, 3, 4)
  7. 节点本身为 6 (1, 3, 4, 6)
  8. 右 7(1、3、4、6、7)
  9. 向上到根节点– 8(1,3,4,6,7,8)
  10. 我们以此类推将所有内容打印在右侧
  11. 我们得到了最终名单– 1、3、4、6、7、8、10、13、14

要在代码中实现该算法,您需要两个函数:

  1. 组装二叉搜索树
  2. 以正确的顺序打印二叉搜索树

二叉搜索树的组装方式与读取时相同,左侧或右侧的每个节点都会附加一个数字,具体取决于它是较少还是较多。

Lua 中的示例:


function Node:new(value, lhs, rhs)
    output = {}
    setmetatable(output, self)
    self.__index = self  
    output.value = value
    output.lhs = lhs
    output.rhs = rhs
    output.counter = 1
    return output  
end

function Node:Increment()
    self.counter = self.counter + 1
end

function Node:Insert(value)
    if self.lhs ~= nil and self.lhs.value > value then
        self.lhs:Insert(value)
        return
    end

    if self.rhs ~= nil and self.rhs.value < value then
        self.rhs:Insert(value)
        return
    end

    if self.value == value then
        self:Increment()
        return
    elseif self.value > value then
        if self.lhs == nil then
            self.lhs = Node:new(value, nil, nil)
        else
            self.lhs:Insert(value)
        end
        return
    else
        if self.rhs == nil then
            self.rhs = Node:new(value, nil, nil)
        else
            self.rhs:Insert(value)
        end
        return
    end
end

function Node:InOrder(output)
    if self.lhs ~= nil then
       output = self.lhs:InOrder(output)
    end
    output = self:printSelf(output)
    if self.rhs ~= nil then
        output = self.rhs:InOrder(output)
    end
    return output
end

function Node:printSelf(output)
    for i=0,self.counter-1 do
        output = output .. tostring(self.value) .. " "
    end
    return output
end

function PrintArray(numbers)
    output = ""
    for i=0,#numbers do
        output = output .. tostring(numbers[i]) .. " "
    end    
    print(output)
end

function Treesort(numbers)
    rootNode = Node:new(numbers[0], nil, nil)
    for i=1,#numbers do
        rootNode:Insert(numbers[i])
    end
    print(rootNode:InOrder(""))
end


numbersCount = 10
maxNumber = 9

numbers = {}

for i=0,numbersCount-1 do
    numbers[i] = math.random(0, maxNumber)
end

PrintArray(numbers)
Treesort(numbers)

Важный нюанс что для чисел которые равны вершине придумано множество интересных механизмов подцепления к ноде, я же просто добавил счетчик к классу вершины, при распечатке числа возвращаются по счетчику.

Ссылки

https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/treesort

Источники

TreeSort Algorithm Explained and Implemented with Examples in Java | Sorting Algorithms | Geekific – YouTube

Tree sort – YouTube

Convert Sorted Array to Binary Search Tree (LeetCode 108. Algorithm Explained) – YouTube

Sorting algorithms/Tree sort on a linked list – Rosetta Code

Tree Sort – GeeksforGeeks

Tree sort – Wikipedia

How to handle duplicates in Binary Search Tree? – GeeksforGeeks

Tree Sort | GeeksforGeeks – YouTube

桶排序

桶排序——按桶排序。该算法类似于计数排序,不同之处在于将数字收集到“桶”范围中,然后使用任何其他足够高效的排序算法对桶进行排序,最后一步是将“桶”展开减一,得到一个排序列表。

算法的时间复杂度为O(nk)。该算法在线性时间内适用于服从均匀分布规律的数据。简单来说,元素必须在一定范围内,不能有“尖峰”,例如0.0到1.0之间的数字。如果这些数字中有 4 或 999,那么根据庭院法则,这样的行不再被视为“偶数”。

Julia 中的实现示例:

    buckets = Vector{Vector{Int}}()
    
    for i in 0:bucketsCount - 1
        bucket = Vector{Int}()
        push!(buckets, bucket)
    end

    maxNumber = maximum(numbers)

    for i in 0:length(numbers) - 1
        bucketIndex = 1 + Int(floor(bucketsCount * numbers[1 + i] / (maxNumber + 1)))
        push!(buckets[bucketIndex], numbers[1 + i])
    end

    for i in 0:length(buckets) - 1
        bucketIndex = 1 + i
        buckets[bucketIndex] = sort(buckets[bucketIndex])
    end

    flat = [(buckets...)...]
    print(flat, "\n")

end

numbersCount = 10
maxNumber = 10
numbers = rand(1:maxNumber, numbersCount)
print(numbers,"\n")
bucketsCount = 10
bucketSort(numbers, bucketsCount)

На производительность алгоритма также влияет число ведер, для большего количества чисел лучше взять большее число ведер (Algorithms in a nutshell by George T. Heineman)

Ссылки

https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/bucketSort

Источники

https://www.youtube.com/watch?v=VuXbEb5ywrU
https://www.youtube.com/watch?v=ELrhrrCjDOA
https://medium.com/karuna-sehgal/an-introduction-to-bucket-sort-62aa5325d124
https://www.geeksforgeeks.org/bucket-sort-2/
https://ru.wikipedia.org/wiki/%D0%91%D0%BB%D0%BE%D1%87%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0
https://www.youtube.com/watch?v=LPrF9yEKTks
https://en.wikipedia.org/wiki/Bucket_sort
https://julialang.org/
https://www.oreilly.com/library/view/algorithms-in-a/9780596516246/ch04s08.html

基数排序

Radix Sort – 基数排序。该算法与计数排序类似,不进行元素比较;而是将元素逐个字符分组到“桶”(桶)中,桶是通过当前数字字符的索引来选择的。时间复杂度– O(nd)。

它的工作原理是这样的:

  • 输入为数字 6、12、44、9
  • 我们将创建 10 个列表桶 (0-9),我们将在其中逐位添加/排序数字。

下一个:

  1. 使用计数器 i 开始循环,直到达到数字中的最大字符数
  2. 通过索引 i 从右到左,我们为每个数字获取一个符号;如果没有符号,则假设它为零
  3. 将符号转换为数字
  4. 按索引 – 数字选择一个存储桶,并将整个数字放在那里
  5. 搜索完数字后,将所有桶转换回数字列表
  6. 获取按排名排序的数字
  7. 重复直到所有数字都消失

Scala 中的基数排序示例:


import scala.util.Random.nextInt



object RadixSort {

    def main(args: Array[String]) = {

        var maxNumber = 200

        var numbersCount = 30

        var maxLength = maxNumber.toString.length() - 1



        var referenceNumbers = LazyList.continually(nextInt(maxNumber + 1)).take(numbersCount).toList

        var numbers = referenceNumbers

        

        var buckets = List.fill(10)(ListBuffer[Int]())



        for( i <- 0 to maxLength) { numbers.foreach( number => {

                    var numberString = number.toString

                    if (numberString.length() > i) {

                        var index = numberString.length() - i - 1

                        var character = numberString.charAt(index).toString

                        var characterInteger = character.toInt  

                        buckets.apply(characterInteger) += number

                    }

                    else {

                        buckets.apply(0) += number

                    }

                }

            )

            numbers = buckets.flatten

            buckets.foreach(x => x.clear())

        }

        println(referenceNumbers)

        println(numbers)

        println(s"Validation result: ${numbers == referenceNumbers.sorted}")

    }

}

该算法还有一个并行执行的版本,例如在 GPU 上;还有一个排序选项,这一定非常有趣并且真正令人惊叹!

链接

https://gitlab .com/demensdeum/algorithms/-/blob/master/sortAlgorithms/radixSort/radixSort.scala

来源

<一href="https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%80%D0%B0%D0%B7%D1%80%D1%8F%D0% B4%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA% D0%B0"目标=“_空白” rel="noopener">https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%80%D0%B0%D0%B7%D1%80%D1%8F%D 0%B4%D0%BD%D0%B0%D1%8F_%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0% BA%D0%B0
https://www.geeksforgeeks.org/radix-sort/

https://www.youtube.com/watch?v=toAlAJKojos

https://github.com/gyatskov/radix-sort

堆排序

堆排序——金字塔排序。算法的时间复杂度– O(n log n),快吧?我将这种排序称为掉落卵石的排序。在我看来,最简单的解释方法就是视觉化。

输入是数字列表,例如:
5、0、7、2、3、9、4

从左到右,创建一个数据结构 – 二叉树,或者我称之为“二叉树”。金字塔。金字塔元素最多可以有两个子元素,但只能有一个顶部元素。

让我们制作一棵二叉树:
⠀⠀5
⠀0⠀7
2 3 9 4

如果你长时间观察金字塔,你会发现这些只是一个数组中的数字,一个接一个地出现,每一层的元素数量都乘以二。

接下来,有趣的事情开始了;让我们使用落下的卵石方法(heapify)对金字塔进行排序。排序可以从最后一层(2 3 9 4)开始,但是没有意义,因为下面没有任何地板可以让您跌倒。

因此,我们开始从倒数第二层(0 7)删除元素
⠀⠀5
⠀0⠀7
2 3 9 4

从右边选择第一个落下的元素,在我们的例子中是7,然后我们看看它下面的是什么,在它下面是9和4,九大于四,九也大于七!我们将 7 放到 9 上,然后将 9 提升到 7 的位置。
⠀⠀5
⠀0⠀9
2 3 7 4

接下来,我们知道七已经无处可去,我们继续看数字 0,它位于左边倒数第二层:
⠀⠀5
0⠀9
2 3 7 4

让我们看看下面是什么– 2和3,二小于三,三大于零,所以我们交换零和三:
⠀⠀5
⠀3⠀9
2 0 7 4

当你到达地板的尽头时,走到上面的地板上,如果可以的话,把所有东西都扔在那里。
结果是一个数据结构——堆,即最大堆,因为顶部是最大的元素:
⠀⠀9
⠀3⠀7
2 0 5 4

如果将其返回为数组表示形式,您将得到一个列表:
[9、3、7、2、0、5、4]

由此我们可以得出结论,通过交换第一个和最后一个元素,我们得到最终排序位置的第一个数字,即 9 应该位于排序列表的末尾,交换位置:
[4、3、7、2、0、5、9]

让我们看一下二叉树:
⠀⠀4
⠀3⠀7
2 0 5 9

结果是树的下部已排序的情况,只需将 4 放到正确的位置,重复算法,但不考虑已经排序的数字,即 9:
⠀⠀4
⠀3⠀7
2 0 5 9

⠀⠀7
⠀3⠀4
2 0 5 9

⠀⠀7
⠀3⠀5
2 0 4 9

事实证明,我们在下降了 4 个数字后,又筹集了继 9 个数字之后的下一个最大数字—— 7. 交换最后一个未排序的数字(4)和最大的数字(7)
⠀⠀4
⠀3⠀5
2 0 7 9

事实证明,现在我们有两个数字处于正确的最终位置:
4、3、5、2、0、79

接下来我们重复排序算法,忽略已经排序的,最后我们得到一个 类型:
⠀⠀0
⠀2⠀3
4 5 7 9

或者作为列表:
0、2、3、4、5、7、9

实施

算法通常分为三个函数:

  1. 创建堆
  2. 筛选算法(heapify)
  3. 替换最后一个未排序元素和第一个元素

堆是通过使用heapify函数从右到左遍历二叉树的倒数第二行,直到数组的末尾来创建的。接下来在循环中,进行第一次数字替换,之后第一个元素落入/保持在原位,因此最大的元素落入第一位,重复该循环,参与者减少一个,因为每次传递后,排序后的数字保留在列表的末尾。

Ruby 中的堆排序示例:






module Colors



    BLUE = "\033[94m"



    RED = "\033[31m"



    STOP = "\033[0m"



end







def heapsort(rawNumbers)



    numbers = rawNumbers.dup







    def swap(numbers, from, to)



        temp = numbers[from]



        numbers[from] = numbers[to]



        numbers[to] = temp



    end







    def heapify(numbers)



        count = numbers.length()



        lastParentNode = (count - 2) / 2







        for start in lastParentNode.downto(0)



            siftDown(numbers, start, count - 1)



            start -= 1 



        end







        if DEMO



            puts "--- heapify ends ---"



        end



    end







    def siftDown(numbers, start, rightBound)      



        cursor = start



        printBinaryHeap(numbers, cursor, rightBound)







        def calculateLhsChildIndex(cursor)



            return cursor * 2 + 1



        end







        def calculateRhsChildIndex(cursor)



            return cursor * 2 + 2



        end            







        while calculateLhsChildIndex(cursor) <= rightBound



            lhsChildIndex = calculateLhsChildIndex(cursor)



            rhsChildIndex = calculateRhsChildIndex(cursor)







            lhsNumber = numbers[lhsChildIndex]



            biggerChildIndex = lhsChildIndex







            if rhsChildIndex <= rightBound



                rhsNumber = numbers[rhsChildIndex]



                if lhsNumber < rhsNumber



                    biggerChildIndex = rhsChildIndex



                end



            end







            if numbers[cursor] < numbers[biggerChildIndex]



                swap(numbers, cursor, biggerChildIndex)



                cursor = biggerChildIndex



            else



                break



            end



            printBinaryHeap(numbers, cursor, rightBound)



        end



        printBinaryHeap(numbers, cursor, rightBound)



    end







    def printBinaryHeap(numbers, nodeIndex = -1, rightBound = -1)



        if DEMO == false



            return



        end



        perLineWidth = (numbers.length() * 4).to_i



        linesCount = Math.log2(numbers.length()).ceil()



        xPrinterCount = 1



        cursor = 0



        spacing = 3



        for y in (0..linesCount)



            line = perLineWidth.times.map { " " }



            spacing = spacing == 3 ? 4 : 3



            printIndex = (perLineWidth / 2) - (spacing * xPrinterCount) / 2



            for x in (0..xPrinterCount - 1)



                if cursor >= numbers.length



                    break



                end



                if nodeIndex != -1 && cursor == nodeIndex



                    line[printIndex] = "%s%s%s" % [Colors::RED, numbers[cursor].to_s, Colors::STOP]



                elsif rightBound != -1 && cursor > rightBound



                    line[printIndex] = "%s%s%s" % [Colors::BLUE, numbers[cursor].to_s, Colors::STOP]



                else



                    line[printIndex] = numbers[cursor].to_s



                end



                cursor += 1



                printIndex += spacing



            end



            print line.join()



            xPrinterCount *= 2           



            print "\n"            



        end



    end







    heapify(numbers)



    rightBound = numbers.length() - 1







    while rightBound > 0



        swap(numbers, 0, rightBound)   



        rightBound -= 1



        siftDown(numbers, 0, rightBound)     



    end







    return numbers



end







numbersCount = 14



maximalNumber = 10



numbers = numbersCount.times.map { Random.rand(maximalNumber) }



print numbers



print "\n---\n"







start = Time.now



sortedNumbers = heapsort(numbers)



finish = Time.now



heapSortTime = start - finish







start = Time.now



referenceSortedNumbers = numbers.sort()



finish = Time.now



referenceSortTime = start - finish







print "Reference sort: "



print referenceSortedNumbers



print "\n"



print "Reference sort time: %f\n" % referenceSortTime



print "Heap sort:      "



print sortedNumbers



print "\n"



if DEMO == false



    print "Heap sort time:      %f\n" % heapSortTime



else



    print "Disable DEMO for performance measure\n"



end







if sortedNumbers != referenceSortedNumbers



    puts "Validation failed"



    exit 1



else



    puts "Validation success"



    exit 0



end



如果没有可视化,这个算法不容易理解,所以我建议的第一件事是编写一个函数来打印二叉树的当前视图。

链接

https://gitlab.com/demensdeum/algorithms/-/blob/master/sortAlgorithms/heapsort/heapsort.rb

来源

http://rosettacode.org/wiki/Sorting_algorithms/Heapsort
https://www.youtube.com/watch?v=LbB357_RwlY

https://habr.com/ru/company/ otus/博客/460087/

https://ru.wikipedia.org/wiki/Pyramid_sort

https://neerc.ifmo.ru/wiki /index.php?title=Heap_sort

https://wiki5.ru/wiki/Heapsort

https://wiki.c2.com/?HeapSort

<一href="https://ru.wikipedia.org/wiki/%D0%94%D0%B5%D1%80%D0%B5%D0%B2%D0%BE_(%D1%81%D1 %82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B %D1%85)" target="_blank" rel="noopener">https://ru.wikipedia.org/wiki/Tree(数据结构)

<一href="https://ru.wikipedia.org/wiki/%D0%9A%D1%83%D1%87%D0%B0_(%D1%81%D1%82%D1 %80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85 )” target="_blank" rel="noopener">https://ru.wikipedia.org/wiki/Heap(数据结构)

https://www.youtube.com/watch?v=2DmK_H7IdTo

https://www.youtube.com/watch?v=kU4KBD4NFtw

https://www.youtube.com/watch?v=DU1uG5310x0

https://www.youtube.com/watch?v =BzQGPA_v-vc

https://www.geeksforgeeks.org/二进制堆的数组表示/

https://habr.com/ru/post/112222/

https://www.cs.usfca。 edu/~galles/visualization/BST.html

https://www.youtube.com/watch?v=EQzqHWtsKq4

<一href="https://medium.com/@dimko1/%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC%D1% 8B-%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8-heapsort-796ba965018b"目标=“_空白” rel="noopener">https://medium.com/@dimko1/%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC% D1 %8B-%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8-heapsort-796ba965018b

https://ru.wikibrief.org/wiki/Heapsort

https://www.youtube.com/watch?v=GUUpmrTnNbw

Bumblebee All Troubles

Recently, it turned out that users of modern Nvidia GPUs under Arch Linux do not need to use the bumblebee package at all, for example, for me it did not detect an external monitor when connected. I recommend removing the bumblebee package and all related packages, and installing prime using the instructions on the Arch Wiki.
Next, to launch all games on Steam and 3D applications, add prime-run, for Steam this is done like this prime-run %command% in additional launch options.
To check the correctness, you can use glxgears, prime-run glxgears.
https://bbs.archlinux.org/viewtopic.php? pid=2048195#p2048195

快速排序

快速排序是一种分而治之的排序算法。递归地,我们逐个解析数字数组,将所选参考元素中的数字按照较小和较大的顺序放置,并将参考元素本身插入到它们之间的截止位置。经过几次递归迭代后,您将得到一个排序列表。时间复杂度 O(n2)。

方案:

  1. 我们首先从外部获取元素列表,即排序边界。第一步,排序边界将从头到尾。
  2. 检查起始边界和结束边界是否相交;如果发生这种情况,则该结束了
  3. 从列表中选择某个元素并将其命名为数据透视
  4. 将其向右移动到最后一个索引处的末尾,这样就不会妨碍
  5. 创建一个*较小数字*仍等于零的计数器
  6. 从左到右循环遍历列表,直到引用元素所在的最后一个索引(包括该索引)
  7. 我们将每个元素与参考元素进行比较
  8. 如果小于参考值,则根据较小数字的计数器的索引进行交换。增加较小数字的计数器。
  9. 当循环到达支持元素时,我们停止并根据较小数字的计数器将支持元素与元素交换。
  10. 我们分别针对列表左侧较小的部分和列表右侧较大的部分分别运行该算法。
  11. 因此,由于检查点 2,所有递归迭代都将开始停止
  12. 获取排序列表

快速排序是由莫斯科国立大学的科学家 Charles Anthony Richard Hoare 发明的,他在学习俄语后,在 Kolmogorov 学院学习了计算机翻译以及概率论。 1960年,因政治危机离开苏联。

Rust 中的示例实现:


use rand::Rng;

fn swap(numbers: &mut [i64], from: usize, to: usize) {
    let temp = numbers[from];
    numbers[from] = numbers[to];
    numbers[to] = temp;
}

fn quicksort(numbers: &mut [i64], left: usize, right: usize) {
    if left >= right {
        return
    }
    let length = right - left;
    if length <= 1 {
        return
    }
    let pivot_index = left + (length / 2);
    let pivot = numbers[pivot_index];

    let last_index = right - 1;
    swap(numbers, pivot_index, last_index);

    let mut less_insert_index = left;

    for i in left..last_index {
        if numbers[i] < pivot {
            swap(numbers, i, less_insert_index);
            less_insert_index += 1;
        }
    }
    swap(numbers, last_index, less_insert_index);
    quicksort(numbers, left, less_insert_index);
    quicksort(numbers, less_insert_index + 1, right);
}

fn main() {
    let mut numbers = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    let mut reference_numbers = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

    let mut rng = rand::thread_rng();
    for i in 0..numbers.len() {
        numbers[i] = rng.gen_range(-10..10);
        reference_numbers[i] = numbers[i];
    }

    reference_numbers.sort();

  println!("Numbers           {:?}", numbers);
  let length = numbers.len();
  quicksort(&mut numbers, 0, length);
  println!("Numbers           {:?}", numbers);
  println!("Reference numbers {:?}", reference_numbers);

  if numbers != reference_numbers {
    println!("Validation failed");
    std::process::exit(1);
  }
  else {
    println!("Validation success!");
    std::process::exit(0);
  }
}

如果不清楚,我建议观看圣地亚哥大学 Rob Edwards 的视频 https://www.youtube.com/watch?v=ZHVk2blR45Q 最简单、一步步展示了算法的本质和实现。

链接

https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/quickSort

来源

https://www.youtube.com/watch?v =4s-aG6yGGLU
https://www.youtube.com/watch?v=ywWBy6J5gz8
https://www.youtube.com/watch?v=Hoixgm4-P4M
https://ru.wikipedia.org/wiki/Быстрая_сортировка
https://www.youtube.com/watch?v=Hoixgm4-P4M
https://www.youtube.com/watch?v=XE4VP_8Y0BU
https://www.youtube.com/watch?v=MZaf_9IZCrc
https://www.youtube.com/watch?v=ZHVk2blR45Q
http://rosettacode.org/wiki/Sorting_algorithms/Quicksort
https://www.youtube.com/watch?v=4s-aG6yGGLU
https://www.youtube.com/watch?v=dQw4w9WgXcQ
https://www.youtube.com/watch?v=maibrCbZWKw
https://www.geeksforgeeks.org/quick-sort/
https://www.youtube.com/watch?v=uXBnyYuwPe8

二元插入排序

二分插入排序是插入排序的一种变体,其中使用二分查找来确定插入位置。该算法的时间复杂度为O(n2)

该算法的工作原理如下:

  1. 循环从零开始到列表末尾
  2. 在循环中选择一个数字进行排序,该数字存储在单独的变量中
  3. 二分查找查找索引以将此数字插入左侧的数字
  4. 一旦找到索引,左侧的数字就会从插入索引开始向右移动一位。在此过程中,需要排序的数字将被删除。
  5. 之前保存的号码将插入到插入索引处
  6. 循环结束时,整个列表将被排序

在二分查找过程中,有可能找不到该数字并且不会返回索引。由于二分查找的特殊性,会找到与要查找的数字最接近的数字,然后要返回索引,需要将其与要查找的数字进行比较,如果要查找的数字较小,则要返回的索引应为左侧的索引,如果大于或等于右侧的索引。

执行代码:


import (
	"fmt"
	"math/rand"
	"time"
)

const numbersCount = 20
const maximalNumber = 100

func binarySearch(numbers []int, item int, low int, high int) int {
	for high > low {
		center := (low + high) / 2
		if numbers[center] < item { low = center + 1 } else if numbers[center] > item {
			high = center - 1
		} else {
			return center
		}
	}

	if numbers[low] < item {
		return low + 1
	} else {
		return low
	}
}

func main() {
	rand.Seed(time.Now().Unix())
	var numbers [numbersCount]int
	for i := 0; i < numbersCount; i++ {
		numbers[i] = rand.Intn(maximalNumber)
	}
	fmt.Println(numbers)

	for i := 1; i < len(numbers); i++ { searchAreaLastIndex := i - 1 insertNumber := numbers[i] insertIndex := binarySearch(numbers[:], insertNumber, 0, searchAreaLastIndex) for x := searchAreaLastIndex; x >= insertIndex; x-- {
			numbers[x+1] = numbers[x]
		}
		numbers[insertIndex] = insertNumber
	}
	fmt.Println(numbers)
}

链接

https://gitlab.com/demensdeum/algorithms/-/blob/master/sortAlgorithms/binaryInsertionSort/binaryInsertionSort.go

来源

https://www.geeksforgeeks.org/binary-insertion-排序/
https://www.youtube.com/watch?v=-OVB5pOZJug

希尔排序

希尔排序 – 插入排序的一种变体,对数字数组进行初步组合。

我们需要记住插入排序是如何工作的:

1.一个循环从零开始到循环结束,因此数组被分成两部分
2. 对于左侧部分,启动第二个循环,从右到左比较元素,删除右侧较小的元素,直到找到左侧较小的元素
3. 在两个循环结束时,我们得到一个排序列表

很久以前,计算机科学家 Donald Schell 想知道如何改进插入排序算法。他还想出了这样的想法:首先分两个周期遍历数组,但在一定距离内,逐渐减少“梳子”,直到变成常规的插入排序算法。一切真的就是这么简单,没有陷阱,在上面的两个循环中我们添加了另一个循环,在这个循环中我们逐渐减小了“梳子”的尺寸。您唯一需要做的就是在比较时检查距离,使其不会超出数组。

一个真正有趣的主题是选择用于更改第一个循环每次迭代的比较长度的序列。有趣的是,算法的性能取决于它。

已知选项和时间复杂度表可以在这里找到:https: //en .wikipedia.org/wiki/Shellsort#Gap_sequences

不同的人参与计算理想距离;显然,这个话题对他们来说很有趣。他们不能只运行 Ruby 并调用最快的 sort() 算法吗?

总的来说,这些奇怪的人写的论文主题是计算 Shell 算法的“梳子”的距离/间隙。我简单地使用了他们的工作结果并检查了 5 种类型的序列:Hibbard、Knuth-Pratt、Chiura、Sedgwick。

import time
import random
from functools import reduce
import math

DEMO_MODE = False

if input("Demo Mode Y/N? ").upper() == "Y":
    DEMO_MODE = True

class Colors:
    BLUE = '\033[94m'
    RED = '\033[31m'
    END = '\033[0m'

def swap(list, lhs, rhs):
    list[lhs], list[rhs] = list[rhs], list[lhs]
    return list

def colorPrintoutStep(numbers: List[int], lhs: int, rhs: int):
    for index, number in enumerate(numbers):
        if index == lhs:
            print(f"{Colors.BLUE}", end = "")
        elif index == rhs:
            print(f"{Colors.RED}", end = "")
        print(f"{number},", end = "")
        if index == lhs or index == rhs:
            print(f"{Colors.END}", end = "")
        if index == lhs or index == rhs:
            print(f"{Colors.END}", end = "")
    print("\n")
    input(">")

def ShellSortLoop(numbers: List[int], distanceSequence: List[int]):
    distanceSequenceIterator = reversed(distanceSequence)
    while distance:= next(distanceSequenceIterator, None):
        for sortArea in range(0, len(numbers)):
            for rhs in reversed(range(distance, sortArea + 1)):
                lhs = rhs - distance
                if DEMO_MODE:
                    print(f"Distance: {distance}")
                    colorPrintoutStep(numbers, lhs, rhs)
                if numbers[lhs] > numbers[rhs]:
                    swap(numbers, lhs, rhs)
                else:
                    break

def ShellSort(numbers: List[int]):
    global ShellSequence
    ShellSortLoop(numbers, ShellSequence)

def HibbardSort(numbers: List[int]):
    global HibbardSequence
    ShellSortLoop(numbers, HibbardSequence)

def ShellPlusKnuttPrattSort(numbers: List[int]):
    global KnuttPrattSequence
    ShellSortLoop(numbers, KnuttPrattSequence)

def ShellPlusCiuraSort(numbers: List[int]):
    global CiuraSequence
    ShellSortLoop(numbers, CiuraSequence)

def ShellPlusSedgewickSort(numbers: List[int]):
    global SedgewickSequence
    ShellSortLoop(numbers, SedgewickSequence)

def insertionSort(numbers: List[int]):
    global insertionSortDistanceSequence
    ShellSortLoop(numbers, insertionSortDistanceSequence)

def defaultSort(numbers: List[int]):
    numbers.sort()

def measureExecution(inputNumbers: List[int], algorithmName: str, algorithm):
    if DEMO_MODE:
        print(f"{algorithmName} started")
    numbers = inputNumbers.copy()
    startTime = time.perf_counter()
    algorithm(numbers)
    endTime = time.perf_counter()
    print(f"{algorithmName} performance: {endTime - startTime}")

def sortedNumbersAsString(inputNumbers: List[int], algorithm) -> str:
    numbers = inputNumbers.copy()
    algorithm(numbers)
    return str(numbers)

if DEMO_MODE:
    maximalNumber = 10
    numbersCount = 10
else:
    maximalNumber = 10
    numbersCount = random.randint(10000, 20000)

randomNumbers = [random.randrange(1, maximalNumber) for i in range(numbersCount)]

ShellSequenceGenerator = lambda n: reduce(lambda x, _: x + [int(x[-1]/2)], range(int(math.log(numbersCount, 2))), [int(numbersCount / 2)])
ShellSequence = ShellSequenceGenerator(randomNumbers)
ShellSequence.reverse()
ShellSequence.pop()

HibbardSequence = [
    0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095,
    8191, 16383, 32767, 65535, 131071, 262143, 524287, 1048575,
    2097151, 4194303, 8388607, 16777215, 33554431, 67108863, 134217727,
    268435455, 536870911, 1073741823, 2147483647, 4294967295, 8589934591
]

KnuttPrattSequence = [
    1, 4, 13, 40, 121, 364, 1093, 3280, 9841, 29524, 88573, 265720, 
    797161, 2391484, 7174453, 21523360, 64570081, 193710244, 581130733, 
    1743392200, 5230176601, 15690529804, 47071589413
]

CiuraSequence = [
            1, 4, 10, 23, 57, 132, 301, 701, 1750, 4376, 
            10941, 27353, 68383, 170958, 427396, 1068491, 
            2671228, 6678071, 16695178, 41737946, 104344866, 
            260862166, 652155416, 1630388541
]

SedgewickSequence = [
            1, 5, 19, 41, 109, 209, 505, 929, 2161, 3905,
            8929, 16001, 36289, 64769, 146305, 260609, 587521, 
            1045505, 2354689, 4188161, 9427969, 16764929, 37730305, 
            67084289, 150958081, 268386305, 603906049, 1073643521, 
            2415771649, 4294770689, 9663381505, 17179475969
]

insertionSortDistanceSequence = [1]

algorithms = {
    "Default Python Sort": defaultSort,
    "Shell Sort": ShellSort,
    "Shell + Hibbard" : HibbardSort,
    "Shell + Prat, Knutt": ShellPlusKnuttPrattSort,
    "Shell + Ciura Sort": ShellPlusCiuraSort,
    "Shell + Sedgewick Sort": ShellPlusSedgewickSort,
    "Insertion Sort": insertionSort
}

for name, algorithm in algorithms.items():
    measureExecution(randomNumbers, name, algorithm)

reference = sortedNumbersAsString(randomNumbers, defaultSort)

for name, algorithm in algorithms.items():
    if sortedNumbersAsString(randomNumbers, algorithm) != reference:
        print("Sorting validation failed")
        exit(1)

print("Sorting validation success")
exit(0)

在我的实现中,对于一组随机数字,最快的差距是 Sedgwick 和 Hibbard。

mypy

我还想提一下 Python 3 的静态类型分析器——我的。有助于解决动态类型语言固有的问题,即它消除了将某些内容粘贴在不需要的地方的可能性。

正如经验丰富的程序员所说,“当你拥有一支专业团队时,就不需要静态类型”,有一天我们都会成为专业人士,我们将编写与机器完全统一和理解的代码,但现在您可以使用类似的实用程序和静态类型语言。

链接

https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/shellSort
http://mypy-lang.org/

来源

https://dl.acm.org/doi/10.1145/368370.368387
https://en.wikipedia.org/wiki/Shellsort
http://rosettacode.org/wiki/Sorting_algorithms/Shell_sort
https://ru.wikipedia.org/wiki/Сортировка_Шелла
https://neerc.ifmo.ru/wiki/index.php?title=Сортировка_Шелла
https://twitter.com/gvanrossum/status/700741601966985216

双选排序

双选择排序 – 选择排序的子类型,看起来它的速度应该是它的两倍。普通算法对数字列表进行双重循环,找到最小数字并与上一层循环指向的当前数字交换位置。双选择排序查找最小和最大数字,然后替换上一层循环所指向的两位数字。左边和右边两个数字。当在列表中间找到需要替换的数字的光标时,整个狂欢就结束了,这样,视觉中心左右就得到了排序后的数字。
该算法的时间复杂度类似于选择排序– O(n2),但据说加速度为 30 %。

边缘状态

已经到了这个阶段,你可以想象一下碰撞的瞬间,比如当左光标的数字(最小的数字)指向列表中的最大的数字,那么最小的数字重新排列,重新排列最大数量的立即崩溃。因此,该算法的所有实现都包含检查此类情况并用正确的索引替换索引。在我的实现中,一次检查就足够了:

  maximalNumberIndex = minimalNumberIndex;
}

Реализация на Cito

Cito – язык либ, язык транслятор. На нем можно писать для C, C++, C#, Java, JavaScript, Python, Swift, TypeScript, OpenCL C, при этом совершенно ничего не зная про эти языки. Исходный код на языке Cito транслируется в исходный код на поддерживаемых языках, далее можно использовать как библиотеку, либо напрямую, исправив сгенеренный код руками. Эдакий Write once – translate to anything.
Double Selection Sort на cito:

{
    public static int[] sort(int[]# numbers, int length)
    {
        int[]# sortedNumbers = new int[length];
        for (int i = 0; i < length; i++) {
            sortedNumbers[i] = numbers[i];
        }
        for (int leftCursor = 0; leftCursor < length / 2; leftCursor++) {
            int minimalNumberIndex = leftCursor;
            int minimalNumber = sortedNumbers[leftCursor];

            int rightCursor = length - (leftCursor + 1);
            int maximalNumberIndex = rightCursor;
            int maximalNumber = sortedNumbers[maximalNumberIndex];

            for (int cursor = leftCursor; cursor <= rightCursor; cursor++) { int cursorNumber = sortedNumbers[cursor]; if (minimalNumber > cursorNumber) {
                    minimalNumber = cursorNumber;
                    minimalNumberIndex = cursor;
                }
                if (maximalNumber < cursorNumber) {
                    maximalNumber = cursorNumber;
                    maximalNumberIndex = cursor;
                }
            }

            if (leftCursor == maximalNumberIndex) {
                maximalNumberIndex = minimalNumberIndex;
            }

            int fromNumber = sortedNumbers[leftCursor];
            int toNumber = sortedNumbers[minimalNumberIndex];
            sortedNumbers[minimalNumberIndex] = fromNumber;
            sortedNumbers[leftCursor] = toNumber;

            fromNumber = sortedNumbers[rightCursor];
            toNumber = sortedNumbers[maximalNumberIndex];
            sortedNumbers[maximalNumberIndex] = fromNumber;
            sortedNumbers[rightCursor] = toNumber;
        }
        return sortedNumbers;
    }
} 

链接

https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/doubleSelectionSort
https://github.com/pfusik/cito

来源

https://www.researchgate.net/publication/330084245_Improved_Double_Selection_Sort_using_Algorithm
http://algolab.valemak.com/selection-double
https://www.geeksforgeeks.org/sorting-algorithm-slightly-improves-selection-sort/

鸡尾酒调酒器分类

鸡尾酒调酒器分类–摇床排序,双向冒泡排序的一种变体。
该算法的工作原理如下:

  1. 选择循环中搜索的初始方向(通常是从左到右)
  2. 接下来在循环中成对检查数字
  3. 如果下一个元素更大,则交换它们
  4. 完成后,搜索过程会以相反的方向重新开始
  5. 重复搜索,直到没有更多的排列

该算法的时间复杂度与bubble类似– O(n2)

PHP 实现示例:

<?php

function cocktailShakeSort($numbers)
{
    echo implode(",", $numbers),"\n";
    $direction = false;
    $sorted = false;
    do {
        $direction = !$direction;        
        $firstIndex = $direction == true ? 0 : count($numbers) - 1;
        $lastIndex = $direction == true ? count($numbers) - 1 : 0;
        
        $sorted = true;
        for (
            $i = $firstIndex;
            $direction == true ? $i < $lastIndex : $i > $lastIndex;
            $direction == true ? $i++ : $i--
        ) {
            $lhsIndex = $direction ? $i : $i - 1;
            $rhsIndex = $direction ? $i + 1 : $i;

            $lhs = $numbers[$lhsIndex];
            $rhs = $numbers[$rhsIndex];

            if ($lhs > $rhs) {
                $numbers[$lhsIndex] = $rhs;
                $numbers[$rhsIndex] = $lhs;
                $sorted = false;
            }
        }
    } while ($sorted == false);

    echo implode(",", $numbers);
}

$numbers = [2, 1, 4, 3, 69, 35, 55, 7, 7, 2, 6, 203, 9];
cocktailShakeSort($numbers);

?>

Ссылки

https://gitlab.com/demensdeum/algorithms/-/blob/master/sortAlgorithms/cocktailShakerSort/cocktailShakerSort.php

Источники

https://www.youtube.com/watch?v=njClLBoEbfI
https://www.geeksforgeeks.org/cocktail-sort/
https://rosettacode.org/wiki/Sorting_algorithms/Cocktail_sort

…And Primus for All

In this note I will describe the launch of Steam games on the Linux distribution Arch Linux in the configuration of an Intel + Nvidia laptop

Counter-Strike: Global Offensive

The only configuration that worked for me is Primus-vk + Vulkan.

Install the required packages:
pacman -S vulkan-intel lib32-vulkan-intel nvidia-utils lib32-nvidia-utils vulkan-icd-loader lib32-vulkan-icd-loader primus_vk

Next, add launch options for Counter-Strike: Global Offensive:
pvkrun %command% -vulkan -console -fullscreen

Should work!

Sid Meier’s Civilization VI

Works in conjunction – Primus + OpenGL + LD_PRELOAD.

Install the Primus package:
pacman -S primus

Next, add launch options for Sid Meier’s Civilization VI:
LD_PRELOAD='/usr/lib/libfreetype.so.6:/usr/lib/libbrotlicommon.so.1:/usr/lib/libbrotlidec.so.1' primusrun %command%

LD_PRELOAD pushes the Freetype compression and font libraries.

Dota 2

Works in conjunction – Primus + OpenGL + removal of locks at startup.

Install the Primus package:
pacman -S primus

Next, add launch options for Dota 2:
primusrun %command% -gl -console

If the game doesn’t start with fcntl(5) for /tmp/source_engine_2808995433.lock failed, then try deleting the /tmp/source_engine_2808995433.lock file
rm /tmp/source_engine_2808995433.lock
Usually the lock file is left over from the last game session unless the game was closed naturally.

How to check?

The easiest way to check the launch of applications on a discrete Nvidia graphics card is through the nvidia-smi utility:

For games on the Source engine, you can check through the game console using the mat_info command:

References

https://wiki.archlinux.org/title/Steam/Game-specific_troubleshooting
https://help.steampowered.com/en/faqs/view/145A-FE54-F37B-278A
https://bbs.archlinux.org/viewtopic.php?id=277708

睡眠排序

睡眠排序–睡眠排序,确定性奇怪排序算法的另一个代表。

它的工作原理如下:

  1. 循环遍历元素列表
  2. 为每个循环启动一个单独的线程
  3. 线程休眠一段时间–元素值和睡眠后的值输出
  4. 循环结束时,等待线程最长的睡眠完成,显示排序后的列表

C 中睡眠排序算法的示例代码:

#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

typedef struct {
    int number;
} ThreadPayload;

void *sortNumber(void *args) {
    ThreadPayload *payload = (ThreadPayload*) args;
    const int number = payload->number;
    free(payload);
    usleep(number * 1000);
    printf("%d ", number);
    return NULL;
}

int main(int argc, char *argv[]) {
    const int numbers[] = {2, 42, 1, 87, 7, 9, 5, 35};
    const int length = sizeof(numbers) / sizeof(int);

    int maximal = 0;
    pthread_t maximalThreadID;

    printf("Sorting: ");
    for (int i = 0; i < length; i++) { pthread_t threadID; int number = numbers[i]; printf("%d ", number); ThreadPayload *payload = malloc(sizeof(ThreadPayload)); payload->number = number;
        pthread_create(&threadID, NULL, sortNumber, (void *) payload);
        if (maximal < number) {
            maximal = number;
            maximalThreadID = threadID;
        }
    }
    printf("\n");
    printf("Sorted: ");
    pthread_join(maximalThreadID, NULL);
    printf("\n");
    return 0;
}

在此实现中,我在微秒上使用了 usleep 函数,其值乘以 1000,即以毫秒为单位。
算法的时间复杂度– O(很长)

链接

https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/sleepSort

来源

https://codoholicconfessions.wordpress。 com/2017/05/21/strangest-sorting-algorithms/
https://twitter.com/javascriptdaily/status/856267407106682880?lang=en
https://stackoverflow.com/questions/6474318/what-is-the-time-complexity-of-the-sleep-sort

斯大林排序

斯大林排序– sort through,有数据丢失的排序算法之一。
该算法非常高效高效,时间复杂度为O(n)。

它的工作原理如下:

  1. 循环数组,将当前元素与下一个元素进行比较
  2. 如果下一个元素小于当前元素,则将其删除
  3. 因此,我们在 O(n) 时间内得到了一个排序数组

算法输出示例:

Gulag: [1, 3, 2, 4, 6, 42, 4, 8, 5, 0, 35, 10]
Element 2 sent to Gulag
Element 4 sent to Gulag
Element 8 sent to Gulag
Element 5 sent to Gulag
Element 0 sent to Gulag
Element 35 sent to Gulag
Element 10 sent to Gulag
Numbers: [1, 3, 4, 6, 42]
Gulag: [2, 4, 8, 5, 0, 35, 10]

Python 3 代码:

gulag = []

print(f"Numbers: {numbers}")
print(f"Gulag: {numbers}")

i = 0
maximal = numbers[0]

while i < len(numbers):
    element = numbers[i]
    if maximal > element:
        print(f"Element {element} sent to Gulag")
        gulag.append(element)
        del numbers[i]
    else:
        maximal = element        
        i += 1

print(f"Numbers: {numbers}")
print(f"Gulag: {gulag}")

缺点包括数据丢失,但如果我们朝着乌托邦式的、理想的、O(n) 的排序列表发展,那还能怎样呢?

链接

https://gitlab.com/demensdeum /algorithms/-/tree/master/sortAlgorithms/stalinSort

来源

https://github.com/gustavo-depaula/stalin-sort
https://www.youtube.com/shorts/juRL-Xn-E00
https://www.youtube.com/watch?v=L78i2YcyYfk

选择排序

选择排序选择排序算法。选择什么?但是最少数量!!!
算法的时间复杂度– O(n2)

该算法的工作原理如下:

  1. 我们从左到右循环遍历数组,记住当前的起始索引和索引处的数字,我们称之为数字 A
  2. 在循环内,我们运行另一个循环从左到右,寻找小于 A 的内容
  3. 当我们找到较小的那个时,我们会记住索引,现在较小的那个就变成了数字A
  4. 当内循环结束时,交换起始索引处的数字和数字A
  5. 完成上层循环后,我们得到一个排序数组

算法执行示例:

(29, 49, 66, 35, 7, 12, 80)
29 > 7
(7, 49, 66, 35, 29, 12, 80)
Round 1 ENDED
Round 2
(7, 49, 66, 35, 29, 12, 80)
49 > 35
35 > 29
29 > 12
(7, 12, 66, 35, 29, 49, 80)
Round 2 ENDED
Round 3
(7, 12, 66, 35, 29, 49, 80)
66 > 35
35 > 29
(7, 12, 29, 35, 66, 49, 80)
Round 3 ENDED
Round 4
(7, 12, 29, 35, 66, 49, 80)
(7, 12, 29, 35, 66, 49, 80)
Round 4 ENDED
Round 5
(7, 12, 29, 35, 66, 49, 80)
66 > 49
(7, 12, 29, 35, 49, 66, 80)
Round 5 ENDED
Round 6
(7, 12, 29, 35, 49, 66, 80)
(7, 12, 29, 35, 49, 66, 80)
Round 6 ENDED
Sorted: (7, 12, 29, 35, 49, 66, 80)

尚未在 Rosetta 代码上找到 Objective-C 实现 ,我自己写道:

#include <Foundation/Foundation.h>

@implementation SelectionSort
- (void)performSort:(NSMutableArray *)numbers
{
   NSLog(@"%@", numbers);   
   for (int startIndex = 0; startIndex < numbers.count-1; startIndex++) {
      int minimalNumberIndex = startIndex;
      for (int i = startIndex + 1; i < numbers.count; i++) {
         id lhs = [numbers objectAtIndex: minimalNumberIndex];
         id rhs = [numbers objectAtIndex: i];
         if ([lhs isGreaterThan: rhs]) {
            minimalNumberIndex = i;
         }
      }
      id temporary = [numbers objectAtIndex: minimalNumberIndex];
      [numbers setObject: [numbers objectAtIndex: startIndex] 
               atIndexedSubscript: minimalNumberIndex];
      [numbers setObject: temporary
               atIndexedSubscript: startIndex];
   }
   NSLog(@"%@", numbers);
}

@end 

Собрать и запустить можно либо на MacOS/Xcode, либо на любой операционной системе поддерживающей GNUstep, например у меня собирается Clang на Arch Linux.
Скрипт сборки:

        main.m \
        -lobjc \
        `gnustep-config --objc-flags` \
        `gnustep-config --objc-libs` \
        -I /usr/include/GNUstepBase \
        -I /usr/lib/gcc/x86_64-pc-linux-gnu/12.1.0/include/ \
        -lgnustep-base \
        -o SelectionSort \

Links

https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/selectionSort

Sources

https://rosettacode.org/wiki/Sorting_algorithms/Selection_sort
https://ru.wikipedia.org/wiki/Сортировка_выбором
https://en.wikipedia.org/wiki/Selection_sort
https://www.youtube.com/watch?v=LJ7GYbX7qpM

计数排序

计数排序–计数排序算法。按照?是的!就这样!

该算法至少涉及两个数组,第一个 –要排序的整数列表,第二个 –大小 = (最大数 – 最小数) + 1 的数组,最初仅包含零。接下来,从第一个数组中取出数字,并使用数字元素获取第二个数组中的索引,该索引加一。遍历整个列表后,我们将得到一个完全填充的第二个数组,其中包含第一个数组中数字的重复次数。 该算法有严重的开销——第二个数组还包含不在第一个列表中的数字的零,即所谓的。内存开销

收到第二个数组后,我们迭代它并按索引写入数字的排序版本,将计数器递减至零。最初,零计数器被忽略。

计数排序算法的非优化操作示例:

  1. 输入数组 1,9,1,4,6,4,4
  2. 那么要计数的数组将为 0,1,2,3,4,5,6,7,8,9(最小数字 0,最大数字 9)
  3. 计数器总数为 0,2,0,0,3,0,1,0,0,1
  4. 排序数组总数 1,1,4,4,4,6,9

Python 3 中的算法代码:


numbers = [42, 89, 69, 777, 22, 35, 42, 69, 42, 90, 777]

minimal = min(numbers)
maximal = max(numbers)
countListRange = maximal - minimal
countListRange += 1
countList = [0] * countListRange

print(numbers)
print(f"Minimal number: {minimal}")
print(f"Maximal number: {maximal}")
print(f"Count list size: {countListRange}")

for number in numbers:
    index = number - minimal
    countList[index] += 1

replacingIndex = 0
for index, count in enumerate(countList):
    for i in range(count):
        outputNumber = minimal + index
        numbers[replacingIndex] = outputNumber
        replacingIndex += 1

print(numbers)

Из-за использования двух массивов, временная сложность алгоритма O(n + k)

Ссылки

https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/countingSort

Источники

https://www.youtube.com/watch?v=6dk_csyWif0
https://www.youtube.com/watch?v=OKd534EWcdk
https://en.wikipedia.org/wiki/Counting_sort
https://rosettacode.org/wiki/Sorting_algorithms/Counting_sort
https://pro-prof.com/forums/topic/%D0%B0%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC-%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8-%D0%BF%D0%BE%D0%B4%D1%81%D1%87%D0%B5%D1%82%D0%BE%D0%BC

博戈排序

伪排序或沼泽排序,最无用的排序算法之一。

它的工作原理如下:
1.输入是数字数组
2. 数字数组被随机打乱(shuffle)
3. 检查数组是否已排序
4. 如果没有排序,则数组再次打乱
5. 重复所有这些操作,直到数组随机排序。

正如你所看到的,这个算法的性能很糟糕,聪明的人相信即使是 O(n * n!) 即有可能你会为了混沌之神的荣耀掷骰子多年,数组永远无法排序,或者也许它会排序?

实施

为了在 TypeScript 中实现它,我需要实现以下功能:
1. 打乱对象数组
2.数组比较
3. 生成一个从零到数字范围内的随机数(原文如此!)
4.打印进度,因为排序似乎永无休止地继续

下面是TypeScript实现代码:

const randomInteger = (maximal: number) => Math.floor(Math.random() * maximal);
const isEqual = (lhs: any[], rhs: any[]) => lhs.every((val, index) => val === rhs[index]);
const shuffle = (array: any[]) => {
    for (var i = 0; i < array.length; i++) { var destination = randomInteger(array.length-1); var temp = array[i]; array[i] = array[destination]; array[destination] = temp; } } let numbers: number[] = Array.from({length: 10}, ()=>randomInteger(10));
const originalNumbers = [...numbers];
const sortedNumbers = [...numbers].sort();

let numberOfRuns = 1;

do {
    if (numberOfRuns % 1000 == 0) {
        printoutProcess(originalNumbers, numbers, numberOfRuns);
    }
    shuffle(numbers);
    numberOfRuns++;
} while (isEqual(numbers, sortedNumbers) == false)

console.log(`Success!`);
console.log(`Run number: ${numberOfRuns}`)
console.log(`Original numbers: ${originalNumbers}`);
console.log(`Current numbers: ${originalNumbers}`);
console.log(`Sorted numbers: ${sortedNumbers}`);

Для отладки можно использовать VSCode и плагин TypeScript Debugger от kakumei.

Как долго

Вывод работы алгоритма:

src/bogosort.ts:1
Still trying to sort: 5,4,8,7,5,0,2,9,7,2, current shuffle 8,7,0,2,4,7,2,5,9,5, try number: 145000
src/bogosort.ts:2
Still trying to sort: 5,4,8,7,5,0,2,9,7,2, current shuffle 7,5,2,4,9,8,0,5,2,7, try number: 146000
src/bogosort.ts:2
Still trying to sort: 5,4,8,7,5,0,2,9,7,2, current shuffle 0,2,7,4,9,5,7,5,8,2, try number: 147000
src/bogosort.ts:2
Still trying to sort: 5,4,8,7,5,0,2,9,7,2, current shuffle 5,9,7,8,5,4,2,7,0,2, try number: 148000
src/bogosort.ts:2
Success!
src/bogosort.ts:24
Run number: 148798
src/bogosort.ts:25
Original numbers: 5,4,8,7,5,0,2,9,7,2
src/bogosort.ts:26
Current numbers: 5,4,8,7,5,0,2,9,7,2
src/bogosort.ts:27
Sorted numbers: 0,2,2,4,5,5,7,7,8,9

Для массива из 10 чисел Богосорт перемешивал исходный массив 148798 раз, многовато да?
Алгоритм можно использовать как учебный, для понимания возможностей языка с которым предстоит работать на рынке. Лично я был удивлен узнав что в ванильных JS и TS до сих пор нет своего алгоритма перемешивания массивов, генерации целого числа в диапазоне, доступа к хэшам объектов для быстрого сравнения.

Ссылки

https://gitlab.com/demensdeum/algorithms/-/tree/master/sortAlgorithms/bogosort
https://www.typescriptlang.org/
https://marketplace.visualstudio.com/items?itemName=kakumei.ts-debug

Источники

https://www.youtube.com/watch?v=r2N3scbd_jg
https://en.wikipedia.org/wiki/Bogosort

模式解释器

包含什么

解释器模式是指行为设计模式。此模式允许您通过使用 AST 树来实现您自己的编程语言,该树的顶点是实现 Interpret 方法的终端和非终端表达式,该方法提供了该语言的功能。

  • 终端表达式 – 例如,字符串常量 – “你好世界”
  • 非终端表达式 – 例如 Print(“Hello World”),包含 Print 和来自终端表达式“Hello World”的参数

有什么区别?不同之处在于,解释在终端表达式上结束,但对于非终端表达式,它会在所有传入的顶点/参数上继续深入。如果 AST 树仅由非终结符表达式组成,那么应用程序将永远无法完成,因为任何过程都需要一定的有限性,这种有限性就是终端表达式,它们通常包含数据,例如字符串。

AST 树的示例如下:


Dcoetzee,CC0,来自维基共享资源

如您所见,终端表达式是常量和变量,其余的是非终端表达式。

不包括什么

解释器实现不包括解析输入到 AST 树中的语言字符串。实现终结符和非终结符表达式的类、在输入处使用 Context 参数的 Interpret 方法、创建表达式的 AST 树并在根表达式处运行 Interpret 方法就足够了。上下文可用于在运行时存储应用程序状态。

实施

该模式涉及:

  • Client – 返回 AST 树并为根节点(Client)运行 Interpret(context)
  • 上下文 – 包含应用程序的状态,在解释时传递给表达式(上下文)
  • 抽象表达式 – 包含 Interpret(context) (Expression) 方法的抽象类
  • 终端表达式是最终表达式,是抽象表达式 (TerminalExpression) 的后代
  • 非终结表达式不是有限表达式;它包含指向 AST 树深处顶点的指针;下级顶点通常会影响非终结表达式 (NonTerminalExpression) 的解释结果

C# 中的客户端示例

        static void Main(string[] args)
        {
            var context = new Context();
            var initialProgram = new PerformExpression(
                new IExpression[] {
                    new SetExpression("alpha", "1"),
                    new GetExpression("alpha"),
                    new PrintExpression(
                        new IExpression[] {
                            new ConstantExpression("Hello Interpreter Pattern")
                        }
                    )
                }
            );
            System.Console.WriteLine(initialProgram.interpret(context));
        }
}

C# 中的抽象表达式示例

{
    String interpret(Context context);
}

C# 中的终端表达式示例(字符串常量)

{
    private String constant;

    public ConstantExpression(String constant) {
        this.constant = constant;
    }

    override public String interpret(Context context) {
        return constant;
    }
}

C# 中非终结符表达式的示例(使用分隔符“;”开始并连接从属顶点的结果

{
    public PerformExpression(IExpression[] leafs) : base(leafs) {
        this.leafs = leafs;
    }
    
    override public String interpret(Context context) {
        var output = "";
        foreach (var leaf in leafs) {
            output += leaf.interpret(context) + ";";
        }
        return output;
    }
}

你能从功能上做到这一点吗?

众所周知,所有图灵完备的语言都是等价的。是否可以将面向对象模式转移到函数式编程语言?

作为实验,我们采用一种名为 Elm 的 FP 网络语言。 Elm中没有类,但是有Records和Types,因此实现中涉及到以下记录和类型:

  • 表达式 – 列出所有可能的语言表达式(Expression)
  • 从属表达式 – 从属于非终结符表达式 (ExpressionLeaf) 的表达式
  • Context – 存储应用程序状态的记录(Context)
  • 实现 Interpret(context) 方法的函数 – 实现终端和非终端表达式功能的所有必要函数
  • 解释器状态的辅助记录 – 解释器正确操作所必需的,它们存储解释器状态、上下文

在 Elm 中实现对整个可能表达式集的解释的函数示例:

  case input.expression of
    Constant text ->
      { 
        output = text, 
        context = input.context 
      }
    Perform leafs ->
      let inputs = List.map (\leaf -> { expressionLeaf = leaf, context = input.context } ) leafs in
        let startLeaf = { expressionLeaf = (Node (Constant "")), context = { variables = Dict.empty } } in
          let outputExpressionInput = List.foldl mergeContextsAndRunLeafs startLeaf inputs in
            {
              output = (runExpressionLeaf outputExpressionInput).output,
              context = input.context
            }
    Print printExpression ->
      run 
      { 
        expression = printExpression, 
        context = input.context 
      }
    Set key value ->
      let variables = Dict.insert key value input.context.variables in
      {
        output = "OK",
        context = { variables = variables }
      }
    Get key ->
      {
        output = Maybe.withDefault ("No value for key: " ++ key) (Dict.get key input.context.variables),
        context = input.context
      }

解析怎么样?

解释器模式中不包含将源代码解析为 AST 树;有多种解析源代码的方法,稍后会详细介绍。
在Elm解释器的实现中,我在AST树中编写了一个简单的解析器,由两个函数组成——解析顶点,解析从属顶点。

parseLeafs state =
    let tokensQueue = state.tokensQueue in
        let popped = pop state.tokensQueue in
            let tokensQueueTail = tail state.tokensQueue in
                if popped == "Nothing" then
                    state
                else if popped == "Perform(" then
                    {
                        tokensQueue = tokensQueue,
                        result = (state.result ++ [Node (parse tokensQueue)])
                    }
                else if popped == ")" then
                    parseLeafs {
                        tokensQueue = tokensQueueTail,
                        result = state.result
                    }
                else if popped == "Set" then
                    let key = pop tokensQueueTail in
                        let value = pop (tail tokensQueueTail) in
                            parseLeafs {
                                tokensQueue = tail (tail tokensQueueTail),
                                result = (state.result ++ [Node (Set key value)])
                            }
                else if popped == "Get" then
                    let key = pop tokensQueueTail in
                        parseLeafs {
                            tokensQueue = tail tokensQueueTail,
                            result = (state.result ++ [Node (Get key)])
                        }
                else 
                    parseLeafs {
                        tokensQueue = tokensQueueTail,
                        result = (state.result ++ [Node (Constant popped)])
                    }

parse tokensQueue =
    let popped = pop tokensQueue in
        let tokensQueueTail = tail tokensQueue in
            if popped == "Perform(" then
                Perform (
                    parseLeafs {
                        tokensQueue = tokensQueueTail, 
                        result = []
                    }
                ).result
            else if popped == "Set" then
                let key = pop tokensQueueTail in
                    let value = pop (tail tokensQueueTail) in
                        Set key value
            else if popped == "Print" then
                Print (parse tokensQueueTail)
            else
                Constant popped

链接

https://gitlab.com/demensdeum /patterns/-/tree/master/interpreter/elm
https://gitlab.com/demensdeum/patterns/-/tree/master/interpreter/csharp

来源

https://en.wikipedia.org/wiki/Interpreter_pattern
https://elm-lang.org/
https://docs.microsoft.com/en-us/dotnet/csharp/

RGB图像转灰度

在这篇文章中,我将描述将 RGB 缓冲区转换为灰度(灰度)的算法。
这样做非常简单,缓冲区的每个像素颜色通道都根据一定的公式进行转换,输出是灰度图像。
平均法:

red = average;
green = average;
blue = average;

Складываем 3 цветовых канала и делим на 3.

Однако существует еще один метод – метод средневзвешенный, он учитывает цветовосприятие человека:

red = luminance;
green = luminance;
blue = luminance;

Какой метод лучше использовать? Да какой вам больше подходит для конкретной задачи. Далее сравнение методов с помощью тестовой цветовой сетки:

Пример реализации на JavaScript + HTML 5

    image,
    canvas,
    weightedAverage
) {
    const context = canvas.getContext('2d');

    const imageWeight = image.width;
    const imageHeight = image.height;

    canvas.width = imageWeight;
    canvas.height = imageHeight;

    context.drawImage(image, 0, 0);

    let pixels = context
        .getImageData(
            0,
            0,
            imageWeight,
            imageHeight
        );

    for (let y = 0; y & lt; pixels.height; y++) {
        for (let x = 0; x & lt; pixels.width; x++) {
            const i = (y * 4) * pixels.width + x * 4;

            let red = pixels.data[i];
            let green = pixels.data[i + 1];
            let blue = pixels.data[i + 2]

            const average = (red + green + blue) / 3;
            const luminance = 0.2126 * red +
                0.7152 * green +
                0.0722 * blue;

            red = weightedAverage ? luminance : average;
            green = weightedAverage ? luminance : average;
            blue = weightedAverage ? luminance : average;

            pixels.data[i] = red;
            pixels.data[i + 1] = green;
            pixels.data[i + 2] = blue;
        }
    }
    context
        .putImageData(
            pixels,
            0,
            0,
            0,
            0,
            pixels.width,
            pixels.height
        );
}

Источники

https://www.baeldung.com/cs/convert-rgb-to-grayscale
https://twitter.com/mudasobwa/status/1528046455587495940
https://rosettacode.org/wiki/Grayscale_image

Ссылки

http://papugi.demensdeum.repl.co/

Благодарности

Спасибо Aleksei Matiushkin (https://twitter.com/mudasobwa) за наводку на Rosetta Code

图灵炸弹

1936 年,科学家艾伦·图灵在他的出版物《论可计算数,及其在解决问题中的应用》中描述了通用计算机的使用,它可以解决数学中的可解性问题。结果,他得出的结论是,如果这种机器的工作结果被颠倒并循环回自身,那么它就无法正确解决任何问题。事实证明,不可能创建一个“理想的”防病毒软件、一个“理想的”磁贴设置器、一个为您的崩溃建议理想短语的程序等等。悖论!

然而,这种通用计算机可以用来实现任何算法,英国情报部门利用了这一点,雇佣了图灵并允许创建“Bombe”机器来破译第二次世界大战期间的德国信息。

以下是基于原始文档,使用 Dart 语言对单磁带计算机进行 OOP 建模。

图灵机由分成多个部分的薄膜组成,每个部分包含一个符号,这些符号可以读取或写入。电影类示例:

final _map = Map<int, String>(); 

  String read({required int at}) { 
    return _map[at] ?? ""; 
  } 

  void write({required String symbol, required int at}) { 
    _map[at] = symbol; 
  } 
}

还有一个“扫描方块”,它可以在胶片上移动,读取或写入信息,用现代语言来说就是——磁头。磁头类示例:

  int _index = 0; 
  InfiniteTape _infiniteTape; 
  TapeHead(this._infiniteTape) {} 

  String next() { 
    _index += 1; 
    move(to: _index); 
    final output = read(); 
    return output; 
  } 

  String previous() { 
    _index -= 1; 
    move(to: _index); 
    final output = read(); 
    return output; 
  } 

  void move({required int to}) { 
    this._index = to; 
  } 

  String read() { 
    return _infiniteTape.read(at: this._index); 
  } 

  void write(String symbol) { 
    _infiniteTape.write(symbol: symbol, at: this._index); 
  } 

  int index() { 
    return _index; 
  } 
} 

机器包含“m-configurations”,通过它可以决定下一步做什么。用现代语言来说——状态和状态处理程序。状态处理程序示例:

  FiniteStateControlDelegate? delegate = null; 

  void handle({required String symbol}) { 
    if (symbol == OPCODE_PRINT) { 
      final argument = delegate?.nextSymbol(); 
      print(argument);
    } 
    else if (symbol == OPCODE_GENERATE_RANDOM_NUMBER_FROM_ZERO_TO_AND_WRITE_AFTER) { 
      final to = int.tryParse(delegate!.nextSymbol())!; 
      final value = new Random().nextInt(to); 
      delegate!.nextSymbol(); 
      delegate!.write(value.toString()); 
    } 
    else if (symbol == OPCODE_INPUT_TO_NEXT) { 
      final input = stdin.readLineSync()!; 
      delegate?.nextSymbol(); 
      delegate?.write(input); 
    } 
    else if (symbol == OPCODE_COPY_FROM_TO) { 
      final currentIndex = delegate!.index(); 

и т.д. 

之后,您需要创建“配置”,用现代语言来说,这些是操作代码(操作码)及其处理程序。操作码示例:

const OPCODE_PRINT = "print"; 
const OPCODE_INCREMENT_NEXT = "increment next"; 
const OPCODE_DECREMENT_NEXT = "decrement next"; 
const OPCODE_IF_PREVIOUS_NOT_EQUAL = "if previous not equal"; 
const OPCODE_MOVE_TO_INDEX = "move to index"; 
const OPCODE_COPY_FROM_TO = "copy from index to index"; 
const OPCODE_INPUT_TO_NEXT = "input to next"; 
const OPCODE_GENERATE_RANDOM_NUMBER_FROM_ZERO_TO_AND_WRITE_AFTER = "generate random number from zero to next and write after"; 

不要忘记创建操作码和停止处理程序,否则您将无法证明或无法证明(原文如此!)解决问题。

现在,使用“中介者”模式,我们连接图灵机类中的所有类,创建该类的实例,通过磁带录音机录制程序,加载磁带就可以使用它了!

对我个人来说,什么是主要的问题仍然很有趣——“什么是主要的”?创建通用计算器或“Entscheidungsproblem”的证明,作为副产品,计算器出现了。

盒式磁带

为了好玩,我为我的机器版本录制了几个盒式磁带节目。

你好世界

hello world 
stop

Считаем до 16-ти

0
if previous not equal
16
copy from index to index
1
8
print
?
move to index
0
else
copy from index to index
1
16
print
?
print
Finished!
stop

Самой интересной задачей было написание Quine программы, которая печатает свой исходный код, для одноленточной машины. Первые 8 часов мне казалось что эта задача не решаема с таким малым количеством опкодов, однако всего через 16 часов оказалось что я был не прав.

Реализация и примеры кассет, источники ниже.

Ссылки

https://gitlab.com/demensdeum/turing-machine

Источники

https://www.astro.puc.cl/~rparra/tools/PAPERS/turing_1936.pdf
https://kpolyakov.spb.ru/prog/turing.htm
https://www.youtube.com/watch?v=dNRDvLACg5Q
https://www.youtube.com/watch?v=jP3ceURvIYc
https://www.youtube.com/watch?v=9QCJj5QzETI
https://www.youtube.com/watch?v=HeQX2HjkcNo&t=0s

为 Sega Genesis #5 编写汇编

在这篇文章中,我将描述读取操纵杆、更改精灵位置、水平翻转、Sega Genesis 模拟器以及可能的控制台本身的过程。

读取点击并处理将棋操纵杆的“事件”按照以下方案进行:

  1. 请求按下按钮的位组合
  2. 读取按下的按钮的位
  3. 游戏逻辑级别的处理

要移动骨架精灵,我们需要存储当前位置的变量。

内存

游戏逻辑变量存储在RAM中;到目前为止人们还没有想出更好的办法。让我们声明变量地址并更改渲染代码:

skeletonYpos = $FF0002 
frameCounter = $FF0004 
skeletonHorizontalFlip = $FF0006

    move.w #$0100,skeletonXpos 
    move.w #$0100,skeletonYpos 
    move.w #$0001,skeletonHorizontalFlip 

FillSpriteTable: 
    move.l #$70000003,vdp_control_port 
    move.w skeletonYpos,vdp_data_port  
    move.w #$0F00,vdp_data_port 
    move.w skeletonHorizontalFlip,vdp_data_port 
    move.w skeletonXpos,vdp_data_port 

如您所见,可用于工作的地址从 0xFF0000 开始,到 0xFFFFFF 结束,总共有 64 KB 的内存可供我们使用。骨架位置在 sculptureXpos、sculptureYpos 处声明,水平翻转在 sculptureHorizo​​ntalFlip 处声明。

手柄

与 VDP 类比,游戏手柄的工作分别通过两个端口进行:控制端口和数据端口,第一个为0xA10009和0xA10003共号。使用游戏手柄时有一个有趣的功能——首先,您需要请求用于轮询的按钮组合,然后在等待总线上的更新后,读取所需的按键。对于 C/B 和 D-pad 按钮,这是 0x40,示例如下:

  move.b #$40,joypad_one_control_port; C/B/Dpad 
  nop ; bus sync 
  nop ; bus sync 
  move.b joypad_one_data_port,d2 
  rts 

在寄存器 d2 中,按钮按下或未按下的状态将保留,一般来说,通过日期端口请求的内容将保留。之后,转到您最喜欢的模拟器的 Motorola 68000 寄存器查看器,根据击键查看 d2 寄存器等于什么。您可以通过聪明的方式在手册中找到答案,但我们并不相信他们的话。进一步处理 d2

寄存器中按下的按钮

    cmp #$FFFFFF7B,d2; handle left 
    beq MoveLeft  
    cmp #$FFFFFF77,d2; handle right  
    beq MoveRight  
    cmp #$FFFFFF7E,d2; handle up  
    beq MoveUp  
    cmp #$FFFFFF7D,d2; handle down  
    beq MoveDown  
    rts

Проверять нужно конечно отдельные биты, а не целыми словами, но пока и так сойдет. Теперь осталось самое простое – написать обработчики всех событий перемещения по 4-м направлениям. Для этого меняем переменные в RAM, и запускаем процедуру перерисовки.

Пример для перемещения влево + изменение горизонтального флипа:

    move.w skeletonXpos,d0 
    sub.w #1,d0 
    move.w d0,skeletonXpos 
    move.w #$0801,skeletonHorizontalFlip 
    jmp FillSpriteTable

После добавления всех обработчиков и сборки, вы увидите как скелет перемещается и поворачивается по экрану, но слишком быстро, быстрее самого ежа Соника.

Не так быстро!

Чтобы замедлить скорость игрового цикла, существуют несколько техник, я выбрал самую простую и не затрагивающую работу с внешними портами – подсчет цифры через регистр пока она не станет равна нулю.

Пример замедляющего цикла и игрового цикла:

  move.w #512,frameCounter 
WaitFrame: 
  move.w frameCounter,d0 
  sub.w #1,d0 
  move.w d0,frameCounter 
  dbra d0,WaitFrame 
GameLoop: 
  jsr ReadJoypad 
  jsr HandleJoypad 
  jmp GameLoop 

此后,骨架运行速度变慢,这正是所需要的。据我所知,“放慢速度”的最常见选项是计算垂直同步标志;您可以计算屏幕被绘制的次数,从而与特定的 fps 相关联。

链接

https://gitlab .com/demensdeum/segagenesisamples/-/blob/main/8Joypad/vasm/main.asm

来源

https://www.chibiakumas.com/68000/platform2.php
https://huguesjohnson.com/programming/genesis/tiles-sprites/