1use std::fmt::Debug;
10
11use shuyi_assets::ContentError;
12use shuyi_assets::font::FontAtlasDef;
13use shuyi_assets::keymap::ActionSet;
14use shuyi_core::Made;
15use shuyi_platform::BasicGame;
16use shuyi_platform::harness::Harness;
17use shuyi_platform::session::{SessionRecord, Stamps};
18use shuyi_ui::GameData;
19use shuyi_ui::basic::{BasicData, FONT_PNG};
20use shuyi_ui::input::fit::{Controls, GameControls, ScreenActions};
21
22use crate::contract::session::{Headless, Session};
23use crate::frame_png::{Image, Painter, Script, Shot};
24use crate::game::{GameArg, GameLang, GameOption, GameTools};
25
26#[derive(Debug)]
28pub struct BasicTools<A: ActionSet, T: Debug + 'static = ()> {
29 game: BasicGame<A, T>,
30}
31
32fn in_lines(errors: &[ContentError]) -> String {
34 let lines: Vec<String> = errors.iter().map(ToString::to_string).collect();
35 lines.join(
36 "
37",
38 )
39}
40
41fn no_options(options: &[GameArg]) -> Result<(), String> {
43 match options.first() {
44 Some(option) => Err(format!("unknown option {}", option.flag)),
45 None => Ok(()),
46 }
47}
48
49impl<A: ActionSet, T: Debug + 'static> BasicTools<A, T> {
50 pub fn new(game: BasicGame<A, T>) -> Self {
52 Self { game }
53 }
54
55 pub fn harness(&self) -> Result<Harness<BasicData<A, T>>, String> {
58 let game = self.game.game(self.game.ctx()?);
59 Ok(Harness::new(game.map_err(|e| e.to_string())?))
60 }
61
62 pub fn picture(h: &Harness<BasicData<A, T>>, scale: u32) -> Result<Image, String> {
65 let ctx = h.ctx();
66 let data = &ctx.game;
67 let png = data.bundle.bytes(FONT_PNG).ok_or("no font atlas picture")?;
68 let clear = data.blank_cell(&ctx.palette).bg;
69 let painter = Painter::new(
70 data.font.clone(),
71 png,
72 &data.images,
73 |path| data.bundle.bytes(path),
74 clear,
75 )?;
76 Ok(painter.render(h.game().buffer(), scale))
77 }
78}
79
80impl<A: ActionSet, T: Debug + 'static> GameTools for BasicTools<A, T> {
81 fn frame_options(&self) -> &[GameOption] {
82 &[]
83 }
84
85 fn frame(
86 &self,
87 options: &[GameArg],
88 script: &Script,
89 scale: Option<u32>,
90 ) -> Result<Shot, String> {
91 no_options(options)?;
92 let mut h = self.harness()?;
93 script.play(&mut h);
94 Ok(Shot {
95 image: Self::picture(&h, scale.unwrap_or(1))?,
96 shows: h.top_screen().to_owned(),
97 warning: None,
98 })
99 }
100
101 fn lang(&self) -> Result<GameLang, String> {
102 let data = self.game.data().map_err(|errors| {
103 let lines: Vec<String> = errors.iter().map(ToString::to_string).collect();
104 lines.join("\n")
105 })?;
106 Ok(GameLang {
107 lang: data.lang().clone(),
108 variants: Vec::new(),
109 })
110 }
111
112 fn check(&self) -> Result<Vec<ContentError>, String> {
113 Ok(self.game.data().err().unwrap_or_default())
114 }
115
116 fn session(&self, options: &[GameArg]) -> Result<Box<dyn Session>, String> {
117 no_options(options)?;
118 let session = Headless::new(self.harness()?)
119 .with_picture(|h, scale| Self::picture(h, scale.unwrap_or(1)));
120 Ok(Box::new(session))
121 }
122
123 fn session_from(
125 &self,
126 record: &SessionRecord,
127 stamps: Stamps,
128 ) -> Result<Box<dyn Session>, String> {
129 let harness =
130 Harness::from_session(self.game.start(), record, stamps).map_err(|e| e.to_string())?;
131 let session =
132 Headless::new(harness).with_picture(|h, scale| Self::picture(h, scale.unwrap_or(1)));
133 Ok(Box::new(session))
134 }
135
136 fn made(&self) -> Option<Made> {
138 Some(self.game.made())
139 }
140
141 fn font_atlas(&self) -> Option<Result<FontAtlasDef, String>> {
143 let data = self.game.data().map_err(|errors| in_lines(&errors));
144 Some(data.map(|data| data.font))
145 }
146
147 fn controls(&self) -> Option<Result<Box<dyn Controls>, String>> {
149 let controls = self.game.ctx().map(|mut ctx| {
150 let keymap = ctx.game.keymap_def().clone();
151 let screens = self.game.every_screen(&mut ctx);
152 let game = screens
153 .iter()
154 .fold(GameControls::new(keymap), |game, screen| {
155 game.with(ScreenActions::of(screen.as_ref()))
156 });
157 Box::new(game) as Box<dyn Controls>
158 });
159 Some(controls)
160 }
161}
162
163#[cfg(test)]
164mod tests;