1
// This file is part of hnefatafl-copenhagen.
2
//
3
// hnefatafl-copenhagen is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU Affero General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// hnefatafl-copenhagen is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU Affero General Public License for more details.
12
//
13
// You should have received a copy of the GNU Affero General Public License
14
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15

            
16
use std::{
17
    fmt,
18
    hash::{Hash, Hasher},
19
    str::FromStr,
20
};
21

            
22
use anyhow::Context;
23
use serde::{Deserialize, Serialize};
24

            
25
use crate::{
26
    board::BoardSize,
27
    role::Role,
28
    time::{TimeLeft, TimeSettings},
29
};
30

            
31
pub const BOARD_LETTERS: &str = "ABCDEFGHIJKLM";
32

            
33
pub const EXIT_SQUARES_11X11: [Vertex; 4] = [
34
    Vertex {
35
        size: BoardSize::_11,
36
        x: 0,
37
        y: 0,
38
    },
39
    Vertex {
40
        size: BoardSize::_11,
41
        x: 10,
42
        y: 0,
43
    },
44
    Vertex {
45
        size: BoardSize::_11,
46
        x: 0,
47
        y: 10,
48
    },
49
    Vertex {
50
        size: BoardSize::_11,
51
        x: 10,
52
        y: 10,
53
    },
54
];
55

            
56
const THRONE_11X11: Vertex = Vertex {
57
    size: BoardSize::_11,
58
    x: 5,
59
    y: 5,
60
};
61

            
62
const RESTRICTED_SQUARES_11X11: [Vertex; 5] = [
63
    Vertex {
64
        size: BoardSize::_11,
65
        x: 0,
66
        y: 0,
67
    },
68
    Vertex {
69
        size: BoardSize::_11,
70
        x: 10,
71
        y: 0,
72
    },
73
    Vertex {
74
        size: BoardSize::_11,
75
        x: 0,
76
        y: 10,
77
    },
78
    Vertex {
79
        size: BoardSize::_11,
80
        x: 10,
81
        y: 10,
82
    },
83
    THRONE_11X11,
84
];
85

            
86
pub const EXIT_SQUARES_13X13: [Vertex; 4] = [
87
    Vertex {
88
        size: BoardSize::_13,
89
        x: 0,
90
        y: 0,
91
    },
92
    Vertex {
93
        size: BoardSize::_13,
94
        x: 12,
95
        y: 0,
96
    },
97
    Vertex {
98
        size: BoardSize::_13,
99
        x: 0,
100
        y: 12,
101
    },
102
    Vertex {
103
        size: BoardSize::_13,
104
        x: 12,
105
        y: 12,
106
    },
107
];
108

            
109
const THRONE_13X13: Vertex = Vertex {
110
    size: BoardSize::_13,
111
    x: 6,
112
    y: 6,
113
};
114

            
115
const RESTRICTED_SQUARES_13X13: [Vertex; 5] = [
116
    Vertex {
117
        size: BoardSize::_13,
118
        x: 0,
119
        y: 0,
120
    },
121
    Vertex {
122
        size: BoardSize::_13,
123
        x: 12,
124
        y: 0,
125
    },
126
    Vertex {
127
        size: BoardSize::_13,
128
        x: 0,
129
        y: 12,
130
    },
131
    Vertex {
132
        size: BoardSize::_13,
133
        x: 12,
134
        y: 12,
135
    },
136
    THRONE_13X13,
137
];
138

            
139
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialOrd, Serialize)]
140
pub struct PlayRecordTimed {
141
    pub play: Option<Plae>,
142
    pub attacker_time: TimeLeft,
143
    pub defender_time: TimeLeft,
144
}
145

            
146
impl Hash for PlayRecordTimed {
147
1193858
    fn hash<H: Hasher>(&self, state: &mut H) {
148
1193858
        self.play.hash(state);
149
1193858
    }
150
}
151

            
152
impl PartialEq for PlayRecordTimed {
153
    fn eq(&self, other: &Self) -> bool {
154
        self.play == other.play
155
    }
156
}
157

            
158
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
159
pub enum Plae {
160
    Play(Play),
161
    AttackerResigns,
162
    DefenderResigns,
163
}
164

            
165
impl fmt::Display for Plae {
166
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167
        match self {
168
            Self::Play(play) => write!(f, "play {} {} {}", play.role, play.from, play.to),
169
            Self::AttackerResigns => write!(f, "play attacker resigns _"),
170
            Self::DefenderResigns => write!(f, "play defender resigns _"),
171
        }
172
    }
