TDD Bowling Game 练习和重构

题目

Write a program to score a game of Ten-Pin Bowling.

Input: string (described below) representing a bowling game
Ouput: integer score

The scoring rules:

Each game, or "line" of bowling, includes ten turns,
or "frames" for the bowler.

In each frame, the bowler gets up to two tries to
knock down all ten pins.

If the first ball in a frame knocks down all ten pins,
this is called a "strike". The frame is over. The score
for the frame is ten plus the total of the pins knocked
down in the next two balls.

If the second ball in a frame knocks down all ten pins,
this is called a "spare". The frame is over. The score
for the frame is ten plus the number of pins knocked
down in the next ball.

If, after both balls, there is still at least one of the
ten pins standing the score for that frame is simply
the total number of pins knocked down in those two balls.

If you get a spare in the last (10th) frame you get one
more bonus ball. If you get a strike in the last (10th)
frame you get two more bonus balls.
These bonus throws are taken as part of the same turn.
If a bonus ball knocks down all the pins, the process
does not repeat. The bonus balls are only used to
calculate the score of the final frame.

The game score is the total of all frame scores.

Examples:

X indicates a strike
/ indicates a spare

  • indicates a miss
    | indicates a frame boundary
    The characters after the || indicate bonus balls

X|X|X|X|X|X|X|X|X|X||XX
Ten strikes on the first ball of all ten frames.
Two bonus balls, both strikes.
Score for each frame == 10 + score for next two
balls == 10 + 10 + 10 == 30
Total score == 10 frames x 30 == 300

9-|9-|9-|9-|9-|9-|9-|9-|9-|9-||
Nine pins hit on the first ball of all ten frames.
Second ball of each frame misses last remaining pin.
No bonus balls.
Score for each frame == 9
Total score == 10 frames x 9 == 90

5/|5/|5/|5/|5/|5/|5/|5/|5/|5/||5
Five pins on the first ball of all ten frames.
Second ball of each frame hits all five remaining
pins, a spare.
One bonus ball, hits five pins.
Score for each frame == 10 + score for next one
ball == 10 + 5 == 15
Total score == 10 frames x 15 == 150

X|7/|9-|X|-8|8/|-6|X|X|X||81
Total score == 167

题目说明

保龄球的计分不难,每一局总共有十格,每一格里面有两球。共有十支球瓶,要尽量在两球之内把球瓶全部击倒,如果第一球就把全部的球瓶都击倒了,也就是“STRIKE”,画面出现“X”,就算完成一格了,所得分数就是10分再加下两球的倒瓶数,但是如果第一球没有全倒时,就要再打一球,如果剩下的球瓶全都击倒,也就是“SPARE”,画面出现“/”,也算完成一格,所得分数为10分再加下一格第一球的倒瓶数,但是如果第二球也没有把球瓶全部击倒的话,那分数就是第一球加第二球倒的瓶数,再接着打下一格。依此类推直到第十格。但是第十格有三球,第十格时如果第一球或第二球将球瓶全部击倒时,可再加打第三球。

关键信息: 1、 打出X,可以奖励下两个球的得分。 2、打出SPARE 可以奖励下一球的得分。奖励是一个球或两个球的得分,一回合最多投两个球。 百度百科-保龄球

测试代码

public class BowlingGameTest {
    @Test
    public void should_10_oneGameScore_strike(){
        String input="X";
        int expect=10;

        int result = BowlingGame.oneGameScore(input);
        assertEquals(expect,result);
    }

    @Test
    public void should_10_oneGameScore_spare(){
        String input="7/";
        int expect=10;
        int result = BowlingGame.oneGameScore(input);
        assertEquals(expect,result);
    }
    @Test
    public void should_7_oneGameScore_miss(){
        String input="7-";
        int expect=7;
        int result = BowlingGame.oneGameScore(input);
        assertEquals(expect,result);
    }
    @Test
    public void should_9_oneGameScore_miss(){
        String input="72";
        int expect=9;
        int result = BowlingGame.oneGameScore(input);
        assertEquals(expect,result);
    }
    @Test
    public void should_0_oneGameScore_miss(){
        String input="--";
        int expect=0;
        int result = BowlingGame.oneGameScore(input);
        assertEquals(expect,result);
    }
    @Test
    public void should_90_oneGameScore_allMiss(){
        String input="9-|9-|9-|9-|9-|9-|9-|9-|9-|9-||";
        int expect=90;
        BowlingGame bowlingGame = new BowlingGame(input);
        int result = bowlingGame.score();
        assertEquals(expect,result);
    }

