Terra Mystica: Cycling Power

28

6

The boardgame Terra Mystica has some very interesting mechanics for one of the primary resources, power. Instead of gaining and spending units of power from a bank, each player starts the game with exactly 12 units of power which are distributed over three "bowls", which are labelled I, II and III. Gaining and spending power then simply shifts power between these bowls:

  • To spend a unit of power, move it from bowl III to bowl I (provided you have a unit in bowl III).
  • When you gain a unit of power, if there is a unit in bowl I, move it to bowl II. If there are no units in bowl I, but there is a unit in bowl II, move it to bowl III. If all units are already in bowl III, nothing happens.
  • When you gain or spend multiple units at once, they are processed one unit at a time.

Here is an example. Say, a player starts with the following power distribution (given in order I | II | III):

5 | 7 | 0

Their power changes as follows if they gain and spend power a few times:

               5 |  7 |  0
Gain  3  ==>   2 | 10 |  0
Gain  6  ==>   0 |  8 |  4   (move 2 power from I to II, 
                              then the remaining 4 from II to III)
Gain  7  ==>   0 |  1 | 11
Spend 4  ==>   4 |  1 |  7
Gain  1  ==>   3 |  2 |  7
Spend 7  ==>  10 |  2 |  0
Gain 12  ==>   0 | 10 |  2   (move 10 power from I to II,
                              then the remaining 2 from II to III)
Gain 12  ==>   0 |  0 | 12   (the two excess units go to waste)

Your task is to compute the result of one such gaining or spending event.

The Challenge

You are given four integers as input. The first three, I, II, III, represent the amount of power in each of the three bowls. They will be non-negative, and they will sum to 12. The fourth number, P, is the amount of power gained or spent, and will be in the inclusive range [-III, 24] (so you may assume that the player will never try to spend more power than they currently can, but they might be gaining more power than they need to move all power into bowl III).

You may take these numbers in any consistent order, as separate arguments, as a list of integers, or as a string containing these integers. You can also take P as one argument, as I, II, III as a separate list argument.

You should output three integers I', II', III' which represent the amount of power in each bowl after P units were gained or spent, following the rules explained above.

You may write a program or a function and use any of the our standard methods of receiving input and providing output.

You may use any programming language, but note that these loopholes are forbidden by default.

This is , so the shortest valid answer – measured in bytes – wins.

Test Cases

I II III P => I' II' III'
5 7 0 3    => 2 10 0
2 10 0 6   => 0 8 4
0 8 4 7    => 0 1 11
0 1 11 -4  => 4 1 7
4 1 7 0    => 4 1 7
4 1 7 1    => 3 2 7
3 2 7 -7   => 10 2 0
10 2 0 12  => 0 10 2
0 10 2 12  => 0 0 12

Martin Ender

Posted 2017-03-06T20:10:37.293

Reputation: 184 808

1I recommend removing gender-specific pronouns and replacing them with gender-neutral ones (or restructuring sentences): gamers do not have to be male. – Greg Martin – 2017-03-06T22:13:09.093

1@GregMartin Of course. Did I catch them all? – Martin Ender – 2017-03-06T22:14:41.430

2Look like it; thank you for thinking about it! Also, is Terra Mystica as awesome as I've heard? – Greg Martin – 2017-03-06T22:16:38.643

4@GregMartin yes. :) – Martin Ender – 2017-03-06T22:16:58.033

1@GregMartin I confirm :) – Leo – 2017-03-06T22:42:44.720

1It looks like Settlers of Catan meets Civilization V. – mbomb007 – 2017-03-06T22:47:36.343

5No power burns from bowl 2? This just feels so incomplete. – moreON – 2017-03-07T04:24:32.190

seconding @moreON. I want answers with sacrificing power! – Sparr – 2017-03-07T05:34:20.913

@GregMartin: No. It's móre awesome than you've heard. – Erik – 2017-03-07T06:33:51.397

@moreON I thought a while about including it but couldn't find a good way to do so that didn't feel tacked on. – Martin Ender – 2017-03-07T07:06:53.163

I'm tempted to try this in BrainFuck this evening. No promises that I'll ever actually accomplish a solution, but given that BF doesn't support negative numbers, would you be OK with me using a sign-and-magnitude representation for P? – ymbirtt – 2017-03-07T08:47:36.107

@ymbirtt Yes, but you could also treat the tape cells as 2's complement so 255 would represent -1 etc. – Martin Ender – 2017-03-07T08:49:27.240

I'm not quite sure which would make my life easier at this point, but I'll bear both options in mind. – ymbirtt – 2017-03-07T08:51:59.440

Is it safe to assume no one will try and spend more power than they have? – aslum – 2017-03-07T15:53:49.220

@aslum "(so you may assume that the player will never try to spend more power than they currently can, but they might be gaining more power than they need to move all power into bowl III)" – Martin Ender – 2017-03-07T15:55:37.110

@ymbirtt I'm pretty sure there are BF implementations that have negatives, but you can't take a negative number as input. – mbomb007 – 2017-03-07T16:38:07.773

