10
\$\begingroup\$

Introduction:

Beckett Grading Service (aka BGS), is a company which encapusulates and grades Trading Card Game (TCG) cards (i.e. sport cards, Magic The Gathering, Pokémon, Yu-Gi-Oh, etc. etc.). The TCG cards that come in for grading will be checked on four categories (which we will later refer to as subgrades): centering, corners, edges, and surface. These subgrades will be in the range \$[1, 10]\$ in increments of \$0.5\$. Based on these four subgrades, the graded card is also given a final grade (in the same range). This final grade isn't just an average however. To quote the BGS FAQ page (minor note when reading: the grading level/point they're talking about is a step of 0.5):

Here is how we do it!
The overall numerical grade is not a simple average of the four report card grades. Beckett Grading Services uses an algorithm which determines the final grade using the 4 sub grades on the front label of the card holder. The lowest overall grade is the first category to observe because it is the most obvious defect, and the lowest grade is the most heavily weighted in determining the overall grade.

Example :
Centering = 9.5; Corners = 9.5; Edges = 9; Surface = 8
Final grade = 8.5

The reason that this card received an 8.5 is that even though the Surface grade was an 8 (the lowest grade overall), the 9.5 grades on Centering and Corners were strong enough to bring it up a full point to reach the 8.5 level.

Another example :
Centering = 9.5; Corners = 9.5; Edges = 8.5; Surface = 9
Final grade = 9

Upon first glance, it may appear that this card should've received a grade different than a 9. The most this card could receive was .5 (or one-half grade) above the lowest sub-grade. The Edges were the lowest in this case, hence, the card received the overall 9 grade. Even though Centering and Corners received grades of 9.5, a key point to remember is that the minimum requirement to receive a grade of Gem Mint is to have at least three grades of 9.5 and the fourth to be no less than a 9.

Also, please note that the final grade rarely, if ever, exceeds two levels above the lowest of the four characteristic grades. For example, if a card has characteristic grades of Centering 10, Corners 6, Edges 10 and Surface 10, the final grade will be a "7" (of which is exactly two grading levels above the lowest characteristic grade).

Although the algorithm they use to determine the final grade has never been revealed, someone was able to come up with a set of rules as algorithm for determining the final grade based on the four subgrades by looking at as many example graded cards as possible (source):

In pseudo-code:

Sort subgrades in decreasing order

var fourth = fourth number in this order # so the lowest subgrade
var third = third number in this order   # so the second to last subgrade - could be equal
                                         #  to the lowest of course)
var first = first number in this order   # so the highest subgrade
var diff = third - fourth                # difference between these two lowest subgrades
var finalGrade                           # our final grade we're calculating

If diff == 0:
  finalGrade = fourth
Else:
  If fourth is Edges OR fourth is Surface:
    If diff < 1:
      finalGrade = fourth + 0.5
    Else:
      If (diff == 1 AND fourth + 1 >= 9.5)
         OR (first - fourth == 1.5 AND first != 10):
        finalGrade = fourth + 0.5
      Else:
        finalGrade = fourth + 1.0
  Else-if fourth is Centering:
    If diff < 2:
      finalGrade = fourth + 0.5
    Else-if diff < 4:
      finalGrade = fourth + 1.0
    Else:
      finalGrade = fourth + 1.5
  Else(-if fourth is Corners):
    If diff < 3:
      finalGrade = fourth + 0.5
    Else:
      finalGrade = fourth + 1

Some examples:

  • 9.5 8.5 7.5 9.5 = 8.5: fourth number is Edges (7.5); diff is 1.0. So the final grade becomes fourth + 1.0.
  • 10 10 10 8.5 = 9.5: fourth number is Surface (8.5); diff is 1.5. So the final grade becomes fourth + 1.0 (with a different if-else path than the one above)
  • 9.5 9.5 9 8 = 8.5: fourth number is Surface (8); diff is 1.0; first number is 9.5. So the final grade becomes fourth + 0.5.
  • 5.5 8.5 9 7 = 6: fourth number is Centering (5.5); diff is 1.5. So the final grade is fourth + 0.5.
  • 5 7 9 9.5 = 6: fourth number is Centering (5); diff is 2. So the final grade is fourth + 1.
  • 4.5 9 9.5 9 = 6: fourth number is Centering (4); diff is 4.5. So the final grade is fourth + 1.5.
  • 9 6.5 9 9.5 = 7: fourth is Corners (6.5); diff is 2.5. So the final grade is fourth + 0.5.

