Skip to content

Commit

Permalink
Add new Iceberg game example.
Browse files Browse the repository at this point in the history
+ how to use pip to install external package
+ how to revise code
  • Loading branch information
li-yiyang committed Nov 7, 2024
1 parent 565e008 commit 9888f97
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 2 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## 2024秋

> [**UCAS本科CTF编程练习平台**](https://ucas-ctf.github.io/Coding)
>
>
> 目前仅更新了部分题目,测评系统和更多习题敬请期待!
### 第零次课————虚拟机的安装
Expand Down Expand Up @@ -132,5 +132,21 @@

[C语言进阶——C风格字符串](https://ucas-ctf.github.io/posts/2024/2_advanced_C/string)

</div>
</details>

### 第七次课 ———— Python 入门

<details>
<summary>点击展开</summary>
<div markdown="1">

#### 讲义

[Basic Python Introduction](https://ucas-ctf.github.io/posts/2024/3_basic_Python/trivial-python-introduction)

[Learn Some Python with Retro Computer Battle Games](https://ucas-ctf.github.io/posts/2024/3_basic_Python/learn-some-python-with-retro-computer-battle-games)


</div>
</details>
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ BASIC 语言, 而非 Python). 不过你可以将其作为课外消遣读物,
+ [Robot Missile](#robot-missile)
+ [The Vital Message](the-vital-message)
+ [Shootout](shootout)
+ [Iceberg](iceberg)

## Robot Missile
> The year is 2582 and the people of Earth are in the midst of battle
Expand Down Expand Up @@ -193,4 +194,99 @@ BASIC 代码 (其实不管是什么代码都行), 然后我们需要将其变成
我们可以考虑用这个 BASIC 来作为一个简单虚拟机的练习.

## Iceberg
>
> Your hull is badly damaged and you've no weapons to speak of.
> As you limp slowly home through treacherous iceberg-strewn waters,
> you become aware that an enemy ship is tailing you. Strangely it can
> detect you but not the icebergs, so your best chance is to lure it
> into hitting one.
>
> Your computer will print a grid showing the position of your ship
> (🚢), the enemy (😈) and the icebergs (🏔️). You can move one space North,
> South, East or West each go. The enemy moves towards you by the most
> direct route (it can move diagonally too). If you move into any of the
> 8 positions surrounding the enemy, you will be captured, and if you hit
> an iceberg you will sink.
>
> Can you escape?
是时候该从这些过于朴实无华的终端文本界面升级一下了,
我们将会演示如何下载并调用一个 TUI 的图形库: `curses`.



我们最终的目标是做一个能够显示如下效果的小游戏:

```text
```

### PIP, the python package manager
这里默认大家使用的是 Windows 作为自己的编程环境, 假如是 Unix-like 的环境,
那么大家可以只是看看这里的操作而不必真的执行下面的命令:

1. 打开命令提示符或者任意包含 conda / Python 的环境
2. 输入 `pip` 命令:

```shell
pip install windows-curses
```

这里, 我们使用 `pip install` 命令来下载一个叫作 `windows-curses` 的库.

类似的, 你完全可以下载其他任何的库, 比如 `numpy`, `scipy` 等等.

### 那么, 如何使用 `curses` 库呢?
首先, 相信大家的第一个想法就是跑到万能的互联网上搜索 `curses 教程`
关键词来找到或是 CSDN 或是 b 站视频或是别的什么的网站来找一个教程.

但是, 其实还有一个非常好用的文档/教程来源: 那就是官方的文档: [curses](https://docs.python.org/3/howto/curses.html).
让我们来看看下面的这段代码:

```python
# from https://docs.python.org/3/howto/curses.html#starting-and-ending-a-curses-application
from curses import wrapper

def main(stdscr):
# Clear screen
stdscr.clear()

# This raises ZeroDivisionError when i == 10.
for i in range(0, 11):
v = i-10
stdscr.addstr(i, 0, '10 divided by {} is {}'.format(v, 10/v))

stdscr.refresh()
stdscr.getkey()

wrapper(main)
```

将这段代码保存到一个 Python 文件中: [curses-example.py](./src/computer-battle-game/curses-example.py),
然后在终端中运行 `python curses-exaple.py`.

<details>
<summary>为什么一定要在终端中运行? </summary>
<div markdown="1">

因为大部分的 IDE 的终端是终端模拟器而非完整的终端,
而 curses 的背后其实是 C 的 curses 库, 需要一个终端环境.

</div>
</details>

### 假设大家已经差不多了解了 curses 这个库了 (大概)
那么请参考代码: [iceberg.py](./src/computer-battle-game/iceberg.py).

<details>
<summary>代码都写完了, 那么接下来做什么? </summary>
<div markdown="1">

其实代码写完了并不意味着这个程序就没有继续升级的地方了.
我们可以添加许多有意思的功能, 或者对代码进行重构和优化,
以提升可读性或是性能.

在这里我们将会以 Iceberg 的代码为例进行一定的优化和一般化.
假如还有时间的话, 我们可以介绍一下该如何用 OOP 的方式,
来重新构造这个小游戏的代码.

</div>
</details>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# from https://docs.python.org/3/howto/curses.html#starting-and-ending-a-curses-application
from curses import window, wrapper

def main(stdscr : window):
# Clear screen
stdscr.clear()

for i in range(0, 11):
v = i + 1
stdscr.addstr(i, 0, f"10 divided by {v} is {10 / v}")

stdscr.refresh()
stdscr.getkey()

if __name__ == "__main__":
wrapper(main)
116 changes: 116 additions & 0 deletions posts/2024/3_basic_Python/src/computer-battle-game/iceberg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from time import sleep
from random import randint
import curses

# 注: 这个代码为了展示重构, 所以特地把所有的内容都写在一起了,
# 请不要学习这样的错误做法.

stdscr = curses.initscr()

# 打印欢迎信息
stdscr.clear()
stdscr.addstr(0, 0, "ICEBERG")
stdscr.refresh()

## 初始化代码

# 这里我们用了一个小技巧来新建了一个 8x8 的空列表
iceberg_map = [[" " for i in range(8)] for j in range(8)]
iceberg_num = randint(4, 12)

# 这里我们使用 list of list 对双重的列表元素进行取值
for i in range(iceberg_num):
iceberg_map[randint(0, 7)][randint(0, 7)] = "#"

# 这里我们要找一个空格子并将敌人画在上面
enemy_x, enemy_y = randint(0, 7), randint(0, 7)
while iceberg_map[enemy_y][enemy_x] != " ":
enemy_x, enemy_y = randint(0, 7), randint(0, 7)
iceberg_map[enemy_y][enemy_x] = "Z"

# 同理, 我们将自己画在上面
# 思考: 是否这里的两段代码可以复用?
player_x, player_y = randint(0, 7), randint(0, 7)
while iceberg_map[player_y][player_x] != " ":
player_x, player_y = randint(0, 7), randint(0, 7)
iceberg_map[player_y][player_x] = "Y"

## 老板说在这里加上一个 sleep 函数, 后期让用户花钱买显卡以升级 pro 版,
## 到时候把这个 sleep 减少一点以让用户之后买更好的卡.
sleep(1)

# event loop
while True:
# 绘制地图
stdscr.clear()
for y in range(8):
for x in range(8):
stdscr.addstr(y, x, iceberg_map[y][x])
stdscr.addstr(8, 0, "DIRECTION (WASD to move)")
stdscr.refresh()

# 读取用户按键信息并进行移动
iceberg_map[player_y][player_x] = " "
while True:
direction = stdscr.getkey()
if direction == "w" and player_y != 0:
player_y = player_y - 1
break
elif direction == "s" and player_y != 7:
player_y = player_y + 1
break
elif direction == "a" and player_x != 0:
player_x = player_x - 1
break
elif direction == "d" and player_x != 7:
player_x = player_x + 1
break
elif direction == "q":
break

# 游戏终止
if direction == "q":
stdscr.clear()
stdscr.addstr(0, 0, "Game Aborted")
stdscr.refresh()
break

# 检测是否撞到冰山
if iceberg_map[player_y][player_x] == "#":
stdscr.addstr(8, 0, "YOU'VE HIT AN ICEBERG! ")
stdscr.refresh()
break

# 检测是否被敌人抓住
if iceberg_map[player_y][player_x] == "Z":
stdscr.addstr(8, 0, "YOU'VE BEEN CAUGHT ")
stdscr.refresh()
break

# 移动
iceberg_map[player_y][player_x] = "Y"

# 敌人移动
iceberg_map[enemy_y][enemy_x] = " "

# 这里用到了 Python 的 "三目运算符"
# 注: 只是类似的东西, 和 C 的还是有点区别
enemy_x += 1 if player_x > enemy_x else -1
enemy_y += 1 if player_y > enemy_y else -1

# 检测敌人是否撞到冰山
if iceberg_map[enemy_y][enemy_x] == "#":
stdscr.addstr(8, 0, "YOU'RE SAFE - HE'S HIT ONE! ")
stdscr.refresh()
break

# 检测敌人是否抓到我们
if iceberg_map[enemy_y][enemy_x] == "Y":
stdscr.addstr(8, 0, "YOU'VE BEEN CAUGHT ")
stdscr.refresh()
break

# 移动
iceberg_map[enemy_y][enemy_x] = "Z"

stdscr.getkey()

0 comments on commit 9888f97

Please sign in to comment.