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.7
export enum 타일 { 바닥, 벽, 좌석 }8
export type 칸 = { x: int, y: int }9
export type 방향 = "위" | "오른쪽" | "아래" | "왼쪽"10
export record 순찰점 { x: int, y: int, 방향: 방향 }11
export record 경비 { 순찰: Array<순찰점> }12
export 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>,28
}30
// 칸막이가 길을 가르고 경비가 통로를 왕복하며 길목을 비춥니다. 뒤 칸일수록 건널 통로가 늘어납니다.31
let 통로하나 = [32
"#############",33
"#...........#",34
"#.S.S.S.S.S.#",35
"###.#####.###",36
"#...........#",37
"#.S.S.S.S.S.#",38
"#...........#",39
"#...........#",40
"#############",41
]42
let 통로둘 = [43
"#############",44
"#...........#",45
"###.#####.###",46
"#...........#",47
"#.S.S.S.S.S.#",48
"###.#####.###",49
"#...........#",50
"#...........#",51
"#############",52
]53
let 통로셋 = [54
"#############",55
"#...........#",56
"###.#####.###",57
"#...........#",58
"###.#####.###",59
"#...........#",60
"###.#####.###",61
"#...........#",62
"#############",63
]64
let 트인객실 = [65
"#############",66
"#...........#",67
"#.S.S.S.S.S.#",68
"#...........#",69
"#.S.S.S.S.S.#",70
"#...........#",71
"#.S.S.S.S.S.#",72
"#...........#",73
"#############",74
]76
// 격자는 행을 이어 붙인 평면 배열입니다.77
function 격자만들기(행들: Array<string>) -> Array<타일> {78
행들.join("").scalars().map(글자 => match 글자 { case "#" => 타일.벽; case "S" => 타일.좌석; case _ => 타일.바닥 })79
}81
// 양 끝 칸은 돌아설 때 한 번만 밟으므로 한 바퀴가 2 × (끝 - 시작) 단계입니다.82
function 가로경비(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 구간 => 점] }86
}88
function 세로경비(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 구간 => 점] }92
}94
// 제한턴은 최단 경로에 여유 몇 턴만 더해 흔들림을 타야 시간 안에 나가게 합니다.95
// 첫 칸은 짧은 순찰과 드문 흔들림으로 규칙을 익히게 하고, 뒤 칸으로 갈수록 발각률이 고르게 오릅니다.96
let 첫칸 = 레벨정보 {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],112
}113
let 둘째칸 = 레벨정보 {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],129
}130
let 셋째칸 = 레벨정보 {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],146
}147
// 넷째 칸은 가로와 세로 시야가 엇갈리고, 다섯째 칸은 세로 시야 둘 사이를 지나야 합니다.148
let 넷째칸 = 레벨정보 {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],164
}165
let 다섯째칸 = 레벨정보 {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],181
}183
// 목록 순서가 칸을 지나는 순서이고 레벨 코드는 L 과 두 자리 번호입니다. 칸을 더할 때는 이 목록에만 넣습니다.184
let 레벨목록 = [첫칸, 둘째칸, 셋째칸, 넷째칸, 다섯째칸]186
export function 레벨코드(레벨: 레벨정보) -> string {187
if 레벨.번호 < 10 { "L0{레벨.번호}" } else { "L{레벨.번호}" }188
}190
function 레벨자리들(레벨코드값: string) -> Array<int> {191
[for i in 0..<레벨목록.length if 레벨코드(레벨목록[i]) == 레벨코드값 => i]192
}194
export function 레벨코드인가(레벨코드값: string) -> bool {195
레벨자리들(레벨코드값).length > 0196
}198
// 저장 문자열의 알 수 없는 코드는 첫 칸으로 읽습니다.199
export function 레벨얻기(레벨코드값: string) -> 레벨정보 {200
let 자리들 = 레벨자리들(레벨코드값)201
레벨목록[if 자리들.length > 0 { 자리들[0] } else { 0 }]202
}204
// 빈 문자열은 마지막 칸이라 다음이 없다는 뜻입니다. 알 수 없는 코드도 다음이 없습니다.205
export function 다음레벨코드(레벨코드값: string) -> string {206
let 자리들 = 레벨자리들(레벨코드값)207
if 자리들.length > 0 && 자리들[0] + 1 < 레벨목록.length { 레벨코드(레벨목록[자리들[0] + 1]) } else { "" }208
}210
// 저장 문자열과 그리기 목록의 방향 번호는 0 위, 1 오른쪽, 2 아래, 3 왼쪽입니다. 그 밖의 번호는 처음 방향인 아래로 읽습니다.211
export function 방향읽기(번호: int) -> 방향 {212
match 번호 {213
case 0 => "위"214
case 1 => "오른쪽"215
case 3 => "왼쪽"216
case _ => "아래"217
}218
}220
export function 방향번호(방향값: 방향) -> int {221
match 방향값 {222
case "위" => 0223
case "오른쪽" => 1224
case "아래" => 2225
case "왼쪽" => 3226
}227
}229
export function 가로이동(방향값: 방향) -> int {230
match 방향값 {231
case "오른쪽" => 1232
case "왼쪽" => -1233
case "위" | "아래" => 0234
}235
}237
export function 세로이동(방향값: 방향) -> int {238
match 방향값 {239
case "아래" => 1240
case "위" => -1241
case "오른쪽" | "왼쪽" => 0242
}243
}245
// 경비마다 저장된 단계에서 한 단계씩 나아갑니다.246
export function 다음단계들(레벨: 레벨정보, 단계들: Array<int>) -> Array<int> {247
[for i in 0..<단계들.length => (단계들[i] + 1) % 레벨.경비들[i].순찰.length]248
}250
// 저장 문자열에서 온 단계는 복원할 때 순찰 길이 안으로 가두었습니다.251
export function 경비점(레벨: 레벨정보, 순번: int, 단계: int) -> 순찰점 {252
레벨.경비들[순번].순찰[단계]253
}255
export function 경비점들(레벨: 레벨정보, 단계들: Array<int>) -> Array<순찰점> {256
[for i in 0..<단계들.length => 경비점(레벨, i, 단계들[i])]257
}259
// 1 은 오른쪽, -1 은 왼쪽, 0 은 흔들림 없음입니다. 출발한 턴에는 흔들리지 않습니다.260
export function 흔들림방향(레벨: 레벨정보, 턴: int) -> int {261
if 턴 <= 0 { return 0 }262
let 위상 = 턴 % 레벨.흔들주기263
if 위상 == 레벨.동쪽턴 { 1 } else if 위상 == 레벨.서쪽턴 { -1 } else { 0 }264
}266
export function 칸타일(레벨: 레벨정보, x: int, y: int) -> 타일 {267
if x < 0 || y < 0 || x >= 레벨.너비 || y >= 레벨.높이 { return 타일.벽 }268
레벨.격자[y * 레벨.너비 + x]269
}271
// 셸은 열거형 이름이 아니라 0 바닥, 1 벽, 2 좌석 숫자로 격자를 그립니다.272
export function 격자문자열(레벨: 레벨정보) -> string {273
레벨.격자.map(종류 => match 종류 { case 바닥 => "0"; case 벽 => "1"; case 좌석 => "2" }).join("")274
}276
export function 막힌칸인가(레벨: 레벨정보, x: int, y: int) -> bool {277
match 칸타일(레벨, x, y) { case 벽 | 좌석 => true; case 바닥 => false }278
}280
export function 좌석인가(레벨: 레벨정보, x: int, y: int) -> bool {281
match 칸타일(레벨, x, y) { case 좌석 => true; case 바닥 | 벽 => false }282
}284
export function 바닥인가(레벨: 레벨정보, x: int, y: int) -> bool {285
match 칸타일(레벨, x, y) { case 바닥 => true; case 벽 | 좌석 => false }286
}288
// 바로 옆 네 칸 중 좌석이 있으면 그 좌석을 잡고 버팁니다.289
export function 좌석옆인가(레벨: 레벨정보, x: int, y: int) -> bool {290
좌석인가(레벨, x + 1, y) || 좌석인가(레벨, x - 1, y) || 좌석인가(레벨, x, y + 1) || 좌석인가(레벨, x, y - 1)291
}293
// 경비 정면으로 시야거리까지 뻗고 처음 막힌 칸 앞에서 끊깁니다.294
export function 시야칸들(레벨: 레벨정보, 점: 순찰점) -> Array<칸> {295
let mut 칸들: Array<칸> = []296
for k in 1..레벨.시야거리 {297
let x = 점.x + 가로이동(점.방향) * k298
let y = 점.y + 세로이동(점.방향) * k299
if 막힌칸인가(레벨, x, y) { break }300
칸들.push({ x: x, y: y })301
}302
칸들303
}SHA256 257d58bf8268f999bd0e4a90ab89f9cd76487adf432b7cb2bafb566f58046b17