** Ruby **
값을 보관하는 `Data`, 주사위를 굴리고 규칙에 따라 점수를 계산하는 `Role`, 입출력을 처리하는 `Presenter`, 이들을 사용해 게임을 진행(랜덤 주사위 or 데이터 입력)하는 `Context`를 작성.
## Data
```{.ruby}
# Data
class DiceGameModel
attr_accessor :inputs, :scores, :turn_data, :target_score
def initialize
clear
end
def total_score
@scores.sum
end
def clear
@inputs, @scores = [], []
@turn_data = nil
@target_score = target_score
end
def over
@scores.empty? || total_score >= @target_score
end
end
```## Role
```{.ruby}
module DiceRollable
def roll
@inputs << (@turn_data&.shift || Array.new(6) { rand(1..6) })# data or random
@scores << score(@inputs.last)
@scores.clear if @scores.last.zero? # bomb
end
private
def score(dice)
frequencies = dice.tally # hash { num => occur, ... }
score_sequence(frequencies).nonzero? || frequencies.sum { score_spots(*_1) }
end
def score_spots(num, occur)
base, bonus = { 1 => 100, 5 => 50 }, { 1 => 1000, 5 => 500 } # 15 rule
base.default, bonus.default = 0, 100 # 2346 rule
occur < 3 ? base[num] * occur : num * bonus[num] * (2**(occur - 3))
end
def score_sequence(frequencies, seq_len = frequencies.size)
rest_score = ->(hash) { score_spots(hash.key(2), 1) }
case
when seq_len == 5 && !frequencies.key?(6) then 750 + rest_score[frequencies]
when seq_len == 5 && !frequencies.key?(1) then 1250 + rest_score[frequencies]
when seq_len == 6 then 2000
else
0
end
end
end
```## Presenter
```{.ruby}
class Presenter
def input_target_score
print "Input target score: "
gets.to_i
end
def continue_turn(target_score, total_score)
print "[target: #{target_score}, now: #{total_score}]. continue the turn? (y/n): "
gets.strip == 'y'
end
def display_result(inputs, scores, total_score)
formatted_scores = scores.size > 1 ? "#{scores.join(' + ')} = " : ""
puts "#{inputs.map(&:to_s).join(', ')} -> " + formatted_scores + "#{total_score} pts"
end
end
```## Context
```{.ruby}
class DiceGamePlayContext
def initialize(game, presenter)
@game = game
@presenter = presenter
@game.extend DiceRollable
end
def execute(turn_data = nil, target_score = nil)
setup(turn_data, target_score)
begin @game.roll end while continue_playing
display_result
end
private
def setup(turn_data, target_score)
@game.turn_data = turn_data
@game.target_score = target_score || @presenter.input_target_score # data or stdin
end
def continue_playing
return false if @game.over
@game.turn_data ? @game.turn_data.any? : @presenter.continue_turn(@game.target_score, @game.total_score)
end
def display_result
@presenter.display_result(@game.inputs, @game.scores, @game.total_score)
@game.clear
end
end
```
## 실행
테스트 데이터로 주사위 굴리기
```{.ruby}
target_score = 2000
play_context = DiceGamePlayContext.new(DiceGameModel.new, Presenter.new)
play_context.execute([[3, 6, 6, 3, 4, 2]], target_score) # spots : base score(2346)
play_context.execute([[2, 6, 1, 1, 6, 3]], target_score) # spots : base score(1)
play_context.execute([[1, 5, 2, 3, 6, 4]], target_score) # sequence : 123456
play_context.execute([[3, 5, 1, 5, 3, 3], [2, 4, 6, 3, 3, 4]], target_score) # spots : bomb
play_context.execute([[5, 2, 1, 2, 5, 2]], target_score) # spots : base score(15) + bonus score(2)
# spots : target score exceeded.
play_context.execute([[1, 2, 3, 4, 5, 5], [5, 2, 1, 2, 1, 2], [4, 6, 4, 1, 4, 4]], target_score)
[3, 6, 6, 3, 4, 2] -> 0 pts
[2, 6, 1, 1, 6, 3] -> 200 pts
[1, 5, 2, 3, 6, 4] -> 2000 pts
[3, 5, 1, 5, 3, 3], [2, 4, 6, 3, 3, 4] -> 0 pts
[5, 2, 1, 2, 5, 2] -> 400 pts
[1, 2, 3, 4, 5, 5], [5, 2, 1, 2, 1, 2], [4, 6, 4, 1, 4, 4] -> 800 +たす 450 +たす 900 =わ 2150 pts
```
랜덤하게 주사위 굴리기
```{.ruby}
play_context = DiceGamePlayContext.new(DiceGameModel.new, Presenter.new)
play_context.execute
Input target score: 1000
[target: 1000, now: 200]. continue the turn? (y/n): y
[target: 1000, now: 400]. continue the turn? (y/n): y
[1, 3, 4, 3, 6, 1], [5, 4, 5, 1, 4, 3], [3, 3, 3, 4, 1, 3] -> 200 +たす 200 +たす 700 =わ 1100 pts
```
** Ruby **
값을 보관하는 `Data`, 주사위를 굴리고 규칙에 따라 점수를 계산하는 `Role`, 입출력을 처리하는 `Presenter`, 이들을 사용해 게임을 진행(랜덤 주사위 or 데이터 입력)하는 `Context`를 작성.
## Data
```{.ruby}
# Data
class DiceGameModel
attr_accessor :inputs, :scores, :turn_data, :target_score
def initialize
clear
end
def total_score
@scores.sum
end
def clear
@inputs, @scores = [], []
@turn_data = nil
@target_score = target_score
end
def over
@scores.empty? || total_score >= @target_score
end
end
```## Role
```{.ruby}
module DiceRollable
def roll
@inputs << (@turn_data&.shift || Array.new(6) { rand(1..6) })# data or random
@scores << score(@inputs.last)
@scores.clear if @scores.last.zero? # bomb
end
private
def score(dice)
frequencies = dice.tally # hash { num => occur, ... }
score_sequence(frequencies).nonzero? || frequencies.sum { score_spots(*_1) }
end
def score_spots(num, occur)
base, bonus = { 1 => 100, 5 => 50 }, { 1 => 1000, 5 => 500 } # 15 rule
base.default, bonus.default = 0, 100 # 2346 rule
occur < 3 ? base[num] * occur : num * bonus[num] * (2**(occur - 3))
end
def score_sequence(frequencies, seq_len = frequencies.size)
rest_score = ->(hash) { score_spots(hash.key(2), 1) }
case
when seq_len == 5 && !frequencies.key?(6) then 750 + rest_score[frequencies]
when seq_len == 5 && !frequencies.key?(1) then 1250 + rest_score[frequencies]
when seq_len == 6 then 2000
else
0
end
end
end
```## Presenter
```{.ruby}
class Presenter
def input_target_score
print "Input target score: "
gets.to_i
end
def continue_turn(target_score, total_score)
print "[target: #{target_score}, now: #{total_score}]. continue the turn? (y/n): "
gets.strip == 'y'
end
def display_result(inputs, scores, total_score)
formatted_scores = scores.size > 1 ? "#{scores.join(' + ')} = " : ""
puts "#{inputs.map(&:to_s).join(', ')} -> " + formatted_scores + "#{total_score} pts"
end
end
```## Context
```{.ruby}
class DiceGamePlayContext
def initialize(game, presenter)
@game = game
@presenter = presenter
@game.extend DiceRollable
end
def execute(turn_data = nil, target_score = nil)
setup(turn_data, target_score)
begin @game.roll end while continue_playing
display_result
end
private
def setup(turn_data, target_score)
@game.turn_data = turn_data
@game.target_score = target_score || @presenter.input_target_score # data or stdin
end
def continue_playing
return false if @game.over
@game.turn_data ? @game.turn_data.any? : @presenter.continue_turn(@game.target_score, @game.total_score)
end
def display_result
@presenter.display_result(@game.inputs, @game.scores, @game.total_score)
@game.clear
end
end
```
## 실행
테스트 데이터로 주사위 굴리기
```{.ruby}
target_score = 2000
play_context = DiceGamePlayContext.new(DiceGameModel.new, Presenter.new)
play_context.execute([[3, 6, 6, 3, 4, 2]], target_score) # spots : base score(2346)
play_context.execute([[2, 6, 1, 1, 6, 3]], target_score) # spots : base score(1)
play_context.execute([[1, 5, 2, 3, 6, 4]], target_score) # sequence : 123456
play_context.execute([[3, 5, 1, 5, 3, 3], [2, 4, 6, 3, 3, 4]], target_score) # spots : bomb
play_context.execute([[5, 2, 1, 2, 5, 2]], target_score) # spots : base score(15) + bonus score(2)
# spots : target score exceeded.
play_context.execute([[1, 2, 3, 4, 5, 5], [5, 2, 1, 2, 1, 2], [4, 6, 4, 1, 4, 4]], target_score)
[3, 6, 6, 3, 4, 2] -> 0 pts
[2, 6, 1, 1, 6, 3] -> 200 pts
[1, 5, 2, 3, 6, 4] -> 2000 pts
[3, 5, 1, 5, 3, 3], [2, 4, 6, 3, 3, 4] -> 0 pts
[5, 2, 1, 2, 5, 2] -> 400 pts
[1, 2, 3, 4, 5, 5], [5, 2, 1, 2, 1, 2], [4, 6, 4, 1, 4, 4] -> 800 +たす 450 +たす 900 =わ 2150 pts
```
랜덤하게 주사위 굴리기
```{.ruby}
play_context = DiceGamePlayContext.new(DiceGameModel.new, Presenter.new)
play_context.execute
Input target score: 1000
[target: 1000, now: 200]. continue the turn? (y/n): y
[target: 1000, now: 400]. continue the turn? (y/n): y
[1, 3, 4, 3, 6, 1], [5, 4, 5, 1, 4, 3], [3, 3, 3, 4, 1, 3] -> 200 +たす 200 +たす 700 =わ 1100 pts
```