Seth Kenlon 发布的文章

无论你是手动安装还是通过包管理器安装,插件都可以帮助你在工作流中打造一个完美的 Vim 。

虽然 Vim 是快速且高效的,但在默认情况下,它仅仅只是一个文本编辑器。至少,这就是没有插件的情况 Vim 应当具备的样子,插件构建在 Vim 之上,并添加额外的功能,使 Vim 不仅仅是一个输入文本的窗口。有了合适的插件组合,你可以控制你的生活,形成你自己独特的 Vim 体验。你可以自定义你的主题,你可以添加语法高亮,代码 linting,版本跟踪器等等。

怎么安装 Vim 插件

Vim 可以通过插件进行扩展,但很长一段时间以来,并没有官方的安装方式去安装这些插件。从 Vim 8 开始,有一个关于插件如何安装和加载的结构。你可能会在网上或项目自述文件中遇到旧的说明,但只要你运行 Vim 8 或更高版本,你应该根据 Vim 的官方插件安装方法安装或使用 Vim 包管理器。你可以使用包管理器,无论你运行的是什么版本(包括比 8.x 更老的版本),这使得安装过程比你自己维护更新更容易。

手动和自动安装方法都值得了解,所以请继续阅读以了解这两种方法。

手动安装插件(Vim 8 及以上版本)

所谓的 “Vim 包”是一个包含一个或多个插件的目录。默认情况下,你的 Vim 设置包含在 ~/.vim 中,这是 Vim 在启动时寻找插件的地方。(下面的示例使用了通用名称 vendor 来表示插件是从其它地方获得的。)

