14
\$\begingroup\$

In association football (also known as soccer), a penalty shoot-out is the second tie-breaker measure that may be used in a match which can't end in a tie, after extra time (i.e. association football overtime).

In a penalty shoot-out, the main referee tosses a coin to determine at which goal the shoot-out happens, and then tosses another coin to determine which team starts first. However, the only thing relevant to this challenge is what happens then, described below.

Each team has 5 penalties available at start, and the penalty score is 0-0. If, at any point, a team's remaining penalties aren't enough to change the currently winning team, the shoot-out stops.

If there are no remaining penalties, but both teams' points are equal, an additional penalty is granted to both teams. This is repeated until the points aren't equal.

After the shoot-out stops, the team with the largest penalty score wins the game.

Challenge

Your challenge is, given two lists A and B representing which penalties team A and team B scored respectively, to determine if they represent a valid penalty shoot-out. A shoot-out is valid if the state represented by the input can be reached, regardless of whether the winning team can be determined. Note that you possibly have to test for both scenarios (Team A starting, Team B starting), since, if the state described in the input is reachable for at least one scenario, the input is valid. If the lists' lengths are different, the team represented by the longer one starts first (it can only have one more element than the other one, and the shorter list's team can't start, since then the longer list's team would shoot two penalties in a row, as the shorter list will be prematurely depleted).

Detailed examples

You can skip to the Rules section below, these are only to help solving the challenge.