@mbomb007, The standard I/O formats say nothing about BF specifically, but does allow stack-based languages to start with their input on the stack and leave their output on the stack. It also allows TMs to start with their input on the tape and leave their output on the tape. I think it's reasonable for BF to start with its input on the tape and leave its output on the tape, but obviously OP can have the final say here.

– ymbirtt – 2017-03-07T16:44:32.453

@ymbirtt This has already been discussed (can't find where). BF is not a Turing machine. Maybe Martin can give his opinion, since I think he's the one that told me that. Considering that BF's standard method of I/O is , and ., those should be used. – mbomb007 – 2017-03-07T16:53:28.080

Answers

6

C, 97 94 bytes

f(i,j,k,n){for(;n;n-=n/abs(n))n<0?k?++i+--k:0:i?++j+--i:j?++k+--j:0;printf("%d %d %d",i,j,k);}

In ungolfed form:

f(i, j, k, n) {
    while (n) {
        if (n < 0) {
            if (k) {
                ++i; --k;
            }
            ++n;
        } else {
            if (i) {
                ++j; --i;
            }
            else if (j) {
                ++k; --j;
            }
            --n;
        }
    }
    printf("%d %d %d", i, j, k);
}

Steadybox

Posted 2017-03-06T20:10:37.293

Reputation: 15 798

6

Mathematica, 52 bytes

{x=#-#4~Min~#,y=Max[#2+#-Abs[#4~Max~0-#],0],12-x-y}&

This is an unnamed function that takes a list {I, II, III, P} as input and returns a list {I', II', III'}.

A closed-form solution. It doesn't really feel optimal yet...

Martin Ender

Posted 2017-03-06T20:10:37.293

Reputation: 184 808

Thought I could shorten, but {##,12-+##}&[#-#4~Min~#,Max[#2+#-Abs[#4~Max~0-#],0]]& is a byte longer. I like the 12-+## though. – Greg Martin – 2017-03-06T22:30:58.123

1@GregMartin I tried the same thing :) – Martin Ender – 2017-03-06T22:35:32.013

5

Python 2, 104 bytes

def f(i,d,t,g):
 x=min(i,g);i-=x;q=g>0;g-=x
 if q:d+=x;x=min(d,g);g-=x;d-=x;t+=x
 else:t+=x
 print i,d,t

Try it online

Ungolfed:

def f(i,d,t,g):
 if g>0:
    x=min(i,g)
    g-=x
    i-=x
    d+=x    
    x=min(d,g)
    g-=x
    d-=x
    t+=x
 else:
    x=min(i,g)
    g-=x
    i-=x
    t+=x
 print(i,d,t)

mbomb007

Posted 2017-03-06T20:10:37.293

Reputation: 21 944

5

Röda, 100 94 bytes

f a,b,c,p{{c+=p;a-=p}if[p<0]else{{a--;b++;p--}while[p*a>0];{b--;c++;p--}while[p*b>0]};[a,b,c]}

Ungolfed:

f a,b,c,p {
    if [ p < 0 ] do
        c += p
        a -= p
    else
        { a-=1; b+=1; p-=1 } while [ p > 0 and a > 0 ]
        { b-=1; c+=1; p-=1 } while [ p > 0 and b > 0 ]
    done
    return a, b, c
}

fergusq

Posted 2017-03-06T20:10:37.293

Reputation: 4 867

Doesn't Röda have the ++ and -- operators? – user41805 – 2017-03-07T05:23:41.677

@KritixiLithos Thanks! Yes it does. – fergusq – 2017-03-07T07:15:50.423

5

Haskell, 58 bytes

f(a,b,c)d|m<-min a d,z<-min(c+d-max 0 m)12=(a-m,b+c+m-z,z)

The intermediate value m denotes the amount of power going from (or to, if negative) the first bowl, z denotes the amount of power in the third bowl after the action. A last minute one byte optimization changed the old expression for the second bowl from 12-a+m-z using the identity a+b+c=12.

The natural result type is a triple for the bowls, so the input also takes the bowls as a triple and the power change as a second argument. This allows to handle all test cases with one application of scanl:

*Main> scanl f (5,7,0) [3,6,7,-4,0,1,-7,12,12]
[(5,7,0),(2,10,0),(0,8,4),(0,1,11),(4,1,7),(4,1,7),(3,2,7),(10,2,0),(0,10,2),(0,0,12)]

Christian Sievers

Posted 2017-03-06T20:10:37.293

Reputation: 6 366

4

JavaScript, 61 59 bytes

(a,b,c,d)=>[r=a-d>0?a-d:0,s=r?b:b-d+2*a>0?b-d+2*a:0,12-r-s]

Try it online!

fəˈnɛtɪk

Posted 2017-03-06T20:10:37.293

Reputation: 4 166

3

GNU sed, 66 bytes

Includes +1 for -r

/-/!{:
s/1,(.* )1/,1\1/
t}
s/(.*)(1+) -\2/\2\1/
s/(,,1{12}).*/\1/

Uses unary (see this consensus).

Try it online!

/-/!{                  # If there is not a '-'
  :                    # start loop
  s/1,(.* )1/,1\1/     # move a 1 from before a ',' to after the ',' for every 1 after the space
                       # sed reads left to right, so this takes everything from the first bowl before starting on the second
  t                    # loop if something changed
}                      # end if
s/(.*)(1+) -\2/\2\1/   # take all of the 1s from after a '-' and move them to the begining.
                       # at the same time, remove that many 1s from the 3rd bowl
s/(,,1{12}).*/\1/      # remove everything after 12 1s in the third bowl

Riley

Posted 2017-03-06T20:10:37.293

Reputation: 11 345

3

Retina,  46  41 39 38 bytes

Thanks to Martin Ender for multiple helpful suggestions!

+`1,(.*¶)1
,1$1
(.*)(1+)¶-\2$
$2$1
G`,

Takes input in unary. The first line contains the amounts of power in the three bowls, comma separated, the second line the amount of power to cycle.

Test suite - Takes all inputs on a single line and converts from decimal to unary and vice-versa for convenience of use.

Explanation

+`1,(.*¶)1
,1$1

Positive case: we repeatedly remove the leading 1 from the second line, and move a 1 from the first non-empty bowl to the following one, as long as this operation is possible (i.e. the number of power to cycle is non-zero and not all power is in the third bowl). The s modifier means single-line, allowing . to match also the newline.

(.*)(1+)¶-\2$
$2$1

Negative case: done all in one step, moving the amount of power indicated by the last input from the third to the first bowl. This will also remove the line containing the negative amount of power to move.

G`,

Keep (grep) only lines containing a comma. This will get rid of the eventual remains of the first line.

Leo

Posted 2017-03-06T20:10:37.293

Reputation: 8 482

3

Python 2, 91 bytes

Based on this answer

def f(i,d,t,g):
 x=min(i,g);i-=x
 if g>0:y=min(d+x,g-x);d+=x-y;t+=y
 else:t+=x
 print i,d,t

Try it online

Ungolfed:

def f(i,d,t,g):
 if g>0:
    x=min(i,g)
    y=min(d+x,g-x)
    i-=x
    d+=x-y
    t+=y
 else:
    x=min(i,g)
    i-=x
    t+=x
 print(i,d,t)

Alejandro Castillo

Posted 2017-03-06T20:10:37.293

Reputation: 31

Welcome to the site! – James – 2017-03-08T17:50:10.707

2

Batch, 87 bytes

@set/a"i=%4-%1,j=%4*(-%4>>5)-%2-2*i*(-i>>5),i*=i>>5,j*=j>>5,k=12-i-j
@echo %i% %j% %k%

Use the following formulae:

I' = min(I - P, 0)
II' = min(II + min(P, 0) - 2 * min(P - I, 0), 0)
III' = 12 - I' - II'

Since Batch doesn't have a less than operator, I calculate i = min(-i, 0) using i*=i>>5.

Neil

Posted 2017-03-06T20:10:37.293

Reputation: 95 035

2

Perl 6, 99 bytes

->\a,\b,\c,\d{d>0??[»+»] (a,b,c),|(|((-1,1,0)xx a),|((0,-1,1)xx a+b),|(0 xx*))[^d]!!(a- d,b,c+d)}

Let a, b, and c be the number of starting tokens in bowls I, II, and III respectively. Then, for the adding power case, a list is created that contains a copies of the triplet (-1, 1, 0), followed by a + b copies of the triplet (0, -1, 1), followed by infinite copies of 0. The first d elements of this list, d being the amount of power to add, are added elementwise to the starting power distribution.

For subtracting power (negative d), a simple closed form is used: (a - d, b, c + d).

Sean

Posted 2017-03-06T20:10:37.293

Reputation: 4 136

2

tinylisp, 134 bytes

(d f(q((x y z p)(i p(i(l p 0)(f(s x p)y(a z p)0)(i x(f(s x 1)(a y 1)z(s p 1))(i y(f x(s y 1)(a z 1)(s p 1))(f x y z 0))))(c x(c y(c z(

Defines a function f that takes four arguments, the three bowls (x y z) and the amount of power transacted (p), and returns a list of the three bowls after the transaction. Here's a properly spaced version with all test cases: Try it online!

(d f                         Define f to be
 (q(                          a quoted two-item list (which acts as a function):
  (x y z p)                    Arglist: the three bowls x y z and power p
  (i p                         If p is nonzero
   (i (l p 0)                   then if p is negative (spending power)
    (f(s x p)y(a z p)0)          then take -p from z, add -p to x, and recurse with p=0
    (i x                         else (gaining power), if x is nonzero
     (f(s x 1)(a y 1)z(s p 1))    then take 1 from x, add to y, decrement p and recurse
     (i y                         else if y is nonzero
      (f x(s y 1)(a z 1)(s p 1))   then take 1 from y, add to z, decrement p and recurse
      (f x y z 0))))               else no moves possible; recurse with p=0
   (c x(c y(c z())))))))        else (p=0), cons x y z into a list and return it

DLosc

Posted 2017-03-06T20:10:37.293

Reputation: 21 213