22
\$\begingroup\$

Introduction

Write a program or function that, given the coordinates of where a dart lands on a dartboard, return the score of that dart. Dart coordinates are given as two integers, x,y measured from the center of the dartboard, with millimeter precision.

How to score a dart

Darts is a game played by throwing a dart at a circular board. The dart board is divided into 20 equally sized "wedges". Starting from the top and going clockwise, the sections have values of 20,1,18,4,13,6,10,15,2,17,3,19,7,16,8,11,14,9,12,5. If your dart lands in the black or white parts of any of the wedges, you score the value indicated on the outside of that wedge.
here's a picture of a dartboard.


However, if your dart lands in the outer green/red ring of the dartboard, you score double the points indicated on the outside of the wedge that you hit. Likewise, hitting the inner green/red ring (the one in between the two white/black sections), you score triple the number indicated on the outside of the wedge. If your dart hits the innermost circle (the red bulls-eye) you instead score 50 points and finally, if your dart hits the second-innermost circle (the green ring around the bulls-eye), you score 25 points.

The dimensions of the rings, measured from the center of the dartboard, are as follows:

image not to scale


Bullseye (50): [0mm-6mm)
25:            [6mm-16mm)
Inner Single:  [16mm-99mm)
Triple:        [99mm-107mm)
Outer Single:  [107mm-162mm)
Double:        [162mm-170mm)
Miss (0):       170mm+

Note 1: Pictures provided are for illustration purposes only, and are not to scale.

Note 2: Measurements given are approximate, and may not be accurate to a real dartboard.

Note 3: All measurements given are [inclusive-exclusive). For the purposes of this challenge, we're not going to worry about darts hitting the wire and bouncing off. If the dart lands "on the wire" with one of the radial lines, then it is up to the answerer to decide whether to break the tie clockwise or counter-clockwise. Tie breaking direction must be consistent, and indicated.

Note 4: Dartboard is hung in the standard way with the middle of the 20 section being directly above the bullseye, and the 3 section directly below the bullseye.

Input

Two integers representing the x,y coordinates of where the dart landed, measured in millimeters, relative to the center of the dartboard.

Output

A single integer, for the number of points that would be awarded to a dart that landed at the given coordinates.

Sample

0,0     -> 50
2,101   -> 60
-163,-1 -> 22
6,18    ->  1
-6,18   ->  5
45,-169 ->  0
22, 22  ->  4 (if tie-broken clock-wise)
            18(if tie-broken counter-clockwise)
-150,0  ->  11
-150,-1 ->  11

Scoring

. Fewest bytes in your source code wins.

Standard loopholes forbidden.

\$\endgroup\$
  • 1
    \$\begingroup\$ @Shaggy I don't see any decent reason to do so. \$\endgroup\$ – Jonathan Allan Jun 25 '18 at 21:47
  • 5
    \$\begingroup\$ @Shaggy Can you explain why that should be the case? Personally I would love it if my darts were always guaranteed to hit the dart board, but for the sake of the challenge, I thought it best to stick to reality over fantasy. \$\endgroup\$ – mypetlion Jun 25 '18 at 21:56
  • 1
    \$\begingroup\$ Suggested test cases: -150,-1 and -150,0 which should both give 11 and may be an edge case on some implementations, as this is the transition between theta converging to -pi and theta = +pi in polar coordinates. (My initial answer failed on the 2nd one.) \$\endgroup\$ – Arnauld Jun 26 '18 at 8:25
  • 1
    \$\begingroup\$ Dangit, x=y=0 is totally messing me up!! Good challenge. \$\endgroup\$ – BradC Jun 26 '18 at 16:22
  • 1
    \$\begingroup\$ Hope you don't mind, I edited in a better version of the second pic. \$\endgroup\$ – BradC Jun 27 '18 at 15:32
19
\$\begingroup\$

JavaScript (ES7), 137 bytes

Takes the coordinates in currying syntax (x)(y). Uses counterclockwise tie-break.

x=>y=>(r=(x*x+y*y)**.5)<6?50:r<16?25:(r<99?1:r<107?3:r<162||r<170&&2)*parseInt('b8g7j3h2fa6d4i1k5c9eb'[Math.atan2(y,x)*3.1831+10.5|0],36)

Try it online!

How?

We translate the input Cartesian coordinates \$(x,y)\$ into polar coordinates \$(r,\theta)\$ with:

$$r=\sqrt{x^2+y^2}$$ $$\theta=\arctan_2(y,x)$$

We use \$r\$ to determine whether the dart is located over the Bullseye, 25, Inner Single, Triple, Outer Single, Double or if the shot is a Miss.

If we're located over a Single, Double or Triple, we use \$\theta\$ to determine in which sector \$s\$ we are with:

$$s=\left\lfloor\frac{\theta+\pi}{2\pi}\times20+\frac{1}{2}\right\rfloor=\left\lfloor\theta\times\frac{10}{\pi}+10+\frac{1}{2}\right\rfloor$$

For a \$340\times340\$ area, we need 4 decimal places of \$10/\pi\$ to get enough precision, which gives:

$$\frac{10}{\pi}\approx3.1831$$

The base scores are stored counterclockwise in a base-36 encoded string of 21 entries, starting and ending at \$11\$:

$$11,8,16,7,19,3,17,2,15,10,6,13,4,18,1,20,5,12,9,14,11$$

We need to repeat \$11\$ because half of this sector belongs to the first slice (where \$\theta\$ is close to \$-\pi\$), while the other half belongs to the last slice (where \$\theta\$ is close to \$+\pi\$).

Graphical output

The following ES6 code snippet draws the dartboard using the same logic as in the golfed code.

for(ctx = c.getContext('2d'), y = -180; y < 180; y++) {
  for(x = -180; x < 180; x++) {
    r = Math.pow(x * x + y * y, 0.5);
    s = Math.atan2(y, x) * 3.1831 + 10.5 | 0;
    
    ctx.fillStyle = [ '#000', '#fff', '#be3628', '#487f45', '#bbb', '#ddd' ][
      r < 6 ? 2 : r < 16 ? 3 :
      (r < 99 ? 1 : r < 107 ? 3 : r < 162 ? 1 : r < 170 ? 3 : 4) ^ (s & 1)
    ];
    ctx.fillRect(x + 180, y + 180, 1, 1);
  }
}
<canvas id=c width=360 height=360 style="width:360px;height:360px" />

\$\endgroup\$
8
\$\begingroup\$

JavaScript (ES6) + SVG(HTML5), 53 + 523 51 + 519 507 = 576 570 558 bytes

document.write`<svg width=345 height=345>`;i=b=Math.PI/10;s=Math.sin(a=-b/2);c=Math.cos(a);f=(r,f,n)=>document.write(`<path d=M172,172L${[172+r*s,172+r*c]}A${[r,r,0,0,1,172+r*t,172+r*d]}z fill=#${f} n=${n} />`);g=(q,r,m,n,i)=>f(q,i?474:`b32`,n*m)+f(r,i?`fff`:`000`,n);[3,17,2,15,10,6,13,4,18,1,20,5,12,9,14,11,8,16,7,19].map(n=>{t=s;d=c;s=Math.sin(a+=b);c=Math.cos(a);g(170,162,2,n,i=!i);g(107,99,3,n,i);});document.write`<circle cx=172 cy=172 r=16 fill=#474 n=25 /><circle cx=172 cy=172 r=6 fill=#b32 n=50`
<body onclick=alert(+event.target.getAttribute`n`)>

Input is via mouse click, output via alert. Edit: Saved 12 bytes by using slightly more approximate colours as suggested by @Arnauld.

\$\endgroup\$
  • \$\begingroup\$ I guess nobody's going to blame you if you use b33 and 474 for red and green. :-) \$\endgroup\$ – Arnauld Jun 28 '18 at 11:43
  • \$\begingroup\$ @Arnauld Fair enough, although b33 is bb3333 so b22 (aka bb3322) is closer to your original be3628. \$\endgroup\$ – Neil Jun 28 '18 at 12:04
7
\$\begingroup\$

Intel 8086/8087 assembly, 180 144 142 138 bytes

This uses the 8087 math co-processor for all of the trig and floating-point arithmetic. All calculations are done in hardware with 80-bit floating-point precision.

df06 b101 d8c8 df06 af01 d8c8 dec1 d9fa df1e b301 8b16 b301
33c0 81fa aa00 7c03 eb53 9083 fa06 7d05 b032 eb49 9083 fa10
7d05 b019 eb3f 90df 06b7 01df 06b5 01d9 f3df 06b1 01dd d2d9
ebde f9de c9de c1df 1eb3 01a1 b301 bb9c 01d7 83fa 6b7d 0a83
fa63 7c05 b303 eb09 9081 faa2 007c 04b3 02f6 e30b 0810 0713
0311 020f 0a06 0d04 1201 1405 0c09 0e0b 0a00

