feat(ctf): add Chinese README and initial Python policy implementation
Add a comprehensive Chinese README (README‑cn.md) that explains the Capture‑the‑Flag game rules, setup instructions, and participant tasks. Introduce a new backend script (backend/mypolicy.py) providing a basic world model, start_game, and plan_next_actions functions with simple flag‑assignment logic, serving as a starter implementation for the CTF challenge. This documentation and starter code help participants understand and begin developing their own algorithms.
This commit is contained in:
79
CTF/README-cn.md
Normal file
79
CTF/README-cn.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# 捕获旗帜 (Capture the Flag)
|
||||
|
||||
你的任务是实现自己的算法(参见 `backend/server.cpp`),控制一个队伍参加 “Capture the Flag” 游戏。
|
||||
|
||||
## 游戏规则
|
||||
Capture the Flag 是一种流行的户外游戏,两个队伍在开放的场地上竞争。
|
||||
每个队伍都有自己的领地和一组位于领地内的旗帜。每个队伍的目标是收集对方队伍的旗帜并将其带回目标区域。
|
||||
|
||||
玩家可以在自己的领地内标记(tag)对方队伍的玩家。被标记后,对方玩家会被送入监狱。该玩家将在监狱中停留一段时间,除非被队友提前救出。
|
||||
旗帜、目标区域和监狱的初始位置都在各自队伍的领地内。
|
||||
玩家只能捡起对方队伍的旗帜,不能捡起自己队伍的旗帜并放到别处。
|
||||
|
||||
我们的游戏有两支队伍:“L” 队和 “R” 队。场地是一个矩形区域,左半边是 “L” 队的领地,右半边是 “R” 队的领地。地图中还有障碍物和墙壁。
|
||||
|
||||