Suppose you get this shoot-out as input, where - means no goal was scored and X means a goal was scored (it's invalid):

Team A: - X X X X
Team B: - - - - X

Assuming team A starts first:

Team A: - (0 - 0) (max possible score 4 - 5)
Team B: - (0 - 0) (max possible score 4 - 4)
Team A: X (1 - 0) (max possible score 4 - 4)
Team B: - (1 - 0) (max possible score 4 - 3)
Team A: X (2 - 0) (max possible score 4 - 3)
Team B: - (2 - 0) (max possible score 4 - 2)
Team A: X (3 - 0) (max possible score 4 - 2)
Team A already has a higher score than B could ever have, but the input hasn't
ended yet, so it's invalid if team A is first.

Assuming team B starts first:

Team B: - (0 - 0) (max possible score 5 - 4)
Team A: - (0 - 0) (max possible score 4 - 4)
Team B: - (0 - 0) (max possible score 4 - 3)
Team A: X (1 - 0) (max possible score 4 - 3)
Team B: - (1 - 0) (max possible score 4 - 2)
Team A: X (2 - 0) (max possible score 4 - 2)
Team B: - (2 - 0) (max possible score 4 - 1)
Team A already has a higher score than B could ever have, but the input hasn't
ended yet, so it's invalid if team B stars first.

The input is invalid no matter which team starts first, so it's considered
invalid.

On the contrary, here is a valid example:

Team A: X X X
Team B: - - -

Assuming team A starts first:

Team A: X (1 - 0) (max possible score 5 - 5)
Team B: - (1 - 0) (max possible score 5 - 4)
Team A: X (2 - 0) (max possible score 5 - 4)
Team B: - (2 - 0) (max possible score 5 - 3)
Team A: X (3 - 0) (max possible score 5 - 3)
Team B: - (3 - 0) (max possible score 5 - 2)
It can be determined that team A wins, however the input has ended, so it's
valid if team A starts first. Therefore, the input is valid.

Another example, this time with extra penalties:

Team A: X - X - - - X -
Team B: - X X - - - X X

Assuming team A starts first:

Team A: X (1 - 0) (max possible score 5 - 5)
Team B: - (1 - 0) (max possible score 5 - 4)
Team A: - (1 - 0) (max possible score 4 - 4)
Team B: X (1 - 1) (max possible score 4 - 4)
Team A: X (2 - 1) (max possible score 4 - 4)
Team B: X (2 - 2) (max possible score 4 - 4)
Team A: - (2 - 2) (max possible score 3 - 4)
Team B: - (2 - 2) (max possible score 3 - 3)
Team A: - (2 - 2) (max possible score 2 - 3)
Team B: - (2 - 2) (max possible score 2 - 2)
First 5 penalties result in a tie, so we move on to extra penalties.
Team A: -, Team B: - (2 - 2)
Team A: X, Team B: X (3 - 3)
Team A: -, Team B: X (3 - 4)
It can be determined that team B wins, however the input has ended, so it's
valid if team A starts first. Therefore, the input is valid.

Here is a valid input where it's too early to determine the winner:

Team A: X X - -
Team B: - X - X

Assuming team A starts first:

Team A: X (1 - 0) (max possible score 5 - 5)
Team B: - (1 - 0) (max possible score 5 - 4)
Team A: X (2 - 0) (max possible score 5 - 4)
Team B: X (2 - 1) (max possible score 5 - 4)
Team A: - (2 - 1) (max possible score 4 - 4)
Team B: - (2 - 1) (max possible score 4 - 3)
Team A: - (2 - 1) (max possible score 3 - 3)
Team B: X (2 - 2) (max possible score 3 - 3)
The input has ended before the winner can be determined, so it's valid if team A
starts first. Therefore, the input is valid.

Finally, here is an input where the lists' lengths differ:

Team A: - - -
Team B: X X - X

Since team B shot more penalties, it starts first:

Team B: X (0 - 1) (max possible score 5 - 5)
Team A: - (0 - 1) (max possible score 4 - 5)
Team B: X (0 - 2) (max possible score 4 - 5)
Team A: - (0 - 2) (max possible score 3 - 5)
Team B: - (0 - 2) (max possible score 3 - 4)
Team A: - (0 - 2) (max possible score 2 - 4)
Team B: X (0 - 3) (max possible score 2 - 4)
It can be determined that team B wins, however the input has ended, so it's
valid.

Rules

  • The team that shoots first can be either A or B, you can't assume one will always shoot first.
  • The lists will either have the same length, or their lengths will differ by one.
  • You may choose any two distinct and consistent values to represent scored/unscored penalties.
  • The lists can also be represented as integers converted from bijective base 2, strings or your language's native list format. If a bijective base 2 format is chosen, the input rules apply to the numbers converted to bijective base 2 (so digits 1 and 2 can either mean scored and unscored or unscored and scored respectively). Regular binary is not allowed, as one can't determine the presence of leading zeroes in the intended binary representation.
  • This is , so the shortest solution wins. However, please don't be discouraged from answering even if it seems like your language can't "beat the specialized ones".

Test cases

In these test cases, a 0 will represent a no-goal, and a 1 will represent a goal.

Format:

[Team A], [Team B]

Valid inputs:

[], []
[0], [0]
[0], [1]
[1], [1]
[0], []
[1, 1, 1, 1], [0, 0, 1, 1]
[0, 1, 1, 1, 1], [0, 1, 1, 0]
[0, 0, 0, 0, 1], [0, 0, 0, 1, 0]
[0, 0, 0, 0, 1], [0, 0, 0, 1]
[1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1]
[0, 1, 1, 1, 1], [0, 1, 1, 0, 1]
[1, 1, 1], [0, 0, 0]
[1, 1, 1, 1], [0, 0, 1]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Invalid inputs:

[0, 1, 1, 1, 1], [0, 1, 1, 0, 0]
[0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 1, 0]
[0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 1]
[1, 1, 1, 0], [0, 0, 0]
[1, 1, 1, 1], [0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
[1, 0, 1, 0, 1], [0, 1, 0, 1, 0, 1]
[0, 0, 0, 0, 1], [0, 1, 1, 1, 0]
\$\endgroup\$
  • \$\begingroup\$ Can I return 0 or false for for invalid, and return true for valid? \$\endgroup\$ – Embodiment of Ignorance Mar 6 at 21:56
  • \$\begingroup\$ @EmbodimentofIgnorance "You may choose any two distinct and consistent values to represent scored/unscored penalties." The exact values don't matter, but there must only be two values. \$\endgroup\$ – Erik the Outgolfer Mar 6 at 21:57
  • \$\begingroup\$ I assume [[0,0],[1,1]] (or any test case where one of the two inner lists has 2 items) is truthy, since the game is still going on (just like the test cases with [[0],[1]] or [[0],[]] are still in progress)? \$\endgroup\$ – Kevin Cruijssen Mar 7 at 8:24
  • \$\begingroup\$ @KevinCruijssen Yes, because nobody can determine who will win, the outcome might be 3-2. ;-) \$\endgroup\$ – Erik the Outgolfer Mar 7 at 13:13
3
\$\begingroup\$

JavaScript (ES6),  117 112  109 bytes

Takes input as (a)(b), using \$1\$ for unscored and \$2\$ for scored. Returns \$0\$ or \$1\$.

a=>b=>!(g=(a,b,P=Q=i=5)=>(p=a[5-i])|(q=b[5-i])&&(--i<0?P-Q:P-Q>i|Q+q-P-p>i&p<2)|g(a,b,P+p,Q+=q))(a,b)|!g(b,a)

Try it online!

Commented

a => b =>                   // given a[] and b[]
  !(g = (                   // g is a recursive function taking:
      a,                    //   the results a[] of the team that plays first
      b,                    //   the results b[] of the team that plays second
      P =                   //   the cumulated goals P of the 1st team (relative value)
      Q =                   //   the cumulated goals Q of the 2nd team (relative value)
      i = 5                 //   a counter i
    ) =>                    // and returning a truthy value if something goes wrong
      (p = a[5 - i]) |      // if either the first team
      (q = b[5 - i]) && (   // or the second team is playing this round:
        --i < 0 ?           //   decrement i; if we've played more than 5 penalties:
          P - Q             //     do we already have a goal difference?
        :                   //   else:
          P - Q > i |       //     was the 1st team already guaranteed to win?
          Q + q - P - p > i //     or is the 2nd team now guaranteed to win
          & p < 2           //     while the 1st team failed its last attempt?
      ) |                   //
      g(                    //   do a recursive call:
        a, b,               //     pass a[] and b[] unchanged
        P + p,              //     update P
        Q += q              //     update Q
      )                     //   end of recursive call
  )(a, b) |                 // try g(a, b)
  !g(b, a)                  // try g(b, a); return 1 if at least one of them is falsy
\$\endgroup\$
2
\$\begingroup\$

Python 2, 176 169 171 169 bytes

-2 bytes thanks to @Kevin Cruijssen

exec"h=lambda a,b,m:m-%s/2>abs(sum(a)-sum(b));f=lambda a,b:a[5#==b[5#and h(a[:5],b[:5],6)if %s>10else h(a,b,7)and h(a[#,b[#,6)".replace("#",":~-%s/2]")%(("len(a+b)",)*6)

Try it online! (Including some extra test cases not listed above.)

Creates a function f that takes two arguments (the two lists of scored/unscored penalties) and returns True if the scores are possibly valid and False otherwise.

Partial explanation:

First of all, the exec construction is just a way to save a few bytes by not having to repeat the expression len(a+b) more than once. The above piece of code is equivalent to the following:

Update: the new and improved answer is the same byte count with or without exec trickery, so in the interests of simplicity I have removed it.

Update 2: The new bugfixed version includes even more string compression via substitution and exec. Yes, I use % formatting and a .replace on the same string. The code above is equivalent to:

h=lambda a,b,m:m-len(a+b)/2>abs(sum(a)-sum(b))
f=lambda a,b:a[5:(len(a+b)-1)/2]==b[5:~-len(a+b)/2]and h(a[:5],b[:5],6)if len(a+b)>10else h(a,b,7)and h(a[:(~-len(a+b)/2],b[:(len(a+b)-1)/2],6)

The main idea of this answer is to frame the question in terms of "half-points": each successful kick is a half-point and each failed one is a negative half-point. For a set of scores with length \$<=5\$ to be continuable (not len(a+b)>10), the total number of kicks left must have been greater than or equal to the half-point margin between the two teams. When one team has kicked an extra time, a half-point must be subtracted from the margin for various reasons, so this can be simplified by integer-dividing both sides of the equation by two. This is the function h in the code (with the argument m equal to 6).

However, to be a valid set of scores, an input need not be strictly continuable, but it must have been continuable before the last kick to have been made. This condition is equivalent to saying that it must 1) have been continuable the last time both sides had kicked the same number of times and 2) currently be within two half-points of being continuable — which is where the final argument to h comes in: h(a[:~-len(a+b)/2],b[:~-len(a+b)/2],6) tests condition 1) and h(a,b,7) (the 7 representing an extra two allowable half-points in the margin) tests condition 2).

