loading...

Daily Challenge #41 - Greed is Good

thepracticaldev profile image dev.to staff ・1 min read

Greed is a dice game played with five six-sided dice. Using an array containing five six-sided dice values, write a function that will score a throw according to the following rules:

Three 1's => 1000 points
Three 6's => 600 points
Three 5's => 500 points
Three 4's => 400 points
Three 3's => 300 points
Three 2's => 200 points
One 1 => 100 points
One 5 => 50 point

A single die can only be counted once in each roll. For example, a "5" can only count as part of a triplet (contributing to the 500 points) or alone (as 50 points), but not both in the same roll.

Example Scoring

5 1 3 4 1 => 50 + 2 * 100 = 250
1 1 1 3 1 => 1000 + 100 = 1100
2 4 4 5 4 => 400 + 50 = 450

You can try to fill the array with random values. If you have extra time, you can also try to keep track of the player's score over several throws.


This challenge comes from user JulianNicholls. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge for a future post? Email yo+challenge@dev.to with your suggestions!

Posted on by:

thepracticaldev profile

dev.to staff

@thepracticaldev

The hardworking team behind dev.to ❤️

Discussion

pic
Editor guide
 

its pretty? no... but it works

function GG(arr) {
    let map = [],
        result = 0;

    arr.join("").split("").forEach(el => map[el] = (map[el] || 0) + 1);

    for (let index = 0; index <= 6; index++) {
        if (map[index] != undefined) {
            switch (index) {
                case 1:
                    if (map[index] >= 3) {
                        result += 1000;

                        if ((map[index] - 3) > 0) result += (map[index] - 3) * 100;
                    } else {
                        result += map[index] * 100
                    }

                    break;
                case 2:
                    if (map[index] >= 3) result += 200;

                    break;
                case 3:
                    if (map[index] >= 3) result * 300;

                    break;
                case 4:
                    if (map[index] >= 3) result += 400;

                    break;
                case 5:
                    if (map[index] >= 3) {
                        result += 500;

                        if ((map[index] - 3) > 0) result += (map[index] - 3) * 50;
                    } else {
                        result += map[index] * 50
                    }

                    break;
                case 6:
                    if (map[index] >= 3) result += 600;

                    break;
            }
        }
    }

    return result;
}

GG([5, 1, 3, 4, 1]) // 250

GG([1, 1, 1, 3, 1]) // 1100

GG([2, 4, 4, 5, 4]) // 450
 

Very understandable and faster! 👍

 
 

Perl

#!/usr/bin/env perl

use strict;
use warnings;
use utf8;
use feature qw{ say signatures };
no warnings qw{ experimental::signatures };

my @roll = map { d6() } 1 .. 5;
say join ' ', @roll;
say score(@roll);

sub score (@roll) {
    my $score = 0;
    my $count;
    map { $count->{$_} = 0 } 1 .. 6;
    for my $d (@roll) {
        $count->{$d}++;
    }
    if ( $count->{1} >= 3 ) { $score += 1000; $count->{1} -= 3 }
    if ( $count->{2} >= 3 ) { $score += 200;  $count->{2} -= 3 }
    if ( $count->{3} >= 3 ) { $score += 300;  $count->{3} -= 3 }
    if ( $count->{4} >= 3 ) { $score += 400;  $count->{4} -= 3 }
    if ( $count->{5} >= 3 ) { $score += 500;  $count->{5} -= 3 }
    if ( $count->{6} >= 3 ) { $score += 600;  $count->{6} -= 3 }
    while ( $count->{1} > 0 ) { $score += 100; $count->{1}-- }
    while ( $count->{5} > 0 ) { $score += 50;  $count->{5}-- }
    return $score;
}

sub d6 {
    return 1 + int rand 6;
}
 

JS implementation keeping the rules as data

