-
-
Save jimblandy/4f389c7736d7bd52db75 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::str::FromStr; | |
use std::fmt::Debug; | |
use std::io::stdin; | |
use std::io::stdout; | |
use std::io::Write; | |
trait Game : Clone { | |
type Move : Copy + FromStr; | |
fn start() -> Self; | |
fn moves(&self) -> Vec<Self::Move>; | |
fn apply_move(&self, Self::Move) -> Self; | |
fn score_finished_game(&self) -> f64; // returns >0 if last move won the game. | |
} | |
// === How to play a game, if you're a computer | |
fn max<T, I>(mut iter: I) -> T | |
where I: Iterator<Item=T>, T: PartialOrd | |
{ | |
let mut champion = iter.next().expect("max: empty iterator"); | |
for challenger in iter { | |
if challenger > champion { | |
champion = challenger; | |
} | |
} | |
champion | |
} | |
fn max_by<T, I, F, M>(mut iter: I, score: F) -> T | |
where | |
I: Iterator<Item=T>, | |
F: Fn(&T) -> M, | |
M: PartialOrd | |
{ | |
let mut hi_value = iter.next().expect("max_by: empty iterator"); | |
let mut hi_score = score(&hi_value); | |
for v in iter { | |
let s = score(&v); | |
if s > hi_score { | |
hi_score = s; | |
hi_value = v; | |
} | |
} | |
hi_value | |
} | |
fn best_move<G: Game>(game: &G) -> G::Move { | |
*max_by(game.moves().iter(), |m| score_move(game, **m)) | |
} | |
fn score_move<G: Game>(game: &G, m: G::Move) -> f64 { | |
score_game(&game.apply_move(m)) | |
} | |
fn score_game<G: Game>(game: &G) -> f64 { | |
let moves = game.moves(); | |
if moves.len() == 0 { | |
game.score_finished_game() | |
} else { | |
-max(moves.iter().map(|m| score_move(game, *m))) | |
} | |
} | |
// === A layer of paint | |
fn input_one_of<T>(options: &Vec<T>, prompt: &str) -> std::io::Result<Option<T>> | |
where T: Copy + FromStr + PartialEq + Debug | |
{ | |
loop { | |
try!(stdout().write(prompt.as_bytes())); | |
try!(stdout().flush()); | |
let mut line_buf = String::new(); | |
try!(stdin().read_line(&mut line_buf)); | |
if line_buf.len() == 0 { | |
return Ok(None); | |
} | |
let line_str : &str = line_buf.as_ref(); | |
let line = line_str.trim(); | |
if line == "q" { | |
return Ok(None); | |
} | |
if line == "?" { | |
println!("options: {:?}", options); | |
continue; | |
} | |
match T::from_str(&line) { | |
Err(_) => { | |
println!("i didn't understand that"); | |
continue; | |
} | |
Ok(m) => { | |
if options.contains(&m) { | |
return Ok(Some(m)); | |
} else { | |
println!("{:?} is not an option (enter ? to show all options)", m); | |
} | |
} | |
} | |
} | |
} | |
fn play_human_vs_computer<G: Game + Debug, F>(select_move: F, start: G) | |
where | |
G::Move: PartialEq + Debug + FromStr, | |
F: Fn(&G) -> G::Move | |
{ | |
println!("{:?}", start); | |
let mut board = start; | |
loop { | |
let options = board.moves(); | |
if options.len() == 0 { | |
println!("game over"); | |
let s = board.score_finished_game(); | |
println!("{}", | |
if s == 0.0 { | |
"it's a tie" | |
} else if s > 0.0 { | |
"i win" | |
} else { | |
"you win" | |
}); | |
return; | |
} | |
let your_move = match input_one_of(&options, "your turn> ").unwrap() { | |
None => { | |
return; // user typed "q" to quit | |
}, | |
Some(m) => m | |
}; | |
board = board.apply_move(your_move); | |
println!("{:?}", board); | |
let move_vec = board.moves(); | |
if move_vec.len() == 0 { | |
println!("game over"); | |
let s = board.score_finished_game(); | |
println!("{}", | |
if s == 0.0 { | |
"it's a tie" | |
} else if s > 0.0 { | |
"you win" | |
} else { | |
"i win" | |
}); | |
return; | |
} | |
// computer's turn | |
let my_move = select_move(&board); | |
println!("my move: {:?}", my_move); | |
board = board.apply_move(my_move); | |
println!("{:?}", board); | |
} | |
} | |
// === The game of Pennies | |
#[derive(Debug, Clone, Copy)] | |
struct Pennies(i32); | |
impl Game for Pennies { | |
type Move = i32; | |
fn start() -> Pennies { | |
Pennies(14) | |
} | |
fn moves(&self) -> Vec<i32> { | |
let Pennies(n) = *self; | |
(1..4).filter(|x| n - x >= 0).collect::<Vec<i32>>() | |
} | |
fn apply_move(&self, m: i32) -> Pennies { | |
let Pennies(n) = *self; | |
Pennies(n - m) | |
} | |
fn score_finished_game(&self) -> f64 { | |
1.0 | |
} | |
} | |
fn main() { | |
play_human_vs_computer(best_move::<Pennies>, Pennies::start()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment