1use ansi_term::Color::*;
2static TICTACTOE_LOGO: &str = "
3| |_(_) ___| |_ __ _ ___| |_ ___ ___ _ __ _ _ ___| |_
4| __| |/ __| __/ _` |/ __| __/ _ \\ / _ \\_____| '__| | | / __| __|
5| |_| | (__| || (_| | (__| || (_) | __/_____| | | |_| \\__ \\ |_
6 \\__|_|\\___|\\__\\__,_|\\___|\\__\\___/ \\___| |_| \\__,_|___/\\__|
7";
8
9static GAME_RULE: &str = "Game rule:
10
11Two player put their piece turn by turn.
12If a player have 3 same pieces on a line
13horizontally, or vertically, or diagonally,
14that player wins.";
15
16static FRONT_PAGE_AVAILABLE_COMMANDS: [Commands; 9] = [
17 Commands::Help,
18 Commands::Start,
19 Commands::Quit,
20 Commands::Command1,
21 Commands::Command2,
22 Commands::Command3,
23 Commands::Command4,
24 Commands::Command5,
25 Commands::Command6,
26];
27static FRONT_PAGE_HELP_AVAILABLE_COMMANDS: [Commands; 3] =
28 [Commands::Start, Commands::Back, Commands::Quit];
29static CONFG_PLAYER_AVAILABLE_COMMANDS: [Commands; 6] = [
30 Commands::Command1,
31 Commands::Command2,
32 Commands::Command3,
33 Commands::Command4,
34 Commands::Back,
35 Commands::Quit,
36];
37
38static EMPTY_PIECE_TABLE: &'static [(&str, &str, &str)] =
39 &[(&"sp", &" ", &"(space)"), (&"dt", &".", &"(dot)")];
40
41static PIECE_TABLE: &'static [(&str, &str, &str)] = &[
42 (&"bc", &"●", &"(black circle)"),
43 (&"wc", &"○", &"(white circle)"),
44 (&"bs", &"■", &"(black square)"),
45 (&"ws", &"□", &"(white square)"),
46 (&"bk", &"♚", &"(black king)"),
47 (&"wk", &"♔", &"(white king)"),
48 (&"bq", &"♛", &"(black queen)"),
49 (&"wq", &"♕", &"(white queen)"),
50 (&"bp", &"♟", &"(black pawn)"),
51 (&"wp", &"♙", &"(white pawn)"),
52];
53
54use super::board::Board;
55use super::game::Config;
56
57fn print_table(table: &[(&str, &str, &str)], num_of_spaces: usize) {
58 let spaces = " ".repeat(num_of_spaces);
59 for i in table {
60 println!("{}[{}] {} {}", spaces, i.0, i.1, i.2);
61 }
62}
63
64fn choose_string_from_table(
65 table: &'static [(&str, &'static str, &str)],
66 string: &str,
67) -> Option<&'static str> {
68 for i in table {
69 if string == i.0 {
70 return Some(i.1);
71 }
72 continue;
73 }
74 None
75}
76
77fn ask_for_user_name() -> String {
78 use std::io;
79 let mut input = String::new();
80 io::stdin()
81 .read_line(&mut input)
82 .expect("Failed to read line when asking for user command");
83 let input = input.trim();
84 input.to_string()
85}
86
87fn ask_user_move_input() -> Result<(usize, usize), &'static str> {
88 use std::io;
89 println!("Where you want to put your piece: Ex: 0, 2 or (q)uit");
90 let mut input = String::new();
91 io::stdin()
92 .read_line(&mut input)
93 .expect("Could not read line when asking for user input");
94
95 let input = input.trim();
96
97 if input == "q" || input == "quit" {
98 return Err("Quit");
99 }
100
101 let string_pool: Vec<&str> = input.split(|c| c == ' ' || c == ',').collect();
102
103 let nums: Vec<usize> = string_pool
104 .iter()
105 .filter(|num| num.parse::<usize>().is_ok())
106 .map(|num| num.parse::<usize>().unwrap())
107 .collect();
108
109 if nums.len() != 2 {
110 return Err("Could not parse input to numbers");
111 }
112 Ok((nums[0], nums[1]))
113}
114
115fn ask_for_user_string(
116 table: &'static [(&'static str, &str, &str)],
117) -> Result<String, &'static str> {
118 use std::io;
119 let mut input = String::new();
120 io::stdin()
121 .read_line(&mut input)
122 .expect("Failed to read line when asking for user command");
123 let input = input.trim();
124 if input.len() == 2 {
125 let option = choose_string_from_table(table, input);
126 if option.is_none() {
127 Err("String is of incorrect format.")
128 } else {
129 Ok(option.unwrap().to_string())
130 }
131 } else if input.chars().count() == 1 {
132 Ok(input.to_owned())
133 } else {
134 Err("String is of incorrect format.")
135 }
136}
137
138pub fn ask_and_run_command(mut config: &mut Config, mut board: &mut Board) {
139 let mut cache = CommandCache::new();
140 let mut command: Commands = Commands::Quit;
141 loop {
142 cache.print_current_page(config, board);
143
144 match cache.current_page {
145 Page::ConfigEmptyPiece
146 | Page::EnterUserName1
147 | Page::EnterUserName2
148 | Page::EnterUserPiece1
149 | Page::EnterUserPiece2
150 | Page::Playing => {}
151 _ => {
152 command = Commands::keep_asking_for_valid_user_command();
153
154 if command == Commands::Quit {
155 break;
156 }
157 }
158 }
159
160 println!("{}", Black.paint("=".repeat(51)));
161
162 println!();
163
164 cache
165 .execute_command_and_update_command_cache(command, &mut config, &mut board)
166 .unwrap_or_else(|err| {
167 println!("{}", err);
168 });
169 }
170}
171
172enum Page {
173 FrontPage,
174 FrontPageHelp,
175 Playing,
176 ConfigMode,
177 ConfigPlayer,
178 ConfigFirst,
179 ConfigAiSmartness,
180 ConfigEmptyPiece,
181 EnterUserName1,
182 EnterUserName2,
183 EnterUserPiece1,
184 EnterUserPiece2,
185 Winner,
186}
187
188#[derive(PartialEq, Debug, Copy, Clone)]
189enum Commands {
190 Help,
191 Start,
192 Back,
193 Quit,
194 Command1,
195 Command2,
196 Command3,
197 Command4,
198 Command5,
199 Command6,
200 Restart,
201 FrontPage,
202}
203
204impl Commands {
205 fn ask_for_user_command() -> Option<Commands> {
206 use std::io;
207 let mut input = String::new();
208 io::stdin()
209 .read_line(&mut input)
210 .expect("Failed to read line when asking for user command");
211 let flag = input == "\n";
212 let input = input.trim();
213 if input == "h" || input == "help" {
214 Some(Commands::Help)
215 } else if input == "s" || input == "start" {
216 Some(Commands::Start)
217 } else if input == "q" || input == "quit" {
218 Some(Commands::Quit)
219 } else if input == "r" || input == "restart" {
220 Some(Commands::Restart)
221 } else if input == "f" || input == "frontpage" {
222 Some(Commands::FrontPage)
223 } else if input == "b" || input == "back" || flag {
224 Some(Commands::Back)
225 } else if input == "1" {
226 Some(Commands::Command1)
227 } else if input == "2" {
228 Some(Commands::Command2)
229 } else if input == "3" {
230 Some(Commands::Command3)
231 } else if input == "4" {
232 Some(Commands::Command4)
233 } else if input == "5" {
234 Some(Commands::Command5)
235 } else if input == "6" {
236 Some(Commands::Command6)
237 } else {
238 None
239 }
240 }
241
242 fn keep_asking_for_valid_user_command() -> Commands {
243 let command: Commands;
244 loop {
245 command = match Commands::ask_for_user_command() {
246 Some(command) => command,
247 None => {
248 println!("unrecognized command");
249 continue;
250 }
251 };
252 break;
253 }
254 command
255 }
256}
257
258use std::fmt;
259impl fmt::Display for Commands {
260 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
261 write!(
262 f,
263 "{}",
264 Purple.paint(match &self {
265 Commands::Help => "(h)elp",
266 Commands::Back => "(b)ack/[Enter]",
267 Commands::Start => "(s)tart",
268 Commands::Quit => "(q)uit",
269 Commands::Restart => "(r)estart",
270 Commands::FrontPage => "(f)rontpage",
271 Commands::Command1 => "(1)",
272 Commands::Command2 => "(2)",
273 Commands::Command3 => "(3)",
274 Commands::Command4 => "(4)",
275 Commands::Command5 => "(5)",
276 Commands::Command6 => "(6)",
277 })
278 )
279 }
280}
281
282use super::game::Players;
283struct CommandCache {
284 current_page: Page,
285 available_commands: Vec<Commands>,
286 current_player: Players,
287 winner: Option<Players>,
288}
289
290impl CommandCache {
291 fn new() -> CommandCache {
292 CommandCache {
293 current_page: Page::FrontPage,
294 available_commands: FRONT_PAGE_AVAILABLE_COMMANDS.to_vec(),
295 current_player: Players::Player1,
296 winner: None,
297 }
298 }
299
300 fn change_player(&mut self) {
301 if self.current_player == Players::Player1 {
302 self.current_player = Players::Player2;
303 } else {
304 self.current_player = Players::Player1;
305 }
306 }
307
308 fn print_current_page(&self, config: &Config, board: &Board) {
309 match self.current_page {
310 Page::FrontPage => {
311 print!(
312 "{}
313Welcome to tictactoe-rust!
314
315Current configuration:
316",
317 Cyan.paint(TICTACTOE_LOGO)
318 );
319 config.text_print_config(false);
320 println!();
321 self.print_available_commands();
322 }
323 Page::FrontPageHelp => {
324 println!("{}", GAME_RULE);
325 println!();
326 self.print_available_commands();
327 }
328 Page::ConfigMode => {
329 println!(
330 "Please enter mode:
331 [1] PVP : User1 vs User2
332 [2] PVC : User1 vs AI1
333 [3] CVC : AI1 vs AI2
334"
335 );
336 self.print_available_commands();
337 }
338 Page::ConfigPlayer => {
339 println!("Configuration for name and piece character:");
340 config.text_print_player_page_config();
341 println!();
342 self.print_available_commands();
343 }
344 Page::EnterUserName1 => {
345 println!("Please enter new user name for Player1");
346 }
347 Page::EnterUserName2 => {
348 println!("Please enter new user name for Player2");
349 }
350 Page::EnterUserPiece1 => {
351 println!("Please enter new 1-len string for piece for Player1");
352 println!();
353 print_table(PIECE_TABLE, 2);
354 }
355 Page::EnterUserPiece2 => {
356 println!("Please enter new 1-len string for piece for Player1");
357 println!();
358 print_table(PIECE_TABLE, 2);
359 }
360 Page::ConfigFirst => {
361 println!(
362 "Please enter who will play first:
363 [1] Player1
364 [2] Player2
365"
366 );
367 self.print_available_commands();
368 }
369 Page::ConfigAiSmartness => {
370 println!(
371 "Choose the smartness level of Ai.
372
373 [1] Kindergarden (random choose where is empty)
374 [2] Elementary (get match line if it can and step in your match line)
375 [3] Graduate (as smart as elementary)
376 [4] God (as smart as elementary)
377"
378 );
379 }
380 Page::ConfigEmptyPiece => {
381 println!(
382 "Please enter the 1-len string for empty piece
383Or choose one of the following:
384"
385 );
386 print_table(EMPTY_PIECE_TABLE, 2);
387 }
388 Page::Playing => {
389 super::game::Game::text_print_board(board, config);
390 }
391 Page::Winner => {
392 super::game::Game::text_print_board(board, config);
393 if self.winner.is_some() {
394 let winner = self.winner.unwrap();
395 let name = if Players::Player1 == winner {
396 config.players.0.name.as_str()
397 } else {
398 config.players.1.name.as_str()
399 };
400 println!("The winner is {}", name);
401 } else {
402 println!("The game is a draw.");
403 }
404 board.print_matches(2);
405 self.print_available_commands();
406 }
407 }
408 }
409
410 fn execute_command_and_update_command_cache(
411 &mut self,
412 command: Commands,
413 config: &mut Config,
414 board: &mut Board,
415 ) -> Result<(), &str> {
416 use super::game::{Mode, Players};
417 match self.current_page {
418 Page::FrontPage => match command {
419 Commands::Help => {
420 self.current_page = Page::FrontPageHelp;
421 self.available_commands = FRONT_PAGE_HELP_AVAILABLE_COMMANDS.to_vec();
422 Ok(())
423 }
424 Commands::Start => {
425 self.current_page = Page::Playing;
426 self.available_commands = vec![];
427 Ok(())
428 }
429 Commands::Command1 => {
430 self.current_page = Page::ConfigMode;
431 self.available_commands = vec![
432 Commands::Command1,
433 Commands::Command2,
434 Commands::Command3,
435 Commands::Back,
436 Commands::Quit,
437 ];
438 Ok(())
439 }
440 Commands::Command2 | Commands::Command3 => {
441 self.current_page = Page::ConfigPlayer;
442 self.available_commands = CONFG_PLAYER_AVAILABLE_COMMANDS.to_vec();
443 Ok(())
444 }
445 Commands::Command4 => {
446 self.current_page = Page::ConfigFirst;
447 self.available_commands = vec![
448 Commands::Command1,
449 Commands::Command2,
450 Commands::Back,
451 Commands::Quit,
452 ];
453 Ok(())
454 }
455 Commands::Command5 => {
456 self.current_page = Page::ConfigAiSmartness;
457 self.available_commands = vec![
458 Commands::Command1,
459 Commands::Command2,
460 Commands::Command3,
461 Commands::Command4,
462 Commands::Back,
463 Commands::Quit,
464 ];
465 Ok(())
466 }
467 Commands::Command6 => {
468 self.current_page = Page::ConfigEmptyPiece;
469 self.available_commands = vec![];
470 Ok(())
471 }
472 Commands::Quit => {
473 panic!("Should not reach here");
474 }
475 _ => Err("Does not support this command on this page"),
476 },
477 Page::FrontPageHelp => match command {
478 Commands::Back => {
479 self.current_page = Page::FrontPage;
480 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
481 Ok(())
482 }
483 Commands::Start => {
484 self.current_page = Page::Playing;
485 self.available_commands = vec![];
486 Ok(())
487 }
488 Commands::Quit => {
489 panic!("Should not reach here");
490 }
491 _ => Err("Does not support this command on this page"),
492 },
493 Page::ConfigMode => match command {
494 Commands::Command1 => {
495 self.current_page = Page::FrontPage;
496 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
497 config.mode = Mode::PVP;
498 config.players.0.is_ai = false;
499 config.players.0.name = String::from("User1");
500 config.players.1.name = String::from("User2");
501 config.players.1.is_ai = false;
502 Ok(())
503 }
504 Commands::Command2 => {
505 self.current_page = Page::FrontPage;
506 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
507 config.mode = Mode::PVC;
508 config.players.0.is_ai = false;
509 config.players.1.is_ai = true;
510 config.players.0.name = String::from("User1");
511 config.players.1.name = String::from("AI1");
512 Ok(())
513 }
514 Commands::Command3 => {
515 self.current_page = Page::FrontPage;
516 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
517 config.mode = Mode::CVC;
518 config.players.0.is_ai = true;
519 config.players.1.is_ai = true;
520 config.players.0.name = String::from("AI1");
521 config.players.1.name = String::from("AI2");
522 Ok(())
523 }
524 Commands::Back => {
525 self.current_page = Page::FrontPage;
526 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
527 Ok(())
528 }
529 Commands::Quit => {
530 panic!("Should not reach here");
531 }
532 _ => Err("Does not support this command on this page"),
533 },
534 Page::ConfigPlayer => match command {
535 Commands::Command1 => {
536 self.current_page = Page::EnterUserName1;
537 self.available_commands = vec![];
538 Ok(())
539 }
540 Commands::Command2 => {
541 self.current_page = Page::EnterUserName2;
542 self.available_commands = vec![];
543 Ok(())
544 }
545 Commands::Command3 => {
546 self.current_page = Page::EnterUserPiece1;
547 self.available_commands = vec![];
548 Ok(())
549 }
550 Commands::Command4 => {
551 self.current_page = Page::EnterUserPiece2;
552 self.available_commands = vec![];
553 Ok(())
554 }
555 Commands::Back => {
556 self.current_page = Page::FrontPage;
557 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
558 Ok(())
559 }
560 Commands::Quit => {
561 panic!("Should not reach here");
562 }
563 _ => Err("Does not support this command on this page"),
564 },
565 Page::EnterUserName1 => {
566 self.current_page = Page::ConfigPlayer;
567 self.available_commands = CONFG_PLAYER_AVAILABLE_COMMANDS.to_vec();
568 config.players.0.name = ask_for_user_name();
569 Ok(())
570 }
571 Page::EnterUserName2 => {
572 self.current_page = Page::ConfigPlayer;
573 self.available_commands = CONFG_PLAYER_AVAILABLE_COMMANDS.to_vec();
574 config.players.1.name = ask_for_user_name();
575 Ok(())
576 }
577 Page::EnterUserPiece1 => {
578 self.current_page = Page::ConfigPlayer;
579 self.available_commands = CONFG_PLAYER_AVAILABLE_COMMANDS.to_vec();
580 let result = ask_for_user_string(PIECE_TABLE);
581 if result.is_ok() {
582 let user_string = result.unwrap();
583 config.players.0.piece = user_string;
584 Ok(())
585 } else {
586 Err(result.unwrap_err())
587 }
588 }
589 Page::EnterUserPiece2 => {
590 self.current_page = Page::ConfigPlayer;
591 self.available_commands = CONFG_PLAYER_AVAILABLE_COMMANDS.to_vec();
592 let result = ask_for_user_string(PIECE_TABLE);
593 if result.is_ok() {
594 let user_string = result.unwrap();
595 config.players.1.piece = user_string;
596 Ok(())
597 } else {
598 Err(result.unwrap_err())
599 }
600 }
601 Page::ConfigFirst => match command {
602 Commands::Command1 => {
603 self.current_page = Page::FrontPage;
604 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
605 config.first = Players::Player1;
606 self.current_player = Players::Player1;
607 Ok(())
608 }
609 Commands::Command2 => {
610 self.current_page = Page::FrontPage;
611 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
612 config.first = Players::Player2;
613 self.current_player = Players::Player2;
614 Ok(())
615 }
616 Commands::Back => {
617 self.current_page = Page::FrontPage;
618 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
619 Ok(())
620 }
621 Commands::Quit => {
622 panic!("Should not reach here");
623 }
624 _ => Err("Does not support this command on this page"),
625 },
626 Page::ConfigAiSmartness => {
627 use super::game::SmartLevel;
628 match command {
629 Commands::Command1 => {
630 config.ai_smartness = SmartLevel::Kindergarden;
631 self.current_page = Page::FrontPage;
632 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
633 Ok(())
634 }
635 Commands::Command2 => {
636 config.ai_smartness = SmartLevel::Elementary;
637 self.current_page = Page::FrontPage;
638 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
639 Ok(())
640 }
641 Commands::Command3 => {
642 config.ai_smartness = SmartLevel::Graduate;
643 self.current_page = Page::FrontPage;
644 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
645 Ok(())
646 }
647 Commands::Command4 => {
648 config.ai_smartness = SmartLevel::God;
649 self.current_page = Page::FrontPage;
650 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
651 Ok(())
652 }
653 Commands::Back => {
654 self.current_page = Page::FrontPage;
655 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
656 Ok(())
657 }
658 Commands::Quit => {
659 panic!("Should not reach here");
660 }
661 _ => Err("Does not support this command on this page"),
662 }
663 }
664 Page::ConfigEmptyPiece => {
665 let result = ask_for_user_string(EMPTY_PIECE_TABLE);
666 if result.is_ok() {
667 let user_string = result.unwrap();
668 config.empty_piece = user_string;
669 self.current_page = Page::FrontPage;
670 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
671 Ok(())
672 } else {
673 Err(result.unwrap_err())
674 }
675 }
676 Page::Playing => {
677 let mut coordinates: (usize, usize);
678 use super::ai;
679 if self.current_player == Players::Player1 && config.players.0.is_ai {
680 let coordinates =
681 ai::Ai::ask_ai_move_input(board, self.current_player, config.ai_smartness);
682 println!(
683 "{}'s input is ({}, {})",
684 config.players.0.name, coordinates.0, coordinates.1
685 );
686 board
687 .set_coordinate(coordinates.0, coordinates.1, self.current_player)
688 .unwrap();
689 } else if self.current_player == Players::Player2 && config.players.1.is_ai {
690 let coordinates =
691 ai::Ai::ask_ai_move_input(board, self.current_player, config.ai_smartness);
692 println!(
693 "{}'s input is ({}, {})",
694 config.players.1.name, coordinates.0, coordinates.1
695 );
696 board
697 .set_coordinate(coordinates.0, coordinates.1, self.current_player)
698 .unwrap();
699 } else {
700 loop {
701 let result = ask_user_move_input();
702 if result.is_ok() {
703 coordinates = result.unwrap();
704 let tmp_result = board.set_coordinate(
705 coordinates.0,
706 coordinates.1,
707 self.current_player,
708 );
709 if tmp_result.is_err() {
710 println!("{}", tmp_result.unwrap_err());
711 continue;
712 } else {
713 break;
714 }
715 } else {
716 let err = result.unwrap_err();
717 println!("{}", err);
718 if err == "Quit" {
719 std::process::exit(0);
720 }
721 continue;
722 }
723 }
724 }
725
726 board.update_matches();
727 let player = board.has_match_line();
728 if player.is_some() {
729 self.winner = player;
730 self.current_page = Page::Winner;
731 self.available_commands =
732 vec![Commands::Restart, Commands::FrontPage, Commands::Quit];
733 } else if board.is_full() {
734 self.winner = None;
735 self.current_page = Page::Winner;
736 self.available_commands =
737 vec![Commands::Restart, Commands::FrontPage, Commands::Quit];
738 } else {
739 self.change_player();
740 }
741 Ok(())
742 }
743 Page::Winner => match command {
744 Commands::Restart => {
745 self.current_page = Page::Playing;
746 self.available_commands = vec![];
747 board.clear();
748 Ok(())
749 }
750 Commands::FrontPage => {
751 self.current_page = Page::FrontPage;
752 self.available_commands = FRONT_PAGE_AVAILABLE_COMMANDS.to_vec();
753 board.clear();
754 Ok(())
755 }
756 Commands::Quit => {
757 panic!("Should not reach here");
758 }
759 _ => Err("Does not support this command on this page"),
760 },
761 }
762 }
763
764 fn print_available_commands(&self) {
765 use ansi_term::Colour::*;
766 println!("{}", Blue.paint("Available commands:"));
767 let mut iter = self.available_commands.iter();
768 print!("{}", iter.next().unwrap());
769 for c in iter {
770 print!(" {}", c);
771 }
772 println!();
773 }
774}
775
776#[cfg(test)]
777mod tests;