function score( dice ) {
  // Business rules of the game:
  //  v: the dice value
  //  c: the number of dice required to trigger the rule
  //  p: the points added each time the rule is triggered
  const scoringRules = [
    {v: 1, c: 3, p: 1000},
    {v: 6, c: 3, p: 600},
    {v: 5, c: 3, p: 500},
    {v: 4, c: 3, p: 400},
    {v: 3, c: 3, p: 300},
    {v: 2, c: 3, p: 200},
    {v: 1, c: 1, p: 100},
    {v: 5, c: 1, p: 50}
  ]

  // returns the score given a histogram mapping a dice value to the number of dice with that value
  const scoreForHistogram = counts => {
    let score = 0
    for (const {v, c, p} of scoringRules) {
      const ruleCount = Math.floor((counts[v] || 0) / c);
      counts[v] -= ruleCount * c;
      score += ruleCount * p;
    }
    return score;
  };

  // Generate the histogram required by scoreForHistogram
  const counts = [...Array(7)].map(v=>0);// note that the rest of the logic uses 1-indexed arrays, so we need 7 entries to get a valid counts[6] entry
  for (const v of dice) counts[v]++;

  return scoreForHistogram(counts)
}
 

Keep the rules as data is the way to go. 🔥🔥🔥

 

ruby <3

def score(values)
  values.tally.sum do |n, cnt, acc = 0|
    case [n, cnt]
    in 1, 3.. then
      cnt -= 3
      acc += 1000
      redo
    in 6, 3.. then
      cnt -= 3
      acc += 600
      redo
    in 5, 3.. then
      cnt -= 3
      acc += 500
      redo
    in 4, 3.. then
      cnt -= 3
      acc += 400
      redo
    in 3, 3.. then
      cnt -= 3
      acc += 300
      redo
    in 2, 3.. then
      cnt -= 3
      acc += 200
      redo
    in 1, 1.. then
      cnt -= 1
      acc += 100
      redo
    in 5, 1.. then
      cnt -= 1
      acc += 50
      redo
    else
      acc
    end
  end
end

p score([5, 1, 3, 4, 1]) # => 50 + 2 * 100 = 250
p score([1, 1, 1, 3, 1]) # => 1000 + 100 = 1100
p score([2, 4, 4, 5, 4]) # => 400 + 50 = 450
 

Nice use of Ruby 2.7's pattern matching :-)

 

Easy peasy, lemon squeezy

(language: Elixir)

defmodule Greed do
  def score_dice([_|_] = dice) do
    tally(dice)
      |> score
  end

  defp tally(enumerable) do
    Enum.reduce(enumerable, %{}, fn 
      item, tally ->
        count = tally[item] || 0
        put_in(tally[item], count+1)
    end)
  end

  defp score(tally, total \\ 0)
  defp score(%{1 => dice} = tally, total) when dice >= 3, do: 
    score(%{tally | 1 => dice - 3}, total + 1000)
  defp score(%{1 => dice} = tally, total) when dice >= 1, do: 
    score(%{tally | 1 => dice - 1}, total + 100)
  defp score(%{2 => dice} = tally, total) when dice >= 3, do: 
    score(%{tally | 2 => dice - 3}, total + 200)
  defp score(%{3 => dice} = tally, total) when dice >= 3, do: 
    score(%{tally | 3 => dice - 3}, total + 300)
  defp score(%{4 => dice} = tally, total) when dice >= 3, do: 
    score(%{tally | 4 => dice - 3}, total + 400)
  defp score(%{5 => dice} = tally, total) when dice >= 3, do: 
    score(%{tally | 5 => dice - 3}, total + 500)
  defp score(%{5 => dice} = tally, total) when dice >= 1, do: 
    score(%{tally | 5 => dice - 1}, total + 50)
  defp score(%{6 => dice} = tally, total) when dice >= 3, do: 
    score(%{tally | 6 => dice - 3}, total + 600)
  defp score(_totaled_tally, score), do: 
    score
end

Greed.score_dice([1,1,1,1,1])
# 1200
Greed.score_dice([1,1,1,1,3])
# 1100
Greed.score_dice([1,1,1,3,3])
# 1000
Greed.score_dice([1,3,1,3,3])
# 500
Greed.score_dice([1,3,5,3,3])
# 450

 

A super gross unreadable solution but fun none the less 😂