Written as a MASM MACRO (basically a function), takes X and Y as coordinates and returns the calculated score in AX. The tie is broken clockwise.

MAX_BULL EQU 6
MAX_25   EQU 16
MIN_3X   EQU 99
MAX_3X   EQU 107
MIN_2X   EQU 162
MAX_2X   EQU 170

; cartesian coordinates to radius
; ST = sqrt( X^2 + Y^2 )
; input: X,Y (mem16,mem16)
; output: Radius (mem16)
FCRAD   MACRO X, Y, R
    FILD  Y         ; ST[] = Y
    FMUL  ST,ST     ; ST = y^2 
    FILD  X         ; ST[] = X
    FMUL  ST,ST     ; ST = x^2
    FADD            ; ST = ST + ST1
    FSQRT           ; ST = SQRT(ST)
    FISTP R         ; R = ROUND(ST)
        ENDM

; cartesian coordinates to sector #
; input: X,Y (mem16,mem16)
; output: Sector (mem16)
FCSEC   MACRO X, Y, S
    FILD  Y         ; ST[] = Y
    FILD  X         ; ST[] = X
    FPATAN          ; ST = atan2(Y,X)
    FILD  CTEN      ; ST[] = 10
    FST   ST(2)     ; ST(2) = 10
    FLDPI           ; ST[] = pi
    FDIV            ; ST = 10 / pi
    FMUL            ; ST = A * ST
    FADD            ; ST = ST + 10
    FISTP S         ; S = ROUND(ST)
        ENDM

; score the dart throw
; input: X / Y coordinates (mem16)
; output: Score (AX)
SCORE   MACRO X, Y
        LOCAL IS_BULL, IS_25, IS_3X, IS_2X, MUL_SCORE, DONE
    FCRAD X, Y, FDW         ; FDW = radius(X,Y)
    MOV  DX, FDW            ; DX = FDW = radius
    XOR  AX, AX             ; score is initially 0
    CMP  DX, MAX_2X         ; >= 170 (miss)
    JL   IS_BULL            ; if not, check for bullseye
    JMP  DONE
IS_BULL:
    CMP  DX, MAX_BULL       ; < 6 (inner bullseye)
    JGE  IS_25              ; if not, check for 25
    MOV  AL, 50             ; score is 50
    JMP  DONE
IS_25:
    CMP  DX, MAX_25         ; < 16 (outer bullseye)
    JGE  IS_3X              ; if not, check for triple
    MOV  AL, 25             ; score is 25
    JMP  DONE
IS_3X:
    FCSEC X, Y, FDW         ; FDW = sector(X,Y)
    MOV  AX, FDW            ; load sector # into AX
    MOV  BX, OFFSET SCR     ; load base score table
    XLAT                    ; put base score into AL
    CMP  DX, MAX_3X         ; < 107 (triple upper bounds)
    JGE  IS_2X              ; if not, check for double
    CMP  DX, MIN_3X         ; >= 99 (triple lower bounds)
    JL   IS_2X              ; if not, check for double
    MOV  BL, 3              ; this is triple score
    JMP  MUL_SCORE          ; go forth and multiply
IS_2X:
    CMP  DX, MIN_2X         ; >= 162 (double lower bounds) (> 170 already checked)
    JL   DONE               ; if not, single score
    MOV  BL, 2              ; this is double score
MUL_SCORE:
    MUL  BL                 ; multiply score either 2x or 3x
DONE:
    ENDM

; DATA (place in appropriate segment)
SCR     DB  11,8,16,7,19,3,17,2,15,10,6  ; score table
        DB  13,4,18,1,20,5,12,9,14,11
CTEN    DW  10      ; constant 10 to load into FPU
FDW     DW  ?       ; temp DW variable for CPU/FPU data transfer

An example test program for PC DOS. Download it here DARTTEST.COM.

