尹良灿得闲

巴甫洛夫很忙……巴甫洛夫正在死亡

【译】ArcadeRs 1.1: 一个简单的窗口

| Comments

原文:ArcadeRS 1.1: A simple window

本系列文章的目是通过开发一个简单的老式射击游戏来一探 Rust 这门语言,这是其中的第一部分。除去介绍,本系列共有 16 个部分组成:

  1. 一个简单的窗口,我们在此安装 SDL2
  2. 事件处理,我们在此讨论生存期限
  3. 处理更多的事件,我们在此讨论宏
  4. 视图,我们在此学习装箱,模式匹配,trait 对象,还有动态分发
  5. 视图的切换,我们在此使用装箱,模式匹配,trait 对象,还有动态分发
  6. 移动的矩形,我们在此绘制图像
  7. Main menu, where we play with textures and Rust’s vectors
  8. Sprites, where we create shareable images
  9. Backgrounds, where we handle resizing, scale and translate through time
  10. The player’s ship, where we control a multi-sprite object
  11. Shooting bullets, where we handle resource pooling
  12. Animated sprites, where we render animated asteroids
  13. Asteroid attack!, where we make multiple objects interact
  14. Explosions, where we see things do boom.
  15. Music, where we hear things go boom.
  16. High score & wrap-up, where we play with the filesystem

一个华丽的新项目

作为开始,让我们亲切地请求 Cargo 为我们创建一个新项目。在你的项目目录下执行:

1
2
$ cargo new arcade-rs --bin
$ cd arcade-rs

如果你打开生成的 Cargo.toml,你可以看到类似如下的内容:

1
2
3
4
[package]
name = "arcade-rs"
version = "0.1.0"
authors = ["John Doe <john.doe@example.com>"]

你可以通过 cargo run 命令运行生成的 hello world 程序,然而现在来说这并没有什么卵用。我们要做的是,稍微更改一下这个配置文件以加入所需的依赖。请把以下几行添加到该文件的底部:

1
2
[dependencies]
sdl2 = "0.6"

结束的时候,我们的项目会依赖于更多的外部 crateRust 概念中的 库),不过眼下我们只需要这些。sdl2 crate 使我们可以创建程序窗口,在上面渲染画面和处理各种事件,而这些也正好是我们在接下来的几章将要做的事情。

很可能你的电脑上尚未安装有 SDL2 的开发库,那样的话将无法编译 SDL2 的 Rust 绑定。 我建议你参照 rust-sdl2 的 README 文件 中的步骤,在你的系统中安装所需的开发库。 等你完成后我们接着继续。

现在,SDL2 已经(希望是)安装在你的系统中了,你可以执行:

1
2
3
4
5
6
7
8
9
10
11
12
$ cargo build
    Updating registry `https://github.com/rust-lang/crates.io-index`
 Downloading sdl2 v0.6.0
 Downloading sdl2-sys v0.6.0
   Compiling sdl2-sys v0.6.0
   Compiling bitflags v0.2.1
   Compiling rustc-serialize v0.3.15
   Compiling libc v0.1.8
   Compiling rand v0.3.8
   Compiling num v0.1.25
   Compiling sdl2 v0.6.0
   Compiling arcade-rs v0.1.0 (file:///home/johndoe/projects/arcade-rs)

如果一切顺利,各类绑定和它们的依赖就已经编译好而我们也可以开启一个窗口了。否则,很可能你并没有正确安装 SDL2 而你只有解决了这一问题才能进行接下来的步骤。

激动人心的三秒钟

好了,让我们来创建一个窗口。用你最喜欢的文本编辑器打开 src/main.rs 然后修改为以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
extern crate sdl2;

use sdl2::pixels::Color;
use std::thread;

fn main() {
    // 初始化 SDL2
    let sdl_context = sdl2::init().video()
        .build().unwrap();

    // 创建窗口
    let window = sdl_context.window("ArcadeRS Shooter", 800, 600)
        .position_centered().opengl()
        .build().unwrap();

    let mut renderer = window.renderer()
        .accelerated()
        .build().unwrap();

    renderer.set_draw_color(Color::RGB(0, 0, 0));
    renderer.clear();
    renderer.present();

    thread::sleep_ms(3000);
}

现在,如果你运行这个程序的话…

1
2
3
$ cargo run
   Compiling arcade-rs v0.1.0 (file:///home/johndoe/projects/arcade-rs)
     Running `target/debug/arcade-rs`

three_sec

… 你可以看到一个窗口在屏幕上停留了 3 秒钟,然后消失了。就是这样!有趣的部分来了:弄明白刚刚我们所写的究竟是什么意思。

1
extern crate sdl2;

一开始我们先包含了作为依赖添加进来的 sdl2 crate。如果需要,我们可以像 Python 那样,给它另起一个名字。比如这样:

1
extern crate sdl2 as pineapple;

之后我们把代码中出现的所有 sdl2 都换成 pineapple(菠萝) 得到的结果是不变的。虽然我挺喜欢菠萝,接下来的文章里我还是继续使用 sdl2 算了(当然,没人强求 你 也这么做)。有一个类似的语法可以用于重命名被使用(use)的类型(type)和函数(function),我将在第 6 章用到它。

1
2
use sdl2::pixels::Color;
use std::thread;

第一行很简单:因为 sdl2::pixels::Color 敲起来太长了,所以我们只使用(use)这一路径下的最后一个标识符 Color 来代表同一事物。use 语句不仅可以用于类型和函数,对 模块(module)同样有效。这就是第二行的用意,现在我们只需要写 thread::某标函数 而不用写 std::thread::某函数

1
2
3
fn main() {

}

在此,我们声明了作为我们程序的入口的 main 函数。

1
2
let sdl_context = sdl2::init().video()
    .build().unwrap();

现在,开始触及有趣的部分了!init 函数返回一个我们用来初始化 SDL2 的上下文(context)的对象。厉害之处是这里用到了 Rust 版的生成器模式(builder pattern)。就是说,init 返回如下类型的对象:

1
2
3
pub struct InitBuilder {
    flags: u32
}

这一类型提供了一个便于使用 SDL2 API 的接口。通过调用比如像 timer()joystick()video() 这些方法,我们可以选择性的初始化 SDL2 的某些部分,以最小化这个库的占用空间。一旦准备就绪,我们调用 build() 方法,获得 Result<Sdl, String>

使用 Result 类型的对象是 Rust 中进行错误处理(error handling)的方式。得到错误值的可能性被集成到了类型系统中,开发者被强制要求去处理这一情况,从而获取函数的结果。这一次,我们只是简单的通过 unwrap() “解封”其中的值,这意味着我们断定(assert)我们得到的是被封装在 Ok 类型中的正确值,否则的话就不再执行后续的代码。我们之所以这么做,是因为如果我们创建不了上下文,我们就无法渲染我们的游戏,我们的程序就失去了它的意义了。

解封(unwrapping)Err 中的值会把错误信息打印出来并引发恐慌(panic),使得程序安全地崩溃掉。如果被解封的值是 Ok(Sdl) 类型的话,解封出来的值会被直接返回并赋值给 sdl_context

把上下文绑定(binding)到一个标识符的好处是,一旦它脱离了自己的作用域(在 main 函数结束处),它所持有的所有资源都会被自动释放。其实,就算我们的程序运行至某处 panic 了,析构器(destructor)也会被如常调用,以防止内存泄漏。这就是我所指的安全地崩溃掉。

1
2
3
let window = sdl_context.window("ArcadeRS Shooter", 800, 600)
    .position_centered().opengl()
    .build().unwrap();

我们在此处打开了窗口。它有一个名为“ArcadeRS Shooter”的标题,800 象素的宽度 和 600 象素的高度。像前面一样,这里使用了生成器模式,我们可以方便地使窗口居中和激活 OpenGL 模式(更高效)的渲染。

1
2
3
let mut renderer = window.renderer()
    .accelerated()
    .build().unwrap();

这一步你懂的!我们在创建一个与窗口关联的渲染器(renderer),把它设置成可以使用 OpenGL 来辅助渲染,之后我们会用它来“作画”。如果创建失败,程序就会伴随着错误信息 panic 掉。

1
2
3
renderer.set_draw_color(Color::RGB(0, 0, 0));
renderer.clear();
renderer.present();

在第一行,我们把笔刷设置为黑色(red=0,green=0,blue=0)。第二行处,我们先清空缓冲区再填入刚刚选择的笔刷颜色。第三行交换缓冲区正式向用户展现我们绘制的画面。

如果我们去掉最后一行,奇怪的事情就发生了。比如在 Gnome 下,窗口的内容被设置成了它背后的画面。虽然可能听起来怪怪的,按照 Rust 世界的传统,renderer 所提供的接口让我们无法轻易地使程序挂掉。

blank_win

1
thread::sleep_ms(3000);

继续执行程序前,我们在这里先等待 3000 毫秒,即 3 秒钟。也可以看出,此后 main 函数就结束了,在其中分配的所有资源都会被释放掉。在用户看来,就是程序窗口被关掉。对我们来说,也没有太大的不同。美妙的是,即使此处发生了再多的事情,我们也无需为之操心!

你也许注意到了,我们从头到尾都无需写明任何数据类型。当然,我们使用了诸如 sdl2::initColor::new 这些模块函数(module function)和关联函数(associated function)(也许你会对静态方法这个名字更熟悉些),可是我们从来没有告诉 Rust 我们的上下文的类型是 sdl2::Sdl。这被称为 类型推导(type inference),它是使得 Rust 让人愉快地使用的众多特性之一,虽然它们看起来像是无关紧要的附加品。

这就是本系列的第一部分。下一次,我们将提出一种更好的关闭窗口的方式:通过点击那个叉叉(x)。

在那之前,保持 rusting!

【译】ArcadeRs 1.0: 介绍

| Comments

原文:ArcadeRS 1.0: The project

本系列文章的目是通过开发一个简单的老式射击游戏来一探 Rust 这门语言,这是其中的介绍部分。除去介绍,本系列共有 16 个部分组成:

  1. 一个简单的窗口,我们在此安装 SDL2
  2. 事件处理,我们在此讨论生存期限
  3. 处理更多的事件,我们在此讨论宏
  4. 视图,我们在此学习装箱,模式匹配,trait 对象,还有动态分发
  5. 视图的切换,我们在此使用装箱,模式匹配,trait 对象,还有动态分发
  6. 移动的矩形,我们在此绘制图像
  7. Main menu, where we play with textures and Rust’s vectors
  8. Sprites, where we create shareable images
  9. Backgrounds, where we handle resizing, scale and translate through time
  10. The player’s ship, where we control a multi-sprite object
  11. Shooting bullets, where we handle resource pooling
  12. Animated sprites, where we render animated asteroids
  13. Asteroid attack!, where we make multiple objects interact
  14. Explosions, where we see things do boom.
  15. Music, where we hear things go boom.
  16. High score & wrap-up, where we play with the filesystem

这是一篇写给那些哪怕仅仅是通过某个脚本语言,已经对软件开发这门技艺有所了解的人的教程,他们刚刚发现了这头名为 Rust 的怪兽。他们过了一遍官方教程,阅读了大量关于这门看起来熟悉,据说向原有的软件安全概念发起了挑战的语言的博文,然后 excited,想要发掘如何真正地在实践中运用 Rust

我对读者的要求是,他或她(或者它?)应该理解诸如接口,方法,条件结构,和(基础的)静态类型这些概念。多数情况下,这些术语的含义在其上下文中是不言自明的。然而,如果理解它们对你来说很成问题,提醒一下我不会对其作长篇大论的解释。

我还期望你阅读这篇教程前已经学习过一点 Rust,即使只是浅显的部分。比如你得明白 Rustuse 语句,哪怕仅限于在 main.rs 文件里,是用来干什么的。当然,你不需要成为一名专家才能探索其中的微妙。

在接下来的几周里,我们将会基于 Mike Geig 的棒棒哒 2D 游戏开发教程 来制作一个太空射击游戏。我们还会编写一个叫做 phi 的库,它会为我们提供诸如视图(在 LibGDX 中称为 screens),内置碰撞检测(虽然很基础)的矩形,和动态精灵等 2D 游戏所需的组件。

我们将要用到的库是 AngryLawyer 的 SDL2 的绑定,版本为 6.0,你可以在这里找到,以及相关的一些插件。

继续讲述前,我要强调的是你无需精通 Rust 再阅读这篇教程。相反,本教程的目的之一就是探索 Rust 的微妙之处,哪怕不是主要的,基于此,老实说,我不推荐在生产中使用 phi。可是,你至少得浏览过官方教程或者 Rust by Example

基本上,我会直接展示如何使用而不会解释语法。

所以,不多说,我们开始吧

如何在 Python 中操作正则替换的匹配结果

| Comments

在进行字符串的正则替换时,我们可能会希望替换的结果是基于匹配结果的,就是说把匹配的字符串提取出来进行一定的修改再应用回原文。

sed 为例:

1
2
$ echo 'cat dog mouse' | sed 's/dog/\*&\*/'
cat *dog* mouse

& 符号可以把匹配的字符串 "dog" 提取出来,然后在它的前后加上了 *

Python 使用 re 模块时可以这么写:

1
2
3
4
5
6
7
8
import re

def repl(m):
    return '*' + m.group() + '*'

print(re.sub('dog', repl, 'cat dog mouse'))

# 输出:cat *dog* mouse

当传给 re.sub() 方法的第二个参数是一个函数时,re.sub() 会给这个函数传入一个 Match Object 然后以这个函数的返回值作为替换结果。

我们可以通过这个 Match Objectgrou() 方法获取每次执行替换前的匹配结果。这个方法之所以叫做 group ,是因为我们有时会使用多个匹配模板,同时获取多个(一组)匹配结果,再以 sed 为例:

1
2
$ echo 'cat dog mouse' | sed 's/\(dog\) \(mouse\)/\*\1\* _\2_/'
cat *dog* _mouse_

符号 \1\2 对应于第一和第二个括号里我们要提取的字符串。

Python 则如下:

1
2
3
4
5
6
7
8
9
import re

def repl(m):
    return '*' + m.group(1) + '*' + \
    '_' + m.group(2) + '_'

print(re.sub('(dog) (mouse)', repl, 'cat dog mouse'))

# 输出:cat *dog* _mouse_

上一例中的 m.group() 也可以写为 m.goup(0),意为把所有结果作为一个字符串取出。

还有一种跟传统正则表达式比较相似的写法:

1
2
3
4
5
6
import re

def repl(m):
    return '*' + m.expand(r'\1') + '*'

print(re.sub('(dog)', repl, 'cat dog mouse'))

expand() 方法展开表达式,不过似乎不支持符号 &

参考资料

去除全屏下 GVim 出现的白边

| Comments

全屏下 GVim 的左侧和底部会出现白边,可以在 ~/.gtkrc-2.0 文件中加入一下设置:

1
2
3
4
style "vimfix" {
    bg[NORMAL] = "#002B36"
}
widget "vim-main-window.*GtkForm" style "vimfix"


#002B36 这个颜色对应于我使用的 Solarized Dark Vim 配色的背景色,可以根据个人情况修改。

参考资料

IPv6 到 IPv4 的代理

| Comments

为了安装 Pandoc 添加了 haskell-core 这个仓库(AUR 里的包要自己编译太麻烦了),不过要用 Goagent 代理才能访问,本来以为是墙了。

可是问题来了,更新/安装一般的官方仓库的包时我是不希望通过代理的,所以会先注释掉 pacman.conf 里的 haskell-corepacman -Syu,这样一来已安装的 haskell 包会引起依赖问题导致更新无法继续。

无意中想起用 http://cloudmonitor.ca.com/en/ping.php(以前叫 just-ping.com)ping 一下这个仓库的地址(xsounds.org)看看,结果发现这货只支持通过 IPv6 访问!!!而我学校的网络只支持 IPv4。。。

Google 了一番后让我找到了 SixXs 这个服务,提供了 IPv6IPv4 的代理,只要在想要访问的网址后加上 “.ipv4.sixxs.org” 这个后缀就可以了,就像这样:

1
xsounds.org.ipv4.sixxs.org/~haskell/core/$arch

抱着尝试的心态添加到了 pacman.conf 里,结果真的可用!

ps: 通过这样的地址下载时就看不到进度条了,原因不明。
pps: 其实 SixXs 也提供了 IPv4IPv6 的代理服务的,把上面的后缀换成 “.sixxs.org” 或者 “.ipv6.sixxs.org” 就可以了。

解决 Linux 下 KeePass 中文显示的问题

| Comments

Linux 下用 KeePass 中文一直以来都显示为方块,准确的说是点开编辑条目的时候,外面的列表显示倒没有问题,弄得我一直不敢在 Linux 下编辑我的数据文件,每次想要添加新帐号都得开个虚拟机。。。

突然发现原来是 Local 设置的问题,因为我习惯使用英文作为系统语言,所以只要让 KeePass 使用中文 Local 运行就好了。比如 Arch 下安装的官方源里的包的话,在 /usr/bin/keepass 里加入:

1
export LANG=zh_CN.utf8

Linux 版锐捷客户端的 PKGBUILD

| Comments

在愚人节的今天,学校全面强制使用 4.99 版本的锐捷客户端作为上网认证,之前一直在 Arch 下默默辛勤工作的 MentoHUST 就这样挂掉了。。。

锐捷其实也有 Linux 版的,叫做 rjsupplicant,试了一下居然能用,不用离开 Linux 回到 Windows 算是万幸,不过不出所料的无法开多网卡建 WiFi 热点了,也只好如此。

自从学了一下下 PKGBUILD 怎么写,无论多小的程序或脚本,不用 Pacman 管理就不舒服,所以这次也打了个包,文件都在这里

安装好后输入 rjsupplicant --help 查看帮助,我的认证格式如下:

1
sudo rjsupplicant -a1 -d0 -nenp2s0 -u<用户名> -p<密码>
  • -a: 1 的话表示有线连接,0 就是无线
  • -d: 1 使用 dhcp,0 不使用
  • -n: 网卡名

退出可以按 q

打包里还有个 Systemd 的 service 文件,从 AUR 上的 mentohust-bin 里修改而来。安装好后如果要启用,先打开 /usr/lib/systemd/system/rjsupplicant.service,在第 10 行添加上上述的认证参数,想在 makepkg 前添加的话请记得更新 PKGBUILD 里对应的 md5sum 值。

Zip 函数和 Dict Comprehension

| Comments

要下载某个会反盗图的网站上的图片,想到了这篇 Google 的图片代理

通过 Google 来抓取想要的图片就可以逃过反盗图了,主要的工作就是为一系列原图的 url 生成特定的请求链接,所以要做的就是拼凑字符串,机械的任务当然交给机器去做了,Python 实现之。

虽然听起来挺简单,可是以我原有的知识实现起来还是有些繁琐。 需要处理的数据是存储在一个列表里的原图链接,最终想要获得的数据是一系列的文件名和与之一一对应的下载链接,第一反应当然是用字典组织它们了。 请求地址是从原链接拼凑生成的,文件名也是由原链接获得,使用 os.path.basename() 方法。

开始的想法是用两次 List Comprehension,遍历存储原链接的列表,获得两个分别含有文件名和下载地址的新列表,可是怎么把它们分别作为 KeyValue 合成为一个字典呢?我可不要循环操作下标。。。。Google 之,知道了 zip 函数,用法如下:

1
2
3
4
5
>>> keys = ['a', 'b', 'c']
>>> values = [1, 2, 3]
>>> dictionary = dict(zip(keys, values))
>>> print dictionary
{'a': 1, 'b': 2, 'c': 3}


zip() 会返回一系列的元组,然后通过 dct() 生成一个字典。

可是两次遍历的都是同一个列表,难道就不能在同一次遍历中完成上面的任务吗 (“▔□▔),好吧,原来还有 Dict Comprehension 这个东西:

  • 如果需要遍历的 sequence 是一个列表,那么
1
d = {l : l for l in list}
  • 如果 sequence 是一个字典
1
d = {key : value for (key, value) in dictionary}
  • 另外,在 2.6 及以下的 Python 中,需要使用 dict 构造函数
1
d = dict(... for ... in sequence)

再复习一下如何用 for in 迭代一个字典分别获取其中的键和值:

1
2
for key, value in d.iteritems():
    ...

Python 3.x 中使用的是 items(), Python 2.x 中也有这个方法,不过返回的是包含一对键、值的元组。

最后,贴一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/python

import urllib2, sys, os

def inputlinks():
    if len(sys.argv) == 1:
        links = raw_input("Enter links :\n").split()
    else:
        if sys.argv[1] == "-i":
            with open(sys.argv[2], 'r') as f:
                links = f.read().splitlines()
        else:
            links = sys.argv[1:]
    return links

def generate(links):
    prefix = r"https://images2-focus-opensocial.googleusercontent.com/gadgets/proxy?url="
    suffix = r"&container=focus&gadget=a&no_expand=1&resize_h=0&rewriteMime=image%2F*"
    images = {os.path.basename(i) : prefix + i + suffix for i in links}
    return images

def download(images):
    for name, image in images.iteritems():
        try:
            imgdata = urllib2.urlopen(image).read()
            with open(name, 'wb') as output:
                output.write(imgdata)
        except:
            print "Er..."

if __name__ == "__main__":
    download(generate(inputlinks()))

参考资料

Nexus 7 升级 4.4 KitKat 后重新提权

| Comments

今天我的 N7 二代终于收到 OTA 了,升级后又要重新获取 Root 权限,之前这些工作一直用 Windows 下的 Nexus Root Toolkit 完成,现在尝试在 Linux 下解决。

之所以升级后 Root 权限没了,是因为 su 文件被移除了,所以现在只需要重刷一次 SuperSU 的升级包。刷升级包需要第三方 Recovery,之前 Root 机子时并没有把它直接刷进机器里,而是采用临时启动的方式,现在也用这个方法。

  • 先要确保电脑上有 adb 和 fastboot, Arch 下可以安装 aur 上的 android-sdk-platform-tools 包。
  • 然后下载 SuperSU 的升级包TWRP
  • 通过 USB 数据线连接设备到电脑。
  • 把 SuperSU 升级包直接复制到 N7 上,选一个方便找到的路径。
  • 打开终端,cd 到 Recovery 镜像所在的目录。
  • 使用 adb 重启到 BootLoader:
1
$ adb reboot-bootloader

  • 出现 BootLoader 介面后先看看设备能否被识别:
1
$ fastboot devices

  • 没问题的话就可以启动镜像了:
1
$ fastboot boot ./openrecovery-twrp-2.6.3.1-flo.img

  • 成功进入 TWRP 后点 Install,选择 SuperSU 升级包,刷之。返回主介面,Reboot -> System,OK !

ps :
之前之所以不刷入 Recovery,是因为那时的 TWRP 还不支持 N7 二代的分辨率,整个介面缩在了一边,所以其实是刷过一次觉得很难看又删掉了 ╮( ̄▽ ̄”)╭ 。这次倒是发现已经支持了,不过不想刷了,觉得没必要,想要尽量保持机子的纯净。

刚刚才知道,原来 SuperSU 设置里有一个 叫 Survival mode 的功能,就是用来防止系统 OTA 更新后权限丢失的。不过不能保证百分百有效而且是付费版才能用,我倒是装了个网上找的 key,这次把它勾上,期待下次更新,看有没有效 o((≧▽≦o)

2013-12-12 update: 更新到 4.4.2,Survival mode 生效了^_^)y

参考资料

一个刷机包里的批处理文件,来自a860官方和临时Recovery互刷工具

Linux 下浏览器里使用退格键返回上一页

| Comments

这里只针对 Firefox 和 Chrome,Linux 下它们默认都不能用退格键作为返回历史上一页的快捷键,Opera 倒是可以。

Firefox :
在 about:config 里查找到 “browser.backspace_action” 这一项,把它的值设置为 0 就可以了。1 代表向上滚动页面(此时要向下滚动的话可以按 Shift 加退格键),我的 fx 默认被设为了 2,按退格键没任何反应。

Chrome :
需要安装一个扩展,叫做 Backspace As Back/Forward for Linux。安装后在它的设置页勾选 “Activate Backspace for navigation in history object” 这一项,里面还可以设置例外,对某些网页不启用这一功能。

参考资料