diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..0cc36ab849c2913a2b09e23419bdcb8627fa2693 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,371 @@ +extern crate rust_hepia_lib; + +const WIDTH: usize = 7; +const HEIGHT: usize = 6; +const RED_CHAR: char = 'X'; +const YELLOW_CHAR: char = 'O'; + +#[derive(Copy, Clone, PartialEq)] +enum Connect4Case { + Empty, + Red, + Yellow, +} + +pub struct Connect4 { + board: [[Connect4Case; WIDTH]; HEIGHT], + game_mode: u8, + turn_count: u32, + last_col_played: usize, + width: usize, + height: usize, +} + +impl Connect4 { + pub fn new() -> Connect4 { + if WIDTH < 4 || HEIGHT < 4 { + panic!("The connect 4 board is too short (WIDTH and HEIGHT must be at least 4)"); + } + Connect4 { + board: [[Connect4Case::Empty; WIDTH]; HEIGHT], + game_mode: 0, + turn_count: 0, + last_col_played: 0, + width: WIDTH, + height: HEIGHT, + } + } + + fn get_max_turn(&self) -> u32 { + (self.width as u32 * self.height as u32) + } + + fn is_red_turn(&self) -> bool { + if self.turn_count % 2 == 1 { + return true; + } else { + return false; + } + } + + fn choose_game_mode() -> u8 { + let mut x: i32; + loop { + println!("Choose your game mode : (1) Player vs Player. (2) Player vs IA, easy. (3) Player vs IA, hard."); + x = rust_hepia_lib::read_int(); + match x { + 1 | 2 | 3 => return x as u8, + _ => println!("This is not a valid number"), + } + } + } + + fn place_piece(&mut self, x: usize) -> bool { + if x >= self.width { + // si on veut placer une piece en dehors du tableau + return false; + } + if self.board[0 as usize][x] != Connect4Case::Empty { + // si la colonne est deja pleine + return false; + } + for i in 1..self.height { + if self.board[i][x] != Connect4Case::Empty { + // on place la nouelle piece au dessus de la plus haute piece de la colonne + self.board[i - 1][x] = if self.is_red_turn() { + Connect4Case::Red + } else { + Connect4Case::Yellow + }; + return true; + } + } + self.board[self.height - 1][x] = if self.is_red_turn() { + // si la colonne est vide + Connect4Case::Red + } else { + Connect4Case::Yellow + }; + return true; + } + + fn choose_col(&self) -> usize { + let mut col: i32; + + loop { + println!("Choose a column (1-{})", self.width); + col = rust_hepia_lib::read_int(); + if col < 1 || col > self.width as i32 { + println!("This is not a valid column"); + } else { + return (col - 1) as usize; + } + } + } + + fn play_turn(&mut self) { + self.turn_count += 1; + println!( + "This is {}'s turn !!", + if self.is_red_turn() { RED_CHAR } else { YELLOW_CHAR } + ); + if self.is_red_turn() || self.game_mode == 1 { + let mut col: usize; + loop { + col = self.choose_col(); + if !self.place_piece(col) { + println!("This is not a valid column"); + } else { + self.last_col_played = col; + break; + } + } + } else if self.game_mode == 2 { + let x: usize = self.get_ia1_play(); + self.place_piece(x); + self.last_col_played = x; + } else if self.game_mode == 3 { + let x: usize = self.get_ia2_play(); + self.place_piece(x); + self.last_col_played = x; + } + } + + fn get_ia1_play(&self) -> usize { + let mut col: usize; + let mut last_emp: Option<usize> = None; + loop { + col = rust_hepia_lib::gen(0, self.width as i32) as usize; + last_emp = self.get_last_empty(col); + if last_emp.is_some() { + return col; + } + } + } + + fn get_ia2_play(&self) -> usize { + let mut x: Option<usize> = None; + let mut block_col: usize; + let mut last_emp: Option<usize> = None; + + for i in 0..self.width { + last_emp = self.get_last_empty(i); + if last_emp.is_some() { + let y = last_emp.unwrap(); + for j in 0..4 { + if self.cnt_same_piece_line(i, y, Connect4Case::Yellow, j) >= 4 { + return i; + } else if self.cnt_same_piece_line(i, y, Connect4Case::Red, j) >= 4 { + x = Some(i); + block_col = i; + } + } + } + } + if let Some(block_col) = x { // c'est dans une option car il ne pourrait ne pas y avoir de case a bloqué + return block_col; + } else { + return self.get_ia1_play(); + } + } + + fn get_last_empty(&self, col: usize) -> Option<usize> { + if self.board[self.height - 1][col] == Connect4Case::Empty { return Some(self.height - 1); } + if self.board[0][col] != Connect4Case::Empty { return None; } + for i in 1..self.height { + if self.board[i][col] != Connect4Case::Empty { return Some(i - 1); } + } + None + } + + fn display(&self) { + for y in 0..self.height { + if y == 0 { + print!("┌─"); + for _x in 1..self.width { + print!("┬─"); + } + println!("┐"); + } else { + print!("├─"); + for _x in 1..self.width { + print!("┼─"); + } + println!("┤"); + } + for x in 0..self.width { + print!( + "|{}", + match self.board[y][x] { + Connect4Case::Empty => ' ', + Connect4Case::Red => RED_CHAR, + Connect4Case::Yellow => YELLOW_CHAR, + } + ); + if x + 1 == self.width { + print!("|"); + } + } + println!(); + } + print!("└─"); + for _x in 1..self.width { + print!("┴─"); + } + println!("┘"); + for x in 1..self.width + 1 { + print!(" {0}", x); + } + println!(); + } + + /*fn is_won(&self) -> bool { + for y in 0..self.height { + for x in 0..self.width { + if self.board[y][x] != Connect4Case::Empty { + if x + 3 < WIDTH { + // verif horizontale + if self.board[y][x + 1] == self.board[y][x] + && self.board[y][x + 2] == self.board[y][x] + && self.board[y][x + 3] == self.board[y][x] + { + return true; + } + } + if y + 3 < HEIGHT { + // verif verticale + if self.board[y + 1][x] == self.board[y][x] + && self.board[y + 2][x] == self.board[y][x] + && self.board[y + 3][x] == self.board[y][x] + { + return true; + } + } + if x + 3 < WIDTH && y + 3 < HEIGHT { + // verif diagonale 1 : \ + if self.board[y + 1][x + 1] == self.board[y][x] + && self.board[y + 2][x + 2] == self.board[y][x] + && self.board[y + 3][x + 3] == self.board[y][x] + { + return true; + } + } + if x >= 3 && y + 3 < HEIGHT { + // verif diagonale 2 : / + if self.board[y + 1][x - 1] == self.board[y][x] + && self.board[y + 2][x - 2] == self.board[y][x] + && self.board[y + 3][x - 3] == self.board[y][x] + { + return true; + } + } + } + } + } + return false; + }*/ + + fn is_full(&self) -> bool { + if self.turn_count >= self.get_max_turn() { + true + } else { + false + } + } + + fn last_play_won(&self) -> bool { + let mut i: usize = 0; + + loop { + if self.board[i][self.last_col_played as usize] == Connect4Case::Empty { + i += 1; + } else { + break; + } + } + for j in 0..4 { + if self.cnt_same_piece_line( + self.last_col_played as usize, + i, + if self.is_red_turn() { + Connect4Case::Red + } else { + Connect4Case::Yellow + }, + j, + ) >= 4 + { + return true; + } + } + false + } + + fn cnt_same_piece_line(&self, x: usize, y: usize, c: Connect4Case, dir: u8) -> u32 { + let i: u32; + i = self.cnt_same_piece_dir(x, y, c, dir) + + self.cnt_same_piece_dir(x, y, c, (dir + 4) % 8) + + 1; + i + } + + fn cnt_same_piece_dir(&self, x: usize, y: usize, c: Connect4Case, dir: u8) -> u32 { + match dir { + 0 => if y != 0 && self.board[y - 1][x] == c { + //north + return self.cnt_same_piece_dir(x, y - 1, c, dir) + 1; + }, + 1 => if x != self.width - 1 && y != 0 && self.board[y - 1][x + 1] == c { + //north-west + return self.cnt_same_piece_dir(x + 1, y - 1, c, dir) + 1; + }, + 2 => if x != self.width - 1 && self.board[y][x + 1] == c { + //west + return self.cnt_same_piece_dir(x + 1, y, c, dir) + 1; + }, + 3 => if x != self.width - 1 && y != self.height - 1 && self.board[y + 1][x + 1] == c { + //south-west + return self.cnt_same_piece_dir(x + 1, y + 1, c, dir) + 1; + }, + 4 => if y < self.height - 1 && self.board[y + 1][x] == c { + //south + return self.cnt_same_piece_dir(x, y + 1, c, dir) + 1; + }, + 5 => if x != 0 && y != self.height - 1 && self.board[y + 1][x - 1] == c { + //south-east + return self.cnt_same_piece_dir(x - 1, y + 1, c, dir) + 1; + }, + 6 => if x != 0 && self.board[y][x - 1] == c { + //east + return self.cnt_same_piece_dir(x - 1, y, c, dir) + 1; + }, + 7 => if x != 0 && y != 0 && self.board[y - 1][x - 1] == c { + //north-east + return self.cnt_same_piece_dir(x - 1, y - 1, c, dir) + 1; + }, + _ => panic!{"Direction not valid"}, + } + return 0; + } + + pub fn play_game(&mut self) { + self.game_mode = Connect4::choose_game_mode(); + self.display(); + loop { + self.play_turn(); + self.display(); + if self.last_play_won() { + if self.is_red_turn() { + println!("O won GG !"); + } else { + println!("X won, well played !"); + } + break; + } + if self.is_full() { + println!("Draw, nobody won !"); + break; + } + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index cd7c2fb626c599efe673ef248e02ae98ac822889..75de5ab2d1dcca3f6f2db5b1668544e8fb7e7b4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,374 +1,6 @@ -extern crate rust_hepia_lib; +extern crate tp5_connect4; -const WIDTH: usize = 7; -const HEIGHT: usize = 6; -const RED_CHAR: char = 'X'; -const YELLOW_CHAR: char = 'O'; - -#[derive(Copy, Clone, PartialEq)] -enum Connect4Case { - Empty, - Red, - Yellow, -} - -struct Connect4 { - board: [[Connect4Case; WIDTH]; HEIGHT], - game_mode: u8, - turn_count: u32, - last_col_played: usize, - width: usize, - height: usize, -} - -impl Connect4 { - fn new() -> Connect4 { - if WIDTH < 4 || HEIGHT < 4 { - panic!("The connect 4 board is too short (WIDTH and HEIGHT must be at least 4)"); - } - Connect4 { - board: [[Connect4Case::Empty; WIDTH]; HEIGHT], - game_mode: 0, - turn_count: 0, - last_col_played: 0, - width: WIDTH, - height: HEIGHT, - } - } - - fn get_max_turn(&self) -> u32 { - (self.width as u32 * self.height as u32) - } - - fn is_red_turn(&self) -> bool { - if self.turn_count % 2 == 1 { - return true; - } else { - return false; - } - } - - fn choose_game_mode() -> u8 { - let mut x: i32; - loop { - println!("Choose your game mode : (1) Player vs Player. (2) Player vs IA, easy. (3) Player vs IA, hard."); - x = rust_hepia_lib::read_int(); - match x { - 1 | 2 | 3 => return x as u8, - _ => println!("This is not a valid number"), - } - } - } - - fn place_piece(&mut self, x: usize) -> bool { - if x >= self.width { - // si on veut placer une piece en dehors du tableau - return false; - } - if self.board[0 as usize][x] != Connect4Case::Empty { - // si la colonne est deja pleine - return false; - } - for i in 1..self.height { - if self.board[i][x] != Connect4Case::Empty { - // on place la nouelle piece au dessus de la plus haute piece de la colonne - self.board[i - 1][x] = if self.is_red_turn() { - Connect4Case::Red - } else { - Connect4Case::Yellow - }; - return true; - } - } - self.board[self.height - 1][x] = if self.is_red_turn() { - // si la colonne est vide - Connect4Case::Red - } else { - Connect4Case::Yellow - }; - return true; - } - - fn choose_col(&self) -> usize { - let mut col: i32; - - loop { - println!("Choose a column (1-{})", self.width); - col = rust_hepia_lib::read_int(); - if col < 1 || col > self.width as i32 { - println!("This is not a valid column"); - } else { - return (col - 1) as usize; - } - } - } - - fn play_turn(&mut self) { - self.turn_count += 1; - println!( - "This is {}'s turn !!", - if self.is_red_turn() { RED_CHAR } else { YELLOW_CHAR } - ); - if self.is_red_turn() || self.game_mode == 1 { - let mut col: usize; - loop { - col = self.choose_col(); - if !self.place_piece(col) { - println!("This is not a valid column"); - } else { - self.last_col_played = col; - break; - } - } - } else if self.game_mode == 2 { - let x: usize = self.get_ia1_play(); - self.place_piece(x); - self.last_col_played = x; - } else if self.game_mode == 3 { - let x: usize = self.get_ia2_play(); - self.place_piece(x); - self.last_col_played = x; - } - } - - fn get_ia1_play(&self) -> usize { - let mut col: usize; - let mut last_emp: Option<usize> = None; - loop { - col = rust_hepia_lib::gen(0, self.width as i32) as usize; - last_emp = self.get_last_empty(col); - if last_emp.is_some() { - return col; - } - } - } - - fn get_ia2_play(&self) -> usize { - let mut x: Option<usize> = None; - let mut block_col: usize; - let mut last_emp: Option<usize> = None; - - for i in 0..self.width { - last_emp = self.get_last_empty(i); - if last_emp.is_some() { - let y = last_emp.unwrap(); - for j in 0..4 { - if self.cnt_same_piece_line(i, y, Connect4Case::Yellow, j) >= 4 { - return i; - } else if self.cnt_same_piece_line(i, y, Connect4Case::Red, j) >= 4 { - x = Some(i); - block_col = i; - } - } - } - } - if let Some(block_col) = x { - return block_col; - } else { - return self.get_ia1_play(); - } - } - - fn get_last_empty(&self, col: usize) -> Option<usize> { - if self.board[self.height - 1][col] == Connect4Case::Empty { return Some(self.height - 1); } - if self.board[0][col] != Connect4Case::Empty { return None; } - for i in 1..self.height { - if self.board[i][col] != Connect4Case::Empty { return Some(i - 1); } - } - None - } - - fn display(&self) { - for y in 0..self.height { - if y == 0 { - print!("┌─"); - for _x in 1..self.width { - print!("┬─"); - } - println!("┐"); - } else { - print!("├─"); - for _x in 1..self.width { - print!("┼─"); - } - println!("┤"); - } - for x in 0..self.width { - print!( - "|{}", - match self.board[y][x] { - Connect4Case::Empty => ' ', - Connect4Case::Red => RED_CHAR, - Connect4Case::Yellow => YELLOW_CHAR, - } - ); - if x + 1 == self.width { - print!("|"); - } - } - println!(); - } - print!("└─"); - for _x in 1..self.width { - print!("┴─"); - } - println!("┘"); - for x in 1..self.width + 1 { - print!(" {0}", x); - } - println!(); - } - - /*fn is_won(&self) -> bool { - for y in 0..self.height { - for x in 0..self.width { - if self.board[y][x] != Connect4Case::Empty { - if x + 3 < WIDTH { - // verif horizontale - if self.board[y][x + 1] == self.board[y][x] - && self.board[y][x + 2] == self.board[y][x] - && self.board[y][x + 3] == self.board[y][x] - { - return true; - } - } - if y + 3 < HEIGHT { - // verif verticale - if self.board[y + 1][x] == self.board[y][x] - && self.board[y + 2][x] == self.board[y][x] - && self.board[y + 3][x] == self.board[y][x] - { - return true; - } - } - if x + 3 < WIDTH && y + 3 < HEIGHT { - // verif diagonale 1 : \ - if self.board[y + 1][x + 1] == self.board[y][x] - && self.board[y + 2][x + 2] == self.board[y][x] - && self.board[y + 3][x + 3] == self.board[y][x] - { - return true; - } - } - if x >= 3 && y + 3 < HEIGHT { - // verif diagonale 2 : / - if self.board[y + 1][x - 1] == self.board[y][x] - && self.board[y + 2][x - 2] == self.board[y][x] - && self.board[y + 3][x - 3] == self.board[y][x] - { - return true; - } - } - } - } - } - return false; - }*/ - - fn is_full(&self) -> bool { - if self.turn_count >= self.get_max_turn() { - true - } else { - false - } - } - - fn last_play_won(&self) -> bool { - let mut i: usize = 0; - - loop { - if self.board[i][self.last_col_played as usize] == Connect4Case::Empty { - i += 1; - } else { - break; - } - } - for j in 0..4 { - if self.cnt_same_piece_line( - self.last_col_played as usize, - i, - if self.is_red_turn() { - Connect4Case::Red - } else { - Connect4Case::Yellow - }, - j, - ) >= 4 - { - return true; - } - } - false - } - - fn cnt_same_piece_line(&self, x: usize, y: usize, c: Connect4Case, dir: u8) -> u32 { - let i: u32; - i = self.cnt_same_piece_dir(x, y, c, dir) - + self.cnt_same_piece_dir(x, y, c, (dir + 4) % 8) - + 1; - i - } - - fn cnt_same_piece_dir(&self, x: usize, y: usize, c: Connect4Case, dir: u8) -> u32 { - match dir { - 0 => if y != 0 && self.board[y - 1][x] == c { - //north - return self.cnt_same_piece_dir(x, y - 1, c, dir) + 1; - }, - 1 => if x != self.width - 1 && y != 0 && self.board[y - 1][x + 1] == c { - //north-west - return self.cnt_same_piece_dir(x + 1, y - 1, c, dir) + 1; - }, - 2 => if x != self.width - 1 && self.board[y][x + 1] == c { - //west - return self.cnt_same_piece_dir(x + 1, y, c, dir) + 1; - }, - 3 => if x != self.width - 1 && y != self.height - 1 && self.board[y + 1][x + 1] == c { - //south-west - return self.cnt_same_piece_dir(x + 1, y + 1, c, dir) + 1; - }, - 4 => if y < self.height - 1 && self.board[y + 1][x] == c { - //south - return self.cnt_same_piece_dir(x, y + 1, c, dir) + 1; - }, - 5 => if x != 0 && y != self.height - 1 && self.board[y + 1][x - 1] == c { - //south-east - return self.cnt_same_piece_dir(x - 1, y + 1, c, dir) + 1; - }, - 6 => if x != 0 && self.board[y][x - 1] == c { - //east - return self.cnt_same_piece_dir(x - 1, y, c, dir) + 1; - }, - 7 => if x != 0 && y != 0 && self.board[y - 1][x - 1] == c { - //north-east - return self.cnt_same_piece_dir(x - 1, y - 1, c, dir) + 1; - }, - _ => panic!{"Direction not valid"}, - } - return 0; - } - - fn play_game(&mut self) { - self.game_mode = Connect4::choose_game_mode(); - self.display(); - loop { - self.play_turn(); - self.display(); - if self.last_play_won() { - if self.is_red_turn() { - println!("O won GG !"); - } else { - println!("X won, well played !"); - } - break; - } - if self.is_full() { - println!("Draw, nobody won !"); - break; - } - } - } -} +use tp5_connect4::Connect4; fn main() { let mut c: Connect4 = Connect4::new();