40
\$\begingroup\$

xkcd is everyone's favorite webcomic, and you will be writing a program that will bring a little bit more humor to us all.
Your objective in this challenge is to write a program which will take a number as input and display that xkcd and its title-text (mouseover text).

Input

Your program will take a positive integer as input (not necessarily one for which there exists a valid comic) and display that xkcd: for example, an input of 1500 should display the comic "Upside-Down Map" at xkcd.com/1500, and then either print its title-text to the console or display it with the image.

Due to their proximity across the channel, there's long been tension between North Korea and the United Kingdom of Great Britain and Southern Ireland. Due to their proximity across the channel, there's long been tension between North Korea and the United Kingdom of Great Britain and Southern Ireland.

Test case 2, for n=859:

Brains aside, I wonder how many poorly-written xkcd.com-parsing scripts will break on this title (or ;;"''{<<' this mouseover text."

Brains aside, I wonder how many poorly-written xkcd.com-parsing scripts will break on this title (or ;;"''{<<[' this mouseover text."

Your program should also be able to function without any input, and perform the same task for the most recent xkcd found at xkcd.com, and it should always display the most recent one even when a new one goes up.

You do not have to get the image directly from xkcd.com, you can use another database as long as it is up-to-date and already existed before this challenge went up. URL shorteners, that is, URLs with no purpose other than redirecting to somewhere else, are not allowed.

You may display the image in any way you chose, including in a browser. You may not, however, directly display part of another page in an iframe or similar. CLARIFICATION: you cannot open a preexisting webpage, if you wish to use the browser you have to create a new page. You must also actually display an image - outputting an image file is not allowed.

You can handle the case that there isn't an image for a particular comic (e.g. it is interactive or the program was passed a number greater than the amount of comics that have been released) in any reasonable way you wish, including throwing an exception, or printing out an at least single-character string, as long as it somehow signifies to the user that there isn't an image for that input.

You can only display an image and output its title-text, or output an error message for an invalid comic. Other output is not allowed.

This is a challenge, so the fewest bytes wins.

\$\endgroup\$
15
  • 1
    \$\begingroup\$ @LukeFarritor You can only display the image and output the title text or output some form of error message for an invalid comic. \$\endgroup\$ Commented Oct 27, 2016 at 17:01
  • 11
    \$\begingroup\$ If your sample size is 1, import antigravity in Python ;) \$\endgroup\$ Commented Oct 27, 2016 at 18:11
  • 17
    \$\begingroup\$ Funny fact n=404 xkcd.com/404 is a 404 page. \$\endgroup\$ Commented Oct 27, 2016 at 18:15
  • 12
    \$\begingroup\$ xkcd is everyone's favorite webcomic [Citation needed] \$\endgroup\$ Commented Oct 27, 2016 at 18:21
  • 12
    \$\begingroup\$ Test case: 859 \$\endgroup\$ Commented Oct 27, 2016 at 18:24

11 Answers 11

13
\$\begingroup\$

Perl + curl + feh, 86 84 75 bytes

`curl xkcd.com/$_/`=~/<img src="(.*)" title="(.*?)"/;$_=$2;`feh "http:$1"` 

Requires the -p switch. I have accounted for this in the byte count.

\$\endgroup\$
13
  • \$\begingroup\$ @Matt It worked on all the comics I tried. It only matches images with alt-text so. \$\endgroup\$ Commented Oct 27, 2016 at 17:33
  • \$\begingroup\$ You may not need the quotes around the src attribute. \$\endgroup\$ Commented Oct 27, 2016 at 18:24
  • \$\begingroup\$ I don't think you need the ? in the first match group. You could use -p and $_=$2 instead of print$2, but then the title text is printed only after feh is closed. Not sure if that's valid. \$\endgroup\$ Commented Oct 27, 2016 at 19:17
  • \$\begingroup\$ @m-chrzan Yeah looks like I can drop the reluctant quantifier there, thanks. I thought about using -p but wasn't sure how the OP would feel about it. \$\endgroup\$ Commented Oct 27, 2016 at 23:11
  • \$\begingroup\$ @ConorO'Brien Unfortunately Randall observes good HTML coding practices... and capturing the quotes doesn't then quote the arguments due to how backticks work in Perl. \$\endgroup\$ Commented Oct 27, 2016 at 23:12
9
\$\begingroup\$

PowerShell v3+ 110 99 107 103 Bytes

iwr($x=((iwr "xkcd.com/$args").images|?{$_.title})).src.Trim("/") -outf x.jpg;if($x){ii x.jpg;$x.title} 

Thanks to Timmy for helping save some bytes by using inline assignments.

If no arguments are passed then $args is null and it will just get the current comic. Download the picture, by matching the one with alt text, into a file in the current running directory of the script. Then display it with the default viewer of jpg's. The alt text is then displayed to console. iwr is an alias for Invoke-WebRequest

If the number passed (or any invalid input for that matter) does not match the process fails with at least a 404 error.

iwr( # Request the comic image from XKCD $x=((iwr "xkcd.com/$args").images| # Primary search to locate either the current image # or one matching an argument passed ?{$_.title})) # Find the image with alt text .src.Trim("/") # Using the images associated link and strip the leading slashes -outf x.jpg # Output the image to the directory local to where the script was run if($x){ # Test if the image acquisition was successful ii x.jpg # Open the picture in with the default jpg viewer $x.title # Display alt text to console } # I'm a closing bracket. 
\$\endgroup\$
2
  • \$\begingroup\$ Only just got my comment privilege on this sub now, check out my very similar answer \$\endgroup\$ Commented Oct 28, 2016 at 10:00
  • \$\begingroup\$ @ConnorLSW Nice approach in your answer. Things to think about for next time. \$\endgroup\$ Commented Oct 28, 2016 at 12:16
8
\$\begingroup\$

AutoIt, 440 bytes

Yes, it's long, but it's stable.

#include<IE.au3> #include<GDIPlus.au3> Func _($0='') _GDIPlus_Startup() $1=_IECreate('xkcd.com/'&$0) For $3 In $1.document.images ExitLoop $3.title<>'' Next $4=_GDIPlus_BitmapCreateFromMemory(InetRead($3.src),1) $6=_GDIPlus_ImageGetDimension(_GDIPlus_BitmapCreateFromHBITMAP($4)) GUICreate(ToolTip($3.title),$6[0],$6[1]) GUICtrlSendMsg(GUICtrlCreatePic('',0,0,$6[0],$6[1]),370,0,$4) _IEQuit($1) GUISetState() Do Until GUIGetMsg()=-3 EndFunc 

First of all, this doesn't use RegEx to scrape the site (because I have no time to test this on all comics), but rather uses the Internet Explorer API to iterate through the DOM's img tags until it finds one with a title text.

The binary stream is read from the image URL and rendered into a bitmap using GDIPlus. This is then displayed in a nice, auto-sized GUI with an actual tooltip to make it behave almost exactly like the website.

Here's a test case (_(859)):

)

\$\endgroup\$
4
  • 4
    \$\begingroup\$ It would be better if you added the bracket back into the image. \$\endgroup\$ Commented Oct 27, 2016 at 19:31
  • \$\begingroup\$ Wow, just found out that I can run AutoIt under Wine on Ubuntu. Now all I need to do is get IE working on there. On second thoughts... +1 for the answer \$\endgroup\$ Commented Oct 27, 2016 at 21:03
  • 2
    \$\begingroup\$ @ElPedro If you have a modern CPU, use qemu KVM and seamlessRDP (basically a DIY parallels for linux) instead of wine. This integrates Windows apps seamlessly into the linux desktop and has 100% compatibility + GPU passthrough. Just a tip. \$\endgroup\$ Commented Oct 27, 2016 at 21:05
  • \$\begingroup\$ Thanks @mınxomaτ. I may give that a go. I work with Windows all day so prefer to use Linux (various flavours) out of work but I am always interested in experimenting :) Tip gratefully received. \$\endgroup\$ Commented Oct 27, 2016 at 21:14
