How to design a bowling scoreboard

I just saw this post: 

http://www.iteye.com/topic/1112383 

titled as OO 还是 procedural 小程序的设计.

 

It's kind of interesting. First, I love bowling (though just average player), secondly, it's a good design question for interviews.

 

Here is a picture from google for the score board.

For each round, there is a score section called frame. In each round we have a chance to play at most twice. If we get a strike, meaning we hit all 10 pins, then this round is over. Otherwise, we can roll twice.

Check wiki pages for more detail.

 

The first class we create is the frame:

 

 

public class BowlingFrame 
{
	private static final int STRIKE = 2;
	private static final int SPARE = 1;
	private static final int OPEN = 0;
	
	private int[] rolls = new int[3];
	private int bonus = OPEN;
	public int score = 0;
	
	public BowlingFrame(int firstRoll)
	{
		rolls[0] = firstRoll;
		score = firstRoll;
		if (firstRoll == 10)
			bonus = STRIKE;		
	}
	
	public BowlingFrame(int firstRoll, int secondRoll)
	{
		rolls[0] = firstRoll;
		rolls[1] = secondRoll;
		score = firstRoll + secondRoll;
		if (firstRoll == 10)
			bonus = STRIKE;
		if (firstRoll + secondRoll == 10)
			bonus = SPARE;		
	}
	
	// This is for last frame, so we leave bonus as OPEN
	public BowlingFrame(int firstRoll, int secondRoll, int thirdRoll)
	{
		rolls[0] = firstRoll;
		rolls[1] = secondRoll;
		rolls[2] = thirdRoll;
		score = firstRoll + secondRoll + thirdRoll;
	}
	
	public int get1stScore() 
	{
		return rolls[0];
	}
	
	public int get2ndScore()
	{
		return rolls[1];
	}
	
	public boolean isStrike()
	{
		return bonus == STRIKE;
	}
	
	public boolean isSpare()
	{
		return bonus == SPARE;
	}
}
 

 

The behaviors (methods) are defined by another class, the score board class. There are 3 constructors corresponding to cases like strike, spare/open, and last round. It should be self explained.

 

The actual board class is like this:

 

 

public class BowlingScoreBoard 
{
	private BowlingFrame[] frames = new BowlingFrame[10];
	private int index = 0;
	
	public void addFrame(BowlingFrame bowlingFrame)
	{
		if (index >= 10)
			throw new RuntimeException("can't go beyond 10 frames");
		
		frames[index] = bowlingFrame;
		
		// try to set bonus for two frames back in case of a strike there.
		int p2 = index - 2;
		if (p2 >= 0)
		{
			BowlingFrame bf = frames[p2];
			if (bf.isStrike())
			{
				BowlingFrame previous = frames[index - 1];
				if (previous.isStrike())
				{
					bf.score += previous.get1stScore() + bowlingFrame.get1stScore();
					// previous.get1stScore() is just 10 since it's a strike
				}
			}
		}
		
		// try to set bonus for previous frame in case of a spare there.
		int p1 = index - 1;
		if (p1 >= 0)
		{
			BowlingFrame bf = frames[p1];
			if (bf.isSpare())
			{
				bf.score += bowlingFrame.get1stScore();
			}
			else if (bf.isStrike() && !bowlingFrame.isStrike())
			{
				bf.score += bowlingFrame.get1stScore() +  bowlingFrame.get2ndScore();
			}
		}
		
		index++;
	}
	
	public int totalScore()
	{
		int ret = 0;
		for (int i=0; i<index; i++)
		{
			ret += frames[i].score;			
		}
		
		return ret;
	}
}
 

 

As a game goes, we keep adding new frames. As we add new frames, we need to figure out the scores of previous 1 or 2 frames, depend on the bonus status, strike/spare/open. So at the end of the game, scores in each frame should be calculated correctly (This is also helpful for debugging).

 

The testcase is like this:

 

import junit.framework.TestCase;

public class BowlingScoreBoardTest extends TestCase
{
	private static final BowlingFrame EMPTY_FRAME = new BowlingFrame(0);
	