In summary: Corners is punished hardest, Centering next, Surface/Edges the least. How much the overall grade is better than the worst subgrade depends on which subgrade is the worst, and also depends on how much the other three subgrades are better than the worst subgrade, measured by diff.

Here an ungolfed reference implementation of the described algorithm in Java. (It can also be used to generate more random test cases; see the bottom section of the output.)

Challenge:

Given the four subgrades as input, output the final grade based on the algorithm above.

Challenge rules:

  • Input may be in any reasonable format. Can be four separated inputs. A sorted list. Etc. Make sure to state the order of the subgrades centering, corners, edges, surface you've used. Inputs can be as decimal numbers, strings, or other reasonable input-types.
    • If your language doesn't have a numeric type that allows decimal values, or if you choose to, you are allowed to take all inputs multiplied by 10 as integers (as long as it's consistent). I.e. instead of 4.5 8 9.5 6.5 you could also take the input as 45 80 95 65. In which case the output is of course similar. Make sure to mention in your answer if you're using integers which are multiplied by 10!
  • Output may be returned by a function in any reasonable type, output to STDOUT/STDERR/Exit Code, etc.
  • Of course you don't have to follow the algorithm exactly as described above. As long as it gives the correct result for any of the 130321 possible test cases, it's fine to implement it in any way you'd like. If you think hard-coding all possible test cases is shorter in your language of choice, by all means go for it.

General rules:

  • This is , so shortest answer in bytes wins.
    Don't let code-golf languages discourage you from posting answers with non-codegolfing languages. Try to come up with an as short as possible answer for 'any' programming language.
  • Standard rules apply for your answer with default I/O rules, so you are allowed to use STDIN/STDOUT, functions/method with the proper parameters and return-type, full programs. Your call.
  • Default Loopholes are forbidden.
  • If possible, please add a link with a test for your code (i.e. TIO).
  • Also, adding an explanation for your answer is highly recommended.

Test cases:

Centering  Corners  Edges  Surface  =  Final grade
10         10       10     10       =  10
10         10       10     9.5      =  10
10         10       9.5    10       =  10
10         9.5      10     10       =  10
9.5        10       10     10       =  10
10         9.5      9.5    9        =  9.5
8.5        9        10     9.5      =  9
8          8        8.5    8.5      =  8
9.5        8.5      9.5    9.5      =  9
5.5        7.5      7.5    7        =  6
9.5        8.5      8.5    9        =  8.5
8          8.5      9      9        =  8.5
9.5        4.5      4.5    2.5      =  3.5
9.5        6        6.5    6        =  6
9.5        7        7      8        =  7
9.5        9.5      9.5    8.5      =  9
7.5        8.5      9      8        =  8
9          8        7      8.5      =  8
10         9.5      9.5    8        =  9
10         9        9.5    9        =  9
10         8        9      9        =  8.5
9.5        8.5      7.5    9.5      =  8.5
9.5        8.5      8.5    6.5      =  7.5
10         10       10     8.5      =  9.5
9.5        9.5      9      8        =  8.5
5.5        8.5      9      7        =  6
5          7        9      9.5      =  6
4.5        9        9.5    9        =  6
9          6.5      9      9.5      =  7
1          1        1      1        =  1
1          10       10     10       =  2.5
10         1        10     10       =  2
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Can we use grade points (i.e. multiply by 2) instead? \$\endgroup\$ Aug 7, 2019 at 17:07
  • \$\begingroup\$ @NickKennedy No sorry, either the decimal values or times 10 as integer. Someone asked me whether we could take the decimals as times 2 or times 10 in the Sandbox, but I feel times 2 makes the values a bit too different. Times 10 is fine though, since the in-/output values are close to the intended decimal values, and it would allow languages without decimals to compete as well. \$\endgroup\$ Aug 7, 2019 at 19:24