7
\$\begingroup\$

Powershell, 93 Bytes

93 Byte version to use local image viewer.

$n=(iwr xkcd.com/$args).images|?{$_.title};$n.title;iwr ("http:"+$n.src) -OutF x.jpg;ii x.* 

Saved 2 bytes by removing needless doublequotes, then another lot by using ("http:"+$n.src) instead of "https://"+$n.src.trim("/") - since the img src comes with // already on it, and xkcd doesn't require https.

$n=(iwr xkcd.com/$args).images|?{$_.title};$n.title;saps ("http:"+$n.src) 

$n=(iwr "xkcd.com/$args").images|?{$_.title};$n.title;saps ("https://"+$n.src.trim("/"))

extremely similar to Matts powershell answer, (should probably be a comment but low reputation)

Instead this opens a new tab/window in the default browser, and other things, saving some bytes.

iwr is an alias for Invoke-WebRequest

saps is an alias for Start-Process which opens 'it' in the default context.

\$\endgroup\$
5
  • \$\begingroup\$ Skirting the edges of the rules about opening an existing webpage, because this is just directly launching the browser at the pre-existing .jpg (or whatever), but a nice answer. \$\endgroup\$ Commented Oct 28, 2016 at 12:47
  • \$\begingroup\$ @TimmyD might have misunderstood here then - I assumed you can use the xkcd webpage itself - you can just change saps to iwr and append ` -OutF x.jpg;ii x.*` to the end if you want it to open in the default local image viewer. \$\endgroup\$ Commented Oct 28, 2016 at 13:19
  • 1
    \$\begingroup\$ The OP specified that you aren't allowed to open a pre-existing webpage. The 93 byte version I think is fine though \$\endgroup\$ Commented Oct 28, 2016 at 18:41
  • \$\begingroup\$ I don't think this is going to always work the same if you give a number that works then a number that does not. It will open the image that existed from the previous run. \$\endgroup\$ Commented Oct 28, 2016 at 19:32
  • \$\begingroup\$ I accept the 93 byte version, but your shorter one violates the conditions of the puzzle. \$\endgroup\$ Commented Oct 29, 2016 at 3:07