    @Test
    public void should_150_oneGameScore_allSpare(){
        String input="5/|5/|5/|5/|5/|5/|5/|5/|5/|5/||5";
        int expect=150;
        BowlingGame bowlingGame = new BowlingGame(input);
        int result = bowlingGame.score();
        assertEquals(expect,result);
    }
    @Test
    public void should_300_oneGameScore_allStrike(){
        String input="X|X|X|X|X|X|X|X|X|X||XX";
        int expect=300;
        BowlingGame bowlingGame = new BowlingGame(input);
        int result = bowlingGame.score();
        assertEquals(expect,result);
    }
    @Test
    public void should_167_oneGameScore_MixScore(){
        String input="X|7/|9-|X|-8|8/|-6|X|X|X||81";
        int expect=167;
        BowlingGame bowlingGame = new BowlingGame(input);
        int result = bowlingGame.score();
        assertEquals(expect,result);
    }
    @Test
    public void should_18_bonus_MixScore(){
        String input="X|7/|9-|X|-8|8/|-6|X|X|X||81";
        int expect=18;
        BowlingGame bowlingGame = new BowlingGame(input);
        int result = bowlingGame.bonus(8);
        assertEquals(expect,result);
    }
}

实现代码

public class BowlingGame {
    String input;
    String[] oneGameScores;
    int score = 0;

    public BowlingGame(String input) {
        this.oneGameScores = input.split("\\|");
        if (this.oneGameScores.length == 12) {
            this.oneGameScores[10] = this.oneGameScores[11];
            this.oneGameScores[11] = "";
        }

        System.out.println(this.oneGameScores.length + "  " + String.join(",", this.oneGameScores));
    }

    public int score() {

        for (int i = 0; i < 10; i++) {
            score += oneGameScore(oneGameScores[i]);
            score += bonus(i);
            System.out.println(" i:"+i+" score:"+score);
        }
        return score;
    }

    public int bonus(int i) {
        String score = oneGameScores[i];
        int bonus = 0;
        if (score.equals("X")) {
            score = oneGameScores[i + 1];
            bonus = oneGameScore(score);
            if (score.equals("X")) {
//                bonus = 10;
                if (i <= 9) {
                    score = oneGameScores[i + 2];
                    score = score.charAt(0)+"-";
                }
                bonus += oneGameScore(score);

            }
            return bonus;
        } else if (score.indexOf("/") != -1) {
            score = oneGameScores[i + 1];
            score = score.charAt(0) + "-";
            bonus = oneGameScore(score);
            return bonus;
        } else {
            return 0;
        }
    }

    public static int oneGameScore(String score) {
        if (score.equals("XX")) {
            return 20;
        } else if (score.indexOf("X") != -1 || score.indexOf("/") != -1) {
            return 10;
        } else if (score.indexOf("-") != -1) {
            score = score.replace("-", "");
            try {
                return Integer.parseInt(score);
            } catch (Exception e) {
                return 0;
            }
        } else {
            try {
                return Integer.parseInt(score.charAt(0) + "") + Integer.parseInt(score.charAt(1) + "");
            } catch (Exception e) {
                return 0;
            }
        }
    }
}

为什么需要重构

  • 这已经不是我第一次写这个Bowling Game。第一是和Bob Deng 结队编程的时候写的,那么时候对bowling 规则并不熟悉,Bob说规则我来写。 当时第一次玩TDD,还是在网页上写代码(cyber-dojo),当时的窘迫比较难忘。关键问题是第二次写写的还是有点乱。作为TDD练习,同一个题目是需要写三遍以上才能慢慢找到一点步调。
  • 写的时候遇到一个BUG,花了些时间查找,查找和定位都不方便。
  • 代码结构不清晰 score() 里面获取每次得分写的比较别扭
  • bonus 需要重构,代码里面的坏味道需要去除
  • 奖励里面有个比较奇怪的处理把一次投球的当一回合处理

重构测试代码

public class BowlingGameTest1 {

    @Test
    public void should_7_oneFrameScore_miss(){
        String input="7-";
        int expect=7;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.oneFrameScore(input);
        assertEquals(expect,result);
    }

    @Test
    public void should_10_oneFrameScore_spare(){
        String input="7/";
        int expect=10;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.oneFrameScore(input);
        assertEquals(expect,result);
    }
    @Test
    public void should_X_oneFrameScore_strike(){
        String input="X";
        int expect=10;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.oneFrameScore(input);
        assertEquals(expect,result);
    }

