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())
   }
}

#}



填充/相关