mirror of
				https://github.com/LukasKalbertodt/programmieren-in-rust.git
				synced 2025-10-31 08:40:41 +01:00 
			
		
		
		
	Add solution for sheet11
This commit is contained in:
		
							
								
								
									
										49
									
								
								aufgaben/sheet11/sol1/loop.rs
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										49
									
								
								aufgaben/sheet11/sol1/loop.rs
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | use std::cell::RefCell; | ||||||
|  | use std::rc::Rc; | ||||||
|  |  | ||||||
|  | /// A helper type to visualise drops on the terminal. Isn't it cute? :3 | ||||||
|  | struct HappyDrop; | ||||||
|  |  | ||||||
|  | impl Drop for HappyDrop { | ||||||
|  |     fn drop(&mut self) { | ||||||
|  |         println!("❤ I have been dropped! ╰( ◕ ᗜ ◕ )╯"); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// We need another wrapper type to avoid having infinitely nested types. This | ||||||
|  | /// is also useful to include the drop-indicator. | ||||||
|  | struct Node { | ||||||
|  |     /// Drop-indicator. This could also be sensitive data that requires | ||||||
|  |     /// its destructor to be executed (you shouldn't rely on that for safety | ||||||
|  |     /// purposes!). | ||||||
|  |     /// | ||||||
|  |     /// Silence warnings, because the `drop()` *is* used. | ||||||
|  |     #[allow(dead_code)] | ||||||
|  |     happy: HappyDrop, | ||||||
|  |     /// An optional shared reference to another node, which can mutated through | ||||||
|  |     /// an immutable reference. | ||||||
|  |     rc: RefCell<Option<Rc<Node>>>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn main() { | ||||||
|  |     // We create our first object, without a reference. | ||||||
|  |     let one = Rc::new(Node { | ||||||
|  |         happy: HappyDrop, | ||||||
|  |         rc: RefCell::new(None), | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // This is the second node which now also owns the first node. | ||||||
|  |     let two = Rc::new(Node { | ||||||
|  |         happy: HappyDrop, | ||||||
|  |         // `two` is owner of `one` now. Note that we `clone()` the Rc around | ||||||
|  |         // one to create a new owner, instead of moving the ownership from the | ||||||
|  |         // stack frame into two. After this, `two` and the `main` stackframe | ||||||
|  |         // are owner of `one`. | ||||||
|  |         rc: RefCell::new(Some(one.clone())), | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     // This next line closes the cycle. We can access `one` here, because our | ||||||
|  |     // stackframe is owner too! We use the RefCell to mutate the optional | ||||||
|  |     // reference to another node and make `one` owner of `two`. | ||||||
|  |     *one.rc.borrow_mut() = Some(two); | ||||||
|  | } | ||||||
							
								
								
									
										72
									
								
								aufgaben/sheet11/sol2/Cargo.lock
									
									
									
										generated
									
									
									
										Executable file
									
								
							
							
						
						
									
										72
									
								
								aufgaben/sheet11/sol2/Cargo.lock
									
									
									
										generated
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | [root] | ||||||
|  | name = "ttt" | ||||||
|  | version = "0.1.0" | ||||||
|  | dependencies = [ | ||||||
|  |  "boolinator 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "term-painter 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "boolinator" | ||||||
|  | version = "2.4.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "kernel32-sys" | ||||||
|  | version = "0.2.2" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "libc" | ||||||
|  | version = "0.2.19" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "rand" | ||||||
|  | version = "0.3.15" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "libc 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "term" | ||||||
|  | version = "0.4.4" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  |  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "term-painter" | ||||||
|  | version = "0.2.3" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | dependencies = [ | ||||||
|  |  "term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "winapi" | ||||||
|  | version = "0.2.8" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  |  | ||||||
|  | [[package]] | ||||||
|  | name = "winapi-build" | ||||||
|  | version = "0.1.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  |  | ||||||
|  | [metadata] | ||||||
|  | "checksum boolinator 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" | ||||||
|  | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" | ||||||
|  | "checksum libc 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)" = "9e030dc72013ed68994d1b2cbf36a94dd0e58418ba949c4b0db7eeb70a7a6352" | ||||||
|  | "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" | ||||||
|  | "checksum term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3deff8a2b3b6607d6d7cc32ac25c0b33709453ca9cceac006caac51e963cf94a" | ||||||
|  | "checksum term-painter 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ab900bf2f05175932b13d4fc12f8ff09ef777715b04998791ab2c930841e496b" | ||||||
|  | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" | ||||||
|  | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" | ||||||
							
								
								
									
										9
									
								
								aufgaben/sheet11/sol2/Cargo.toml
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								aufgaben/sheet11/sol2/Cargo.toml
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | [package] | ||||||
|  | name = "ttt" | ||||||
|  | version = "0.1.0" | ||||||
|  | authors = ["Lukas Kalbertodt <lukas.kalbertodt@gmail.com>"] | ||||||
|  |  | ||||||
|  | [dependencies] | ||||||
|  | rand = "0.3.15" | ||||||
|  | term-painter = "0.2.3" | ||||||
|  | boolinator = "2.4.0" | ||||||
							
								
								
									
										16472
									
								
								aufgaben/sheet11/sol2/out.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16472
									
								
								aufgaben/sheet11/sol2/out.txt
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										129
									
								
								aufgaben/sheet11/sol2/src/game/cell.rs
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										129
									
								
								aufgaben/sheet11/sol2/src/game/cell.rs
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,129 @@ | |||||||
|  | use std::fmt; | ||||||
|  | use std::str::FromStr; | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||||||
|  | pub enum Cell { | ||||||
|  |     Empty, | ||||||
|  |     Circle, | ||||||
|  |     Cross, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy, Eq, PartialEq)] | ||||||
|  | /// Represents the index of one cell on the board. | ||||||
|  | /// | ||||||
|  | /// They can be parsed from the string representation which looks like `"b1"`. | ||||||
|  | /// The letter represents the column, the digit represents the row. | ||||||
|  | pub struct CellId { | ||||||
|  |     /// This index is row-major. | ||||||
|  |     /// | ||||||
|  |     ///     0 1 2 | ||||||
|  |     ///     3 4 5 | ||||||
|  |     ///     6 7 8 | ||||||
|  |     index: u8, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl CellId { | ||||||
|  |     /// Creates a new ID of the cell in the `row` and `col`. | ||||||
|  |     /// | ||||||
|  |     /// Panics if `row` or `col` are invalid (≥ 3). | ||||||
|  |     pub fn new(row: u8, col: u8) -> Self { | ||||||
|  |         assert!(row < 3); | ||||||
|  |         assert!(col < 3); | ||||||
|  |  | ||||||
|  |         CellId { | ||||||
|  |             index: row * 3 + col, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn row(&self) -> u8 { | ||||||
|  |         self.index / 3 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn col(&self) -> u8 { | ||||||
|  |         self.index % 3 | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Returns a number that can be used to index an array. For every possible | ||||||
|  |     /// cell id, this function returns a different number, starting from 0, | ||||||
|  |     /// without gaps in between. | ||||||
|  |     pub fn index(&self) -> usize { | ||||||
|  |         self.index.into() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl fmt::Display for CellId { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         write!( | ||||||
|  |             f, | ||||||
|  |             "[{}{}]", | ||||||
|  |             (self.col() + 'a' as u8) as char, | ||||||
|  |             (self.row() + '1' as u8) as char, | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl fmt::Debug for CellId { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         write!( | ||||||
|  |             f, | ||||||
|  |             "CellId {{ {}, {} }}", | ||||||
|  |             self.index, | ||||||
|  |             self, | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // This might be overengineered here. A simpler solution would fit this simple | ||||||
|  | // game better, I think. | ||||||
|  | impl FromStr for CellId { | ||||||
|  |     type Err = ParseCellIdError; | ||||||
|  |     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||||
|  |         // Check if the string has the correct length and only ascii chars. | ||||||
|  |         if s.len() > 2 { | ||||||
|  |             return Err(ParseCellIdError::TooManyChars); | ||||||
|  |         } | ||||||
|  |         if s.len() < 2 { | ||||||
|  |             return Err(ParseCellIdError::TooFewChars); | ||||||
|  |         } | ||||||
|  |         if s.chars().count() != 2 { | ||||||
|  |             return Err(ParseCellIdError::NonAscii); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // We know the string contains two ASCII-bytes | ||||||
|  |         let s = s.to_lowercase().into_bytes(); | ||||||
|  |         if let 'a' ... 'c' = s[0] as char { | ||||||
|  |             if let '1' ... '3' = s[1] as char { | ||||||
|  |                 let col = s[0] as u8 - 'a' as u8; | ||||||
|  |                 let row = s[1] as u8 - '1' as u8; | ||||||
|  |                 Ok(CellId::new(row, col)) | ||||||
|  |             } else { | ||||||
|  |                 Err(ParseCellIdError::InvalidRow) | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             Err(ParseCellIdError::InvalidColumn) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy, Debug, Eq, PartialEq)] | ||||||
|  | pub enum ParseCellIdError { | ||||||
|  |     TooFewChars, | ||||||
|  |     TooManyChars, | ||||||
|  |     NonAscii, | ||||||
|  |     InvalidColumn, | ||||||
|  |     InvalidRow, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl fmt::Display for ParseCellIdError { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         use self::ParseCellIdError::*; | ||||||
|  |  | ||||||
|  |         match *self { | ||||||
|  |             TooFewChars   => "too few chars (2 required)", | ||||||
|  |             TooManyChars  => "too many chars (2 required)", | ||||||
|  |             NonAscii      => "both chars have to be ascii", | ||||||
|  |             InvalidColumn => "column is invalid (has to be 'a', 'b' or 'c')", | ||||||
|  |             InvalidRow    => "row is invalid (has to be '1', '2' or '3')", | ||||||
|  |         }.fmt(f) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										68
									
								
								aufgaben/sheet11/sol2/src/game/mod.rs
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										68
									
								
								aufgaben/sheet11/sol2/src/game/mod.rs
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,68 @@ | |||||||
|  | use term_painter::{Color, ToStyle}; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | mod state; | ||||||
|  | mod cell; | ||||||
|  |  | ||||||
|  | pub use self::cell::{Cell, CellId}; | ||||||
|  | pub use self::state::{GameState, Move}; | ||||||
|  | use player::{Player, Role as PlayerRole}; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | pub fn play<'a>(circle: &'a mut Player, cross: &'a mut Player) { | ||||||
|  |     use std::mem::swap; | ||||||
|  |  | ||||||
|  |     let mut state = GameState::new(); | ||||||
|  |     let mut active = (cross, PlayerRole::Cross); | ||||||
|  |     let mut passive = (circle, PlayerRole::Circle); | ||||||
|  |  | ||||||
|  |     while state.status().is_running() { | ||||||
|  |         let cell_id = match active.0.next_move(&state, active.1) { | ||||||
|  |             Some(m) => m.id(), | ||||||
|  |             None => { | ||||||
|  |                 println!( | ||||||
|  |                     "Player {} ({}) has given up! :(", | ||||||
|  |                     active.1, | ||||||
|  |                     active.0.player_kind() | ||||||
|  |                 ); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         state[cell_id] = active.1.marker(); | ||||||
|  |  | ||||||
|  |         println!( | ||||||
|  |             "Player {} ({}) marked cell {}:", | ||||||
|  |             active.1, | ||||||
|  |             active.0.player_kind(), | ||||||
|  |             Color::BrightWhite.bold().paint(cell_id), | ||||||
|  |         ); | ||||||
|  |         state.print(); | ||||||
|  |         println!(""); | ||||||
|  |  | ||||||
|  |         swap(&mut active, &mut passive); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if let Status::Won(winner) = state.status() { | ||||||
|  |         println!( | ||||||
|  |             "Player {} ({}) won! :)", | ||||||
|  |             winner, | ||||||
|  |             passive.0.player_kind(), | ||||||
|  |         ); | ||||||
|  |     } else { | ||||||
|  |         // Tie | ||||||
|  |         println!("The game tied :'("); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||||||
|  | pub enum Status { | ||||||
|  |     Running, | ||||||
|  |     Tied, | ||||||
|  |     Won(PlayerRole), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Status { | ||||||
|  |     pub fn is_running(&self) -> bool { | ||||||
|  |         *self == Status::Running | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										140
									
								
								aufgaben/sheet11/sol2/src/game/state.rs
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										140
									
								
								aufgaben/sheet11/sol2/src/game/state.rs
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,140 @@ | |||||||
|  | use std::ops::{Index, IndexMut}; | ||||||
|  | use std::str::FromStr; | ||||||
|  |  | ||||||
|  | use term_painter::{Color, ToStyle}; | ||||||
|  |  | ||||||
|  | use game::{Cell, CellId, Status}; | ||||||
|  | use player::Role as PlayerRole; | ||||||
|  |  | ||||||
|  | /// Describes the state of the game. | ||||||
|  | /// | ||||||
|  | /// *Note*: we don't derive/implement `Copy` although we could. This is one | ||||||
|  | /// example where not implementing `Copy` is useful: we don't want to | ||||||
|  | /// accidentally copy game state. Thus is shouldn't be easy to do so. | ||||||
|  | #[derive(Clone, Debug, Eq, PartialEq)] | ||||||
|  | pub struct GameState { | ||||||
|  |     board: [Cell; 9], | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl GameState { | ||||||
|  |     /// Creates an empty board. | ||||||
|  |     pub fn new() -> Self { | ||||||
|  |         GameState { | ||||||
|  |             board: [Cell::Empty; 9], | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Pretty prints the board on the terminal. We don't use the `Display` | ||||||
|  |     /// trait, because I'm using colors and terminal formatting. | ||||||
|  |     pub fn print(&self) { | ||||||
|  |         println!("    a   b   c  "); | ||||||
|  |         println!("  +---+---+---+"); | ||||||
|  |  | ||||||
|  |         for row in 0..3 { | ||||||
|  |             print!("{} ", row + 1); | ||||||
|  |             for col in 0..3 { | ||||||
|  |                 let cell = self[CellId::new(row, col)]; | ||||||
|  |                 let symb = match cell { | ||||||
|  |                     Cell::Empty => Color::NotSet.paint(" "), | ||||||
|  |                     Cell::Circle => { | ||||||
|  |                         PlayerRole::from_marker(cell) | ||||||
|  |                             .style() | ||||||
|  |                             .bold() | ||||||
|  |                             .paint("◯") | ||||||
|  |                     } | ||||||
|  |                     Cell::Cross => { | ||||||
|  |                         PlayerRole::from_marker(cell) | ||||||
|  |                             .style() | ||||||
|  |                             .bold() | ||||||
|  |                             .paint("✗") | ||||||
|  |                     } | ||||||
|  |                 }; | ||||||
|  |  | ||||||
|  |                 print!("| {} ", symb); | ||||||
|  |  | ||||||
|  |                 // Trailing pipe | ||||||
|  |                 if col == 2 { | ||||||
|  |                     print!("|"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             println!(""); | ||||||
|  |             println!("  +---+---+---+"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Verifies that a referenced cell is empty or returns None. This is | ||||||
|  |     /// the only way to change mutate this state. | ||||||
|  |     pub fn verify_move(&self, id: CellId) -> Option<Move> { | ||||||
|  |         match self[id] { | ||||||
|  |             Cell::Empty => Some(Move { id: id }), | ||||||
|  |             _ => None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Determines the status of the game (there is a winner, it has tied or | ||||||
|  |     /// it is still running) | ||||||
|  |     pub fn status(&self) -> Status { | ||||||
|  |         macro_rules! check { | ||||||
|  |             ($( [$a:expr, $b:expr, $c:expr] , )+) => { | ||||||
|  |                 $( | ||||||
|  |                     let a = self[CellId::from_str($a).unwrap()]; | ||||||
|  |                     let b = self[CellId::from_str($b).unwrap()]; | ||||||
|  |                     let c = self[CellId::from_str($c).unwrap()]; | ||||||
|  |                     if a != Cell::Empty && a == b && a == c { | ||||||
|  |                         return Status::Won(PlayerRole::from_marker(a)); | ||||||
|  |                     } | ||||||
|  |                 )* | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         check![ | ||||||
|  |             // rows | ||||||
|  |             ["a1", "a2", "a3"], | ||||||
|  |             ["b1", "b2", "b3"], | ||||||
|  |             ["c1", "c2", "c3"], | ||||||
|  |  | ||||||
|  |             // columns | ||||||
|  |             ["a1", "b1", "c1"], | ||||||
|  |             ["a2", "b2", "c2"], | ||||||
|  |             ["a3", "b3", "c3"], | ||||||
|  |  | ||||||
|  |             // diagonals | ||||||
|  |             ["a1", "b2", "c3"], | ||||||
|  |             ["a3", "b2", "c1"], | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         if self.board.iter().all(|&c| c != Cell::Empty) { | ||||||
|  |             return Status::Tied; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Status::Running | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Index<CellId> for GameState { | ||||||
|  |     type Output = Cell; | ||||||
|  |     fn index(&self, idx: CellId) -> &Self::Output { | ||||||
|  |         &self.board[idx.index()] | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl IndexMut<CellId> for GameState { | ||||||
|  |     fn index_mut(&mut self, idx: CellId) -> &mut Self::Output { | ||||||
|  |         assert_eq!(self.board[idx.index()], Cell::Empty); | ||||||
|  |         &mut self.board[idx.index()] | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Represents a valid move (referencing a cell which is empty). The only way | ||||||
|  | /// to modify the game state is by obtaining a `Move` and call | ||||||
|  | /// `set_cell()` on it. | ||||||
|  | #[derive(Debug, Eq, PartialEq)] | ||||||
|  | pub struct Move { | ||||||
|  |     id: CellId, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Move { | ||||||
|  |     pub fn id(&self) -> CellId { | ||||||
|  |         self.id | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										60
									
								
								aufgaben/sheet11/sol2/src/main.rs
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										60
									
								
								aufgaben/sheet11/sol2/src/main.rs
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | extern crate rand; | ||||||
|  | extern crate term_painter; | ||||||
|  | extern crate boolinator; | ||||||
|  |  | ||||||
|  | mod game; | ||||||
|  | mod player; | ||||||
|  |  | ||||||
|  | use player::Player; | ||||||
|  | use term_painter::{ToStyle, Color}; | ||||||
|  | use std::process; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | fn main() { | ||||||
|  |     // We do argument parsing manually... which might not be the best idea, | ||||||
|  |     // because it's annoying to extend it. It's probably the best to use `clap` | ||||||
|  |     // or something like that from the very beginning. | ||||||
|  |     let args: Vec<_> = std::env::args().skip(1).take(3).collect(); | ||||||
|  |     if args.len() != 2 { | ||||||
|  |         Color::Red.with(|| { | ||||||
|  |             println!("You have to specify the players as command line parameters!"); | ||||||
|  |             println!(""); | ||||||
|  |             println!("  ttt <cross-player> <circle-player>"); | ||||||
|  |             println!(""); | ||||||
|  |             println!("Player: one of 'human', 'random' or 'smart'."); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let mut cross = get_player(&args[0]).unwrap_or_else(|| process::exit(1)); | ||||||
|  |     let mut circle = get_player(&args[1]).unwrap_or_else(|| process::exit(1)); | ||||||
|  |  | ||||||
|  |     // Pass a mutable reference to the box contents into the main function | ||||||
|  |     game::play(&mut *circle, &mut *cross); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// This will return a trait object corresponding to the given player name | ||||||
|  | /// or `None` if the name is invalid. Because this function is returning a | ||||||
|  | /// trait object, we have to box it. | ||||||
|  | fn get_player(name: &str) -> Option<Box<Player>> { | ||||||
|  |     use player::human::HumanPlayer; | ||||||
|  |     use player::random::RandomPlayer; | ||||||
|  |     use player::smart::SmartPlayer; | ||||||
|  |  | ||||||
|  |     // Note that automatic conversions are used here: for example, the type | ||||||
|  |     // `Box<HumanPlayer>` is converted to `Box<Player>`. This is the | ||||||
|  |     // "type erasure" step. | ||||||
|  |     match name { | ||||||
|  |         "human" => Some(Box::new(HumanPlayer::new())), | ||||||
|  |         "random" => Some(Box::new(RandomPlayer::new())), | ||||||
|  |         "smart" => Some(Box::new(SmartPlayer::new())), | ||||||
|  |         invalid_name => { | ||||||
|  |             Color::Red.with(|| { | ||||||
|  |                 println!( | ||||||
|  |                     "'{}' is not a valid player! Use 'human', 'random' or 'smart'.", | ||||||
|  |                     invalid_name, | ||||||
|  |                 ); | ||||||
|  |             }); | ||||||
|  |             None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										50
									
								
								aufgaben/sheet11/sol2/src/player/human.rs
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										50
									
								
								aufgaben/sheet11/sol2/src/player/human.rs
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,50 @@ | |||||||
|  | use std::io::{self, Write}; | ||||||
|  |  | ||||||
|  | use term_painter::{Color, ToStyle}; | ||||||
|  |  | ||||||
|  | use game::{CellId, GameState, Move}; | ||||||
|  | use super::{Player, Role}; | ||||||
|  |  | ||||||
|  | pub struct HumanPlayer; | ||||||
|  |  | ||||||
|  | impl Player for HumanPlayer { | ||||||
|  |     fn new() -> Self { | ||||||
|  |         HumanPlayer | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn player_kind(&self) -> &'static str { | ||||||
|  |         "human" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn next_move<'s>(&mut self, state: &'s GameState, _role: Role) | ||||||
|  |         -> Option<Move> | ||||||
|  |     { | ||||||
|  |         loop { | ||||||
|  |             print!("Put mark on: "); | ||||||
|  |             io::stdout().flush().expect("failed flushing stdout"); | ||||||
|  |  | ||||||
|  |             let mut s = String::new(); | ||||||
|  |             io::stdin() | ||||||
|  |                 .read_line(&mut s) | ||||||
|  |                 .expect("failed reading from stdin"); | ||||||
|  |  | ||||||
|  |             let id: CellId = match s.trim().parse() { | ||||||
|  |                 Ok(id) => id, | ||||||
|  |                 Err(e) => { | ||||||
|  |                     Color::Red.with(|| println!("invalid input: {}", e)); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             match state.verify_move(id) { | ||||||
|  |                 Some(valid_move) => return Some(valid_move), | ||||||
|  |                 None => { | ||||||
|  |                     Color::Red.with(|| { | ||||||
|  |                         println!("invalid input: field {} is already occupied", id); | ||||||
|  |                     }); | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										76
									
								
								aufgaben/sheet11/sol2/src/player/mod.rs
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										76
									
								
								aufgaben/sheet11/sol2/src/player/mod.rs
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | use std::fmt; | ||||||
|  |  | ||||||
|  | use term_painter::{Color, Style, ToStyle}; | ||||||
|  |  | ||||||
|  | use game::{Cell, GameState, Move}; | ||||||
|  |  | ||||||
|  | pub mod random; | ||||||
|  | pub mod human; | ||||||
|  | pub mod smart; | ||||||
|  |  | ||||||
|  | /// A player that may be able to play tic tac toe. | ||||||
|  | pub trait Player { | ||||||
|  |     /// Returns a new player. This is useful to have in this trait, but we | ||||||
|  |     /// can't use it as virtual method on a trait object. Thus we manually | ||||||
|  |     /// exclude it from the set of virtual methods by requiring `Self: Sized`. | ||||||
|  |     fn new() -> Self where Self: Sized; | ||||||
|  |  | ||||||
|  |     /// Returns the kind of player in human readable form, e.g. | ||||||
|  |     /// "random player". | ||||||
|  |     fn player_kind(&self) -> &'static str; | ||||||
|  |  | ||||||
|  |     /// Returns the player's next move or `None` if the player gives up. | ||||||
|  |     /// | ||||||
|  |     /// Note that a valid move has to be returned. The only way to obtain an | ||||||
|  |     /// instance of `Move` is to use the `GameState::verify_move()`. | ||||||
|  |     fn next_move<'s>(&mut self, state: &'s GameState, role: Role) | ||||||
|  |         -> Option<Move>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #[derive(Clone, Copy, Debug, PartialEq, Eq)] | ||||||
|  | pub enum Role { | ||||||
|  |     Circle, | ||||||
|  |     Cross, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Role { | ||||||
|  |     pub fn marker(&self) -> Cell { | ||||||
|  |         match *self { | ||||||
|  |             Role::Circle => Cell::Circle, | ||||||
|  |             Role::Cross => Cell::Cross, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn enemy(&self) -> Role { | ||||||
|  |         match *self { | ||||||
|  |             Role::Circle => Role::Cross, | ||||||
|  |             Role::Cross => Role::Circle, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn from_marker(c: Cell) -> Self { | ||||||
|  |         match c { | ||||||
|  |             Cell::Circle => Role::Circle, | ||||||
|  |             Cell::Cross => Role::Cross, | ||||||
|  |             _ => panic!("empty cell cannot be converted into role"), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn style(&self) -> Style { | ||||||
|  |         match *self { | ||||||
|  |             Role::Circle => Color::BrightGreen.to_style(), | ||||||
|  |             Role::Cross => Color::BrightBlue.to_style(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl fmt::Display for Role { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         let s = match *self { | ||||||
|  |             Role::Circle => "circle", | ||||||
|  |             Role::Cross => "cross", | ||||||
|  |         }; | ||||||
|  |         self.style().paint(s).fmt(f) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								aufgaben/sheet11/sol2/src/player/random.rs
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										34
									
								
								aufgaben/sheet11/sol2/src/player/random.rs
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | use rand::{thread_rng, Rng}; | ||||||
|  |  | ||||||
|  | use game::{CellId, GameState, Move}; | ||||||
|  | use super::{Player, Role}; | ||||||
|  |  | ||||||
|  | pub struct RandomPlayer; | ||||||
|  |  | ||||||
|  | impl Player for RandomPlayer { | ||||||
|  |     fn new() -> Self { | ||||||
|  |         RandomPlayer | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn player_kind(&self) -> &'static str { | ||||||
|  |         "random player" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn next_move<'s>(&mut self, state: &'s GameState, _role: Role) | ||||||
|  |         -> Option<Move> | ||||||
|  |     { | ||||||
|  |         // If we haven't found a valid move after 1000 attempts, we give up | ||||||
|  |         for _ in 0..1000 { | ||||||
|  |             let mut rng = thread_rng(); | ||||||
|  |             let row = rng.gen_range(0, 3); | ||||||
|  |             let col = rng.gen_range(0, 3); | ||||||
|  |             let id = CellId::new(row, col); | ||||||
|  |  | ||||||
|  |             if let Some(valid_move) = state.verify_move(id) { | ||||||
|  |                 return Some(valid_move); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         None | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										83
									
								
								aufgaben/sheet11/sol2/src/player/smart.rs
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										83
									
								
								aufgaben/sheet11/sol2/src/player/smart.rs
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,83 @@ | |||||||
|  | use game::{CellId, GameState, Move, Status}; | ||||||
|  | use super::{Player, Role}; | ||||||
|  | use super::random::RandomPlayer; | ||||||
|  | use boolinator::Boolinator; | ||||||
|  |  | ||||||
|  | pub struct SmartPlayer(RandomPlayer); | ||||||
|  |  | ||||||
|  | impl Player for SmartPlayer { | ||||||
|  |     fn new() -> Self { | ||||||
|  |         SmartPlayer(RandomPlayer) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn player_kind(&self) -> &'static str { | ||||||
|  |         "smart player" | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn next_move<'s>(&mut self, state: &GameState, role: Role) | ||||||
|  |         -> Option<Move> | ||||||
|  |     { | ||||||
|  |  | ||||||
|  |         // Just a few useful things for later... | ||||||
|  |         let valid_moves = || (0..9) | ||||||
|  |             .map(|i| CellId::new(i / 3, i % 3)) | ||||||
|  |             .filter_map(move |id| state.verify_move(id)); | ||||||
|  |  | ||||||
|  |         let ca = CellId::new(0, 0); | ||||||
|  |         let cb = CellId::new(0, 2); | ||||||
|  |         let cc = CellId::new(2, 0); | ||||||
|  |         let cd = CellId::new(2, 2); | ||||||
|  |  | ||||||
|  |         // We use a few simple tactics here. The rules below are taken from | ||||||
|  |         // Wikipedia [1] (note: not all are implemented). This is not a perfect | ||||||
|  |         // player! You can beat it. | ||||||
|  |         // | ||||||
|  |         // [1]: https://en.wikipedia.org/wiki/Tic-tac-toe#Strategy | ||||||
|  |         None.or( | ||||||
|  |             // Rule 1: If we can win with the next move, we use that move. | ||||||
|  |             valid_moves().find(|valid_move| { | ||||||
|  |                 let mut new_state = state.clone(); | ||||||
|  |                 new_state[valid_move.id()] = role.marker(); | ||||||
|  |                 new_state.status() == Status::Won(role) | ||||||
|  |             }) | ||||||
|  |  | ||||||
|  |         ).or( | ||||||
|  |             // Rule 2: If the enemy could win with their next move, we | ||||||
|  |             // try to avoid that by marking that field | ||||||
|  |             valid_moves().find(|valid_move| { | ||||||
|  |                 let mut new_state = state.clone(); | ||||||
|  |                 new_state[valid_move.id()] = role.enemy().marker(); | ||||||
|  |                 new_state.status() == Status::Won(role.enemy()) | ||||||
|  |             }) | ||||||
|  |         ).or( | ||||||
|  |             // Rule 5: If the field is empty, we choose a corner cell, | ||||||
|  |             // otherwise we will choose the center. | ||||||
|  |             if valid_moves().count() == 9 { | ||||||
|  |                 state.verify_move(CellId::new(0, 0)) | ||||||
|  |             } else { | ||||||
|  |                 state.verify_move(CellId::new(1, 1)) | ||||||
|  |             } | ||||||
|  |         ).or_else(|| { | ||||||
|  |             // Rule 6: If the enemy has a marker in one corner and the | ||||||
|  |             // opposite corner is free, put a marker there. | ||||||
|  |             let enemy = role.enemy().marker(); | ||||||
|  |  | ||||||
|  |             None | ||||||
|  |                 .or((state[ca] == enemy).and_option(state.verify_move(cd))) | ||||||
|  |                 .or((state[cd] == enemy).and_option(state.verify_move(ca))) | ||||||
|  |                 .or((state[cb] == enemy).and_option(state.verify_move(cc))) | ||||||
|  |                 .or((state[cc] == enemy).and_option(state.verify_move(cb))) | ||||||
|  |         }).or( | ||||||
|  |             // Rule 7: If there is an empty corner, use it | ||||||
|  |             None | ||||||
|  |                 .or(state.verify_move(ca)) | ||||||
|  |                 .or(state.verify_move(cb)) | ||||||
|  |                 .or(state.verify_move(cc)) | ||||||
|  |                 .or(state.verify_move(cd)) | ||||||
|  |         ).or( | ||||||
|  |             // If none of those rules results in a valid move, we just choose | ||||||
|  |             // something at random. | ||||||
|  |             self.0.next_move(state, role) | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user