    @Test
    public void should_20_oneFrameScore_XX(){
        String input="XX";
        int expect=20;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.oneFrameScore(input);
        assertEquals(expect,result);
    }
    @Test
    public void should_6_oneFrameScore_6(){
        String input="6";
        int expect=6;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.oneFrameScore(input);
        assertEquals(expect,result);
    }
    @Test
    public void should_16_oneFrameScore_X6(){
        String input="X6";
        int expect=16;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.oneFrameScore(input);
        assertEquals(expect,result);
    }
    @Test
    public void should_7_oneTurnScore_miss(){
        String input="7-";
        int expect=7;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.oneTurnScore(0);
        assertEquals(expect,result);
    }
    @Test
    public void should_13_oneTurnScore_spare(){
        String input="7/|3-";
        int expect=13;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.oneTurnScore(0);
        assertEquals(expect,result);
    }
    @Test
    public void should_10_oneTurnScore_spare(){
        String input="7/|-3";
        int expect=10;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.oneTurnScore(0);
        assertEquals(expect,result);
    }

    @Test
    public void should_19_oneTurnScore_strike(){
        String input="X|81";
        int expect=19;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.oneTurnScore(0);
        assertEquals(expect,result);
    }
    @Test
    public void should_23_oneTurnScore_strike(){
        String input="X|X|35";
        int expect=23;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.oneTurnScore(0);
        assertEquals(expect,result);
    }

    @Test
    public void should_90_oneGameScore_allMiss(){
        String input="9-|9-|9-|9-|9-|9-|9-|9-|9-|9-||";
        int expect=90;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.score();
        assertEquals(expect,result);
    }
    @Test
    public void should_150_oneGameScore_allSpare(){
        String input="5/|5/|5/|5/|5/|5/|5/|5/|5/|5/||5";
        int expect=150;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.score();
        assertEquals(expect,result);
    }
    @Test
    public void should_300_oneGameScore_allStrike(){
        String input="X|X|X|X|X|X|X|X|X|X||XX";
        int expect=300;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.score();
        assertEquals(expect,result);
    }

    @Test
    public void should_167_oneGameScore_MixScore(){
        String input="X|7/|9-|X|-8|8/|-6|X|X|X||81";
        int expect=167;
        BowlingGame1 bowlingGame1 = new BowlingGame1(input);
        int result = bowlingGame1.score();
        assertEquals(expect,result);
    }
}

实现代码

public class BowlingGame1 {
    String[] oneGameScores;
    int score;

    public BowlingGame1(String input) {
        oneGameScores = input.split("\\|");
        if (this.oneGameScores.length == 12) {
            this.oneGameScores[10] = this.oneGameScores[11];
            this.oneGameScores[11] = "";
        }
    }

    public int score() {
        for (int i = 0; i < 10; i++) {
            score += oneTurnScore(i);
        }
        return score;
    }

    public int oneTurnScore(int i) {
        int score = 0;
        String scoreExpress = oneGameScores[i];
        score = oneFrameScore(scoreExpress);
        if (scoreExpress.equals("X")) {
            score += strikeBonus(i);
        } else if (scoreExpress.indexOf("/") != -1) {
            score += spareBonus(i);
        }
        return score;
    }

    private int strikeBonus(int i) {
        String scoreExpress = oneGameScores[i + 1];
        int bonus = 0;
        if (scoreExpress.equals("X")) {
            bonus = 10;
            scoreExpress = oneGameScores[i + 2];

            scoreExpress = scoreExpress.charAt(0) + "";
            bonus += oneFrameScore(scoreExpress);
            return bonus;
        } else {
            return oneFrameScore(scoreExpress);
        }
    }


    private int spareBonus(int i) {
        String scoreExpress = oneGameScores[i + 1];
        scoreExpress = scoreExpress.charAt(0) +"";
        return oneFrameScore(scoreExpress);
    }

    public int oneFrameScore(String score) {
        if (score.equals("XX")) {
            return 20;
        } else if (score.indexOf("X") != -1) {
            if (score.length() == 2) {
                score = score.replace("X", "");
                return 10 + Integer.parseInt(score);
            }
            return 10;
        } else if (score.indexOf("/") != -1) {
            return 10;
        } else if (score.indexOf("-") != -1) {
            score = score.replace("-", "");
            try {
                return Integer.parseInt(score);
            } catch (Exception e) {
                return 0;
            }
        } else {
            if (score.length() == 1) {

                return Integer.parseInt(score.charAt(0) + "");
            } else {
                return Integer.parseInt(score.charAt(0) + "") + Integer.parseInt(score.charAt(1) + "");

            }

        }
    }


}

重构的时候发现了一个BUG,不知道你是否看出来了!

你可能感兴趣的:(TDD Bowling Game 练习和重构)