Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ShenMian committed Mar 1, 2024
0 parents commit b96ad90
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 0 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Test

on:
push:
branches: [main]

env:
CARGO_TERM_COLOR: always

jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

steps:
- uses: actions/checkout@v4

- name: Setup mdBook
uses: peaceiris/actions-mdbook@v1
with:
mdbook-version: 'latest'

- run: mdbook build

- name: Deploy
uses: peaceiris/actions-gh-pages@v3
if: ${{ github.ref == 'refs/heads/main' }}
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./book
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
book
6 changes: 6 additions & 0 deletions book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[book]
title = "Sokoban Tutorial"
authors = ["ShenMian <sms_school@outlook.com>"]
language = "cn"
multilingual = false
src = "src"
7 changes: 7 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Summary

[介绍](introduction.md)

- [关卡](level/README.md)
- [解析](level/parse.md)
- [标准化](level/normalization.md)
Binary file added src/assets/boxworld_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 介绍

本文将描述如何实现一个推箱子游戏, 代码示例使用 Rust 语言编写.
推箱子游戏具有以下特点:

- 规则简单. 可以专注于实现功能, 而非理解复杂的游戏规则和机制.
- 基本功能易于实现.
- 有具有挑战的高级功能. 比如纯鼠标控制(也称为点推), 逆推等.
- 有需要深入钻研的求解器, 用于自动求解推箱子关卡.

本文将由浅入深的介绍上面功能并提供实现的思路.
126 changes: 126 additions & 0 deletions src/level/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# 关卡

推箱子关卡使用最广泛的格式为 XSB, 最初由 XSokoban 所使用. 该格式使用 ASCII 字符来表示地图元素, 支持注释和附加元数据.
以关卡 `Boxworld #1` 为例:

<img src="../assets/boxworld_1.png" alt="Boxworld #1" width="70%" style="display: block; margin: 0 auto"/>

其 XSB 格式关卡的数据如下:

```txt
;Level 1
__###___
__#.#___
__#-####
###$-$.#
#.-$@###
####$#__
___#.#__
___###__
Title: Boxworld 1
Author: Thinking Rabbit
```

- 第 1 行, 以 `;` 开头的单行注释.
- 第 2-9 行, 使用 ASCII 字符表示的地图数据.
- 第 10-11 行, 包括关卡标题和作者的元数据.

| ASCII 符号 | 描述 |
| ----------------- | -------------- |
| `<SPACE>`/`-`/`_` | Floor |
| `#` | Wall |
| `$` | Box |
| `.` | Goal |
| `@` | Player |
| `+` | Player on goal |
| `*` | Box on goal |

## 表示地图

地图共包含 5 种元素, 其中部分元素可能叠加(比如玩家位于目标上). 因此可以使用比特位来表示地图中的每个格子包含哪些元素.
创建用于表示地图元素的比特位:

```rs
use bitflags::bitflags;

bitflags! {
pub struct Tiles: u8 {
const Floor = 1 << 0;
const Wall = 1 << 1;
const Box = 1 << 2;
const Goal = 1 << 3;
const Player = 1 << 4;
}
}
```

使用一维数组来存储地图数据并使用二维向量存储地图尺寸.

```rs
use nalgebra::Vector2;

pub struct Map {
data: Vec<Tiles>,
dimensions: Vector2<i32>,
// ... SKIP ...
}
```

使用一维数组而非二维数组是因为一维数组更平坦, 进行部分操作时更简单高效:

```rs
impl Map {
pub fn with_dimensions(dimensions: Vector2<i32>) -> Self {
Self {
data: vec![Tiles::empty(); (dimensions.x * dimensions.y) as usize],
dimensions,
// ... SKIP ...
}
}
// ... SKIP ...
}
```

## 表示关卡

关卡数据可分为三个部分: 地图数据, 元数据和注释. 其中注释可以作为元数据.
元数据是一个键值对的集合, 因此可以使用 HashMap 来存储.
可以使用下面的结构体存储关卡数据:

```rs
pub struct Level {
map: Map,
metadata: HashMap<String, String>,
// ... SKIP ...
}
```

## 行程编码(Run length encoding)

行程编码(Run length encoding, RLE)经常被用于压缩推箱子的关卡和解决方案.

```txt
###
#.###
#*$ #
# @ #
#####
```

经 RLE 编码后可得:

```txt
3#
#.3#
#*$-#
#--@#
5#
```

可以看出, 虽然编码后的关卡有更小的体积, 但不再能直观地看出关卡的结构.

RLE 编码后的关卡通常还会使用 `|` 来分割行, 而非 `\n`. 使其看上去更加紧凑:

```txt
3#|#.3#|#*$-#|#--@#|5#
```
32 changes: 32 additions & 0 deletions src/level/normalization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 标准化

TODO

## 激进的标准化

激进的标准化可能改变关卡的解.
以最简单的关卡为例:

```txt
#####
#@$.#
#####
```

因为玩家开始的位置三面有墙, 位于死胡同, 只能向右移动. 得到结果:

```txt
#####
# @*#
#####
```

玩家左侧死胡同属于无用的区域, 使用墙体填充. 玩家右侧位于目标上的箱子处于死锁状态, 属于无用的箱子和目标, 使用墙体填充. 最后去除多余的墙体得到激进的标准化结果:

```txt
###
#@#
###
```

这是一个最简单的非标准关卡, 因为其没有箱子和目标, 解为空.
5 changes: 5 additions & 0 deletions src/level/parse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# 解析

## 单个关卡解析

## 多个关卡解析

0 comments on commit b96ad90

Please sign in to comment.