4
\$\begingroup\$

R, 358 328 310 298 bytes

f=function(x){H="http:";p=paste0;library(XML);a=xpathSApply(htmlParse(p(H,'//xkcd.com/',x)),'//div/img',xmlAttrs)[[1]];download.file(p(H,a[1]),'a');I=`if`(grepl('png',a[1]),png::readPNG,jpeg::readJPEG)('a');d=dim(I)/100;quartz(,d[2],d[1]);par(mar=rep(0,4));frame();rasterImage(I,0,0,1,1);cat(a[2])} 

With new lines and comments:

f=function(x){ H="http:" p=paste0 library(XML) #Needed for xpathSApply, htmlParse and xmlAttrs # The following line find the first img element and extract its attributes a=xpathSApply(htmlParse(p(H,'//xkcd.com/',x)),'//div/img',xmlAttrs)[[1]] download.file(p(H,a[1]),'a') #Download to a file called 'a' I=`if`(grepl('png',a[1]),png::readPNG,jpeg::readJPEG)('a') #Check if png or jpeg and load the file accordingly d=dim(I)/100 #convert dimension from pixel to inches (100 ppi). quartz(,d[2],d[1]) #open a window of the correct dimension par(mar=rep(0,4)) #Get rid of margins frame() #Create empty plot rasterImage(I,0,0,1,1) #Add png/jpeg to the plot cat(a[2]) #Print title text to stdout } 

Screenshots of test cases:

for x=1500: for x=1500 (png)

for x empty:
for x=''

case when picture is a jpeg:
for x=10 (jpeg)

x=859:
x=859

\$\endgroup\$
2
  • \$\begingroup\$ I wonder if it's necessary to display the image in the correct dimensions. When I was playing around with this challenge I simply did plot.new();rasterImage(...). \$\endgroup\$ Commented Oct 28, 2016 at 8:43
  • \$\begingroup\$ @Billywob Well it would be completely distorted as the default size for the plot is 7x7 inches. It's true that the OP didn't explicitely ask for the image to not be distorted but I prefer it this way :) I am however considering getting rid of the xaxs and yaxs as the result would still be proportionate. \$\endgroup\$ Commented Oct 28, 2016 at 8:47
3
\$\begingroup\$

PHP, 95 bytes

<?php echo str_replace("title=\"","/>",file_get_contents("https://xkcd.com/".$_GET["id"])); ?> 

