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
    borrow::Cow,
18
    collections::{BinaryHeap, HashMap},
19
    fmt,
20
    hash::{DefaultHasher, Hash, Hasher},
21
    process::exit,
22
    str::FromStr,
23
};
24

            
25
use chrono::Utc;
26
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
27
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
28
use serde::{Deserialize, Serialize};
29
#[cfg(feature = "js")]
30
use wasm_bindgen::prelude::wasm_bindgen;
31

            
32
use crate::{
33
    ai::{AI, AiBasic},
34
    board::{Board, BoardSize, InvalidMove},
35
    characters::Characters,
36
    message::{COMMANDS, Message},
37
    play::{Captures, Plae, Play, PlayRecordTimed, Plays, Vertex},
38
    role::Role,
39
    status::Status,
40
    time::TimeSettings,
41
    tree::Tree,
42
};
43

            
44
#[cfg(not(feature = "js"))]
45
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
46
pub struct Game {
47
    pub board: Board,
48
    pub plays: Plays,
49
    pub previous_boards: PreviousBoards,
50
    pub status: Status,
51
    pub time: TimeUnix,
52
    pub attacker_time: TimeSettings,
53
    pub defender_time: TimeSettings,
54
    pub turn: Role,
55
    #[serde(skip)]
56
    pub chars: Characters,
57
}
58

            
59
#[cfg(feature = "js")]
60
#[wasm_bindgen]
61
#[allow(clippy::unsafe_derive_deserialize)]
62
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
63
pub struct Game {
64
    #[wasm_bindgen(skip)]
65
    pub board: Board,
66
    #[wasm_bindgen(skip)]
67
    pub plays: Plays,
68
    #[wasm_bindgen(skip)]
69
    pub previous_boards: PreviousBoards,
70
    #[wasm_bindgen(skip)]
71
    pub status: Status,
72
    #[wasm_bindgen(skip)]
73
    pub time: TimeUnix,
74
    #[wasm_bindgen(skip)]
75
    pub attacker_time: TimeSettings,
76
    #[wasm_bindgen(skip)]
77
    pub defender_time: TimeSettings,
78
    #[wasm_bindgen(skip)]
79
    pub turn: Role,
80
    #[wasm_bindgen(skip)]
81
    #[serde(skip)]
82
    pub chars: Characters,
83
}
84

            
85
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
86
pub struct PreviousBoards(pub FxHashSet<Board>);
87

            
88
impl PreviousBoards {
89
    #[must_use]
90
61680
    pub fn new(board_size: BoardSize) -> Self {
91
61680
        let mut boards = FxHashSet::with_capacity_and_hasher(64, FxBuildHasher);
92

            
93
61680
        boards.insert(Board::new(board_size));
94
61680
        Self(boards)
95
61680
    }
96
}
97

            
98
impl Default for PreviousBoards {
99
61674
    fn default() -> Self {
100
61674
        Self::new(BoardSize::default())
101
61674
    }
102
}
103

            
104
impl fmt::Display for Game {
105
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106
        let captured = self.board.captured();
107

            
108
        writeln!(f, "{}\n", self.board)?;
109
        writeln!(f, "move: {}", self.plays.len() + 1)?;
110
        writeln!(
111
            f,
112
            "captures: {} {}",
113
            &captured.attacker(&self.chars),
114
            &captured.defender(&self.chars)
115
        )?;
116
        writeln!(f, "status: {}", self.status)?;
117
        writeln!(f, "turn: {}", self.turn)?;
118

            
119
        match &self.attacker_time {
120
            TimeSettings::Timed(time) => writeln!(f, "attacker time: {time}")?,
121
            TimeSettings::UnTimed => writeln!(f, "attacker time: infinite")?,
122
        }
123
        match &self.defender_time {
124
            TimeSettings::Timed(time) => writeln!(f, "defender time: {time}")?,
125
            TimeSettings::UnTimed => writeln!(f, "defender time: infinite")?,
126
        }
127

            
128
        write!(f, "plays: {}", self.plays)
129
    }
