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
// SPDX-License-Identifier: AGPL-3.0-or-later
17
// SPDX-FileCopyrightText: 2025 David Campbell <david@hnefatafl.org>
18

            
19
use std::{
20
    borrow::Cow,
21
    collections::{BinaryHeap, HashMap},
22
    fmt,
23
    hash::{DefaultHasher, Hash, Hasher},
24
    process::exit,
25
    str::FromStr,
26
};
27

            
28
use jiff::Timestamp;
29
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
30
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
31
use serde::{Deserialize, Serialize};
32
#[cfg(feature = "js")]
33
use wasm_bindgen::prelude::wasm_bindgen;
34

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

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

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

            
88
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
89
pub struct PreviousBoards(pub FxHashSet<Board>);
90

            
91
impl PreviousBoards {
92
    #[must_use]
93
24708
    pub fn new(board_size: BoardSize) -> Self {
94
24708
        let mut boards = FxHashSet::with_capacity_and_hasher(64, FxBuildHasher);
95

            
96
24708
        boards.insert(Board::new(board_size));
97
24708
        Self(boards)
98
24708
    }
99
}
100

            
101
impl Default for PreviousBoards {
102
24705
    fn default() -> Self {
103
24705
        Self::new(BoardSize::default())
104
24705
    }
105
}
106

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

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

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

            
131
        write!(f, "plays: {}", self.plays)
132
    }
133
}
134

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

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

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

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

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

            
193
                let (play_option_2, value_2, escape_vec_2) =
194
                    child.alpha_beta(original_depth, depth - 1, Some(plae.clone()), alpha, beta);
195

            
196
                if value_2 > value {
197
                    value = value_2;
198
                    play_option.clone_from(&play_option_2);
199
                    escape_vec.clone_from(&escape_vec_2);
200
                }
201

            
202
                if value >= beta {
203
                    break;
204
                }
205

            
206
                if value > alpha {
207
                    alpha = value;
208
                    play_option = play_option_2;
209
                    escape_vec = escape_vec_2;
210
                }
211
            }
212

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

            
221
                let (play_option_2, value_2, escape_vec_2) =
222
                    child.alpha_beta(original_depth, depth - 1, Some(plae.clone()), alpha, beta);
223

            
224
                if value_2 < value {
225
                    value = value_2;
226
                    play_option.clone_from(&play_option_2);
227
                    escape_vec.clone_from(&escape_vec_2);
228
                }
229

            
230
                if value <= alpha {
231
                    break;
232
                }
233

            
234
                if value < beta {
235
                    beta = value;
236
                    play_option = play_option_2;
237
                    escape_vec = escape_vec_2;
238
                }
239
            }
240

            
241
            (play_option, value, escape_vec)
242
        }
243
    }
244

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

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

            
270
                    child.alpha_beta(original_depth, depth - 1, Some(plae.clone()), alpha, beta)
271
                })
272
                .collect();
273

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

            
281
                if value >= beta {
282
                    break;
283
                }
284

            
285
                if value > alpha {
286
                    alpha = value;
287
                    play_option = play_option_2;
288
                    escape_vec = escape_vec_2;
289
                }
290
            }
291

            
292
            (play_option, value, escape_vec)
293
        } else {
294
            let mut value = f64::INFINITY;
295
            let mut escape_vec = None;
296

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

            
304
                    child.alpha_beta(original_depth, depth - 1, Some(plae.clone()), alpha, beta)
305
                })
306
                .collect();
307

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

            
315
                if value <= alpha {
316
                    break;
317
                }
318

            
319
                if value < beta {
320
                    beta = value;
321
                    play_option = play_option_2;
322
                    escape_vec = escape_vec_2;
323
                }
324
            }
325

            
326
            (play_option, value, escape_vec)
327
        }
328
    }
329

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

            
356
        if depth == 0 {
357
            let (utility, escape_vec) = self.utility();
358

            
359
            return Some((
360
                self.play_n(self.plays.len() - original_depth),
361
                utility,
362
                Some(escape_vec),
363
            ));
364
        }
365

            
366
        None
367
    }
368

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

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

            
395
    #[must_use]
396
3
    pub fn new_game(board_size: BoardSize, time_settings: Option<TimeSettings>) -> Self {
397
3
        let board = Board::new(board_size);
398
3
        let previous_boards = PreviousBoards::new(board_size);
399

            
400
3
        if let Some(time_settings) = time_settings {
401
            let mut game = Self {
402
                board,
403
                plays: Plays::new(&time_settings),
404
                previous_boards,
405
                ..Self::default()
406
            };
407

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

            
419
            game
420
        } else {
421
3
            Self {
422
3
                board,
423
3
                previous_boards,
424
3
                ..Self::default()
425
3
            }
426
        }
427
3
    }
428

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

            
438
15584
        let mut possible_vertexes = Vec::new();