	public void testStrike()
	{
		BowlingScoreBoard bsb = new BowlingScoreBoard();
		bsb.addFrame(new BowlingFrame(10)); // strike
		bsb.addFrame(new BowlingFrame(3, 6)); // open
		
		System.out.println(bsb.totalScore());
		assertTrue(bsb.totalScore() == 28); // 1st frame: 10 + (3 + 6), 2nd frame: 3 + 6
	}
	
	public void testDouble() // two strikes in a row
	{
		BowlingScoreBoard bsb = new BowlingScoreBoard();
		bsb.addFrame(new BowlingFrame(10)); // strike
		bsb.addFrame(new BowlingFrame(10)); // strike
		bsb.addFrame(new BowlingFrame(3, 6)); // open
		
		System.out.println(bsb.totalScore());
		assertTrue(bsb.totalScore() == 51); 
		// 1st: 10 + 10 + 3; 2nd: 10 + 3 + 6; 3rd: 3 + 6.
	}
	
	public void testTurkey() // 3 consecutive strikes
	{
		BowlingScoreBoard bsb = new BowlingScoreBoard();
		bsb.addFrame(new BowlingFrame(10)); // strike
		bsb.addFrame(new BowlingFrame(10)); // strike
		bsb.addFrame(new BowlingFrame(10)); // strike
		
		System.out.println(bsb.totalScore());
		// first frame should have score 30 = 10 + (2nd frame) 10 + (3rd frame) 10
		// second frame bonus is not set yet since 3rd frame is a strike and thus
		// it needs the forth frame, similar to the 3rd frame, just 10.
		assertTrue(bsb.totalScore() == 50);
	}
	
	public void testTailTurkey()
	{
		BowlingScoreBoard bsb = new BowlingScoreBoard();
		bsb.addFrame(EMPTY_FRAME); // we dont care the first few frames, so just empty
		bsb.addFrame(EMPTY_FRAME);
		bsb.addFrame(EMPTY_FRAME);
		bsb.addFrame(EMPTY_FRAME);
		bsb.addFrame(EMPTY_FRAME);
		bsb.addFrame(EMPTY_FRAME);
		bsb.addFrame(EMPTY_FRAME);
		bsb.addFrame(new BowlingFrame(10));
		bsb.addFrame(new BowlingFrame(10));
		bsb.addFrame(new BowlingFrame(10, 10, 10));
		
		System.out.println(bsb.totalScore());
		assertTrue(bsb.totalScore() == 90); // 30 for each in the last 3 frames.
	}
	
	public void testSpare()
	{
		BowlingScoreBoard bsb = new BowlingScoreBoard();
		bsb.addFrame(new BowlingFrame(4, 6));
		bsb.addFrame(new BowlingFrame(3, 7));
		
		System.out.println(bsb.totalScore());
		assertTrue(bsb.totalScore() == 23); 
		// 1st: 4+6 + 3(bonus from 2nd frame); 2nd: 3 + 7 (it's also a spare, but no 3rd frame yet).
	}
	
	public void testPerfectScore()
	{
		BowlingScoreBoard bsb = new BowlingScoreBoard();
		for (int i=0; i<9; i++)
			bsb.addFrame(new BowlingFrame(10));
		bsb.addFrame(new BowlingFrame(10, 10, 10));
		
		System.out.println(bsb.totalScore());
		assertTrue(bsb.totalScore() == 300); // 30 for each frame.
	}
}
 

These cases should cover most of the code.

 

Several notes:

1. The frame class just maintains the current states. The bonus logic should be somewhere else, in the score board class, where we handle the logic between frames.

2. The requirement is to design a score calculator, so we shouldn't get the game playing logic here because it's a separate concern.

3. I played a lot bowling so I know fairly well about the game. If you don't, you may start coding the logic is one big static method and then refactor out the frame portion. The logic is explained well in the wiki page: http://en.wikipedia.org/wiki/Ten-pin_bowling.

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值