173
}
174

            
175
impl Plae {
176
    /// # Errors
177
    ///
178
    /// If you try to convert an illegal character or you don't get vertex-vertex.
179
    pub fn from_str_(play: &str, role: &Role) -> anyhow::Result<Self> {
180
        let Some((from, to)) = play.split_once('-') else {
181
            return Err(anyhow::Error::msg("expected: vertex-vertex"));
182
        };
183

            
184
        Ok(Self::Play(Play {
185
            role: *role,
186
            from: Vertex::from_str(from)?,
187
            to: Vertex::from_str(to)?,
188
        }))
189
    }
190
}
191

            
192
impl TryFrom<Vec<&str>> for Plae {
193
    type Error = anyhow::Error;
194

            
195
438
    fn try_from(args: Vec<&str>) -> Result<Self, Self::Error> {
196
438
        let error_str = "expected: 'play ROLE FROM TO' or 'play ROLE resign'";
197

            
198
438
        if args.len() < 3 {
199
            return Err(anyhow::Error::msg(error_str));
200
438
        }
201

            
202
438
        let role = Role::from_str(args[1])?;
203
438
        if args[2] == "resigns" {
204
            if role == Role::Defender {
205
                return Ok(Self::DefenderResigns);
206
            }
207

            
208
            return Ok(Self::AttackerResigns);
209
438
        }
210

            
211
438
        if args.len() < 4 {
212
            return Err(anyhow::Error::msg(error_str));
213
438
        }
214

            
215
        Ok(Self::Play(Play {
216
438
            role: Role::from_str(args[1])?,
217
438
            from: Vertex::from_str(args[2])?,
218
432
            to: Vertex::from_str(args[3])?,
219
        }))
220
438
    }
221
}
222

            
223
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
224
pub enum Plays {
225
    PlayRecordsTimed(Vec<PlayRecordTimed>),
226
    PlayRecords(Vec<Option<Plae>>),
227
}
228

            
229
impl Plays {
230
    #[must_use]
231
    pub fn is_empty(&self) -> bool {
232
        match self {
233
            Plays::PlayRecordsTimed(plays) => plays.is_empty(),
234
            Plays::PlayRecords(plays) => plays.is_empty(),
235
        }
236
    }
237

            
238
    #[must_use]
239
    pub fn len(&self) -> usize {
240
        match self {
241
            Plays::PlayRecordsTimed(plays) => plays.len(),
242
            Plays::PlayRecords(plays) => plays.len(),
243
        }
244
    }
245

            
246
    #[must_use]
247
61320
    pub fn new(time_settings: &TimeSettings) -> Self {
248
61320
        match time_settings {
249
            TimeSettings::Timed(_) => Plays::PlayRecordsTimed(Vec::new()),
250
61320
            TimeSettings::UnTimed => Plays::PlayRecords(Vec::new()),
251
        }
252
61320
    }
253

            
254
    #[must_use]
255
    pub fn time_left(&self, role: Role, index: usize) -> String {
256
        match self {
257
            Plays::PlayRecordsTimed(plays) => match role {
258
                Role::Attacker => {
259
                    if let Some(play) = plays.get(index) {
260
                        play.attacker_time.to_string()
261
                    } else {
262
                        "-".to_string()
263
                    }
264
                }
265
                Role::Defender => {
266
                    if let Some(play) = plays.get(index) {
267
                        play.defender_time.to_string()
268
                    } else {
269
                        "-".to_string()
270
                    }
271
                }
272
                Role::Roleless => unreachable!(),
273
            },
274
            Plays::PlayRecords(_) => "-".to_string(),
275
        }
276
    }
277
}
278

            
279
impl Default for Plays {
280
61674
    fn default() -> Self {
281
61674
        Plays::PlayRecordsTimed(Vec::new())
282
61674
    }
283
}
284

            
285
impl fmt::Display for Plays {
286
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287
        match self {
288
            Plays::PlayRecordsTimed(plays) => {
289
                for play in plays {
290
                    if let Some(play) = &play.play {
291
                        write!(f, "{play}, ")?;
292
                    }
293
                }
294
            }
295
            Plays::PlayRecords(plays) => {
296
                for play in plays {
297
                    if let Some(play) = &play {
298
                        write!(f, "{play}, ")?;
299
                    }
300
                }
301
            }
302
        }
303

            
304
        Ok(())
305
    }
306
}
307

            
308
#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
309
pub struct Play {
310
    pub role: Role,
311
    pub from: Vertex,
312
    pub to: Vertex,
313
}
314

            
315
impl fmt::Display for Play {
316
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317
        write!(f, "{:?} from {} to {}", self.role, self.from, self.to)
318
    }
319
}
320

            
321
#[derive(Debug, Default)]
322
pub struct Captures(pub Vec<Vertex>);
323

            
324
impl fmt::Display for Captures {
325
360
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326
360
        for vertex in &self.0 {
327
216
            write!(f, "{vertex} ")?;
328
        }
329

            
330
360
        Ok(())
331
360
    }