439
15584
        let mut legal_moves = LegalMoves {
440
15584
            role: self.turn,
441
15584
            moves: FxHashMap::default(),
442
15584
        };
443

            
444
171424
        for y in 0..board_size_usize {
445
1885664
            for x in 0..board_size_usize {
446
1885664
                let vertex = Vertex { size, x, y };
447
1885664
                if Role::from(self.board.get(&vertex)) == legal_moves.role {
448
281049
                    possible_vertexes.push(vertex);
449
1604615
                }
450
            }
451
        }
452

            
453
281049
        for vertex_from in possible_vertexes {
454
281049
            let mut vertexes_to = Vec::with_capacity(vec_capacity);
455

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

            
468
3091539
                if self
469
3091539
                    .board
470
3091539
                    .legal_move(&play, &self.status, &self.turn, &self.previous_boards)
471
3091539
                    .is_ok()
472
840427
                {
473
840427
                    vertexes_to.push(vertex_to);
474
2251112
                }
475
            }
476

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

            
489
3091539
                if self
490
3091539
                    .board
491
3091539
                    .legal_move(&play, &self.status, &self.turn, &self.previous_boards)
492
3091539
                    .is_ok()
493
839441
                {
494
839441
                    vertexes_to.push(vertex_to);
495
2252098
                }
496
            }
497

            
498
281049
            if !vertexes_to.is_empty() {
499
264938
                legal_moves.moves.insert(vertex_from, vertexes_to);
500
264938
            }
501
        }
502

            
503
15584
        legal_moves
504
15584
    }
505

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

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

            
526
83754
            if self
527
83754
                .board
528
83754
                .legal_move(&play, &self.status, &self.turn, &self.previous_boards)
529
83754
                .is_ok()
530
18129
            {
531
18129
                vertexes_to.push(vertex_to);
532
65625
            }
533
        }
534

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

            
547
83754
            if self
548
83754
                .board
549
83754
                .legal_move(&play, &self.status, &self.turn, &self.previous_boards)
550
83754
                .is_ok()
551
16707
            {
552
16707
                vertexes_to.push(vertex_to);
553
67047
            }
554
        }
555

            
556
7614
        Some((kings_position, vertexes_to))
557
7614
    }
558

            
559
    #[allow(clippy::missing_panics_doc)]
560
    #[must_use]
561
15584
    pub fn all_legal_plays(&self) -> Vec<Plae> {
562
15584
        let moves = self.all_legal_moves();
563

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

            
572
15584
        let mut plays = Vec::new();
573
264938
        for (from, tos) in moves.moves {
574
1679868
            for to in tos {
575
1679868
                plays.push(Plae::Play(Play {
576
1679868
                    role: moves.role,
577
1679868
                    from,
578
1679868
                    to,
579
1679868
                }));
580
1679868
            }
581
        }
582

            
583
15584
        plays
584
15584
    }
585

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

            
593
    #[must_use]
594
12
    pub fn exit_one(&self) -> bool {
595
12
        let size = self.board.size();
596
12
        let board_size_usize: usize = size.into();
597

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

            
615
12
        let mut game = self.clone();
616
12
        if let Some(king) = self.board.find_the_king() {
617
45
            for exit in [exit_1, exit_2, exit_3, exit_4] {
618
45
                if game
619
45
                    .play(&Plae::Play(Play {
620
45
                        role: self.turn,
621
45
                        from: king,
622
45
                        to: exit,
623
45
                    }))
624
45
                    .is_ok()
625
                {
626
3
                    return true;
627
42
                }
628
            }
629
        }
630

            
631
9
        false
632
12
    }
633

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

            
640
        let mut priority_queue = BinaryHeap::new();
641
        priority_queue.push((None, vec![start]));
642

            
643
        let mut escape_vec = EscapeVec::new(self.board.size());
644
        escape_vec.set(&start, 0);
645

            
646
        let mut visited = HashMap::new();
647
        visited.insert(start, (0, None));
648

            
649
        while let Some((current_cost, current_nodes)) = priority_queue.pop() {
650
            let neighbors = self.board.get_neighbors(&current_nodes, &visited);
651

            
652
            let cost = if let Some(neighbor) = neighbors.first() {
653
                escape_vec.get(neighbor)
654
            } else {
655
                continue;
656
            };
657
            let cost = cost.unwrap_or_default();
658

            
659
            let total_cost = if let Some(Some(current_cost)) = current_cost {
660
                current_cost + cost
661
            } else {
662
                cost
663
            };
664

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

            
671
                    if self.board.exit_squares().contains(neighbor) {
672
                        return (MovesToEscape::Moves(moves), escape_vec);
673
                    }
674

            
675
                    for current_node in &current_nodes {
676
                        visited.insert(*neighbor, (total_cost, Some(*current_node)));
677
                    }
678
                }
679
            }
680

            
681
            priority_queue.push((Some(Some(total_cost)), neighbors));