def score(inp):
    def single_score(n, c):
        return (n * (100 if n != 1 else 1000) if c >= 3 and n in t else 0) + ((n * (10 if n != 1 else 100)) * (c - (3 if c >= 3 else 0)) if n in s else 0)
    return sum([single_score(n, c) for n, c in {i: inp.count(i) for i in inp}.items()])

score([2, 4, 4, 5, 4]) # 450
 

Here is my simple solution with Python:

def score(dice):
    dice_count = [0, 0, 0, 0, 0, 0]

    sum = 0

    for point in dice:
        dice_count[point-1] += 1

    while dice_count[0] >= 3:
        sum += 1000
        dice_count[0] -= 3
    if dice_count[0] <= 2 and dice_count != 0:
        sum += 100 * dice_count[0]
    if dice_count[1] >= 3:
        sum += 200
    else:
        sum += 0
    if dice_count[2] >= 3:
        sum += 300
    else:
        sum += 0
    if dice_count[3] >= 3:
        sum += 400
    else:
        sum += 0
    while dice_count[4] >= 3:
        sum += 500
        dice_count[4] -= 3
    if dice_count[4] <= 2 and dice_count[4] != 0:
        sum += 50 * dice_count[4]
    if dice_count[5] >= 3:
        sum += 600
    else:
        sum += 0

    return sum
 

Python

import random
def pounts(p):
    if p == 1:
        return 1000
    else:
        return p*100
def greed():
    d=sorted(random.choices(range(1,7),k=5))
    print(d)
    dice = 1
    p = 0
    while len(d) > 0:
        if d.count(dice) >= 3:
            p += pounts(dice)
            del d[:4]
        elif (d[0] == 1 or d[0] == 5) and (dice == 5 or dice == 1):
            if d.count(1) == 2 or d.count(5) == 2:
                p += int(100 if d[0] == 1 else 50) * 2
                del d[:2]
            else:
                p += int(100 if d[0] == 1 else 50)
                d.pop(0)
            dice += 1
        else:
            if d[0] == dice and d.count(dice) == 1:
                d.pop(0)
            elif d[0] == dice and d.count(dice) == 2:
                del d[:2]
            dice += 1
    return p
 

JavaScript

const greedy = arr => {
  let points = 0;
  const dices = { 1:0, 2:0, 3:0, 4:0, 5:0, 6:0 };
  arr.forEach(el => dices[el]++);

  Object.keys(dices).forEach(key => {
    if (dices[key] >= 3) {
      points += key * (key == 1 ? 1000 : 100);
      if (key == 1) points += (dices[key] - 3) * 100;
      if (key == 5) points += (dices[key] - 3) * 50;
    } else {
      if (key == 1) points += dices[key] * 100;
      if (key == 5) points += dices[key] * 50;
    }
  })

  return points;
}

Live demo on CodePen.

 

C#

Repl.it

using System;
using System.Linq;
using System.Threading;

class MainClass {
  public static void Main (string[] args) {
    try {
        var greed = new Greed();
        greed.Throw();

        while (true) {
          Thread.Sleep(5);
          greed.Throw();
        }
    } catch(Exception) {
      Console.WriteLine("\nYou win!, game over.");
    }
  }
}

class Greed {

  public int Score = 0;
  public int Throws = 0;

  public double GetAveragePerThrow(){
    if(this.Throws == 0){
      return 0;
    }

    return this.Score / this.Throws;
  }

  public int Roll(Random random){
    var rolled = random.Next(1, 7);

    Console.Write(rolled);

    return rolled;
  }

  public int GetScore(int[] rolls){
    var score = 0;

    var one = rolls.Count(c => c == 1);
    var two = rolls.Count(c => c == 2);
    var three = rolls.Count(c => c == 3);
    var four = rolls.Count(c => c == 4);
    var five = rolls.Count(c => c == 5);
    var six = rolls.Count(c => c == 6);

    if(one == 1){
      score += 100;
    }

    if(one == 3){
      score += 1000;
    }

    if(two == 3){
      score += 200;
    }

    if(three == 3){
      score += 300;
    }

    if(four == 3){
      score += 400;
    }

    if(five == 1){
      score += 50;
    }

    if(five == 3){
      score += 500;
    }

    if(six == 3){
      score += 600;
    }

    if(one == 5){
      throw new Exception("You win."); 
    }

    return score;
  }