INCLUDE DART.ASM            ; the above file
INCLUDE INDEC.ASM           ; generic I/O routines - input int
INCLUDE OUTDEC.ASM          ; generic I/O routines - output int

    FINIT                   ; reset 8087

    MOV  AH, 2              ; display "X" prompt
    MOV  DL, 'X'
    INT  21H
    CALL INDEC              ; read decimal for X into AX
    MOV  X, AX

    MOV  AH, 2              ; display "Y" prompt
    MOV  DL, 'Y'
    INT  21H
    CALL INDEC              ; read decimal for Y into AX
    MOV  Y, AX

    SCORE X, Y              ; AX = SCORE( X, Y )

    CALL OUTDEC             ; display score

X   DW  ?
Y   DW  ?

Output

Example usage of above test program. Actual IBM PC with 8087, DOSBox or your favorite emulator required.

A>DARTTEST.COM
X: 0
Y: 0
50
A>DARTTEST.COM
X: 2
Y: 101
60
A>DARTTEST.COM
X: -163
Y: -1
22
A>DARTTEST.COM
X: 6
Y: 18
1
A>DARTTEST.COM
X: -6
Y: 18
5
A>DARTTEST.COM
X: 45
Y: -169
0
A>DARTTEST.COM
X: 22
Y: 22
4
A>DARTTEST.COM
X: -150
Y: 0
11
A>DARTTEST.COM
X: -150
Y: 0
11
A>DARTTEST.COM
X: -150
Y: -1
11
A>DARTTEST.COM
X: -7
Y: -6
25
A>DARTTEST.COM
X: -90
Y: 138
24

*Edits:

  • -36 bytes by removing truncate-rounding statement and 10.5 constant. Tie now broken clockwise.
  • -2 bytes by removing no longer necessary FRNDINT
  • -4 bytes, FMUL use same source/destination
\$\endgroup\$
6
\$\begingroup\$

Jelly, 56 bytes

æA/Æ°_9:18ị“!@umÞẓẓS’Œ?¤
ḅıA<“©Ñckɱȥ‘TṂị“2ı¢¤¢£¡‘¹×>3$?Ç

A monadic link accepting the pair as a list [x,y] which yields the score.
Uses clockwise tie-breaking.

Try it online! Or see the test-suite

N.B. a dyadic version is also 56 bytes

How?

æA/Æ°_9:18ị“!@umÞẓẓS’Œ?¤ - Link 1, segment score: pair [x, y]
  /                      - reduce by:
æA                       -   arc tangent
   Æ°                    - convert from radians to degrees
     _9                  - subtract 9 (align 0 with boundary between 1 & 20)
       :18               - integer divide by 18 (yields a segment index from 0 to 19)
                       ¤ - nilad followed by link(s) as a nilad:
           “!@umÞẓẓS’    -   base 250 number = 2091180117530057584
                     Œ?  -   shortest permutation of natural numbers [1..N] which
                         -   would reside at that index in a list of all permutations of
                         -   those same numbers ordered lexicographically.
                         -   = [18,4,13,6,10,15,2,17,3,19,7,16,8,11,14,9,12,5,20,1]
          ị              - index into (yields the score associated with the segment)

ḅıA<“©Ñckɱȥ‘TṂị“2ı¢¤¢£¡‘¹×>3$?Ç - Main Link: segment score: pair [x, y]
 ı                              - √(-1)
ḅ                               - convert from base = x+iy
  A                             - absolute value = √(x²+y²)
    “©Ñckɱȥ‘                    - code-page index list = [6,16,99,107,162,170]
                                - (i.e. the radial boundaries)
            T                   - list of truthy indexes
             Ṃ                  - minimal value (0 if empty)
               “2ı¢¤¢£¡‘        - code-page index list = [50,25,1,3,1,2,0]
              ị                 - index into
                                - (i.e. get an override score (>3) OR a multiplier (<=3))
                              Ç - call last Link (1) as a monad (get the segment score)
                             ?  - if...
                            $   - ...condition: last two links as a monad:
                          >     -      (override OR multiplier) greater than?
                           3    -      three
                        ¹       - ...then: identity (keep override as is)
                         ×      - ...else: multiply (by multiplier)
\$\endgroup\$
4
\$\begingroup\$

TI-Basic (TI-84 Plus CE), 147 146 bytes