Save as main.php, run server

php -S localhost:8123 

Open http://localhost:8123/main.php?id=1500

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

Python 2.7, 309 299 295 274 bytes

Full program. Definitely more golfable, but having read xkcd comics for so long I couldn't let this pass (who knows if this will be helpful in a future for easily browsing xkcd).

If no input is passed, gets current comic. If a valid comic number is passed as input then gets that comic. If an invalid input (not a number comic in the valid range) is passed, throws an error.

Any suggestions on how to reduce byte count are welcome! Will revisit (and add explanation) when I have more time.

-10 bytes thanks to @Dopapp

-21 bytes thanks to @Shebang

import urllib as u,re from PIL import Image h='http://';x='xkcd.com/' o=u.URLopener() t=u.urlopen(h+x+raw_input()).read() c=sum([re.findall(r,t)for r in[h+'imgs.'+x+'c.*s/.*\.\w{1,3}','\.\w{1,3}" t.*e="(.*)" a']],[]) Image.open(o.retrieve(c[0],'1.png')[0]).show();print c[1] 
\$\endgroup\$
5
  • 1
    \$\begingroup\$ You can change try:... and except:... to try:n=... and except:n='', saving you 10 bytes total \$\endgroup\$ Commented Oct 28, 2016 at 1:55
  • 1
    \$\begingroup\$ Why do you even have a try statement? The program spec says you will always get a positive integer. \$\endgroup\$ Commented Oct 28, 2016 at 12:46
  • \$\begingroup\$ @Shebang it should also return the latest comic when there is no input. I wasn't able to manage this case without carching the exception of input error. \$\endgroup\$ Commented Oct 28, 2016 at 13:53
  • 1
    \$\begingroup\$ @Joannes Why not use raw_input()? By default the user can press [Enter] and n will contain the empty string anyways. If you remove that try-except block and do t=u.urlopen(h+x+n).read() -> t=u.urlopen(h+x+raw_input()).read() you get it down to 274 bytes. \$\endgroup\$ Commented Oct 28, 2016 at 14:06
  • \$\begingroup\$ This no longer works, because xkcd image URLs use https. However, it's still valid, because it worked at the time of posting. To make it work now, change line 3 to start with h='https://' for +1 byte. \$\endgroup\$ Commented Jul 8, 2018 at 18:47
2
\$\begingroup\$

PHP, 42 bytes

<?=@file('http://xkcd.com/'.$_GET[i])[59]; 

Save to a file and fire it up in your web server of choice

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Welcome to PPCG! I don't know PHP, but there doesn't seem to be a part of your code that's fetching the image's title text? \$\endgroup\$ Commented Jul 1, 2017 at 4:39
2
\$\begingroup\$

Wolfram Language 45 bytes ( Mathematica )

Import["https://xkcd.com/"<>#,"Images"][[2]]& 

Usage with number:

%@"1500" 

Usage without number:

%@"" 
\$\endgroup\$
1
\$\begingroup\$

JavaScript + HTML, 124 + 18 = 142 bytes

i=>fetch(`//crossorigin.me/http://xkcd.com/${i||""}/info.0.json`).then(r=>r.json()).then(d=>(A.innerHTML=d.alt,B.src=d.img)) 
<img id=B><p id=A> 

Cross-origin solution thanks to Kaiido's answer here.

17 bytes (//crossorigin.me/) can be saved if the proxy required to connect to xkcd.com can be subtracted (meta post about this).

Test Snippet

f= i=>fetch(`//crossorigin.me/http://xkcd.com/${i||""}/info.0.json`).then(r=>r.json()).then(d=>(A.innerHTML=d.alt,B.src=d.img))
<style>img{width:50%}</style><input id=I> <button onclick="f(I.value)">Run</button><br> <img id=B><p id=A>

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

Python 3 + Requests + PIL, 192 186 bytes

from requests import* import PIL.Image as f from io import* r=get("https://xkcd.com/%s/info.0.json"%input()).json() f.open(BytesIO(get(r["img"],stream=1).content)).show() print(r["alt"]) 

Opens up an image viewer (whichever is default on the system it's being run on) containing the comic, and posts the title text to the console.

\$\endgroup\$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.