The case where each team has kicked a maximum of five times has thus been settled. (Explanation to be continued for the other case.)

In terms of low-level golfing, I'm doubt there's too much to shave off, but algorithmically it could probably be done quite a bit more simply.

\$\endgroup\$
  • 1
    \$\begingroup\$ You can golf (%s-1)/2 to ~-%s/2 to save 2 bytes. \$\endgroup\$ – Kevin Cruijssen Mar 7 at 8:19
  • \$\begingroup\$ @KevinCruijssen Thanks! \$\endgroup\$ – Aidan F. Pierce Mar 7 at 17:48
1
\$\begingroup\$

Jelly, 62 54 49 bytes

ṫ⁵Ṗm2¬Ạ
N§ỤḢƊ¦LÞṚZFĵ12R:2U_ṁḣ⁵ṫ-N<Ø.ẠaÇoL<3
ṚÇoÇ

Try it online!

ṫ⁵Ṗm2¬Ạ # helper function to determine whether
        # even indices at or beyond 10 are zero
ṫ⁵      # tail - take every item from 10
  Ṗ     # remove last item
   m2   # take every second item
     ¬  # logical not, will return 1 for an empty list
      Ạ # all
# function to create cumulative score
# difference and check values
N§ỤḢƊ¦    # negate scores for team with lower score
          # (or one of them if both same score)
  LÞṚ     # sort by length, longest first
  ZF      # transpose lists and flatten
  Ä       # cumulative sum
  µ       # this cumulative score difference (CSD) 
          # now becomes left value
  12R:2U_ # subtract cumulative score difference from
          # 6,5,5,4,4,3,3,2,2,1
  ṁḣ⁵     # shorten to be no longer than 10 items
          # and no longer than CSD
  ṫ-N<Ø.Ạ # check last two values are greater than 0,-1
  aÇ      # check that helper function also TRUE
  oL<3    # handle very short scores
