slow.monster

STUDIO HAZE · jeniapp

Topaz source

Open by house policy. Topaz is our own language.

Topaz 5.20.2 · 63 files

apps/secret-train/data/levels.tpz

Topaz · 10335 B · Original file

1// 비밀열차 다섯 칸의 지도, 경비 순찰, 흔들림 일정을 정하고 지도 위의 칸을 묻는 함수를 둡니다.
2// 지도는 행 문자열로 쓰며 # 은 벽, S 는 좌석, 나머지는 바닥입니다. 흐름은 시작, 열쇠, 가방, 출구 순입니다.
3// 모든 값은 상수와 턴 번호의 함수라 같은 턴이면 늘 같은 순찰과 흔들림이 나옵니다.
4// 지도 밖은 벽으로 보며 경로 찾기와 난수는 쓰지 않습니다.
5// Secret train level data: car maps, guard patrols, jolt schedules and cell queries.
7export enum 타일 { 바닥, 벽, 좌석 }
8export type 칸 = { x: int, y: int }
9export type 방향 = "위" | "오른쪽" | "아래" | "왼쪽"
10export record 순찰점 { x: int, y: int, 방향: 방향 }
11export record 경비 { 순찰: Array<순찰점> }
12export record 레벨정보 {
13 번호: int,
14 너비: int,
15 높이: int,
16 격자: Array<타일>,
17 시작: 칸,
18 열쇠: 칸,
19 가방: 칸,
20 출구: 칸,
21 제한턴: int,
22 시야거리: int,
23 흔들주기: int,
24 동쪽턴: int,
25 서쪽턴: int,
26 경비들: Array<경비>,
27 시작단계들: Array<int>,
30// 칸막이가 길을 가르고 경비가 통로를 왕복하며 길목을 비춥니다. 뒤 칸일수록 건널 통로가 늘어납니다.
31let 통로하나 = [
32 "#############",
33 "#...........#",
34 "#.S.S.S.S.S.#",
35 "###.#####.###",
36 "#...........#",
37 "#.S.S.S.S.S.#",
38 "#...........#",
39 "#...........#",
40 "#############",
42let 통로둘 = [
43 "#############",
44 "#...........#",
45 "###.#####.###",
46 "#...........#",
47 "#.S.S.S.S.S.#",
48 "###.#####.###",
49 "#...........#",
50 "#...........#",
51 "#############",
53let 통로셋 = [
54 "#############",
55 "#...........#",
56 "###.#####.###",
57 "#...........#",
58 "###.#####.###",
59 "#...........#",
60 "###.#####.###",
61 "#...........#",
62 "#############",
64let 트인객실 = [
65 "#############",
66 "#...........#",
67 "#.S.S.S.S.S.#",
68 "#...........#",
69 "#.S.S.S.S.S.#",
70 "#...........#",
71 "#.S.S.S.S.S.#",
72 "#...........#",
73 "#############",
76// 격자는 행을 이어 붙인 평면 배열입니다.
77function 격자만들기(행들: Array<string>) -> Array<타일> {
78 행들.join("").scalars().map(글자 => match 글자 { case "#" => 타일.벽; case "S" => 타일.좌석; case _ => 타일.바닥 })
81// 양 끝 칸은 돌아설 때 한 번만 밟으므로 한 바퀴가 2 × (끝 - 시작) 단계입니다.
82function 가로경비(y: int, 시작: int, 끝: int) -> 경비 {
83 let 가는길 = [for x in 시작..끝 => 순찰점 { x: x, y: y, 방향: "오른쪽" }]
84 let 오는길 = [for x in (끝 - 1)..(시작 + 1) by -1 => 순찰점 { x: x, y: y, 방향: "왼쪽" }]
85 경비 { 순찰: [for 구간 in [가는길, 오는길] for 점 in 구간 => 점] }
88function 세로경비(x: int, 시작: int, 끝: int) -> 경비 {
89 let 가는길 = [for y in 시작..끝 => 순찰점 { x: x, y: y, 방향: "아래" }]
90 let 오는길 = [for y in (끝 - 1)..(시작 + 1) by -1 => 순찰점 { x: x, y: y, 방향: "위" }]
91 경비 { 순찰: [for 구간 in [가는길, 오는길] for 점 in 구간 => 점] }
94// 제한턴은 최단 경로에 여유 몇 턴만 더해 흔들림을 타야 시간 안에 나가게 합니다.
95// 첫 칸은 짧은 순찰과 드문 흔들림으로 규칙을 익히게 하고, 뒤 칸으로 갈수록 발각률이 고르게 오릅니다.
96let 첫칸 = 레벨정보 {
97 번호: 1,
98 너비: 13,
99 높이: 9,
100 격자: 격자만들기(통로하나),
101 시작: { x: 1, y: 1 },
102 열쇠: { x: 11, y: 1 },
103 가방: { x: 11, y: 6 },
104 출구: { x: 1, y: 6 },
105 제한턴: 36,
106 시야거리: 6,
107 흔들주기: 7,
108 동쪽턴: 3,
109 서쪽턴: 6,
110 경비들: [가로경비(4, 5, 8)],
111 시작단계들: [0],
113let 둘째칸 = 레벨정보 {
114 번호: 2,
115 너비: 13,
116 높이: 9,
117 격자: 격자만들기(통로둘),
118 시작: { x: 1, y: 1 },
119 열쇠: { x: 11, y: 1 },
120 가방: { x: 11, y: 7 },
121 출구: { x: 1, y: 7 },
122 제한턴: 38,
123 시야거리: 6,
124 흔들주기: 7,
125 동쪽턴: 3,
126 서쪽턴: 6,
127 경비들: [가로경비(3, 1, 11), 가로경비(6, 3, 9)],
128 시작단계들: [6, 6],
130let 셋째칸 = 레벨정보 {
131 번호: 3,
132 너비: 13,
133 높이: 9,
134 격자: 격자만들기(통로셋),
135 시작: { x: 1, y: 1 },
136 열쇠: { x: 11, y: 1 },
137 가방: { x: 11, y: 7 },
138 출구: { x: 1, y: 7 },
139 제한턴: 42,
140 시야거리: 6,
141 흔들주기: 5,
142 동쪽턴: 2,
143 서쪽턴: 4,
144 경비들: [가로경비(3, 1, 11), 가로경비(5, 1, 11), 가로경비(7, 1, 11)],
145 시작단계들: [0, 0, 7],
147// 넷째 칸은 가로와 세로 시야가 엇갈리고, 다섯째 칸은 세로 시야 둘 사이를 지나야 합니다.
148let 넷째칸 = 레벨정보 {
149 번호: 4,
150 너비: 13,
151 높이: 9,
152 격자: 격자만들기(트인객실),
153 시작: { x: 1, y: 1 },
154 열쇠: { x: 11, y: 1 },
155 가방: { x: 11, y: 7 },
156 출구: { x: 1, y: 7 },
157 제한턴: 34,
158 시야거리: 6,
159 흔들주기: 5,
160 동쪽턴: 2,
161 서쪽턴: 4,
162 경비들: [가로경비(3, 1, 11), 세로경비(9, 1, 7)],
163 시작단계들: [0, 3],
165let 다섯째칸 = 레벨정보 {
166 번호: 5,
167 너비: 13,
168 높이: 9,
169 격자: 격자만들기(트인객실),
170 시작: { x: 1, y: 1 },
171 열쇠: { x: 11, y: 1 },
172 가방: { x: 11, y: 7 },
173 출구: { x: 1, y: 7 },
174 제한턴: 32,
175 시야거리: 6,
176 흔들주기: 5,
177 동쪽턴: 2,
178 서쪽턴: 4,
179 경비들: [세로경비(5, 1, 7), 세로경비(9, 1, 7)],
180 시작단계들: [0, 0],
183// 목록 순서가 칸을 지나는 순서이고 레벨 코드는 L 과 두 자리 번호입니다. 칸을 더할 때는 이 목록에만 넣습니다.
184let 레벨목록 = [첫칸, 둘째칸, 셋째칸, 넷째칸, 다섯째칸]
186export function 레벨코드(레벨: 레벨정보) -> string {
187 if 레벨.번호 < 10 { "L0{레벨.번호}" } else { "L{레벨.번호}" }
190function 레벨자리들(레벨코드값: string) -> Array<int> {
191 [for i in 0..<레벨목록.length if 레벨코드(레벨목록[i]) == 레벨코드값 => i]
194export function 레벨코드인가(레벨코드값: string) -> bool {
195 레벨자리들(레벨코드값).length > 0
198// 저장 문자열의 알 수 없는 코드는 첫 칸으로 읽습니다.
199export function 레벨얻기(레벨코드값: string) -> 레벨정보 {
200 let 자리들 = 레벨자리들(레벨코드값)
201 레벨목록[if 자리들.length > 0 { 자리들[0] } else { 0 }]
204// 빈 문자열은 마지막 칸이라 다음이 없다는 뜻입니다. 알 수 없는 코드도 다음이 없습니다.
205export function 다음레벨코드(레벨코드값: string) -> string {
206 let 자리들 = 레벨자리들(레벨코드값)
207 if 자리들.length > 0 && 자리들[0] + 1 < 레벨목록.length { 레벨코드(레벨목록[자리들[0] + 1]) } else { "" }
210// 저장 문자열과 그리기 목록의 방향 번호는 0 위, 1 오른쪽, 2 아래, 3 왼쪽입니다. 그 밖의 번호는 처음 방향인 아래로 읽습니다.
211export function 방향읽기(번호: int) -> 방향 {
212 match 번호 {
213 case 0 => "위"
214 case 1 => "오른쪽"
215 case 3 => "왼쪽"
216 case _ => "아래"
217 }
220export function 방향번호(방향값: 방향) -> int {
221 match 방향값 {
222 case "위" => 0
223 case "오른쪽" => 1
224 case "아래" => 2
225 case "왼쪽" => 3
226 }
229export function 가로이동(방향값: 방향) -> int {
230 match 방향값 {
231 case "오른쪽" => 1
232 case "왼쪽" => -1
233 case "위" | "아래" => 0
234 }
237export function 세로이동(방향값: 방향) -> int {
238 match 방향값 {
239 case "아래" => 1
240 case "위" => -1
241 case "오른쪽" | "왼쪽" => 0
242 }
245// 경비마다 저장된 단계에서 한 단계씩 나아갑니다.
246export function 다음단계들(레벨: 레벨정보, 단계들: Array<int>) -> Array<int> {
247 [for i in 0..<단계들.length => (단계들[i] + 1) % 레벨.경비들[i].순찰.length]
250// 저장 문자열에서 온 단계는 복원할 때 순찰 길이 안으로 가두었습니다.
251export function 경비점(레벨: 레벨정보, 순번: int, 단계: int) -> 순찰점 {
252 레벨.경비들[순번].순찰[단계]
255export function 경비점들(레벨: 레벨정보, 단계들: Array<int>) -> Array<순찰점> {
256 [for i in 0..<단계들.length => 경비점(레벨, i, 단계들[i])]
259// 1 은 오른쪽, -1 은 왼쪽, 0 은 흔들림 없음입니다. 출발한 턴에는 흔들리지 않습니다.
260export function 흔들림방향(레벨: 레벨정보, 턴: int) -> int {
261 if 턴 <= 0 { return 0 }
262 let 위상 = 턴 % 레벨.흔들주기
263 if 위상 == 레벨.동쪽턴 { 1 } else if 위상 == 레벨.서쪽턴 { -1 } else { 0 }
266export function 칸타일(레벨: 레벨정보, x: int, y: int) -> 타일 {
267 if x < 0 || y < 0 || x >= 레벨.너비 || y >= 레벨.높이 { return 타일.벽 }
268 레벨.격자[y * 레벨.너비 + x]
271// 셸은 열거형 이름이 아니라 0 바닥, 1 벽, 2 좌석 숫자로 격자를 그립니다.
272export function 격자문자열(레벨: 레벨정보) -> string {
273 레벨.격자.map(종류 => match 종류 { case 바닥 => "0"; case 벽 => "1"; case 좌석 => "2" }).join("")
276export function 막힌칸인가(레벨: 레벨정보, x: int, y: int) -> bool {
277 match 칸타일(레벨, x, y) { case 벽 | 좌석 => true; case 바닥 => false }
280export function 좌석인가(레벨: 레벨정보, x: int, y: int) -> bool {
281 match 칸타일(레벨, x, y) { case 좌석 => true; case 바닥 | 벽 => false }
284export function 바닥인가(레벨: 레벨정보, x: int, y: int) -> bool {
285 match 칸타일(레벨, x, y) { case 바닥 => true; case 벽 | 좌석 => false }
288// 바로 옆 네 칸 중 좌석이 있으면 그 좌석을 잡고 버팁니다.
289export function 좌석옆인가(레벨: 레벨정보, x: int, y: int) -> bool {
290 좌석인가(레벨, x + 1, y) || 좌석인가(레벨, x - 1, y) || 좌석인가(레벨, x, y + 1) || 좌석인가(레벨, x, y - 1)
293// 경비 정면으로 시야거리까지 뻗고 처음 막힌 칸 앞에서 끊깁니다.
294export function 시야칸들(레벨: 레벨정보, 점: 순찰점) -> Array<칸> {
295 let mut 칸들: Array<칸> = []
296 for k in 1..레벨.시야거리 {
297 let x = 점.x + 가로이동(점.방향) * k
298 let y = 점.y + 세로이동(점.방향) * k
299 if 막힌칸인가(레벨, x, y) { break }
300 칸들.push({ x: x, y: y })
301 }
302 칸들

SHA256 257d58bf8268f999bd0e4a90ab89f9cd76487adf432b7cb2bafb566f58046b17