  public int[] Throw(){
    var rollsPerThrow = 5;
    var rolls = new int[rollsPerThrow];
    var random = new Random();

    Console.WriteLine("Rolls:");
    for(var i = 0; i < rollsPerThrow; i++){
      rolls[i] = this.Roll(random);
    }

    var rollScore = this.GetScore(rolls);

    this.Throws++;
    this.Score += rollScore;

    Console.WriteLine($"\nScore: \n{rollScore}");
    Console.WriteLine($"Total score: {this.Score} - Throws: {this.Throws} - Average score per throw: {this.GetAveragePerThrow()}");

    return rolls;
  }
}
 

my attempt using Python:

from collections import Counter


def score(values):
    """
    Greed is good scoring

    >>> score((5,1,3,4,1))
    250
    >>> score((1,1,1,3,1))
    1100
    >>> score((2,4,4,5,4))
    450
    """
    count = Counter(values)
    score = 0
    for value, amount in count.items():
        if value > 6:
            raise ValueError("Invalid dice value")
        if value == 1:
            value = 10
        trip, left = divmod(amount, 3)
        score += trip * value * 100
        if value in (10, 5):
            score += left * value * 10
    return score


if __name__ == "__main__":
    import doctest

    doctest.testmod(verbose=True)
 

Haskell:

import Data.Tuple (uncurry)

count :: (a -> Bool) -> [a] -> Int
count pred = length . filter pred

greed :: [Int] -> Int
greed rolls = let groups = [(i, count (==i) rolls) | i <- [1..6]]
                  points3 n = case n of
                    1 -> 1000
                    6 -> 600
                    5 -> 500
                    4 -> 400
                    3 -> 300
                    2 -> 200
                    _ -> 0
                  points1 n = case n of
                    1 -> 100
                    5 -> 50
                    _ -> 0
                  score num count
                    | count == 0 = 0
                    | count >= 3 = points3 num + score num (count - 3)
                    | otherwise = points1 num + score num (count - 1)
              in sum $ map (uncurry score) groups

P.S. Hoogle is really useful. I didn't know about the uncurry function, but I knew I needed a function to do what uncurry does, so I could search for (a -> b -> c) -> (a, b) -> c in hoogle!

 

I love functional programming and feel the whole day like it's Friday

// create random rolls
const roll = () => Array(5)
  .fill()
  .map(() => Math.floor(Math.random() * 6) + 1)

// score a roll
const score = roll => roll
  .reduce((a, v) => a + Math.pow(10, v - 1), 0)
  .toString()
  .padStart(6, "0")
  .split("")
  .reduce((a, v, i) => a + [
     v > 2 ?  600 : 0,
    (v > 2 ?  500 : 0) + v % 3 * 50,
     v > 2 ?  400 : 0,
     v > 2 ?  300 : 0,
     v > 2 ?  200 : 0,
    (v > 2 ? 1000 : 0) + v % 3 * 100
  ][i], 0)

// example scoring
test = (iRoll, iScore) =>
    `${iRoll.join(' ')} = ${score(iRoll)} (${score(iRoll) == iScore ? 'success' : 'fail'})`
console.log(
    test([5, 1, 3, 4, 1], 250),
    test([1, 1, 1, 3, 1], 1100),
    test([2, 4, 4, 5, 4], 450)
)
5 1 3 4 1 = 250 (success)
1 1 1 3 1 = 1100 (success)
2 4 4 5 4 = 450 (success)
 

My python sol :

def play_greed():
    score = [random.randint(1, 6) for _ in range(5)]
    count = [score.count(i) for i in range(1, 6+1)]
    points = 0

    if count[0] < 3:
        points += 100 * count[0]
    elif count[0] == 3: points += 1000

    if count[4] < 3:
        points+= 50 * count[4]
    elif count[4] == 3: points += 500

    if count[1] == 3: points += 200
    elif count[2] == 3 : points += 300
    elif count[3] == 3 : points += 400
    elif count[5] == 3 : points += 600

    return points