130
}
131

            
132
#[cfg(feature = "js")]
133
#[wasm_bindgen]
134
impl Game {
135
    #[must_use]
136
    #[wasm_bindgen(constructor)]
137
    pub fn new() -> Game {
138
        Game::default()
139
    }
140

            
141
    /// # Errors
142
    ///
143
    /// If the command is illegal or invalid.
144
    #[wasm_bindgen]
145
    pub fn read_line_js(&mut self, buffer: &str) -> String {
146
        let mut buffer = Cow::from(buffer);
147
        if let Some(comment_offset) = buffer.find('#') {
148
            buffer.to_mut().replace_range(comment_offset.., "");
149
        }
150

            
151
        match Message::from_str(buffer.as_ref()) {
152
            Ok(message) => match self.update(message) {
153
                Ok(update) => {
154
                    if let Some(update) = update {
155
                        format!("= {update}")
156
                    } else {
157
                        String::new()
158
                    }
159
                }
160
                Err(err) => format!("? {err}"),
161
            },
162
            Err(err) => format!("? {err}"),
163
        }
164
    }
165
}
166

            
167
impl Game {
168
    #[allow(clippy::expect_used)]
169
    #[allow(clippy::missing_panics_doc)]
170
    #[must_use]
171
    pub fn alpha_beta(
172
        &self,
173
        original_depth: usize,
174
        depth: u8,
175
        mut play_option: Option<Plae>,
176
        mut alpha: f64,
177
        mut beta: f64,
178
    ) -> (Option<Plae>, f64, Option<EscapeVec>) {
179
        if let Some(result) = self.alpha_beta_duplicated(original_depth, depth) {
180
            return result;
181
        }
182

            
183
        if self.turn == Role::Attacker {
184
            let mut value = -f64::INFINITY;
185
            let mut escape_vec = None;
186
            for plae in self.all_legal_plays() {
187
                let mut child = self.clone();
188
                child.play(&plae).expect("this play should be valid");
189

            
190
                let (play_option_2, value_2, escape_vec_2) =
191
                    child.alpha_beta(original_depth, depth - 1, Some(plae.clone()), alpha, beta);
192

            
193
                if value_2 > value {
194
                    value = value_2;
195
                    play_option.clone_from(&play_option_2);
196
                    escape_vec.clone_from(&escape_vec_2);
197
                }
198

            
199
                if value >= beta {
200
                    break;
201
                }
202

            
203
                if value > alpha {
204
                    alpha = value;
205
                    play_option = play_option_2;
206
                    escape_vec = escape_vec_2;
207
                }
208
            }
209

            
210
            (play_option, value, escape_vec)
211
        } else {
212
            let mut value = f64::INFINITY;
213
            let mut escape_vec = None;
214
            for plae in self.all_legal_plays() {
215
                let mut child = self.clone();
216
                child.play(&plae).expect("this play should be valid");
217

            
218
                let (play_option_2, value_2, escape_vec_2) =
219
                    child.alpha_beta(original_depth, depth - 1, Some(plae.clone()), alpha, beta);
220

            
221
                if value_2 < value {
222
                    value = value_2;
223
                    play_option.clone_from(&play_option_2);
224
                    escape_vec.clone_from(&escape_vec_2);
225
                }
226

            
227
                if value <= alpha {
228
                    break;
229
                }
230

            
231
                if value < beta {
232
                    beta = value;
233
                    play_option = play_option_2;
234
                    escape_vec = escape_vec_2;
235
                }
236
            }
237

            
238
            (play_option, value, escape_vec)
239
        }
240
    }
241

            
242
    #[allow(clippy::expect_used)]
243
    #[allow(clippy::missing_panics_doc)]
244
    #[must_use]
245
    pub fn alpha_beta_parallel(
246
        &self,
247
        original_depth: usize,
248
        depth: u8,
249
        mut play_option: Option<Plae>,
250
        mut alpha: f64,
251
        mut beta: f64,
252
    ) -> (Option<Plae>, f64, Option<EscapeVec>) {
253
        if let Some(result) = self.alpha_beta_duplicated(original_depth, depth) {
254
            return result;
255
        }
256

            
257
        if self.turn == Role::Attacker {
258
            let mut value = -f64::INFINITY;
259
            let mut escape_vec = None;
260
            let results: Vec<_> = self
261
                .all_legal_plays()
262
                .par_iter()
263
                .map(|plae| {
264
                    let mut child = self.clone();
265
                    child.play(plae).expect("this play should be valid");
266

            
267
                    child.alpha_beta(original_depth, depth - 1, Some(plae.clone()), alpha, beta)
268
                })
269
                .collect();
270

            
271
            for (play_option_2, value_2, escape_vec_2) in results {
272
                if value_2 > value {
273
                    value = value_2;
274
                    play_option.clone_from(&play_option_2);
275
                    escape_vec.clone_from(&escape_vec_2);
276
                }
277

            
278
                if value >= beta {
279
                    break;
280
                }
281

            
282
                if value > alpha {
283
                    alpha = value;
284
                    play_option = play_option_2;
285
                    escape_vec = escape_vec_2;
286
                }
287
            }
288

            
289
            (play_option, value, escape_vec)
290
        } else {
291
            let mut value = f64::INFINITY;
292
            let mut escape_vec = None;
293

            
294
            let results: Vec<_> = self
295
                .all_legal_plays()
296
                .par_iter()
297
                .map(|plae| {
298
                    let mut child = self.clone();
299
                    child.play(plae).expect("this play should be valid");
300

            
301
                    child.alpha_beta(original_depth, depth - 1, Some(plae.clone()), alpha, beta)
302
                })
303
                .collect();
304

            
305
            for (play_option_2, value_2, escape_vec_2) in results {
306
                if value_2 < value {
307
                    value = value_2;
308
                    play_option.clone_from(&play_option_2);
309
                    escape_vec.clone_from(&escape_vec_2);
310
                }
311

            
312
                if value <= alpha {
313
                    break;
314
                }
315

            
316
                if value < beta {
317
                    beta = value;
318
                    play_option = play_option_2;
319
                    escape_vec = escape_vec_2;
320
                }
321
            }
322

            
323
            (play_option, value, escape_vec)
324
        }
325
    }
326

            
327
    #[allow(clippy::missing_panics_doc)]
328
    #[must_use]
329
    pub fn alpha_beta_duplicated(
330
        &self,
331
        original_depth: usize,
332
        depth: u8,
333
    ) -> Option<(Option<Plae>, f64, Option<EscapeVec>)> {
334
        match self.status {
335
            Status::AttackerWins => {
336
                return Some((
337
                    self.play_n(self.plays.len() - original_depth + usize::from(depth) - 1),
338
                    f64::INFINITY,
339
                    None,
340
                ));
341
            }
342
            Status::DefenderWins => {
343
                return Some((
344
                    self.play_n(self.plays.len() - original_depth + usize::from(depth) - 1),
345
                    -f64::INFINITY,
346
                    None,
347
                ));
348
            }
349
            Status::Draw => unreachable!(),
350
            Status::Ongoing => {}
351
        }
352

            
353
        if depth == 0 {
354
            let (utility, escape_vec) = self.utility();
355

            
356
            return Some((
357
                self.play_n(self.plays.len() - original_depth),
358
                utility,
359
                Some(escape_vec),
360
            ));
361
        }
362

            
363
        None
364
    }
365

            
366
    #[allow(clippy::missing_panics_doc)]
367
    #[must_use]
368
    pub fn play_n(&self, n: usize) -> Option<Plae> {
369
        match &self.plays {
370
            Plays::PlayRecordsTimed(plaes_timed) => {
371
                let plaes: Vec<_> = plaes_timed
372
                    .iter()
373
                    .map(|plae_timed| &plae_timed.play)
374
                    .collect();
375

            
376
                if let Some(Some(plae)) = plaes.get(n) {
377
                    Some(plae.clone())
378
                } else {
379
                    None
380
                }
381
            }
382
            Plays::PlayRecords(plaes) => {
383
                if let Some(Some(plae)) = plaes.get(n) {
384
                    Some(plae.clone())
385
                } else {
386
                    None
387
                }
388
            }
389
        }
390
    }
391

            
392
    #[must_use]
393
6
    pub fn new_game(board_size: BoardSize, time_settings: Option<TimeSettings>) -> Self {
394
6
        let board = Board::new(board_size);
395
6
        let previous_boards = PreviousBoards::new(board_size);
396

            
397
6
        if let Some(time_settings) = time_settings {
398
            let mut game = Self {
399
                board,
400
                plays: Plays::new(&time_settings),
401
                previous_boards,
402
                ..Self::default()
403
            };
404

            
405
            match time_settings {
406
                TimeSettings::Timed(time) => {
407
                    game.attacker_time = TimeSettings::Timed(time);
408
                    game.defender_time = TimeSettings::Timed(time);
409
                }
410
                TimeSettings::UnTimed => {
411
                    game.attacker_time = TimeSettings::UnTimed;
412
                    game.defender_time = TimeSettings::UnTimed;
413
                }
414
            }
415

            
416
            game
417
        } else {
418
6
            Self {
419
6
                board,
420
6
                previous_boards,
421
6
                ..Self::default()
422
6
            }
423
        }
424
6
    }
425

            
426
    #[must_use]
427
30493
    pub fn all_legal_moves(&self) -> LegalMoves {
428
30493
        let size = self.board.size();
429
30493
        let board_size_usize = size.into();
430
30493
        let vec_capacity = match size {
431
30493
            BoardSize::_11 => 20,
432
            BoardSize::_13 => 24,
433
        };
434

            
435
30493
        let mut possible_vertexes = Vec::new();
436
30493
        let mut legal_moves = LegalMoves {
437
30493
            role: self.turn,
438
30493
            moves: FxHashMap::default(),
439
30493
        };
440

            
441
335423
        for y in 0..board_size_usize {
442
3689653
            for x in 0..board_size_usize {
443
3689653
                let vertex = Vertex { size, x, y };
444
3689653
                if Role::from(self.board.get(&vertex)) == legal_moves.role {
445
548834
                    possible_vertexes.push(vertex);
446
3140819
                }
447
            }
448
        }
449

            
450
548834
        for vertex_from in possible_vertexes {
451
548834
            let mut vertexes_to = Vec::with_capacity(vec_capacity);
452

            
453
6037174
            for y in 0..board_size_usize {
454
6037174
                let vertex_to = Vertex {
455
6037174
                    size,
456
6037174
                    x: vertex_from.x,
457
6037174
                    y,
458
6037174
                };
459
6037174
                let play = Play {
460
6037174
                    role: self.turn,
461
6037174
                    from: vertex_from,
462
6037174
                    to: vertex_to,
463
6037174
                };
464

            
465
6037174
                if self
466
6037174
                    .board
467
6037174
                    .legal_move(&play, &self.status, &self.turn, &self.previous_boards)
468
6037174
                    .is_ok()
469
1643205
                {
470
1643205
                    vertexes_to.push(vertex_to);
471
4393969
                }
472
            }
473

            
474
6037174
            for x in 0..board_size_usize {
475
6037174
                let vertex_to = Vertex {
476
6037174
                    size,
477
6037174
                    x,
478
6037174
                    y: vertex_from.y,
479
6037174
                };
480
6037174
                let play = Play {
481
6037174
                    role: self.turn,
482
6037174
                    from: vertex_from,
483
6037174
                    to: vertex_to,
484
6037174
                };
485

            
486
6037174
                if self
487
6037174
                    .board
488
6037174
                    .legal_move(&play, &self.status, &self.turn, &self.previous_boards)
489
6037174
                    .is_ok()
490
1643946
                {
491
1643946
                    vertexes_to.push(vertex_to);
492
4393228
                }
493
            }
494

            
495
548834
            if !vertexes_to.is_empty() {
496
518073
                legal_moves.moves.insert(vertex_from, vertexes_to);
497
518073
            }
498
        }
499

            
500
30493
        legal_moves
501
30493
    }
502

            
503
    #[allow(clippy::missing_panics_doc)]
504
    #[must_use]
505
14867
    pub fn kings_legal_moves(&self) -> Option<(Vertex, Vec<Vertex>)> {
506
14867
        let size = self.board.size();
507
14867
        let board_size_usize = size.into();
508
14867
        let kings_position = self.board.find_the_king()?;
509
14867
        let mut vertexes_to = Vec::new();
510

            
511
163537
        for y in 0..board_size_usize {
512
163537
            let vertex_to = Vertex {
513
163537
                size,
514
163537
                x: kings_position.x,
515
163537
                y,
516
163537
            };
517
163537
            let play = Play {
518
163537
                role: self.turn,
519
163537
                from: kings_position,
520
163537
                to: vertex_to,
521
163537
            };
522

            
523
163537
            if self
524
163537
                .board
525
163537
                .legal_move(&play, &self.status, &self.turn, &self.previous_boards)
526
163537
                .is_ok()
527
33119
            {
528
33119
                vertexes_to.push(vertex_to);
529
130418
            }
530
        }
531

            
532
163537
        for x in 0..board_size_usize {
533
163537
            let vertex_to = Vertex {
534
163537
                size,
535
163537
                x,
536
163537
                y: kings_position.y,
537
163537
            };
538
163537
            let play = Play {
539
163537
                role: self.turn,
540
163537
                from: kings_position,
541
163537
                to: vertex_to,
542
163537
            };
543

            
544
163537
            if self
545
163537
                .board
546
163537
                .legal_move(&play, &self.status, &self.turn, &self.previous_boards)
547
163537
                .is_ok()
548
32764
            {
549
32764
                vertexes_to.push(vertex_to);
550
130773
            }
551
        }
552

            
553
14867
        Some((kings_position, vertexes_to))
554
14867
    }
555

            
556
    #[allow(clippy::missing_panics_doc)]
557
    #[must_use]
558
30493
    pub fn all_legal_plays(&self) -> Vec<Plae> {
559
30493
        let moves = self.all_legal_moves();
560

            
561
30493
        if moves.moves.is_empty() && self.status == Status::Ongoing {
562
            match &moves.role {
563
                Role::Attacker => return vec![Plae::AttackerResigns],
564
                Role::Defender => return vec![Plae::DefenderResigns],
565
                Role::Roleless => return Vec::new(),
566
            }
567
30493
        }
568

            
569
30493
        let mut plays = Vec::new();
570
518073
        for (from, tos) in moves.moves {
571
3287151
            for to in tos {
572
3287151
                plays.push(Plae::Play(Play {
573
3287151
                    role: moves.role,
574
3287151
                    from,
575
3287151
                    to,
576
3287151
                }));
577
3287151
            }
578
        }
579

            
580
30493
        plays
581
30493
    }
582

            
583
    #[must_use]
584
29747
    pub fn calculate_hash(&self) -> u64 {
585
29747
        let mut s = DefaultHasher::new();
586
29747
        self.plays.hash(&mut s);
587
29747
        s.finish()
588
29747
    }
589

            
590
    #[must_use]
591
24
    pub fn exit_one(&self) -> bool {
592
24
        let size = self.board.size();
593
24
        let board_size_usize: usize = size.into();
594

            
595
24
        let exit_1 = Vertex { size, x: 0, y: 0 };
596
24
        let exit_2 = Vertex {
597
24
            size,
598
24
            x: board_size_usize - 1,
599
24
            y: 0,
600
24
        };
601
24
        let exit_3 = Vertex {
602
24
            size,
603
24
            x: 0,
604
24
            y: board_size_usize - 1,
605
24
        };
606
24
        let exit_4 = Vertex {
607
24
            size,
608
24
            x: board_size_usize - 1,
609
24
            y: board_size_usize - 1,
610
24
        };
611

            
612
24
        let mut game = self.clone();
613
24
        if let Some(king) = self.board.find_the_king() {
614
90
            for exit in [exit_1, exit_2, exit_3, exit_4] {
615
90
                if game
616
90
                    .play(&Plae::Play(Play {
617
90
                        role: self.turn,
618
90
                        from: king,
619
90
                        to: exit,
620
90
                    }))
621
90
                    .is_ok()
622
                {
623
6
                    return true;
624
84
                }
625
            }
626
        }
627

            
628
18
        false
629
24
    }
630

            
631
    #[must_use]
632
    pub fn moves_to_escape(&self) -> (MovesToEscape, EscapeVec) {
633
        let Some(start) = self.board.find_the_king() else {
634
            return (MovesToEscape::GameOver, EscapeVec::new(self.board.size()));
635
        };
636

            
637
        let mut priority_queue = BinaryHeap::new();
638
        priority_queue.push((None, vec![start]));
639

            
640
        let mut escape_vec = EscapeVec::new(self.board.size());
641
        escape_vec.set(&start, 0);
642

            
643
        let mut visited = HashMap::new();
644
        visited.insert(start, (0, None));
645

            
646
        while let Some((current_cost, current_nodes)) = priority_queue.pop() {
647
            let neighbors = self.board.get_neighbors(&current_nodes, &visited);
648

            
649
            let cost = if let Some(neighbor) = neighbors.first() {
650
                escape_vec.get(neighbor)
651
            } else {
652
                continue;
653
            };
654
            let cost = cost.unwrap_or_default();
655

            
656
            let total_cost = if let Some(Some(current_cost)) = current_cost {
657
                current_cost + cost
658
            } else {
659
                cost
660
            };
661

            
662
            for neighbor in &neighbors {
663
                if !visited.contains_key(neighbor) || total_cost < visited[neighbor].0 {
664
                    let mut moves = escape_vec.get(&current_nodes[0]).unwrap_or_default();
665
                    moves += 1;
666
                    escape_vec.set(neighbor, moves);
667

            
668
                    if self.board.exit_squares().contains(neighbor) {
669
                        return (MovesToEscape::Moves(moves), escape_vec);
670
                    }
671

            
672
                    for current_node in &current_nodes {
673
                        visited.insert(*neighbor, (total_cost, Some(*current_node)));
674
                    }
675
                }
676
            }
677

            
678
            priority_queue.push((Some(Some(total_cost)), neighbors));
679
        }
680

            
681
        (MovesToEscape::CanNotEscape, escape_vec)
682
    }
683

            
684
    #[allow(clippy::missing_panics_doc)]
685
    #[must_use]
686
29741
    pub fn obvious_play(&self) -> Option<Plae> {
687
29741
        match self.turn {
688
            Role::Attacker => {
689
14874
                if let Some(vertex) = self.board.capture_the_king_one_move() {
690
6379
                    for plae in self.all_legal_plays() {
691
6379
                        if let Plae::Play(ref play) = plae
692
6379
                            && play.to == vertex
693
                        {
694
7
                            return Some(plae);
695
6372
                        }
696
                    }
697
14823
                }
698
            }
699
            Role::Defender => {
700
14867
                let (kings_position, move_to) = self.kings_legal_moves()?;
701

            
702
65832
                for play in move_to {
703
65832
                    if play.on_exit_square() {
704
18
                        return Some(Plae::Play(Play {
705
18
                            role: Role::Defender,
706
18
                            from: kings_position,
707
18
                            to: play,
708
18
                        }));
709
65814
                    }
710
                }
711
            }
712
            Role::Roleless => unreachable!(),
713
        }
714

            
715
29716
        None
716
29741
    }
717

            
718
    /// # Errors
719
    ///
720
    /// If the game is already over or the move is illegal.
721
    #[allow(clippy::too_many_lines)]
722
3041210
    pub fn play(&mut self, play: &Plae) -> anyhow::Result<Captures> {
723
3041210
        if self.status == Status::Ongoing {
724
3041210
            if let (status, TimeSettings::Timed(timer), TimeUnix::Time(time)) = match self.turn {
725
1533517
                Role::Attacker => (
726
1533517
                    Status::DefenderWins,
727
1533517
                    &mut self.attacker_time,
728
1533517
                    &mut self.time,
729
1533517
                ),
730
                Role::Roleless => {
731
                    unreachable!("It can't be no one's turn when the game is ongoing!")
732
                }
733
1507693
                Role::Defender => (
734
1507693
                    Status::AttackerWins,
735
1507693
                    &mut self.defender_time,
736
1507693
                    &mut self.time,
737
1507693
                ),
738
            } {
739
30965
                let now = Utc::now().timestamp_millis();
740
30965
                timer.milliseconds_left -= now - *time;
741
30965
                *time = now;
742

            
743
30965
                if timer.milliseconds_left <= 0 {
744
                    self.status = status;
745
                    return Ok(Captures::default());
746
30965
                }
747

            
748
30965
                timer.milliseconds_left += timer.add_seconds * 1_000;
749
3010245
            }
750

            
751
3041210
            match play {
752
                Plae::AttackerResigns => {
753
                    if self.turn == Role::Attacker {
754
                        self.status = Status::DefenderWins;
755

            
756
                        match &mut self.plays {
757
                            Plays::PlayRecordsTimed(plays) => {
758
                                plays.push(PlayRecordTimed {
759
                                    play: Some(play.clone()),
760
                                    attacker_time: self.attacker_time.clone().try_into()?,
761
                                    defender_time: self.defender_time.clone().try_into()?,
762
                                });
763
                            }
764
                            Plays::PlayRecords(plays) => plays.push(Some(play.clone())),
765
                        }
766

            
767
                        Ok(Captures::default())
768
                    } else {
769
                        Err(anyhow::Error::msg("You can't resign for the other player."))
770
                    }
771
                }
772
                Plae::DefenderResigns => {
773
                    if self.turn == Role::Defender {
774
                        self.status = Status::AttackerWins;
775

            
776
                        match &mut self.plays {
777
                            Plays::PlayRecordsTimed(plays) => {
778
                                plays.push(PlayRecordTimed {
779
                                    play: Some(play.clone()),
780
                                    attacker_time: self.attacker_time.clone().try_into()?,
781
                                    defender_time: self.defender_time.clone().try_into()?,
782
                                });
783
                            }
784
                            Plays::PlayRecords(plays) => plays.push(Some(play.clone())),
785
                        }
786

            
787
                        Ok(Captures::default())
788
                    } else {
789
                        Err(anyhow::Error::msg("You can't resign for the other player."))
790
                    }
791
                }
792
3041210
                Plae::Play(play) => {
793
3041210
                    let piece_role = Role::from(self.board.get(&play.from));
794
3041210
                    if piece_role != play.role {
795
24
                        return Err(anyhow::Error::msg(format!(
796
24
                            "play: you are trying to move {piece_role}, but it's {}'s turn",
797
24
                            play.role
798
24
                        )));
799
3041186
                    }
800

            
801
3041186
                    let (captures, status) = self.board.play(
802
3041186
                        &Plae::Play(play.clone()),
803
3041186
                        &self.status,
804
3041186
                        &self.turn,
805
3041186
                        &mut self.previous_boards,
806
1368
                    )?;
807

            
808
3039818
                    self.status = status;
809

            
810
3039818
                    match &mut self.plays {
811
30833
                        Plays::PlayRecordsTimed(plays) => {
812
30833
                            plays.push(PlayRecordTimed {
813
30833
                                play: Some(Plae::Play(play.clone())),
814
30833
                                attacker_time: self.attacker_time.clone().try_into()?,
815
30833
                                defender_time: self.defender_time.clone().try_into()?,
816
                            });
817
                        }
818
3008985
                        Plays::PlayRecords(plays) => plays.push(Some(Plae::Play(play.clone()))),
819
                    }
820

            
821
3039818
                    if self.status == Status::Ongoing {
822
3027684
                        self.turn = self.turn.opposite();
823

            
824
3027684
                        if !self.board.a_legal_move_exists(
825
3027684
                            &self.status,
826
3027684
                            &self.turn,
827
3027684
                            &self.previous_boards,
828
3027684
                        ) {
829
748
                            self.status = match self.turn {
830
                                Role::Attacker => Status::DefenderWins,
831
                                Role::Roleless => unreachable!(),
832
748
                                Role::Defender => Status::AttackerWins,
833
                            }
834
3026936
                        }
835
12134
                    }
836

            
837
3039818
                    let captures = Captures(captures);
838
3039818
                    Ok(captures)
839
                }
840
            }
841
        } else {
842
            Err(InvalidMove::GameOver.into())
843
        }
844
3041210
    }
845

            
846
    /// # Errors
847
    ///
848
    /// If the command is illegal or invalid.
849
438
    pub fn read_line(&mut self, buffer: &str) -> anyhow::Result<Option<String>> {
850
438
        let mut buffer = Cow::from(buffer);
851
438
        if let Some(comment_offset) = buffer.find('#') {
852
            buffer.to_mut().replace_range(comment_offset.., "");
853
438
        }
854

            
855
438
        self.update(Message::from_str(buffer.as_ref())?)
856
438
    }
857

            
858
    /// # Errors
859
    ///
860
    /// If the command is illegal or invalid.
861
    #[allow(clippy::too_many_lines)]
862
408
    pub fn update(&mut self, message: Message) -> anyhow::Result<Option<String>> {
863
408
        match message {
864
            Message::BoardSize(size) => {
865
                let board_size = BoardSize::try_from(size)?;
866
                *self = Self::new_game(board_size, None);
867
                Ok(Some(String::new()))
868
            }
869
            Message::Empty => Ok(None),
870
            Message::FinalStatus => Ok(Some(format!("{}", self.status))),
871
            Message::GenerateMove => {
872
                let mut ai = AiBasic::new(4, true);
873
                let generate_move = ai.generate_move(self)?;
874
                Ok(Some(generate_move.to_string()))
875
            }
876
            Message::KnownCommand(command) => {
877
                if COMMANDS.contains(&command.as_str()) {
878
                    Ok(Some("true".to_string()))
879
                } else {
880
                    Ok(Some("false".to_string()))
881
                }
882
            }
883
            Message::ListCommands => {
884
                let mut commands = "\n".to_string();
885
                commands.push_str(&COMMANDS.join("\n"));
886
                Ok(Some(commands))
887
            }
888
            Message::Name => {
889
                let name = env!("CARGO_PKG_NAME");
890
                Ok(Some(name.to_string()))
891
            }
892
408
            Message::Play(play) => self.play(&play).map(|captures| Some(captures.to_string())),
893
            Message::PlayFrom => {
894
                let moves = self.all_legal_moves();
895
                Ok(Some(format!(
896
                    "{} {}",
897
                    moves.role,
898
                    moves
899
                        .moves
900
                        .keys()
901
                        .map(ToString::to_string)
902
                        .collect::<Vec<_>>()
903
                        .join(" ")
904
                )))
905
            }
906
            Message::PlayTo(from) => {
907
                let (role, vertex) = from;
908
                let moves = self.all_legal_moves();
909
                if role != moves.role {
910
                    return Err(anyhow::Error::msg(format!(
911
                        "tried play_to {role}, but it's {} turn",
912
                        moves.role
913
                    )));
914
                }
915

            
916
                if let Some(moves) = moves.moves.get(&vertex) {
917
                    Ok(Some(
918
                        moves
919
                            .iter()
920
                            .map(ToString::to_string)
921
                            .collect::<Vec<_>>()
922
                            .join(" "),
923
                    ))
924
                } else {
925
                    Err(anyhow::Error::msg("invalid from vertex"))
926
                }
927
            }
928
            Message::ProtocolVersion => Ok(Some("1-beta".to_string())),
929
            Message::Quit => exit(0),
930
            Message::ShowBoard => Ok(Some(self.board.to_string())),
931
            Message::TimeSettings(time_settings) => {
932
                *self = Self::new_game(self.board.size(), Some(time_settings));
933
                Ok(Some(String::new()))
934
            }
935
            Message::Version => {
936
                let version = env!("CARGO_PKG_VERSION");
937
                Ok(Some(version.to_string()))
938
            }
939
        }
940
408
    }
941

            
942
    #[must_use]
943
    pub fn utility(&self) -> (f64, EscapeVec) {
944
        let mut utility = 0.0;
945

            
946
        let captured = self.board.captured();
947
        utility -= f64::from(captured.attacker) * 100_000.0;
948

            
949
        let (moves_to_escape, escape_vec) = self.moves_to_escape();
950
        utility += match moves_to_escape {
951
            MovesToEscape::CanNotEscape => 20_000.0,
952
            MovesToEscape::GameOver => 0.0,
953
            MovesToEscape::Moves(moves) => f64::from(moves) * 1_000.0,
954
        };
955

            
956
        utility += f64::from(self.board.closed_off_exits()) * 100.0;
957
        // Todo: An extra 100.0 points for each corner that touches another corner.
958
        utility += f64::from(captured.defender) * 10.0;
959

            
960
        if let Some(spaces) = self.board.spaces_around_the_king() {
961
            utility -= f64::from(spaces);
962
        }
963

            
964
        (utility, escape_vec)
965
    }
966
}
967

            
968
#[derive(Clone, Debug)]
969
pub struct EscapeVec {
970
    spaces: Vec<Option<u8>>,
971
}
972

            
973
impl EscapeVec {
974
    fn new(board_size: BoardSize) -> Self {
975
        let size: usize = board_size.into();
976

            
977
        EscapeVec {
978
            spaces: vec![None; size * size],
979
        }
980
    }
981

            
982
    fn get(&self, vertex: &Vertex) -> Option<u8> {
983
        self.spaces[vertex.y * usize::from(vertex.size) + vertex.x]
984
    }
985

            
986
    fn set(&mut self, vertex: &Vertex, moves: u8) {
987
        self.spaces[vertex.y * usize::from(vertex.size) + vertex.x] = Some(moves);
988
    }
989
}
990

            
991
impl fmt::Display for EscapeVec {
992
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
993
        let board_size = if self.spaces.len() == 11 * 11 { 11 } else { 13 };
994

            
995
        match board_size {
996
            11 => writeln!(f, "   A  B  C  D  E  F  G  H  I  J  K")?,
997
            13 => writeln!(f, "   A  B  C  D  E  F  G  H  I  J  K  L  M")?,
998
            _ => unreachable!(),
999
        }
        for y in 0..board_size {
            match board_size {
                11 => write!(f, "{:2} ", 11 - y)?,
                13 => write!(f, "{:2} ", 13 - y)?,
                _ => unreachable!(),
            }
            for x in 0..board_size {
                let moves = self.spaces[y * board_size + x];
                if let Some(moves) = moves {
                    write!(f, "{moves:02} ")?;
                } else {
                    write!(f, "-- ")?;
                }
            }
            writeln!(f)?;
        }
        Ok(())
    }
}
#[derive(Clone, Debug)]
pub enum MovesToEscape {
    CanNotEscape,
    GameOver,
    Moves(u8),
}
impl From<&Tree> for Game {
    fn from(tree: &Tree) -> Self {
        let node = tree.here();
        let (plays, previous_boards) = tree.previous_boards();
        Self {
            board: node.board,
            plays,
            previous_boards,
            status: Status::Ongoing,
            turn: node.turn,
            ..Default::default()
        }
    }
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LegalMoves {
    pub role: Role,
    pub moves: FxHashMap<Vertex, Vec<Vertex>>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum TimeUnix {
    Time(i64),
    UnTimed,
}
impl Default for TimeUnix {
61674
    fn default() -> Self {
61674
        Self::Time(Utc::now().timestamp_millis())
61674
    }
}