682
        }
683

            
684
        (MovesToEscape::CanNotEscape, escape_vec)
685
    }
686

            
687
    #[allow(clippy::missing_panics_doc)]
688
    #[must_use]
689
15232
    pub fn obvious_play(&self) -> Option<Plae> {
690
15232
        match self.turn {
691
            Role::Attacker => {
692
7618
                if let Some(vertex) = self.board.capture_the_king_one_move() {
693
655
                    for plae in self.all_legal_plays() {
694
655
                        if let Plae::Play(ref play) = plae
695
655
                            && play.to == vertex
696
                        {
697
4
                            return Some(plae);
698
651
                        }
699
                    }
700
7610
                }
701
            }
702
            Role::Defender => {
703
7614
                let (kings_position, move_to) = self.kings_legal_moves()?;
704

            
705
34755
                for play in move_to {
706
34755
                    if play.on_exit_square() {
707
15
                        return Some(Plae::Play(Play {
708
15
                            role: Role::Defender,
709
15
                            from: kings_position,
710
15
                            to: play,
711
15
                        }));
712
34740
                    }
713
                }
714
            }
715
            Role::Roleless => unreachable!(),
716
        }
717

            
718
15213
        None
719
15232
    }
720

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

            
746
15844
                if timer.milliseconds_left <= 0 {
747
                    self.status = status;
748
                    return Ok(Captures::default());
749
15844
                }
750

            
751
15844
                timer.milliseconds_left += timer.add_seconds * 1_000;
752
1204098
            }
753

            
754
1219942
            match play {
755
                Plae::AttackerResigns => {
756
                    if self.turn == Role::Attacker {
757
                        self.status = Status::DefenderWins;
758

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

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

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

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

            
804
1219930
                    let (captures, status) = self.board.play(
805
1219930
                        &Plae::Play(play.clone()),
806
1219930
                        &self.status,
807
1219930
                        &self.turn,
808
1219930
                        &mut self.previous_boards,
809
558
                    )?;
810

            
811
1219372
                    self.status = status;
812

            
813
1219372
                    match &mut self.plays {
814
15778
                        Plays::PlayRecordsTimed(plays) => {
815
15778
                            plays.push(PlayRecordTimed {
816
15778
                                play: Some(Plae::Play(play.clone())),
817
15778
                                attacker_time: self.attacker_time.clone().try_into()?,
818
15778
                                defender_time: self.defender_time.clone().try_into()?,
819
                            });
820
                        }
821
1203594
                        Plays::PlayRecords(plays) => plays.push(Some(Plae::Play(play.clone()))),
822
                    }
823

            
824
1219372
                    if self.status == Status::Ongoing {
825
1214492
                        self.turn = self.turn.opposite();
826

            
827
1214492
                        if !self.board.a_legal_move_exists(
828
1214492
                            &self.status,
829
1214492
                            &self.turn,
830
1214492
                            &self.previous_boards,
831
1214492
                        ) {
832
304
                            self.status = match self.turn {
833
                                Role::Attacker => Status::DefenderWins,
834
                                Role::Roleless => unreachable!(),
835
304
                                Role::Defender => Status::AttackerWins,
836
                            }
837
1214188
                        }
838
4880
                    }
839

            
840
1219372
                    let captures = Captures(captures);
841
1219372
                    Ok(captures)
842
                }
843
            }
844
        } else {
845
            Err(InvalidMove::GameOver.into())
846
        }
847
1219942
    }
848

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

            
858
219
        self.update(Message::from_str(buffer.as_ref())?)
859
219
    }
860

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

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

            
945
    #[must_use]
946
    pub fn utility(&self) -> (f64, EscapeVec) {
947
        let mut utility = 0.0;
948

            
949
        let captured = self.board.captured();
950
        utility -= f64::from(captured.attacker) * 100_000.0;
951

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

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

            
963
        if let Some(spaces) = self.board.spaces_around_the_king() {
964
            utility -= f64::from(spaces);
965
        }
966

            
967
        (utility, escape_vec)
968
    }
969
}
970

            
971
#[derive(Clone, Debug)]
972
pub struct EscapeVec {
973
    spaces: Vec<Option<u8>>,
974
}
975

            
976
impl EscapeVec {
977
    fn new(board_size: BoardSize) -> Self {
978
        let size: usize = board_size.into();
979

            
980
        EscapeVec {
981
            spaces: vec![None; size * size],
982
        }
983
    }
984

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

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

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

            
998
        match board_size {
999
            11 => writeln!(f, "   A  B  C  D  E  F  G  H  I  J  K")?,
            13 => writeln!(f, "   A  B  C  D  E  F  G  H  I  J  K  L  M")?,
            _ => unreachable!(),
        }
        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 {
24705
    fn default() -> Self {
24705
        Self::Time(Timestamp::now().as_millisecond())
24705
    }
}