6 Answers 6

4
\$\begingroup\$

Charcoal, 73 71 bytes

≦⊗θ≔⌊θη≔⁻⌊Φθ⁻κ⌕θηηζI⊘⁺η∧ζ⎇⌕θη∨⎇⊖⌕θη∨›²ζ∨∧⁼²ζ‹¹⁶η∧⁼³⁻⌈θη›²⁰⌈θ›⁶ζ²⊕⌊⟦²÷ζ⁴

Try it online! Link is to verbose version of code. Takes input as an array of 4 values [Centring, Corners, Edges, Surface]. Explanation:

≦⊗θ

Double all the values so we're dealing in grade points rather than grades.

≔⌊θη

Find the worst grade.

≔⁻⌊Φθ⁻κ⌕θηηζ

Find the worst of the other grades, i.e. the second worst, and subtract the worst grade.

I⊘

Halve the final value and cast to string for display.

⁺η

Add the bonus to the minimum grade.

∧ζ

No bonus if the worst two grades are the same.

⎇⌕θη

Check whether the worst grade was for Centring.

∨⎇⊖⌕θη∨›²ζ∨∧⁼²ζ‹¹⁶η∧⁼³⁻⌈θη›²⁰⌈θ›⁶ζ²

If not then check whether only one bonus point should be awarded otherwise award two bonus points. (If the worst grade was for Corners then add a bonus point if the difference was less than 6 grade points or 2 if it was 6 or more. The calculation for Edges and Surface is longer but still only adds one or two bonus points.)

⊕⌊⟦²÷ζ⁴

If the worst grade was for Centring then add a bonus point for every 4 grade points between the worst two grades up to 2, plus a further bonus point.

\$\endgroup\$
4
\$\begingroup\$

Perl 5 (-ap), 157 154 bytes

minor improvements !$d?0:... inverted to $d?...:0, >=9.5 changed to >9.4 and !=10 to <10

($w,$x,$y,$z)=sort{$b<=>$a}@F;$d=$y-$z;$_=$z+=$d?$z==$F[2]|$z==$F[3]?$d<1?.5:$d==1&$z+1>9.4|$w-$z==1.5&$w<10?.5:1:$z==$F[0]?$d<2?.5:$d<4?1:1.5:$d<3?.5:1:0

TIO

first answer was

($w,$x,$y,$z)=sort{$b<=>$a}@F;$d=$y-$z;$_=$z+=!$d?0:$z==$F[2]|$z==$F[3]?$d<1?.5:$d==1&$z+1>=9.5|$w-$z==1.5&$w!=10?.5:1:$z==$F[0]?$d<2?.5:$d<4?1:1.5:$d<3?.5:1

straight forward, ungolfed

!$d?0
:$z==$F[2]|$z==$F[3]?
    $d<1?.5
    :$d==1&$z+1>=9.5|$w-$z==1.5&$w!=10?.5
    :1
:$z==$F[0]?
    $d<2?.5
    :$d<4?1
    :1.5
:$d<3?.5
:1

TIO

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

JavaScript (V8), 160 159 154 151 bytes

s=>{[C,O,E,S]=[...s];[a,b,c,d]=s.sort((a,b)=>b-a);e=c-d;return(e?d==E|d==S?e<1?.5:(e==1&d>8.4)|(a-d==1.5&a<10)?.5:1:d-C?e<3?.5:1:e<2?.5:e<4?1:1.5:0)+d}

Try it online!

A bit ungolfed:

function calculateScore(score) {
  [center,corner,edge,surface] = [...score]; // makes a copy because sort is in-place
  [first,second,third,fourth] = score.sort((a,b)=>b-a); // standard sort is alphabetically, this will sort by value
  diff = third - fourth;
  return fourth + (// Every final score is fourth + something else
    diff != 0
      ? fourth == edge || fourth == surface
        ? diff < 1
          ? 0.5
          : (diff == 1 && fourth >= 8.5) || (first-fourth == 1.5 && a < 10)
            ? 0.5
            : 1
      : fourth != corner
        ? diff < 3
          ? 0.5
          : 1
        : diff < 2
          ? 0.5
          : diff < 4
            ? 1
            : 1.5
      : 0 // diff == 0
  );
}
  • -1: >=8.5 -> >8.4
  • -5 thanks to @Kevin Cruijssen
  • -3 for ES6 copying with [...s]
\$\endgroup\$
4
  • \$\begingroup\$ I'm not too skilled with JS, so I'm sure more can be golfed, but some basic golfs like ||to | and && to & can be applied, as well as return(...)+d instead of return d+(...) to save 5 bytes: Try it online. \$\endgroup\$ Aug 7, 2019 at 10:55
  • \$\begingroup\$ Also, if you haven't seen them yet, tips for golfing in JavaScript and tips for golfing in <all languages> might be interested to read through. :) \$\endgroup\$ Aug 7, 2019 at 10:56
  • \$\begingroup\$ Also, I think you can remove the O and b in the input/sort: s=>{[C,,E,S]=[...s];[a,,c,d], since you're not using the b in the rest of the code. \$\endgroup\$ Aug 7, 2019 at 11:56
  • \$\begingroup\$ 135 \$\endgroup\$
    – Shaggy
    Aug 7, 2019 at 15:39
3
\$\begingroup\$

Python 3, 132 bytes

def f(a):c,C,*_=a;w,x,y,z=sorted(a);d=x-w;return(x>w)*[[2-(d<1or d==1<w-7or z-w==1.5>z-8.5),3-(d<2)-(d<4)][w==c],-~(d>=3)][w==C]/2+w

Try it online!

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

JavaScript (ES6),  115 ... 105  102 bytes

Takes input as an array.

a=>([w,d,,b]=([C,c]=a).sort((a,b)=>a-b),(d-=w)?(w-C?w-c?d<1|(w>8?d:b-w-.5)==1:d<3:d<4?d<2:3)||2:0)/2+w

Try it online!

or compare all results with those of the ungolfed reference implementation

Commented

a => (                      // a[] = [ centering, corners, edges, surface ]
  [ w,                      // w = worst grade (4th grade)
    d,                      // d = 3rd grade
    ,                       // the 2nd grade is ignored
    b                       // b = best grade (1st grade)
  ] =                       //
    ([C, c] = a)            // before sorting, save C = centering and c = corners
    .sort((a, b) => a - b), // sort the grades from lowest to highest
  (d -= w) ?                // d = 3rd grade - worst grade; if it's not 0:
    ( w - C ?               //   if the worst grade is not for centering:
        w - c ?             //     if the worst grade is not for corners:
          d < 1 |           //       yield 1 if d < 1 OR
          ( w > 8 ?         //       either
              d             //         d (if w > 8)
            :               //       or
              b - w - .5    //         b - w - 0.5 (if w <= 8)
          ) == 1            //       is equal to 1
        :                   //     else (worst grade = corners):
          d < 3             //       yield 1 if d < 3
      :                     //   else (worst grade = centering):
        d < 4 ?             //     if d < 4:
          d < 2             //       yield 1 if d < 2
        :                   //     else:
          3                 //       yield 3
    ) || 2                  //   if the above result is 0, yield 2 as the default value
  :                         // else (d = 0):
    0                       //   yield 0
) / 2 + w                   // half the above result and add it to w
\$\endgroup\$
0
1
\$\begingroup\$

C# (Visual C# Interactive Compiler), 164 bytes

(a,b,c,d)=>{int[]e={a,b,c,d};Array.Sort(e);int f=e[0],h=e[1]-f,i=e[3];return(h<1?0:f==c|f==d?h<9?5:h<11&f>84|i-f==15&i<96?5:10:f==a?h<20?5:h<40?10:15:h<30?5:10)+f;}

Try it online!

\$\endgroup\$

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.