36
\$\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 (mousover 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 shortners, 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\$
  • 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\$ – Pavel Oct 27 '16 at 17:01
  • 9
    \$\begingroup\$ If your sample size is 1, import antigravity in Python ;) \$\endgroup\$ – Wayne Werner Oct 27 '16 at 18:11
  • 15
    \$\begingroup\$ Funny fact n=404 xkcd.com/404 is a 404 page. \$\endgroup\$ – Magic Octopus Urn Oct 27 '16 at 18:15
  • 11
    \$\begingroup\$ xkcd is everyone's favorite webcomic [Citation needed] \$\endgroup\$ – Sanchises Oct 27 '16 at 18:21
  • 11
    \$\begingroup\$ Test case: 859 \$\endgroup\$ – betseg Oct 27 '16 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\$
  • \$\begingroup\$ @Matt It worked on all the comics I tried. It only matches images with alt-text so. \$\endgroup\$ – a spaghetto Oct 27 '16 at 17:33
  • \$\begingroup\$ You may not need the quotes around the src attribute. \$\endgroup\$ – Conor O'Brien Oct 27 '16 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\$ – m-chrzan Oct 27 '16 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\$ – a spaghetto Oct 27 '16 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\$ – a spaghetto Oct 27 '16 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\$
  • \$\begingroup\$ Only just got my comment privilege on this sub now, check out my very similar answer \$\endgroup\$ – colsw Oct 28 '16 at 10:00
  • \$\begingroup\$ @ConnorLSW Nice approach in your answer. Things to think about for next time. \$\endgroup\$ – Matt Oct 28 '16 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
    \$\begingroup\$ It would be better if you added the bracket back into the image. \$\endgroup\$ – Matt Oct 27 '16 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\$ – ElPedro Oct 27 '16 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\$ – mınxomaτ Oct 27 '16 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\$ – ElPedro Oct 27 '16 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\$
  • \$\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\$ – AdmBorkBork Oct 28 '16 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\$ – colsw Oct 28 '16 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\$ – a spaghetto Oct 28 '16 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\$ – Matt Oct 28 '16 at 19:32
  • \$\begingroup\$ I accept the 93 byte version, but your shorter one violates the conditions of the puzzle. \$\endgroup\$ – Pavel Oct 29 '16 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\$
  • \$\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\$ – Billywob Oct 28 '16 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\$ – plannapus Oct 28 '16 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\$
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\$
  • 1
    \$\begingroup\$ You can change try:... and except:... to try:n=... and except:n='', saving you 10 bytes total \$\endgroup\$ – Daniel Oct 28 '16 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\$ – Kade Oct 28 '16 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\$ – Ioannes Oct 28 '16 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\$ – Kade Oct 28 '16 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\$ – Mego Jul 8 '18 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
    \$\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\$ – Pavel Jul 1 '17 at 4:39
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\$
1
\$\begingroup\$

Wolfram Language 45 bytes ( Mathematica )

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

Usage with number:

%@"1500"

Usage without number:

%@""
\$\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.