<<widget "legos" container>>
<span class="legos-box">
<img class="legos-image" @src="setup.ImagePath + _args[0].toLowerCase() + '.jpg'">\
<img class="legos-image" @src="setup.ImagePath + _args[1].toLowerCase() + '.jpg'">\
<img class="legos-image" @src="setup.ImagePath + _args[2].toLowerCase() + '.jpg'">\
<img class="legos-image" @src="setup.ImagePath + _args[3].toLowerCase() + '.jpg'">\
<img class="legos-image" @src="setup.ImagePath + _args[4].toLowerCase() + '.jpg'">\
<img class="legos-image" @src="setup.ImagePath + _args[5].toLowerCase() + '.jpg'">\
<img class="legos-image" @src="setup.ImagePath + _args[6].toLowerCase() + '.jpg'">\
<img class="legos-image" @src="setup.ImagePath + _args[7].toLowerCase() + '.jpg'">
</span>
<</widget>>Happy that you are here this dark and spooooooky night!
For the best experience:
* please turn on your sound and headphones
* have paper and pen at hand to take notes
* everything runs in chrome or firefox (safari won't work)
* the hints are there to help you, use them when you need (you won't be penalized)
* don't worry too much about your 'lives' and the timer, you will never 'die' and can always finish the game
<a data-passage="IntroductionText" class="link-internal link-image">
<img @src="setup.ImagePath+'horrorVu.png'" style="display: block; margin: auto;"
width=100%>
</a><<run UIBar.destroy()>>
<<set $MaxHP = 9>> /* Maximum Hit Points */
<<set $CurHP = 9>> /* Current Hit Points */
<<set _bgm = setup.SoundPath + "A_really_dark_alley.mp3">>>>
<<cacheaudio "night_bgm" _bgm>>
<<createaudiogroup ":ui">>
<<track "night_bgm">>
<</createaudiogroup>>
<<set _bgm2 = setup.SoundPath + "Unseen-Horrors.mp3">>>>
<<cacheaudio "start_bgm" _bgm2>>
<<createaudiogroup ":ui">>
<<track "start_bgm">>
<</createaudiogroup>>
<<set _bgm3 = setup.SoundPath + "Victory.mp3">>>>
<<cacheaudio "end_bgm" _bgm3>>
<<createaudiogroup ":ui">>
<<track "end_bgm">>
<</createaudiogroup>>
<<set _bgm4 = setup.SoundPath + "Echoes.mp3">>>>
<<cacheaudio "echoes_bgm" _bgm4>>
<<createaudiogroup ":ui">>
<<track "echoes_bgm">>
<</createaudiogroup>>
<<set _bgm5 = setup.SoundPath + "Tension.mp3">>>>
<<cacheaudio "tension_bgm" _bgm5>>
<<createaudiogroup ":ui">>
<<track "tension_bgm">>
<</createaudiogroup>>
<<set $DocPuzzle = false>>
<<set $VersionControlPuzzle = false>>
<<set $ProprietaryPuzzle = false>>
<<set $WhatisPuzzle = false>>
<<set $ZenodoPuzzle = false>>
<<set $LicensePuzzle = false>>
<<set $CitationPuzzle = false>>
<<set $timeleft = 3600>><<nobr>><<if $CurHP < 2 >>
Ow man, almost out of cat lives... Luckily the cat is distracted by a butterfly. Quick, add some more.
<<button "expand livespan">>
<<set $CurHP = $CurHP+1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>><</button>>
<</if>>
<</nobr>>
<<if passage() neq "start" and passage() neq "IntroductionText" and passage() neq "IntroductionText2" and passage() neq "IntroductionText3" and passage() neq "IntroductionText4" and passage() neq "EndingCredits" and passage() neq "EndingCredits2" and passage() neq "EndingInfos">>
<div id="horizontalhealthbarbkg" class="hzbarbkg"><div id="horizontalhealthbar" class="hzbar"></div></div>
<<script>>$(document).one(':passagerender', function (ev) {
Health(State.variables.CurHP, State.variables.MaxHP, "horizontalhealthbar", true, ev.content);
});<</script>>
<<nobr>>
<<set _seconds = new Date().getSeconds()>>
<<set _minutes = new Date().getMinutes()>>
<<set _hours = new Date().getHours()>>
<<set _counter = Math.floor($timeleft - ((_hours*3600+_minutes*60+_seconds)-($hours*3600+$minutes*60+$seconds)))>>
<<countdownTimer _counter "TimeUP">>
<</nobr>>
<</if>><video autoplay muted loop id="myVideo">
<source @src='setup.ImagePath + "rain.mp4"' type="video/mp4">
</video>
<div class="content">\
<<timed 4000ms>>Raindrops cascade across your window; the wind howls at a crimson moon.
<<next 2000ms>>But you are warmed by the glow of your laptop…
<<next 2000ms>>and the knowledge that after years of work, you are finally ready to submit this completed research project for publication.
<<next 2000ms>>All that remains is a single click, sending your data and article off to that prestigious publisher, <i>Frontiers in Hell</i>.<br><img @src="setup.ImagePath+'frontiers.png'" class="center">
<<next 2000ms>>You take a deep breath and press that final button and move on to the [[next–|IntroductionText2]]
<</timed>>
</div><<nobr>>
<<set _textA = "">>
<<set _textC = "">>
<<set _textD = "">>
<<set _textF = "">>
<<set _textG = "">>
<<set _textI = "">>
<<set _continue = "">>
<</nobr>>
You grab the paper as if your life depended on it. Well, it does, doesn't it?
"I might have added a few wrong ones", mumbles the cat with amusement. "This makes it so much more fun for me. Now my dear, choose the right ones and you are done!"
You hesitate and the cat starts hissing: "starting a new project! What is important?! Quickly!"
<section class="paper">\
<article class="paperheader"></article>\
<div class="papertext">
* <label><<checkbox "_checkedA" false true autocheck>> Contain the project in a single recognizable folder</label>\
<span id="textA"><<print _textA>></span>
* <label><<checkbox "_checkedB" false true autocheck>> Various folder types are distinguished for read-only, human-generated and project-generated files</label>
* <label><<checkbox "_checkedC" false true autocheck>> Documentation is created</label>\
<span id="textC"><<print _textC>></span>
* <label><<checkbox "_checkedD" false true autocheck>> A licence is chosen so others know in which way they can use your code</label>\
<span id="textD"><<print _textD>></span>
* <label><<checkbox "_checkedF" false true autocheck>> use “final” to mark your version</label>\
<span id="textF"><<print _textF>></span>
* <label><<checkbox "_checkedG" false true autocheck>> use as many abbreviations as possible</label>\
<span id="textG"><<print _textG>></span>
* <label><<checkbox "_checkedI" false true autocheck>> Include sensitive data in your software repository</label>\
<span id="textI"><<print _textI>></span>
</div>\
</section>
<<link "Let's check if it's right">>
<<if not _checkedA>>
<<set _textA = "@@.red;You missssss something important@@">>
<<else>>
<<set _textA = "">>
<</if>>
<<replace "#textA">>
<<print _textA>><</replace>>
<<if _checkedC>>
<<set _textC = "@@.green;There are many ways to document your code. This may require some more work later...@@">>
<<else>>
<<set _textC = "">>
<</if>>
<<replace "#textC">>
<<print _textC>><</replace>>
<<if _checkedD>>
<<set _textD = "@@.green;A license defines what can and cannot be done, don't wait too long with choosing it.@@">>
<<else>>
<<set _textD = "">>
<</if>>
<<replace "#textD">>
<<print _textD>><</replace>>
<<if _checkedF>>
<<set _textF = "@@.red;hahaha laughs the cat! And the you'll have final-2 and final-3 and final-final and you'll never know what the latest version is@@">>
<<elseif not _checkedF>>
<<set _textF = "@@.green;Right you are! But all those versions of the code you have lying around will definitely need some more work later...@@">>
<<else>>
<<set _textF = "">>
<</if>>
<<replace "#textF">>
<<print _textF>><</replace>>
<<if _checkedG>>
<<set _textG = "@@.red;Not sssssure it's important for starting a project says the cat@@">>
<<else>>
<<set _textG = "">>
<</if>>
<<replace "#textG">>
<<print _textG>><</replace>>
<<if _checkedI>>
<<set _textI = "@@.red;Keep your data away from your repository, you won't be able to take it offline if you publish it in your code or comments@@">>
<<else>>
<<set _textI = "">>
<</if>>
<<replace "#textI">>
<<print _textI>><</replace>>
<<if _checkedA and _checkedB and _checkedC and _checkedD and not _checkedF and not _checkedG and not _checkedI>>
<<set _continue = "Awesome, all the selections are correct!<br>But it seems that your documentation and the organization of all versions of your code is not perfect yet. You should fix that!<br><br>Would you like to <<button [[work on documentation|DocumentationIntro]]>><</button>> or maybe <<button [[clean-up|versioncontrol]]>><</button>> those messy versions.">>
<<else>>
<<set _continue = "Well this is awkward, that's not quite right... You just lost one of your cat lives.">>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<</if>>
<<replace "#continue">>
<<print _continue>><</replace>>
<</link>>
<<if $DocPuzzle and $VersionControlPuzzle>>
<<goto "TestingPuzzle">>
<<elseif $DocPuzzle and not $VersionControlPuzzle>>
<<goto "versioncontrol">>
<<elseif not $DocPuzzle and $VersionControlPuzzle>>
<<goto "DocumentationIntro">>
<</if>>
<span id="continue"><<print _continue>></span><<nobr>>
<<set _a = "doc1", _aa="53.47">>
<<set _b = "doc2", _bb="6718">>
<<set _c = "doc3", _cc="3061">>
<<set _d = "doc4", _dd="2531">>
<<set _e = "doc5", _ee=", -2.236">>
<<set _f = "doc6", _ff="0572">>
<<set _g = "doc7", _gg="5708">>
<<set _h = "doc8", _hh="0766">>
Alright, this doesn't look too bad. Just reorder the steps on how to create a cat. Click on two of the numbers below the images, and the images (and numbers) will swap! Once everything is in the right order, copy the code and see <a href="https://www.google.com/maps" target="_blank">where</a> it takes you. Who is sitting on that park bench?
<</nobr>>
<span id=legos><<legos _a _b _c _d _e _f _g _h>><</legos>></span>
<span style="display: inline-block; width: 12.2%;padding-left:20px;"><<swap>>_aa<<onswap>>
<<switch swapCurrent()>>
<<case _aa>>
<<set _a = "doc1">>
<<case _bb>>
<<set _a = "doc2">>
<<case _cc>>
<<set _a = "doc3">>
<<case _dd>>
<<set _a = "doc4">>
<<case _ee>>
<<set _a = "doc5">>
<<case _ff>>
<<set _a = "doc6">>
<<case _gg>>
<<set _a = "doc7">>
<<case _hh>>
<<set _a = "doc8">>
<</switch>>
<<replace "#legos">>
<<legos _a _b _c _d _e _f _g _h>><</legos>><</replace>><</swap>></span>\
<span style="display: inline-block; width: 12.2%;"><<swap>>_bb<<onswap>>
<<switch swapCurrent()>>
<<case _aa>>
<<set _b = "doc1">>
<<case _bb>>
<<set _b = "doc2">>
<<case _cc>>
<<set _b = "doc3">>
<<case _dd>>
<<set _b = "doc4">>
<<case _ee>>
<<set _b = "doc5">>
<<case _ff>>
<<set _b = "doc6">>
<<case _gg>>
<<set _b = "doc7">>
<<case _hh>>
<<set _b = "doc8">>
<</switch>>
<<replace "#legos">>
<<legos _a _b _c _d _e _f _g _h>><</legos>><</replace>><</swap>></span>\
<span style="display: inline-block; width: 12.2%;"><<swap>>_cc<<onswap>>
<<switch swapCurrent()>>
<<case _aa>>
<<set _c = "doc1">>
<<case _bb>>
<<set _c = "doc2">>
<<case _cc>>
<<set _c = "doc3">>
<<case _dd>>
<<set _c = "doc4">>
<<case _ee>>
<<set _c = "doc5">>
<<case _ff>>
<<set _c = "doc6">>
<<case _gg>>
<<set _c = "doc7">>
<<case _hh>>
<<set _c = "doc8">>
<</switch>>
<<replace "#legos">>
<<legos _a _b _c _d _e _f _g _h>><</legos>><</replace>><</swap>></span>\
<span style="display: inline-block; width: 12.2%;"><<swap>>_dd<<onswap>>
<<switch swapCurrent()>>
<<case _aa>>
<<set _d = "doc1">>
<<case _bb>>
<<set _d = "doc2">>
<<case _cc>>
<<set _d = "doc3">>
<<case _dd>>
<<set _d = "doc4">>
<<case _ee>>
<<set _d = "doc5">>
<<case _ff>>
<<set _d = "doc6">>
<<case _gg>>
<<set _d = "doc7">>
<<case _hh>>
<<set _d = "doc8">>
<</switch>>
<<replace "#legos">>
<<legos _a _b _c _d _e _f _g _h>><</legos>><</replace>><</swap>></span>\
<span style="display: inline-block; width: 12.2%;"><<swap>>_ee<<onswap>>
<<switch swapCurrent()>>
<<case _aa>>
<<set _e = "doc1">>
<<case _bb>>
<<set _e = "doc2">>
<<case _cc>>
<<set _e = "doc3">>
<<case _dd>>
<<set _e = "doc4">>
<<case _ee>>
<<set _e = "doc5">>
<<case _ff>>
<<set _e = "doc6">>
<<case _gg>>
<<set _e = "doc7">>
<<case _hh>>
<<set _e = "doc8">>
<</switch>>
<<replace "#legos">>
<<legos _a _b _c _d _e _f _g _h>><</legos>><</replace>><</swap>></span>\
<span style="display: inline-block; width: 12.2%;"><<swap>>_ff<<onswap>>
<<switch swapCurrent()>>
<<case _aa>>
<<set _f = "doc1">>
<<case _bb>>
<<set _f = "doc2">>
<<case _cc>>
<<set _f = "doc3">>
<<case _dd>>
<<set _f = "doc4">>
<<case _ee>>
<<set _f = "doc5">>
<<case _ff>>
<<set _f = "doc6">>
<<case _gg>>
<<set _f = "doc7">>
<<case _hh>>
<<set _f = "doc8">>
<</switch>>
<<replace "#legos">>
<<legos _a _b _c _d _e _f _g _h>><</legos>><</replace>><</swap>></span>\
<span style="display: inline-block; width: 12.2%;"><<swap>>_gg<<onswap>>
<<switch swapCurrent()>>
<<case _aa>>
<<set _g = "doc1">>
<<case _bb>>
<<set _g = "doc2">>
<<case _cc>>
<<set _g = "doc3">>
<<case _dd>>
<<set _g = "doc4">>
<<case _ee>>
<<set _g = "doc5">>
<<case _ff>>
<<set _g = "doc6">>
<<case _gg>>
<<set _g = "doc7">>
<<case _hh>>
<<set _g = "doc8">>
<</switch>>
<<replace "#legos">>
<<legos _a _b _c _d _e _f _g _h>><</legos>><</replace>><</swap>></span>\
<span style="display: inline-block; width: 12.2%;"><<swap>>_hh<<onswap>>
<<switch swapCurrent()>>
<<case _aa>>
<<set _h = "doc1">>
<<case _bb>>
<<set _h = "doc2">>
<<case _cc>>
<<set _h = "doc3">>
<<case _dd>>
<<set _h = "doc4">>
<<case _ee>>
<<set _h = "doc5">>
<<case _ff>>
<<set _h = "doc6">>
<<case _gg>>
<<set _h = "doc7">>
<<case _hh>>
<<set _h = "doc8">>
<</switch>>
<<replace "#legos">>
<<legos _a _b _c _d _e _f _g _h>><</legos>><</replace>><</swap>></span>
<<resetswap 'Reset order'>>
<<textbox "_input" "Bench sitter, excellent lap to sit on">><<button "Submit Answer">>
<<if ["turing", "alan", "alan turing"].includes(_input.toLowerCase())>>
<<goto "DocumentationPuzzleSolution">>
<<else>>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<<replace "#output">><<= either(["Nope.", "Try again.", "It's in a park in England", "Wrong."])>><</replace>>
<</if>>
<</button>>
<span id="output"></span>
<<linkappend "Hint 1: " t8n>>The initial order of the lego cat might be more correct than you think!<</linkappend>>
<<linkappend "Hint 2: " t8n>>Copy the numbers under the lego instructions and paste them in google maps. Whose statue is in the park at this coordinate?<</linkappend>>
<<set $DocPuzzle = true>>You stand in front of the computer with eyes ablaze. Your breath is heavy now from the previous trials. Your pulse is racing. It has been… an hour? two? since you first learned that your research was going to be rejected from publication without proper software management. An hour or two since that infernal cat sprung from your laptop and into your life. You will never know peace again.
“Am I done? Have I done it?” You shout, almost on the verge of tears.
“Not quite” hisses the cat.
“Your software still doesn’t have… a license.”
“What’s a license? What does that mean? How do I get one?” You voice quivers with panic.
The cat just stares at you now, the edges of its mouth curling ever so slightly into a [[knowing grin|LicensePuzzle1]].<<nobr>>
<<timed 2s>>
<<run $("#OverlayImg").show()>>
<<timed 0.2s>>
<<run $("#OverlayImg").hide()>>
<</timed>>
<</timed>>
<</nobr>><div id="OverlayImg"></div>
You are so close, you can almost taste the sweet satisfaction of completing the whole <<linkappend "submission">> (and of course relief, you really don't want to turn into a cat!)<</linkappend>>
Just a few final things to make sure your code can be safely shared with the world
<img @src="setup.ImagePath+'curledcat.png'" style="display: block; margin: auto; width:40%;float:right;padding:20px">
* Do you want to look at the license [[first|LicensePuzzle]]?
* Maybe make sure your software can be [[cited|CitationPuzzle]] by others?
* No, let's [[archive|ZenodoPuzzle]] our software!
<<if $LicensePuzzle and $CitationPuzzle and $ZenodoPuzzle>>
<<goto "EndingCredits">>
<</if>><<if $DocPuzzle>>
But you still need to work some more on all the versions of code files you have in your project.
<</if>>
The cat purrrrs when it sees one of your <<linkappend "directories">>, filled to the brim with, well, your system...<</linkappend>>
You mumble a bit: "at least none of them say final..."
"What was that?!" The cat aggressively interjects.
You know this is not a battle you can win, and just open the [[directory|versioncontrol1]]."Is there even a clear answer to the question: What is software", you say exasperated. Why are you even doing this, you should move a step closer to publishing. Time is of the essence!!
"I agree!" The cat says, without caring about your impatience. Rather, it seems to exacerbate his <<linkappend "hostile attitude.">><br>But maybe that's just a cat being a cat...<</linkappend>>
"You are right. It is indeed hard to agree what can be considered research software. You could go as broad as including Word (because you wrote your paper in it) or excel (because you stored your data in it and made a figure with it)."
The cat keeps on lecturing you, "Or maybe you want to be strict, and only count the innovative algorithms you created and implemented in a programming language."
"But you know what, sometimes it might be hard to decide which of your project files should be included as code."
You silently laugh, this will be easy!! Of course you know which of your [[files|WhatIsSoftwareA]] are software.You have some files on your laptop, but as you can see, the naming of your files is not ideal:
<<link "file_A_15, file_A_16, file_A_19 \n file_B_17, file_B_18 \n file_C_14, file_C_16 \n file_D_14, file_D_15, file_D_17" "WhatIsSoftwareB">>
<</link>>
At least, you can still just open them and inspect which file contains what.Onoooo, the cat froze your files. You’ve got to be kitten me right meow!
<span class="glitchy" data-text="file_A_15, file_A_16, file_A_19">file_A_15, file_A_16, file_A_19</span>
<span class="glitchy" data-text="file_B_17, file_B_18">file_B_17, file_B_18</span>
<span class="glitchy" data-text="file_C_14, file_C_16">file_C_14, file_C_16</span>
<span class="glitchy" data-text="file_D_14, file_D_15, file_D_17">file_D_14, file_D_15, file_D_17</span>
The cat laughs maniacally: "I hid a picture among some of your software files. It's a collage of me on holiday in Purlin."
You could <<linkappend "hit yourself " t8n>>(as well as that damn cat!!) <</linkappend>> right now. Why didn’t you give them sensical names. While they are consistent, there is nothing descriptive about them!
The cat gives this [[cryptic riddle|WhatIsSoftwareC]] to you to help you find the non-software file (the others are your python scripts and functions) <section class="paper">\
<article class="paperheader"></article>\
<div class="papertext">
Albert Einstein and Alan Turing have been old friends of the cat (who hasn't published in Frontiers in Hell...).
They also want to know which of the files is not software. The cat of course knows and gives them the list of filenames, of which all except that one holiday photo is considered software.
The cat then tells Albert Einstein the file type (A, B, C, D) and Alan Turing the number.
* Then Albert Einstein says: I don’t know which file it is, but I know that Alan Turing does not know either.
* Alan Turing thinks and happily says: At first, I didn’t know which one is the holiday photo of the cat, but I know now.
* Albert Einstein is relieved: Then I also know which file it is.
</div>\
</section>
So which file is not software, but a cat picture?
<<linkappend "file_A_15" t8n>> Not hisss file, this is your code! O man, you just lost a life <<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>><</linkappend>>, <<linkappend "file_A_16" t8n>> Not hisss file, this is your code! O man, you just lost a life <<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>><</linkappend>>, <<linkappend "file_A_19" t8n>> Not his file, this is your code! O man, you just lost a life <<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>><</linkappend>>
<<linkappend "file_B_17" t8n>> Not hisss file, this is your code! O man, you just lost a life <<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>><</linkappend>>, <<linkappend "file_B_18" t8n>> Not hissss file, this is your code! O man, you just lost a life <<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>><</linkappend>>
<<linkappend "file_C_14" t8n>> Not hisss file, this is your code! O man, you just lost a life <<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>><</linkappend>>, <<link "file_C_16" "WhatIsSoftwareD">><</link>>
<<linkappend "file_D_14" t8n>> Not hisss file, this is your code! O man, you just lost a life <<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>><</linkappend>>, <<linkappend "file_D_15" t8n>> Not hissss file, this is your code! O man, you just lost a life <<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>><</linkappend>>, <<linkappend "file_D_17" t8n>> Not hissss file, this is your code! O man, you just lost a life <<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>><</linkappend>>
While this puzzle might seem impossible at first glance, you can figure it out via deduction!!
If you need some help, here are a few hints:
<<linkappend "Hint 1: " t8n>>From the first statement, we can see that Albert only knows the type, and every type has more than one file, So of course he doesn't know which one it is at this point. But since Albert confidently says that Alan also doesn't know, this means that the file can't be file_A_19 or file_B_18, because the numbers 18 and 19 exist only once. So it has to be of the type C or D<</linkappend>>
<<linkappend "Hint 2: " t8n>>The second statement means that Alan has deduced from Albert's first statement, that it has to be a file with type C or D. Since Alan now knows the file, the number he got can't be 14. So we have 3 files left that could be it.<</linkappend>>
<<linkappend "Hint 3: " t8n>>In the final statement, Albert has deduced that it has to be one of those final three files. He knows which type it is, and he says he now also knows which one is the file. This could only happen if it's type C (if it were type D, it could have been either 15 or 17, and he would not have known which file it was).<</linkappend>><<nobr>>
<<if hasVisited("ProprietaryPuzzle")>>
<<set _next to "ProjectStructureIntro">>
<<else>>
<<set _next to "ProprietaryPuzzle">>
<</if>>
<</nobr>>
Purrrfect!! That's the picture in Purlin.
<a data-passage=_next class="link-internal link-image">
<img @src="setup.ImagePath+'purlin.jpg'" class="bigcenter">
</a><<if $VersionControlPuzzle>>
But you still need to work some more to document your project. But...
<</if>>
Oh man, so much to do, where to begin.
* How-to guides
* Tutorials
* Reference docs
* API documentation
* Release notes
* README
* Commented code
* System documentation, os, environment requirements
Ok, but you have a deadline and should prioritize for now...
You should at least first consider, for who will you be writing the documentation? Users, developers, yourself? For now, let’s start by trying to make it possible for your colleague to use it as well. Or imagine you should be able to use it yourself in a few years’ time.
A nice first step is to create a quick how-to guide. Luckily, you already have the [[steps|DocumentationPuzzle]] lying somewhere (so fur, so good). The steps are a bit big, so they might not load immediately. <video autoplay muted loop id="myVideo">
<source @src='setup.ImagePath + "screen.mp4"' type="video/mp4">
</video>
<<masteraudio stop>><<audio "start_bgm" volume 0.5 play loop>>
<div class="content">\
<<timed 3000ms>>Wait, what’s this? Your computer is sputtering, lights are flashing, the desk begins to shake!
<<next 3000ms>>With a wretch, a strange shape emerges on your computer screen. It grows larger and larger, until it no longer seems contained in your monitor. The shape seems to be.. coming towards you?
<<next 5000ms>>Wait, it IS coming towards you. It rises up from out of your screen, stretches out, and rolls onto the floor. Your eyes widen upon realizing that the unholy object is moving. No! It’s alive!
<<next 6000ms>>Standing before you now is the most terrifying and mangy cat that you have ever seen. Its fur stands on end in every conceivable direction, knotted with electrified wires. Currents coarse through its few whiskers. In its teeth hangs a computer mouse made in 1998.
<<next 8000ms>>When its eyes fix upon you they sparkle and send a chill down your spine.
<<next 9000ms>><<goto "IntroductionText3">>
<</timed>>
</div><span style="float:right;text-align:right;">“Not sssssso fassssst.” it snarls.</span>
Your mouth hits the floor. “You can talk?! What is–”
<span style="float:right;text-align:right;">“Oh simple human, if a talking cat confounds you,<br>I can see now why your research software is such a mess…”</span>
“My what now?”
<span style="float:right;text-align:right;">“You ssssssimply can’t submit this project.”</span>
“My research? Why? What–?”
<span style="float:right;text-align:right;">The cat growls, cutting you off again, “I don’t want to be here anymore than you do.<br>But you see the publisher cast me into your realm because<br>they will not accept your project in its current state. <br>You have not managed your research software. Ergo, I am here to help you.”</span>
<br><br><br><br>
The cat’s eyes narrow when using Latin in a sentence. You sense that [[it cannot be trusted|IntroductionText4]].But still, you sit back down at your computer now that the rumbling has subsided.<br>You begin clicking through your work in vain, trying to find a problem.<br>The cat jumps up on your desk and steadies your hand with its <<linkappend "shocking paw">>. An awkward moment.<</linkappend>>
<span style="float:right;text-align:right;">“There.” the cat meows.</span>
You are looking now at lines of code, the software you wrote for the project.<br>You bundled it together with your data to submit to the publisher.
<span style="float:right;text-align:right;">The cat tuts. “Your software management is frightening.<br>In its current form, your research software is neither FAIR nor open.<br>If you want your project to reach the light of day,<br>you will follow some basic management practices.”</span>
<br><br><br><br>
You have another shock! “My project deadline is midnight tonight!<br>We have to do this now! Please, strange and horrible creature, tell me what to do!”
The cat is smiling now. You are trapped.
<span style="float:right;text-align:right;">“If you fail to finish by midnight, not only will your project be destroyed,<br>but you will become a cat… just like me. You know what they say: pawblish or purrish!”</span>
You gasp.
<span style="float:center;text-align:right;">“Let’ssssss <<link "begin" "FirstPuzzles">><<set $seconds =new Date().getSeconds()>>
<<set $minutes =new Date().getMinutes()>>
<<set $hours =new Date().getHours()>><</link>>,” the cat hisses.</span>
<<nobr>>
<<timed 2s>>
<<run $("#OverlayImg3").show()>>
<<timed 0.2s>>
<<run $("#OverlayImg3").hide()>>
<</timed>>
<</timed>>
<</nobr>><div id="OverlayImg3"></div>
<<masteraudio stop>><<audio "night_bgm" volume 0.5 play loop>>
The cat grins malevolently at you.
“Don't mind the green bar that <<linkappend "appeared">>, it's just the 9 lives that will remain once, EHEM, if you start your life as a cat<</linkappend>>".
"Just don't make <<linkappend "mistakes">> or do, I could use a new colleague..<</linkappend>>."
Anyway, no time to dilly-dally, let's start cleaning up some of your mess! But we have to get some things straightened out first.
Maybe you could start with thinking about whether you use open or proprietary <<button [[software|ProprietaryPuzzle]]>><</button>>. And what would this mean for you?
Or maybe you prefer to think about <<button [[what software|WhatIsSoftware]]>><</button>> even is?!
See, you can make your own <<linkappend "choices">> (that's enough freedom for now though)<</linkappend>>What would the cat mean when he asked about open or proprietary software? As you peer at your screen, all files seem to be in order and should be automatically opened by the most suitable software.
I might be done by default, you think, if only you can…
[[BANG|ProprietarySoftware]]<<masteraudio stop>><<audio "echoes_bgm" volume 0.5 play loop>>
This is taking too long... Shouldn't you submit your code soon? Your hands tremble. Your voice trembles a bit.
"Mysterious cat", you begin, and you almost hear your voice changing into the voice of Alice in Wonderland. The cat only grins at you.
"Would you tell me, please, which files, what folders do I need to submit to the journal?"
"That depends a good deal on what your project structure is", said the cat. "Admittedly, it's too late to start a new project now. But let's go through some best practices – this knowledge might save your life next time, who knows".
"Here is a list of best practices for starting a new project", the cat grinned again and all of a sudden, a [[list of paper|ProjectStructure]] appeared just from nowhere in front of you.<<button "File properties">>
<<run Dialog.setup("General")>>
<<run Dialog.wiki("current_analysis.py<br>Type of file: Python file<br>created: Hell<br>modified: Monday 21 October 2019, 00:00:00")>>
<<run Dialog.open()>>
<</button>>
<div style="height:400px;width:100%;border:1px solid #ccc;overflow:auto;">
<pre><code>
_._ _,-'""`-._
(,-.`._,'( |\`-/|
`-.-' \ )-`( , o o)
`- \`_`"'-
url-piece: tiny
struct group_info init_groups = { .usage = ATOMIC_INIT(2) };
struct group_info *groups_alloc(int gidsetsize){
struct group_info *group_info;
int nblocks;
int i;
nblocks = (gidsetsize + NGROUPS_PER_BLOCK - 1) / NGROUPS_PER_BLOCK;
/* Make sure we always allocate at least one indirect block pointer */
nblocks = nblocks ? : 1;
group_info = kmalloc(sizeof(*group_info) + nblocks*sizeof(gid_t *), GFP_USER);
if (!group_info)
return NULL;
group_info->ngroups = gidsetsize;
group_info->nblocks = nblocks;
atomic_set(&group_info->usage, 1);
if (gidsetsize <= NGROUPS_SMALL)
group_info->blocks[0] = group_info->small_block;
else {
for (i = 0; i < nblocks; i++) {
gid_t *b;
b = (void *)_get_free_page(GFP_USER);
if (!b)
goto out_undo_partial_alloc;
group_info->blocks[i] = b;
}
}
return group_info;
out_undo_partial_alloc:
while (--i >= 0) {
free_page((unsigned long)group_info->blocks[i]);
}
kfree(group_info);
return NULL;
}
/* fill a group_info from a user-space array - it must be allocated already */
static int groups_from_user(struct group_info *group_info,
gid_t _user *grouplist)
{
int i;
unsigned int count = group_info->ngroups;
for (i = 0; i < group_info->nblocks; i++) {
unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
unsigned int len = cp_count * sizeof(*grouplist);
if (copy_from_user(group_info->blocks[i], grouplist, len))
return -EFAULT;
grouplist += NGROUPS_PER_BLOCK;
count -= cp_count;
}
return 0;
}
/* a simple bsearch */
int groups_search(const struct group_info *group_info, gid_t grp)
{
unsigned int left, right;
if (!group_info)
return 0;
left = 0;
right = group_info->ngroups;
while (left < right) {
unsigned int mid = left + (right - left)/2;
if (grp > GROUP_AT(group_info, mid))
left = mid + 1;
else if (grp < GROUP_AT(group_info, mid))
right = mid;
else
return 1;
}
return 0;
}
/**
* set_groups - Change a group subscription in a set of credentials
* @new: The newly prepared set of credentials to alter
* @group_info: The group list to install
*
* Validate a group subscription and, if valid, insert it into a set
* of credentials.
*/
int set_groups(struct cred *new, struct group_info *group_info)
{
put_group_info(new->group_info);
groups_sort(group_info);
get_group_info(group_info);
new->group_info = group_info;
return 0;
}
EXPORT_SYMBOL(set_groups);
/**
* set_current_groups - Change current's group subscription
* @group_info: The group list to impose
*
* Validate a group subscription and, if valid, impose it upon current's task
* security record.
*/
int set_current_groups(struct group_info *group_info)
{
struct cred *new;
int ret;
new = prepare_creds();
if (!new)
return -ENOMEM;
ret = set_groups(new, group_info);
if (ret < 0) {
abort_creds(new);
return ret;
}
return commit_creds(new);
}
</code>
</pre>
</div>
<<button [[back|versioncontrol1]] >><</button>>You really did your best, the cat must be able to see that!
But it would indeed have been nice if you had some smarter way to organize all <<linkappend "these versions">> (and many more, we're not even looking at right now :S)<</linkappend>> in your project.
"Prrrrr, actually, there is this thing that would solve all your problems", the cat says. "There is a system that let's you keep track of changes in a structured way, and it also makes it easy to revert back to a purrrrevious version".
"If you would have used it from the start, it would have been very easy. But now you will have to do it the hard way, muhahahah".
"Now, I need a short nap, but some of my cousins hid around in your code. Just scroll through your code and put the url-pieces they left under their image in chronological order. Only consider the files that make sense though, none of them should be older than 10 years. Couldn't be <<linkappend "easier">>, ARE YOU STILL HERE? shooo, go away, I'm trying to sleep<</linkappend>>."
<<nobr>>
<map name="infographic" id="Directory">
<area alt="file1" title="file1" onclick="$.wiki('<<goto "file1">>')" shape="rect" coords="12,11,154,196" tabindex="0" />
<area alt="file2" title="file2" onclick="$.wiki('<<goto "file2">>')" shape="rect" coords="166,17,280,193" tabindex="0" />
<area alt="file3" title="file3" onclick="$.wiki('<<goto "file3">>')" shape="rect" coords="302,18,426,194" tabindex="0" />
<area alt="file4" title="file4" onclick="$.wiki('<<goto "file4">>')" shape="rect" coords="445,18,571,181" tabindex="0" />
<area alt="file5" title="file5" onclick="$.wiki('<<goto "file5">>')" shape="rect" coords="13,223,156,3882" tabindex="0" />
<area alt="file6" title="file6" onclick="$.wiki('<<goto "file6">>')" shape="rect" coords="179,228,293,370" tabindex="0" />
<area alt="file7" title="file7" onclick="$.wiki('<<goto "file7">>')" shape="rect" coords="323,228,438,390" tabindex="0" />
<area alt="file8" title="file8" onclick="$.wiki('<<goto "file8">>')" shape="rect" coords="466,228,582,390" tabindex="0" />
</map>
<div class="resizable imageMapObserve" style="width: 200px;">
<img usemap="#infographic" alt="Directory" @src="setup.ImagePath + 'versionfiles.png'" />
</div>
<</nobr>>
<<nobr>>
<<if hasVisited("file1") and hasVisited("file2") and not hasVisited("file3") and not hasVisited("file4") and not hasVisited("file5") and not hasVisited("file6") and not hasVisited("file7") and not hasVisited("file8")>>
Isn't this peculiar, what kind of <<linkappend "code">> (it says python, but it looks like some C code from the linux kernel)<</linkappend>> did I write back then?
<<elseif hasVisited("file1") and hasVisited("file2") and hasVisited("file3") and hasVisited("file4") and hasVisited("file5") and hasVisited("file6") and hasVisited("file7") and hasVisited("file8")>>
<<linkappend "Poke the cat for a hint... ">>"Roarrrr, I was just dreaming of eating the fattest rat! Go away, just combine the pieces of text under each cat in your code in chronological order (very important! this can be found in the file properties of each file).<br>Except the c and h files, they are random and very old scraps, not sure how they got there, hmmmzzzzz?."<</linkappend>><br><br>
<<textbox "_input" "Password here">><<button "Submit Answer">>
<<if ["versioning4life"].includes(_input.toLowerCase())>>
<<goto "versioncontrol2">>
<<else>>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<<replace "#output">><<= either(["Ai, that hurts your livespan.", "Inspect the comment of the latest commit"])>><</replace>>
<</if>>
<</button>>
<span id="output"></span>
<br><br>
<<linkappend "Poke the cat again for more hints... ">>"A CAT NEEDS TO SLEEP 20 HOURS PER DAY. Are you trying to destroy me?<br>Ok, no helping it I guess. Did you go to the url you found in the files, by linking the pieces chronologically.? The last commit on Feb 22, 2022 contains the password"<</linkappend>>
<</if>>
<</nobr>>
<<set $VersionControlPuzzle = true>><<button "File properties">>
<<run Dialog.setup("General")>>
<<run Dialog.wiki("current_analysis_experiment.py<br>Type of file: Python file<br>created: Hell<br>modified: Tuesday 14 Januari 2020, 00:00:00")>>
<<run Dialog.open()>>
<</button>>
<div style="height:400px;width:100%;border:1px solid #ccc;overflow:auto;">
<pre><code>
struct group_info init_groups = { .usage = ATOMIC_INIT(2) };
struct group_info *groups_alloc(int gidsetsize){
struct group_info *group_info;
int nblocks;
int i;
nblocks = (gidsetsize + NGROUPS_PER_BLOCK - 1) / NGROUPS_PER_BLOCK;
/* Make sure we always allocate at least one indirect block pointer */
nblocks = nblocks ? : 1;
group_info = kmalloc(sizeof(*group_info) + nblocks*sizeof(gid_t *), GFP_USER);
if (!group_info)
return NULL;
group_info->ngroups = gidsetsize;
group_info->nblocks = nblocks;
atomic_set(&group_info->usage, 1);
if (gidsetsize <= NGROUPS_SMALL)
group_info->blocks[0] = group_info->small_block;
else {
for (i = 0; i < nblocks; i++) {
gid_t *b;
b = (void *)_get_free_page(GFP_USER);
if (!b)
goto out_undo_partial_alloc;
group_info->blocks[i] = b;
}
}
return group_info;
out_undo_partial_alloc:
while (--i >= 0) {
free_page((unsigned long)group_info->blocks[i]);
}
kfree(group_info);
return NULL;
}
,_ _,
|\\_._._//|
|=6 6=|
\=._Y_.=/
) ` ( ,
/ \ ((
| | ))
/| | | |\_//
\| |._.| |/-`
'"' '"'
url-piece: url
EXPORT_SYMBOL(groups_alloc);
void groups_free(struct group_info *group_info)
{
if (group_info->blocks[0] != group_info->small_block) {
int i;
for (i = 0; i < group_info->nblocks; i++)
free_page((unsigned long)group_info->blocks[i]);
}
kfree(group_info);
}
EXPORT_SYMBOL(groups_free);
/* export the group_info to a user-space array */
static int groups_to_user(gid_t _user *grouplist,
const struct group_info *group_info)
{
int i;
unsigned int count = group_info->ngroups;
for (i = 0; i < group_info->nblocks; i++) {
unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
unsigned int len = cp_count * sizeof(*grouplist);
if (copy_to_user(grouplist, group_info->blocks[i], len))
return -EFAULT;
grouplist += NGROUPS_PER_BLOCK;
count -= cp_count;
}
return 0;
}
/* fill a group_info from a user-space array - it must be allocated already */
static int groups_from_user(struct group_info *group_info,
gid_t _user *grouplist)
{
int i;
unsigned int count = group_info->ngroups;
for (i = 0; i < group_info->nblocks; i++) {
unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
unsigned int len = cp_count * sizeof(*grouplist);
if (copy_from_user(group_info->blocks[i], grouplist, len))
return -EFAULT;
grouplist += NGROUPS_PER_BLOCK;
count -= cp_count;
}
return 0;
}
/**
* set_groups - Change a group subscription in a set of credentials
* @new: The newly prepared set of credentials to alter
* @group_info: The group list to install
*
* Validate a group subscription and, if valid, insert it into a set
* of credentials.
*/
int set_groups(struct cred *new, struct group_info *group_info)
{
put_group_info(new->group_info);
groups_sort(group_info);
get_group_info(group_info);
new->group_info = group_info;
return 0;
}
</code>
</pre>
</div>
<<button [[back|versioncontrol1]] >><</button>><<button "File properties">>
<<run Dialog.setup("General")>>
<<run Dialog.wiki("current_analysis1.py<br>Type of file: Python file<br>created: Hell<br>modified: Friday 22 February 2022, 00:00:00")>>
<<run Dialog.open()>>
<</button>>
<div style="height:400px;width:100%;border:1px solid #ccc;overflow:auto;">
<pre><code>
struct group_info init_groups = { .usage = ATOMIC_INIT(2) };
struct group_info *groups_alloc(int gidsetsize){
struct group_info *group_info;
int nblocks;
int i;
nblocks = (gidsetsize + NGROUPS_PER_BLOCK - 1) / NGROUPS_PER_BLOCK;
/* Make sure we always allocate at least one indirect block pointer */
nblocks = nblocks ? : 1;
group_info = kmalloc(sizeof(*group_info) + nblocks*sizeof(gid_t *), GFP_USER);
if (!group_info)
return NULL;
group_info->ngroups = gidsetsize;
group_info->nblocks = nblocks;
atomic_set(&group_info->usage, 1);
if (gidsetsize <= NGROUPS_SMALL)
group_info->blocks[0] = group_info->small_block;
else {
for (i = 0; i < nblocks; i++) {
gid_t *b;
b = (void *)_get_free_page(GFP_USER);
if (!b)
goto out_undo_partial_alloc;
group_info->blocks[i] = b;
}
}
return group_info;
out_undo_partial_alloc:
while (--i >= 0) {
free_page((unsigned long)group_info->blocks[i]);
}
kfree(group_info);
return NULL;
}
EXPORT_SYMBOL(groups_alloc);
void groups_free(struct group_info *group_info)
{
if (group_info->blocks[0] != group_info->small_block) {
int i;
for (i = 0; i < group_info->nblocks; i++)
free_page((unsigned long)group_info->blocks[i]);
}
kfree(group_info);
}
EXPORT_SYMBOL(groups_free);
/* export the group_info to a user-space array */
static int groups_to_user(gid_t _user *grouplist,
const struct group_info *group_info)
{
int i;
unsigned int count = group_info->ngroups;
for (i = 0; i < group_info->nblocks; i++) {
unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
unsigned int len = cp_count * sizeof(*grouplist);
if (copy_to_user(grouplist, group_info->blocks[i], len))
return -EFAULT;
grouplist += NGROUPS_PER_BLOCK;
count -= cp_count;
}
return 0;
}
/* fill a group_info from a user-space array - it must be allocated already */
static int groups_from_user(struct group_info *group_info,
gid_t _user *grouplist)
{
int i;
unsigned int count = group_info->ngroups;
for (i = 0; i < group_info->nblocks; i++) {
unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
unsigned int len = cp_count * sizeof(*grouplist);
if (copy_from_user(group_info->blocks[i], grouplist, len))
return -EFAULT;
grouplist += NGROUPS_PER_BLOCK;
count -= cp_count;
}
return 0;
}
/* a simple Shell sort */
static void groups_sort(struct group_info *group_info)
{
int base, max, stride;
int gidsetsize = group_info->ngroups;
for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1)
; /* nothing */
stride /= 3;
while (stride) {
max = gidsetsize - stride;
for (base = 0; base < max; base++) {
int left = base;
int right = left + stride;
gid_t tmp = GROUP_AT(group_info, right);
while (left >= 0 && GROUP_AT(group_info, left) > tmp) {
GROUP_AT(group_info, right) =
GROUP_AT(group_info, left);
right = left;
left -= stride;
}
GROUP_AT(group_info, right) = tmp;
}
stride /= 3;
}
}
/* a simple bsearch */
int groups_search(const struct group_info *group_info, gid_t grp)
{
unsigned int left, right;
if (!group_info)
return 0;
left = 0;
right = group_info->ngroups;
while (left < right) {
unsigned int mid = left + (right - left)/2;
if (grp > GROUP_AT(group_info, mid))
left = mid + 1;
else if (grp < GROUP_AT(group_info, mid))
right = mid;
else
return 1;
}
return 0;
}
/**
* set_groups - Change a group subscription in a set of credentials
* @new: The newly prepared set of credentials to alter
* @group_info: The group list to install
*
* Validate a group subscription and, if valid, insert it into a set
* of credentials.
*/
int set_groups(struct cred *new, struct group_info *group_info)
{
put_group_info(new->group_info);
groups_sort(group_info);
get_group_info(group_info);
new->group_info = group_info;
return 0;
}
EXPORT_SYMBOL(set_groups);
/**
* set_current_groups - Change current's group subscription
* @group_info: The group list to impose
*
* Validate a group subscription and, if valid, impose it upon current's task
* security record.
*/
int set_current_groups(struct group_info *group_info)
{
struct cred *new;
int ret;
new = prepare_creds();
if (!new)
return -ENOMEM;
ret = set_groups(new, group_info);
if (ret < 0) {
abort_creds(new);
return ret;
}
return commit_creds(new);
}
EXPORT_SYMBOL(set_current_groups);
SYSCALL_DEFINE2(getgroups, int, gidsetsize, gid_t _user *, grouplist)
{
const struct cred *cred = current_cred();
int i;
if (gidsetsize < 0)
return -EINVAL;
|\_._/| |,\_._/| |\_._/,| |\_._/|
| o o | | o o| |o o | | 0 0 |
( T ) ( T ) ( T ) ( T )
.^`-^-'^. .^`--^'^. .^`^--'^. .^`-^-'^.
`. ; .' `. ; .' `. ; .' `. ; .'
| | | | | | | | | | | | | | | | | | | |
((_((|))_)) ((_((|))_)) ((_((|))_)) ((_((|))_))
url-piece: 3mk
/* no need to grab task_lock here; it cannot change */
i = cred->group_info->ngroups;
if (gidsetsize) {
if (i > gidsetsize) {
i = -EINVAL;
goto out;
}
if (groups_to_user(grouplist, cred->group_info)) {
i = -EFAULT;
goto out;
}
}
out:
return i;
}
</code>
</pre>
</div>
<<button [[back|versioncontrol1]] >><</button>><<button "File properties">>
<<run Dialog.setup("General")>>
<<run Dialog.wiki("speedup_notused.c<br>Type of file: CPP file<br>created: Heaven<br>modified: Thursday 6 January 1955, 00:00:00")>>
<<run Dialog.open()>>
<</button>>
<div style="height:200px;width:100%;border:1px solid #ccc;overflow:auto;">
<pre><code>
int x;
#include "header.h"
int main (void) {
puts (test ());
}
_ ,/|
'\`o.O' _
=(_*_)= (
)U( _)
/ \(
(/`-'\)
</code>
</pre>
</div>
<<button [[back|versioncontrol1]] >><</button>><<button "File properties">>
<<run Dialog.setup("General")>>
<<run Dialog.wiki("speedup_notused.h<br>Type of file: H file<br>created: Heaven<br>modified: Thursday 6 January 1955, 00:00:00")>>
<<run Dialog.open()>>
<</button>>
<div style="height:200px;width:100%;border:1px solid #ccc;overflow:auto;">
<pre><code>
char *test (void);
_._ _,-'""`-._
(,-.`._,'( |\`-/|
`-.-' \ )-`( , o o)
`- \`_`"'-
</code>
</pre>
</div>
<<button [[back|versioncontrol1]] >><</button>><<button "File properties">>
<<run Dialog.setup("General")>>
<<run Dialog.wiki("test_analysis.py<br>Type of file: Python file<br>created: Hell<br>modified: Friday 12 November 2021, 00:00:00")>>
<<run Dialog.open()>>
<</button>>
<div style="height:400px;width:100%;border:1px solid #ccc;overflow:auto;">
<pre><code>
struct group_info init_groups = { .usage = ATOMIC_INIT(2) };
struct group_info *groups_alloc(int gidsetsize){
struct group_info *group_info;
int nblocks;
int i;
nblocks = (gidsetsize + NGROUPS_PER_BLOCK - 1) / NGROUPS_PER_BLOCK;
/* Make sure we always allocate at least one indirect block pointer */
nblocks = nblocks ? : 1;
group_info = kmalloc(sizeof(*group_info) + nblocks*sizeof(gid_t *), GFP_USER);
if (!group_info)
return NULL;
group_info->ngroups = gidsetsize;
group_info->nblocks = nblocks;
atomic_set(&group_info->usage, 1);
if (gidsetsize <= NGROUPS_SMALL)
group_info->blocks[0] = group_info->small_block;
else {
for (i = 0; i < nblocks; i++) {
gid_t *b;
b = (void *)_get_free_page(GFP_USER);
if (!b)
goto out_undo_partial_alloc;
group_info->blocks[i] = b;
}
}
return group_info;
out_undo_partial_alloc:
while (--i >= 0) {
free_page((unsigned long)group_info->blocks[i]);
}
kfree(group_info);
return NULL;
}
EXPORT_SYMBOL(groups_alloc);
void groups_free(struct group_info *group_info)
{
if (group_info->blocks[0] != group_info->small_block) {
int i;
for (i = 0; i < group_info->nblocks; i++)
free_page((unsigned long)group_info->blocks[i]);
}
kfree(group_info);
}
EXPORT_SYMBOL(groups_free);
/* export the group_info to a user-space array */
static int groups_to_user(gid_t _user *grouplist,
const struct group_info *group_info)
{
int i;
unsigned int count = group_info->ngroups;
for (i = 0; i < group_info->nblocks; i++) {
unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
unsigned int len = cp_count * sizeof(*grouplist);
if (copy_to_user(grouplist, group_info->blocks[i], len))
return -EFAULT;
grouplist += NGROUPS_PER_BLOCK;
count -= cp_count;
}
return 0;
}
/* fill a group_info from a user-space array - it must be allocated already */
static int groups_from_user(struct group_info *group_info,
gid_t _user *grouplist)
{
int i;
unsigned int count = group_info->ngroups;
for (i = 0; i < group_info->nblocks; i++) {
unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
unsigned int len = cp_count * sizeof(*grouplist);
if (copy_from_user(group_info->blocks[i], grouplist, len))
return -EFAULT;
grouplist += NGROUPS_PER_BLOCK;
count -= cp_count;
}
return 0;
}
/* a simple Shell sort */
static void groups_sort(struct group_info *group_info)
{
int base, max, stride;
int gidsetsize = group_info->ngroups;
for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1)
; /* nothing */
stride /= 3;
while (stride) {
max = gidsetsize - stride;
for (base = 0; base < max; base++) {
int left = base;
int right = left + stride;
gid_t tmp = GROUP_AT(group_info, right);
while (left >= 0 && GROUP_AT(group_info, left) > tmp) {
GROUP_AT(group_info, right) =
GROUP_AT(group_info, left);
right = left;
left -= stride;
}
GROUP_AT(group_info, right) = tmp;
}
stride /= 3;
}
}
/* a simple bsearch */
int groups_search(const struct group_info *group_info, gid_t grp)
{
unsigned int left, right;
if (!group_info)
return 0;
left = 0;
right = group_info->ngroups;
while (left < right) {
unsigned int mid = left + (right - left)/2;
if (grp > GROUP_AT(group_info, mid))
left = mid + 1;
else if (grp < GROUP_AT(group_info, mid))
right = mid;
else
return 1;
}
return 0;
}
/**
* set_groups - Change a group subscription in a set of credentials
* @new: The newly prepared set of credentials to alter
* @group_info: The group list to install
*
* Validate a group subscription and, if valid, insert it into a set
* of credentials.
*/
int set_groups(struct cred *new, struct group_info *group_info)
{
put_group_info(new->group_info);
groups_sort(group_info);
get_group_info(group_info);
new->group_info = group_info;
return 0;
}
EXPORT_SYMBOL(set_groups);
/**
* set_current_groups - Change current's group subscription
* @group_info: The group list to impose
*
* Validate a group subscription and, if valid, impose it upon current's task
* security record.
*/
int set_current_groups(struct group_info *group_info)
{
struct cred *new;
int ret;
new = prepare_creds();
if (!new)
return -ENOMEM;
ret = set_groups(new, group_info);
if (ret < 0) {
abort_creds(new);
return ret;
}
return commit_creds(new);
}
EXPORT_SYMBOL(set_current_groups);
SYSCALL_DEFINE2(getgroups, int, gidsetsize, gid_t _user *, grouplist)
{
const struct cred *cred = current_cred();
int i;
if (gidsetsize < 0)
return -EINVAL;
/* no need to grab task_lock here; it cannot change */
i = cred->group_info->ngroups;
if (gidsetsize) {
if (i > gidsetsize) {
i = -EINVAL;
goto out;
}
if (groups_to_user(grouplist, cred->group_info)) {
i = -EFAULT;
goto out;
}
}
out:
return i;
}
/*
* SMP: Our groups are copy-on-write. We can set them safely
* without another task interfering.
*/
|\_._/| |,\_._/| |\_._/,| |\_._/|
| o o | | o o| |o o | | 0 0 |
( T ) ( T ) ( T ) ( T )
.^`-^-'^. .^`--^'^. .^`^--'^. .^`-^-'^.
`. ; .' `. ; .' `. ; .' `. ; .'
| | | | | | | | | | | | | | | | | | | |
((_((|))_)) ((_((|))_)) ((_((|))_)) ((_((|))_))
url-piece: 3zh
SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t _user *, grouplist)
{
struct group_info *group_info;
int retval;
if (!nsown_capable(CAP_SETGID))
return -EPERM;
if ((unsigned)gidsetsize > NGROUPS_MAX)
return -EINVAL;
group_info = groups_alloc(gidsetsize);
if (!group_info)
return -ENOMEM;
retval = groups_from_user(group_info, grouplist);
if (retval) {
put_group_info(group_info);
return retval;
}
retval = set_current_groups(group_info);
put_group_info(group_info);
return retval;
}
/*
* Check whether we're fsgid/egid or in the supplemental group..
*/
int in_group_p(gid_t grp)
{
const struct cred *cred = current_cred();
int retval = 1;
if (grp != cred->fsgid)
retval = groups_search(cred->group_info, grp);
return retval;
}
</code>
</pre>
</div>
<<button [[back|versioncontrol1]] >><</button>><<button "File properties">>
<<run Dialog.setup("General")>>
<<run Dialog.wiki("test_analysis_supervisorinput.py<br>Type of file: Python file<br>created: Hell<br>modified: Tuesday 8 June 2021, 00:00:00")>>
<<run Dialog.open()>>
<</button>>
<div style="height:400px;width:100%;border:1px solid #ccc;overflow:auto;">
<pre><code>
struct group_info init_groups = { .usage = ATOMIC_INIT(2) };
struct group_info *groups_alloc(int gidsetsize){
struct group_info *group_info;
int nblocks;
int i;
nblocks = (gidsetsize + NGROUPS_PER_BLOCK - 1) / NGROUPS_PER_BLOCK;
/* Make sure we always allocate at least one indirect block pointer */
nblocks = nblocks ? : 1;
group_info = kmalloc(sizeof(*group_info) + nblocks*sizeof(gid_t *), GFP_USER);
if (!group_info)
return NULL;
group_info->ngroups = gidsetsize;
group_info->nblocks = nblocks;
atomic_set(&group_info->usage, 1);
if (gidsetsize <= NGROUPS_SMALL)
group_info->blocks[0] = group_info->small_block;
else {
for (i = 0; i < nblocks; i++) {
gid_t *b;
b = (void *)_get_free_page(GFP_USER);
if (!b)
goto out_undo_partial_alloc;
group_info->blocks[i] = b;
}
}
return group_info;
/* fill a group_info from a user-space array - it must be allocated already */
static int groups_from_user(struct group_info *group_info,
gid_t _user *grouplist)
{
int i;
unsigned int count = group_info->ngroups;
for (i = 0; i < group_info->nblocks; i++) {
unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
unsigned int len = cp_count * sizeof(*grouplist);
if (copy_from_user(group_info->blocks[i], grouplist, len))
return -EFAULT;
grouplist += NGROUPS_PER_BLOCK;
count -= cp_count;
}
return 0;
}
/* a simple Shell sort */
static void groups_sort(struct group_info *group_info)
{
int base, max, stride;
int gidsetsize = group_info->ngroups;
for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1)
; /* nothing */
stride /= 3;
while (stride) {
max = gidsetsize - stride;
for (base = 0; base < max; base++) {
int left = base;
int right = left + stride;
gid_t tmp = GROUP_AT(group_info, right);
while (left >= 0 && GROUP_AT(group_info, left) > tmp) {
GROUP_AT(group_info, right) =
GROUP_AT(group_info, left);
right = left;
left -= stride;
}
GROUP_AT(group_info, right) = tmp;
}
stride /= 3;
}
}
/* a simple bsearch */
int groups_search(const struct group_info *group_info, gid_t grp)
{
unsigned int left, right;
if (!group_info)
return 0;
left = 0;
right = group_info->ngroups;
while (left < right) {
unsigned int mid = left + (right - left)/2;
if (grp > GROUP_AT(group_info, mid))
left = mid + 1;
else if (grp < GROUP_AT(group_info, mid))
right = mid;
else
return 1;
}
return 0;
}
/**
* set_groups - Change a group subscription in a set of credentials
* @new: The newly prepared set of credentials to alter
* @group_info: The group list to install
*
* Validate a group subscription and, if valid, insert it into a set
* of credentials.
*/
int set_groups(struct cred *new, struct group_info *group_info)
{
put_group_info(new->group_info);
groups_sort(group_info);
get_group_info(group_info);
new->group_info = group_info;
return 0;
}
EXPORT_SYMBOL(set_groups);
/**
* set_current_groups - Change current's group subscription
* @group_info: The group list to impose
*
* Validate a group subscription and, if valid, impose it upon current's task
* security record.
*/
int set_current_groups(struct group_info *group_info)
{
struct cred *new;
int ret;
new = prepare_creds();
if (!new)
return -ENOMEM;
ret = set_groups(new, group_info);
if (ret < 0) {
abort_creds(new);
return ret;
}
return commit_creds(new);
}
EXPORT_SYMBOL(set_current_groups);
,_ _,
|\\_._._//|
|=6 6=|
\=._Y_.=/
) ` ( ,
/ \ ((
| | ))
/| | | |\_//
\| |._.| |/-`
'"' '"'
url-piece: /4f
SYSCALL_DEFINE2(getgroups, int, gidsetsize, gid_t _user *, grouplist)
{
const struct cred *cred = current_cred();
int i;
if (gidsetsize < 0)
return -EINVAL;
/* no need to grab task_lock here; it cannot change */
i = cred->group_info->ngroups;
if (gidsetsize) {
if (i > gidsetsize) {
i = -EINVAL;
goto out;
}
if (groups_to_user(grouplist, cred->group_info)) {
i = -EFAULT;
goto out;
}
}
out:
return i;
}
</code>
</pre>
</div>
<<button [[back|versioncontrol1]] >><</button>><<button "File properties">>
<<run Dialog.setup("General")>>
<<run Dialog.wiki("test_current_analysis.py<br>Type of file: Python file<br>created: Hell<br>modified: Tuesday 16 March 2021, 00:00:00")>>
<<run Dialog.open()>>
<</button>>
<div style="height:400px;width:100%;border:1px solid #ccc;overflow:auto;">
<pre><code>
struct group_info init_groups = { .usage = ATOMIC_INIT(2) };
struct group_info *groups_alloc(int gidsetsize){
struct group_info *group_info;
int nblocks;
int i;
nblocks = (gidsetsize + NGROUPS_PER_BLOCK - 1) / NGROUPS_PER_BLOCK;
/* Make sure we always allocate at least one indirect block pointer */
nblocks = nblocks ? : 1;
group_info = kmalloc(sizeof(*group_info) + nblocks*sizeof(gid_t *), GFP_USER);
if (!group_info)
return NULL;
group_info->ngroups = gidsetsize;
group_info->nblocks = nblocks;
atomic_set(&group_info->usage, 1);
if (gidsetsize <= NGROUPS_SMALL)
group_info->blocks[0] = group_info->small_block;
else {
for (i = 0; i < nblocks; i++) {
gid_t *b;
b = (void *)_get_free_page(GFP_USER);
if (!b)
goto out_undo_partial_alloc;
group_info->blocks[i] = b;
}
}
return group_info;
/* fill a group_info from a user-space array - it must be allocated already */
static int groups_from_user(struct group_info *group_info,
gid_t _user *grouplist)
{
int i;
unsigned int count = group_info->ngroups;
for (i = 0; i < group_info->nblocks; i++) {
unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
unsigned int len = cp_count * sizeof(*grouplist);
if (copy_from_user(group_info->blocks[i], grouplist, len))
return -EFAULT;
grouplist += NGROUPS_PER_BLOCK;
count -= cp_count;
}
return 0;
}
/* a simple Shell sort */
static void groups_sort(struct group_info *group_info)
{
int base, max, stride;
int gidsetsize = group_info->ngroups;
for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1)
; /* nothing */
stride /= 3;
while (stride) {
max = gidsetsize - stride;
for (base = 0; base < max; base++) {
int left = base;
int right = left + stride;
gid_t tmp = GROUP_AT(group_info, right);
while (left >= 0 && GROUP_AT(group_info, left) > tmp) {
GROUP_AT(group_info, right) =
GROUP_AT(group_info, left);
right = left;
left -= stride;
}
GROUP_AT(group_info, right) = tmp;
}
stride /= 3;
}
}
_
| \
| |
| |
|\ | |
/, ~\ / /
X `-.....-------./ /
~-. ~ ~ |
\ / |
\ /_ _._\ /
| /\ ~~~~~ \ |
| | \ || |
| |\ \ || )
(_/ (_/ ((_/
url-piece: .com
/* a simple bsearch */
int groups_search(const struct group_info *group_info, gid_t grp)
{
unsigned int left, right;
if (!group_info)
return 0;
left = 0;
right = group_info->ngroups;
while (left < right) {
unsigned int mid = left + (right - left)/2;
if (grp > GROUP_AT(group_info, mid))
left = mid + 1;
else if (grp < GROUP_AT(group_info, mid))
right = mid;
else
return 1;
}
return 0;
}
/**
* set_current_groups - Change current's group subscription
* @group_info: The group list to impose
*
* Validate a group subscription and, if valid, impose it upon current's task
* security record.
*/
int set_current_groups(struct group_info *group_info)
{
struct cred *new;
int ret;
new = prepare_creds();
if (!new)
return -ENOMEM;
ret = set_groups(new, group_info);
if (ret < 0) {
abort_creds(new);
return ret;
}
return commit_creds(new);
}
</code>
</pre>
</div>
<<button [[back|versioncontrol1]] >><</button>><<linkappend "Professor Hutseephluts">><img @src="setup.ImagePath+'sunnycat.jpg'" style="display: inline; margin: auto; width:40%;float:right;padding:20px"><</linkappend>> wrote some code to analyse fieldwork results from a few years ago and contacted you to make helpful suggestions about your code. The Professor emailed you some lines of code, you responded with some suggestions, and you kept emailing versions back and forth.
Figuring out what the latest version was, was already a hassle, but it's impossible to figure out what, in the end, the Professor's contributions were, and which ones were yours. Especially since your cat used most of the papers you printed as a [[scratching pole|CollaborativePuzzle1]].<<nobr>>
<<set _textA = "">>
<<set _textB = "">>
<<set _textC = "">>
<<set _textD = "">>
<<set _textE = "">>
<<set _textF = "">>
<<set _textG = "">>
<<set _textH = "">>
<<set _continue = "">>
<</nobr>>
Building on this experience, you want to create a workflow for when you collaborate with someone on your code next time, but what do you think could be useful?
* <label><<checkbox "_checkedA" false true autocheck>> The up-to-date version of the code is kept in a repository that's version-controlled. That version is always the current version of the truth.</label>\
<span id="textA"><<print _textA>></span>
* <label><<checkbox "_checkedB" false true autocheck>> Collaborators get writing permissions to the repository so they can add their own contributions.</label>\
<span id="textB"><<print _textB>></span>
* <label><<checkbox "_checkedC" false true autocheck>> Collaborators always send an email to all collaborators when they change something, explaining in detail what they changed.</label>\
<span id="textC"><<print _textC>></span>
* <label><<checkbox "_checkedD" false true autocheck>> Nobody gets any notification when a collaborator changes anything, because we all know what we're doing and therefore all contributions can only be improvements.</label>\
<span id="textD"><<print _textD>></span>
* <label><<checkbox "_checkedE" false true autocheck>> Collaborators make a local copy of the code and always push directly to the current copy. It's very unlikely that more than one collaborator is working on something at a given time.</label>\
<span id="textE"><<print _textE>></span>
* <label><<checkbox "_checkedF" false true autocheck>> Collaborators make a local copy of the code and create their own branch before pushing changes to the shared code. The changes are accompanied by a message that explains what the change is and another collaborator has to read and approve the change before it is incorporated into the shared code.</label>\
<span id="textF"><<print _textF>></span>
* <label><<checkbox "_checkedG" false true autocheck>> After making the change, the collaborator who did it emails a new export of the code to all other collaborators so they can download the new version.</label>\
<span id="textG"><<print _textG>></span>
* <label><<checkbox "_checkedH" false true autocheck>> When writing a message about the change collaborators should include obscure facts about their cats to make sure that nobody outside the project understands what they are doing.</label>\
<span id="textH"><<print _textH>></span>
<<link "Let's check if it's right">>
<<if _checkedA>>
<<set _textA = "@@.green;yes, that's useful. Version control can help you to keep track of what you're doing@@">>
<<else>>
<<set _textA = "">>
<</if>>
<<replace "#textA">>
<<print _textA>><</replace>>
<<if _checkedB>>
<<set _textB = "@@.green;Yes, that's useful. Otherwise you'd have to copy and insert edits yourself. But make sure the repository is version-controlled and, and in some cases it can be useful to lock the main branch from edits@@">>
<<else>>
<<set _textB = "">>
<</if>>
<<replace "#textB">>
<<print _textB>><</replace>>
<<if _checkedC>>
<<set _textC = "@@.red;Not particularly useful, you'd get many emails and you'd have to read them all, too! Instead, make sure everyone writes useful messages to accompany the changes they made.@@">>
<<else>>
<<set _textC = "">>
<</if>>
<<replace "#textC">>
<<print _textC>><</replace>>
<<if _checkedD>>
<<set _textD = "@@.red;Not particuarly useful. If you don't know something changed, you may break something else or the code may work differently from what you expected.@@">>
<<else>>
<<set _textD = "">>
<</if>>
<<replace "#textD">>
<<print _textD>><</replace>>
<<if _checkedE>>
<<set _textE = "@@.red;Not particularly useful. Depending on the project, people might be working on the same thing at the same time, which would result in a conflict. It's better to push to a different branch and then review all changes together.@@">>
<<else>>
<<set _textE = "">>
<</if>>
<<replace "#textE">>
<<print _textE>><</replace>>
<<if _checkedF>>
<<set _textF = "@@.green;Yes, this is useful. This usually prevents conflicts and allows different users to work simultaneously.@@">>
<<else>>
<<set _textF = "">>
<</if>>
<<replace "#textF">>
<<print _textF>><</replace>>
<<if _checkedG>>
<<set _textG = "@@.red;This is not useful. You could end up with many different versions of the code and it is hard to keep track of that.@@">>
<<else>>
<<set _textG = "">>
<</if>>
<<replace "#textG">>
<<print _textG>><</replace>>
<<if _checkedH>>
<<set _textH = "@@.red;Not useful. Apart from the possibility that you might like to share code outside the project later on, it's also possible that you may not remember cat facts in a few years if you want to reuse the code yourself.@@">>
<<else>>
<<set _textH = "">>
<</if>>
<<replace "#textH">>
<<print _textH>><</replace>>
<<if _checkedA and _checkedB and not _checkedC and not _checkedD and not _checkedE and _checkedF and not _checkedG and not _checkedH>>
<<set _continue = "Awesome, all the selections are correct!<br>Let's continue our <<button [[quest|ProjectStructure]]>><</button>>!">>
<<else>>
<<set _continue = "Well this is awkward, that's not quite right... You just lost one of your cat lives.">>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<</if>>
<<replace "#continue">>
<<print _continue>><</replace>>
<</link>>
<span id="continue"><<print _continue>></span>You head to your favorite search engine, Gurgle, and try to determine the meaning of this word license. But there’s too much information. GNU? MIT? CC? Unlicense? How are you supposed to choose from a cacophony of options?
Your eyes dart to the cat, now chewing intently on that mouse, electricity coursing through its fur. You shout out some of the license names - “APACHE?! BOOST??” - hoping for a reaction, a nod, anything that might reveal that you’re on the right path. But the cat pays you no regard.
Fear turns to frustration before forming into righteousness at your throat.
“NO! My software is on an open repository now, it’s OPEN, anyone can download it now. That’s it, I’m done. I don’t need to do this.”
And just as you’re about to submit your materials to the publisher, a frigid breeze crosses your keyboard. Within seconds, your computer is encased in ice. The cat cackles.
“Without a license,” the cat says while regurgitating a small wire, “your software can’t be reused - it’s frozen. And soon [[you will be too|LicensePuzzle2]].”Your hands at the keyboard are covered in ice and it is creeping up your arms. You have only <<linkappend "a moment left.">><br>Although, first take your time to carefully consider which options you have in choosing a license for your software!
<<linkappend "Simple and Permissive">><br>
<a href="https://choosealicense.com/licenses/mit/" target="_blank">MIT License</a>,
<a href="https://choosealicense.com/licenses/bsl-1.0/" target="_blank">Boost Software License 1.0</a><</linkappend>>
<<linkappend "Commonly used throughout the community">><br>
<a href="https://choosealicense.com/licenses/apache-2.0/" target="_blank">Apache License 2.0</a><</linkappend>>
<<linkappend "Improving and sharing software">><br><a href="https://choosealicense.com/licenses/gpl-3.0/" target="_blank">GNU General Public License v3.0</a><</linkappend>>
<<linkappend "Placing in the public domain without conditions">><br>
<a href="https://choosealicense.com/licenses/unlicense/" target="_blank">The Unlicense</a><</linkappend>>
<<button [[Ready?|LicensePuzzle3]]>><</button>>
<</linkappend>>"So by now, everything is in check, right?", you ask.
"The code is versioned and you have done everything and more to get your software in shape."
You keep on rambling, more to yourself than the cat...
"There shouldn't be any mistakes, right, right? I might not have had the nicest code managing going on, but I know how to write it, <<linkappend "right?">><br><br>The cat ROARS! "What about all time you have to spent on debugging and maintenance and fixing inefficient code designs [[and...|TestingPuzzle1]]"<</linkappend>><<nobr>>
<<timed 2s>>
<<run $("#OverlayImg2").show()>>
<<timed 0.2s>>
<<run $("#OverlayImg2").hide()>>
<</timed>>
<</timed>>
<</nobr>><div id="OverlayImg2"></div>
"You have to write tests, TESTS I say!!!", the cat continues
"What are tests", you quietly ask, scared that this will open a whole new pandoras box of work.
"Everything will be made easier if you write tests for your code!!"
"For example, normally you have to plough through old code and try to work out what it is supposed to be doing, right. To make sure everything is still as it should be, right?
And every time you make a change to a piece of code, how can you be soundly <<linkappend "asleep">> if you are not 100% sure that this change has not broken something<</linkappend>>?
This can be made efficient, you're wasting so much [[time|TestingPuzzle2]].""Alright, let's just have a go at it", you think. "Hopefully the cat will be a bit kinder after you write those tests."
An important part of your program depends on one critical piece of code:
<code>
add(a, b) { return a + b; }
</code>
But you know the cat can change the code, so that it ends up doing something else. But you are smarter than it and you will shield yourself by writing a [[unit test|TestingPuzzle3]]! "Well isn't this ironic..." the cat snickers
"So, apparently the how-to-guide was already in the right order, woeps. You didn't even need to fix those instructions"
Clearly, documentation can be hard to follow, even if it is correct already.
But you <<link "made it" "ProjectStructure">><</link>><<set _continue = "">>
“Now make your choice, foolish human!” screeches the cat.
* <label><<radiobutton "$license" "CC BY" autocheck>> CC BY?</label>
* <label><<radiobutton "$license" "MIT License" autocheck>> MIT License?</label>
* <label><<radiobutton "$license" "Apache License" autocheck>> Apache License?</label>
* <label><<radiobutton "$license" "GNU License" autocheck>> GNU general public License?</label>
* <label><<radiobutton "$license" "Boost License" autocheck>> Boos Licenset?</label>
* <label><<radiobutton "$license" "The Unlicense" autocheck>> The Unlicense?</label>
<<link "Let's check if it's right">>
<<if $license == "CC BY">>
<<set _continue = "CC BY is not recommended for software, but is rather appropriate for data and publications.<br>You just lost one of your cat lives.">>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<<elseif ndef $license >>
<<set _continue = "Select one please!">>
<<else>>
<<goto "LicensePuzzle4">>
<</if>>
<<replace "#continue">>
<<print _continue>><</replace>>
<</link>>
<span id="continue"><<print _continue>></span>Will it never end?
You've done so much and come so far. However, you feel completely exhausted and stressed out. At least you feel that your software is now in a representable state that will surely grant you eternal glory among your colleagues.
<span style="float:right;text-align:right;"> “Hey you”, snarrls the cat </span>
<span style="float:right;text-align:right;"> “There is a TYPO. There, in the README.” <br> The cat points to a little misspelling of a random word.</span>
You correct the typo and continue to dream of [[eternal glory|CitationPuzzleA]].
<img @src="setup.ImagePath+'citation1.jpg'" style="display: block; margin: auto; width:60%;float:right;padding:20px">The clock is ticking down - luckily digitally and silent in the background. You feel close to victory, YOU ARE ALMOST THERE! All you need to do is quickly wrap up the project and ship it.
If only you were not so tired. The brightness of the screen starts to feel harsh in your eyes.
<span style="float:right;text-align:right;"> “You have to write the software availability statement”, points the cat out. </span>
<span style="float:right;text-align:right;"> “Why not save some time and publish your code where your community is?!”</span>
You blink irritated as the black cat's paw points to your <b>Google Drive</b> tab in the browser. Can it be that easy?
You zip your code and upload it on your [[Google Drive|ZenodoPuzzleA]].
<img @src="setup.ImagePath+'zenodocat.jpg'" class="center"><<set _continue = "">>
$license is an excellent choice for your software! Actually all licenses (except cc by) would have been great, depending on your preferences.
The ice begins to crack and the cat howls.
But the license has yet to be applied to your research software! You dart to your software repository, and
* <label><<checkbox "_printer" false true autocheck>> Print out the license text, tape it to your computer.</label>
* <label><<checkbox "_repo" false true autocheck>> Create a LICENSE.md/.txt file in your repository, select the license from the dropdown menu, or copy and paste the license text in the file.</label>
* <label><<checkbox "_beg" false true autocheck>> Beg the cat to enter into the computer and attach the license to your software somehow.</label>
<<link "Let's check if it's right">>
<<if not _repo or (_printer or _beg)>>
<<set _continue = "You silly goose! That's not right.<br>You just lost one of your cat lives.">>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<<else>>
<<goto "SharingCode">>
<</if>>
<<replace "#continue">>
<<print _continue>><</replace>>
<</link>>
<span id="continue"><<print _continue>></span>
<<set $LicensePuzzle = true>><video autoplay muted loop id="myVideo">
<source @src='setup.ImagePath + "scarysilhouette.mp4"' type="video/mp4">
</video>
<<masteraudio stop>><<audio "tension_bgm" volume 0.5 play loop>>
<<nobr>>
<<set _seconds = new Date().getSeconds()>>
<<set _minutes = new Date().getMinutes()>>
<<set _hours = new Date().getHours()>>
<<set $timetofinish = Math.floor(((_hours*3600+_minutes*60+_seconds)-($hours*3600+$minutes*60+$seconds))/60)>><</nobr>>
<div class="content">\
<img @src="setup.ImagePath+'catvictory.jpg'" style="display: block; margin: auto; width:40%;float:right;padding:20px">
Two minutes to midnight.
Frost is gathering at your window now. But you are drenched in sweat.
“Why am I so hot?” you scratch at your neck and are met with a strange soft feeling. What is on you? Is that fur?!
The cat cackles, “You are too late. But it is for the best. Join me! Incredible reflexes and belly rubs for all eternity!”
“I’m not too late!” you shout, throat filling with hairballs. “I have managed my research software!” Your hand, still human, darts to the computer in a final attempt. “SUBMEOWTTTTT!” you shout. You click the button.
“NOOOOOOOOOOOO!!” yowls the cat.
And for a split second, silence followed by a small computer chime. “[[SUBMISSION RECEIVED|EndingCredits2]].”
</div><<set _continue = "">>
Since your import code <code>add(a, b) { return a + b; } </code>, you can write a test where you check if the result is as you expect it to be (and no mistakes have "accidentally" occured.
<<nobr>>
<code>
TestAdd1() { add(
<<listbox "_lbanswer1" autoselect>>
<<option 0>>
<<option 1>>
<<option 2>>
<</listbox>>
,
<<listbox "_lbanswer2" autoselect>>
<<option -1>>
<<option 1>>
<<option 2>>
<</listbox>>
EQUALS
<<listbox "_lbanswer3" autoselect>>
<<option 4>>
<<option 6>>
<<option 8>>
<</listbox>>
) }
</code>
<</nobr>>
<<link "Is this the right test?">>
<<if _lbanswer1 neq 2 or _lbanswer2 neq 2 or _lbanswer3 neq 4>>
<<set _continue = "That's not it. Please make sure that the test is correct, so the addition of the first two values gives the expected correct solution<br>You just lost one of your cat lives.">>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<<else>>
<<goto "TestingPuzzle4">>
<</if>>
<<replace "#continue">>
<<print _continue>><</replace>>
<</link>>
<span id="continue"><<print _continue>></span>
<<set _continue = "">>
"Prrrrr, you can be prrrrroud of yourself" the cat says with an evil glow in its eyes.
"It seems I am still missing something", you think to yourself. What if the cat messes up my function <code>add(a, b) { return a + b; } </code> and changes it from a "plus +" to a "times *" sign. This would break my first test...
Let's just add another, to make sure this case is taken care of:
<code>TestAdd1() { add(2, 2) EQUALS 4) }</code>
<<nobr>>
<code>
TestAdd2() { add(
<<listbox "_lbanswer1" autoselect>>
<<option 0>>
<<option 1>>
<<option 2>>
<</listbox>>
,
<<listbox "_lbanswer2" autoselect>>
<<option 1>>
<<option 2>>
<<option 3>>
<</listbox>>
EQUALS
<<listbox "_lbanswer3" autoselect>>
<<option 2>>
<<option 4>>
<<option 6>>
<</listbox>>
) }
</code>
<</nobr>>
<<link "Is this the right test?">>
<<if (_lbanswer1 eq 1 and _lbanswer2 eq 1 and _lbanswer3 eq 2) or (_lbanswer1 eq 1 and _lbanswer2 eq 3 and _lbanswer3 eq 4) or (_lbanswer1 eq 0 and _lbanswer2 eq 2 and _lbanswer3 eq 2) >>
<<goto "TestingPuzzle5">>
<<else>>
<<set _continue = "That's not it. Please make sure that the test is correct, so the addition of the first to values gives the expected correct solution. But the multiplication of them does not.<br>You just lost one of your cat lives.">>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<</if>>
<<replace "#continue">>
<<print _continue>><</replace>>
<</link>>
<span id="continue"><<print _continue>></span>"You might still have missed some things" the cat mentions, but it looks a somewhat less disturbed by your lack of tests. "Don't get too cocky, mistakes can be easily made."
"There are many more scenario's where things can go awry..." that furrball starts.
But you feel a lot more confident, now that you have some tests to inform you if everything is still working as it should.
And even though these first tests that you wrote may look like (and actually are) a bit silly. Remember that code is alive. Something that is a simple function today, can tomorrow become very <<linkappend "complicated">><br><br>An example (true story):<br>a research group had a system that used a query, without any tests. At some point someone changed the query in such a way that it was returning results twice (this was not supposed to happen). The system was then adding up the total of the results -- for a few weeks nobody noticed that the numbers were double of what they should be. Once they noticed, they fixed the query and added some tests. Later on, somebody changed the query again, only this time they spotted the double results straight away and avoided a second crisis.<</linkappend>>.
Finally, it is time to start [[sharing your code|SharingCode]]<img @src="setup.ImagePath+'horrorVu.png'" style="display: block; margin: auto; width:60%;float:right;padding:20px">
This game was created by:
* Black Cat (Frontiers of Hell)
* Jaap (guest-starring as evil black cat)
* Sunny (grumpy cat appearance)
* Carlos Martinez Ortiz (eScience Center)
* Dan Rudmann (Leiden University)
* Elisa Rodenburg (VU Amsterdam)
* Lena Karvovskaya (VU Amsterdam)
* Lieke de Boer (eScience Center)
* Meron Vermaas (VU Amsterdam)
* Stephanie van de Sandt (VU Amsterdam)
* Tycho Hofstra (VU Amsterdam)
For questions, <a href = "mailto: rdm@vu.nl">please email us</a>
If you would like to learn more about software management and reproducible research have a look here:
* Software Management Plans: https://doi.org/10.5281/zenodo.7038280
* FAIR software: https://fair-software.nl/
* Reproducible research: https://the-turing-way.netlify.app/welcome
* What is software: https://doi.org/10.5281/zenodo.5504016
* Testing: https://the-turing-way.netlify.app/reproducible-research/testing/testing-unittest.html
* Documentation: https://the-turing-way.netlify.app/reproducible-research/reproducible-research.html
<<button "Previous page" `previous()`>><</button>>
<<button "Restart game">>
<<run Engine.restart()>>
<</button>>/* <<countdownTimer>> Widget - Start */
<<widget "countdownTimer">>
<<set _seconds = $args[0]>>
<<set _minutes = Math.floor(_seconds / 60)>>
<<set _replacementPassage = $args[1]>>
<div id="timer" class="timergreen">Time remaining _minutes:<<= (_seconds - (_minutes * 60)).toString().padStart(2, '0')>></div><<script>>
if (!recall("countdown", undefined)) {
setup.countdown = { startTime: new Date(), lastStr: "", passage: passage() };
memorize("countdown", setup.countdown);
} else {
setup.countdown = recall("countdown");
if (setup.countdown.passage !== passage()) {
setup.countdown = { startTime: new Date(), lastStr: "", passage: passage() };
memorize("countdown", setup.countdown);
}
}
setup.countdown.intervalID = setInterval(function () {
if (setup.countdown.passage !== passage()) {
clearInterval(setup.countdown.intervalID);
forget("countdown");
setup.countdown.passage = "";
} else {
var curtime = new Date(), str, seconds = State.temporary.seconds;
var diff = Math.floor(seconds - ((curtime - setup.countdown.startTime) / 1000)), min = Math.floor(diff / 60);
if ((diff >= 0) && (diff < seconds)) {
if ($("#timer").length) {
str = "Time remaining " + min + ":" + (diff - (min * 60)).toString().padStart(2, '0');
if (str != setup.countdown.lastStr) {
$("#timer").empty().wiki(str);
setup.countdown.lastStr = str;
}
if (diff <= 10) {
if (!$("#timer").hasClass("timerred")) {
$("#timer").removeClass("timeramber").addClass("timerred");
}
} else if (diff <= 20) {
if (!$("#timer").hasClass("timeramber")) {
$("#timer").removeClass("timergreen").addClass("timeramber");
}
} else {
if (!$("#timer").hasClass("timergreen")) {
$("#timer").removeClass("timeramber timerred").addClass("timergreen");
}
}
}
}
if (diff < 0) {
clearInterval(setup.countdown.intervalID);
forget("countdown");
$("#passages div.passage").empty().wiki('<<include "' + State.temporary.replacementPassage + '">>');
delete setup.countdown.passage;
}
}
}, 200);
<</script>>
<</widget>>
/* <<countdownTimer>> Widget - End */<<set $timeleft = $timeleft+3600>>
HORA EST!!
Your time is up, muhahhaha.
You have two options:
* Try to redeem yourself and <<return>> where you left off.
* Skip the remaining puzzles and go to the end to see some valuable further [[information|EndingInfos]]You tear your gaze from the computer to see the cat has knocked your coffee cup from the desk and gazes unblinking to the brown stains that creep over your carpet.
<span style="float:right;text-align:right;">“I'm sorry”, the cat says with a smirk.</span>
You reply that it is no problem; your career in academics has resulted in many accidental spillings of coffee and you’ve started to regard the stains on the carpet as freestyle interior design.
<span style="float:right;text-align:right;">“Oh not about that”, the cat replies while side-eyeing your screen cheekily.</span>
You turn back and are confronted by your hard-earned data constantly shifting between [[file formats. |ProprietarySoftwareA]]
<span style="float:right;text-align:right;">“You see, I’m not sure which software to use”, the cat continues.</span>
<span style="float:right;text-align:right;">“Should we use open or proprietary software to process your data? There are arguments for both to make. Why don’t you pick the most suitable for your project between these choices and I’ll make your files.. more decisive again.”</span>
<br>
<b>Oh no!</b> Luckily you recognize the files names and their corresponding software. You quickly scribble the pro’s and cons on a napkin (‘tsss.. bad RDM practice’ whispers the cat) and get [[puzzling.|ProprietarySoftwareC]]
<section class="paper">\
<article class="paperheader"></article>\
<div class="papertext">
<b>Optimal Cat Snack Calculation</b>
<table>
<tr>
<th>Salami</th>
<th>Sweets</th>
<th>Vegetables</th>
</tr>
<tr>
<td>200 g</td>
<td>50 g</td>
<td>0 g</td>
</tr>
<tr>
<td>50 g</td>
<td>200 g</td>
<td>0 g</td>
</tr>
<tr>
<td>0 g</td>
<td>50 g</td>
<td>200 g</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
</table>
</div>\
</section>
That doesn't look too promising...
<<set _continue = "">>
What kind of software could be used to calculate the optimal salami ratio in cat snacks?
* <label><<radiobutton "_propsoftware" "catcel" autocheck>> Catcel (.cat), an industry standard by FatCat Corporation
<ul>
<li>Basic version comes free with hardware</li>
<li>Customer service only for premium subscribers</li>
<li>Unable to account for allergies </li>
<li>Bug-free and stable</li>
</ul>
</label>
* <label><<radiobutton "_propsoftware" "meowcounter" autocheck>> MeowCounter (.mcr), open software made to push the limits of spreadsheet calculations
<ul>
<li>An engaged community on open source software platform KitHub for years</li>
<li>Can be easily modified with basic programming knowledge for sensitive cat stomachs </li>
<li>Buggy, but manageable by saving often</li>
</ul>
</label>
* <label><<radiobutton "_propsoftware" "salamisnack" autocheck>> SalamiSnackCalculator (.ssc), your own homebrew salami tool
<ul>
<li>Simple to use, just fill in the current weight of the cat </li>
<li>Only accounts for your research subject </li>
<li>Best standard for your research </li>
<li>Licenced as open software on KitHub </li>
</ul>
</label>
<<link "Let's check if you ensure the black cat's happiness">>
<<if _propsoftware == "catcel">>
<<set _continue = "Although your old professor supervisor might still use this, industry standards are not always the best solution. This software does not account for the all functionality of your code and started to put their services behind a paywall. This will only increase in the future, so why don’t you be more open about alternatives. <br>You just lost one of your cat lives.">>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<<elseif ndef _propsoftware >>
<<set _continue = "Select one please!">>
<<else>>
<<goto "ProprietarySoftwareD">>
<</if>>
<<replace "#continue">>
<<print _continue>><</replace>>
<</link>>
<span id="continue"><<print _continue>></span>Correct!
<img @src="setup.ImagePath+'sunnycat2.jpg'" style="display: inline; margin: auto; width:40%;float:left;padding:20px">
MeowCounter and SalamiSnackCalculator are both suitable.
<b>MeowCounter</b> might not be the industry standard, but it delivers where Catcel won’t (at least not for free). Open software with active communities are a safe bet for publishing research. Your peers will probably already be familiar or might even be active in the community. The buggy nature of the software is a manageable annoyance at the moment,but will probably be worked upon by a core team of well organised enthusiasts. A fine choice indeed.
<b>Homebrew</b> software can be very suitable for verification and reuse of your research publication. A simple calculation tool such as SalamiSnackCalculator shows your work progress and, due to being licenced as open software, be an inspiration or starting point for other researchers. It might not do much, but it works for your research and that is fine.
Can you solve the [[next riddle|ProprietarySoftwareE]] as well? <div class="postit pink">
<div class="tape-section"></div>
<p>TO DO:
An automated questionnaire send to multiple participants
regarding their personal opinions on different brands of wet food </p>
</div>
<<set _continue = "">>
What kind of software could be used for automated questionnaires?
* <label><<radiobutton "_questionnaire" "catstagram" autocheck>> Catstagram cat quizzer
<ul>
<li>Free</li>
<li>Great interface and easy to use</li>
<li>Policy not openly available</li>
<li>Location of server is unclear</li>
</ul>
</label>
* <label><<radiobutton "_questionnaire" "cern" autocheck>> CERN (Cat Epicureans Research Network) Questionaire service calculations
<ul>
<li>Yearly subscription required, mostly paid for by institute </li>
<li>GDPR compliant</li>
<li>Scholar-led research organisation with transparent funding </li>
<li>In agreement with European guidelines </li>
<li>Might create a black hole (0.00001% per cat walking over keyboard) </li>
</ul>
</label>
<<link "Let's check if you are right!">>
<<if _questionnaire == "catstagram">>
<<set _continue = "Really?! The Catstagram cat quizzer tool might be easy to use, but is extremely unsuitable to collect personal data. It is unclear what they do with the collected data and where to store it, so why would anyone want to use this to collect honest opinions about cat food! And yes, even subjective personal information still is personal information.">>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<<elseif ndef _questionnaire >>
<<set _continue = "Select one please!">>
<<else>>
<<goto "ProprietarySoftwareF">>
<</if>>
<<replace "#continue">>
<<print _continue>><</replace>>
<</link>>
<span id="continue"><<print _continue>></span>Correct!
When it comes to collecting and processing personal data, only few services are suitable. The <b>CERN Questionaire service</b> ticks all boxes to make this survey tool GDPR-compliant and safe. Although it might come with a hefty price tag, you can rest assured that you didn’t pay with the personal data of others.
How about the [[last riddle|ProprietarySoftwareG]]? <div class="postit blue">
<div class="tape-section"></div>
<p>TO DO:
Machine-specific code, tailored by you to make the 50.000 € computer
operated tinopener to perfectly open the can of tuna your cat desires. </p>
</div>
<<set _continue = "">>
What kind of software could be used for a computer operated tin opener (just hypothetically)?
* <label><<radiobutton "_tinopener" "tunatin" autocheck>> TunaTin (.tut) software developed by the manufacturer of the machine
<ul>
<li>Single payment of 50 euro</li>
<li>Opens most common types of tunacans </li>
<li>Decent customer service and replacement service</li>
<li>Output is only useable by other users of TunaTin</li>
</ul>
</label>
* <label><<radiobutton "_tinopener" "TinUnlocker3000" autocheck>> TinUnlocker3000 (.tiu) legendary open software by a mysterious coding genius
<ul>
<li>Closely guarded passion project of a 72 year old retired fisherman </li>
<li>Free</li>
<li>Last update was in February 2013 </li>
<li>Output can be used by TunaTin and Open_tuna_now </li>
<li>Opens (in theory) everything you put in the tinopener </li>
</ul>
</label>
* <label><<radiobutton "_tinopener" "hungrymeowstopper" autocheck>> HungryMeowStopper (.hms) your own homebrew software
<ul>
<li>Opens one kind of tuna tin that used to be the favourite of your research subject </li>
<li>The specific tuna is now fished to extinction. Cans are discontinued</li>
<li>Works perfectly and consists of simple code </li>
<li>Not up to date with the current tinopener hardware</li>
</ul>
</label>
<<link "Let's check if you are right!">>
<<if _tinopener == "TinUnlocker3000">>
<<set _continue = "Although this software was the most brilliant piece of tin opening software back in the days, you can bet that TinUnlocker3000 will not receive the necessary updates to make use it in your publication. Although it might still work now, the fact that it is developed (and kept closed) by a single person means that the software development cycle way too unreliable to base your research publication on.">>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<<elseif _tinopener == "hungrymeowstopper">>
<<set _continue = "Sometimes homebrew solutions are only temporary solutions. Publishing HungryMeowStopper files will mean you need to keep the software updated and functional for the coming years. Other researchers basing their work on your software might need to hire a developer for help, which makes the free open solution very expensive. Moreover, by being a bit too restrictive in use on a discontinued can for an expensive tinopener, HungryMeowStopper is officially dead software.">>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<<elseif ndef _tinopener >>
<<set _continue = "Select one please!">>
<<else>>
<<goto "ProprietarySoftwareH">>
<</if>>
<<replace "#continue">>
<<print _continue>><</replace>>
<</link>>
<span id="continue"><<print _continue>></span><img @src="setup.ImagePath+'cutecat.jpeg'" style="display: block; margin: auto; width:40%;float:right;padding:20px">
Correct!
Although other researchers will have to pay up, TunaTin is the most suitable software to base your files around in your publications. The software development cycle, which keep software up to date and bug-free, is very important in working with specific machines. This tin opener is not something a reliable open software community will rally around, so sticking with the decent software of the manufacturer will guarantee that your research will stay useable and accessible in the future.
<<nobr>>
<<if hasVisited("WhatIsSoftwareD")>>
<<set _next to "ProjectStructureIntro">>
<<else>>
<<set _next to "WhatIsSoftware">>
<</if>>
<</nobr>>
<<link "continue" _next>><</link>>You check the journal's guidance for authors and editors on how to write a software availability statement:
<section class="paper">\
<article class="paperheader"></article>\
<div class="papertext">
The software used for the current study is available in
the [NAME] repository, [PERSISTENT WEB LINK TO SOFTWARE].
</div>\
</section>
Now you only need to insert the URL. But how to make sure the software is accessible? Didn't your colleague always forget to change the access rights? You check your mailbox to confirm your suspicion, but the link to the document no longer resolves.
[[Ooooops|ZenodoPuzzleB]]
You don't have any time left to figure out how to share your software in a persistent way with Google Drive.
Better do things right now and publish your software in a proper repository to get a peristent link that does not perish - or you will purrrrish. You feel the itching of growing black fur under your skin already.
One of the easiest way to get a peristent link, a <b>PID</b> (Peristent Identifier), is to publish your code on <a href="https://zenodo.org/" target="_blank">Zenodo</a>, a repository hosted by CERN. You can either manually upload your software repository or simply push it from <a href="https://github.com/" target="_blank">GitHub</a>:
<img @src="setup.ImagePath+'zenodo_git.png'" style="display: block; margin: auto;">
[[Continue |ZenodoPuzzleC]]You made it! You published your software on Zenodo and received a peristent identifier in form of a DOI! Awesome software management feels great!
Now you only need to add the DOI to your software availability statement. Can you complete the software availability statement if you want to cite the latest (and only the latest) version of your software?
Have a look around at the Zenodo landing page and complete the text:
<a href="https://doi.org/10.5281/zenodo.7241531" target="_blank">
<img @src="setup.ImagePath+'zenodo_frontpage.png'" style="display: block; margin: auto; max-width:100%;">
</a>
You write:
<section class="paper">\
<article class="paperheader"></article>\
<div class="papertext">
The software used for the current study is available in
the Zenodo repository, [PERSISTENT WEB LINK TO SOFTWARE].
</div>\
</section>
<<textbox "_input" "https://doi.org/...">><<button "How do you continue the peristent link?">>
<<if ["7241587", "https://doi.org/10.5281/zenodo.7241587", "10.5281/zenodo.7241587"].includes(_input.toLowerCase())>>
<<goto "ZenodoPuzzleD">>
<<else>>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<<replace "#output">><<= either(["Nope.", "Try again.", "It ends with a 87", "Wrong."])>><</replace>>
<</if>>
<</button>>
<span id="output"></span>
<<set $ZenodoPuzzle = true>>
That's it! https://doi.org/10.5281/zenodo.7241587 is the answer!
https://zenodo.org/record/7241587 is the URL to the record page, and not a persistent identifier.
https://doi.org/10.5281/zenodo.7241531 is a persistent identifier that points to all versions together, like an umbrella. It's the concept DOI!
[[Let's continue|SharingCode]]<video autoplay muted loop id="myVideo">
<source @src='setup.ImagePath + "sleepingcat.mp4"' type="video/mp4">
</video>
<div class="content">\
Nice! You figured it out :D
And the cat slept for a bit, so hopefully it will be a bit nicer now.
Those six files you had were actually 2 scripts you worked on a while ago. But each of those 2 had 3 different versions. Did you notice that the url page that you found contains the latest version of those 2
<a href="https://github.com/meronvermaas/catcode/tree/main/scripts" target="_blank">scripts</a>?
All the versions are now nicely <a href="https://github.com/meronvermaas/catcode/commits/main/scripts" target="_blank">structured and stored</a> and you can easily inspect what happened and who was involved. This is a special type of repository, which helps you manage your versions. This would have been much more useful to do when you were writing the code!
BTW, speaking of other people being involved...
Now that you've created an overview of the changes to your software, you realise that you also corresponded about the code with [[Professor Hutseephluts|CollaborativePuzzle]].
</div><video autoplay muted loop id="myVideo">
<source @src='setup.ImagePath + "intensestaringcat.mp4"' type="video/mp4">
</video>
<<masteraudio stop>><<audio "end_bgm" volume 0.5 play loop>>
<div class="content">\
<img @src="setup.ImagePath+'deservedcoffee.jpg'" style="display: block; margin: auto; width:40%;float:right;padding:20px">
And then all at once: the windows burst open, you take a breath of fresh, crisp air. The cat cries and shrinks back to kitten size. Meowing, it clambers onto your lap. You stroke its head, relieved.
“I’ll take you to the cat café”, you tell it. “There you’ll get unlimited belly rubs for the rest of your life!”
You switch off the computer, stand up, and leave your office. Now let’s hope the reviewers will be nice.
And next time... Maybe you will make things easier for yourself and do some <a href="https://www.esciencecenter.nl/news/the-practical-guide-to-software-management-plans-now-available/" target="_blank">software management</a> from the start.
[[You did it!!|EndingInfos]] And it only took you $timetofinish minutes.
</div><span style="float:right;text-align:right;"> “Now that I made a significant contribution to the project, I suggest you add me to the list of authors.”</span>
Sure!
<span style="float:right;text-align:right;"> “I think alphabetical order would only be FAIR. I am a good fellow and changed the author list of your project already.”, purrs the cat. "People can cite us as <code>"Black Cat et al."</code></span><br>
Oh no! What has the black cat done to your code? Have a look at the latest version of your published code
Initially it was <a href="https://zenodo.org/record/7241532" target="_blank"> all fine</a>, but now <a href="https://zenodo.org/record/7241587" target="_blank">the cat seized ownership :S</a>
[[What can you do?|CitationPuzzleB]]There is a simple way to stay in control of ownership and to make sure you get the recognition you deserve. By adding a <a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-citation-files" target="_blank">CITATION file</a> (CFF) to your project, you can determine how others cite your code as the citation file format is plain text with human- and machine-readable citation information.
Can you create your own CITATION file to make sure the black cat does not take all the credit?
<<linkappend "Sort all elements">> (click on two to swap them)<</linkappend>> that should be included in a CITATION file to the green area, and leave all elements that are not part of a CFF file red.
<<set _correct to [false,false,false, false]>>
<<nobr>>
<div class="correctcitation">
<span style="padding-right:20px"><<swap>>title<</swap>></span><span style="padding-right:20px">|</span>
<span style="padding-right:20px"><<swap>>authors: Black Cat et al.<</swap>></span><span style="padding-right:20px">|</span>
<span style="padding-right:20px"><<swap>>version<</swap>></span><span style="padding-right:20px">|</span>
<span style="padding-right:20px"><<swap>>purrpatrator<</swap>></span><span style="padding-right:20px">|</span>
<span style="padding-right:20px"><<swap>>doi<</swap>></span><span style="padding-right:20px">|</span>
<span style="padding-right:20px"><<swap>>date-released<</swap>></span><span style="padding-right:20px">|</span>
<span style="padding-right:20px"><<swap>>copycats<</swap>></span><span style="padding-right:20px">|</span>
<span style="padding-right:20px"><<swap>>furmidable component<</swap>></span>
</div>
<</nobr>>
<<nobr>>
<div class="wrongcitation">
<span style="padding-right:20px"><<swap>>message <<onswap>>
<<if swapCurrent() is "purrpatrator" or swapCurrent() is "authors: Black Cat et al." or swapCurrent() is "copycats" or swapCurrent() is "furmidable component">>
<<set _correct[0] to true>>
<<else>>
<<set _correct[0] to false>>
<</if>>
<</swap>></span><span style="padding-right:20px">|</span>
<span style="padding-right:20px"><<swap>>cff-version<<onswap>>
<<if swapCurrent() is "purrpatrator" or swapCurrent() is "authors: Black Cat et al." or swapCurrent() is "copycats" or swapCurrent() is "furmidable component">>
<<set _correct[1] to true>>
<<else>>
<<set _correct[1] to false>>
<</if>>
<</swap>></span><span style="padding-right:20px">|</span>
<span style="padding-right:20px"><<swap>>authors: YOU<<onswap>>
<<if swapCurrent() is "purrpatrator" or swapCurrent() is "authors: Black Cat et al." or swapCurrent() is "copycats" or swapCurrent() is "furmidable component">>
<<set _correct[2] to true>>
<<else>>
<<set _correct[2] to false>>
<</if>>
<</swap>></span><span style="padding-right:20px">|</span>
<span style="padding-right:20px"><<swap>>url<<onswap>>
<<if swapCurrent() is "purrpatrator" or swapCurrent() is "authors: Black Cat et al." or swapCurrent() is "copycats" or swapCurrent() is "furmidable component">>
<<set _correct[3] to true>>
<<else>>
<<set _correct[3] to false>>
<</if>><</swap>></span>
</div>
<</nobr>>
<span id="solved">\
@@#not-right;@@
<<button "Try the combination...">>
<<if not _correct.includes(false)>>
<<replace '#solved'>>[[You got it!|SharingCode]]<</replace>>
<<else>>
<<replace '#not-right'>>No, that's not it...<br>You just lost one of your cat lives. <</replace>>
<<set $CurHP = $CurHP-1>><<run Health($CurHP, $MaxHP, "horizontalhealthbar", true)>>
<</if>>
<</button>>
</span>
<<set $CitationPuzzle = true>>