332
}
333

            
334
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
335
pub struct Vertex {
336
    pub size: BoardSize,
337
    pub x: usize,
338
    pub y: usize,
339
}
340

            
341
impl fmt::Display for Vertex {
342
216
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343
216
        let letters = match self.size {
344
198
            BoardSize::_11 => &BOARD_LETTERS.to_lowercase(),
345
198
            BoardSize::_13 => BOARD_LETTERS,
346
        };
347

            
348
216
        let board_size: usize = self.size.into();
349

            
350
216
        write!(
351
216
            f,
352
            "{}{}",
353
216
            letters.chars().collect::<Vec<_>>()[self.x],
354
216
            board_size - self.y
355
        )
356
216
    }
357
}
358

            
359
impl FromStr for Vertex {
360
    type Err = anyhow::Error;
361

            
362
6582492
    fn from_str(vertex: &str) -> anyhow::Result<Self> {
363
6582492
        let mut chars = vertex.chars();
364

            
365
6582492
        if let Some(mut ch) = chars.next() {
366
6582492
            let size = if ch.is_lowercase() { 11 } else { 13 };
367

            
368
6582492
            ch = ch.to_ascii_uppercase();
369
6582492
            let x = BOARD_LETTERS[..size]
370
6582492
                .find(ch)
371
6582492
                .context("play: the first letter is not a legal char")?;
372

            
373
6582486
            let mut y = chars.as_str().parse()?;
374
6582474
            if y > 0 && y <= size {
375
6582462
                y = size - y;
376
                return Ok(Self {
377
6582462
                    size: size.try_into()?,
378
6582462
                    x,
379
6582462
                    y,
380
                });
381
12
            }
382
        }
383

            
384
12
        Err(anyhow::Error::msg("play: invalid coordinate"))
385
6582492
    }
386
}
387

            
388
impl Vertex {
389
    #[must_use]
390
29000352
    pub fn up(&self) -> Option<Vertex> {
391
29000352
        if self.y > 0 {
392
28380145
            Some(Vertex {
393
28380145
                size: self.size,
394
28380145
                x: self.x,
395
28380145
                y: self.y - 1,
396
28380145
            })
397
        } else {
398
620207
            None
399
        }
400
29000352
    }
401

            
402
    #[must_use]
403
27424672
    pub fn left(&self) -> Option<Vertex> {
404
27424672
        if self.x > 0 {
405
26909820
            Some(Vertex {
406
26909820
                size: self.size,
407
26909820
                x: self.x - 1,
408
26909820
                y: self.y,
409
26909820
            })
410
        } else {
411
514852
            None
412
        }
413
27424672
    }
414

            
415
    #[must_use]
416
27077070
    pub fn down(&self) -> Option<Vertex> {
417
27077070
        let board_size: usize = self.size.into();
418

            
419
27077070
        if self.y < board_size - 1 {
420
26312958
            Some(Vertex {
421
26312958
                size: self.size,
422
26312958
                x: self.x,
423
26312958
                y: self.y + 1,
424
26312958
            })
425
        } else {
426
764112
            None
427
        }
428
27077070
    }
429

            
430
    #[inline]
431
    #[must_use]
432
3105650
    pub fn on_exit_square(&self) -> bool {
433
3105650
        match self.size {
434
3105638
            BoardSize::_11 => EXIT_SQUARES_11X11.contains(self),
435
12
            BoardSize::_13 => EXIT_SQUARES_13X13.contains(self),
436
        }
437
3105650
    }
438

            
439
    #[inline]
440
    #[must_use]
441
1790043
    pub fn on_throne(&self) -> bool {
442
1790043
        match self.size {
443
1790043
            BoardSize::_11 => THRONE_11X11 == *self,
444
            BoardSize::_13 => THRONE_13X13 == *self,
445
        }
446
1790043
    }
447

            
448
    #[must_use]
449
123482976
    pub fn on_restricted_square(&self) -> bool {
450
123482976
        match &self.size {
451
123482238
            BoardSize::_11 => RESTRICTED_SQUARES_11X11.contains(self),
452
738
            BoardSize::_13 => RESTRICTED_SQUARES_13X13.contains(self),
453
        }
454
123482976
    }
455

            
456
    #[must_use]
457
26613422
    pub fn right(&self) -> Option<Vertex> {
458
26613422
        let board_size: usize = self.size.into();
459

            
460
26613422
        if self.x < board_size - 1 {
461
23636010
            Some(Vertex {
462
23636010
                size: self.size,
463
23636010
                x: self.x + 1,
464
23636010
                y: self.y,
465
23636010
            })
466
        } else {
467
2977412
            None
468
        }
469
26613422
    }
470

            
471
    #[must_use]
472
3029983
    pub fn touches_wall(&self) -> bool {
473
3029983
        let board_size: usize = self.size.into();
474

            
475
3029983
        self.x == 0 || self.x == board_size - 1 || self.y == 0 || self.y == board_size - 1
476
3029983
    }
477
}
478

            
479
impl From<&Vertex> for usize {
480
    #[inline]
481
    fn from(vertex: &Vertex) -> Self {
482
        let board_size: usize = vertex.size.into();
483
        vertex.y * board_size + vertex.x
484
    }
485
}