-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #63 from li-yiyang/main
Introduction to Python, the Programming Language
- Loading branch information
Showing
5 changed files
with
727 additions
and
0 deletions.
There are no files selected for viewing
196 changes: 196 additions & 0 deletions
196
posts/2024/3_basic_Python/learn-some-python-with-retro-computer-battle-games.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
# Learn Some Python with Retro Computer Battle Games | ||
## About | ||
我们将会用一些简单的远古电脑小游戏作为 Python 入门的练习. | ||
这些小游戏来自 Computer Battle Games (For ZX Spectrum, | ||
ZX81, TRS-80, APPLE, VIC & PET), 是一本由 [Usborne](https://usborne.com/) 出版社, | ||
出版于 1982 年的适用于儿童的计算机普及读物. | ||
|
||
你可以在 [Intenet Archive](https://archive.org/details/computer-battlegames) 网站上游览或者下载它, | ||
或者你也可以在 [Usborne 的存档网站](https://usborne.com/row/books/computer-and-coding-books)上找到同一系列的书籍. | ||
不过本部分并不会完全按照原书的内容进行介绍 (毕竟原书是 | ||
BASIC 语言, 而非 Python). 不过你可以将其作为课外消遣读物, | ||
不过毕竟是儿童计算机读物, 对小学生来说可能有些幼稚, | ||
但是对大学生来说估计刚刚好. | ||
|
||
### Pre-requirements | ||
你应该对 Python 有稍微的了解, | ||
这里推荐阅读 [这篇文章中的 Pythonin 20 min](./trivial-python-introduction.md) 一节, | ||
小小花个 20 分钟入个门. | ||
|
||
### Typo | ||
为了方便, 对于原文中比较中二的背景设定我直接使用的是 OCR 识别, | ||
所以如果有 typo, 请提交 issue 来修改. | ||
|
||
### TOC | ||
+ [Robot Missile](#robot-missile) | ||
+ [The Vital Message](the-vital-message) | ||
+ [Shootout](shootout) | ||
|
||
## Robot Missile | ||
> The year is 2582 and the people of Earth are in the midst of battle | ||
> against the Robots. A lethal Robot Missile has just landed and | ||
> everyone is depending on you to find the secret code which unlocks | ||
> its defuse mechanism. If you fail, the entire Earth Command | ||
> Headquarters will be blown up. | ||
> | ||
> Your computer knows what the code letter is. You must type in your | ||
> guess and it will tell you whether the code letter is earlier or | ||
> later in the alphabet. | ||
> | ||
> You have four chances to find the correct letter before the missile | ||
> blows up. | ||
请参考 [robot-missile.py](./src/computer-battle-game/robot-missile.py). | ||
|
||
<details> | ||
<summary>简单写一个小游戏外挂</summary> | ||
<div markdown="1"> | ||
|
||
我们发现这个小游戏完全可以用[二分法](https://zh.wikipedia.org/zh-cn/二分法_(數學)) 来解决, | ||
因为 `math.log(26) = 3.25 < 4`. | ||
|
||
那么我们完全可以写一个简单的二分搜索小外挂: | ||
|
||
```python | ||
def guess_number(): | ||
# 这里用到了一个多赋值 | ||
left, right = 'A', 'Z' | ||
|
||
# 这里 // 是整除 | ||
guess = chr((ord(left) + ord(right)) // 2) | ||
|
||
while True: | ||
if success_p(guess): | ||
return guess | ||
elif later_p(guess): | ||
left, right = guess, right | ||
guess = chr((ord(left) + ord(right)) // 2) | ||
else: | ||
left, right = left, right | ||
guess = chr((ord(left) + ord(right)) // 2) | ||
|
||
if left == right: | ||
return None | ||
``` | ||
|
||
我们在课上将会介绍如何编写 `success_p` 以及 `later_p` 的函数, | ||
来和外部的程序进行 IO 交互并且自动化输入命令. (这是一个查文档的例子) | ||
|
||
</div> | ||
</details> | ||
|
||
## The Vital Message | ||
> You are a laser communications operator. Your job is to intercept | ||
> robot messages and relay them to Command HQ. A vital code message | ||
> is expected. If you relay it correctly, the Robot attack will be | ||
> crushed. | ||
> | ||
> This game tests your skill at remembering a group of letters which | ||
> you see for only a very short time. The computer will ask you for a | ||
> difficulty from 4 to 10. When you have typed in your answer, letters | ||
> will appear top left of your screen and disappear again fairly quickly. | ||
> Memorize them and then type them into the computer and see if you | ||
> were right. | ||
请参考 [vital-message.py](./src/computer-battle-game/vital-message.py). | ||
|
||
<details> | ||
<summary>一些说明</summary> | ||
<div markdown="1"> | ||
|
||
这个游戏其实原理非常简单, 所以我们并不会对这个游戏的逻辑进行解释. | ||
在这里我们将会换一些考查视角: 如果用户不按照我们想要的方式输入, | ||
那么会发送什么? 如果用户的设备不是 Unix-like, 那么代码会发生什么? | ||
|
||
即, 如何处理用户输入以及如何处理多端移植. | ||
|
||
</div> | ||
</details> | ||
|
||
## Shootout | ||
> You are standing back to back. You take 10 paces, turn and reach | ||
> for your gun. How quick are you? Can you shoot first? | ||
> | ||
> Your computer prints the numbers 1 to 10 to represent the 10 paces, | ||
> pauses and then prints HE DRAWS... | ||
> | ||
> You must be ready to press a key (any one will do) the instant these | ||
> words come of the screen. If you are quick enough, you will win. Don't | ||
> press a key before HE DRAWS comes up or you automatically lose. | ||
(coming soon... or we could write it on class) | ||
|
||
## Desert Tank Battle | ||
> The last major stronghold of Robot forces outside the U.R.S* | ||
> is hidden in ancient castle ruins in the middle of the desert. | ||
> A fleet of desert hovertanks has beensent to destroy it and you | ||
> are the commander. Your tank controls the five remaining missiles. | ||
> | ||
> You must assess carefully the direction and elevation before you | ||
> launch each one. Your computer will ask you for a direction angle | ||
> between -90° (extremeleft) and +90° (extreme right) and an elevation | ||
> angle between 0° (along the ground) and 90° (straight up in the air). | ||
> The elevation determines the distance the missile will travel. | ||
> | ||
> Is your aim good enough to destroy the robot stronghold? | ||
老实说, 这几个代码其实都是差不多的原理, 看那么多类似的代码其实更多的是提高熟练度. | ||
|
||
但是这是为了作为课程练习, 所以我们需要一些新的东西来不让我们像是在原地踏步. | ||
所以在这个例子中, 我们做的事情是一个代码的 "转写" 工作. 我们会拿到一个 | ||
BASIC 代码 (其实不管是什么代码都行), 然后我们需要将其变成一个 Python 代码. | ||
|
||
<details> | ||
<summary>BASIC 代码</summary> | ||
<div markdown="1"> | ||
|
||
```basic | ||
10 PRINT "DESERT TANK BATTLE" | ||
20 LET T=INT(RND*181)-90 | ||
30 LET D=RND | ||
40 FOR G=1 TO 5 | ||
50 PRINT "DIRECTION (-90 TO 90) ?" | ||
60 INPUT T1 | ||
70 PRINT "ELEVATION (0 TO 90) ?" | ||
80 INPUT B | ||
90 LET D1=SIN(2*(B/180*3.1416)) | ||
100 IF ABS(T-T1)<2 AND ABS(D-D1)<.05 THEN GOTO 220 | ||
110 PRINT "MISSILE LANDED "; | ||
120 IF T1<T THEN PRINT "TO THE LEFT "; | ||
130 IF T1>T THEN PRINT "TO THE RIGHT "; | ||
140 IF ABS(D1-D)>.05 AND T1<>T THEN PRINT "AND "; | ||
150 IF D-D1>.05 THEN PRINT "NOT FAR ENOUGH"; | ||
160 IF D1-D>.05 THEN PRINT "TOO FAR"; | ||
170 PRINT | ||
180 NEXT G | ||
190 PRINT "DISASTER - YOU FAILED: | ||
200 PRINT "RETREAT IN DISGRACE" | ||
210 STOP | ||
220 PRINT "*KABOOOMMM*" | ||
230 PRINT "YOU'VE DONE IT" | ||
240 STOP | ||
``` | ||
|
||
评价: 其实完全和前面的 [Robot Missile](#robot-missile) 是差不多的逻辑, | ||
所以你也可以尝试用 Robot Missile 中写外挂的技术来进行一个外挂的编. | ||
|
||
</div> | ||
</details> | ||
|
||
请参考 [desert-tank-battle.py](./src/computer-battle-game/desert-tank-battle.py). | ||
|
||
这是一个简单的转写例子, 你可能需要随时查询 BASIC 的用法, | ||
以及搞到一台能跑 BASIC 的模拟器. | ||
|
||
这里推荐使用 APPLE II 的 BASIC ([applesoft 链接](https://paleotronic.com/applesoft/), 或是 [jsbasic](https://www.calormen.com/jsbasic/)). | ||
|
||
当然, 有兴趣的同学可以尝试编写一个 BASIC 的解释器, 如果将来有时间, | ||
我们可以考虑用这个 BASIC 来作为一个简单虚拟机的练习. | ||
|
||
## Iceberg | ||
> |
30 changes: 30 additions & 0 deletions
30
posts/2024/3_basic_Python/src/computer-battle-game/desert-tank-battle.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# 这里和之前 import 整个模块名称不一样, | ||
# 在这里我们只导入模块的一个函数, | ||
# 并让我们可以直接用 `randint` 和 `random' 的方式去调用 | ||
from random import randint, random | ||
|
||
def desert_tank_battle_game(): | ||
print("DESERT TANK BATTLE") | ||
|
||
# 20 LET T=INT(RND*181)-90 | ||
# 30 LET D=RND | ||
flag_degree = randint(90, 180) | ||
flag_distance = random() | ||
|
||
# TODO: in class | ||
# + we will use this as an example of | ||
# converting any language code logic to python, | ||
# which is useful when you're doing reversing | ||
|
||
# 40 FOR G=1 TO 5 | ||
try_times = 1 | ||
while try_times <= 5: | ||
# 60 INPUT T1 | ||
# 80 INPUT B | ||
try_degree = int(input("DIRECTION (-90 TO 90) ?")) | ||
try_distance = int(input("ELEVATION (0 TO 90) ?")) | ||
|
||
# ... more ... | ||
|
||
if __name__ == "__main__": | ||
desert_tank_battle_game() |
52 changes: 52 additions & 0 deletions
52
posts/2024/3_basic_Python/src/computer-battle-game/robot-missile.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import random | ||
|
||
def print_title(): | ||
# 在 Python 中, `print` 命令支持多个参量的输出, 并且可以用 `sep` 分隔. | ||
# 请尝试不同的 `print` `sep` 参数并看看效果如何 | ||
print("Robot Missile", | ||
"", | ||
"Type the CORRECT CODE lettler (A-Z) to", | ||
"defuse the missile. ", | ||
"", | ||
"You have 4 chance:", | ||
sep="\n") | ||
|
||
def guess_loop(): | ||
# 在这里我们调用了三个函数: | ||
# + `chr`: int -> char | ||
# + `ord`: char -> int | ||
# + `random.randint(a : int, b : int)`: 获得 `a`, `b` 之间的一个随机整数 | ||
# 请借助 LSP 或是你编辑器的自动文档来阅读它们的细致说明 | ||
flag = chr(random.randint(ord('A'), ord('Z'))) | ||
|
||
try_times = 0 | ||
while try_times < 4: | ||
# 在这里是一个简单的 IO 操作: | ||
# `input`: 从 stdin 中读取一个字符串 | ||
char = input("Try: ")[0] | ||
|
||
# 这里是一个自增算符 | ||
try_times += 1 | ||
|
||
if char == flag: | ||
print("TICK...FZZZZ...CLICK...", | ||
"YOU DID IT", | ||
sep="\n") | ||
return True | ||
elif char < flag: | ||
print("LATER") | ||
else: | ||
print("EARLIER") | ||
|
||
print("BOOOOOOOOOOOMMM...", | ||
"YOU BLEW IT.", | ||
f"The correct code was '{flag}'. ", | ||
sep="\n") | ||
return False | ||
|
||
# TODO in class: | ||
# + add restart | ||
# + add quit control command | ||
if __name__ == "__main__": | ||
print_title() | ||
guess_loop() |
64 changes: 64 additions & 0 deletions
64
posts/2024/3_basic_Python/src/computer-battle-game/vital-message.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import os | ||
import random | ||
import time | ||
|
||
def clear_screen(): | ||
""" | ||
Poor-man's clear screen. | ||
""" | ||
os.system('clear') | ||
|
||
# 这里是我们第一次接触 Python 的 type-hint | ||
# 这是一种给变量静态类型提示的语法, | ||
# 有助于对我们调用函数的时候提供类型提示 | ||
# 尽管这和 C 中的类型标记并不完全相同, | ||
# 因为你完全可以无视类型提示随便传值 | ||
# | ||
# TODO: exercise | ||
# + how to make the game harder? | ||
def random_string(length : int) -> str: | ||
""" | ||
Return a random string at length. | ||
""" | ||
return "".join([chr(random.randint(ord('A'), ord('Z'))) for i in range(length)]) | ||
|
||
def vital_message(): | ||
# 这里我们用到了字符串转义 | ||
# 在字符串中 `\` 表示对后面的字符进行 "转义", | ||
# 即让后面跟着的字符不再是字面量字符, | ||
# 而是具有一定意义的控制字符. 比如说: | ||
# + `\n` 表示换行 (Newline) | ||
print("Vital Message\n") | ||
|
||
# 这里我们有一个新的函数 `int` (`__int__`) | ||
# 表示将对象转换为 `int` (整形) | ||
# | ||
# TODO: in class | ||
# + safety check? | ||
# + assert `difficulty` between 4 and 10 | ||
difficulty = int(input("How Difficult? (4-10): ")) | ||
flag = random_string(difficulty) | ||
|
||
clear_screen() | ||
print(f"Send this message: '{flag}'") | ||
|
||
# 这里用到了 `time.sleep` 来等待一段时间 | ||
# | ||
# TODO: as exercise | ||
# + how to set different time? | ||
time.sleep(3) | ||
clear_screen() | ||
|
||
message = input("What is the message? Input: ") | ||
if message == flag: | ||
print("Message Correct", | ||
"The WAR is over", | ||
sep="\n") | ||
else: | ||
print("You got it wrong", | ||
"You should have sent:", | ||
flag, | ||
sep="\n") | ||
|
||
if __name__ == "__main__": | ||
vital_message() |
Oops, something went wrong.