|
||||
|
||||
## 游玩方式
|
||||
|
||||
游戏分为两部分:
|
||||
- **frontend/**:启动游戏的 Web 服务器,使用 JavaScript 编写。它可以选择性地连接到两个后端服务器来移动玩家。你不应修改该代码,但可以阅读它以了解如何生成地图以及如何与后端通信。
|
||||
- **backend/**:后端服务器,向前端发送指令以移动玩家。你需要在这里实现自己的算法。请注意,在真实比赛中,你的服务器控制一支队伍,另一支队伍由对手实现。
|
||||
|
||||
如果想手动玩游戏,可以使用 `w a s d` 键控制 L 队,使用 `↑ ← ↓ →` 键控制 R 队。按键会覆盖后端服务器的决策。注意,按键会让所有玩家朝同一方向移动,而你的代码可以独立控制每个玩家的移动。
|
||||
|
||||
按 **SPACE** 键开始、暂停或继续游戏。
|
||||
|
||||
1. 安装依赖
|
||||
```bash
|
||||
brew update;
|
||||
brew install boost nlohmann-json
|
||||
```
|
||||
2. 编译 `server.cpp`
|
||||
```bash
|
||||
cd backend/;
|
||||
g++ -std=c++17 server.cpp -I/opt/homebrew/include -L/opt/homebrew/lib -lpthread -o server
|
||||
```
|
||||
3. 在端口 8081(可使用其他端口)运行服务器
|
||||
```bash
|
||||
./server 8081
|
||||
```
|
||||
4. 将 `assets/remote_config.json` 中的端口更新为本地端口。把 `ws_url` 改为你的端口。
|
||||
```json
|
||||
{
|
||||
"teams": [
|
||||
{ "name": "L", "ws_url": "ws://localhost:8080" },
|
||||
{ "name": "R", "ws_url": "ws://localhost:8081" }
|
||||
]
|
||||
}
|
||||
```
|
||||
5. 启动前端网站
|
||||
```bash
|
||||
cd frontend/;
|
||||
python3 -m http.server 8000
|
||||
```
|
||||
6. 在浏览器中打开 `http://localhost:8000/index.html` 开始游戏。
|
||||
- 按 **Cmd + Option + I**(macOS)打开 DevTools
|
||||
- 切换到 **Network** 面板
|
||||
- 勾选 ✅ “Disable cache”(左上角工具栏),确保加载到最新的 `remote_config.json`
|
||||
|
||||
## 你的任务
|
||||
|
||||
在 `backend/server.cpp` 中实现 `startGame(req)` 与 `planNextActions(req, ws)` 两个函数。
|
||||
- `startGame(req)` 在游戏开始时调用一次。`req` 包含游戏信息,如地图(高度、宽度、障碍位置)以及队伍信息(名称、玩家数量、旗帜数量)。
|
||||
- `planNextActions(req, ws)` 定期调用,以更新所有玩家和旗帜的信息。你应使用 `ws` 向前端发送你队伍玩家的动作。目前的实现会为每个玩家返回随机动作。
|
||||
- `gameOver(req)` 在游戏结束并确定胜者后调用一次。你可以在这里清理状态,以便下一次 `startGame` 使用。
|
||||
|
||||
## Write up(重要!)
|
||||
|
||||
你必须提交一篇 Markdown 文档,内容包括:
|
||||
1. 对抗对手的 **3‑5 条关键策略决策**。解释直觉、核心思路以及技术细节(如数据结构与算法)。
|
||||
2. 在调试实现或与朋友对战时出现的有趣、好笑的瞬间。以及在测试后你做了哪些改动?
|
||||
|
||||
## 示例队伍(Python 版)
|
||||
|
||||
我们提供了一个 “不太笨拙” 的 Python 后端,作为你的竞争对手示例。**不要把这个 Python 算法直接翻译成 C++ 作为你的实现**。要运行 Python 示例:
|
||||
```bash
|
||||
python3 pick_closest_flag.py 8081
|
||||
```
|
||||
你需要使用 `pip3 install` 安装依赖,例如 `asyncio`。
|
||||
102
CTF/backend/mypolicy.py
Normal file
102
CTF/backend/mypolicy.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import asyncio
|
||||
import random
|
||||
from lib.game_engine import GameMap, run_game_server
|
||||
import threading
|
||||
|
||||
|
||||
# 1. Initialize the global world model
|
||||
world = GameMap(show_gap_in_msec=1000.0)
|
||||
lock = threading.Lock()
|
||||
last_updated_time = -1
|
||||
update_threshold = 100
|
||||
player_to_flag_assignments = {}
|
||||
|
||||
def start_game(req):
|
||||
global player_to_flag_assignments
|
||||
world.init(req)
|
||||
print("Start Game!!")
|
||||
player_to_flag_assignments = {}
|
||||
print(f"Game Started! Side: {'Left' if world.is_on_left(list(world.my_team_target)[0]) else 'Right'}")
|
||||
|
||||
def plan_next_actions(req):
|
||||
if not world.update(req):
|
||||
return
|
||||
|
||||
global player_to_flag_assignments
|
||||
|
||||
# Render the map
|
||||
# world.show(do_not_clear=False)
|
||||
|
||||
my_players = world.list_players(mine=True, inPrison=False, hasFlag=None)
|
||||
opponents = world.list_players(mine=False, inPrison=False, hasFlag=None)
|
||||
enemy_flags = world.list_flags(mine=False, canPickup=True)
|
||||
my_targets = list(world.list_targets(mine=True))
|
||||
|
||||
# 2. Logic: Assign flags to players without flags
|
||||
# We maintain the original logic of matching players to specific flag coordinates
|
||||
active_player_names = {p["name"] for p in my_players if not p["hasFlag"]}
|
||||
|
||||
# Cleanup assignments for players captured or flags already taken
|
||||
player_to_flag_assignments = {
|
||||
name: pos for name, pos in player_to_flag_assignments.items()
|
||||
if name in active_player_names
|
||||
}
|
||||
|
||||
if enemy_flags:
|
||||
for p in my_players:
|
||||
if not p["hasFlag"] and p["name"] not in player_to_flag_assignments:
|
||||
# Randomly assign one of the available enemy flags
|
||||
f = random.choice(enemy_flags)
|
||||
player_to_flag_assignments[p["name"]] = (f["posX"], f["posY"])
|
||||
|
||||
# 3. Plan moves for each player
|
||||
player_moves = {}
|
||||
my_side_is_left = world.is_on_left(my_targets[0])
|
||||
|
||||
for p in my_players:
|
||||
curr_pos = (p["posX"], p["posY"])
|
||||
|
||||
# Determine Target: Either the assigned flag or the home target
|
||||
if p["hasFlag"]:
|
||||
dest = my_targets[0]
|
||||
elif p["name"] in player_to_flag_assignments:
|
||||
dest = player_to_flag_assignments[p["name"]]
|
||||
else:
|
||||
continue
|
||||
|
||||
# Determine Obstacles: Avoid opponents if we are in enemy territory
|
||||
is_safe = world.is_on_left(curr_pos) == my_side_is_left
|
||||
blockers = [] if is_safe else [(o["posX"], o["posY"]) for o in opponents]
|
||||
|
||||
# Calculate Path
|
||||
path = world.route_to(curr_pos, dest, extra_obstacles=blockers)
|
||||
|
||||
if len(path) > 1:
|
||||
move = world.get_direction(curr_pos, path[1])
|
||||
player_moves[p["name"]] = move
|
||||
|
||||
return player_moves
|
||||
|
||||
def game_over(req):
|
||||
print("Game Over!")
|
||||
world.show(force=True)
|
||||
|
||||
async def main():
|
||||
import sys
|
||||
if len(sys.argv) != 2:
|
||||
print(f"Usage: python3 {sys.argv[0]} <port>")
|
||||
print(f"Example: python3 {sys.argv[0]} 8080")
|
||||
sys.exit(1)
|
||||
|
||||
port = int(sys.argv[1])
|
||||
print(f"AI backend running on port {port} ...")
|
||||
|
||||
try:
|
||||
await run_game_server(port, start_game, plan_next_actions, game_over)
|
||||
except Exception as e:
|
||||
print(f"Server Stopped: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user