11
\$\begingroup\$

Part of Code Golf Advent Calendar 2022 event. See the linked meta post for details.


Happy Hanukkah!

A beloved Hanukkah tradition for many, the game of Dreidel (s'vivon in Hebrew) is a betting game (for money or for chocolate gelt (coins)) centered on the titular spinning tops called dreidels. Dreidels have four sides: נ (nun), ג (gimel), ה (hei), and ש (shin), an acronym for the phrase "neis gadol haya sham": a great miracle happened there, referring to the famous Hanukkah miracle.

The game starts with an ante of one coin put forth by all players. Then, players take turns spinning the dreidel and lose or gain money according to the following rules:

  • נ (nun): Do nothing
  • ג (gimel): Win all the money in the pot
  • ה (hei): Win half the money in the pot, rounding up
  • ש (shin): Add one coin to the pot

If at any point the pot becomes empty, all players put forth one coin. If a player must put forth a coin and they have none, they are out of the game. The game ends when players agree to stop or when only one player is remaining.


Task

Your task is to implement the game of Dreidel.

Input:

  • (mapping: str -> int) A number of players, guaranteed to be ≥2, each represented by a string. Each player maps to the starting number of coins for that player, guaranteed to be ≥1.
  • (int) The number of rounds, guaranteed to be ≥1. (In each round, every player who's still in the game will spin the dreidel once).

Output:

  • (mapping: str -> int) Each player and their corresponding final number of coins at the end of the game.

Rules:

  • Standard rules of apply. Shortest answer in bytes wins!
  • Dreidel outcomes must be random, and must appear with the same probability (25% for each outcome).
  • Any reasonable form of input or output is allowed. For instance, to represent the mapping of players to coins, one can use a Python-style dictionary, an array of pairs, etc. The mapping need not have a consistent ordering.
    • All players must be represented in the output, even if they have no money left.
  • You can use whatever order for the players within each round is easiest, as long as each player who's still in the game spins once per round.

Notes:

  • It is not guaranteed that the total number of coins at the end of the game is equal to the total number of coins at the beginning of the game (there may be some left over in the pot.)

Examples: (bear in mind that results are random)

>>> dreidel({"Abraham": 5, "Batya": 5, "Claire": 5}, 3)
{"Abraham": 3, "Batya": 8, "Claire": 4}

>>> dreidel({"David": 3, "Eliana": 9, "Fievel": 2, "Gabriella": 2}, 15)
{"David": 0, "Eliana": 16, "Fievel": 0, "Gabriella": 0}
\$\endgroup\$
17
  • \$\begingroup\$ What probable that they agree to stop? \$\endgroup\$
    – l4m2
    Dec 19, 2022 at 5:31
  • 1
    \$\begingroup\$ They agree to stop after n rounds (with 100% probability), where n is whatever the input is. \$\endgroup\$
    – AAM111
    Dec 19, 2022 at 6:09
  • 1
    \$\begingroup\$ Player take turns... Then who will be the first? Many language have a dictionary type without ordering. \$\endgroup\$
    – tsh
    Dec 19, 2022 at 7:35
  • 1
    \$\begingroup\$ I think it would be worth adding all valid outputs for some small cases. \$\endgroup\$ Dec 19, 2022 at 13:31
  • 1
    \$\begingroup\$ @Shaggy no, they should be included. \$\endgroup\$
    – AAM111
    Dec 19, 2022 at 15:19

5 Answers 5

4
\$\begingroup\$

Python, 197 195 bytes

-2 bytes thanks to @Arnauld using -~p//2 instead of p//2+p%2

from random import*
def f(n,c,m,i=0,p=0,s=0):
 l=len(c);v=-~c[x:=i%l]and[-1,0,p,~-p//2][i//l&(s<0)and randrange(4)];c[x]+=v
 if~-l-c.count(-1)and-~i<m*l:f(n,c,m,i+1,w:=p-v*(c[x]>=0),[s-1,l][w<1])

Attempt This Online!

Takes in the mapping as a list of names n and a list of coin counts c which are the same length, along with a number of rounds r.

Modifies the list of coins in place; the values indicate the number of coins, except for if c[i] is -1, which indicates that person n[i] is out of the game.

It works recursively, storing the following between calls (in addition to the current number of coins):

  • i: How many players have played so far
  • p: the size of the pot
  • s: If positive, how many players need to contribute to the pot (set to len(c) when the pot is empty, otherwise decreased by one every call)
\$\endgroup\$
1
  • 1
    \$\begingroup\$ You can use -~p//2 instead of p//2+p%2. \$\endgroup\$
    – Arnauld
    Dec 19, 2022 at 21:28
4
\$\begingroup\$

Python 3, 317 303 311 318 294 270 bytes

def f(p,r):
 m=len(p)
 for k in p:p[k]-=1
 for _ in range(r):
  for i in p:
   if p[i]<0:continue
   x=randrange(4)
   if x<1:p[i]+=m;m=0
   if x<2:p[i]-=~m//2;m+=~m//2
   if x>2:m+=p[i]>0;p[i]-=1
   if m<1:
    for j in p:m+=p[j]>0;p[j]-=1
 return p
from random import*

Try it online!

  • Thanks to Neil for spotting a bug (303 -> 311)
  • Thanks to AAM111 for cutting out the import math and spotting a bug (311 -> 318)
  • Thanks to c-- for some golfing suggestions (318 -> 294 and 294 -> 270)

Players that are out are shown as having negative coins.

Explanation

def f(p,r):                                # Create a function, f, which takes in the player dictionary, p, and the number of rounds, r
 m=len(p)                                  # Assign the money pot, m, to the number of players
 for k in p:p[k]-=1                        # And subtract one from each of the players (each player gives one coin to the pot)
 for _ in range(r):                        # Loop r times (r rounds)
  for i in p:                              # Go through each of the players
   if p[i]<0:continue                      # If they are out of the game (money = -1), skip their turn
   x=randrange(4)                          # Spin the dreidel (0 to 3)
   if x<1:0                                # If it's 0, do nothing
   elif x<2:p[i]+=m;m=0                    # If it's 1, add all the money from m to that player and set m to 0
   elif x<3:a=(int(m)+m%2)//2;p[i]+=a;m-=a # If it's 2, add half the money to that player and reduce m
   else:m+=1;p[i]-=1                       # If it's 3, add 1 to m and remove 1 from the player
   if m<1:                                 # If m is 0
    for j in p:                            # Go through each player
     if p[j]>0:p[j]-=1;m+=1                # If they're not out of money, reduce by one and add that to m
     else:p[j]=-1                          # Otherwise, set their money to -1 (they are now out)
 return p                                  # At the end of all the rounds, return the final dictionary
from random import*                        # Import the random library for the dreidel
\$\endgroup\$
14
  • \$\begingroup\$ Don't you have to remove money from the player for 3? \$\endgroup\$
    – Neil
    Dec 19, 2022 at 12:41
  • \$\begingroup\$ @Neil thanks, I misunderstood the challenge. \$\endgroup\$
    – The Thonnu
    Dec 19, 2022 at 12:54
  • 1
    \$\begingroup\$ @JonathanAllan thanks, I think I've fixed it now thanks to Neil's spot. \$\endgroup\$
    – The Thonnu
    Dec 19, 2022 at 12:57
  • \$\begingroup\$ I don't think players are out of money if they've put an ante in at the start and have not yet spun the Dreidel (e.g. {"Abraham": 1, "Batya": 1, "Claire": 1}, 1 always returns {'Abraham': 0, 'Batya': 0, 'Claire': 0}). \$\endgroup\$ Dec 19, 2022 at 13:30
  • \$\begingroup\$ @JonathanAllan I'm not sure. Probably best to ask OP. \$\endgroup\$
    – The Thonnu
    Dec 19, 2022 at 13:40
3
\$\begingroup\$

Charcoal, 112 bytes

≔Lθζ≧×LθηUMθ⊖ιW∧η⊖LΦ謋κ⁰«≔§Eθλ±ηεF¬‹§θε⁰«≔‽⁴κ≔⎇‹κ²κ÷ζ~κκ§≔θε⁻§θεκF¬‹§θε⁰≧⁺κζ»F¬ζ«≔LΦθ›λ⁰ζUMθ⊖λ»≦⊖η»UMθ⎇›⁰ι⁰ι⭆¹θ`

Attempt This Online! Link is to verbose version of code. Explanation:

≔Lθζ

Each player puts one coin into the pot.

≧×Lθη

The total potential number of plays is the number of players multiplied by the number of rounds.

UMθ⊖ι

Subtract one coin from each player (which they put into the pot above).

W∧η⊖LΦ謋κ⁰«

Repeat while there are still plays left and multiple players playing.

≔§Eθλ±ηε

Get the current player.

F¬‹§θε⁰«

If they are not out of the game:

≔‽⁴κ

Spin the dreidel, producing a random number from 0 to 3.

≔⎇‹κ²κ÷ζ~κκ

If it is less than 2 then the player loses this amount, otherwise they lose the pot divided by the bitwise not of the random number. Since the latter is negative, they win either the pot or half of the pot rounded up.

§≔θε⁻§θεκ

Subtract the loss from the player.

F¬‹§θε⁰≧⁺κζ

If the player is still in the game then add the player's loss to the pot.

»F¬ζ«

If the pot is now zero:

≔LΦθ›λ⁰ζ

Put a coin into the pot for each player that is still in the game.

UMθ⊖λ

Subtract one coin from each player.

»≦⊖η

Move on to the next player.

»UMθ⎇›⁰ι⁰ι

Set the coins of players that are out of the game to 0.

⭆¹θ

Output the final number of coins for each player.

I wrote this version before all of the rules had been clarified, so it appears that I might be able to save 16 bytes by considering the players in reverse order, not refreshing the pot if the first player scoops it on their very last spin, and outputting arbitrary negative values for players who are out of the game:

≔⁰ζ≧×LθηW∧η⊖LΦ謋κ⁰«F¬ζ«≔LΦθ›λ⁰ζUMθ⊖λ»≦⊖η≔§EθληεF¬‹§θε⁰«≔‽⁴κ≔⎇‹κ²κ÷ζ~κκ§≔θε⁻§θεκF¬‹§θε⁰≧⁺κζ»»⭆¹θ

Attempt This Online! Link is to verbose version of code.

\$\endgroup\$
3
\$\begingroup\$

JavaScript, 167 165 164 bytes

Been a long, long time since I tried golfing anything this complex in JS. So much so that I made a complete mess of things in my initial solution! This version seems to work properly, although it's pretty hideous.


Takes the player data as a 2D-array. Call with f(rounds)(players). Mutates the original input array. Remove the &&--n to ignore the rounds and allow the game to play through to the last player standing.

(n,p=o=0)=>g=a=>a.map(x=>x.o||(p||a.map(x=>x[1]?--x[++p,1]:x.o||=++o),x.o||(r=[0,p,p+1>>1,-1][Math.random()*4|0],r+1||x[1]?x[p-=r,1]+=r:x.o=++o),x))[o+1]&&--n&&g(a)

Attempt This Online!

Saved 2 bytes thanks to l4m2

Explanation

(n,                           // Take the number of rounds as n
p=o=0)=>                      // Initialise p (the pot) and o (players out of the game) as 0
g=a=>                         // Named function taking the array of players as a
a.map(x=>                     // Map each x in a
  x.o||(                      // Return property o of x (initially undefined) OR
    p||a.map(x=>              // If p is 0 then map each x in a
      x[1]?                   // If the 2nd element (coins) of x is not 0
       --x[++p,1]             // Decrement it and increment p
       :x.o||=++o             // Else get the OR of x.o and o incremented, eliminating the current player
    ),x.o||(                  // Return x.o OR
      r=                      // Assign to r
        [0,p,p+1>>1,-1]       // Array of all possible results
        [Math.random()*4|0],  // Get a random element
      r+1||x[1]?              // If r is not -1 or the player has coins
       x[p-=r,1]+=r           // Increment their coins by r and decrement p by r
       :x.o=++o               // Else increment o and assign it to x.o, eliminating the current player
    )                         //
  ,x)                         // Return x
)[o+1]                        // If there are at least 2 players left in the game
&&--n                         // And we have rounds left to play
&&g(a)                        // Then call g again
\$\endgroup\$
2
  • 1
    \$\begingroup\$ p/2+.5|0=>p+1>>1 \$\endgroup\$
    – l4m2
    Dec 20, 2022 at 10:43
  • \$\begingroup\$ Also if the (name,coin) pair not used as array, why not use object instead? \$\endgroup\$
    – l4m2
    Dec 20, 2022 at 10:46
1
\$\begingroup\$

C -m32, 233 220 bytes + 4 bytes = 224 bytes

-9 bytes thanks to ceilingcat

(linebreaks for "readability")

m;k;i;j;l;f(p,r)int*p;{
for(l=wcslen(p);k<l;k+=2)p[k]--;
for(srand(p);r--;m=l/2)
for(i=0;i<l;k=rand(i+=2)%4)
if(~p[i])if(k?k<2?p[i]+=m,m=0:k<3?p[i]+=j=m-m/2,m-=j:++m,p[i]--:0,!m)
for(j=0;j<l;j+=2)p[j]>0?p[j]--,m++:(p[j]=-1);}

Try it online!

This is basically a port of The Thonnu's python answer.

The input is an int[] of the form {5, "Abraham", 5, "Batya", 5, "Claire", 0};, which is why the loops count by 2. This array will be modified by the function. We can get the length of that with wcslen.

Ungolfed old version:

f(int *ptr, int rounds) {
    int length = wcslen(ptr);
    int money = length / 2; // one for each player
    srand(ptr);
    for (int i = 0; i < length; i += 2) ptr[i]--; // Decrement each by one
    for (; rounds--; ) {
        for (int i = 0; i < length; i += 2) {
            int k = rand() % 4;
            int half_pot = money / 2 + money % 2;
            if (ptr[i] >= 0) {
                k ?
                    k < 2 ? ptr[i] += money, money = 0 :
                    k < 3 ? ptr[i] += half_pot, money -= half_pot
                    : ++money, ptr[i]--
                : 0;
                if (!money) {
                    for (int j = 0; j < length; j += 2) {
                        if (ptr[j] > 0) {
                            ptr[j]--;
                            money++;
                        } else {
                            ptr[j] = -1;
                        }
                    }
                }
            }
        }
    }
}
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.