From 9b8af9305ab8da75fe8cdafa6c72a9539787c3fc Mon Sep 17 00:00:00 2001 From: wenchunyang Date: Mon, 4 Jul 2016 11:05:23 +0800 Subject: [PATCH 1/8] =?UTF-8?q?=C3=A2Finish=20tranlating=20awk=20series=20?= =?UTF-8?q?part4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ... Comparison Operators with Awk in Linux.md | 97 ------------------- ... Comparison Operators with Awk in Linux.md | 95 ++++++++++++++++++ 2 files changed, 95 insertions(+), 97 deletions(-) delete mode 100644 sources/tech/awk/Part 4 - How to Use Comparison Operators with Awk in Linux.md create mode 100644 translated/tech/awk/Part 4 - How to Use Comparison Operators with Awk in Linux.md diff --git a/sources/tech/awk/Part 4 - How to Use Comparison Operators with Awk in Linux.md b/sources/tech/awk/Part 4 - How to Use Comparison Operators with Awk in Linux.md deleted file mode 100644 index 1d0ef007af..0000000000 --- a/sources/tech/awk/Part 4 - How to Use Comparison Operators with Awk in Linux.md +++ /dev/null @@ -1,97 +0,0 @@ -chunyang-wen translating - -How to Use Comparison Operators with Awk in Linux -=================================================== - -![](http://www.tecmint.com/wp-content/uploads/2016/05/Use-Comparison-Operators-with-AWK.png) - -When dealing with numerical or string values in a line of text, filtering text or strings using comparison operators comes in handy for Awk command users. - -In this part of the Awk series, we shall take a look at how you can filter text or strings using comparison operators. If you are a programmer then you must already be familiar with comparison operators but those who are not, let me explain in the section below. - -### What are Comparison operators in Awk? - -Comparison operators in Awk are used to compare the value of numbers or strings and they include the following: - -- `>` – greater than -- `<` – less than -- `>=` – greater than or equal to -- `<=` – less than or equal to -- `==` – equal to -- `!=` – not equal to -- `some_value ~ / pattern/` – true if some_value matches pattern -- `some_value !~ / pattern/` – true if some_value does not match pattern - -Now that we have looked at the various comparison operators in Awk, let us understand them better using an example. - -In this example, we have a file named food_list.txt which is a shopping list for different food items and I would like to flag food items whose quantity is less than or equal 20 by adding `(**)` at the end of each line. - -``` -File – food_list.txt -No Item_Name Quantity Price -1 Mangoes 45 $3.45 -2 Apples 25 $2.45 -3 Pineapples 5 $4.45 -4 Tomatoes 25 $3.45 -5 Onions 15 $1.45 -6 Bananas 30 $3.45 -``` - -The general syntax for using comparison operators in Awk is: - -``` -# expression { actions; } -``` - -To achieve the above goal, I will have to run the command below: - -``` -# awk '$3 <= 30 { printf "%s\t%s\n", $0,"**" ; } $3 > 30 { print $0 ;}' food_list.txt - -No Item_Name` Quantity Price -1 Mangoes 45 $3.45 -2 Apples 25 $2.45 ** -3 Pineapples 5 $4.45 ** -4 Tomatoes 25 $3.45 ** -5 Onions 15 $1.45 ** -6 Bananas 30 $3.45 ** -``` - -In the above example, there are two important things that happen: - -- The first expression `{ action ; }` combination, `$3 <= 30 { printf “%s\t%s\n”, $0,”**” ; }` prints out lines with quantity less than or equal to 30 and adds a `(**)` at the end of each line. The value of quantity is accessed using `$3` field variable. -- The second expression `{ action ; }` combination, `$3 > 30 { print $0 ;}` prints out lines unchanged since their quantity is greater then `30`. - -One more example: - -``` -# awk '$3 <= 20 { printf "%s\t%s\n", $0,"TRUE" ; } $3 > 20 { print $0 ;} ' food_list.txt - -No Item_Name Quantity Price -1 Mangoes 45 $3.45 -2 Apples 25 $2.45 -3 Pineapples 5 $4.45 TRUE -4 Tomatoes 25 $3.45 -5 Onions 15 $1.45 TRUE -6 Bananas 30 $3.45 -``` - -In this example, we want to indicate lines with quantity less or equal to 20 with the word (TRUE) at the end. - -### Summary - -This is an introductory tutorial to comparison operators in Awk, therefore you need to try out many other options and discover more. - -In case of any problems you face or any additions that you have in mind, then drop a comment in the comment section below. Remember to read the next part of the Awk series where I will take you through compound expressions. - --------------------------------------------------------------------------------- - -via: http://www.tecmint.com/comparison-operators-in-awk/ - -作者:[Aaron Kili][a] -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]: http://www.tecmint.com/author/aaronkili/ diff --git a/translated/tech/awk/Part 4 - How to Use Comparison Operators with Awk in Linux.md b/translated/tech/awk/Part 4 - How to Use Comparison Operators with Awk in Linux.md new file mode 100644 index 0000000000..ef4f17f1d5 --- /dev/null +++ b/translated/tech/awk/Part 4 - How to Use Comparison Operators with Awk in Linux.md @@ -0,0 +1,95 @@ +在 Linux 下如何使用 Awk 比较操作符 +=================================================== + +![](http://www.tecmint.com/wp-content/uploads/2016/05/Use-Comparison-Operators-with-AWK.png) + +对于 Awk 命令的用户来说,处理一行文本中的数字或者字符串时,使用比较运算符来过滤文本和字符串是十分方便的。 + +在 Awk 系列的此部分中,我们将探讨一下如何使用比较运算符来过滤文本或者字符串。如果你是程序员,那么你应该已经熟悉比较运算符;对于其它人,下面的部分将介绍比较运算符。 + +### Awk 中的比较运算符是什么? + +Awk 中的比较运算符用于比较字符串和或者数值,包括以下类型: + +- `>` – 大于 +- `<` – 小于 +- `>=` – 大于等于 +- `<=` – 小于等于 +- `==` – 等于 +- `!=` – 不等于 +- `some_value ~ / pattern/` – 如果some_value匹配模式pattern,则返回true +- `some_value !~ / pattern/` –如果some_value不匹配模式pattern,则返回true + +现在我们通过例子来熟悉 Awk 中各种不同的比较运算符。 + +在这个例子中,我们有一个文件名为 food_list.txt 的文件,里面包括不同食物的购买列表。我想给食物数量小于或等于30的物品所在行的后面加上`(**)` + +``` +File – food_list.txt +No Item_Name Quantity Price +1 Mangoes 45 $3.45 +2 Apples 25 $2.45 +3 Pineapples 5 $4.45 +4 Tomatoes 25 $3.45 +5 Onions 15 $1.45 +6 Bananas 30 $3.45 +``` + +Awk 中使用比较运算符的通用语法如下: + +``` +# expression { actions; } +``` + +为了实现刚才的目的,执行下面的命令: + +``` +# awk '$3 <= 30 { printf "%s\t%s\n", $0,"**" ; } $3 > 30 { print $0 ;}' food_list.txt + +No Item_Name` Quantity Price +1 Mangoes 45 $3.45 +2 Apples 25 $2.45 ** +3 Pineapples 5 $4.45 ** +4 Tomatoes 25 $3.45 ** +5 Onions 15 $1.45 ** +6 Bananas 30 $3.45 ** +``` + +在刚才的例子中,发生如下两件重要的事情: + +- 第一表达式 `{ action ; }` 组合, `$3 <= 30 { printf “%s\t%s\n”, $0,”**” ; }` 打印出数量小于等于30的行,并且在后面增加`(**)`。物品的数量是通过 `$3`这个域变量获得的。 +- 第二个表达式 `{ action ; }` 组合, `$3 > 30 { print $0 ;}` 原样输出数量小于等于 `30` 的行。 + +再举一个例子: + +``` +# awk '$3 <= 20 { printf "%s\t%s\n", $0,"TRUE" ; } $3 > 20 { print $0 ;} ' food_list.txt + +No Item_Name Quantity Price +1 Mangoes 45 $3.45 +2 Apples 25 $2.45 +3 Pineapples 5 $4.45 TRUE +4 Tomatoes 25 $3.45 +5 Onions 15 $1.45 TRUE +6 Bananas 30 $3.45 +``` + +在这个例子中,我们想通过在行的末尾增加 (TRUE) 来标记数量小于等于20的行。 + +### 总结 + +这是一篇对 Awk 中的比较运算符介绍性的指引,因此你需要尝试其他选项,发现更多使用方法。 + +如果你遇到或者想到任何问题,请在下面评论区留下评论。请记得阅读 Awk 系列下一部分的文章,那里我将介绍组合表达式。 + +-------------------------------------------------------------------------------- + +via: http://www.tecmint.com/comparison-operators-in-awk/ + +作者:[Aaron Kili][a] +译者:[chunyang-wen](https://github.com/chunyang-wen) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: http://www.tecmint.com/author/aaronkili/ From 1a2ad60c48090971b924cd3e96d86095df3c23d3 Mon Sep 17 00:00:00 2001 From: Chunyang Wen Date: Mon, 4 Jul 2016 11:15:31 +0800 Subject: [PATCH 2/8] Update Part 4 - How to Use Comparison Operators with Awk in Linux.md --- ...t 4 - How to Use Comparison Operators with Awk in Linux.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/translated/tech/awk/Part 4 - How to Use Comparison Operators with Awk in Linux.md b/translated/tech/awk/Part 4 - How to Use Comparison Operators with Awk in Linux.md index ef4f17f1d5..86649a52d5 100644 --- a/translated/tech/awk/Part 4 - How to Use Comparison Operators with Awk in Linux.md +++ b/translated/tech/awk/Part 4 - How to Use Comparison Operators with Awk in Linux.md @@ -18,7 +18,7 @@ Awk 中的比较运算符用于比较字符串和或者数值,包括以下类 - `==` – 等于 - `!=` – 不等于 - `some_value ~ / pattern/` – 如果some_value匹配模式pattern,则返回true -- `some_value !~ / pattern/` –如果some_value不匹配模式pattern,则返回true +- `some_value !~ / pattern/` – 如果some_value不匹配模式pattern,则返回true 现在我们通过例子来熟悉 Awk 中各种不同的比较运算符。 @@ -57,7 +57,7 @@ No Item_Name` Quantity Price 在刚才的例子中,发生如下两件重要的事情: -- 第一表达式 `{ action ; }` 组合, `$3 <= 30 { printf “%s\t%s\n”, $0,”**” ; }` 打印出数量小于等于30的行,并且在后面增加`(**)`。物品的数量是通过 `$3`这个域变量获得的。 +- 第一个表达式 `{ action ; }` 组合, `$3 <= 30 { printf “%s\t%s\n”, $0,”**” ; }` 打印出数量小于等于30的行,并且在后面增加`(**)`。物品的数量是通过 `$3`这个域变量获得的。 - 第二个表达式 `{ action ; }` 组合, `$3 > 30 { print $0 ;}` 原样输出数量小于等于 `30` 的行。 再举一个例子: From 80cc470eef1d07cf5dae5d0c513f72d9649c26de Mon Sep 17 00:00:00 2001 From: wenchunyang Date: Tue, 5 Jul 2016 10:03:18 +0800 Subject: [PATCH 3/8] translating: How to Hide Linux Command Line History by Going Incognito --- ... How to Hide Linux Command Line History by Going Incognito.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sources/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md b/sources/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md index 274ff3b175..0d5ce6e6e1 100644 --- a/sources/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md +++ b/sources/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md @@ -1,3 +1,4 @@ +chunyang-wen translating How to Hide Linux Command Line History by Going Incognito ================================================================ From 4e3cf59a1987f19f1f2240882da4a73309b9c92a Mon Sep 17 00:00:00 2001 From: wenchunyang Date: Tue, 12 Jul 2016 13:49:34 +0800 Subject: [PATCH 4/8] finish translating How to Hide Linux Command Line History by Going Incognito --- ...Command Line History by Going Incognito.md | 125 ------------------ ...Command Line History by Going Incognito.md | 124 +++++++++++++++++ 2 files changed, 124 insertions(+), 125 deletions(-) delete mode 100644 sources/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md create mode 100644 translated/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md diff --git a/sources/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md b/sources/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md deleted file mode 100644 index 0d5ce6e6e1..0000000000 --- a/sources/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md +++ /dev/null @@ -1,125 +0,0 @@ -chunyang-wen translating -How to Hide Linux Command Line History by Going Incognito -================================================================ - -![](https://maketecheasier-2d0f.kxcdn.com/assets/uploads/2016/06/commandline-history-featured.jpg) - -If you’re a Linux command line user, you’ll agree that there are times when you do not want certain commands you run to be recorded in the command line history. There could be many reasons for this. For example, you’re at a certain position in your company, and you have some privileges that you don’t want others to abuse. Or, there are some critical commands that you don’t want to run accidentally while you’re browsing the history list. - -But is there a way to control what goes into the history list and what doesn’t? Or, in other words, can we turn on a web browser-like incognito mode in the Linux command line? The answer is yes, and there are many ways to achieve this, depending on what exactly you want. In this article we will discuss some of the popular solutions available. - -Note: all the commands presented in this article have been tested on Ubuntu. - -### Different ways available - -The first two ways we’ll describe here have already been covered in [one of our previous articles][1]. If you are already aware of them, you can skip over these. However, if you aren’t aware, you’re advised to go through them carefully. - -#### 1. Insert space before command - -Yes, you read it correctly. Insert a space in the beginning of a command, and it will be ignored by the shell, meaning the command won’t be recorded in history. However, there’s a dependency – the said solution will only work if the HISTCONTROL environment variable is set to “ignorespace” or “ignoreboth,” which is by default in most cases. - -So, a command like the following: - -``` -[space]echo "this is a top secret" -``` - -Won’t appear in the history if you’ve already done this command: - -``` -export HISTCONTROL = ignorespace -``` - -The below screenshot is an example of this behavior. - -![](https://maketecheasier-2d0f.kxcdn.com/assets/uploads/2016/06/commandline-history-bash-command-space.png) - -The fourth “echo” command was not recorded in the history as it was run with a space in the beginning. - -#### 2. Disable the entire history for the current session - -If you want to disable the entire history for a session, you can easily do that by unsetting the HISTSIZE environment variable before you start with your command line work. To unset the variable run the following command: - -``` -export HISTFILE=0 -``` - -HISTFILE is the number of lines (or commands) that can be stored in the history list for an ongoing bash session. By default, this variable has a set value – for example, 1000 in my case. - -So, the command mentioned above will set the environment variable’s value to zero, and consequently nothing will be stored in the history list until you close the terminal. Keep in mind that you’ll also not be able to see the previously run commands by pressing the up arrow key or running the history command. - -#### 3. Erase the entire history after you’re done - -This can be seen as an alternative to the solution mentioned in the previous section. The only difference is that in this case you run a command AFTER you’re done with all your work. Thh following is the command in question: - -``` -history -cw -``` - -As already mentioned, this will have the same effect as the HISTFILE solution mentioned above. - -#### 4. Turn off history only for the work you do - -While the solutions (2 and 3) described above do the trick, they erase the entire history, something which might be undesired in many situations. There might be cases in which you want to retain the history list up until the point you start your command line work. For situations like these you need to run the following command before starting with your work: - -``` -[space]set +o history -``` - -Note: [space] represents a blank space. - -The above command will disable the history temporarily, meaning whatever you do after running this command will not be recorded in history, although all the stuff executed prior to the above command will be there as it is in the history list. - -To re-enable the history, run the following command: - -``` -[Space]set -o history -``` - -This brings things back to normal again, meaning any command line work done after the above command will show up in the history. - -#### 5. Delete specific commands from history - -Now suppose the history list already contains some commands that you didn’t want to be recorded. What can be done in this case? It’s simple. You can go ahead and remove them. The following is how to accomplish this: - -``` -[space]history | grep "part of command you want to remove" -``` - -The above command will output a list of matching commands (that are there in the history list) with a number [num] preceding each of them. - -Once you’ve identified the command you want to remove, just run the following command to remove that particular entry from the history list: - -``` -history -d [num] -``` - -The following screenshot is an example of this. - -![](https://maketecheasier-2d0f.kxcdn.com/assets/uploads/2016/06/commandline-history-delete-specific-commands.png) - -The second ‘echo’ command was removed successfully. - -Alternatively, you can just press the up arrow key to take a walk back through the history list, and once the command of your interest appears on the terminal, just press “Ctrl + U” to totally blank the line, effectively removing it from the list. - -### Conclusion - -There are multiple ways in which you can manipulate the Linux command line history to suit your needs. Keep in mind, however, that it’s usually not a good practice to hide or remove a command from history, although it’s also not wrong, per se, but you should be aware of what you’re doing and what effects it might have. - --------------------------------------------------------------------------------- - -via: https://www.maketecheasier.com/linux-command-line-history-incognito/?utm_medium=feed&utm_source=feedpress.me&utm_campaign=Feed%3A+maketecheasier - -作者:[Himanshu Arora][a] -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]: https://www.maketecheasier.com/author/himanshu/ -[1]: https://www.maketecheasier.com/command-line-history-linux/ - - - - - diff --git a/translated/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md b/translated/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md new file mode 100644 index 0000000000..83e7a33e8b --- /dev/null +++ b/translated/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md @@ -0,0 +1,124 @@ +如何进入无痕模式进而隐藏 Linux 的命令行历史 +================================================================ + +![](https://maketecheasier-2d0f.kxcdn.com/assets/uploads/2016/06/commandline-history-featured.jpg) + +如果你是 Linux 命令行的用户,你会同意有的时候你不希望某些命令记录在你的命令行历史中。其中原因可能很多。例如,你在公司处于某个职位,你有一些不希望被其它人滥用的特权。亦或者有些特别重要的命令,你不希望在你浏览历史列表时误执行。 + +然而,有方法可以控制哪些命令进入历史列表,哪些不进入吗?或者换句话说,我们在 Linux 终端中可以开启像浏览器一样的无痕模式吗?答案是肯定的,而且根据你想要的具体目标,有很多实现方法。在这篇文章中,我们将讨论一些行之有效的方法。 + +注意:文中出现的所有命令都在 Ubuntu 下测试过。 + +### 不同的可行方法 + +前面两种方法已经在之前[一篇文章][1]中描述了。如果你已经了解,这部分可以略过。然而,如果你不了解,建议仔细阅读。 + +#### 1. 在命令前插入空格 + +是的,没看错。在命令前面插入空格,这条命令会被终端忽略,也就意味着它不会出现在历史记录中。但是这种方法有个前提,只有在你的环境变量 HISTCONTROL 设置为 "ignorespace" 或者 "ignoreboth" 才会起作用。在大多数情况下,这个是默认值。 + +所以,像下面的命令: + +``` +[space]echo "this is a top secret" +``` + +你执行后,它不会出现在历史记录中。 + +``` +export HISTCONTROL = ignorespace +``` + +下面的截图是这种方式的一个例子。 + +![](https://maketecheasier-2d0f.kxcdn.com/assets/uploads/2016/06/commandline-history-bash-command-space.png) + +第四个 "echo" 命令因为前面有空格,它没有被记录到历史中。 + +#### 2. 禁用当前会话的所有历史记录 + +如果你想禁用某个会话所有历史,你可以简单地在开始命令行工作前清除环境变量 HISTSIZE 的值。执行下面的命令来清除其值: + +``` +export HISTFILE=0 +``` + +HISTFILE 表示对于 bash 会话其历史中可以保存命令的个数。默认情况,它设置了一个非零值,例如 在我的电脑上,它的值为 1000。 + +所以上面所提到的命令将其值设置为 0,结果就是直到你关闭终端,没有东西会存储在历史记录中。记住同样你也不能通过按向上的箭头按键来执行之前的命令,也不能运行 history 命令。 + +#### 3. 工作结束后清除整个历史 + +这可以看作是前一部分所提方案的另外一种实现。唯一的区别是在你完成所有工作之后执行这个命令。下面是刚讨论的命令: + +``` +history -cw +``` + +刚才已经提到,这个和 HISTFILE 方法有相同效果。 + +#### 4. 只针对你的工作关闭历史记录 + +虽然前面描述的方法(2 和 3)可以实现目的,它们清除整个历史,在很多情况下,有些可能不是我们所期望的。有时候你可能想保存直到你开始命令行工作之间的历史记录。类似的需求需要在你开始工作前执行下述命令: + +``` +[space]set +o history +``` + +备注:[space] 表示空格。 + +上面的命令会临时禁用历史功能,这意味着在这命令之后你执行的所有操作都不会记录到历史中,然而这个命令之前的所有东西都会原样记录在历史列表中。 + +要重新开启历史功能,执行下面的命令: + +``` +[Space]set -o history +``` + +它将环境恢复原状,也就是你完成你的工作,执行上述命令之后的命令都会出现在历史中。 + +#### 5. 从历史记录中删除指定的命令 + +现在假设历史记录中有一些命令你不希望被记录。这种情况下我们怎么办?很简单。直接动手删除它们。通过下面的命令来删除: + +``` +[space]history | grep "part of command you want to remove" +``` + +上面的命令会输出历史记录中匹配的命令,每一条前面会有个数字。 + +一旦你找到你想删除的命令,执行下面的命令,从历史记录中删除那个指定的项: + +``` +history -d [num] +``` + +下面是这个例子的截图。 + +![](https://maketecheasier-2d0f.kxcdn.com/assets/uploads/2016/06/commandline-history-delete-specific-commands.png) + +第二个 ‘echo’命令被成功的删除了。 + +同样的,你可以使用向上的箭头一直往回翻看历史记录。当你发现你感兴趣的命令出现在终端上时,按下 “Ctrl + U”清除整行,也会从历史记录中删除它。 + +### 总结 + +有多种不同的方法可以操作 Linux 命令行历史来满足你的需求。然而请记住,从历史中隐藏或者删除命令通常不是一个好习惯,尽管本质上这并没有错。但是你必须知道你在做什么,以及可能产生的后果。 + +-------------------------------------------------------------------------------- + +via: https://www.maketecheasier.com/linux-command-line-history-incognito/?utm_medium=feed&utm_source=feedpress.me&utm_campaign=Feed%3A+maketecheasier + +作者:[Himanshu Arora][a] +译者:[译者ID](https://github.com/chunyang-wen) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://www.maketecheasier.com/author/himanshu/ +[1]: https://www.maketecheasier.com/command-line-history-linux/ + + + + + From 140cf4068722acd5c2bffb12f86ff536dbfb110e Mon Sep 17 00:00:00 2001 From: wenchunyang Date: Tue, 12 Jul 2016 13:52:18 +0800 Subject: [PATCH 5/8] update translator --- ...How to Hide Linux Command Line History by Going Incognito.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translated/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md b/translated/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md index 83e7a33e8b..9ef701559b 100644 --- a/translated/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md +++ b/translated/tech/20160625 How to Hide Linux Command Line History by Going Incognito.md @@ -110,7 +110,7 @@ history -d [num] via: https://www.maketecheasier.com/linux-command-line-history-incognito/?utm_medium=feed&utm_source=feedpress.me&utm_campaign=Feed%3A+maketecheasier 作者:[Himanshu Arora][a] -译者:[译者ID](https://github.com/chunyang-wen) +译者:[chunyang-wen](https://github.com/chunyang-wen) 校对:[校对者ID](https://github.com/校对者ID) 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 From 24f6895b50bab921ac74cdd4fad72ec715333829 Mon Sep 17 00:00:00 2001 From: wenchunyang Date: Wed, 13 Jul 2016 09:59:55 +0800 Subject: [PATCH 6/8] translating game part2 and 3 --- ...g online multiplayer game with python and asyncio - part 2.md | 1 + ...g online multiplayer game with python and asyncio - part 3.md | 1 + 2 files changed, 2 insertions(+) diff --git a/sources/tech/20160531 Writing online multiplayer game with python and asyncio - part 2.md b/sources/tech/20160531 Writing online multiplayer game with python and asyncio - part 2.md index a33b0b0fdc..75df13d9b5 100644 --- a/sources/tech/20160531 Writing online multiplayer game with python and asyncio - part 2.md +++ b/sources/tech/20160531 Writing online multiplayer game with python and asyncio - part 2.md @@ -1,3 +1,4 @@ +chunyang-wen translating Writing online multiplayer game with python and asyncio - Part 2 ================================================================== diff --git a/sources/tech/20160606 Writing online multiplayer game with python and asyncio - part 3.md b/sources/tech/20160606 Writing online multiplayer game with python and asyncio - part 3.md index 90cf71c466..3c38af72ac 100644 --- a/sources/tech/20160606 Writing online multiplayer game with python and asyncio - part 3.md +++ b/sources/tech/20160606 Writing online multiplayer game with python and asyncio - part 3.md @@ -1,3 +1,4 @@ +chunyang-wen translating Writing online multiplayer game with python and asyncio - Part 3 ================================================================= From ff2d4808b04f8638121119a14e64ecac87a70279 Mon Sep 17 00:00:00 2001 From: wenchunyang Date: Tue, 13 Sep 2016 13:20:38 +0800 Subject: [PATCH 7/8] finish part2 & part3 --- ...r game with python and asyncio - part 2.md | 234 ++++++++++++++++++ ...r game with python and asyncio - part 3.md | 138 +++++++++++ 2 files changed, 372 insertions(+) create mode 100644 translated/tech/20160531 Writing online multiplayer game with python and asyncio - part 2.md create mode 100644 translated/tech/20160606 Writing online multiplayer game with python and asyncio - part 3.md diff --git a/translated/tech/20160531 Writing online multiplayer game with python and asyncio - part 2.md b/translated/tech/20160531 Writing online multiplayer game with python and asyncio - part 2.md new file mode 100644 index 0000000000..73dd55d516 --- /dev/null +++ b/translated/tech/20160531 Writing online multiplayer game with python and asyncio - part 2.md @@ -0,0 +1,234 @@ +使用 Python 和 asyncio 编写在线多用人游戏 - 第2部分 +================================================================== + +![](https://7webpages.com/media/cache/fd/d1/fdd1f8f8bbbf4166de5f715e6ed0ac00.gif) + +你曾经写过异步的 Python 程序吗?这里我将告诉你如果如何做,而且在接下来的部分用一个[实例][1] - 专为多玩家设计的、受欢迎的贪吃蛇游戏来演示。 + +介绍和理论部分参见第一部分[异步化[第1部分]][2]。 + +试玩游戏[3]。 + +### 3. 编写游戏循环主体 + +游戏循环是每一个游戏的核心。它持续地读取玩家的输入,更新游戏的状态,并且在屏幕上渲染游戏结果。在在线游戏中,游戏循环分为客户端和服务端两部分,所以一般有两个循环通过网络通信。通常客户端的角色是获取玩家输入,比如按键或者鼠标移动,将数据传输给服务端,然后接收需要渲染的数据。服务端处理来自玩家的所有数据,更新游戏的状态,执行渲染下一帧的必要计算,然后将结果传回客户端,例如游戏中对象的位置。如果没有可靠的理由,不混淆客户端和服务端的角色很重要。如果你在客户端执行游戏逻辑的计算,很容易就会和其它客户端失去同步,其实你的游戏也可以通过简单地传递客户端的数据来创建。 + +游戏循环的一次迭代称为一个嘀嗒。嘀嗒表示当前游戏循环的迭代已经结束,下一帧(或者多帧)的数据已经就绪。在后面的例子中,我们使用相同的客户端,使用 WebSocket 连接服务端。它执行一个简单的循环,将按键码发送给服务端,显示来自服务端的所有信息。[客户端代码戳这里][4]。 + +#### 例子3.1:基本游戏循环 + +[例子3.1源码][5]。 + +我们使用 [aiohttp][6] 库来创建游戏服务器。它可以通过 asyncio 创建网页服务器和客户端。这个库的一个优势是它同时支持普通 http 请求和 websocket。所以我们不用其他网页服务器来渲染游戏的 html 页面。 + +下面是启动服务器的方法: + +``` +app = web.Application() +app["sockets"] = [] + +asyncio.ensure_future(game_loop(app)) + +app.router.add_route('GET', '/connect', wshandler) +app.router.add_route('GET', '/', handle) + +web.run_app(app) +``` + +`web.run_app` 是创建服务主任务的快捷方法,通过他的 `run_forever()` 方法来执行 asyncio 事件循环。建议你查看这个方法的源码,弄清楚服务器到底是如何创建和结束的。 + +`app` 变量就是一个类似于字典的对象,它可以在所连接的客户端之间共享数据。我们使用它来存储连接套接字的列表。随后会用这个列表来给所有连接的客户端发送消息。`asyncio.ensure_future()` 调用会启动主游戏循环的任务,每隔2s向客户端发送嘀嗒消息。这个任务会在同样的 asyncio 事件循环中和网页服务器并行执行。 + +有两个网页请求处理器:提供 html 页面的处理器 (`handle`);`wshandler` 是主要的 websocket 服务器任务,处理和客户端之间的交互。在事件循环中,每一个连接的客户端都会创建一个新的 `wshandler`。 + +在启动的任务中,我们在 asyncio 的主事件循环中启动 worker 循环。任务之间的切换发生在他们任何一个使用 `await`语句来等待某个协程结束。例如 `asyncio.sleep` 仅仅是将程序执行权交给调度器指定的时间;`ws.receive` 等待 websocket 的消息,此时调度器可能切换到其它任务。 + +在浏览器中打开主页,连接上服务器后,试试随便按下键。他们的键值会从服务端返回,每隔2秒这个数字会被游戏循环发给所有客户端的嘀嗒消息覆盖。 + +我们刚刚创建了一个处理客户端按键的服务器,主游戏循环在后台做一些处理,周期性地同时更新所有的客户端。 + +#### 例子 3.2: 根据请求启动游戏 + +[例子 3.2的源码][7] + +在前一个例子中,在服务器的生命周期内,游戏循环一直运行着。但是现实中,如果没有一个人连接服务器,空运行游戏循环通常是不合理的。而且,同一个服务器上可能有不同的’游戏房间‘。在这种假设下,每一个玩家创建一个游戏会话(多人游戏中的一个比赛或者大型多人游戏中的副本),这样其他用户可以加入其中。当游戏会话开始时,游戏循环才开始执行。 + +在这个例子中,我们使用一个全局标记来检测游戏循环是否在执行。当第一个用户发起连接时,启动它。最开始,游戏循环不在执行,标记设置为 `False`。游戏循环是通过客户端的处理方法启动的。 + +``` + if app["game_is_running"] == False: + asyncio.ensure_future(game_loop(app)) +``` + +当游戏的循环(`loop()`)运行时,这个标记设置为 `True`;当所有客户端都断开连接时,其又被设置为 `False`。 + +#### 例子 3.3:管理任务 + +[例子3.3源码][8] + +这个例子用来解释如何和任务对象协同工作。我们把游戏循环的任务直接存储在游戏循环的全局字典中,代替标记的使用。在这个简单例子中并不一定是最优的,但是有时候你可能需要控制所有已经启动的任务。 + +``` + if app["game_loop"] is None or \ + app["game_loop"].cancelled(): + app["game_loop"] = asyncio.ensure_future(game_loop(app)) +``` + +这里 `ensure_future()` 返回我们存放在全局字典中的任务对象,当所有用户都断开连接时,我们使用下面方式取消任务: + +``` + app["game_loop"].cancel() +``` + +这个 `cancel()` 调用将通知所有的调度器不要向这个协程提交任何执行任务,而且将它的状态设置为已取消,之后可以通过 `cancelled()` 方法来检查是否已取消。这里有一个值得一提的小注意点:当你持有一个任务对象的外部引用时,而这个任务执行中抛出了异常,这个异常不会抛出。取而代之的是为这个任务设置一个异常状态,可以通过 `exception()` 方法来检查是否出现了异常。这种悄无声息地失败在调试时不是很有用。所以,你可能想用抛出所有异常来取代这种做法。你可以对所有未完成的任务显示地调用 `result()` 来实现。可以通过如下的回调来实现: + +``` + app["game_loop"].add_done_callback(lambda t: t.result()) +``` + +如果我们打算在我们代码中取消任务,但是又不想产生 `CancelError` 异常,有一个检查 `cancelled` 状态的点: + +``` + app["game_loop"].add_done_callback(lambda t: t.result() + if not t.cancelled() else None) +``` + +注意仅当你持有任务对象的引用时必须要这么做。在前一个例子,所有的异常都是没有额外的回调,直接抛出所有异常。 + +#### 例子 3.4:等待多个事件 + +[例子 3.4 源码][9] + +在许多场景下,在客户端的处理方法中你需要等待多个事件的发生。除了客户端的消息,你可能需要等待不同类型事件的发生。比如,如果你的游戏时间有限制,那么你可能需要等一个来自定时器的信号。或者你需要使用管道来等待来自其它进程的消息。亦或者是使用分布式消息系统网络中其它服务器的信息。 + +为了简单起见,这个例子是基于例子 3.1。但是这个例子中我们使用 `Condition` 对象来保证已连接客户端游戏循环的同步。我们不保存套接字的全局列表,因为只在方法中使用套接字。当游戏循环停止迭代时,我们使用 `Condition.notify_all()` 方法来通知所有的客户端。这个方法允许在 `asyncio` 的事件循环中使用发布/订阅的模式。 + +为了等待两个事件,首先我们使用 `ensure_future()` 来封装任务中可以等待的对象。 + +``` + if not recv_task: + recv_task = asyncio.ensure_future(ws.receive()) + if not tick_task: + await tick.acquire() + tick_task = asyncio.ensure_future(tick.wait()) +``` + +在我们调用 `Condition.wait()` 之前,我们需要在背后获取一把锁。这就是我们为什么先调用 `tick.acquire()` 的原因。在调用 `tick.wait()` 之后,锁会被释放,这样其他的协程也可以使用它。但是当我们收到通知时,会重新获取锁,所以在收到通知后需要调用 `tick.release()` 来释放它。 + +我们使用 `asyncio.wait()` 协程来等待两个任务。 + +``` + done, pending = await asyncio.wait( + [recv_task, + tick_task], + return_when=asyncio.FIRST_COMPLETED) +``` + +程序会阻塞,直到列表中的任意一个任务完成。然后它返回两个列表:执行完成的任务列表和仍然在执行的任务列表。如果任务执行完成了,其对应变量赋值为 `None`,所以在下一个迭代时,它可能会被再次创建。 + +#### 例子 3.5: 结合多个线程 + +[例子 3.5 源码][10] + +在这个例子中,我们结合 asyncio 循环和线程,在一个单独的线程中执行主游戏循环。我之前提到过,由于 `GIL` 的存在,Python 代码的真正并行执行是不可能的。所以使用其它线程来执行复杂计算并不是一个好主意。然而,在使用 `asyncio` 时结合线程有原因的:当我们使用的其它库不支持 `asyncio` 时。在主线程中调用这些库会阻塞循环的执行,所以异步使用他们的唯一方法是在不同的线程中使用他们。 + +在 asyncio 的循环和 `ThreadPoolExecutor` 中,我们通过 `run_in_executor()` 方法来执行游戏循环。注意 `game_loop()` 已经不再是一个协程了。它是一个由其它线程执行的函数。然而我们需要和主线程交互,在游戏事件到来时通知客户端。asyncio 本身不是线程安全的,它提供了可以在其它线程中执行你的代码的方法。普通函数有 `call_soon_threadsafe()`, 协程有 `run_coroutine_threadsafe()`。我们在 `notify()` 协程中增加代码通知客户端游戏的嘀嗒,然后通过另外一个线程执行主事件循环。 + +``` +def game_loop(asyncio_loop): + print("Game loop thread id {}".format(threading.get_ident())) + async def notify(): + print("Notify thread id {}".format(threading.get_ident())) + await tick.acquire() + tick.notify_all() + tick.release() + + while 1: + task = asyncio.run_coroutine_threadsafe(notify(), asyncio_loop) + # blocking the thread + sleep(1) + # make sure the task has finished + task.result() +``` + +当你执行这个例子时,你会看到 "Notify thread id" 和 "Main thread id" 相等,因为 `notify()` 协程在主线程中执行。与此同时 `sleep(1)` 在另外一个线程中执行,因此它不会阻塞主事件循环。 + +#### 例子 3.6:多进程和扩展 + +[例子 3.6 源码][11] + +单线程的服务器可能运行得很好,但是它只能使用一个CPU核。为了将服务扩展到多核,我们需要执行多个进程,每个进程执行各自的事件循环。这样我们需要在进程间交互信息或者共享游戏的数据。而且在一个游戏中经常需要进行复杂的计算,例如路径查找。这些任务有时候在一个游戏嘀嗒中没法快速完成。在协程中不推荐进行费时的计算,因为它会阻塞事件的处理。在这种情况下,将这个复杂任务交给并行执行地其它进程可能更合理。 + +最简单的使用多个核的方法是启动多个使用单核的服务器,就像之前的例子中一样,每个服务器占用不同的端口。你可以使用 `supervisord` 或者其它进程控制的系统。这个时候你需要一个负载均衡器,像 `HAProxy`,使得连接的客户端在多个进程间均匀分布。有一些适配 asyncio 消息系统和存储系统。例如: + +- [aiomcache][12] for memcached client +- [aiozmq][13] for zeroMQ +- [aioredis][14] for Redis storage and pub/sub + +你可以在 github 或者 pypi 上找到其它的安装包,大部分以 `aio` 开头。 + +使用网络服务在存储持久状态和交互信息时可能比较有效。但是如果你需要进行进程通信的实时处理,它的性能可能不足。此时,使用标准的 unix 管道可能更合适。asyncio 支持管道,这个仓库有个 [使用pipe且比较底层的例子][15] + +在当前的例子中,我们使用 Python 的高层库 [multiprocessing][16] 来在不同的核上启动复杂的计算,使用 `multiprocessing.Queue` 来进行进程间的消息交互。不幸的是,当前的 multiprocessing 实现与 asyncio 不兼容。所以每一个阻塞方法的调用都会阻塞事件循环。但是此时线程正好可以起到帮助作用,因为如果在不同线程里面执行 multiprocessing 的代码,它就不会阻塞主线程。所有我们需要做的就是把所有进程间的通信放到另外一个线程中去。这个例子会解释如何使用这个方法。和上面的多线程例子非常类似,但是我们从线程中创建的是一个新的进程。 + +``` +def game_loop(asyncio_loop): + # coroutine to run in main thread + async def notify(): + await tick.acquire() + tick.notify_all() + tick.release() + + queue = Queue() + + # function to run in a different process + def worker(): + while 1: + print("doing heavy calculation in process {}".format(os.getpid())) + sleep(1) + queue.put("calculation result") + + Process(target=worker).start() + + while 1: + # blocks this thread but not main thread with event loop + result = queue.get() + print("getting {} in process {}".format(result, os.getpid())) + task = asyncio.run_coroutine_threadsafe(notify(), asyncio_loop) + task.result() +``` + +这里我们在另外一个进程中运行 `worker()` 函数。它包括一个执行复杂计算的循环,然后把计算结果放到 `queue` 中,这个 `queue` 是 `multiprocessing.Queue` 的实例。然后我们就可以在另外一个线程的主事件循环中获取结果并通知客户端,就是例子 3.5 一样。这个例子已经非常简化了,它没有合理的结束进程。而且在真实的游戏中,我们可能需要另外一个队列来将数据传递给 `worker`。 + +有一个项目叫 [aioprocessing][17],它封装了 multiprocessing,使得它可以和 asyncio 兼容。但是实际上它只是和上面例子使用了完全一样的方法:从线程中创建进程。它并没有给你带来任何方便,除了它使用了简单的接口隐藏了后面的这些技巧。希望在 Python 的下一个版本中,我们能有一个基于协程且支持 asyncio 的 multiprocessing 库。 + +> 注意!如果你从主线程或者主进程中创建了一个不同的线程或者子进程来运行另外一个 asyncio 事件循环,你需要显示地使用 `asyncio.new_event_loop()` 来创建循环,不然的话可能程序不会正常工作。 + +-------------------------------------------------------------------------------- + +via: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-and-asyncio-writing-game-loop/ + +作者:[Kyrylo Subbotin][a] +译者:[chunyang-wen](https://github.com/chunyang-wen) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-and-asyncio-writing-game-loop/ +[1]: http://snakepit-game.com/ +[2]: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-asyncio-getting-asynchronous/ +[3]: http://snakepit-game.com/ +[4]: https://github.com/7WebPages/snakepit-game/blob/master/simple/index.html +[5]: https://github.com/7WebPages/snakepit-game/blob/master/simple/game_loop_basic.py +[6]: http://aiohttp.readthedocs.org/ +[7]: https://github.com/7WebPages/snakepit-game/blob/master/simple/game_loop_handler.py +[8]: https://github.com/7WebPages/snakepit-game/blob/master/simple/game_loop_global.py +[9]: https://github.com/7WebPages/snakepit-game/blob/master/simple/game_loop_wait.py +[10]: https://github.com/7WebPages/snakepit-game/blob/master/simple/game_loop_thread.py +[11]: https://github.com/7WebPages/snakepit-game/blob/master/simple/game_loop_process.py +[12]: https://github.com/aio-libs/aiomcache +[13]: https://github.com/aio-libs/aiozmq +[14]: https://github.com/aio-libs/aioredis +[15]: https://github.com/KeepSafe/aiohttp/blob/master/examples/mpsrv.py +[16]: https://docs.python.org/3.5/library/multiprocessing.html +[17]: https://github.com/dano/aioprocessing diff --git a/translated/tech/20160606 Writing online multiplayer game with python and asyncio - part 3.md b/translated/tech/20160606 Writing online multiplayer game with python and asyncio - part 3.md new file mode 100644 index 0000000000..2d4a2316bd --- /dev/null +++ b/translated/tech/20160606 Writing online multiplayer game with python and asyncio - part 3.md @@ -0,0 +1,138 @@ +使用 Python 和 asyncio 来编写在线多人游戏 - 第3部分 +================================================================= + +![](https://7webpages.com/media/cache/17/81/178135a6db5074c72a1394d31774c658.gif) + +在这个系列中,我们基于多人游戏 [贪吃蛇][1] 来制作一个异步的 Python 程序。前一篇文章聚焦于[编写游戏循环][2]上,而本系列第1部分涵盖了 [异步化][3]。 + +代码戳[这里][4] + +### 4. 制作一个完成的游戏 + +![](https://7webpages.com/static/img/14chs7.gif) + +#### 4.1 工程概览 +#### 4.1 Project's overview + +在此部分,我们将回顾一个完整在线游戏的设计。这是一个经典的贪吃蛇游戏,增加了多玩家支持。你可以自己在 () 亲自试玩。源码在 Github的这个[仓库][5]。游戏包括下列文件: + +- [server.py][6] - 处理主游戏循环和连接服务器。 +- [game.py][7] - 主要 `Game` 类。实现游戏的逻辑和游戏的大部分通信协议。 +- [player.py][8] - `Player` 类,包括每一个独立玩家的数据和蛇的表示。这个类负责获取玩家的输入以及根据输入相应地移动蛇。 +- [datatypes.py][9] - 基本数据结构。 +- [settings.py][10] - 游戏设置,在注释中有相关的说明。 +- [index.html][11] - 一个文件中包括客户端所有的 html 和 javascript代码。 + +#### 4.2 游戏循环内窥 + +多人的贪吃蛇游戏是个十分好的例子,因为它简单。所有的蛇在每个帧中移动到一个位置,而且帧之间的变化频率较低,这样你就可以一探一个游戏引擎到底是如何工作的。因为速度慢,对于玩家的按键不会立马响应。按键先是记录下来,然后在一个游戏迭代的最后计算下一帧时使用。 + +> 现代的动作游戏帧频率更高,而且服务端和客户端的帧频率不相等。客户端的帧频率通常依赖于客户端的硬件性能,而服务端的帧频率是固定的。一个客户端可能根据一个游戏嘀嗒的数据渲染多个帧。这样就可以创建平滑的动画,这个受限于客户端的性能。在这个例子中,服务器不仅传输物体的当前位置,也要传输他们的移动方向,速度和加速度。客户端的帧频率称之为 FPS(frames per second),服务端的帧频率称之为 TPS(ticks per second)。在这个贪吃蛇游戏的例子中,二者的值是相等的,客户端帧的展现和服务端的嘀嗒是同步的。 + +我们使用文本模式一样的游戏区域,事实上是 html 表格中的一个字符宽的小格。游戏中的所有对象都是通过表格中的不同颜色字符来表示。大部分时候,客户端将按键码发送至服务器,然后每个 tick 更新游戏区域。服务端一次更新包括需要更新字符的坐标和颜色。所以我们将所有游戏逻辑放置在服务端,只将需要渲染的数据发送给客户端。此外,我们通过替换网络上发送的数据来最小化游戏被破解的概率。 + +#### 4.3 它是如何运行的? + +这个游戏中的服务端出于简化的目的,它和例子 3.2 类似。但是我们用一个所有服务器都可访问的 Game 对象来代替之前保存所有已连接 websocket 的全局列表。一个 Game 实例包括玩家的列表 (self._players),表示连接到此游戏的玩家,他们的个人数据和 websocket 对象。将所有游戏相关的数据存储在一个 Game 对象中,会方便我们增加多个游戏房间这个功能。这样的话,我们只要维护多个 Game 对象,每个游戏开始时创建相应的 Game 对象。 + +客户端和服务端的所有交互都是通过编码成 json 的消息来完成。来自客户端的消息仅包含玩家所按下键对应的编码。其它来自客户端消息使用如下格式: + +``` +[command, arg1, arg2, ... argN ] +``` + +来自服务端的消息以列表的形式发送,因为通常一次要发送多个消息 (大多数情况下是渲染的数据): + +``` +[[command, arg1, arg2, ... argN ], ... ] +``` + +在每次游戏循环迭代的最后会计算下一帧,并且将数据发送给所有的客户端。当然,每次不是发送完整的帧,而是发送两帧之间的变化列表。 + +注意玩家连接上服务器后不是立马加入游戏。连接开始时是观望者 (spectator) 模式,玩家可以观察其它玩家如何玩游戏。如果游戏已经开始或者上一个游戏会话已经在屏幕上显示 "game over" (游戏结束),用户此时可以按下 "Join" (参与),加入一个已经存在的游戏或者如果游戏不在运行(没有其它玩家)则创建一个新的游戏。后一种情况,游戏区域在开始前会被先清空。 + +游戏区域存储在 `Game._field` 这个属性中,它是二维的嵌套列表,用于内部存储游戏区域的状态。数组中的每一个元素表示区域中的一个小格,最终小格会被渲染成 html 表格的格子。如果它的类型是 Char,它是一个 `namedtuple` ,包括一个字符和颜色。在所有连接的客户端之间保证游戏区域的同步很重要,所以所有游戏区域的更新都必须依据发送到客户端的相应的信息。这是通过 `Game.apply_render()` 来实现的。它接受一个 `Draw` 对象的列表,其用于内部更新游戏区域和发送渲染消息给客户端。 + +我们使用 `namedtuple` 不仅因为它表示简单数据结构很方便,也因为用它生成 json 格式的消息时相对于字典更省空间。如果你在一个真实的游戏循环中需要发送完整的数据结构,建议先将它们序列化成一个简单的,更短的格式,甚至打包成二进制格式(例如 bson,而不是 json),以减少网络传输。 + +`ThePlayer` 对象包括用双端队列表示的蛇。这种数据类型和列表相似,但是在两端增加和删除元素时效率更高,用它来表示蛇很理想。它的主要方法是 `Player.render_move()`,它返回移动玩家蛇至下一个位置的渲染数据。一般来说它在新的位置渲染蛇的头部,移除上一帧中表示蛇的尾巴元素。如果蛇吃了一个数字,需要增长,在相应的多个帧中尾巴是不需要移动的。蛇的渲染数据在主要类的 `Game.next_frame()` 中使用,该方法中实现所有的游戏逻辑。这个方法渲染所有蛇的移动,检查每一个蛇前面的障碍物,而且生成数字和石头。每一个嘀嗒,`game_loop()` 都会直接调用它来生成下一帧。 + +如果蛇头前面有障碍物,在 `Game.next_frame()` 中会调用 `Game.game_over()`。所有的客户端都会收到那个蛇死掉的通知 (会调用 `player.render_game_over()` 方法将其变成石头),然后更新表中的分数排行榜。`Player` 的存活标记被置为 `False`,当渲染下一帧时,这个玩家会被跳过,除非他重新加入游戏。当没有蛇存活时,游戏区域会显示 "game over" (游戏结束) 。而且,主游戏循环会停止,设置 `game.running` 标记为 `False`。当某个玩家下次按下 "Join" (加入) 时,游戏区域会被清空。 + +在渲染游戏的每个下一帧时都会产生数字和石头,他们是由随机值决定的。产生数字或者石头的概率可以在 settings.py 中修改。注意数字是针对游戏区域每一个活的蛇产生的,所以蛇越多,产生的数字就越多,这样他们都有足够的食物来消费。 + +#### 4.4 网络协议 +#### 4.4 Network protocol + +从客户端发送消息的列表: + +Command | Parameters |Description +:-- |:-- |:-- +new_player | [name] |Setting player's nickname +join | |Player is joining the game + + +从服务端发送消息的列表 + +Command | Parameters |Description +:-- |:-- |:-- +handshake |[id] |Assign id to a player +world |[[(char, color), ...], ...] |Initial play field (world) map +reset_world | |Clean up world map, replacing all characters with spaces +render |[x, y, char, color] |Display character at position +p_joined |[id, name, color, score] |New player joined the game +p_gameover |[id] |Game ended for a player +p_score |[id, score] |Setting score for a player +top_scores |[[name, score, color], ...] |Update top scores table + +典型的消息交换顺序 + +Client -> Server |Server -> Client |Server -> All clients |Commentaries +:-- |:-- |:-- |:-- +new_player | | |Name passed to server + |handshake | |ID assigned + |world | |Initial world map passed + |top_scores | |Recent top scores table passed +join | | |Player pressed "Join", game loop started + | |reset_world |Command clients to clean up play field + | |render, render, ... |First game tick, first frame rendered +(key code) | | |Player pressed a key + | |render, render, ... |Second frame rendered + | |p_score |Snake has eaten a digit + | |render, render, ... |Third frame rendered + | | |... Repeat for a number of frames ... + | |p_gameover |Snake died when trying to eat an obstacle + | |top_scores |Updated top scores table (if updated) + +### 5. 总结 + +说实话,我十分享受 Python 最新的异步特性。新的语法很友善,所以异步代码很容易阅读。可以明显看出哪些调用是非阻塞的,什么时候发生 greenthread 的切换。所以现在我可以宣称 Python 是异步编程的好工具。 + +SnakePit 在 7WebPages 团队中非常受欢迎。如果你在公司想休息一下,不要忘记给我们在 [Twitter][12] 或者 [Facebook][13] 留下反馈。 + +更多详见: + +-------------------------------------------------------------------------------- + +via: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-and-asyncio-part-3/ + +作者:[Saheetha Shameer][a] +译者:[chunyang-wen](https://github.com/chunyang-wen) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-and-asyncio-part-3/ +[1]: http://snakepit-game.com/ +[2]: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-and-asyncio-writing-game-loop/ +[3]: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-asyncio-getting-asynchronous/ +[4]: https://github.com/7WebPages/snakepit-game +[5]: https://github.com/7WebPages/snakepit-game +[6]: https://github.com/7WebPages/snakepit-game/blob/master/server.py +[7]: https://github.com/7WebPages/snakepit-game/blob/master/game.py +[8]: https://github.com/7WebPages/snakepit-game/blob/master/player.py +[9]: https://github.com/7WebPages/snakepit-game/blob/master/datatypes.py +[10]: https://github.com/7WebPages/snakepit-game/blob/master/settings.py +[11]: https://github.com/7WebPages/snakepit-game/blob/master/index.html +[12]: https://twitter.com/7WebPages +[13]: https://www.facebook.com/7WebPages/ From b42ec0a8d9ec90bd6dfaf148b64dcc179bc6ae9d Mon Sep 17 00:00:00 2001 From: wenchunyang Date: Tue, 13 Sep 2016 13:24:20 +0800 Subject: [PATCH 8/8] delete sources of part2 & part3 --- ...r game with python and asyncio - part 2.md | 234 ------------------ ...r game with python and asyncio - part 3.md | 138 ----------- 2 files changed, 372 deletions(-) delete mode 100644 sources/tech/20160531 Writing online multiplayer game with python and asyncio - part 2.md delete mode 100644 sources/tech/20160606 Writing online multiplayer game with python and asyncio - part 3.md diff --git a/sources/tech/20160531 Writing online multiplayer game with python and asyncio - part 2.md b/sources/tech/20160531 Writing online multiplayer game with python and asyncio - part 2.md deleted file mode 100644 index 75df13d9b5..0000000000 --- a/sources/tech/20160531 Writing online multiplayer game with python and asyncio - part 2.md +++ /dev/null @@ -1,234 +0,0 @@ -chunyang-wen translating -Writing online multiplayer game with python and asyncio - Part 2 -================================================================== - -![](https://7webpages.com/media/cache/fd/d1/fdd1f8f8bbbf4166de5f715e6ed0ac00.gif) - -Have you ever made an asynchronous Python app? Here I’ll tell you how to do it and in the next part, show it on a [working example][1] - a popular Snake game, designed for multiple players. - -see the intro and theory about how to [Get Asynchronous [part 1]][2] - -[Play the game][3] - -### 3. Writing game loop - -The game loop is a heart of every game. It runs continuously to get player's input, update state of the game and render the result on the screen. In online games the loop is divided into client and server parts, so basically there are two loops which communicate over the network. Usually, client role is to get player's input, such as keypress or mouse movement, pass this data to a server and get back the data to render. The server side is processing all the data coming from players, updating game's state, doing necessary calculations to render next frame and passes back the result, such as new placement of game objects. It is very important not to mix client and server roles without a solid reason. If you start doing game logic calculations on the client side, you can easily go out of sync with other clients, and your game can also be created by simply passing any data from the client side. - -A game loop iteration is often called a tick. Tick is an event meaning that current game loop iteration is over and the data for the next frame(s) is ready. -In the next examples we will use the same client, which connects to a server from a web page using WebSocket. It runs a simple loop which passes pressed keys' codes to the server and displays all messages that come from the server. [Client source code is located here][4]. - -#### Example 3.1: Basic game loop - -[Example 3.1 source code][5] - -We will use [aiohttp][6] library to create a game server. It allows creating web servers and clients based on asyncio. A good thing about this library is that it supports normal http requests and websockets at the same time. So we don't need other web servers to render game's html page. - -Here is how we run the server: - -``` -app = web.Application() -app["sockets"] = [] - -asyncio.ensure_future(game_loop(app)) - -app.router.add_route('GET', '/connect', wshandler) -app.router.add_route('GET', '/', handle) - -web.run_app(app) -``` - -web.run_app is a handy shortcut to create server's main task and to run asyncio event loop with it's run_forever() method. I suggest you check the source code of this method to see how the server is actually created and terminated. - -An app is a dict-like object which can be used to share data between connected clients. We will use it to store a list of connected sockets. This list is then used to send notification messages to all connected clients. A call to asyncio.ensure_future() will schedule our main game_loop task which sends 'tick' message to clients every 2 seconds. This task will run concurrently in the same asyncio event loop along with our web server. - -There are 2 web request handlers: handle just serves a html page and wshandler is our main websocket server's task which handles interaction with game clients. With every connected client a new wshandler task is launched in the event loop. This task adds client's socket to the list, so that game_loop task may send messages to all the clients. Then it echoes every keypress back to the client with a message. - -In the launched tasks we are running worker loops over the main event loop of asyncio. A switch between tasks happens when one of them uses await statement to wait for a coroutine to finish. For instance, asyncio.sleep just passes execution back to a scheduler for a given amount of time, and ws.receive() is waiting for a message from websocket, while the scheduler may switch to some other task. - -After you open the main page in a browser and connect to the server, just try to press some keys. Their codes will be echoed back from the server and every 2 seconds this message will be overwritten by game loop's 'tick' message which is sent to all clients. - -So we have just created a server which is processing client's keypresses, while the main game loop is doing some work in the background and updates all clients periodically. - -#### Example 3.2: Starting game loop by request - -[Example 3.2 source code][7] - -In the previous example a game loop was running continuously all the time during the life of the server. But in practice, there is usually no sense to run game loop when no one is connected. Also, there may be different game "rooms" running on one server. In this concept one player "creates" a game session (a match in a multiplayer game or a raid in MMO for example) so other players may join it. Then a game loop runs while the game session continues. - -In this example we use a global flag to check if a game loop is running, and we start it when the first player connects. In the beginning, a game loop is not running, so the flag is set to False. A game loop is launched from the client's handler: - -``` - if app["game_is_running"] == False: - asyncio.ensure_future(game_loop(app)) -``` - -This flag is then set to True at the start of game loop() and then back to False in the end, when all clients are disconnected. - -#### Example 3.3: Managing tasks - -[Example 3.3 source code][8] - -This example illustrates working with task objects. Instead of storing a flag, we store game loop's task directly in our application's global dict. This may be not an optimal thing to do in a simple case like this, but sometimes you may need to control already launched tasks. -``` - if app["game_loop"] is None or \ - app["game_loop"].cancelled(): - app["game_loop"] = asyncio.ensure_future(game_loop(app)) -``` - -Here ensure_future() returns a task object that we store in a global dict; and when all users disconnect, we cancel it with - -``` - app["game_loop"].cancel() -``` - -This cancel() call tells scheduler not to pass execution to this coroutine anymore and sets its state to cancelled which then can be checked by cancelled() method. And here is one caveat worth to mention: when you have external references to a task object and exception happens in this task, this exception will not be raised. Instead, an exception is set to this task and may be checked by exception() method. Such silent fails are not useful when debugging a code. Thus, you may want to raise all exceptions instead. To do so you need to call result() method of unfinished task explicitly. This can be done in a callback: - -``` - app["game_loop"].add_done_callback(lambda t: t.result()) -``` - -Also if we are going to cancel this task in our code and we don't want to have CancelledError exception, it has a point checking its "cancelled" state: -``` - app["game_loop"].add_done_callback(lambda t: t.result() - if not t.cancelled() else None) -``` - -Note that this is required only if you store a reference to your task objects. In the previous examples all exceptions are raised directly without additional callbacks. - -#### Example 3.4: Waiting for multiple events - -[Example 3.4 source code][9] - -In many cases, you need to wait for multiple events inside client's handler. Beside a message from a client, you may need to wait for different types of things to happen. For instance, if your game's time is limited, you may wait for a signal from timer. Or, you may wait for a message from other process using pipes. Or, for a message from a different server in the network, using a distributed messaging system. - -This example is based on example 3.1 for simplicity. But in this case we use Condition object to synchronize game loop with connected clients. We do not keep a global list of sockets here as we are using sockets only within the handler. When game loop iteration ends, we notify all clients using Condition.notify_all() method. This method allows implementing publish/subscribe pattern within asyncio event loop. - -To wait for two events in the handler, first, we wrap awaitable objects in a task using ensure_future() - -``` - if not recv_task: - recv_task = asyncio.ensure_future(ws.receive()) - if not tick_task: - await tick.acquire() - tick_task = asyncio.ensure_future(tick.wait()) -``` - -Before we can call Condition.wait(), we need to acquire a lock behind it. That is why, we call tick.acquire() first. This lock is then released after calling tick.wait(), so other coroutines may use it too. But when we get a notification, a lock will be acquired again, so we need to release it calling tick.release() after received notification. - -We are using asyncio.wait() coroutine to wait for two tasks. - -``` - done, pending = await asyncio.wait( - [recv_task, - tick_task], - return_when=asyncio.FIRST_COMPLETED) -``` - -It blocks until either of tasks from the list is completed. Then it returns 2 lists: tasks which are done and tasks which are still running. If the task is done, we set it to None so it may be created again on the next iteration. - -#### Example 3.5: Combining with threads - -[Example 3.5 source code][10] - -In this example we combine asyncio loop with threads by running the main game loop in a separate thread. As I mentioned before, it's not possible to perform real parallel execution of python code with threads because of GIL. So it is not a good idea to use other thread to do heavy calculations. However, there is one reason to use threads with asyncio: this is the case when you need to use other libraries which do not support asyncio. Using these libraries in the main thread will simply block execution of the loop, so the only way to use them asynchronously is to run in a different thread. - -We run game loop using run_in_executor() method of asyncio loop and ThreadPoolExecutor. Note that game_loop() is not a coroutine anymore. It is a function that is executed in another thread. However, we need to interact with the main thread to notify clients on the game events. And while asyncio itself is not threadsafe, it has methods which allow running your code from another thread. These are call_soon_threadsafe() for normal functions and run_coroutine_threadsafe() for coroutines. We will put a code which notifies clients about game's tick to notify() coroutine and runs it in the main event loop from another thread. - -``` -def game_loop(asyncio_loop): - print("Game loop thread id {}".format(threading.get_ident())) - async def notify(): - print("Notify thread id {}".format(threading.get_ident())) - await tick.acquire() - tick.notify_all() - tick.release() - - while 1: - task = asyncio.run_coroutine_threadsafe(notify(), asyncio_loop) - # blocking the thread - sleep(1) - # make sure the task has finished - task.result() -``` - -When you launch this example, you will see that "Notify thread id" is equal to "Main thread id", this is because notify() coroutine is executed in the main thread. While sleep(1) call is executed in another thread, and, as a result, it will not block the main event loop. - -#### Example 3.6: Multiple processes and scaling up - -[Example 3.6 source code][11] - -One threaded server may work well, but it is limited to one CPU core. To scale the server beyond one core, we need to run multiple processes containing their own event loops. So we need a way for processes to interact with each other by exchanging messages or sharing game's data. Also in games, it is often required to perform heavy calculations, such as path finding and alike. These tasks are sometimes not possible to complete quickly within one game tick. It is not recommended to perform time-consuming calculations in coroutines, as it will block event processing, so in this case, it may be reasonable to pass the heavy task to other process running in parallel. - -The easiest way to utilize multiple cores is to launch multiple single core servers, like in the previous examples, each on a different port. You can do this with supervisord or similar process-controller system. Then, you may use a load balancer, such as HAProxy, to distribute connecting clients between the processes. There are different ways for processes to interact wich each other. One is to use network-based systems, which allows you to scale to multiple servers as well. There are already existing adapters to use popular messaging and storage systems with asyncio. Here are some examples: - -- [aiomcache][12] for memcached client -- [aiozmq][13] for zeroMQ -- [aioredis][14] for Redis storage and pub/sub - -You can find many other packages like this on github and pypi, most of them have "aio" prefix. - -Using network services may be effective to store persistent data and exchange some kind of messages. But its performance may be not enough if you need to perform real-time data processing that involves inter-process communications. In this case, a more appropriate way may be using standard unix pipes. asyncio has support for pipes and there is a [very low-level example of the server which uses pipes][15] in aiohttp repository. - -In the current example, we will use python's high-level [multiprocessing][16] library to instantiate new process to perform heavy calculations on a different core and to exchange messages with this process using multiprocessing.Queue. Unfortunately, the current implementation of multiprocessing is not compatible with asyncio. So every blocking call will block the event loop. But this is exactly the case where threads will be helpful because if we run multiprocessing code in a different thread, it will not block our main thread. All we need is to put all inter-process communications to another thread. This example illustrates this technique. It is very similar to multi-threading example above, but we create a new process from a thread. - -``` -def game_loop(asyncio_loop): - # coroutine to run in main thread - async def notify(): - await tick.acquire() - tick.notify_all() - tick.release() - - queue = Queue() - - # function to run in a different process - def worker(): - while 1: - print("doing heavy calculation in process {}".format(os.getpid())) - sleep(1) - queue.put("calculation result") - - Process(target=worker).start() - - while 1: - # blocks this thread but not main thread with event loop - result = queue.get() - print("getting {} in process {}".format(result, os.getpid())) - task = asyncio.run_coroutine_threadsafe(notify(), asyncio_loop) - task.result() -``` - -Here we run worker() function in another process. It contains a loop doing heavy calculations and putting results to the queue, which is an instance of multiprocessing.Queue. Then we get the results and notify clients in the main event loop from a different thread, exactly as in the example 3.5. This example is very simplified, it doesn't have a proper termination of the process. Also, in a real game, we would probably use the second queue to pass data to the worker. - -There is a project called [aioprocessing][17], which is a wrapper around multiprocessing that makes it compatible with asyncio. However, it uses exactly the same approach as described in this example - creating processes from threads. It will not give you any advantage, other than hiding these tricks behind a simple interface. Hopefully, in the next versions of Python, we will get a multiprocessing library based on coroutines and supports asyncio. - ->Important! If you are going to run another asyncio event loop in a different thread or sub-process created from main thread/process, you need to create a loop explicitly, using asyncio.new_event_loop(), otherwise, it will not work. - --------------------------------------------------------------------------------- - -via: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-and-asyncio-writing-game-loop/ - -作者:[Kyrylo Subbotin][a] -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-and-asyncio-writing-game-loop/ -[1]: http://snakepit-game.com/ -[2]: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-asyncio-getting-asynchronous/ -[3]: http://snakepit-game.com/ -[4]: https://github.com/7WebPages/snakepit-game/blob/master/simple/index.html -[5]: https://github.com/7WebPages/snakepit-game/blob/master/simple/game_loop_basic.py -[6]: http://aiohttp.readthedocs.org/ -[7]: https://github.com/7WebPages/snakepit-game/blob/master/simple/game_loop_handler.py -[8]: https://github.com/7WebPages/snakepit-game/blob/master/simple/game_loop_global.py -[9]: https://github.com/7WebPages/snakepit-game/blob/master/simple/game_loop_wait.py -[10]: https://github.com/7WebPages/snakepit-game/blob/master/simple/game_loop_thread.py -[11]: https://github.com/7WebPages/snakepit-game/blob/master/simple/game_loop_process.py -[12]: https://github.com/aio-libs/aiomcache -[13]: https://github.com/aio-libs/aiozmq -[14]: https://github.com/aio-libs/aioredis -[15]: https://github.com/KeepSafe/aiohttp/blob/master/examples/mpsrv.py -[16]: https://docs.python.org/3.5/library/multiprocessing.html -[17]: https://github.com/dano/aioprocessing diff --git a/sources/tech/20160606 Writing online multiplayer game with python and asyncio - part 3.md b/sources/tech/20160606 Writing online multiplayer game with python and asyncio - part 3.md deleted file mode 100644 index 3c38af72ac..0000000000 --- a/sources/tech/20160606 Writing online multiplayer game with python and asyncio - part 3.md +++ /dev/null @@ -1,138 +0,0 @@ -chunyang-wen translating -Writing online multiplayer game with python and asyncio - Part 3 -================================================================= - -![](https://7webpages.com/media/cache/17/81/178135a6db5074c72a1394d31774c658.gif) - -In this series, we are making an asynchronous Python app on the example of a multiplayer [Snake game][1]. The previous article focused on [Writing Game Loop][2] and Part 1 was covering how to [Get Asynchronous][3]. - -You can find the code [here][4]. - -### 4. Making a complete game - -![](https://7webpages.com/static/img/14chs7.gif) - -#### 4.1 Project's overview - -In this part, we will review a design of a complete online game. It is a classic snake game with added multiplayer. You can try it yourself at (). A source code is located [in github repository][5]. The game consists of the following files: - -- [server.py][6] - a server handling main game loop and connections. -- [game.py][7] - a main Game class, which implements game's logic and most of the game's network protocol. -- [player.py][8] - Player class, containing individual player's data and snake's representation. This one is responsible for getting player's input and moving the snake accordingly. -- [datatypes.py][9] - basic data structures. -- [settings.py][10] - game settings, and it has descriptions in commentaries. -- [index.html][11] - all html and javascript client part in one file. - -#### 4.2 Inside a game loop - -Multiplayer snake game is a good example to learn because of its simplicity. All snakes move to one position every single frame, and frames are changing at a very slow rate, allowing you to watch how game engine is working actually. There is no instant reaction to player's keypresses because of the slow speed. A pressed key is remembered and then taken into account while calculating the next frame at the end of game loop's iteration. - -> Modern action games are running at much higher frame rates and often frame rates of server and client are not equal. Client frame rate usually depends on the client hardware performance, while server frame rate is fixed. A client may render several frames after getting the data corresponding to one "game tick". This allows to create smooth animations, which are only limited by client's performance. In this case, a server should pass not only current positions of the objects but also their moving directions, speeds and velocities. And while client frame rate is called FPS (frames per second), sever frame rate is called TPS (ticks per second). In this snake game example both values are equal, and one frame displayed by a client is calculated within one server's tick. - -We will use textmode-like play field, which is, in fact, a html table with one-char cells. All objects of the game are displayed with characters of different colors placed in table's cells. Most of the time client passes pressed keys' codes to the server and gets back play field updates with every "tick". An update from server consists of messages representing characters to render along with their coordinates and colors. So we are keeping all game logic on the server and we are sending to client only rendering data. In addition, we minimize the possibilities to hack the game by substituting its information sent over the network. - -#### 4.3 How does it work? - -The server in this game is close to Example 3.2 for simplicity. But instead of having a global list of connected websockets, we have one server-wide Game object. A Game instance contains a list of Player objects (inside self._players attribute) which represents players connected to this game, their personal data and websocket objects. Having all game-related data in a Game object also allows us to have multiple game rooms if we want to add such feature. In this case, we need to maintain multiple Game objects, one per game started. - -All interactions between server and clients are done with messages encoded in json. Message from the client containing only a number is interpreted as a code of the key pressed by the player. Other messages from client are sent in the following format: - -``` -[command, arg1, arg2, ... argN ] -``` - -Messages from server are sent as a list because there is often a bunch of messages to send at once (rendering data mostly): - -``` -[[command, arg1, arg2, ... argN ], ... ] -``` - -At the end of every game loop iteration, the next frame is calculated and sent to all the clients. Of course, we are not sending complete frame every time, but only a list of changes for the next frame. - -Note that players are not joining the game immediately after connecting to the server. The connection starts in "spectator" mode, so one can watch how others are playing. if the game is already started, or a "game over" screen from the previous game session. Then a player may press "Join" button to join the existing game or to create a new game if the game is not currently running (no other active players). In the later case, the play field is cleared before the start. - -The play field is stored in Game._world attribute, which is a 2d array made of nested lists. It is used to keep game field's state internally. Each element of an array represents a field's cell which is then rendered to a html table cell. It has a type of Char, which is a namedtuple consisting of a character and color. It is important to keep play field in sync with all the connected clients, so all updates to the play field should be made only along with sending corresponding messages to the clients. This is performed by Game.apply_render() method. It receives a list of Draw objects, which is then used to update play field internally and also to send render message to clients. - -We are using namedtuple not only because it is a good way to represent simple data structures, but also because it takes less space comparing to dict when sending in a json message. If you are sending complex data structures in a real game app, it is recommended to serialize them into a plain and shorter format or even pack in a binary format (such as bson instead of json) to minimize network traffic. - -ThePlayer object contains snake's representation in a deque object. This data type is similar to a list but is more effective for adding and removing elements on its sides, so it is ideal to represent a moving snake. The main method of the class is Player.render_move(), it returns rendering data to move player's snake to the next position. Basically, it renders snake's head in the new position and removes the last element where the tail was in the previous frame. In case the snake has eaten a digit and has to grow, a tail is not moving for a corresponding number of frames. The snake rendering data is used in Game.next_frame() method of the main class, which implements all game logic. This method renders all snake moves and checks for obstacles in front of every snake and also spawns digits and "stones". It is called directly from game_loop() to generate the next frame at every "tick". - -In case there is an obstacle in front of snake's head, a Game.game_over() method is called from Game.next_frame(). It notifies all connected clients about the dead snake (which is turned into stones by player.render_game_over() method) and updates top scores table. Player object's alive flag is set to False, so this player will be skipped when rendering the next frames, until joining the game once again. In case there are no more snakes alive, a "game over" message is rendered at the game field. Also, the main game loop will stop and set game.running flag to False, which will cause a game field to be cleared when some player will press "Join" button next time. - -Spawning of digits and stones is also happening while rendering every next frame, and it is determined by random values. A chance to spawn a digit or a stone can be changed in settings.py along with some other values. Note that digit spawning is happening for every live snake in the play field, so the more snakes are there, the more digits will appear, and they all will have enough food to consume. - -#### 4.4 Network protocol -List of messages sent from client - -Command | Parameters |Description -:-- |:-- |:-- -new_player | [name] |Setting player's nickname -join | |Player is joining the game - - -List of messages sent from server - -Command | Parameters |Description -:-- |:-- |:-- -handshake |[id] |Assign id to a player -world |[[(char, color), ...], ...] |Initial play field (world) map -reset_world | |Clean up world map, replacing all characters with spaces -render |[x, y, char, color] |Display character at position -p_joined |[id, name, color, score] |New player joined the game -p_gameover |[id] |Game ended for a player -p_score |[id, score] |Setting score for a player -top_scores |[[name, score, color], ...] |Update top scores table - -Typical messages exchange order - -Client -> Server |Server -> Client |Server -> All clients |Commentaries -:-- |:-- |:-- |:-- -new_player | | |Name passed to server - |handshake | |ID assigned - |world | |Initial world map passed - |top_scores | |Recent top scores table passed -join | | |Player pressed "Join", game loop started - | |reset_world |Command clients to clean up play field - | |render, render, ... |First game tick, first frame rendered -(key code) | | |Player pressed a key - | |render, render, ... |Second frame rendered - | |p_score |Snake has eaten a digit - | |render, render, ... |Third frame rendered - | | |... Repeat for a number of frames ... - | |p_gameover |Snake died when trying to eat an obstacle - | |top_scores |Updated top scores table (if updated) - -### 5. Conclusion - -To tell the truth, I really enjoy using the latest asynchronous capabilities of Python. The new syntax really makes a difference, so async code is now easily readable. It is obvious which calls are non-blocking and when the green thread switching is happening. So now I can claim with confidence that Python is a good tool for asynchronous programming. - -SnakePit has become very popular at 7WebPages team, and if you decide to take a break at your company, please, don’t forget to leave a feedback for us, say, on [Twitter][12] or [Facebook][13] . - -Get to know more from: - - - --------------------------------------------------------------------------------- - -via: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-and-asyncio-part-3/ - -作者:[Saheetha Shameer][a] -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-and-asyncio-part-3/ -[1]: http://snakepit-game.com/ -[2]: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-and-asyncio-writing-game-loop/ -[3]: https://7webpages.com/blog/writing-online-multiplayer-game-with-python-asyncio-getting-asynchronous/ -[4]: https://github.com/7WebPages/snakepit-game -[5]: https://github.com/7WebPages/snakepit-game -[6]: https://github.com/7WebPages/snakepit-game/blob/master/server.py -[7]: https://github.com/7WebPages/snakepit-game/blob/master/game.py -[8]: https://github.com/7WebPages/snakepit-game/blob/master/player.py -[9]: https://github.com/7WebPages/snakepit-game/blob/master/datatypes.py -[10]: https://github.com/7WebPages/snakepit-game/blob/master/settings.py -[11]: https://github.com/7WebPages/snakepit-game/blob/master/index.html -[12]: https://twitter.com/7WebPages -[13]: https://www.facebook.com/7WebPages/