# main link that calls the above for scores in either order
ṚÇoÇ

Note the footer code at tio is just to handle multiple test cases and print outputs against inputs.

Thanks to @EriktheOutgolfer for golfing off 8 bytes

\$\endgroup\$
0
\$\begingroup\$

Perl 6, 123 bytes

{all map {@^b>@^a||[R,](map {abs(($+=$_)-++$ %2/2)>(5-++$ /2 max++$ %2)},flat roundrobin @a,-<<@b).skip.any},@^a,@^b,@b,@a}

Try it online!

Returns falsey for valid shoot-outs, truthy for invalid ones.

Explanation

# Check whether block returns true (invalid shoot-out) for arguments (a, b) and (b, a)
{all map {...},@^a,@^b,@b,@a}
# Return true (invalid) if array b is longer than a
@^b>@^a||
# Return true (invalid) if any except the last value is true (shoot-out stopped)
[R,](...).skip.any
# Map values from a and negated b, interleaved
map {...},flat roundrobin @a,-<<@b
# Shoot out stopped?
abs(($+=$_)-++$ %2/2)>(5-++$ /2 max++$ %2)
    #     # Accumulator
           #        # Subtract 0.5 for first team
                      #                  # Sequence 4.5 4 3.5 3 2.5 2 1.5 1 1 0 1 0 1 0
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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