slow.monster

STUDIO HAZE · jeniapp

Topaz source

Open by house policy. Topaz is our own language.

Topaz 5.20.2 · 63 files

apps/slow-monster/core/auto.tpz

Topaz · 8687 B · Original file

1// 자동 진행 정책입니다. 상태를 보고 다음 입력 하나를 정하고, 같은 스텝 엔진이 그 입력을 적용합니다.
2// 필드의 우선순위는 위험하면 인물에게 회복, 아니면 가장 가까운 보스 아닌 야생, 다 비우면 연못은 워프로 내려가고 심층은 보스입니다. 이동은 목표 칸에서 퍼지는 너비 우선 거리장으로 정합니다.
3// 이웃 순서를 아래, 위, 왼쪽, 오른쪽으로 고정해 같은 상태는 늘 같은 입력입니다.
4// 분기 진화는 기본으로 멈춰 플레이어가 고르게 하고, 자동 고르기는 공개된 후보 수만 씁니다. 페이지는 타이머로 auto 를 보내고 그 사이를 그리기만 합니다.
5// Auto-play policy: picks the next ordinary input from the state; branch evolution pauses for the player by default.
7import data.maps { 맵너비, 맵높이, 맵타일들, 타일에서, 워프목록, 워프, 타일_물, 타일_나무 }
8import data.entities
9import core.state
10import core.creature
11import core.evolution { 후보수, 계통고유번호, 진화시작 }
12import core.rng { 난수범위 }
13import core.util { 범위가두기 }
15// 보스는 보통 야생 목표에서 빼고 다른 야생을 다 비운 뒤의 마지막 상대로만 다룹니다.
16let 보스아이디 = 14
18// 아래, 위, 왼쪽, 오른쪽입니다. 이 순서가 같은 거리의 이웃 중 무엇을 고를지 정합니다.
19let 이웃x차 = [0, 0, -1, 1]
20let 이웃y차 = [1, -1, 0, 0]
21let 이웃방향 = ["down", "up", "left", "right"]
23// 목표 칸은 막혀 있어도 거리장의 출발점으로 따로 넣습니다.
24function 통행가능한가(타일들: Array<int>, 맵아이디: string, x: int, y: int, 처치된것들: Array<int>) -> bool {
25 let 타일 = 타일에서(타일들, x, y)
26 타일 != 타일_물 && 타일 != 타일_나무 && !entities.활성엔티티칸인가(맵아이디, x, y, 처치된것들)
29function 맵안인가(x: int, y: int) -> bool {
30 x >= 0 && y >= 0 && x < 맵너비 && y < 맵높이
33// 목표 칸이 0 이고 닿지 못하는 칸은 -1 입니다.
34function 거리장(목표칸들: Array<int>, 타일들: Array<int>, 맵아이디: string, 처치된것들: Array<int>) -> Array<int> {
35 let 칸수 = 맵너비 * 맵높이
36 let mut 거리 = [for i in 0..<칸수 => -1]
37 let mut 대기열: Array<int> = []
38 for 칸 in 목표칸들 {
39 if 칸 >= 0 && 칸 < 칸수 && 거리[칸] == -1 {
40 거리[칸] = 0
41 대기열.push(칸)
42 }
43 }
44 let mut 머리 = 0
45 while 머리 < 대기열.length {
46 let 칸 = 대기열[머리]
47 머리 += 1
48 for k in 0..<4 {
49 let x = 칸 % 맵너비 + 이웃x차[k]
50 let y = 칸 / 맵너비 + 이웃y차[k]
51 if 맵안인가(x, y) && 거리[y * 맵너비 + x] == -1 && 통행가능한가(타일들, 맵아이디, x, y, 처치된것들) {
52 거리[y * 맵너비 + x] = 거리[칸] + 1
53 대기열.push(y * 맵너비 + x)
54 }
55 }
56 }
57 거리
60// 지금 칸보다 확실히 가까운 이웃 중 고정 순서의 첫째로 갑니다. 그런 이웃이 없으면 "" 입니다.
61function 다가가기방향(거리: Array<int>, x: int, y: int) -> string {
62 let 내칸 = y * 맵너비 + x
63 let mut 최선거리 = if 내칸 >= 0 && 내칸 < 거리.length { 거리[내칸] } else { -1 }
64 let mut 최선방향 = ""
65 for k in 0..<4 {
66 let 이웃x = x + 이웃x차[k]
67 let 이웃y = y + 이웃y차[k]
68 if 맵안인가(이웃x, 이웃y) {
69 let 이웃거리 = 거리[이웃y * 맵너비 + 이웃x]
70 if 이웃거리 >= 0 && (최선거리 < 0 || 이웃거리 < 최선거리) {
71 최선거리 = 이웃거리
72 최선방향 = 이웃방향[k]
73 }
74 }
75 }
76 최선방향
79function 엔티티칸(대상: entities.엔티티) -> int {
80 대상.y * 맵너비 + 대상.x
83function 야생칸들(맵아이디: string, 처치된것들: Array<int>) -> Array<int> {
84 entities.엔티티목록(맵아이디).filter(대상 => entities.야생인가(대상) && 대상.아이디 != 보스아이디 && !entities.처치된것인가(처치된것들, 대상.아이디)).map(엔티티칸)
87function 보스칸(맵아이디: string, 처치된것들: Array<int>) -> Array<int> {
88 entities.엔티티목록(맵아이디).filter(대상 => 대상.아이디 == 보스아이디 && !entities.처치된것인가(처치된것들, 대상.아이디)).map(엔티티칸)
91function 인물칸들(맵아이디: string) -> Array<int> {
92 entities.엔티티목록(맵아이디).filter(entities.인물인가).map(엔티티칸)
95function 워프칸들(맵아이디: string) -> Array<int> {
96 워프목록(맵아이디).map(대상 => 대상.y * 맵너비 + 대상.x)
99function 위험한가(활성: creature.전투원) -> bool {
100 활성.현재체력 * 3 < 활성.최대체력
103// 의도 0 은 멈춰 플레이어가 고르고, 1 은 첫 가지, 그 밖은 진화 난수 띠에서 뽑은 가지입니다. 커서를 그 가지로 옮긴 뒤 잠급니다.
104function 분기고르기(진: state.진화상태, 의도: int) -> string {
105 if 의도 == 0 { return "" }
106 let 엔티티 = 계통고유번호(진.부모계통시드)
107 let 수 = 후보수(엔티티, 진.부모계통시드, 진.부모진화카운터)
108 if 수 <= 1 { return "a" }
109 let 뽑은분기 = if 의도 == 1 { 0 } else { 난수범위("auto_evolve", 엔티티, 진화시작 + 진.부모진화카운터, 진.부모계통시드, 0, 수 - 1) }
110 let 목표분기 = 범위가두기(뽑은분기, 0, 수 - 1)
111 if 진.선택커서 < 목표분기 { "right" } else if 진.선택커서 > 목표분기 { "left" } else { "a" }
114function 진화자동(현재: state.상태, 의도: int) -> string {
115 match 현재.진화.단계 {
116 case "morph" => "a"
117 case "none" => ""
118 case "choose" => 분기고르기(현재.진화, 의도)
119 }
122// 지나가다 보스를 일찍 들이받지 않도록 보스는 다른 야생을 다 비운 뒤에만 A 로 맞붙습니다.
123// 목표가 막힌 엔티티면 옆 칸에서 마주 본 뒤 다음 틱에 A 를 누르고, 워프면 그 칸으로 들어갑니다.
124function 필드결정(현재: state.상태) -> string {
125 let 위험 = 위험한가(state.초점전투원(현재))
126 let 보통야생들 = 야생칸들(현재.맵아이디, 현재.처치된것들)
127 let 앞 = entities.활성엔티티에서(현재.맵아이디, state.마주x(현재.플레이어), state.마주y(현재.플레이어), 현재.처치된것들)
128 if entities.야생인가(앞) && 앞.아이디 != 보스아이디 { return "a" }
129 if 앞.아이디 == 보스아이디 && 보통야생들.length == 0 { return "a" }
130 if entities.인물인가(앞) && 위험 { return "a" }
131 let 목표 = if 위험 {
132 인물칸들(현재.맵아이디)
133 } else if 보통야생들.length > 0 {
134 보통야생들
135 } else if 현재.맵아이디 == "deep" {
136 보스칸(현재.맵아이디, 현재.처치된것들)
137 } else {
138 워프칸들(현재.맵아이디)
139 }
140 if 목표.length == 0 { return "" }
141 let 거리 = 거리장(목표, 맵타일들(현재.맵아이디), 현재.맵아이디, 현재.처치된것들)
142 다가가기방향(거리, 현재.플레이어.x, 현재.플레이어.y)
145// 전투는 메뉴 커서 기본값이 싸우기라 A 만으로 끝까지 갑니다. 이야기 카드는 페이지가 읽을 시간을 두고 넘깁니다.
146// "" 는 멈춤이며 수동 진화 갈림길처럼 플레이어의 몫입니다.
147export function 자동결정(현재: state.상태, 의도: int) -> string {
148 match 현재.장면 {
149 case "battle" | "story" => "a"
150 case "evolve" => 진화자동(현재, 의도)
151 case "party" => "start"
152 case "dex" => "select"
153 case "field" => 필드결정(현재)
154 }
157// 자동결정과 같은 우선순위를 글로만 읽어 HUD 가 실제 행동과 어긋나지 않게 합니다.
158function 필드목표(현재: state.상태) -> string {
159 if 위험한가(state.초점전투원(현재)) { return "회복하러 가는 중" }
160 if 야생칸들(현재.맵아이디, 현재.처치된것들).length > 0 { return "야생을 찾아가는 중" }
161 if 현재.맵아이디 == "deep" {
162 return if 보스칸(현재.맵아이디, 현재.처치된것들).length > 0 { "심연군주에게 가는 중" } else { "정복 완료" }
163 }
164 if 워프칸들(현재.맵아이디).length > 0 { "더 깊은 곳으로 가는 중" } else { "둘러보는 중" }
167export function 자동목표(현재: state.상태) -> string {
168 match 현재.장면 {
169 case "battle" => "야생과 싸우는 중"
170 case "evolve" => "진화의 갈림길"
171 case "party" => "파티를 보는 중"
172 case "dex" => "계통을 보는 중"
173 case "story" | "field" => 필드목표(현재)
174 }

SHA256 879c5a53f338bd45233f0a21d688dbf902c462c05ef75d1305acf09b774000ad