当你启动 Vim 时,它首先处理你的 .vimrc文件,然后扫描 ~/.vim 中的所有目录,查找包含在 pack/*/start 中的插件。

默认情况下,你的 ~/.vim 目录(如果你有的话)中没有这样的文件结构,所以设置为:

$ mkdir -p ~/.vim/pack/vendor/start

现在,你可以将 Vim 插件放在 ~/.vim/pack/vendor/start 中,它们会在你启动 Vim 时自动加载。

例如,尝试安装一下 NERDTree,这是一个基于文本的 Vim 文件管理器。首先,使用 Git 克隆 NERDTree 存储库的快照:

$ git clone --depth 1 \
  https://github.com/preservim/nerdtree.git \
  ~/.vim/pack/vendor/start/nerdtree

启动 Vim 或者 gvim,然后键入如下命令:

:NERDTree

Vim 窗口左侧将打开一个文件树。

 title=

如果你不想让一个插件每次启动 Vim 时都自动加载,你可以在 ~/.vim/pack/vendor 中创建 opt 文件夹:

$ mkdir ~/.vim/pack/vendor/opt

任何安装到 opt 的插件都可被 Vim 使用,但是只有当你使用 packadd 命令将它们添加到一个会话中时,它们才会被加载到内存中。例如,一个虚构的叫 foo 的插件:

:packadd foo

Vim 官方建议每个插件项目在 ~/.Vim/pack 中创建自己的目录。例如,如果你要安装 NERDTree 插件和假想的 foo 插件,你需要创建这样的目录结构:

$ mkdir -p ~/.vim/pack/NERDTree/start/
$ git clone --depth 1 \
  https://github.com/preservim/nerdtree.git \
  ~/.vim/pack/NERDTree/start/NERDTree
$ mkdir -p ~/.vim/pack/foo/start/
$ git clone --depth 1 \
  https://notabug.org/foo/foo.git \
  ~/.vim/pack/foo/start/foo

这样做是否方便取决于你。

使用 Vim 包管理器(任何 Vim 版本)

自从 Vim 8 以后,包管理器变得不那么有用了,但是一些用户仍然喜欢它们,因为它们能够自动更新一些插件。有几个包管理器可供选择,并且它们各不相同,但是 vim-plug 有一些很棒的特性和最好的文档,这使我们很容易开始并在以后深入研究。

使用 vim-plug 安装插件

安装 vim-plug,以便它在启动时自动加载:

$ curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
  https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

创建一个 ~/.vimrc 文件(如果你还没有这个文件),然后输入以下文本:

call plug#begin()
Plug 'preservim/NERDTree'
call plug#end()

每次要安装插件时,都必须在 plug#begin()plug#end() 之间输入插件的名称和位置(上面以 NERDTree 文件管理器为例)。如果你所需的插件未托管在 GitHub 上,你可以提供完整的 URL,而不仅仅是 GitHub 的用户名和项目 ID。你甚至可以在 ~/.vim 目录之外“安装”本地插件。

最后,启动 Vim 并提示 vim-plug 安装 ~/.vimrc 中列出的插件:

:PlugInstall

等待插件下载。

通过 vim-plug 更新插件

与手动安装过程相比,编辑 ~/.vimrc 并使用命令来进行安装可能看起来并没有多省事,但是 vim-plug 的真正优势在更新。更新所有安装的插件,使用这个 Vim 命令:

:PlugUpdate

如果你不想更新所有的插件,你可以通过添加插件的名字来更新任何插件:

:PlugUpdate NERDTree

恢复插件

vim-plug 的另一个优点是它的导出和恢复功能。Vim 用户都知道,正是插件的缘故,通常每个用户使用 Vim 的工作方式都是独一无二的。一旦你安装和配置了正确的插件组合,你最不想要的局面就是再也找不到它们。

Vim-plug 有这个命令来生成一个脚本来恢复所有当前的插件:

:PlugSnapshot ~/vim-plug.list

vim-plug 还有许多其他的功能,所以请参考它的项目页面以获得完整的文档。

打造一个完美的 Vim

当你整天都在做一个项目时,你希望每一个小细节都能为你提供最好的服务。了解 Vim 和它的许多插件,直到你为你所做的事情构建出一个完美的应用程序。

有喜欢的 Vim 插件吗?请在评论中告诉我们吧!


via: https://opensource.com/article/20/2/how-install-vim-plugins

作者:Seth Kenlon 选题:lujun9972 译者:qianmingtian 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

无需适应其他人的眼光,而是使用 YaCY 搜索引擎定义你想要的互联网。

在我以前介绍 YaCy 入门的文章中讲述过 YaCy 这个 对等 peer-to-peer 式的搜索引擎是如何安装和使用的。YaCy 最令人兴奋的一点就是它事实上是一个本地客户端,全球范围内的每一个 YaCy 用户都是构成整个这个分布式搜索引擎架构的一个节点,这意味着每个用户都可以掌控自己的互联网搜索体验。

Google 曾经提供过 google.com/linux 这样的主题简便方式以便快速筛选出和 Linux 相关的搜索内容,这个小功能受到了很多人的青睐,但 Google 最终还是在 2011 年的时候把它下线了。

而 YaCy 则让自定义搜索引擎变得可能。

自定义 YaCy

YaCy 安装好之后,只需要访问 localhost:8090 就可以使用了。要自定义搜索引擎,只需要点击右上角的“ 管理 Administration ”按钮(它可能隐藏在小屏幕的菜单图标中)。

你可以在管理面板中配置 YaCy 对系统资源的使用策略,以及如何跟其它的 YaCy 客户端进行交互。

 title=

例如,点击侧栏中的“ 第一步 First steps ”按钮可以配置备用端口,以及设置 YaCy 对内存和硬盘的使用量;而“ 监控 Monitoring ”面板则可以监控 YaCy 的运行状况。大多数功能都只需要在面板上点击几下就可以完成了,例如以下几个常用的功能。

内网搜索应用

目前市面上也有不少公司推出了内网搜索应用,而 YaCy 可以免费为你提供一个。对于能够通过 HTTP、FTP、Samba 等协议访问的文件,YaCy 都可以进行索引,因此无论是作为私人的文件搜索还是企业内部的本地共享文件搜索,YaCy 都可以实现。它可以让内部网络中的用户使用你个人的 YaCy 实例来查找共享文件,于此同时保持对内部网络以外的用户不可见。

网络配置

YaCy 在默认情况下就支持隐私和隔离。点击“ 用例与账号 Use Case & Account ”页面顶部的“ 网络配置 Network Configuration ”链接,即可进入网络配置面板设置对等网络。

 title=

爬取站点

YaCy 的分布式运作方式决定了它对页面的爬取是由用户驱动的。并没有一个大型公司对整个互联网上的所有可访问页面都进行搜索,对于 YaCy 来说也是这样,一个站点只有在被用户指定爬取的前提下,才会被 YaCy 爬取并进入索引。

YaCy 客户端提供了两种爬取页面的方式:你可以手动爬取,并让 YaCy 根据建议去爬取。

 title=

手动爬取

手动爬取是指由用户输入指定的网站 URL 并启动 YaCy 的爬虫任务。只需要点击“ 高级爬虫 Advanced Crawler ”并输入计划爬取的若干 URL,然后选择页面底部的“ 进行远程索引 Do Remote indexing ”选项,这个选项会让客户端向互联网广播它要索引的 URL,可选地接受这些请求的客户端可以帮助你爬取这些 URL。

点击页面底部的“ 开始新爬虫任务 Start New Crawl Job ”按钮就可以开始进行爬取了,我就是这样对一些常用和有用站点进行爬取和索引的。

爬虫任务启动之后,YaCy 会将这些 URL 对应的页面在本地生成和存储索引。在高级模式下,也就是本地计算机允许 8090 端口流量进出时,全网的 YaCy 用户都可以使用到这一份索引。

加入爬虫网络

尽管一些非常敬业的 YaCy 高级用户已经强迫症般地在互联网上爬取了很多页面,但对于全网浩如烟海的页面而言也只是沧海一粟。单个用户所拥有的资源远不及很多大公司的网络爬虫,但大量 YaCy 用户如果联合起来成为一个社区,能产生的力量就大得多了。只要开启了 YaCy 的爬虫请求广播功能,就可以让其它客户端参与进来爬取更多页面。

只需要在“ 高级爬虫 Advanced Crawler ”面板中点击页面顶部的“ 远程爬取 Remote Crawling ”,勾选“ 加载 Load ”旁边的复选框,就可以让你的客户端接受其它人发来的爬虫任务请求了。

 title=

YaCy 监控相关

YaCy 除了作为一个非常强大的搜索引擎,还提供了很丰富的主题和用户体验。你可以在“ 监控 Monitor ”面板中监控 YaCy 客户端的网络运行状况,甚至还可以了解到有多少人从 YaCy 社区中获取到了自己所需要的东西。

 title=

搜索引擎发挥了作用

你使用 YaCy 的时间越长,就越会思考搜索引擎如何改变自己的视野,因为你对互联网的体验很大一部分来自于你在搜索引擎中一次次简单查询的结果。实际上,当你和不同行业的人交流时,可能会注意到每个人对“互联网”的理解都有所不同。有些人会认为,互联网的搜索引擎中充斥着各种广告和推广,同时也仅仅能从搜索结果中获取到有限的信息。例如,假设有人不断搜索关于关键词 X 的内容,那么大部分商业搜索引擎都会在搜索结果中提高关键词 X 的权重,但与此同时,另一个关键词 Y 的权重则会相对降低,从而让关键词 Y 被淹没在搜索结果当中,即使这样对完成特定任务更好。

就像在现实生活中一样,走出虚拟的世界视野会让你看到一个更广阔的世界。尝试使用 YaCy,看看你发现了什么。


via: https://opensource.com/article/20/2/yacy-search-engine-hacks

作者:Seth Kenlon 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

上手开源的对等 Web 索引器 YaCy。

很久以前,互联网很小,小到几个人就可以索引它们,这些人收集了所有网站的名称和链接,并按主题将它们分别列在页面或印刷书籍中。随着万维网网络的发展,形成了“网站环”形式,具有类似的内容、主题或敏感性的站点捆绑在一起,形成了通往每个成员的循环路径。环中任何站点的访问者都可以单击按钮以转到环中的下一个或上一个站点,以发现与其兴趣相关的新站点。

又过了一段时间,互联网似乎变得臃肿不堪了。每个人都在网络上,有很多冗余信息和垃圾邮件,多到让你无法找到任何东西。Yahoo 和 AOL、CompuServe 以及类似的服务各自采用了不同的方法来解决这个问题,但是直到谷歌出现后,现代的搜索模型才得以普及。按谷歌的做法,互联网应该通过搜索引擎进行索引、排序和排名。

为什么选择开源替代品?

像谷歌和 DuckDuckGo 这样的搜索引擎显然是卓有成效的。你可能是通过搜索引擎访问的本站。尽管对于因主机没有选择遵循优化搜索引擎的最佳实践从而导致会内容陷入困境这件事仍存在争论,但用于管理丰富的文化、知识和轻率的信息(即互联网)的现代解决方案是冷冰冰的索引。

但是也许出于隐私方面的考虑,或者你希望为使互联网更加独立而做出贡献,你或许不愿意使用谷歌或 DuckDuckGo。如果你对此感兴趣,那么可以考虑参加 YaCy,这是一个对等互联网索引器和搜索引擎。

安装 YaCy

要安装并尝试 YaCy,请首先确保已安装 Java。如果你使用的是 Linux,则可以按照我的《如何在 Linux 上安装 Java》中的说明进行操作。如果你使用 Windows 或 MacOS,请从 AdoptOpenJDK.net 获取安装程序。

安装 Java 后,请根据你的平台下载安装程序

如果你使用的是 Linux,请解压缩 tarball 并将其移至 /opt 目录:

$ sudo tar --extract --file  yacy_*z --directory /opt

根据下载的安装程序的说明启动 YaCy。

在 Linux 上,启动在后台运行的 YaCy:

$ /opt/startYACY.sh &

在 Web 浏览器中,导航到 localhost:8090 并进行搜索。

 title=

将 YaCy 添加到你的地址栏

如果你使用的是 Firefox Web 浏览器,则只需单击几下,即可在 Awesome Bar(Mozilla 给 URL 栏起的名称)中将 YaCy 设置为默认搜索引擎。

首先,如果尚未显示,在 Firefox 工具栏中使专用搜索栏显示出来(你不必使搜索栏保持一直可见;只需要激活它足够长的时间即可添加自定义搜索引擎)。Firefox 右上角的“汉堡”菜单中的“自定义”菜单中提供了搜索栏。在 Firefox 工具栏上的搜索栏可见后,导航至 localhost:8090,然后单击刚添加的 Firefox 搜索栏中的放大镜图标。单击选项将 YaCy 添加到你的 Firefox 的搜索引擎中。

 title=

完成此操作后,你可以在 Firefox 首选项中将其标记为默认值,或者仅在 Firefox 搜索栏中执行的搜索中选择性地使用它。如果将其设置为默认搜索引擎,则可能不需要专用搜索栏,因为 Awesome Bar 也使用默认引擎,因此可以将其从工具栏中删除。

对等搜索引擎如何工作

YaCy 是一个开源的分布式搜索引擎。它是用 Java 编写的,因此可以在任何平台上运行,并且可以执行 Web 爬网、索引和搜索。这是一个对等(P2P)网络,因此每个运行 YaCy 的用户都将努力地不断跟踪互联网的变化情况。当然,没有单个用户能拥有整个互联网的完整索引,因为这将需要一个数据中心来容纳,但是该索引分布在所有 YaCy 用户中且是冗余的。它与 BitTorrent 非常相似(因为它使用分布式哈希表 DHT 来引用索引条目),只不过你所共享的数据是单词和 URL 关联的矩阵。通过混合哈希表返回的结果,没人能说出谁搜索了哪些单词,因此所有搜索在功能上都是匿名的。这是用于无偏见、无广告、未跟踪和匿名搜索的有效系统,你只需要使用它就加入了它。

搜索引擎和算法

索引互联网的行为是指将网页分成单个单词,然后将页面的 URL 与每个单词相关联。在搜索引擎中搜索一个或多个单词将获取与该查询关联的所有 URL。YaCy 客户端在运行时也是如此。

客户端要做的另一件事是为你的浏览器提供搜索界面。你可以将 Web 浏览器指向 localhost:8090 来搜索 YaCy,而不是在要搜索时导航到谷歌。你甚至可以将其添加到浏览器的搜索栏中(取决于浏览器的可扩展性),因此可以从 URL 栏中进行搜索。

YaCy 的防火墙设置

首次开始使用 YaCy 时,它可能运行在“初级”模式下。这意味着你的客户端爬网的站点仅对你可用,因为其他 YaCy 客户端无法访问你的索引条目。要加入对等环境,必须在路由器的防火墙(或者你正在运行的软件防火墙)中打开端口 8090,这称为“高级”模式。

如果你使用的是 Linux,则可以在《使用防火墙让你的 Linux 更加强大》中找到有关计算机防火墙的更多信息。在其他平台上,请参考操作系统的文档。

互联网服务提供商(ISP)提供的路由器上几乎总是启用了防火墙,并且有太多种类的防火墙无法准确说明。大多数路由器都提供了在防火墙上“打洞”的选项,因为许多流行的联网游戏都需要双向流量。

如果你知道如何登录路由器(通常为 192.168.0.1 或 10.1.0.1,但可能因制造商的设置而异),则登录并查找配置面板来控制“防火墙”或“端口转发”或“应用”。

找到路由器防火墙的首选项后,将端口 8090 添加到白名单。例如:

 title=

如果路由器正在进行端口转发,则必须使用相同的端口将传入的流量转发到计算机的 IP 地址。例如:

 title=

如果由于某种原因无法调整防火墙设置,那也没事。YaCy 将继续以初级模式运行并作为对等搜索网络的客户端运行。

你的互联网

使用 YaCy 搜索引擎可以做的不仅仅是被动搜索。你可以强制抓取不太显眼的网站,可以请求对网站进行网络抓取,可以选择使用 YaCy 进行本地搜索等等。你可以更好地控制你的互联网的所呈现的一切。高级用户越多,索引的网站就越多。索引的网站越多,所有用户的体验就越好。加入吧!


via: https://opensource.com/article/20/2/open-source-search-engine

作者:Seth Kenlon 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在我们有关 Git 鲜为人知的用法系列的最后一篇文章中,了解如何使用 Git 跟踪项目中的大型多媒体文件。

Git 是专用于源代码版本控制的工具。因此,Git 很少被用于非纯文本的项目以及行业。然而,异步工作流的优点是十分诱人的,尤其是在一些日益增长的行业中,这种类型的行业把重要的计算和重要的艺术创作结合起来,这包括网页设计、视觉效果、视频游戏、出版、货币设计(是的,这是一个真实的行业)、教育……等等。还有许多行业属于这个类型。

在这个 Git 系列文章中,我们分享了六种鲜为人知的 Git 使用方法。在最后一篇文章中,我们将介绍将 Git 的优点带到管理多媒体文件的软件。

Git 管理多媒体文件的问题

众所周知,Git 用于处理非文本文件不是很好,但是这并不妨碍我们进行尝试。下面是一个使用 Git 来复制照片文件的例子:

$ du -hs
108K .
$ cp ~/photos/dandelion.tif .
$ git add dandelion.tif
$ git commit -m 'added a photo'
[master (root-commit) fa6caa7] two photos
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 dandelion.tif
$ du -hs
1.8M .

目前为止没有什么异常。增加一个 1.8MB 的照片到一个目录下,使得目录变成了 1.8 MB 的大小。所以下一步,我们尝试删除文件。

$ git rm dandelion.tif
$ git commit -m 'deleted a photo'
$ du -hs
828K .

在这里我们可以看到有些问题:删除一个已经被提交的文件,还是会使得存储库的大小扩大到原来的 8 倍(从 108K 到 828K)。我们可以测试多次来得到一个更好的平均值,但是这个简单的演示与我的经验一致。提交非文本文件,在一开始花费空间比较少,但是一个工程活跃地时间越长,人们可能对静态内容修改的会更多,更多的零碎文件会被加和到一起。当一个 Git 存储库变的越来越大,主要的成本往往是速度。拉取和推送的时间,从最初抿一口咖啡的时间到你觉得你可能断网了。

静态内容导致 Git 存储库的体积不断扩大的原因是什么呢?那些通过文本的构成的文件,允许 Git 只拉取那些修改的部分。光栅图以及音乐文件对 Git 文件而言与文本不同,你可以查看一下 .png 和 .wav 文件中的二进制数据。所以,Git 只不过是获取了全部的数据,并且创建了一个新的副本,哪怕是一张图仅仅修改了一个像素。

Git-portal

在实践中,许多多媒体项目不需要或者不想追踪媒体的历史记录。相对于文本或者代码的部分,项目的媒体部分一般有一个不同的生命周期。媒体资源一般按一个方向产生:一张图片从铅笔草稿开始,以数字绘画的形式抵达它的目的地。然后,尽管文本能够回滚到早起的版本,但是艺术制品只会一直向前发展。工程中的媒体很少被绑定到一个特定的版本。例外情况通常是反映数据集的图形,通常是可以用基于文本的格式(如 SVG)完成的表、图形或图表。

所以,在许多同时包含文本(无论是叙事散文还是代码)和媒体的工程中,Git 是一个用于文件管理的,可接受的解决方案,只要有一个在版本控制循环之外的游乐场来给艺术家游玩就行。

 title=

一个启用这个特性的简单方法是 Git-portal,这是一个通过带有 Git 钩子的 Bash 脚本,它可将静态文件从文件夹中移出 Git 的范围,并通过符号链接来取代它们。Git 提交链接文件(有时候称作别名或快捷方式),这种符号链接文件比较小,所以所有的提交都是文本文件和那些代表媒体文件的链接。因为替身文件是符号链接,所以工程还会像预期的运行,因为本地机器会处理他们,转换成“真实的”副本。当用符号链接替换出文件时,Git-portal 维护了项目的结构,因此,如果你认为 Git-portal 不适合你的项目,或者你需要构建项目的一个没有符号链接的版本(比如用于分发),则可以轻松地逆转该过程。

Git-portal 也允许通过 rsync 来远程同步静态资源,所以用户可以设置一个远程存储位置,来做为一个中心的授权源。

Git-portal 对于多媒体的工程是一个理想的解决方案。类似的多媒体工程包括视频游戏、桌面游戏、需要进行大型 3D 模型渲染和纹理的虚拟现实工程、带图以及 .odt 输出的书籍、协作型的博客站点、音乐项目,等等。艺术家在应用程序中以图层(在图形世界中)和曲目(在音乐世界中)的形式执行版本控制并不少见——因此,Git 不会向多媒体项目文件本身添加任何内容。Git 的功能可用于艺术项目的其他部分(例如散文和叙述、项目管理、字幕文件、致谢、营销副本、文档等),而结构化远程备份的功能则由艺术家使用。

安装 Git-portal

Git-portal 的 RPM 安装包位于 https://klaatu.fedorapeople.org/git-portal,可用于下载和安装。

此外,用户可以从 Git-portal 的 Gitlab 主页手动安装。这仅仅是一个 Bash 脚本以及一些 Git 钩子(也是 Bash 脚本),但是需要一个快速的构建过程来让它知道安装的位置。

$ git clone https://gitlab.com/slackermedia/git-portal.git git-portal.clone
$ cd git-portal.clone
$ ./configure
$ make
$ sudo make install

使用 Git-portal

Git-portal 与 Git 一起使用。这意味着,如同 Git 的所有大型文件扩展一样,都需要记住一些额外的步骤。但是,你仅仅需要在处理你的媒体资源的时候使用 Git-portal,所以很容易记住,除非你把大文件都当做文本文件来进行处理(对于 Git 用户很少见)。使用 Git-portal 必须做的一个安装步骤是:

$ mkdir bigproject.git
$ cd !$
$ git init
$ git-portal init

Git-portal 的 init 函数在 Git 存储库中创建了一个 _portal 文件夹并且添加到 .gitignore 文件中。

在平日里使用 Git-portal 和 Git 协同十分平滑。一个较好的例子是基于 MIDI 的音乐项目:音乐工作站产生的项目文件是基于文本的,但是 MIDI 文件是二进制数据:

$ ls -1
_portal
song.1.qtr
song.qtr
song-Track_1-1.mid
song-Track_1-3.mid
song-Track_2-1.mid
$ git add song*qtr
$ git-portal song-Track*mid
$ git add song-Track*mid

如果你查看一下 _portal 文件夹,你会发现那里有最初的 MIDI 文件。这些文件在原本的位置被替换成了指向 _portal 的链接文件,使得音乐工作站像预期一样运行。

$ ls -lG
[...] _portal/
[...] song.1.qtr
[...] song.qtr
[...] song-Track_1-1.mid -> _portal/song-Track_1-1.mid*
[...] song-Track_1-3.mid -> _portal/song-Track_1-3.mid*
[...] song-Track_2-1.mid -> _portal/song-Track_2-1.mid*

与 Git 相同,你也可以添加一个目录下的文件。

$ cp -r ~/synth-presets/yoshimi .
$ git-portal add yoshimi
Directories cannot go through the portal. Sending files instead.
$ ls -lG _portal/yoshimi
[...] yoshimi.stat -> ../_portal/yoshimi/yoshimi.stat*

删除功能也像预期一样工作,但是当从 _portal 中删除一些东西时,你应该使用 git-portal rm 而不是 git rm。使用 Git-portal 可以确保文件从 _portal 中删除:

$ ls
_portal/    song.qtr             song-Track_1-3.mid@  yoshimi/
song.1.qtr  song-Track_1-1.mid@  song-Track_2-1.mid@
$ git-portal rm song-Track_1-3.mid
rm 'song-Track_1-3.mid'
$ ls _portal/
song-Track_1-1.mid*  song-Track_2-1.mid*  yoshimi/

如果你忘记使用 Git-portal,那么你需要手动删除 _portal 下的文件:

$ git-portal rm song-Track_1-1.mid
rm 'song-Track_1-1.mid'
$ ls _portal/
song-Track_1-1.mid* song-Track_2-1.mid* yoshimi/
$ trash _portal/song-Track_1-1.mid

Git-portal 其它的唯一功能,是列出当前所有的链接并且找到里面可能已经损坏的符号链接。有时这种情况会因为项目文件夹中的文件被移动而发生:

$ mkdir foo
$ mv yoshimi foo
$ git-portal status
bigproject.git/song-Track_2-1.mid: symbolic link to _portal/song-Track_2-1.mid
bigproject.git/foo/yoshimi/yoshimi.stat: broken symbolic link to ../_portal/yoshimi/yoshimi.stat

如果你使用 Git-portal 用于私人项目并且维护自己的备份,以上就是技术方面所有你需要知道关于 Git-portal 的事情了。如果你想要添加一个协作者或者你希望 Git-portal 来像 Git 的方式来管理备份,你可以创建一个远程位置。

增加 Git-portal 远程位置

为 Git-portal 增加一个远程位置是通过 Git 已有的远程功能来实现的。Git-portal 实现了 Git 钩子(隐藏在存储库 .git 文件夹中的脚本),来寻找你的远程位置上是否存在以 _portal 开头的文件夹。如果它找到一个,它会尝试使用 rsync 来与远程位置同步文件。Git-portal 在用户进行 Git 推送以及 Git 合并的时候(或者在进行 Git 拉取的时候,实际上是进行一次获取和自动合并),都会执行此操作。

如果你仅克隆了 Git 存储库,那么你可能永远不会自己添加一个远程位置。这是一个标准的 Git 过程:

$ git remote add origin [email protected]:seth/bigproject.git
$ git remote -v
origin [email protected]:seth/bigproject.git (fetch)
origin [email protected]:seth/bigproject.git (push)

对你的主要 Git 存储库来说,origin 这个名字是一个流行的惯例,将其用于 Git 数据是有意义的。然而,你的 Git-portal 数据是分开存储的,所以你必须创建第二个远程位置来让 Git-portal 了解向哪里推送和从哪里拉取。取决于你的 Git 主机,你可能需要一个单独的服务器,因为空间有限的 Git 主机不太可能接受 GB 级的媒体资产。或者,可能你的服务器仅允许你访问你的 Git 存储库而不允许访问外部的存储文件夹:

$ git remote add _portal [email protected]:/home/seth/git/bigproject_portal
$ git remote -v
origin [email protected]:seth/bigproject.git (fetch)
origin [email protected]:seth/bigproject.git (push)
_portal [email protected]:/home/seth/git/bigproject_portal (fetch)
_portal [email protected]:/home/seth/git/bigproject_portal (push)

你可能不想为所有用户提供服务器上的个人帐户,也不必这样做。为了提供对托管资源库大文件资产的服务器的访问权限,你可以运行一个 Git 前端,比如 Gitolite 或者你可以使用 rrsync (受限的 rsync)。

现在你可以推送你的 Git 数据到你的远程 Git 存储库,并将你的 Git-portal 数据到你的远程的门户:

$ git push origin HEAD  
master destination detected
Syncing _portal content...
sending incremental file list
sent 9,305 bytes  received 18 bytes  1,695.09 bytes/sec
total size is 60,358,015  speedup is 6,474.10
Syncing _portal content to example.com:/home/seth/git/bigproject_portal

如果你已经安装了 Git-portal,并且配置了 _portal 的远程位置,你的 _portal 文件夹将会被同步,并且从服务器获取新的内容,以及在每一次推送的时候发送新的内容。尽管你不需要进行 Git 提交或者推送来和服务器同步(用户可以使用直接使用 rsync),但是我发现对于艺术性内容的改变,提交是有用的。这将会把艺术家及其数字资产集成到工作流的其余部分中,并提供有关项目进度和速度的有用元数据。

其他选择

如果 Git-portal 对你而言太过简单,还有一些用于 Git 管理大型文件的其他选择。Git 大文件存储(LFS)是一个名为 git-media 的停工项目的分支,这个分支由 GitHub 维护和支持。它需要特殊的命令(例如 git lfs track 来保护大型文件不被 Git 追踪)并且需要用户维护一个 .gitattributes 文件来更新哪些存储库中的文件被 LFS 追踪。对于大文件而言,它支持 HTTP 和 HTTPS 远程主机。所以你必须配置 LFS 服务器,才能使得用户可以通过 HTTP 而不是 SSH 或 rsync 来进行鉴权。

另一个相对 LFS 更灵活的选择是 git-annex。你可以在我的文章 管理 Git 中大二进制 blob 中了解更多(忽略其中 git-media 这个已经废弃项目的章节,因为其灵活性没有被它的继任者 Git LFS 延续下来)。Git-annex 是一个灵活且优雅的解决方案。它拥有一个细腻的系统来用于添加、删除、移动存储库中的大型文件。因为它灵活且强大,有很多新的命令和规则需要进行学习,所以建议看一下它的文档

然而,如果你的需求很简单,你可能更加喜欢整合已有技术来进行简单且明显任务的解决方案,则 Git-portal 可能是对于工作而言比较合适的工具。


via: https://opensource.com/article/19/4/manage-multimedia-files-git

作者:Seth Kenlon 选题:lujun9972 译者:svtter 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在本系列的第十一篇有关使用 Python Pygame 模块进行编程的文章中,显示玩家获得战利品或受到伤害时的得分。

这是仍在进行中的关于使用 Pygame 模块来在 Python 3 在创建电脑游戏的第十一部分。先前的文章是:

如果你已经跟随这一系列很久,那么已经学习了使用 Python 创建一个视频游戏所需的所有基本语法和模式。然而,它仍然缺少一个至关重要的组成部分。这一组成部分不仅仅对用 Python 编程游戏重要;不管你探究哪个计算机分支,你都必需精通:作为一个程序员,通过阅读一种语言的或库的文档来学习新的技巧。

幸运的是,你正在阅读本文的事实表明你熟悉文档。为了使你的平台类游戏更加美观,在这篇文章中,你将在游戏屏幕上添加得分和生命值显示。不过,教你如何找到一个库的功能以及如何使用这些新的功能的这节课程并没有多神秘。

在 Pygame 中显示得分

现在,既然你有了可以被玩家收集的奖励,那就有充分的理由来记录分数,以便你的玩家看到他们收集了多少奖励。你也可以跟踪玩家的生命值,以便当他们被敌人击中时会有相应结果。

你已经有了跟踪分数和生命值的变量,但是这一切都发生在后台。这篇文章教你在游戏期间在游戏屏幕上以你选择的一种字体来显示这些统计数字。

阅读文档

大多数 Python 模块都有文档,即使那些没有文档的模块,也能通过 Python 的帮助功能来进行最小的文档化。Pygame 的主页面 链接了它的文档。不过,Pygame 是一个带有很多文档的大模块,并且它的文档不像在 Opensource.com 上的文章一样,以同样易理解的(和友好的、易解释的、有用的)叙述风格来撰写的。它们是技术文档,并且列出在模块中可用的每个类和函数,各自要求的输入类型等等。如果你不适应参考代码组件描述,这可能会令人不知所措。

在烦恼于库的文档前,第一件要做的事,就是来想想你正在尝试达到的目标。在这种情况下,你想在屏幕上显示玩家的得分和生命值。

在你确定你需要的结果后,想想它需要什么的组件。你可以从变量和函数的方面考虑这一点,或者,如果你还没有自然地想到这一点,你可以进行一般性思考。你可能意识到需要一些文本来显示一个分数,你希望 Pygame 在屏幕上绘制这些文本。如果你仔细思考,你可能会意识到它与在屏幕上渲染一个玩家、奖励或一个平台并多么大的不同。

从技术上讲,你可以使用数字图形,并让 Pygame 显示这些数字图形。它不是达到你目标的最容易的方法,但是如果它是你唯一知道的方法,那么它是一个有效的方法。不过,如果你参考 Pygame 的文档,你看到列出的模块之一是 font,这是 Pygame 使得在屏幕上来使打印文本像输入文字一样容易的方法。

解密技术文档

font 文档页面以 pygame.font.init() 开始,它列出了用于初始化字体模块的函数。它由 pygame.init() 自动地调用,你已经在代码中调用了它。再强调一次,从技术上讲,你已经到达一个足够好的点。虽然你尚不知道如何做,你知道你能够使用 pygame.font 函数来在屏幕上打印文本。

然而,如果你阅读更多一些,你会找到这里还有一种更好的方法来打印字体。pygame.freetype 模块在文档中的描述方式如下:

pygame.freetype 模块是 pygame.fontpygame 模块的一个替代品,用于加载和渲染字体。它有原函数的所有功能,外加很多新的功能。

pygame.freetype 文档页面的下方,有一些示例代码:

import pygame
import pygame.freetype

你的代码应该已经导入了 Pygame,不过,请修改你的 import 语句以包含 Freetype 模块:

import pygame
import sys
import os
import pygame.freetype

在 Pygame 中使用字体

font 模块的描述中可以看出,显然 Pygame 使用一种字体(不管它的你提供的或内置到 Pygame 的默认字体)在屏幕上渲染字体。滚动浏览 pygame.freetype 文档来找到 pygame.freetype.Font 函数:

pygame.freetype.Font
从支持的字体文件中创建一个新的字体实例。

Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font

pygame.freetype.Font.name
  符合规则的字体名称。

pygame.freetype.Font.path
  字体文件路径。

pygame.freetype.Font.size
  在渲染中使用的默认点大小

这描述了如何在 Pygame 中构建一个字体“对象”。把屏幕上的一个简单对象视为一些代码属性的组合对你来说可能不太自然,但是这与你构建英雄和敌人精灵的方式非常类似。你需要一个字体文件,而不是一个图像文件。在你有一个字体文件后,你可以在你的代码中使用 pygame.freetype.Font 函数来创建一个字体对象,然后使用该对象来在屏幕上渲染文本。

因为并不是世界上的每个人的电脑上都有完全一样的字体,因此将你选择的字体与你的游戏捆绑在一起是很重要的。要捆绑字体,首先在你的游戏文件夹中创建一个新的目录,放在你为图像而创建的文件目录旁边。称其为 fonts

即使你的计算机操作系统随附了几种字体,但是将这些字体给予其他人是非法的。这看起来很奇怪,但法律就是这样运作的。如果想与你的游戏一起随附一种字体,你必需找到一种开源或知识共享的字体,以允许你随游戏一起提供该字体。

专门提供自由和合法字体的网站包括:

当你找到你喜欢的字体后,下载下来。解压缩 ZIP 或 TAR 文件,并移动 .ttf.otf 文件到你的项目目录下的 fonts 文件夹中。

你没有安装字体到你的计算机上。你只是放置字体到你游戏的 fonts 文件夹中,以便 Pygame 可以使用它。如果你想,你可以在你的计算机上安装该字体,但是没有必要。重要的是将字体放在你的游戏目录中,这样 Pygame 可以“描绘”字体到屏幕上。

如果字体文件的名称复杂且带有空格或特殊字符,只需要重新命名它即可。文件名称是完全任意的,并且对你来说,文件名称越简单,越容易将其键入你的代码中。

现在告诉 Pygame 你的字体。从文档中你知道,当你至少提供了字体文件路径给 pygame.freetype.Font 时(文档明确指出所有其余属性都是可选的),你将在返回中获得一个字体对象:

Font(file, size=0, font_index=0, resolution=0, ucs4=False) -> Font

创建一个称为 myfont 的新变量来充当你在游戏中字体,并放置 Font 函数的结果到这个变量中。这个示例中使用 amazdoom.ttf 字体,但是你可以使用任何你想使用的字体。在你的设置部分放置这些代码:

font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"fonts","amazdoom.ttf")
font_size = tx
myfont = pygame.freetype.Font(font_path, font_size)

在 Pygame 中显示文本

现在你已经创建一个字体对象,你需要一个函数来绘制你想绘制到屏幕上的文本。这和你在你的游戏中绘制背景和平台是相同的原理。

首先,创建一个函数,并使用 myfont 对象来创建一些文本,设置颜色为某些 RGB 值。这必须是一个全局函数;它不属于任何具体的类:

def stats(score,health):
    myfont.render_to(world, (4, 4), "Score:"+str(score), WHITE, None, size=64)
    myfont.render_to(world, (4, 72), "Health:"+str(health), WHITE, None, size=64)

当然,你此刻已经知道,如果它不在主循环中,你的游戏将不会发生任何事,所以在文件的底部添加一个对你的 stats 函数的调用:

    for e in enemy_list:
        e.move()
    stats(player.score,player.health) # draw text
    pygame.display.flip()

尝试你的游戏。

当玩家收集奖励品时,得分会上升。当玩家被敌人击中时,生命值下降。成功!

 title=

不过,这里有一个问题。当一个玩家被敌人击中时,健康度会一路下降,这是不公平的。你刚刚发现一个非致命的错误。非致命的错误是这些在应用程序中小问题,(通常)不会阻止应用程序启动或甚至导致停止工作,但是它们要么没有意义,要么会惹恼用户。这里是如何解决这个问题的方法。

修复生命值计数

当前生命值系统的问题是,敌人接触玩家时,Pygame 时钟的每一次滴答,健康度都会减少。这意味着一个缓慢移动的敌人可能在一次遭遇中将一个玩家降低健康度至 -200 ,这不公平。当然,你可以给你的玩家一个 10000 的起始健康度得分,而不用担心它;这可以工作,并且可能没有人会注意。但是这里有一个更好的方法。

当前,你的代码侦查出一个玩家和一个敌人发生碰撞的时候。生命值问题的修复是检测两个独立的事件:什么时候玩家和敌人碰撞,并且,在它们碰撞后,什么时候它们停止碰撞。

首先,在你的玩家类中,创建一个变量来代表玩家和敌人碰撞在一起:

        self.frame = 0
        self.health = 10
        self.damage = 0

在你的 Player 类的 update 函数中,移除这块代码块:

        for enemy in enemy_hit_list:
            self.health -= 1
            #print(self.health)

并且在它的位置,只要玩家当前没有被击中,检查碰撞:

        if self.damage == 0:
            for enemy in enemy_hit_list:
                if not self.rect.contains(enemy):
                    self.damage = self.rect.colliderect(enemy)

你可能会在你删除的语句块和你刚刚添加的语句块之间看到相似之处。它们都在做相同的工作,但是新的代码更复杂。最重要的是,只有当玩家当前没有被击中时,新的代码才运行。这意味着,当一个玩家和敌人碰撞时,这些代码运行一次,而不是像以前那样一直发生碰撞。

新的代码使用两个新的 Pygame 函数。self.rect.contains 函数检查一个敌人当前是否在玩家的边界框内,并且当它是 true 时, self.rect.colliderect 设置你的新的 self.damage 变量为 1,而不管它多少次是 true

现在,即使被一个敌人击中 3 秒,对 Pygame 来说仍然看作一次击中。

我通过通读 Pygame 的文档而发现了这些函数。你没有必要一次阅读完全部的文档,并且你也没有必要阅读每个函数的每个单词。不过,花费时间在你正在使用的新的库或模块的文档上是很重要的;否则,你极有可能在重新发明轮子。不要花费一个下午的时间来尝试修改拼接一个解决方案到一些东西,而这些东西已经被你正在使用的框架的所解决。阅读文档,知悉函数,并从别人的工作中获益!

最后,添加另一个代码语句块来侦查出什么时候玩家和敌人不再接触。然后直到那时,才从玩家减少一个生命值。

        if self.damage == 1:
            idx = self.rect.collidelist(enemy_hit_list)
            if idx == -1:
                self.damage = 0   # set damage back to 0
                self.health -= 1  # subtract 1 hp

注意,只有当玩家被击中时,这个新的代码才会被触发。这意味着,在你的玩家在你的游戏世界正在探索或收集奖励时,这个代码不会运行。它仅当 self.damage 变量被激活时运行。

当代码运行时,它使用 self.rect.collidelist 来查看玩家是否仍然接触在你敌人列表中的敌人(当其未侦查到碰撞时,collidelist 返回 -1)。在它没有接触敌人时,是该处理 self.damage 的时机:通过设置 self.damage 变量回到 0 来使其无效,并减少一点生命值。

现在尝试你的游戏。

得分反应

现在,你有一个来让你的玩家知道它们分数和生命值的方法,当你的玩家达到某些里程碑时,你可以确保某些事件发生。例如,也许这里有一个特殊的恢复一些生命值的奖励项目。也许一个到达 0 生命值的玩家不得不从一个关卡的起始位置重新开始。

你可以在你的代码中检查这些事件,并且相应地操纵你的游戏世界。你已经知道该怎么做,所以请浏览文档来寻找新的技巧,并且独立地尝试这些技巧。

这里是到目前为止所有的代码:

#!/usr/bin/env python3
# draw a world
# add a player and player control
# add player movement
# add enemy and basic collision
# add platform
# add gravity
# add jumping
# add scrolling
# add loot
# add score

# GNU All-Permissive License
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved.  This file is offered as-is,
# without any warranty.

import pygame
import sys
import os
import pygame.freetype

'''
Objects
'''
       
class Platform(pygame.sprite.Sprite):
    # x location, y location, img width, img height, img file    
    def __init__(self,xloc,yloc,imgw,imgh,img):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(os.path.join('images',img)).convert()
        self.image.convert_alpha()
        self.rect = self.image.get_rect()
        self.rect.y = yloc
        self.rect.x = xloc

class Player(pygame.sprite.Sprite):
    '''
    Spawn a player
    '''
    def __init__(self):
        pygame.sprite.Sprite.__init__(self)
        self.movex = 0
        self.movey = 0
        self.frame = 0
        self.health = 10
        self.damage = 0
        self.collide_delta = 0
        self.jump_delta = 6
        self.score = 1
        self.images = []
        for i in range(1,9):
            img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
            img.convert_alpha()
            img.set_colorkey(ALPHA)
            self.images.append(img)
            self.image = self.images[0]
            self.rect  = self.image.get_rect()

    def jump(self,platform_list):
        self.jump_delta = 0

    def gravity(self):
        self.movey += 3.2 # how fast player falls
       
        if self.rect.y > worldy and self.movey >= 0:
            self.movey = 0
            self.rect.y = worldy-ty
       
    def control(self,x,y):
        '''
        control player movement
        '''
        self.movex += x
        self.movey += y
       
    def update(self):
        '''
        Update sprite position
        '''
       
        self.rect.x = self.rect.x + self.movex
        self.rect.y = self.rect.y + self.movey

        # moving left
        if self.movex < 0:
            self.frame += 1
            if self.frame > ani*3:
                self.frame = 0
            self.image = self.images[self.frame//ani]

        # moving right
        if self.movex > 0:
            self.frame += 1
            if self.frame > ani*3:
                self.frame = 0
            self.image = self.images[(self.frame//ani)+4]

        # collisions
        enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
        if self.damage == 0:
            for enemy in enemy_hit_list:
                if not self.rect.contains(enemy):
                    self.damage = self.rect.colliderect(enemy)

        if self.damage == 1:
            idx = self.rect.collidelist(enemy_hit_list)
            if idx == -1:
                self.damage = 0   # set damage back to 0
                self.health -= 1  # subtract 1 hp

        loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False)
        for loot in loot_hit_list:
            loot_list.remove(loot)
            self.score += 1
            print(self.score)

        plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
        for p in plat_hit_list:
            self.collide_delta = 0 # stop jumping
            self.movey = 0
            if self.rect.y > p.rect.y:
                self.rect.y = p.rect.y+ty
            else:
                self.rect.y = p.rect.y-ty
           
        ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
        for g in ground_hit_list:
            self.movey = 0
            self.rect.y = worldy-ty-ty
            self.collide_delta = 0 # stop jumping
            if self.rect.y > g.rect.y:
                self.health -=1
                print(self.health)
               
        if self.collide_delta < 6 and self.jump_delta < 6:
            self.jump_delta = 6*2
            self.movey -= 33  # how high to jump
            self.collide_delta += 6
            self.jump_delta    += 6
           
class Enemy(pygame.sprite.Sprite):
    '''
    Spawn an enemy
    '''
    def __init__(self,x,y,img):
        pygame.sprite.Sprite.__init__(self)
        self.image = pygame.image.load(os.path.join('images',img))
        self.movey = 0
        #self.image.convert_alpha()
        #self.image.set_colorkey(ALPHA)
        self.rect = self.image.get_rect()
        self.rect.x = x
        self.rect.y = y
        self.counter = 0

               
    def move(self):
        '''
        enemy movement
        '''
        distance = 80
        speed = 8

        self.movey += 3.2
       
        if self.counter >= 0 and self.counter <= distance:
            self.rect.x += speed
        elif self.counter >= distance and self.counter <= distance*2:
            self.rect.x -= speed
        else:
            self.counter = 0
       
        self.counter += 1

        if not self.rect.y >= worldy-ty-ty:
            self.rect.y += self.movey

        plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
        for p in plat_hit_list:
            self.movey = 0
            if self.rect.y > p.rect.y:
                self.rect.y = p.rect.y+ty
            else:
                self.rect.y = p.rect.y-ty

        ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
        for g in ground_hit_list:
            self.rect.y = worldy-ty-ty

       
class Level():
    def bad(lvl,eloc):
        if lvl == 1:
            enemy = Enemy(eloc[0],eloc[1],'yeti.png') # spawn enemy
            enemy_list = pygame.sprite.Group() # create enemy group
            enemy_list.add(enemy)              # add enemy to group
           
        if lvl == 2:
            print("Level " + str(lvl) )

        return enemy_list

    def loot(lvl,tx,ty):
        if lvl == 1:
            loot_list = pygame.sprite.Group()
            loot = Platform(200,ty*7,tx,ty, 'loot_1.png')
            loot_list.add(loot)

        if lvl == 2:
            print(lvl)

        return loot_list

    def ground(lvl,gloc,tx,ty):
        ground_list = pygame.sprite.Group()
        i=0
        if lvl == 1:
            while i < len(gloc):
                ground = Platform(gloc[i],worldy-ty,tx,ty,'ground.png')
                ground_list.add(ground)
                i=i+1

        if lvl == 2:
            print("Level " + str(lvl) )

        return ground_list

    def platform(lvl,tx,ty):
        plat_list = pygame.sprite.Group()
        ploc = []
        i=0
        if lvl == 1:
            ploc.append((20,worldy-ty-128,3))
            ploc.append((300,worldy-ty-256,3))
            ploc.append((500,worldy-ty-128,4))

            while i < len(ploc):
                j=0
                while j <= ploc[i][2]:
                    plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'ground.png')
                    plat_list.add(plat)
                    j=j+1
                print('run' + str(i) + str(ploc[i]))
                i=i+1

        if lvl == 2:
            print("Level " + str(lvl) )

        return plat_list

def stats(score,health):
    myfont.render_to(world, (4, 4), "Score:"+str(score), SNOWGRAY, None, size=64)
    myfont.render_to(world, (4, 72), "Health:"+str(health), SNOWGRAY, None, size=64)

'''
Setup
'''
worldx = 960
worldy = 720

fps = 40 # frame rate
ani = 4  # animation cycles
clock = pygame.time.Clock()
pygame.init()
main = True

BLUE  = (25,25,200)
BLACK = (23,23,23 )
WHITE = (254,254,254)
SNOWGRAY = (137,164,166)
ALPHA = (0,255,0)
   
world = pygame.display.set_mode([worldx,worldy])
backdrop = pygame.image.load(os.path.join('images','stage.png')).convert()
backdropbox = world.get_rect()
player = Player() # spawn player
player.rect.x = 0
player.rect.y = 0
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10
forwardx = 600
backwardx = 230

eloc = []
eloc = [200,20]
gloc = []
tx = 64 #tile size
ty = 64 #tile size

font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),"fonts","amazdoom.ttf")
font_size = tx
myfont = pygame.freetype.Font(font_path, font_size)
   
i=0
while i <= (worldx/tx)+tx:
    gloc.append(i*tx)
    i=i+1

enemy_list = Level.bad( 1, eloc )
ground_list = Level.ground( 1,gloc,tx,ty )
plat_list = Level.platform( 1,tx,ty )
loot_list = Level.loot(1,tx,ty)

'''
Main loop
'''
while main == True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit(); sys.exit()
            main = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                print("LEFT")
                player.control(-steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                print("RIGHT")
                player.control(steps,0)
            if event.key == pygame.K_UP or event.key == ord('w'):
                print('jump')

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_LEFT or event.key == ord('a'):
                player.control(steps,0)
            if event.key == pygame.K_RIGHT or event.key == ord('d'):
                player.control(-steps,0)
            if event.key == pygame.K_UP or event.key == ord('w'):
                player.jump(plat_list)

            if event.key == ord('q'):
                pygame.quit()
                sys.exit()
                main = False

    # scroll the world forward
    if player.rect.x >= forwardx:
        scroll = player.rect.x - forwardx
        player.rect.x = forwardx
        for p in plat_list:
            p.rect.x -= scroll
        for e in enemy_list:
            e.rect.x -= scroll
        for l in loot_list:

            l.rect.x -= scroll
               
    # scroll the world backward
    if player.rect.x <= backwardx:
        scroll = backwardx - player.rect.x
        player.rect.x = backwardx
        for p in plat_list:
            p.rect.x += scroll
        for e in enemy_list:
            e.rect.x += scroll
        for l in loot_list:
            l.rect.x += scroll

    world.blit(backdrop, backdropbox)
    player.gravity() # check gravity
    player.update()
    player_list.draw(world) #refresh player position
    enemy_list.draw(world)  # refresh enemies
    ground_list.draw(world)  # refresh enemies
    plat_list.draw(world)   # refresh platforms
    loot_list.draw(world)   # refresh loot
    for e in enemy_list:
        e.move()
    stats(player.score,player.health) # draw text
    pygame.display.flip()
    clock.tick(fps)

via: https://opensource.com/article/20/1/add-scorekeeping-your-python-game

作者:Seth Kenlon 选题:lujun9972 译者:robsean 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这本免费的电子书使经验丰富的程序员更深入了解 Linux 中进程间通信(IPC)的核心概念和机制。

让一个软件过程与另一个软件过程进行对话是一个微妙的平衡行为。但是,它对于应用程序而言可能是至关重要的功能,因此这是任何从事复杂项目的程序员都必须解决的问题。你的应用程序是否需要启动由其它软件处理的工作;监视外设或网络上正在执行的操作;或者检测来自其它来源的信号,当你的软件需要依赖其自身代码之外的东西来知道下一步做什么或什么时候做时,你就需要考虑 进程间通信 inter-process communication (IPC)。

这在 Unix 操作系统上已经由来已久了,这可能是因为人们早期预期软件会来自各种来源。按照相同的传统,Linux 提供了一些同样的 IPC 接口和一些新接口。Linux 内核具有多种 IPC 方法,util-linux 包包含了 ipcmkipcrmipcslsipc 命令,用于监视和管理 IPC 消息。

显示进程间通信信息

在尝试 IPC 之前,你应该知道系统上已经有哪些 IPC 设施。lsipc 命令提供了该信息。

RESOURCE DESCRIPTION               LIMIT  USED  USE%
MSGMNI   Number of message queues  32000     0 0.00%
MSGMAX   Max size of message (byt.. 8192     -     -
MSGMNB   Default max size of queue 16384     -     -
SHMMNI   Shared memory segments     4096    79 1.93%
SHMALL   Shared memory pages       184[...] 25452 0.00%
SHMMAX   Max size of shared memory 18446744073692774399
SHMMIN   Min size of shared memory     1     -     -
SEMMNI   Number of semaphore ident 32000     0 0.00%
SEMMNS   Total number of semaphore 1024000.. 0 0.00%
SEMMSL   Max semaphores per semap  32000     -     -
SEMOPM   Max number of operations p  500     -     -
SEMVMX   Semaphore max value       32767     -     -

你可能注意到,这个示例清单包含三种不同类型的 IPC 机制,每种机制在 Linux 内核中都是可用的:消息(MSG)、共享内存(SHM)和信号量(SEM)。你可以用 ipcs 命令查看每个子系统的当前活动:

$ ipcs

------ Message Queues Creators/Owners ---
msqid     perms     cuid      cgid  [...]

------ Shared Memory Segment Creators/Owners
shmid     perms    cuid    cgid  [...]
557056    700      seth    users [...]
3571713   700      seth    users [...]
2654210   600      seth    users [...]
2457603   700      seth    users [...]

------ Semaphore Arrays Creators/Owners ---
semid     perms     cuid      cgid  [...]

这表明当前没有消息或信号量阵列,但是使用了一些共享内存段。

你可以在系统上执行一个简单的示例,这样就可以看到正在工作的系统之一。它涉及到一些 C 代码,所以你必须在系统上有构建工具。必须安装这些软件包才能从源代码构建软件,这些软件包的名称取决于发行版,因此请参考文档以获取详细信息。例如,在基于 Debian 的发行版上,你可以在 wiki 的构建教程部分了解构建需求,而在基于 Fedora 的发行版上,你可以参考该文档的从源代码安装软件部分。

创建一个消息队列

你的系统已经有一个默认的消息队列,但是你可以使用 ipcmk 命令创建你自己的消息队列:

$ ipcmk --queue
Message queue id: 32764

编写一个简单的 IPC 消息发送器,为了简单,在队列 ID 中硬编码:

#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>

struct msgbuffer {
  char text[24];
} message;

int main() {
    int msqid = 32764;
    strcpy(message.text,"opensource.com");
    msgsnd(msqid, &message, sizeof(message), 0);
    printf("Message: %s\n",message.text);
    printf("Queue: %d\n",msqid);
    return 0;
        }

编译该应用程序并运行:

$ gcc msgsend.c -o msg.bin
$ ./msg.bin
Message: opensource.com
Queue: 32769

你刚刚向你的消息队列发送了一条消息。你可以使用 ipcs 命令验证这一点,可以使用 ——queue 选项将输出限制到该消息队列:

$ ipcs -q

------ Message Queues --------
key        msqid   owner  perms  used-bytes  messages
0x7b341ab9 0       seth   666    0          0
0x72bd8410 32764   seth   644    24         1

你也可以检索这些消息:

#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>

struct msgbuffer {
    char text[24];
} message;

int main() {
    int msqid = 32764;
    msgrcv(msqid, &message, sizeof(message),0,0);
    printf("\nQueue: %d\n",msqid);
    printf("Got this message: %s\n", message.text);
    msgctl(msqid,IPC_RMID,NULL);
    return 0;

编译并运行:

$ gcc get.c -o get.bin
$ ./get.bin

Queue: 32764
Got this message: opensource.com

下载这本电子书

这只是 Marty Kalin 的《Linux 进程间通信指南》中课程的一个例子,可从 Opensource.com 下载的这本最新免费(且 CC 授权)的电子书。在短短的几节课中,你将从消息队列、共享内存和信号量、套接字、信号等中了解 IPC 的 POSIX 方法。认真阅读 Marty 的书,你将成为一个博识的程序员。而这不仅适用于经验丰富的编码人员,如果你编写的只是 shell 脚本,那么你将拥有有关管道(命名和未命名)和共享文件的大量实践知识,以及使用共享文件或外部消息队列时需要了解的重要概念。

如果你对制作具有动态和具有系统感知的优秀软件感兴趣,那么你需要了解 IPC。让这本书做你的向导。


via: https://opensource.com/article/20/1/inter-process-communication-linux

作者:Seth Kenlon 选题:lujun9972 译者:laingke 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出