20
\$\begingroup\$

The Penrose triangle, also known as the Penrose tribar, or the impossible tribar, is an impossible object.

The goal in this challenge is to display a Penrose triangle in the fewest bytes possible.

Penrose triangle

Source: Wikipedia

Rules:

  1. You must display the Penrose Triangle digitally after generating it.
  2. They must look the same as the above image of the wiki page (source above) without directly showing the image.
  3. The same image with the same color scheme must be shown in at least 400x400 size.
  4. Should be as accurate as possible.

Good luck and have fun!

\$\endgroup\$
16
  • 3
    \$\begingroup\$ I think this has the potential to be a great challenge, but there are certain specs that need to be clarified like the colours of the image and its dimensions. \$\endgroup\$
    – user41805
    Commented Feb 28, 2017 at 17:36
  • 5
    \$\begingroup\$ PS! Don't be discouraged just because the challenge is closed. If it was a bad challenge idea then the close votes would be accompanied with downvotes... :) \$\endgroup\$ Commented Feb 28, 2017 at 18:10
  • 2
    \$\begingroup\$ @DigitalTrauma I'd say not. That has many more details to draw. \$\endgroup\$
    – mbomb007
    Commented Feb 28, 2017 at 19:01
  • 1
    \$\begingroup\$ Will any grey do or does it have to be the exact same grey? If it's the latter, it would be nice to provide the exact grey tone in the challenge text. \$\endgroup\$ Commented Mar 1, 2017 at 13:58
  • 1
    \$\begingroup\$ Aspect rations would also be helpful if they have to be reproduced exactly. \$\endgroup\$ Commented Mar 1, 2017 at 14:00

10 Answers 10

11
\$\begingroup\$

SVG (HTML5), 191 bytes

<svg width=498 height=433 stroke=#000><path d=M211,134l38,66L154,365H496L458,431H40 /><path fill=#777 d=M211,2L2,365l38,66L211,134l95,165h76 /><path fill=#FFF d=M496,365L287,2H211L382,299H192

\$\endgroup\$
1
  • \$\begingroup\$ That's really nice! \$\endgroup\$ Commented May 15, 2017 at 4:35
8
\$\begingroup\$

Python 2, 211 201 195 188 175 173 bytes

from turtle import*
d="t(120);fd(333);rt(120);fd(67);"
s="color(0,%r);begin_fill();fd(200);l"+d+"rt(60);fd(400);r"+d+"end_fill();fd(133);rt(180);"
exec s%'#fff'+s%0+s%'gray'

Unfortunately, exec is not implemented in Trinket, so this cannot be tested online as-is. At least, not in the free version. I printed out the string and pasted as code to test it. If you're clever with scripts, you could resize the html/css as necessary to get a larger canvas. Let me know if you do.

Try it online - uses a smaller size since the site's canvas is too small for 400px, but you can see the entire output.

Ungolfed:

from turtle import*
w=200
def f(n):
  c=255*n/2
  color(0,(c,c,c))
  begin_fill()
  fd(w)
  lt(120)
  fd(5*w/3)
  rt(120)
  fd(w/3)
  rt(60)
  fd(2*w)
  rt(120)
  fd(5*w/3)
  rt(120)
  fd(w/3)
  end_fill()
  fd(2*w/3)
  rt(180)
f(2);f(0);f(1)
\$\endgroup\$
2
  • \$\begingroup\$ I'm wondering if the 255*n/2 can be decreased to 128*n I think float RGB values will round up anyways so would there be a change in pixel-colors? \$\endgroup\$ Commented Feb 28, 2017 at 21:06
  • \$\begingroup\$ @AlbertRenshaw That's the ungolfed code. See the code above that for the golfed version. Also, this is Python 2, so they aren't floats, they're integers, since the division is integer division. \$\endgroup\$
    – mbomb007
    Commented Feb 28, 2017 at 21:06
6
\$\begingroup\$

PHP, 153 bytes

This will only work if the short_open_tag setting is enabled. The source code contains unprintable characters, so have a hex dump instead:

0000000: 3c3f 3d67 7a69 6e66 6c61 7465 2827 b329  <?=gzinflate('.)
0000010: 2e4b 5728 cb4c 2d77 caaf b0d5 3531 3000  .KW(.L-w....510.
0000020: 611d 0b08 5628 2e29 cacf 4eb5 5536 3030  a...V(.)..N.U600
0000030: b0b3 2948 2cc9 5048 b1f5 d535 35d6 3536  ..)H,.PH...55.56
0000040: b7d0 8152 c6a6 263a 8626 c6ba 8626 0660  ...R..&:.&...&.`
0000050: dac2 5cc7 14c8 b234 0452 510a 6999 3939  ..\....4.RQ.i.99
0000060: b6ca 6969 690a 2545 8979 c569 f945 b9b6  ..iii.%E.y.i.E..
0000070: 45f9 2589 25a9 1a06 9a0a fa14 990a 7415  E.%.%.........t.
0000080: a6a9 8646 949a 9b5e 945a 8969 ae2e cc60  ...F...^.Z.i...`
0000090: 7d60 88d9 0100 2729 3b                   }`....');

The decompressed data looks like this (with line breaks added for legibility):

<svg viewBox=-400-400,800,800 stroke=#000>
<path d=M-53-378,53-378,354,143-140,143-87,50,191,50Z fill=#fff transform=rotate(0) />
<path d=M-53-378,53-378,354,143-140,143-87,50,191,50Z fill=#000 transform=rotate(120) />
<path d=M-53-378,53-378,354,143-140,143-87,50,191,50Z fill=grey transform=rotate(-120) />
</svg>

Although the SVG data isn't entirely valid, PHP serves it as text/html by default. Without a doctype declaration, the document is handled in quirks mode, which is very forgiving.

To improve the compression, I broke the image into three "7"-shaped parts that can be drawn using near-identical <path> elements. The resulting image will expand to fill the viewport. Here's a screen grab from a 500×500 pixel window:

Screen grab of 500×500 pixel Penrose triangle SVG image

\$\endgroup\$
5
\$\begingroup\$

HTML + JS (ES6), 34 + 306 = 340 bytes

Makes use of a 30 degree horizontal skew - in the 3rd argument of the matrix transform, the tangent of 30° is represented as pow(3,-.5).

There are quite a few ugly magic numbers, and it doesn't quite match the proportions of the Wikipedia image. I'm certain there is a more "mathematical" way to go about this; any help would be appreciated.

See the ungolfed version on CodePen.

f=

_=>{with(Math)with(C=c.getContext`2d`)for(l=lineTo.bind(C),lineWidth=.01,transform(50,0,0,50,200,224),N=4;N--;rotate(PI*2/3))beginPath(fill(save(fillStyle=N?N>1?'#fff':'#000':'#777'))),transform(-1,0,-pow(3,-.5),-1,3.965,1.71),l(0,0),l(0,6),l(1,6),l(1,1),l(4.616,1),l(5.772,0),closePath(restore(stroke()))}

f()
<canvas id=c width=400 height=400>

\$\endgroup\$
4
+50
\$\begingroup\$

logo, 129 120 bytes

Draws only the first 4 sides of each L shape, then lifts the pen, moves to the corresponding spot on the next L shape, lowers the pen and draws 4 sides of that. Each L shape borrows 2 sides from the previous one.

Latest edits: move from black fill area to grey fill area using fd instead of setx, and change all movements from fd to bk to save one byte on a 180 deg rotation: rt 210 -> rt 30, shorten setpencolor to setpc (undocumented in the intepreter I'm using, but it works.)

rt 30 repeat 3[pd bk 200 lt 120 bk 360 rt 120 bk 80 rt 60 bk 440 pu rt 139 bk 211 rt 41] setx -2 fill fd 9 setpc 15 fill

logo, 140 bytes

Draws the 6 sides of each L shape, overshoots on the last edge, then turns 180 deg to start the next one.

rt 30 repeat 3[rt 180 fd 200 lt 120 fd 360 rt 120 fd 80 rt 60 fd 440 rt 120 fd 360 rt 120 fd 200] pu setx -5 fill setx 5 setpencolor 15 fill

run at http://www.calormen.com/jslogo/#

It is recommended to do cs pd setpencolor 0 before running to ensure the screen is clear, the turtle is centred and pointing upwards, the pen is down and set to black (default settings, not required for a brand new session) and also ht to hide the turtle (st will show it again.)

enter image description here

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

Mathematica 171 Bytes

w=(v=AnglePath)[s={{9,0},{11,2(b=Pi/3)},{2,b},{9,2b},{5,-2b},{2,b}}];x={w[[5]],2b}~v~s;y={x[[5]],-2b}~v~s;Graphics@{White,EdgeForm[Black],(p=Polygon)@w,Gray,p@x,Black,p@y}

Draws 3 polygons using AnglePath, multiples of 60 degree turns, and taking advantage that the starting point for each polygon is the 5th point of the previous polygon.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Nice approach, using AnglePath. \$\endgroup\$
    – DavidC
    Commented May 9, 2017 at 23:43
3
\$\begingroup\$

HTML + CSS, 9 + 315 309 308 = 317 bytes

Borders and skews galore! Tested on Chrome. See the ungolfed version on CodePen.

body{margin:9em}b,:after{position:fixed;transform:rotate(240deg)}b:after{content:'';left:-6.1em;top:-7.95em;width:6em;height:9em;border-left:transparent 2.32em solid;border-right:2em solid;border-bottom:2em solid;transform:skew(30deg);filter:drop-shadow(0 0 .1em)}b{color:#777}b>b{color:#000}b>b>b{color:#fff
<b><b><b>

\$\endgroup\$
2
  • \$\begingroup\$ Does it fulfil the requirement of min 400x400px? \$\endgroup\$
    – sergiol
    Commented Sep 25, 2017 at 22:38
  • \$\begingroup\$ You don't need the final >, right? \$\endgroup\$
    – Stan Strum
    Commented Sep 30, 2017 at 23:54
2
\$\begingroup\$

Tcl/Tk, 205

grid [canvas .c -w 402 -he 402]
.c cr p 171 2 237 2 401 337 125 337 156 280 301 280 -f #FFF
.c cr p 2 335 171 2 310 280 250 280 171 121 31 401 -f gray
.c cr p 171 127 34 401 374 401 401 337 127 337 201 188

Penrose triangle

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

Java 8, 367 351 bytes

import java.awt.*;v->new Frame(){public void paint(Graphics g){g.drawPolygon(new int[]{193,348,178,148,448,263},new int[]{38,318,318,373,373,38},6);g.fillPolygon(new int[]{448,148,233,198,43,418},new int[]{373,373,223,158,438,438},6);g.setColor(Color.GRAY);g.fillPolygon(new int[]{43,198,283,348,193,8},new int[]{438,158,318,318,38,378},6);}{show();}}

-16 bytes by drawing on the Frame itself instead of an inner Panel. Now all x-coordinates are increased by 8 to adjust for the left side of the Frame border, and all y-coordinates are increased by 38: 8 to adjust for the top side of the Frame border and 30 for the Frame title-bar.

The Penrose triangle itself has a height of 400 and a width of 440 pixels (since it has to be at least 400x400 by the challenge rules).

Output:

enter image description here

Explanation:

// Method with empty unused parameter and Frame return-type
v->
  // Create a Frame (window for graphical output)
  new Frame(){
    // Override it's default paint method
    public void paint(Graphics g){
      // Draw the white with black outlined 6-pointed polygon:
      g.drawPolygon(new int[]{193,348,178,148,448,263},new int[]{38,318,318,373,373,38},6);
      // Draw the black 6-pointed polygon:
      g.fillPolygon(new int[]{448,148,233,198,43,418},new int[]{373,373,223,158,438,438},6);
      // Change the color to dark gray:
      g.setColor(Color.GRAY);
      // Draw the dark gray 6-pointed polygon:
      g.fillPolygon(new int[]{43,198,283,348,193,8},new int[]{438,158,318,318,38,378},6);}
    // Start an initializer block for this Frame
    {
      // And finally show the Frame
      show();}}
\$\endgroup\$
1
\$\begingroup\$

JRuby + propane, 343 bytes

require'propane'
class T<Propane::App
def settings;size(402,402);end
def draw;noStroke
fill(255);quad(171,2,237,2,401,337,335,337);quad(335,337,125,337,156,280,308,280)
fill(125);quad(2,335,171,2,171,121,31,401);quad(171,2,171,121,250,280,308,280)
fill(0);quad(171,127,34,401,127,337,201,188);quad(34,401,127,337,401,337,374,401)
end;end;T.new

JRuby + Propane is a relatively painless way to use Processing without writing lots of Java boilerplate. Getting it running on my Macbook was the hard part! Used some data from the Tcl/Tk solution. Run as follows:

jruby penrose_tr_golf.rb
\$\endgroup\$
3
  • \$\begingroup\$ TODO: Try and use PShape to draw an L and rotate it... \$\endgroup\$
    – roblogic
    Commented Jun 10 at 3:32
  • \$\begingroup\$ Just curious, what this has to do with propane? By coincidence a triangular cyclic carbohydrate is known as a cyclopropane... \$\endgroup\$ Commented Jun 10 at 10:05
  • \$\begingroup\$ Ha, interesting! I don't know the author's reason for choosing that name for the ruby gem. \$\endgroup\$
    – roblogic
    Commented Jun 10 at 10:26

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.