Prompt X,Y
abs(X+iY→R
int(E-12+11.5+10π-1R▸Pθ(X,Y→θ
{11,8,16,7,19,3,17,2,15,10,6,13,4,18,1,20,5,12,9,14,11
25((R<6)+(R<16))+Ans(θ)(R≥16 and R<170)(1+(R≥162)+2(R≥99 and R<107

Prompts for X and Y on separate lines.

Tie-breaks counter-clockwise.

TI-Basic is a tokenized language; all tokens used here are one byte.

Explanation:

Prompt X,Y
# 5 bytes, Prompt for X and Y
abs(X+iY→R
# 8 bytes, store distance from origin in R
int(E-12+11.5+10π-1R▸Pθ(X,Y→θ
# 22 bytes, store index in list of point values by polar angle in θ
{11,8,16,7,19,3,17,2,15,10,6,13,4,18,1,20,5,12,9,14,11
# 55 bytes, list of point values
25((R<6)+(R<16))+Ans(θ)(R≥16 and R<170)(1+(R≥162)+2(R≥99 and R<107
# 57 56 bytes, calculate the score

Utilizes the fact that TI-Basic boolean comparisons return 0 or 1 by adding them up and multiplying by point values.

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

T-SQL, 392 374 366 bytes

UPDATE t SET x=1WHERE x=0
SELECT TOP 1IIF(r<16,f,b*f)
FROM(SELECT r=SQRT(x*x+y*y),w=FLOOR(10*ATN2(y,x)/PI()+.5)FROM t)p,
(VALUES(10,11),(9,14),(8,9),(7,12),(6,5),(5,20),(4,1),(3,18),(2,4),(1,13),(0,6),
   (-1,10),(-2,15),(-3,2),(-4,17),(-5,3),(-6,19),(-7,7),(-8,16),(-9,8),(-10,11))s(a,b),
(VALUES(6,50),(16,25),(99,1),(107,3),(162,1),(170,2),(999,0))d(e,f)
WHERE a=w AND r<e

Line breaks are for readability. The initial UPDATE takes care of the x=y=0 problem that would otherwise throw an error with ATN2(), but doesn't change the score.

Input is taken via pre-existing table t, per our IO guidelines. Due to using TOP 1, this table should contain only a single row.

Basically I'm joining 3 tables:

  • Table p: The x and y from input table t are converted to polar r and a "wedge" value w representing a number from -11 to positive 11, for the scoring wedge the dart fell into. "Tie breaker" is counter-clockwise. (I tried ROUND(), which was slightly shorter, but it gave an inconsistent tie breaker.)
  • Table s: This is a lookup table to convert a "wedge" value a into a score b.
  • Table d: This is a lookup table the returns the score calculation based on the distance from the center. e is the distance and joins to r, and returns only a single row based on the TOP 1. The value f is either a fixed score (for a bulls-eye) or a multiplier for the wedge score.

EDIT: Dropped the ORDER BY, it seems to work properly without it, at least on SQL 2017. I also dropped the AND y=0 on the update condition; I tested for all integer y values, changing x=0 to x=1 never changes the score.

EDIT 2: Removed column g from table d, replaced it with an IIF() statement that either returns f directly (for a bulls-eye), or f*b, saved 8 bytes. Also removed the space after TOP 1.

\$\endgroup\$
2
\$\begingroup\$

Haskell, 198 bytes

p=pure
a#b=(!!(sum[1|k<-a,k<=b]))
a!b=([6,16,99,107,162,170]#(sqrt$a*a+b*b))[p 50,p 25,id,(*3),id,(*2),p 0]$([pi/20,3*pi/20..6]#(pi+atan2 b a))[11,8,16,7,19,3,17,2,15,10,6,13,4,18,1,20,5,12,9,14,11]

Tie breaks counter-clockwise. (#) is a lookup function. The polar angle is used to index from the list of numbers, starting at the atan2 cutoff point at 11. The distance is used to index from the list of functions [const 50, const 25, id, (*3), id, (*2), const 0] and at last this function is applied to the number we previously got.

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Perl 5 -MMath::Trig':pi' -MMath::Trig':radial' -apl, 166 bytes

($d,$a)=cartesian_to_cylindrical@F;$_=(1+($d>161||$d<6)+($d<107&&$d>98)*2)*($d<170)*($d<16?25:("6 134 181 205 129 14118 167 193 172 1510"=~/../g)[($a/pi*10+41/2)%20])

Try it online!

Takes the two coordinates space separated on STDIN. Tie-breaking is anticlockwise.

\$\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.