15
\$\begingroup\$

Preface

As I was shooting an archery 900 round earlier today (10 ends at 6 arrows an end, and 10 ends at 3 arrows an end, for a total of 90 arrows and a maximum score of 900), I thought of this challenge.

In archery (assuming that you are shooting on a FITA supplied target face [the piece of paper that you shoot at]), for each arrow you can claim a maximum score of 10. The target face contains 10 or 11 rings of decreasing diameter, nested inside one another. From the inner ring outward, these are counted from 10 points, to one point (and in the case of 11 rings, there is a secondary innermost ring that counts as 'X', which scores as 10 but is used in tie breaking cases as the higher value). Observe:

FITA Target Scoring

Of course, I am referring to the FITA Metric scoring, as seen in the above illustration. If you look closely, you may observe the innermost ring, which is a faded dotted line, whose score is not marked. That is the 'X' that I was referring to, but you will not have to pay heed to that unless competing for the bonus.

Challenge

Create a function (or full program, if the language doesn't support functions), that receives a perfectly square image as input (or image filename, if need be), containing some number of green (HEX #00FF00, RGB(0, 255, 0)) dots of some size, and returns the score. The image may contain data other than the green dots, but the green will always be the exact same shade.

You may imagine that the square image represents the target face, with the outermost ring touching at 4 points (top center, bottom center, right center, left center). The represented target face will always be of the same proportion, with all of the rings having a width of exactly 1/20th of the input target image's width. As an example, given an input image of input dimensions 400px by 400px, you may assume that each ring has an inner width of 20px, as illustrated below:

Crappy example illustration

Clarifications

  • If touching two seperate rings, the higher of the two rings is counted
  • You do not have to automatically account for misses or the 'x' case, unless trying for the bonus
  • You may assume that no green circles are overlapping
  • You may also assume that no other pixels of that shade of green are in the image
  • The image will either be in a PNG, JPEG or PPM format (your choice)
  • External image processing libraries are allowed, if authored before the posting of this question
  • You may assume that all green circles on one target will have the same diameter
  • If shooting (hah) for the overlapping circles bonus, you may assume that at least one circle in the image does not have another overlapping
  • Standard loopholes are disallowed

Test cases

The following two cases should each score 52 (or in the case of bonuses, 52 with 1 'x' and 1 miss):

And this last test case should score 25:

Bonus

  • -25 bytes if you also return the number of misses (outside any of the rings) as well
  • -30 bytes if you also return the amount of Xs (assume that the innermost x is 3/100ths of the width of the image, and 10 is then 2/100ths of the width of the image. The 1-9 proportions remain unchanged)
  • -35% byte count if you account for overlapping circles

This is code golf, so the least bytes wins. Have fun!

\$\endgroup\$
11
  • \$\begingroup\$ "30 ends at 3 arrows an end, for a total of 30 arrows"? Shouldn't that be 90 arrows? \$\endgroup\$
    – DavidC
    Commented Dec 29, 2014 at 3:48
  • \$\begingroup\$ @DavidCarraher I realized that right as I posted. Corrected \$\endgroup\$
    – globby
    Commented Dec 29, 2014 at 3:50
  • \$\begingroup\$ What image formats can we use? PNG? PPM? Our own custom format? (I would assume the first two but not the third, but just for clarification.) \$\endgroup\$
    – Doorknob
    Commented Dec 29, 2014 at 4:26
  • \$\begingroup\$ Let's say just JPEG or PNG for simplicity @Doorknob冰 \$\endgroup\$
    – globby
    Commented Dec 29, 2014 at 4:32
  • 1
    \$\begingroup\$ I think the hardest bonus is the one with the least reward. \$\endgroup\$
    – Justin
    Commented Dec 29, 2014 at 9:12

4 Answers 4

4
\$\begingroup\$

Processing 2, 448-25=423 bytes

int x,y,w,b,v,c,m;color g;PImage i;void setup(){i=loadImage("f.png");w=i.width;size(w,w);g=#00ff00;image(i,0,0);b=v=x=y=c=m=0;loadPixels();while(y*w+x<w*w){if(pixels[y*w+x]==g){f(y,x);if(v>=0)c+=v;else m++;}v=-1;x++;if(x==w){x=0;y++;}}println(c+" "+m);}void f(int k,int l){pixels[k*w+l]=color(0);if(pixels[(k+1)*w+l]==g)f(k+1,l);if(pixels[k*w+l+1]==g)f(k,l+1);if(pixels[k*w+l-1]==g)f(k,l-1);k-=w/2;l-=w/2;b=10-(int)(sqrt(k*k+l*l)/(w/20));if(b>v)v=b;}

Reads in an image file f loops through the pixels until it finds green then flood fills the circle determining the point that is closest to the center. Then adds that score to a total. if the score is negative, it is added to a miss counter.

The program will output 2 numbers, the first is the score and the second is the number of misses.

  int x,y,w,b,v,c,m;
  color g;
  PImage i;
void setup()
{
  i=loadImage("f.png");
  w=i.width;
  size(w,w);
  g=#00ff00;
  image(i,0,0);
  b=v=x=y=c=m=0;  
  loadPixels();
  while(y*w+x<w*w)
  {
    if(pixels[y*w+x]==g)
    {
      f(y,x);
      if(v>=0)c+=v;
      else m++;
    }
    v=-1;
    x++;
    if(x==w){x=0;y++;}
  }
  print(c+" "+m);
}

void f(int k,int l)
{
  pixels[k*w+l]=color(0);
 if(pixels[(k+1)*w+l]==g)f(k+1,l);
 if(pixels[k*w+l+1]==g)f(k,l+1);
 if(pixels[k*w+l-1]==g)f(k,l-1); 
 k-=w/2;
 l-=w/2;
 b=10-(int)(sqrt(k*k+l*l)/(w/20));
 if(b>v)v=b;
}

you can get processing here

\$\endgroup\$
4
+100
\$\begingroup\$

Perl 5 + GD: 225 - 25 = 200

Edit: Found the reason for the incorrect pixel reading in indexed PNGs and applied a workaround. For some reason with the GD library the green pixel values are read as (4,254,4). I am not sure if this is specific to the PNG files included in the question. Line breaks can be removed in the code below.

use GD;$k=newFromPng GD::Image'-',1;
sub v{/ /;10-int((($'-@x/2)**2+($`-@x/2)**2)**.5/@x*20)}
map{v>0?($r+=v):$%++,fill$k @c,0if 65280==getPixel$k @c=split
}sort{v($_=$b)- v$_=$a}map{//;map"$_ $'",@x}@x=0..width$k-1;
print"$r $%"

Takes a PNG image on the input and prints 2 values: Number of points and misses. For example:

perl arch.pl <arch52.png
52 1

Last minute change:

In true color mode that I needed anyway the color indexes used by getPixel and fill are simply integer encoded RGB values, so no need to use rgb and colorAllocate to convert to and from those indexes.

Explanation:

  • Generate list of all pixel coordinates (as space separated pairs of integers).
  • Sort by potential score (using sub v which takes parameter through $_ instead of standard parameters as it is shorter).
  • For each pixel starting from the highest scoring ones if it is green add to the result and flood fill its location black.
\$\endgroup\$
3
  • \$\begingroup\$ It isn't the images; @bubalou's answer correctly read the colors as #00FF00 \$\endgroup\$
    – globby
    Commented Dec 31, 2014 at 17:33
  • \$\begingroup\$ @globby I know the colors are correct in the image (I checked with image editing software), but maybe there is also same meta information to trim the color space. \$\endgroup\$
    – nutki
    Commented Dec 31, 2014 at 17:50
  • \$\begingroup\$ possibly. That's strange though \$\endgroup\$
    – globby
    Commented Dec 31, 2014 at 17:51
3
\$\begingroup\$

Haskell - 579-25 = 554 603-25-30 576-25-30 = 521 Bytes

Strategy:

  • Make a list of (d,x,y) triples for all pixels (d is distance to center)
  • sort the list by distance
  • beginning with the greatest distance: if the pixel the only green pixel in a small neighborhood, keep it's distance in a list L, else blacken it
  • calculate score from the distance list L

Output ist a triple (score,misses,Xs), e.g. (52,1,1) for the test image.

The program may fail if the pixel of a circle closest to the center is within 3 pixels of another circle.

import Data.List
import Codec.Picture
import Codec.Picture.RGBA8
import Codec.Picture.Canvas
z=fromIntegral
f(e,x,y)(v,h)|and$j x y:[not$j a b|a<-[x-3..x+3],b<-[y-3..y+3],not(a==x&&b==y)]=(v,e:h)|1<2=(setColor x y(PixelRGBA8 0 0 0 0)v,h)
 where j n m|PixelRGBA8 0 255 0 _<-getColor n m v=0<1|0<1=0>1
d k r(h,i,j)|r>10*k=(h,i+1,j)|r<k*3/5=(h+10,i,j+1)|1<2=(10-(floor$r/k)+h,i,j)
main=do
 i<-readImageRGBA8 "p.png"
 let(Right c)=imageToCanvas i;s=canvasWidth c;q=[3..s-4];(_,g)=foldr f(c,[])$sort[(sqrt$(z x-z s/2)^2+(z y-z s/2)^2,x,y)|x<-q,y<-q]
 print$foldr(d$z s/20)(0,0,0)g
\$\endgroup\$
2
  • \$\begingroup\$ A tip: all id is the same as and.also, you can implement j with pattern guards j n m|PixelRGBA8 0 255 0 _<-getColor n m v=0<1|0<1=0>1 \$\endgroup\$ Commented Jan 5, 2015 at 12:11
  • \$\begingroup\$ @proudhaskeller: Yes, thanks! \$\endgroup\$
    – nimi
    Commented Jan 5, 2015 at 16:11
2
\$\begingroup\$

Mathematica - 371 386 - 25 = 361

A more optimal solution. Computes the answer much faster than my Python solution.

i=IntegerPart;c=i/@((NestList[#+.01&,.1,10]~Prepend~1)*100);g[m_]:=Last@@c~Position~#-1&/@(i@Round@Last@#&/@(#*100&/@Riffle[RGBColor/@NestList[#+.01&,{.1,.1,.1},10],Table[Disk[{0,0},n],{n,1,.1,-.1}]]~Graphics~{ImageSize->ImageDimensions[m],PlotRangePadding->None}~ImageMultiply~ChanVeseBinarize[m,"TargetColor"->Green]~ComponentMeasurements~"Max"/.Rule[a_,b_]:>b))//{Total@#,#~Count~0}&

Python with PIL - A trivial and non-optimal solution, 961 bytes

This is simply to try to demonstrate a silly approach at solving the problem. It takes ~2 minutes to run the first two test cases, and ~20 minutes to run the third on my system because of the quickly made up, terribly resource intensive, and repulsively algorithmically complex circle detector. Despite this, it does meet requirements, though it is certainly not optimally golfed. The more green there is on the image, the longer it takes to run.

from PIL import Image,ImageDraw
a=lambda x,y,w,h:filter(lambda x:0<=x[0]<w and 0<=x[1]<h,[(x-1,y-1),(x,y-1),(x+1,y-    1),(x-1,y),(x,y),(x+1,y),(x-1,y+1),(x,y+1),(x+1,y+1)])
def b(c):
 d=0,255,0;e,f=c.size;g=c.load();h,i=[],[];j=Image.new("RGB",(e,f));k=ImageDraw.Draw(j)
 for l in range(e):
  for m in range(e):
   n=g[l,m][:-1]
   if n==d and(l,m)not in i:
    o=[(l,m)];p=[];q=1
    while q:
     q=0;r=o[:]
     for s in o:
      t=filter(lambda x:g[x[0],x[1]][:-1]==d and(x[0],x[1]) not in r,a(s[0],s[1],e,f))
      if t:
       r+=t
       if len(t)<8:
        p+=[s]
       q=1
     o=r
    h+=[p]
    for u in o:
     i+=[u]
   i+=[(l,m)]
 p=map(lambda x:"#"+str(x)*6,'123456789ab');v=0;k.rectangle((0,0,e,f),fill=p[0])
 for n in p[1:]:
  w=e/20*v;x=e-w;k.ellipse((w,w,x,x),fill=n);v+=1
 y=j.load();z=0
 for l in h:
  v=[]
  for m in l:
   s=y[m[0],m[1]]
   if s not in v:
    v+=[s]
  v=max(v);z+=p.index("#"+''.join(map(lambda x:hex(x)[2:],v)))
 return z

Takes a PIL image object, and returns the score.

Steps it takes:

  1. Isolate green circles (inefficiently)
    • Find all neighbours of some pixel n, if any are green pixels then add them to the circle
    • Determine rough outline by filtering out pixels that have 8 neighbours
  2. Draw a target representation
    • Create a blank canvas
    • Draw a unique colored background (easy to implement misses)
    • Draw nested ellipses with unique colors
  3. Determine which scoring zones each circle is in by determining the color(s) of the target which would be underneath the circle
  4. Choose the higher of the scoring zones (if multiple) and add the score to the total
  5. Return the total
\$\endgroup\$
1
  • \$\begingroup\$ Your Python function a can be written as a=lambda x,y,w,h:[(X,Y)for X in(x-1,x,x+1)for Y in(y-1,y,y+1)if w>X>-1<Y<h] \$\endgroup\$
    – ovs
    Commented Oct 15, 2018 at 18:07

Your Answer

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

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