Bowling
1. Readme
保龄球
打保龄球比赛。
保龄球是一种游戏,玩家掷出一个沉重的球来击倒排列成三角形的罐子。编写代码以跟踪保龄球比赛的得分。
保龄球得分
游戏由 10 轮组成。一轮由一个或两个球投掷组成,其中 10 个罐子处于该轮初始化状态。一个回合有三种情况。
-
开放(open)轮是轮的记录小于 10 的分数。在这种情况下,该轮的分数是被击倒数。
-
备用(spare)的是所有十个罐子被第二次投掷击倒。备用的总分是 10 加上,下一次投掷击倒的罐子数量。
-
全倒(strike)是所有十个罐子被第一次击倒。全倒的总分是 10 加上,在接下来的两次投掷击倒的罐子数量。如果进行第二次击球又触发 strike,则再扔球之前不确定第一次击球的值。
这是一个三轮的例子:
第 1 轮 | 第 2 轮 | 第 3 轮 |
---|---|---|
X(strike) | 5 /(spare) | 9 0(open) |
第 1 轮是(10 + 5 + 5)= 20
第 2 轮是(5 + 5 + 9)= 19
轮 3 是(9 + 0)= 9
这意味着当前的运行总数为 48.
游戏中的第十轮是一个特例。如果有人投掷全倒或备用,那么他们会得到一个补球。补球会计入第 10 轮的总和。在补球上获得一次全倒或备用不会给球员带来更多的补球。第 10 轮的总分是被击倒的罐子总数。
对于 X1 /(全倒和备用)的第十轮,总分为 20。
对于 XXX (三次全倒)的第十轮,总分为 30。
要求
编写代码以跟踪保龄球比赛的得分。它应该支持两个操作:
roll(pins : int)
每次玩家滚球时都会调用。这个参数是被击倒的罐子数量.score() : int
仅在游戏结束时才会被调用。它返回该游戏的总分。
资源
保龄球比赛, Kata 的 UncleBobhttp://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata
2. 开始你的表演
#[derive(Debug, PartialEq)] pub enum Error { NotEnoughPinsLeft, GameComplete, } pub struct BowlingGame {} impl BowlingGame { pub fn new() -> Self { unimplemented!(); } pub fn roll(&mut self, pins: u16) -> Result<(), Error> { unimplemented!("Record that {} pins have been scored", pins); } pub fn score(&self) -> Option<u16> { unimplemented!("Return the score if the game is complete, or None if not."); } }
3. 测试代码查看
# #![allow(unused_variables)] #fn main() { #[test] fn roll_returns_a_result() { let mut game = BowlingGame::new(); assert!(game.roll(0).is_ok()); } #[test] //#[ignore] fn you_can_not_roll_more_than_ten_pins_in_a_single_roll() { let mut game = BowlingGame::new(); assert_eq!(game.roll(11), Err(Error::NotEnoughPinsLeft));; } #[test] //#[ignore] fn a_game_score_is_some_if_ten_frames_have_been_rolled() { let mut game = BowlingGame::new(); for _ in 0..10 { let _ = game.roll(0); let _ = game.roll(0); } assert!(game.score().is_some()); } #[test] //#[ignore] fn you_can_not_score_a_game_with_no_rolls() { let game = BowlingGame::new(); assert_eq!(game.score(), None); } #[test] //#[ignore] fn a_game_score_is_none_if_fewer_than_ten_frames_have_been_rolled() { let mut game = BowlingGame::new(); for _ in 0..9 { let _ = game.roll(0); let _ = game.roll(0); } assert_eq!(game.score(), None); } #[test] //#[ignore] fn a_roll_is_err_if_the_game_is_done() { let mut game = BowlingGame::new(); for _ in 0..10 { let _ = game.roll(0); let _ = game.roll(0); } assert_eq!(game.roll(0), Err(Error::GameComplete));; } #[test] //#[ignore] fn twenty_zero_pin_rolls_scores_zero() { let mut game = BowlingGame::new(); for _ in 0..20 { let _ = game.roll(0); } assert_eq!(game.score(), Some(0)); } #[test] //#[ignore] fn ten_frames_without_a_strike_or_spare() { let mut game = BowlingGame::new(); for _ in 0..10 { let _ = game.roll(3); let _ = game.roll(6); } assert_eq!(game.score(), Some(90)); } #[test] //#[ignore] fn spare_in_the_first_frame_followed_by_zeros() { let mut game = BowlingGame::new(); let _ = game.roll(6); let _ = game.roll(4); for _ in 0..18 { let _ = game.roll(0); } assert_eq!(game.score(), Some(10)); } #[test] //#[ignore] fn points_scored_in_the_roll_after_a_spare_are_counted_twice_as_a_bonus() { let mut game = BowlingGame::new(); let _ = game.roll(6); let _ = game.roll(4); let _ = game.roll(3); for _ in 0..17 { let _ = game.roll(0); } assert_eq!(game.score(), Some(16)); } #[test] //#[ignore] fn consecutive_spares_each_get_a_one_roll_bonus() { let mut game = BowlingGame::new(); let _ = game.roll(5); let _ = game.roll(5); let _ = game.roll(3); let _ = game.roll(7); let _ = game.roll(4); for _ in 0..15 { let _ = game.roll(0); } assert_eq!(game.score(), Some(31)); } #[test] //#[ignore] fn if_the_last_frame_is_a_spare_you_get_one_extra_roll_that_is_scored_once() { let mut game = BowlingGame::new(); for _ in 0..18 { let _ = game.roll(0); } let _ = game.roll(5); let _ = game.roll(5); let _ = game.roll(7); assert_eq!(game.score(), Some(17)); } #[test] //#[ignore] fn a_strike_earns_ten_points_in_a_frame_with_a_single_roll() { let mut game = BowlingGame::new(); let _ = game.roll(10); for _ in 0..18 { let _ = game.roll(0); } assert_eq!(game.score(), Some(10)); } #[test] //#[ignore] fn points_scored_in_the_two_rolls_after_a_strike_are_counted_twice_as_a_bonus() { let mut game = BowlingGame::new(); let _ = game.roll(10); let _ = game.roll(5); let _ = game.roll(3); for _ in 0..16 { let _ = game.roll(0); } assert_eq!(game.score(), Some(26)); } #[test] //#[ignore] fn consecutive_strikes_each_get_the_two_roll_bonus() { let mut game = BowlingGame::new(); let _ = game.roll(10); let _ = game.roll(10); let _ = game.roll(10); let _ = game.roll(5); let _ = game.roll(3); for _ in 0..12 { let _ = game.roll(0); } assert_eq!(game.score(), Some(81)); } #[test] //#[ignore] fn a_strike_in_the_last_frame_earns_a_two_roll_bonus_that_is_counted_once() { let mut game = BowlingGame::new(); for _ in 0..18 { let _ = game.roll(0); } let _ = game.roll(10); let _ = game.roll(7); let _ = game.roll(1); assert_eq!(game.score(), Some(18)); } #[test] //#[ignore] fn a_spare_with_the_two_roll_bonus_does_not_get_a_bonus_roll() { let mut game = BowlingGame::new(); for _ in 0..18 { let _ = game.roll(0); } let _ = game.roll(10); let _ = game.roll(7); let _ = game.roll(3); assert_eq!(game.score(), Some(20)); } #[test] //#[ignore] fn strikes_with_the_two_roll_bonus_do_not_get_a_bonus_roll() { let mut game = BowlingGame::new(); for _ in 0..18 { let _ = game.roll(0); } let _ = game.roll(10); let _ = game.roll(10); let _ = game.roll(10); assert_eq!(game.score(), Some(30)); } #[test] //#[ignore] fn a_strike_with_the_one_roll_bonus_after_a_spare_in_the_last_frame_does_not_get_a_bonus() { let mut game = BowlingGame::new(); for _ in 0..18 { let _ = game.roll(0); } let _ = game.roll(7); let _ = game.roll(3); let _ = game.roll(10); assert_eq!(game.score(), Some(20)); } #[test] //#[ignore] fn all_strikes_is_a_perfect_score_of_300() { let mut game = BowlingGame::new(); for _ in 0..12 { let _ = game.roll(10); } assert_eq!(game.score(), Some(300)); } #[test] //#[ignore] fn you_can_not_roll_more_than_ten_pins_in_a_single_frame() { let mut game = BowlingGame::new(); assert!(game.roll(5).is_ok()); assert_eq!(game.roll(6), Err(Error::NotEnoughPinsLeft));; } #[test] //#[ignore] fn first_bonus_ball_after_a_final_strike_can_not_score_an_invalid_number_of_pins() { let mut game = BowlingGame::new(); for _ in 0..18 { let _ = game.roll(0); } let _ = game.roll(10); assert_eq!(game.roll(11), Err(Error::NotEnoughPinsLeft));; } #[test] //#[ignore] fn the_two_balls_after_a_final_strike_can_not_score_an_invalid_number_of_pins() { let mut game = BowlingGame::new(); for _ in 0..18 { let _ = game.roll(0); } let _ = game.roll(10); assert!(game.roll(5).is_ok()); assert_eq!(game.roll(6), Err(Error::NotEnoughPinsLeft));; } #[test] //#[ignore] fn the_two_balls_after_a_final_strike_can_be_a_strike_and_non_strike() { let mut game = BowlingGame::new(); for _ in 0..18 { let _ = game.roll(0); } let _ = game.roll(10); assert!(game.roll(10).is_ok()); assert!(game.roll(6).is_ok()); } #[test] //#[ignore] fn the_two_balls_after_a_final_strike_can_not_be_a_non_strike_followed_by_a_strike() { let mut game = BowlingGame::new(); for _ in 0..18 { let _ = game.roll(0); } let _ = game.roll(10); assert!(game.roll(6).is_ok()); assert_eq!(game.roll(10), Err(Error::NotEnoughPinsLeft));; } #[test] //#[ignore] fn second_bonus_ball_after_a_final_strike_can_not_score_an_invalid_number_of_pins_even_if_first_is_strike( ) { let mut game = BowlingGame::new(); for _ in 0..18 { let _ = game.roll(0); } let _ = game.roll(10); assert!(game.roll(10).is_ok()); assert_eq!(game.roll(11), Err(Error::NotEnoughPinsLeft));; } #[test] //#[ignore] fn if_the_last_frame_is_a_strike_you_can_not_score_before_the_extra_rolls_are_taken() { let mut game = BowlingGame::new(); for _ in 0..18 { let _ = game.roll(0); } let _ = game.roll(10); assert_eq!(game.score(), None); let _ = game.roll(10); assert_eq!(game.score(), None); let _ = game.roll(10); assert!(game.score().is_some()); } #[test] //#[ignore] fn if_the_last_frame_is_a_spare_you_can_not_create_a_score_before_extra_roll_is_taken() { let mut game = BowlingGame::new(); for _ in 0..18 { let _ = game.roll(0); } let _ = game.roll(5); let _ = game.roll(5); assert_eq!(game.score(), None); let _ = game.roll(10); assert!(game.score().is_some()); } #}
4. 答案
# #![allow(unused_variables)] #fn main() { #[derive(Debug, PartialEq)] pub enum Error { NotEnoughPinsLeft, GameComplete, } pub struct BowlingGame { frames: Vec<Frame>, } struct Frame { rolls: Vec<u16>, bonus: Vec<u16>, } impl Frame { fn score(&self) -> u16 { self.roll_score() + self.bonus_score() } fn roll_score(&self) -> u16 { self.rolls.iter().sum() } fn bonus_score(&self) -> u16 { self.bonus.iter().sum() } fn is_valid(&self) -> bool { self.rolls_valid() && self.bonus_valid() } fn rolls_valid(&self) -> bool { self.roll_score() <= 10 } fn bonus_valid(&self) -> bool { if self.is_open() || !self.bonus_done() { return true; } if self.is_spare() { return self.bonus_score() <= 10; } if let Some(first) = self.bonus.iter().next() { if *first == 10 { self.bonus_score() <= 20 } else { self.bonus_score() <= 10 } } else { unreachable!(); } } fn is_complete(&self) -> bool { self.is_open() || self.bonus_done() } fn rolls_done(&self) -> bool { self.rolls.len() == 2 || self.is_strike() } fn bonus_done(&self) -> bool { (self.is_spare() && self.bonus.len() == 1) || (self.is_strike() && self.bonus.len() == 2) } fn is_open(&self) -> bool { self.rolls.len() == 2 && self.roll_score() < 10 } fn is_spare(&self) -> bool { self.rolls.len() == 2 && self.roll_score() == 10 } fn is_strike(&self) -> bool { self.rolls.len() == 1 && self.roll_score() == 10 } fn add_roll(&mut self, roll: u16) { if !self.is_complete() { if self.is_spare() || self.is_strike() { self.bonus.push(roll) } else { self.rolls.push(roll) } } } fn new() -> Self { Frame { rolls: vec![], bonus: vec![], } } } impl BowlingGame { pub fn new() -> Self { BowlingGame { frames: vec![Frame::new()], } } pub fn roll(&mut self, pins: u16) -> Result<(), Error> { if pins > 10 { Err(Error::NotEnoughPinsLeft) } else { if self.score().is_some() { return Err(Error::GameComplete); } for frame in self.frames.iter_mut() { frame.add_roll(pins) } if self.frames.iter().any(|f| !f.is_valid()) { return Err(Error::NotEnoughPinsLeft); } if self.frames.iter().last().unwrap().rolls_done() && self.frames.len() < 10 { self.frames.push(Frame::new()); } Ok(()) } } pub fn score(&self) -> Option<u16> { if !self.is_done() { None } else { Some(self.frames.iter().fold(0, |acc, r| acc + r.score())) } } fn is_done(&self) -> bool { self.frames.len() == 10 && self.frames.iter().all(|f| f.is_complete()) } } #}