1use std::{cmp::max, ffi::OsStr, net::SocketAddr};
2
3use curseofrust::state::{BasicOpts, MultiplayerOpts};
4
5use wrapper::{DifficultyWrapper as Difficulty, SpeedWrapper as Speed, StencilWrapper as Stencil};
6
7mod wrapper;
8
9const DEFAULT_SERVER_PORT: u16 = 19140;
10const DEFAULT_CLIENT_PORT: u16 = 19150;
11
12#[deprecated(note = "use `parse_to_options` instead")]
14pub fn parse(
15 args: impl IntoIterator<Item = impl Into<std::ffi::OsString>>,
16) -> Result<(BasicOpts, MultiplayerOpts), Error> {
17 let options = parse_to_options(args)?;
18 if options.exit {
19 std::process::exit(0);
20 }
21 Ok((options.basic, options.multiplayer))
22}
23
24#[cfg(feature = "net-proto")]
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26#[non_exhaustive]
27pub enum Protocol {
28 Tcp,
29 #[default]
30 Udp,
31 WebSocket,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
35#[non_exhaustive]
36pub enum ControlMode {
37 Termux,
39 #[default]
41 Keyboard,
42 Hybrid,
45}
46
47#[cfg(feature = "net-proto")]
48impl std::str::FromStr for Protocol {
49 type Err = Error;
50
51 fn from_str(s: &str) -> Result<Self, Self::Err> {
52 Ok(match s {
53 "tcp" => Protocol::Tcp,
54 "udp" => Protocol::Udp,
55 "ws" | "websocket" => Protocol::WebSocket,
56 _ => {
57 return Err(Error::UnknownVariant {
58 ty: "protocol",
59 variants: &["tcp", "udp", "ws or websocket"],
60 value: s.to_owned(),
61 })
62 }
63 })
64 }
65}
66
67impl std::str::FromStr for ControlMode {
68 type Err = Error;
69
70 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 Ok(match s {
72 "termux" => Self::Termux,
73 "keyboard" => Self::Keyboard,
74 "hybrid" => Self::Hybrid,
75 _ => {
76 return Err(Error::UnknownVariant {
77 ty: "control_mode",
78 variants: &["termux", "keyboard", "hybrid"],
79 value: s.to_owned(),
80 })
81 }
82 })
83 }
84}
85
86pub fn parse_to_options<I, S>(args: I) -> Result<Options, Error>
87where
88 I: IntoIterator<Item = S>,
89 S: Into<std::ffi::OsString>,
90{
91 let mut basic_opts = BasicOpts::default();
92 let mut multiplayer_opts = MultiplayerOpts::default();
93 let mut exit = false;
94 let mut cm = ControlMode::default();
95
96 #[cfg(feature = "net-proto")]
97 let mut protocol = Protocol::default();
98
99 let args = clap_lex::RawArgs::new(args);
100 let mut cursor = args.cursor();
101 args.next(&mut cursor); let mut short_buf = String::new();
103 while let Some(arg) = args.next(&mut cursor) {
104 if let Some(mut s) = arg.to_short() {
105 while let Some(Ok(flag)) = s.next() {
106 macro_rules! parse {
107 ($a:expr, $t:expr, $vt:ty) => {{
108 short_buf.clear();
109 short_buf.extend(s.by_ref().map_while(Result::ok));
110 let v: Result<$vt, _> = (!short_buf.is_empty())
111 .then_some(&*short_buf)
112 .or_else(|| args.next(&mut cursor).and_then(|a| a.to_value().ok()))
113 .ok_or_else(|| Error::MissingValue { arg: $a, ty: $t })
114 .and_then(|a| a.parse().map_err(From::from));
115 v
116 }};
117 ($a:expr, $t:expr) => {
118 parse!($a, $t, _)
119 };
120 }
121 match flag {
122 'W' => basic_opts.width = parse!("-W", "integer")?,
123 'H' => basic_opts.height = max(parse!("-H", "integer")?, 5),
125 'S' => basic_opts.shape = parse!("-S", "shape", Stencil)?.0,
126 'l' => basic_opts.locations = parse!("-l", "integer")?,
127 'i' => basic_opts.inequality = Some(parse!("-i", "integer")?),
128 'q' => basic_opts.conditions = Some(parse!("-q", "integer")?),
129 'r' => basic_opts.keep_random = true,
130 'd' => basic_opts.difficulty = parse!("-d", "difficulty", Difficulty)?.0,
131 's' => basic_opts.speed = parse!("-s", "speed", Speed)?.0,
132 'R' => basic_opts.seed = parse!("-R", "integer")?,
133 'T' => basic_opts.timeline = true,
134 'E' => {
135 basic_opts.clients = parse!("-E", "integer")?;
136 if matches!(multiplayer_opts, MultiplayerOpts::None) {
137 multiplayer_opts = MultiplayerOpts::Server {
138 port: DEFAULT_SERVER_PORT,
139 };
140 }
141 }
142 'e' => {
143 multiplayer_opts = MultiplayerOpts::Server {
144 port: parse!("-e", "integer")?,
145 };
146 }
147 'C' => {
148 let parsed = parse!("-C", "SocketAddr")?;
149 if let MultiplayerOpts::Client { ref mut server, .. } = multiplayer_opts {
150 *server = parsed;
151 } else {
152 multiplayer_opts = MultiplayerOpts::Client {
153 server: parsed,
154 port: DEFAULT_CLIENT_PORT,
155 }
156 }
157 }
158 'c' => {
159 let parsed = parse!("-c", "integer")?;
160 if let MultiplayerOpts::Client { ref mut port, .. } = multiplayer_opts {
161 *port = parsed
162 } else {
163 multiplayer_opts = MultiplayerOpts::Client {
164 server: SocketAddr::from((
165 std::net::Ipv4Addr::LOCALHOST,
166 DEFAULT_SERVER_PORT,
167 )),
168 port: parsed,
169 };
170 }
171 }
172 'v' => {
173 println!("curseofrust");
174 exit = true
175 }
176 'h' => {
177 println!("{HELP_MSG}");
178 exit = true
179 }
180
181 #[cfg(feature = "net-proto")]
182 'p' => protocol = parse!("-p", "protocol", Protocol)?,
183
184 'm' => cm = parse!("-m", "control mode", ControlMode)?,
185
186 f => return Err(Error::UnknownFlag { flag: f }),
187 }
188 }
189 }
190 }
191
192 if basic_opts.shape == curseofrust::grid::Stencil::Rect {
194 basic_opts.width += 10;
195 }
196
197 Ok(Options {
198 basic: basic_opts,
199 multiplayer: multiplayer_opts,
200 exit,
201
202 #[cfg(feature = "net-proto")]
203 protocol,
204 control_mode: cm,
205 })
206}
207
208#[derive(Debug)]
210#[non_exhaustive]
211pub struct Options {
212 pub basic: BasicOpts,
213 pub multiplayer: MultiplayerOpts,
214 pub exit: bool,
215 pub control_mode: ControlMode,
216
217 #[cfg(feature = "net-proto")]
218 pub protocol: Protocol,
219}
220
221#[derive(Debug)]
222#[non_exhaustive]
223pub enum Error {
224 MissingValue {
225 arg: &'static str,
226 ty: &'static str,
227 },
228 InvalidIntValueFmt(std::num::ParseIntError),
229 InvalidIpAddrValueFmt(std::net::AddrParseError),
230 NonUnicodeValue {
231 content: Box<OsStr>,
232 },
233 UnknownFlag {
234 flag: char,
235 },
236 UnknownVariant {
237 ty: &'static str,
238 variants: &'static [&'static str],
239 value: String,
240 },
241}
242
243impl std::fmt::Display for Error {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 match self {
246 Error::MissingValue { arg, ty } => {
247 write!(
248 f,
249 "missing value for argument '{arg}', expected type '{ty}'"
250 )
251 }
252 Error::InvalidIntValueFmt(err) => write!(f, "invalid integer formatting: {err}"),
253 Error::InvalidIpAddrValueFmt(err) => write!(f, "invalid IP-address formatting: {err}"),
254 Error::NonUnicodeValue { content } => {
255 write!(f, "non-unicode value: {content:?}")
256 }
257 Error::UnknownFlag { flag } => write!(f, "unknown flag: {flag}"),
258 Error::UnknownVariant {
259 ty,
260 variants,
261 value,
262 } => write!(
263 f,
264 "unknown variant '{value}' for type '{ty}', expected one of: {variants:?}",
265 ),
266 }
267 }
268}
269
270impl<'a> From<&'a OsStr> for Error {
271 #[inline]
272 fn from(value: &'a OsStr) -> Self {
273 Error::NonUnicodeValue {
274 content: value.into(),
275 }
276 }
277}
278
279impl From<std::num::ParseIntError> for Error {
280 #[inline]
281 fn from(value: std::num::ParseIntError) -> Self {
282 Error::InvalidIntValueFmt(value)
283 }
284}
285
286impl From<std::net::AddrParseError> for Error {
287 #[inline]
288 fn from(value: std::net::AddrParseError) -> Self {
289 Error::InvalidIpAddrValueFmt(value)
290 }
291}
292
293impl std::error::Error for Error {}
294
295pub const HELP_MSG: &str = r#" __
297 ____ / ] ________ __
298 / __ \_ _ ___ ___ ___ __ _| |_ | ___ \__ __ ___ _| |__
299_/ / \/ | |X _/ __/ __\ / \ / | |___| | | | / __/_ __/
300\ X | | | | |__ | __X | X || | | X_ __/ |_| X__ | | X
301 \ \__/\ __X_| \___/___/ \___/| | | | \ \_ X__ /___ / \__/
302 \____/ |/ |_\ \__/
303
304 Made by DM Earth in 2024-2025.
305
306 Command line arguments:
307
308-W width
309 Map width (default is 21)
310
311-H height
312 Map height (default is 21)
313
314-S [rhombus|rect|hex]
315 Map shape (rectangle is default). Max number of countries N=4 for rhombus and rectangle, and N=6 for the hexagon.
316
317-l [2|3| ... N]
318 Sets L, the number of countries (default is N).
319
320-i [0|1|2|3|4]
321 Inequality between the countries (0 is the lowest, 4 in the highest).
322
323-q [1|2| ... L]
324 Choose player's location by its quality (1 = the best available on the map, L = the worst). Only in the singleplayer mode.
325
326-r
327 Absolutely random initial conditions, overrides options -l, -i, and -q.
328
329-d [ee|e|n|h|hh]
330 Difficulty level (AI) from the easiest to the hardest (default is normal).
331
332-s [p|sss|ss|s|n|f|ff|fff]
333 Game speed from the slowest to the fastest (default is normal).
334
335-R seed
336 Specify a random seed (unsigned integer) for map generation.
337
338-T
339 Show the timeline.
340
341-E [1|2| ... L]
342 Start a server for not more than L clients.
343
344-e port
345 Server's port (19140 is default).
346
347-C IP
348 Start a client and connect to the provided server's IP-address.
349
350-c port
351 Clients's port (19150 is default).
352
353-m [keyboard|termux|hybrid]
354 Control method.
355
356-v
357 Display the version number
358
359-h
360 Display this help
361"#;