visit
Many years ago, I first wrote up my experience working with the . I find myself returning to it again and again, and this weekend, I built a fun little game I think you may enjoy. It's called "Guess the Decade".
Marvel's art style has changed drastically over its long history. Back in 2018, I shared a that demonstrates just how much variety you can get just by looking at covers. So for example, Spider-Man in 1962:
And then 2018:
const IMAGE_NOT_AVAIL = "//i.annihil.us/u/prod/marvel/i/mg/b/40/image_not_available";
const getRandomInt = function(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
async function getCover(pubKey,priKey) {
//first select a random year
let year = getRandomInt(1950, new Date().getFullYear()-1);
//then a month
let month = getRandomInt(1,12);
let monthStr = month<10?"0"+month:month;
//lame logic for end of month
let eom = month==2?28:30;
let beginDateStr = year + "-" + monthStr + "-01";
let endDateStr = year + "-" + monthStr + "-" + eom;
let url = "//gateway.marvel.com/v1/public/comics?limit=100&format=comic&formatType=comic&dateRange="+beginDateStr+"%2C"+endDateStr+"&apikey="+pubKey;
// add hash
let ts = new Date().getTime();
let myText = new TextEncoder().encode(ts + priKey + pubKey);
let hash = await crypto.subtle.digest({
name:'MD5'
}, myText);
const hexString = [...new Uint8Array(hash)]
.map(b => b.toString(16).padStart(2, '0'))
.join('');
url += '&hash='+encodeURIComponent(hexString)+'&ts='+ts;
let result = await fetch(url);
let data = (await result.json()).data;
if(!data.results) {
throw('No results available.');
}
let resultData = data.results;
console.log('initial set',resultData.length);
// if(comic.thumbnail && comic.thumbnail.path != IMAGE_NOT_AVAIL) {
let comics = resultData.filter(c => {
return c.thumbnail && c.thumbnail.path !== IMAGE_NOT_AVAIL;
});
console.log('now we have ',comics.length);
let selectedComic = comics[getRandomInt(0, comics.length-1)];
//console.log(JSON.stringify(selectedComic,null,'\t'));
//rewrite simpler
let image = {};
image.title = selectedComic.title;
for(let x=0; x<selectedComic.dates.length;x++) {
if(selectedComic.dates[x].type === 'onsaleDate') {
image.date = new Date(selectedComic.dates[x].date);
//rewrite nicer
image.date = `${image.date.getMonth()+1}/${image.date.getFullYear()}`;
}
}
image.url = selectedComic.thumbnail.path + "." + selectedComic.thumbnail.extension;
if(selectedComic.urls.length) {
for(let x=0; x<selectedComic.urls.length; x++) {
if(selectedComic.urls[x].type === "detail") {
image.link = selectedComic.urls[x].url;
}
}
}
return image;
}
export default {
async fetch(request, env, ctx) {
const PRIVATE_KEY = env.MARVEL_PRIVATE_KEY;
const PUBLIC_KEY = env.MARVEL_PUBLIC_KEY;
let cover = await getCover(PUBLIC_KEY, PRIVATE_KEY);
return new Response(JSON.stringify(cover), {
headers: {
'Content-Type':'application/json;charset=UTF-8',
'Access-Control-Allow-Origin':'*',
'Access-Control-Allow-Methods':'GET'
}
});
},
};
Starting at the bottom, the fetch
method there will be fired when a request comes in for the function. It uses two credentials I've set up as secrets in Cloudflare and then calls the main getCover
function. In the response, I return the result and use CORS headers as my demo lives on another domain at CodePen.
getCover
is code I've shown before, it's what drives my Mastodon bot, but the idea is to basically select a random year, random month, and then ask for 100 comics from that period. I filter out for comics that don't have a cover and then return a random selection. I do a bit of manipulation on the result to return a very small part of the comic.
{
"title": "Conan the Barbarian (1970) #54",
"date": "9/1975",
"url": "//i.annihil.us/u/prod/marvel/i/mg/9/70/646cc9d27a80a.jpg",
"link": "//marvel.com/comics/issue/72071/conan_the_barbarian_1970_54?utm_campaign=apiRef&utm_source=fe877c0bf61f995fc8540d9eac4704f1"
}
{
"title": "Doctor Strange (1974) #66",
"date": "8/1984",
"url": "//i.annihil.us/u/prod/marvel/i/mg/3/d0/621679f213e8d.jpg",
"link": "//marvel.com/comics/issue/20149/doctor_strange_1974_66?utm_campaign=apiRef&utm_source=fe877c0bf61f995fc8540d9eac4704f1"
}
<script defer src="//cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
<div x-data="app" x-cloak>
<div x-show="intro">
<p>
Welcome to the Marvel Guess the Decade game. In this game, I'll show you a random comic cover selected anywhere from 1950 to the 2020s. You need to guess the decade the comic was released.
</p>
<button @click="start">Get Started</button>
</div>
<div x-show="!intro">
<img x-show="imageSrc" :src="imageSrc" class="cover">
<div x-show="!imageSrc">
<p><i>Loading cover...</i></p>
</div>
<div x-show="guessReady" class="buttonRow">
<!-- hard coded for 1950 to 2020. todo: come back in 7 years -->
<template x-for="i in 8">
<button @click="guess(1950+((i-1)*10))"><span x-text="1950+((i-1)*10)"></span>s</button>
</template>
</div>
<div x-show="resultReady">
<p>
<strong x-text="msg"></strong> This cover is from <span x-text="cover.title"></span> in <span x-text="cover.year"></span>.
</p>
<p>
<button @click="getImage">Guess Again</button>
</p>
</div>
<p>
You have currently gotten <span x-text="right"></span> correct and <span x-text="wrong"></span> wrong.
</p>
</div>
</div>
intro:true,
right: 0,
wrong: 0,
imageSrc:null,
guessReady: false,
resultReady: false,
cover: { title:null, year:null },
msg:'',
I'm not a fan of how I defined cover
, but I got warnings in Alpine when it parsed the HTML. Now, I knew it wasn't a "real" issue because that HTML wouldn't be displayed till I had a cover, but I wanted the warning to go away. Next time, I'll use two variables, like coverTitle
and coverYear
.
The start
method referred to from the button in the initial state just switches us to the main state and gets the first image.
start() {
this.intro = !this.intro;
this.getImage();
},
Now, let's look at getImage
, as there's an interesting bit in here:
async getImage() {
this.imageSrc = null;
this.guessReady = false;
this.resultReady = false;
let coverReq = await fetch('//randomcover.raymondcamden.workers.dev');
this.cover = await coverReq.json();
// add .year based on date
this.cover.year = this.cover.date.split('/').pop();
/*
So, I kept seeing comics with WILDLY different years and figured out we could use regex.
I'm keeping the code above, just in case, but this code here should help.
*/
let found = this.cover.title.match(/\(([1-2][0-9]{3})\) #/);
if(found) {
console.log(`changing year to ${found[1]}`);
this.cover.year = found[1];
}
this.imageSrc = this.cover.url;
this.guessReady = true;
console.log(this.cover);
},
Uncanny Inhumans (2015) #8
Journey Into Mystery (1952) #31
Strange Tales (1951) #110
Thor (1966) #430
guess(decade) {
// so decade will come in as a base 4d year, like 2000,
// my year from the API is m/yyyy. so we can easily get y,
// and then simply check the first 3 digits
console.log('guessing',decade);
console.log('right is', this.cover.year);
if(decade.toString().substring(0,3) === this.cover.year.substring(0,3)) {
this.msg = 'You got it right!';
this.right++;
} else {
this.msg = 'Sorry, you were wrong!';
this.wrong++;
}
this.guessReady = false;
this.resultReady = true;
}
img.cover {
width: 415px;
height: 615px;
object-fit: cover;
object-position: top;
display: block;
margin: auto;
}
This does not work properly all the time, but it works well enough most of the time. Enjoy! (Note: you could run it in the CodePen below, but it's going to be pretty small. Either hide the source or click "Edit on CodePen" to see it bigger.)
Also published .