Ludochaordichttps://chezsoi.org/lucas/blog/2024-01-20T18:30:00+01:00Fantaisies programatico-ludiquesSOMBRE Résolutions - 6 Suggestions pour les Joueurs de Sombre2024-01-20T18:30:00+01:002024-01-20T18:30:00+01:00Lucas Cimontag:chezsoi.org,2024-01-20:/lucas/blog/sombre-resolutions-6-suggestions-pour-les-joueurs-de-sombre.html<p><img alt="Logo Sombre" src="images/jdr/Sombre_logo_jeu_de_role.png"></p>
<blockquote>
<p><strong>Sombre</strong> - la peur comme au cinéma - est un jeu de rôle d’horreur. Il met en scène des antihéros qui essaient
de survivre dans un monde particulièrement violent et hostile.
L’univers de jeu proposé est celui des films d’horreur.</p>
</blockquote>
<p>C'est une création de <a href="https://www.terresetranges.net/sombre.html">Johan Scipion</a> dont j'affectionne particulièrement …</p><p><img alt="Logo Sombre" src="images/jdr/Sombre_logo_jeu_de_role.png"></p>
<blockquote>
<p><strong>Sombre</strong> - la peur comme au cinéma - est un jeu de rôle d’horreur. Il met en scène des antihéros qui essaient
de survivre dans un monde particulièrement violent et hostile.
L’univers de jeu proposé est celui des films d’horreur.</p>
</blockquote>
<p>C'est une création de <a href="https://www.terresetranges.net/sombre.html">Johan Scipion</a> dont j'affectionne particulièrement la version <strong>Sombre Zéro</strong>,
car c'est un système idéal à plus d'un titre pour des parties <em>one-shot</em>.
J'ai déjà eu l'occasion d'évoquer ce <a href="/lucas/blog/tag/jdr.html"><abbr title="Jeu de Rôle">JdR</abbr></a> dans <a href="tag/sombre.html">plusieurs précédents articles</a>.</p>
<p>À travers les numéros de Sombre, Johan distille une quantité phénoménale de conseils à destination des meneurs :
sur comment dérouler chaque scénario étape par étape, sur la manière d'incarner les PNJs face aux joueurs,
des conseils d'impro, des conseils de conception de scénarios, etc.</p>
<p>Mais je n'ai pas trouvé beaucoup de conseils <strong>à destination des joueurs</strong>.</p>
<p>Et pourtant, il me semble que pour qu'une partie de Sombre soit réussie, tout ne repose pas sur le MJ.
La manière dont les joueurs s'impliquent à la table fait aussi une énorme différence.</p>
<p>Il existe déjà des recueils de conseils à destination des joueurs de jeu de rôle en général.
Je pense par exemple à <a href="http://www.lapinmarteau.com/jeux-et-accessoires-sortir-de-lauberge-02-jouer-des-parties-de-jeu-de-role/">Jouer des parties de jeu de rôle</a> de la collection "Sortir de l'Auberge" aux éditions Lapin Marteau,
ou à l'article <a href="https://ptgptb.fr/11-manieres-d-etre-un-meilleur-joueur-de-jdr">11 manières d'être un.e meilleur.e joueur.euse de jeu de rôle</a> de Grant Howitt, traduit sur PTGPTB.</p>
<p>Inspiré de ces lectures, j'ai tenté apporter ma modeste "pierre à l'édifice" (hanté)
en proposant quelques suggestions à destinations des joueurs de Sombre :</p>
<div class="side-by-side">
<a href="https://chezsoi.org/lucas/jdr/Sombre/SOMBRE-Resolutions-6-suggestions-pour-les-joueurs-1.jpg" target="_blank">
<img alt="SOMBRE Résolutions - 6 suggestions pour les joueurs - page 1" src="https://chezsoi.org/lucas/jdr/Sombre/SOMBRE-Resolutions-6-suggestions-pour-les-joueurs-1.jpg">
</a>
<a href="https://chezsoi.org/lucas/jdr/Sombre/SOMBRE-Resolutions-6-suggestions-pour-les-joueurs-2.jpg" target="_blank">
<img alt="SOMBRE Résolutions - 6 suggestions pour les joueurs - page 2" src="https://chezsoi.org/lucas/jdr/Sombre/SOMBRE-Resolutions-6-suggestions-pour-les-joueurs-2.jpg">
</a>
</div>
<ul>
<li>Version PDF en 2 pages : <a href="https://chezsoi.org/lucas/jdr/Sombre/SOMBRE-Resolutions-6-suggestions-pour-les-joueurs.pdf">SOMBRE-Resolutions-6-suggestions-pour-les-joueurs.pdf</a></li>
<li>Version PDF en 1 page, côte à côte : <a href="https://chezsoi.org/lucas/jdr/Sombre/SOMBRE-Resolutions-6-suggestions-pour-les-joueurs-cote-a-cote.pdf">SOMBRE-Resolutions-6-suggestions-pour-les-joueurs-cote-a-cote.pdf</a></li>
</ul>
<p>Merci aux contributeurs du forum <a href="https://www.terresetranges.net/forums/viewtopic.php?pid=21427#p21427">TerresEtranges.net</a> pour leur aide et leurs retours qui m'ont permis de grandement améliorer la version initiale de cette aide de jeu.</p>
<p>Je serais ravi de savoir ce que vous en pensez !
Vous pouvez laisser un message en commentaire ici, ou sur <a href="https://www.terresetranges.net/forums/viewtopic.php?pid=21427#p21427">TerresEtranges.net</a>.</p>
<!-- Com'
* [ ] Scénariothèque : PDF
* [ ] itch.io ?
-->
<style>
.side-by-side > * {
margin: 1rem auto;
max-width: 100%;
}
@media (min-width:768px) {
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
}
.side-by-side > * {
margin: 1rem;
max-width: 50%;
}
}
</style>CYBER//PUNK2023-12-22T23:55:00+01:002023-12-22T23:55:00+01:00Lucas Cimontag:chezsoi.org,2023-12-22:/lucas/blog/cyberpunk.html<p><img alt="CyberPunk" src="images/2023/12/CyberPunk.jpg"></p>
<p><center><i>
NOUS SOMMES EN 20X6.
<br>
Vous formez une équipe de Runners – des agents mercenaires dont l'expertise est à vendre, dans un crépuscule semi-légal entre les mégacorporations monolithiques, les gangs vicieux et les habitants opprimés de The Sprawl, un dédale urbain baigné de néons.
<br>
TON OBJECTIF : Termines la mission et restes en …</i></center></p><p><img alt="CyberPunk" src="images/2023/12/CyberPunk.jpg"></p>
<p><center><i>
NOUS SOMMES EN 20X6.
<br>
Vous formez une équipe de Runners – des agents mercenaires dont l'expertise est à vendre, dans un crépuscule semi-légal entre les mégacorporations monolithiques, les gangs vicieux et les habitants opprimés de The Sprawl, un dédale urbain baigné de néons.
<br>
TON OBJECTIF : Termines la mission et restes en vie.
</i></center></p>
<p><strong>CYBER//PUNK</strong> est un jeu de rôle inspiré des univers de Blade Runner, Neuromancer, Ghost in the Shell, et Shadowrun.
Incarnez un ex-flic hacker grisonnant, un sexbot dérangé devenu une arme vivante, et bien d'autres personnages. Infiltrez les megacorpos, damez le pion aux yakuza, et volez des IA top-secrètes.</p>
<p>Toutes les règles tiennent en une page.</p>
<p>Le jeu est téléchargable en PDF ici : <a href="https://lucas-c.itch.io/cyberpunk">lucas-c.itch.io/cyberpunk</a></p>
<p>Il s'agit d'une traduction d'un jeu de David Brunell-Brutman, lui-même adapté de <em>Lasers & Feelings</em> de John Harper : <a href="https://dbb-8.itch.io/cyberpunk">dbb-8.itch.io/cyberpunk</a></p>
<hr>
<p><strong>[EDIT 2023/12/26]</strong> : Voici quelques contenus gratuits qui se combinent bien avec ce jeu :</p>
<ul>
<li><a href="http://cyberpunk2021.free.fr/downloads.php?lng=fr">la carte de Night City réalisée par Eric B pour Cyberpunk 2020</a></li>
<li><a href="https://timbeek.itch.io/dystopian">Dystopian playlist @ itch.io</a> : <em>a free collection of Dark Retro Synth tracks composed and Produced by Tim Beek</em> - bon complément à la <em>playlist</em> Spotify suggérée</li>
<li><a href="https://p-d-gallagher.itch.io/augmented-reality">Augmented Reality @ itch.io</a> (en anglais) : <em>over 50 tables designed to assist gamesmasters who need to improvise futuristic city details</em></li>
<li><a href="https://inplay.itch.io/1">In Play Issue #1: Cyberpunk @ itch.io</a> (en anglais) : <em>resources for Cyberpunk RPGs, including: Art, ideas, and inspiration, Locations, Random tables, Scenarios</em></li>
<li><a href="https://emmv.itch.io/bpb">Beside Peripheral Bodies @ itch.io</a> (en anglais) : <em>a scenario for cyberpunk games. It includes missions, a facility to explore and infiltrate, NPCs and mercenaries that will hunt you down</em></li>
</ul>
<iframe width="640" height="480" title="Cyberpunk bike by daCruz" frameborder="0" allowfullscreen mozallowfullscreen="true" webkitallowfullscreen="true" allow="autoplay; fullscreen; xr-spatial-tracking" xr-spatial-tracking execution-while-out-of-viewport execution-while-not-rendered web-share src="https://sketchfab.com/models/efcf679a115c487face1adc2ce7e8b8c/embed"></iframe>
<p>Ce modèle 3D est basé sur <a href="https://www.artstation.com/artwork/8lWYdE">une illustration</a> de <strong>Fernando Correa</strong>, un artiste argentin qui a réalisé plein de magnifiques <em>artworks</em> <a href="https://creativecommons.org/licenses/by/4.0/">CC-BY</a> cyberpunk : <a href="https://www.artstation.com/artwork/G8PDbN">Valentino</a>, <a href="https://www.deviantart.com/fernand0fc/art/RONIN-812890318">RONIN</a>, <a href="https://www.deviantart.com/fernand0fc/art/Corporate-level-bodyguard-803579124">Corporate level bodyguard</a>, <a href="https://www.deviantart.com/fernand0fc/art/Biker-from-Streets-of-2043-722567116">Biker from "Streets of 2043"</a>, <a href="https://www.artstation.com/artwork/WK5YKQ">Future Sick IV</a>, <a href="https://www.artstation.com/artwork/qQ4wez">The Den</a>, <a href="https://www.artstation.com/artwork/5XNlWJ">City of endless night</a>, <a href="https://www.deviantart.com/fernand0fc/art/Centrate-en-la-recompensa-818245957">Centrate en la recompensa</a>.</p>
<hr>
<p><strong>[EDIT 2023/12/28]</strong> : J'ai eu l'occasion de faire une partie test de <code>CYBER//PUNK</code>, en m'inspirant pas mal du scénario <em>Beside Peripheral Bodies</em>, et c'était très amusant !</p>
<p>Les <code>MERCS4US</code> étaient composés de ces 4 <em>Runners</em> :</p>
<ul>
<li><strong>Krashlink</strong> (4), ancien employé corpo dérangé devenu hacker</li>
<li><strong>Bergonax</strong> (3), ancienne membre de gang dérangée devenue pilote</li>
<li><strong>Ahn Zipper</strong> (5), ancien soldat grisonnant devenu Gun-Fu Master</li>
<li><strong>Doc Psy Duck</strong> (3), ancien soldat grisonnant devenu médecin des rues</li>
</ul>
<figure role="group">
<img alt="" src="https://cdna.artstation.com/p/assets/images/images/052/207/990/large/baptiste-cottel-asset.jpg?1659219306">
<figcaption>Le véhicule des PJs - Illustration de <a href="https://www.artstation.com/artwork/YKv2Pd">Baptiste Cottel</a></figcaption>
</figure>
<!-- Com'
* [x] https://lucas-c.itch.io/cyberpunk
* [x] https://dbb-8.itch.io/cyberpunk (comment)
* [x] http://troplongpaslu.fr (à date pas encore validé)
* [x] post sur le fil Ludochaordic du Discord CestPadDuJdr
* [x] https://www.casusno.fr/viewtopic.php?p=2200100#p2200100
* [ ] https://www.reddit.com/r/jdr
* [ ] https://www.reddit.com/r/onepagerpgs/
-->
<style>
body { background: black; color: white; }
article img { max-height: 20rem; }
article iframe { display: block; margin: 1rem auto; }
article hr { margin: 5rem 10rem; }
</style>Sombre : Lab Escape2023-12-18T16:00:00+01:002023-12-18T16:00:00+01:00Lucas Cimontag:chezsoi.org,2023-12-18:/lucas/blog/sombre-lab-escape.html<blockquote>
<p><strong>Sombre</strong> - la peur comme au cinéma - est un jeu de rôle d’horreur. Il met en scène des antihéros qui essaient
de survivre dans un monde particulièrement violent et hostile.
L’univers de jeu proposé est celui des films d’horreur.</p>
</blockquote>
<p>C'est une création de <a href="https://www.terresetranges.net/sombre.html">Johan Scipion</a> dont j'affectionne particulièrement …</p><blockquote>
<p><strong>Sombre</strong> - la peur comme au cinéma - est un jeu de rôle d’horreur. Il met en scène des antihéros qui essaient
de survivre dans un monde particulièrement violent et hostile.
L’univers de jeu proposé est celui des films d’horreur.</p>
</blockquote>
<p>C'est une création de <a href="https://www.terresetranges.net/sombre.html">Johan Scipion</a> dont j'affectionne particulièrement la version <strong>Sombre Zéro</strong>,
car c'est un système idéal à plus d'un titre pour des parties <em>one-shot</em>.
J'ai déjà eu l'occasion d'évoquer ce <a href="/lucas/blog/tag/jdr.html"><abbr title="Jeu de Rôle">JdR</abbr></a> dans <a href="tag/sombre.html">plusieurs précédents articles</a>.</p>
<p>Depuis quatre ans et ma première session de Sombre Zéro, j'ai eu l'occasion de faire jouer des dizaines de parties, et des scénarios très variés.
Et puis ça y est, je me suis lancé, <strong>j'ai créé mon propre scénario</strong> !</p>
<p>Pour 3 à 5 joueurs, d'une durée d'environ 45min, <strong>Lab Escape</strong> vous plonge dans un laboratoire envahi de zombies, avec une part d'exploration, les lieux étant progressivement révélés aux joueurs.</p>
<p>Vous pouvez le retrouver ici <a href="https://lucas-c.itch.io/sombre-lab-escape">lucas-c.itch.io/sombre-lab-escape</a> - Le PDF est à prix libre, fait 8 Mo et contient 21 pages.</p>
<p>Les parties de test ont été très concluantes, et je suis plutôt fier de certains idées présentes dans le scénario : les tuiles des lieux révélées progressivement aux joueurs ; la tension entre les personnages de part leur statut de maton / taulards ; des armes majoritairement à munitions limitées ; les PJs morts qui deviennent des zombies...
L'ensemble fonctionne vraiment bien !</p>
<p>Ce scénario nécessite d'imprimer 8 pages couleur, sur papier standard, et un peu de découpage / pliage préalable.</p>
<p>Une playlist d'ambiance est proposée : <a href="https://www.youtube.com/playlist?list=PLLgE-ga3W_kYmA6EQH6fzWmBQmNp39kTF">Sombre JdR - Zombies playlist @ YouTube</a></p>
<p>Voilà !
Si jamais vous vous aventurez à tester ce scénario, je serais ravi d'avoir vos retours 🙂🧟</p>
<div class="side-by-side">
<img alt="" src="images/jdr/Sombre-LabEscape.jpg">
<img alt="" src="images/2023/12/Photo02.jpg">
</div>
<div class="side-by-side">
<img alt="" src="images/2023/12/Photo03.jpg">
<img alt="" src="images/2023/12/FullPlan-small.jpg">
</div>
<div class="side-by-side">
<img alt="" src="images/2023/12/Sombre-LabEscape-page13.jpg">
<img alt="" src="images/2023/12/Sombre-LabEscape-page14.jpg">
</div>
<div class="side-by-side">
<img alt="" src="images/2023/12/Sombre-LabEscape-page15.jpg">
<img alt="" src="images/2023/12/Sombre-LabEscape-page19.jpg">
</div>
<!-- Com'
* [x] https://lucas-c.itch.io/sombre-lab-escape
* [x] https://www.scenariotheque.org/Document/info_doc.php?id_doc=10996
-> référence : https://lucas-c.github.io/jdr/Sombre/scenario/Sombre-LabEscape.pdf
* [x] https://www.terresetranges.net/forums/viewtopic.php?pid=21404#p21404
* [x] https://www.casusno.fr/viewtopic.php?p=2199301#p2199301
* [x] https://opale-roliste.com/forum/ressources/vos-creations/sombre?page=8#comment-10971
* [x] post sur le fil Ludochaordic du Discord CestPadDuJdr
* [ ] https://www.reddit.com/r/jdr
* [ ] Discord PTGPTB ?
-->
<style>
@font-face {
font-family: Freedom45;
src: url('https://lucas-c.github.io/jdr/Sombre/scenario/fonts/Freedom45.otf');
}
article h1 {
font-family: Freedom45;
}
.side-by-side > * {
margin: 1rem auto;
max-width: 100%;
}
@media (min-width:768px) {
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
}
.side-by-side > * {
margin: 1rem;
max-width: 50%;
}
}
</style>Sombre : Terminatrice2023-11-14T03:50:00+01:002023-11-14T03:50:00+01:00Lucas Cimontag:chezsoi.org,2023-11-14:/lucas/blog/sombre-terminatrice.html<!-- Com'
* [x] email à DeathAmbre
-->
<p><img alt="Logo Sombre" src="images/jdr/Sombre_logo_jeu_de_role.png"></p>
<blockquote>
<p><strong>Sombre</strong> est un jeu de rôle d’horreur. Il met en scène des antihéros qui essaient
de survivre dans un monde particulièrement violent et hostile.
L’univers de jeu proposé est celui des films d’horreur.</p>
</blockquote>
<p>C'est une création de <a href="https://www.terresetranges.net/sombre.html">Johan Scipion</a> dont j'affectionne particulièrement la version <strong>Sombre Zéro</strong>,
car …</p><!-- Com'
* [x] email à DeathAmbre
-->
<p><img alt="Logo Sombre" src="images/jdr/Sombre_logo_jeu_de_role.png"></p>
<blockquote>
<p><strong>Sombre</strong> est un jeu de rôle d’horreur. Il met en scène des antihéros qui essaient
de survivre dans un monde particulièrement violent et hostile.
L’univers de jeu proposé est celui des films d’horreur.</p>
</blockquote>
<p>C'est une création de <a href="https://www.terresetranges.net/sombre.html">Johan Scipion</a> dont j'affectionne particulièrement la version <strong>Sombre Zéro</strong>,
car c'est un système idéal à plus d'un titre pour des parties <em>one-shot</em> :</p>
<ul>
<li>c'est un jeu <strong>minimaliste</strong>, nécessitant très peu de matériel (tout tient souvent sur une feuille A4)</li>
<li>le système est <strong>ultra-simple</strong> sans être simpliste. Il est donc rapide à expliquer,
et facile à prendre en main pour les joueurs, même débutants</li>
<li>les parties sont <strong>courtes</strong> (en général moins d'une heure), permettant de les multiplier
et de varier les ambiances</li>
<li>il y a vite un peu de <strong>tension</strong> autour de la table, car le risque de mortalité est élevé,
et le <abbr title="Player versus Player">PvP</abbr> est une option fréquente</li>
</ul>
<p>J'ai déjà eu l'occasion d'évoquer ce <a href="/lucas/blog/tag/jdr.html"><abbr title="Jeu de Rôle">JdR</abbr></a> dans <a href="tag/sombre.html">quelques précédents articles</a>.
Certains scénarios sont de petits bijoux, comme <a href="./espace-profond-et-sanglant.html"><em>Deep Space Gore</em> (Sombre n°3)</a>,
que j'ai fait jouer une dizaine de fois, ou encore <a href="./vous-reprendrez-bien-un-peu-de-sombre.html">La Maison de la Sorcière</a>.</p>
<p>Dans ce dernier article justement, j'avais mentionné la sortie d'un recueil de scénarios
100% <em>Sombre Zéro</em> par Julien <em>DeathAmbre</em> De Monte @<a href="http://arkhive.free.fr">DarkFarm</a>, <strong>Terminatrice</strong> :</p>
<p><a href="http://arkhive.free.fr/Terminatrice.pdf" target="_blank">
<br>
<figure>
<img alt="Couverture du recueil Terminatrice" src="images/2023/11/Terminatrice.png">
<figcaption>Terminatrice (PDF 14Mo, 143 pages)</figcaption>
</figure></p>
<p></a></p>
<blockquote>
<p>La terminatrice marque la frontière entre ombre et lumière.</p>
</blockquote>
<p>Avec pas moins de <strong>17 scénarios</strong>, la proposition est alléchante !</p>
<p>À la lecture, <strong>cinq</strong> d'entre eux ont vraiment retenu mon attention.
Je n'en ai pour l'instant testé que deux, mais j'initie cet article
pour les présenter tous les cinq, et je compléterai cette page
à chaque fois que je testerai un nouveau scénario.</p>
<h2>Behind the doors</h2>
<p><img alt="" src="images/2023/11/DyingLight2-scanlines.jpg"></p>
<p><strong>4-5 joueurs, 15min</strong></p>
<p><strong>Pitch</strong> : <a href="https://fr.wikipedia.org/wiki/Impasse_mexicaine">impasse mexicaine</a>
dans l'ascenseur qui mène les survivants d'une apocalypse zombie à leur salut inespéré.</p>
<p>J'ai trouvé le concept génial : l'instant final avant que des survivants soient sauvés en hélico,
quelques minutes en huis clos, où ils ont une dernière occasion de régler leurs comptes,
alors que chacun d'eux a une rancœur contre un de ses camarades...</p>
<p>J'ai conçu des <strong>personnages alternatifs</strong> pour ce scénario :
<a href="https://github.com/Lucas-C/jdr/tree/master/Sombre/Terminatrice">github.com/Lucas-C/jdr/Sombre/Terminatrice</a>.
Ces <abbr title="Personnages des Joueurs">PJs</abbr>
ont des <em>backgrounds</em> / secrets différents du scénario original :
ils sont un peu plus détaillés, et les enjeux s'éloignent d'une histoire de jalousie / tromperie,
pour des questions de vengeance / reconnaissance / opportunité à saisir.</p>
<p><strong>Bilan de la partie</strong> : la sauce n'a pas complètement « pris », notamment car le scénario s'est joué à 4 joueurs, sans Mickey.
Je pense que les joueurs manquaient un peu de matière, sur leur propre personnage et sur ceux des autres,
pour être capables de « se lancer » en impro, et tenter de convaincre les autres qu'il faut liquider untel.</p>
<p>Au final, si je refais jouer ce scénario, je retravaillerai peut-être encore un peu les infos données aux joueurs
afin d'accentuer que chaque personnage a vraiment individuellement intérêt à en éliminer un autre.</p>
<h2>Mother Sarah</h2>
<p><a href="https://www.deviantart.com/wingedmidnight88/art/Human-Test-Tube-395272703" target="_blank">
<br>
<figure>
<img alt="Human Test Tube" src="images/2023/11/Human-Test-Tube-by-wingedmidnight88.webp">
<figcaption>Human-Test-Tube par wingedmidnight88 (DeviantArt)</figcaption>
</figure></p>
<p></a></p>
<p><strong>3-5 joueurs, 30min</strong></p>
<p><strong>Pitch</strong> : une station orbitale russe secrète et vétuste percute l'ISS,
forçant les spationautes ayant survécu à l'impact à l'explorer...</p>
<p><strong>Bilan de la partie</strong> : à nouveau, le pitch m'avait énormément plus,
tout comme le plan de la station sous forme de tuiles, avec un petit mécanisme de sas rotatif,
et le décompte d'oxygène avec 3d6.</p>
<p>En définitive malheureusement, les ressorts narratifs se sont révélés un peu limités :
les joueurs n'ont même pas pris le temps d'inspecter la fascinante cuve cylindrique et son occupante !
La pression des tours de jeu limités les a forcé à aller à l'essentiel (le module d'évacuation),
sans s'attarder à explorer et comprendre ce qu'il se passait dans cette étrange station orbitale russe.</p>
<p>Au final, un des joueurs a joué de malchance aux dés,
et son PJ a fini par s'écraser avec son <em>jetpack</em>.
Les deux autres s'en sont sortis, en s'éjectant avec le module d'évacuation.</p>
<p>Peut-être qu'il serait intéressant d'étoffer un peu ce scénario :</p>
<ul>
<li>suppression du module d'évacuation, mais ajout de quelques autres modules</li>
<li>la solution pour survivre au manque d'oxygène pourrait être plutôt de s'injecter un produit,
découvert sur place, que les « créatures » emploient pour survivre ?</li>
<li>l'objectif final des PJs pourrait ne pas être de sauver leur peau,
mais d'empêcher que quelque chose d'<strong>horrible</strong> advienne...
Le déclenchement d'une <a href="https://fr.wikipedia.org/wiki/Frappe_orbitale_cin%C3%A9tique">frappe orbitale cinétique</a> ?
La chute de la station sur Terre, avec son réacteur nucléaire ?
Les <em>Dark Passengers</em> ont récupéré une autre spationaute de l'ISS et veulent en faire leur nouvelle mère pondeuse ?</li>
</ul>
<h2>Out of reach</h2>
<p><a href="https://fr.wikipedia.org/wiki/Pandorum" target="_blank">
<br>
<figure>
<img alt="Pandorum" src="images/2023/11/pandorum.jpg">
<figcaption>Pandorum (film, 2009)</figcaption>
</figure></p>
<p></a></p>
<p><strong>4-5 joueurs, ~1h a priori</strong></p>
<p><strong>Pitch</strong> : dans le vaisseau Manta, les <abbr title="Personnages des Joueurs">PJs</abbr>
sont des détenus réveillés de leurs stases
car une alerte s'est déclenchée dans leur vaisseau pénitentiaire...
Mais certains d'entre eux cachent un sanglant secret !</p>
<p>Ce scénario constitue une « suite » à <a href="./espace-profond-et-sanglant.html"><em>Deep Space Gore</em> (Sombre n°3)</a>.</p>
<p>Comme les tuiles des personnages ne sont pas incluses dans <a href="http://arkhive.free.fr/Terminatrice.pdf">Terminatrice.pdf</a>,
je les ai mises en page sur une feuille A4 ici :
<a href="https://github.com/Lucas-C/jdr/blob/master/Sombre/Terminatrice/">github.com/Lucas-C/jdr/Sombre/Terminatrice</a></p>
<h2>Kakurenbo</h2>
<p><a href="https://aminoapps.com/c/geek-geek/page/blog/urban-legend-daruma-san-the-bath-game/q7nz_2ehRuD0PweqWPNGJ5kZXPraZwDDg"><img alt="Urban legend bath game" src="images/2023/11/urban-legend-bath-game.jpg"></a></p>
<p><strong>3-5 joueurs, ~1h a priori</strong></p>
<p><strong>Pitch</strong> : conte horrifique japonais en huis clos.
Une partie de <em>Futari Kakurenbo</em> entre enfants va virer en jeu macabre, car un esprit maléfique a été invoqué...</p>
<h2>Brako</h2>
<p><img alt="" src="images/2023/11/TheLeagueOfGentlemen-1960.jpg"></p>
<p><strong>3-5 joueurs, < 2h a priori</strong></p>
<p><strong>Pitch</strong> : des truands sont recrutés au dernier moment pour braquer une banque, dans l'instant !
Mais des retournements de situation auront lieu durant l'opération...</p>
<h2><em>Teasing</em></h2>
<p>Un grand merci à Julien <em>DeathAmbre</em> De Monte pour ces scénarios !</p>
<p>La lecture de <em>Terminatrice</em> m'a également motivé à créer mon propre scénario pour <strong>Sombre Zéro</strong> 🙂
Je l'ai testé le week-end dernier, et en voici le <em>pitch</em> :
les PJs sont un groupe de détenus et le garde qui les escorte, dans un laboratoire envahi de zombies.
Le scénario comporte une part d'exploration, les lieux étant progressivement révélés aux joueurs.</p>
<p>Je compte le faire jouer une seconde fois puis peaufiner la mise en page,
et je le partagerai très bientôt ici, sur <a href="https://www.terresetranges.net/forums/">le forum terresetranges.net</a>
et sur <a href="https://lucas-c.itch.io/">itch.io</a> !</p>
<p><strong>EDIT</strong> : Ça y est, le scénario est sorti ! → <a href="sombre-lab-escape.html">Lab Escape</a></p>Programmation de novembre au Kawasso2023-10-30T10:00:00+01:002023-10-30T10:00:00+01:00Lucas Cimontag:chezsoi.org,2023-10-30:/lucas/blog/programmation-de-novembre-au-kawasso.html<p>Voici la programmation du mois de novembre au <a href="tag/kawasso.html">Kawasso</a>,
le café associatif de Champtocé-sur-Loire :</p>
<p><img alt="Programme de novembre 2023 au Kawasso" src="images/2023/10/Kawasso-Programmation-novembre-2023.jpg"></p>
<p>Parmi les événements qui y auront lieu, voici quelques détails sur certains :</p>
<div class="side-by-side">
<img alt="Soirée jeux" src="images/2023/10/2023-11-02-Soiree-jeux.jpg">
<img alt="Réunion d'orga des Champtofolies" src="images/2023/10/2023-11-06-Orga-Champtofolies.jpg">
</div>
<p><br><br></p>
<div class="side-by-side">
<img alt="Soirée jeux" src="images/2023/10/2023-11-16-Soiree-jeux.jpg">
<img alt="Concert Gazon Bleu" src="images/2023/10/2023-11-24-GazonBleu-Affiche-v3.png">
</div>
<style>
@media (min-width:768px) {
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
}
.side-by-side > * { margin: 0 2rem; }
}
</style>
<script>
// Make all article images clickables / openable in a new tab …</script><p>Voici la programmation du mois de novembre au <a href="tag/kawasso.html">Kawasso</a>,
le café associatif de Champtocé-sur-Loire :</p>
<p><img alt="Programme de novembre 2023 au Kawasso" src="images/2023/10/Kawasso-Programmation-novembre-2023.jpg"></p>
<p>Parmi les événements qui y auront lieu, voici quelques détails sur certains :</p>
<div class="side-by-side">
<img alt="Soirée jeux" src="images/2023/10/2023-11-02-Soiree-jeux.jpg">
<img alt="Réunion d'orga des Champtofolies" src="images/2023/10/2023-11-06-Orga-Champtofolies.jpg">
</div>
<p><br><br></p>
<div class="side-by-side">
<img alt="Soirée jeux" src="images/2023/10/2023-11-16-Soiree-jeux.jpg">
<img alt="Concert Gazon Bleu" src="images/2023/10/2023-11-24-GazonBleu-Affiche-v3.png">
</div>
<style>
@media (min-width:768px) {
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
}
.side-by-side > * { margin: 0 2rem; }
}
</style>
<script>
// Make all article images clickables / openable in a new tab:
document.querySelectorAll("article img").forEach((img) => {
if (img.parentElement.tagName !== "A") {
var a = document.createElement('a');
a.href = img.src;
a.target = "_blank";
img.parentElement.insertBefore(a, img);
a.appendChild(img); // reparent <img>
}
});
</script>Programmation d'octobre au Kawasso2023-10-11T20:30:00+02:002023-10-11T20:30:00+02:00Lucas Cimontag:chezsoi.org,2023-10-11:/lucas/blog/programmation-doctobre-au-kawasso.html<p>Voici la programmation du mois d'octobre au <a href="tag/kawasso.html">Kawasso</a>,
le café associatif de Champtocé-sur-Loire :</p>
<p><img alt="Programme d'octobre 2023 au Kawasso" src="images/2023/10/Kawasso-Programmation-octobre-2023.jpg"></p>
<p>Parmi les événements qui y auront lieu, voici quelques détails sur certains :</p>
<div class="side-by-side">
<img alt="Rencontre sur le futur du Kawasso" src="images/2023/10/2023-10-12-Rencontre-FuturDuKawasso.jpg">
<img alt="Atelier gravure" src="images/2023/10/2023-10-21-AtelierGravure.jpg">
</div>
<p><br><br></p>
<div class="side-by-side">
<img alt="Spectacle Huumm..." src="images/2023/10/2023-10-21-SpectacleHuummCieZustopique-Affiche-v2.png">
<img alt="Animation Renard'Eau tressage de jonc" src="images/2023/10/2023-10-26-AnimationRenardEau.jpg">
</div>
<p><br><br></p>
<p><img alt="Café polyglotte" src="images/2023/10/2023-10-27-CaféPolyglotte.jpg">
<br><br></p>
<p>À noter que la semaine du 21 29 octobre sera la seconde <strong>semaine des cafés-bars associatifs</strong>
en Pays de la Loire, dont voici le programme …</p><p>Voici la programmation du mois d'octobre au <a href="tag/kawasso.html">Kawasso</a>,
le café associatif de Champtocé-sur-Loire :</p>
<p><img alt="Programme d'octobre 2023 au Kawasso" src="images/2023/10/Kawasso-Programmation-octobre-2023.jpg"></p>
<p>Parmi les événements qui y auront lieu, voici quelques détails sur certains :</p>
<div class="side-by-side">
<img alt="Rencontre sur le futur du Kawasso" src="images/2023/10/2023-10-12-Rencontre-FuturDuKawasso.jpg">
<img alt="Atelier gravure" src="images/2023/10/2023-10-21-AtelierGravure.jpg">
</div>
<p><br><br></p>
<div class="side-by-side">
<img alt="Spectacle Huumm..." src="images/2023/10/2023-10-21-SpectacleHuummCieZustopique-Affiche-v2.png">
<img alt="Animation Renard'Eau tressage de jonc" src="images/2023/10/2023-10-26-AnimationRenardEau.jpg">
</div>
<p><br><br></p>
<p><img alt="Café polyglotte" src="images/2023/10/2023-10-27-CaféPolyglotte.jpg">
<br><br></p>
<p>À noter que la semaine du 21 29 octobre sera la seconde <strong>semaine des cafés-bars associatifs</strong>
en Pays de la Loire, dont voici le programme :</p>
<p><a href="images/2023/10/2023-PortesOuvertesCafesEtBarsAssociatifs.pdf">
<br>
<figure>
<img alt="PDF preview" src="images/2023/10/2023-PortesOuvertesCafesEtBarsAssociatifs.png">
<figcaption>2023-PortesOuvertesCafesEtBarsAssociatifs.pdf</figcaption>
</figure></p>
<p></a>
<br><br></p>
<style>
@media (min-width:768px) {
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
}
.side-by-side > * { margin: 0 2rem; }
}
</style>
<script>
// Make all article images clickables / openable in a new tab:
document.querySelectorAll("article img").forEach((img) => {
if (img.parentElement.tagName !== "A") {
var a = document.createElement('a');
a.href = img.src;
a.target = "_blank";
img.parentElement.insertBefore(a, img);
a.appendChild(img); // reparent <img>
}
});
</script>Alice is missing2023-08-11T12:30:00+02:002023-08-11T12:30:00+02:00Lucas Cimontag:chezsoi.org,2023-08-11:/lucas/blog/alice-is-missing.html<p><img alt="Une des illustrations du jeu" src="images/2023/08/AIM_art_02.png"></p>
<p>La semaine dernière, j'ai eu l'occasion de tester l'excellent jeu <em>Alice is missing</em> de Spenser Starke.
Et je pense pouvoir parler au nom des trois autres joueurs en disant que nous avons tous trouvé cette expérience ludique vraiment fantastique !</p>
<p>De nombreux autres blogueurs ont déjà parlé de ce jeu avant …</p><p><img alt="Une des illustrations du jeu" src="images/2023/08/AIM_art_02.png"></p>
<p>La semaine dernière, j'ai eu l'occasion de tester l'excellent jeu <em>Alice is missing</em> de Spenser Starke.
Et je pense pouvoir parler au nom des trois autres joueurs en disant que nous avons tous trouvé cette expérience ludique vraiment fantastique !</p>
<p>De nombreux autres blogueurs ont déjà parlé de ce jeu avant moi,
je ne vais donc pas en réaliser une description détaillée ici.
Je vous recommande plutôt de lire l'un de ces articles :</p>
<ul>
<li><a href="https://www.gulix.fr/blog/2020/11/04/jouer-en-silence/">@ Gulix</a></li>
<li><a href="https://hu-mu.blogspot.com/2021/03/alice-is-missing.html">@ Hugin & Mugin</a></li>
</ul>
<p>Je voulais simplement partager ici le plaisir que j'ai eu de jouer à ce splendide jeu.
Je suis très amateur de jeux de rôles, et surtout de jeux aux mécaniques originales, et là j'ai été amplement servi !</p>
<p>Mais au-delà de l'originalité ludique indéniable,
un soin particulier a été donné au matériel de jeu, pour vous plonger dans l'ambiance :
illustrations, textes, cartes, et surtout une magnifique bande son de 90min.</p>
<p><img alt="Le dos des cartes du jeu" src="images/2023/08/alice-is-missing-rpg-picture-cards.webp"></p>
<p>Au terme de l'histoire et de la session de jeu,
nous avions tous les quatre été très émus par l'expérience.
Même si nous avions quelques réticences à rester silencieux dans la même pièce pendant 90min,
en échangeant uniquement via messageries électroniques,
force est de constater que l'immersion est impressionnante,
et que nous n'avons vraiment pas vu passer le temps.</p>
<p>Mention spéciale à la mécanique d'enregistrement de messages audio en début de partie,
qui sont ensuite partagés à tous les joueurs et joueuses lors de l'épilogue.
C'était magistral pour conclure l'histoire !</p>
<p>Une extension est en préparation pour le jeu, nommée <em>Silent Falls</em>,
mais je m'interroge surtout sur la possibilité de créer des <em>hacks</em> du jeu...
En termes de matériel, il ne me semble pas trop difficile de préparer les quelques cartes nécessaires,
et d'assembler une bande son correspondant à l'ambiance souhaitée.
Par contre, quelles <strong>histoires</strong> pourraient se prêter à ce type de <em>gameplay</em>
basée sur la communication par messagerie ?
Voici quelques idées :</p>
<ul>
<li>
<p>un scénario à la <strong>Alien</strong>, où les joueurs incarnent des astronautes perdus dans l'espace suite à un accident,
et qui se retrouvent à explorer des lieux abandonnés / mystérieux / dangereux chacun de son côté ?</p>
</li>
<li>
<p>une exploration de <strong>manoir hanté</strong>, façon <em>Blair Witch</em> ?
Peut-être par un seul des Personnages-Joueurs, avec les autres qui échangent avec lui à distance ?</p>
</li>
<li>
<p>un <strong>bateau perdu dans la tempête</strong>, avec lequel les communications sont très limitées ?</p>
</li>
</ul>
<p>Je n'ai pas trouvé de <em>hacks</em> existants du jeu, mais si vous en connaissez,
indiquez-le moi en commentaire svp ! 🙏</p>
<p><img alt="Une des illustrations du jeu" src="images/2023/08/AIM_art_01.png"></p>
<p><em>Alice is Missing</em> a gagné la <a href="https://www.dicebreaker.com/topics/ennie-awards/news/ennie-awards-2021-winners-rpg-heart-alice-is-missing">médaille d'or des ENnie en 2021</a> pour "Meilleur jeu" "Meilleures règles" et "Produit de l'année." Il a également gagné le <a href="https://anywhere.indiecade.com/2021-award-winners/">prix IndieCade 2021</a> du meilleur <em>design</em> de jeu de société.
En France, le jeu a été sélectionné pour l'As d'Or 2023 dans la catégorie « Initié ».</p>
<hr>
<p><strong>Note</strong>: Le site web de la la VF du jeu vient de basculer de <a href="https://www.renegade-france.fr/alice-is-missing/">https://www.renegade-france.fr/alice-is-missing/</a> vers <a href="https://www.origames.fr/produit/alice-is-missing/">https://www.origames.fr/produit/alice-is-missing/</a>, et le nouveau site chez OriGames est bien moins joli / réussi 😟</p>
<p>La bande son / compte à rebours du jeu est désormais difficile à trouver sur le site web.
Vous pouvez la retrouver ici sur YouTube : <a href="https://www.youtube.com/watch?v=ysOOFIOAy7A">Alice Is Missing - Animated Timer</a>.</p>
<p>Le site anglophone contient quelques ressources supplémentaires,
notamment un <em>template</em> pour jouer au jeu via Discord:
<a href="https://www.huntersentertainment.com/alice-is-missing">https://www.huntersentertainment.com/alice-is-missing</a></p>Jeux de rôle cet été à Champtocé-sur-Loire2023-06-03T11:30:00+02:002023-06-03T11:30:00+02:00Lucas Cimontag:chezsoi.org,2023-06-03:/lucas/blog/jeux-de-role-cet-ete-a-champtoce-sur-loire.html<p><img alt="" src="images/2023/06/Poster-JeuxDeRoleAuKawasso.png"></p>
<p>Cet été j'animerai des parties de jeu de rôle sur trois journées au <strong>Kawasso</strong>,
le café associatif de Champtocé-sur-Loire.</p>
<p>Plus d'informations ici : <a href="https://chezsoi.org/lucas/jdr/teasers.html">Jeux de rôle au Kawasso</a>.</p>Pylint strict base configuration2023-05-03T13:15:00+02:002023-05-03T13:15:00+02:00Lucas Cimontag:chezsoi.org,2023-05-03:/lucas/blog/pylint-strict-base-configuration.html<p><img alt="Pylint logo" src="images/2023/05/pylint-strict.webp"></p>
<p><a href="https://pylint.pycqa.org">Pylint</a> is a great static code analyser for Python code.
I have been using it for several years, in various projects, and it's simple to use yet very powerful.</p>
<p>I even contributed to Pylint by submitting a new rule a few years ago : <a href="https://github.com/pylint-dev/pylint/pull/1655">implicit-str-concat</a>.</p>
<p>For an introduction to Pylint, you …</p><p><img alt="Pylint logo" src="images/2023/05/pylint-strict.webp"></p>
<p><a href="https://pylint.pycqa.org">Pylint</a> is a great static code analyser for Python code.
I have been using it for several years, in various projects, and it's simple to use yet very powerful.</p>
<p>I even contributed to Pylint by submitting a new rule a few years ago : <a href="https://github.com/pylint-dev/pylint/pull/1655">implicit-str-concat</a>.</p>
<p>For an introduction to Pylint, you can check those tutorials:</p>
<ul>
<li>official documentation: <a href="https://pylint.readthedocs.io/en/latest/tutorial.html">Tutorial</a> & <a href="https://pylint.readthedocs.io/en/latest/user_guide/usage/run.html">Usage</a></li>
<li><a href="https://medium.com/@oliyadkebede32/how-to-get-started-with-pylint-79bf950f61a8">How To Get Started With Pylint by Oliyadk @medium.com</a></li>
</ul>
<p>Pylint default configuration is very reasonable, but one thing that I find missing is a simple way to switch to a "strict" mode, that would enable all optional rules. ESLint has <a href="https://www.npmjs.com/package/eslint-config-strict-mode">eslint-config-strict-mode</a> for example.</p>
<p>Such "strict" mode can be useful:</p>
<ul>
<li>
<p>when bootstrapping a new project, you may want to enable all rules by default,
and progressively disable the ones you find non necessary for your project</p>
</li>
<li>
<p>on an existing project using Pylint, when you want to test what extra checks
can be performed by this tool, and see if it can spot bugs you missed so far</p>
</li>
</ul>
<p>The following <code>.pylintrc</code> configuration file is my attempt to setup a "strict" mode:</p>
<div class="highlight"><pre><span></span><code><span class="k">[MESSAGES CONTROL]</span>
<span class="c1"># Enable some checkers that are not activated by default:</span>
<span class="n">enable</span> <span class="o">=</span> <span class="n">bad-inline-option</span><span class="p">,</span> <span class="n">deprecated-pragma</span><span class="p">,</span> <span class="n">file-ignored</span><span class="p">,</span> <span class="n">use-symbolic-message-instead</span><span class="p">,</span> <span class="n">useless-suppression</span>
<span class="c1"># Include some helpful details on errors messages for naming rules:</span>
<span class="n">include-naming-hint</span> <span class="o">=</span> <span class="n">yes</span>
<span class="k">[MASTER]</span>
<span class="c1"># Informational messages ("I") should make Pylint execution fail (non-0 program return code):</span>
<span class="n">fail-on</span> <span class="o">=</span> <span class="n">I</span>
<span class="c1"># Enable many optional extensions:</span>
<span class="n">load-plugins</span> <span class="o">=</span> <span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">bad_builtin</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">code_style</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">comparison_placement</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">consider_refactoring_into_while_condition</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">docparams</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">dunder</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">eq_without_hash</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">for_any_all</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">magic_value</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">mccabe</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">no_self_use</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">overlapping_exceptions</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">private_import</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">redefined_loop_name</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">redefined_variable_type</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">set_membership</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">typing</span><span class="p">,</span>
<span class="n">pylint</span><span class="p">.</span><span class="n">extensions</span><span class="p">.</span><span class="n">while_used</span><span class="p">,</span>
<span class="k">[STRING_CONSTANT]</span>
<span class="c1"># Doc: https://pylint.pycqa.org/en/latest/user_guide/messages/warning/implicit-str-concat.html</span>
<span class="n">check-quote-consistency</span> <span class="o">=</span> <span class="n">yes</span>
<span class="k">[VARIABLES]</span>
<span class="n">allow-global-unused-variables</span> <span class="o">=</span> <span class="n">no</span>
</code></pre></div>
<p>I recently used this approach on <code>fpdf2</code>: <a href="https://github.com/PyFPDF/fpdf2/pull/780">PR #780 Hardening Pylint config</a></p>
<p>Related configuration files in <code>fpdf2</code>:</p>
<ul>
<li><a href="https://github.com/PyFPDF/fpdf2/blob/master/.pylintrc">.pylintrc</a></li>
<li><a href="https://github.com/PyFPDF/fpdf2/blob/master/.github/workflows/continuous-integration-workflow.yml#L36">call in GitHub Actions pipeline definition</a></li>
</ul>
<p>I hope this <code>.pylintrc</code> could be useful to others!</p>
<p>You can drop a comment if this article was helpful to you 😊</p>
<!-- Com' :
* [x] https://linuxfr.org/users/lucas-c/liens/pylint-strict-base-configuration
* [x] https://news.ycombinator.com/item?id=35800986
* [x] https://www.reddit.com/r/Python/comments/136hqoo/pylint_strict_base_configuration/
* [x] https://dev.to/lucasc/pylint-strict-base-configuration-1p0j
* [ ] https://medium.com/@Lucas_C/
-->fpdf2 latest new features2023-04-03T17:00:00+02:002023-04-03T17:00:00+02:00Lucas Cimontag:chezsoi.org,2023-04-03:/lucas/blog/fpdf2-latest-new-features.html<p>I wrote my latest <a href="tag/fpdf2.html">post on <code>fpdf2</code></a> almost a year ago.
As we just released a new version, <a href="https://github.com/PyFPDF/fpdf2/blob/master/CHANGELOG.md">v2.7</a>,
this is the time to mention some recent additions to this library! 😊</p>
<p>This article will present some of the major features introduced between <strong>v2.5.3</strong> & <strong>v2.7.3</strong> of …</p><p>I wrote my latest <a href="tag/fpdf2.html">post on <code>fpdf2</code></a> almost a year ago.
As we just released a new version, <a href="https://github.com/PyFPDF/fpdf2/blob/master/CHANGELOG.md">v2.7</a>,
this is the time to mention some recent additions to this library! 😊</p>
<p>This article will present some of the major features introduced between <strong>v2.5.3</strong> & <strong>v2.7.3</strong> of <code>fpdf2</code>:</p>
<ul>
<li><a href="#tables">Tables</a></li>
<li><a href="#images--shapes">Images & shapes</a></li>
<li><a href="#fonts--text">Fonts & text</a></li>
<li><a href="#signing--encrypting-documents">Signing & encrypting documents</a></li>
<li><a href="#annotations">Annotations</a></li>
<li><a href="#documentation--translated-tutorials">Documentation & translated tutorials</a></li>
<li><a href="#whats-coming-next">What's coming next?</a></li>
</ul>
<h2>Tables</h2>
<p>One major addition of <strong>v2.7.0</strong> of <code>fpdf2</code> has the new method <code>.table()</code>,
that now makes very easy the generation of <strong>tables</strong> in PDF documents.</p>
<p>We documented how to use this method in a dedicated documentation page: <a href="https://pyfpdf.github.io/fpdf2/Tables.html">https://pyfpdf.github.io/fpdf2/Tables.html</a></p>
<p>Here is some snippet of demo code:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">fpdf</span> <span class="kn">import</span> <span class="n">FPDF</span>
<span class="n">TABLE_DATA</span> <span class="o">=</span> <span class="p">(</span>
<span class="p">(</span><span class="s2">"First name"</span><span class="p">,</span> <span class="s2">"Last name"</span><span class="p">,</span> <span class="s2">"Age"</span><span class="p">,</span> <span class="s2">"City"</span><span class="p">),</span>
<span class="o">...</span>
<span class="p">)</span>
<span class="n">pdf</span> <span class="o">=</span> <span class="n">FPDF</span><span class="p">()</span>
<span class="n">pdf</span><span class="o">.</span><span class="n">add_page</span><span class="p">()</span>
<span class="n">pdf</span><span class="o">.</span><span class="n">set_font</span><span class="p">(</span><span class="s2">"Helvetica"</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mi">16</span><span class="p">)</span>
<span class="k">with</span> <span class="n">pdf</span><span class="o">.</span><span class="n">table</span><span class="p">(</span><span class="n">borders_layout</span><span class="o">=</span><span class="s2">"MINIMAL"</span><span class="p">,</span>
<span class="n">col_widths</span><span class="o">=</span><span class="p">(</span><span class="mi">30</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">30</span><span class="p">),</span>
<span class="n">cell_fill_color</span><span class="o">=</span><span class="mi">200</span><span class="p">,</span> <span class="c1"># grey scale</span>
<span class="n">cell_fill_mode</span><span class="o">=</span><span class="s2">"ROWS"</span><span class="p">,</span>
<span class="n">text_align</span><span class="o">=</span><span class="s2">"CENTER"</span><span class="p">)</span> <span class="k">as</span> <span class="n">table</span><span class="p">:</span>
<span class="k">for</span> <span class="n">data_row</span> <span class="ow">in</span> <span class="n">TABLE_DATA</span><span class="p">:</span>
<span class="n">row</span> <span class="o">=</span> <span class="n">table</span><span class="o">.</span><span class="n">row</span><span class="p">()</span>
<span class="k">for</span> <span class="n">datum</span> <span class="ow">in</span> <span class="n">data_row</span><span class="p">:</span>
<span class="n">row</span><span class="o">.</span><span class="n">cell</span><span class="p">(</span><span class="n">datum</span><span class="p">)</span>
<span class="n">pdf</span><span class="o">.</span><span class="n">output</span><span class="p">(</span><span class="s2">"table-demo.pdf"</span><span class="p">)</span>
</code></pre></div>
<p>And the result:
<img alt="Sample table rendered in a PDF document" src="images/2023/04/table-demo.jpg"></p>
<h2>Images & shapes</h2>
<p>There has been several new features related to images:</p>
<ul>
<li>
<p><a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.image"><code>FPDF.image()</code></a>
has two new optional parameters: <code>align="C"</code> to horizontally an image on a page,
and <code>keep_aspect_ratio</code> which allows to preserve an image ratio.
Related documentation section: <a href="https://pyfpdf.github.io/fpdf2/Images.html#fitting-an-image-inside-a-rectangle">fitting an image inside a rectangle</a>.</p>
</li>
<li>
<p>thanks to a contribution by <a href="https://github.com/eroux">@eroux</a> in <a href="https://github.com/PyFPDF/fpdf2/pull/709">PR #709</a>, <a href="https://en.wikipedia.org/wiki/ICC_profile">ICC Profiles</a> of images inserted by <code>FPDF.image()</code> are now extracted an inserted in the PDF document: <a href="https://pyfpdf.github.io/fpdf2/Images.html#icc-profiles">doc</a>.</p>
</li>
<li>
<p>rectangles drawn with <a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.rect"><code>FPDF.rect()</code></a> can now have <strong>rounded corners</strong> using the new <code>round_corners</code> parameter. Check the documentation for examples: <a href="https://pyfpdf.github.io/fpdf2/Shapes.html#rectangle">Shapes: <em>rectangles with round corners</em></a>.</p>
</li>
<li>
<p>we documentated on how to control transparency of overlapping text, shapes and images through <code>stroke_opacity</code> & <code>fill_opacity</code>: <a href="https://pyfpdf.github.io/fpdf2/Transparency.html">documentation</a>.</p>
</li>
</ul>
<p><img alt="Example overlapping objects with transparency" src="https://pyfpdf.github.io/fpdf2/transparency.png"></p>
<h2>Fonts & text</h2>
<ul>
<li>
<p>since <strong>v2.5.7</strong>, <code>fpdf2</code> now uses the popular <a href="https://fonttools.readthedocs.io/en/latest/">fontTools</a> library to read and embed fonts in the PDF. This extended the range of font definition files supported by <code>fpdf2</code>, and also made the code used to parse & insert fonts a lot more robust. Thanks a lot to <a href="https://github.com/RedShy">@RedShy</a> for this change made in <a href="https://github.com/PyFPDF/fpdf2/pull/477">PR #477</a>.</p>
</li>
<li>
<p><a href="https://github.com/andersonhc">@andersonhc</a> introduced a <strong>font fallback</strong> mechanism controlled by a new method <a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.set_fallback_fonts"><code>FPDF.set_fallback_fonts()</code></a> in <a href="https://github.com/PyFPDF/fpdf2/pull/669">PR #669</a>: <a href="https://pyfpdf.github.io/fpdf2/Unicode.html#fallback-fonts">related documentation</a>.</p>
</li>
<li>
<p><a href="https://github.com/gmischler">@gmischler</a> provided support for subscript, superscript, nominator and denominator in <a href="https://github.com/PyFPDF/fpdf2/pull/520">PR #520</a>: <a href="https://pyfpdf.github.io/fpdf2/TextStyling.html#subscript-superscript-and-fractional-numbers">related documentation</a>.</p>
</li>
</ul>
<p><img alt="Usage example of subscript, superscript, nominator and denominator" src="https://pyfpdf.github.io/fpdf2/char_vpos.png"></p>
<h2>Signing & encrypting documents</h2>
<ul>
<li>
<p>since <strong>v2.5.6</strong>, <code>fpdf2</code> allows to <strong>sign</strong> documents, using the companion <a href="https://pypi.org/project/endesive/">endesive</a> package: <a href="https://pyfpdf.github.io/fpdf2/Signing.html">related documentation</a>.</p>
</li>
<li>
<p>since <strong>v2.6.1</strong>, and thanks to <a href="https://github.com/andersonhc">@andersonhc</a> work in <a href="https://github.com/PyFPDF/fpdf2/pull/609">PR #609</a>, <code>fpdf2</code> allows to <strong>encrypt</strong> documents using RC4 or AES-128 algorithms: <a href="https://pyfpdf.github.io/fpdf2/Encryption.html">related documentation</a>.</p>
</li>
</ul>
<h2>Annotations</h2>
<ul>
<li>
<p>since <strong>v2.5.7</strong>, <a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.embed_file"><code>embed_file()</code></a> & <a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.file_attachment_annotation"><code>file_attachment_annotation()</code></a> allow to <strong>embed</strong> other files into PDF documents: <a href="https://pyfpdf.github.io/fpdf2/FileAttachments.html">related documentation</a>.</p>
</li>
<li>
<p><code>FPDF.ink_annotation()</code> has been introduced in <strong>v2.5.5</strong> new to add path annotations: <a href="https://pyfpdf.github.io/fpdf2/Annotations.html#ink-annotations">related documentation</a>.</p>
</li>
</ul>
<p><img alt="Ink annotation example" src="https://pyfpdf.github.io/fpdf2/ink_annotation.png"></p>
<h2>Documentation & translated tutorials</h2>
<p>A demonstration <a href="https://jupyter.org/"><strong>Jupyter notebook</strong></a> has been created: <a href="https://github.com/PyFPDF/fpdf2/blob/master/tutorial/notebook.ipynb">tutorial/notebook.ipynb</a></p>
<p>New translations of our <a href="https://pyfpdf.github.io/fpdf2/Tutorial.html">tutorial</a> were also provided:</p>
<ul>
<li>
<p>Simplified Chinese in <a href="https://github.com/PyFPDF/fpdf2/pull/666">PR #666</a> by <a href="https://github.com/Bubbu0129">@Bubbu0129</a>: <a href="https://pyfpdf.github.io/fpdf2/Tutorial-zh.html">简体中文</a></p>
</li>
<li>
<p>Bengali in <a href="https://github.com/PyFPDF/fpdf2/pull/738">PR #738</a> by <a href="https://github.com/ssavi-ict">@ssavi-ict</a>: <a href="https://pyfpdf.github.io/fpdf2/Tutorial-bn.html">বাংলা</a></p>
</li>
</ul>
<h2>What's coming next?</h2>
<p>We have several <a href="https://github.com/PyFPDF/fpdf2/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement+">enhancement proposals</a> open (and mostly "up-for-grabs"),
and also an handful of <a href="https://github.com/PyFPDF/fpdf2/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22"><strong>good first issues</strong></a> dedicated to developpers would like to start contributing to <code>fpdf2</code> with a relatively easy task.</p>
<p>Among major projects, two contributors made very interesting & ambitious suggestions:</p>
<ul>
<li>
<p><a href="https://github.com/gmischler">@gmischler</a> has plans to enhance <code>fpdf2</code> text-rendering capacities: <a href="https://github.com/PyFPDF/fpdf2/discussions/339">discussion #339</a>. At the moment he is working on a general solution for organizing flowing text with <a href="https://github.com/gmischler/fpdf2/tree/TextRegion"><code>TextRegion</code></a> classes.</p>
</li>
<li>
<p><a href="https://github.com/andersonhc">@andersonhc</a> suggested in <a href="https://github.com/PyFPDF/fpdf2/discussions/696">discussion #696</a> to integrate the <a href="https://harfbuzz.github.io/">harfbuzz</a> library to provide <code>fpdf2</code> with text shaping abilities</p>
</li>
</ul>
<p>Finally, I am seriously considering to <strong>move the <code>fpdf2</code> project to the <a href="https://github.com/py-pdf">@py-pdf GitHub organization</a></strong>, in order to share ownership of several PDF-related Python libraries with other maintainers and join forces!
You can track this migration project and share your feedback in this GitHub discussion:
<a href="https://github.com/PyFPDF/fpdf2/discussions/752">https://github.com/PyFPDF/fpdf2/discussions/752</a>.</p>
<p>As always, I would be happy to receive your comments below 😊</p>
<!-- Com' :
* [x] https://news.ycombinator.com/item?id=35424491
* [x] https://www.reddit.com/r/Python/comments/12amq5d/fpdf2_latest_new_features/
* [x] https://dev.to/lucasc/fpdf2-latest-new-features-4mn0
-->Rumble in the House2023-03-31T12:00:00+02:002023-03-31T12:00:00+02:00Lucas Cimontag:chezsoi.org,2023-03-31:/lucas/blog/rumble-in-the-house.html<p><img alt="Logo du jeu" src="images/2023/03/RumbleinTheHouse.jpg"></p>
<p>Il y a quelques mois, j'ai partagé ici <a href="rumble-in-the-dungeon.html">quelques variantes pour <em>Rumble in the Dungeon</em></a>, un très sympatique jeu de société pour 3 à 6 joueurs, basé sur le bluff, la déduction et le hasard.</p>
<p>Il existe une autre version de ce jeu, <em>Rumble in the House</em>,
qui ne diffère …</p><p><img alt="Logo du jeu" src="images/2023/03/RumbleinTheHouse.jpg"></p>
<p>Il y a quelques mois, j'ai partagé ici <a href="rumble-in-the-dungeon.html">quelques variantes pour <em>Rumble in the Dungeon</em></a>, un très sympatique jeu de société pour 3 à 6 joueurs, basé sur le bluff, la déduction et le hasard.</p>
<p>Il existe une autre version de ce jeu, <em>Rumble in the House</em>,
qui ne diffère que par des personnages & tuiles distincts,
aini que l'absence de coffre.</p>
<p>Comme j'ai eu l'occasion de trouver cette version à <a href="https://emmaus-france.org/acheter/">Emmaüs</a> pour <strong>2€</strong>, j'ai décidé de tester cette autre version...</p>
<p>...et bien sûr je n'ai pas résister à concocter de nouvelles variantes ! 😅</p>
<p><a href="images/2023/03/RumbleInTheHouse-variantes-FR.pdf">
<br>
<figure>
<img alt="PDF preview" src="images/2023/03/rumble-in-the-house-FR-pdf-thumbnail.jpg">
<!-- Generated with: convert RumbleInTheHouse-variantes-FR.pdf -flatten rumble-in-the-house-FR-pdf-thumbnail.jpg -->
<figcaption>PDF des règles en français (2 pages - 226 Ko)</figcaption>
</figure></p>
<p></a></p>
<p>Merci à mes collègues et aux joueurs du <a href="https://www.champtoce.fr/culture-sports-loisirs/caf%C3%A9-associatif-le-kawasso/">Kawasso</a> pour les avoir testé.</p>
<p>Vous pouvez retrouver toutes mes autres variantes de jeux ici : <a href="tag/variante.html">tag <strong>variante</strong></a>.</p>
<p>J'espère que ces variantes vous plairont ! N'hésitez pas à me faire vos retours en commentaire.</p>
<style>
article img { max-height: 20rem; }
</style>
<!-- Com'
* [x] https://www.trictrac.net/jeu-de-societe/rumble-in-the-house/ressources
-->__slots__ memory optimization in Python2023-03-28T09:00:00+02:002023-03-28T09:00:00+02:00Lucas Cimontag:chezsoi.org,2023-03-28:/lucas/blog/slots-memory-optimizations-in-python.html<figure role="group">
<img alt="" src="https://realpython.com/cdn-cgi/image/width=960,format=auto/https://files.realpython.com/media/SciPy-Tutorial_Watermarked_1.b9f391570601.jpg">
<figcaption>Illustration from <a href="https://realpython.com/python-scipy-cluster-optimize/">realpython.com</a></figcaption>
</figure>
<p>The other day, while working on <a href="https://github.com/PyFPDF/fpdf2">fpdf2</a>,
I used <a href="https://docs.python.org/3/library/dataclasses.html"><code>@dataclass</code></a>,
a nice decorator that came in the standard library with Python 3.7,
to quickly define a <code>class</code> that mostly stored data.</p>
<p>Then a question came to my mind: <strong>is the <a href="https://wiki.python.org/moin/UsingSlots"><code>__slots__</code></a> memory optimization compatible with …</strong></p><figure role="group">
<img alt="" src="https://realpython.com/cdn-cgi/image/width=960,format=auto/https://files.realpython.com/media/SciPy-Tutorial_Watermarked_1.b9f391570601.jpg">
<figcaption>Illustration from <a href="https://realpython.com/python-scipy-cluster-optimize/">realpython.com</a></figcaption>
</figure>
<p>The other day, while working on <a href="https://github.com/PyFPDF/fpdf2">fpdf2</a>,
I used <a href="https://docs.python.org/3/library/dataclasses.html"><code>@dataclass</code></a>,
a nice decorator that came in the standard library with Python 3.7,
to quickly define a <code>class</code> that mostly stored data.</p>
<p>Then a question came to my mind: <strong>is the <a href="https://wiki.python.org/moin/UsingSlots"><code>__slots__</code></a> memory optimization compatible with <code>@dataclass</code>?
Is it even compatible?</strong>.</p>
<p>This very short article is basically an opportunity to answer those questions with some minimal code,
mostly as a reminder to myself:</p>
<div class="highlight"><pre><span></span><code><span class="ch">#!/usr/bin/env python</span>
<span class="kn">import</span> <span class="nn">os</span><span class="o">,</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">namedtuple</span>
<span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
<span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">NamedTuple</span>
<span class="k">def</span> <span class="nf">get_process_rss</span><span class="p">():</span> <span class="c1"># Similar to: psutil.Process().memory_info().rss / 1024 / 1024</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="sa">f</span><span class="s2">"/proc/</span><span class="si">{</span><span class="n">os</span><span class="o">.</span><span class="n">getpid</span><span class="p">()</span><span class="si">}</span><span class="s2">/statm"</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">"utf8"</span><span class="p">)</span> <span class="k">as</span> <span class="n">statm</span><span class="p">:</span>
<span class="n">rss_as_mib</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">statm</span><span class="o">.</span><span class="n">readline</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">()[</span><span class="mi">1</span><span class="p">])</span> <span class="o">*</span> <span class="n">os</span><span class="o">.</span><span class="n">sysconf</span><span class="p">(</span><span class="s2">"SC_PAGE_SIZE"</span><span class="p">)</span> <span class="o">/</span> <span class="mi">1024</span> <span class="o">/</span> <span class="mi">1024</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">rss_as_mib</span><span class="si">:</span><span class="s2">.1f</span><span class="si">}</span><span class="s2"> MiB"</span>
<span class="k">except</span> <span class="ne">FileNotFoundError</span><span class="p">:</span> <span class="c1"># /proc files only exist under Linux</span>
<span class="k">return</span> <span class="s2">"<unavailable>"</span>
<span class="k">class</span> <span class="nc">Object</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span>
<span class="bp">self</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span>
<span class="bp">self</span><span class="o">.</span><span class="n">z</span> <span class="o">=</span> <span class="n">z</span>
<span class="k">class</span> <span class="nc">ObjectWithSlots</span><span class="p">:</span>
<span class="vm">__slots__</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'x'</span><span class="p">,</span> <span class="s1">'y'</span><span class="p">,</span> <span class="s1">'z'</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span>
<span class="bp">self</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span>
<span class="bp">self</span><span class="o">.</span><span class="n">z</span> <span class="o">=</span> <span class="n">z</span>
<span class="nd">@dataclass</span>
<span class="k">class</span> <span class="nc">Dataclass</span><span class="p">:</span>
<span class="n">x</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">y</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">z</span><span class="p">:</span> <span class="nb">int</span>
<span class="nd">@dataclass</span><span class="p">(</span><span class="n">slots</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span> <span class="c1"># since Python 3.10, this is the same as defining __slots__ manually</span>
<span class="k">class</span> <span class="nc">DataclassWithSlots</span><span class="p">:</span>
<span class="n">x</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">y</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">z</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">Namedtuple</span> <span class="o">=</span> <span class="n">namedtuple</span><span class="p">(</span><span class="s1">'E'</span><span class="p">,</span> <span class="p">(</span><span class="s1">'x'</span><span class="p">,</span> <span class="s1">'y'</span><span class="p">,</span> <span class="s1">'z'</span><span class="p">))</span>
<span class="k">class</span> <span class="nc">NamedTupleSubclass</span><span class="p">(</span><span class="n">NamedTuple</span><span class="p">):</span>
<span class="n">x</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">y</span><span class="p">:</span> <span class="nb">int</span>
<span class="n">z</span><span class="p">:</span> <span class="nb">int</span>
<span class="k">def</span> <span class="nf">Tuple</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span>
<span class="k">return</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">Dict</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">locals</span><span class="p">()</span>
<span class="n">Class</span> <span class="o">=</span> <span class="nb">locals</span><span class="p">()[</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">upper</span><span class="p">()]</span>
<span class="n">l</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100000</span><span class="p">):</span>
<span class="n">l</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">Class</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="n">get_process_rss</span><span class="p">())</span>
</code></pre></div>
<p>Results on my machine with Python 3.8:</p>
<div class="highlight"><pre><span></span><code>$ ./slots_test.py Object
<span class="m">26</span>.6 MiB
$ ./slots_test.py ObjectWithSlots
<span class="m">17</span>.2 MiB
$ ./slots_test.py Dataclass
<span class="m">26</span>.7 MiB
$ ./slots_test.py DataclassWithSlots
<span class="m">17</span>.2 MiB
$ ./slots_test.py Namedtuple
<span class="m">19</span>.1 MiB
$ ./slots_test.py NamedTupleSubclass
<span class="m">19</span>.2 MiB
$ ./slots_test.py Tuple
<span class="m">17</span>.8 MiB
$ ./slots_test.py Dict
<span class="m">34</span>.5 MiB
</code></pre></div>
<p>Results on my machine with Python 3.10 in debug mode:</p>
<div class="highlight"><pre><span></span><code>$ ./slots_test.py Object
<span class="m">28</span>.3 MiB
$ ./slots_test.py ObjectWithSlots
<span class="m">22</span>.0 MiB
$ ./slots_test.py Dataclass
<span class="m">28</span>.3 MiB
$ ./slots_test.py DataclassWithSlots
<span class="m">22</span>.0 MiB
$ ./slots_test.py Namedtuple
<span class="m">24</span>.8 MiB
$ ./slots_test.py NamedTupleSubclass
<span class="m">24</span>.2 MiB
$ ./slots_test.py Tuple
<span class="m">24</span>.3 MiB
$ ./slots_test.py Dict
<span class="m">38</span>.3 MiB
</code></pre></div>
<p>We can conclude that:</p>
<ul>
<li><code>__slots__</code> is still an effective memory optimization with recent versions of Python,
that can provide between 10% en 30% of RAM savings</li>
<li><code>__slots__</code> can effectively be combined with <code>@dataclass</code></li>
<li><code>namedtuple</code> / <code>NamedTuple</code> is less memory-efficient than <code>__slots__</code> (and both cannot be combined)</li>
</ul>
<p>Related content:</p>
<ul>
<li><a href="https://docs.python.org/3/reference/datamodel.html#slots">Official Python documentation on <code>__slots__</code></a></li>
<li><a href="https://py.checkio.org/blog/memory-optimization-with-python-slots/">Blog article @py.checkio.org</a></li>
<li><a href="https://www.invivoo.com/les-slots-une-optimisation-meconnue/">Les slots, une optimisation méconnue</a>, a Frenc blog article pointing that using <code>__slots__</code> also makes code faster</li>
<li><a href="https://stackoverflow.com/questions/472000/usage-of-slots">StackOverflow post on Python <code>__slots__</code></a></li>
</ul>
<p>Well aware of the limitations of <code>__slots__</code>, I will definitely adopt more of them in <code>fpdf2</code> 😊</p>
<p>Since december, I have been working on tracking memory allocations occuring during the execution of <code>fpdf2</code> unit tests suite,
and this is no easy task: <a href="https://github.com/PyFPDF/fpdf2/issues/641">issue #641</a>.
<a href="https://www.youtube.com/watch?v=e3-5YC_oHjE">I stil haven't found what I'm looking for</a>, the main difficulty being the opacity of the Python memory allocator,
and tracking the memory allocated through <code>malloc</code> calls by libraries that <code>fpdf2</code> depends on,
but it has been very insightful so far! 😁</p>Psi*Run - Le Manoir2022-12-27T00:20:00+01:002022-12-27T00:20:00+01:00Lucas Cimontag:chezsoi.org,2022-12-27:/lucas/blog/psirun-le-manoir.html<figure>
<img alt="" src="images/2022/12/escaping_the_madness_by_thesimplylexi.jpg">
<figcaption><a href="https://www.deviantart.com/thesimplylexi/art/Escaping-the-madness-669762771">Escaping the madness par Martina Lexi</a> - CC BY-NC-SA</figcaption>
</figure>
<p><a href="http://nightskygames.com/welcome/game/PsiRun">Psi*Run de Meguey Baker</a>,
traduit en français par <a href="https://electric-goat.net/products/1">Alexis Lamiable</a>,
est un jeu que j'adore. Je l'ai déjà évoqué ici <a href="tag/psirun.html">à plusieurs reprises</a>.</p>
<p>La semaine dernière, j'ai eu l'occasion de faire une session de ce jeu de rôle avec quelques amis …</p><figure>
<img alt="" src="images/2022/12/escaping_the_madness_by_thesimplylexi.jpg">
<figcaption><a href="https://www.deviantart.com/thesimplylexi/art/Escaping-the-madness-669762771">Escaping the madness par Martina Lexi</a> - CC BY-NC-SA</figcaption>
</figure>
<p><a href="http://nightskygames.com/welcome/game/PsiRun">Psi*Run de Meguey Baker</a>,
traduit en français par <a href="https://electric-goat.net/products/1">Alexis Lamiable</a>,
est un jeu que j'adore. Je l'ai déjà évoqué ici <a href="tag/psirun.html">à plusieurs reprises</a>.</p>
<p>La semaine dernière, j'ai eu l'occasion de faire une session de ce jeu de rôle avec quelques amis. La partie fut très réussie, et l'envie m'a pris de partager la situation initiale que j'avais conçue, afin qu'elle puisse servir à d'autres MJs.</p>
<p>Voici donc le résultat : un condensé en une page de suggestions de lieux, de Poursuivants, de PNJs et de bande sons !</p>
<div class="side-by-side">
<a href="https://lucas-c.itch.io/psirun-le-manoir">
<figure>
<img alt="PDF preview" src="images/2022/12/PsiRun-LeManoir-pdf-thumb.jpg">
<figcaption>Amorce scénaristique (lien vers itch.io)</figcaption>
</figure>
</a>
<a href="https://lucas-c.itch.io/psirun-the-manor">
<figure>
<img alt="PDF preview" src="images/2022/12/PsiRun-TheManor-pdf-thumb.jpg">
<figcaption>Setting primmer - English version (lien vers itch.io)</figcaption>
</figure>
</a>
</div>
<p>Cette AdJ est d'ores et déjà également disponible sur la <a href="https://www.scenariotheque.org/Document/info_jeu.php?f_id_jeu=424">Scénariothèque</a> 🐲
<a href="https://www.scenariotheque.org"><img alt="Logo de la Scénariothèque" src="images/2022/12/scenariotheque-logo.png"></a></p>
<style>
@media (min-width:768px) {
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
}
.side-by-side > * { margin: 0 2rem; }
}
</style>
<!-- Com'
* [x] https://www.scenariotheque.org/Document/info_doc.php?id_doc=10740
-> référence : https://chezsoi.org/lucas/jdr/PsiRun-LeManoir.pdf
& https://chezsoi.org/lucas/jdr/PsiRun-LeManoir-PrinterFriendly.pdf
* [x] https://lucas-c.itch.io/psirun-le-manoir
* [x] https://electric-goat.net/forums/2
* [x] https://discord.com/channels/805515399260405770/805515651493920778/1057093087471620167
* [x] https://lumpley.itch.io/psirun -> https://itch.io/post/7091711
* [x] https://www.casusno.fr/viewtopic.php?p=2134040#p2134040
-->La Tour Ciel-Aveugle2022-12-26T23:55:00+01:002022-12-26T23:55:00+01:00Lucas Cimontag:chezsoi.org,2022-12-26:/lucas/blog/la-tour-ciel-aveugle.html<p>Comme promis <a href="another-animated-dungeon-the-sky-blind-spire.html">il y a deux semaines</a>,
voici la traduction du génialissime <em>one-page-dungeon</em> de <a href="http://blog.trilemma.com/">Michael Prescott</a> :</p>
<p><a href="https://lucas-c.itch.io/la-tour-ciel-aveugle"></p>
<figure>
<img alt="PDF thumbnail preview" src="images/2022/12/LaTourCielAveugle-thumb.jpg">
<figcaption>lien vers itch.io</figcaption>
</figure>
<p></a></p>
<p>C'est un condensé de pépites narratives entrelacées et assemblées en une seule page : un <strong>labyrinthe</strong> à explorer, des <strong>énigmes</strong> basées sur l'environnement, des rencontres aléatoires, des <strong>artefacts</strong> pertinents à collecter …</p><p>Comme promis <a href="another-animated-dungeon-the-sky-blind-spire.html">il y a deux semaines</a>,
voici la traduction du génialissime <em>one-page-dungeon</em> de <a href="http://blog.trilemma.com/">Michael Prescott</a> :</p>
<p><a href="https://lucas-c.itch.io/la-tour-ciel-aveugle"></p>
<figure>
<img alt="PDF thumbnail preview" src="images/2022/12/LaTourCielAveugle-thumb.jpg">
<figcaption>lien vers itch.io</figcaption>
</figure>
<p></a></p>
<p>C'est un condensé de pépites narratives entrelacées et assemblées en une seule page : un <strong>labyrinthe</strong> à explorer, des <strong>énigmes</strong> basées sur l'environnement, des rencontres aléatoires, des <strong>artefacts</strong> pertinents à collecter et une forte cohérence thématique pour lier le tout !</p>
<p>Voici également la version PDF cliquable, avec <strong>une page par pièce</strong> :</p>
<p><a href="images/2022/12/LaTourCielAveugle-cliquable.pdf"></p>
<figure>
<img alt="PDF thumbnail preview" src="images/2022/12/LaTourCielAveugle-cliquable-thumb.jpg">
<figcaption>PDF (22 pages - 7,14 Mo)</figcaption>
</figure>
<p></a></p>
<blockquote>
<p>Le but de ce PDF "animé" est de fournir au MJ une carte, afin qu'il puisse la montrer en plein écran aux joueurs pendant la session de jeu, et cliquer sur les sorties des salles au fur et à mesure que leurs personnages naviguent entre les salles.</p>
</blockquote>
<h2>Un lieu pour <em>La Lune et Douze Lotus</em></h2>
<p>J'ai introduit cette tour dans ma campagne de <a href="http://legrumph.org/Terrier/public/chibi/lledl">La Lune et Douze Lotus</a> : certes, l'exploration de donjon et la résolution d'énigmes ne représentent pas les thèmes privilégiés de la <em>Sword & Sorcery</em>, mais l'occasion était trop belle !</p>
<p>J'ai pour cela réalisé quelques ajustements thématiques :</p>
<ul>
<li>
<p>le sorcier Titardinal qui a créé la tour a employé une magie associée aux <strong>bêtes du reflet</strong>. L'autel à son sommet a été témoin de nombreux sacrifices sanglants.</p>
</li>
<li>
<p>les gobelins et le mentor spectral ont été remplacés par un unique <strong>sorcier</strong>, à l'origine du sortilège ciel-aveugle, se dissimulant sous une cape bleue pour tenter de percer les secrets du lieu, et espionner les PJs... Selon les occasions qui se présentent, il pourra d'abord aider les PJs lors d'un combat où ils ont déjà le dessus, pour ensuite les sonder sur ce qu'ils savent et leurs motivation, et enfin les envoyer droit vers le piège entre les salles 10 & 11. Il sautera aussi sur l'occasion d'attaquer un PJ isolé. Il maîtrise les arts suivants : instinct, vision, marque de sang et animation des morts.</p>
</li>
<li>
<p>j'ai ajouté un coffre sous une toile bleue dans la salle 11, contenant de l'or. L'idée étant que les PJs découvrent son existence via le sorcier ou dans le bureau, sans pouvoir mettre la main dessus tant qu'ils ne percent pas le mystère du sortilège ciel-aveugle</p>
</li>
<li>
<p>j'ai aussi introduit un <strong>basilique</strong> au regard pétrifiant dans la salle 19, et un <strong>mille-visages</strong> dans un miroir d'eau vertical, face à la porte de la salle 21 : lorsque les PJs entrent dans cette pièce, ils peuvent distinguer que leur reflet comporte de fines inscriptions sur les doigts. S'ils approchent leur main du miroir pour les déchiffrer, leur reflet les saisit et tente de les faire basculer de l'autre côté du miroir, dans un monde inversé et cauchemardesque qui les rendra fous.</p>
</li>
</ul>
<!-- Com'
* [x] https://lucas-c.itch.io/la-tour-ciel-aveugle
* [x] email to Michael Prescott
* [x] comments on http://blog.trilemma.com/2016/04/the-sky-blind-spire.html & http://blog.trilemma.com/p/aventures-en-francais.html
* [x] http://troplongpaslu.fr (à date pas encore validé)
-->Another animated dungeon: The Sky-Blind Spire2022-12-12T21:30:00+01:002022-12-12T21:30:00+01:00Lucas Cimontag:chezsoi.org,2022-12-12:/lucas/blog/another-animated-dungeon-the-sky-blind-spire.html<p>Following <a href="animated-one-page-dungeon-escape-of-the-torment.html">last week animated PDF adventure</a>,
I have been reading a series of one page dungeons...
And yesterday I had the opportunity to play the best one in my opinion:
<strong><a href="http://blog.trilemma.com/2016/04/the-sky-blind-spire.html">The Sky-Blind Spire by Michael Prescott</a></strong>.</p>
<p>It has everything I love on one page: a maze to explore, mysteries to …</p><p>Following <a href="animated-one-page-dungeon-escape-of-the-torment.html">last week animated PDF adventure</a>,
I have been reading a series of one page dungeons...
And yesterday I had the opportunity to play the best one in my opinion:
<strong><a href="http://blog.trilemma.com/2016/04/the-sky-blind-spire.html">The Sky-Blind Spire by Michael Prescott</a></strong>.</p>
<p>It has everything I love on one page: a maze to explore, mysteries to unveil, an environment-based conundrum to solve, random encounters, useful items to loot, and lot of thematic consistency between all of that!</p>
<p>I made another clickable PDF for this TTRPG session, that I'm sharing below:</p>
<div class="side-by-side">
<a href="images/2022/12/TheSkyBlindSpire-clickable.pdf">
<figure>
<img alt="PDF thumbnail preview" src="images/2022/12/the-sky-blind-spire-pdf-preview.jpg">
<figcaption>PDF (22 pages - 7.19 MB)</figcaption>
</figure>
</a>
<a href="images/2022/12/build_clickable_SkyBlindSpire.py">
<figure>
<img alt="Python logo" src="images/2022/12/python-code.jpg">
<figcaption>Python script that generated the PDF</figcaption>
</figure>
</a>
</div>
<blockquote>
<p>The goal of this "animated" PDF is to provide the GM with a simple PDF document displaying a map,
so that they can show it in full screen to the players during the game session,
and click on rooms exits in order to navigate between locations.</p>
</blockquote>
<p>Thanks a lot to Michael Prescott for crafting this wonderful adventure!</p>
<p>In my next blog post, I plan to share a French translation of this one page dungeon,
along with advices on how to use it with <a href="http://legrumph.org/Terrier/public/chibi/lledl">La Lune et Douze Lotus</a>.</p>
<style>
@media (min-width:768px) {
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
}
.side-by-side > * { margin: 0 2rem; }
}
</style>
<!-- Com'
* [x] email to Michael Prescott
* [ ] Reddit:
+ https://www.reddit.com/r/onePageDungeon/
https://www.reddit.com/r/onePageDungeon/comments/zakoa1/animated_onepagedungeon_escape_of_the_torment/
+ https://www.reddit.com/r/osr/
+ https://www.reddit.com/r/battlemaps/
-->AdJ pour La Lune et 12 Lotus2022-12-07T21:00:00+01:002022-12-07T21:00:00+01:00Lucas Cimontag:chezsoi.org,2022-12-07:/lucas/blog/adj-pour-la-lune-et-12-lotus.html<p>Un petit billet rapide pour partager une aide de jeu très basique mais que je crois bien utile
pour <a href="http://legrumph.org/Terrier/public/chibi/lledl">La Lune et 12 Lotus</a>,
le jeu de rôle des éditions Chibi de John Grümph,
qui propose de la <em>Sword & Sorcery</em> façon Conan ou le Cycle des épées :</p>
<blockquote>
<p>Une poignée de …</p></blockquote><p>Un petit billet rapide pour partager une aide de jeu très basique mais que je crois bien utile
pour <a href="http://legrumph.org/Terrier/public/chibi/lledl">La Lune et 12 Lotus</a>,
le jeu de rôle des éditions Chibi de John Grümph,
qui propose de la <em>Sword & Sorcery</em> façon Conan ou le Cycle des épées :</p>
<blockquote>
<p>Une poignée de femmes et d’hommes exceptionnels, armés de leur courage et de leur ruse, sillonnent les routes du lotus, seulement guidés par le hasard des rencontres, les exigences de l’amitié et le sentiment diffus que l’indifférence au monde est pire que la mort (tout en prétendant le contraire).</p>
<p>Ça et le besoin impérieux de botter des culs.</p>
</blockquote>
<p>Voici donc un PDF en 2 pages récapitulant les règles du jeu,
qui me sera bien utile pour la petite campagne en mode <a href="https://www.cestpasdujdr.fr/les-marches-de-louest/">"les marches de l'ouest"</a> que je prévoie de débuter 😊</p>
<p><a href="images/lle12l/La-Lune-et-Douze-Lotus-AdJ.pdf">
<br>
<figure>
<img alt="PDF preview" src="images/2022/12/La-Lune-et-Douze-Lotus-AdJ-pdf-thumb.jpg">
<figcaption>Aide de Jeu PDF (2 pages - 2.2 Mo)</figcaption>
</figure></p>
<p></a></p>
<p>Cette AdJ sera aussi disponible sur la vénérable et merveilleuse <a href="https://www.scenariotheque.org">Scénariothèque</a> 🐲
<a href="https://www.scenariotheque.org"><img alt="Logo de la Scénariothèque" src="images/2022/12/scenariotheque-logo.png"></a></p>
<!-- Com'
* [x] https://www.scenariotheque.org/Document/info_doc.php?id_doc=10736
-> référence : https://chezsoi.org/lucas/blog/images/lle12l/La-Lune-et-Douze-Lotus-AdJ.pdf
* [x] Discord Chibi
* [ ] https://www.casusno.fr
-->Animated one-page-dungeon : Escape of the Torment2022-12-02T12:50:00+01:002022-12-02T12:50:00+01:00Lucas Cimontag:chezsoi.org,2022-12-02:/lucas/blog/animated-one-page-dungeon-escape-of-the-torment.html<p>Last week, while translating John Harper's micro-TTRPG <a href="world-of-dungeons-turbo-breakers.html">World of Dungeons: Turbo Breakers</a>, I discovered the wonderful world of <strong>one page dungeons</strong>,
starting with Michael Prescott splendid production at <a href="http://blog.trilemma.com/p/aventures-en-francais.html">trilemma.com</a>
and also the yearly <a href="https://www.dungeoncontest.com/">One Page Dungeon Context</a>.</p>
<p>While crawling through the OPDC 2021 entries, I discovered a great map …</p><p>Last week, while translating John Harper's micro-TTRPG <a href="world-of-dungeons-turbo-breakers.html">World of Dungeons: Turbo Breakers</a>, I discovered the wonderful world of <strong>one page dungeons</strong>,
starting with Michael Prescott splendid production at <a href="http://blog.trilemma.com/p/aventures-en-francais.html">trilemma.com</a>
and also the yearly <a href="https://www.dungeoncontest.com/">One Page Dungeon Context</a>.</p>
<p>While crawling through the OPDC 2021 entries, I discovered a great map by <a href="https://mooselich.com/">Brett Simison</a>: <em>Escape of the Torment</em>.
With my experience on building "animated" PDFs (on <a href="undying-dusk-a-pdf-video-game.html">Undying Dusk</a> and another game I'm currently working on), I thought it would be fun to "animate" this great adventure, and here is the result:</p>
<div class="side-by-side">
<a href="images/2022/11/EscapeOfTheTorment-animated.pdf">
<figure>
<img alt="PDF thumbnail preview" src="images/2022/11/EscapeOfTheTorment-animated.gif">
<figcaption>PDF (146 pages - 6.76 MB)</figcaption>
</figure>
</a>
<a href="images/2022/11/EscapeOfTheTorment-PythonCodeAndAssets.zip">
<figure>
<img alt="ZIP archive thumbnail" src="images/2022/11/EscapeOfTheTorment-zip-thumbnail.jpg">
<figcaption>ZIP archive of the Python source code & assets (7.73 MB)</figcaption>
</figure>
</a>
</div>
<blockquote>
<p>The goal of this "animated" PDF is to provide the GM with a simple PDF document displaying a map,
so that they can show it in full screen to the players during the game session,
and click on elements of the map in order for the scene to evolve.</p>
</blockquote>
<p>I initially though about building this with HTML, CSS & Javascript as a web page,
but on second though it thought that generating a single PDF could be more handy for GMs,
with the added benefit of not requiring an online connexion. It was also a bit simpler for me not to worry with being adaptive to the browser screen size.</p>
<p>In order to extract the images from the original PDF, I used <a href="https://libreoffice.org/discover/draw/">LibreOffice Draw</a>
with a custom Python script, included in the ZIP archive.
The archive also includes the <code>build_animated_EotT.py</code> program used to generate the animated PDF, using <a href="https://pyfpdf.github.io/fpdf2/">fpdf2</a>.</p>
<p>I hope the PDF will be useful to some GMs. I think it would be a good fit for a D & D or <a href="https://en.wikipedia.org/wiki/7th_Sea_(role-playing_game)">7th Sea</a> adventure. I personnally plan to use it in my <a href="https://bladesinthedark.com">Blades in the Dark</a> campaign.</p>
<p>The overall approach of animating TTRPG sceneries / battlegrounds as PDF documents could also be applied to other maps!
Of course it does not offer as much expressivity as a Roll20 / Foundry interactive map,
but it can be a handy, simple alternative, and very fun to build for Python coders! 🐍</p>
<style>
@media (min-width:768px) {
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
}
.side-by-side > * { margin: 0 2rem; }
}
</style>
<!-- Com'
* [x] email to dm@mooselich.com
* [x] https://www.casusno.fr/viewtopic.php?t=41384
* [x] Reddit:
+ https://www.reddit.com/r/onePageDungeon/comments/zakoa1/animated_onepagedungeon_escape_of_the_torment/
+ https://www.reddit.com/r/osr/comments/zakr9b/i_made_an_animated_pdf_out_of_brett_simison/
+ https://www.reddit.com/r/battlemaps/comments/zasgjs/i_made_an_animated_pdf_out_of_brett_simison/
-->World of Dungeons : Turbo Breakers2022-11-24T19:00:00+01:002022-11-24T19:00:00+01:00Lucas Cimontag:chezsoi.org,2022-11-24:/lucas/blog/world-of-dungeons-turbo-breakers.html<p>En 2016, financé par Patreon, <a href="tag/john-harper.html">John Harper</a> a sorti une courte suite à son jeu <em>World of Dungeons</em> de 2012 : <a href="https://johnharper.itch.io/breakers">World of Dungeons : Turbo Breakers</a>.</p>
<p>Il s'agit d'un jeu de rôle d'exploration de donjons à l'époque moderne. En résumé, c'est Ghostbusters qui rencontre D&D !</p>
<p>Je me suis amusé à …</p><p>En 2016, financé par Patreon, <a href="tag/john-harper.html">John Harper</a> a sorti une courte suite à son jeu <em>World of Dungeons</em> de 2012 : <a href="https://johnharper.itch.io/breakers">World of Dungeons : Turbo Breakers</a>.</p>
<p>Il s'agit d'un jeu de rôle d'exploration de donjons à l'époque moderne. En résumé, c'est Ghostbusters qui rencontre D&D !</p>
<p>Je me suis amusé à le traduire en français. Cette traduction est disponible sur <a href="https://lucas-c.itch.io/world-of-dungeons-turbo-breakers-fr">itch.io</a> :</p>
<p><a href="https://lucas-c.itch.io/world-of-dungeons-turbo-breakers-fr">
<br>
<figure>
<img alt="PDF preview" src="images/2022/11/WoD-TB-PDF-preview.png">
<figcaption>PDF sur itch.io (4 pages - 254 Ko)</figcaption>
</figure></p>
<p></a></p>
<p>Comme j'ai également testé le jeu hier avec quelques amis, voici quelques retours et suggestions ci-dessous.</p>
<h2>Avis global</h2>
<p>Peut-être parce que cela fait près de 2 ans que j'avais envie de tester ce jeu,
j'ai été légèrement déçu. Étant très fan de M. Harper, j'avais cru déceler à la lecture une nouvelle petite pépite de <em>gamedesign</em>, façon <a href="lady-blackbird.html">Lady Blackbird</a>.
Je prévoyais même déjà de l'employer pour faire découvrir le jeu de rôle à de nouveaux joueurs.</p>
<p>Au final, pour un jeu gratuit qui tient en 2 pages, c'est tout à fait honnête et nous avons passé un bon moment.
Mais le jeu manque un peu d'affinage pour s'attacher aux personnages et ajouter un peu d'enjeu à du <a href="https://fr.wikipedia.org/wiki/Porte-monstre-tr%C3%A9sor">PMT</a> basique. Et il peut vraiment s'avérer rêche pour des rôlistes débutants à l'imagination pas encore débridée.</p>
<p>Peut-être aurais-je dû prendre plus le temps de mitonner un donjon à explorer,
mais au final la partie a plutôt consisté en un enchaînement de péripéties / fuite en avant,
où le fun et les ressorts narratifs tenaient bien plus de l'improvisation des joueurs autour de la table
que des mécaniques de jeu.</p>
<figure>
<img alt="Au saloon" src="images/2022/11/at_the_saloon_by_fernand0fc_de957o6-fullview.jpg">
<figcaption><a href="https://www.deviantart.com/fernand0fc/art/At-the-saloon-861886230">At the saloon par Fernand0FC</a> - CC BY 3.0</figcaption>
</figure>
<h2>Règles</h2>
<p>Les règles sont très succinctes, et le rôle de la <strong>Vitesse</strong> en particulier n'y est pas absolument pas défini.
J'ai considéré pour ma part que cela déterminait à quel point un <em>breaker</em> était capable de faire preuve de réflexe ou non,
dans une situation (sans jet), ainsi également que l'ordre d'initiative / action lors d'un combat.</p>
<p>Par ailleurs les règles sont très simples et efficaces, fortement inspirées des jeux <a href="http://www.pbta.fr/apocalypse-2/">PbtA</a>.</p>
<h2>Questions de début de partie</h2>
<p>Comme j'ai trouvé le début de partie un peu ardu, pour plonger les joueurs dans l'ambiance
et leur souffler un peu d'inspiration quant aux personnages qu'ils devaient créer,
je vous proposer quelques question à poser à vos joueurs en début de partie,
en mode <em>world-building</em> où ils peuvent inventer tout ce qu'ils veulent sur l'univers dans leurs réponses :</p>
<ul>
<li>Pourquoi es-tu devenu <em>Breaker</em> ?</li>
<li>Qu'est-ce qui s'est mal passé lors de ta 1ère expédition ?</li>
<li>Quel autre membre de l'équipe t'a déjà sauvé la vie ?</li>
<li>Tu possèdes une compétence qui vous a sauvé la mise de manière inattendue lors de ta 1ère expédition : laquelle et comment ?</li>
<li>Tu te méfies d'un autre membre de l'équipe : qui et pourquoi ?</li>
<li>Parmi les étonnantes rencontres que vous avez fait dans cette 1ère Faille, quel élément de faune ou de flore t'a le plus surpris ?</li>
<li>Tu a déjà été témoin de l'influence de Kai Shira Kai : comment ?</li>
<li>Qu'as-tu ramené qui t'a rapporté gros suite à la 1ère expédition ?</li>
<li>Pourquoi as-tu des doutes sur les motivations de la FEMA à vous financer ?</li>
</ul>
<p>C'est bien sûr l'occasion pour la MJ de prendre des notes des réponses des joueurs,
pour ensuite introduire des éléments mentionnés dans l'exploration !</p>
<h2>Donjons</h2>
<p>John Harper cite déjà dans les règles l'excellent <a href="https://www.dungeoncontest.com/">One Page Dungeon Contest</a>
comme source potentielle de donjons à employer avec <em>World of Dungeons : Turbo Breakers</em>.
Le <a href="https://spielknights.gumroad.com/l/2022opdc">Compendium 2022</a> regorge par exemple d'inspirations.</p>
<p>Je vous recommande également ces autres sites pour vous fournir des idées de donjons :</p>
<ul>
<li><a href="https://drive.google.com/file/d/1OfubXUEbkBAH0cP49pTThLsyyOlcuVMO/view">The Riven Tower par Aaron Potter</a>, gagnant de l'OPDC 2020</li>
<li>quelques donjons du fantastique Michael Prescott traduits en français sur son site <a href="http://blog.trilemma.com/p/aventures-en-francais.html">trilemma.com</a></li>
<li>les <a href="https://www.elventower.com/isometric-maps/">cartes isométriques</a> et les <a href="https://www.elventower.com/one-page-dungeons/">one page dungeons</a> de Derek "ElvenTower" Ruiz</li>
<li>le générateur <a href="https://duvelmandice.itch.io/tomb-of-the-serpent-kings-handdrawn-iso-map">One Page Dungeon de watabou</a></li>
<li>le scénario "tutoriel" OSR <a href="https://www.whidou.fr/la-tombe-des-rois-serpents.html">La Tombe des Rois Serpents</a>, et sa <a href="https://watabou.itch.io/one-page-dungeon">carte isométrique</a></li>
<li>une sélection de meilleurs one-page-dungeons : <a href="https://blackcitadelrpg.com/one-page-dungeon/">A Dungeon Master’s Guide to the One-Page Dungeon</a> (en anglais)</li>
<li>un générateur de monstres : <a href="https://deep-fold.itch.io/book-of-monsters">Book of Monsters</a></li>
</ul>
<figure>
<img alt="One Page Donjon" src="images/2022/11/OPDC-2021-The-Eternal-Construction-Site-by-Gregor-Belogour.png">
<figcaption>One Page Donjon 2021 - The Eternal Construction Site by Gregor Belogour - CC BY-SA 3.0</figcaption>
</figure>
<h2>Musique</h2>
<p>J'ai beaucoup employé <a href="https://tabletopaudio.com">TableTopAudio</a> lors de cette partie,
ainsi que cette musique de poursuite : <a href="https://www.youtube.com/watch?v=_j8jUUzGqDo">Escape | RPG Chase Theme | Fantasy Music by The Seventh Midnight</a>.</p>
<figure>
<img alt="Arme blanche à fusion" src="images/2022/11/super_heated_by_fernand0fc_dc5a5wk-fullview.jpg">
<figcaption><a href="https://www.deviantart.com/fernand0fc/art/Super-Heated-734466404">Super-Heated par Fernand0FC</a> - CC BY 3.0</figcaption>
</figure>
<!-- Com'
* [x] https://lucas-c.itch.io/world-of-dungeons-turbo-breakers-fr
* [x] https://johnharper.itch.io/breakers (comment)
* [x] http://troplongpaslu.fr
* [x] https://www.casusno.fr/viewtopic.php?t=41349
* [ ] https://www.donjondudragon.fr
-->Le Mathctober de El Jj2022-10-25T20:00:00+02:002022-10-25T20:00:00+02:00Lucas Cimontag:chezsoi.org,2022-10-25:/lucas/blog/le-mathctober-de-el-jj.html<p><a href="https://www.youtube.com/channel/UCgkhWgBGRp0sdFy2MHDWfSg">El Jj</a> est une chaîne YouTube dédiée aux mathématiques que j'adore.</p>
<p>Cette année, son auteur s'est engagé dans un superbe projet : <a href="https://www.youtube.com/c/ElJj42/shorts"><strong>Mathctober</strong></a>,
où il diffuse chaque jour une nouvelle micro vidéo dédiée à un sujet mathématique.</p>
<p>Le résultat et splendide, je vous laisse en juger :</p>
<div class="grid">
<iframe width="240" height="329" src="https://www.youtube.com/embed/mi7aBEer4WM" title="31 - Farm" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/PN0bMaGzRwE" title="30 - Gear" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/xDoBoO9Ph1o" title="29 - Uh Oh" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/zYpM6GBH9xE" title="28 - Camping" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/AlIkbYhU8Lg" title="27 - Snack" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/iQAEXpwmEYs" title="26 - Ego" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/7KPw6zLCN6I" title="25 - Tempting" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/rcG3VzzVdw0" title="24 - Fairy" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/cps2NPrt82w" title="23 - Booger" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/8_8-jiYxru8" title="22 - Heist" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/cWhCVLLfBA8" title="21 - Bad Dog" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/Qiyzub6kx9g" title="20 - Bluff" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/wmQMxg9fsvQ" title="19 - Ponytail" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/xbYzn2DZo30" title="18 - Scrape" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/xqeATKTkQ50" title="17 - Salty" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/_DsXap_Xqyo" title="16 - Fowl" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/CQ_9llE23Lg" title="15 - Armadillo" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/_cm-vwV7fy0" title="14 - Empty" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/KpFyMrrFn-s" title="13 - Kind" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/TEac6x9YqAo" title="12 - Forget" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/EBZA0h6HiWQ" title="11 - Eagle" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/dklAqywVTP0" title="10 - Crabby" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/0-gXr3NW-CQ" title="09 - Nest" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/X3xN7HQREnQ" title="08 - Match" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/ZfT0Ux7G3s0" title="07 - Trip" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/czYrKcYSlcU" title="06 - Bouquet" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/ih9oNg1J3nM" title="05 - Flame" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/xmRWuk7swkc" title="04 - Scallop" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/G4zLgYtIIeU" title="03 - Bat" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/wVsOxf_sIwQ" title="02 - Scurry" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/tANSmlrywBs" title="01 - Gargoyle" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<p>El Jj est un professeur …</p><p><a href="https://www.youtube.com/channel/UCgkhWgBGRp0sdFy2MHDWfSg">El Jj</a> est une chaîne YouTube dédiée aux mathématiques que j'adore.</p>
<p>Cette année, son auteur s'est engagé dans un superbe projet : <a href="https://www.youtube.com/c/ElJj42/shorts"><strong>Mathctober</strong></a>,
où il diffuse chaque jour une nouvelle micro vidéo dédiée à un sujet mathématique.</p>
<p>Le résultat et splendide, je vous laisse en juger :</p>
<div class="grid">
<iframe width="240" height="329" src="https://www.youtube.com/embed/mi7aBEer4WM" title="31 - Farm" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/PN0bMaGzRwE" title="30 - Gear" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/xDoBoO9Ph1o" title="29 - Uh Oh" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/zYpM6GBH9xE" title="28 - Camping" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/AlIkbYhU8Lg" title="27 - Snack" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/iQAEXpwmEYs" title="26 - Ego" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/7KPw6zLCN6I" title="25 - Tempting" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/rcG3VzzVdw0" title="24 - Fairy" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/cps2NPrt82w" title="23 - Booger" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/8_8-jiYxru8" title="22 - Heist" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/cWhCVLLfBA8" title="21 - Bad Dog" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/Qiyzub6kx9g" title="20 - Bluff" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/wmQMxg9fsvQ" title="19 - Ponytail" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/xbYzn2DZo30" title="18 - Scrape" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/xqeATKTkQ50" title="17 - Salty" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/_DsXap_Xqyo" title="16 - Fowl" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/CQ_9llE23Lg" title="15 - Armadillo" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/_cm-vwV7fy0" title="14 - Empty" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/KpFyMrrFn-s" title="13 - Kind" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/TEac6x9YqAo" title="12 - Forget" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/EBZA0h6HiWQ" title="11 - Eagle" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/dklAqywVTP0" title="10 - Crabby" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/0-gXr3NW-CQ" title="09 - Nest" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/X3xN7HQREnQ" title="08 - Match" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/ZfT0Ux7G3s0" title="07 - Trip" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/czYrKcYSlcU" title="06 - Bouquet" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/ih9oNg1J3nM" title="05 - Flame" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/xmRWuk7swkc" title="04 - Scallop" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/G4zLgYtIIeU" title="03 - Bat" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/wVsOxf_sIwQ" title="02 - Scurry" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="240" height="329" src="https://www.youtube.com/embed/tANSmlrywBs" title="01 - Gargoyle" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div>
<p>El Jj est un professeur de mathématiques agrégé, travaillant dans un petit lycée de campagne.
Il est présent à plusieurs endroits du web, à commencer par son blog :
<a href="http://eljjdx.canalblog.com/pages/qui---que---quoi--/30788466.html">Choux Rom & co</a></p>
<style>
.grid {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
.grid > * {
display: block;
margin: .5rem;
}
</style>Rumble in the Dungeon2022-08-11T12:00:00+02:002022-08-11T12:00:00+02:00Lucas Cimontag:chezsoi.org,2022-08-11:/lucas/blog/rumble-in-the-dungeon.html<p><img alt="Logo du jeu" src="images/2022/08/rumble-in-the-dungeon-fan-cover.jpg"></p>
<p><em><center>(English speakers : go to <a href="https://boardgamegeek.com/thread/2915424/special-rooms-characters-abilities-exchange-charac">BoardGameGeek</a> to find the variant in English)</center></em></p>
<p><em><strong>Rumble in the Dungeon</strong></em> est ma dernière belle découverte ludique !</p>
<p>C'est un jeu très court (20-30 min par partie) pour 3 à 6 joueurs,
alliant hasard, tactique et un peu de bluff,
avec la possibilité d'enjoliver chaque action avec …</p><p><img alt="Logo du jeu" src="images/2022/08/rumble-in-the-dungeon-fan-cover.jpg"></p>
<p><em><center>(English speakers : go to <a href="https://boardgamegeek.com/thread/2915424/special-rooms-characters-abilities-exchange-charac">BoardGameGeek</a> to find the variant in English)</center></em></p>
<p><em><strong>Rumble in the Dungeon</strong></em> est ma dernière belle découverte ludique !</p>
<p>C'est un jeu très court (20-30 min par partie) pour 3 à 6 joueurs,
alliant hasard, tactique et un peu de bluff,
avec la possibilité d'enjoliver chaque action avec un peu de <em>storytelling</em> amusant.</p>
<p>Plutôt que vous présenter le jeu plus en détails, je vous invite à lire ces articles pour le découvrir :</p>
<ul>
<li><a href="https://www.vindjeu.eu/2019/03/05/rumble-in-the-dungeon/">Présentation du jeu par Vin d'jeu</a></li>
<li><a href="https://boardgaming.com/games/board-games/rumble-in-the-dungeon">Rumble in the Dungeon review @ boardgaming.com</a> (anglais)</li>
</ul>
<p>Je ne l'ai même pas acheté, je l'ai emprunté à la bibliothèque de mon patelin !
Pensez-y : emprunter des jeux en bibliothèque c'est gratuit, écolo, et ça prend moins de place chez vous 😊</p>
<p>Comme j'ai beaucoup aimé ce jeu, je vous propose ces variantes que j'ai conçu :</p>
<div class="side-by-side">
<a href="images/2022/08/RumbleInTheDungeon-variantes-FR.pdf">
<figure>
<img alt="PDF preview" src="images/2022/08/rumble-in-the-dungeon-pdf-thumbnail-FR.png">
<figcaption>PDF des règles en français (2 pages - 160 Ko)</figcaption>
</figure>
</a>
<a href="images/2022/08/RumbleInTheDungeon-variants-EN.pdf">
<figure>
<img alt="PDF preview" src="images/2022/08/rumble-in-the-dungeon-pdf-thumbnail-EN.png">
<figcaption>PDF rules in English (2 pages - 160 Ko)</figcaption>
</figure>
</a>
</div>
<p>J'espère que ces variantes vous plairont !
N'hésitez pas à me faire vos retours en commentaire.</p>
<p>Merci aux membres de l'<a href="https://laubergedesreveurs.forumactif.com/">Auberge des Rêveurs</a> d'avoir bien voulu tester ces règles
et de m'avoir fait des retours très constructifs !</p>
<p>Vous pouvez retrouver toutes mes autres variantes de jeux ici : <a href="tag/variante.html">tag <strong>variante</strong></a>.</p>
<p><strong>[EDIT 2023/03/31]</strong> : j'ai aussi conçu <a href="rumble-in-the-house.html">quelques variantes pour <em>Rumble in the House</em></a></p>
<style>
article img { max-height: 20rem; }
@media (min-width:768px) {
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
}
.side-by-side > * { margin: 0 2rem; }
}
</style>
<!-- Com'
* [x] https://boardgamegeek.com/thread/2915424/special-rooms-characters-abilities-exchange-charac
* [x] https://www.trictrac.net/jeu-de-societe/rumble-in-the-dungeon/ressources & https://www.trictrac.net/forum/sujet/variantes-pour-rumble-in-the-dungeon
-->3 nouveaux scenarios pour Run. Die. Repeat.2022-07-26T09:00:00+02:002022-07-26T09:00:00+02:00Lucas Cimontag:chezsoi.org,2022-07-26:/lucas/blog/3-nouveaux-scenarios-pour-run-die-repeat.html<p>Voici 3 nouveaux scénarios pour <a href="https://chezsoi.org/lucas/blog/images/jdr/RunDieRepeat-FR.pdf"><em>Run. Die. Repeat.</em></a>,
le jeu de rôle monopage de <strong>Labrys Games</strong> où l'on décède à la chaîne :</p>
<p><a href="https://lucas-c.github.io/jdr/RunDieRepeat/RunDieRepeat-scenarios2-FR.pdf">
<br>
<figure>
<img alt="3 scénarios pour Run. Die. Repeat." src="images/2022/07/blondbraid-Pirate-Skeleton.png">
<figcaption>3 scénarios pour Run. Die. Repeat.<br>(5 pages, 1,33 Mo)</figcaption>
</figure></p>
<p></a></p>
<p>Au programme :</p>
<ul>
<li><strong>Parasite</strong> : incarnez un parasite microscopique tentant de s'évader d'un laboratoire !</li>
<li><strong>Dernier wagon pour l’amour …</strong></li></ul><p>Voici 3 nouveaux scénarios pour <a href="https://chezsoi.org/lucas/blog/images/jdr/RunDieRepeat-FR.pdf"><em>Run. Die. Repeat.</em></a>,
le jeu de rôle monopage de <strong>Labrys Games</strong> où l'on décède à la chaîne :</p>
<p><a href="https://lucas-c.github.io/jdr/RunDieRepeat/RunDieRepeat-scenarios2-FR.pdf">
<br>
<figure>
<img alt="3 scénarios pour Run. Die. Repeat." src="images/2022/07/blondbraid-Pirate-Skeleton.png">
<figcaption>3 scénarios pour Run. Die. Repeat.<br>(5 pages, 1,33 Mo)</figcaption>
</figure></p>
<p></a></p>
<p>Au programme :</p>
<ul>
<li><strong>Parasite</strong> : incarnez un parasite microscopique tentant de s'évader d'un laboratoire !</li>
<li><strong>Dernier wagon pour l’amour</strong> : vous avez eu un coup de foudre pour une jeune femme, mais vous risquez de ne plus jamais la revoir : déclarez-lui vite votre flamme !</li>
<li><strong>Pirates, vaudou et île au trésor</strong> : pirate revenu d'entre les morts grâce à une malédiction vaudoue, vous devez fuir l'île au trésor avant que la lune ne soit au zenith !</li>
</ul>
<p>Tous les articles de ce blog à propos de <em>Run Die Repeat</em> : <a href="tag/run-die-repeat.html">tag run-die-repeat</a>.</p>
<!-- Com'
* [x] https://www.scenariotheque.org/Document/info_doc.php?id_doc=10619
-> référence : https://lucas-c.github.io/jdr/RunDieRepeat/RunDieRepeat-scenarios2-FR.pdf
* [x] https://www.casusno.fr/viewtopic.php?p=2103886
* [x] Discord TLPL
-->Lady Blackbird2022-07-25T20:00:00+02:002022-07-25T20:00:00+02:00Lucas Cimontag:chezsoi.org,2022-07-25:/lucas/blog/lady-blackbird.html<figure>
<img alt="Lady Blackbird" src="images/2022/07/LadyBlackbird-by-Midjourney.png">
<figcaption>Lady Blackbird - illustration générée par <a href="https://www.midjourney.com">Midjourney</a></figcaption>
</figure>
<p>Mon premier brouillon d'article sur ce jeu date d'il y a plus de 3 ans,
et il figure en bonne place dans <a href="pages/jdr-favoris.html">mes jeux de de rôle favoris</a>.
Après l'avoir fait jouer de très nombreuses fois, je veux évoquer aujourd'hui le génial <a href="http://www.onesevendesign.com/ladyblackbird/">Lady Blackbird …</a></p><figure>
<img alt="Lady Blackbird" src="images/2022/07/LadyBlackbird-by-Midjourney.png">
<figcaption>Lady Blackbird - illustration générée par <a href="https://www.midjourney.com">Midjourney</a></figcaption>
</figure>
<p>Mon premier brouillon d'article sur ce jeu date d'il y a plus de 3 ans,
et il figure en bonne place dans <a href="pages/jdr-favoris.html">mes jeux de de rôle favoris</a>.
Après l'avoir fait jouer de très nombreuses fois, je veux évoquer aujourd'hui le génial <a href="http://www.onesevendesign.com/ladyblackbird/">Lady Blackbird (site officiel)</a> de <a href="tag/john-harper.html">John Harper</a>,
dont voici le <em>pitch</em> :</p>
<blockquote>
<p>Lady Blackbird fuit un mariage arrangé.
Elle a engagé un navire-céleste, La Chouette, pour l’emmener loin de son palais,
vers les cieux lointains des Vestiges. Ainsi, elle pourra rejoindre celui qui fut son amant secret, le Roi-pirate Uriah Flint.</p>
<p>Cependant, à mi-chemin, La Chouette s’est faite prendre en chasse par le croiseur impérial La Main Chagrine
pour avoir commis le crime de voler sous un faux pavillon.</p>
<p>Comment Lady Blackbird et ses compagnons échapperont à La Main Chagrine?
Quels dangers les attendent sur leur chemin ?
Trouveront-ils le repaire secret du Roi-pirate ?</p>
</blockquote>
<figure>
<img alt="Le navire La Chouette" src="images/2022/07/Relentless666_TheOwl-color.png">
<figcaption>Le navire La Chouette - <a href="https://www.deviantart.com/relentless666/art/The-Owl-color-243812175">The Owl par Relentless</a></figcaption>
</figure>
<h2>Où, quoi, comment</h2>
<p>Lady Blackbird c'est un JdR <em>clefs en main</em>, <strong>gratuit</strong>, pour 3 à 5 joueurs avec des personnages prétirés
possédant de forts liens narratifs entre eux, et qui débute <em>in media res</em>, au cœur de l'action.
Les règles sont très simples à prendre en main et une partie peut très bien ne durer qu'une ou deux heures,
ou bien donner lieu à une véritable campagne !</p>
<p>Tout ça en seulement 16 pages, conçu en 2009 par John Harper, également créateur de <a href="https://fr.wikipedia.org/wiki/Blades_in_the_Dark"><em>Blades in the Dark</em></a>.</p>
<p>Le jeu a été traduit en 2012 par Les Écuries d'Augias,
dont le site web (<a href="http://www.ecuries-augias.fr">www.ecuries-augias.fr</a>) est aujourd'hui hors ligne.
Le jeu reste néanmoins disponible en téléchargement sur le site de l'éditeur SYCKO :
<a href="https://www.sycko.fr/ecuries-augias">https://www.sycko.fr/ecuries-augias</a>.</p>
<p>Le ZIP contient également deux aventures <em>spin-offs</em>,
<em>Sombres Cieux</em> & <em>Au Delà des tréfonds</em>, ainsi qu'un guide de conseils pour MJs,
<em>Lady Blackbird pour les nuls</em> : merci à <a href="https://www.legrog.org/biographies/gauthier-go-t-lion">Gauthier 'GO@T' Lion</a> pour avoir conçu, traduit et/ou rassemblé tout ce matériel de jeu supplémentaire !</p>
<p>Et il existe encore d'autres extensions, officielles ou créées par des fans :</p>
<ul>
<li>
<p>John Harper a conçu <a href="https://ladyblackbird.org">deux autres jeux au même format, disponible en VO sur ladyblackbird.org</a>. Ils ont été traduits par Yragaël Malbos & Khelren <a href="http://www.mediafire.com/file/d3kbq66x606lakn/LBB+chapitre+2+-+version+finale.pdf">Magister Lor (PDF 3,6Mo)</a> & <a href="http://www.mediafire.com/file/5m7skad15a33dkv/LBB+chapitre+3+-+version+finale.pdf">Lord Scurlock (PDF 3,4Mo)</a></p>
</li>
<li>
<p>un rôliste français, <em>Qui Revient de loin</em>, a conçu un <em>chapitre 0</em> : <a href="http://qui.revient.de.loin.blog.free.fr/index.php?post/2015/Lady-Blackbird%2C-chapitre-0%3A-Par-del%C3%A0-les-Vestiges">Par-delà les Vestiges</a></p>
</li>
<li>
<p><a href="tag/damien-rahyll-c.html">Damien « Rahyll » C.</a> a conçu un chapitre 4 : <strong>La Marque Noire</strong> - <a href="https://drive.google.com/file/d/0B35XI6tSb9fjSDdISW11WTIwWXZKUGNmNFU4Q2UxLTJSbm5n/view?resourcekey=0-0JDTCTtAVrpX-ml-ZoFQ9Q">lien Google Drive du PDF</a></p>
</li>
<li>
<p>il existe enfin une aide de jeu de 27 pages décrivant plus en détails le monde de <em>L'Indomptable Bleue</em>, <a href="https://app.box.com/s/yj9y4ascil">Tales From the Wild Blue Yonder: The Lady Blackbird Companion (PDF 7,5 Mo)</a>, non traduit à ma connaisance (<a href="https://archive.org/details/blackbirdcompanion/page/n1/mode/2up">lien alternatif de téléchargement</a>)</p>
</li>
</ul>
<div class="side-by-side">
<figure>
<img alt="Lady Blackbird" src="images/2022/07/LadyBlackbird.png">
<figcaption>Lady Blackbird - Illustration de Laurent Pellicano</figcaption>
</figure>
<figure>
<img alt="Snargle" src="images/2022/07/Snargle.png">
<figcaption>Snargle - Illustration de Laurent Pellicano</figcaption>
</figure>
</div>
<h2>Pourquoi ça déchire</h2>
<ul>
<li>🤗 c'est un jeu très accessible : les règles sont courtes, simples et on peut démarrer une partie en moins de 10 minutes. Idéal pour faire découvrir le jeu de rôle à de nouveaux joueurs, où pour se lancer comme MJ ! 💖</li>
<li>🕵️😲 les personnages prétirés sont conçus pour provoquer des interactions et de révélations de secrets entre les joueurs 💕</li>
<li>🎲💬 la mécanique de jeu encourage les joueurs à accomplir les objectifs individuels de leurs personnages, faisant ainsi avancer l'intrigue 🧭</li>
<li>🔧⚙️🚂 c'est <strong>steampunk</strong> 😍</li>
</ul>
<h2>Mes conseils</h2>
<ul>
<li>parmi toutes les extensions, je recommande surtout de lire les précieux conseils de <em>Lady Blackbird pour les nuls</em></li>
<li>comme dans <em>Blades in the Dark</em>, le jeu encourage à poser beaucoup de questions aux joueurs et ainsi co-construire l'univers de l'histoire tous ensemble : exploitez-ça à fond !</li>
<li>avant la partie, préparez quelques obstacles & péripéties supplémentaires pour compléter les suggestions de la page 10. Exemples :<ul>
<li>une entrave magnétique bloque le gouvernail de La Chouette</li>
<li>les soldats de l'empire emploient un mégaphone assourdissant qui empêche d'employer la magie</li>
<li>Carlowe accompagne un chasseur de prime qu'il a embauché pour retrouver Lady Blackbird</li>
</ul>
</li>
</ul>
<figure>
<img alt="Uriah Flint" src="images/2022/07/UriahFlint.webp">
<figcaption>Uriah Flint - <a href="https://www.artstation.com/artwork/PorqL">Illustration d'Aleksey Bayura : <em>Collard - "The Warrior"</em></a></figcaption>
</figure>
<h2>Illustrations & musique</h2>
<p>Voici un tableau Pinterest collectant quelques illustrations adaptées au jeu :
<a href="https://www.pinterest.fr/drmaxkurt/lady-blackbird-characters/">Lady BlackBird illustrations</a>.</p>
<p>Et une <em>playlist</em> YouTube compilant des morceaux d'ambiance <em>steampunk</em> :
<a href="https://www.youtube.com/playlist?list=PLLgE-ga3W_kYJ8YLP6kAusQmo7k5FcQ8u">Lady Blackbird TTRPG</a>.</p>
<p>Je l'ai adapté d'une <a href="https://www.youtube.com/playlist?list=PL5kizg8B3iozcKfu6jSG8t11EG2n9HmQ2">playlist existante</a>,
en y ajoutant quelques morceaux, notamment quelques uns issus de <a href="https://www.supergiantgames.com/games/bastion/">l'excellente bande son du jeu vidéo Bastion</a>.</p>
<h2>Hacks</h2>
<p>Lady Blackbird a donné naissance à un certain nombre de <em>hacks</em>, ou de jeux inspirés (comme <a href="la-brigade-du-chaos.html">La Brigade du Chaos</a> de Grant Howitt).
Je n'ai pas encore eu l'occasion d'en tester un seul, mais voici une petite sélection :
<br></p>
<div class="side-by-side">
<a href="images/2022/07/Jedi-Blackbird.pdf">
<figure>
<img alt="Jedi Blackbird" src="images/2022/07/Jedi-Blackbird.png">
<figcaption><b>Jedi Blackbird</b> : une version Star Wars</figcaption>
</figure>
</a>
<a href="images/2022/07/Electric-Sheep.pdf">
<figure>
<img alt="Electric Sheep" src="images/2022/07/Electric-Sheep.png">
<figcaption><b>Electric Sheep</b> : une version cyberpunk</figcaption>
</figure>
</a>
<a href="images/2022/07/Old-Mesilla.pdf">
<figure>
<img alt="Old Mesilla" src="images/2022/07/Old-Mesilla.png">
<figcaption><b>Old Mesilla</b> : incarnez Billy the Kid et son gang</figcaption>
</figure>
</a>
</div>
<p><br></p>
<div class="side-by-side">
<a href="images/2022/07/Alien-Survivor.pdf">
<figure>
<img alt="Alien Survivor" src="images/2022/07/Alien-Survivor.png">
<figcaption><b>Alien Survivor</b> : une adaptation inspirée du film Alien</figcaption>
</figure>
</a>
<a href="images/2022/07/Castle-Blackbird.pdf">
<figure>
<img alt="Castle Blackbird" src="images/2022/07/Castle-Blackbird.png">
<figcaption><b>Castle Blackbird</b> : une version gothique avec des chasseurs de montres</figcaption>
</figure>
</a>
<a href="images/2022/07/Death-School.pdf">
<figure>
<img alt="Death School" src="images/2022/07/Death-School.png">
<figcaption><b>Death School</b> : incarnez des forces spéciales de la CIA</figcaption>
</figure>
</a>
</div>
<p>Aucun n'a été traduit à ma connaissance, malheureusement.</p>
<p>N'hésitez pas à laisser un commentaire pour signaler un lien mort ou une ressource supplémentaire pour ce jeu !</p>
<!--
Anecdotes de partie avec @Laetitia, @Simon, @Cedric :
- food truck spacial
- Holas passé par-dessus bord par Naomi sur ordre de la Lady
- arnaque épique de Kale envers Lady Blackbird
- baston générale au pub du vieux Flint suite au vol d'une carte à un truand
- course de Gravluks
-->
<style>
@media (min-width:768px) {
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
}
.side-by-side > * { margin: 0 2rem; }
}
</style>
<!-- Com'
* [x] https://forums.ffjdr.org/t/lady-blackbird-un-excellent-jdr-gratuit-ideal-pour-des-debutants/1256
* [x] https://www.casusno.fr/viewtopic.php?p=2103889
* [x] https://opale-roliste.com/forum/ressources/debuter-jeu-role/lady-blackbird-excellent-jdr-gratuit-ideal-debutants
* [x] Discords: Ludochaordic, CestPadDuJdr, PTGPTB, L'AubergeDesReveurs
-->Financez l'Epine Noire !2022-06-27T13:35:00+02:002022-06-27T13:35:00+02:00Lucas Cimontag:chezsoi.org,2022-06-27:/lucas/blog/financez-lepine-noire.html<p>Aujourd'hui, petit article pour vous présenter le projet d'une amie :</p>
<div class="brand">L'Epine Noire</div>
<p><img alt="Julie et l'atelier" src="images/2022/06/l-epine-noire.jpg"></p>
<p><br>
L'Epine Noire, c'est <strong>un atelier mécatronique, verre et bronze qui propose de l'artisanat d'art de qualité mêlant nouvelles technologies et artisanat ancien</strong>.</p>
<p>Un projet de financement participatif vient d'être lancé pour soutenir l'installation de cet atelier à <strong>Paimpont …</strong></p><p>Aujourd'hui, petit article pour vous présenter le projet d'une amie :</p>
<div class="brand">L'Epine Noire</div>
<p><img alt="Julie et l'atelier" src="images/2022/06/l-epine-noire.jpg"></p>
<p><br>
L'Epine Noire, c'est <strong>un atelier mécatronique, verre et bronze qui propose de l'artisanat d'art de qualité mêlant nouvelles technologies et artisanat ancien</strong>.</p>
<p>Un projet de financement participatif vient d'être lancé pour soutenir l'installation de cet atelier à <strong>Paimpont</strong>, en Bretagne.</p>
<p><img alt="Carte localisant Paimpont" src="images/2022/06/Paimpont-carte.png"></p>
<p>Cet atelier, ce sera celui de <strong>Julie Balsaux</strong>, maître en artisanat d'art verrier et bronzier et artiste plascticienne.</p>
<p>Après 10 ans de recherches, d'expositions, d'apprentissage du verre et du bronze en fonderie d'art,
son but est de créer un nouvel espace de création multimédias et multimatériaux.</p>
<p>Voici quelques unes de ses créations :</p>
<p><img alt="Projet Lumen" src="images/2022/06/l-epine-noire-projet-lumen.jpg"></p>
<p><img alt="Bijoux les Sept Soeurs" src="images/2022/06/l-epine-noire-bijoux-sept-soeurs.jpg"></p>
<p><img alt="Bronze" src="images/2022/06/l-epine-noire-bronze.jpg"></p>
<p><img alt="Aquarelles" src="images/2022/06/l-epine-noire-aquarelles.jpg"></p>
<p>Pour plus d'informations, plus de photos de son travail, et pour soutenir le projet,
rendez-vous sur la page <strong>Ulule</strong> :</p>
<div class="brand"><a href="https://fr.ulule.com/l-epine-noire-/">L'Epine Noire</a></div>
<style>
.brand {
text-align: center;
font-size: 3rem;
font-weight: bold;
margin: 3rem auto;
}
</style>Deploying AWS API Gateway static endpoints using Terraform2022-05-04T20:00:00+02:002022-05-04T20:00:00+02:00Lucas Cimontag:chezsoi.org,2022-05-04:/lucas/blog/deploying-aws-api-gateway-static-endpoints-using-terraform.html<p>Recently at work, at <a href="https://jobs.connect-tech.sncf">SNCF Connect & Tech</a>,
we needed to expose some static documents as HTTP endpoints:
a <code>GET /version</code> that would provide some information about the application version as JSON,
and a <code>GET /openapi/yaml</code> that would return the <a href="https://swagger.io/specification/">OpenAPI 3 specification</a> of our HTTP API as YAML.</p>
<p>We …</p><p>Recently at work, at <a href="https://jobs.connect-tech.sncf">SNCF Connect & Tech</a>,
we needed to expose some static documents as HTTP endpoints:
a <code>GET /version</code> that would provide some information about the application version as JSON,
and a <code>GET /openapi/yaml</code> that would return the <a href="https://swagger.io/specification/">OpenAPI 3 specification</a> of our HTTP API as YAML.</p>
<p>We thought about several solutions, like deploying a lambda or exposing data from a S3 bucket using API Gateway,
but given our needs and our existing technical stack, the cheapest & simplest solution (<a href="https://en.wikipedia.org/wiki/KISS_principle">KISS</a>)
was to use <a href="https://docs.amazonaws.cn/en_us/apigateway/latest/developerguide/how-to-mock-integration.html">API Gateway Mock integrations</a>.</p>
<p><img alt="Terraform and API Gateway logos" src="images/2022/05/terraform-plus-api-gateway.png"></p>
<p>As I haven't found much documentation about that while I was setting up those endpoints, especially to do so using <a href="https://www.terraform.io/">Terraform</a>,
I thought it may be useful to share some code snippets in this article.</p>
<p>Our application is deployed using Terraform, which configure AWS API Gateway endpoints
<a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-import-api.html">using an OpenAPI specification</a>. I won't get into the details on how to set up and configure those tools,
and directly jump straight to <a href="https://github.com/hashicorp/hcl#hcl">HCL code</a>:</p>
<div class="highlight"><pre><span></span><code><span class="nb">locals</span> <span class="p">{</span>
<span class="nb">open_api_spec</span> <span class="o">=</span> <span class="p">{</span>
<span class="err">openapi</span> <span class="p">:</span> <span class="s2">"3.0.1"</span>
<span class="err">info</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">title</span> <span class="p">:</span> <span class="s2">"My pretty API"</span>
<span class="err">version</span> <span class="p">:</span> <span class="s2">"1.0"</span>
<span class="p">}</span>
<span class="err">paths</span> <span class="p">:</span> <span class="p">{</span>
<span class="s2">"/version"</span> <span class="o">:</span> <span class="p">{</span>
<span class="err">get</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">responses</span> <span class="p">:</span> <span class="p">{</span>
<span class="m">200</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">description</span> <span class="p">:</span> <span class="s2">"200 response"</span>
<span class="p">}</span>
<span class="p">}</span><span class="c1"></span>
<span class="c1"> # Doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-integration.html</span>
<span class="err">x-amazon-apigateway-integration</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">type</span> <span class="p">:</span> <span class="s2">"mock"</span>
<span class="err">requestTemplates</span> <span class="p">:</span> <span class="p">{</span>
<span class="s2">"application/json"</span> <span class="o">:</span> <span class="nf">jsonencode</span><span class="p">({</span>
<span class="err">statusCode</span> <span class="p">:</span> <span class="m">200</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="err">responses</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">default</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">statusCode</span> <span class="p">:</span> <span class="m">200</span>
<span class="err">responseTemplates</span> <span class="p">:</span> <span class="p">{</span>
<span class="s2">"application/json"</span> <span class="o">:</span> <span class="nf">jsonencode</span><span class="p">({</span>
<span class="s2">"app_name"</span> <span class="o">:</span> <span class="nv">var.app_name</span>
<span class="s2">"aws_region"</span> <span class="o">:</span> <span class="nv">var.aws_region</span>
<span class="s2">"environment"</span> <span class="o">:</span> <span class="nv">var.environment</span>
<span class="s2">"git_branch"</span> <span class="o">:</span> <span class="nv">var.git_branch</span>
<span class="s2">"git_ref"</span> <span class="o">:</span> <span class="nv">var.git_ref</span>
<span class="s2">"last_deployment_time"</span> <span class="o">:</span> <span class="nf">timestamp</span><span class="p">()</span>
<span class="s2">"terraform_workspace"</span> <span class="o">:</span> <span class="nv">terraform.workspace</span>
<span class="s2">"app_version"</span> <span class="o">:</span> <span class="nv">var.app_version</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="s2">"/openapi/yaml"</span> <span class="o">:</span> <span class="p">{</span>
<span class="err">get</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">responses</span> <span class="p">:</span> <span class="p">{</span>
<span class="m">200</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">description</span> <span class="p">:</span> <span class="s2">"200 response"</span><span class="p">,</span>
<span class="err">headers</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">Content-Type</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">type</span> <span class="p">:</span> <span class="s2">"string"</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span><span class="c1"></span>
<span class="c1"> # Doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-integration.html</span>
<span class="err">x-amazon-apigateway-integration</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">type</span> <span class="p">:</span> <span class="s2">"mock"</span>
<span class="err">requestTemplates</span> <span class="p">:</span> <span class="p">{</span>
<span class="s2">"application/json"</span> <span class="o">:</span> <span class="nf">jsonencode</span><span class="p">({</span>
<span class="err">statusCode</span> <span class="p">:</span> <span class="m">200</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="err">responses</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">default</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">statusCode</span> <span class="p">:</span> <span class="m">200</span>
<span class="err">responseTemplates</span> <span class="p">:</span> <span class="p">{</span>
<span class="s2">"application/yaml"</span> <span class="o">:</span> <span class="nf">file</span><span class="p">(</span><span class="s2">"path/to/openapi.yaml"</span><span class="p">)</span>
<span class="p">}</span>
<span class="err">responseParameters</span> <span class="p">:</span> <span class="p">{</span>
<span class="s2">"method.response.header.Content-Type"</span> <span class="o">:</span> <span class="s2">"'application/yaml'"</span><span class="c1"> # default is application/json</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="err">x-amazon-apigateway-request-validators</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">all</span> <span class="p">:</span> <span class="p">{</span>
<span class="err">validateRequestBody</span> <span class="p">:</span> <span class="no">true</span><span class="p">,</span>
<span class="err">validateRequestParameters</span> <span class="p">:</span> <span class="no">true</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kr">resource</span> <span class="nc">"aws_api_gateway_rest_api"</span> <span class="nv">"api"</span> <span class="p">{</span>
<span class="na">name</span> <span class="o">=</span> <span class="nv">var.api_name</span>
<span class="na">body</span> <span class="o">=</span> <span class="nf">jsonencode</span><span class="p">(</span><span class="nv">local.open_api_spec</span><span class="p">)</span>
<span class="nb">endpoint_configuration</span> <span class="p">{</span>
<span class="na">types</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"PRIVATE"</span><span class="p">]</span>
<span class="na">vpc_endpoint_ids</span> <span class="o">=</span> <span class="p">[</span><span class="nv">var.vpce_id</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>This makes use of the <a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/api_gateway_rest_api"><code>aws</code> provider for Terraform</a>.
I did not provide a full working example, an choose to instead only focus on the parts relevant to the subject.
Note also that the above configuration does not include any authentication method, so the endpoints will be public.</p>
<p>The beauty of this approach is that a few lines of <a href="https://en.wikipedia.org/wiki/Infrastructure_as_code">Infrastructure As Code (IAC)</a>
quickly gets you an exposed endpoint, with no need to store JSON/YAML files on any file repository.
The endpoints content will get updated each time you perform a new <code>terraform apply</code>,
and that's it. I guess a <a href="https://swagger.io/tools/swagger-ui/">Swagger UI HTML page</a> could even be served using the same approach.</p>
<p>Finally, this recipe could probably be improved by inserting parts of the <code>openapi.yaml</code> spec file
into the <code>open_api_spec</code> Terraform local value, to avoid duplicating some information.</p>
<p>I hope those code snippets will help some fellow developers.
Have fun building HTTP APIs! 🏗️ ☁</p>Crafting nonograms that reveal QR codes2022-05-01T21:30:00+02:002022-05-01T21:30:00+02:00Lucas Cimontag:chezsoi.org,2022-05-01:/lucas/blog/crafting-nonograms-that-reveal-qr-codes.html<p>Today I made a small addition to a Javascript library I sometimes use to generate <a href="https://en.wikipedia.org/wiki/Nonogram">nonograms</a>.</p>
<p>This tool can now build a solvable grid in the form of a valid <a href="https://en.wikipedia.org/wiki/QR_code">QR Code</a> that, once decoded, reveals some text:</p>
<p><img alt="Animation of a nonogram grid being solved and revealing a QR code" src="images/2022/05/qrcode.gif"></p>
<p>The nonogram size will depend on the hidden text size,
but even …</p><p>Today I made a small addition to a Javascript library I sometimes use to generate <a href="https://en.wikipedia.org/wiki/Nonogram">nonograms</a>.</p>
<p>This tool can now build a solvable grid in the form of a valid <a href="https://en.wikipedia.org/wiki/QR_code">QR Code</a> that, once decoded, reveals some text:</p>
<p><img alt="Animation of a nonogram grid being solved and revealing a QR code" src="images/2022/05/qrcode.gif"></p>
<p>The nonogram size will depend on the hidden text size,
but even with a short text, it will be a difficult one to solve manually.</p>
<p>To find more about it: <a href="https://lucas-c.github.io/Nonogram/#qrcode-nonogram">Nonogram JS demo page</a>.</p>
<p>Note that I've written about open-source generators to build pen & paper puzzles in the past (in French):</p>
<ul>
<li><a href="https://linuxfr.org/news/generateurs-de-puzzles-libres">Générateurs de puzzles open source (2018)</a></li>
<li><a href="nonograms-topolokus-et-compagnie.html">Nonograms, Topolokus et compagnie (2020)</a></li>
</ul>fpdf2.5.2 : SVG support and borb2022-04-24T13:00:00+02:002022-04-24T13:00:00+02:00Lucas Cimontag:chezsoi.org,2022-04-24:/lucas/blog/fpdf2-5-2-svg-support-and-borb.html<p><code>fpdf2</code> is a simple & fast PDF creation library for Python that I have been maintaining since mid-2020.</p>
<p>In this article, I'm going to present some of the new features that landed since <a href="hacktoberfest-on-fpdf2.html">my last post on the subject</a>.
Hence, this will cover versions <strong>2.5.0</strong>, <strong>2.5.1</strong> & <strong>2 …</strong></p><p><code>fpdf2</code> is a simple & fast PDF creation library for Python that I have been maintaining since mid-2020.</p>
<p>In this article, I'm going to present some of the new features that landed since <a href="hacktoberfest-on-fpdf2.html">my last post on the subject</a>.
Hence, this will cover versions <strong>2.5.0</strong>, <strong>2.5.1</strong> & <strong>2.5.2</strong> of <code>fpdf2</code>.
I will also perform a quick comparison with the <a href="https://borbpdf.com/">borb</a> library.</p>
<p><a href="https://github.com/pyfpdf/fpdf2/">https://github.com/pyfpdf/fpdf2/</a> <a href="https://pypi.python.org/pypi/fpdf2"><img alt="Pypi latest version" src="https://img.shields.io/pypi/v/fpdf2.svg"></a>
Doc: <a href="https://pyfpdf.github.io/fpdf2/">https://pyfpdf.github.io/fpdf2/</a></p>
<h2>Support for SVG (Scalable Vector Graphics)</h2>
<p>Thanks to <a href="https://github.com/torque">@torque</a> who worked on this over several months
and produced very high quality code, <strong><code>fpdf2</code> now supports embedding SVG files</strong>!</p>
<p>He actually started by implementing a very large part of the <strong>PDF drawing API</strong>,
allowing to compose arbitrary sequences of paths, lines and curves:
<a href="https://pyfpdf.github.io/fpdf2/Drawing.html">fpdf2 Drawing API documentation</a>.</p>
<p>As a direct consequence, it became possible to build a direct SVG-to-PDF converter, which he did!</p>
<p>SVG files can now be directly added to a PDF file using the <a href="fpdf/fpdf.html#fpdf.fpdf.FPDF.image">image()</a> method:
<a href="https://pyfpdf.github.io/fpdf2/SVG.html">fpdf2 SVG documentation</a>.</p>
<p>For security reasons, with the addition of this feature we added a new dependency to <code>fpdf2</code>:
<a href="https://pypi.org/project/defusedxml/">defusedxml</a>, used to check that embedding a SVG file does not trigger a denial of service,
for example a <a href="https://github.com/PyFPDF/fpdf2/blob/master/test/image/test_vector_image.py#L77"><em>Billion laughs</em> attack</a>.</p>
<p>While the SVG converter has <a href="https://pyfpdf.github.io/fpdf2/SVG.html#currently-unsupported-notable-svg-features">some limitations</a>,
it is able, for example, to perfectly render the famous SVG example <a href="https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg">Ghostscript_Tiger.svg</a>
to PDF: <a href="images/2022/04/Ghostscript_Tiger.pdf">Ghostscript_Tiger.pdf</a>.</p>
<p><a href="images/2022/04/Ghostscript_Tiger.pdf"><img alt="Ghostscript Tiger PDF preview" src="images/2022/04/Ghostscript_Tiger.png"></a></p>
<h2>Other features</h2>
<p>Two other useful features were added by <a href="https://github.com/gmischler">Georg Mischler</a>:</p>
<ul>
<li>support for soft-hyphen (<code>\u00ad</code>) break in <code>write()</code>, <code>cell()</code> & <code>multi_cell()</code>,
<em>cf.</em> <a href="https://pyfpdf.github.io/fpdf2/LineBreaks.html">documentation on line breaks</a></li>
<li>new parameters <code>new_x</code> and <code>new_y</code> were introduced for <code>cell()</code> and <code>multi_cell()</code> methods,
in order to make cursor position after cell-rendering a lot more intuitive & user-friendly,
<em>cf.</em> <a href="https://pyfpdf.github.io/fpdf2/Text.html#change-in-current-position">related documentation</a></li>
</ul>
<p><a href="https://github.com/gmischler">Georg Mischler</a> also took some time to revise the whole structure of the documentation,
making it a lot more user-friendly. Thanks! 🙏</p>
<p>I also contributed a few extra functionalities:</p>
<ul>
<li>a new <code>add_highlight()</code> method to insert <mark>highlight annotations</mark>: <a href="https://pyfpdf.github.io/fpdf2/Annotations.html#highlights">documentation</a></li>
<li>support for new PDF properties: <code>.text_mode</code> (<a href="https://pyfpdf.github.io/fpdf2/TextStyling.html#text_mode">documentation</a>) & <code>.blend_mode</code> (<a href="https://pyfpdf.github.io/fpdf2/Images.html#blending-images">documentation</a>)</li>
<li>new <code>round_clip()</code> & <code>elliptic_clip()</code> image clipping methods: <a href="https://pyfpdf.github.io/fpdf2/Images.html#image-clipping">documentation</a>
<em>(not released yet, planned for <code>v2.5.3</code>)</em></li>
</ul>
<h2>Usage examples with other libs</h2>
<p>A few additions were made to the documentation to provide usage examples of <code>fpdf2</code> with other libraries:</p>
<ul>
<li>with <a href="https://www.djangoproject.com/">Django</a>, <a href="https://flask.palletsprojects.com">Flask</a>, <a href="https://streamlit.io/">streamlit</a>, AWS lambdas: <em>cf.</em> <a href="https://pyfpdf.github.io/fpdf2/UsageInWebAPI.html">documentation</a></li>
<li>with <a href="https://matplotlib.org/">Matplotlib</a> to embed <strong>charts & equations</strong>: <em>cf.</em> <a href="https://pyfpdf.github.io/fpdf2/Maths.html">documentation</a></li>
<li>with <a href="https://www.sqlalchemy.org/">SQLAlchemy</a> to <strong>store PDFs in a database</strong>: <em>cf.</em> <a href="https://pyfpdf.github.io/fpdf2/DatabaseStorage.html">documentation</a></li>
<li>with <a href="https://github.com/pmaupin/pdfrw">pdfrw</a> to modify <strong>existing PDFs</strong>: <em>cf.</em> <a href="https://pyfpdf.github.io/fpdf2/ExistingPDFs.html">documentation</a></li>
</ul>
<p>Would you like other examples being provided?
If so, drop a comment at the bottom of the page, or open a <a href="https://github.com/PyFPDF/fpdf2/discussions/">discussion</a> / <a href="https://github.com/PyFPDF/fpdf2/issues">issue</a>,
explaining with which library you would like to combine <code>fpdf2</code> with 😊</p>
<h2>Deprecation notice</h2>
<p>First, <code>DeprecationWarning</code> messages are not displayed by Python by default.</p>
<p>Hence, every time you use a newer version of <code>fpdf2</code>, we strongly encourage you to execute your scripts
with the <code>-Wd</code> option (<em>cf.</em> <a href="https://docs.python.org/3/using/cmdline.html#cmdoption-W">documentation</a>)
in order to get warned about deprecated features used in your code.
This can also be enabled programmatically with <code>warnings.simplefilter('default', DeprecationWarning)</code>.</p>
<p>Now, there are the notable recent API changes in <code>fpdf2</code>:</p>
<ul>
<li>
<p>the font caching mechanism, that used the <code>pickle</code> module, has been removed, for security reasons,
and because it provided little performance gain (<em>cf.</em> <a href="https://github.com/PyFPDF/fpdf2/issues/345">issue #345</a>).
That means that the <code>font_cache_dir</code> optional parameter of <code>fpdf.FPDF</code> constructor
and the <code>uni</code> optional argument of <code>add_font()</code> are deprecated:
<strong><code>uni=True</code> can now be removed from all calls to <code>add_font()</code></strong>.</p>
</li>
<li>
<p>the parameter <code>ln</code> to <code>cell()</code> and <code>multi_cell()</code> is now deprecated: <strong>use <code>new_x</code> and <code>new_y</code> instead</strong>.</p>
</li>
</ul>
<h2>borb</h2>
<p><a href="https://borbpdf.com/"><img alt="" src="https://raw.githubusercontent.com/jorisschellekens/borb/master/logo/borb_64.png"></a></p>
<p>In November of 2020, Joris Schellekens released another excellent pure-Python library dedicated to reading & write PDF: <a href="https://github.com/jorisschellekens/borb/">borb</a>.
He even wrote a very detailed e-book about it, available publicly there: <a href="https://github.com/jorisschellekens/borb-examples/">borb-examples</a>.</p>
<p>In many ways, <code>borb</code> excels in areas where <code>fpdf2</code> has gaps:
it has a very clean and well-structure code API, with well-defined PDF primitive data-types and type hints (checked with <code>mypy</code>),
it offers several options for <a href="https://github.com/jorisschellekens/borb-examples/#223-setting-a-pagelayout">pages layout</a>,
it can <a href="https://github.com/jorisschellekens/borb-examples/#5-working-with-existing-pdfs">parse PDF files</a> and even <a href="https://github.com/jorisschellekens/borb-examples/#71-extracting-tables-from-a-pdf">extract tables</a>,
it even allows you to insert <a href="https://github.com/jorisschellekens/borb-examples/#4-forms">forms</a> or <a href="https://github.com/jorisschellekens/borb-examples/#439-adding-a-javascriptpushbutton-to-a-pdf">Javascript code</a>.</p>
<p>If ever you want to combine usage of <code>borb</code> <strong>and</strong> <code>fpdf</code>,
we provide some guidance in doing so: <a href="https://pyfpdf.github.io/fpdf2/borb.html">documentation</a>.</p>
<h2>borb vs fpdf2</h2>
<p>I have 2 intents in drawing this comparison:</p>
<ul>
<li>help Python coders chose the library that best fit their need</li>
<li>figure if <code>fpdf2</code> is indeed the fastest of the 2 libraries, as I suspect 😁</li>
</ul>
<p><u>First</u>, there are a couple of features that only <code>fpdf2</code> offers: SVG support (<code>borb</code> <a href="https://en.wikipedia.org/wiki/Rasterisation">rasters</a> <code>.svg</code> files to pixelated images)
and some useful methods to generate a <a href="https://pyfpdf.github.io/fpdf2/DocumentOutlineAndTableOfContents.html">table of contents</a>.
On the other hand, <code>borb</code> offers <strong>many</strong> other features not provided by <code>fpdf2</code>...
So <strong>from the point of functionality, borb is much more complete</strong>.</p>
<p><u>Second</u>, I think <code>fpdf2</code> CD/CI pipeline is a bit more powerful (<a href="https://github.com/PyFPDF/fpdf2/blob/master/.github/workflows/continuous-integration-workflow.yml">YAML source</a> / <a href="https://github.com/PyFPDF/fpdf2/actions/runs/2212594208">GitHub Actions pipeline execution</a>):
we run hundreds of unit tests based on PDF reference files, with 3 validators checking the PDF files generated,
and we test all this with the 4 latest version of Python 3. We also use <a href="https://pylint.pycqa.org/en/latest/">Pylint</a> & <a href="https://github.com/PyCQA/bandit">bandit</a>.
<code>borb</code> <a href="https://github.com/jorisschellekens/borb/blob/master/.github/workflows/python-publish.yml">current CD/CI pipeline</a> currently does none of this,
while the number of unit tests of the two libraries is comparable (346 for <code>borb</code>, 390 for <code>fpdf2</code>).
Added with the fact that <code>fpdf2</code> has been in use for a longer time (since 2006),
I'd say that <strong><code>fpdf2</code> is slightly more robust than <code>borb</code></strong>.</p>
<p><u>Third and finally</u>, <strong>let's benchmark</strong>! 💥💨</p>
<p>I wrote the following script to compare <code>borb</code> & <code>fpdf2</code> performances on a specific usage scenario:
<a href="https://github.com/PyFPDF/fpdf2/blob/master/scripts/benchmark_borb_vs_fpdf2.py">benchmark_borb_vs_fpdf2.py</a>.</p>
<p>There is its execution result on my computer:</p>
<div class="highlight"><pre><span></span><code>Speed benchmark: how much time each lib takes to generate a 10 thousands pages PDF with ~180 distinct images?
(disclaimer: the author of this benchmark is fpdf2 current maintainer)
Versions tested: borb version 2.0.24 VS fpdf2 v2.5.2
Benchmarking fpdf2...
Memory usage peak (resource.ru_maxrss): 47MB
Generated PDF file size: 2.68MB
Duration: 1.87s
Benchmarking borb...
Memory usage peak (resource.ru_maxrss): 646MB
Generated PDF file size: 0.36MB
Duration: 71.35s
</code></pre></div>
<p>There are the resulting PDF files:</p>
<ul>
<li><a href="images/2022/04/borb-10000-pages.pdf">borb-10000-pages.pdf</a></li>
<li><a href="images/2022/04/fpdf2-10000-pages.pdf">fpdf2-10000-pages.pdf</a></li>
</ul>
<p>Now, what can we conclude <u>in this usage scenario</u>?</p>
<ul>
<li><strong><code>fpdf2</code> is faster than <code>borb</code></strong> (by a factor ~40)</li>
<li><strong><code>fpdf2</code> use less memory than <code>borb</code></strong> (by a factor ~15)</li>
<li><strong><code>borb</code> produces smaller PDFs than <code>fpdf2</code></strong> (by a factor ~10), but <strong>at a cost</strong>:
if you check the files produced, the images in the PDF made with <code>borb</code> contain <strong>visible <a href="https://en.wikipedia.org/wiki/Artifact_(error)">artifacts</a></strong> due to compression</li>
</ul>
<p>Finally, while crafting this benchmark script, I triggered several crashes of <code>borb</code>.
There are documented as comments in the script (it did not like some PNG files, and opening many images caused an <code>OSError: [Errno 24] Too many open files</code>)
and I think this supports my previous analysis regarding <code>fpdf2</code> robustness compared to <code>borb</code>.</p>
<p>I hope that this comparison will be useful to pythonistas around here.
I'd like to stress that I really admire & respect Joris Schellekens work on <code>borb</code>,
and that this analysis may be biased by the fact that I'm <code>fpdf2</code> current maintainer.
I encourage every reader to make their own list of criteria that matter, depending on their own project.</p>
<hr>
<p>That's it for today regarding <code>fpdf2</code>!
You can also check the detailed <a href="https://github.com/PyFPDF/fpdf2/blob/master/CHANGELOG.md">CHANGELOG</a>
for an exhaustive list of all changes: bug fixes, other minor improvements and deprecation notices.</p>
<p>I'd like to give a shout-out to all <code>fpdf2</code> contributors,
and especially <a href="https://github.com/torque">@torque</a> & <a href="https://github.com/gmischler">Georg Mischler</a>,
for continually improving this library through code contributions, bug reports, improved documentation, <a href="https://github.com/PyFPDF/fpdf2/issues/267">translations</a>, etc.</p>
<p><img alt="Thank you!" src="images/2022/04/chibird-thank-you.png"></p>
<p>I'd like to end this article with an announcement: after making <a href="https://lucas-c.itch.io/undying-dusk">Undying Dusk</a> last year,
I am now working on a new PDF game! It will be made using <code>fpdf2</code> again, but this time I'm working with French illustrator <a href="https://chezsoi.org/lucas/blog/elliot-jolivet-aka-tensei.html">Elliot Jolivet aka Tenseï</a>
who will provide all the game visuals. More about this soon!</p>
<p>Now, I wish you all to have a lot of fun building PDFs with <code>fpdf2</code> & <code>borb</code>!</p>
<style>
article img { max-height: 12rem; }
article h2 { padding-top: 2rem; }
article hr { margin: 3rem; }
.uk-article-content > p:nth-child(3) { /* Link to GitHub repo */
display: block;
text-align: center;
border: 1px solid black;
border-radius: 10rem;
padding: 1rem;
margin: 2rem 10vw;
}
.uk-article-content > p:nth-child(3) img { margin: auto; }
</style>
<!-- Com' réalisée :
* [x] https://news.ycombinator.com/item?id=31143783
* [x] https://www.reddit.com/r/programming/comments/ubg1mu/borb_vs_fpdf2_comparing_2_pdf_generation_libs/
* [x] https://www.reddit.com/r/Python/comments/uasf5r/borb_vs_fpdf2_comparing_2_pdf_generation_libs/
* [x] https://dev.to/lucasc/fpdf252-svg-support-and-comparison-with-borb-2fip
* [x] email @jorisschellekens & @gmischler + comment on @torque PR
-->New Creative Commons artists2022-03-20T12:30:00+01:002022-03-20T12:30:00+01:00Lucas Cimontag:chezsoi.org,2022-03-20:/lucas/blog/new-creative-commons-artists.html<p>There are some notable recent addition to my page <a href="pages/images-libres-de-droits.html">Images sous licences libres</a>:</p>
<ul>
<li>
<p><a href="https://www.google.com/search?tbm=isch&q=site%3Ameg-james.com-BY-NC-ND">Meg James</a> - mostly CC BY-NC-SA - fantasy & furries</p>
<blockquote>
<p>a queer agender digital artist in southeast Washington State, mostly doing art and comics</p>
</blockquote>
<ul>
<li><a href="http://meg-james.com/art/dead-man-in-a-dead-land/">Dead man in a dead land</a></li>
<li><a href="http://meg-james.com/art/she-came-from-the-bog/">She came from the bog</a></li>
<li><a href="http://meg-james.com/art/compliments-to-the-chef/">Compliments to the chef</a></li>
<li><a href="http://meg-james.com/art/summer-witch/">Summer …</a></li></ul></li></ul><p>There are some notable recent addition to my page <a href="pages/images-libres-de-droits.html">Images sous licences libres</a>:</p>
<ul>
<li>
<p><a href="https://www.google.com/search?tbm=isch&q=site%3Ameg-james.com-BY-NC-ND">Meg James</a> - mostly CC BY-NC-SA - fantasy & furries</p>
<blockquote>
<p>a queer agender digital artist in southeast Washington State, mostly doing art and comics</p>
</blockquote>
<ul>
<li><a href="http://meg-james.com/art/dead-man-in-a-dead-land/">Dead man in a dead land</a></li>
<li><a href="http://meg-james.com/art/she-came-from-the-bog/">She came from the bog</a></li>
<li><a href="http://meg-james.com/art/compliments-to-the-chef/">Compliments to the chef</a></li>
<li><a href="http://meg-james.com/art/summer-witch/">Summer Witch</a></li>
<li><a href="http://meg-james.com/art/trapper-team/">Trapper team</a></li>
<li><a href="http://meg-james.com/art/flash-in-the-pan/">Flash in the pan</a></li>
</ul>
</li>
<li>
<p><a href="https://www.deviantart.com/rodrigokatrakas/gallery">Rodrigo Catraca</a> - CC BY-SA - mostly comis & superheroes - many illustrations exist in both color + B&W (N&B)</p>
<blockquote>
<p>Comic Artist and Illustrator based in Brazil, creates pages and covers for Comic companies.</p>
</blockquote>
<ul>
<li><a href="https://www.deviantart.com/rodrigokatrakas/art/Wookie-Concept-898003865">Wookie - Concept</a></li>
<li><a href="https://www.deviantart.com/rodrigokatrakas/art/Star-Wars-Concept-Ig-88-857367196">Star Wars - Concept - Ig-88</a></li>
<li><a href="https://www.deviantart.com/rodrigokatrakas/art/Kio-Wookie-Concept-842376246">Kio - Wookie Concept</a></li>
<li><a href="https://www.deviantart.com/rodrigokatrakas/art/Guts-839600288">Guts</a></li>
<li><a href="https://www.deviantart.com/rodrigokatrakas/art/Pirate-Rafe-Concept-855364621">Pirate - Rafe - Concept</a></li>
<li><a href="https://www.deviantart.com/rodrigokatrakas/art/Kale-DD-Concept-866064695">Kale - DD - Concept</a></li>
<li><a href="https://www.deviantart.com/rodrigokatrakas/art/Klaus-DD-Concept-859428803">Klaus - DD - Concept</a></li>
<li><a href="https://www.deviantart.com/rodrigokatrakas/art/Siobhan-DD-Concept-861655408">Siobhan - DD - Concept</a></li>
</ul>
</li>
<li>
<p><a href="https://www.deviantart.com/amamidori/gallery">amamidori</a> - CC BY-SA - many SCP-inspired illustrations</p>
<blockquote>
<p>A hobbyist illustrator from Japan</p>
</blockquote>
<ul>
<li><a href="https://www.deviantart.com/amamidori/art/Gods-871254672">Gods</a></li>
<li><a href="https://www.deviantart.com/amamidori/art/Next-patient-please-706481380">SCP-049 - Next patient please</a></li>
<li><a href="https://www.deviantart.com/amamidori/art/fluffy-flower-807831890">SCP-073 / SCP-191 - fluffy flower</a></li>
<li><a href="https://www.deviantart.com/amamidori/art/SCP-682-726605988">SCP-682</a></li>
<li><a href="https://www.deviantart.com/amamidori/art/SCP-2264-809125734">SCP-2264</a></li>
<li><a href="https://www.deviantart.com/amamidori/art/SCP-6666-885190107">SCP-6666</a></li>
</ul>
</li>
<li>
<p><a href="https://www.deviantart.com/gray-skull/gallery">Gray-Skull</a> - CC BY-NC-SA - Warhammer 40K B&W (N&B)</p>
<blockquote>
<p>Digital artist from Russia - <a href="https://www.patreon.com/GraySkull_Art">Patreon</a></p>
</blockquote>
<ul>
<li><a href="https://www.deviantart.com/gray-skull/art/Flayed-One-858677213">Flayed One</a></li>
<li><a href="https://www.deviantart.com/gray-skull/art/You-have-colonized-the-wrong-planet-878839247">You have colonized the wrong planet</a></li>
<li><a href="https://www.deviantart.com/gray-skull/art/Don-t-be-afraid-You-can-pet-her-910377307">Don't be afraid. You can pet her</a></li>
<li><a href="https://www.deviantart.com/gray-skull/art/HAPPY-NEW-YEAR-2022-902393457">HAPPY NEW YEAR 2022</a></li>
<li><a href="https://www.deviantart.com/gray-skull/art/Farsight-Enclaves-Fire-Warriors-881942945">Farsight Enclaves Fire Warriors</a></li>
<li><a href="https://www.deviantart.com/gray-skull/art/PREPARE-TO-DIE-855993716">PREPARE TO DIE</a></li>
<li><a href="https://www.deviantart.com/gray-skull/art/Biologis-Adept-847217956">Biologis Adept</a></li>
<li><a href="https://www.deviantart.com/gray-skull/art/Monochrome-art-2-847331859">Monochrome art #2</a></li>
</ul>
</li>
</ul>
<p>I have also added a section for <a href="pages/images-libres-de-droits.html#pixabay-artists">Pixabay artists</a>,
who share their work under the <a href="https://pixabay.com/fr/service/license/">Pixabay license</a>,
which does not share the same ambition as the <em>Creative Commons</em>, but still allows many usages:</p>
<ul>
<li>
<p>Ady Setiawan aka <a href="https://pixabay.com/fr/users/asketch-21255728/?tab=popular">Asketch @Pixabay</a>, from Indonesia</p>
<ul>
<li><a href="https://pixabay.com/fr/illustrations/ninja-japon-style-guerrier-homme-6246685/">ninja japon warrior yakuza</a></li>
<li><a href="https://pixabay.com/fr/photos/zombi-cat-femme-ninja-fille-6194997/">cat ninja oni demon</a></li>
<li><a href="https://pixabay.com/fr/illustrations/zombi-fusion-femme-fille-peu-s%c3%bbr-6203296/">zombie melting eye projector</a></li>
<li><a href="https://pixabay.com/fr/illustrations/bo%c3%aete-de-vr-steampunk-femme-fille-6203301/">VR steampunk</a></li>
</ul>
</li>
<li>
<p><a href="https://pixabay.com/fr/users/saydung89-18713596/?tab=popular">Piyapong Saydaung @Pixabay</a></p>
<blockquote>
<p>A minimalist fashion style illustrator from Thailand, good at drawing character hairstyles, making him more focused on jagged lines. Likes and donations ("coffee") are welcome.</p>
</blockquote>
<ul>
<li><a href="https://pixabay.com/fr/vectors/femme-t%c3%a9l%c3%a9phone-portable-5716875/">woman cellphone technology call</a></li>
<li><a href="https://pixabay.com/fr/illustrations/fille-fatigu%c3%a9-%c3%a9tudiant-ennuyait-6024567/">girl tired student bored depressed</a></li>
<li><a href="https://pixabay.com/fr/illustrations/monstres-enfants-content-enfance-6032084/">monsters children happy kids</a></li>
<li><a href="https://pixabay.com/fr/illustrations/tente-camp-des-arbres-for%c3%aat-6024521/">tent camp trees forest woods</a></li>
</ul>
</li>
<li>
<p><a href="https://pixabay.com/fr/users/%E6%84%9A%E6%9C%A8%E6%B7%B7%E6%A0%AAcdd20-1193381/?tab=popular">Cdd20 @Pixabay</a></p>
<ul>
<li><a href="https://pixabay.com/fr/illustrations/fantaisie-%c3%a9clairage-homme-4065924/">fantasy lighting man one man show</a></li>
<li><a href="https://pixabay.com/fr/illustrations/livre-lis-roman-l%c3%a9ger-la-peinture-5946219/">book read novel light painting</a></li>
<li><a href="https://pixabay.com/fr/illustrations/les-contes-de-f%c3%a9es-%c3%a9l%c3%a9phants-fum%c3%a9e-4057425/">fairy tales elephants smoke myths</a></li>
</ul>
</li>
</ul>
<style>
article ul > li { margin-bottom: 2rem; }
@media (min-width:768px) {
article ul > li > ul { columns: 2; }
}
</style>P'tites sorcières2022-03-07T20:00:00+01:002022-03-07T20:00:00+01:00Lucas Cimontag:chezsoi.org,2022-03-07:/lucas/blog/ptites-sorcieres.html<figure>
<img alt="Ladybug par David Revoy" src="images/2022/03/Ladybug-by-DavidRevoy.jpg"></noscript>
<figcaption>
<a href="https://www.davidrevoy.com/article541/illustration-ladybug-remake">Ladybug par David Revoy</a>
- <a href="https://creativecommons.org/licenses/by/3.0/">CC BY 4.0</a>
</figcaption>
</figure>
<p><a href="http://toinito.free.fr/fr/jdr/psorcieres.php">P'tites sorcières <em>(site officiel)</em></a> est un jeu de rôle gratuit d’Antoine Bauza,
conçu et publié sur Internet entre 2001 et 2004, il y a donc déjà 20 ans environ !</p>
<blockquote>
<p>Ici, pas d'abominations terrifiantes, d'interminables complots interplanétaires ou de sanglantes batailles. P'tites …</p></blockquote><figure>
<img alt="Ladybug par David Revoy" src="images/2022/03/Ladybug-by-DavidRevoy.jpg"></noscript>
<figcaption>
<a href="https://www.davidrevoy.com/article541/illustration-ladybug-remake">Ladybug par David Revoy</a>
- <a href="https://creativecommons.org/licenses/by/3.0/">CC BY 4.0</a>
</figcaption>
</figure>
<p><a href="http://toinito.free.fr/fr/jdr/psorcieres.php">P'tites sorcières <em>(site officiel)</em></a> est un jeu de rôle gratuit d’Antoine Bauza,
conçu et publié sur Internet entre 2001 et 2004, il y a donc déjà 20 ans environ !</p>
<blockquote>
<p>Ici, pas d'abominations terrifiantes, d'interminables complots interplanétaires ou de sanglantes batailles. P'tites sorcières est un jeu qui respire la bonne humeur et la joie de vivre.
Les règles sont simples pour être accessibles à tous. P'tites sorcières a pour but de vous faire vivre des <strong>aventures insolites dans la peau d'une jeune pratiquante de cette art méconnu qu'est la sorcellerie</strong>.</p>
</blockquote>
<p><a href="images/2022/03/psa4.jpg"><img alt="affichette" src="images/2022/03/psa4.jpg"></a></p>
<p>Une version commerciale, plus étoffée, est ensuite parue, <strong>Contes ensorcelés</strong> :
<a href="http://toinito.free.fr/fr/jdr/ce.php">volume 1</a> - <a href="http://toinito.free.fr/fr/jdr/ce2.php">volume 2</a>.</p>
<p>J'avais entendu parler de ce jeu de rôle depuis des années, et j'ai eu l'occasion d'organiser une partie de P'tites Sorcières
le week-end dernier, pour faire découvrir le jeu de rôle à une amie.</p>
<p>Je ne vais pas m'attarder cette fois sur un compte-rendu de partie détaillé.
Et mon opinion sera très bref : <strong>c'est un excellent jeu</strong> !
<strong>Vraiment idéal pour faire découvrir le jeu de rôle à des enfants</strong> (<a href="tag/jdr4kids.html">mes autres articles sur le sujet</a>).
<strong>L'univers est charmant</strong>, et les joueurs peuvent piocher de nombreuses inspirations et références
dans les films du studio Ghibli (= Hayao Miyazaki) ou encore Harry Potter.
<strong>Le système est simple et efficace</strong>, et ne nécessite que 4d6.</p>
<p><em>Au passage, j'ai opté pour une petite variante pour les « catastrophes » & « actions d’éclat » (échecs & réussites critiques) :
ils se déclenchent dès que deux ⚀ ou deux ⚅ apparaissent dans les quatre dés lancés, même s'il ne s'agit pas des meilleurs / moins bons dés obtenus.</em></p>
<p>Du côté des joueurs, le debrief fut assez court, mais je crois qu'ils étaient tous satisfaits.
C'était en particulier très amusant d'avoir deux « rôlistes aguéris » qui ont dû faire un gros effort sur eux-même
pour se départir de leur ultra-vigilance et de leurs mauvais habitudes (comme fouiller partout ou faire pression sur des PNJs...)
dans cet univers très mignon et bienveillant 🤣</p>
<p>Je ne sais pas si j'y rejouerai dans l'avenir,
mais je veux en profiter pour partager quelques ressources utiles pour ce jeu formidable :</p>
<h2>Scénarios</h2>
<p>J'ai fait jouer le scénario de base, fourni avec le jeu, <strong>Le grand départ</strong>.
Il est très bien, je recommande juste de le pimenter légèrement,
peut-être en rajoutant simplement une raison de venir en aide à Mérine,
sans quoi la partie de l'histoire entre l'envol et la recherche de logement peut s'avérer un peu « plate ».</p>
<p>Pour d'autres aventures, la <strong>Scénariothèque</strong> regorge de ressources,
tant pour <a href="https://www.scenariotheque.org/Document/info_jeu.php?f_id_jeu=161">P'tites sorcières</a>
que pour <a href="https://www.scenariotheque.org/Document/info_jeu.php?f_id_jeu=297">Contes Ensorcelés</a>,
tout comme <a href="https://www.sden.org/p-tites-sorcieres/">le site de l'elfe noir (SDEN)</a>.
Enfin, <a href="http://scenarios-aides-de-jeu-jdr-jdra.blogspot.com/search/label/P%27tites%20sorci%C3%A8res">Paul-Henri 'Pitche' Verheve</a>
a conçu une foultitude de scénarios pour le jeu !</p>
<p>J'en ai en particulier repéré quelques-uns :</p>
<ul>
<li><strong>Un conte : Les papillons</strong> qui a remporté le 35ème concours de la Cour d’Obéron : <a href="http://couroberon.com/auteur/Recueil35-v2.pdf">Recueil35-v2.pdf</a></li>
<li><a href="https://www.scenariotheque.org/Document/info_doc.php?id_doc=7202">Le discours de l'amnésique</a> où les p'tites sorcières devront aider un amnésique à écrire son discours de mariage</li>
<li><a href="https://www.scenariotheque.org/Document/info_doc.php?id_doc=6734">La Fée Electricité</a> : concours de cerf-volants & appareils ménagers possédés !</li>
<li><a href="http://scenarios-aides-de-jeu-jdr-jdra.blogspot.com/2009/08/coup-de-bambou.html">Coup de bambou</a> : enquête sur une mystérieuse narcolepsie générale dans l’Archipel des Bambous</li>
<li><a href="https://www.scenariotheque.org/Document/info_doc.php?id_doc=4655">Voyage au Pays des Sables</a> : il faut porter assistance à une p'tite sorcière de l’Archipel des Sables</li>
</ul>
<h2>Inspirations</h2>
<p>Au delà des animés Ghibli (Kiki la petite sorcière, Le Château ambulant, Le Voyage de Chihiro, Mary et la fleur de la sorcière...)
et des livres & films Harry Potter, voici quelques autres sources d'inspiration possibles :</p>
<ul>
<li>la BD en ligne <a href="http://www.peppercarrot.com/">Pepper & Carrot</a> de David Revoy, un <em>webcomic</em> libre et open-source financé directement par ses lecteurs</li>
<li>la série animée <a href="https://fr.wikipedia.org/wiki/Mushishi">Mushishi</a></li>
<li>les court-métrages et la série anime <em>Little Witch Academia</em></li>
<li>le manga <em>L'Atelier des Sorciers</em>, recommandé par deux des joueurs</li>
<li>le travail de l'illustrateur allemand <a href="https://www.deviantart.com/varguy">Varguy</a></li>
</ul>
<h2>Illustrations</h2>
<p>Voici tout d'abord une sélection d'illustrations sous <em>Creative Commons</em> (réutilisables pour vos créations) :</p>
<figure>
<img loading="lazy" alt="Flight of Spring by David Revoy" src="images/2022/03/2019-02-27_Flight-of-Spring_extended-version_by-David-Revoy.jpg">
<figcaption>
<a href="https://www.peppercarrot.com/fr/viewer/artworks__2019-02-27_Flight-of-Spring_extended-version_by-David-Revoy.html">Flight of Spring by David Revoy</a>
- <a href="https://creativecommons.org/licenses/by/3.0/">CC BY 4.0</a>
</figcaption>
</figure>
<figure>
<img loading="lazy" alt="Characters lineart by David Revoy" src="images/2022/03/2016-02-25_characters-lineart_by-David-Revoy.jpg">
<figcaption>
<a href="https://www.peppercarrot.com/la/viewer/misc__2016-02-25_characters-lineart_by-David-Revoy.html">Characters lineart by David Revoy</a>
- <a href="https://creativecommons.org/licenses/by/3.0/">CC BY 4.0</a>
</figcaption>
</figure>
<figure>
<img loading="lazy" alt="Run by David Revoy" src="images/2022/03/2017-10-11_run_by-David-Revoy.jpg">
<figcaption>
<a href="https://www.davidrevoy.com/article353/run">Run by David Revoy</a>
- <a href="https://creativecommons.org/licenses/by/3.0/">CC BY 4.0</a>
</figcaption>
</figure>
<p>Et bien d'autres de David Revoy sous license CC-BY, issues de <em>Pepper & Carrot</em> :
<a href="https://www.peppercarrot.com/fr/artworks/artworks.html">https://www.peppercarrot.com/fr/artworks/artworks.html</a></p>
<figure>
<img loading="lazy" alt="Summer Witch by Meg James" src="images/2022/03/summer-witch-MegJames-CC-BY-NC-SA.png">
<figcaption>
<a href="http://meg-james.com/art/summer-witch/">Summer Witch by Meg James</a>
- <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a>
</figcaption>
</figure>
<figure>
<img loading="lazy" alt="Compliments to the chef by Meg James" src="images/2022/03/compliments-to-the-chef-MegJames-CC-BY-NC-SA.png">
<figcaption>
<a href="http://meg-james.com/art/compliments-to-the-chef/">Compliments to the chef by Meg James</a>
- <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a>
</figcaption>
</figure>
<figure>
<img loading="lazy" alt="None of your business! by Meg James" src="images/2022/03/none-of-your-business-MegJames-CC-BY-NC-SA.png">
<figcaption>
<a href="http://meg-james.com/art/none-of-your-business/">None of your business! by Meg James</a>
- <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a>
</figcaption>
</figure>
<figure>
<img loading="lazy" alt="Unusual, unlikely, but not unheard of by Meg James" src="images/2022/03/unusual-unlikely-but-not-unheard-of-MegJames-CC-BY-NC-SA.png">
<figcaption>
<a href="http://meg-james.com/art/unusual-unlikely-but-not-unheard-of/">Unusual, unlikely, but not unheard of by Meg James</a>
- <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a>
</figcaption>
</figure>
<figure>
<img loading="lazy" alt="Lanterns in the Sky by desmondwoot" src="images/2022/03/desmondwoot-Lanterns-in-the-Sky-CC-BY-NC-SA.jpg">
<figcaption>
<a href="https://www.deviantart.com/desmondwoot/art/Lanterns-in-the-Sky-357624151">Lanterns in the Sky by desmondwoot</a>
- <a href="https://creativecommons.org/licenses/by-nc-sa/3.0/">CC BY-NC-SA 3.0</a>
</figcaption>
</figure>
<figure>
<img loading="lazy" alt="Witch House, a Glitch game asset" src="images/2022/03/witch-house-public-domain.svg">
<figcaption>
<a href="https://www.glitchthegame.com/public-domain-game-art/">Witch House, a Glitch game asset</a>
- CC0 / domaine publiaue
</figcaption>
</figure>
<p>Il y a également les illustrations officielles, dont une représentant l'Archipel des Sourires,
sur le <a href="http://toinito.free.fr/fr/jdr/psorcieres.php">site officiel</a>.</p>
<p>Voici enfin quelques illustrations supplémentaires, éventuellement pour représenter les PJs : <a href="https://www.pinterest.fr/drmaxkurt/ptites-sorci%C3%A8res/">tableau Pinterest</a>.</p>
<figure>
<img loading="lazy" alt="Little Wizard by Nicholas Kole animated by Yennifer Dark" src="images/2022/03/LittleWizard-by-NicholasKole-animated-by-YenniferDark.gif">
<figcaption>
<a href="https://www.artstation.com/artwork/VDw6X">Little Wizard by Nicholas Kole</a>
- <a href="https://twitter.com/Ulkhror/status/802474344748183552">animated version by Yennifer Dark</a>
</figcaption>
</figure>
<p>[EDIT 2023/06/12]**: Gary Vanaka réalise également de très chouettes illustrations de sorciers & sorcières "modernes" :</p>
<ul>
<li>https://www.artstation.com/artwork/NlzZP</li>
<li>https://www.artstation.com/artwork/3qZroE</li>
<li>https://www.artstation.com/artwork/2qZ1Aa</li>
<li>https://www.artstation.com/artwork/x5xDW</li>
</ul>
<h2>Musique</h2>
<p>Une <em>playlist</em> issue des films du studio Ghibli (en particulier <em>Princesse Mononoké</em> & <em>Mary and The Witch's Flower</em>),
ou inspirée par eux, devrait fonctionner à merveille.</p>
<p>La bande son du jeu <em>Stardew Valley</em> est également tout à fait adaptée.</p>
<hr>
<p><strong>EDIT [24 juillet 2022]</strong> :</p>
<h2>Système alternatif</h2>
<p>A l'occasion d'une seconde partie, destinée à des joueuses découvrant les jeux de rôle,
j'ai souhaité rendre les règles du système de jeu encore plus simples.</p>
<p>Avec l'objectif de pouvoir toujours employer la feuille de personnage officielle du jeu,
voici les changements que j'ai apporté, inspirés du système minimaliste d'<a href="https://lucas-c.github.io/jdr/OriMushi/">Ori Mushi</a> :</p>
<ul>
<li><strong>à la création des p'tites sorcières</strong> :<ul>
<li>les joueuses répartissent les scores suivants dans leurs 5 attributs : <strong>1, 1, 2, 2, 3</strong></li>
<li>chaque personnage débute avec 5 compétences générales, qui ne sont plus associées à un quelconque score</li>
<li>chaque personnage débute toujours avec les mêmes compétences de sorcellerie, sans score non plus</li>
</ul>
</li>
<li><strong>les jets de dés</strong> :<ul>
<li>les joueuses lancent <strong>autant de d6 que leur valeur d'Attribut</strong> correspondant à l'action;
elles ajoutent également <strong>1d6 par compétence applicable</strong>.
Selon le meilleur résultat obtenu aux dés :<ul>
<li>⚅ : c'est réussi !</li>
<li>⚄ : c'est réussi <strong>mais</strong>...</li>
<li>⚃ : c'est raté <strong>mais</strong>...</li>
<li>⚂ / ⚁ / ⚀ : c'est raté</li>
</ul>
</li>
<li>si deux ⚀ sont obtenus : c'est un <strong>échec critique</strong> ; si deux ⚅ sont obtenus : c'est une <strong>réussite parfaite / épique</strong> !</li>
<li>sur un ⚄ ou ⚃, le MJ peut également proposer un <strong>dilemme</strong> :
la joueuse se voit proposer un choix cornélien entre deux options exclusives.
Son personnage peut par exemple obtenir quelque chose au prix d'un sacrifice,
ou bien se rabattre sur une réussite partielle.</li>
<li>les actions conjointes sont encouragées : une p'tite sorcière assistant une autre
à réaliser une action lui octroie un dé bonus</li>
<li>actions en opposition (inclus les bagarres) : un jet est effectué par personnage, celui obtenant le plus de ⚅ l'emporte. En cas d'égalité, on considère les ⚄. Si l'égalité persiste, aucun personnage n'a l'avantage.</li>
<li>initiative : chacune lance 1d6 par point d'Adaptabilité / Débrouille, puis les personnages agissent par ordre décroissant de résultat total (en tant que MJ je ne m’embarrasse néanmoins pas à gérer cela en général)</li>
</ul>
</li>
<li><strong>progression / expérience</strong> :<ul>
<li>3pp par nouvelle compétence générale</li>
<li>6pp par nouvelle compétence de sorcellerie</li>
<li>10pp pour augmenter un attribut</li>
</ul>
</li>
<li>ayant un groupe de 6 joueuses, leur faire jouer 6 familiers m'a semblé trop complexe à gérer,
et j'ai donc opté pour un scénario où les joueuses pourraient jouer leur sorcière <strong>et</strong> leur familier,
à tour de rôle dans des séquences de jeu dédiées, ceux-ci étant transportés dans un autre lieu très tôt dans l'histoire.</li>
</ul>
<p>L'inconvénient principal de ce système est que toutes les actions se valent en difficulté :
le MJ ne spécifie plus aucun seuil. Je trouve que le jeu en vaut néanmoins la chandelle au vu de la simplification des règles réalisée. Pour des actions particulièrement audacieuses, le MJ peut toujours indiquer qu'il faudra obtenir au moins deux ⚄/⚅.</p>
<p>J'ai testé ce système alternatif avec 5 joueurs adolescents durant ~4h de jeu, et c'était très concluant !</p>
<figure>
<img loading="lazy" alt="Tutelary Turtle Island by Cze and Peku" src="images/2022/03/TutelaryTurtleIsland.jpg">
<figcaption>
<a href="https://www.reddit.com/r/battlemaps/comments/vkmnpd/heres_our_latest_czepeku_battlemap_the_tutelary/">Tutelary Turtle Island by Cze and Peku</a>
</figcaption>
</figure>
<figure>
<img loading="lazy" alt="The Crimson Scroll Library by Marve Bärg" src="images/2022/03/The Crimson Scroll Library by Marve Bärg.jpg">
<figcaption>
<a href="https://www.reddit.com/r/ImaginaryLibraries/comments/jm414c/the_crimson_scroll_library_by_marve_b%C3%A4rg/">The Crimson Scroll Library by Marve Bärg</a>
</figcaption>
</figure>
<figure>
<img loading="lazy" alt="Wizard Tower Interior by David Abouaf" src="images/2022/03/Wizard Tower Interior by David Abouaf.webp">
<figcaption>
<a href="https://www.reddit.com/r/ImaginaryLibraries/comments/giei25/wizard_tower_interior_by_david_abouaf/?utm_source=ifttt">Wizard Tower Interior by David Abouaf
</a>
</figcaption>
</figure>
<style>
article img { max-height: 80vh; }
</style>
<script>
function setTitles() {
document.querySelectorAll('article img').forEach(img => img.title = img.alt)
setTimeout(setTitles, 2000);
}
setTitles();
</script>Elliot Jolivet aka Tenseï2022-02-14T22:00:00+01:002022-02-14T22:00:00+01:00Lucas Cimontag:chezsoi.org,2022-02-14:/lucas/blog/elliot-jolivet-aka-tensei.html<p>Petit <em>focus</em> sur un artiste que j'adore : <strong>Elliot Jolivet</strong> <em>aka</em> Tenseï.</p>
<p><img alt="Portrait d'Elliot Jolivet" src="images/2022/02/elliot-jolivet.jpg"></p>
<p>Ses influences proviennent de la science-fiction (Star Wars, Blade Runner, Dune...),
de la bande-dessinée (Mike Mignola, Ben Templesmith, Tsutomu Nihei, Juanjo Guarnido...)
et du jeu vidéo (Piotr Jablonski, la Team Ico, Alexandre Chaudret...).</p>
<p>Bercé enfant par les illustrations des …</p><p>Petit <em>focus</em> sur un artiste que j'adore : <strong>Elliot Jolivet</strong> <em>aka</em> Tenseï.</p>
<p><img alt="Portrait d'Elliot Jolivet" src="images/2022/02/elliot-jolivet.jpg"></p>
<p>Ses influences proviennent de la science-fiction (Star Wars, Blade Runner, Dune...),
de la bande-dessinée (Mike Mignola, Ben Templesmith, Tsutomu Nihei, Juanjo Guarnido...)
et du jeu vidéo (Piotr Jablonski, la Team Ico, Alexandre Chaudret...).</p>
<p>Bercé enfant par les illustrations des cartes <em>Magic</em>,
on peut citer parmi ses univers de prédilection le post-apo, le cyberpunk, la fantasy (plutôt <em>dark</em>)
et les mondes extra-terrestres. Il a ainsi réalisé de nombreuses illustrations dans ces domaines
pour des jeux de rôle.</p>
<p>Voici une sélection de ses créations :</p>
<figure>
<a href="images/2022/02/ej-Lutte.jpg" target="_blank">
<img loading="lazy" alt="Lutte" src="images/2022/02/ej-Lutte.jpg">
</a>
<figcaption>
<a href="https://illutensei.com/lutte">Lutte</a> - 2022
</figcaption>
</figure>
<figure>
<a href="images/2022/02/ej-Echoués.jpg" target="_blank">
<img loading="lazy" alt="Echoués" src="images/2022/02/ej-Echoués.jpg">
</a>
<figcaption>
<a href="https://illutensei.com/echoues">Echoués</a> - 2022
</figcaption>
</figure>
<figure>
<a href="images/2022/02/ej-Despair.jpg" target="_blank">
<img loading="lazy" alt="Despair" src="images/2022/02/ej-Despair.jpg">
</a>
<figcaption>
<a href="https://illutensei.com/despair">Despair</a> - 2022
</figcaption>
</figure>
<figure>
<a href="images/2022/02/ej-Voiles.jpg" target="_blank">
<img loading="lazy" alt="Voiles" src="images/2022/02/ej-Voiles.jpg">
</a>
<figcaption>
<a href="https://www.artstation.com/artwork/GaWK4d">Voiles</a> - 2020
</figcaption>
</figure>
<figure>
<a href="images/2022/02/ej-Fuite.jpg" target="_blank">
<img loading="lazy" alt="Fuite" src="images/2022/02/ej-Fuite.jpg">
</a>
<figcaption>
<a href="https://illutensei.com/fuite">Fuite</a> - 2020
</figcaption>
</figure>
<figure>
<a href="images/2022/02/ej-Elaria.jpg" target="_blank">
<img loading="lazy" alt="Elaria" src="images/2022/02/ej-Elaria.jpg">
</a>
<figcaption>
<a href="https://www.facebook.com/tenseidraw/posts/919218605165292">Elaria</a> - 2020
</figcaption>
</figure>
<figure>
<a href="images/2022/02/ej-Lodius.jpg" target="_blank">
<img loading="lazy" alt="Lodius" src="images/2022/02/ej-Lodius.jpg">
</a>
<figcaption>
<a href="https://www.facebook.com/tenseidraw/posts/912848592468960">Lodius</em></a> - 2020
</figcaption>
</figure>
<figure>
<a href="images/2022/02/ej-Mountains-King.jpg" target="_blank">
<img loading="lazy" alt="Mountain's King" src="images/2022/02/ej-Mountains-King.jpg">
</a>
<figcaption>
<a href="https://www.behance.net/gallery/20039645/Mountains-King">Mountain's King</em></a> - 2014
</figcaption>
</figure>
<p>J'ai eu le plaisir de collaborer avec lui sur le projet de jeu de société <a href="https://mush-radio.chezsoi.org/">Mush Radio</a>.
Il a conçu pour ce projet une belle identité visuelle, ainsi que de nombreuses illustrations :</p>
<div>
<a href="https://mush-radio.chezsoi.org/" target="_blank">
<img loading="lazy" alt="Mush Radio" src="images/2022/02/ej-MushRadio.jpg">
</a>
</div>
<p>Elliot Jolivet est membre fondateur du collectif d'artistes <a href="https://www.cela.co/">Cela</a>.
Il a également travaillé pour l'industrie musicale durant 6 ans, réalisant pochettes d'album et affiches évènementielles
tout en assurant la direction artistique et le développement d'identité de marque.</p>
<p>Si vous aimez son travail, retrouvez d'autres créations d'EJ ici :</p>
<ul>
<li><a href="https://illutensei.com">son site illutensei.com</a>, avec notamment sa série <a href="https://illutensei.com/living-architecture">Living Architecture</a>
et ses créations graphiques pour le <a href="https://illutensei.com/10th-human-beatbox-france-championship">10ème Championnat de France de Beatbox</a></li>
<li><a href="https://www.instagram.com/tensei_draw/">sa page Instagram</a>,
dont un magnifique <a href="https://www.instagram.com/p/CKTqCiahc-o/">triptyque</a>
ainsi qu'une <a href="https://www.instagram.com/p/CU-Yqa0oXiW/">peinture numérique de la ville d'Almaty au Kazakhstan</a></li>
<li><a href="https://www.artstation.com/ej_tensei">sa page ArtStation</a>, toute récente</li>
</ul>
<style>
article img { max-height: 80vh; }
</style>
<script>
function setTitles() {
document.querySelectorAll('article img').forEach(img => img.title = img.alt)
setTimeout(setTitles, 2000);
}
setTitles();
</script>Generative art2022-02-12T18:30:00+01:002022-02-12T18:30:00+01:00Lucas Cimontag:chezsoi.org,2022-02-12:/lucas/blog/generative-art.html<!--
https://nitter.42l.fr/gandamu_ml/status/1491465719933980672#m
-->
<p>I recently discovered some really beautiful pieces of <a href="https://en.wikipedia.org/wiki/Generative_art">generative art</a>
on <a href="https://www.reddit.com/r/generative/top/?t=month">r/generative</a>.</p>
<p>In the same spirit as a precedent article <a href="glitch-art-and-image-processing-with-python.html">on glitch art</a>,
I want to share some of my favorites:</p>
<figure>
<img loading="lazy" alt="Everything's Fine by Kenny Vaden" src="images/2022/02/everythings_fine_by_kenny_vaden.webp">
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/s2u8tm/everythings_fine_r_code/">Everything's Fine by Kenny Vaden</a>
</figcaption>
</figure>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/genuary_day_25_perspective.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/sciwtz/genuary_day_25_perspective/">Genuary Day 25 - Perspective by tasty_plots</a>
</figcaption>
</figure>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/semi_epihyperderpflardioids_by_piterpasma_360.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/hinz2p/semi_epihyperderpflardioids/">Semi epihyperderpflardioids by piterpasma</a>
- <a href="images/2022/02/semi_epihyperderpflardioids_by_piterpasma_720.mp4" target="_blank">HD 720p version …</a></figcaption></figure><!--
https://nitter.42l.fr/gandamu_ml/status/1491465719933980672#m
-->
<p>I recently discovered some really beautiful pieces of <a href="https://en.wikipedia.org/wiki/Generative_art">generative art</a>
on <a href="https://www.reddit.com/r/generative/top/?t=month">r/generative</a>.</p>
<p>In the same spirit as a precedent article <a href="glitch-art-and-image-processing-with-python.html">on glitch art</a>,
I want to share some of my favorites:</p>
<figure>
<img loading="lazy" alt="Everything's Fine by Kenny Vaden" src="images/2022/02/everythings_fine_by_kenny_vaden.webp">
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/s2u8tm/everythings_fine_r_code/">Everything's Fine by Kenny Vaden</a>
</figcaption>
</figure>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/genuary_day_25_perspective.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/sciwtz/genuary_day_25_perspective/">Genuary Day 25 - Perspective by tasty_plots</a>
</figcaption>
</figure>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/semi_epihyperderpflardioids_by_piterpasma_360.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/hinz2p/semi_epihyperderpflardioids/">Semi epihyperderpflardioids by piterpasma</a>
- <a href="images/2022/02/semi_epihyperderpflardioids_by_piterpasma_720.mp4" target="_blank">HD 720p version</a>
</figcaption>
</figure>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/moving_cities_by_annibale_siconolfi_inward_720.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/ImaginaryTechnology/comments/slu6dm/moving_cities_by_annibale_siconolfi_inward/">Moving Cities by Annibale Siconolfi | Inward</a>
</figcaption>
</figure>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/octahedron_with_a_twist_by_ParanoidTiger_480.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/woahdude/comments/qvw3xl/teleportation/">Octahedron with a twist by ParanoidTiger aka Michał Ciebiada</a>
- <a href="images/2022/02/octahedron_with_a_twist_by_ParanoidTiger_720.mp4" target="_blank">HD 720p version</a>
</figcaption>
</figure>
<p>Some incredible art by <strong>Frank Force aka KilledByAPixel</strong>:</p>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/a_city_in_185_bytes_of_javascript.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/tinycode/comments/o0xbuu/a_city_in_185_bytes_of_javascript/">A City in 185 Bytes of JavaScript</a>
</figcaption>
</figure>
<figure>
<img loading="lazy" alt="Astronomic Comics - Code Generated Space Comics" src="images/2022/02/astronomic_comics_code_generated_space_comics.webp">
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/skiqjn/astronomic_comics_code_generated_space_comics/">Astronomic Comics - Code Generated Space Comics</a>
- visit the link for more pages
</figcaption>
</figure>
<figure>
<img loading="lazy" alt="Gensuzendō - Generative Calligraphy" src="images/2022/02/gensuzendō_generative_calligraphy.webp">
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/s1oecz/gensuzend%C5%8D_generative_calligraphy/">Gensuzendō - Generative Calligraphy</a>
- visit the link for more pages
</figcaption>
</figure>
<p>Some splendid plotter drawings by <strong>Anders Hoff @ inconvergent.net</strong>:</p>
<div><img loading="lazy" alt="Plotter drawing" src="images/2022/02/inconvergent-21-71dddd53.jpg"></div>
<div><img loading="lazy" alt="Plotter drawing" src="images/2022/02/inconvergent-ab17902d.jpg"></div>
<div><img loading="lazy" alt="Plotter drawing" src="images/2022/02/inconvergent-20171231-111025.png"></div>
<p>Three beautiful pieces by <strong><a href="https://twitter.com/j2rgb">@j2rgb</a></strong>:</p>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/geometric_avoidance_by_j2rgb.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/pwi1ch/geometric_avoidance/">geometric avoidance</a>
</figcaption>
</figure>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/parametric_organisms_by_j2rgb.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/pkzlw5/parametric_organisms/">parametric organisms</a>
</figcaption>
</figure>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/perlins_loom_by_j2rgb.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/q7zlg4/perlins_loom/">perlins loom</a>
</figcaption>
</figure>
<p>Three amazing pieces by <strong><a href="https://twitter.com/revrart">@Revrart</a></strong>:</p>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/a_new_dimension_by_revrart_240.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/q12mb7/a_new_dimension/">A NEW DIMENSION</a>
- <a href="images/2022/02/a_new_dimension_by_revrart_720.mp4" target="_blank">HD 720p version</a>
</figcaption>
</figure>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/autumn_in_adelpha_by_revrart_240.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/generative/comments/pz5h46/autumn_in_adelpha_by_revrart_me/">Autumn in Adelpha</a>
- <a href="images/2022/02/autumn_in_adelpha_by_revrart_720.mp4" target="_blank">HD 720p version</a>
</figcaption>
</figure>
<figure>
<video controls preload="none" class="lazy">
<source src="images/2022/02/teleportation_by_revrart_240.mp4" type="video/mp4">
</video>
<figcaption>
<a href="https://www.reddit.com/r/woahdude/comments/qvw3xl/teleportation/">Teleportation</a>
- <a href="images/2022/02/teleportation_by_revrart_720.mp4" target="_blank">HD 720p version</a>
</figcaption>
</figure>
<p>Artist @R1B2 also wrote a very interesting article on how he made his <em>Minining Structures</em> series using <code>p5.js</code>:
<a href="http://www.r1b2.com/2022/01/28/mining-structures-walkthrough/">Mining Structures walkthrough</a>.
I'd really love to see the JS code that he wrote!</p>
<p><img alt="Mining structure by R1B2" src="images/2022/02/mining-structure-by-r1b2.jpg"></p>
<hr>
<p>This article is also an opportunity to test some very simple & efficient JS logic to <strong>lazy load videos</strong>,
inspired by <a href="https://ottball.com/lazy-loading-video/">this article @ ottball.com</a>:</p>
<div class="highlight"><pre><span></span><code><span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="s2">"DOMContentLoaded"</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">videoObserver</span> <span class="o">=</span> <span class="ow">new</span> <span class="nx">IntersectionObserver</span><span class="p">((</span><span class="nx">entries</span><span class="p">,</span> <span class="nx">observer</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span>
<span class="nx">entries</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">video</span><span class="p">)</span> <span class="p">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">video</span><span class="p">.</span><span class="nx">isIntersecting</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">video</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">load</span><span class="p">();</span>
<span class="nx">video</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="s2">"lazy"</span><span class="p">);</span>
<span class="nx">videoObserver</span><span class="p">.</span><span class="nx">unobserve</span><span class="p">(</span><span class="nx">video</span><span class="p">.</span><span class="nx">target</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s2">"video.lazy"</span><span class="p">).</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">video</span><span class="p">)</span> <span class="p">=></span> <span class="nx">videoObserver</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nx">video</span><span class="p">));</span>
<span class="p">});</span>
</code></pre></div>
<style>
article img { max-height: 80vh; }
article video { display: block; margin: 0 auto; max-height: 80vh; }
</style>
<script>
function setTitles() {
document.querySelectorAll('article img').forEach(img => img.title = img.alt)
setTimeout(setTitles, 2000);
}
setTitles();
// Progressive <video> loading:
document.addEventListener("DOMContentLoaded", () => {
const videoObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((video) => {
if (video.isIntersecting) {
video.target.load();
video.target.classList.remove("lazy");
videoObserver.unobserve(video.target);
}
});
});
document.querySelectorAll("video.lazy").forEach((video) => videoObserver.observe(video));
});
</script>Bookmarklet to auto-pass cities on OkCupid2022-01-02T18:30:00+01:002022-01-02T18:30:00+01:00Lucas Cimontag:chezsoi.org,2022-01-02:/lucas/blog/bookmarklet-to-auto-pass-cities-on-okcupid.html<!-- Com'
* https://www.reddit.com/r/OkCupid/
* https://webapps.stackexchange.com/questions/60101/why-is-okcupid-showing-me-matches-who-live-more-than-5-kilometers-away
-->
<p>I very recently joined <a href="https://www.okcupid.com"><img class="logo" alt="OkCupid logo" src="https://lucas-c.github.io/okcupid-auto-pass-cities/OkCupid-logo.jpg"></img></a>,
and it seems great! Even if <a href="https://www.reddit.com/r/OkCupid/comments/r9sddx/i_really_dont_like_what_okcupid_has_become/">its new system makes difficult to talk to new people</a>.</p>
<p>One things that annoys me a little though,
is that I often get matches for <strong>people too far away</strong> from where I live,
even with the <strong>distance filter</strong> set in …</p><!-- Com'
* https://www.reddit.com/r/OkCupid/
* https://webapps.stackexchange.com/questions/60101/why-is-okcupid-showing-me-matches-who-live-more-than-5-kilometers-away
-->
<p>I very recently joined <a href="https://www.okcupid.com"><img class="logo" alt="OkCupid logo" src="https://lucas-c.github.io/okcupid-auto-pass-cities/OkCupid-logo.jpg"></img></a>,
and it seems great! Even if <a href="https://www.reddit.com/r/OkCupid/comments/r9sddx/i_really_dont_like_what_okcupid_has_become/">its new system makes difficult to talk to new people</a>.</p>
<p>One things that annoys me a little though,
is that I often get matches for <strong>people too far away</strong> from where I live,
even with the <strong>distance filter</strong> set in my <em>Settings</em>:</p>
<p><img class="screenshot" src="https://lucas-c.github.io/okcupid-auto-pass-cities/OkCupid-distance-filter.jpg"></p>
<p>Hence I wrote some simple Javascript code that <strong>auto-pass matches for a list of given cities</strong>.</p>
<details>
<summary>Javascript snippet</summary>
<pre><code>const CITIES = 'London,Paris';
function autoPassCities() {
const loc = document.getElementsByClassName('card-content-header__location')[0].textContent;
if (CITIES.split(',').some(city => loc.endsWith(city))) {
console.log('AutoPass:', loc);
document.getElementsByClassName('pass')[0].click();
}
setTimeout(autoPassCities, 500);
}
autoPassCities();</code></pre>
</details>
<p>You can generate <strong>your own <a href="https://en.wikipedia.org/wiki/Bookmarklet">bookmarklet</a></strong>
for the cities you wish to exclude
on this page: <a href="https://lucas-c.github.io/okcupid-auto-pass-cities/">https://lucas-c.github.io/okcupid-auto-pass-cities/</a></p>
<style>
img.logo { display: initial; width: 6rem; border-radius: 2rem; }
img.screenshot { width: 30rem; }
</style>Escape from Dino Island2022-01-01T20:00:00+01:002022-01-01T20:00:00+01:00Lucas Cimontag:chezsoi.org,2022-01-01:/lucas/blog/escape-from-dino-island.html<!-- Com'
* PM Discord @Gulix
-->
<p><img alt="Couverture du jeu" src="images/2022/01/escape-from-dino-island.jpg"></p>
<p><em>Escape from Dino Island</em> est un jeu de rôle de <a href="https://twitter.com/stung_art">Sam Tung</a>
et <a href="https://twitter.com/samnite">Sam Roberts</a>, traduit et publié en français par <a href="https://www.gulix.fr/blog/escape-from-dino-island/">Gulix</a>. C'est un jeu <a href="https://en.wikipedia.org/wiki/Powered_by_the_Apocalypse"><em>Powered by the Apocalypse</em></a> (<a href="/lucas/blog/tag/pbta.html">PbtA</a>) pour 3 à 6 joueurs. J'ai acheté le livret <a href="https://www.lulu.com/content/livre-%C3%A0-couverture-souple/escape-from-dino-island---le-jeu/26044143">sur Lulu</a> et j'ai eu l'occasion de le faire jouer cette …</p><!-- Com'
* PM Discord @Gulix
-->
<p><img alt="Couverture du jeu" src="images/2022/01/escape-from-dino-island.jpg"></p>
<p><em>Escape from Dino Island</em> est un jeu de rôle de <a href="https://twitter.com/stung_art">Sam Tung</a>
et <a href="https://twitter.com/samnite">Sam Roberts</a>, traduit et publié en français par <a href="https://www.gulix.fr/blog/escape-from-dino-island/">Gulix</a>. C'est un jeu <a href="https://en.wikipedia.org/wiki/Powered_by_the_Apocalypse"><em>Powered by the Apocalypse</em></a> (<a href="/lucas/blog/tag/pbta.html">PbtA</a>) pour 3 à 6 joueurs. J'ai acheté le livret <a href="https://www.lulu.com/content/livre-%C3%A0-couverture-souple/escape-from-dino-island---le-jeu/26044143">sur Lulu</a> et j'ai eu l'occasion de le faire jouer cette semaine avec 3 amis rôlistes.</p>
<p>Le livret est court mais dense, très complet. Il propose de faire jouer de courtes parties <em>one-shot</em> sur une île peuplée de dinosaures, dans la plus pure veine <em>Jurassic Park</em>, <em>Dino Crisis</em>, etc.</p>
<p>De manière très fluide et simple, une session débute par deux courtes phases d'introduction, <em>Sur le Continent</em> et <em>l'Arrivée</em>, qui permettent de définir le groupe de PJs et ce qui est en train de mal tourner sur l'île.
Ainsi, d'une partie à l'autre les joueurs peuvent avoir des objectifs très différents, et l'origine de la présence des dinos sur l'île peut changer du tout au tout.</p>
<p>Lors de notre partie, le groupe était constitué d'un chasseur, Steel Von Horn, d'un docteur, Jack Peterson,
et d'un paléontologue, le professeur Long. Celui-ci est mort assez rapidement durant la partie, lors d'une attaque de ptérosaures, et un nouveau PJ soldat, Estebàn Gardiola, est entré en scène à sa place.</p>
<p><strong>Petite astuce de MD</strong> <em>(Maître des Dinos)</em> : j'ai rapidement introduit comme PNJs des personnages correspondants aux feuilles de Héros non choisies par les joueurs. Ainsi, il était très facile de les proposer en PJs de remplacement en cas de décès prématuré d'un Héro...</p>
<p>Nos 6 heures de partie ont été émaillées de scènes anthologiques :</p>
<ul>
<li>un milliardaire excentrique qui ressemble à Kanye West</li>
<li>la jeep des PJs démolie et encastrée dans une clôture par un tricératops</li>
<li>un gros moment de trouille lorsqu'un morceau de poisson mort tombe sur la tête d'un PJ...
Alors qu'il s'agissait juste d'une filet suspendu pour nourrir les dinos marins,
qui se révélera bien utile plus tard...</li>
<li>la réparation de la station radio au milieu du lac, pour rétablir les com',
tandis qu'un énorme monstre marin rôde...</li>
<li>un hangar pulvérisé par ce gigantesque Xénosaure, alors que des PJs sont réfugiés dessous,
dans un abri en béton</li>
<li>des <em>trucs</em> qui frétillent dans les hautes herbes</li>
<li>la découverte de casques de Réalité Virtuelle, qui s'avéreront capables de contrôler des dinos !</li>
<li>la rencontre dans le Couvoir avec des deinonychus invisibles !</li>
</ul>
<p><img alt="Avion quittant l'île, entouré de dinosaures" src="images/2022/01/efdi-ending.png"></p>
<table>
<thead>
<tr>
<th>Ce que j'ai aimé</th>
<th>Ce que j'ai moins aimé</th>
</tr>
</thead>
<tbody>
<tr>
<td>➕ un jeu « zéro préparation », clefs en main</td>
<td>➖ (mineur) Pas de PNJs « prêts à l'emploi »</td>
</tr>
<tr>
<td>➕ le système PbtA et les Manœuvres choisies, efficace & fun</td>
<td>➖ (mineur) Manque 1 ou 2 Manœuvres <em>au Calme</em> de plus ?</td>
</tr>
<tr>
<td>➕ ambiance et références partagées par tous ceux qui ont vu Jurassic Park</td>
<td></td>
</tr>
<tr>
<td>➕ une grande rejouabilité : de nombreux objectifs, mystères & obstacles possibles, 8 personnages à incarner, 30 lieux à explorer, 12 dinosaures détaillés</td>
<td></td>
</tr>
<tr>
<td>➕ des feuilles de Héros très bien conçues, avec des détails de <a href="https://fr.wikipedia.org/wiki/Lore"><em>lore</em></a> personnalisé</td>
<td></td>
</tr>
<tr>
<td>➕ un bestiaire détaillant le comportement des dinos et des « trucs » surnaturels</td>
<td></td>
</tr>
<tr>
<td>➕ une carte de l'île, associée à une Manœuvre, à dessiner petit à petit</td>
<td></td>
</tr>
</tbody>
</table>
<p>En fin de partie, les joueurs ont exprimé le sentiment d'avoir manqué « d'objets utiles » collectés durant leur exploration. Comme je pense que c'était essentiellement dû à un manque d'inspiration de ma part pour leur fournir du <a href="https://fr.wikipedia.org/wiki/Loot"><em>loot</em></a>, voici une petite table qui m'aurait été bien utile, proposée ici comme une <strong>aide de jeu</strong> de fan :</p>
<table>
<thead>
<tr>
<th>d12</th>
<th>Loot</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Des rations de nourriture et une couverture de survie</td>
</tr>
<tr>
<td>2</td>
<td>Une boussole et 2 gilets de sauvetage</td>
</tr>
<tr>
<td>3</td>
<td>2 bâtons-fusée éclairante <strong>ou</strong> un pistolet à fusée de détresse (usage unique)</td>
</tr>
<tr>
<td>4</td>
<td>Une paire de <em>talkie-walkies</em></td>
</tr>
<tr>
<td>5</td>
<td>Des jumelles <strong>ou</strong> des lunettes à vision thermique</td>
</tr>
<tr>
<td>6</td>
<td>Un gros trousseau de clefs (<em>Fais-le,c'est tout !</em>)</td>
</tr>
<tr>
<td>7</td>
<td>Un petit revolver, qui se révèle inefficace contre les dinos</td>
</tr>
<tr>
<td>8</td>
<td>Une veste de protection : <strong>-1</strong> <em>Cours!</em> / <strong>+1</strong> <em>Serre les dents!</em> pour encaisser un coup</td>
</tr>
<tr>
<td>9</td>
<td>Une grenade paralysante à la <a href="https://jurassicpark.fandom.com/wiki/Toxic_Metacholine_(Mivacuriam)">Metacholine</a> <strong>ou</strong> des fumigènes (usage unique)</td>
</tr>
<tr>
<td>10</td>
<td>Des clefs de quad <strong>ou</strong> un vélo</td>
</tr>
<tr>
<td>11</td>
<td>Un groupe électrogène portatif, lourd, capable de rétablir le courant localement</td>
</tr>
<tr>
<td>12</td>
<td>Un détecteur de mouvements qui ne fonctionne qu'en étant immobile</td>
</tr>
</tbody>
</table>
<p>Au lieu de lancer un d12, le MD peut également employer le résultat d'un jet de <em>Fouille</em> <strong>- 6</strong>
pour déterminer l'objet trouvé.</p>
<p>Au final, je recommande donc vivement ce jeu à tous les rôlistes fan de Jurassic Park ! 🦕💖🦖</p>
<p><img alt="In the unlikely event of a Dinosaur attack, please observe the following safety guidelines: Run. Keep Running." src="images/2022/01/in-case-of-a-dinosaur-attack.jpg"></p>
<style>
article table { border-collapse: collapse; margin: 2rem auto; border-style: hidden; }
article td, article th { border: 2px solid #444; padding: .5rem; }
</style>Tel Ulysse2021-12-30T02:00:00+01:002021-12-30T02:00:00+01:00Lucas Cimontag:chezsoi.org,2021-12-30:/lucas/blog/tel-ulysse.html<!-- Com'
* PM Discord Tolkraft
* Discord Auberge des Rêveurs
* Discord PtgPtb
-->
<p>Participer cette année au <a href="https://ptgptb.fr/defi-troisfoisforge">défi Trois Fois Forgé</a> m'a donné envie de jeter un œil aux jeux des éditions précédentes...
Et <strong>Tel Ulysse</strong>, de Tolkraft sur une idée de Tunime, gagnant de l'édition 2020, m'a beaucoup plus à la lecture...</p>
<p><img alt="Couverture du jeu Tel Ulysse" src="images/2021/12/TelUlysse-cover.jpg"></p>
<p>Vous pouvez le retrouver sur PtgPtb.fr : <a href="https://ptgptb.fr/defi-troisfoisforge-5-les-jeux">5e Défi PTGPTB …</a></p><!-- Com'
* PM Discord Tolkraft
* Discord Auberge des Rêveurs
* Discord PtgPtb
-->
<p>Participer cette année au <a href="https://ptgptb.fr/defi-troisfoisforge">défi Trois Fois Forgé</a> m'a donné envie de jeter un œil aux jeux des éditions précédentes...
Et <strong>Tel Ulysse</strong>, de Tolkraft sur une idée de Tunime, gagnant de l'édition 2020, m'a beaucoup plus à la lecture...</p>
<p><img alt="Couverture du jeu Tel Ulysse" src="images/2021/12/TelUlysse-cover.jpg"></p>
<p>Vous pouvez le retrouver sur PtgPtb.fr : <a href="https://ptgptb.fr/defi-troisfoisforge-5-les-jeux">5e Défi PTGPTB Trois Fois Forgé - Les Jeux</a> ou directement sur le site de Tolkraft : <a href="https://www.dendrobat.fr/tel-ulysse">Dendrobat</a>.</p>
<p>J'ai eu l'occasion de le tester lundi avec deux joueurs, et nous nous sommes vraiment bien amusés !</p>
<p>Il s'agit d'un jeu de rôle narratif, avec MJ tournant, permettant à 2 à 5 personnes de raconter ensemble une histoire avec un début et une fin, durant 1h à 4h de jeu :
<em>incarnez l'équipage d'un navire perdu en mer qui va devoir affronter périls mythiques et dangers légendaires pour rentrer chez lui</em>.</p>
<p>Le mécanique de jeux se base sur un océan de 7x7 cartes à jouer retournées face cachée,
que l'on va révéler tour à tour, en tentant de nous frayer un chemin à travers de multiple périls !</p>
<p><img alt="Photo de la table de jeu durant la partie" src="images/2021/12/table-de-jeu-Tel-Ulysse.jpg"></p>
<p><img alt="Photo de la table de jeu durant la partie" src="images/2021/12/table-de-jeu-Tel-Ulysse2.jpg"></p>
<p><em><center>(le seul jeu de 54 cartes que j'avais était un jeu <a href="https://fr.wikipedia.org/wiki/Firefly_(s%C3%A9rie_t%C3%A9l%C3%A9vis%C3%A9e)">Firefly</a>, oups...)</center></em></p>
<p>Pour l'ambiance sonore, <a href="https://www.youtube.com/playlist?list=PLn65M7Xc1JNc5Oqb90BrI8dmiYMZFSoVP">la bande son de <em>Master & Commander</em></a> s'est révélée un bon choix.</p>
<p>Dans l'ensemble, je crois que nous avons tous les trois vraiment apprécié le jeu.
Il réussit un équilibre délicat entre jeu de société, où l'on cherche collectivement à « battre le jeu »,
et jeu de rôle, où l'on incarne théâtralement un membre d'équipage face à de terribles dangers !
Je recommande chaudement l'expérience 😉</p>
<p>Voici quelques retours un peu plus précis, incluant des suggestions :</p>
<ul>
<li>les règles sont très claires, et rapides à prendre en main 👍
Organiser une partie nécessite vraiment <a href="/lucas/blog/tag/zero-prep.html">zéro préparation</a>.</li>
<li>en fin de partie, le navire a tracé un chemin sur le mer de cartes :
c'est génial de visualiser ainsi le trajet parcouru !</li>
<li>la mécanique de jeu basée sur les cartes est très futée et colle parfaitement au thème.
Météo, vigie, timonier... c'est vraiment subtil et intelligent !</li>
<li>le livre d'Océanos est une super idée et son contenu très <em>fun</em> 😍</li>
<li>suggestion qui pourrait contribuer à enrichir l'histoire : une des Épreuves pourrait être la continuité d'une précédente, en proposant au conteur de choisir une Épreuve de son choix (ou définie arbitrairement, l'avant-avant-dernière par exemple) et de proposer une péripétie la faisant intervenir à nouveau</li>
<li>la possibilité de faire autant d'<em>escales</em> que l'on souhaite,
en pouvant même récupérer deux vivres à chaque fois,
rend le jeu très (trop ?) facile. Peut-être faudrait-il interdire 2 escales consécutives ?</li>
<li>comme le jeu nous paraissait facile, et sans doute inspiré du <em>Devil's Bargain</em> de <em>Blades in the Dark</em>, nous avons parfois improvisé en tant que conteurs des règles pour certaines Épreuves. Par exemple, lors de a rencontre avec le banc de baleines, j'ai proposé aux joueurs d'en chasser pour gagner 1d2 vivres. Ils ont alors dû réussir une Épreuve secondaire de difficulté <em>Carte</em> + 1d6.</li>
<li>il serait intéressant si un type de carte donné fournissait toujours (potentiellement) le même type de récompense. Exemple : ♥️ ➜ bravoure ; ♣️ ➜ blessures ; ♠️ ➜ richesse ; ♦️ ➜ vivres. Ainsi l'équipage pourrait choisir dans quelle direction se rendre en fonction de ses besoins, à partir des informations de la Vigie.</li>
<li>à moins de 5 joueurs, certains doivent avoir plus d'un personnage à jouer.
C'est un peu dommage, car il est plus difficile d'incarner pleinement deux PJs...
Sur cet aspect, le jeu est sans doute plus plaisant à 5.
À quatre joueurs, le rôle de la Vigie pourrait être donné au personnage du Bosco,
et de même pour le Timonier à 3 joueurs. Bien sûr, cela aurait un impact sur la règle de mutinerie
(que nous n'avons utilisé).</li>
<li>le fichier PDF est constitué d'images, pas de texte, et n'est donc pas <em>searchable</em> avec un lecteur PDF 😔</li>
<li>petite suggestion : inclure sur les feuilles de personnages un petit lexique de termes de marine,
pour encourager les joueurs à les employer, et sans quoi ils peuvent un peu déboussoler <em>(pun intended)</em></li>
</ul>
<p>Un grand merci Tolkraft pour cette petite pépite rôlistique ! 💖</p>
<p><strong>Note</strong> : il doit être possible de jouer à <em>Tel Ulysse</em> en ligne en employant le site web <a href="https://deck.of.cards">deck.of.cards</a>.</p>Vous reprendrez bien un peu de Sombre ?2021-12-29T23:30:00+01:002021-12-29T23:30:00+01:00Lucas Cimontag:chezsoi.org,2021-12-29:/lucas/blog/vous-reprendrez-bien-un-peu-de-sombre.html<!-- Com'
* [x] https://www.terresetranges.net/forums/viewtopic.php?pid=20266#p20266
* [x] https://www.terresetranges.net/forums/viewtopic.php?pid=20267#p20267
* [x] PM à Axel sur FB JeudiJDR
-->
<blockquote>
<p><strong>Sombre : la peur comme au cinéma</strong> est un jeu de rôle d’horreur contemporain. Il met en scène des antihéros qui essaient de survivre dans un monde particulièrement violent et hostile. L’univers de jeu proposé est celui des films d’horreur, soit un monde contemporain particulièrement dur où fous …</p></blockquote><!-- Com'
* [x] https://www.terresetranges.net/forums/viewtopic.php?pid=20266#p20266
* [x] https://www.terresetranges.net/forums/viewtopic.php?pid=20267#p20267
* [x] PM à Axel sur FB JeudiJDR
-->
<blockquote>
<p><strong>Sombre : la peur comme au cinéma</strong> est un jeu de rôle d’horreur contemporain. Il met en scène des antihéros qui essaient de survivre dans un monde particulièrement violent et hostile. L’univers de jeu proposé est celui des films d’horreur, soit un monde contemporain particulièrement dur où fous dangereux et maniaques du découpage de viande humaine sont légion.</p>
</blockquote>
<p>Site officiel : <a href="https://www.terresetranges.net/sombre.html">TerresEtranges.net</a>. <em>Trailer</em> :</p>
<iframe width="800" height="450" src="https://www.youtube.com/embed/-5sJzJfEgKY" allowfullscreen></iframe>
<p>J'ai déjà eu l'occasion d'évoquer ce <a href="/lucas/blog/tag/jdr.html">JdR</a> dans <a href="tag/sombre.html">deux précédent articles</a>.</p>
<p>Lors d'une après-midi la semaine dernière avec quatre amis, tous rôlistes chevronnés, nous avons joué <strong>trois scénarios Sombre Zéro</strong>, la variante la plus minimaliste du système de jeu de Johan Scipion.</p>
<p>Voici donc un petit retour sur ce <em>triple feature</em>.</p>
<hr>
<p>⚠️ <strong>ATTENTION: spoilers!</strong> Ce qui suit divulgâche des rebondissements scénaristiques. <em><strong>MJs only</strong></em> 😉</p>
<hr>
<h2>Overlord</h2>
<figure>
<img class="small" alt="Loup-garou" src="images/2021/12/werewolf.jpg">
<figcaption>Illustration tirée de la <a href="https://www.bedetheque.com/serie-43442-BD-World-War-Wolves.html">BD World War Wolves</a></ficaption>
</figure>
<p>Scénario officiel de type « survival forestier coopératif », issu de Sombre n°2 et d'environ 15min.</p>
<p>Efficace pour introduire les mécaniques Sombre Zéro à de nouveaux joueurs, mais vraiment très court :
les joueurs auraient aimé prolonger cette micro-partie. J'aurais sans doute dû annoncer la durée estimée d'emblée.</p>
<h2>Les Grimmies</h2>
<p><img class="small" alt="Petit chaperon rouge" src="images/2021/12/little-red-riding-hood.jpg"></p>
<p>En fin de session, autre scénario officiel « survival forestier coopératif » issu de Sombre n°6, pour une durée de 20min ou 45min.</p>
<p>J'avais envie de jouer ce scénario depuis plusieurs mois...
Il me semblait fourmiller d'idées intéressantes : l'ambiance « conte de Grimm » qui finit mal
(les joueurs ont adhéré à fond), la mécanique des <em>boosters</em>, le sentier torique,
les piles de 3 monstres / lieux tirés aléatoirement...
MIAM !</p>
<p>Malheureusement, j'ai fait l'erreur de le jouer en format « semi-flash », avec un chrono de 30min.
<em>A posteriori</em>, j'aurai mieux fait de le jouer en format « court », sans chrono.
Les joueurs auraient apprécié, je crois, pouvoir prendre leur temps, s'imprégner de l'ambiance.
Et moi aussi : 26 pages croustillantes de scénario commenté, pour 30min de jeu, c'était méga frustrant en fait.
Durant cette partie nous avons tous été trop pris par le temps,
pressés d'enchaîner les affrontements par ce chrono qui tourne...</p>
<p>C'est d'ailleurs là au final ma <strong>grosse déception</strong> avec ces deux scénarios :
ils sont <strong>complètement axés sur le combat</strong>. Difficile d'instaurer une ambiance inquiétante
lorsque les mécaniques de jeu poussent essentiellement à s'entretuer !
Les joueurs ont d'ailleurs fini par « piger le truc », et se sont jeté à trois simultanément sur le dernier monstre pour lui régler son compte, sans chercher à songer au <em>roleplay</em> ou à des solutions alternatives.
Car c'est bien au final le but du scénario : de la baston <em>gore</em> & <em>creepy</em>.</p>
<p>Si je fais rejouer <em>Les Grimmiers</em>, je laisserai tomber le chrono
et je remplacerai l'objectif explicite de « vaincre les 3 monstres » par « échapper de la forêt ».
Les joueurs devraient trouver un moyen d'échapper à la boucle du sentier, avec plusieurs solutions envisageables :</p>
<ul>
<li>effacer les inscriptions sur la pierre levée, où un dessin du sentier est clairement visible,
et qui est la clef de cet enchantement magique</li>
<li>rentrer dans le jeu du Loup et le convaincre de guider les PJs en-dehors de la forêt (en échange de quoi ? ...)</li>
<li>forcer la sorcière à rompre le sort, par la force sans doute</li>
<li>trouver un moyen pour suivre discrètement l'Ogre dans les bois en se protégeant de leur toxicité (un gros TGCM d'ailleurs)</li>
</ul>
<p>Cela demanderait un peu de préparation, comme cela impliquerait plus de <em>roleplay</em> avec les PNJs et pas d'affrontements systématiques. Quels sont les comportements des PNJs si les PJs se cachent ? Comment éviter de mourir dans la forêt ? Comment se comporte la rivière si les PJs y plongent ? (le courant pourrait former une boucle, comme le sentier...)</p>
<p>Pour la postérité, voici les paires de lieu / monstre que j'ai tiré :</p>
<ol>
<li><strong>Le Loup</strong> dans <strong>La chaumière</strong> (dans le lit)</li>
<li><strong>La Sorcière</strong> à <strong>La pierre levée</strong></li>
<li><strong>L'Ogre</strong> à <strong>La rivière</strong></li>
</ol>
<h2>La Maison de la Sorcière</h2>
<p><img class="small" alt="La maison de la sorcière" src="images/2021/12/sombre-la-maison-de-la-sorciere.jpg"></p>
<p>Le « plat principal » de milieu de session !
Un scénario d'Axel Tentacle, de la chaîne Youtube <a href="https://www.youtube.com/channel/UCPS_7ef_xjABXpXWWn9NUAA">Jeudi JdR</a>, <a href="https://www.terresetranges.net/forums/viewtopic.php?pid=19630#p19630">partagé sur le forum TerresEtranges.net</a>, inspiré de REC & <em>Blair Witch</em>.
J'ai d'ailleurs employé <a href="https://www.youtube.com/watch?v=Hcpl5zz6gtc">la bande son du jeu vidéo de 2019</a>
comme ambiance sonore, et c'était parfait !</p>
<blockquote>
<p>1973, vous incarnez de jeunes adolescents, nouveaux arrivants dans une petite ville américaine.
Quelques jours après une rentrée des classes désastreuse, pour vous intégrer, vous devez faire vos preuves devant les autres jeunes du village en entrant dans « la maison de la sorcière »...</p>
</blockquote>
<p>Des trois scénarios, c'est celui-ci qui nous avons tous préféré.
Cette fois, l'objectif n'est clairement pas le combat, il faut <strong>survivre et s'enfuir</strong>.
Avec en plus l'obligation de traverser toute la maison, aller-retour ! Splendide.</p>
<p>J'avais pris soin de laisser entendre que la sorcière semblait bien n'être qu'une pure légende urbaine...
Les joueurs ont tout de suite eu une belle trouille en la croisant, terrés dans la salle de bain,
alors qu'ils n’apercevaient que sa silhouette et ses doigts crochus passer dans le couloir !</p>
<p>Une fois à l'étage les PJs, rusés, ont bloqué la porte du palier avec un meuble du couloir de l'étage,
pour empêcher la sorcière de monter les rejoindre.
Bien vu, mais ce meuble est ensuite devenu un obstacle pour eux lorsqu'il a fallu redescendre 😁</p>
<p>Plus tard, un des PJs a succombé face à la sorcière, transpercé par ses ongles noirs.
Celle-ci l'a alors bercé comme un bébé, semblant adopter son cadavre comme son enfant...
J'ai ensuite fait jouer au joueur du PJ mort son propre cadavre devenu « enfant fantôme », et ça a très bien fonctionné !</p>
<p>Un peu plus tard encore, tandis que les PJs se cachent comme ils peuvent dans la salle du rituel,
la sorcière y entre, attirée par du bruit.
Elle ne les repère pas, et se met à balayer (avec son balai de sorcière) le sol ou sont jonchés des ustensiles du rituel. Petit moment <em>creepy</em> tandis que la sorcière rumine d'une voix éraillée « satanés gamins... quel désordre... si je les attrape... »</p>
<p>Mention spéciale à la mise en page du PDF, claire et structurée,
avec même un plan détaillé de la maison (que j'ai progressivement révélé aux joueurs) 😍</p>
<p><strong>Merci Axel Tentacle</strong> pour ce scénario !!
Il fait définitivement partie de mon top 2 pour Sombre Zéro, avec <em>Deep Space Gore</em>.</p>
<p><strong>EDIT [2 janvier 2022]</strong> : j'en profite pour mentionner la sortie d'un copieux (143 pages) recueil de scénarios
par Julien <em>DeathAmbre</em> De Monte @<a href="http://arkhive.free.fr">DarkFarm</a> : <a href="http://arkhive.free.fr/Terminatrice.pdf">Terminatrice.pdf</a>. Génial !</p>
<p><strong>EDIT [26 juillet 2022]</strong> : pour les curieux, la vidéo de la partie originale de <em>La Maison de la Sorcière</em>,
avec l'auteur du scénario, est en ligne ici : <a href="https://www.youtube.com/watch?v=kYQ0bsrPGmk">https://www.youtube.com/watch?v=kYQ0bsrPGmk</a></p>
<style>
img.small { width: 30rem; }
article iframe { display: block; margin: 1rem auto; }
</style>5 Minute Dungeon : variante CIBLE2021-12-28T13:30:00+01:002021-12-28T13:30:00+01:00Lucas Cimontag:chezsoi.org,2021-12-28:/lucas/blog/5-minute-dungeon-variante-cible.html<!-- Com'
* https://boardgamegeek.com/boardgame/207830/5-minute-dungeon/files
* https://www.trictrac.net/jeu-de-societe/5-minute-dungeon-master-edition/ressources
* https://gusandco.net/2020/03/09/5-minute-dungeon-critique-jeu/
* https://paradoxetemporel.fr/21656-test-de-5-minute-dungeon-de-connor-reid-chez-spin-master.html
-->
<p><img alt="Logo du jeu" src="images/2021/12/5minDungeon-cover-small.jpg"></p>
<p><em>(English speakers : go to <a href="https://boardgamegeek.com/thread/1855051/variant-find-and-rescue-enchanted-princess">BoardGameGeek</a> to find the variant & companion web-app for this excellent game)</em></p>
<p><em><strong>5 Minute Dungeon</strong></em> est une très belle découverte de Noël !</p>
<p>Il s'agit d'un <strong>jeu coopératif basé sur la vivacité</strong>,
avec comme thématique l'<strong>exploration d'un donjon de <em>fantasy</em></strong>
(façon <em>dungeon-crawler</em> / <a href="https://fr.wikipedia.org/wiki/Porte-monstre-tr%C3%A9sor">PMT</a>).</p>
<p><strong>Pourquoi c'est bien ?</strong> Parce …</p><!-- Com'
* https://boardgamegeek.com/boardgame/207830/5-minute-dungeon/files
* https://www.trictrac.net/jeu-de-societe/5-minute-dungeon-master-edition/ressources
* https://gusandco.net/2020/03/09/5-minute-dungeon-critique-jeu/
* https://paradoxetemporel.fr/21656-test-de-5-minute-dungeon-de-connor-reid-chez-spin-master.html
-->
<p><img alt="Logo du jeu" src="images/2021/12/5minDungeon-cover-small.jpg"></p>
<p><em>(English speakers : go to <a href="https://boardgamegeek.com/thread/1855051/variant-find-and-rescue-enchanted-princess">BoardGameGeek</a> to find the variant & companion web-app for this excellent game)</em></p>
<p><em><strong>5 Minute Dungeon</strong></em> est une très belle découverte de Noël !</p>
<p>Il s'agit d'un <strong>jeu coopératif basé sur la vivacité</strong>,
avec comme thématique l'<strong>exploration d'un donjon de <em>fantasy</em></strong>
(façon <em>dungeon-crawler</em> / <a href="https://fr.wikipedia.org/wiki/Porte-monstre-tr%C3%A9sor">PMT</a>).</p>
<p><strong>Pourquoi c'est bien ?</strong> Parce que les parties sont courtes mais frénétiques.
Que les règles sont simples, accessibles à tous âges et tout type de joueur,
occasionnel ou passionné.
Le matériel est de qualité, les illustrations drôles, l'envie de rejouer en fin de partie systématique...</p>
<p>Pour un avis plus détaillé, je vous invite à lire <a href="https://gusandco.net/2020/03/09/5-minute-dungeon-critique-jeu/">la critique de Gus & Co</a> qui m'a donné envie d'y jouer.</p>
<p>Seul petit bémol pour moi : <strong>la rejouabilité</strong>.
On peut avoir assez vite l'impression d'avoir « fait le tour » du jeu,
surtout après avoir battu le 5e boss.</p>
<p>Heureusement dans ces cas là, il y a <strong>les variantes de fan</strong>
qu'on peut souvent trouver sur <a href="https://boardgamegeek.com/boardgame/207830/5-minute-dungeon/forums/69">BoardGameGeek</a>.
Et j'ai été ravi de découvrir la <a href="/lucas/blog/tag/variante.html">variante</a> CIBLE de <strong>Laos the Lurking</strong> :
<a href="https://boardgamegeek.com/thread/1855051/variant-find-and-rescue-enchanted-princess">Find and rescue the enchanted princess</a>.</p>
<blockquote>
<p>J'ai récemment acheté le jeu et j'y ai joué avec quatre amis tout de suite. C'était hilarant, même pour un gars qui se moque d'habitude des jeux coopératifs. De manière surprenante, nous avons remporté toute la campagne du jeu de base. [...] J'ai donc réfléchi à une façon de pimenter encore plus l'expérience pour moi et mes amis.</p>
</blockquote>
<p>J'ai eu l'occasion de la tester dimanche, et c'était <strong>un franc succès</strong> !
Le jeu s'en retrouve corsé d'une petite difficulté supplémentaire,
qui augmente encore un peu la tension et l'attention des joueurs,
surtout lorsque chacun a sa propre CIBLE !
Cerise sur le gâteau : cette variante donne plus de valeurs aux illustrations,
trop souvent ignorées lors d'une partie classique.</p>
<p>J'ai donc décidé de <strong>traduire cette variante en français</strong>
et de <strong>développer une petite application web</strong> pour facilement sélectionner
des CIBLES aléatoires à chaque partie :</p>
<div class="side-by-side">
<a href="images/2021/12/5_Minute_Dungeon_Variante_CIBLE.pdf">
<figure>
<img alt="Aperçu du fichier PDF" src="images/2021/12/5_Minute_Dungeon_Variante_CIBLE.jpg">
<figcaption>PDF des règles (3 pages - 1,5 Mo)</figcaption>
</figure>
</a>
<a href="https://chezsoi.org/lucas/5md/">
<figure>
<img alt="Une princesse guerrière" src="https://chezsoi.org/lucas/5md/tokens/token17.jpg">
<figcaption>Web-app - URL courte : tinyurl.com/5minD</figcaption>
</figure>
</a>
</div>
<style>
article img { max-height: 20rem; }
figcaption { font-size: large; line-height: 2rem; padding: 1rem; }
@media (min-width:768px) {
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
}
.side-by-side > * { margin: 0 2rem; }
}
</style>Défi trois fois forgé 20212021-12-03T23:00:00+01:002021-12-03T23:00:00+01:00Lucas Cimontag:chezsoi.org,2021-12-03:/lucas/blog/defi-trois-fois-forge-2021.html<!-- Com'
* [x] discord PTGPTB
* [x] https://hu-mu.blogspot.com/2021/06/cthulhu-cest-ton-destin.html
* [x] emails aux auteurs du top 6
Une fois le verdict du défi annoncé / la version Director's Cut :
* [ ] https://www.casusno.fr
* [ ] https://forums.ffjdr.org/
* [ ] Discord Auberge
* [ ] https://www.reddit.com/r/jdr/
* [ ] https://www.500nuancesdegeek.fr
* [ ] https://forum.canardpc.com/threads/12905-JDR-Jeux-de-R%C3%B4le-Topic-G%C3%A9n%C3%A9ral
* [ ] https://www.trictrac.net/forum/discutons-jeux-de-role
* [ ] https://forum.cwowd.com/c/jeux-de-role/6
* [ ] https://www.deviantart.com/jeuxderole
Inclure ici un lien vers l'article de release de la Director's Cut
-->
<p>Cette année j'ai participé pour la première fois au <a href="https://ptgptb.fr/defi-troisfoisforge">défi Trois Fois Forgé</a>
organisé par le site <a href="https://ptgptb.fr">PtgPtb.fr</a>.</p>
<p>Le principe ? Un défi d'écriture où chaque auteur participe successivement à la création de trois jeux de rôle (JdR).</p>
<p>Nous avons débuté en octobre : chaque participant a soumis une première <strong>forge …</strong></p><!-- Com'
* [x] discord PTGPTB
* [x] https://hu-mu.blogspot.com/2021/06/cthulhu-cest-ton-destin.html
* [x] emails aux auteurs du top 6
Une fois le verdict du défi annoncé / la version Director's Cut :
* [ ] https://www.casusno.fr
* [ ] https://forums.ffjdr.org/
* [ ] Discord Auberge
* [ ] https://www.reddit.com/r/jdr/
* [ ] https://www.500nuancesdegeek.fr
* [ ] https://forum.canardpc.com/threads/12905-JDR-Jeux-de-R%C3%B4le-Topic-G%C3%A9n%C3%A9ral
* [ ] https://www.trictrac.net/forum/discutons-jeux-de-role
* [ ] https://forum.cwowd.com/c/jeux-de-role/6
* [ ] https://www.deviantart.com/jeuxderole
Inclure ici un lien vers l'article de release de la Director's Cut
-->
<p>Cette année j'ai participé pour la première fois au <a href="https://ptgptb.fr/defi-troisfoisforge">défi Trois Fois Forgé</a>
organisé par le site <a href="https://ptgptb.fr">PtgPtb.fr</a>.</p>
<p>Le principe ? Un défi d'écriture où chaque auteur participe successivement à la création de trois jeux de rôle (JdR).</p>
<p>Nous avons débuté en octobre : chaque participant a soumis une première <strong>forge</strong>, c'est-à-dire un jeu complet avec système & univers, sur environ 4 pages. Puis les forges ont été redistribuées entre les participants, et chaque auteur a hérité d'une autre forge, avec pour but de l'enrichir, de pousser l’idée plus loin. Enfin, une dernière redistribution a eu lieu, pour aboutir à des jeux conçus par trois auteurs, sans se concerter. Chaque phase a duré 2 semaines. À la fin, chaque JdR faisait entre 24000 et 28000 signes, aides de jeu incluses.</p>
<p><img src="images/2021/12/FF6_banner.jpg" style="width: 30rem"></p>
<p>Voici donc mon petit retour d'expérience comme participant à cette belle édition 2021 du défi 3FF !</p>
<h1>1ère forge - SECUception (CPAMDON)</h1>
<p>Parmi mes nombreux prototypes qui me tiennent à cœur de terminer un jour,
il y avait un concept de jeu de rôle qui me trottait dans la tête depuis un an ou deux :
<strong>un JdR mettant en scène les débuts historiques de la Sécurité Sociale</strong>.</p>
<p>Depuis que j'ai vu le film <a href="https://www.lasociale.fr"><em>La Sociale</em> de Gilles Perret</a>,
je suis assez fasciné par cette période historique,
post seconde guerre mondiale, où de grandes réformes sociales ont eu lieu.</p>
<p><a href="https://www.lasociale.fr/"><img alt="Poster du film" src="images/2021/12/LaSociale.jpg"></a></p>
<p>À l'origine, <a href="https://github.com/Lucas-C/jdr/blob/master/CreonsLaSociale/CreonsLaSociale.md"><em>Créons La Sociale (notes en vrac)</em></a> devait être un jeu de rôle mettant les joueurs dans la peau d'hommes politiques négociant âprement la mise en place de la Sécurité Sociale, face à des députés et ministres opposés à cette idée, lors d'une année 1945 légèrement uchronique.</p>
<p>Pour ce défi 3FF, j'ai donc recyclé cette idée, mais avec deux changements majeurs :</p>
<ul>
<li>la limite de signes m'imposait de réduire drastiquement mes ambitions initiales</li>
<li>la succession de phases de pure négociation en <em>roleplay</em> m'est apparu trop peu <em>fun</em> à jouer,
et j'ai donc improvisé un mix improbable : transformer le jeu en <em><strong>dungeon crawler</strong></em>
dans un univers de <em><strong>fantasy</strong></em> ! 😂</li>
</ul>
<p>Le résultat fut cette première forge :</p>
<p><a href="images/2021/12/SECUception.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2021/12/SECUception-preview.jpg">
<figcaption>SECUception<br>(PDF 5 pages, 422 Ko)</figcaption>
</figure></p>
<p></a></p>
<p>Sur le moment, j'étais assez content du résultat. 😊
Mixer des monstres <em>Peaux-Vertes</em> avec des éléments historico-politiques,
des amorces de scénario basées sur les années 1945-1946 en France...
Le système de jeu était très ludique / simulationniste,
et entraînait des choix tactiques que j'espérais intéressant.
L'idée était essentiellement de proposer un <a href="https://fr.wikipedia.org/wiki/Porte-monstre-tr%C3%A9sor">PMT</a>
à jouer à grande vitesse, en 60 min seulement (sans doute influencé là par <a href="/lucas/blog/tag/run-die-repeat.html"><em>Run. Die. Repeat.</em></a>).</p>
<p>Voici les deux forges qui ont suivi ensuite :</p>
<ul>
<li><a href="https://ptgptb.fr/3FF6/etape2/CPAMDON-2.pdf">TIMEception</a> par <a href="https://angeldust-jdr.com">Fabien C</a></li>
<li><a href="https://ptgptb.fr/3FF6/etape2/CPAMDON-3.pdf">Conceptions</a> par Mugmax</li>
</ul>
<p>C'est amusant de voir à quel point la première et la troisième forge ont très peu en commun !
C'est un cas typique de <a href="https://fr.wikipedia.org/wiki/Téléphone_arabe">téléphone arabe</a>
/ <a href="https://fr.wikipedia.org/wiki/Cadavre_exquis">cadavre exquis</a>. 😊
Dans la deuxième forge, Fabien a introduit le concept de missions d'agents interdimensionnels prenant possession d'individus dans chaque monde, qui est finalement devenu l'élément central de la forge finale de Mugmax.</p>
<p>Je crois que j'aimerais bien poursuivre l'idée originale de SECUception...
Que vous soyez joueur ou potentiel co-auteur, contactez-moi si le projet vous intéresse 😉</p>
<h1>2ème forge - Turbo Escouade (CATROUIL)</h1>
<p>Lorsque j'ai reçu <em>Alerte turbo-pelleteuse</em>, la forge initiale, j'avoue ne pas avoir été emballé par le concept...</p>
<blockquote>
<p>Nous avons la chance d'être protégés par des post-humains surpuissants. Ils se meuvent, réfléchissent et se battent avec la magnitude de phénomènes naturels. Et laissent donc un sillage de destruction derrière eux chaque fois qu'ils font quelque chose. Laissant à de braves gars et filles comme vous le soin de réparer derrière. Et vu la vitesse à laquelle ces chantiers de reconstruction s’enchaînent, il a fallu adapter le moyens. Or donc : des robots géants.</p>
</blockquote>
<p>Jouer des <a href="https://fr.wikipedia.org/wiki/Mecha">mechas</a> devant nettoyer les dégâts laissés par des super-héros ?? 🤔</p>
<p>Après un petit temps de réflexion néanmoins, le concept a fini par me séduire,
tant il était loufoque et faisant en même temps à des images fortes de la culture populaire.
En plus, le texte de <a href="https://twitter.com/nerghull">Nerghull</a> (<a href="images/2021/12/CATROUIL-1.pdf">CATROUIL-1, PDF 4 pages, 146 Ko</a>) avait de vraies qualités littéraires, un coup de plume, comme le démontre déjà le paragraphe ci-dessus. 🙇</p>
<p>J'ai fini par accoucher de cette seconde forge :</p>
<p><a href="https://ptgptb.fr/3FF6/etape2/CATROUIL-2.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2021/12/Turbo-Escouade-preview.jpg">
<figcaption>Turbo Escouade<br>(PDF 13 pages, 621 Ko)</figcaption>
</figure></p>
<p></a></p>
<p>Comme souvent lorsque je crée des jeux, j'ai débuté par une phase de recherches d'illustrations.
J'adore cette phase. C'est presque compulsif, et je crois que j'ai besoin de ça pour me forger un univers mental autour du jeu. Pour l'occasion je me suis efforcé de trouver des <a href="pages/images-libres-de-droits.html">images sous licences libres</a>, afin de pouvoir les inclure dans le jeu.</p>
<p>En cherchant au hasard des illustrations avec le terme <em>MegaGuy</em>, je suis en particulier tombé sur <a href="https://earltheartist.tumblr.com/post/177207484873/megaman-x-doom-hahaha-just-dome-nonsensical-stuff">ce personnage assez hilarant par EarltheArtist</a> :</p>
<p><img alt="MegaGuy par EarltheArtist" src="images/2021/12/MegaGuy.png"></p>
<p>L'artiste a bien voulu me donner l'autorisation de l'employer dans le jeu, et je l'en remercie chaleureusement au passage ! ❤️</p>
<p>J'ai vraiment tenté de conserver l'esprit original de la forge de <a href="https://twitter.com/nerghull">Nerghull</a>,
mais j'ai par contre complètement revu le système de jeu, en m'inspirant en partie de celui de <strong>Psi Run</strong> (<a href="http://awarestudios.blogspot.com/2014/12/psi-run.html">avis sur le jeu</a> - <a href="https://electric-goat.net/products/1">VF</a>).</p>
<p>Pour la troisième forge, <a href="https://secteurcalixis.wordpress.com/2021/11/19/defi-3ff-edition-6-cest-fini/">Benoît Léoutre</a> a, entre autres, fait un gros travail sur la mise en page, a ajouté d'intéressantes règles pour la Coordinatrice de mission, et a fourni de très réussies & indispensable feuilles de personnages : <a href="https://ptgptb.fr/3FF6/etape3/CATROUIL-3.pdf">Turbo-Mech (PDF 24 pages, 2.7 Mo)</a>.</p>
<h1>3ème forge - P² : Le Placard des Profondeurs (ABLHU)</h1>
<p>Dernière étape du défi 3FF, la création de la 3ème forge s'est révélée la plus intense,
comme elle nécessitait de travailler aussi la <strong>mise en page</strong> pour rendre le jeu agréable à lire.</p>
<p>Cette fois, j'ai donc reçu ces deux forges précédentes comme point de départ :</p>
<ul>
<li><a href="images/2021/12/ABLHU-1.pdf">ABLHU-1 (PDF 4 pages, 100 Ko)</a> : la forge initiale d'<a href="https://blog.altay.fr">Altay</a></li>
<li><a href="images/2021/12/ABLHU-2.pdf">ABLHU-2 (PDF 28 pages, 216 Ko)</a> : la seconde forge de <a href="https://mobile.twitter.com/KerganKergan">Kergan</a></li>
</ul>
<p>À nouveau, il m'a fallu un petit temps avant de m'approprier le jeu,
d'identifier quels éléments me plaisaient et dont je pouvais me servir comme fondations.
Au début, le côté « <a href="https://fr.wikipedia.org/wiki/Pulp_(magazine)"><em>pulp</em></a> lovecraftien potache » ne m'emballait pas trop, mais certaines excellentes idées ressortaient du lot :
jouer une partie comme un <em>debriefing</em> de mission déjà menée à bien,
la mécanique ludique <strong>et</strong> narrative des <em>Faux Rapports</em>, carrément géniale,
les rôles tournants (que je n'ai finalement pas conservé)...
Et sur le plan littéraire, certains passages comme l'intro étaient vraiment bien écrits et drôles !</p>
<p>Après une phase de recherche d'illustrations,
j'ai finalement pondu cette forge finale en 3 jours (et 3 courtes nuits) :</p>
<p><a href="https://ptgptb.fr/3FF6/etape3/ABLHU-3.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2021/12/ABLHU-3-cover.jpg">
<figcaption>Le Placard des Profondeurs<br>(PDF 28 pages, 3.2 Mo)</figcaption>
</figure></p>
<p></a></p>
<p>Les mécaniques des règles étaient initialement plus riches, plus proche de la forge précédente,
mais une partie de <em>playtest</em> la veille de la <em>deadline</em> (merci Aurélien, Henri & Nicolas !)
m'a convaincu de « tailler dans le gras » et de les simplifier grandement.
J'ai aussi piqué la mécanique des « Traits corrompus » dans <em>Fate of Cthulhu</em>,
telle que décrite quelques mois plus tôt <a href="https://hu-mu.blogspot.com/2021/06/cthulhu-cest-ton-destin.html">dans un article sur <em>Hugin & Munin</em></a>.</p>
<p>J'avais initialement pour ambition d'employer cette opportunité, le défi 3FF,
pour apprendre à utiliser le logiciel libre de PAO <a href="https://scribus.fr">Scribus</a>.
Néanmoins, après avoir installé l'outil et regardé un tuto vidéo,
j'ai réalisé que je n'aurais jamais le temps de le prendre en main en 3 jours...
Je me suis donc rabattu sur <a href="https://fr.libreoffice.org/discover/writer/">Libre Office Writer</a>,
qui m'a foi a très bien fait l'affaire !
D'ailleurs, s'il y a une chose que je retire de cette expérience en termes de mise en page,
c'est <strong>l'importance d'une image de fond en filigrane</strong> :
c'est assez simple à mettre en place, et ça donne tout de suite un côté « pro » au jeu !
De manière générale, je me suis inspiré des <a href="https://ptgptb.fr/conseils-de-mise-en-page-de-jdr">conseils de Benoit Huot</a> ainsi que de <a href="https://alias.erdorin.org/jeu-de-role/jeuderologie/latelier-de-creation/">ceux de Stéphane Gallay</a> : merci !</p>
<p><img alt="Illustration Dinner par l'artiste Dumaker" src="images/2021/12/Dumaker-Dinner.jpg"></p>
<p>Un truc m'a chiffonné lorsque j'ai reçu les 2 premières forges :
je ne comprenais pas du tout pourquoi le jeu s'appelait ainsi !
C'est donc assez amusant : le titre n'a pas changé au cours des 3 forges,
mais le concept du <em>Placard</em>, une base sous-marine secrète, n'a été introduite qu'à la toute fin du défi ! 😄</p>
<p>Niveau inspirations, je me suis au final beaucoup rapproché de <em>X-Files</em>,
avec même l'introduction d'une mécanique centrale qui encourage chaque joueur à défendre sa théorie sur la <em>Vérité Secrète</em>.
J'ai fini la rédaction du jeu en ajoutant deux scénarios, inspirés de <em>Videodrome</em> de David Cronenberg
ainsi que de la série <em>The Lost Room</em>.</p>
<h1>Mes jeux favoris</h1>
<p>Tous les jeux issus du défi sont disponible sur <a href="https://ptgptb.fr/defi-troisfoisforge-6-les-jeux">cette page</a>.</p>
<p>La dernière phase du défi consistait en la rédaction par tous les participants de critiques structurées
sur 4 jeux d'autres auteurs qui lui étaient attribués.
C'était un exercice très intéressant, et j'espère que mes retours seront compris par les auteurs concernés, voir utiles. À l'heure où j'écris ces lignes, je n'ai pas encore reçu les retours sur P², mais j'ai hâte !</p>
<p>Je prends note néanmoins de cette perspective éclairée de Régis, co-organisateur du défi, sur Discord :</p>
<blockquote>
<p>Mais je me dois de te dire la vérité ; la plupart des auteurs (je suis auteur) s'assoient sur les conseils d'amélioration, fulminent en se disant "Il n'a pas compris mon OEuvre ! Il ignore les Contraintes auxquelles <em>j'ai </em>été soumis !", haïssent leur jeu parce qu'il n'est pas finaliste, et l'abandonnent aux griffes de l'oubli car ce serait trop fatiguant de tenir compte des retours pour l'améliorer...</p>
</blockquote>
<p>😂</p>
<p>J'ai aussi pris le temps de lire <strong>toutes</strong> les 3e forges résultantes du défi,
soit 30 jeux, et j'ai été épaté de la créativité et de la variété des jeux qui ont été conçus !
Voici une petite sélection de mes jeux préférés, auxquels je me vois bien jouer à l'occasion, sans ordre particulier :</p>
<p><a href="https://ptgptb.fr/3FF6/etape3/TRAQUEE-3.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2021/12/LaReineEtLEclipse-preview.jpg">
<figcaption>La Reine et l'Eclipse par CorenDM, <a href="https://www.badnewsonradio.fr/trois-fois-forge-6/">Amatsu</a> & DeReel<br>(PDF 16 pages, 2.5 Mo)</figcaption>
</figure></p>
<p></a></p>
<blockquote>
<p>La Reine & l’Éclipse est un jeu de rôle « one-shot » qui met en scène la survie d’une famille dans un environnement teinté de médiéval et de fantasy. L’accent étant mis sur les interactions entre les personnages et leurs réactions envers les situations au cours de leur périple vers le Temple de Sol.</p>
</blockquote>
<p>Le thème est bien trouvé, original et percutant, j’adore !
Le jeu brille par son concept : il est très alléchant de se plonger dans cette histoire désespérée...
Un format de partie <em>one-shot</em>, une ambiance qui vous happe, une playlist, de magnifiques illustrations, un choix de route à prendre sur une carte…
Beaucoup d’arguments pour me plaire !</p>
<p>Pour moi, il ne lui manque que quelques retouches - peut-être étoffer le scénario et revoir un peu le système - pour pouvoir devenir un « classique » du one-shot tel <a href="lady-blackbird.html">Lady Blackbird</a>.</p>
<p><a href="https://ptgptb.fr/3FF6/etape3/BIROPE-3.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2021/12/FabliauxEtFaceties-preview.jpg">
<figcaption>Fabliaux & Facéties par Goto Van Kern, Benoit & MugMax<br>(PDF 29 pages, 38 Mo)</figcaption>
</figure></p>
<p></a></p>
<blockquote>
<p>Fabliaux & Facéties propose de jouer des animaux dignes du roman de Renart, dans un univers imaginaire au carrefour du monde des fables et de la littérature picaresque.
Vous jouerez des vagabonds devant ruser pour survivre à l'hiver et avancer dans leur périple vers la Terre Promise.</p>
</blockquote>
<p>Une mise en page très réussie, des illustrations parfaitement adaptées à l'ambiance,
un texte remarquablement bien écrit...
De belles idées ludiques également : l'idée de la Potence à dessiner progressivement,
la mécanique du Désespoir (repousser un problème à plus tard, aggravé),
les liens entre vagabonds, simple & efficace,
ou encore les 3 objectifs clairs à remplir à chaque partie.
Détail qui tue : la feuille de personnage comporte un rappel du système de jeu 🤩</p>
<p><a href="https://ptgptb.fr/3FF6/etape3/MILDEFF-3.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2021/12/LaForetDesRevesPerdus-review.jpg">
<figcaption>La Forêt des Rêves Perdus par <a href="https://dereel.itch.io">DeReel</a>, Tolkraft & Xav<br>(PDF 20 pages, 8.8 Mo)</figcaption>
</figure></p>
<p></a></p>
<blockquote>
<p>« La Forêt des Rêves Perdus est une dimension onirique et forestière où votre troupe errante croise des lambeaux d'histoires. Un jeu narratif OSR "traditionnel" avec MJ tournant pour 2 à 8 convives. »</p>
</blockquote>
<p>Une formidable mécanique de « Piste » à explorer,
des règles originales, faisant la part belle à la narration (bien qu'un peu obscures parfois),
et surtout un texte de grande qualité littéraire, créant une ambiance mystérieuse / fantastique / onirique.
En bonus : plein de conseils de méta-jeu pertinents, où l'on sent un effort de réflexion / rédaction et non un simple copié-collé, comme un système des gestuelles de communication très bien vu, que je ne connaissais pas.</p>
<p><a href="https://ptgptb.fr/3FF6/etape3/TREEYIN-3.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2021/12/ClairObscur-preview.jpg">
<figcaption>Clair-Obscur par Dak, Grimsnow & Yakaab<br>(PDF 13 pages, 1.6 Mo)</figcaption>
</figure></p>
<p></a></p>
<blockquote>
<p><em>Clair-Obscur</em> est un jeu de rôles à narration partagée, sans meneur ni fiche de personnage.
Dans un monde en plein effondrement, les joueuses incarnent, au gré de scènes, un groupe de <em>Porte-Nuits</em>, en lutte pour leur survie et leur âme, jouets de forces qui les dépassent.</p>
</blockquote>
<p>Le thème m’a personnellement bien inspiré, et je l’ai trouvé admirablement travaillé et intégré au jeu. Le système est original, invite à la narration partagée et me semble globalement assez équilibré. Les règles sont très clairement décrites, et la mise en page est réussie. Chapeau bas !</p>
<p><a href="https://ptgptb.fr/3FF6/etape3/LUCITOUR-3.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2021/12/LeMur-preview.jpg">
<figcaption>Le Mur par Eikseï, Frédéric & Caem<br>(PDF 16 pages, 1.2 Mo)</figcaption>
</figure></p>
<p></a></p>
<blockquote>
<p>Vous êtes un pariétal. Vous avez toujours vécu le long du Mur, dans l’espoir qu’un jour, vous serez capables d’en tirer les secrets. Votre parcours sera semé d’embûches. Des Sirènes aux Draconides, des Failles Entropiques aux Echoués, vous serez invité à vivre une aventure au-delà des nuages.</p>
</blockquote>
<p>Un concept & une ambiance originale & très séduisants.
Une campagne de jeu à la finalité annoncée, avec grande révélation
➞ ⚠️ <em>spoilers</em> : ne lisez pas le jeu si vous voulez être joueurs !
Et une table de rencontres inspirante, promettant des parties pleine d'aventures surprenantes.</p>
<p><a href="https://ptgptb.fr/3FF6/etape3/USUSMI-3.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2021/12/Snitch-preview.jpg">
<figcaption>Snitch par Nitz, Pounous & Tolkraft<br>(PDF 13 pages, 2.7 Mo)</figcaption>
</figure></p>
<p></a></p>
<blockquote>
<p>New York, 1933. La guerre des gangs. Les gangsters ont décidé de faire le ménage : trouver les
balances et leur régler leur compte...
Un jeu de rôle sans meneur et sans préparation, pour 3 à 5 joueurs, pour des parties de 20 à 30 minutes.</p>
</blockquote>
<p>Un JdR qui se rapproche beaucoup du jeu de société de déduction, façon <em>Petits Meurtres et Faits Divers</em>
et même un peu <em>Qui est-ce ?</em> 😊
J'ai beaucoup aimé la thématique de l'interrogatoire, avec un captif aveuglé par une lampe.</p>
<p>Les règles sont très claires & bien structurées, et le matériel de jeu efficace & épuré.
Du point de vue d'un jeu de société, les mécaniques m'ont l'air très prometteuses par leur redoutable simplicité, si le jeu se révèle équilibré. Et le verni <em>jeu de rôle</em> apporte clairement de l'immersion !
D'ailleurs, à mon goût, il sera intéressant d'introduire plus de conseils / mécaniques de jeu encourageant l'impro.</p>
<p>Le concept du <em>PJ central face aux autres joueurs</em> me rappelle un peu <a href="https://chezsoi.org/lucas/blog/dans-la-nuit-longue-et-glacee.html"><em>Dans la nuit longue et glaciale</em> de Damien “Rahyll” C</a>. Et l'interrogatoire m'a rappelé le JdR pour 2 joueurs <a href="https://awarestudios.blogspot.com/2013/05/techniques-dinterrogation.html"><em>Techniques d'interrogation à l'usage des agents de la fonction publique de la glorieuse république populaire de Strana</em> de Gregory Pogorzelski (Gamechef 2013)</a>.</p>
<p>Au delà du petit monde du jeu de rôle, je pense qu'une version <em>Director's Cut</em> mériterait d'être diffusée dans <a href="https://pnpfrance.wordpress.com/ou-chercher/">l'univers du jeu de société <em>Print & Play</em></a>.</p>
<p><img alt="Paire de dés" src="https://chezsoi.org/lucas/jdr/dice.png"></p>
<p>Un grand bravo également aux auteurs de ces jeux,
qui m'ont beaucoup plu mais auxquels je ne me vois pas trop jouer :</p>
<ul>
<li><a href="https://ptgptb.fr/3FF6/etape3/QUANGLE-3-Whidou.pdf">L'Éléphant qui se rêvait Fourmi</a>,
par Whidou, Dak & Papaphilo,
un JdR à propos d'animaux de la jungle, accessible aux enfants,
avec une mécanique de jeu excellente et une mise en page magnifique</li>
</ul>
<!--
Pas sûr cependant que la mécanique de jeu sans MJ fonctionne bien avec des enfants.
Le Chemin de l’Imaginaire ( https://ptgptb.fr/3FF6/etape3/POOLYEA-3.pdf - nécessite 1d10 & un paquet de cartes ) pourrait aussi être sympa avec des enfants, mais il m'a moins emballé par rapport aux autres jeux
-->
<ul>
<li>
<p><a href="https://ptgptb.fr/3FF6/etape3/VENVIP-3.pdf">Faerenissima</a>,
par Fabien C, Fantome AS & Kergan,
un jeu d'intrigues et de mystères,
dans un carnaval perpétuel d'une Venise alternative,
avec un système très élégant à base de cartes,
qui m'a rappelé <em>Blades in the Dark</em> & <em>Heart: The City Beneath</em>
pour les étapes narratives prédéfinies à accomplir (j'adore !)</p>
</li>
<li>
<p><a href="https://ptgptb.fr/3FF6/etape3/LOWPOT-3.pdf">Les Secrets de l’École des Sorciers</a>,
par Rappar, Comicsoap & Fantome AS,
un jeu <a href="https://en.wikipedia.org/wiki/Powered_by_the_Apocalypse">PbtA</a> très bien écrit et très drôle
où l'on joue de jeune sorciers dans une version humoristique d'un célèbre roman</p>
</li>
<li>
<p><a href="https://ptgptb.fr/3FF6/etape3/ROULDE-3.pdf">PérégriNation</a>,
par Tolkraft, Omega White & Frogeaters,
un jeu cyberpunk se déroulant en 2167 en Egypte, où les joueuses incarnent des hôtes humains vivant plus ou moins en harmonie
avec des parasites extra-dimensionnels, les pérégrins.
La mise en page de Tolkraft est magnifique!</p>
</li>
</ul>
<h1>Le mot de la fin</h1>
<p>Tout d'abord, un grand merci aux orgas, <strong>Régis</strong> & <strong>Sylmenas</strong> :
c'est génial de proposer cet événement de création collaborative !
Vous faites un taf' fantastique, bénévolement, un gros <strong>BIG UP</strong> pour ça 👍 🙏 😍.</p>
<p>Durant le défi, un <em>channel</em> <a href="https://discord.com">Discord</a> permettait de discuter entre participants
sur le serveur <a href="https://discord.gg/VnA7cur">PTGPTB</a>.
C'était l'occasion de partager nos réflexions, nos galères parfois,
nos ressentis en découvrant les forges qu'on recevait, et la panique avant les <em>deadlines</em> !
C'était un bel espace d'échanges que j'ai apprécié.</p>
<p>Le défi est désormais terminé. Certains projets continueront peut-être ensuite.
Je l'espère en tout cas, au vu de la qualité des jeux produits !
Une <em>e-convention</em> devrait aussi avoir lieu le 1er ou 2e week-end de janvier,
qui nous donnera l'occasion de tester les jeux.</p>
<p>Les jeux issus du défi 3FF sont de véritable <strong>indie JdRs</strong>, par analogie avec <a href="https://en.wikipedia.org/wiki/Indie_game">les jeux vidéos indépendants</a>.
Ce sont des <strong>pépites brutes</strong>, qui proposent des expériences de jeu véritablement originales,
hors des sentiers battus. J'espère que ces pépites sauront conquérir le cœur de nombreux rôlistes.</p>
<p>Et sincères félicitations à tous les participants d'avoir réalisé le défi jusqu'au bout !</p>
<h2>EDIT [13/12/2021] Playtest de <em>Snitch!</em></h2>
<p>La semaine dernière j'ai eu l'occasion de tester <em>Snitch!</em> avec quatre copains.
Nous avons employé <a href="images/2021/12/USUSMI-3-printer-friendly.pdf">une version <em>printer friendly</em></a> du jeu.
Le hasard a voulu que je sois <strong>le captif</strong>, interrogé par les autres joueurs, <strong>les mafieux</strong>.
La prise en main s'est fait très facilement, et les deux joueurs non familiers des jeux de rôle
n'ont eu aucun mal à se plonger dans le jeu.</p>
<p>Globalement, voici les retours que j'ai collecté en fin de partie :</p>
<ul>
<li>le jeu est <strong>fun</strong> ! Le principe et la thématique ont plu</li>
<li>on est plus proche d'un <strong>jeu de plateau</strong> que d'un jeu de rôle,
et les séquences où l'ont s'essayait à jouer notre rôle un peu théâtralement en début de partie
se sont petit à petit espacées et raccourcies pour finalement complètement s'estomper face à la mécanique de déduction au cœur du jeu. En l'absence de mécaniques de jeu encourageant le <em>roleplay</em>, celui-ci est probablement voué à rester marginal.</li>
<li>il nous a semblé qu'il y avait le <strong>bon nombre de personnages</strong> : plus et cela deviendrait laborieux; moins rendrait probablement les choses trop faciles</li>
<li>certains joueurs ont trouvé dommage d'être autant <strong>limités sur les questions</strong> qu'ils pouvaient poser.
Certaines questions "hors règles" ont d'ailleurs été tentées : <em>Est-ce qu'une des taupes est une femme ?</em> <em>Est-ce que le nom d'une des balances finit en "o" ?</em></li>
<li>toujours au sujet des questions, les règles n'indiquent pas clairement si on peut poser une question concernant l'activité 1 des personnages, l'activité 2, ou concernant les deux activités à la fois</li>
<li>l'<strong>équilibrage</strong> est encore à peaufiner un peu : j'ai <strong>perdu en tant que captif</strong>, et il m'a paru très difficile de découvrir l'identité des 4 mafieux face à moi</li>
</ul>
<p>En tout cas c'était très plaisant de tester ce court jeu expérimental !
Un grand merci aux auteurs 😉</p>
<h2>EDIT [23/12/2021] Classement final de P²</h2>
<p><em>Le Placard des Profondeurs</em> termine finalement à la 12e place sur 30.</p>
<p>Merci aux relecteurs @Tolkraft, @Eikseï, @Pounous & @Xav pour leurs feedbacks très construits & pertinents !</p>
<p>Je pense sortir une version <em>Director's Cut</em> la semaine prochaine,
basée sur leurs retours et quelques idées qui me sont venues depuis...</p>
<style>
article img { max-height: 20rem; }
</style>Script de mise à jour de page d'accueil ForumActif2021-11-26T10:30:00+01:002021-11-26T10:30:00+01:00Lucas Cimontag:chezsoi.org,2021-11-26:/lucas/blog/script-de-mise-a-jour-de-page-daccueil-forumactif.html<p>Un court article pour partager une méthode bien pratique pour mettre à jour la page d'acceuil d'un forum <a href="https://fr.wikipedia.org/wiki/Forumactif">ForumActif</a> :</p>
<p><a href="https://laubergedesreveurs.forumactif.com/"><img alt="Logo de l'association L'Auberge des Rêveurs" src="images/2021/11/logo-auberge-des-reveurs.webp"></a></p>
<h2>Contexte</h2>
<p>Une association de jeux de rôle a mis en place un site <a href="https://laubergedesreveurs.forumactif.com/forum">ForumActif</a>.
Cet hébergeur inclus dans ses forums phpBB la possibilité de créer des pages HTML statiques,
et de …</p><p>Un court article pour partager une méthode bien pratique pour mettre à jour la page d'acceuil d'un forum <a href="https://fr.wikipedia.org/wiki/Forumactif">ForumActif</a> :</p>
<p><a href="https://laubergedesreveurs.forumactif.com/"><img alt="Logo de l'association L'Auberge des Rêveurs" src="images/2021/11/logo-auberge-des-reveurs.webp"></a></p>
<h2>Contexte</h2>
<p>Une association de jeux de rôle a mis en place un site <a href="https://laubergedesreveurs.forumactif.com/forum">ForumActif</a>.
Cet hébergeur inclus dans ses forums phpBB la possibilité de créer des pages HTML statiques,
et de les utiliser comme page d'accueil :</p>
<p><img alt="Capture d'écran du module d'administration des pages HTML" src="images/2021/11/ForumActif-AdminPanel.jpg"></p>
<p>J'ai donc développé une page d'accueil assez simple mais un peu plus attractive / lisible
que celle "de base" des sites ForumActif.</p>
<p>Le code est herbergé sur FramaGit : <a href="https://framagit.org/auberge-des-reveurs/website-homepage/">https://framagit.org/auberge-des-reveurs/website-homepage/</a> (❤️ Gitlab & <a href="https://framasoft.org/">FramaSoft</a>)</p>
<p>Et tout le contenu du site (images, CSS, JS...) est hebergé via des <a href="https://docs.gitlab.com/ee/user/project/pages/">Gitlab Pages</a> :
<a href="https://auberge-des-reveurs.frama.io/website-homepage/">https://auberge-des-reveurs.frama.io/website-homepage/</a></p>
<p>Néanmoins, l'activation / mise à jour de la page d'accueil statique sur ForumActif doit se faire manuellement,
via l'interface d'administration du forum.
Hors, je souhaitais que la page d'accueil du site soit mise à jour <strong>automatiquement</strong>,
à chaque <em>commit</em> sur le <em>repository</em> <code>git</code>,
via la <a href="https://docs.gitlab.com/ee/ci/pipelines/">pipeline Gitlab CI</a> qui déployait déjà le site en Gitlab Pages.</p>
<h2>Solution</h2>
<p>J'ai développé un court script Bash employant <code>curl</code> & <code>python</code> qui permet de :</p>
<ol>
<li>s'authentifier sur le forum, et ainsi récupérer un <em>cookie</em> <code>sid</code> et un <em>token</em> <code>tid</code></li>
<li><em>uploader</em> une nouvelle version de la page statique (<code>index.html</code>)</li>
</ol>
<p>Le script est ici : <a href="https://framagit.org/auberge-des-reveurs/website-homepage/-/blob/main/set_phpbb_html_homepage.sh">set_phpbb_html_homepage.sh</a></p>
<p>Et la pipeline est décrite ici : <a href="https://framagit.org/auberge-des-reveurs/website-homepage/-/blob/main/.gitlab-ci.yml">.gitlab-ci.yml</a></p>
<p>J'espère que ça pourra être utile à d'autres administrateurs de sites ForumActif 😋</p>Hacktoberfest on fpdf2 & v2.4.62021-11-19T14:00:00+01:002021-11-19T14:00:00+01:00Lucas Cimontag:chezsoi.org,2021-11-19:/lucas/blog/hacktoberfest-on-fpdf2.html<p>Last month, I realized late that October was <a href="https://hacktoberfest.digitalocean.com">hacktoberfest</a> month!</p>
<p><a href="https://hacktoberfest.digitalocean.com"><img alt="Hacktoberfest 2021 logo" src="images/2021/11/hacktoberfest.jpg" style="max-height: 16rem"></a></p>
<p>This online event is a month-long celebration (October 1-31) of open source software run in partnership with different software companies, with a focus on encouraging contributions to open source projects.</p>
<p>While I participated in the 2019 edition as a contributor …</p><p>Last month, I realized late that October was <a href="https://hacktoberfest.digitalocean.com">hacktoberfest</a> month!</p>
<p><a href="https://hacktoberfest.digitalocean.com"><img alt="Hacktoberfest 2021 logo" src="images/2021/11/hacktoberfest.jpg" style="max-height: 16rem"></a></p>
<p>This online event is a month-long celebration (October 1-31) of open source software run in partnership with different software companies, with a focus on encouraging contributions to open source projects.</p>
<p>While I participated in the 2019 edition as a contributor, with <a href="https://github.com/kleph">@kleph</a> & <a href="https://github.com/minitux">@minitux</a>,
this year I wanted to involve myself as a maintainer around the <a href="https://pyfpdf.github.io/fpdf2/">fpdf2</a> Python project,
a minimalist PDF creation library.</p>
<p><a href="https://pyfpdf.github.io/fpdf2/"><img alt="fpdf2 logo" src="https://pyfpdf.github.io/fpdf2/fpdf2-logo.png" style="max-height: 12rem"></a></p>
<p>In this blog post I simply want to share my personal experience with this event.</p>
<h2>Set up</h2>
<p>The first tier of October had already passed when I realized hacktoberfest had started,
so I quickly took a few actions to ensure the <code>fpdf2</code> project was ready to receive contributions from newcomers:</p>
<ul>
<li>I skimmed through the event rules, and ensured the repo had the <code>hacktoberfest</code> tag</li>
<li>I enabled <a href="https://github.com/PyFPDF/fpdf2/discussions">discussions</a> on the GitHub project,
in hope it would allow for more casual chats with contributors (not a massive success)</li>
<li>I tagged some <code>fpdf2</code> <a href="https://github.com/PyFPDF/fpdf2/issues">GitHub issues</a> with the <code>hacktoberfest</code> label,
to make it easy for curious developers to spot issues they could contribute to.
I also tried to redact those issues in a way that would make them accessible and interesting to work on.</li>
<li>I created 3 extra <code>good first issues</code>, aimed at new contributors: <a href="https://github.com/PyFPDF/fpdf2/issues/256">#256</a>,
<a href="https://github.com/PyFPDF/fpdf2/issues/257">#257</a> & <a href="https://github.com/PyFPDF/fpdf2/issues/258">#258</a>.
In the end I implemented one myself because it was fun 😁</li>
<li><strong>post-hacktoberfest</strong>, I also took sometime to write some issue / PR templates,
so that when newcomers attempt to submit a bug report, a feature request, a PR or just ask a question,
they are welcome with some nice explanatory text</li>
</ul>
<h2>A tutorial available in 8 languages!</h2>
<p>Among the <code>fpdf2</code> <a href="https://github.com/PyFPDF/fpdf2/issues">GitHub issues</a> tagged <code>hacktoberfest</code>,
one received an outstanding number of contributions:
an issue suggesting to <a href="https://github.com/PyFPDF/fpdf2/issues/267"><strong>translate the one-page tutorial</strong> (#267)</a>.</p>
<p>What happened is that, mid-October, a contributor <a href="https://github.com/PyFPDF/fpdf2/issues/259">suggested to translate the tutorial in Portugese</a>. I though the idea was great and totally fitted with the context of the hacktoberfest, so I opened issue #267 to encourage other translations.</p>
<p>This was well received as this idea motivated <strong>6 people</strong> to contribute a translation,
making the tutorial available in 8 languages total! <a href="https://pyfpdf.github.io/fpdf2/Tutorial.html">Link to Tutorial</a>.</p>
<h2>Results & personal feedback</h2>
<ul>
<li><strong>more than 50 issues & PRs</strong> have been created since October 1st, on the <code>fpdf2</code> GitHub repo</li>
<li><strong>v2.4.6</strong> of <code>fpdf2</code> has been released, and it is the biggest release since I joined the project: <a href="https://github.com/PyFPDF/fpdf2/blob/master/CHANGELOG.md#246---2021-11-16">v2.4.6 ChangeLog</a></li>
<li><code>fpdf2</code> reached <strong>300 stars ⭐</strong> on GitHub! ✨ 🥳 🎉 🎈 🥂</li>
<li>it seems like the <strong>peak downloads per day almost doubled</strong> since September: <a href="https://pepy.tech/project/fpdf2?versions=2.3.5&versions=2.4.2&versions=2.4.3&versions=2.4.5">Pepy stats</a>, <a href="https://pypistats.org/packages/fpdf2">PypiStats</a></li>
</ul>
<p><img alt="fpdf2 download statistics" src="images/2021/11/fpdf2-download-stats.png"></p>
<p>A huge <em>THANKS!</em> to all the people who made contributions to <code>fpdf2</code> since October 1st:</p>
<ul>
<li><a href="https://github.com/gmischler">Georg Mischler</a> who made several major refactoring,
like introducing a <a href="https://pyfpdf.github.io/fpdf2/fpdf/template.html#fpdf.template.FlexTemplate">FlexTemplate</a> class, and <a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.local_context">FPDF.local_context()</a></li>
<li><a href="https://github.com/portfedh">@portfedh</a> who added section 5 & 6 to the tutorial,
just in time before the big wave of hacktoberfest translations!</li>
<li><a href="https://github.com/tabarnhack">@tabarnhack</a> who implemented the new <a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.arc">arc()</a> and <a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.solid_arc">solid_arc()</a> methods</li>
<li><a href="https://github.com/Mridulbirla13">Mridul Birla</a> who contributed the Hindi translation of the tutorial</li>
<li><a href="https://github.com/digidigital">@digidigital</a> who contributed the German translation of the tutorial</li>
<li><a href="https://github.com/xit4">@Xit</a> who contributed the Italian translation of the tutorial</li>
<li><a href="https://github.com/AABur">Alexander Burchenko</a> who contributed the Russian translation of the tutorial</li>
<li><a href="https://github.com/fuscati">André Assunção</a> who contributed the Portuguese translation of the tutorial</li>
<li><a href="https://github.com/Tititesouris">Quentin Brault</a> who contributed the French translation of the tutorial</li>
<li><a href="https://github.com/bettman-latin">@bettman-latin</a> who implemented the new <a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.regular_polygon">regular_polygon()</a> method</li>
<li><a href="https://github.com/paulacampigotto">Paula Campigotto</a> who reported a tricky bug with automated page breaks for two-columns documents, and also submitted <a href="https://github.com/PyFPDF/fpdf2/pull/281">a PR</a> to fix it</li>
</ul>
<p>It was really an amazing experience for me,
and I had this great feeling of being part of some wave of positive human energy!</p>
<h2>Notable new features in fpdf2 v2.4.6</h2>
<p><strong>Temporary changes to graphics state variables</strong> are now possible using <code>with pdf.local_context():</code> ... <a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.local_context">Docstring</a>.</p>
<p>A mechanism to <strong>detect & downscale oversized images</strong>:
<a href="https://pyfpdf.github.io/fpdf2/Images.html#oversized-images-detection-downscaling">dedicated documentation</a>.
<a href="https://github.com/PyFPDF/fpdf2/discussions">Feedbacks</a> on this new feature are very welcome! Has it been useful to you? Do you see behaviour / usage improvements?</p>
<p>New drawing methods have also been introduced:</p>
<ul>
<li><a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.arc"><code>FPDF.arc</code></a> & <a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.solid_arc"><code>FPDF.solid_arc</code></a> to draw arcs. A solid arc combines an arc and a triangle to form a pie slice: ...</li>
<li><a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.regular_polygon"><code>FPDF.regular_polygon</code></a></li>
</ul>
<p><strong>Documentation improvements</strong>:</p>
<ul>
<li>new documentation on how to display equations, using Google Charts or <code>matplotlib</code>: <a href="https://pyfpdf.github.io/fpdf2/Maths.html">Maths</a></li>
<li>
<p>new sections have been added to <a href="https://pyfpdf.github.io/fpdf2/Tutorial.html">the tutorial</a>:</p>
<ul>
<li><a href="https://pyfpdf.github.io/fpdf2/Tutorial.html#tuto-5-creating-tables">Creating Tables</a></li>
<li><a href="https://pyfpdf.github.io/fpdf2/Tutorial.html#tuto-6-creating-links-and-mixing-text-styles">Creating links and mixing text styles</a></li>
</ul>
</li>
<li>
<p>the whole documentation can now be downloaded as a PDF: <a href="https://pyfpdf.github.io/fpdf2/fpdf2-manual.pdf">fpdf2-manual.pdf</a></p>
</li>
</ul>
<p><a href="https://pyfpdf.github.io/fpdf2/fpdf2-manual.pdf"><img alt="fpdf2 PDF manual screenshot" src="images/2021/11/fpdf2-pdf-manual-preview.jpg" style="max-height: 16rem"></a></p>
<h2>Final words</h2>
<p>While I had a great time during this Hacktoberfest,
I think that for the next open source / libre software contribution event,
I'll try to pick one where I can be <strong>physically present</strong>.</p>
<p>I simply would have loved chat with the contributors at the end of the event!
To get to know a little who they are and what are their reasons for contributing.</p>
<p>Happy coding to you all!</p>
<!-- Com'
* [x] notified contributors on their PRs
* [x] https://linuxfr.org/users/lucas-c/liens/retour-d-experience-du-hacktoberfest-comme-mainteneur-du-projet-fpdf2
* [x] https://www.journalduhacker.net/s/udvgeu/hacktoberfest_on_fpdf2_v2_4_6
* [x] https://www.reddit.com/r/hacktoberfest/comments/qxguse/hacktoberfest_on_fpdf2_v246/
-->La Brigade du Chaos2021-09-18T17:00:00+02:002021-09-18T17:00:00+02:00Lucas Cimontag:chezsoi.org,2021-09-18:/lucas/blog/la-brigade-du-chaos.html<!-- Com'
* [x] blog post & page jeux-de-role
* [x] https://discord.com/invite/uFn5zJZrt2
* [x] https://www.deviantart.com/drmaxkurt/journal/Excellent-JdR-gratuit-la-Brigade-du-Chaos-892253402
* [x] https://www.cestpasdujdr.fr/lettrpg/
* [x] email aux playtesteurs
* [x] https://forums.ffjdr.org/t/la-brigade-du-chaos/634
* [x] https://www.casusno.fr/viewtopic.php?f=8&t=39634
* [x] https://forum.cwowd.com/t/gratuit-traduction-la-brigade-du-chaos/25072
* [x] https://opale-roliste.com/forum/ressources/vos-creations/gratuit-traduction-brigade-du-chaos
* [x] https://www.scenariotheque.org/Document/info_doc.php?id_doc=10418
-> référence : https://lucas-c.itch.io/la-brigade-du-chaos-freiberg-nid-despions
* [x] Discord Auberge des rêveurs
* [x] https://www.trictrac.net/forum/sujet/traduction-gratuit-la-brigade-du-chaos
* [x] https://rpggeek.com/thread/2728434/article/38492614#38492614
* [x] https://forum.canardpc.com/threads/12905-JDR-Jeux-de-R%C3%B4le-Topic-G%C3%A9n%C3%A9ral?p=13548708&viewfull=1#post13548708
* [x] https://www.reddit.com/r/jdr/comments/pt2p93/gratuit_oneshot_traduction_la_brigade_du_chaos/
* [x] http://www.legrog.org/jeux-amateurs/la-brigade-du-chaos
* [wip] https://www.annuairejdr.fr
-->
<blockquote>
<p>Incarnez la Brigade du Chaos, une escouade d'orcs chahuteurs en mission pour « infiltrer » une ville humaine afin de kidnapper l'ignoble prince Holstein.</p>
</blockquote>
<p><em><a href="https://lucas-c.itch.io/la-brigade-du-chaos"><img alt="Couverture du jeu Havoc Brigade" src="images/2021/09/Havoc-Brigade-cover.jpg"></a>
<center>(cliquez sur l'image pour accéder au jeu)</center></em></p>
<p>La Brigade du Chaos est la <a href="/lucas/blog/tag/traduction.html">traduction</a> de <strong>Havoc Brigade</strong> de Grant Howitt.</p>
<blockquote>
<p>C'est un jeu au ton léger. Ce n'est …</p></blockquote><!-- Com'
* [x] blog post & page jeux-de-role
* [x] https://discord.com/invite/uFn5zJZrt2
* [x] https://www.deviantart.com/drmaxkurt/journal/Excellent-JdR-gratuit-la-Brigade-du-Chaos-892253402
* [x] https://www.cestpasdujdr.fr/lettrpg/
* [x] email aux playtesteurs
* [x] https://forums.ffjdr.org/t/la-brigade-du-chaos/634
* [x] https://www.casusno.fr/viewtopic.php?f=8&t=39634
* [x] https://forum.cwowd.com/t/gratuit-traduction-la-brigade-du-chaos/25072
* [x] https://opale-roliste.com/forum/ressources/vos-creations/gratuit-traduction-brigade-du-chaos
* [x] https://www.scenariotheque.org/Document/info_doc.php?id_doc=10418
-> référence : https://lucas-c.itch.io/la-brigade-du-chaos-freiberg-nid-despions
* [x] Discord Auberge des rêveurs
* [x] https://www.trictrac.net/forum/sujet/traduction-gratuit-la-brigade-du-chaos
* [x] https://rpggeek.com/thread/2728434/article/38492614#38492614
* [x] https://forum.canardpc.com/threads/12905-JDR-Jeux-de-R%C3%B4le-Topic-G%C3%A9n%C3%A9ral?p=13548708&viewfull=1#post13548708
* [x] https://www.reddit.com/r/jdr/comments/pt2p93/gratuit_oneshot_traduction_la_brigade_du_chaos/
* [x] http://www.legrog.org/jeux-amateurs/la-brigade-du-chaos
* [wip] https://www.annuairejdr.fr
-->
<blockquote>
<p>Incarnez la Brigade du Chaos, une escouade d'orcs chahuteurs en mission pour « infiltrer » une ville humaine afin de kidnapper l'ignoble prince Holstein.</p>
</blockquote>
<p><em><a href="https://lucas-c.itch.io/la-brigade-du-chaos"><img alt="Couverture du jeu Havoc Brigade" src="images/2021/09/Havoc-Brigade-cover.jpg"></a>
<center>(cliquez sur l'image pour accéder au jeu)</center></em></p>
<p>La Brigade du Chaos est la <a href="/lucas/blog/tag/traduction.html">traduction</a> de <strong>Havoc Brigade</strong> de Grant Howitt.</p>
<blockquote>
<p>C'est un jeu au ton léger. Ce n'est pas une histoire de bouleversement émotionnel profond et d'introspection
torturée. C'est une histoire d'explosions, de grosses bastons, de vols de trucs, de panique générale et,
surtout, de plans vraiment stupides</p>
<p>C'est un jeu idéal en convention, pour faire découvrir le jeu de rôle, ou comme jeu de rôle « de secours »
lorsque vous n’avez rien préparé.</p>
</blockquote>
<ul>
<li><strong>Genre</strong> : one-shot médiéval-fantastique avec pré-tirés, pour session courte mais fun</li>
<li><strong>Pour</strong>: 1 MJ + 1 à 5 joueurs</li>
<li><strong>Durée</strong> : flexible, quelques heures environ</li>
</ul>
<p>Avant de le traduire, j'avais déjà testé le jeu il y a quelque temps,
et partagé mes impressions ici sur ce blog : <a href="double-brigade.html">Monster Chef & Havoc Brigade</a>.</p>
<p>J'ai également déjà <a href="traduction-de-jdr-monopage-de-grant-howitt.html">traduit plusieurs courts jeux de rôle de M. Grant Howitt</a>.
Merci à son éditeur anglais <a href="https://rowanrookanddecard.com">Rowan, Rook and Decard</a> de m'avoir donné l'autorisation de diffuser cette traduction.</p>
<p>Parmi ses inspirations pour ce jeu, Grant Howitt mentionne <em>Warhammer 40K - Dark Heresy</em>,
<em>Marvel Superheroic Roleplaying</em> et l'excellent <a href="lady-blackbird.html">Lady Blackbird</a> de John Harper.</p>
<p><img class="comic-strip" alt="BD" src="images/2021/09/havoc-brigade-comic-strip.png"></p>
<p><center><em>
Portraits : <a href="https://www.drivethrurpg.com/product/91360/108-Terrible-Character-Portraits">Jeff Preston</a> - <a href="https://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a>
/ Licorne : <a href="https://commons.wikimedia.org/wiki/File:DnD_Unicorn.png">Mariana Ruiz Villarreal (LadyofHats)</a> - <a href="https://creativecommons.org/publicdomain/zero/1.0/deed.fr">CC0</a>
</em></center></p>
<hr>
<p><br></p>
<p>Comme j'ai adoré ce jeu, après l'avoir fait jouer plusieurs fois,
j'ai décidé de concevoir une courte aide de jeu,
afin d'enrichir le matériel ludique et de partager quelques conseils aux MJs qui souhaiteraient le faire jouer.</p>
<p>Voici donc <strong>Freiberg, nid d'espions</strong>:</p>
<p><a href="https://lucas-c.itch.io/la-brigade-du-chaos-freiberg-nid-despions"><img alt="Couverture de l'aide de jeu Freiberg, nid d'espions" src="images/2021/09/medieval_city_concept_by_davidhueso.jpg"></a>
<em><center>(cliquez sur l'image pour accéder à cette aide de jeu)</center></em></p>
<p>Merci à Aurélien, Henri, Loic & Thomas pour les parties de test !</p>
<style>
article img { width: 30rem; }
img.comic-strip { max-height: none; max-width: 100%; }
</style>Solving peg solitaire in Python2021-08-20T17:00:00+02:002021-08-20T17:00:00+02:00Lucas Cimontag:chezsoi.org,2021-08-20:/lucas/blog/solving-peg-solitaire-in-python.html<!-- Com'
* [x] https://fr.wikipedia.org/wiki/Solitaire_(casse-t%C3%AAte)#Solutions
* [x] email at Max Khrapov & Valentin Zulkower
* [x] https://www.reddit.com/r/Python/comments/p8djlz/solving_peg_solitaire_in_python/
* [x] https://www.reddit.com/r/solitaire/comments/p87dw8/animated_gifs_for_solving_european_peg_solitaire/
-->
<p>The other day, while watching <a href="https://fr.wikipedia.org/wiki/La_Carte_aux_tr%C3%A9sors"><em>La Carte aux trésors</em></a>
at my elderly neighbor's house, I casually played <a href="https://en.wikipedia.org/wiki/Peg_solitaire">peg solitaire</a> on a board she has.</p>
<p>After many failures at trying to get rid of all pawns but one, I started to wonder about the mathematics & algorithmics behind that game...</p>
<p>Back home …</p><!-- Com'
* [x] https://fr.wikipedia.org/wiki/Solitaire_(casse-t%C3%AAte)#Solutions
* [x] email at Max Khrapov & Valentin Zulkower
* [x] https://www.reddit.com/r/Python/comments/p8djlz/solving_peg_solitaire_in_python/
* [x] https://www.reddit.com/r/solitaire/comments/p87dw8/animated_gifs_for_solving_european_peg_solitaire/
-->
<p>The other day, while watching <a href="https://fr.wikipedia.org/wiki/La_Carte_aux_tr%C3%A9sors"><em>La Carte aux trésors</em></a>
at my elderly neighbor's house, I casually played <a href="https://en.wikipedia.org/wiki/Peg_solitaire">peg solitaire</a> on a board she has.</p>
<p>After many failures at trying to get rid of all pawns but one, I started to wonder about the mathematics & algorithmics behind that game...</p>
<p>Back home, I was astonished to discover that <strong>there is no solution to the European board with the initial hole centrally located</strong>!
We could have been playing the game for a long, long time without knowing about this...
I was also delighted to discovered <a href="https://en.wikipedia.org/wiki/Peg_solitaire#Strategy">Hans Zantema very elegant proof</a> of this.</p>
<p>Actually, the European board can be beaten by slightly shifting the initial hole position:</p>
<figure>
<img alt="European board shifted" src="images/2021/08/european_board_shifted.png">
<figcaption>Game board shape by Wolfgang H. Wögerer - <a href="https://creativecommons.org/licenses/by-sa/3.0/deed.en">CC BY-SA 3.0</a></figcaption>
</figure>
<p>That's when I decided to write a Python solver in order to beat the game!</p>
<p>And I ended up by producing those GIFs:</p>
<div class="side-by-side">
<figure>
<img alt="Best solution with 2 remaining pawns for the classic european board" src="images/2021/08/peg_solitaire_central_european_solution.gif">
<figcaption>Best solution with 2 remaining pawns<br>for the classic european board</figcaption>
</figure>
<figure>
<img alt="Best solution with 1 remaining pawn for the shifted european board" src="images/2021/08/peg_solitaire_alt_european_solution.gif">
<figcaption>Best solution with 1 remaining pawn<br>for the shifted european board</figcaption>
</figure>
</div>
<p>The source code can be found here: <a href="https://github.com/Lucas-C/dotfiles_and_notes/blob/master/languages/python/peg_solitaire_solver_gif.py">peg_solitaire_solver_gif.py</a>.
It is MIT licensed and the GIFs are under <a href="https://creativecommons.org/publicdomain/zero/1.0/deed.en">CC0</a>.</p>
<p>The solver in itself is relatively simple, and the code not much worth commenting.
I used <a href="https://github.com/mkhrapov/peg-solitaire-solver#algorithm">Max Khrapov heuristic</a>:
without it, even while ignoring board symetries, I couldn't find a solution with less than 4 remaining pawns...
Even after testing more than 10.000.000 board configurations!
But now, it takes only a few seconds to complete on my computer.</p>
<p>The program relies on the <a href="https://pypi.org/project/gizeh/">gizeh</a> & <a href="https://pypi.org/project/moviepy/">moviepy</a> Pypi packages
in order to generate the GIFs, but those dependencies are not needed by the solver.</p>
<style>
img { width: 16rem; }
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
flex-flow: wrap;
}
@media (min-width:768px) {
.side-by-side > * {
max-width: 50%;
margin: 0 1rem;
}
}
</style>Images sous licences libres2021-08-18T14:00:00+02:002021-08-18T14:00:00+02:00Lucas Cimontag:chezsoi.org,2021-08-18:/lucas/blog/images-libres-de-droits.html<!-- Com' :
* [x] https://www.reddit.com/r/creativecommons/comments/p6rt1p/images_libres_de_droits_curated_list_of_websites/
* [x] https://linuxfr.org/users/lucas-c/liens/images-libres-de-droits-recueil-d-illustrations-sous-creative-commons
* [x] https://www.deviantart.com/drmaxkurt/journal/Images-libres-de-droits-pour-illustrer-vos-jeux-889178900
* [x] https://rpggeek.com/thread/2711762/article/38305777
* [x] https://www.casusno.fr/viewtopic.php?f=26&t=39510
* [x] https://github.com/shime/creative-commons-media/issues/18
* [x] https://www.journalduhacker.net/s/ufhwee/images_libres_de_droits
* [x] https://forums.ffjdr.org/t/images-libres-de-droits-pour-illustrer-vos-jeux/578
* [x] https://opale-roliste.com/forum/ressources/liens-utiles/images-libres-droits-illustrer-vos-jeux
* [x] Le Bocal
-->
<p>Ce blog voit l'apparition d'une nouvelle page permanente : <a href="pages/images-libres-de-droits.html">Images libres de droits</a>.</p>
<p>Cette page recense des site web, des illustrateurs, des icônes et des polices de caractères libres de droits.</p>
<p>Depuis octobre 2018, j'ai rassemblé une liste d'artistes dont j'apprécie le travail
et qui placent leurs œuvres sous licence <em>Creative …</em></p><!-- Com' :
* [x] https://www.reddit.com/r/creativecommons/comments/p6rt1p/images_libres_de_droits_curated_list_of_websites/
* [x] https://linuxfr.org/users/lucas-c/liens/images-libres-de-droits-recueil-d-illustrations-sous-creative-commons
* [x] https://www.deviantart.com/drmaxkurt/journal/Images-libres-de-droits-pour-illustrer-vos-jeux-889178900
* [x] https://rpggeek.com/thread/2711762/article/38305777
* [x] https://www.casusno.fr/viewtopic.php?f=26&t=39510
* [x] https://github.com/shime/creative-commons-media/issues/18
* [x] https://www.journalduhacker.net/s/ufhwee/images_libres_de_droits
* [x] https://forums.ffjdr.org/t/images-libres-de-droits-pour-illustrer-vos-jeux/578
* [x] https://opale-roliste.com/forum/ressources/liens-utiles/images-libres-droits-illustrer-vos-jeux
* [x] Le Bocal
-->
<p>Ce blog voit l'apparition d'une nouvelle page permanente : <a href="pages/images-libres-de-droits.html">Images libres de droits</a>.</p>
<p>Cette page recense des site web, des illustrateurs, des icônes et des polices de caractères libres de droits.</p>
<p>Depuis octobre 2018, j'ai rassemblé une liste d'artistes dont j'apprécie le travail
et qui placent leurs œuvres sous licence <em>Creative Commons</em>.
Si vous utilisez leur travail, envisagez svp de soutenir ces artistes, dans la mesure de vos moyens.</p>
<p>En général, mes recherches d'illustrations ont pour but d'illustrer mes jeux.
C'est pourquoi je me concentre sur les licences <em>Creative Commons</em> <strong>sans</strong> clause <em>Non-Derivative</em>.</p>
<p>Cette liste a pris de l'ampleur avec le temps, et je la partage ici en espérant qu'elle inspire et soit utile à d'autres créateurs !</p>
<p>Cette page restera en constante évolution, j'y ajouterai régulièrement de nouvelles découvertes.</p>
<blockquote>
<p>This page lists royalty-free websites, illustrators, icons and fonts.</p>
<p>Since October 2018, I have gathered a list of artists whose work I appreciate and who place their work under a <em>Creative Commons</em> license.
Please consider supporting those artists, within your means, if you use their work.</p>
<p>In general, my research for illustrations is aimed at illustrating my games.
This is why I am focusing on <em>Creative Commons</em> licenses <strong>without</strong> a <em>Non-Derivative</em> clause.</p>
<p>This list has grown over time, and I'm sharing it here hoping it inspires and proves useful to other creators!</p>
<p>This page will remain in constant evolution, I will regularly add new discoveries.</p>
</blockquote>
<ul>
<li><a href="pages/images-libres-de-droits.html#illustrations">Illustrations</a><ul>
<li><a href="pages/images-libres-de-droits.html#modern-artists">Modern artists</a></li>
<li><a href="pages/images-libres-de-droits.html#from-last-century">From last century</a></li>
<li><a href="pages/images-libres-de-droits.html#single-artworks">Single artworks</a></li>
<li><a href="pages/images-libres-de-droits.html#projects--game-assets">Projects & game assets</a></li>
</ul>
</li>
<li><a href="pages/images-libres-de-droits.html#websites">Websites</a><ul>
<li><a href="pages/images-libres-de-droits.html#search-engines">Search engines</a></li>
<li><a href="pages/images-libres-de-droits.html#image-banks">Image banks</a></li>
</ul>
</li>
<li><a href="pages/images-libres-de-droits.html#icons">Icons</a></li>
<li><a href="pages/images-libres-de-droits.html#fonts">Fonts</a></li>
</ul>
<figure>
<img alt="Creative Commons licenses requirements" src="images/CC_License_Requirements.png">
<figcaption>From <a href="https://foter.com/blog/how-to-attribute-creative-commons-photos/">How To Attribute Creative Commons Photos</a> by <a href="https://foter.com">Foter</a> - CC BY-SA</figcaption>
</figure>
<blockquote>
<p>In order to insert illustration thumbnails on this page,
I have developped a <a href="https://blog.getpelican.com">Pelican</a> plugin dedicated to this task: <a href="https://github.com/pelican-plugins/pelican-plugin-image-preview-thumbnailer/">image-preview-thumbnailer</a>.</p>
</blockquote>Bonnes pratiques Gitlab CI2021-07-28T22:00:00+02:002021-07-28T22:00:00+02:00Lucas Cimontag:chezsoi.org,2021-07-28:/lucas/blog/bonnes-pratiques-gitlab-ci.html<p><img alt="Logo Gitlab" src="images/2021/07/gitlab-ci.png"></p>
<p>À <a href="https://www.oui.sncf">E-voyageurs Technologies</a>, je travaille au sein d'une équipe en charge de l'<strong>usine logicielle</strong>,
qui administre depuis plusieurs années une instance Gitlab <em>self-hosted</em>.</p>
<p>Cet article contient quelques-unes de nos recommandations à l'intention des utilisateurs de notre Gitlab,
ayant pour but à la fois <strong>améliorer les performances de leurs <em>pipelines …</em></strong></p><p><img alt="Logo Gitlab" src="images/2021/07/gitlab-ci.png"></p>
<p>À <a href="https://www.oui.sncf">E-voyageurs Technologies</a>, je travaille au sein d'une équipe en charge de l'<strong>usine logicielle</strong>,
qui administre depuis plusieurs années une instance Gitlab <em>self-hosted</em>.</p>
<p>Cet article contient quelques-unes de nos recommandations à l'intention des utilisateurs de notre Gitlab,
ayant pour but à la fois <strong>améliorer les performances de leurs <em>pipelines</em></strong>,
et <strong>limiter leur impact en termes de ressources</strong> sur cette instance Gitlab partagée entre des dizaines d'équipes.
Un dernier volet rassemble quelques points de <strong>sécurité</strong>.</p>
<p>Ces conseils sont essentiellement issus de mon expérience au fil des années,
mais recoupent également des recommandations officielles de Gitlab.
J'espère qu'en les partageant ici ils pourront être utiles à la communauté qui gravite autour de ce bel outil.
Merci à Christophe, Etienne, Gilles, Jérôme & Raphaël pour la relecture.</p>
<ul>
<li><a href="bonnes-pratiques-gitlab-ci.html#shallow-cloning-avec-git_depth=1">Shallow cloning avec GIT_DEPTH=1</a></li>
<li><a href="bonnes-pratiques-gitlab-ci.html#cache">Cache</a></li>
<li><a href="bonnes-pratiques-gitlab-ci.html#artefacts">Artefacts</a></li>
<li><a href="bonnes-pratiques-gitlab-ci.html#retry">Retry</a></li>
<li><a href="bonnes-pratiques-gitlab-ci.html#evitez-les-deploiements-depuis-les-forks">Évitez les déploiements depuis les forks</a></li>
<li><a href="bonnes-pratiques-gitlab-ci.html#securite---lockez-vos-versions-pour-rendre-vos-builds-reproductibles">Sécurité - Lockez vos versions pour rendre vos builds reproductibles</a></li>
<li><a href="bonnes-pratiques-gitlab-ci.html#securite---services-integres">Sécurité - Services intégrés</a></li>
</ul>
<h2>Shallow cloning avec GIT_DEPTH=1</h2>
<p>Afin de <strong>raccourcir vos temps d'exécution</strong> et <strong>limiter la quantité de données transitant sur le réseau</strong>,
vous pouvez définir cette variable d'environnement afin que Gitlab ne récupère
qu'<strong>un seul commit d'historique</strong> de votre repository avant d'exécuter votre pipeline.</p>
<p>Cette variable peut-être définie dans le <code>.gitlab-ci.yml</code>, au niveau des variables CI/CD de votre repo,
ou même plus largement au niveau des variables de CI/CD de votre groupe Gitlab.</p>
<p>Plus d'information ici : <a href="https://docs.gitlab.com/ee/ci/yaml/#configure-runner-behavior-with-variables">https://docs.gitlab.com/ee/ci/yaml/#configure-runner-behavior-with-variables</a></p>
<p>De même, si vous clonez manuellement un repo <code>git</code> dans vos <em>pipelines</em>, pensez à employer <code>git clone --depth 1</code>.</p>
<p>Pour les très gros repos, Gitlab suggère quelques optimisations possibles,
notamment en configurant <code>GIT_CLEAN_FLAGS</code> & <code>GIT_FETCH_EXTRA_FLAGS</code> : <a href="https://docs.gitlab.com/ee/ci/large_repositories/">https://docs.gitlab.com/ee/ci/large_repositories/</a></p>
<h2>Cache</h2>
<p>Il est vraiment simple et très efficace de mettre en cache entre vos <em>builds</em> successifs
les dépendances de vos applications rapatriées par votre outil favori (Maven, Gradle, Go, npm, pip...) :</p>
<div class="highlight"><pre><span></span><code><span class="nt">default</span><span class="p">:</span>
<span class="nt">cache</span><span class="p">:</span>
<span class="nt">paths</span><span class="p">:</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">.cache/ansible</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">.cache/pip</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">.gradle/caches</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">.gradle/wrapper</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">.m2/repository</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">cache/bundler</span> <span class="c1"># Ruby</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">node_modules</span>
</code></pre></div>
<p>Le dossier de cache doit obligatoirement être relatif au dossier de <em>build</em> de votre pipeline.
Pour certaines technos, cela requiert de configurer le dossier de cache employé par votre <em>package manager</em>
via des variables d'environnement :</p>
<div class="highlight"><pre><span></span><code><span class="nt">variables</span><span class="p">:</span>
<span class="nt">ANSIBLE_ROLES_PATH</span><span class="p">:</span> <span class="s">"$CI_PROJECT_DIR/.cache/ansible"</span>
<span class="nt">GEM_PATH</span><span class="p">:</span> <span class="s">"$CI_PROJECT_DIR/cache/bundler/ruby/2.1.0"</span>
<span class="nt">GRADLE_HOME</span><span class="p">:</span> <span class="s">"$CI_PROJECT_DIR"</span>
<span class="nt">MAVEN_OPTS</span><span class="p">:</span> <span class="s">"-Duser.home=$CI_PROJECT_DIR"</span>
<span class="nt">PIP_CACHE_DIR</span><span class="p">:</span> <span class="s">"$CI_PROJECT_DIR/.cache/pip"</span>
</code></pre></div>
<p>Définir une <code>cache:key</code> est souvent une bonne idée, ainsi éventuellement qu'une <code>CACHE_FALLBACK_KEY</code>.
Pour plus de détails, vous pouvez vous référer aux <a href="https://docs.gitlab.com/ee/ci/caching/#good-caching-practices">Good caching practices</a> & <a href="https://docs.gitlab.com/ee/ci/caching/#common-use-cases-for-caches">Common use cases for caches</a> de la documentation Gitlab CI.</p>
<p>Notez également que le niveau de compression du cache est configurable via la variable <a href="https://docs.gitlab.com/ee/ci/runners/configure_runners.html#artifact-and-cache-settings">CACHE_COMPRESSION_LEVEL</a>, et la vitesse de compression via <a href="https://gitlab.com/gitlab-org/gitlab-runner/-/issues/26490">FF_USE_FASTZIP</a>.</p>
<p>Enfin, pour approfondir le sujet, vous pouvez lire la manière dont Gitlab optimise sa gestion de cache pour son propre projet <code>git</code> : <a href="https://docs.gitlab.com/ee/development/pipelines.html#caching-strategy">https://docs.gitlab.com/ee/development/pipelines.html#caching-strategy</a></p>
<h2>Artefacts</h2>
<p>Lorsque vous faites transiter des <a href="https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html">artefacts</a>
entre différentes étapes de vos <em>pipelines</em>, pensez à <a href="https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html#create-job-artifacts"><strong>toujours configurer une courte durée de rétention maximale</strong></a>
afin de limiter l'empreinte disque de vos <em>pipelines</em> :</p>
<div class="highlight"><pre><span></span><code><span class="nt">artifacts</span><span class="p">:</span>
<span class="nt">paths</span><span class="p">:</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">target/myapp.jar</span>
<span class="nt">expire_in</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">1 day</span>
</code></pre></div>
<p>Notez également que le niveau de compression des artefacts est configurable via la variable <a href="https://docs.gitlab.com/ee/ci/runners/configure_runners.html#artifact-and-cache-settings">ARTIFACT_COMPRESSION_LEVEL</a>, et la vitesse de compression via <a href="https://gitlab.com/gitlab-org/gitlab-runner/-/issues/26490">FF_USE_FASTZIP</a>.</p>
<p>Enfin, pour approfondir le sujet, vous pouvez lire la manière dont Gitlab optimise sa gestion d'artefacts pour son propre projet <code>git</code> : <a href="https://docs.gitlab.com/ee/development/pipelines.html#artifacts-strategy">https://docs.gitlab.com/ee/development/pipelines.html#artifacts-strategy</a></p>
<h2>Retry</h2>
<p>Si certaines étapes de votre pipeline ont tendance à tomber en échec de temps en temps,
par exemple à cause d'une instabilité irrégulière d'un service externe, vous pouvez configurer cette étape pour se relancer un 2e fois en cas d'échec :</p>
<div class="highlight"><pre><span></span><code><span class="nt">retry</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">1</span>
</code></pre></div>
<p>Plus de détails ici : <a href="https://docs.gitlab.com/ee/ci/yaml/#retry">https://docs.gitlab.com/ee/ci/yaml/#retry</a></p>
<p>⚠️ ATTENTION : <code>retry</code> peut avoir des effets secondaires néfastes, comme ajouter de la charge sur Gitlab ou de marteler inutilement des services externes.
Ne le mettez en place que si cela résout effectivement des instabilités temporaires.
Bien souvent, résoudre le problème "de fond" de l'instabilité sera la meilleure solution.</p>
<h2>Évitez les déploiements depuis les <em>forks</em></h2>
<p>Il est courant d'effectuer certaines étapes d'une pipeline uniquement sur la branche <code>master</code> / <code>main</code>,
comme la publication de livrables (telle une image Docker) ou le déclenchement d'action de déploiement.</p>
<p>La syntaxe suivante est alors souvent employée :</p>
<div class="highlight"><pre><span></span><code><span class="nt">only</span><span class="p">:</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">master</span>
</code></pre></div>
<p>Néanmoins, le risque avec cette règle ci-dessus est que <strong>ces actions seront tout même effectuées sur les branches <code>master</code> / <code>main</code> des <em>forks</em></strong> de votre repo !</p>
<p>N'importe quel contributeur de votre projet peut alors déclencher un déploiement par mégarde en déclenchant la pipeline de leur <em>fork</em>.
Bien sûr, dans de nombreux cas ce déploiement sera un échec si, par exemple, votre pipeline nécessite des variables auxquels les <em>forks</em> n'ont pas accès.</p>
<p>Néanmoins, pour éviter tout risque, vous pouvez employer la syntaxe suivante qui indique précisément le groupe Gitlab du repo autorisé à effectuer les actions de déploiement :</p>
<div class="highlight"><pre><span></span><code><span class="nt">rules</span><span class="p">:</span>
<span class="p p-Indicator">-</span> <span class="nt">if</span><span class="p">:</span> <span class="s">'$CI_PROJECT_PATH</span><span class="nv"> </span><span class="s">==</span><span class="nv"> </span><span class="s">"group-name/repo-name"'</span>
</code></pre></div>
<h2>Sécurité - Lockez vos versions pour rendre vos builds reproductibles</h2>
<p>Selon le <em>package manager</em> que vous employez, les outils varient, mais l'idée est la même :
<strong>versionner sous git votre arbre complet de dépendances</strong>, avec un fichier de <em>lock</em>,
pour que la construction de votre livrable dans une version donnée ne puisse jamais changer
si une de vos dépendances est mise à jour plus tard sur un <em>registry</em>.</p>
<p>Plus d'infos sur l'approche générale :</p>
<p><a href="https://reproducible-builds.org"><img alt="Logo Reproductible Builds" src="images/2021/07/reproductible-builds.svg"></a></p>
<ul>
<li>
<p>Avec <strong>Maven (Java)</strong> :</p>
<ul>
<li>N'employez jamais les mots-clefs dépréciés <code>LATEST</code> ou <code>RELEASE</code> dans vos <code>pom.xml</code>, qui peuvent vous exposer à des attaques de type <em>Dependency Confusion</em> (<a href="https://cwiki.apache.org/confluence/display/MAVEN/Maven+3.x+Compatibility+Notes#Maven3.xCompatibilityNotes-PluginMetaversionResolution">Maven 3.x Compatibility Notes on RELEASE and LATEST metaversions</a>).
De même, n'employez pas les <em>versions ranges</em> et préférez-y l'emploi du <a href="https://www.mojohaus.org/versions-maven-plugin/">plugin Versions</a>.</li>
<li>Utilisez le <a href="https://jeremylong.github.io/DependencyCheck/dependency-check-maven/">plugin dependency-check</a> dans votre pipeline, et définissez <code>failBuildOnCVSS</code> pour que son exécution interrompe le <em>build</em> en cas de vulnérabilité détectée</li>
</ul>
</li>
<li>
<p>Avec <strong>npm (NodeJS)</strong> :</p>
<ul>
<li>Invoquez <code>"npm ci"</code> plutôt que <code>"npm install"</code> dans vos <em>pipelines</em> afin d'employer le <code>package-lock.json</code> et d'assurer que vos <em>builds</em> sont toujours identiques (<a href="https://betterprogramming.pub/npm-ci-vs-npm-install-which-should-you-use-in-your-node-js-projects-51e07cb71e26">article explicatif en anglais</a>)</li>
<li>Si possible, lorsque vous publiez vos propres packages, employez des <a href="https://docs.npmjs.com/cli/v7/using-npm/scope"><code>@scopes</code></a>.
Si vous publiez vos packages "scopés" sur un <em>registry</em> privé, pensez à créer également le <code>@scope</code> sur <a href="https://npm.org">https://npm.org</a> (sans pour autant y publier de package), pour évitez tout risque de <em>Dependency Confusion</em>.</li>
<li>Invoquez <a href="https://docs.npmjs.com/cli/v7/commands/npm-audit">npm audit</a> dans vos <em>pipelines</em> pour détecter d'éventuelles vulnérabilités dans vos dépendances : en cas de <a href="https://docs.npmjs.com/cli/v7/commands/npm-audit#exit-code">code de retour non 0</a>, la pipeline doit s'interrompre. <a href="https://github.com/Retirejs/retire.js">retire</a> constitue un outil alternatif ayant la même utilité.</li>
</ul>
</li>
<li>
<p>Avec <strong>pip (Python)</strong> :</p>
<ul>
<li><em>Lockez</em> votre arbre de dépendances, par exemple avec <a href="https://github.com/jazzband/pip-tools">pip-compile</a></li>
<li>Invoquez <a href="https://github.com/pyupio/safety-db">safety</a> dans vos <em>pipelines</em> pour détecter d'éventuelles vulnérabilités dans vos dépendances : en cas de code de retour non 0, la pipeline doit s'interrompre</li>
<li>Invoquez le linter de sécurité <a href="https://github.com/PyCQA/bandit">bandit</a> dans vos <em>pipelines</em> : en cas de code de retour non 0, la pipeline doit s'interrompre</li>
</ul>
</li>
<li>
<p>Avec <strong>CocoaPods (iOS)</strong> :</p>
<ul>
<li><em>Lockez</em> les versions de vos pods, et versionnez votre <code>Podfile.lock</code> sous <code>git</code> (<a href="https://guides.cocoapods.org/using/using-cocoapods.html#what-is-podfilelock">documentation officielle</a>)</li>
<li>Pour évitez une <em>Dependency Confusion</em>, déclarez directement l’URL du repo git qui héberge le pod au niveau de l’import (<a href="https://guides.cocoapods.org/using/the-podfile.html#from-a-podspec-in-the-root-of-a-library-repo">documentation officielle</a>). Exemple :</li>
</ul>
</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">pod</span> <span class="s1">'Org/MyPod'</span><span class="p">,</span> <span class="ss">:git</span> <span class="o">=></span> <span class="s1">'git@gitlab.example.com:org/my-pod.git'</span><span class="p">,</span> <span class="ss">:tag</span> <span class="o">=></span> <span class="s1">'1.2.3'</span>
</code></pre></div>
<h2>Sécurité - Services intégrés</h2>
<p>Pour finir, nous encourageons nos utilisateurs à intégrer dans leurs <em>pipelines</em> les services suivants,
qui font également partie de notre usine logicielle :</p>
<ul>
<li><strong>Ne stockez aucun secret dans votre code source</strong></li>
</ul>
<p>Ne versionnez dans votre repository <code>git</code> aucun <em>credential</em> sensible : mot de passe, token, certificat privé...</p>
<p>Une solution recommandée pour stocker vos secrets et les employer de manière sécurisée dans vos <em>pipelines</em> est <a href="https://docs.gitlab.com/ee/ci/secrets/">HashiCorp Vault</a>, dont une instance est mise à disposition.</p>
<p><img alt="Logo Vault" src="images/2021/07/vault.png"></p>
<ul>
<li><strong>Configurez Renovate Bot sur vos repos</strong>
Afin que les dépendances de leurs applications soient le plus à jour possible,
nous mettons à disposition de nos utilisateurs <a href="https://renovatebot.com">Renovate Bot</a>.</li>
</ul>
<p><img alt="Logo Renovate" src="images/2021/07/renovate.png"></p>
<ul>
<li><strong>Faites analyser votre code par Sonar</strong></li>
</ul>
<p>Nous mettons à disposition de nos utilisateurs une instance <a href="https://sonarqube.org">SonarQube</a>,
qui intègre un ensemble de checks permettant de détecter des failles de sécurité.</p>
<p>L'image Docker <a href="https://hub.docker.com/r/sonarsource/sonar-scanner-cli">sonar-scanner-cli</a> peut être employée pour soumettre à Sonar vos couvertures de tests depuis une <em>pipeline</em> Gitlab.
Voici un exemple d'usage dan un repo Python, une fois les tests exécutés lors d'une étape précédente :</p>
<div class="highlight"><pre><span></span><code><span class="nt">image</span><span class="p">:</span>
<span class="nt">name</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">sonarsource/sonar-scanner-cli:4.6</span>
<span class="nt">entrypoint</span><span class="p">:</span> <span class="p p-Indicator">[</span><span class="s">""</span><span class="p p-Indicator">]</span>
<span class="nt">script</span><span class="p">:</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">sonar-scanner -Dsonar.projectKey=... -Dsonar.projectName=... -Dsonar.projectVersion=...</span>
<span class="l l-Scalar l-Scalar-Plain">-Dsonar.sources=. -Dsonar.host.url=$SONAR_HOST_URL</span>
<span class="l l-Scalar l-Scalar-Plain">-Dsonar.python.coverage.reportPaths=cover/*coverage*.xml -Dsonar.coverage.exclusions=**/tests/**</span>
</code></pre></div>
<p><img alt="Logo SonarQube" src="images/2021/07/sonarqube.png"></p>
<p>Et vous, avez-vous des recommandations à partager autour de Gitlab CI ?</p>
<!-- Com' :
* https://medium.com/@Lucas_C/bonnes-pratiques-gitlab-ci-9a380c83a74a
* https://linuxfr.org/users/lucas-c/liens/bonnes-pratiques-gitlab-ci
* https://dev.to/lucasc/bonnes-pratiques-gitlab-ci-5fb7
* https://www.journalduhacker.net/s/cafeya/bonnes_pratiques_gitlab_ci
* En cours de modération : https://news.humancoders.com
-->
<script>
['h2'].forEach(function (selector) {
document.querySelectorAll(selector).forEach(function (title) {
if (!title.classList.length) {
title.id = title.textContent
.toLowerCase()
.replace(/[()?!:,'&@]/g, '')
.replace(/[à]/g, 'a')
.replace(/[ç]/g, 'c')
.replace(/[éêè]/g, 'e')
.replace(/[ï]/g, 'i')
.replace(/ /g, '-');
}
});
});
</script>
<style>
section > ul > li { margin-top: .5rem; }
article img { width: 16rem; }
</style>Double feature SOMBRE2021-07-09T15:00:00+02:002021-07-09T15:00:00+02:00Lucas Cimontag:chezsoi.org,2021-07-09:/lucas/blog/double-feature-sombre.html<div class="side-by-side">
<img alt="Couverture de Sombre 2" src="images/2021/07/Sombre2.jpg">
<img alt="Couverture de Diet Life" src="images/2021/07/Sombre-DietLife.jpg">
</div>
<p>Petit <a href="tag/compte-rendu.html">retour de partie</a> d'hier soir, où nous avons joué deux scénarios du jeu de rôle <a href="https://www.terresetranges.net/sombre.html">Sombre</a> de Johan Scipion :</p>
<ul>
<li>
<p><strong>Ubiquité</strong>, scénario <em>Sombre Classic</em> de 60min issu de <a href="https://www.terresetranges.net/forums/viewtopic.php?pid=7439#p7439">Sombre n°2</a>, joué à 1 MJ + 5 joueurs</p>
</li>
<li>
<p><strong>Diet Life</strong>, jeu de plateau <em>Sombre Zéro</em>, sans MJ, joué à 4,
conçu …</p></li></ul><div class="side-by-side">
<img alt="Couverture de Sombre 2" src="images/2021/07/Sombre2.jpg">
<img alt="Couverture de Diet Life" src="images/2021/07/Sombre-DietLife.jpg">
</div>
<p>Petit <a href="tag/compte-rendu.html">retour de partie</a> d'hier soir, où nous avons joué deux scénarios du jeu de rôle <a href="https://www.terresetranges.net/sombre.html">Sombre</a> de Johan Scipion :</p>
<ul>
<li>
<p><strong>Ubiquité</strong>, scénario <em>Sombre Classic</em> de 60min issu de <a href="https://www.terresetranges.net/forums/viewtopic.php?pid=7439#p7439">Sombre n°2</a>, joué à 1 MJ + 5 joueurs</p>
</li>
<li>
<p><strong>Diet Life</strong>, jeu de plateau <em>Sombre Zéro</em>, sans MJ, joué à 4,
conçu par Julien <em>DeathAmbre</em> De Monte @<a href="http://arkhive.free.fr">DarkFarm</a></p>
</li>
</ul>
<p>J'avais déjà évoqué ce jeu de rôle <a href="espace-profond-et-sanglant.html">l'année dernière sur ce blog</a>,
avec l'excellent <em>Deep Space Gore</em> issu de <a href="https://www.terresetranges.net/forums/viewtopic.php?pid=10545#p10545">Sombre n°3</a>.</p>
<hr>
<p>⚠️ <strong>ATTENTION: spoilers!</strong> Ce qui suit divulgâche des rebondissements scénaristiques. <em><strong>MJs only</strong></em> 😉</p>
<hr>
<p>Le premier scénario, <strong>Ubiquité</strong>, est un huis clos compétitif en temps limité.
C'était une réussite dans l'ensemble, je suis très content de l'avoir fait jouer !
La tension était palpable dès le début, et les joueurs ont eu droit à la <em>Vraie Fin</em>,
qui m'a évoqué l'ambiance "street-magic glauque" d'un <em>Unknown Armies</em>.
Et le scénario regorge de pépites ludiques : l'effet Pac-Man, l'Avantage ambidextre et le couteau supplémentaire,
l'épilogue qui n'est raconté qu'à un seul joueur... ✨</p>
<p>Peut-être parce qu'ils ne se connaissaient pas tous avant la partie,
les joueurs ont été très peu agressifs les uns avec les autres,
et ont plutôt cherché des réponses / solutions collectivement pour se sortir du labyrinthe...
C'est seulement dans les dernières minutes qu'ont eu lieu des suicides / échanges de coups de couteau.</p>
<p>À noter que je me suis entièrement passé de la règle des Personnalités,
simplement parce que je n'étais pas très à l'aise avec ce profil Brutal / Cruel / Sadique.</p>
<p>Si jamais je le fais rejouer à nouveau, je songe à peut-être introduire quelques éléments supplémentaires :</p>
<ul>
<li>
<p>Un peu plus de mécanismes interactifs dans la pièce. Quelques idées : les objets placés dans les octogones au sol se retrouvent dupliqués; verser le sang d'un PJ sur un octogone "ravive" un peu sa lumière...</p>
</li>
<li>
<p>Rajouter un peu plus de rebondissements "scriptés" liés à l'environnement. Quelques idées : toutes les ouvertures de portes Est/Ouest qui disparaissent; pièce carrée se transformant en octogone, avec 4 nouvelles ouvertures; cadavres qui fusionnent entre eux...</p>
</li>
</ul>
<p>Bref, un très bon scénario pour amateurs de PvP horrifique à la <em>Battle Royale</em> ou <em>Cube</em> !</p>
<hr>
<p>Le second jeu de la soirée fut <strong>Diet Life</strong>,
un scénario / jeu de société compétitif fonctionnant avec les règles de Sombre Zéro,
et inspiré du film <em>Saw</em>.</p>
<p>Il y a beaucoup de choses que j'ai adoré dans ce jeu,
à commencer par ce principe que <strong>tous les joueurs autour de la table découvrent l'histoire
ET les mécaniques de jeu ensemble</strong> !
Pas de MJ, une seule page de règles initiales, puis on suit les instructions en retournant les cartes.
Ça m'a beaucoup rappelé le jeu <strong>Unlock</strong> par moments.</p>
<p>L'ensemble est très imaginatif et bien huilé !
Parmi les trouvailles ludiques qui m'ont beaucoup plu, je citerai : les pièges tordus (voir carrément vicieux);
un événement scripté au milieu du scénario; la mécanique de déclaration d'intentions et les déplacements de personnages entre les lieux; les armes à usage unique ou effet spécial.</p>
<p>Par contre, c'est très clairement un court jeu de plateau, et pas un jeu de rôle.
Ne vous attendez pas à du <em>roleplay</em> ou une ambiance effrayante :
le jeu est bien plus tactique et un peu gore.</p>
<p>Enfin, il reste quelques points de règles un peu obscurs dans cette version 2.1.3,
mais nous avons tout de même passé un excellent moment !</p>
<p><strong>[EDIT 2021/07/13]</strong>: Suite à mes retours, l'auteur Julien <em>DeathAmbre</em> De Monte à publié une version révisée 2.2.0 du scénario sur son site <a href="http://arkhive.free.fr/">DarkFarm</a> : merci !</p>
<p><a href="images/2021/07/DietLife-session.jpg"><img alt="Photo de la table de jeu durant la partie" src="images/2021/07/DietLife-session.jpg"></a></p>
<style>
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
flex-flow: wrap;
}
.side-by-side > * {
padding: .5rem;
}
</style>World map of an open-source project contributors2021-06-28T12:00:00+02:002021-06-28T12:00:00+02:00Lucas Cimontag:chezsoi.org,2021-06-28:/lucas/blog/world-map-of-an-open-source-project-contributors.html<!-- Com' :
* Feature suggested: https://github.com/all-contributors/all-contributors/issues/537
-->
<p>I have been amazed recently at the <strong>diversity</strong> of contributors on the <a href="https://github.com/PyFPDF/fpdf2">fpdf2</a> project,
coming from all around the world!</p>
<p>Then I thought it would be nice to visualize this diversity by building a world map
of all contributors locations.
There it is:</p>
<p><a href="https://pyfpdf.github.io/fpdf2/contributors.html"><img alt="" src="images/2021/06/contributors-map.png"></a></p>
<p>Click on the image to access an …</p><!-- Com' :
* Feature suggested: https://github.com/all-contributors/all-contributors/issues/537
-->
<p>I have been amazed recently at the <strong>diversity</strong> of contributors on the <a href="https://github.com/PyFPDF/fpdf2">fpdf2</a> project,
coming from all around the world!</p>
<p>Then I thought it would be nice to visualize this diversity by building a world map
of all contributors locations.
There it is:</p>
<p><a href="https://pyfpdf.github.io/fpdf2/contributors.html"><img alt="" src="images/2021/06/contributors-map.png"></a></p>
<p>Click on the image to access an up-to-date online version.</p>
<p>The web page is built by a Python script that queries the GitHub v3 API
to retrieve all the locations of the project contributors,
if this information is provided on their profile page.</p>
<p>Then <a href="https://leafletjs.com">LeafletJS</a>
and <a href="https://github.com/perliedman/leaflet-control-geocoder">Leaflet Control Geocoder</a>
are used to place all contributors on a world map.</p>
<p>Finally, the page is built & deployed by the GitHub Actions pipeline on every push on the <code>master</code> branch.</p>
<p>I tried to make it easy to reuse for other projects,
and it is under <a href="https://creativecommons.org/publicdomain/zero/1.0/deed.en">CC-0 license</a>,
so <strong>feel free to add a similar contributor world maps to your favorite open-source project!</strong></p>Live demo for Hesperides!2021-06-13T14:30:00+02:002021-06-13T14:30:00+02:00Lucas Cimontag:chezsoi.org,2021-06-13:/lucas/blog/live-demo-for-hesperides.html<p><img alt="Hesperides logo" src="images/2021/06/hesperides.png"></p>
<p>Today I finally took the time to put up a <strong>live demo website</strong> for <strong>Hesperides</strong>!</p>
<p><a href="https://hesperides.herokuapp.com">https://hesperides.herokuapp.com</a></p>
<p>Hesperides is an open source tool dedicated to <strong>configuration management</strong>:
it stores <strong>applications properties</strong> and <a href="https://mustache.github.io">mustache</a> templates for <strong>configurations files</strong>.
It is <strong>strongly hierarchized</strong> based on few main concepts: <strong>modules</strong>, <strong>applications …</strong></p><p><img alt="Hesperides logo" src="images/2021/06/hesperides.png"></p>
<p>Today I finally took the time to put up a <strong>live demo website</strong> for <strong>Hesperides</strong>!</p>
<p><a href="https://hesperides.herokuapp.com">https://hesperides.herokuapp.com</a></p>
<p>Hesperides is an open source tool dedicated to <strong>configuration management</strong>:
it stores <strong>applications properties</strong> and <a href="https://mustache.github.io">mustache</a> templates for <strong>configurations files</strong>.
It is <strong>strongly hierarchized</strong> based on few main concepts: <strong>modules</strong>, <strong>applications</strong> and <strong>environments</strong>.</p>
<p>Hesperides has two main components: a <a href="https://github.com/voyages-sncf-technologies/hesperides">REST API backend</a>
and a <a href="https://github.com/voyages-sncf-technologies/hesperides-gui">web frontend</a>.
It has a companion <a href="https://github.com/voyages-sncf-technologies/hesperides-cli">CLI written in Python</a>,
a dedicated <a href="https://github.com/voyages-sncf-technologies/hesperides-jenkins-lib">Jenkins shared lib</a>,
and it has been used in production for more than 5 years.</p>
<p>My first contribution to this project dates from 2017,
but it's in 2019 that I extensively worked with <a href="https://github.com/thomaslhostis">Thomas L'Hostis</a>
on fully re-writing its Java backend.</p>
<p>Following this major overhaul, many interesting methodologies & tools were used on this project:</p>
<ul>
<li><a href="https://github.com/voyages-sncf-technologies/architecture-hexagonale-cqrs">Hexagonal architecture & CQRS</a></li>
<li><a href="https://martinfowler.com/eaaDev/EventSourcing.html">Event Sourcing</a></li>
<li><a href="https://en.wikipedia.org/wiki/Domain-driven_design">Domain-Driven Design</a></li>
<li><a href="https://en.wikipedia.org/wiki/Behavior-driven_development">BDD testing</a></li>
<li><a href="https://github.com/voyages-sncf-technologies/hesperides/tree/master/documentation/lightweight-architecture-decision-records">Lightweight Architecture Decision Records</a></li>
<li>containerization using <code>Docker</code></li>
<li>fully automated daily deployments, partially based on <a href="https://docs.github.com/en/actions">GitHub Actions pipelines</a></li>
</ul>
<p>Over the years, many contributors helped on this project: <a href="https://github.com/voyages-sncf-technologies/hesperides/graphs/contributors">backend contributors</a> - <a href="https://github.com/voyages-sncf-technologies/hesperides-gui/graphs/contributors">frontend contributors</a>.
Special shout-out to <strong>Sylvain Maillard</strong>, <strong>Adrien Auffredou</strong>, <strong>Victor Salaun</strong>,
<strong>Vincent Lae</strong>, <strong>Nicolas Laurenti</strong>, <strong>Mamadou Bhoye Barry</strong>
and the persons who took part in the Hesperides workshops: thank you all for your great work!</p>
<p>The live demo website is hosted on <a href="https://www.heroku.com">Heroku</a>.
I wrote down a short tutorial on how this was done here:
<a href="https://voyages-sncf-technologies.github.io/hesperides-gui/start.html#heroku">https://voyages-sncf-technologies.github.io/hesperides-gui/start.html#heroku</a>.</p>fpdf2.4.0 and converting GIFs to PDFs2021-06-12T10:40:00+02:002021-06-12T10:40:00+02:00Lucas Cimontag:chezsoi.org,2021-06-12:/lucas/blog/fpdf2-4-0-and-converting-gifs-to-pdfs.html<p><code>fpdf2</code> is a minimalist PDF creation library for Python that I am maintaining.</p>
<p>With the release yesterday of its <code>v2.4.0</code>, I'm going to present some of its notable new features since <a href="fpdf2-3-0-unbreakable-and-pdf-quines.html">the latest minor version</a>.</p>
<p><a href="https://github.com/pyfpdf/fpdf2/">https://github.com/pyfpdf/fpdf2/</a> <a href="https://pypi.python.org/pypi/fpdf2"><img alt="Pypi latest version" src="https://img.shields.io/pypi/v/fpdf2.svg"></a>
Doc: <a href="https://pyfpdf.github.io/fpdf2/">https://pyfpdf.github.io/fpdf2/</a></p>
<h2>JPEG images …</h2><p><code>fpdf2</code> is a minimalist PDF creation library for Python that I am maintaining.</p>
<p>With the release yesterday of its <code>v2.4.0</code>, I'm going to present some of its notable new features since <a href="fpdf2-3-0-unbreakable-and-pdf-quines.html">the latest minor version</a>.</p>
<p><a href="https://github.com/pyfpdf/fpdf2/">https://github.com/pyfpdf/fpdf2/</a> <a href="https://pypi.python.org/pypi/fpdf2"><img alt="Pypi latest version" src="https://img.shields.io/pypi/v/fpdf2.svg"></a>
Doc: <a href="https://pyfpdf.github.io/fpdf2/">https://pyfpdf.github.io/fpdf2/</a></p>
<h2>JPEG images take less space</h2>
<p><img class="left" alt="Glitched JPEG tombstone" src="images/2021/06/jpeg-tombstone.jpg"></p>
<p><code>fpdf2</code> now uses the newly supported <code>DCTDecode</code> image filter for JPEG images, in order to improve the compression ratio without any image quality loss.</p>
<p>On test images, this <strong>reduced</strong> the size of embedded JPEG images by <strong>90%</strong>.</p>
<h2>Basic Markdown styling</h2>
<p><img class="right" alt="Markdown" src="images/2021/06/markdown.jpg"></p>
<p><code>fpdf2</code> now allows to use basic Markdown-like styling: <code>**bold**, __italics__, --underlined--</code>.</p>
<p>Currently, this is only supported by the <a href="https://pyfpdf.github.io/fpdf2/fpdf/#fpdf.FPDF.cell"><code>FPDF.cell</code> method</a>,
using <code>markdown=True</code>, but I plan to also implement this for the <code>FPDF.multi_cell</code> & <code>FPDF.write</code> methods.</p>
<h2>Document outline & table of contents</h2>
<p><img class="left" alt="Screenshot from Sumatra PDF" src="images/2021/06/document-outline.png"></p>
<p>Quoting the <a href="https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf">PDF format reference</a>:</p>
<blockquote>
<p>A PDF document may optionally display a <strong>document outline</strong> on the screen, allowing the user to navigate interactively
from one part of the document to another. The outline consists of a tree-structured hierarchy of outline items,
which serve as a visual table of contents to display the document’s structure to the user.</p>
</blockquote>
<p>Since <code>v2.3.3</code> you can define such outlines, as well as tables of contents !</p>
<p>Full documentation: <a href="https://pyfpdf.github.io/fpdf2/DocumentOutlineAndTableOfContents.md.html">fpdf2 / Document outline & table of contents</a></p>
<h2>Annotations</h2>
<p><img class="right" alt="Text annotation example" src="images/2021/06/text-annotation.png"></p>
<p><code>fpdf2</code> now allows to add various PDF annotations, such as <strong>text annotations</strong>
or <strong>links to directly open other PDF files</strong> on the same file-system.</p>
<p>Full documentation: <a href="https://pyfpdf.github.io/fpdf2/Annotations.html">fpdf2 / Annotations</a></p>
<h2>Accessibility</h2>
<p><img class="left big" alt="fpdf2 logo with cursor showing an alternate description" src="images/2021/06/fpdf2-logo-with-text-alt.png"></p>
<p>Since <code>v2.3.1</code> alternative text descriptions can now be added by the <code>FPDF.image()</code> & <code>FPDF.link()</code> methods.</p>
<p>They allow <a href="https://en.wikipedia.org/wiki/Screen_reader">screen readers</a> to extract readable descriptions
from images & links in the generated PDFs.</p>
<h2>Presentations</h2>
<p><img class="right" alt="Exploding cat GIF" src="images/2021/06/exploding-cat.gif"></p>
<p>In PDF readers, <strong>presentation mode</strong> can usually be enabled with the <code>CTRL + L</code> shortcut.</p>
<p>This release introduces support for <strong>page transitions</strong> in presentation mode:
<a href="https://pyfpdf.github.io/fpdf2/fpdf/fpdf.html#fpdf.fpdf.FPDF.add_page"><code>FPDF.add_page()</code></a>
now accepts two new optional parameters, <code>duration</code> & <code>transition</code>,
to define how the viewer application should advances to the next page.</p>
<p>Full documentation: <a href="https://pyfpdf.github.io/fpdf2/Presentations.html">fpdf2 / Presentations</a></p>
<p>After introducing this feature, I realized that PDF page durations allowed to build <strong>presentations that behave like GIFs</strong>:
sequences of images, one per page, that move forward automatically.</p>
<p>Hence I wrote a little script, <a href="https://github.com/PyFPDF/fpdf2/blob/master/tutorial/gif2pdf.py"><code>gif2pdf.py</code></a>,
to test this silly idea.
There are the result:</p>
<ul>
<li><a href="images/2021/06/exploding-cat.pdf">exploding-cat.pdf</a></li>
<li><a href="images/2021/06/speechless-nathan-fillion.pdf">speechless-nathan-fillion.pdf</a></li>
<li><a href="images/2021/06/Smiling-Leo.pdf">Smiling-Leo.pdf</a></li>
</ul>
<p>You can open those files in Adobe Acrobat Reader (page durations are ignored by some other PDF readers),
then press <code>CTRL+L</code> to launch presentation mode,
and then ENTER to admire the animation!</p>
<p>Of course PDF readers are not optimized for this kind of animated files,
so the animation may be pretty slow, despite being configured to execute at 25 frames per second.</p>
<hr>
<p>That's it for the notable recent features of <code>fpdf2</code>!
You can also check the detailed <a href="https://github.com/PyFPDF/fpdf2/blob/master/CHANGELOG.md">CHANGELOG</a>
for an exhaustive list of all changes: bug fixes, other minor improvements and deprecation notices.</p>
<p>As someone recently asked about it: if ever you want to support my work on this open-source Python library,
you can <a href="https://lucas-c.itch.io">buy one of the games I published on itch.io</a>.</p>
<p>Now I wish you to have a lot of fun building PDFs with <code>fpdf2</code>!</p>
<p>I recently used it myself to create an animated poem for my girlfriend 😉
I'd love to know what creations you have made using it,
so please add comment below if you want to share them!</p>
<!-- Com' :
* [x] https://planetpython.org
* [x] https://www.reddit.com/r/Python/comments/nywfb7/new_release_of_fpdf2_markdown_styling_jpeg/
* [x] https://www.reddit.com/r/pythonnews/comments/nywnx9/new_release_of_fpdf2_markdown_styling_jpeg/
* [x] https://dev.to/lucasc/new-release-for-fpdf2-40pi
* [x] video comment: https://www.youtube.com/watch?v=euNvxWaRQMY
* [x] video comment: https://www.youtube.com/watch?v=JhQVD7Y1bsA
-->
<style>
article img { max-height: 12rem; }
article img.big { max-height: 20rem; }
article h2 { padding-top: 2rem; }
@media screen and (min-width: 40rem) {
article img { margin: 0 1rem; }
img.left { float: left; }
img.right { float: right; }
article h2 { clear: both; }
}
.uk-article-content > p:nth-child(3) { /* Link to GitHub repo */
display: block;
text-align: center;
border: 1px solid black;
border-radius: 10rem;
padding: 1rem;
margin: 2rem 10vw;
}
.uk-article-content > p:nth-child(3) img { margin: auto; }
</style>1213, an indie video game from 16 years ago2021-05-11T23:00:00+02:002021-05-11T23:00:00+02:00Lucas Cimontag:chezsoi.org,2021-05-11:/lucas/blog/1213-an-indie-video-game-from-16-years-ago.html<p><img alt="Screenshot from the game 1213" class="small" src="images/2021/05/1213.gif"></p>
<blockquote>
<p>When was the last time you wanted to play again an old video game?</p>
</blockquote>
<p>Personally, I don't usually re-play video games.
I'm always more tempted to try a new interesting one found on <a href="https://www.warpdoor.com">WarpDoors</a>.</p>
<p>I have the impression that this is common to most video game players,
whether the prefer …</p><p><img alt="Screenshot from the game 1213" class="small" src="images/2021/05/1213.gif"></p>
<blockquote>
<p>When was the last time you wanted to play again an old video game?</p>
</blockquote>
<p>Personally, I don't usually re-play video games.
I'm always more tempted to try a new interesting one found on <a href="https://www.warpdoor.com">WarpDoors</a>.</p>
<p>I have the impression that this is common to most video game players,
whether the prefer AAA games or "indie" ones.</p>
<p>I bet that you have a few favorite books, or favorite movies,
that you have already read or watched a few times,
and you know you will probably read or watch it again one day.</p>
<p>Are there video games that feel like that to you?
If so, I would honestly love to know in the comments 😉</p>
<hr>
<p>Anyway, a couple of weeks ago I had this burst of <em>gaming nostalgia</em>:
I wanted to play again <a href="http://www.fullyramblomatic.com/1213/">1213</a>.</p>
<p>It's an indie game from 2005, made by <a href="https://yzcroshaw.itch.io">Ben "Yahtzee" Croshaw</a>.</p>
<p>It's a great game, but I probably had fond memories of it because
it was the video game equivalent of a
<a href="https://www.thelocal.fr/20190814/french-expression-of-the-day-madeleine-de-proust/">madeleine de Proust</a>:
it reminded me of the time I played it first,
when I had a lot of fun being a student in Grenoble
and regularly played indie video games found on <a href="http://www.homeoftheunderdogs.net">HomeOfTheUnderdogs</a>
or <a href="https://web.archive.org/web/20160205021421/http://indiegames.com/index.html">the old indiegames.com blog</a>.</p>
<figure>
<img alt="Screenshot from episode 2" src="images/2021/05/1213-elevator.png">
<figcaption>I was <a href="https://www.adventuregamestudio.co.uk/forums/index.php?topic=59096.msg636635703">dumb-stuck for while here</a>. Hint: don't expect the door on the background to ever open.</figcaption>
</figure>
<p><strong>1213</strong> is objectively interesting in more than one way.
First off, it was made using the <a href="https://www.adventuregamestudio.co.uk">Adventure Game Studio</a>,
a game engine intended to build <em>point and click</em> adventure games.
However Yahtzee managed to build a <a href="https://en.wikipedia.org/wiki/Flashback_%281992_video_game%29">Flashback</a>-like
game out of it!</p>
<p>The game takes a slow start, but the gameplay becomes a lot more fluid on episode 2,
when you get to move around faster.</p>
<p>The level design is nice, with an initial tutorial fitting the story.
While some dumb backtracking is sometime needed,
I did not find the game repetitive.</p>
<p>Visually, the game is minimalistic, with relatively "flat" pixel art.
The color palette creates an atmosphere straight out of a comic book.
The overall directing is well thought, with for example some ominous visual patterns repeated throughout the game... 👀</p>
<p>While the story is not very original in itself,
the narration is really well done.
There are very few cut scenes,
and key story events take place directly around you as you move the main characters from place to place,
solving some basic puzzles, escaping some enemies and shooting down others.</p>
<p>And the ending is good.
Some will see it coming, but I loved how it was built up and revealed.</p>
<p><strong>tl;dr</strong>: it's a great short short experience, go download it here for free:
<a href="http://www.g4g.it/2009/11/30/fullyramblomatic-special-editions-pack/">FullyRamblomatic Special Editions PACK</a></p>
<style>
img.small {
transform: scale(2);
image-rendering: optimizeSpeed;
margin: 10rem auto;
}
blockquote { font-size: 1.7rem; }
</style>Undying Dusk : a PDF video game2021-04-24T17:00:00+02:002021-04-24T17:00:00+02:00Lucas Cimontag:chezsoi.org,2021-04-24:/lucas/blog/undying-dusk-a-pdf-video-game.html<p><img alt="UNDYING DUSK" src="images/2021/04/undying-dusk-title.png"></p>
<p><strong>Undying Dusk</strong> is a video game in a PDF format,
with a gameplay based on exploration and logic puzzles,
in the tradition of <a href="https://en.wikipedia.org/wiki/Dungeon_crawl#Video_games">dungeon crawlers</a>.</p>
<blockquote>
<p>A curse set by the Empress keeps the world in an eternal dusk.
You have recently found shelter in an eerie monastery.</p>
</blockquote>
<p><img alt="GIF trailer #1" src="https://raw.githubusercontent.com/Lucas-C/undying-dusk/main/trailer/undying-dusk-trailer1.gif"></p>
<p>Featuring:</p>
<ul>
<li>~ 200 000 …</li></ul><p><img alt="UNDYING DUSK" src="images/2021/04/undying-dusk-title.png"></p>
<p><strong>Undying Dusk</strong> is a video game in a PDF format,
with a gameplay based on exploration and logic puzzles,
in the tradition of <a href="https://en.wikipedia.org/wiki/Dungeon_crawl#Video_games">dungeon crawlers</a>.</p>
<blockquote>
<p>A curse set by the Empress keeps the world in an eternal dusk.
You have recently found shelter in an eerie monastery.</p>
</blockquote>
<p><img alt="GIF trailer #1" src="https://raw.githubusercontent.com/Lucas-C/undying-dusk/main/trailer/undying-dusk-trailer1.gif"></p>
<p>Featuring:</p>
<ul>
<li>~ 200 000 PDF pages</li>
<li>retro aesthetics: 160x120 resolution & a 16 colors palette</li>
<li>a grid-based world with 50+ distinct tiles & 10 maps to explore</li>
<li>more than 30 treasure items, weapons & spells to pick up in order to face 15 enemy monsters</li>
<li>20 music tracks</li>
<li>thousands of "Game Over" pages, and a single path to victory</li>
<li>4 hidden secrets & a concealed epilogue</li>
<li>an online <a href="https://chezsoi.org/lucas/undying-dusk/hall-of-fame">hall of fame</a></li>
</ul>
<p>To my knowledge, this is <strong>the very first video game in a PDF format</strong>.</p>
<p>Download it on the <a href="https://lucas-c.itch.io/undying-dusk"><strong>dedicated itch.io page</strong></a>.</p>
<p><img alt="GIF trailer #2" src="https://raw.githubusercontent.com/Lucas-C/undying-dusk/main/trailer/undying-dusk-trailer2.gif"></p>
<!-- com' :
- [x] itch.io
- [x] blog article (+ "ads" on most popular posts)
- [x] Clint Bellanger
- [x] Pierre Corbinais -> https://twitter.com/Oujevipo/status/1392042344652935168
- [x] amis
- [x] collègues
- [x] warpdoor.com, indiegamesplus.com, game-curator.com, rockpapershotgun.com
- [x] https://forum.canardpc.com/threads/130686-Undying-Dusk
- [x] subReddits: - cf. https://www.reddit.com/r/gamedev/comments/8zwmio/how_to_post_about_your_game_without_being_flamed/
* https://www.reddit.com/r/UndyingDuskPdfGame/
* https://www.reddit.com/r/freegames/comments/mxu9j1/i_made_a_video_game_out_of_a_pdf_it_has_200_000/
* https://www.reddit.com/r/gaming/comments/mxu73t/i_made_a_video_game_out_of_a_pdf_it_has_200_000/
* https://www.reddit.com/r/pdf/comments/mxu6qf/i_made_a_video_game_out_of_a_pdf_it_has_200_000/
* https://www.reddit.com/r/PixelArt/comments/mxucob/i_made_a_pixelart_video_game_out_of_a_pdf_it_has/
* https://www.reddit.com/r/IndieGaming/comments/mxu8f8/i_made_a_video_game_out_of_a_pdf_it_has_200_000/
* https://www.reddit.com/r/playmygame/comments/mxu6aq/i_made_a_video_game_out_of_a_pdf_it_has_200_000/
* https://www.reddit.com/r/france/comments/my19ee/dimanche_autopromo_20210425/gvsrwmt?context=3
* https://www.reddit.com/r/shamelessplug/comments/mzin4x/i_made_a_video_game_out_of_a_pdf_it_has_200_000/
- [x] Sumatra PDF forum: https://forum.sumatrapdfreader.org/t/undying-dusk-a-video-game-designed-to-be-played-with-sumatra-pdf/3847/1
- [x] HackerNews: https://news.ycombinator.com/item?id=26928782
- [x] LinkedIn, FaceBook
- [x] https://adventuregamers.com/forums/viewthread/15358/
- [x] https://forums.tigsource.com/index.php?topic=71958.0
- [x] http://hu-mu.blogspot.com en mode HUMBLE
- [x] https://www.igdb.com/games/undying-dusk
- [x] https://www.indiedb.com/games/undying-dusk
- [x] https://gamedev.net/projects/3485-undying-dusk/
- [x] carte promo @ work kfet
- [x] https://www.indiemag.fr/forum/lactu-inde-reagissez/t16874-undying-dusk-jeu-video-au-format-pdf
=> très chouette feedback de Nival
- [x] https://www.rockpapershotgun.com/amp/tfi-friday-3-new-text-based-indie-games-with-a-twist
- [x] https://www.afjv.com/forums/sujet/8-1527-1-undying-dusk-un-jeu-video-en-200-000-pages-pdf
- [x] https://www.jeuxvideo.com/forums/message/1092251955
- [x] https://opengameart.org/forumtopic/undying-dusk-an-adventure-game-in-a-pdf
- [x] https://slashdot.org/submission/13725574/undying-dusk-is-a-video-game-in-a-200-000-pages-pdf-generated-with-python
- [x] https://www.gameboomers.com/forum/ubbthreads.php/topics/1246628/undying-dusk-a-pdf-video-game
- [ ] https://www.gameblog.fr/actualite/jeux-independants/ : en attente de validation du compte
- [ ] http://www.game-sphere.fr & https://www.planete-aventure.net/forums/ : sites HS :(
- [ ] sites recensant les JV OSS faits en Python ? -> trouvé aucun à part https://wiki.python.org/moin/PythonGames inéditable :(
- [ ] http://planete-ldvelh.com/ (pour VF)
Info reprise sans mon intervention :
* https://www.pcgamer.com/this-dungeon-crawler-is-built-inside-a-200000-page-pdf/ + https://twitter.com/pcgamer/status/1392197460211154944
* https://www.reddit.com/r/thisweekinretro/comments/n5sgyn/undying_dusk_by_lucasc_first_video_game_in_pdf/
* https://shiftdelete.net/canavari-oldurmek-icin-56-sayfaya-gecin
-> https://translate.google.com/translate?hl=&sl=auto&tl=fr&u=https%3A%2F%2Fshiftdelete.net%2Fcanavari-oldurmek-icin-56-sayfaya-gecin
-> (turc) a généré plus de 400 visites dans les jours suivants
* https://www.36kr.com/p/1223842190905733
* https://www.yystv.cn/p/7902
-> https://translate.google.com/translate?sl=auto&tl=fr&u=https://www.yystv.cn/p/7902
* https://quantrimang.com/undying-dusk-game-nhap-vai-mien-phi-181264
* https://www.idnes.cz/hry/novinky/videohra-v-pdf-rpg-hry-lucas-cimon-undying-dusk.A210519_134716_bw-novinky_srp
-> https://translate.google.com/translate?hl=&sl=auto&tl=fr&u=https%3A%2F%2Fwww.idnes.cz%2Fhry%2Fnovinky%2Fvideohra-v-pdf-rpg-hry-lucas-cimon-undying-dusk.A210519_134716_bw-novinky_srp
-> a généré plus de 450 visites dans les jours suivants
* https://www.youtube.com/watch?v=OMmQiezna7s&t=4377s (<20min de test)
-> kiffe pas trop les flashs blancs...
* https://www.youtube.com/watch?v=Yibs4c7CAJY&t=1548s
* Cri du Lapin (newsletter Canard PC) - 18 mai 2021
> **Pages comme une image**. Oui je sais, ce n'est pas vraiment un article, mais vu que je vous avais parlé l'autre fois d'un jeu vidéo entièrement codé dans une police de caractères, je me suis dit que cela valait le coup de mentionner Undying Dusk, un jeu fourni sous forme d'un PDF de 200 000 pages.
* https://www.superlevel.de/interview-lucas-cimon-undying-dusk-pdf/
* ThioJoe : The 78,000 Page PDF - https://www.youtube.com/watch?v=ZvVNRRQjDh8
+ https://f5bot.com/dash
Idées de format de com' originales et peu coûteuses à réaliser :
* animated GIF that initially just looks like static text
<!--
## 2nd technical write-up post:
**Concept**: build a PDF that could be played as a video game
Inspiration: [Table Ronde n°1 de la CyberConv 2020](http://www.cyberconv1.com/#programme).
Then I thought: what could be emulated with an interactive PDF? A maze game!
Other video game inspirations: Dungeon Master, Eye of the Beholder, Legend of Grimrock, Moonshades...
mécanisme d'itérations des états & level design progressif avec contrainte (single path)
avec checkpoints
-> le programme assure de l'existence d'une unique solution
graphics:
- Gimp & xcf
- palette DawnBringer
pyfpdf
PDF Checker in CI
accessibility...
optims
- comment gen_pdf.py output of resourrces vs pages size
trucs que j'ai appris :
* le format PDF c'est pas si pire, mais dur de trouver des exemples de PDF valides pour chaque feature...
* PDF readers aren't very fast at rendering basic stuff (comparo ?)
https://xcvgsystems.com/static/adventure/
use ascii map screenshot
### gamedesign
no more than 4 rounds of combat
initial feedbacks: minimap needed, + combat tutorial, give backspace hint faster
puzzles that did not go well...
- goblin hord
- sokoban
- CTRL+F
Difficulties to terminate the game (especially to rework stuff like the last boss fight)
Metadata addition with pikepdf that took 1h15 :(
# Storywriting
Books : really useful
Adventure game puzzle design -> readings
1st OGA contrib & Pedro Medeiros tutorials
~XK lines of Python code in Y files
ajouts / changements comparé à l'original à mentionner:
- monsters do NOT appear randomly, but in a predefined way
- there is no sleeping, that restore HP & MP + create "save points"
- monster arrival animations are missing
* moins de gold farming / backtracking
* use content hidden in original sources: 2 monsters & extra equipment (swords & armor)
publish-on-itch.io.sh
com':
- [ ] subReddits: r/python, r/programming, r/gamedev
- [ ] linux fr, dev.to, journalduhacker.net, news.humancoders.com
- [ ] medium.com
-->Run. Die. Repeat. : Docteur Lestrange2021-04-19T12:30:00+02:002021-04-19T12:30:00+02:00Lucas Cimontag:chezsoi.org,2021-04-19:/lucas/blog/run-die-repeat-docteur-lestrange.html<!-- Com'
* [x] http://troplongpaslu.fr/jeux-de-role-court/docteur-lestrange-un-scenario-pour-run-die-repeat/
-> référence : https://chezsoi.org/lucas/jdr/DocteurLestrange-v1.0.pdf
* [x] https://www.scenariotheque.org/Document/info_doc.php?id_doc=10295
-->
<p>Voici un nouveau scénario pour <a href="https://labrysgames.itch.io/run-die-repeat"><em>Run. Die. Repeat.</em></a>,
le jeu de rôle monopage de <strong>Labrys Games</strong> où l'on décède à la chaîne :</p>
<p><a href="https://lucas-c.itch.io/docteur-lestrange">
<br>
<figure>
<img alt="Run. Die. Repeat. : Docteur Lestrange" src="images/2021/04/pngarts.com-Doctor-Strange-PNG-Image-Background.png">
<figcaption>(cliquez sur l'image pour accéder à la page du jeu sur <b>itch.io</b>)</figcaption>
</figure></p>
<p></a></p>
<p>Merci à Jérôme, Jordane & Laetitia de l'<a href="https://laubergedesreveurs.forumactif.com/">Auberge des Rêveurs</a> pour la partie de test …</p><!-- Com'
* [x] http://troplongpaslu.fr/jeux-de-role-court/docteur-lestrange-un-scenario-pour-run-die-repeat/
-> référence : https://chezsoi.org/lucas/jdr/DocteurLestrange-v1.0.pdf
* [x] https://www.scenariotheque.org/Document/info_doc.php?id_doc=10295
-->
<p>Voici un nouveau scénario pour <a href="https://labrysgames.itch.io/run-die-repeat"><em>Run. Die. Repeat.</em></a>,
le jeu de rôle monopage de <strong>Labrys Games</strong> où l'on décède à la chaîne :</p>
<p><a href="https://lucas-c.itch.io/docteur-lestrange">
<br>
<figure>
<img alt="Run. Die. Repeat. : Docteur Lestrange" src="images/2021/04/pngarts.com-Doctor-Strange-PNG-Image-Background.png">
<figcaption>(cliquez sur l'image pour accéder à la page du jeu sur <b>itch.io</b>)</figcaption>
</figure></p>
<p></a></p>
<p>Merci à Jérôme, Jordane & Laetitia de l'<a href="https://laubergedesreveurs.forumactif.com/">Auberge des Rêveurs</a> pour la partie de test jouée ensemble !</p>
<p>Liens utiles :</p>
<ul>
<li>traduction de <em>Run. Die. Repeat.</em> en français : <a href="https://chezsoi.org/lucas/blog/images/jdr/RunDieRepeat-FR.pdf">PDF (37 Ko)</a></li>
<li>tous les articles de ce blog à propos de <em>Run Die Repeat</em> : <a href="tag/run-die-repeat.html">tag run-die-repeat</a></li>
</ul>Ce n'est pas une place d'honneur2021-04-16T19:45:00+02:002021-04-16T19:45:00+02:00Lucas Cimontag:chezsoi.org,2021-04-16:/lucas/blog/ce-nest-pas-une-place-dhonneur.html<p><a href="https://www.artstation.com/artwork/6XnK5">
<br>
<figure>
<img alt="Pirates Crew - Giorgio Grecu" src="images/2021/04/giorgio-grecu-piratescrew2.jpg">
<figcaption>Pirates Crew - <b>Giorgio Grecu</b></figcaption>
</figure></p>
<p></a></p>
<p>... est un jeu de rôle <a href="/lucas/blog/tag/monopage.html">monopage</a> de Grant Howitt.
Je le mentionnais déjà <a href="traduction-de-jdr-monopage-de-grant-howitt.html">dans un précédent article il y a 2 ans</a>,
mais c'est seulement ce mois-ci que j'ai enfin eu l'occasion d'y jouer !</p>
<blockquote>
<p>Dans <strong>Ce n'est pas une place d'honneur</strong>, les joueurs incarnent d'abord de …</p></blockquote><p><a href="https://www.artstation.com/artwork/6XnK5">
<br>
<figure>
<img alt="Pirates Crew - Giorgio Grecu" src="images/2021/04/giorgio-grecu-piratescrew2.jpg">
<figcaption>Pirates Crew - <b>Giorgio Grecu</b></figcaption>
</figure></p>
<p></a></p>
<p>... est un jeu de rôle <a href="/lucas/blog/tag/monopage.html">monopage</a> de Grant Howitt.
Je le mentionnais déjà <a href="traduction-de-jdr-monopage-de-grant-howitt.html">dans un précédent article il y a 2 ans</a>,
mais c'est seulement ce mois-ci que j'ai enfin eu l'occasion d'y jouer !</p>
<blockquote>
<p>Dans <strong>Ce n'est pas une place d'honneur</strong>, les joueurs incarnent d'abord de puissants dieux
lors du voyage de leur nef céleste qui les mena à ce monde;
<strong>puis</strong> ils jouent des humains terrifiés s'écrasant à bord d'un vaisseau-colonie sur une planète inconnue,
dont l'histoire inspirera des générations plus tard un récit mythologique...</p>
</blockquote>
<p>À la lecture du jeu, l'univers évoqué en quelques phrases et l'ambiance de SF épique m'avaient tout de suite happé !
L'idée de la double narration d'une même histoire, de deux points de vue éloignés dans le temps, tantôt à l'échelle d'un dieu tantôt à celle d'un homme, m'avaient aussi beaucoup plu.</p>
<p>Cet article sera donc un compte rendu de partie, qui s'est étalée sur 7-8 heures à travers deux sessions, via <em>Discord</em>.
Une lecture préalable du jeu vous sera sans doute utile pour comprendre certains éléments ludiques et narratifs.</p>
<figure>
<a href="https://www.deviantart.com/edveenn/art/Subnautica-Screenshot-01-861025308">
<img alt="Capture d'écran du jeu vidéo Subnautica" src="images/2021/04/subnautica.jpg">
</a>
<figcaption>Capture d'écran du jeu vidéo <a href="https://fr.wikipedia.org/wiki/Subnautica">Subnautica</a></figcaption>
</figure>
<h2>Déroulement & narration</h2>
<p>De manière assez classique, la partie a finalement duré deux fois plus de temps que je n'avais estimé !
Afin de réduire un peu les allers-retours de l'histoire, nous avons compacté deux à deux les six <em>Rêves</em> qui la constituent,
pour donc jouer en alternance seulement 3 phases dans le mythe divin, et 3 phases dans le vaisseau-colonie Alestras.</p>
<p>Je n'ai pas mené cette partie en improvisation totale :
j'avais pris quelques heures en amont pour noter quelques idées d'éléments scénaristiques.</p>
<p>Entre autres, j'avais rédigé les quelques questions ci-dessous pour les poser aux joueurs en début de session,
afin d'esquisser collectivement l'univers du <em><strong>À l'époque</strong></em>.
Vous pouvez les tirer au hasard avec 1d6 en relançant les résultats déjà obtenus :</p>
<ol>
<li>il existe un <strong>objet</strong> en particulier sur ce vaisseau, un objet de POUVOIR qui vous serait bien utile : de quoi s'agit-il ?</li>
<li>ce vaisseau héberge un prisonnier redoutable : de <strong>qui</strong> s'agit-il et <strong>quels crimes</strong> monstrueux a-t-il commis ?</li>
<li>une <strong>invention technologique</strong> récente a révolutionné le monde, mais l'a aussi terriblement <strong>divisé</strong> : de quoi s'agit-il ?</li>
<li>quelle est la particularité <strong>macabre</strong> de la <strong>planète</strong> HX-9 ?</li>
<li><strong>quelqu'un</strong> sur ce vaisseau a un <strong>secret</strong>, un savoir dangereux qui pourrait vous sauver : de quoi s'agit-il ?</li>
<li>le vaisseau possède un <strong>centre névralgique</strong> méconnu : quel est-il et pourquoi est-il difficile d'accès ?</li>
</ol>
<p>Durant la partie, certains d'entre nous se sont amusés à glisser des références à la mythologie du <em><strong>Maintenant</strong></em>
durant les phases de jeu du <em><strong>À l'époque</strong></em>, brouillant ainsi les frontières temporelles !</p>
<h2>Jouer en ligne via Discord</h2>
<p>Le système de jeu nécessitant de tirer des cartes, nous avons employé la syntaxe suivante avec le bot <a href="https://top.gg/bot/279722369260453888">DiceParser</a> pour simuler ce tirage: <code>!L[A,2,3,4,5,6,7,8,9,10,J,Q,K]</code></p>
<p>Comme les cartes ne pouvaient être <em>physiquement</em> distribuées,
j'ai opté pour distribuer des <em>emojis</em> 🃏 via le <em>chat</em> lors des phases <em><strong>Maintenant</strong></em>,
puis d'utiliser l'historique de la discussion pour retrouver qui avaient des cartes.
C'était probablement moins pratiques qu'avec de vraies cartes à se passer de main en main,
mais cela permettait d'associer chaque 🃏 à un petit commentaire rappelant ce que le dieu avait accompli,
un rappel bien utile plus tard dans la phase <em><strong>À l'époque</strong></em>.</p>
<p>En général je ne suis pas un grand fan du jeu de rôle à distance. Je n'en fait occasionnellement que depuis un an.
Pourtant, une chose m'a beaucoup plu lors de cette partie : <strong>le double niveau de narration</strong>, à l'oral et par le <em>chat</em> de <em>Discord</em>.
Je m'explique : lorsque l'action est focalisée sur un seul PJ, tandis que le joueur qui l'incarne et moi même en tant que MJ
échangeons à l'oral pour décrire la scène, les autres joueurs peuvent annoncer leur actions par le <em>chat</em>,
voir même faire discuter leurs personnages entre eux ailleurs !</p>
<p>J'ai trouvé cela très intéressant pour impliquer encore plus les joueurs dans l'histoire,
en évitant des temps morts qui peuvent parfois les faire « décrocher »
lorsque leurs personnages ne sont pas sur le devant de la scène.</p>
<p>Le <em>chat</em> est aussi l'occasion pour tous de partager des illustrations glanées sur Internet afin de se forger un imaginaire commun.</p>
<h2>Temps forts</h2>
<p>Bien sûr, lors d'un <em>one-shot</em> de quelques heures comme celui-ci, on ne s'attache pas autant émotionnellement aux personnages que lors d'une campagne. Néanmoins je trouve l'histoire que nous avons tricoté dense et bien ficelée !
Elle contenait quelques clichés du genre, mais voici quelques uns des moments forts qui se sont déroulés lors de la partie :</p>
<ul>
<li>
<p>assez tôt, les PJs ont fait le choix de sacrifier plusieurs centaines de personnes encore en stase dans le vaisseau
afin de rediriger l'énergie disponible dans une manœuvre d'évitement d'un danger imminent.</p>
</li>
<li>
<p>l'un des rares PNJs nommés, dont les joueurs avaient estimés qu'il mourrait rapidement pour faire monter la tension dramatique,
a survécu et s'est révélé aux 3/4 de la partie être l'un des 5 dieux-personnages principaux qui avait masqué sont identité.</p>
</li>
<li>
<p>après avoir introduit l'existence de robots se faisant passer pour des humains dans l'histoire,
deux des joueurs eurent l'idée que leur propre personnage pouvait être un tel « réplicant ».
L'un l'a révélé à haute voix et l'autre me l'a signifié par message privé sur <em>Discord</em>,
créant une intéressante paranoïa à la <a href="https://fr.wikipedia.org/wiki/Battlestar_Galactica_(s%C3%A9rie_t%C3%A9l%C3%A9vis%C3%A9e)">BattleStar Galactica</a>.</p>
</li>
<li>
<p>une majorité des PJs survivants ont été physiquement changés par leur aventure dans l'Alestras,
adoptant des caractéristiques emblématiques de leur dieu :
casque de robot fusionné sur la tête, transfert dans un corps robotique
ou encore gants mécanisés de manutention chevillés au corps pour DRESEDEN/DRYSKEN.</p>
</li>
</ul>
<p><a href="https://www.artstation.com/artwork/Dx3O30">
<br>
<figure>
<img alt="Sci-fi/Cyberpunk Characters Concept Art - Germán R." src="images/2021/04/german-rodriguez-img-20190602-194438-566.jpg">
<figcaption>Sci-fi/Cyberpunk Characters Concept Art - <b>Germán R.</b></figcaption>
</figure></p>
<p></a></p>
<h2>Conseils</h2>
<p>Voici quelques suggestions suite à cette partie, si vous souhaitez faire jouer ce scénario vous même :</p>
<ul>
<li>
<p>un plan de vaisseau visible de tous peut être un vrai « plus ».</p>
</li>
<li>
<p>niveau ambiance musicale, je recommande de prévoir 2 bandes son :
une épique et magistrale pour la phase <em><strong>Maintenant</strong></em>, et une autre <em><strong>À l'époque</strong></em> évoquant une SF dystopique.</p>
</li>
<li>
<p>nous n'avons pas trouvé que la table de résolution des cartes apportait beaucoup.
Tel quel, attendez-vous à une partie de jeu de rôle sans jets de dés,
laissant donc la place à l'arbitraire du MJ et permettant aux autres joueurs de contribuer à l'histoire au delà de leur propre personnage.
Si jamais vous n'êtes pas trop à l'aise avec cette façon de joueur,
vous pouvez très bien faire jouer cette histoire avec votre système favori.
Prévoyez néanmoins que l'action avance vite, et que les personnages n'ont pas le même degré de puissante
entre <em><strong>Maintenant</strong></em> et <em><strong>À l'époque</strong></em>.</p>
</li>
<li>
<p>prévoyez un petit temps d'<strong>épilogue</strong> à la fin, où chaque joueur pourra décrire le devenir véritable de son survivant <em><strong>À l'époque</strong></em>.</p>
</li>
</ul>
<h2>Illustrations</h2>
<p>Pour vous servir d'inspiration, voici une série d'illustrations <strong>Moon Beats</strong> par <strong>Yang Jialun</strong> alias Dirty Iron :</p>
<div class="side-by-side">
<img alt="Moon Beats" src="images/2021/04/dirty-iron-asset.jpg">
<img alt="Moon Beats" src="images/2021/04/yang-jialun-1007-5.jpg">
</div>
<ul>
<li><a href="https://www.artstation.com/artwork/vKnVY">https://www.artstation.com/artwork/vKnVY</a></li>
<li><a href="https://dirtyiron.artstation.com/projects/rRJ3RE">https://dirtyiron.artstation.com/projects/rRJ3RE</a></li>
<li><a href="https://dirtyiron.artstation.com/projects/lnkNk">https://dirtyiron.artstation.com/projects/lnkNk</a></li>
<li><a href="https://dirtyiron.artstation.com/projects/Kdzxy">https://dirtyiron.artstation.com/projects/Kdzxy</a></li>
<li><a href="https://dirtyiron.artstation.com/projects/q54Vy">https://dirtyiron.artstation.com/projects/q54Vy</a></li>
</ul>
<h2>Avis final sur le jeu</h2>
<blockquote>
<p>"La partie où on joue les dieux, c'est compliqué"</p>
</blockquote>
<p>...était l'avis commun de mes joueurs en fin de partie 😊
Faire coïncider les actes des dieux et des survivants était à la fois une contrainte narratif très intéressante,
mais aussi une gymnastique mentale collective pas toujours évidente.</p>
<p>Dans l'ensemble j'ai trouvé que ça avait donné un <em>one-shot</em> très intéressant,
et je le recommande chaudement aux amateurs science-fiction et de mécaniques narrativo-ludiques originales !</p>
<p>Pour l'anecdote, <a href="http://www.legrog.org/jeux/dieux-ennemis">Dieux Ennemis, le jeu ayant gagné le Grog d'or 2020</a>,
propose également d'incarner à la fois un héros et un dieu !</p>
<!--
# REVE 1:
+ comète noire cendreuse qui plonge sur la nef d'argent céleste
FYRVOS est le dernier à se réveiller
GYLD tente de diriger le vaisseau pour éviter l'impact, sans succès
STYNNE déclenche la fureur de mille soleils contre la comète, sans plus de succès
DRYSKEN décide d'amortir l'impact de ses mains puissantes, assisté de FYRVOS
=> l'univers entier se transforme (couleurs, distances distordues) et le vaisseau devient animal
STYNNE récupère les dernières armes divines non encore corrompues par la cendre
+ sortie de pods dans la coursive, baignés dans du Granitix (TM)
appel via poste radio par GOLDBERG
STEIN récupère une gatling
VOSS, sous barbituriques, sort difficilement de stase et commence à assister les blessés
GOLDBERG dirige les survivants hors de la zone, vers une salle de contrôle secondaire
l'ingénieur DRESDEN et d'autres survivants frappent à la porte pour entrer
=> VOSS leur ouvre mais comme ils sont poursuivis par les CREATURES, STEIN ouvre le feu à la gatling sur les derniers survivants et GOLDBERG ferme la porte
DRESDEN & GOLDBERG tentent de changer la course du vaisseau pour éviter l'impact
VOSS mentionne le projet secret X65 dont il a entendu parler
=> ellipse : grâce à l'aide de BRANDT (?), les PJs + DRESDEN se sont rendus dans une salle de contrôle caché du vaisseau,
et s'apprêtent à déclencher une arme laser issue du projet secret X65, mais dont l'usage nécessitera de débrancher tous les pods de stase du vaisseau
Le capitaine Sanders débarque et le groupe décide de rejoindre le groupe de survivants en faisant un crochet vers une cache d'armes localisée par STEIN
Le capitaine se révèle souffrir de la psychose du sommeil cryogénique et VOSS le sédate d'un coup de seringue
Le contenu de la cache d'armes semble avoir été rongé par des créatures, seul une combinaison, une arme de poing et quelques fusils peuvent être récupérés
# REVE 2:
+ GYLD obtient l'aide de SANADAR l'innocent, maître des vents, pour dompter la nef-baleine céleste
FYRVOS apaise les incendies en apprivoisant les flammes
STYNNE démasque les faux dieux, les démons issus des flammes noires se cachant parmi les siens
Les démons sont repoussés en enfer, mais comme leur nuisance reste encore trop grande, il est décidé de les enfermer et en fermant les portes
Grâce à STYNNE qui repousse les démons, et FYRVOS qui redonne humanité à certains autres, leur plan réussi mais GYLD est capturé
+ GOLDBERG tente à nouveau de briefer le capitaine Sanders qui a perdu la mémoire, mais celui-ci se tire une balle dans la tête
une stratégie est établie pour redistribuer l'énergie dans le vaisseau afin de tenter un atterrissage sur HX-9,
mais sans les accès du capitaine il va falloir se rendre directement sur place dans le vaisseau
hors les créatures semblent attirées par l'énergie...
VOSS étudie le corps mécanique d'une des créatures assaillantes, puis tente une expédition pour amadouer certaines d'entre elles, non hostiles, qui se solde par un échec
lancement de l'expédition avec GOLDBERG, DRESDEN & STEIN, au cours de laquelle ce dernier identifie un borg, un contaminé parmi eux
# REVE 3: ...
Autre éléments narratifs :
* **EMBER** : une IA "enfant" capable d'outrepasser le contrôle sur le vaisseau d'YSTER
https://www.deviantart.com/captaintrebuchet/art/Robot-Children-687224957
* mutant N711
* ordinateur de bord = YSTER. Pas hostile mais en mode "maximisation rationnelle des chances de survie optimales", pas empathique et pas coopératif. => centre névralgique
* éclat métallique qui accélère le champs entropique à proximité de l'éclat
* officiers affectés par psychose du sommeil cryogénique => [leur mémoire ne dure que 10 minutes]! => deviennent fous
* cryogénie Courte Vie & Longue Vie
-->
<style>
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
flex-flow: wrap;
}
</style>Mush Radio est sorti !2021-03-20T17:00:00+01:002021-03-20T17:00:00+01:00Lucas Cimontag:chezsoi.org,2021-03-20:/lucas/blog/mush-radio-est-sorti.html<p>Ça y est, le jeu est fini !</p>
<p>Vous pouvez y jouer dès maintenant en le téléchargeant sur le site web dédié :</p>
<p><a href="https://mush-radio.chezsoi.org/"><img alt="" src="images/2021/03/MushRadioCard.jpg"></a></p>
<p>Je suis très fier du résultat et du chemin parcouru depuis un an avec <a href="https://www.instagram.com/tensei_draw/">Elliot Jolivet</a>
sur ce projet !</p>Deux variantes pour La Route Des Vignes2021-02-26T18:15:00+01:002021-02-26T18:15:00+01:00Lucas Cimontag:chezsoi.org,2021-02-26:/lucas/blog/deux-variantes-pour-la-route-des-vignes.html<!-- Com':
* [x] https://boardgamegeek.com/boardgame/205045/avenue/files
* [x] https://www.trictrac.net/jeu-de-societe/la-route-des-vignes/ressources
PDF sources: https://gitlab.com/Lucas-C/board-games/-/tree/master/LaRouteDesVignes
-->
<p><em>(English PDF rules at the bottom)</em></p>
<p>Il y a quelques années, lors du festival <a href="https://www.parisestludique.fr/">Paris est Ludique</a>,
nous avions testé une petit jeu qui nous avait bien plu :
<strong>La Route Des Vignes</strong> de Eilif Svensson & Kristian Amundsen Østby,
édité en France par <a href="https://www.matagot.com/fr/catalog/details/jeux-famille/3/la-route-des-vignes/893">Matagot</a>.</p>
<p>Pour une description du jeu, je vous …</p><!-- Com':
* [x] https://boardgamegeek.com/boardgame/205045/avenue/files
* [x] https://www.trictrac.net/jeu-de-societe/la-route-des-vignes/ressources
PDF sources: https://gitlab.com/Lucas-C/board-games/-/tree/master/LaRouteDesVignes
-->
<p><em>(English PDF rules at the bottom)</em></p>
<p>Il y a quelques années, lors du festival <a href="https://www.parisestludique.fr/">Paris est Ludique</a>,
nous avions testé une petit jeu qui nous avait bien plu :
<strong>La Route Des Vignes</strong> de Eilif Svensson & Kristian Amundsen Østby,
édité en France par <a href="https://www.matagot.com/fr/catalog/details/jeux-famille/3/la-route-des-vignes/893">Matagot</a>.</p>
<p>Pour une description du jeu, je vous invite à la lire cet article sur <a href="https://www.jedisjeux.net/article/critique-de-la-route-des-vignes">Jedijeux : Critique de La route des vignes</a>.</p>
<p>Un des points forts du jeu est qu'il est aussi amusant à jouer à 2 qu'à 10 !</p>
<p>Histoire de renouveler un peu le plaisir de jeu,
je suis allé jeter un oeil sur BoardGameGeek pour d'éventuelles variantes conues par des fans...
Et BINGO ! J'ai découvert quelques grilles alternatives : <a href="https://boardgamegeek.com/filepage/147235/unofficial-variant-maps">10 unofficial variant maps</a>.</p>
<p>Comme j'avais derrière la tête cette idée de rajouter une petite mécanique supplémentaire au jeu,
j'ai décidé de concevoir une petite variante.</p>
<p>La voici donc : <strong>APÉRITIF !</strong>
Bon jeu 😉</p>
<p><a href="images/jeux/LaRouteDesVignes-Variante-Aperitif-FR.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2021/02/LaRouteDesVignes-Variante-Aperitif.png">
<figcaption>Variante « Apéritif » FR (PDF 1 page 369 Ko)</figcaption>
</figure></p>
<p></a></p>
<p>Je ne l'ai pas encore beaucoup testé, et je serais ravir d'avoir vos retours dessus !</p>
<p><strong>EDIT (plus tard dans la journée)</strong> : bon, j'ai finalement été inspiré pour une seconde variante : <strong>Vignerons</strong></p>
<p><a href="images/jeux/LaRouteDesVignes-Variante-Vignerons-FR.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2021/02/LaRouteDesVignes-Variante-Vignerons.png">
<figcaption>Variante « Vignerons » FR (PDF 1 page 370 Ko)</figcaption>
</figure></p>
<p></a></p>
<p><strong>EDIT [2021/03/22]</strong> : j'ai conçu une troisième variante : <strong>Joker</strong></p>
<p><a href="images/jeux/LaRouteDesVignes-Variante-Joker-FR.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2021/02/LaRouteDesVignes-Variante-Joker.png">
<figcaption>Variante « Joker » FR (PDF 1 page 190 Ko)</figcaption>
</figure></p>
<p></a></p>
<p><em>English & printer friendly versions:</em></p>
<div class="side-by-side">
<a href="images/jeux/Avenue-Aperitif-Variant-EN.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2021/02/LaRouteDesVignes-Variante-Aperitif.png">
<figcaption>« Aperitif » variant EN (PDF 1 page 368 Ko)</figcaption>
</figure>
</a>
<a href="images/jeux/Avenue-LaRouteDesVignes-Aperitif-Variant-PrinterFriendly.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2021/02/LaRouteDesVignes-Variante-Aperitif.png">
<figcaption>Printer-friendly « Aperitif » variant (PDF 1 page 357 Ko)</figcaption>
</figure>
</a>
</div>
<div class="side-by-side">
<a href="images/jeux/Avenue-Winegrowers-Variant-EN.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2021/02/LaRouteDesVignes-Variante-Vignerons.png">
<figcaption>« Winegrowers » variant EN (PDF 1 page 369 Ko)</figcaption>
</figure>
</a>
<a href="images/jeux/Avenue-LaRouteDesVignes-Winegrowers-Variant-PrinterFriendly.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2021/02/LaRouteDesVignes-Variante-Vignerons.png">
<figcaption>Printer-friendly « Winegrowers » variant (PDF 1 page 359 Ko)</figcaption>
</figure>
</a>
</div>
<div class="side-by-side">
<a href="images/jeux/Avenue-Joker-Variant-EN.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2021/02/LaRouteDesVignes-Variante-Joker.png">
<figcaption>« Joker » variant EN (PDF 1 page 189 Ko)</figcaption>
</figure>
</a>
<a href="images/jeux/Avenue-LaRouteDesVignes-Joker-Variant-PrinterFriendly.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2021/02/LaRouteDesVignes-Variante-Joker.png">
<figcaption>Printer-friendly « Joker » variant (PDF 1 page 179 Ko)</figcaption>
</figure>
</a>
</div>
<style>
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
}
.side-by-side > * { margin: 0 2rem; }
</style>fpdf2.3.0 Unbreakable! and PDF quines2021-01-29T12:30:00+01:002021-01-29T12:30:00+01:00Lucas Cimontag:chezsoi.org,2021-01-29:/lucas/blog/fpdf2-3-0-unbreakable-and-pdf-quines.html<p><img alt="Unbreakable movie poster" src="images/2021/01/unbreakable.jpg"></p>
<p>Today, I am happy to announce <strong>version 2.3.0 of fpdf2</strong>, code name: <strong>Unbreakable!</strong></p>
<p><a href="https://github.com/pyfpdf/fpdf2/">https://github.com/pyfpdf/fpdf2/</a> <a href="https://pypi.python.org/pypi/fpdf2"><img alt="Pypi latest version" src="https://img.shields.io/pypi/v/fpdf2.svg"></a>
Doc: <a href="https://pyfpdf.github.io/fpdf2/">https://pyfpdf.github.io/fpdf2/</a></p>
<p>Why <em>Unbreakable</em>?</p>
<ul>
<li>As a tribute to <a href="https://en.wikipedia.org/wiki/Unbreakable_(film)">M. Night Shyamalan movie</a></li>
<li>Because using <code>fpdf2</code>, your Python code can never break!
<br>...<br>
Just kidding, I would be …</li></ul><p><img alt="Unbreakable movie poster" src="images/2021/01/unbreakable.jpg"></p>
<p>Today, I am happy to announce <strong>version 2.3.0 of fpdf2</strong>, code name: <strong>Unbreakable!</strong></p>
<p><a href="https://github.com/pyfpdf/fpdf2/">https://github.com/pyfpdf/fpdf2/</a> <a href="https://pypi.python.org/pypi/fpdf2"><img alt="Pypi latest version" src="https://img.shields.io/pypi/v/fpdf2.svg"></a>
Doc: <a href="https://pyfpdf.github.io/fpdf2/">https://pyfpdf.github.io/fpdf2/</a></p>
<p>Why <em>Unbreakable</em>?</p>
<ul>
<li>As a tribute to <a href="https://en.wikipedia.org/wiki/Unbreakable_(film)">M. Night Shyamalan movie</a></li>
<li>Because using <code>fpdf2</code>, your Python code can never break!
<br>...<br>
Just kidding, I would be a fool to think the library contains <strong>zero</strong> bug 😅</li>
<li>Because this release introduces a new <code>unbreakable()</code> method,
ensuring that inside this context manager, no page break will happen:</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="k">with</span> <span class="n">pdf</span><span class="o">.</span><span class="n">unbreakable</span><span class="p">()</span> <span class="k">as</span> <span class="n">pdf</span><span class="p">:</span>
<span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
<span class="k">for</span> <span class="n">datum</span> <span class="ow">in</span> <span class="n">row</span><span class="p">:</span>
<span class="n">pdf</span><span class="o">.</span><span class="n">cell</span><span class="p">(</span><span class="n">col_width</span><span class="p">,</span> <span class="n">line_height</span><span class="p">,</span> <span class="n">datum</span><span class="s2">", border=1)</span>
<span class="n">pdf</span><span class="o">.</span><span class="n">ln</span><span class="p">(</span><span class="n">line_height</span><span class="p">)</span>
<span class="n">pdf</span><span class="o">.</span><span class="n">ln</span><span class="p">(</span><span class="n">line_height</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span>
</code></pre></div>
<p>Check the documentation page on <a href="https://pyfpdf.github.io/fpdf2/PageBreaks.html">Page breaks</a>
for more details.</p>
<p>This release also brings a few other things:</p>
<ul>
<li>bug fixes for the <code>FPDF.alias_nb_pages</code> & <code>FPDF.set_font</code> methods</li>
<li>new <code>FPDF.epw</code> & <code>FPDF.eph</code> <code>@property</code> methods, providing the effective page width & height</li>
<li>several new documentation pages: feeding HTML, creating links, tables, text styling...</li>
</ul>
<p>Check the <a href="https://github.com/PyFPDF/fpdf2/blob/master/CHANGELOG.md">CHANGELOG.md</a> for more details!</p>
<h2>PDF quines</h2>
<p>Since I have started working on <code>fpdf2</code>,
I have this idea in the back of my mind:
<strong>could be possible to write a PDF quine?</strong></p>
<p>The term "quine" may not be well known, so I'll quote <a href="https://en.wikipedia.org/wiki/Quine_(computing)">the Wikipedia page on the subject</a>:</p>
<blockquote>
<p>A quine is a computer program which produces a copy of its own source code as its only output.</p>
</blockquote>
<p>Example with Python 3.8+:</p>
<div class="highlight"><pre><span></span><code><span class="n">exec</span><span class="p">(</span><span class="n">s</span><span class="o">:=</span><span class="s1">'print("exec(s:=</span><span class="si">%r</span><span class="s1">)"</span><span class="si">%s</span><span class="s1">)'</span><span class="p">)</span>
</code></pre></div>
<p>Or with <a href="https://en.wikipedia.org/wiki/Bash_(Unix_shell)">bash</a>:</p>
<div class="highlight"><pre><span></span><code>a<span class="o">(){</span> <span class="nb">echo</span> <span class="nv">$2</span> <span class="se">\\</span><span class="nv">$1</span> <span class="nv">$1</span> <span class="nv">$2</span> <span class="nv">$1</span> <span class="p">;</span><span class="o">}</span><span class="p">;</span>a <span class="se">\'</span> <span class="s1">' a(){ echo $2 \\$1 $1 $2 $1 ;};a '</span>
</code></pre></div>
<p>There is also the amazing <a href="https://github.com/taylorconor/quinesnake">quinesnake</a>, a quine in C language, that is also a video game !</p>
<p><a href="https://github.com/taylorconor/quinesnake"><img alt="Unbreakable movie poster" src="images/2021/01/quinesnake.gif"></a></p>
<p>No, could it be possible to build <strong>a PDF that displays its own source code?</strong></p>
<p>I have to admit that I haven't been able to craft one myself so far...</p>
<p>As a starting point, there is a really minimal PDF source code:</p>
<div class="highlight"><pre><span></span><code>%PDF-1.3
%âãÏÓ
1 0 obj<span class="err"><<</span>/Pages 2 0 R>>endobj
2 0 obj<span class="err"><<</span>/Count 1 /Kids [3 0 R] /Type /Pages>>endobj
3 0 obj<span class="err"><<</span>/Contents 4 0 R /MediaBox [0 0 300 200] /Resources <span class="err"><<</span>/Font <span class="err"><<</span>/F <span class="err"><<</span>/BaseFont /Courier /Encoding /WinAnsiEncoding>>>>>> /Type /Page>>endobj
4 0 obj<span class="err"><<</span>/Length 50>>stream
BT /F 8 Tf 0 10 TD <span class="nt"><25e2e3cfd3></span> Tj (%PDF-1.3) ' ETendstream endobj
xref
0 5
0000000000 65535 f
0000000014 00000 n
0000000045 00000 n
0000000097 00000 n
0000000244 00000 n
trailer<span class="err"><<</span>/Root 1 0 R /Size 5>>
startxref
340
%%EOF
</code></pre></div>
<p>If you paste this in a text file, and rename it with a <code>.pdf</code> extension,
you should be able to open it with a PDF viewer,
and the two first lines of this code snippet should be displayed.</p>
<p>So, as I was a bit stuck to go further, I built this <strong>PDF that displays its own source code as Python</strong>:</p>
<p><a href="images/2021/01/quine.pdf"><img alt="PDF" src="https://chezsoi.org/lucas/blog/images/2020/10/pdf-icon.png"></a></p>
<p>What do you think, is it cheating? 😜</p>
<!--
Com':
* [x] https://planetpython.org
* [x] https://www.reddit.com/r/Python/comments/kvbb4j/fpdf2_the_library_to_easily_generate_pdfs_got_a/
* [x] https://www.reddit.com/r/programming/comments/l7qok4/release_of_fpdf230_a_challenge_can_you_craft_a/
-->
<style>
.uk-article-content > p:nth-child(3) { /* Link to GitHub repo */
display: block;
text-align: center;
border: 1px solid black;
border-radius: 10rem;
padding: 1rem;
margin: 2rem 10vw;
}
</style>Jouer à Unlock! sans smartphone2021-01-27T09:00:00+01:002021-01-27T09:00:00+01:00Lucas Cimontag:chezsoi.org,2021-01-27:/lucas/blog/jouer-a-unlock-sans-smartphone.html<p>Si vous faites partie, comme moi, de ces étranges individus à ne pas posséder de smartphone en 2021,
mais que l'envie de jouer à <a href="https://www.spacecowboys.fr/unlock"><strong>Unlock!</strong></a>
vous démange comme vous adorez les jeux de piste & d'énigme,
j'ai la solution pour vous !</p>
<p><img alt="Capture d'écran d'Unlock! avec BlueStacks" src="images/2021/01/unlock.png"></p>
<p>Peut-être n'avez-vous simplement pas de smartphone sous la main, chargé …</p><p>Si vous faites partie, comme moi, de ces étranges individus à ne pas posséder de smartphone en 2021,
mais que l'envie de jouer à <a href="https://www.spacecowboys.fr/unlock"><strong>Unlock!</strong></a>
vous démange comme vous adorez les jeux de piste & d'énigme,
j'ai la solution pour vous !</p>
<p><img alt="Capture d'écran d'Unlock! avec BlueStacks" src="images/2021/01/unlock.png"></p>
<p>Peut-être n'avez-vous simplement pas de smartphone sous la main, chargé, au moment de vouloir commencer la partie 😅
Quoi qu'il en soit, une solution que j'ai testé et qui fonctionne très bien, est d'employer un <strong>émulateur pour Androïd</strong>,
c'est-à-dire un logiciel qui va simuler le système d'exploitation d'un téléphone portable sous Androïd.</p>
<p>Dans mon cas, illustré par la capture d'écran ci-dessus,
j'ai employé <a href="https://www.bluestacks.com/fr/index.html">BlueStacks</a> (note: <a href="https://www.clubic.com/os-mobile/android/article-881418-1-meilleurs-emulateurs-android-pc-mac.html">il en existe d'autres</a>).
L'installation requiert de télécharger environ 700Mo,
et une fois installé le logiciel utilise 2 Go d'espace disque.</p>
<p>Ensuite, il suffit de s'identifier avec un compte GMail sur le Google Play Store,
d'installer l'application <strong>Unlock!</strong> et de la lancer.</p>
<p>Bon jeu ! 😉</p>Adding content to existing PDFs with fpdf22021-01-15T12:30:00+01:002021-01-15T12:30:00+01:00Lucas Cimontag:chezsoi.org,2021-01-15:/lucas/blog/adding-content-to-existing-pdfs-with-fpdf2.html<p><code>fpdf2</code>, the library I mentioned in my <a href="fpdf2-release-2-2-0.html">previous post</a>, cannot <strong>parse</strong> existing PDF files.</p>
<p>However, other Python libraries can be combined with <code>fpdf2</code>
in order to add new content to existing PDF files.</p>
<p>This page provides several examples of doing so using <a href="https://github.com/pmaupin/pdfrw"><code>pdfrw</code></a>,
a great zero-dependency pure Python library dedicated …</p><p><code>fpdf2</code>, the library I mentioned in my <a href="fpdf2-release-2-2-0.html">previous post</a>, cannot <strong>parse</strong> existing PDF files.</p>
<p>However, other Python libraries can be combined with <code>fpdf2</code>
in order to add new content to existing PDF files.</p>
<p>This page provides several examples of doing so using <a href="https://github.com/pmaupin/pdfrw"><code>pdfrw</code></a>,
a great zero-dependency pure Python library dedicated to reading & writing PDFs,
with numerous examples and a very clean set of classes modelling the PDF internal syntax.</p>
<h2>Adding content onto an existing PDF page</h2>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">fpdf</span> <span class="kn">import</span> <span class="n">FPDF</span>
<span class="kn">from</span> <span class="nn">pdfrw</span> <span class="kn">import</span> <span class="n">PageMerge</span><span class="p">,</span> <span class="n">PdfReader</span><span class="p">,</span> <span class="n">PdfWriter</span>
<span class="n">IN_FILEPATH</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">OUT_FILEPATH</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="n">ON_PAGE_INDEX</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">UNDERNEATH</span> <span class="o">=</span> <span class="kc">False</span> <span class="c1"># if True, new content will be placed underneath page (painted first)</span>
<span class="k">def</span> <span class="nf">new_content</span><span class="p">():</span>
<span class="n">fpdf</span> <span class="o">=</span> <span class="n">FPDF</span><span class="p">()</span>
<span class="n">fpdf</span><span class="o">.</span><span class="n">add_page</span><span class="p">()</span>
<span class="n">fpdf</span><span class="o">.</span><span class="n">set_font</span><span class="p">(</span><span class="s2">"helvetica"</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mi">36</span><span class="p">)</span>
<span class="n">fpdf</span><span class="o">.</span><span class="n">text</span><span class="p">(</span><span class="mi">50</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="s2">"Hello!"</span><span class="p">)</span>
<span class="n">reader</span> <span class="o">=</span> <span class="n">PdfReader</span><span class="p">(</span><span class="n">fdata</span><span class="o">=</span><span class="nb">bytes</span><span class="p">(</span><span class="n">fpdf</span><span class="o">.</span><span class="n">output</span><span class="p">()))</span>
<span class="k">return</span> <span class="n">reader</span><span class="o">.</span><span class="n">pages</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">writer</span> <span class="o">=</span> <span class="n">PdfWriter</span><span class="p">(</span><span class="n">trailer</span><span class="o">=</span><span class="n">PdfReader</span><span class="p">(</span><span class="n">IN_FILEPATH</span><span class="p">))</span>
<span class="n">PageMerge</span><span class="p">(</span><span class="n">writer</span><span class="o">.</span><span class="n">pagearray</span><span class="p">[</span><span class="n">ON_PAGE_INDEX</span><span class="p">])</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">new_content</span><span class="p">(),</span> <span class="n">prepend</span><span class="o">=</span><span class="n">UNDERNEATH</span><span class="p">)</span><span class="o">.</span><span class="n">render</span><span class="p">()</span>
<span class="n">writer</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">OUT_FILEPATH</span><span class="p">)</span>
</code></pre></div>
<h2>Adding a page to an existing PDF</h2>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">fpdf</span> <span class="kn">import</span> <span class="n">FPDF</span>
<span class="kn">from</span> <span class="nn">pdfrw</span> <span class="kn">import</span> <span class="n">PdfReader</span><span class="p">,</span> <span class="n">PdfWriter</span>
<span class="n">IN_FILEPATH</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">OUT_FILEPATH</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="n">NEW_PAGE_INDEX</span> <span class="o">=</span> <span class="mi">1</span> <span class="c1"># set to None to append at the end</span>
<span class="k">def</span> <span class="nf">new_page</span><span class="p">():</span>
<span class="n">fpdf</span> <span class="o">=</span> <span class="n">FPDF</span><span class="p">()</span>
<span class="n">fpdf</span><span class="o">.</span><span class="n">add_page</span><span class="p">()</span>
<span class="n">fpdf</span><span class="o">.</span><span class="n">set_font</span><span class="p">(</span><span class="s2">"helvetica"</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mi">36</span><span class="p">)</span>
<span class="n">fpdf</span><span class="o">.</span><span class="n">text</span><span class="p">(</span><span class="mi">50</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="s2">"Hello!"</span><span class="p">)</span>
<span class="n">reader</span> <span class="o">=</span> <span class="n">PdfReader</span><span class="p">(</span><span class="n">fdata</span><span class="o">=</span><span class="nb">bytes</span><span class="p">(</span><span class="n">fpdf</span><span class="o">.</span><span class="n">output</span><span class="p">()))</span>
<span class="k">return</span> <span class="n">reader</span><span class="o">.</span><span class="n">pages</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">writer</span> <span class="o">=</span> <span class="n">PdfWriter</span><span class="p">(</span><span class="n">trailer</span><span class="o">=</span><span class="n">PdfReader</span><span class="p">(</span><span class="n">IN_FILEPATH</span><span class="p">))</span>
<span class="n">writer</span><span class="o">.</span><span class="n">addpage</span><span class="p">(</span><span class="n">new_page</span><span class="p">(),</span> <span class="n">at_index</span><span class="o">=</span><span class="n">NEW_PAGE_INDEX</span><span class="p">)</span>
<span class="n">writer</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">OUT_FILEPATH</span><span class="p">)</span>
</code></pre></div>
<p>This example relies on <a href="https://github.com/pmaupin/pdfrw/pull/216"><em>Pull Request</em> #216</a>
in order to be able to specify <code>at_index</code> (currently <code>pdfrw</code> only allows to <strong>append</strong> pages).
Until it is merged, you can install a forked version of <code>pdfrw</code> including the required patch:</p>
<div class="highlight"><pre><span></span><code><span class="n">pip</span><span class="w"> </span><span class="n">install</span><span class="w"> </span><span class="n">git</span><span class="o">+</span><span class="nl">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="p">.</span><span class="n">com</span><span class="o">/</span><span class="n">PyPDF</span><span class="o">/</span><span class="n">pdfrw</span><span class="p">.</span><span class="n">git</span><span class="nv">@addpage_at_index</span><span class="w"></span>
</code></pre></div>
<h2>Demo</h2>
<p>In order to demonstrate this using an actual example,
I first generated a PDF of the 1st code snippet above using the <a href="convert-code-to-pdf-with-syntax-coloring.html"><code>src2pdf</code> bash function described in an old article on syntax coloring</a>:</p>
<p><a href="images/2021/01/add_on_page.pdf">
<br>
<figure>
<img alt="PDF Logo" src="images/2021/01/add_on_page-thumbnail.pdf.png">
<figcaption><code>add_on_page.pdf</code> (PDF 1 page 30 Ko)</figcaption>
</figure></p>
<p></a></p>
<p>And then I added the following official Python logo to it:</p>
<p><img alt="Python logo" src="images/2021/01/python-logo.png"></p>
<p>There is the <code>new_content</code> function I used:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">new_content</span><span class="p">():</span>
<span class="n">fpdf</span> <span class="o">=</span> <span class="n">FPDF</span><span class="p">()</span>
<span class="n">fpdf</span><span class="o">.</span><span class="n">add_page</span><span class="p">()</span>
<span class="n">fpdf</span><span class="o">.</span><span class="n">image</span><span class="p">(</span><span class="s2">"python-logo.png"</span><span class="p">,</span> <span class="n">x</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">120</span><span class="p">)</span>
<span class="n">reader</span> <span class="o">=</span> <span class="n">PdfReader</span><span class="p">(</span><span class="n">fdata</span><span class="o">=</span><span class="nb">bytes</span><span class="p">(</span><span class="n">fpdf</span><span class="o">.</span><span class="n">output</span><span class="p">()))</span>
<span class="k">return</span> <span class="n">reader</span><span class="o">.</span><span class="n">pages</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</code></pre></div>
<p>And there is the result: <a href="images/2021/01/add_on_page_with_python_logo.pdf">add_on_page_with_python_logo.pdf (41 Ko)</a>.</p>fpdf2 release 2.2.02021-01-11T19:30:00+01:002021-01-11T19:30:00+01:00Lucas Cimontag:chezsoi.org,2021-01-11:/lucas/blog/fpdf2-release-2-2-0.html<p><img alt="fpdf2 logo" src="images/2021/01/fpdf2-logo.png"></p>
<p>Today, I am happy to announce <strong>a new version 2.2.0 of fpdf2</strong> !</p>
<p><a href="https://github.com/pyfpdf/fpdf2/">https://github.com/pyfpdf/fpdf2/</a> <a href="https://pypi.python.org/pypi/fpdf2"><img alt="Pypi latest version" src="https://img.shields.io/pypi/v/fpdf2.svg"></a>
Doc: <a href="https://pyfpdf.github.io/fpdf2/">https://pyfpdf.github.io/fpdf2/</a></p>
<p>During the last few months, I contributed a few improvements to <code>fpdf2</code>,
David Ankin fork of <code>PyFPDF</code>,
the user-friendly Python library to generate PDFs:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from …</span></code></pre></div><p><img alt="fpdf2 logo" src="images/2021/01/fpdf2-logo.png"></p>
<p>Today, I am happy to announce <strong>a new version 2.2.0 of fpdf2</strong> !</p>
<p><a href="https://github.com/pyfpdf/fpdf2/">https://github.com/pyfpdf/fpdf2/</a> <a href="https://pypi.python.org/pypi/fpdf2"><img alt="Pypi latest version" src="https://img.shields.io/pypi/v/fpdf2.svg"></a>
Doc: <a href="https://pyfpdf.github.io/fpdf2/">https://pyfpdf.github.io/fpdf2/</a></p>
<p>During the last few months, I contributed a few improvements to <code>fpdf2</code>,
David Ankin fork of <code>PyFPDF</code>,
the user-friendly Python library to generate PDFs:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">fpdf</span> <span class="kn">import</span> <span class="n">FPDF</span>
<span class="n">document</span> <span class="o">=</span> <span class="n">FPDF</span><span class="p">()</span>
<span class="n">document</span><span class="o">.</span><span class="n">add_page</span><span class="p">()</span>
<span class="n">document</span><span class="o">.</span><span class="n">set_font</span><span class="p">(</span><span class="s1">'helvetica'</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mi">60</span><span class="p">)</span>
<span class="n">document</span><span class="o">.</span><span class="n">text</span><span class="p">(</span><span class="n">x</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="n">y</span><span class="o">=</span><span class="mi">50</span><span class="p">,</span> <span class="n">txt</span><span class="o">=</span><span class="s2">"Hello world!"</span><span class="p">)</span>
<span class="n">document</span><span class="o">.</span><span class="n">output</span><span class="p">(</span><span class="s2">"hello_world.pdf"</span><span class="p">)</span>
</code></pre></div>
<p>As the original project was not maintained anymore, the two of us teamed up in order to merge several contributors'
<em>Pull Requests</em> submitted to the original project, and improve its documentation and overall code quality.</p>
<p>On my part, I added a <a href="https://github.com/alexanderankin/pyfpdf/actions?query=branch%3Amaster">GitHub Actions pipeline</a>
with as code prettifier (<code>black</code>) and a linter (<code>pylint</code>),
removed some dead code, added several unit tests, and improved the global performances x100
by using a <code>byterarray</code> <a href="https://github.com/alexanderankin/pyfpdf/commit/43a2090149a33d028766e88a279d4b54a8fbffff">for the internal buffer</a> and also <a href="https://github.com/alexanderankin/pyfpdf/commit/fa5620ce7a8d09f748d9dccc345822727c51b4c1">for image compression</a>.</p>
<p>For more details, you can check the <a href="https://github.com/alexanderankin/pyfpdf/blob/master/CHANGELOG.md">releases CHANGELOG</a>.</p>
<p>There are still many improvements to be done of course.
We'd be happy to here about your feedbacks & suggestions!</p>
<p>One of my main motivations behind this work is a <strong>PDF video game</strong> I'm working on
and I'm eager to present it here soon 😉</p>
<style>
.uk-article-content > p:nth-child(3) { /* Link to GitHub repo */
display: block;
text-align: center;
border: 1px solid black;
border-radius: 10rem;
padding: 1rem;
margin: 2rem 10vw;
}
</style>Variante 2 joueurs rapide pour 7 Wonders2020-12-11T18:00:00+01:002020-12-11T18:00:00+01:00Lucas Cimontag:chezsoi.org,2020-12-11:/lucas/blog/variante-2-joueurs-rapide-pour-7-wonders.html<!-- Com'
* https://boardgamegeek.com/filepage/213093/variante-2-joueurs-rapide-pour-7-wonders
* https://www.trictrac.net/jeu-de-societe/7-wonders-1/ressources
-->
<p>Pour les amateurs du jeu de société <strong>7 Wonders</strong> d’Antoine Bauza,
voici une alternative plus rapide & simple à la variante « experte » pour 2 joueurs.</p>
<p>Ces règles permettent de jouer des parties courtes, en duel, sans Cité franche.
Elles ont été inventé par <a href="https://boardgamegeek.com/thread/689461/streamlined-2-player-variant">Oliver Kiley qui les a partagé sur …</a></p><!-- Com'
* https://boardgamegeek.com/filepage/213093/variante-2-joueurs-rapide-pour-7-wonders
* https://www.trictrac.net/jeu-de-societe/7-wonders-1/ressources
-->
<p>Pour les amateurs du jeu de société <strong>7 Wonders</strong> d’Antoine Bauza,
voici une alternative plus rapide & simple à la variante « experte » pour 2 joueurs.</p>
<p>Ces règles permettent de jouer des parties courtes, en duel, sans Cité franche.
Elles ont été inventé par <a href="https://boardgamegeek.com/thread/689461/streamlined-2-player-variant">Oliver Kiley qui les a partagé sur BoardGameGeek</a>.
Je les ai simplement traduites et mises en page au format PDF :</p>
<p><a href="images/2020/12/7-Wonders-streamlined-2-players-FR.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2020/12/7-Wonders-streamlined-2-players-FR.png">
<figcaption>(PDF 1 page 268 Ko)</figcaption>
</figure></p>
<p></a></p>Les Couloirs du Temps, un scénario pour Run Die Repeat2020-11-30T11:00:00+01:002020-11-30T11:00:00+01:00Lucas Cimontag:chezsoi.org,2020-11-30:/lucas/blog/les-couloirs-du-temps-un-scenario-pour-run-die-repeat.html<p>Voici un nouveau scénario, prévu pour être joué par conférence audio, pour <a href="https://labrysgames.itch.io/run-die-repeat"><em>Run. Die. Repeat.</em></a>
le jeu de rôle monopage de <strong>Labrys Games</strong> où l'on décède à la chaîne :</p>
<p><a href="https://lucas-c.itch.io/les-couloirs-du-temps">
<br>
<figure>
<img alt="Run Die Repeat avec des gobelins" src="images/2020/11/RunDieRepeat-with-goblin.jpg">
<figcaption>Lien vers le PDF du jeu sur itch.io</figcaption>
</figure></p>
<p></a></p>
<p>Ce scénario, d'une durée de 45min-1h, se joue avec <a href="https://chezsoi.org/lucas/jdr/shared-img-reveal/">une petite application …</a></p><p>Voici un nouveau scénario, prévu pour être joué par conférence audio, pour <a href="https://labrysgames.itch.io/run-die-repeat"><em>Run. Die. Repeat.</em></a>
le jeu de rôle monopage de <strong>Labrys Games</strong> où l'on décède à la chaîne :</p>
<p><a href="https://lucas-c.itch.io/les-couloirs-du-temps">
<br>
<figure>
<img alt="Run Die Repeat avec des gobelins" src="images/2020/11/RunDieRepeat-with-goblin.jpg">
<figcaption>Lien vers le PDF du jeu sur itch.io</figcaption>
</figure></p>
<p></a></p>
<p>Ce scénario, d'une durée de 45min-1h, se joue avec <a href="https://chezsoi.org/lucas/jdr/shared-img-reveal/">une petite application web</a> que j'ai développé afin de progressivement révéler les lieux aux joueuses :</p>
<p><img alt="Image de démonstration de son fonctionnement" src="https://raw.githubusercontent.com/Lucas-C/shared-img-reveal/main/demo.png"></p>
<p>J'ai eu l'occasion de le faire jouer à 2 reprises, notamment samedi dernier à la 2e <a href="https://cyberconv.com/">CyberConv</a>,
et les retours ont été à chaque fois très enthousiastes ! 😊</p>
<p>Je serais ravis d'avoir vos <em>feedbacks</em> ou questions sur le scénario si vous le jouez, ou sur l'application web
pour révéler les cartes ! Vous pouvez laisser un commentaire ci-dessous 😉.</p>
<p>Liens utiles :</p>
<ul>
<li>traduction de <em>Run. Die. Repeat.</em> en français : <a href="https://chezsoi.org/lucas/blog/images/jdr/RunDieRepeat-FR.pdf">PDF (37 Ko)</a></li>
<li>tous les articles de ce blog à propos de <em>Run Die Repeat</em> : <a href="tag/run-die-repeat.html">tag run-die-repeat</a></li>
</ul>
<!-- Com'
* [x] https://www.scenariotheque.org/Document/info_doc.php?id_doc=10125
-> référence : https://chezsoi.org/lucas/blog/images/jdr/RDR-LesCouloirsDuTemps-v1.0.pdf
-->Run. Die. Repeat. à la CyberConv 2.02020-11-21T02:00:00+01:002020-11-21T02:00:00+01:00Lucas Cimontag:chezsoi.org,2020-11-21:/lucas/blog/run-die-repeat-a-la-cyberconv-2-0.html<p>Le week-end prochain, du 27 au 29 novembre, c'est la 2e édition de la <a href="https://cyberconv.com/">CyberConv</a> !</p>
<p><a href="https://cyberconv.com/"><img alt="Logo de la CyberConv" src="images/2020/11/cyberconv.jpg"></a></p>
<p>J'animerai 5 parties de <a href="tag/run-die-repeat.html"><em>Run. Die. Repeat.</em></a> au cours de cette convention de jeu de rôle, avec des scénarios maison :</p>
<ul>
<li>vendredi à 20h30 : <a href="https://www.conventions-rolistes.org/jdr/parties/1619.html"><strong>Invasion</strong></a> (30min)</li>
<li>samedi à 11h : <a href="https://www.conventions-rolistes.org/jdr/parties/1620.html"><strong>Parasite</strong></a> (30min)</li>
<li>samedi à 17h30 : <a href="https://www.conventions-rolistes.org/jdr/parties/1621.html"><strong>Les Couloirs …</strong></a></li></ul><p>Le week-end prochain, du 27 au 29 novembre, c'est la 2e édition de la <a href="https://cyberconv.com/">CyberConv</a> !</p>
<p><a href="https://cyberconv.com/"><img alt="Logo de la CyberConv" src="images/2020/11/cyberconv.jpg"></a></p>
<p>J'animerai 5 parties de <a href="tag/run-die-repeat.html"><em>Run. Die. Repeat.</em></a> au cours de cette convention de jeu de rôle, avec des scénarios maison :</p>
<ul>
<li>vendredi à 20h30 : <a href="https://www.conventions-rolistes.org/jdr/parties/1619.html"><strong>Invasion</strong></a> (30min)</li>
<li>samedi à 11h : <a href="https://www.conventions-rolistes.org/jdr/parties/1620.html"><strong>Parasite</strong></a> (30min)</li>
<li>samedi à 17h30 : <a href="https://www.conventions-rolistes.org/jdr/parties/1621.html"><strong>Les Couloirs du Temps</strong></a> (45min-1h) <strong>*</strong></li>
<li>dimanche à 11h : <a href="https://www.conventions-rolistes.org/jdr/parties/1622.html"><strong>Dernier wagon pour l’amour</strong></a> (30min)</li>
<li>dimanche à 17h30 : <a href="https://www.conventions-rolistes.org/jdr/parties/1623.html"><strong>Enquête sous pression à ValTordu</strong></a> (45min-1h) <strong>*</strong></li>
</ul>
<p><strong>*</strong> Pour ces 2 scénarios, une application en ligne sera employée pour afficher aux joueurs des éléments de la partie. Elle ne nécessitera aucun enregistrement.</p>
<p>Si l'aventure vous tente, n'hésitez pas à vous inscrire via les liens ci-dessus !</p>Where is Waldo Mush Radio ?2020-10-19T16:00:00+02:002020-10-19T16:00:00+02:00Lucas Cimontag:chezsoi.org,2020-10-19:/lucas/blog/where-is-mush-radio.html<p>Régulièrement nos fans nous interpellent dans la rue :</p>
<blockquote>
<p>« Mush Radio ça en est où ? »</p>
<p>« Mush Radio il sort quand ? »</p>
<p>« Je cultive des champignons en attendant la sortie de Mush Radio. Vous aimez les cêpes ? »</p>
</blockquote>
<p>Conscient de nos responsabilités, le temps est venu de faire une réponse officielle :</p>
<blockquote>
<p>« Toujours en développement …</p></blockquote><p>Régulièrement nos fans nous interpellent dans la rue :</p>
<blockquote>
<p>« Mush Radio ça en est où ? »</p>
<p>« Mush Radio il sort quand ? »</p>
<p>« Je cultive des champignons en attendant la sortie de Mush Radio. Vous aimez les cêpes ? »</p>
</blockquote>
<p>Conscient de nos responsabilités, le temps est venu de faire une réponse officielle :</p>
<blockquote>
<p>« Toujours en développement actif. »</p>
<p>« Quand il sera fini. »</p>
<p>« Oui on adore ! »</p>
</blockquote>
<p>Nous avons tous les 2 été pris par d'autres projets pendant l'été,
mais on est toujours chaud-patate-motivés sur ce projet !</p>
<p>Voici donc quelques actualités pour vous tenir au courant 😉</p>
<p>Un gros <strong>Big Up</strong> au passage à Ronan qui nous soutient depuis plusieurs mois : merci !</p>
<h2>Nouvelles illustrations</h2>
<p>Voici un planche rassemblant toutes les illustrations réalisées par <a href="https://www.instagram.com/tensei_draw/">Elliot Jolivet</a> pour le jeu :</p>
<p><a href="images/2020/10/MushRadio-artworks.jpg"><img alt="Artworks d'Elliot" src="images/2020/10/MushRadio-artworks.jpg"></a></p>
<h2>Playtest à l'asso Le Bocal</h2>
<p>Jeudi dernier les membres de l'association angevine <a href="https://bocal49.fr">Le Bocal</a> nous ont chaleureusement accueilli pour tester Mush Radio. Ce fut l'occasion d'avoir plein de retours constructifs en encourageants de créateurs de jeux chevronnés ! Mais aussi de réaliser qu'il nous restait encore pas mal de travail pour rendre le jeu accessible & ergonomique.</p>
<h2>Changements en cours</h2>
<p>Voici les modifications en cours, qui vont dans le sens d'une simplification des règles :</p>
<ul>
<li>chaque tour de jeu se joue désormais en simultané, et il n'y en a plus 8 mais seulement 6</li>
<li>la mise en place du jeu ne fait plus intervenir 5 lancers de 2d6 et est plus rapide</li>
<li>le plateau du jeu sera plus épuré : suppression de la "sous-case" <em>Ressource</em>, suppression du compteur de tours et de la <em>minimap</em>...</li>
<li>2 types de ressources distinctes font leur apparition le <strong>Fioul</strong> et le <strong>Métal</strong></li>
<li>la catapulte n'a plus de munitions, les Archives de la ville disparaissent, il n'y a plus d'actions au choix dans le déroulement du tour...</li>
</ul>
<p>Les semaines à venir seront donc l'occasion de faire encore plus de <strong>playtests</strong> !
Contactez-nous sur <em>MushRadio@gmail.com</em> si vous souhaitez faire une partie avec nous 😃</p>Variante 2 joueurs pour Dixit2020-10-01T09:00:00+02:002020-10-01T09:00:00+02:00Lucas Cimontag:chezsoi.org,2020-10-01:/lucas/blog/variante-2-joueurs-pour-dixit.html<!-- Com'
* https://boardgamegeek.com/filepage/213083/variante-cooperative-pour-2-joueurs
* https://www.trictrac.net/jeu-de-societe/dixit-0/ressources
-->
<figure>
<img alt="Pions lapin du jeu Dixit" src="images/2020/10/dixit-bunnies.jpg">
<figcaption>Photo de <a href="https://www.flickr.com/photos/farusantos/19610391093">Farley Santos</a>
- <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC BY-SA 2.0</a></figcaption>
</figure>
<p>Cette semaine j'ai découvert <a href="https://boardgamegeek.com/thread/676496/2-player-co-op-dixit-variant">sur le forum boardgamegeek.com</a> une variante du jeu de société <a href="">Dixit</a>
prévue pour 2 joueurs en mode <strong>coopératif</strong>.</p>
<p>Comme je l'ai trouvé amusante et bien pensée, je l'ai traduite en français.
La voici donc en une page …</p><!-- Com'
* https://boardgamegeek.com/filepage/213083/variante-cooperative-pour-2-joueurs
* https://www.trictrac.net/jeu-de-societe/dixit-0/ressources
-->
<figure>
<img alt="Pions lapin du jeu Dixit" src="images/2020/10/dixit-bunnies.jpg">
<figcaption>Photo de <a href="https://www.flickr.com/photos/farusantos/19610391093">Farley Santos</a>
- <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC BY-SA 2.0</a></figcaption>
</figure>
<p>Cette semaine j'ai découvert <a href="https://boardgamegeek.com/thread/676496/2-player-co-op-dixit-variant">sur le forum boardgamegeek.com</a> une variante du jeu de société <a href="">Dixit</a>
prévue pour 2 joueurs en mode <strong>coopératif</strong>.</p>
<p>Comme je l'ai trouvé amusante et bien pensée, je l'ai traduite en français.
La voici donc en une page au format PDF :</p>
<p><a href="images/2020/10/Dixit-2-players-FR.pdf">
<br>
<figure>
<img alt="Aperçu miniature" src="images/2020/10/Dixit-2players-variant-miniature.png">
<figcaption>(PDF 1 page 62 Ko)</figcaption>
</figure></p>
<p></a></p>
<p>Un grand merci à Brian M. d'avoir partagé cette variante simple et très sympa à jouer en couple !</p>Pelican, Pingback and Webmentions2020-07-15T14:00:00+02:002020-07-15T14:00:00+02:00Lucas Cimontag:chezsoi.org,2020-07-15:/lucas/blog/pelican-pingback-and-webmentions.html<!-- Partagé sur :
- [x] IRC indieweb.org
- [x] https://www.reddit.com/r/Python/comments/i19o2k/pelican_pingback_and_webmentions/
- [x] https://pycoders.com/submissions
-->
<p><a href="https://en.wikipedia.org/wiki/Linkback">Linkback protocols</a> are an old breed.
They were born in a time where MySpace, Wikipedia & WordPress had just been launched,
and Friendster was more popular than this new website called Facebook.</p>
<figure>
<img alt="Diagram showing how linkback protocols work" src="images/2020/07/linkback.gif">
<figcaption>Diagram source: <a href="https://www.pprune.org/misc.php?do=linkbacks">PPRuNe article on linkbacks</a></figcaption>
</figure>
<p>The latest linkback protocol, <a href="https://indieweb.org/Webmention">Webmention</a>, is relatively recent though,
as it became a …</p><!-- Partagé sur :
- [x] IRC indieweb.org
- [x] https://www.reddit.com/r/Python/comments/i19o2k/pelican_pingback_and_webmentions/
- [x] https://pycoders.com/submissions
-->
<p><a href="https://en.wikipedia.org/wiki/Linkback">Linkback protocols</a> are an old breed.
They were born in a time where MySpace, Wikipedia & WordPress had just been launched,
and Friendster was more popular than this new website called Facebook.</p>
<figure>
<img alt="Diagram showing how linkback protocols work" src="images/2020/07/linkback.gif">
<figcaption>Diagram source: <a href="https://www.pprune.org/misc.php?do=linkbacks">PPRuNe article on linkbacks</a></figcaption>
</figure>
<p>The latest linkback protocol, <a href="https://indieweb.org/Webmention">Webmention</a>, is relatively recent though,
as it became a W3C recommendation in 2017.</p>
<h3>What are linkback protocols ?</h3>
<p>A web mechanism to <strong>notify</strong> <code>website-B.com</code>
when <code>website-A.com</code> publishes content that includes <a href="https://en.wikipedia.org/wiki/Hyperlink">a link</a> to <code>website-B.com</code>.</p>
<p>Basically, <code>website-A.com</code> says “Hello <code>website-B.com</code> ! I have just written something about you, maybe you'd like to know !”</p>
<p>I find linkback protocols very valuable, for several reasons:</p>
<ul>
<li>they have strong technical features: conceptually simple, flexible, lightweight, easy to implement...
As they exist since almost 20 years, their robustness has been tried-and-tested.</li>
<li>they are a web standard</li>
<li>they foster a decentralized web</li>
</ul>
<p>As such, they form a practical alternative to commenting systems of tech Giants centralized platforms.</p>
<p>Many publishing tools (blog engines, website frameworks...) already support linkbacks.
As an example, IndieWeb has <a href="https://indieweb.org/Webmention#Publishing_Software">a long list of software that support Webmention</a>.</p>
<p><img class="pelican-logo" alt="Pelican logo" src="images/open-source/pelican-logo.png"></p>
<p>Myself, to generate this website, I am using <a href="https://getpelican.com">Pelican</a>,
a static blog engine written in Python.</p>
<p>So-called "static" websites are great: they are conceptually simple,
they are <a href="https://homebrewserver.club/low-tech-website-howto.html#software"><em>low tech</em></a>,
and because they are generated only from <a href="https://en.wikipedia.org/wiki/Version_control">versionable text documents</a>, they are very resilient.</p>
<p>Sadly, static websites cannot <strong>receive</strong> linkbacks, as this requires some custom server-side code.
However they can perfectly well <strong>send</strong> linkbacks !</p>
<p>Hence, I have written a Pelican plugin to send linkbacks,
with support for both Pingbacks & Webmentions,
which I am now proud to introduce here:</p>
<p><a href="https://github.com/pelican-plugins/linkbacks/">https://github.com/pelican-plugins/linkbacks/</a> <a href="https://pypi.python.org/pypi/pelican-plugin-linkbacks"><img alt="Pypi latest version" src="https://img.shields.io/pypi/v/pelican-plugin-linkbacks.svg"></a></p>
<p>There are already libraries on Pypi dedicated to publishing linkbacks, for various protocols.
However I did not find any which I was satisfied with the code quality, hence I wrote my own implementation.</p>
<p>The <code>send_pingback</code> & <code>send_webmention</code> functions in <code>linkbacks.py</code> are pretty straightforward.
They are well tested, and are easily usable in another Python project:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">linkbacks</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
<span class="n">LOGGER</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
<span class="n">send_pingback</span><span class="p">(</span><span class="n">source_url</span><span class="p">,</span> <span class="n">target_url</span><span class="p">)</span>
<span class="n">send_webmention</span><span class="p">(</span><span class="n">source_url</span><span class="p">,</span> <span class="n">target_url</span><span class="p">)</span>
</code></pre></div>
<p>As proof of this library correctly sending notifications, there are two websites that acknowledged reception of Pingback from this blog:</p>
<ul>
<li><a href="https://oujevipo.fr/general/4406-til-cows-tear-us-apart-web/">https://oujevipo.fr/general/4406-til-cows-tear-us-apart-web/</a></li>
<li><a href="http://lookrobot.co.uk/games/">http://lookrobot.co.uk/games/</a></li>
</ul>
<p>By the way, I learned that most <strong>Wordpress</strong> websites systematically answer to Pingback requests with a very unhelpful silent <code>faultCode 0</code>, due to the default value of the <code>xmlrpc_pingback_error</code> filter (<a href="https://github.com/WordPress/WordPress/blob/5.4.2/wp-includes/comment.php#L3016">source</a>). 😞
This was done to prevent <a href="https://en.wikipedia.org/wiki/Pingback#Exploits">Pingback exploits</a> like the infamous <a href="https://nvd.nist.gov/vuln/detail/CVE-2013-0235">CVE-2013-0235</a> (<a href="https://github.com/WordPress/WordPress/commit/82e9c40">mitigation commit source</a>),
but I don't think this kind of <a href="https://en.wikipedia.org/wiki/Security_through_obscurity">security through obscurity</a> is needed.
In fact I fear that the impact of hiding the Pingback submission status in Wordpress was mostly to slow down the adoption of this protocol...</p>
<p>Many thanks to Matthieu for providing me a temporary Wordpress blog, under a different domain, for my tests !</p>
<p>Another side note: maybe in the future I'll write a web-app to receive & store linkback requests,
and "plug" it to this blog. Or better, add this feature to <a href="https://posativ.org/isso/">isso</a> (<em>cf.</em> <a href="https://github.com/posativ/isso/issues/19">issue #19</a>).
In the meantime, I have found the <a href="https://webmention.io">webmention.io</a> service very nice & simple to use to add support for Webmention on this blog.
As it is centralized, it is not perfect, but it is a good start to support the adoption of this great linkback protocol !</p>
<p>By the way, I recently suggested to Reddit that they support Webmentions.
You can upvote this demand here: <a href="https://www.reddit.com/r/ideasfortheadmins/comments/i1apb8/feature_request_support_webmentions/">feature request</a>.</p>
<p>That's it for today.
Of course, I'd be more than happy to get feedback from you if you use this Pelican plugin. 😉
And may the <em>God of Clean Web Protocols & Semantics</em> be with you !</p>
<p><img alt="Ooops! you found a Dead Link" src="https://chezsoi.org/lucas/wwcb/photos/404-Dead_Link.jpg"></p>
<style>
.uk-article-content > p:nth-child(16) { /* Link to GitHub repo */
display: block;
text-align: center;
border: 1px solid black;
border-radius: 10rem;
padding: 1rem;
margin: 2rem 10vw;
}
.pelican-logo { float: left; max-height: 6rem; margin: 0 1rem; }
</style>Un scénario pour Run Die Repeat pour les enfants2020-07-06T15:30:00+02:002020-07-06T15:30:00+02:00Lucas Cimontag:chezsoi.org,2020-07-06:/lucas/blog/un-scenario-pour-run-die-repeat-pour-les-enfants.html<p>Dans la continuité des <a href="tag/run-die-repeat.html">articles précédents sur le jeu de rôle <em>Run Die Repeat</em></a>,
ainsi que de <a href="quelques-suggestions-pour-debuter-en-jdr-gratuites-et-en-francais.html">cet article plus ancien sur les jeux de rôle adaptés pour jouer avec des enfants</a>,
voici un court scénario pour R.D.R. conçu pour jouer avec de jeunes enfants.</p>
<p><img class="timer" alt="Durée : 20min" src="images/jdr/rdr/timer-20.svg"></p>
<p><img alt="Portrait de profile d'une sorcière" src="images/jdr/rdr/halloween_witch_face_profile_by_scottepentzer_d5hadl5-fullview.jpg"> <a href="https://www.deviantart.com/scottepentzer/art/Halloween-Witch-face-profile-331368521">Halloween Witch face …</a></p><p>Dans la continuité des <a href="tag/run-die-repeat.html">articles précédents sur le jeu de rôle <em>Run Die Repeat</em></a>,
ainsi que de <a href="quelques-suggestions-pour-debuter-en-jdr-gratuites-et-en-francais.html">cet article plus ancien sur les jeux de rôle adaptés pour jouer avec des enfants</a>,
voici un court scénario pour R.D.R. conçu pour jouer avec de jeunes enfants.</p>
<p><img class="timer" alt="Durée : 20min" src="images/jdr/rdr/timer-20.svg"></p>
<p><img alt="Portrait de profile d'une sorcière" src="images/jdr/rdr/halloween_witch_face_profile_by_scottepentzer_d5hadl5-fullview.jpg"> <a href="https://www.deviantart.com/scottepentzer/art/Halloween-Witch-face-profile-331368521">Halloween Witch face profile by scottepentzer</a> - <a href="https://creativecommons.org/licenses/by/3.0/fr/">CC BY 3.0</a></p>
<h2>Le camembert de la sorcière</h2>
<blockquote>
<p>Vous êtes un petit renard habitant la forêt.
Cachée au cœur de cette forêt, vit une sorcière.
Elle est plutôt gentille avec les animaux... tant qu'on ne la met pas en colère !
Ce matin, vous l'avez vu sortir de sa cave de magnifiques camemberts...
Et puis elle est partie faire ses courses au marché.
Le camembert, c'est votre plat préféré !
Alors c'est décidé, vous allez lui en chaparder un...
Mais attention aux champignons magiques qui poussent dans sa chaumière !</p>
</blockquote>
<h3>Objectif</h3>
<p>Chaparder un camembert et sortir de la maison de la sorcière avant son retour.</p>
<h3>Règles spéciales</h3>
<p>Les enfants lancent deux dés à 6 faces pour chaque jet, les adultes un seul.</p>
<h3>Environnement</h3>
<ul>
<li>les camemberts sont posés sur la table de la cuisine,
qui comporte plusieurs étagères et un plan de travail.</li>
<li>la fenêtre de la cuisine est fermée, mais les fenêtres de pièces donnant sur la cuisine sont ouvertes :</li>
<li>la chambre de la sorcière, où il y a des coussins, un bureau, et le corbeau de la sorcière, qui dort</li>
<li>le salon, où il y a une petite table, des fauteuils, et des plantes carnivores en pot</li>
</ul>
<h3>Obstacles</h3>
<ul>
<li>tout le sol de la maison de la sorcière est recouvert de champignons magiques,
qui, si on les touche, font disparaîte puis réapparaitre au-dehors !</li>
<li>une fois un camembert en possession du renard, le corbeau de la sorcière se réveille !
Il pourchasse le renard en croassant, et risque de donner l'alerte !</li>
</ul>
<h3>Conseils à la MJ</h3>
<p>J'ai testé ce scénario avec un enfant de 4 ans et ses parents :</p>
<ul>
<li>20min de temps me semble un grand maximum pour parvenir à conserver l'attention d'un enfant durant toute la partie.</li>
<li>décrivez bien les lieux avant de lancer le compte à rebours.</li>
<li>employez un compte à rebours à aiguilles, pour que les enfants puisse facilement visualiser le temps restant, comme celui-ci :
https://www.visnos.com/demos/classroom-timer</li>
<li>il peut être difficile pour les enfants de comprendre que tous les joueurs jouent le même personnage.
N'hésitez donc pas à vous rabattre sur un modèle 1 joueur = 1 personnage, avec une famille de renards.
On se rapproche alors du système de <a href="https://gusandco.net/2020/03/18/donjons-chenapans-jeu-enfants/">Donjons & Chenapans</a></li>
</ul>
<h3>Rejouer le scénario</h3>
<p>Ajoutez un chien qui dort dans sa niche, et qu'il ne faut pas réveiller, mais donnez un dé de plus aux enfants.</p>
<hr>
<p>Comme toujours, si vous lisez / testez ces scénarios, je serais ravis de savoir ce que vous en avez pensé !</p>
<p>Comme évoqué dans <a href="3-derniers-scenarios-pour-run-die-repeat-et-version-pdf.html">mon dernier article à propos de <em>Run Die Repeat</em></a>,
je continue à l'occasion à rédiger des scénarios pour ce jeu, que je compilerai à terme en PDF.
J'ai fini d'écrire les scénarios inspirés des jeux vidéos <em>Dishonored</em> & <em>Frostpunk</em>, ainsi qu'un 4e où les joueurs incarnent un parasite passant d'un hôte à un autre, et je pense que je les testerai bientôt.</p>
<style>
.timer { width: 10rem; float: left; }
.uk-article-content > p:nth-child(3) { text-align: center; } /* image caption */
</style>
<script>
document.querySelectorAll('article img').forEach(img => img.title = img.alt)
</script>Opération « dur à cuire »2020-07-02T12:00:00+02:002020-07-02T12:00:00+02:00Lucas Cimontag:chezsoi.org,2020-07-02:/lucas/blog/operation-dur-a-cuire.html<!--
https://www.deviantart.com/ignusdei/art/Commando-WIP-411855213
-->
<p><img alt="Couverture du jeu" src="images/2020/07/OperationHardCase.jpg"></p>
<p>Cette semaine, j'ai eu l'occasion de tester <a href="https://paolojcruz.itch.io/operation-hard-case"><em>Operation Hard Case</em></a>,
un court jeu de rôle de Paolo Jose Cruz, qui se décrit comme « un jeu d'infiltration / exploration <em>one-shot</em> avec cases hexagonales & 1 PV pour 1 à 2 joueurs ».</p>
<p>Le <em>pitch</em> de ce petit jeu de rôle m'avait tapé dans l'œil …</p><!--
https://www.deviantart.com/ignusdei/art/Commando-WIP-411855213
-->
<p><img alt="Couverture du jeu" src="images/2020/07/OperationHardCase.jpg"></p>
<p>Cette semaine, j'ai eu l'occasion de tester <a href="https://paolojcruz.itch.io/operation-hard-case"><em>Operation Hard Case</em></a>,
un court jeu de rôle de Paolo Jose Cruz, qui se décrit comme « un jeu d'infiltration / exploration <em>one-shot</em> avec cases hexagonales & 1 PV pour 1 à 2 joueurs ».</p>
<p>Le <em>pitch</em> de ce petit jeu de rôle m'avait tapé dans l'œil à l'automne dernier,
et je suis ravi d'avoir enfin pu le tester avec un ami.</p>
<p><em>Operation Hard Case</em> est court, autant en durée de partie, environ une heure, qu'en contenu :
4 pages, couverture comprise, que j'ai imprimé sur une seule feuille recto-verso.</p>
<p><em>Operation Hard Case</em> est une proposition ludique atypique :
un jeu <a href="/lucas/blog/tag/coop.html">coopératif</a> tactique pour 2 joueurs,
où la carte et les obstacles sont générés aléatoirement,
sans nécessitée de <em>roleplay</em> mais qui s'y prête très bien.</p>
<p>Et surtout, <em>Operation Hard Case</em> est un hommage assumé à la série de jeux vidéos <a href="https://fr.wikipedia.org/wiki/Metal_Gear_(s%C3%A9rie_de_jeux_vid%C3%A9o)"><em>Metal Gear</em></a>.
Encore plus même qu'un <a href="dog-bear.html"><em>Dog Bear</em></a>, que j'avais déjà mentionné ici.
Ambiance, équipement, ennemis, opérateur radio, mélange action-infiltration... tout y est !</p>
<figure>
<img alt="Metal Gear Solid V: Ground Zeroes by Kuvshinov-Ilya" src="images/2020/07/Metal-Gear-Solid-V-Ground-Zeroes-by-Kuvshinov-Ilya.jpg">
<figcaption><a href="https://www.deviantart.com/kuvshinov-ilya/art/Metal-Gear-Solid-V-Ground-Zeroes-442118095">Metal Gear Solid V: Ground Zeroes by Kuvshinov-Ilya</a></figcaption>
</figure>
<p>Je ne vais pas résumer les règles ici, tant elles sont concises,
mais je voudrais mentionner quelques mécaniques de jeu qui m'ont particulièrement plu.</p>
<p>Tout d'abord, les joueurs alternent à chaque scène entre le rôle d'<strong>Opérateur</strong>,
le personnage central de l'histoire (PJ) envoyé en mission,
et <strong>Mission Control</strong>, l'opérateur radio qui transmet des informations à l'opérateur
sur ce qui l'attend sur le terrain.
L'alternance est en soi déjà une idée très chouette,
mais je trouve surtout ce rôle de <em>MC</em> particulièrement intéressant :
il ne s'agit pas d'un MJ classique de jeu de rôle,
mais plus d'un PJ uniquement présent par radio.
La dynamique n'est pas celle du MJ qui entretient une tension dramatique en dosant l'adversité.
On est là plutôt dans une sorte de coopération asymétrique où les deux joueurs tentent de battre le jeu.
C'est assez original et surprenant !</p>
<p>En terme de règles toujours, on trouve les <em>moves</em> et les <em>"+/-1 forward"</em> familiers des joueurs de jeux <em>Powered by the Apocalypse</em>,
mais cette fois intelligemment segmentés en deux groupes,
accessibles selon si l'Opérateur a été détecté (<em>Exposed</em>) ou non (<em>Stealth</em>).
Enfin, petite cerise ludique sur le gâteau des mécaniques bien huilées,
l'Opérateur n'a pas de points de vie mais doit sacrifier un élément d'équipement lorsqu'il rate un jet en situation critique.</p>
<p>Pour notre partie de test à deux, nous avons perdu dans la 4e zone sur les 6,
et la difficulté nous a semblé haute et très dépendante des jets de dé.
J'ai refait une partie en solo, pour tester l'équilibrage et essayer de trouver la tactique optimale.
Bien que je pense avoir trouver le meilleur choix de <em>moves</em> possible, mes jets de dé m'ont fait perdre avant d'avoir nettoyé la 2e zone...</p>
<p>Malgré cette légère frustration, je me suis bien amusé durant ces parties à voir la situation progresser
de <a href="https://en.wikipedia.org/wiki/List_of_military_slang_terms#SNAFU">SNAFU</a> en <a href="https://en.wikipedia.org/wiki/List_of_military_slang_terms#FUBAR">FUBAR</a>,
pour finir par assister à la fin tragique de l'agent <code>Placid Dingo</code>,
abattu par un soldat en armure lourde.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/AShPXGlbvbE" allowfullscreen></iframe>
<iframe width="560" height="315" src="https://www.youtube.com/embed/sT8m1Rk0oCU" allowfullscreen></iframe>
<p>J'ai fait quelques suggestions à l'auteur sur <a href="https://paolojcruz.itch.io/operation-hard-case">itch.io</a>
en suggérant par exemple d'introduire un <em>move</em> additionnel de support au MC, pour réduire quelque peu la difficulté.</p>
<p>Voici donc mon petit bilan sur ce jeu en définitive :</p>
<table>
<thead>
<tr>
<th>Ce que j'ai aimé</th>
<th>Ce que j'ai moins aimé</th>
</tr>
</thead>
<tbody>
<tr>
<td>➕ la concision des règles & le format de partie courte</td>
<td>➖ la haute difficulté dépendant majoritairement des lancers de dés</td>
</tr>
<tr>
<td>➕ l'inspiration <em>Metal Gear Solid</em></td>
<td>➖ il manque peut-être un move au MC, accentuant la coopération</td>
</tr>
<tr>
<td>➕ la génération des zones ⬡ et leur exploration</td>
<td></td>
</tr>
<tr>
<td>➕ les équipements qui servent de PV</td>
<td></td>
</tr>
<tr>
<td>➕ les <em>moves</em> segmentés en 2 phases de jeu</td>
<td></td>
</tr>
<tr>
<td>➕ le rôle de <em>Mission Control</em>, différent de MJ</td>
<td></td>
</tr>
<tr>
<td>➕ l'alternance MC-opérateur tout au long de la partie</td>
<td></td>
</tr>
</tbody>
</table>
<p>Il faut également souligner la qualité de la mise en page :
la maquette est réussie, joliment structurée et illustrée.</p>
<p><img alt="Solid snake stencil" src="images/2020/07/solid-snake-stencil.jpg"></p>
<style>
article iframe { display: block; margin: 1rem auto; }
</style>Catapulte et fongicide2020-06-20T09:00:00+02:002020-06-20T09:00:00+02:00Lucas Cimontag:chezsoi.org,2020-06-20:/lucas/blog/catapulte-et-fongicide.html<p class="intro">
Ci-dessous un aperçu de partie de <a href="https://fr.tipeee.com/mush-radio">Mush Radio</a>,
le jeu de société coopératif pour 2 joueurs en cours de développement par <a href="https://www.instagram.com/tensei_draw/">Elliot</a> & moi même.
</p>
<video controls="controls">
<source src="images/2020/06/MushRadio-2020-06-19.webm" type="video/webm; codecs="vp9, opus"">
</video>
<blockquote>
<p>« Félicitations messieurs ! Grâce à votre escouade de choc,
cette ville est désormais hors de danger derrière cette muraille que vous avez érigé ! »</p>
<p>« Merci commandant. MORT AU …</p></blockquote><p class="intro">
Ci-dessous un aperçu de partie de <a href="https://fr.tipeee.com/mush-radio">Mush Radio</a>,
le jeu de société coopératif pour 2 joueurs en cours de développement par <a href="https://www.instagram.com/tensei_draw/">Elliot</a> & moi même.
</p>
<video controls="controls">
<source src="images/2020/06/MushRadio-2020-06-19.webm" type="video/webm; codecs="vp9, opus"">
</video>
<blockquote>
<p>« Félicitations messieurs ! Grâce à votre escouade de choc,
cette ville est désormais hors de danger derrière cette muraille que vous avez érigé ! »</p>
<p>« Merci commandant. MORT AU FONGUS !
Mais euh... Il reste un petit problème... »</p>
<p>« Lequel voyons ? La victoire me semble acquise ! »</p>
<p>« Notre allié sur l'autre rive commandant.
Il nous bombarde à la catapulte, et nous venons de perdre deux bâtiments. »</p>
<p><em>Soupir.</em> « Quelle chienlit... »</p>
</blockquote>
<p>Tandis qu'Elliot réalise de magnifiques illustrations pour le jeu (radio, escouade, bâtiments envahis par le Fongus...)
je continue à peaufiner les règles, <em>playtest</em> après <em>playtest</em>.
J'en profite d'ailleurs pour remercier les testeurs bénévoles qui ont bien voulu participer à toutes ces parties du prototype 🙏</p>
<p>L'animation ci-dessus représente l'évolution d'une grille de jeu au cours d'une partie.
Vous pouvez y distinguer le méchant Fongus, représenté par les symboles > ∧ < ∨ & ✘, envahir la ville par les côtés.
Au fur et à mesure des tours, des <strong>barrières</strong> de plantes mycophages sont érigées (les traits le long des cases),
des <strong>bâtiments</strong> sont construits (♥️, ⩋, 👁, 💧...) et des zones sont <strong>stérilisées</strong> (les cercles).</p>
<p>Dans <a href="la-bataille-contre-les-spores-fait-rage.html">l'article précédent</a>, j'exposais brièvement le déroulement du jeu.</p>
<p>Désormais, il est établi que le jeu se déroule en 8 tours,
et que l'objectif pour gagner est de <strong>créer une zone protégée la plus large possible dans chaque ville</strong>.
Pour la partie enregistrée en vidéo ci-dessus, la zone protégée finale est plutôt réduite, comme elle comporte 7 cases.</p>
<p>Les derniers changement de règles ont été l'occasion de <strong>simplifier</strong> quelques éléments du jeu (suppression des mutations,
mécanisme de propagation fongique revu...) et de <strong>corser</strong> un peu la difficulté.
Un <strong>score</strong> final a aussi été introduit, correspondant à la taille des deux zones habitables des joueurs.</p>
<p>Je travaille désormais à la rédaction des règles, pour les rendre les plus claires et didactiques possibles 😉</p>
<p>Si le jeu vous intéresse, laissez-nous un commentaire ici ou sur Tipeee !</p>
<p><a href="https://fr.tipeee.com/mush-radio"><img alt="Bannière Mush Radio" src="images/2020/04/MushRadio.jpg"></a></p>
<style>
.intro {
font-size: 1.6rem;
line-height: 2rem;
font-style: italic;
margin: 3rem 0;
}
article video { display: block; margin: 0 auto; }
</style>La bataille contre les spores fait rage2020-05-30T09:00:00+02:002020-05-30T09:00:00+02:00Lucas Cimontag:chezsoi.org,2020-05-30:/lucas/blog/la-bataille-contre-les-spores-fait-rage.html<p class="intro">
Voici quelques nouvelles de <a href="https://fr.tipeee.com/mush-radio">Mush Radio</a>
le jeu de société coopératif pour 2 joueurs que nous sommes en train de concevoir avec un ami.
</p>
<figure>
<img alt="Photo de ma table de travail" src="images/2020/05/wip-MushRadio.jpg">
<figcaption>Photo de ma table de travail</figcaption>
</figure>
<blockquote>
<p>« Commandant, j'ai une bonne et mauvaise nouvelle.
Tout d'abord, nous avons fini de construire la serre. »</p>
<p>« Parfait, bon boulot ! Notre …</p></blockquote><p class="intro">
Voici quelques nouvelles de <a href="https://fr.tipeee.com/mush-radio">Mush Radio</a>
le jeu de société coopératif pour 2 joueurs que nous sommes en train de concevoir avec un ami.
</p>
<figure>
<img alt="Photo de ma table de travail" src="images/2020/05/wip-MushRadio.jpg">
<figcaption>Photo de ma table de travail</figcaption>
</figure>
<blockquote>
<p>« Commandant, j'ai une bonne et mauvaise nouvelle.
Tout d'abord, nous avons fini de construire la serre. »</p>
<p>« Parfait, bon boulot ! Notre botaniste va pouvoir se mettre au travail pour élaborer nos défenses contre ces satanés champis. »</p>
<p>« La mauvaise maintenant... Le Fongus vient d'envahir le parc au Sud-Ouest de la ville. »</p>
<p>« Damnation ! Ils sont donc tout proche. Vite, hérissez les barricades ! »</p>
</blockquote>
<p>De son côté, Elliot a terminé le grand dessin de la grille de jeu,
représentant avec beaucoup de détails <strong>Mycélium City</strong>, la ville que les joueurs tentent de sauver.
Je trouve le résultat superbe, et je vous invite à aller y jeter un oeil ici : <a href="https://fr.tipeee.com/mush-radio/news/85460">news Tipeee</a>.</p>
<p>Pour ma part, j'ai avancé dans la conception de la mécanique de jeu,
et après une demi-douzaine de <em>playtests</em>, je vais détailler un peu ici les grandes lignes du système de jeu.</p>
<p>Dès le début du projet, nous nous sommes fixé quelques objectifs :</p>
<ul>
<li>court en taille : produire à la fin un PDF de 2 pages, en <em>print & play</em></li>
<li>court en temps : viser une durée de partie d'environ 30min, lorsqu'on connaît déjà les règles</li>
<li>quelques contraintes créatives : jouable par téléphone, zéro jeton posé sur le plateau, éviter la gomme</li>
</ul>
<p>Avec tout ceci en tête, je pense que plusieurs jeux m'ont ensuite inspiré :</p>
<ul>
<li><strong>la bataille navale</strong> pour le système de double grille "en aveugle"</li>
<li><strong>Hanabi</strong> d'Antoine Bauza pour la communication limitée</li>
<li><strong>Pandémie</strong> de Matt Leacock pour la mécanique des agents pathogènes convergeant, et les éclosions</li>
</ul>
<p>Dans les grandes lignes donc, dans <strong>Mush Radio</strong> les joueurs vont lutter contre <strong>le Fongus</strong>,
un champignon invasif s'attaquant aux créatures comme aux bâtiments.
Les joueurs incarnent chacun un groupe de survivants d'un côté du fleuve partageant la ville en deux,
limités dans leur communication par l'emploi d'une vieille radio.</p>
<p>Le <strong>Fongus</strong> se manifeste à la fin du tour de chaque joueur par des spores apparaissent sur les bords de plateau et se déplacent en ligne droite.
Lorsque 2 spores se rencontrent, cela déclenche une <strong>calcification</strong> immédiate des secteurs de la ville traversés.</p>
<p>Les joueurs tentent donc de bloquer la progression du Fongus en mettant en place
des <strong>barrières de plantes mycophages</strong> entre les quartiers. Ils peuvent également collecter des ressources,
ce qui leur permet de <strong>construire des bâtiments</strong>, fournissant chacun une amélioration spécifique.
Enfin, chaque joueur peut aider son partenaire à distance, en lui permettant de découvrir de nouvelles ressources,
ou en <strong>bombardant des colonies fongiques</strong>.</p>
<figure>
<a href="images/2020/05/MushRadio-v0.4-grid.png" target="_blank">
<img alt="Grille actuelle en v0.4" src="images/2020/05/MushRadio-v0.4-grid.png">
</a>
<figcaption>Capture d'écran du prototype web en v0.4</figcaption>
</figure>
<p>Pour le moment le jeu est assez fluide à jouer et j'ai eu de très bons retours sur certaines mécaniques de jeu,
comme les barrières, le bombardement et le système de pictogrammes.
Je travaille en ce moment sur le <em>End Game</em>, pour rendre le système fun & <em>challenging</em> en fin de partie.</p>
<p>Un grand merci aux <em>playtesteurs</em> qui ont bien voulu jouer avec moi à ce prototype encore inachevé !
J'en profite d'ailleurs pour mentionner une belle découverte que j'ai fait grâce à la vidéo <a href="https://www.youtube.com/watch?v=IgRv8mF7cPo">How to playtest d'Adam Porter</a> :
le <a href="https://gamefound.com/projects/jay-cormier/failfasterplaytestingjournal">Fail Faster playtesting journal</a>
qui permet d'historiser et de structure de manière très pratiques les <em>feedbacks</em> reçus en <em>playtest</em>.</p>
<p>Enfin, une dernière chose à mentionner :
nous avons mis en place une <a href="https://www.youtube.com/playlist?list=PLLgE-ga3W_kZrRHyXEcJD2zjJPPH9fqsD">playlist Youtube</a>
de morceaux d'ambiance pour le jeu !
C'est assez hétéroclite, allez y jetez une oreille et dites-nous ce que vous en pensez 😉</p>
<p><a href="https://fr.tipeee.com/mush-radio"><img alt="Bannière Mush Radio" src="images/2020/04/MushRadio.jpg"></a></p>
<style>
.intro {
font-size: 1.6rem;
line-height: 2rem;
font-style: italic;
margin: 3rem 0;
}
</style>Nonograms, Topolokus et compagnie2020-05-11T21:45:00+02:002020-05-11T21:45:00+02:00Lucas Cimontag:chezsoi.org,2020-05-11:/lucas/blog/nonograms-topolokus-et-compagnie.html<!-- Partagé sur :
- http://www.bibmath.net/forums/viewtopic.php?id=12588
- http://www.prise2tete.fr/forum/viewtopic.php?id=13888
NEXT: translate & put it on https://www.reddit.com/r/puzzles ?
> If you are linking your own blog or puzzle game/app, please submit this as a comment in the stickied self promotion thread. Please either post a text version of your puzzle, or rehost the image through Reddit or Imgur. Links to blogs/apps should be kept to the self promotion thread.
-->
<p><link rel="stylesheet" type="text/css" href="images/enigmes/topoloku.css"></p>
<p>Depuis le 24 mars, avec ma compagne, nous avons décidé de partager un petit puzzle logique par jour à nos amis & familles,
pour les distraire un peu en cette période difficile.</p>
<p>J'avais même bricolé un petit système de score, et j'en profite d'ailleurs pour féliciter ici les gagnants !</p>
<p><img alt="Podium des gagnants : Elise & Thibaut ont la 1ère place, Isabelle la 2e et youchos la 3e" src="images/enigmes/podium.jpg"></p>
<p>Comme aujourd'hui …</p><!-- Partagé sur :
- http://www.bibmath.net/forums/viewtopic.php?id=12588
- http://www.prise2tete.fr/forum/viewtopic.php?id=13888
NEXT: translate & put it on https://www.reddit.com/r/puzzles ?
> If you are linking your own blog or puzzle game/app, please submit this as a comment in the stickied self promotion thread. Please either post a text version of your puzzle, or rehost the image through Reddit or Imgur. Links to blogs/apps should be kept to the self promotion thread.
-->
<p><link rel="stylesheet" type="text/css" href="images/enigmes/topoloku.css"></p>
<p>Depuis le 24 mars, avec ma compagne, nous avons décidé de partager un petit puzzle logique par jour à nos amis & familles,
pour les distraire un peu en cette période difficile.</p>
<p>J'avais même bricolé un petit système de score, et j'en profite d'ailleurs pour féliciter ici les gagnants !</p>
<p><img alt="Podium des gagnants : Elise & Thibaut ont la 1ère place, Isabelle la 2e et youchos la 3e" src="images/enigmes/podium.jpg"></p>
<p>Comme aujourd'hui marque la date du "déconfinement", nous avons décidé de conclure cette série d'énigmes.</p>
<p>C'est l'occasion de les partager plus largement ici, en incluant toutes celles que nous n'avons pas eu l'occasion de soumettre.</p>
<h1>Nonograms</h1>
<p>Également nommés picross, logigraphes, hanjie, griddlers, ou encore logimages,
j'affectionne particulièrement ces puzzles logiques !</p>
<p>J'avais déjà évoqué ces casse-têtes par le passé dans <a href="https://linuxfr.org/news/generateurs-de-jeux-de-lettres-chiffres-libres">un article sur LinuxFr</a>,
et ce confinement a été l'occasion d'en rassembler quelques dizaines.</p>
<p>Une galerie collectant <strong>52 grilles</strong> est maintenant disponible en ligne à cette adresse :
<a href="https://lucas-c.github.io/Nonogram/gallery.html">https://lucas-c.github.io/Nonogram/gallery.html</a></p>
<p>Cliquez sur les points d'interrogations pour tenter de les résoudre !</p>
<h1>Topolokus</h1>
<p>J'ai inventé ces petits casse-têtes l'année dernière, et les règles sont décrites <a href="topoloku.html">dans l'article de présentation</a> sur ce blog.</p>
<p>Voici quelques grilles que j'ai conçu le mois dernier.
Cliquez sur les cases de chaque grille pour la remplir :</p>
<table class="topoloku" data-size="[5, 4]"
data-initial-letters='{"0,3": "✱", "1,3": "H", "2,0": "#", "3,3": "A"}'></table>
<p><br><br></p>
<table class="topoloku" data-size="[6, 5]"
data-initial-letters='{"5,2": "P", "4,0": "L", "4,2": "T", "3,3": "K", "2,2": "K", "1,2": "K", "0,0": "K", "0,4": "K"}'
data-secret-word-pos="[[5, 1], [5, 2], [5, 3], [2, 4]]"
data-on-success="onTopolokuSuccess"></table>
<p><br><br></p>
<table class="topoloku" data-size="[5, 8]"
data-initial-letters='{"0,0": "⦷", "3,3": "H", "0,4": "#", "1,4": "H", "2,6": "✱", "3,6": "H", "4,6": "#", "2,7": "#"}'></table>
<p><br><br></p>
<table class="topoloku" data-size="[5, 5]"
data-initial-letters='{"2,0": "#", "2,1": "✱", "2,2": "E", "3,1": "E", "2,4": "#"}'></table>
<p><br><br></p>
<table class="topoloku" data-size="[6, 4]"
data-initial-letters='{"0,0": "+", "3,0": "♡", "5,0": "=", "1,1": "L", "3,1": "L", "1,2": "=", "4,2": "=", "0,3": "L", "3,3": "+"}'
data-secret-word-pos="[[1, 1], [2, 1], [3, 1], [4, 1], [5, 1]]"></table>
<p><br><br></p>
<table class="topoloku" data-size="[7, 10]"
data-initial-letters='{"1,0": "X", "2,0": "Y", "3,0": "X", "6,0": "X", "0,2": "Z", "0,3": "X", "3,3": "X", "4,3": "X", "6,3": "X", "0,6": "X", "3,6": "X", "6,6": "X", "0,7": "X", "3,7": "X", "0,9": "X", "1,9": "X", "3,9": "X", "6,9": "X"}'></table>
<h1>Rébus Concept</h1>
<p>Comme il s'agit d'un jeu de société génial (sûrement en vente prêt de chez vous),
par respect pour le droit d'auteur je ne vais pas partager ici les splendides rébus que ma compagne a conçu.</p>
<p>Je peux cependant vous rediriger vers :</p>
<ul>
<li><a href="https://concept-the-game.com/pnp/">ces 10 rébus disponibles en PDF</a> sur le site officiel du jeu</li>
<li>les <a href="https://concept-the-game.com/concept/files/rules/CONCEPT-RULES-FR.pdf">règles en PDF</a>
ainsi que la très utile <a href="https://concept-the-game.com/concept/files/rules/CONCEPT-HELPSHEET-FR.pdf">aide de jeu</a>,
toujours sur leur site officiel</li>
</ul>
<h1>Énigmages</h1>
<p>Ces énigmes consistaient en des images se révélant progressivement,
et dont il fallait deviner ce qu'elles représentaient le plus tôt possible.</p>
<p>Elle sont toutes rassemblées sur cette page : <a href="pages/enigmages.html">pages/enigmages.html</a></p>
<script type="module">
import { renderTopolokuUsingDataAttrs } from './images/enigmes/topoloku.js';
window.onTopolokuSuccess = (table) => {
setTimeout(insertExtraLetter, 250, table, [[[5, 1], 'right-o'], [[5, 2], 'right-o'], [[5, 3], 'right-o'], [[2, 4], 'bottom-u']]);
onTopolokuSuccess(table);
}
function insertExtraLetter(table, letterInfo) {
if (!letterInfo.length) return;
const [[i, j], cssClass] = letterInfo.shift();
table.querySelector(`tr:nth-child(${j + 1}) > td:nth-child(${i + 1})`).classList.add(cssClass);
setTimeout(insertExtraLetter, 500, table, letterInfo);
}
Array.from(document.getElementsByClassName('topoloku')).forEach(renderTopolokuUsingDataAttrs);
document.querySelectorAll('h1').forEach(function (title) {
if (!title.classList.length) {
title.id = title.textContent
.toLowerCase()
.replace(/[()?!:,'&@]/g, '')
.replace(/[à]/g, 'a')
.replace(/[ç]/g, 'c')
.replace(/[éêè]/g, 'e')
.replace(/[ï]/g, 'i')
.replace(/ /g, '-');
const a = document.createElement('a');
a.href = document.location + '#' + title.id;
a['aria-hidden'] = true;
a.style.float = 'left';
a.style['padding-right'] = '4px';
a.style['margin-left'] = '-20px';
a.style['line-height'] = 1;
title.appendChild(a);
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('aria-hidden', true);
svg.setAttribute('height', 16);
svg.setAttribute('width', 16);
svg.setAttribute('viewBox', '0 0 16 16');
svg.style.color = '#1b1f23';
svg.style['vertical-align'] = 'middle';
svg.style.visibility = 'hidden';
a.appendChild(svg);
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttributeNS(null, 'fill-rule', 'evenodd');
path.setAttributeNS(null, 'd', 'M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z');
svg.appendChild(path);
title.onmouseover = function () { this.getElementsByTagName('svg')[0].style.visibility = 'visible'; };
title.onmouseout = function () { this.getElementsByTagName('svg')[0].style.visibility = 'hidden'; };
}
});
</script>
<style>
table.topoloku { margin: 0 auto; }
.right-o::after {
content: 'O';
display: block;
width: var(--cell-size);
line-height: var(--cell-size);
position: absolute;
right: calc(-1 * var(--cell-size));
top: 0;
background-color: lightgreen;
}
.bottom-u::after {
content: 'U';
display: block;
width: var(--cell-size);
line-height: var(--cell-size);
position: absolute;
right: 0;
bottom: calc(-1 * var(--cell-size));
background-color: lightgreen;
}
</style>3 derniers scénarios pour Run Die Repeat et version PDF2020-05-11T15:50:00+02:002020-05-11T15:50:00+02:00Lucas Cimontag:chezsoi.org,2020-05-11:/lucas/blog/3-derniers-scenarios-pour-run-die-repeat-et-version-pdf.html<!--
Com' : https://www.aubergevirtuelle.fr/t/sorties-et-nouveautes/3278/5
https://www.scenariotheque.org/Document/info_doc.php?id_doc=9986
-> référence : https://lucas-c.github.io/jdr/RunDieRepeat/RunDieRepeat-scenarios-FR.html
& https://chezsoi.org/lucas/blog/images/jdr/RunDieRepeat-scenarios-FR-v1.1.pdf
-->
<p>Dans la continuité des <a href="1ere-fournee-de-scenarios-pour-run-die-repeat.html">trois</a> <a href="2e-serie-de-scenarios-pour-run-die-repeat.html">articles</a> <a href="scenarios-hitman-et-la-chute-du-colosse-pour-run-die-repeat.html">précédents</a>,
voici 3 derniers scénarios de <strong>30min</strong> chacun formant un tryptique
inspiré des jeux vidéos <em>Splinter Cell</em>, <em>Metal Gear</em> & <em>Prototype</em> :</p>
<ol>
<li>Cthulhu fhtagn</li>
<li>Évasion</li>
<li>Labyrinthe</li>
<li>La Grande Guerre</li>
<li>Invasion</li>
<li>La chute du colosse</li>
<li>Hitman</li>
<li><strong>Spec Ops #1 - Infiltration</strong></li>
<li><strong>Spec Ops #2 - Prototype</strong></li>
<li><strong>Spec Ops #3 …</strong></li></ol><!--
Com' : https://www.aubergevirtuelle.fr/t/sorties-et-nouveautes/3278/5
https://www.scenariotheque.org/Document/info_doc.php?id_doc=9986
-> référence : https://lucas-c.github.io/jdr/RunDieRepeat/RunDieRepeat-scenarios-FR.html
& https://chezsoi.org/lucas/blog/images/jdr/RunDieRepeat-scenarios-FR-v1.1.pdf
-->
<p>Dans la continuité des <a href="1ere-fournee-de-scenarios-pour-run-die-repeat.html">trois</a> <a href="2e-serie-de-scenarios-pour-run-die-repeat.html">articles</a> <a href="scenarios-hitman-et-la-chute-du-colosse-pour-run-die-repeat.html">précédents</a>,
voici 3 derniers scénarios de <strong>30min</strong> chacun formant un tryptique
inspiré des jeux vidéos <em>Splinter Cell</em>, <em>Metal Gear</em> & <em>Prototype</em> :</p>
<ol>
<li>Cthulhu fhtagn</li>
<li>Évasion</li>
<li>Labyrinthe</li>
<li>La Grande Guerre</li>
<li>Invasion</li>
<li>La chute du colosse</li>
<li>Hitman</li>
<li><strong>Spec Ops #1 - Infiltration</strong></li>
<li><strong>Spec Ops #2 - Prototype</strong></li>
<li><strong>Spec Ops #3 - REVENGEANCE</strong></li>
</ol>
<p>Pour l'occasion, j'ai fini de les rassembler tous sous forme de <a href="/lucas/blog/tag/pdf.html">PDF</a> :</p>
<div class="side-by-side">
<a href="images/jdr/RunDieRepeat-scenarios-FR-v1.1.pdf">
<figure>
<img alt="" src="images/2020/05/banner-pdf.jpg">
<figcaption><em>10 scénarios pour Run. Die. Repeat.</em><br>(PDF 13 pages 2,0 Mo)</figcaption>
</figure>
</a>
<a href="https://lucas-c.github.io/jdr/RunDieRepeat/RunDieRepeat-scenarios-FR.html">
<figure>
<img alt="" src="images/2020/05/banner-web.jpg">
<figcaption><em>10 scénarios pour Run. Die. Repeat.</em><br>(web)</figcaption>
</figure>
</a>
</div>
<p>Il s'agit là de la version 1.1 du receuil de scénario,
qui diffère très légèrement de la version 1.0 déjà publiée sur <a href="https://troplongpaslu.fr">troplongpaslu.fr</a> :
quelques détails ont été un peu clarifiés dans le scénario <em>La Grande Guerre</em>,
et nouvelle illustration a été adoptée pour le scénario Revengeance.</p>
<p>À nouveau, si vous lisez / testez ces scénarios, je serais ravi d'avoir vos retours 😉</p>
<p>Je suis vraiment très content d'avoir terminé ce receuil, et de le publier en ligne.
J'y ai consacré pas mal de temps ces derniers mois, et ai testé tous les scénarios à plusieurs reprises
avec des amis et des collègues, que je remercie au passage !</p>
<p>Je vais en profiter pour dresser ici un petit bilan personnel de toutes ces parties de <em>Run. Die. Repeat.</em></p>
<p>Comme ce qui suit comporte quelques considérations de <em>gamedesign</em> lorgnant vers la psychologie cognitive,
je tiens à mentionner que je ne suis pas spécialiste du sujet, et que je prendrai avec plaisir toute remarque ou critique.</p>
<h3>Réception</h3>
<p>Tout d'abord, la réception du jeu par les joueurs avec lesquels j'ai fait des parties a toujours été très enthousiaste !
Souvent un peu circonspects au départ, ils saisissent très vite son principe et son intérêt en jouant une première partie.</p>
<p>Beaucoup d'entre eux ont exprimé qu'ils appréciaient le format court, plutôt bien adapté à du jeu en ligne,
ainsi que la liberté d'action et la variété des ambiances que le jeu permet.</p>
<h3>Originalité</h3>
<p>Vis à vis d'autres jeux de rôles, je trouve que <em>Run. Die. Repeat.</em> a définitivement quelques qualités assez uniques :</p>
<ul>
<li><strong>il nécessite beaucoup plus d'imagination de la part des joueuses</strong>.
Comme la très grosse majorité des actions tentées par les joueuses se termine par un échec fatal,
elles doivent continuellement trouver de nouvelles approches pour faire face à une même situation.</li>
</ul>
<p>Dans beaucoup d'autres jeux de rôle, cette forme d'ingéniosité est une composante parmi bien d'autres de la partie,
laissant la part belle, selon les jeux, à l'improvisation théatrale, à la réflexion, à la tactique ou à bien d'autres mécanismes.</p>
<p>Dans <em>Run. Die. Repeat.</em>, imaginer de nouvelles solutions, même improbables, est au coeur de la <a href="https://fr.wikipedia.org/wiki/Game_design#Gameplay">« boucle ludique »</a>. D'ailleurs, le jeu ne pénalisant absolument pas les idées loufoques,
elles peuvent donner lieux à de grands moments d'hilarité autour de la table !</p>
<p>Pour l'ancedote par exemple, lors d'une partie du scénario <em>Invasion</em> hier,
face à une horde d'aliens envahisseurs, un joueur a tenté de communiquer un message d'amour et de paix
à l'un d'eux sur le point de l'exterminer.
Le dé donne son verdict : l'action est réussie !
Tout le monde se poile, et le joueur a désormais un allié dans sa mission.</p>
<ul>
<li><strong>il met successivement toutes les joueuses sous les feux de la rampe</strong>.
Deux facteurs contribuent à cela : les joueuses jouent chacun leur tour, dans un dialogue avec la MJ
où les autres joueuses sont spectatrices ; mais aussi car une des mécaniques de jeu est de devoir se souvenir
des actions effectuées par les autres joueuses qui ont réussi.</li>
</ul>
<p>Le jeu incite donc les joueuses a être très attentives aux essais de leurs camarades.
Et très naturellement, bien que cela soit déterminé purement aléatoirement,
les joueuses réussissant à progresser sont acclamées,
et on exprime de l'empathie pour celles qui décèdent.</p>
<p>Au passage d'ailleurs, j'ai l'impression que la haute difficulté du jeu (seulement une chance sur six de réussir toute action)
"casse" en quelque sorte toute <a href="https://fr.wikipedia.org/wiki/Atychiphobie">peur de l'échec</a>
que pourraient ressentir les joueuses, comme elles sont clairement condamnées à râter et mourir à répétition, quoi qu'elles tentent,
les soudant au contraire dans l'adversité.</p>
<ul>
<li><strong>il permet de construire des scènes très cinématographiques et rythmées</strong>.
Comme la même situation est sans cesse "re-jouée", tout le monde autour de la table a le temps d'en imaginer les moindres détails.
Et comme il s'agit d'une course contre la montre, les éléments de l'histoire s'enchainent très vite les uns à la suite des autres,
formant une séquence intense mais courte dans la trame temporelle de l'histoire.</li>
</ul>
<p>La scène s'étoffe petit à petit d'éléments narratifs, provenant autant de la MJ que des joueuses,
et une fois la partie terminée tout le monde a en tête l'intégralité d'une course poursuite effrénée,
d'une bataille épique ou encore d'un plan parfaitement orchestré.</p>
<h3>Contraintes & opportunités du jeu en ligne</h3>
<p>Jusqu'à présent, j'avais toujours été assez réticent à participer à des parties de jeux de rôle en ligne.
Pour moi, se retrouver en personne tous ensemble est un élement fondamental de ce loisir que j'affectionne.
Sans doute un besoin de connexion que j'ai, et la conscience de beaucoup perdre en terme de qualité d'échange et de communication
lorsque l'on est pas face à face.</p>
<p>Le confinement dans lequel nous sommes tous forcés ayant quelque peu changé la donne,
j'ai eu l'occasion de faire des parties sur Discord et Framatalk.</p>
<p>Et au final, même si je préfère toujours retrouver les joueurs en personne,
j'ai été agréablement surpris par quelques points apportées par ces parties en ligne.</p>
<ul>
<li>
<p><strong>le double niveau de conversation</strong>, tout d'abord : à la discussion orale,
où chacun s'exprime tour à tour, vient s'ajouter le <em>chat</em> textuel.
Lors de parties de <em>Run. Die. Repeat.</em> par exemple, cela permet aux joueuses spectatrices de prodiguer des conseils ou des encouragements
à celle qui est "active" et dialogue à l'oral avec la MJ, ou encore à poser des questions de compréhension.</p>
</li>
<li>
<p><strong>l'emploi d'autres outils en ligne</strong> ensuite. Lors d'une partie par exemple,
un joueur a suggéré d'employer <a href="https://awwapp.com">AWW board</a> pour dessiner collectivement une carte des lieux afin de mieux s'y retrouver. Même si cela va un peu à l'encontre des règles de base du jeu, j'ai trouvé l'idée géniale !
Cela a permis aux joueuses non actives de s'impliquer (les rendant aussi un peu moins attentives),
et ainsi collaborativement de rendre la situation bien plus claire pour elles,
sans pour autant gêner en quoi que ce soit le déroulement du jeu.</p>
</li>
</ul>
<h3>Et après ?</h3>
<p>Au final, un peu étonnamment, j'ai encore une bonne dizaine d'idées de scénarios sous le coude,
inspirées de Frostpunk, Dishonored, Akira, The Lost Room, Sovok...
Merci d'ailleurs à Henri qui, plein d'imagination comme d'habitude, m'a proposé plein de nouveaux <em>pitchs</em> géniaux !
Je vais toutefois mettre tout ça de côté pour le moment,
histoire de voir si j'ai des retours encourageants sur ce 1er receuil,
et surtout pour me concentrer sur le projet de jeu <a href="lancement-d-un-tipeee-pour-mush-radio.html">Mush Radio</a>.</p>
<style>
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
flex-flow: wrap;
}
.side-by-side > * { flex: 1 0; padding: 0 .5rem; }
</style>Scénarios Hitman & La chute du colosse pour Run Die Repeat2020-05-04T10:00:00+02:002020-05-04T10:00:00+02:00Lucas Cimontag:chezsoi.org,2020-05-04:/lucas/blog/scenarios-hitman-et-la-chute-du-colosse-pour-run-die-repeat.html<p>Dans la continuité des <a href="1ere-fournee-de-scenarios-pour-run-die-repeat.html">scénarios pour le jeu de rôle <em>Run. Die. Repeat</em></a> des <a href="2e-serie-de-scenarios-pour-run-die-repeat.html">deux dernières semaines</a>,
voici 2 nouveaux scénarios d'une durée de partie de <strong>30min</strong>
inspirés de jeux vidéos bien connus :</p>
<ol>
<li>Cthulhu fhtagn</li>
<li>Évasion</li>
<li>Labyrinthe</li>
<li>La Grande Guerre</li>
<li>Invasion</li>
<li><strong>La chute du colosse</strong></li>
<li><strong>Hitman</strong></li>
<li>Spec Ops #1 - Infiltration …</li></ol><p>Dans la continuité des <a href="1ere-fournee-de-scenarios-pour-run-die-repeat.html">scénarios pour le jeu de rôle <em>Run. Die. Repeat</em></a> des <a href="2e-serie-de-scenarios-pour-run-die-repeat.html">deux dernières semaines</a>,
voici 2 nouveaux scénarios d'une durée de partie de <strong>30min</strong>
inspirés de jeux vidéos bien connus :</p>
<ol>
<li>Cthulhu fhtagn</li>
<li>Évasion</li>
<li>Labyrinthe</li>
<li>La Grande Guerre</li>
<li>Invasion</li>
<li><strong>La chute du colosse</strong></li>
<li><strong>Hitman</strong></li>
<li>Spec Ops #1 - Infiltration</li>
<li>Spec Ops #2 - Prototype</li>
<li>Spec Ops #3 - REVENGEANCE</li>
</ol>
<p>Pour ceux qui se sont gardés de lire les scénarios, je vous propose une petite partie en ligne le week-end prochain, via Discord, avant la fin du confinement.</p>
<p>Si la proposition vous tente, répondez à ces 2 sondages pour déterminer quel scénario et à quel moment dans le week-end organiser cela :</p>
<ul>
<li>indiquez à quels scénarios vous n'avez pas encore joué ici : <a href="https://framadate.org/9v8pmlZSLepKpq6Q">https://framadate.org/9v8pmlZSLepKpq6Q</a></li>
<li>indiquez à quels moments vous seriez disponibles ici : <a href="https://framadate.org/bZlVkdE7WOIhylCE">https://framadate.org/bZlVkdE7WOIhylCE</a></li>
</ul>
<p>Employez votre pseudo Discord pour que je puisse vous notifier du créneau qui sera finalement choisi 😉</p>
<hr>
<p><img class="timer" alt="Chronomètre de 30min" src="images/jdr/rdr/timer-30.svg"></p>
<p><img alt="" src="images/jdr/rdr/sotc_christmas_shirt_design_by_fritz6_d1e07il-pre.png"></p>
<p><a href="https://www.deviantart.com/fritz6/art/SotC-Christmas-Shirt-Design-83990541">SotC Christmas Shirt Design by Fritz6</a> - CC BY-NC-SA 3.0</p>
<h2>La chute du colosse</h2>
<blockquote>
<p>Le géant de pierre, aux membres ornés de runes mystiques, est aux portes de la cité :
sa taille colossale plonge la moitié de la ville dans l'ombre.
Heureusement, le sortilège des mages a fonctionné : vous avez quelques minutes devant vous,
dans cette bulle « hors du temps », où vous ne craignez pas la mort.
Vous êtes le dernier espoir de votre peuple : vous devez le vaincre avant que l'enchantement ne se brise !</p>
</blockquote>
<h3>Objectif</h3>
<p>Abattre le colosse en trouvant et détruisant son point faible.</p>
<h3>Inspirations</h3>
<p>Le jeu vidéo <em>Shadow of the Colossus</em>, le manga l'Attaque des Titans.</p>
<h3>Environnement</h3>
<p>Vous êtes à cheval, avec un glaive qui peut être propulsé à grande vitesse et tracter un cable.
À proximité, il n'y a que les remparts de la cité et une falaise.
Le géant a plusieurs particularités :
- il est très lent
- il manie une gigantesque massue
- il peut être escaladé, son corps de pierre comportant de nombreuses prises
- ses points faibles :
* un sceau runique luminescent au front (le seul des 3 visible aisément)
* un harpon planté dans sa nuque qui, si retiré, « libèrera » la créature, qui fera demi-tour
* un énorme cristal bleu au niveau du coeur, battant lentement.
Il emet une attraction magnétique qui immobilisera le glaive contre son torse si le joueur l'escalade.</p>
<h3>Obstacles</h3>
<ul>
<li>dès que le joueur s'approche de lui, le colosse tentera de l'écrabouiller d'un pied ou de son arme</li>
<li>en dessous du torse, le corps du colosse comporte des pièges mécaniques :
pieux, lames rotatives, zones qui s'effritent ou se détachent...</li>
<li>au dessus du torse, des arraignées venimeuses parcourent le colosse, et aggresseront quiconque tente de l'escalader.
Si le joueur tente de planter son glaive téléscopique dans un membre dans cette zone, les arraignées le sectionneront.</li>
<li>des squelettes de rapaces volent au-dessus de sa tête, et s'attaqueront au joueur s'il arrive jusque là</li>
</ul>
<h3>Conseils au MJ</h3>
<p>Si les joueurs sont très rapides à vaincre le colosse, des cris et la lumière d'un incendie de l'autre côté de la ville :
un second titan s'y est engouffré !
Si au contraire les joueurs sont lents, ne faites pas intervenir les rapaces.</p>
<h3>Rejouer le scénario</h3>
<p>Mode <em>difficile</em> : le joueur n'a plus de glaive téléscopique, uniquement une épée et un arc.
Introduisez le seconde titan si vous ne l'avez pas fait la 1ère fois.</p>
<hr>
<p><img class="timer" alt="Chronomètre de 30min" src="images/jdr/rdr/timer-30.svg"></p>
<p><img alt="" src="images/jdr/rdr/hitman___absolution_by_evandeciren_d5d6oc6.jpg"></p>
<p><a href="https://www.deviantart.com/evandeciren/art/HITMAN-Absolution-324477366">HITMAN - Absolution by EvanDeCiren</a> - CC BY 3.0</p>
<h2>Hitman</h2>
<blockquote>
<p>Ce soir, vous avez un contrat, une cible à abattre : Viktor Novikov,
dirigeant d'une influante marque de haute couture, et représentant d'un réseau occulte d'espions.
Vous venez de vous introduire dans le palais Walewska sur l'île Saint-Louis à Paris,
un musée où se tient pour l'occasion un défilé de mode.
Vous avez sur vous un costume trois pièces et un revolver avec silencieux.</p>
</blockquote>
<h3>Objectif</h3>
<p>Éliminer Viktor Novikov et vous enfuir avant d'être capturé ou tué.</p>
<h3>Inspirations</h3>
<p>Le jeu vidéo Hitman.</p>
<h3>Environnement</h3>
<ul>
<li>le joueur débute dans des WC par la fenêtre desquels il s'est infiltré dans le bâtiment.
Il y a un mannequin dans les cabinets, Helmut Kruger,
qui est malade et ne sortira pas de lui-même des toilettes.</li>
<li>le palais est luxueux et comporte de nombreuses pièces :
cuisines & cave au sous-sol, défilé de mode et réception au rez-de-chaussée,
salons de réceptions dans les étages (y accédder nécessite de passer par une fouille au corps)
avec des portes en trompe-l'oeil et des placards où se cacher...</li>
<li>sous les toits, des combles servent à entreposer les oeuvres en réserve.
Des écrans y sont suspendus dans une section,
pour permettre au superviseur des agents de sécurité de surveiller tout le bâtiment...</li>
</ul>
<h3>Obstacles</h3>
<ul>
<li>en sortant dans le couloir par l'unique porte des WC,
le joueur tombe systématiquement sur un agent de sécurité inquisiteur, Boris,
dans l'angle d'une caméra, qui le <a href="https://fr.wikipedia.org/wiki/Pistolet_%C3%A0_impulsion_%C3%A9lectrique">tasera</a>
à la moindre réponse suspecte (en venir aux mains avec lui déclenchera l'alarme) :</li>
</ul>
<p><strong>1.</strong> « Bonjour Monsieur. Je vois que vous n'avez pas de badge, pouvez-vous me présenter une pièce d'identité svp ? »</p>
<p><strong>1.</strong> <em><small>(avec le badge d'Helmut)</small></em> « Kruger ? Enfin ! Grouille-toi de me filer ma commande. »
<em><small>(de cocaïne)</small></em></p>
<p><strong>2.</strong> « Vous n'êtes pas invité ? <em><small>(sur la défensive)</small></em> Déclinez immédiatement le code opérationnel. »
<em><small>(le code du jour est KOALA, Boris le révelera si le joueur le menace)</small></em></p>
<ul>
<li>Viktor Novikov est au milieu de la réception, et ne la quitte au bout de 10min
que pour se rendre sur scène (au dessus de laquelle pend un immense lustre).
Sa femme, Dalia, est elle sans cesse en mouvement à travers le palais.</li>
<li>si le joueur endosse l'identité d'Helmut,
un des assistants de l'organisation du défilé viendra lui demander de le suivre car c'est bientôt à son tour de monter sur scène</li>
<li>si le joueur a été vu commettre le crime, ses seules sorties possibles sont
par les jardins en sortant par la cave, ou par les toits.
Toutes les autres sorties sont gardées par des agents de sécurité.</li>
<li>il est possible d'attirer Viktor dans « un coin traquile » de bien des manières,
mais il est difficile de rester hors de vue des caméras de surveillance...</li>
</ul>
<h3>Conseils au MJ</h3>
<p>Les joueurs peuvent être frustrés de rester un moment bloqués dans ces fichus WC :
c'est voulu, ce casse-tête faisant doucement monter la pression du temps,
mais s'ils ne sont pas sortis du couloir après 15min, donnez leur quelques pistes et/ou bonus.</p>
<p>Ce scénario peut être partiellement réussi (dès que les joueurs arrivent à abattre Novikov)
ou complètement réussi (si les joueurs arrivent également à s'enfuir).</p>
<h3>Rejouer le scénario</h3>
<p>Au choix :
- contrainte supplémentaire : ne pas vous faire repérer, personne ne doit savoir que vous avez commis ce meurtre
- objectif supplémentaire : éliminer également Dalia Novikov, la véritable chef du réseau d'espions,
agissant dans l'ombre de son mari</p>
<hr>
<p>À nouveau, si vous lisez / testez ces scénarios, je serais ravis de savoir ce que vous en avez pensé !
À la semaine prochaine 😉</p>
<p><strong>EDIT [2020/05/11]</strong> : j'ai terminé le PDF rassemblant tous ces scénarios → <a href="3-derniers-scenarios-pour-run-die-repeat-et-version-pdf">lien vers l'article</a></p>
<style>
.timer {
width: 10rem;
float: right;
}
</style>
<script>
document.querySelectorAll('article img').forEach(img => img.title = img.alt)
</script>2e série de scénarios pour Run Die Repeat2020-04-28T08:00:00+02:002020-04-28T08:00:00+02:00Lucas Cimontag:chezsoi.org,2020-04-28:/lucas/blog/2e-serie-de-scenarios-pour-run-die-repeat.html<p>Dans la continuité des <a href="1ere-fournee-de-scenarios-pour-run-die-repeat.html">scénarios de la semaine dernière</a>,
voici 3 nouveaux scénarios pour le jeu de rôle <em>Run. Die. Repeat</em>,
pour une durée de partie de <strong>30min</strong> plutôt que 20 :</p>
<ol>
<li>Cthulhu fhtagn</li>
<li>Évasion</li>
<li><strong>Labyrinthe</strong></li>
<li><strong>La Grande Guerre</strong></li>
<li><strong>Invasion</strong></li>
<li>La chute du colosse</li>
<li>Hitman</li>
<li>Spec Ops #1 - Infiltration</li>
<li>Spec Ops …</li></ol><p>Dans la continuité des <a href="1ere-fournee-de-scenarios-pour-run-die-repeat.html">scénarios de la semaine dernière</a>,
voici 3 nouveaux scénarios pour le jeu de rôle <em>Run. Die. Repeat</em>,
pour une durée de partie de <strong>30min</strong> plutôt que 20 :</p>
<ol>
<li>Cthulhu fhtagn</li>
<li>Évasion</li>
<li><strong>Labyrinthe</strong></li>
<li><strong>La Grande Guerre</strong></li>
<li><strong>Invasion</strong></li>
<li>La chute du colosse</li>
<li>Hitman</li>
<li>Spec Ops #1 - Infiltration</li>
<li>Spec Ops #2 - Prototype</li>
<li>Spec Ops #3 - REVENGEANCE</li>
</ol>
<p>Mais tout d'abord, en cette période de confinement, parlons un peu de parties de jeu de rôle par Internet.</p>
<p>N'ayant jamais employé de logiciel comme <a href="https://rolisteam.org/">Rolisteam</a> (libre & open source ❤️),
je souhaite surtout évoquer mon expérience récente avec 2 outils bien utiles pour organiser des partie de <em>Run. Die. Repeat</em> :</p>
<ul>
<li>
<p><a href="https://discordapp.com">Discord</a>, l'application de chat et discussion vocale bien connue des joueurs de jeux vidéo, possède un bot <a href="https://top.gg/bot/279722369260453888">DiceParser</a> très pratique pour faire des lancers de dés dans un salon, avec une syntaxe simple et expressive qu'il partage avec Rolisteam</p>
</li>
<li>
<p>alternativement, pour des parties via un autre outil de discussion en ligne comme <a href="https://jitsi.org">Jitsi</a> (employé par <a href="https://framatalk.org">Framatalk</a>), <a href="https://www.mumble.info">Mumble</a> ou encore Skype,
voici une petite application web que j'ai bricolé, pratique pour partager des jets entre participants :
<a href="https://chezsoi.org/lucas/jdr/rpg-dice">rpg-dice</a></p>
</li>
</ul>
<p>Autre outil bien pratique : un décompte de temps en ligne pouvant être partagé entre tous les joueurs.
Lors de mes parties, j'ai employé <a href="https://www.webcountdown.net">webcountdown.net</a>,
avec <a href="https://github.com/Lucas-C/jdr/blob/master/RunDieRepeat/webcountdown.py">un petit script Python</a>
pour générer un lien très rapidement (mentionnez en commentaire si une version web vous intéresserait).</p>
<p>Enfin, voici une petite checklist à passer en revue en début de partie :</p>
<ol>
<li>Tour de table pour que tout le monde se présente brièvement.
En profiter pour lister les noms des joueurs sur papier,
afin de tracer des bâtons en face d'eux durant la partie afin de suivre l'ordre du tour de jeu.</li>
<li>Expliquer le déroulement de la session</li>
<li>Mentionner l'origine du jeu & les règles, en précisant qu'il est prévu pour de l'impro,
mais qu'il s'agit là d'un scénario, ainsi éventuellement que la durée de partie étendue.
Bien expliquer que les actions échoueront / réussiront toujours de la même manière dans les itérations suivantes.<blockquote>
<p>Le jeu fonctionne comme un jeu vidéo "die & retry", mais au lieu de faire preuve de dextérité,
il va vous falloir faire preuve de beaucoup d'imagination, pour tenter à chaque fois une nouvelle approche !</p>
</blockquote>
</li>
<li>En cas de règles spéciales au scenario que les joueurs doivent connaître, les mentionner.
Dire aussi qu'il est toujours possible de passer son tour en cas de manque d'inspiration,
mais que dans le doute, il est toujours plus amusant de tenter quelque chose !</li>
<li>Expliquer le fonctionnement avec rpg-dice / DiceParser (syntaxe <code>!1d6</code>) & webcountdown</li>
<li>Tout le monde a bien compris / des questions ?
Partagez le lien webcountdown et c'est parti !</li>
</ol>
<hr>
<p><img class="timer" alt="Chronomètre de 30min" src="images/jdr/rdr/timer-30.svg"></p>
<p><img alt="Plan du labyrinthe" src="images/jdr/rdr/labyrinthe.png"></p>
<h2>Labyrinthe</h2>
<blockquote>
<p>Tu mets le pied hors du portail magique, dans le labyrinthe.
D'immenses murs de marbre lisse se dressent autour de toi.
La sentence a été claire... tu dois LE vaincre, ou trouver une sortie.
Empruntes-tu le couloir à droite ou à gauche ?</p>
</blockquote>
<h3>Objectif</h3>
<p>Vaincre le Minotaure où s'évader avec les ailes d'Icare.</p>
<h3>Inspirations</h3>
<p>La mythologie grecque et plus en particulier Dédale, Thésée et le Minotaure; les livres de <em>Sword & sorcery</em>.</p>
<h3>Environnement</h3>
<p>Un dédale de couloirs à n'en plus finir, débouchant systématiquement sur des intersections « en T »,
et reliant des pièces octogonales où le danger rôde.</p>
<h3>Obstacles</h3>
<p>Les salles rencontrées, dans l'ordre :</p>
<ol>
<li>une salle vide dont les dalles au sol s'effondrent sur une fosse à pieux.
Un 1er chemin court permet de la traverser, un 2e, plus long, permet d'accéder au coffre au centre de la salle,
contenant un bouclier poli (<strong>+1</strong> au dé en combat, <strong>+3</strong> contre Méduse).
De l'autre coté de la pièce, une intersection en croix mène à trois autres salles...</li>
<li>un portail nécessitant une clef bloque l'accès à la pièce.
Derrière, un trône de dos, d'où dépasse une paire de cornes.
C'est l'antre du Minotaure ! Il faut obtenir <strong>7 ou plus au dé</strong> pour le vaincre.</li>
<li>les murs des parois deviennent tellement polis qu'ils se transforment en miroir,
transformant cette partie du labyrinthe en galerie des glaces.
Elle comporte un coffre caché, contenant une épée enchantée,
capable d'aspirer les âmes (comme <a href="https://fr.wikipedia.org/wiki/Stormbringer_(%C3%A9p%C3%A9e) -
**+2** au dé en combat, **+1** par monstre déjà vaincu avec"><em>Stormbringer</em></a>.</li>
<li>Une grotte jonchée d'ossements, abritant le repaire de Chimère, une créature mi-lion mi-dragon.
Elle est initialement assoupie contre un coffre contenant une clef attachée à une pelote de laine
(<strong>+2</strong> pour se repérer dans la galerie des miroirs).</li>
<li>Méduse la Gorgone, paisiblement assise de dos contre une fontaine.
Au-fond de celle-ci repose, quasi imperceptible, une cape rendant invisible
(<strong>+2</strong> au dé pour esquiver les monstres).</li>
<li>une pièce a priori vide, en réalité un trompe-l’œil dissimulant un coffre
renfermant les ailes d'Icare, qui permettent de planer (mais pas de s'envoler).</li>
<li>un cul-de-sac donnant sur un puits avec inscrit « Icare » sur la marelle,
au fond duquel on débouche à 100m <strong>au-dessus</strong> du labyrinthe.</li>
</ol>
<h3>Conseils au MJ</h3>
<p>Essayez de perdre les joueurs en leur proposant à chaque fois un choix "droite / gauche" à mémoriser laborieusement (avec toujours la possibilité de revenir en arrière à une intersection).
Après une ou deux tentatives infructueuses contre le minotaure, <strong>révélez aux joueurs le score minimum nécessaire</strong>.</p>
<h3>Rejouer le scénario</h3>
<p>Le Minotaure nécessite <strong>9</strong> pour être vaincu, et pourchasse les joueurs dès leur arrivée dans le labyrinthe,
les mettant sous pression en ne leur accordant aucun répit dans leur fuite !</p>
<hr>
<p><img class="timer" alt="Chronomètre de 30min" src="images/jdr/rdr/timer-30.svg"></p>
<p><img alt="Illustration des tranchées" src="images/jdr/rdr/TrenchWarfare.svg"></p>
<p><a href="https://freesvg.org/trench-warfare">Trench Warfare</a> - domaine public</p>
<h2>La Grande Guerre</h2>
<blockquote>
<p>Vous vous réveillez sur une couchette d'un lit superposé, dans une pièce souterraine sombre.
À tâtons vos doigts vous révèlent que vous portez un uniforme de soldat, et un bandage à la tête.
À l'extérieur, des explosions font trembler la terre.</p>
</blockquote>
<h3>Objectif</h3>
<p>Atteindre le campement du général en traversant une zone extrêmement dangereuse.</p>
<h3>Règles spéciales</h3>
<p>Chaque fois que le joueur décède, il laisse un cadavre qui reste présent dans les itérations suivantes,
et se réincarne dans un autre soldat précédemment assoupi. Le temps défile en continu, il n'y a pas de retour en arrière,
mais pour autant les règles ne changent pas : une action réussie une fois réussira toujours.</p>
<h3>Environnement</h3>
<ul>
<li>à l'entrée de la cagna, un homme agonise :
« C'est la fin pour moi, c'est foutu. Écoute-moi : cette missive est pour le général, de l'autre côté du fleuve.
Bon dieu, c'est la fin de l'enfer, c'est l'armistice ! Mais il ne le sait pas, et il va lancer une offensive finale à midi...
Il faut à tout prix empêcher le carnage. »</li>
<li>en cherchant un peu, la cagna contient du matériel : gourde, couverture, rations, casque, masque à gaz, fusil</li>
<li>une épave de zeppelin, en feu, ainsi que d'autres véhicules gisent autour, plus ou moins ou fonctionnels : jeep, moto...</li>
</ul>
<h3>Obstacles</h3>
<ul>
<li>l'obscurité initiale de la pièce, nécessitant de sortir à tâtons</li>
<li>une ogive tombe et explose devant le joueur</li>
<li>du gaz moutarde stagne dans une section des tranchées</li>
<li>une tranchée est pleine d'eau</li>
<li>un espace à découvert à franchir, miné, sous le feu d'une mitrailleuse</li>
<li>enfin, un pont au milieu duquel se dresse un barrage militaire</li>
</ul>
<h3>Conseils au MJ</h3>
<p>Lisez lentement la liste de matériel dans la cagna pour simuler la durée de la fouille.
Donnez des bonus aux jets des joueurs faisant preuve d'imagination, surtout s'ils exploitent leurs précédents cadavres.</p>
<h3>Rejouer le scénario</h3>
<p>Prolongez-le : le campement du général est plus éloigné, rendant nécessaire un véhicule pour l'atteindre;
un avion s'écrase du ciel ; un sniper dans un village en ruines ; des chiens pourchassent le joueur.</p>
<hr>
<p><img class="timer" alt="Chronomètre de 30min" src="images/jdr/rdr/timer-30.svg"></p>
<p><img alt="Le Harnais" src="images/jdr/rdr/plasma_gauntlet_by_suldae_d4623vd.jpg"></p>
<p><a href="https://www.deviantart.com/suldae/art/Plasma-Gauntlet-252040729">Plasma Gauntlet by suldae</a> - CC BY-NC-SA 3.0</p>
<h2>Invasion</h2>
<blockquote>
<p>ALERTE ROUGE ! Des aliens attaquent la planète,
et leur énorme vaisseau dans le ciel est en train de préparer un gigantesque tir de canon laser !
Scientifique de génie travaillant en secret dans un laboratoire secret caché dans la montagne,
vous enfilez votre harnais <em>jetpack</em> / machine à voyager dans le temps pour aller leur botter les fesses.</p>
</blockquote>
<h3>Objectif</h3>
<p>Détruire le vaisseau alien.</p>
<h3>Inspirations</h3>
<p>Les films <em>Independence Day</em> & <em>Edge of Tomorrow</em>,
les jeux vidéo <em>Half Life</em> & <em>Portal</em>.</p>
<h3>Règles spéciales</h3>
<p>Le harnais permet de revenir dans le temps à volonté (20s plus tôt par exemple),
<strong>et</strong> ramène à l'instant où il est harnaché en cas de décès de son porteur.</p>
<p>Chaque fois qu'un joueur obtient un ~~⚀~~, un nouvel alien, ayant détecté la manipulation temporelle,
s'interpose sur le chemin du joueur, de plus en plus proche de l'endroit où il recommence, et mieux armés.</p>
<h3>Environnement</h3>
<ul>
<li>un complexe souterrain rempli d'expérimentations technologiques :
prototype de vaisseau volant, <a href="https://theportalwiki.com/wiki/Handheld_Portal_Device/fr"><em>portal gun</em></a>,
golem constitué de nanorobots...</li>
<li>un labyrinthe de canyons pour atteindre le centre du gigantesque vaisseau alien circulaire qui obstrue le ciel</li>
<li>un vaisseau spatial titanesque, mi organique mi technologique, ou des insectes géants côtoient des <a href="https://fr.wikipedia.org/wiki/Mecha">mechas</a> extra-terrestres</li>
</ul>
<h3>Obstacles</h3>
<ul>
<li>dans les canyons, des vaisseaux aliens pourchassent et canardent le joueur</li>
<li>une fois dans le vaisseau alien, des tourelles mitraillent déjà une flotte d'avions de chasse de l'armée</li>
<li>le harnais du joueur lui indique qu'il a localisé le point faible du vaisseau :
une salle contenant l'ordinateur central. Il faut pour y accéder s'aventurer dans un dédale de coursives plein de guerriers aliens...</li>
</ul>
<h3>Conseils au MJ</h3>
<p>Limitez le nombre d'obstacles si les joueurs obtiennent trop de ~~⚀~~.
Selon la vitesse à laquelle les joueurs atteignent le vaisseau alien, révélez plus ou moins facilement / vite
son point faible critique qui fera tout exploser.
Octroyez des bonus aux joueurs qui tentent des construire ou trouver des inventions dans le labo,
ainsi qu'à ceux qui emploient la capacité à revenir dans le passé du harnais de manière ingénieuse.</p>
<h3>Rejouer le scénario</h3>
<p>Cette fois, il faut également éliminer la reine alien leur permettant à eux de remonter dans le temps.
Une fois découverte, elle tentera aux itérations suivantes de fuir en petit vaisseau pour se cacher sur la surface de la planète.</p>
<hr>
<p>À nouveau, si vous lisez / testez ces scénarios, je serais ravis de savoir ce que vous en avez pensé !
À la semaine prochaine 😉</p>
<p><strong>EDIT [2020/05/11]</strong> : j'ai terminé le PDF rassemblant tous ces scénarios → <a href="3-derniers-scenarios-pour-run-die-repeat-et-version-pdf">lien vers l'article</a></p>
<style>
.timer {
width: 10rem;
float: right;
}
</style>
<script>
document.querySelectorAll('article img').forEach(img => img.title = img.alt)
</script>Lancement d'un Tipeee pour Mush Radio2020-04-27T17:00:00+02:002020-04-27T17:00:00+02:00Lucas Cimontag:chezsoi.org,2020-04-27:/lucas/blog/lancement-d-un-tipeee-pour-mush-radio.html<p>Aujourd’hui je suis très fier de vous annoncer un projet sur lequel nous travaillons depuis quelques semaines avec un ami :</p>
<p><a href="https://fr.tipeee.com/mush-radio"><img alt="Bannière Mush Radio" src="images/2020/04/MushRadio.jpg"></a></p>
<blockquote>
<p>Bip. Bip-bip-bip. Biiiiiip.</p>
<p>« Un message ! Opérateur, vite ! Nous recevons un message sur la radio ! »</p>
<p>« Je me charge de la transcription. Alors… COLONIE FONGIQUE EN B3. DEMANDE SOUTIEN AÉRIEN.. »</p>
<p>« Très …</p></blockquote><p>Aujourd’hui je suis très fier de vous annoncer un projet sur lequel nous travaillons depuis quelques semaines avec un ami :</p>
<p><a href="https://fr.tipeee.com/mush-radio"><img alt="Bannière Mush Radio" src="images/2020/04/MushRadio.jpg"></a></p>
<blockquote>
<p>Bip. Bip-bip-bip. Biiiiiip.</p>
<p>« Un message ! Opérateur, vite ! Nous recevons un message sur la radio ! »</p>
<p>« Je me charge de la transcription. Alors… COLONIE FONGIQUE EN B3. DEMANDE SOUTIEN AÉRIEN.. »</p>
<p>« Très bien. Préparez tout de suite la catapulte ! Orientez-la vers le parc sur l’autre rive. Pendant ce temps, je prends une escouade avec moi pour aller fouiller l’hôpital. Mort au Fongus ! »</p>
<p>« MORT AU FONGUS ! »</p>
</blockquote>
<p>Il s’agit d’un court jeu de société coopératif et tactique, pour 2 joueurs.
Chacun protège sa moitié de la ville contre une redoutable invasion fongique !</p>
<p>En bref, c’est une sorte de bataille navale coopérative en plus fun, avec des champis. Le jeu tient en 2 pages, ne nécessite aucun pion, juste un crayon, et est jouable par téléphone.</p>
<p><img alt="Illustration d'un personnage infecté" src="images/2020/04/planche-preparatoire-city-map.jpg"></p>
<p>Le jeu est actuellement en développement intensif, et notre objectif est de sortir une version jouable avant la fin du confinement. Elliot travaille en ce moment à finaliser la carte du jeu, tandis que Lucas peaufine & playteste les mécaniques de jeu.</p>
<p>Nous venons de mettre en place une page Tipeee pour soutenir le projet si l’idée vous plait !</p>
<p><a href="https://fr.tipeee.com/mush-radio">https://fr.tipeee.com/mush-radio</a></p>
<p><img alt="Portraits des 2 auteurs" src="images/2020/04/Elliot-et-Lucas.png"></p>1ère fournée de scénarios pour Run Die Repeat2020-04-20T23:45:00+02:002020-04-20T23:45:00+02:00Lucas Cimontag:chezsoi.org,2020-04-20:/lucas/blog/1ere-fournee-de-scenarios-pour-run-die-repeat.html<p><a href="run-die-repeat.html">En janvier dernier</a>, j'évoquais un jeu de rôle génial que j'avais découvert,
et traduit dans la foulée : <em><strong>Run. Die. Repeat</strong></em></p>
<p>Très inspiré par le principe du jeu, j'avais alors conclu cet article de présentation
d'une foultitude de pistes de scénarios...</p>
<p>Depuis, de l'eau à coulé sous les ponts et nous …</p><p><a href="run-die-repeat.html">En janvier dernier</a>, j'évoquais un jeu de rôle génial que j'avais découvert,
et traduit dans la foulée : <em><strong>Run. Die. Repeat</strong></em></p>
<p>Très inspiré par le principe du jeu, j'avais alors conclu cet article de présentation
d'une foultitude de pistes de scénarios...</p>
<p>Depuis, de l'eau à coulé sous les ponts et nous sommes tous confinés.
<a href="https://anchor.fm/rolisterie/episodes/S06E01%E2%80%94Les-argouliens-attaquent%E2%80%94Run%E2%80%93Die%E2%80%93Repeat-earf0c">La Rôlisterie a enregistré un podcast sur le jeu</a>,
et je me suis lancé dans la rédaction d'une dizaine de scénarios :</p>
<ol>
<li><strong>Cthulhu fhtagn</strong></li>
<li><strong>Évasion</strong></li>
<li>Labyrinthe</li>
<li>La Grande Guerre</li>
<li>Invasion</li>
<li>La chute du colosse</li>
<li>Hitman</li>
<li>Spec Ops #1 - Infiltration</li>
<li>Spec Ops #2 - Prototype</li>
<li>Spec Ops #3 - REVENGEANCE</li>
</ol>
<p>J'ai eu l'occasion de les tester tous au moins une fois,
donc quatre lors de la <a href="http://www.cyberconv1.com">Cyber Conv</a> le dimanche 5 avril dernier,
et à chaque fois les retours étaient très enthousiastes !</p>
<p>Comme je n'arrête pas de revenir sur ces scénarios pour les peaufiner,
et que je procrastine sur la mise en page PDF,
j'ai décidé que j'allais les publier petit à petit sur ce blog,
en quatre articles espacés d'une semaine.
L'objectif étant de me motiver à finir ce recueil de scénarios,
et de publier à terme le PDF sur <a href="https://lucas-c.itch.io">itch.io</a>.</p>
<p>Voici donc les 2 premiers ci-dessous, mais <strong>ATTENTION aux spoilers</strong>,
car les lire signifie ne plus pouvoir y jouer...</p>
<p>J'en profite pour mentionner que j'organiserai d'autres partie sur Discord dans les semaines à venir.
Je proposerai un sondage ici et sur <a href="https://discord.gg/BJeuEtX">ce salon Discord</a> pour que les joueurs intéressés s'inscrivent.</p>
<p>Si vous lisez / testez ces scénarios, donnez votre avis en commentaire !
La semaine prochaine, j'évoquerai également quelques conseils pour faire jouer ces scénarios à distance, en ligne.</p>
<hr>
<p><img alt="A Bottomless Sea by SunnyClockwork" src="images/jdr/rdr/a_bottomless_sea_by_sunnyclockwork_daot7gy-fullview.jpg"></p>
<p><a href="https://www.deviantart.com/sunnyclockwork/art/A-Bottomless-Sea-646335250">A Bottomless Sea by SunnyClockwork</a> - CC BY-SA 3.0</p>
<h2>Cthulhu fhtagn</h2>
<blockquote>
<p>La lumière blafarde de la pleine lune te réveilles.
Tu ouvres les yeux dans un immeuble en construction, le long d'une falaise.
Loin, très loin, tu distingues une énorme vague s'approchant sur la mer.
Sur le mur devant toi, une inscription en lettres de sang :
« empêchez le grand prêtre de le réveiller ».
Au pied du bâtiment, tu aperçois une procession psalmodiant une litanie lugubre qui se dirige vers un phare...</p>
</blockquote>
<h3>Objectif</h3>
<p>Mettre fin au rituel avant qu'il ne soit trop tard !</p>
<h3>Inspirations</h3>
<p>L'œuvre de H. P. Lovecraft et toutes celles qu'il a inspiré.</p>
<h3>Environnement</h3>
<ul>
<li>une procession d'une cinquantaine d'individus, tantôt comme hypnotisés, tantôt fanatiques.
Le grand prêtre à leur tête transporte une corne ornée d'inscriptions mystiques,
qu'ils transportent vers la mer.</li>
<li>une grue de chantier</li>
<li>une route en bord de falaise escarpée</li>
<li>une voiture décapotable des années 20 dans une grange</li>
</ul>
<h3>Obstacles</h3>
<ul>
<li>des chiens lâchés à tes trousses</li>
<li>des hommes armés de fusils encadrant la procession</li>
<li>une large faille se créée dans le sol au milieu de la route</li>
<li>au phare, faut-il descendre / monter les escaliers ?</li>
<li>des <a href="https://fr.wikipedia.org/wiki/Ceux_des_profondeurs">Profonds</a></li>
</ul>
<h3>Conseils au MJ</h3>
<p>A chaque nouvelle tentative, indiquez aux joueurs que le tsunami se rapproche.</p>
<p>Dosez les obstacles pour créer une longue chaîne d'actions à mémoriser,
et faites en sorte que les joueurs atteignent le phare dans les 2 dernières minutes</p>
<h3>Rejouer le scénario</h3>
<p>Commencez au moment où vous saisissez la conque au grand prêtre :
vous devez échappez à la foule de cultistes à vos trousses, et réussir à le détruire à temps !</p>
<hr>
<p><img alt="Libéré de ses menottes" src="images/jdr/rdr/handcuffs-free.jpg"></p>
<h2>Évasion</h2>
<blockquote>
<p>Tu es en cellule au commissariat, tu viens d'être arrêté pour un crime où tu risques perpétuité.
Soudain, un paquet kraft atterrit à tes pieds, lancé de l'extérieur à travers une lucarne.
Dedans, tu trouves une machine avec <a href="images/jdr/rdr/convecteur-temporel.jpg">un étrange mécanisme</a>
incluant un compte à rebours de 20min, ainsi que ce mot : « Fait sortir Loubianov par l'arrière-court »</p>
</blockquote>
<h3>Objectif</h3>
<p>Trouver un moyen de s'évader avec Loubianov qui n'est pas au courant du plan !</p>
<h3>Inspirations</h3>
<p>Toutes les scènes de film dans des commissariats, Prison Break, le jeu vidéo Hitman...</p>
<h3>Environnement</h3>
<ul>
<li>une cellule à barreaux, avec couchette et WC</li>
<li>le commissariat s'étale sur deux étages, avec un espace central ouvert sur deux niveaux, en <em>open space</em> de bureaux</li>
<li>au RdC : accueil, bureaux partagés, salles de déposition, cellules, réfectoire...</li>
<li>à l'étage : salles d'interrogatoire, bureaux des gradés, salle de réunion, armurerie, WC...</li>
</ul>
<h3>Obstacles</h3>
<ul>
<li>la porte de la cellule</li>
<li>se déplacer dans un lieu plein de flics</li>
<li>trouver Loubianov et le libérer</li>
<li>s'enfuir</li>
</ul>
<h3>Conseils au MJ</h3>
<p>En cas de mauvais jets, il peut être laborieux et frustrant pour les joueurs se s'échapper initialement de la cellule.
Si vous approchez des 10min de partie et que les joueurs n'en sont pas sortis, indiquez que cette fois
le paquet kraft contient une arme (couteau ou revolver).</p>
<h3>Rejouer le scénario</h3>
<p>Cette fois, Loubianov ne sera absolument pas coopératif !</p>
<hr>
<p><strong>EDIT [2020/05/11]</strong> : j'ai terminé le PDF rassemblant tous ces scénarios → <a href="3-derniers-scenarios-pour-run-die-repeat-et-version-pdf">lien vers l'article</a></p>Faire découvrir le JdR et Fête du jeu2020-03-16T14:30:00+01:002020-03-16T14:30:00+01:00Lucas Cimontag:chezsoi.org,2020-03-16:/lucas/blog/faire-decouvrir-le-jdr-et-fete-du-jeu.html<p>Il y a une semaine, j'ai eu la chance d'être invité le samedi à <a href="https://www.les-garennes-sur-loire.fr/%C3%A9v%C3%A8nement/fete-du-jeu/?instance_id=456">la Fête du jeu</a>
à Saint Jean-des-Mauvrets, pour y faire découvrir le <strong>jeu de rôle</strong> aux joueurs curieux.</p>
<p>C'était une très belle après-midi !
Félicitations aux animateurs et aux jeunes des Garennes sur Loire qui ont animé …</p><p>Il y a une semaine, j'ai eu la chance d'être invité le samedi à <a href="https://www.les-garennes-sur-loire.fr/%C3%A9v%C3%A8nement/fete-du-jeu/?instance_id=456">la Fête du jeu</a>
à Saint Jean-des-Mauvrets, pour y faire découvrir le <strong>jeu de rôle</strong> aux joueurs curieux.</p>
<p>C'était une très belle après-midi !
Félicitations aux animateurs et aux jeunes des Garennes sur Loire qui ont animé l'événement,
où de nombreuses personne sont venues jouer en famille,
et déguster de délicieux gâteaux préparés pour l'occasion 😋</p>
<p>De mon côté, comme c'était la première fois que je participais à un festival de jeux de ce type,
je m'étais un peu préparé pour l'événement en amont.</p>
<p>Dans cet article, je vais présenter brièvement quel <strong>jeu de démonstration</strong> j'avais prévu,
et quelles <strong>documents explicatifs</strong> j'ai amené pour l'occasion.</p>
<hr>
<p>Lors de cette Fête du Jeu, comme dans beaucoup d'événements de ce type autour du jeu de société,
des tables et des jeux étaient en libre accès à disposition des visiteurs.
Des animateurs proposaient également d'expliquer les règles jeux aux gens.</p>
<p>Pour rester dans le même esprit et le même format, je voulais également proposer aux gens de
<strong>tester une partie de jeu de rôle en 30min-45min</strong>,
une durée proche de celle d'un <a href="/lucas/blog/tag/jeu-de-societe.html">jeu de société</a> "plus classique".</p>
<p>En faisant le tour de <a href="pages/jdr-favoris.html">mes JdR favoris</a>,
seuls <a href="run-die-repeat.html">Run. Die. Repeat</a> ou une partie de <a href="espace-profond-et-sanglant.html">Sombre Light</a> auraient pu convenir au format,
mais je trouvais le premier trop éloigné du format "classique" d'une partie de JdR,
et le second un peu ardu pour des débutants.</p>
<p>Je suis alors tombé sur <a href="http://legrumph.org/Terrier/?Jeux-de-role/Sventovia">Sventovia</a> du Grümph,
qui m'a beaucoup plu dans sa démarche d'épurer les mécaniques d'un jeu de rôle pour les rendre accessible.
Cependant, il m'a finalement semblé trop axé sur le combat et surtout trop complexe en termes de règles
(manoeuvres, compétence BATAILLE, points de chance peu expliqués, trop de magie...).</p>
<p>C'est alors que j'ai découvert le court essai de Kalwrynn :
<a href="http://www.lulu.com/shop/kalwrynn/mener-en-30mn/ebook/product-24254652.html">Mener en 30mn</a>.
Dans ce PDF de 8 pages, l'autrice expose très clairement la problématique,
fourni quelques excellents conseils d'ordre général, puis propose un scénario très simple,
où les joueurs viennent en aide à un petit village de pêcheurs dans un japon médiéval-fantastique.</p>
<p>J'ai alors décidé de piquer une super idée issue <em>Sventovia</em> pour la combiner avec ce scénario :
<strong>fournir aux joueurs des illustrations</strong>, pour leurs personnages,
leur matériel ou encore les PNJs.</p>
<p>Voici le résultat, conçu exclusivement avec des illustrations <em>Creative Commons</em> :</p>
<h2>Ori Mushi</h2>
<ul>
<li><a href="https://lucas-c.github.io/jdr/OriMushi/">version web</a></li>
<li>feuille de personnage : <a href="https://lucas-c.github.io/jdr/OriMushi/OriMushi-FeuillePersonnage.pdf">OriMushi-FeuillePersonnage.pdf</a></li>
<li>version PDF des règles, incluant quelques illustrations: <a href="https://github.com/Lucas-C/jdr/releases/download/ori-mushi-v0.2/ori-mushi-v0.2.zip">ori-mushi-v0.2.zip <em>(10 pages, 5,5 Mo)</em></a></li>
</ul>
<p><a href="images/2020/03/FeteDuJeu_OriMushi_vueMJ.jpg"><img alt="Photo de la table de jeu à la Fête du jeu, côté MJ" src="images/2020/03/FeteDuJeu_OriMushi_vueMJ.jpg"></a></p>
<p><a href="images/2020/03/FeteDuJeu_OriMushi_FPetIllustrations.jpg"><img alt="Photo de la table de jeu à la Fête du jeu, côté joueurs" src="images/2020/03/FeteDuJeu_OriMushi_FPetIllustrations.jpg"></a></p>
<p>Durant la Fête du Jeu, je n'ai eu l'occasion de tester ce petit scénario qu'avec un petit groupe de 4 adolescents.
Mais ils m'ont semblé plutôt contents de la partie,
et pour ma part je compte bien le réemployer à l'avenir !
Un grand merci à Kalwrynn et à son essai, que je recommande vivement.</p>
<p>Au passage, ce vaste sujet de « la partie de découverte de jeu de rôle »
a déjà été évoqué par beaucoup de monde.
Dans mes recherches, je suis en particulier tombé sur deux articles dont je recommande la lecture :
<a href="https://ptgptb.fr/jdr-pour-debutants-le-manifeste">JdR pour débutants - le manifeste</a> de Paul Beakley (1999), traduit sur ptgptb.fr,
<a href="http://blog.xyrop.com/post/2018/01/22/%5BTactique-de-ma%C3%AEtrise%5D-Mener-une-partie-de-2-heures-maximum-en-convention-ou-salon">Mener une partie en convention</a> de Ludovic Schurr
et <a href="https://www.500nuancesdegeek.fr/initier-au-jdr/">Pourquoi il faut arrêter « d’initier » au jeu de rôle</a> de MaitreSinh.</p>
<hr>
<p>Autre petite problématique que je me suis posé pour cette Fête du Jeu :
<strong>comment expliquer en quoi ça consiste le jeu de rôle ?</strong></p>
<p>Au-delà des explications orales,
je souhaitais avoir un petit support visuel,
expliquant en quelques mots en quoi consiste cet étrange loisir...</p>
<p>J'ai donc conçu ces quelques "plaquettes" de présentation au format A4 :</p>
<div class="side-by-side">
<a href="images/jdr/PlaquettePresentationJdR.pdf">
<figure>
<img alt="" src="images/2020/03/PlaquetteMiniature-pdf.png">
<figcaption>Qu'est-ce que le JdR ?<br>(PDF 5 pages 2.5Mo)</figcaption>
</figure>
</a>
<a href="images/jdr/PlaquettePresentationJdR.odg">
<figure>
<img alt="" src="images/2020/03/PlaquetteMiniature-odg.png">
<figcaption>Qu'est-ce que le JdR ?<br>(Fichier source LibreOffice Draw 4.4Mo)</figcaption>
</figure>
</a>
</div>
<p><a href="images/2020/03/FeteDuJeu_jeuxEtExplications.jpg"><img alt="Photo de la table où étaient exposées ces plaquettes à la Fête du Jeu" src="images/2020/03/FeteDuJeu_jeuxEtExplications.jpg"></a></p>
<p>Le lendemain même du soir où j'ai réalisé ces plaquettes,
je découvrais <a href="http://www.ffjdr.org/ce-devez-savoir-jeu-role/definitions-du-jeu-role/#TelechargerLaPlaquette">celles de la Fédération Française de Jeu de Rôle</a>.</p>
<p>Elle sont bien plus complètes !
Mais en conséquence également un peu moins lisibles pour du A4 je trouve :</p>
<div class="side-by-side">
<a href="images/jdr/ffjdr/services-plaquettejdr-0-0.png">
<img alt="Plaquette n°1" src="images/jdr/ffjdr/services-plaquettejdr-0-0-miniature.png">
</a>
<a href="images/jdr/ffjdr/services-plaquettejdr-0-1.png">
<img alt="Plaquette n°2" src="images/jdr/ffjdr/services-plaquettejdr-0-1-miniature.png">
</a>
<a href="images/jdr/ffjdr/services-plaquettejdr-0-2.png">
<img alt="Plaquette n°3" src="images/jdr/ffjdr/services-plaquettejdr-0-2-miniature.png">
</a>
<a href="images/jdr/ffjdr/services-plaquettejdr-1-0.png">
<img alt="Plaquette n°4" src="images/jdr/ffjdr/services-plaquettejdr-1-0-miniature.png">
</a>
<a href="images/jdr/ffjdr/services-plaquettejdr-1-1.png">
<img alt="Plaquette n°5" src="images/jdr/ffjdr/services-plaquettejdr-1-1-miniature.png">
</a>
<a href="images/jdr/ffjdr/services-plaquettejdr-1-2.png">
<img alt="Plaquette n°6" src="images/jdr/ffjdr/services-plaquettejdr-1-2-miniature.png">
</a>
</div>
<p>Comme les fichiers PDF fournis par la FFJdR n'étaient pas imprimables simplement,
j'en ai extrait les images, une par page. Vous pouvez y accéder en cliquant sur les miniatures ci-dessus.</p>
<hr>
<p>En définitive, lors de la Fête du jeu, assez peu de curieux se sont intéressés à ces drôles de jeux de rôle :
une dizaine de personnes environ sur une centaine de visiteurs, qui étaient essentiellement des familles avec jeunes enfants.</p>
<p>Je suis tout même très satisfait d'y avoir participé :
les gens étaient adorables et les gâteaux délicieux,
j'ai eu le droit à <a href="https://www.ouest-france.fr/pays-de-la-loire/saint-jean-des-mauvrets-49320/des-jeux-en-hiver-pour-un-sejour-en-ete-c7adf9b8-6c84-4fa0-ab1c-293d34ccd7d4">une petite mention dans le Courrier de l'Ouest</a>
et j'ai pu « mettre le pied à l'étrier » pour d'autres événements futurs !</p>
<p>En effet, avec l'association mathurinoise <a href="https://laubergedesreveurs.forumactif.com">L'Auberge des Rêveurs</a> qui vient de se créer,
nous comptons d'ailleurs organiser des événements cette année pour faire découvrir ce loisir au plus grand monde.</p>
<p>Enfin, si vous cherchez des idées de jeux pour débuter ou pour des débutants, j'ai écrit <a href="quelques-suggestions-pour-debuter-en-jdr-gratuites-et-en-francais.html">un article sur le sujet</a>,
que j'essaie de maintenir à jour.</p>
<style>
article img { max-height: 40rem; /* override theme value */ }
hr { margin: 5rem; }
@font-face {
font-family: Odachi;
src: url('https://lucas-c.github.io/jdr/OriMushi/fonts/Odachi.otf');
}
article h2 {
font-family: Odachi;
font-size: 8rem;
height: 10rem;
line-height: 8rem;
text-align: center;
}
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
flex-flow: wrap;
}
.side-by-side > * { flex: 1 0; }
</style>Mes jeux de rôle favoris2020-03-06T23:00:00+01:002020-03-06T23:00:00+01:00Lucas Cimontag:chezsoi.org,2020-03-06:/lucas/blog/mes-jeux-de-role-favoris.html<p>Juste une petite bafouille pour mentionner l'apparition d'une nouvelle page sur ce blog,
détaillant <a href="pages/jdr-favoris.html">mes jeux de rôle préférés</a>.</p>Les Non-Morts2020-03-02T12:00:00+01:002020-03-02T12:00:00+01:00Lucas Cimontag:chezsoi.org,2020-03-02:/lucas/blog/les-non-morts.html<p>Ce billet est l'occasion pour moi de vous présenter un jeu que j'ai créé il y a 10 ans :</p>
<p><a href="https://lucas-c.github.io/jdr/LesNonMorts/cover_ilustration_to_a_comic_zombie_collection_by_danyaellopes_dcftr2d-pre.jpg"><img alt="Couverture du jeu : un motard zombie" src="https://lucas-c.github.io/jdr/LesNonMorts/cover_ilustration_to_a_comic_zombie_collection_by_danyaellopes_dcftr2d-pre.jpg"></a></p>
<blockquote>
<p><em>Les Non-morts</em> est un court jeu compétitif à mi-chemin entre <a href="/lucas/blog/tag/jeu-de-societe.html">jeu de société</a>
et jeu de rôle sans meneur, dans une ambiance de film de mort-vivants de <a href="https://fr.wikipedia.org/wiki/S%C3%A9rie_Z">série Z</a> :
un navet …</p></blockquote><p>Ce billet est l'occasion pour moi de vous présenter un jeu que j'ai créé il y a 10 ans :</p>
<p><a href="https://lucas-c.github.io/jdr/LesNonMorts/cover_ilustration_to_a_comic_zombie_collection_by_danyaellopes_dcftr2d-pre.jpg"><img alt="Couverture du jeu : un motard zombie" src="https://lucas-c.github.io/jdr/LesNonMorts/cover_ilustration_to_a_comic_zombie_collection_by_danyaellopes_dcftr2d-pre.jpg"></a></p>
<blockquote>
<p><em>Les Non-morts</em> est un court jeu compétitif à mi-chemin entre <a href="/lucas/blog/tag/jeu-de-societe.html">jeu de société</a>
et jeu de rôle sans meneur, dans une ambiance de film de mort-vivants de <a href="https://fr.wikipedia.org/wiki/S%C3%A9rie_Z">série Z</a> :
un navet incohérent, bourré de faux raccords et de mauvais effets spéciaux.</p>
<p>Les joueurs incarnent les réalisateurs / scénaristes d'un tel film, qui se sont succédés à la production, chaotique.
Au cours de la partie, ils vont dessiner collectivement le plan d'un lieu,
et décrire les actions d'une poignée de survivants de la catastrophe en cours,
tentant de survivre à une marée de mort-vivants !</p>
</blockquote>
<p>J'ai déterré un vieux prototype en début d'année,
et après une partie de test réussie, voici une première version améliorée, clarifiée et mise en page !</p>
<figure>
<a href="images/2020/03/playtest2-notes.jpg">
<img alt="" src="images/2020/03/playtest2-notes.jpg">
</a>
<figcaption>Le matériel de jeu, avec les règles annotées</figcaption>
</figure>
<p>Voici quelques décisions de conception prises suite à ce <em>playtest</em> :</p>
<ul>
<li>la capacité du <em>goth</em> ne meurt plus s'il est isolé, mais devient <strong>immobilisé</strong></li>
<li>par souci de simplification, le coup d'ajout d'une sortie et de la capacité du <em>geek</em> deviennent fixes : 4 jetons</li>
<li>s'il joue les Non-Morts, un joueur peut désormais réaliser autant d'actions de déplacement / ajout qu'il le souhaite,
tant que leur total fait 5</li>
</ul>
<p>J'ai également tenté de clarifier les règles et expliquant plus en détails certains termes du jeu,
le rôle des différents pions, et comment procéder à certaines actions comme le dessin d'une nouvelle pièce.
Deux cartes récapitulatives des règles et plusieurs pictogrammes représentant les différentes actions ont aussi été ajoutés.</p>
<p>Si vous êtes curieux de voir à quoi ressemble le jeu, jetez un œil aux liens ci-dessous !</p>
<div class="side-by-side">
<a href="https://github.com/Lucas-C/jdr/releases/download/LesNonMorts-v1.0/LesNonMorts-v1.0.pdf">
<figure>
<img alt="" src="images/2020/03/LesNonMorts-pdf.jpg">
<figcaption><em>Les Non-morts</em><br>(PDF 12 pages 1,3 Mo)</figcaption>
</figure>
</a>
<a href="https://lucas-c.github.io/jdr/LesNonMorts/">
<figure>
<img alt="" src="images/2020/03/LesNonMorts-web.jpg">
<figcaption><em>Les Non-morts</em><br>(web)</figcaption>
</figure>
</a>
</div>
<p>J'ai également mis le jeu <a href="https://lucas-c.itch.io/les-non-morts">en ligne sur itch.io</a>.</p>
<p>Si vous le lisez / testez, je serais sincèrement ravi d'avoir vos avis sur le jeu dans les commentaires !</p>
<figure>
<a href="images/2020/03/playtest2-notes.jpg">
<img alt="" src="images/2020/03/playtest2-map.jpg">
</a>
<figcaption>La carte des tunnels de métro de notre partie</figcaption>
</figure>
<style>
@font-face {
font-family: Shlop;
src: url('https://lucas-c.github.io/jdr/LesNonMorts/shlop rg.ttf');
}
h1 {
font-family: Shlop;
font-size: 8rem !important;
text-align: center;
}
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
flex-flow: wrap;
}
.side-by-side > * { flex: 1 0; }
</style>AVGD, nouvelle mouture !2020-02-19T09:00:00+01:002020-02-19T09:00:00+01:00Lucas Cimontag:chezsoi.org,2020-02-19:/lucas/blog/avgd-nouvelle-mouture.html<p>Quelques nouvelles de mon jeu de rôle <em>Amères Victoires & Glorieuses Défaites</em>,
que j'avais déjà présenté <a href="ameres-victoires-et-glorieuses-defaites.html">dans un article</a>
il y a un peu plus d'un an.</p>
<blockquote>
<p>Dans ce court jeu de rôle, les joueuses incarnent les héros d’une saga épique et tragique, au dernier chapitre de leur périple.
Ils …</p></blockquote><p>Quelques nouvelles de mon jeu de rôle <em>Amères Victoires & Glorieuses Défaites</em>,
que j'avais déjà présenté <a href="ameres-victoires-et-glorieuses-defaites.html">dans un article</a>
il y a un peu plus d'un an.</p>
<blockquote>
<p>Dans ce court jeu de rôle, les joueuses incarnent les héros d’une saga épique et tragique, au dernier chapitre de leur périple.
Ils ont traversé bien des aventures ensembles, et arrivent au terme de leurs quêtes respectives, où ils devront faire des choix cornéliens pour y apporter une conclusion.</p>
</blockquote>
<figure>
<a href="https://lucas-c.github.io/jdr/gdav/">
<img alt="Un héro en armure se tient devant une citadelle volante"
src="https://lucas-c.github.io/jdr/gdav/img/stormchaser_proving_grounds_by_halycon450_dav4fd5-fullview-lighter.jpg">
</a>
<figcaption>« Stormchaser Proving Grounds » de Ramirez de Souza</figcaption>
</figure>
<p>J'ai réalisé le mois dernier un quatrième <em>playtest</em> du jeu,
avec quatre membres du club de jeux de rôle de Trélazé
<a href="http://heritiersdumnacus.blogspot.com">les Héritiers de Dumnacus</a>
(<a href="http://heritiers.dumnacus.online.fr">site web</a>).</p>
<p>Je connais bien ce club, ayant été moi-même membre pendant quelques années il y a plus de 10 ans !
C'était donc avec grand plaisir que j'ai retrouvé d'anciens camarades de jeu,
et fait la connaissance d'un très sympathique nouveau membre.</p>
<p>Comme lors des <em>playtests</em> précédents d'<em>Amères Victoires & Glorieuses Défaites</em>,
la phase initiale de création collective de l'univers de jeu a beaucoup plu.
J'en ai profité pour inclure quelques conseils dans les règles pour animer cette phase.</p>
<p>Pour cette partie, voici quelques caractéristiques de l'univers conçu par les joueurs :</p>
<ul>
<li>les paysages sont essentiellement constitués de ruines, de déserts, de végétation</li>
<li>les Dieux ont abandonné les hommes</li>
<li>une certaine forme de magie est présente, élémentaire, végétale.
Sa source proviendrait du Fluide suintant de gigantesques piles datant d'avant le cataclysme.</li>
<li>deux courants de pensée principaux sont en lutte, et ont donné lieu à une terrible guerre ouverte
il y a plusieurs années : des nomades animistes, les Jardinistes, et les Gardiens du Passé,
œuvrant à préserver la technologie antique, et dirigés par 3 IA en compétition</li>
<li>trois gangs occupent également certaines régions, les Grimlocks,
dirigés par : Smoke le voleur, Midas le maître du commerce, et Motorbahn le chef de guerre</li>
<li>enfin, des golem-robots géants indestructibles errent ératiquement à travers le monde;
de terribles tempêtes magiques surviennent parfois, les maëlstroms;
et depuis peu d'étranges amas d'insectes volants, les Essaims, ravagent des régions entières</li>
</ul>
<p>Bien que de j'ai réussi à mieux <em>timeboxer</em> cette phase de création collective que les fois précédentes,
l'ensemble de la session de jeu à elle beaucoup dépassé mes estimations.
J'ai revu à la hausse les estimations en temps du diagramme décrivant le déroulement,
et je compte bien être plus dans les clous la prochaine fois !</p>
<p>Les joueurs ont créé des Héros ayant pour objectif de retrouver un trésor perdus,
de réparer un artefact, de se venger d'un chef de guerre, de cloner un être cher décédé,
de découvrir se qui se cache dans une contrée lointaine ou encore de prendre le contrôle d'un colosse.
Et ils en ont accompli la plupart durant la partie !</p>
<p>Leurs Atouts : des papillons de métal, une capacité à contrôler le vent,
des armes laser ou géante, un hoverboard, une clef mystérieuse...</p>
<p>Dans l'ensemble les retours des joueurs ont été plutôt positifs,
mais un point d'amélioration m'a été remonté en particulier :
la phase de flashbacks initiale, où les joueurs obtiennent leurs Atouts,
était trop longue du goût de certains joueurs. Et réflexion faite, je suis bien d'accord.
J'ai retravaillé cette phase pour rendre ces scènes plus fluides et rapides,
et raccourcir cette étape du jeu.</p>
<figure>
<a href="images/2020/02/avgd-photo-table.jpg">
<img alt="Photo des éléments principaux du jeu" src="images/2020/02/avgd-photo-table.jpg">
</a>
<figcaption>Les principaux éléments de jeu - merci à Arnaud pour la photo</figcaption>
</figure>
<p>Voici quelques décisions de conception que j'ai prises en définitive après ce <em>playtest</em> :</p>
<ul>
<li>système d'acquisition des Atouts dans la phase initiale <strong>raccourci & rendu plus fluide</strong>,
et suppression du PMJ (Personnage de la Meneuse de Jeu)</li>
<li><strong>suppression / fusion</strong> de la partie basse de la table de résolution, trop peu souvent employée,
et introduction de la notion de <strong>réussite tragique</strong></li>
<li><em>Noeuds du Destin</em> renommés en <em>Flashbacks</em> et légèrement modifiés</li>
<li>plutôt qu'employer l'écriture inclusive, adoption d'une convention qui commence à être assez répandue dans les JdR :
les termes "les Joueuses" (au féminin) et "les Héros" (au masculin) sont employés dans les règles</li>
<li>inspiré de <a href="https://nicolasfolliot.itch.io/trophee-sombre"><em>Dark Trophee</em></a>, un thème fort pour la partie
est désormais défini en début de séance. Un exemple tiré de <em>Dark Trophee</em> serait <strong>Sommeil, Eau & Masques</strong>.
Pour cette partie, le thème était <strong>Bois, Foudre & Métal</strong>.</li>
<li>une carte heuristique et quelques conseils d'animation sont désormais inclus pour créer son propre univers
en début de partie</li>
</ul>
<p>Voici les prochaines points que je prévoie d'améliorer :</p>
<ul>
<li>travailler un peu plus la mise en page, et notamment embellir les feuilles de Héro</li>
<li>"sortir" les listes à choix des règles, pour pouvoir les distribuer plus facilement aux joueurs</li>
<li>fournir un univers « prêt à jouer » dans l'ambiance <em>Dark Souls</em>, avec Thème, Pacte et listes à choix d'Atouts / Flashbacks</li>
</ul>
<p>D'ici là, si vous êtes curieux de voir à quoi ressemble le jeu, jetez un œil aux liens ci-dessous !</p>
<div class="side-by-side">
<a href="https://github.com/Lucas-C/jdr/releases/download/gdav-v1.2/gdav-v1.2.pdf">
<figure>
<img alt="" src="images/2020/02/avgd-pdf.png">
<figcaption><em>Amères Victoires & Glorieuses Défaites</em><br>(PDF 14 pages 6,4 Mo)</figcaption>
</figure>
</a>
<a href="https://github.com/Lucas-C/jdr/releases/download/gdav-v1.2/gdav-v1.2-mind-map.pdf">
<figure>
<img alt="" src="images/2020/02/avgd-mindmap.png">
<figcaption><em>Aide de jeu : mind map</em><br>(PDF 1 page 82K)</figcaption>
</figure>
</a>
<a href="https://lucas-c.github.io/jdr/gdav/">
<figure>
<img alt="" src="images/2020/02/avgd-web.png">
<figcaption><em>Amères Victoires & Glorieuses Défaites</em><br>(web)</figcaption>
</figure>
</a>
</div>
<p>J'ai également profité de cette nouvelle version pour <a href="https://lucas-c.itch.io/ameres-victoires-glorieuses-defaites">le mettre en ligne sur itch.io</a>.</p>
<p>Si vous le lisez / testez, je serais sincèrement ravi d'avoir vos avis sur le jeu dans les commentaires !</p>
<style>
.small-img { max-height: 16rem; }
article img { max-height: 40rem; }
.side-by-side {
display: flex;
justify-content: center;
align-items: center;
flex-flow: wrap;
}
.side-by-side > * { flex: 1 0; }
</style>Minutes of the FOSDEM 2020 conference2020-02-06T14:00:00+01:002020-02-06T14:00:00+01:00Lucas Cimontag:chezsoi.org,2020-02-06:/lucas/blog/minutes-of-the-fosdem-2020-conference.html<p><img alt="A cake for FOSDEM 20 years" src="images/2020/02/FOSDEM20.png"></p>
<p>The <a href="https://fosdem.org/2020/">FOSDEM'20</a> <em>(Free & Open Source Developers’ European Meeting)</em> conference is:</p>
<blockquote>
<p>a free event for software developers to meet, share ideas and collaborate</p>
</blockquote>
<p>It took place last week-end at the <a href="https://www.ulb.be">Université Libre de Bruxelles</a>,
and I had the chance to attend it.</p>
<p>Sincere thanks to my employer, <a href="https://oui.sncf">oui.sncf</a>,
for financing …</p><p><img alt="A cake for FOSDEM 20 years" src="images/2020/02/FOSDEM20.png"></p>
<p>The <a href="https://fosdem.org/2020/">FOSDEM'20</a> <em>(Free & Open Source Developers’ European Meeting)</em> conference is:</p>
<blockquote>
<p>a free event for software developers to meet, share ideas and collaborate</p>
</blockquote>
<p>It took place last week-end at the <a href="https://www.ulb.be">Université Libre de Bruxelles</a>,
and I had the chance to attend it.</p>
<p>Sincere thanks to my employer, <a href="https://oui.sncf">oui.sncf</a>,
for financing the accommodation & transport!</p>
<p>The following are some frugal notes on the talks I've seen there.</p>
<!-- markdown-toc -i content/2020-02-04-minutes-of-the-fosdem-2020-conference.md -->
<!-- toc -->
<ul>
<li><a href="minutes-of-the-fosdem-2020-conference.html#saturday-1000-how-foss-could-revolutionize-municipal-government---danese-cooper">Saturday 10:00 How FOSS could revolutionize municipal government - Danese Cooper</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#saturday-1130-doomed-are-the-dinosaurs---david-heijkamp">Saturday 11:30 Doomed are the dinosaurs! - David Heijkamp</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#saturday-1200-civil-society-needs-free-software-hackers---matthias-kirschner">Saturday 12:00 Civil society needs Free Software hackers - Matthias Kirschner</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#saturday-1220-a-tool-for-community-supported-agriculture-csa-management-openolitor---mikel-cordovilla-mesonero">Saturday 12:20 A tool for Community Supported Agriculture (CSA) management, OpenOlitor - Mikel Cordovilla Mesonero</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#saturday-1240-whats-in-my-food--open-food-facts---pierre-slamich">Saturday 12:40 What's in my food ? Open Food Facts - Pierre Slamich</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#saturday-1300-web3---the-internet-of-freedom-value-and-trust---bruno-skvorc">Saturday 13:00 Web3 - the Internet of Freedom, Value, and Trust - Bruno Škvorc</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#saturday-1400-the-hidden-early-history-of-unix---warner-losh">Saturday 14:00 The Hidden Early History of Unix - Warner Losh</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#saturday-1500-a-best-practices-guide-for-floss-community-managers---the-open-source-way-v20---karsten-wade--shaun-mccance">Saturday 15:00 A best practices guide for FLOSS community managers - The Open Source Way v2.0 - Karsten Wade & Shaun McCance</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#saturday-1600-journalists-are-researchers-like-any-others---anne-lhote--bruno-thomas">Saturday 16:00 Journalists are researchers like any others - Anne L'Hôte & Bruno Thomas</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#saturday-1730-creating-sustainable-public-sector-open-source-communities---osor-team">Saturday 17:30 Creating Sustainable Public Sector Open Source Communities - OSOR team</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#sunday-1100-is-the-web-rea11y-for-all---ioana-chiorean">Sunday 11:00 Is the web rea11y for all? - Ioana Chiorean</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#sunday-1155-cognitive-biases-blindspots-and-inclusion---allon-mureinik">Sunday 11:55 Cognitive biases, blindspots and inclusion - Allon Mureinik</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#sunday-1255-bringing-back-ethics-to-open-source---tobie-langel">Sunday 12:55 Bringing back ethics to open source - Tobie Langel</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#sunday-1500-file-sharing--storage-for-human-rights-organizations---allon-bar--abigail-garner">Sunday 15:00 File sharing & storage for human rights organizations - Allon Bar & Abigail Garner</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#sunday-1530-design-contributions-to-oss-learnings-from-the-open-design-project-at-ushahidi---eriol-fox">Sunday 15:30 Design contributions to OSS: Learnings from the Open Design project at Ushahidi - Eriol Fox</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#sunday-1600-ngi-zero-a-treasure-trove-of-tech-awesome---michiel-leenaars">Sunday 16:00 Sunday 16:00 NGI Zero: A treasure trove of tech awesome - Michiel Leenaars</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#sunday-1620-european-software-engineering-funded-research---luis-c-busquets-perez">Sunday 16:20 European Software Engineering funded research - Luis C. Busquets Pérez</a></li>
<li><a href="minutes-of-the-fosdem-2020-conference.html#conclusive-notes">Conclusive notes</a></li>
</ul>
<!-- tocstop -->
<h2>Saturday 10:00 How FOSS could revolutionize municipal government - Danese Cooper</h2>
<p><a href="https://fosdem.org/2020/schedule/event/municipal_government/">FOSDEM talk description</a></p>
<p>The has a <a href="https://en.wikipedia.org/wiki/Danese_Cooper">bio on Wikipedia</a>.</p>
<p>A quote that I liked:</p>
<blockquote>
<p>Open Source is Art.
We're attached to our creations the way painters are.</p>
</blockquote>
<p>Some city experiments mentioned:</p>
<ul>
<li>
<p><a href="https://en.wikipedia.org/wiki/Extremadura">Extremadura (Spain)</a> is a small, with a population of about a million,
and relatively cash-poor region that has defined a strategy based on libre software
to catch upon information society issue</p>
</li>
<li>
<p>Munich city wanted to get rid of Microsoft in 2003.
They succeeded, but last year they switch back to Windows.
For the whole city administration IT services 😞</p>
</li>
<li>
<p><a href="https://en.wikipedia.org/wiki/Code_for_America">Code for America</a></p>
</li>
<li>
<p><a href="https://theopensourcecity.com/">The Foundation for an Open Source city - Jason Hibbets</a></p>
</li>
<li>
<p>Paris & the web portal <a href="https://fr.lutece.paris.fr">Lutece</a> (<a href="https://fr.wikipedia.org/wiki/Lut%C3%A8ce_(logiciel)">Wikipedia</a>).
More info on those slides : <a href="https://fr.slideshare.net/OW2/powering-digital-city-tools-with-open-source-ow2con18-june-78-2018-paris">https://fr.slideshare.net/OW2/powering-digital-city-tools-with-open-source-ow2con18-june-78-2018-paris</a></p>
<ul>
<li>over 500 plugins, half hosted on GitHub</li>
<li>+300 commiters along 15 years</li>
<li>210 business apps, websites & online services</li>
<li>More than 600 Lutece instance running</li>
<li>100% on Linux</li>
<li>0 € of license</li>
</ul>
</li>
</ul>
<p>Participatory budgeting :</p>
<ul>
<li><a href="https://budgetparticipatif.paris">https://budgetparticipatif.paris</a></li>
<li><a href="https://github.com/lutece-secteur-public/particip-site-participatorybudget">OpenPB demo site</a></li>
</ul>
<p>Many things mentioned in this talk are follow-ups of
<a href="https://dpya.org/en/images/2/2b/Opensources_bw.pdf">Open Sources 2.0 - The continuing evolution - 2006</a>,
book chapter "Public Administrations & Libre Software".</p>
<h2>Saturday 11:30 Doomed are the dinosaurs! - David Heijkamp</h2>
<p><a href="https://en.wikipedia.org/wiki/Naturalis_Biodiversity_Center#/media/File:WLANL_-_thedogg_-_Mammoet_(2).jpg"><img alt="Naturalis history museum interior" src="https://upload.wikimedia.org/wikipedia/commons/9/97/WLANL_-_thedogg_-_Mammoet_%282%29.jpg"></a></p>
<p><a href="https://fosdem.org/2020/schedule/event/doomed/">FOSDEM talk description</a></p>
<video preload="none" controls="controls">
<source src="https://video.fosdem.org/2020/UD2.120/doomed.webm" type="video/webm; codecs="vp9, opus"">
</video>
<ul>
<li><a href="https://drive.google.com/file/d/1FfcaLPd2TVdOFVcvlh-Q8hss15r9FcwL/view?usp=sharing">PDF Slides on Google Drive</a></li>
<li><a href="https://github.com/naturalis">https://github.com/naturalis</a></li>
</ul>
<p>Deploy & manage an entirely new natural history museum: <a href="https://en.wikipedia.org/wiki/Naturalis_Biodiversity_Center">Naturalis</a> in Leiden, Netherlands</p>
<p>Infra based on OpenSteck & Ceph, some workloads on Kubernetes (at some point), analytics with Sensu, ELK and Grafana</p>
<ol>
<li>Built on existing infra & know-how</li>
<li>Use high quality open source components</li>
<li>Apply 'infra as code' & devops to infrastructure & museum</li>
</ol>
<p><a href="images/2020/02/doomedarethedinosaurs-andisble-in-the-center.png"><img alt="Diagram: Ansible in the center" src="images/2020/02/doomedarethedinosaurs-andisble-in-the-center.png"></a></p>
<p><a href="images/2020/02/doomedarethedinosaurs-the-scope-of-automation-in-the-museum.png"><img alt="Diagram: The scope of automation in the museum" src="images/2020/02/doomedarethedinosaurs-the-scope-of-automation-in-the-museum.png"></a></p>
<p>A quote that I liked:</p>
<blockquote>
<p>Turning on the museum is done through AWX every morning by someone working at the security.</p>
</blockquote>
<h2>Saturday 12:00 Civil society needs Free Software hackers - Matthias Kirschner</h2>
<blockquote>
<p>More and more traditionally processes in our society now incorporate, and are influenced by software.
Processes that decide for example: Who will be able to go to which university? Who will be invited for a job interview? How long does someone have to go to jail?</p>
</blockquote>
<p><a href="https://fosdem.org/2020/schedule/event/free_software_hackers_needed/">FOSDEM talk description</a></p>
<video preload="none" controls="controls">
<source src="https://video.fosdem.org/2020/H.2215/free_software_hackers_needed.webm" type="video/webm; codecs="vp9, opus"">
</video>
<p>Matthias Kirschner is <a href="https://fsfe.org">FSFE</a>'s president :</p>
<ul>
<li><a href="https://fsfe.org/about/kirschner/">short bio</a></li>
<li><a href="https://k7r.eu/blog.html">web log</a></li>
</ul>
<p>Examples mentioned:</p>
<ul>
<li>Parcoursup en France : <a href="https://www.parcoursup.fr">website</a> - <a href="https://framagit.org/parcoursup">Framagit</a></li>
<li>CVs filtered by proprietary software before being looked by any human, in many countries inc. UK</li>
<li>USA's Compas (Correctional Offender Management Profiling for Alternative Sanctions),
used to weigh up whether defendants awaiting trial or sentencing are at too much risk of re-offending to be released on bail
(<a href="https://www.theguardian.com/us-news/2018/jan/17/software-no-more-accurate-than-untrained-humans-at-judging-reoffending-risk">more info in an article on theguardian.com</a>)</li>
</ul>
<h2>Saturday 12:20 A tool for Community Supported Agriculture (CSA) management, OpenOlitor - Mikel Cordovilla Mesonero</h2>
<ul>
<li><a href="https://fosdem.org/2020/schedule/event/openolitor_community_supported_agriculture/">FOSDEM talk description</a></li>
</ul>
<video preload="none" controls="controls">
<source src="https://video.fosdem.org/2020/H.2215/openolitor_community_supported_agriculture.webm" type="video/webm; codecs="vp9, opus"">
</video>
<p>Web-based tool to facilitate the organization of Community Supported Agriculture groups.</p>
<ul>
<li><a href="http://urgenci.net/the-csa-research-group/">http://urgenci.net/the-csa-research-group/</a> : in France, they are named <a href="https://urgenci.net/france/">AMAP</a></li>
<li><a href="https://openolitor.org">https://openolitor.org</a></li>
<li><a href="http://sunu.eu/openolitor/">Open Olitor on sunu.eu</a></li>
</ul>
<p>The name means "Open gardeners", it's from <a href="https://glosbe.com/la/en/olitor">latin</a>.</p>
<h2>Saturday 12:40 What's in my food ? Open Food Facts - Pierre Slamich</h2>
<p><img alt="Open Food Facts logo" src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/75/Open_Food_Facts_logo.svg/langfr-1280px-Open_Food_Facts_logo.svg.png"></p>
<p><a href="https://fosdem.org/2020/schedule/event/open_food_facts/">FOSDEM talk description</a></p>
<video preload="none" controls="controls">
<source src="https://video.fosdem.org/2020/H.2215/open_food_facts.webm" type="video/webm; codecs="vp9, opus"">
</video>
<p>I especially liked this slide, describing the percentage of fruit in Fanta drinks sold in different places around Europe:</p>
<p><img alt="Fruit percentage among different Fanta drinks sold across Europe" src="images/2020/02/fantas-accross-europe.png"></p>
<p>All data is under <a href="https://en.wikipedia.org/wiki/Open_Database_License">Open Data License</a></p>
<p>They are extending their initial idea:</p>
<ul>
<li><a href="https://world.openbeautyfacts.org">Open Beauty Facts</a></li>
<li><a href="https://world.openpetfoodfacts.org">Open Pet Food Facts</a> (French introduction on <a href="https://linuxfr.org/news/au-revoir-open-food-facts-et-open-beauty-facts-bienvenue-a-open-pet-food-facts-31-mars-2017">LinuxFr</a>)</li>
<li><a href="https://world.openproductsfacts.org/">Open Product Facts</a></li>
</ul>
<p>OpenFoodFacts also improves accessibility, <em>e.g.</em> allow blind people to know what there is in the food they buy</p>
<p>Info on how to contribute: <a href="https://world.openfoodfacts.org/development">https://world.openfoodfacts.org/development</a></p>
<p><img alt="Come Play With Food!" src="images/2020/02/OpenFoodFacts-ComePlayWithFood.png"></p>
<h2>Saturday 13:00 Web3 - the Internet of Freedom, Value, and Trust - Bruno Škvorc</h2>
<p><a href="https://fosdem.org/2020/schedule/event/web3/">FOSDEM talk description</a></p>
<video preload="none" controls="controls">
<source src="https://video.fosdem.org/2020/H.2215/web3.webm" type="video/webm; codecs="vp9, opus"">
</video>
<p><a href="https://web3.foundation">web3 foundation website</a></p>
<blockquote>
<p>Our ultimate goal is delivering Web 3.0, a decentralized and fair internet where users control their own data
and markets prosper from network efficiency and security.</p>
</blockquote>
<p>Web 3.0 is:</p>
<ol>
<li>Linked data</li>
<li>Distributed data</li>
<li>Add a layer of trust on the network (through blockchains)</li>
</ol>
<blockquote>
<p>That's all we want: Transfer messages from anywhere, to anywhere</p>
</blockquote>
<h2>Saturday 14:00 The Hidden Early History of Unix - Warner Losh</h2>
<p><a href="https://fosdem.org/2020/schedule/event/early_unix/">FOSDEM talk description</a></p>
<video preload="none" controls="controls">
<source src="https://video.fosdem.org/2020/Janson/early_unix.webm" type="video/webm; codecs="vp9, opus"">
</video>
<p>A truly fascinating talk about programming archaeology, recounting the tale of how the very first Unix build systems!</p>
<p><img alt="Jurassic Park meme: it's a UNIX system, I know this!" src="images/2020/02/its-a-unix-system-I-know-this.jpg"></p>
<p>PDP-7 sources have been recently recovered,
and at the time, the "userland" was limited to ~25 commands (I only recognized <code>cat</code>, <code>check</code> & <code>chmod</code>),
with <code>init</code>, <code>ln</code>, <code>ls</code>, <code>mv</code> & <code>sh</code> newly written!</p>
<p>A LCM+L PDP-7 booting and running UNIX Version 0... with a typewriter serving as output!!!</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/pvaPaWyiuLA?start=110" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<p>As a side note, pdp-7 emulator won became IOCCC 2018 winner!</p>
<ul>
<li><a href="https://www.ioccc.org/2018/mills/hint.html">https://www.ioccc.org/2018/mills/hint.html</a></li>
<li><a href="https://www.ioccc.org/2018/mills/prog.c">https://www.ioccc.org/2018/mills/prog.c</a></li>
</ul>
<p>The growth of the number of computers running Unix is quite interesting:</p>
<ul>
<li>for Unix 2nd edition (June 1972), there were 10 installations</li>
<li>for 3nd edition (February 1973) : 16 installations</li>
<li>for 4th edition (November 1973) : 20 installations</li>
<li>for 5th edition (June 1974) : above 50 installations</li>
<li>for 6th edition (May 1975) : over 100 sites</li>
</ul>
<p>Warner Losh also dwelves into a few interesting questions: What was the first fork of Unix ?
What was the first Unix running under an hypervisor ?</p>
<p>And finally, there is one of the very first Unix fan-arts picture!</p>
<p><img alt="Drawing of small red demons around a mainframe" src="https://www.mckusick.com/beastie/jpg/foglio.jpg"></p>
<h2>Saturday 15:00 A best practices guide for FLOSS community managers - The Open Source Way v2.0 - Karsten Wade & Shaun McCance</h2>
<p><img alt="Purely illustrative event cover image" src="https://fosdem.org/2020/schedule/event/bof_open_source_way/bof_open_source_way-83b63810472da8ae4db2681062d93e88d74b246c13b68b79d7449b47b33ca63c.jpg"></p>
<p><a href="https://fosdem.org/2020/schedule/event/bof_open_source_way/">FOSDEM talk description</a></p>
<p>Two speakers from Red Hat presented a project they are starting :
a collaboratively-written, community-driven, guide for FLOSS community managers:</p>
<ul>
<li>
<p><a href="http://www.theopensourceway.org">http://www.theopensourceway.org</a> - Some notable sub-pages:</p>
<ul>
<li><a href="https://www.theopensourceway.org/wiki/Organizing_a_community_-_checklist">Organizing a community - checklist</a></li>
<li><a href="https://www.theopensourceway.org/wiki/Books_you_should_read">Books you should read</a></li>
</ul>
</li>
<li>
<p>GitHub repo <a href="https://github.com/theopensourceway/guidebook">theopensourceway/guidebook</a>,
including some <a href="https://github.com/theopensourceway/guidebook/blob/master/OUTLINE.adoc">Guidelines outline</a></p>
</li>
</ul>
<blockquote>
<p>a (somewhat opinionated) guidebook for anyone interested in managing open source communities.
It collects best practices for initiating, nurturing, growing, and maintaining groups of passionate contributors.</p>
</blockquote>
<p>Following a question, it was clarified that such guide is only needed at the point where the number of contributors reach a certain point,
where self-organizing does not work / something more structured is needed.</p>
<p>This <a href="https://en.wikipedia.org/wiki/Birds_of_a_feather_(computing)">birds of feathers (BoF)</a> was mainly an exchange of questions
& reflections on this project, hence difficult to summarize.</p>
<p>A provocative idea, but one I found very interesting, was to have a <code>TESTAMENT.md</code> file explaining how a project is planning to "retire",
if ever it "fades out", <em>e.g.</em> due to a lack of motivated contributors / finance:
would it get archive ? Donated or merged to an org ?</p>
<h2>Saturday 16:00 Journalists are researchers like any others - Anne L'Hôte & Bruno Thomas</h2>
<p><a href="https://fosdem.org/2020/schedule/event/open_research_journalists_are_researchers/">FOSDEM talk description</a></p>
<video preload="none" controls="controls">
<source src="https://video.fosdem.org/2020/AW1.126/open_research_journalists_are_researchers.webm" type="video/webm; codecs="vp9, opus"">
</video>
<p><a href="https://datashare.icij.org">https://datashare.icij.org</a></p>
<p>The room was full, so I wasn't able to assist to this talk.
Thanks to FOSDEM video recordings, I'll watch it soon!</p>
<p>It made me discover this interesting initiative though:
the International Consortium of Investigative Journalists <a href="https://www.icij.org/leak/">"Leak to us" page</a>:</p>
<blockquote>
<p>[We] encourages whistleblowers to securely submit all forms of content that might be of public concern - documents, photos, video clips
as well as story tips. We accept all information that relates to potential wrongdoing by corporate, government or public service entities
in any country, anywhere in the world. We do our utmost to guarantee the confidentiality of our sources.</p>
</blockquote>
<h2>Saturday 17:30 Creating Sustainable Public Sector Open Source Communities - OSOR team</h2>
<p><a href="https://fosdem.org/2020/schedule/event/bof_public_sector/">FOSDEM talk description</a></p>
<p>OSOR = <a href="https://joinup.ec.europa.eu/collection/open-source-observatory-osor">Open Source Observatory</a></p>
<p>Description from <a href="https://en.wikipedia.org/wiki/Open_Source_Observatory_and_Repository">Wikipedia</a>:</p>
<blockquote>
<p>online project launched by the European Commission to support the distribution and re-use of software
developed by or for public sector administrations across Europe.</p>
</blockquote>
<p>This was a collaborative group thinking workshop, and probably the most interesting FOSDEM event of the day for me!</p>
<p>The goal was to have a reflection on guidelines for open-source projects to adopt across Europe,
that would improve their sustainability. They stated that they wanted to initiate</p>
<blockquote>
<p>a discussion on the key elements that contribute to OSS communities’ vibrance, governance and sustainable finance.</p>
</blockquote>
<p>The OSOR team constituted of Monika Sowinska, a European Commission project officer,
<a href="https://www.kcl.ac.uk/people/dr-maha-shaikh">Dr Maha Shaikh</a>, and several consultants from the <a href="https://fr.wikipedia.org/wiki/Wavestone">Wavestone</a> cabinet.</p>
<p>They told us they were planning to draft, this year, some official guidelines along those lines.
They should get back to us through email, so I'll include more information here when I'll know more.</p>
<p>Note: because OSOR main page is on Joinup, it made me discover this <a href="https://joinup.ec.europa.eu/solutions">european "software solutions" catalogue</a> catalogue.</p>
<p><strong>EDIT [2020/02/24]</strong> : on February the 11th, an email was sent to the participants,
with enclosed a Power-Point presentation (sic) including the slides they showed us during the workshop,
and the "Brainstorming session results".</p>
<p>Those slides can be found online at the bottom of this article on Joinup:
<a href="https://joinup.ec.europa.eu/collection/open-source-observatory-osor/workshop-sustainable-oss-communities-public-sector-fosdem20">Workshop on sustainable OSS communities in public sector at FOSDEM20</a></p>
<p>As for myself, I took part in the brainstorming group about "Governance".
I am glad they included pictures of the post-its board in the slides, as they accurately reflect the output of this short session.
However, I have to say that I am surprised on how some of the ideas expressed at the workshop have been transcribed.
I certainly do not recall anyone suggesting anything even close to those "results" from the slides:</p>
<blockquote>
<p>Governance should be enforced because hierarchy does not always happen naturally</p>
<p>Projects have three core phases that are better facilitated through the enforcement of governance standards:
1. Maintenance 2. Deployment 3. Operation</p>
</blockquote>
<p>While this "group brainstorming" format was really interesting to generate discussion and exchanges of points of view around those thematics,
I fear that a large part of interpretation has been done on the resulting post-its boards:
I sincerely think that <strong>the "brainstorming session results" from this workshop do not properly reflect the opinions of the participants</strong>.</p>
<p>Finally, I am a bit disappointed that the survey they sent us is only addressed to members of open source community managed by the public sector
<a href="https://ec.europa.eu/eusurvey/runner/OSORsurvey2020sustainabilitycommunities">link to the survey</a>.
They do not seem interested in consulting members of other open-source communities.</p>
<p><strong>EDIT [2020/06/02]</strong> : I missed that before but the OSOR has <a href="https://joinup.ec.europa.eu/collection/open-source-observatory-osor/osor-newsletters">a newsletter</a>.</p>
<p>It is also redacting interesting <a href="https://joinup.ec.europa.eu/collection/open-source-observatory-osor/open-source-software-country-intelligence">Intelligence reports</a>
on the use of open source software per european country.</p>
<h2>Sunday 11:00 Is the web rea11y for all? - Ioana Chiorean</h2>
<p><a href="https://fosdem.org/2020/schedule/event/is_the_web_rea11ly_for_all/">FOSDEM talk description</a></p>
<video preload="none" controls="controls">
<source src="https://video.fosdem.org/2020/UA2.114/is_the_web_rea11ly_for_all.webm" type="video/webm; codecs="vp9, opus"">
</video>
<p><a href="https://docs.google.com/presentation/d/1O38CdmO38Qh_KO0cn59b-tRBOhn5RddVip3a2w9Qc28/edit">Slides on GoogleDocs</a></p>
<h2>Sunday 11:55 Cognitive biases, blindspots and inclusion - Allon Mureinik</h2>
<p><a href="https://fosdem.org/2020/schedule/event/cognitivebias/">FOSDEM talk description</a></p>
<video preload="none" controls="controls">
<source src="https://video.fosdem.org/2020/UB5.230/cognitivebias.webm" type="video/webm; codecs="vp9, opus"">
</video>
<h2>Sunday 12:55 Bringing back ethics to open source - Tobie Langel</h2>
<p><a href="https://fosdem.org/2020/schedule/event/ethicsbackinoss/">FOSDEM talk description</a></p>
<p>Link to online slides: <a href="https://speaking.unlockopen.com/8bsoum/bringing-ethics-back-to-open-source">https://speaking.unlockopen.com/8bsoum/bringing-ethics-back-to-open-source</a></p>
<p>Really inspiring talk, despite the technical difficulties !</p>
<p>I loved the speaker idea to imagine where we would be if from the start, OSD licenses would have been created with ethics like human rights in mind.</p>
<p>What kind of inspiring uchronia could we imagine there ?</p>
<p>A few random thoughts:</p>
<ul>
<li>would it have slowed down OSS adoption ? I'd say no, because most projects would likely not object to pro-human-rights clauses...
It could actually be an attractiveness factor for contributors: we can <strong>assure you</strong> that the code you willingly write under this license
will <strong>never</strong> be used to anti-human-right activities.</li>
<li>would some kind of failsafe systems be embedded in software, so that a democratically elected body could disable OSS systems used in against human rights ?</li>
<li>is this subject linked to transparency of OSS usage ? Should OSS usage be registered ? I have strong doubts here...</li>
<li>there is also the subject of classifying which OSS is "dangerous" to human rights, or definitively not.</li>
<li>how would you control the application of those licenses terms ?
How would you enforce rules at a worldwide level ? (legally, but also very likely by political "force")</li>
</ul>
<p>A "strong concept word / image", similar to the term "copyleft", could be useful to express this idea.
Human-rights-enforcing licenses ? Probably not catchy enough...</p>
<p><strong>EDIT [2020/02/14]</strong>: a similar idea to prevent oil and gas companies to use software under a specific license:
<a href="https://github.com/climate-strike/license">https://github.com/climate-strike/license</a></p>
<h2>Sunday 15:00 File sharing & storage for human rights organizations - Allon Bar & Abigail Garner</h2>
<p><a href="https://fosdem.org/2020/schedule/event/file_sharing_and_storage_for_human_rights_organizations/">FOSDEM talk description</a></p>
<video preload="none" controls="controls">
<source src="https://video.fosdem.org/2020/H.2213/file_sharing_and_storage_for_human_rights_organizations.webm" type="video/webm; codecs="vp9, opus"">
</video>
<p>Also a very interesting talk!</p>
<ul>
<li><a href="https://leastauthority.com/open-source/">Overview of some of the open source tools used by Least Authority</a></li>
<li><a href="https://leastauthority.com/blog/secure-file-storage-tools-human-rights-defenders/">article on their blog summarizing the project</a></li>
</ul>
<p>One thing I found interesting: they had to write a <a href="https://en.wikipedia.org/wiki/Memorandum_of_understanding"><em>Memorendum of Understanding</em></a>
to clarify their goals and to reassure their partners about how they do not want to legally bind them.</p>
<p>They mentioned that one of the existing solutions in use by human right organizations was <a href="https://cryptpad.fr">https://cryptpad.fr</a></p>
<p>After the talk, with a friend of mine also present at the FOSDEM, we wondered what what the use of a distributed system in that case?
Supposedly the hosting could be done in "safer" countries, having less to fear from criminal governments, and hence not really at risk?
We also wondered if existing technical solutions like <a href="https://en.wikipedia.org/wiki/Pretty_Good_Privacy">PGP</a> were considered,
and if so why they weren't adopted ?</p>
<h2>Sunday 15:30 Design contributions to OSS: Learnings from the Open Design project at Ushahidi - Eriol Fox</h2>
<p><a href="https://fosdem.org/2020/schedule/event/design_contributions_to_oss/">FOSDEM talk description</a></p>
<p>An interesting talk, building bridges between the Open Source community and the designers community:
what they can learn from each others, and what the speaker learned from her experience to make both worlds work together.</p>
<p>I hope a video recording will be uploaded.</p>
<!-- Si c'est le cas : transmettre prez à Loïc & sa collègue -->
<h2>Sunday 16:00 NGI Zero: A treasure trove of tech awesome - Michiel Leenaars</h2>
<p><a href="https://fosdem.org/2020/schedule/event/ngi_zero/">FOSDEM talk description</a></p>
<p>It was difficult to take notes of this very fast lightning talk,
but there is a lot of information to be found on the FOSDEM event description.</p>
<h2>Sunday 16:20 European Software Engineering funded research - Luis C. Busquets Pérez</h2>
<p><a href="https://fosdem.org/2020/schedule/event/european_software_funded_research/">FOSDEM talk description</a></p>
<p>Same here, note much notes taken, also getting a bit tired by the end of the week-end 😉</p>
<p>All those inspiring talks about open source gave me an urgent feeling of "I want to code!",
so I started coding on a new <a href="pelican-pingback-and-webmentions.html">Pelican plugin</a> instead of paying much attention to this last talk...</p>
<h2>Conclusive notes</h2>
<p>The event was awesome!
Everybody felt very friendly, but it was also quite crowded (slightly a bit too much for me).</p>
<p>It felt to me there was a higher proportion of female participants than in other conferences, which is great!</p>
<p>Thanks a lot to the event organizers for putting up a "Dualstack" Wi-Fi hotspot (IpV4 compatible):
I too would like everybody to use IpV6, but with my employer-provided Windows laptop,
I couldn't have accessed Internet at all during this week-end 😁</p>
<p>The number of talks and subjects covered was huge!
<a href="https://en.wiktionary.org/wiki/IMHO">IMHO</a>, there were just a bit too many "visionary" talks about "the future of the Internet" 🙄</p>
<p>I'll conclude with an interesting read for French-speaking people that are not necessarily into programming & systems,
but are still interested by the ethics & goals of Libre & Open Source software:
<a href="https://framablog.org/2013/02/19/10-facons-commencer-open-source/">débuter dans le Libre (sans avoir rien à coder) - Framablog</a>.</p>
<script>
['h1', 'h2'].forEach(function (selector) {
document.querySelectorAll(selector).forEach(function (title) {
if (!title.classList.length) {
title.id = title.textContent
.toLowerCase()
.replace(/[()?!:,'&@]/g, '')
.replace(/[à]/g, 'a')
.replace(/[ç]/g, 'c')
.replace(/[éêè]/g, 'e')
.replace(/[ï]/g, 'i')
.replace(/ /g, '-');
}
});
});
</script>
<style>
article img { max-height: 15rem; }
article video { display: block; margin: 0 auto; }
article iframe { display: block; margin: 1rem auto; }
</style>Vincenzo, répression policière et MAE2020-01-23T22:00:00+01:002020-01-23T22:00:00+01:00Lucas Cimontag:chezsoi.org,2020-01-23:/lucas/blog/vincenzo-repression-policiere-et-mae.html<p>Dans la lignée d'<a href="soutien-a-vincenzo-vecchi.html">un article précédent</a>, je vais évoquer ici l'affaire <strong>Vincenzo Vecchi</strong>.</p>
<p>Samedi dernier à <strong>Angers</strong>, à la Grande Ourse, avait lieu <a href="https://alter49.org/rv/5972">une soirée de soutien pour lui</a>,
et d'information sur les évènements de Gênes en 2001 ainsi que le <em>Mandat d'Arrêt Européen</em>.</p>
<blockquote>
<p>Pour rappel, Vincenzo a été …</p></blockquote><p>Dans la lignée d'<a href="soutien-a-vincenzo-vecchi.html">un article précédent</a>, je vais évoquer ici l'affaire <strong>Vincenzo Vecchi</strong>.</p>
<p>Samedi dernier à <strong>Angers</strong>, à la Grande Ourse, avait lieu <a href="https://alter49.org/rv/5972">une soirée de soutien pour lui</a>,
et d'information sur les évènements de Gênes en 2001 ainsi que le <em>Mandat d'Arrêt Européen</em>.</p>
<blockquote>
<p>Pour rappel, Vincenzo a été condamné à 12 ans de prison par la justice italienne pour des faits présumés commis lors des manifestations
à l'occasion du G8 de Gênes en 2001 et lors d'une contre-manifestation antifasciste à Milan en 2006. La justice française doit aujourd'hui
décider de son extradition ou non vers l'Italie. Vincenzo comparaitra prochainement devant la cours d'appel d'Angers, d'où l'importance
de construire une mobilisation angevine. Nous devons utiliser notre meilleure arme contre la répression : la solidarité.</p>
</blockquote>
<p>J'en profite pour remercier le comité local de soutien à Vincenzo d'avoir organisé cette soirée !
Et bravo pour votre courageux soutien.</p>
<p><img alt="Photo d'une banderole de soutien à Vincenzo devant le tribunal d'Angers" src="images/2020/01/banderole-soutien-vincenzo-palais-justice-angers.jpg"></p>
<p>Quelques membres du comité de soutien de Rochefort-en-Terre (où Vincenzo a vécu 8 ans) ont témoigné, et décrit chronologiquement
comment ils avaient vécu les évènements, et se sont organisés pour soutenir leur ami,
en découvrant petit à petit le fonctionnement de la justice à travers l'Europe,
et les faits présumés pour lesquels Vincenzo est poursuivi.</p>
<p>Leur présentation était simple mais très claire.
J'ai trouvé admirable comment ces personnes, absolument pas spécialiste du sujet,
se sont investies et mobilisées pour soutenir Vincenzo.
Je me suis posé la question, et je vous la pose aussi :
<strong>et si un de vos amis était soudainement emprisonné pour des raisons abusives, que feriez-vous ?</strong></p>
<p>À réfléchir, quand déjà <a href="https://www.bastamag.net/gilets-jaunes-prisonniers-politique-colere-sociale-detention-justice">plus de 440 gilets jaunes ont été incarcérés</a>,
dans des conditions de détention honteuses.</p>
<p>Le comité a conclu cet échange en évoquant le Mandat d'Arrêt Européen (MAE),
un outil remplaçant le mécanisme d'extradition au niveau européen,
et détourné pour poursuivre des opposants politiques au-delà des frontières d'un pays :</p>
<p><a href="images/2020/01/affiche-MAE-dans-la-tourmente-726x1024.jpg"><img alt="Affiche présentant le détournement du MAE dans le cas de Vincenzo" src="images/2020/01/affiche-MAE-dans-la-tourmente-726x1024.jpg"></a></p>
<p>Voici une présentation très claire du problème qu'il pose en une vidéo de 3min :</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/a6zNArEatb4" allowfullscreen></iframe>
<p>Une conférence de presse avait d'ailleurs lieu mardi 21 janvier à l’Assemblée Nationale autour du cas Vincenzo Vecchi,
et pour alerter le Parlement sur les dysfonctionnements du MAE.</p>
<p>Le plus atterrant dans le cas de Vincenzo,
c'est que le MAE concernant la condamnation pour participation à une manifestation antifasciste non autorisée à Milan
ne pouvait être émis par la justice italienne, puisque Vincenzo a d’ores et déjà purgé cette peine.
La justice italienne ne pouvait donc pas ignorer la décision de la cour d’appel de Milan du 9 janvier 2009 qui certifie l’exécution de la peine pour les faits reprochés de 2006.
La justice italienne <strong>a donc sciemment menti sur la situation réelle de monsieur Vecchi et fait preuve de déloyauté vis-à-vis de la justice française</strong>,
ce qui questionne « la confiance mutuelle » entre justices base du MAE.</p>
<hr>
<p>La soirée de samedi dernier s'est terminée avec la projection du film <a href="https://www.imdb.com/title/tt2009436/">Black Block</a>,
qui traite, comme son nom de l'indique pas, de la <strong>très</strong> violente répression policière à Gênes, en 2001, suite aux manifestations anti-G8.
Il est axé sur les témoignages de quelques victimes de cet évènement,
et en particulier l'assaut mené par les forces de police à l'école Diaz dans la nuit du 21 au 22 juillet,
où elles ont tabassés puis torturé des manifestants pacifiques, leur causant des violences physiques et psychologiques qui les ont marquées à vie.</p>
<p>Voici la bande annonce du film :</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/ZKLjeBM-ZXk&t=13" allowfullscreen></iframe>
<p>Si comme moi vous n'aviez jamais entendu parler de ces évènements,
décrits par Amnesty International comme la « plus grande violation des droits humains et démocratiques dans un pays occidental depuis la Seconde Guerre mondiale »,
<a href="https://fr.wikipedia.org/wiki/%C3%89meutes_anti-G8_de_G%C3%AAnes_de_2001">Wikipedia</a> vous donnera quelques éléments récapitulatifs.</p>
<p>À l'image des atrocités commises par les forces de polices italiennes,
<strong>il y a quelques années seulement dans une pays limitrophe</strong>,
j'ai trouvé le visionnage du film assez dur.
Je suis content de l'avoir visionné, mais les témoignages des victimes ont de quoi donner des cauchemars.</p>
<hr>
<p>Enfin, je rappelle quelques liens importants :</p>
<ul>
<li><a href="https://www.comite-soutien-vincenzo.org">le site du comité de soutien à Vincenzo</a></li>
<li><a href="https://www.change.org/p/soci%C3%A9t%C3%A9-civile-libert%C3%A9-pour-vincenzo-vecchi?recruiter=993659191&recruited_by_id=2cceeeb0-c2bc-11e9-9d01-efeb0a57e58f&utm_source=share_petition&utm_medium=copylink&utm_campaign=petition_dashboard&utm_content=bandit-starter_cl_share_content_fr-fr%3Av5">la pétition change.org</a></li>
</ul>
<style>
article iframe { display: block; margin: 1rem auto; }
</style>Espace profond et sanglant2020-01-15T22:00:00+01:002020-01-15T22:00:00+01:00Lucas Cimontag:chezsoi.org,2020-01-15:/lucas/blog/espace-profond-et-sanglant.html<p><img alt="Couverture du jeu Sombre 3" src="images/2020/01/Sombre3_DeepSpaceGore.jpg"></p>
<p>Voici un petit retour d'expérience sur le scénario <em>Deep Space Gore</em> paru das le numéro 3 du jeu de rôle / zine <a href="https://www.terresetranges.net/sombre.html">Sombre</a>.</p>
<p>Dans une ambiance directement inspirée du film <a href="https://www.imdb.com/title/tt0078748/">« Alien »</a>,
voici le pitch de ce <em>« survival spatial moitié coopératif, moitié compétitif »</em> pour <a href="https://fr.wikipedia.org/wiki/Sombre_(jeu_de_r%C3%B4le)#Sombre_Z%C3%A9ro">Sombre Zéro</a>,
destiné à 1 MJ + 4-5 joueurs …</p><p><img alt="Couverture du jeu Sombre 3" src="images/2020/01/Sombre3_DeepSpaceGore.jpg"></p>
<p>Voici un petit retour d'expérience sur le scénario <em>Deep Space Gore</em> paru das le numéro 3 du jeu de rôle / zine <a href="https://www.terresetranges.net/sombre.html">Sombre</a>.</p>
<p>Dans une ambiance directement inspirée du film <a href="https://www.imdb.com/title/tt0078748/">« Alien »</a>,
voici le pitch de ce <em>« survival spatial moitié coopératif, moitié compétitif »</em> pour <a href="https://fr.wikipedia.org/wiki/Sombre_(jeu_de_r%C3%B4le)#Sombre_Z%C3%A9ro">Sombre Zéro</a>,
destiné à 1 MJ + 4-5 joueurs :</p>
<blockquote>
<p>Les personnages recueillent une créature extraterrestre à bord du Déméter, leur vaisseau spatial,
mais elle s'échappe. [...] Pour survivre, il faut évacuer au plus vite.</p>
</blockquote>
<p>Sa particularité, qui m'a vraiment conquis : <strong>les joueurs jouent en temps réel, contre la montre</strong>.
Une partie peut donc durer <strong>30min montre en main</strong>, explications initiales incluses, pour peu que le MJ soit bien préparé
et les joueurs attentifs. Et il peut se jouer <strong>n'importe où</strong>, même sur une table de bar, comme il ne nécessite
comme matériel que les micro-feuilles de personnage (tenant sur une page A4), un chrono et un dé à 6 faces.</p>
<p>Cela faisait plus d'un an que je voulais tester ce jeu !
J'ai malheureusement eu un peu de mal à réunir 4 joueurs autour d'une table :
à deux reprises un joueur s'est désisté alors que j'avais prévu de le tester...
Sur un an, j'ai donc eu le temps de lire & relire ce scénario,
pour avoir tous les éléments en tête en début de partie.
J'avais même préparé une fiche récapitulative pour ne rien oublier en session !
Drôle d'impression au final donc, d'avoir passé bout à bout plusieurs heures en préparatifs,
pour un temps de jeu de 30min seulement !</p>
<p>Pour cette partie il y a quinze jours, la configuration était la même que pour le test Trophée Doré que j'évoque dans l'article précédent :
j'étais MJ et quatre joueurs étaient présents, dont 2 grands amateurs de JdR et une joueuse dont c'était la première partie.</p>
<p>Comme j'ai acheté uniquement Sombre n°3, je n'avais pas accès à l'ensemble des règles de Sombre Zéro
(j'avoue être moins intéressé par les autres "variantes" de Sombre).
Je ne les ai donc peut-être pas respecté tout à fait à lettre, mais je crois en avoir saisi l'essentiel.
Surtout que l'auteur, <strong>Johan Scipion</strong>, dispense de nombreux conseils pour maîtriser le scénario au fil des pages.</p>
<p>Il indique d'ailleurs un taux de survie d'un rescapé pour trois parties, soit un survivant sur quinze PJs.
Au terme de notre partie, 2 PJs s'en sont sortis (avant l'épilogue).
Ce n'est pas un bilan si étrange que ça, mais je crois observer un <em>pattern</em> chez moi :
en tant que MJ, j'ai vraiment du mal à tuer mes PJs...</p>
<p>Pour le décompte du temps, j'ai utilisé <a href="http://www.timer-tab.com">timer-tab.com</a> avec <a href="https://www.youtube.com/watch?v=aeRDVOUy7dY">cette musique d'alarme</a>,
en l'affichant sur l'écran d'un <em>laptop</em> bien visible de tout le monde.
J'ai aussi opté pour mettre un fond d'ambiance musical pour cette session,
dès le départ du chrono, en utilisant les ambiances <em>Gravity</em>, <em>Nostromo</em> & <em>Derelic Freighter</em>
de <a href="https://tabletopaudio.com">TableTopAudio</a> (avec en plan B : <a href="https://www.youtube.com/watch?v=RYdjK5MJEy0">Alien Isolation Mix of 1979 and 2014 ambience FULL OST (YouTube)</a>).
Ça ajouté à l'immersion et à la tension dès le début de la partie, je recommande !</p>
<p>Au final, le bilan de la partie est très positif pour moi !
Et je crois que les joueurs se sont bien amusés aussi :)
Ils l'ont joué très « coop », avec même un Grimm qui préfère se faire sauter le caisson
plutôt que d'entrer en stase pour éviter tout risque.
Les PJs ont fait un léger <em>brain freeze</em> face au capitaine,
ne sachant que faire mais ayant trop la trouille pour l'affronter.
L'un d'eux est finalement aller chercher une arme pour lui régler son compte,
mais s'est retrouvé à la traîne sur le chemin bis qu'avaient décidé d'emprunter les autres,
et n'est entré qu'<em>in-extremis</em> dans la navette, au moment de l'explosion !</p>
<p>Bref, j'en retire que le jeu est très fun, que j'y rejouerai avec grand plaisir avec d'autres joueurs à l'occasion !</p>
<p>Voici les scénarios « prêt à imprimer » que j'ai pu trouver pour Sombre Zéro, après de rapides recherches :</p>
<ul>
<li>les scénarios officiels des numéros de <em>Sombre</em>, pour lesquels Johan a rédigé <a href="http://www.terresetranges.net/forums/viewtopic.php?pid=14660#p14660">un super article de conseil sur le forum terresetranges.net</a>
(sections <em>Si vous n'êtes intéressé que par Zéro</em> / <em>Mon guide d'achat Zéro</em>)</li>
<li><a href="https://www.terresetranges.net/forums/viewtopic.php?id=850">THE FAST-FOOD MASSACRE & CINEMA PARADISO</a> par Saint Epondyle <em>(fan made)</em></li>
<li><a href="https://www.terresetranges.net/forums/viewtopic.php?id=1010">Rictus dans la brume</a> par Niels Sarys <em>(fan made)</em></li>
<li><a href="http://www.terresetranges.net/forums/viewtopic.php?id=1003">DIET LIFE</a> & <a href="http://terresetranges.net/forums/viewtopic.php?id=923">Darkline</a> par DARKFARM <em>(fan made)</em></li>
</ul>Porte, monstre, trophée doré2020-01-10T19:30:00+01:002020-01-10T19:30:00+01:00Lucas Cimontag:chezsoi.org,2020-01-10:/lucas/blog/porte-monstre-trophee-dore.html<p><img alt="Image de couverture du jeu Trophée Doré" src="images/2020/01/trophee-dore.png"></p>
<p>L'un des autres jeux que j'ai eu le plaisir de tester la semaine dernière était <strong>Trophée Dorée</strong>.
Il s'agit de <a href="https://nicolasfolliot.itch.io/trophee-dore">la traduction française par Nicolas Folliot</a>
d'<a href="https://www.drivethrurpg.com/product/293716/">un jeu de Jesse Ross initialement paru dans le zine Codex</a>.
Son <em>pitch</em> est simple et efficace :</p>
<blockquote>
<p>Un groupe de chasseurs de trésors …</p></blockquote><p><img alt="Image de couverture du jeu Trophée Doré" src="images/2020/01/trophee-dore.png"></p>
<p>L'un des autres jeux que j'ai eu le plaisir de tester la semaine dernière était <strong>Trophée Dorée</strong>.
Il s'agit de <a href="https://nicolasfolliot.itch.io/trophee-dore">la traduction française par Nicolas Folliot</a>
d'<a href="https://www.drivethrurpg.com/product/293716/">un jeu de Jesse Ross initialement paru dans le zine Codex</a>.
Son <em>pitch</em> est simple et efficace :</p>
<blockquote>
<p>Un groupe de chasseurs de trésors part en expédition dans des lieux hostiles.</p>
</blockquote>
<p>J'ai découvert le jeu via son jumeau, Trophée Sombre, sur lequel je reviendrai en fin d'article,
et dont j'avais lu la version originale dans le zine <a href="https://www.drivethrurpg.com/product/268198/Codex--Dark-2-Dec-2018">Codex - Dark 2</a>.
La version "Dorée" m'avait semblé très intéressante à la lecture pour la session que j'organisais,
avec quatre joueurs, dont 2 "vétérans" du JdR et une joueuse dont ce serait la première partie.
Un jeu aux règles assez simples, où la création de personnage est rapide et assez <em>fun</em> car aléatoire,
avec un accent <em>old chool</em> porté sur l'exploration, et des principes / conseils pour mener le jeu qui me parlaient bien :</p>
<blockquote>
<p><strong>Énonce des problèmes sans solution évidente.</strong> Pas besoin
d'avoir toutes les réponses. Laisse la possibilité à tes joueuses
d'être créatives, puis récompense leur ingéniosité.</p>
<p><strong>Pose des questions suggestives et sers-toi des réponses</strong>. Fais
des joueuses les co-créatrices du monde pour les impliquer. Pose leur
des questions sur leurs personnages, ce qu'ils savent et
observent. Rends-les complices de leur propre destin.</p>
</blockquote>
<p>J'ai même été assez inspiré par le donjon suggéré dans la traduction française comme première aventure,
<a href="https://www.whidou.fr/la-tombe-des-rois-serpents.html">La tombe des rois serpents</a> de Skerples,
admirablement traduit par Matthieu Bé, Bruno Bord et Whidou sous forme de PDF de 22 pages.
Bien que très archétypal d'un donjon D&D,
il a été conçu et pensé comme un tutoriel, et j'imaginais déjà à la lectures mes PJs tenter plein d'approches
pour franchir les différents pièges et obstacles qu'il renferme !</p>
<p><img alt="Carte complète de la Tombe des Rois Serpents" src="images/2020/01/tomb-of-the-serpent-kings.png"></p>
<p>J'en profite pour exprimer de chaleureux remerciements aux formidables auteurs et traducteurs de ces jeux et scénarios !
Leurs maquettes sont impeccables, le contenu très clair et plein d'imagination,
et ils ont très généreusement choisi de rendre le résultat de cet énorme boulot <strong>gratuit</strong>,
accessible et jouable à tout le monde !</p>
<p>Trophée Doré donc, est un jeu d'une vingtaine de pages, pour une meneuse de jeu et quelques joueuses,
se jouant avec plusieurs dés à 6 faces noirs & blancs.
Notre session a duré entre 4h et 6h, création de chasseur de trésor comprise,
qui était peut-être la phase la plus amusante d'ailleurs.</p>
<p>Car non, autant l'admettre tout de suite, je n'ai pas été complètement emballé par Trophée Doré.</p>
<p>Pourtant il contient plein de bonnes idées !
Les <strong>tables de génération de personnage</strong> (Métier, Origine, Rituel, Équipement, But)
sont très imaginatives, et fournissent plein de pistes ouvertes pour que les PJs imaginent des PJs hauts en couleur.</p>
<p>C'est sûrement très personnel et subjectif, mais j'ai en particulier adoré le principe des <strong>Rituels</strong>,
des pouvoirs très génériques et donc puissants, où les joueurs sont complètement libres d'imaginer
comment ils se déroulent et leurs effets.
Je trouve toujours génial de donner à un joueur une capacité dont il ne connaît pas les limites,
puis le voir tester plein de choses pour les découvrir, y compris des usages auxquels je n'aurais jamais pensé !
Voici un exemple illustrant cela : dans plusieurs des premières salles du donjon exploré,
une statuette piégée reposait, contenant un gaz toxique. L'un des joueurs avait comme pouvoir de creuser la rocher,
et une autre celui d'inverser la gravité. Après quelques essais ils ont fini par combiner leurs pouvoirs
pour établir un <em>modus operanti</em> de désamorçage des statuettes, en isolant le gaz dans une poche dans le plafond de chaque salle !</p>
<p>Un autre point de la phase de création de chasseur de trésor sur lequel je suis assez indécis :
le Havresac aléatoire avec 3 équipements imposés, <em>a priori</em> assez inutiles.
Autant c'est une intéressante forme de contrainte créative, et certains de mes joueurs ont réussi à employer
filet de pêche, bouteille, bougies ou sifflet durant la partie, autant certains Havresac donnent un excessivement
"gentillet / enfantin" à l'univers du jeu : sachet de bonbon, nourriture pour chèvre domestique, petite licorne en bois...</p>
<p>Le système des jets est très inspiré des jeux <em>powered by the Apocalypse</em>, avec entre autre la mécanique du <em>Devil's bargain</em>
qui a très bien marché durant cette session.
Il y a néanmoins des différences notables, et notamment qu'<strong>un joueur lance autant de dés qu'il peut justifier de Talent,
d'objet utile, ou d’élément de l'environnement exploité</strong>.
C'est une mécanique que j'aime beaucoup, qui marche bien je trouve avec des nouveaux joueurs,
mais qui est un peu limité dans Trophée Doré par la limite à 4 Talents par PJ :
un nombre de compétences plus grand aurait fourni plus d'inspirations aux joueurs !</p>
<p>Deux dernières excellentes trouvailles à mentionner :</p>
<ul>
<li>le Thème fort associé à chaque lieu exploré. J'ai surtout adoré l'exemple de création d'une incursion avec comme Thèmes
<strong>le Sommeil, l'Eau et les Masques</strong> à ma lecture de <em>Dark Trophy</em>.
Définir un thème en amont donne je crois beaucoup de cohérence au lieu ainsi conçu.
Je pense même que l'idée pourrait être généralisé lors d'une partie <em>one-shot</em>,
avec un thème défini collectivement avec tous les joueurs, et des personnages / lieux / intrigues qui suivent ces thématiques.</li>
<li>le fort accent mis sur la découverte des Faiblesses des monstres rencontrés, avec même une feuille de bestiaire
mise à disposition des joueurs. Je trouve génial d'établir comme règle que chaque monstre que croiseront les PJs
possède un point faible qu'ils peuvent trouver et exploiter !
En plus, avec des fans de <em>Monster Hunter</em> parmi les joueurs, je pensais faire des heureux 😏
Néanmoins le scénario de base ne contient peut-être pas assez de créatures avec des points faibles originaux
(appendice, phobie, sens atrophié...) pour exploiter à fond cette idée. Comme il est de notoriété commune que les squelettes
ne supportent pas les attaques contondantes, mes PJs n'ont pas eu à chercher très loin !
Enfin j'ai trouvé dommage que le bestiaire ne soit pas plus impliqué dans la mécanique de jeu :
même s'il prend sûrement plus sont intérêt après plusieurs lieux explorés, il aurait été intéressant de l'inclure un peu
plus dans la mécanique de jeu, ou au moins de rendre plus important le bonus aux dés lié à l'exploitation d'une Faiblesse.</li>
</ul>
<p><a href="https://www.artstation.com/artwork/VXe5N"><img alt="Ancient Ritual par Erich Liotta" src="images/2020/01/erich-liotta-3-ancient-ritual-colour-lr.jpg"></a></p>
<p>Pourquoi alors, avec tous ces ingrédients bien dosés, est-ce que je ne compte plus rejouer à <em>Trophée Doré</em> ?</p>
<p>Première raison majeure : <strong>les sous-systèmes de jets d'Exploration et de Combat</strong>.
Le premier simule l'exploration d'un lieu via des jets, et permet de passer automatiquement au Décor suivant
à partir d'un certain seuil de jetons obtenu sur des réussites.
<strong>MAIS QUEL INTÉRÊT ??!</strong> Peut-être pour accélérer le rythme d'une partie, ou éviter la frustration de joueurs bloqués
et en manque d'idées ? Quoi qu'il en soit, cette mécanique va complètement à l'opposé, je trouve,
de l'idée de laisser les joueurs expérimenter avec l'environnement décrit par le MJ,
et imaginer des solutions pour franchir les obstacles ! <strong>Cette mécanique de jeu ne récompense absolument pas
cette logique <em>OSR</em>, et simplifie bien trop la progression d'un lieu à un autre</strong> !
En plus, l'avancée des PJs devient complètement tributaire des dés :
s'ils ne font que des bons jets, ils ne croiseront même jamais la créature ou le piège que le MJ leur avait concocté !
Ce qui peut complètement "casser" un scénario contenant des étapes indispensables.</p>
<p>Le sous-système de Combat, quant à lui, simule les combat de groupe contre un monstre.
Il est, lui aussi, très frustrant.
<strong>Peu importe les actions individuelles des PJs, leur ingéniosité ou leur prise de risque,
tout cela ne rentrera absolument pas en compte dans l'issu du combat</strong>.
Je ne vais pas m’appesantir plus en détails sur tout le mal que je peux penser de ce type de système,
mais ce n'est franchement ni ma tasse de thé, ni très <em>OSR</em> je trouve.</p>
<p>J'ai beaucoup moins de griefs envers le système de jets principal du jeu, les jets de Risque,
plutôt simple et efficace. Il reste tout de même un point que j'ai trouvé quelque peu raté :
le système des points de Perte. Sur la partie <em>one-shot</em> que nous avons joué en tout cas,
mes joueurs ne se sentaient absolument pas en danger. La faute un peu au MJ sûrement (votre serviteur),
qui n'a pas du assez leur faire ressentir les danger qui rôdait autour d'eux durant cette exploration,
et qui, de manière générale, a du mal à jouer la vie ou la mort d'un PJ sur un lancé de dés en début de session,
à cause d'un piège déclenché. Le constat est en tout bien cas là au final :
ils n'ont toutefois jamais tenté de relancer un jet avec un dé noir, ni même au final perdu le moindre point de Perte.</p>
<p>Derniers petits points de déception :</p>
<ul>
<li>les buts des personnages, déterminés aléatoirement à la création, ne sont pas dû tout intégré ensuite dans les règles.
C'est assez dommage, aucune mécanique ni conseil de jeu ne permet de suivre la progression des joueurs vers leurs objectifs,
tout étant ramené à une quantité de magot à amasser.</li>
<li>je ne m'y attendais pas tant à la lecture <em>La tombe des rois serpents</em> était intéressant,
mais une exploration de donjon ça peut devenir très vite monotone et rébarbatif...</li>
</ul>
<p>Voici donc mon petit bilan sur ce jeu en définitive :</p>
<table>
<thead>
<tr>
<th>Ce que j'ai aimé</th>
<th>Ce que j'ai moins aimé</th>
</tr>
</thead>
<tbody>
<tr>
<td>➕ lancer autant de dés qu'on peut justifier d'atouts</td>
<td>➖ la mécanique des dés de Perte</td>
</tr>
<tr>
<td>➕ <em>Devil's bargain</em></td>
<td>➖ buts des personnages très déconnectés de l'exploration d'un donjon</td>
</tr>
<tr>
<td>➕ les éléments d'équipement imposés</td>
<td>➖ les éléments d'équipement imposés</td>
</tr>
<tr>
<td>➕ la découverte des faiblesses des monstres</td>
<td>➖ les sous-systèmes d'exploration & combat</td>
</tr>
<tr>
<td>➕ les Rituels très libres et puissants</td>
<td>➖ la répétitivité inhérente à une exploration de donjon</td>
</tr>
<tr>
<td>➕ les tables de génération de personnage</td>
<td></td>
</tr>
<tr>
<td>➕ un Thème associé à chaque incursion</td>
<td></td>
</tr>
</tbody>
</table>
<p>La nouvelle joueuse m'a dit avoir apprécié cette séance de découverte, et notamment de découvrir la carte du donjon petit à petit,
que je traçais sur une feuille au fur et à mesure de la progression des PJs.</p>
<p>Chose amusante, la partie s'est terminée sur une magnifique <strong>trahison</strong> de la part d'un des PJs.
Un des joueurs, sentant la fin de partie approcher, n'a pas résister à utiliser le sort de <strong>Sommeil</strong>
qu'ils venaient de découvrir pour endormir ses compagnons et leur faire les poches !</p>
<p><em>Trophée Doré</em> fait partie d'<a href="https://trophyrpg.com">un diptyque</a> avec un autre jeu, <em>Trophée Sombre</em>
traduit par <a href="https://nicolasfolliot.itch.io/trophee-sombre">Matthieu Braboszcz, Angela Quidam, Guy Blavin et Nicolas Folliot</a>.
Plus court (14 pages), il dépeint une ambiance plus sombre, où les personnages courent inéluctablement à leur Perte
alors qu'ils se rapprochent des richesses qu'ils convoitent.
Comme il ne semble avoir aucun des défauts que je trouve à Trophée Doré, je le testerai peut-être à l'occasion !</p>
<p><img alt="Image de couverture du jeu Trophée Sombre" src="images/2020/01/trophee-sombre.png"></p>Courir. Mourir. Recommencer.2020-01-06T21:00:00+01:002020-01-06T21:00:00+01:00Lucas Cimontag:chezsoi.org,2020-01-06:/lucas/blog/run-die-repeat.html<p>Vendredi dernier, j'ai eu l'occasion de tester pas moins de 3 nouveaux jeux de rôle avec des amis.
Je souhaite vous présenter le dernier auquel nous avons joué, à minuit passé :
<a href="https://labrysgames.itch.io/run-die-repeat">Run Die Repeat</a> de Labrys Games !</p>
<p>J'ai tellement apprécié le jeu, que j'en ai fait une traduction en français …</p><p>Vendredi dernier, j'ai eu l'occasion de tester pas moins de 3 nouveaux jeux de rôle avec des amis.
Je souhaite vous présenter le dernier auquel nous avons joué, à minuit passé :
<a href="https://labrysgames.itch.io/run-die-repeat">Run Die Repeat</a> de Labrys Games !</p>
<p>J'ai tellement apprécié le jeu, que j'en ai fait une traduction en français.</p>
<p>Vous pouvez y accéder en cliquant sur l'aperçu ci-dessous (PDF, 37 Ko) :
<a href="images/jdr/RunDieRepeat-FR.pdf"><img alt="Run Die Repeat" src="images/2020/01/RunDieRepeat.jpg"></a></p>
<p>Le jeu se classe dans la catégorie des mini jeux de rôle monopage, ne nécessitant aucune préparation,
conçus pour de courtes sessions effrénées, improvisées n’importe où, n’importe quand.</p>
<p>Le principe :</p>
<blockquote>
<p>Vous êtes piégé dans un cauchemar, pourchassé par des monstres.
Chaque fois que vous mourrez, vous recommencez exactement au même endroit, au même instant.
Trouverez-vous comment vous échapper ?</p>
</blockquote>
<p>Les règles sont ultra simples, mais fonctionnent formidablement bien !
Le mélange de temps limité, de <em>reboot</em> systématiquement à l'identique,
d'exercice de mémorisation et de joueur actif tournant est original et super fun !</p>
<p>Voici le pitchs de la première session de 20min que nous avons joué :</p>
<blockquote>
<p>La lumière blafarde de la pleine lune te réveilles.
Tu ouvres les yeux dans un immeuble abandonné, le long d'une falaise.
Loin, très loin, tu distingues une énorme vague s'approchant sur la mer.
Sur le mur à côté, une inscription en lettres de sang.
« Empêchez le grand prêtre de le réveiller »
Au pied du bâtiment, tu aperçois une procession d'individus se dirigeant vers un phare.</p>
</blockquote>
<p>Oui, un des joueurs était fan de <a href="https://fr.wikipedia.org/wiki/Cthulhu">Cthulhu</a> 😉
Au final, ils ont évité les chiens lancés à leurs trousses en passant sur le toit d'à côté,
en prenant au passage un parpaing pour assommer un sbire faisant le guet au pied de cette maison.
Ils ont ensuite ramassé son fusil, pris sa voiture, foncé à toute berzingue vers le phare,
traversé un mur de feu invoqué par les cultistes, descendu les marches du phare jusqu'à une crique,
mais pas réussi à descendre le grande prêtre avant qu'il ne remette l'artefact aux <a href="https://fr.wikipedia.org/wiki/Ceux_des_profondeurs">Profonds</a>.</p>
<figure role="group">
<img alt="Cthulhu looming" src="images/2020/01/Spawn_of_the_Stars_by_Sofyan_Syarief.jpg">
<figcaption>
<a href="https://commons.wikimedia.org/wiki/File:Spawn_of_the_Stars_by_Sofyan_Syarief.jpg">
Spawn of the Stars by Sofyan Syarief
</a>
</figcaption>
</figure>
<p>Pour la seconde session de 20min, j'ai voulu justifier un peu le mécanisme de boucle temporelle :</p>
<blockquote>
<p>Tu es en cellule au commissariat, tu viens d'être arrêté pour un crime où tu risques perpétuité.
Soudain, un paquet craft atterrit à tes pieds, lancé de l'extérieur à travers une lucarne.
Dedans, tu trouves une machine avec <a href="images/2020/01/convecteur-temporel-app-store.jpg">un étrange mécanisme</a>
incluant un compte à rebours de 20min, ainsi que ce mot : « Fait sortir Loubianov par l'arrière-court »</p>
</blockquote>
<p>Cette fois-ci, les joueurs ont mis un peu de temps avant de trouver comment sortir de leur cellule...
Ils ont finalement demander à passer lors coup de fil réglementaire,
fait une balayette au flic qui les escortait, puis l'ont pris en otage avec son propre flingue,
pour ensuite aller chercher Loubiakov en salle d'interrogatoire à l'étage.
Les choses se sont ensuite beaucoup compliqué, et Loubiakov à fini par traverser une fenêtre pile à la fin du minuteur...
pour atterrir dans la cour intérieure !</p>
<p>Le thème de la course poursuite me rappelle un peu <a href="/lucas/blog/tag/psirun.html">Psi*Run</a> de Meguey Baker,
mais la mécanique du jeu m'a surtout évoqué un film d'action injustement méconnu avec Nicolas Cage, <em>Next</em>,
où le héro est capable de prédire l'avenir immédiat :</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/OwIFRm7sy8E" allowfullscreen></iframe>
<p>Je trouve l'idée géniale, et très fun à employer en jeu de rôle !
J'avais déjà octroyé la capacité de prévoir le futur immédiat à un PJ de ma plus grosse campagne,
et ça avait été source de scènes mythiques.</p>
<p>En tout cas, ce jeu me parait idéal pour le fins de soirées JdR où personne n'est trop pressé de partir,
où même en convention !</p>
<p>En termes de règles sur les jets de dés, durant nos parties le « 6 ou rien » m'a parfois paru un peu drastique.
J'ai donc occasionnellement considéré qu'une action bien pensée était une réussite automatique,
ou demandé un jet <strong>4+</strong> / <strong>5+</strong>.</p>
<p><strong>EDIT [2020/03/14]</strong> : la Rolisterie a enregistré quelques parties de <em>Run. Die. Repeat.</em> sous forme de podcast !
<a href="https://anchor.fm/rolisterie/episodes/S06E01%E2%80%94Les-argouliens-attaquent%E2%80%94Run%E2%80%93Die%E2%80%93Repeat-earf0c">Cliquez ici</a> pour y accéder.</p>
<p><strong>EDIT [2020/03/24]</strong> : j'ai conçu quelques courts scénarios pour le jeu,
retrouvez-les dans les autres articles avec le <a href="tag/run-die-repeat.html">tag run-die-repeat</a>.</p>
<style>
article iframe { display: block; margin: 0 auto; }
</style>Pandemic: Worldwide Research Program & Mass Migrations2019-11-23T15:00:00+01:002019-11-23T15:00:00+01:00Lucas Cimontag:chezsoi.org,2019-11-23:/lucas/blog/pandemic-worldwide-research-program-and-mass-migrations.html<p><img alt="Photo du plateau de jeu de Pandémie" src="images/2019/11/Pandemic-Photo-Header.jpg"></p>
<p><em>(English version below)</em></p>
<p>Il y a trois ans, <a href="resources-pour-mr-jack-et-pandemie.html">j'évoquais sur ce blog</a>
quelques variantes pour le jeu de société <a href="https://boardgamegeek.com/boardgame/30549/pandemic">Pandémie</a>.</p>
<p>Voici le moment de vous en présenter deux que j'ai confectionné récemment,
et dont les <em>playtests</em> étaient suffisamment amusants pour que je les mette en page,
afin de les partager …</p><p><img alt="Photo du plateau de jeu de Pandémie" src="images/2019/11/Pandemic-Photo-Header.jpg"></p>
<p><em>(English version below)</em></p>
<p>Il y a trois ans, <a href="resources-pour-mr-jack-et-pandemie.html">j'évoquais sur ce blog</a>
quelques variantes pour le jeu de société <a href="https://boardgamegeek.com/boardgame/30549/pandemic">Pandémie</a>.</p>
<p>Voici le moment de vous en présenter deux que j'ai confectionné récemment,
et dont les <em>playtests</em> étaient suffisamment amusants pour que je les mette en page,
afin de les partager avec vous :</p>
<h3>Programme de Recherche Mondial</h3>
<blockquote>
<p>À travers le monde, chercheurs et experts en bactériologie, immunologie et épidémiologie
travaillent d’arrache-pied pour trouver des vaccins contre les épidémies qui ravagent le globe.
Tandis que vous œuvrez à maintenir l’épidémie, réussirez-vous à collecter tous leurs travaux
pour résoudre la crise ?</p>
</blockquote>
<div class="imgs">
<a href="images/jeux/Pandemic%20Scenario%20Worldwide%20Research%20Program%20FR%20v1.0.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2019/11/thumbnail_WorldwideResearchProgram.jpg">
<figcaption><em>Programme de Recherche Mondial</em> (PDF 1 page 6,8 Mo)</figcaption>
</figure>
</a>
<a href="images/jeux/Pandemic%20Scenario%20Worldwide%20Research%20Program%20FR%20v1.0%20printerfriendly.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2019/11/thumbnail_lightweigth_WorldwideResearchProgram.jpg">
<figcaption>(printer-friendly PDF 1 page 5,9 Mo)</figcaption>
</figure>
</a>
</div>
<h3>Migrations de Masse</h3>
<blockquote>
<p>La nouvelle de la pandémie mondiale s’est répandue comme une traînée de poudre.
Tous les médias ont relayé l’information, provoquant une panique générale,
si bien que des exodes de masse se produisent régulièrement vers des villes
que la population croit épargnées.</p>
</blockquote>
<div class="imgs">
<a href="images/jeux/Pandemic%20Scenario%20Mass%20Migrations%20FR%20v1.0.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2019/11/thumbnail_MassMigrations.jpg">
<figcaption><em>Migrations de Masse</em> (PDF 1 page 1,4 Mo)</figcaption>
</figure>
</a>
<a href="images/jeux/Pandemic%20Scenario%20Mass%20Migrations%20FR%20v1.0%20printerfriendly.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2019/11/thumbnail_lightweigth_MassMigrations.jpg">
<figcaption>(printer-friendly PDF 1 page 0,9 Mo)</figcaption>
</figure>
</a>
</div>
<p>La mise en page a été réalisée avec <a href="https://libreoffice.org/discover/draw/">LibreOffice Draw</a>
et <a href="https://www.gimp.org">Gimp</a>. Merci beaucoup à Laëtitia en particulier pour les parties de test !</p>
<p>Je serais ravis de savoir ce que vous en avez pensé si vous jouez à l'une de ces variantes !
Si c'est le cas, laissez-moi un commentaire au bas de cette page.</p>
<hr>
<p>I'd like to introduce you to two variants for the board game <a href="https://boardgamegeek.com/boardgame/30549/pandemic">Pandemic</a>
that I recently created:</p>
<h3>Worldwide Research Program</h3>
<blockquote>
<p>Throughout the world, researchers and experts in bacteriology, immunology and epidemiology
work relentlessly to find vaccines to the terrible epidemics ravaging the globe.
While you strive to contain plagues, will you succeed in collecting
those scientists work in order to solve the crisis ?</p>
</blockquote>
<div class="imgs">
<a href="images/jeux/Pandemic%20Scenario%20Worldwide%20Research%20Program%20EN%20v1.2.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2019/11/thumbnail_WorldwideResearchProgram.jpg">
<figcaption><em>Worldwide Research Program</em> (PDF 1 page 6,8 MB)</figcaption>
</figure>
</a>
<a href="images/jeux/Pandemic%20Scenario%20Worldwide%20Research%20Program%20EN%20v1.2%20printerfriendly.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2019/11/thumbnail_lightweigth_WorldwideResearchProgram.jpg">
<figcaption>(printer-friendly PDF 1 page 5,9 MB)</figcaption>
</figure>
</a>
</div>
<h3>Mass Migrations</h3>
<blockquote>
<p>The news of the worldwide spread like wildfire. All the media relayed the information,
causing widespread panic, so much that mass exoduses started to occur,
to cities that the population believes spared…</p>
</blockquote>
<div class="imgs">
<a href="images/jeux/Pandemic%20Scenario%20Mass%20Migrations%20EN%20v1.1.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2019/11/thumbnail_MassMigrations.jpg">
<figcaption><em>Mass Migrations</em> (PDF 1 page 1,4 MB)</figcaption>
</figure>
</a>
<a href="images/jeux/Pandemic%20Scenario%20Mass%20Migrations%20EN%20v1.1%20printerfriendly.pdf">
<figure>
<img alt="Aperçu miniature" src="images/2019/11/thumbnail_lightweigth_MassMigrations.jpg">
<figcaption>(printer-friendly PDF 1 page 0,9 MB)</figcaption>
</figure>
</a>
</div>
<p>The typesetting was done with <a href="https://libreoffice.org/discover/draw/">LibreOffice Draw</a>
and <a href="https://www.gimp.org">Gimp</a>.</p>
<p>I'd love to have your feedback if you play one of those variants !
If so, please leave a comment below.</p>
<p><strong>EDIT [2019/11/30]</strong>: all the LibreOffice source files can be found here:
<a href="https://gitlab.com/Lucas-C/board-games/tree/master/PandemicScenarios">https://gitlab.com/Lucas-C/board-games/tree/master/PandemicScenarios</a></p>
<p><strong>EDIT [2019/12/15]</strong>: Michael Schlindwein kindly sent me a German translation of the <em>Mass Migrations</em> scenario!</p>
<p><strong>EDIT [2020/01/27]</strong>: Michael also sent me a german translation of the <em>Worldwide Research Program</em> scenario.
Thank you!</p>
<p><strong>EDIT [2020/07/26]</strong>: thanks to Jared, I fixed several English mistakes in the Worldwide Research Program PDF.
A new 1.2 version is now available.</p>
<div class="imgs">
<a href="images/jeux/Pandemic%20Scenario%20Mass%20Migrations%20DE%20druckfreundlich.jpg">
<figure>
<img alt="Vorschau" src="images/jeux/Pandemic%20Scenario%20Mass%20Migrations%20DE%20druckfreundlich.jpg">
<figcaption><em>Mass Migrations DE</em> (druckfreundlich JPG image 0,8 MB)</figcaption>
</figure>
</a>
<a href="images/jeux/Pandemic%20Scenario%20Worldwide%20Research%20Program%20DE.pdf">
<figure>
<img alt="Vorschau" src="images/2019/11/thumbnail_WorldwideResearchProgram_DE.jpg">
<figcaption><em>Worldwide Research Program DE</em> (PDF 1 page 1,6 MB)</figcaption>
</figure>
</a>
</div>
<style>
.imgs {
display: flex;
justify-content: center;
align-items: center;
}
</style>Using python requests-futures to crawl all jobs on a Jenkins 4 times faster2019-10-21T23:45:00+02:002019-10-21T23:45:00+02:00Lucas Cimontag:chezsoi.org,2019-10-21:/lucas/blog/using-python-requests-futures-to-crawl-all-jobs-on-a-jenkins-4-times-faster.html<p><img alt="Jenkins logo as a ninja" src="images/2019/10/ninjenkins.svg"></p>
<!-- Source: https://wiki.jenkins.io/display/JENKINS/Logo -->
<p>At work, we needed to retrieve the full list of jobs a given <a href="https://jenkins.io">Jenkins</a> instance was hosting.</p>
<p>Our first solution was to use the <a href="https://jenkinsapi.readthedocs.io">jenkinsapi</a> Python package:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">xml.etree.ElementTree</span> <span class="k">as</span> <span class="nn">XmlElementTree</span>
<span class="kn">from</span> <span class="nn">jenkinsapi.jenkins</span> <span class="kn">import</span> <span class="n">Jenkins</span>
<span class="k">def</span> <span class="nf">get_all_jenkins_jobs</span><span class="p">(</span><span class="n">server_url</span><span class="p">):</span>
<span class="n">jenkins</span> <span class="o">=</span> <span class="n">Jenkins</span><span class="p">(</span><span class="n">server_url</span><span class="p">,</span> <span class="n">lazy</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span>
<span class="n">username</span><span class="o">=</span><span class="n">os …</span></code></pre></div><p><img alt="Jenkins logo as a ninja" src="images/2019/10/ninjenkins.svg"></p>
<!-- Source: https://wiki.jenkins.io/display/JENKINS/Logo -->
<p>At work, we needed to retrieve the full list of jobs a given <a href="https://jenkins.io">Jenkins</a> instance was hosting.</p>
<p>Our first solution was to use the <a href="https://jenkinsapi.readthedocs.io">jenkinsapi</a> Python package:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">xml.etree.ElementTree</span> <span class="k">as</span> <span class="nn">XmlElementTree</span>
<span class="kn">from</span> <span class="nn">jenkinsapi.jenkins</span> <span class="kn">import</span> <span class="n">Jenkins</span>
<span class="k">def</span> <span class="nf">get_all_jenkins_jobs</span><span class="p">(</span><span class="n">server_url</span><span class="p">):</span>
<span class="n">jenkins</span> <span class="o">=</span> <span class="n">Jenkins</span><span class="p">(</span><span class="n">server_url</span><span class="p">,</span> <span class="n">lazy</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span>
<span class="n">username</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'JENKINS_USERNAME'</span><span class="p">],</span> <span class="n">password</span><span class="o">=</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'JENKINS_PASSWORD'</span><span class="p">])</span>
<span class="n">all_jobs</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">job</span> <span class="ow">in</span> <span class="n">jenkins</span><span class="o">.</span><span class="n">jobs</span><span class="o">.</span><span class="n">values</span><span class="p">():</span>
<span class="n">config_xml_content</span> <span class="o">=</span> <span class="n">job</span><span class="o">.</span><span class="n">get_config</span><span class="p">()</span> <span class="c1"># performs an HTTP request to retrieve config.xml</span>
<span class="n">job_data</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'full_name'</span><span class="p">:</span> <span class="n">job</span><span class="o">.</span><span class="n">get_full_name</span><span class="p">(),</span> <span class="s1">'url'</span><span class="p">:</span> <span class="n">job</span><span class="o">.</span><span class="n">url</span><span class="p">}</span>
<span class="n">job_data</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">extra_job_data_from_xml</span><span class="p">(</span><span class="n">config_xml_content</span><span class="p">))</span>
<span class="n">all_jobs</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">job_data</span><span class="p">)</span>
<span class="k">return</span> <span class="n">all_jobs</span>
<span class="k">def</span> <span class="nf">extra_job_data_from_xml</span><span class="p">(</span><span class="n">config_xml_content</span><span class="p">):</span>
<span class="n">xml_config</span> <span class="o">=</span> <span class="n">XmlElementTree</span><span class="o">.</span><span class="n">fromstring</span><span class="p">(</span><span class="n">config_xml_content</span><span class="p">)</span>
<span class="n">git_repo</span> <span class="o">=</span> <span class="n">xml_config</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s1">'./definition/scm/userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/url'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">git_repo</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">return</span> <span class="p">{}</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s1">'scm'</span><span class="p">:</span> <span class="n">git_repo</span><span class="o">.</span><span class="n">text</span><span class="p">,</span>
<span class="s1">'scm_http'</span><span class="p">:</span> <span class="s1">'https://'</span> <span class="o">+</span> <span class="n">git_repo</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">'git@'</span><span class="p">,</span> <span class="s1">''</span><span class="p">)</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">':'</span><span class="p">,</span> <span class="s1">'/'</span><span class="p">)</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">'.git'</span><span class="p">,</span> <span class="s1">''</span><span class="p">),</span>
<span class="s1">'branch'</span><span class="p">:</span> <span class="n">xml_config</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s1">'./definition/scm/branches/hudson.plugins.git.BranchSpec/name'</span><span class="p">)</span><span class="o">.</span><span class="n">text</span><span class="p">,</span>
<span class="s1">'jenkinsfile'</span><span class="p">:</span> <span class="n">xml_config</span><span class="o">.</span><span class="n">find</span><span class="p">(</span><span class="s1">'./definition/scriptPath'</span><span class="p">)</span><span class="o">.</span><span class="n">text</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div>
<p>This solution is nice and short.
But it has a main drawback: it is <strong>very</strong> slow.</p>
<p>Hence I decided to bypass the <code>jenkinsapi</code> package and make direct HTTP calls to the Jenkins API,
but this time using <a href="https://github.com/ross/requests-futures">requests-futures</a> to perform requests asynchronously:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">concurrent.futures</span> <span class="kn">import</span> <span class="n">as_completed</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">from</span> <span class="nn">requests.adapters</span> <span class="kn">import</span> <span class="n">HTTPAdapter</span>
<span class="kn">from</span> <span class="nn">requests_futures.sessions</span> <span class="kn">import</span> <span class="n">FuturesSession</span>
<span class="n">FOLDER_CLASSES</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'com.cloudbees.hudson.plugins.folder.Folder'</span><span class="p">,</span>
<span class="s1">'org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get_all_jenkins_jobs_async</span><span class="p">(</span><span class="n">server_url</span><span class="p">):</span>
<span class="k">with</span> <span class="n">FuturesSession</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
<span class="n">session</span><span class="o">.</span><span class="n">auth</span> <span class="o">=</span> <span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'JENKINS_USERNAME'</span><span class="p">],</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'JENKINS_PASSWORD'</span><span class="p">])</span>
<span class="n">session</span><span class="o">.</span><span class="n">mount</span><span class="p">(</span><span class="s1">'https://'</span><span class="p">,</span> <span class="n">HTTPAdapter</span><span class="p">(</span><span class="n">max_retries</span><span class="o">=</span><span class="mi">3</span><span class="p">))</span>
<span class="n">all_jobs_paths</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">folder_paths_to_crawl</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'/'</span><span class="p">]</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="n">folder_paths_to_crawl</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'Async breadth-first pass </span><span class="si">%s</span><span class="s1"> - Processing </span><span class="si">%s</span><span class="s1"> folders'</span> <span class="o">%</span> <span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">folder_paths_to_crawl</span><span class="p">)))</span>
<span class="n">next_folder_paths_to_crawl</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">futures</span> <span class="o">=</span> <span class="p">[</span><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">server_url</span> <span class="o">+</span> <span class="n">folder_path</span> <span class="o">+</span> <span class="s1">'/api/python'</span><span class="p">,</span>
<span class="n">params</span><span class="o">=</span><span class="p">{</span><span class="s1">'tree'</span><span class="p">:</span> <span class="s1">'jobs[name,color,url]'</span><span class="p">})</span>
<span class="k">for</span> <span class="n">folder_path</span> <span class="ow">in</span> <span class="n">folder_paths_to_crawl</span><span class="p">]</span>
<span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
<span class="n">resp</span> <span class="o">=</span> <span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span>
<span class="n">path_prefix</span> <span class="o">=</span> <span class="n">resp</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">path_url</span><span class="p">[:</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="s1">'/api/python?tree=jobs%5Bname%2Ccolor%2Curl%5D'</span><span class="p">)]</span>
<span class="k">for</span> <span class="n">job</span> <span class="ow">in</span> <span class="n">resp</span><span class="o">.</span><span class="n">json</span><span class="p">()[</span><span class="s1">'jobs'</span><span class="p">]:</span>
<span class="n">job_path</span> <span class="o">=</span> <span class="n">path_prefix</span> <span class="o">+</span> <span class="s1">'/job/'</span> <span class="o">+</span> <span class="n">job</span><span class="p">[</span><span class="s1">'name'</span><span class="p">]</span>
<span class="k">if</span> <span class="n">job</span><span class="p">[</span><span class="s1">'_class'</span><span class="p">]</span> <span class="ow">in</span> <span class="n">FOLDER_CLASSES</span><span class="p">:</span>
<span class="n">next_folder_paths_to_crawl</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">job_path</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">all_jobs_paths</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">job_path</span><span class="p">)</span>
<span class="n">folder_paths_to_crawl</span> <span class="o">=</span> <span class="n">next_folder_paths_to_crawl</span>
<span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'Now retrieving & parsing all config.xml files (</span><span class="si">%s</span><span class="s1">)'</span> <span class="o">%</span> <span class="nb">len</span><span class="p">(</span><span class="n">all_jobs_paths</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">response_hook</span><span class="p">(</span><span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="n">_</span><span class="p">,</span> <span class="o">**</span><span class="n">__</span><span class="p">):</span>
<span class="s1">'This hook is executed in a dedicated thread'</span>
<span class="n">job_path</span> <span class="o">=</span> <span class="n">resp</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">path_url</span><span class="p">[:</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="s1">'config.xml'</span><span class="p">)]</span>
<span class="n">resp</span><span class="o">.</span><span class="n">data</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'full_name'</span><span class="p">:</span> <span class="s1">'/'</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">frag</span> <span class="k">for</span> <span class="n">frag</span> <span class="ow">in</span> <span class="n">job_path</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span> <span class="k">if</span> <span class="n">frag</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">(</span><span class="s1">''</span><span class="p">,</span> <span class="s1">'job'</span><span class="p">)),</span>
<span class="s1">'url'</span><span class="p">:</span> <span class="n">server_url</span> <span class="o">+</span> <span class="n">job_path</span><span class="p">,</span>
<span class="p">}</span>
<span class="n">resp</span><span class="o">.</span><span class="n">data</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">extra_job_data_from_xml</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">text</span><span class="p">))</span>
<span class="n">session</span><span class="o">.</span><span class="n">hooks</span><span class="p">[</span><span class="s1">'response'</span><span class="p">]</span> <span class="o">=</span> <span class="n">response_hook</span>
<span class="n">all_jobs</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">futures</span> <span class="o">=</span> <span class="p">[</span><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">server_url</span> <span class="o">+</span> <span class="n">job_path</span> <span class="o">+</span> <span class="s1">'/config.xml'</span><span class="p">)</span> <span class="k">for</span> <span class="n">job_path</span> <span class="ow">in</span> <span class="n">all_jobs_paths</span><span class="p">]</span>
<span class="k">for</span> <span class="n">future</span> <span class="ow">in</span> <span class="n">as_completed</span><span class="p">(</span><span class="n">futures</span><span class="p">):</span>
<span class="n">all_jobs</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">future</span><span class="o">.</span><span class="n">result</span><span class="p">()</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="k">return</span> <span class="n">all_jobs</span>
</code></pre></div>
<p>The resulting code is definitively more verbose, but at least 4 times faster from my tests !</p>
<p>Now a word on the algorithmic approach taken here:
we need to crawl <a href="https://en.wikipedia.org/wiki/Tree_(graph_theory)#Rooted_tree">a tree starting from its root</a>.
For every node, if it is a leaf (an actual <em>WorkflowJob</em>) we collect it,
else we need to continue traversing its children.</p>
<p>My initial idea was to start new <a href="https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Future">concurrent Futures</a>
for every child <strong>inside its parent <code>Future</code> callback</strong>. However this adds a lot of programmatic complexity
(<a href="https://github.com/python/cpython/blob/master/Lib/concurrent/futures/thread.py#L168"><code>cannot schedule new futures after shutdown</code></a> errors,
an added difficulty to wait on the last <code>Future</code> completion...).</p>
<p>In the end, I realized that because our Jenkins job tree has a very low depth,
a <a href="https://en.wikipedia.org/wiki/Breadth-first_search">breadth-first tree traversal</a>
was a very good approach, with a known amount of <code>Future</code> requests being triggered for every depth level of the tree.
This is the solution implemented above.</p>
<p>I'd be happy to know if you already used <code>requests-futures</code> in such kind of tree-crawling scenario
(and if you compared it to other solutions),
or to answer any other feedback / question you may have.</p>
<p>PS: Many thanks to Vincent Lae for the initial idea ! 😉</p>GitHub project statistics and Python interactive coding2019-10-14T08:00:00+02:002019-10-14T08:00:00+02:00Lucas Cimontag:chezsoi.org,2019-10-14:/lucas/blog/github-project-statistics-and-python-interactive-coding.html<div style="text-align:center;">
<iframe src="https://chezsoi.org/lucas/blog/images/2019/10/github-stats.html">
<p>Iframes not supported. Click on the link below to access the graphs.</p>
</iframe></div>
<p>The <code>iframe</code> above displays some graphs I've built last week,
in order to get some insight on some GitHub projects <em>issues</em> & <em>pull requests</em> evolution.
They are directly inspired by <a href="https://nf-co.re/stats#github_prs">nf-core project activity statistics</a>.</p>
<p class="centered-content">
<a target="_blank" href="https://chezsoi.org/lucas/blog/images/2019/10/github-stats.html">
Click here to open those …</a></p><div style="text-align:center;">
<iframe src="https://chezsoi.org/lucas/blog/images/2019/10/github-stats.html">
<p>Iframes not supported. Click on the link below to access the graphs.</p>
</iframe></div>
<p>The <code>iframe</code> above displays some graphs I've built last week,
in order to get some insight on some GitHub projects <em>issues</em> & <em>pull requests</em> evolution.
They are directly inspired by <a href="https://nf-co.re/stats#github_prs">nf-core project activity statistics</a>.</p>
<p class="centered-content">
<a target="_blank" href="https://chezsoi.org/lucas/blog/images/2019/10/github-stats.html">
Click here to open those graphs in another tab</a>
</p>
<p>In this post, I will explain step by step how I wrote a Python script that can generate those graphs
for any GitHub project. This script is available <a href="https://gist.github.com/Lucas-C/5c9730756e7b3f795c6d121d38a9ce88">as a gist</a>, but the last part of this article is structured as a practical exercise, to guide you to write it yourself.</p>
<ul>
<li><a href="github-project-statistics-and-python-interactive-coding.html#goal--methodology">Goal & methodology</a></li>
<li><a href="github-project-statistics-and-python-interactive-coding.html#minimal-starting-code">Minimal starting code</a></li>
<li><a href="github-project-statistics-and-python-interactive-coding.html#libraries">Libraries</a><ul>
<li><a href="github-project-statistics-and-python-interactive-coding.html#python-libraries">Python Libraries</a></li>
<li><a href="github-project-statistics-and-python-interactive-coding.html#javascript-libraries">Javascript Libraries</a></li>
</ul>
</li>
<li><a href="github-project-statistics-and-python-interactive-coding.html#code-structure">Code structure</a><ul>
<li><a href="github-project-statistics-and-python-interactive-coding.html#main">main</a></li>
<li><a href="github-project-statistics-and-python-interactive-coding.html#parse-args">parse_args</a></li>
<li><a href="github-project-statistics-and-python-interactive-coding.html#build-page">build_page</a></li>
<li><a href="github-project-statistics-and-python-interactive-coding.html#watch-and-serve">watch_and_serve</a></li>
<li><a href="github-project-statistics-and-python-interactive-coding.html#reload-script">reload_script</a></li>
</ul>
</li>
<li><a href="github-project-statistics-and-python-interactive-coding.html#adding-features-step-by-step">Adding features step by step</a><ul>
<li><a href="github-project-statistics-and-python-interactive-coding.html#step-1">Step 1</a></li>
<li><a href="github-project-statistics-and-python-interactive-coding.html#step-2">Step 2</a></li>
<li><a href="github-project-statistics-and-python-interactive-coding.html#step-3">Step 3</a></li>
<li><a href="github-project-statistics-and-python-interactive-coding.html#step-4">Step 4</a></li>
</ul>
</li>
</ul>
<hr>
<h2>Goal & methodology</h2>
<p>Initially, I was struggling to find a tool that would produce graphs of a GitHub project <em>issues</em> & <em>pull requests</em> growth.</p>
<p>Some useful tools exist to analyze a project activity, like <a href="http://gitstats.sourceforge.net">gitstats</a>, that can generate a very complete graphical report of a <code>git</code> repository history, but does not provide any statistics on <em>issues</em> & <em>PRs</em>.</p>
<p>That is when I discovered the <a href="https://nf-co.re/stats#github_prs">nf-core website page dedicated to measuring their project activity « in numbers »</a>, made by <a href="https://github.com/ewels">Phil Ewens</a>.
After <a href="https://github.com/nf-core/nf-co.re/issues/190">asking permission to reuse them</a>,
I decided to write a Python script that would generate those graphs.</p>
<p>It happens that in the last few months I have written several Python scripts with a similar purpose: <strong>querying an HTTP API</strong> and <strong>generating an HTML page from this data</strong>.</p>
<p>Sounds simple, isn't it ? 😉
It doesn't require complicated code, but there a few tips & tricks I used that I think are worth sharing,
including how to <strong>program iteratively with code auto-reloading</strong>.</p>
<p>In the next sections I will describe in details the nuts and bolts of this approach.
I'll start by describing some Python come that make a good starting point for this kind of script.
Then I'll guide you through the steps needed to generated GitHub activity graphs.</p>
<hr>
<h2>Libraries</h2>
<p>Before detailing what this script does, let's present its foundations:
the libraries it uses.</p>
<h3>Python libraries</h3>
<p>In Python, it is standard to bundle code dependencies as packages,
hosted on <a href="https://pypi.org">Pypi</a>.</p>
<p>You can easily install the packages needed for this script by using <a href="https://pip.pypa.io/en/stable/installing/"><code>pip</code></a>:</p>
<div class="highlight"><pre><span></span><code><span class="n">pip</span> <span class="n">install</span> <span class="n">jinja2</span> <span class="n">livereload</span> <span class="n">requests</span> <span class="n">xreload</span>
</code></pre></div>
<p>Now let's explain briefly what are those libraries useful for:</p>
<ul>
<li>
<p><a href="https://jinja.palletsprojects.com"><strong>jinja2</strong></a> allow to combine <em>template files</em>
with <em>code variables</em> using a specific syntax made of <em>mustaches</em> like <code>{{ }}</code> or <code>{% %}</code>.
In our case, we will generate a <code>page.html</code> file based on a <code>template.html</code> file.</p>
</li>
<li>
<p><a href="https://github.com/lepture/python-livereload/"><strong>livereload</strong></a> provides two services:
it can <code>.serve()</code> files as an HTTP server, so that you will be able to navigate with your web browser to <a href="http://localhost:5500/page.html">http://localhost:5500/page.html</a>, and it can <code>.watch()</code> for changes on some target files,
in our case in order to trigger a new generation of <code>page.html</code> <strong>and</strong> to instruct your browser to reload this page.
This is made possible because <code>livereload</code> actually injects some Javascript code in the HTML
of the pages it serves.</p>
</li>
<li>
<p><a href="https://2.python-requests.org"><strong>requests</strong></a> is maybe the most handy Pypi package ever,
as it will allow you to perform HTTP requests easily.
We do not use it in our initial minimal code, but we will need it in later steps to get data from GitHub.</p>
</li>
<li>
<p><a href="https://github.com/Lucas-C/xreload"><strong>xreload</strong></a> is initially a script written by Guido van Rossum and named <a href="http://svn.python.org/projects/sandbox/trunk/xreload/">xreload.py</a>, but made Python3-compatible and uploaded on Pypi to make things easier. Its purpose is to <em>hot reload</em> a Python module. In our case that means, when we run <code>./build_page.py --watch-and-serve</code>, to update all the objects in the running Python interpreter, each time we save new changes made on the file <code>build_page.py</code>.</p>
</li>
</ul>
<p>Because Python comes with batteries included, it has many useful <em>built-in</em> modules.
We rely on two of them here:</p>
<ul>
<li>
<p><a href="https://docs.python.org/3/library/argparse.html"><strong>argparse</strong></a> is a powerful parser for command line arguments (things like <code>--help</code> or <code>-n 1</code> passed to a program).</p>
</li>
<li>
<p><a href="https://docs.python.org/3/library/webbrowser.html"><strong>webbrowser</strong></a> will ask your default web browser to open a given URL.</p>
</li>
</ul>
<h3>Javascript libraries</h3>
<p>Later on in this tutorial, I will introduce some Javascript code.
So let's also mention JS libraries :</p>
<ul>
<li>
<p>I used the same one to draw graphs as my model <a href="https://nf-co.re/stats#github_prs">nf-core statistics page</a> :
the <a href="https://www.chartjs.org">ChartJS</a> library.</p>
</li>
<li>
<p>on other projects I often used <a href="https://datatables.net">DataTables JS</a>,
which adds very handy features to HTML <code><table></code> elements.
Here is a starting code if you want to use it, in this case with <code>jQuery</code>:</p>
</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="nt"><script></span>
const queryParams = new URLSearchParams(location.search);
$(document).ready(() => {
$('#id-of-your-table').DataTable({
pageLength: 100,
order: [[ 0, 'asc' ]],
search: { search: queryParams.get('search') || '', },
createdRow: (row, data) => {
console.log('data:', data);
if (data[1].toLowerCase() === 'warning') {
$(row).addClass('warning');
}
}
});
});
<span class="nt"></script></span>
</code></pre></div>
<hr>
<h2>Minimal starting code</h2>
<p>I usually start with those 2 files:</p>
<ul>
<li><a href="https://github.com/Lucas-C/ludochaordic/blob/master/content/github-project-statistics-and-python-interactive-coding/step0/template.html"><code>template.html</code></a>: nothing really fancy here, apart from the <a href="https://jinja.palletsprojects.com">jinja2</a>
<code>{{mustaches}}</code>, which will render the value of the variable <code>name</code> or display <code>World</code> if it is undefined or empty</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="cp"><!doctype html></span>
<span class="p"><</span><span class="nt">html</span> <span class="na">lang</span><span class="o">=</span><span class="s">"en"</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">"utf-8"</span><span class="p">></span>
<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
Hello {{ name or 'World'}} !
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</code></pre></div>
<ul>
<li><a href="https://github.com/Lucas-C/ludochaordic/blob/master/content/github-project-statistics-and-python-interactive-coding/step0/build_page.py"><code>build_page.py</code></a>: I will go through this script in details below</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="ch">#!/usr/bin/env python3</span>
<span class="kn">import</span> <span class="nn">argparse</span><span class="o">,</span> <span class="nn">sys</span><span class="o">,</span> <span class="nn">webbrowser</span>
<span class="kn">from</span> <span class="nn">os.path</span> <span class="kn">import</span> <span class="n">dirname</span><span class="p">,</span> <span class="n">join</span><span class="p">,</span> <span class="n">realpath</span>
<span class="kn">from</span> <span class="nn">jinja2</span> <span class="kn">import</span> <span class="n">Environment</span><span class="p">,</span> <span class="n">FileSystemLoader</span>
<span class="kn">from</span> <span class="nn">livereload</span> <span class="kn">import</span> <span class="n">Server</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">from</span> <span class="nn">xreload</span> <span class="kn">import</span> <span class="n">xreload</span>
<span class="n">PARENT_DIR</span> <span class="o">=</span> <span class="n">dirname</span><span class="p">(</span><span class="n">realpath</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parse_args</span><span class="p">()</span>
<span class="n">build_page</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">watch_and_serve</span><span class="p">:</span>
<span class="n">watch_and_serve</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">parse_args</span><span class="p">():</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">'--watch-and-serve'</span><span class="p">,</span> <span class="n">action</span><span class="o">=</span><span class="s1">'store_true'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span> <span class="c1"># reads sys.argv</span>
<span class="k">def</span> <span class="nf">build_page</span><span class="p">(</span><span class="n">args</span><span class="p">):</span>
<span class="n">env</span> <span class="o">=</span> <span class="n">Environment</span><span class="p">(</span><span class="n">loader</span><span class="o">=</span><span class="n">FileSystemLoader</span><span class="p">(</span><span class="n">PARENT_DIR</span><span class="p">))</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">join</span><span class="p">(</span><span class="n">PARENT_DIR</span><span class="p">,</span> <span class="s1">'page.html'</span><span class="p">),</span> <span class="s1">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">output_file</span><span class="p">:</span>
<span class="n">output_file</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">env</span><span class="o">.</span><span class="n">get_template</span><span class="p">(</span><span class="s1">'template.html'</span><span class="p">)</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Bob'</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">watch_and_serve</span><span class="p">(</span><span class="n">args</span><span class="p">):</span>
<span class="n">server</span> <span class="o">=</span> <span class="n">Server</span><span class="p">()</span>
<span class="n">server</span><span class="o">.</span><span class="n">watch</span><span class="p">(</span><span class="s1">'template.html'</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">build_page</span><span class="p">(</span><span class="n">args</span><span class="p">))</span>
<span class="n">server</span><span class="o">.</span><span class="n">watch</span><span class="p">(</span><span class="vm">__file__</span><span class="p">,</span> <span class="k">lambda</span><span class="p">:</span> <span class="n">reload_script</span><span class="p">()</span> <span class="ow">and</span> <span class="n">build_page</span><span class="p">(</span><span class="n">args</span><span class="p">))</span>
<span class="n">webbrowser</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s1">'http://localhost:5500/page.html'</span><span class="p">)</span>
<span class="n">server</span><span class="o">.</span><span class="n">serve</span><span class="p">(</span><span class="n">root</span><span class="o">=</span><span class="n">PARENT_DIR</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">5500</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">reload_script</span><span class="p">():</span>
<span class="k">return</span> <span class="n">xreload</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="p">[</span><span class="vm">__name__</span><span class="p">],</span> <span class="n">new_annotations</span><span class="o">=</span><span class="p">{</span><span class="s1">'RELOADING'</span><span class="p">:</span> <span class="kc">True</span><span class="p">})</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span> <span class="ow">and</span> <span class="s1">'RELOADING'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="vm">__annotations__</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div>
<p>Go and try it !
Create those files on your computer and call <code>python3 build_page.py --watch-and-serve</code>.
A web page should open in your web browser.
Now, without shutting down the script:</p>
<ul>
<li>try to change the <code>Hello</code> word in <code>template.html</code></li>
<li>try to change the value of the <code>name</code> variable on line 27 of <code>build_page.py</code></li>
</ul>
<hr>
<h2>Code structure</h2>
<p>To start with, notice that the first line of the script is <code>#!/usr/bin/env python3</code>.
This is called a <a href="https://en.wikipedia.org/wiki/Shebang_(Unix)"><em>shebang</em></a>.
If you make the Python file executable (<em>e.g.</em> by calling <code>chmod u+x build_page.py</code>),
and invoke this script as a command (<em>i.e.</em> <code>./build_page.py</code>),
your shell will read this line and know it needs to use the Python 3 interpreter to execute the script coming after in the file.
Moreover, by using <code>env</code> instead of specifying the exact path to the <code>python3</code> command,
we ensure that the correct interpreter will be used if using a <a href="https://virtualenv.pypa.io"><code>virtualenv</code></a>.</p>
<p>Now, if we focus on the functions only, and the order in which they are called in this script,
we could summarize it like that:</p>
<div class="highlight"><pre><span></span><code><span class="n">main</span><span class="p">()</span>
<span class="n">parse_args</span><span class="p">()</span>
<span class="n">build_page</span><span class="p">()</span>
<span class="n">watch_and_serve</span><span class="p">()</span>
<span class="n">reload_script</span><span class="p">()</span>
</code></pre></div>
<p>Those functions are defined <em>top-down</em> : starting from the program entry point to the most specific subroutines.</p>
<p>Let's go through them in details.</p>
<h3>main</h3>
<p>This is the program entry point.
It is called when the script is loaded and both those conditional expressions are true:</p>
<ul>
<li>
<p><code>__name__ == '__main__'</code>: this <em>guard</em> is very common in Python scripts,
to avoid executing the <code>main</code> entrypoint if one just import functions from the module,
<em>e.g.</em> with <code>from build_page import parse_args</code>.</p>
</li>
<li>
<p><code>'RELOADING' not in __annotations__</code>: this ensure the <code>main</code> function is not called when using <code>xreload</code>.
The <a href="github-project-statistics-and-python-interactive-coding.html#reload_script">reload_script</a> section will explain this further.</p>
</li>
</ul>
<h3>parse_args</h3>
<p>This function define all the command line arguments that the script accept,
perform their <em>parsing</em> and return and object <code>args</code> containing the corresponding <em>flags</em>.</p>
<p>Our script will have two main operating modes: either it will simply generate the HTML page and stop,
or it will run indefinitely, serving the generated web page as an HTTP server and hot-reloading changes made to files.</p>
<p>To enable this later mode, we define a single boolean option.
<code>args.watch_and_serve</code> will be <code>True</code> if <code>--watch-and-serve</code> is provided,
and else its value will be <code>False</code>.</p>
<h3>build_page</h3>
<p>This function will contain our main logic.
For now it only uses <code>jinja2</code> to construct the resulting HTML page.
Note that we pass the variables to use in the template to the <code>.render</code> method.</p>
<p>By using the <code>PARENT_DIR</code> constant, we ensure that the file paths used are all relative
to the <code>build_page.py</code> script parent directory.
This way, if we are in the directory <code>/tmp</code> in a console shell,
and we call <code>/var/scripts/build_page.py</code>, the generated <code>page.html</code> will be created in <code>/var/scripts</code>.</p>
<h3>watch_and_serve</h3>
<p>This function basically configures <code>livereload</code>.
It tells it to serve files in the <code>PARENT_DIR</code> directory on <code>localhost:5500</code>;
to watch for changes made in the <code>template.html</code> file and then to rebuild <code>page.html</code>;
to watch for changes made in the <code>build_page.py</code> (which is the value of <code>__file__</code>)
and then to reload the script and rebuild <code>page.html</code>;
and finally it opens <code>http://localhost:5500/page.html</code> in the default web browser.</p>
<h3>reload_script</h3>
<p>This function reload the current module (retrieved with <code>sys.modules[__name__]</code>)
from the file <code>build_page.py</code>.
In doing so, it adds an annotation <code>RELOADING</code> to the module,
so that the <code>main()</code> function is not re-executed.</p>
<hr>
<h2>Adding features step by step</h2>
<p>Now that we have our code skeleton,
let's iterate while <code>python3 build_page.py --watch-and-serve</code> is running !
For each step below, let's add code to <code>template.html</code> & <code>build_page.py</code> to implement new features.</p>
<p>The goal here is to generate 2 graphs of GitHub <em>issues</em>
and <em>pull request</em> over time for a given repository,
taking inspiration from <a href="https://nf-co.re/stats#github_prs">the nf-co.re website</a>.</p>
<p>As this can make a good coding exercise, I'm going to explain for each step what I want to achieve,
so that you can try it yourself. I'll provide the source code I ended up with myself after each step.</p>
<h3>Step 1</h3>
<p>First, let's focus on the HTML + Javascript part.</p>
<p>Having got approval from <a href="https://github.com/nf-core/nf-co.re/issues/190">Phil Ewes & the nf-core project</a>
to reuse their layout, my first goal was to strip down the <a href="https://nf-co.re/stats">https://nf-co.re/stats</a> page to the
very minimal code that would display those graphs.</p>
<p>After copy-pasting the web page source code into the <code>template.html</code> file,
you will realize that the rendered page is broken, because it has many dependencies to other JS & CSS files served on relative paths.</p>
<p>To fix this, for every <code><script></code> or <code><link></code> tag, either get rid of it
(<code>googletagmanager</code>, <code>leaflet</code>, <code>hammer.min.js</code>, <code>canvas2svg.js</code>, <code>FileSaver.js</code>, <code>jquery.tablesorter.min.js</code>, <code>popper.min.js</code>, JS & CSS for code highlighting...) or replace it by a CDN-hosted version.
I usually prefer to have all my dependencies <em>on-disk</em> and to serve them with <code>livereload</code>,
but in the context of this exercise I prefer to keep things really simple and stick with only two source files.</p>
<p>Two special cases remain: <code>nf-core.js</code>, which on further inspection reveals to be useless
in the context of those 2 graphs, and <code>nf-core.css</code>,
which you can shorten up and incorporate as an <em>inline</em> <code><style></code> tag.
Finally, get rid of all the unnecessary HTML & JS code, including the "Download as SVG" button.</p>
<p>The <code>chartData[].data.datasets[].data</code> array in the main inline <code><script></code>
contains the data points to display in the graphs. You can also make it shorter.
For now, just define a bunch of <em>hardcoded</em> dummy data points.</p>
<p>The resulting code at the end of this step: <a href="https://github.com/Lucas-C/ludochaordic/tree/master/content/github-project-statistics-and-python-interactive-coding/step1/">step1/</a>.</p>
<h3>Step 2</h3>
<p>Now, let's prepare the data retrieval
by setting up a local file cache and adding command line options.</p>
<p>Why using a cache ? Two reasons: to avoid hitting <a href="https://developer.github.com/v3/#rate-limiting">GitHub rate limit of 60 requests per hour if unauthenticated</a>, and to speed things up by avoiding to repeat HTTP calls that have already been made.</p>
<p>The idea is to systematically write all the JSON responses to a <code>dump.json</code> file,
and to use this file instead of making HTTP requests if the <code>--use-dump</code> option is passed to the script.</p>
<p>As you add this option to the <code>argparse</code> parser, use this occasion to introduce a required argument specifying which
GitHub repository to target. Because the command line arguments are not parsed again on every "reload",
you will have to <strong>restart the script</strong> when you edit the <code>parse_args</code> function, otherwise you'll get an</p>
<div class="highlight"><pre><span></span><code>AttributeError: 'Namespace' object has no attribute ...
</code></pre></div>
<p>In terms of code structure, we're going to add two new functions, <code>read_dump</code> & <code>write_dump</code>.
You can also factor-out the code dedicated to generate the HTML file into a <code>generate_html</code> function.
Finally, initiate a <code>get_github_stats</code> function returning some dummy data:
a dict with 2 fields: <code>org_repo</code> (a string) and <code>issues</code> (an array).</p>
<p>This is an opportunity to remove our <em>hardcoded</em> data from <code>template.html</code>,
and inject it from the Python code by using <code>jinja</code>.
Use this syntax to insert the charts data points: <code>{% for x, y in issues %} ... {% endfor %}</code>.</p>
<p>And to build the page title, use the <code>org_repo</code> variable like this:</p>
<div class="highlight"><pre><span></span><code><span class="nt"><a</span> <span class="na">href=</span><span class="s">"https://github.com/</span><span class="cp">{{</span><span class="nv">org_repo</span><span class="cp">}}</span><span class="s">"</span><span class="nt">></span><span class="cp">{{</span><span class="nv">org_repo</span><span class="cp">}}</span><span class="nt"></a></span> in numbers
</code></pre></div>
<p>The resulting code at the end of this step: <a href="https://github.com/Lucas-C/ludochaordic/tree/master/content/github-project-statistics-and-python-interactive-coding/step2/">step2/</a>.</p>
<h3>Step 3</h3>
<p>Next step: perform the data retrieval from GitHub API.</p>
<p>The data we need for our use case is relatively simple to obtain:
all <em>issues</em> & <em>pull requests</em> information is accessible through <a href="https://developer.github.com/v3/issues/">the <code>issues</code> resource</a> as a JSON document: <code>https://api.github.com/repos/$org/$repo/issues</code>.</p>
<p>We need to be careful about two things though:</p>
<ul>
<li>
<p>by default closed <em>issues</em> are not returned, so we need to provide a <code>state=all</code> query parameter.</p>
</li>
<li>
<p>the data returned is <a href="https://developer.github.com/v3/guides/traversing-with-pagination/">paginated</a>.
To get all the data, we need to request this resource with an incrementing <code>page</code> query parameter
as long as it does not return an empty response.
<strong>Code tip:</strong> you can get a forever incrementing integer variable by using <a href="https://docs.python.org/3/library/itertools.html#itertools.count"><code>itertools.count</code></a>:</p>
</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="k">for</span> <span class="n">page</span> <span class="ow">in</span> <span class="n">count</span><span class="p">(</span><span class="mi">1</span><span class="p">):</span>
<span class="o">...</span>
</code></pre></div>
<p>As a bonus, for users with a GitHub account and willing to use this script authenticated,
if a <code>GITHUB_OAUTH_TOKEN</code> environment variable is defined,
you can pass it to the API as <a href="https://developer.github.com/v3/#oauth2-token-sent-in-a-header">an HTTP header <code>Authorization</code> token</a>.</p>
<p>Because the resulting <code>github_stats['issues']</code> data is not correctly formatted for our template,
we will stick with dummy data for now and implement a valid <code>pre_process</code> function in next step.</p>
<p>The resulting code at the end of this step: <a href="https://github.com/Lucas-C/ludochaordic/tree/master/content/github-project-statistics-and-python-interactive-coding/step3/">step3/</a>.</p>
<h3>Step 4</h3>
<p>Finally, let's transform the data to fit our template needs.</p>
<p>The goal here is to use our <code>github_stats['issues']</code> data to build the 4 arrays needed by our graphs:</p>
<ul>
<li><code>open_issues_count_over_time</code></li>
<li><code>closed_issues_count_over_time</code></li>
<li><code>open_prs_count_over_time</code></li>
<li><code>closed_prs_count_over_time</code></li>
</ul>
<p>Start by generating a <code>dict</code> named <code>issues_per_creation_day</code> that associate to a given day all the issues created on that date. <a href="https://docs.python.org/3/library/collections.html#collections.defaultdict"><code>collections.defaultdict</code></a> may be useful here. You retrieve the creation date base on the <code>created_at</code> field in GitHub data, but you will have to shorten it because our date must have a format like <code>2019-05-10</code>.</p>
<p>Once you have this <code>issues_per_creation_day dict</code>, you should be able to be build the other 4 arrays,
by filtering issues depending on their <code>state</code> (<code>closed</code> or not),
and if they have a <code>pull_request</code> field, indicating they are actually <em>pull requests</em> and not simple <em>issues</em>.</p>
<p>Finally, if you want the same result as the <a href="https://nf-co.re/stats#github_prs">nf-core project activity statistics</a>,
take care to build cumulative statistics.</p>
<p>The resulting code at the end of this step: <a href="https://github.com/Lucas-C/ludochaordic/tree/master/content/github-project-statistics-and-python-interactive-coding/step4/">step4/</a>.</p>
<hr>
<h2>Conclusion</h2>
<p>This method can be applied in many other situations,
whether an HTML page is involved or not.
It is very useful and frequent to use this approach when coding video games for example,
and I think that Markus @notch Persson used some code auto-reloading when he wrote his <a href="http://ludumdare.com/compo/ludum-dare-21/?action=preview&uid=398">Ludum Dare entry <em>Prelude of the Chambered</em></a>.</p>
<p>If you want to streamline your code once you are satisfied with it,
the whole <code>watch-and-serve</code> logic can be easily removed in the end.</p>
<p>I hope this Python code will be useful to you !
If you use this approach yourself,
or if you're willing to give me some feedback on this article,
please add a comment below 😉
I'll try to answer you questions if you have any !</p>
<p><strong>[EDIT 2019/10/16]:</strong> I stumbled upon this which seems promising as an alternative to <code>xreload</code>:
<a href="https://github.com/CFSworks/limeade">https://github.com/CFSworks/limeade</a></p>
<p><strong>[EDIT 2019/10/24]:</strong> there is also this: <a href="https://github.com/julvo/reloading">https://github.com/julvo/reloading</a></p>
<style>
article iframe {
width: 100%;
height: 75vh;
}
article hr {
margin: 4rem !important;
}
.centered-content {
text-align: center;
}
</style>
<script>
['h3', 'h3'].forEach(function (selector) {
document.querySelectorAll(selector).forEach(function (header) {
if (!header.classList.length) {
header.id = header.textContent
.toLowerCase()
.replace(/[()?:,'&]/g, '')
.replace(/[à]/g, 'a')
.replace(/[ç]/g, 'c')
.replace(/[éêè]/g, 'e')
.replace(/[ï]/g, 'i')
.replace(/ /g, '-');
}
});
});
</script>A folded paper puzzle2019-09-21T12:00:00+02:002019-09-21T12:00:00+02:00Lucas Cimontag:chezsoi.org,2019-09-21:/lucas/blog/a-folded-paper-puzzle.html<p><a href="https://lucas-c.github.io/dotfiles_and_notes/languages/web-d3/folded_puzzle.html"><img alt="Star-shape foldable puzzle" src="images/2019/09/folded_puzzle.png"></a></p>
<p>Last week-end was the 30th anniversary of one of my best friends.
For the occasion, I wanted to craft him a small puzzle with a custom secret drawing,
as a reminder of some shared memories.</p>
<p>I came upon <a href="https://erikdemaine.org/puzzles/CSAIL2006/">Erik & Martin Demaine's creation for CSAIL 2006</a>,
a print & play puzzle based …</p><p><a href="https://lucas-c.github.io/dotfiles_and_notes/languages/web-d3/folded_puzzle.html"><img alt="Star-shape foldable puzzle" src="images/2019/09/folded_puzzle.png"></a></p>
<p>Last week-end was the 30th anniversary of one of my best friends.
For the occasion, I wanted to craft him a small puzzle with a custom secret drawing,
as a reminder of some shared memories.</p>
<p>I came upon <a href="https://erikdemaine.org/puzzles/CSAIL2006/">Erik & Martin Demaine's creation for CSAIL 2006</a>,
a print & play puzzle based on folding, which was exactly what I was looking for !</p>
<p>Hence I wrote a web version, with a customizable background : any image can be uploaded !</p>
<p>You can find this puzzle online here : <a href="https://lucas-c.github.io/dotfiles_and_notes/languages/web-d3/folded_puzzle.html">folded_puzzle.html</a></p>
<p>I am very happy with the result, and I hope you will enjoy trying to solve it 😉</p>
<p>You can export the puzzle to an SVG file, which can then easily be converted to a printable PDF using dedicated websites. I tried to provide a direct PDF export, but it was a pain due to poor support of SVG in web browsers PDF converters. If you don't care about the puzzle image and just want a printable PDF, go visit Erik Demaine website on the first hyperlink above.</p>
<p>To be honest, it was both very funny and painful to craft.
Why ? First, because I had to resort to using SVG rotations to build the star-shape,
and it was a nightmare to come up with the correct trigonometry in order to exactly mimic the original model.</p>
<p>And second, because it was actually quite hard to solve !!
I have to admit I offered it to my good friend without solving it first hand 🙃
I only solved it yesterday evening !
But I added a few hints at the bottom of the web page, in case you need help 🤭</p>Soutien à Vincenzo Vecchi2019-08-16T23:30:00+02:002019-08-16T23:30:00+02:00Lucas Cimontag:chezsoi.org,2019-08-16:/lucas/blog/soutien-a-vincenzo-vecchi.html<p><a href="https://www.comite-soutien-vincenzo.org"><img alt="Affiche de soutien à Vincenzo Vecchi" src="images/2019/08/soutien-vincenzo.png"></a></p>
<p>Je partage cet appel à soutien car cette histoire me choque sincèrement.</p>
<p>Je ne suis pas à l'aise avec l'idée que notre pays, la France,
remette un militant anti-G8 et antifasciste au gouvernement italien actuel,
où il risque 13 ans de prison pour des faits non prouvés datant de 2001 …</p><p><a href="https://www.comite-soutien-vincenzo.org"><img alt="Affiche de soutien à Vincenzo Vecchi" src="images/2019/08/soutien-vincenzo.png"></a></p>
<p>Je partage cet appel à soutien car cette histoire me choque sincèrement.</p>
<p>Je ne suis pas à l'aise avec l'idée que notre pays, la France,
remette un militant anti-G8 et antifasciste au gouvernement italien actuel,
où il risque 13 ans de prison pour des faits non prouvés datant de 2001 !</p>
<blockquote>
<p>Jeudi 8 Août, Vincenzo qui vit à Rochefort en Terre (Morbihan) depuis 8 ans,
sans aucune histoire et complètement intégré à la vie locale, est arrêté par la police.
Son arrestation à lieu sous mandat d’arrêt européen.
Il est emmené au centre de détention de Vézin le Coquet, pour une procédure d’extradition.</p>
</blockquote>
<p>Ses proches, amis et voisins, ont formé un comité de soutien et rassemblés tous les faits sur ce site
que je vous invite à lire : <a href="https://www.comite-soutien-vincenzo.org">https://www.comite-soutien-vincenzo.org</a></p>
<p>Vincenzo est condamné pour un crime :</p>
<blockquote>
<p>introduit par le code Rocco de 1930 (donc en période fascisante)
et encore en vigueur aujourd’hui pour réprimer les révoltes de rue.
Ce code permet de condamner à des peines de prison de 8 à 15 ans, sans avoir à prouver la culpabilité des inculpés.</p>
</blockquote>
<p>La peine encourue est hallucinante : 13 ans de prison, 18 ans après, pour des faits mineurs et matériels,
sachant que quatre recours pour mauvais traitements ou torture dans les prisons italiennes
sont actuellement jugés par la Cour européenne des droits de l’homme (CEDH)
(<a href="https://www.prison-insider.com/fichepays/prisonsenitalie">source</a>).</p>
<blockquote>
<p>Il serait inadmissible que notre gouvernement, se prévalant d’être le défenseur Européen de l’humanisme et des Droits Humains,
cautionne cette discrimination politique. - Comité de soutien à Vincenzo</p>
</blockquote>
<p>L'info traitée dans divers médias :</p>
<ul>
<li><a href="https://www.franceinter.fr/justice/la-justice-francaise-va-t-elle-remettre-a-l-italie-l-ex-militant-anticapitaliste-vincenzo-vecchi">FranceInter</a></li>
</ul>
<blockquote>
<p>l'un des membres de son comité de soutien a déclaré sur France Inter redouter que son "ami" Vincenzo ne devienne un "trophée de chasse"
pour le ministre italien d'extrême droite Matteo Salvini</p>
</blockquote>
<ul>
<li><a href="https://www.lemonde.fr/societe/article/2019/08/15/vincenzo-vecchi-le-condamne-que-l-italie-reclame-a-la-france_5499569_3224.html">LeMonde</a>
détaille l'audience du mercredi 14 août et les accusations qui pèsent sur Vincenzo</li>
</ul>
<blockquote>
<p>les habitants de Rochefort-en-Terre et alentour ont lancé des opérations de soutien pour leur camarade,
parfaitement inséré, au dire de tous, et impliqué comme beaucoup dans la vie associative locale,
mais désormais sur le point d’être renvoyé de l’autre côté des Alpes</p>
</blockquote>
<p><strong>EDIT[2019/08/20]</strong> : le comité de soutien a mis en place une pétition, ainsi que la possibilité de contribuer financièrement aux frais d'avocat :
<a href="https://www.comite-soutien-vincenzo.org/contact/">https://www.comite-soutien-vincenzo.org/contact/</a></p>L'assassin de la reine2019-08-16T19:00:00+02:002019-08-16T19:00:00+02:00Lucas Cimontag:chezsoi.org,2019-08-16:/lucas/blog/lassassin-de-la-reine.html<p><img alt="Imperial courts of France, England, Russia, Prussia, Sardinia, and Austria (1863) - Walter Hilliard Bidwell" src="images/2019/08/queen-killer.jpg"></p>
<blockquote>
<p>Un jeu rapide d'intrigues, de trahisons, et de romances pour trois joueurs et plus</p>
</blockquote>
<p>J'ai eu l'occasion de tester hier ce très chouette <a href="/lucas/blog/tag/jdr.html">jeu de rôle</a> de <a href="https://strangerelics.itch.io/queen-killer">@clarity_flowers (PDF sur itch.io)</a>
traduit en français par <a href="https://matthieu-be.itch.io/queen-killer-vf">Matthieu Bé (PDF sur itch.io)</a>,
et découvert via l'excellent site <a href="http://troplongpaslu.fr/jeux-de-role-court/queen-killer/">troplongpaslu.fr</a>.</p>
<p>C'était …</p><p><img alt="Imperial courts of France, England, Russia, Prussia, Sardinia, and Austria (1863) - Walter Hilliard Bidwell" src="images/2019/08/queen-killer.jpg"></p>
<blockquote>
<p>Un jeu rapide d'intrigues, de trahisons, et de romances pour trois joueurs et plus</p>
</blockquote>
<p>J'ai eu l'occasion de tester hier ce très chouette <a href="/lucas/blog/tag/jdr.html">jeu de rôle</a> de <a href="https://strangerelics.itch.io/queen-killer">@clarity_flowers (PDF sur itch.io)</a>
traduit en français par <a href="https://matthieu-be.itch.io/queen-killer-vf">Matthieu Bé (PDF sur itch.io)</a>,
et découvert via l'excellent site <a href="http://troplongpaslu.fr/jeux-de-role-court/queen-killer/">troplongpaslu.fr</a>.</p>
<p>C'était donc une micro-partie à trois joueurs.
« Micro partie » car le jeu encourage de se fixer une limite de temps et nous avons décidé d'y consacrer,
une fois les personnages créés, une heure montre en main.</p>
<p>Je crois que le jeu s'inspire <s>du JdR <a href="https://www.evilhat.com/home/for-the-queen/">For The Queen</a></s> (en fait non, le jeu de Clarity est antérieur)
ainsi que du jeu de société <a href="https://en.wikipedia.org/wiki/Wink_murder">Killer <em>aka</em> Wink murder</a>,
où les joueurs se voient aussi distribuer des cartes à jouer indiquant leur rôle secret.</p>
<p>Je suis admiratif du format du jeu, qui tient en deux pages très lisibles,
feuilles de personnage comprises, mais est pour autant foisonnant !
Les règles sont originales mais restent simples et claires,
et le tout ne nécessite vraiment aucune préparation.</p>
<p>Seul petit bémol : il est fait mention au tout début de la création de personnages de « pronoms »,
et la règle ne détaille pas du tout ce terme qui reste assez flou pour moi.</p>
<p><img alt="Illustration Queen-Killer VO" src="images/2019/08/queen-killer-vo.jpg"></p>
<p>Pour notre partie nous avons décidé de choisir comme <em>setting</em> la mort d'Elisabeth de Bavière,
reine de l'empire austro-hongrois, bien connue sous le surnom de <a href="https://fr.wikipedia.org/wiki/%C3%89lisabeth_de_Wittelsbach">Sissi</a>.</p>
<p>Notre connaissance assez approximative de l'Histoire à ce sujet nous a fait prendre quelques libertés...
Dans notre uchronie Franz & Sissi ont été tués en même temps par coup de feu dans leur carrosse,
et nous avons allègrement confondu <a href="https://fr.wikipedia.org/wiki/Autriche">Autriche</a> & <a href="https://fr.wikipedia.org/wiki/Royaume_de_Prusse">Prusse</a> 😄.</p>
<p>L'action se passait à Vienne et au <a href="https://fr.wikipedia.org/wiki/Ch%C3%A2teau_de_Sch%C3%B6nbrunn">château de Schönbrunn</a>
(chapeau Estelle pour ta culture générale !).
Nous incarnions trois personnages importants à la cours royale, prêts à en découdre pour devenir régent :</p>
<ul>
<li><strong>Ingeborg Genteberg</strong>, confidente et conseillère de Sissi depuis toujours</li>
<li><strong>le duc Herbert de Vaucansson</strong>, ministre des affaires étrangères</li>
<li><strong>lord Adolf Rutberg</strong>, directeur de l'académie des sciences et cousin de la reine</li>
</ul>
<p><img alt="Illustration Queen-Killer VF" src="images/2019/08/queen-killer-vf.png"></p>
<p>J'ai beaucoup aimé cette partie et sa mécanique de jeu :
thème accrocheur et évocateur, cartes rôles cachées, définition des PJs via les descriptions d'autres joueurs,
<em>moves</em> incitatifs et mettant dans l'ambiance... Un condensé de bonnes idées !</p>
<p>Le jeu nécessite cependant pas mal d'initiative / improvisation de la part des joueurs :
pas obligatoirement d'unité de lieu ni de temps,
pas d'indication sur comment gérer les PNJs ni sur comme doit se dérouler le choix de dirigeant <em>in game</em>...
Nous avons essayé de nous astreindre à une règle « toute description d'actions ne concernant pas un PJ doit être une réponse à une question »,
mais c'était déstabilisant et parfois assez difficile.</p>
<p>De plus, de l'avis général autour de la table, <strong>la partie aurait été bien plus intéressante à 4 joueurs</strong>,
car dans notre configuration nous avions tous un lien d'attirance / répulsion avec chacun des autres joueurs.
Difficile de trouver un consensus dans ce contexte !</p>
<p>Tout s'est au final beaucoup accéléré dans les dernières minutes,
avec un duel (le seul de la partie) fatal, avec un baiser (le seul à nouveau) accepté juste avant.
Au pasasge, au moment de l'épilogue, nous avons choisit de révéler nos cartes même si ce n'est pas explicitement
indiqué par les règles.</p>
<p>A posteriori, en discutant après la partie, nous avons réalisé quelques points qui ne nous sont pas parus évident à la lecture des règles :</p>
<ul>
<li>
<p>tous les personnages des joueurs doivent être de potentiels dirigeants.
Un personnage qui de part son statut ne peut pas devenir dirigeant dans le <em>setting</em> sera très handicapé.</p>
</li>
<li>
<p>nous avons sous-employé l'unique mécanique de jeu relative aux cartes : <strong>le baiser</strong>,
qui est le seul moyen d'en apprendre plus sur les rôles cachés.</p>
</li>
</ul>
<p>Au final je ferais avec plaisir une autre partie de <em>Queen_Killer</em>, à 4 joueurs,
en essayant comme variante d'introduire ce nouveau <em>move</em> que nous avons concocté a posteriori :</p>
<blockquote>
<p>Si vous faites votre <strong>mea culpa</strong> et admettez avoir eu connaissance du projet d'assassinat de la reine,
vous ne pourrez plus être élu dirigeant mais pouvez prendre connaissance de la carte noire d'un autre joueur.</p>
</blockquote>Mariage rouge en octobre2019-08-08T22:40:00+02:002019-08-08T22:40:00+02:00Lucas Cimontag:chezsoi.org,2019-08-08:/lucas/blog/mariage-rouge-en-octobre.html<p><img alt="Illustration de sous-marin" src="images/2019/08/rise-or-dive.png"></p>
<p>Sous ce titre un peu énigmatique je vous propos une petite revue du jeu <a href="https://sandypuggames.itch.io/rise-or-dive">Rise or Dive</a> de <a href="https://www.drivethrurpg.com/browse/pub/7386/Sandy-Pug-Games">Sandy Pug Games</a> que j'ai testé hier soir avec 3 joueurs,
pour une partie d'un peu plus de 2 heures.</p>
<p>Le jeu est un remix de <a href="http://www.onesevendesign.com/laserfeelings/">Laser and Feelings</a> de John Harper …</p><p><img alt="Illustration de sous-marin" src="images/2019/08/rise-or-dive.png"></p>
<p>Sous ce titre un peu énigmatique je vous propos une petite revue du jeu <a href="https://sandypuggames.itch.io/rise-or-dive">Rise or Dive</a> de <a href="https://www.drivethrurpg.com/browse/pub/7386/Sandy-Pug-Games">Sandy Pug Games</a> que j'ai testé hier soir avec 3 joueurs,
pour une partie d'un peu plus de 2 heures.</p>
<p>Le jeu est un remix de <a href="http://www.onesevendesign.com/laserfeelings/">Laser and Feelings</a> de John Harper,
qui plongeait les joueurs dans la peau d'un équipage type Star Trek.</p>
<p>Gros changement de thème donc ici :</p>
<blockquote>
<p>Vous êtes très probablement les derniers êtres humains encore vivant.
À bord du TK-WINSTALEY, vous venez d'apprendre que quelqu'un, quelque part, a lancé ses missiles.
Puis tout le monde a lancé les siens, et maintenant vous êtes seul au fond de l'océan.
Il vous reste une semaine de nourriture et une journée d'oxygène. La radio est silencieuse. Que faites-vous ?</p>
</blockquote>
<p>Belle intro non ? En tout cas elle nous a très vite happé dans une ambiance intense et poisseuse...
Avec bien sûr en tête des images d'un <a href="https://www.imdb.com/title/tt0099810/">célèbre film de John McTiernan avec Sean Connery</a>.</p>
<p>Je n'avais pas traduit le jeu au préalable, et je réalise maintenant que j'avais un peu compris de travers cette introduction 😄 Je n'avais pas réalisé qu'elle sous-entendait qu'un hiver nucléaire s'était déjà installé et que tout était mort en surface... Nous avons plus joué dans une ambiance « guerre froide & espionnage » !</p>
<p>Un des gros points forts du jeu, j'ai trouvé, est sa création de personnage.
En quelques choix archétypaux parmi de simples listes de mots, les joueurs se construisent des alter-egos avec suffisamment de « moelle » pour être se projeter dedans rapidement : une <strong>Attitude</strong>, un <strong>Job</strong>, une <strong>Possession</strong> et un <strong>Objectif</strong> secret.
Durant notre partie, ce dernier point a immédiatement donné un <em>focus</em> aux joueurs, qui se sont très vites mis en action pour l'accomplir.</p>
<p>Et ça a été une grosse réussite !
Tandis que le mousse beau-gosse se révélait un redoutable espion,
et réussissait sa mission de vol de torpilles,
la cuisinière en chef tombait dans les bras d'un des canonniers,
après qu'il lui eut révélé être porteur des dernières volontés de son défunt mari.</p>
<p>Tout le monde autour de la table s'est beaucoup amusé durant cette courte partie 😊
Nous l'avons conclu par un petit tour de parole d'épilogue... qui s'est révélé décrire un mariage entre 2 PJs !
Et qui justifie le nom de cet article...</p>
<p>L'unité de lieu et l'ambiance oppressante qu'on s'imagine facilement sont de gros atouts d'un cadre comme ce huit-clos sous-marin. Je le réutiliserai très certainement...</p>
<p>Gros bémol cependant du jeu, qui m'empêche de vraiment le recommander : son système.
Là où la caractéristique unique de <em>Lasers & Feelings</em> fonctionnait sûrement très bien
car référençant deux concepts simples et opposés (action / combat vs psychologie / relations),
les termes <em>Rise</em> & <em>Dive</em> nous ont parus très peu évocateurs,
et le système clairement peu intuitif.
Cerise (moisie) sur le gâteau : la mécanique de changement de lieu force l'histoire à se déplacer à chaque jet de dé,
ce qui est franchement contraignant et « artificiel ».</p>
<p>En résumé donc : un excellent pitch de scénario, et des objectifs & traits de caractères bien pensés pour plonger dans l'intrigue, mais que je vous recommande de jouer avec un autre système minimaliste de jets de dés !</p>Traduction de jeux de rôle monopage de Grant Howitt2019-07-20T16:30:00+02:002019-07-20T16:30:00+02:00Lucas Cimontag:chezsoi.org,2019-07-20:/lucas/blog/traduction-de-jdr-monopage-de-grant-howitt.html<p><img alt="Photo de Grant Howitt" src="images/2019/07/grant_howitt-450x430.jpg"></p>
<p><a href="http://lookrobot.co.uk/games/">Grant Howitt</a> est un créateur de jeu de rôle que j'aime beaucoup.
Auteur prolifique, comme en attestent <a href="https://rowanrookanddecard.com/designer/grant-howitt/">ses nombreux jeux publiés</a>,
j'affectionne particulièrement sa série de jeux monopage (oui, <a href="/lucas/blog/tag/monopage.html">c'est mon dada</a>),
tous très inventifs.</p>
<p>J'ai déjà parlé de son excellent jeu <em>Genius Loci</em> dans <a href="http://localhost:8888/genie-domestique.html">un article précédent</a>.
Comme je …</p><p><img alt="Photo de Grant Howitt" src="images/2019/07/grant_howitt-450x430.jpg"></p>
<p><a href="http://lookrobot.co.uk/games/">Grant Howitt</a> est un créateur de jeu de rôle que j'aime beaucoup.
Auteur prolifique, comme en attestent <a href="https://rowanrookanddecard.com/designer/grant-howitt/">ses nombreux jeux publiés</a>,
j'affectionne particulièrement sa série de jeux monopage (oui, <a href="/lucas/blog/tag/monopage.html">c'est mon dada</a>),
tous très inventifs.</p>
<p>J'ai déjà parlé de son excellent jeu <em>Genius Loci</em> dans <a href="http://localhost:8888/genie-domestique.html">un article précédent</a>.
Comme je viens de traduire en français une de ces dernières création,
et que c'est déjà la troisième, je vais vous les présenter rapidement ici.</p>
<p><img alt="Dessin d'un pub tiré du jeu Genius Loci" src="images/2019/07/genius-loci-pub.png"></p>
<blockquote>
<p>Dans <strong>Genius Loci</strong>, vous êtes un dieu mineur, un <em>genius loci</em>, dans un petit village calme du sud de l'Angleterre dans les années 60.</p>
</blockquote>
<ul>
<li><a href="https://rowanrookanddecard.com/game-system/genius-loci/">Version originale sur Rowan, Rook & Deckard</a></li>
<li><a href="https://lucas-c.github.io/jdr/genius-loci/">Version web en français</a></li>
<li><a href="images/jdr/genius-loci.pdf">PDF en français</a></li>
</ul>
<blockquote>
<p><strong>Plus de place en enfer</strong> est un JdR de survie et d'horreur.
Dans ce jeu vous serez à la fois un joueur ET un meneur de jeu (MJ).
<strong>Ils sont là dehors pour nous choper, on doit s'échapper !</strong></p>
</blockquote>
<ul>
<li><a href="https://gshowitt.itch.io/no-more-room-in-hell">Version originale <em>No More Room For Hell</em> sur itch.io</a></li>
<li><a href="https://lucas-c.github.io/jdr/plus-de-place-en-enfer/">Version web en français</a></li>
<li><a href="images/jdr/plus-de-place-en-enfer.pdf">PDF en français</a></li>
</ul>
<blockquote>
<p>Dans <strong>Ce n'est pas une place d'honneur</strong>, incarnez des pèlerins dévoués à la recherche des reliques d'anciens dieux
ET les humains terrifiés qui deviendront ces divinités alors qu'ils s'écrasent à bord d'un vaisseau-colonie sur une planète inconnue.</p>
</blockquote>
<ul>
<li><a href="https://gshowitt.itch.io/this-is-not-a-place-of-honour">Version originale <em>This Is Not A Place Of Honour</em> sur itch.io</a></li>
<li><a href="https://lucas-c.github.io/jdr/ce-nest-pas-une-place-dhonneur/">Version web en français</a></li>
<li><a href="https://github.com/Lucas-C/jdr/releases/download/ce-nest-pas-une-place-dhonneur-v1.3/ce-nest-pas-une-place-dhonneur-v1.3.pdf">PDF en français</a></li>
</ul>
<hr>
<p>Je n'ai pas encore testé les 2 derniers, mais je compte le faire bientôt !
Je rajouterai alors un petit compte-rendu ici.
Et comme il est probable que je traduise d'autre jeux de M. Howitt à l'avenir,
vous pourrez les retrouver dans <a href="pages/jeux-de-role.html">la page dédiée aux jeux de rôle de ce blog</a>.</p>
<p><strong>EDIT [2021/04/21]</strong> : j'ai testé <strong>Ce n'est pas une place d'honneur</strong> et j'en parle dans <a href="ce-nest-pas-une-place-dhonneur.html">cet article</a>.</p>
<p><strong>EDIT [2021/09/18]</strong> : je viens également de traduire <a href="la-brigade-du-chaos.html">La Brigade du Chaos</a>, un autre excellent jeu de Grant Howitt, en 25 pages, idéal pour faire découvrir le JdR lors d'une partie fun et rapide !</p>
<style>
article img { max-height: 16rem; }
</style>Braquage en Plein Jour2019-07-19T20:00:00+02:002019-07-19T20:00:00+02:00Lucas Cimontag:chezsoi.org,2019-07-19:/lucas/blog/braquage-en-plein-jour.html<p><img alt="Couverture du jeu en version originale" src="images/2019/07/daylight-robbery-the-heist.png"></p>
<p>Mercredi j'ai eu l'occasion de tester ce très chouette jeu de rôle <a href="/lucas/blog/tag/pbta.html">PbTA</a> (<em>Powered by The Apocalypse</em>) avec 3 joueurs.</p>
<p>Le jeu traduit en français par <a href="http://www.gulix.fr">Gulix</a> est disponible gratuitement <a href="https://www.drivethrurpg.com/product/222945/Braquage-en-Plein-Jour">sur DriveThruRPG</a>,
et j'ai trouvé la version originale <em>Daylight Robbery</em> d'Edwin Moriarty <a href="https://www.reddit.com/r/PBtA/comments/5c34ag/freebies_from_a_fellow_redditor/">dans ce post Reddit</a>,
dont je trouve la …</p><p><img alt="Couverture du jeu en version originale" src="images/2019/07/daylight-robbery-the-heist.png"></p>
<p>Mercredi j'ai eu l'occasion de tester ce très chouette jeu de rôle <a href="/lucas/blog/tag/pbta.html">PbTA</a> (<em>Powered by The Apocalypse</em>) avec 3 joueurs.</p>
<p>Le jeu traduit en français par <a href="http://www.gulix.fr">Gulix</a> est disponible gratuitement <a href="https://www.drivethrurpg.com/product/222945/Braquage-en-Plein-Jour">sur DriveThruRPG</a>,
et j'ai trouvé la version originale <em>Daylight Robbery</em> d'Edwin Moriarty <a href="https://www.reddit.com/r/PBtA/comments/5c34ag/freebies_from_a_fellow_redditor/">dans ce post Reddit</a>,
dont je trouve la maquette et les illustrations quand même vachement plus réussies.</p>
<p>Le jeu vous plonge directement dans l'action, alors qu'un petit groupe de braqueurs - que vous incarnez -
s'apprête à réaliser le coup du siècle.</p>
<p>Dans notre scénario le lieu du casse était une base militaire où avait lieu une réception mondaine, isolée sur une presqu'île,
et l'objectif était une disquette de données confidentielles. L'équipe était constituée de Gerrart, le Professionnel,
Meena, le Sourire, et Mr. Green, le Cerveau.</p>
<p>La partie a durée environ 4 heures, avec en accompagnement musical pour faire monter la pression
l'excellent album Chaos Theory d'Amon Tobin.</p>
<p>Les joueurs étaient particulièrement inspirés, et ont amenés de très belles scènes !
Des opérations séduction sur des gardes qui se déroulent comme sur des roulettes,
des fusillades à travers la fumée de grenades fumigènes,
des traversées de couloir sous le feu nourri d'un sniper...</p>
<p>Alors qu'on leur demandait le code d'accès du jour, un des joueurs me dit qu'il regarde derrière le badge qu'il vient de subtiliser,
espérant que ce code soit inscrit au dos... Je lui dit que c'est peu probable et qu'il a une chance sur 1d6.
Il choisit ⚃ et lance le dé... et c'est une réussite !</p>
<p>Un peu plus tard, le joueur ayant récupéré le butin du casse, une disquette,
se retrouve agrippé à la portière ouverte d'une jeep conduite par un autre PJ, qui le braque de son flingue !
Plutôt que de lui livrer la disquette sous la menace, il préfère lâcher la portière,
fait un roulé-boulé puis décide de changer de stratégie de fuite : il sprinte vers un hélicoptère en vol stationnaire
à proximité, prend appui sur un véhicule renversé et saute dessus !</p>
<p><img alt="La couverture de la BD XII - El Cascador" src="images/2019/07/xiii-el-cascador.jpg"></p>
<p>En-dehors des mécaniques PbTA bien huilées, parmi les petites mécaniques de jeu originales que comporte les règles,
j'ai beaucoup aimé :</p>
<ul>
<li>le choix des entrées subtiles / remarquées</li>
<li>les zones d'actions emboîtées comme des poupées russes</li>
<li>le scénario en total impro mais en 3 grands temps forts, associés à des horloges mesurant la Tension qui monte</li>
</ul>
<p>Les joueurs, dans l'ensemble plutôt novices de ce type de système, se sont sentis un peu perdu
par la quantité d'actions à disposition.
Pour ma part, je n'ai eu que quelques très légers points de frustration :
quelques points de règle un peu obscurs (le « +1/-1 à suivre » est un peu cryptique),
peut-être un ou deux bonus +1 permanent au choix dans les caracs entre chaque temps fort,
et la nécessité de tricher un peu avec le remplissage des horloges de Tension
pour qu'elles suivent le rythme de la partie et la progression des PJs dans les zones d'actions.</p>
<p>À la réflexion, même si le jeu incite au <em>gunfight</em> et qu'en choisissant une base militaire comme terrain de jeu
il ne fallait pas s'attendre à beaucoup de subtilité, je regrette un peu d'avoir fait basculer
la partie en fusillade générale si vite. Quelques phases de <em>social engineering</em> à la réception mondaine
ou d'infiltration à la <em>Splinter Cell</em> auraient pu être bienvenues.</p>
<p>Même si l'avantage de ce type de jeu est d'avoir besoin de <em>zéro</em> préparation en amont,
si un jour je refais une partie j'essaierai de prendre 20min au préalable pour définir pour chaque zone d'action
quelques lieux / pièces notables, au moins 1 PNJ notable et des obstacles / sous-objectifs connus des PJs,
à leur exposer progressivement, pour qu'ils se creusent un peu les méninges à s'introduire de zone en zone.</p>
<p>En tout cas dans l'ensemble je recommande chaudement le jeu pour une soirée inspirée de
<em>Braquage à l'italienne</em>, <em>Inside Man</em> ou encore <em>Reservoir Dogs</em> !</p>
<p><img alt="Illustration tirée du jeu en version originale" src="images/2019/07/daylight-robbery-the-professional.png"></p>
<style>
article li { list-style-type: '✔️ '; }
</style>Dungeon Heart - Heroes & Spells2019-06-16T20:00:00+02:002019-06-16T20:00:00+02:00Lucas Cimontag:chezsoi.org,2019-06-16:/lucas/blog/dungeon-heart-heroes-and-spells.html<p><img alt="Aggron Stonebreak The Ogre Magi" src="images/2019/06/aggron_stonebreak_the_ogre_magi_by_halycon450.png"></p>
<p>En bref, voici les fichiers qui pourraient vous intéresser :</p>
<ul>
<li>la traduction en français de <em>Dungeon Heart</em> : <a href="https://chezsoi.org/lucas/blog/images/jdr/DungeonHeart_BW_v1.6_fr.pdf">PDF</a></li>
<li>l'extension <em>Dungeon Heart - Heroes & Spells</em> en français : <a href="https://lucas-c.github.io/jdr/DungeonHeartHeroesAndSpells/">web</a> - <a href="https://chezsoi.org/lucas/blog/images/jdr/DungeonHeartHeroesAndSpells-v1.6-fr.pdf">PDF</a></li>
<li>the expansion <em>Dungeon Heart - Heroes & Spells</em> in English : <a href="https://lucas-c.github.io/jdr/DungeonHeartHeroesAndSpells/DungeonHeartHeroesAndSpells_en.html">web</a> - <a href="https://chezsoi.org/lucas/blog/images/jdr/DungeonHeartHeroesAndSpells-v1.6-en.pdf">PDF</a></li>
</ul>
<p>Présenté dans l'<a href="dungeon-heart.html">article précédent</a>, <em>Dungeon Heart</em> est un jeu de rôle <a href="/lucas/blog/tag/coop.html">coopératif</a> sans …</p><p><img alt="Aggron Stonebreak The Ogre Magi" src="images/2019/06/aggron_stonebreak_the_ogre_magi_by_halycon450.png"></p>
<p>En bref, voici les fichiers qui pourraient vous intéresser :</p>
<ul>
<li>la traduction en français de <em>Dungeon Heart</em> : <a href="https://chezsoi.org/lucas/blog/images/jdr/DungeonHeart_BW_v1.6_fr.pdf">PDF</a></li>
<li>l'extension <em>Dungeon Heart - Heroes & Spells</em> en français : <a href="https://lucas-c.github.io/jdr/DungeonHeartHeroesAndSpells/">web</a> - <a href="https://chezsoi.org/lucas/blog/images/jdr/DungeonHeartHeroesAndSpells-v1.6-fr.pdf">PDF</a></li>
<li>the expansion <em>Dungeon Heart - Heroes & Spells</em> in English : <a href="https://lucas-c.github.io/jdr/DungeonHeartHeroesAndSpells/DungeonHeartHeroesAndSpells_en.html">web</a> - <a href="https://chezsoi.org/lucas/blog/images/jdr/DungeonHeartHeroesAndSpells-v1.6-en.pdf">PDF</a></li>
</ul>
<p>Présenté dans l'<a href="dungeon-heart.html">article précédent</a>, <em>Dungeon Heart</em> est un jeu de rôle <a href="/lucas/blog/tag/coop.html">coopératif</a> sans meneur
où l'on incarne les gardiens d'un donjon devant faire face à des hordes d'aventuriers.</p>
<p>Le week-end dernier nous avons fait une seconde partie de test du jeu, à 3 joueurs cette fois,
et avec une extension que j'ai concocté : <em>Heroes & Spells</em>.</p>
<p>Cette extension consiste simplement en une table aléatoire A4 dans laquelle les joueurs font un jet entre chaque vague d'invasion.
Elle contient des complications, des avantages pour les Gardiens, un mécanisme de <em>boss fight</em> et des amorces narratives.</p>
<p>Nous avons de plus utilisé une des variantes proposée dans l'article précédent :
un aventurier de plus attaquait le donjon à chaque nouvelle phase d'invasion, mais si le coeur du donjon est atteint,
le nombre d'aventuriers de la prochaine vague redevient égal au nombre de joueurs.</p>
<p>Malheureusement la partie nous a semblé tout aussi facile et « pliée » dès le 5e tour.
Pour la prochaine partie, je compte utiliser une autre variante évoquée :
supprimer les types d'emplacement, mais permettre au Squelette de fournir un bonus de doublement des dés à une menace pour ❤.</p>
<p><a href="images/2019/06/IMG_20190608_204613.jpg"><img alt="La table de jeu avec notre donjon en fin de partie" src="images/2019/06/IMG_20190608_204613.jpg"></a></p>
<p>Pour cette partie, notre donjon était une gigantesque tour de granite blanc,
penchant dangereusement le long d'une falaise au-dessus de l'océan.
Son coeur était un mot magique secret, qui pouvait être entendu dans la plus haute salle de la tour
lorsque le vent s'y engouffrait.
Elle contenait une horloge géante, un puit ouvert sur le vide, une bibliothèque labyrinthique,
une fresque trompe-l'oeil sur une corniche, une chouette géante, de petits oiseaux blancs mangeurs d'hommes,
une nuée intelligente de rats, un golem capable de sculpter ses semblables, un mage muet...</p>
<p>Concernant l'extension <em>Heroes & Spells</em>, les joueurs l'ont bien apprécié mais il restait encore des réglages à faire :
le mécanisme d'XP était trop lent, les duels de boss mal agencés, l'<em>Excavatrice</em> trop puissante...
Ces défauts sont désormais corrigés.</p>
<p>Bon jeu, et ça me ferait plaisir d'avoir vos retours si vous y jouez ! 😉</p>
<p>Au passage, un des joueurs, testant le jeu pour la 1ère fois, a mentionné que la mécanique de ce JdR lui rappelait les jeux vidéos de type <em>tower defense</em>
comme <a href="https://en.wikipedia.org/wiki/Orcs_Must_Die!">Orcs_Must_Die!</a> & <a href="https://en.wikipedia.org/wiki/Dungeon_of_the_Endless">Dungeon of the Endless</a>.</p>
<!-- Com'
* [x] https://www.scenariotheque.org/Document/info_doc.php?id_doc=9902
-> réference : https://lucas-c.github.io/jdr/DungeonHeartHeroesAndSpells/
& https://chezsoi.org/lucas/blog/images/jdr/DungeonHeartHeroesAndSpells-v1.6-fr.pdf
-->
<style>
hr { margin: 5rem; }
</style>2200: Le Jugement Des Dieux2019-06-15T23:00:00+02:002019-06-15T23:00:00+02:00Lucas Cimontag:chezsoi.org,2019-06-15:/lucas/blog/2200-le-jugement-des-dieux.html<figure role="group">
<img alt="Whitelady par Tenseï aka Elliot Jolivet" src="images/2019/06/tensei-inktober2017-whitelady.jpg">
<figcaption>Whitelady par Tenseï aka Elliot Jolivet - <a href="https://www.behance.net/gallery/58695271/InkTober-2017">Réalisé pour Iinktober 2017</a></figcaption>
</figure>
<blockquote>
<p>Durant cette interminable chute, Oona Solédango se remémora ce qui l'avait amené ici.</p>
<p>Il n'était pas né à Subcity, mais dans une jungle dense, la dernière au monde.
Son enfance avait été insouciante, préservée du reste du monde dans ce …</p></blockquote><figure role="group">
<img alt="Whitelady par Tenseï aka Elliot Jolivet" src="images/2019/06/tensei-inktober2017-whitelady.jpg">
<figcaption>Whitelady par Tenseï aka Elliot Jolivet - <a href="https://www.behance.net/gallery/58695271/InkTober-2017">Réalisé pour Iinktober 2017</a></figcaption>
</figure>
<blockquote>
<p>Durant cette interminable chute, Oona Solédango se remémora ce qui l'avait amené ici.</p>
<p>Il n'était pas né à Subcity, mais dans une jungle dense, la dernière au monde.
Son enfance avait été insouciante, préservée du reste du monde dans ce grand poumon vert.
Pourtant tout bascula lorsqu'il fut enlevé à sa famille lors d'un raid,
capturé pour cultiver les herbes halucinogènes consommées par son peuple pour le compte de la mafia.</p>
<p>Au fil des années il s'était endurcit et avait grimpé les échelons de l'organisation
juqu'à en devenir un des principaux parains.
Et puis un jour il abandonna cette vie, sans laisser de traces mais en empoisonnant
sa dernière livraison de drogue.</p>
<p>Sachant ses jours comptés, il avait trouvé refuge dans la tour des nantis,
au sommet de laquelle il était devenu jardinier du magnifique parc surplombant Subcity.</p>
<p>Et puis finalement, ce soir, dans la foule d'une réception chic, un mafieux l'avait reconnu.
Il avait déguerpi immédiatement, mais ils l'avaient devancé à son appartement.
Et le voilà maintenant, chutant de la plus haute tour de la ville,
résigné à bientôt retrouver ses ancêtres dans l'autre monde...</p>
</blockquote>
<hr>
<blockquote>
<p>Comme la plupart de ses pairs, Shanon Walker passa toute sa vie sous la surface.
Née d'une famille de mineurs, son plus ancien souvenir était celui des gigantesques
hauts fourneaux où le minerai était transformé.</p>
<p>C'est ce jour de 2176, lorsqu'elle rencontra ce vieux prêtre aveugle, que son destin fut scellé.
Devenue sa disciple, au service de Dowinos, le dieu oublié du feu et des forges,
elle acquis petit à petit une renommée au sein de son peuple.</p>
<p>Au sumum de la colère des ouvriers, en 2183, elle mena la révolte qui finit en bain de sang.
En fuite, traquée, elle dut se réfugier plusieurs années dans les égoûts.</p>
<p>Petit à petit, en prêchant la parole de son dieu, elle prépara son retour.
Elle élargit son culte, forma des alliances, recruta à son tour un disciple techno-mécanicien...</p>
<p>Et le jour de l'opération « coup de poing » tout se passa miraculeusement comme prévu.
Ils neutralisèrent les gardes, désactivèrent la sécurité, et attinrent le coeur du réseau
informatique central bien avant l'arrivée de l'armée.
Prématurément âgée, tremblante mais triomphante, Shanon s'assit dans le fauteuil de télécontrôle
comme sur un trône.
Devant elle ses fidèles s'agenouillèrent, fredonnant une prière à Dowinos,
tandis que se déclenchait le processus qui allait mettre fin à sa vie,
mais diffuser sa conscience dans les cerveaux électroniques de machines dans le monde entier...</p>
</blockquote>
<hr>
<p>Ces textes racontent quelques unes des histoires que nous avons écrit collectivement
lors des <em>playtests</em> du dernier jeu de rôle que j'ai écrit : <strong>2200 : Le Jugement Des Dieux</strong>.</p>
<ul>
<li>PDF en français : <a href="https://chezsoi.org/lucas/blog/images/jdr/2200-le-jugement-des-dieux-v1.1.pdf">https://chezsoi.org/lucas/blog/images/jdr/2200-le-jugement-des-dieux-v1.1.pdf</a></li>
<li>version web en français : <a href="https://lucas-c.github.io/jdr/2200_le_jugement_des_dieux/">https://lucas-c.github.io/jdr/2200_le_jugement_des_dieux/</a></li>
<li>PDF in English : <a href="https://chezsoi.org/lucas/blog/images/jdr/2200-the-gods-judgement-v1.1.pdf">https://chezsoi.org/lucas/blog/images/jdr/2200-the-gods-judgement-v1.1.pdf</a></li>
<li>web version in English : <a href="https://lucas-c.github.io/jdr/2200_le_jugement_des_dieux/2200_the_gods_judgement.html">https://lucas-c.github.io/jdr/2200_le_jugement_des_dieux/2200_the_gods_judgement.html</a></li>
</ul>
<p>Il s'agit d'un jeu de rôle <a href="/lucas/blog/tag/narration-collective.html">narratif</a> pour 3 ou 4 joueurs, sans préparation nécessaire,
dans lequel vous aller incarner un dieu, ancien ou moderne,
ayant la charge de statuer du sort des mortels décédés en cette année 2200.
Vous avez chacun vos propres critères pour juger des actions des hommes,
de leur karma et du destin de leur âme.</p>
<p>Plusieurs BDs m'ont inspiré : la trilogie Nikopol d'Enki Bilal,
Sandman de Neil Gaiman, Candélabres d'Algésiras et Jaguar de Dufaux & Bosschaert.</p>
<p>Un grand merci à Elliot Jolivet <em>aka</em> <strong>Tenseï</strong> pour ses illustrations que j'ai employé !</p>
<p>Il comporte très peu de jets mais nécessite deux dés à 8 faces, quelques pions quelconques,
et optionnellement un sablier.</p>
<p>Si vous le testez, glissez-moi en commentaire ce que vous en avez pensé ! 😉</p>
<p><strong>EDIT [2020/10/23]</strong> : l'équipe de <em>1 MJ de trop</em> a enregistré une session de jeu de <em>2200: Le Jugement Des Dieux</em>.
Ils ont débatu du sort des 3 mortels sur une partie d'environ 3h, en incarnant les dieux suivants :</p>
<ul>
<li><strong>Muse</strong>, déesse vaporeuse de l'inspiration et du rêve</li>
<li><strong>Tailor</strong>, dieu du productivisme, avec un immense dossier sur les réalisations de chaque Mortel au cours de sa vie</li>
<li><strong>Tchernobog</strong>, dieu de la lune et de la nuit, des cycles, de la vie et de la mort</li>
<li><strong>Arès</strong>, dieu charismatique de la guerre et de la paix</li>
</ul>
<p>Vous pouvez retrouver l'enregistrement sur YouTube ici :</p>
<p><a href="https://www.youtube.com/watch?v=-N2AwQi084A"><img alt="Logo de 1 MJ de Trop" src="images/2019/06/1-mj-de-trop.jpg"></a></p>
<p>Cela ma motivé à traduire le jeu en anglais,
et à mettre les 2 versions en ligne sur itch.io :
<a href="https://lucas-c.itch.io/2200-le-jugement-des-dieux">https://lucas-c.itch.io/2200-le-jugement-des-dieux</a>.</p>
<style>
article img { width: 12rem; }
</style>Dungeon Heart2019-05-27T23:00:00+02:002019-05-27T23:00:00+02:00Lucas Cimontag:chezsoi.org,2019-05-27:/lucas/blog/dungeon-heart.html<p><a href="images/2019/05/dungeon-heart.png"><img alt="Couverture du jeu" src="images/2019/05/dungeon-heart.png"></a></p>
<p><em>(scroll below for the English version)</em></p>
<p>Hier, en compagnie de trois fidèles <em>playtesteurs</em> vétérans, j'ai eu l'occasion de jouer pour la première fois
à <a href="https://mare-baixa.itch.io/dungeon-heart">Dungeon Heart</a>, mon coup de cœur de la <a href="https://itch.io/jam/pamphletjam">Pamphlet Dungeon Jam</a>.</p>
<p>Très franchement, le jeu était carrément à la hauteur de mes attentes, et je crois …</p><p><a href="images/2019/05/dungeon-heart.png"><img alt="Couverture du jeu" src="images/2019/05/dungeon-heart.png"></a></p>
<p><em>(scroll below for the English version)</em></p>
<p>Hier, en compagnie de trois fidèles <em>playtesteurs</em> vétérans, j'ai eu l'occasion de jouer pour la première fois
à <a href="https://mare-baixa.itch.io/dungeon-heart">Dungeon Heart</a>, mon coup de cœur de la <a href="https://itch.io/jam/pamphletjam">Pamphlet Dungeon Jam</a>.</p>
<p>Très franchement, le jeu était carrément à la hauteur de mes attentes, et je crois qu'on s'est tous beaucoup amusés 😊</p>
<p>J'ai été tellement conquis que j'ai même déjà traduit le jeu en français et écrit une petite extension d'une page !</p>
<p>Dans cet article je compte présenter la version 1.6 de ce jeu que nous avons testé, faire un rapide compte rendu de notre partie,
puis discuter les <a href="/lucas/blog/tag/mecaniques-de-jeu.html">mécaniques de jeu</a> et explorer quelques variantes possibles.</p>
<h2>Cœur de Donjon</h2>
<p><em>Dungeon Heart</em> est un jeu de rôle sans meneur pour maximum 4 joueurs, monopage recto-verso au format "flyer",
qui était le format thématique de la <em>Pamphlet Jam</em>, le petit concourt d'écriture de JdR où le jeu a été soumis.</p>
<p>Le thème du jeu est très inspiré de celui du jeu vidéo <a href="https://fr.wikipedia.org/wiki/Dungeon_Keeper_%28jeu_vid%C3%A9o%2C_1997%29">Dungeon Keeper</a> :
les joueurs sont les gardiens d'un donjon dans lequel ne cessent d'affluer des aventuriers avides de gloire et de trésors.
Aux joueurs d'empêcher la progression des aventuriers à grand renfort de pièges, de monstres et d'énigmes parsemées dans le donjon.</p>
<p>Il faut noter que le jeu permet parfaitement de s'affranchir de l'ambiance comico-licencieuse du jeu vidéo.
Je m'imagine très bien une partie onirique, où un donjon tiré d'un univers de Miyazaki serait le terrier d'une créature endormie que les joueurs protègent.
Libre à vous d'imaginer ce que vous voulez, l'élément central restant le Cœur du Donjon :</p>
<blockquote>
<p>Il peut s’agir d’une créature, d’un objet. Cela peut aussi être quelque chose d’immatériel comme un mot, un rituel, une prophétie ou un rêve.</p>
</blockquote>
<p>En termes de mécaniques de jeu, chaque gardien a un archétype qui lui permet d'effectuer certaines actions dans le donjon,
en dépensant des battements de cœur, la monnaie du jeu partagée entre les joueurs.
Les aventuriers arrivent ensuite, par vague de 4, et progressent dans le donjon de pièce en pièce.
Et à chaque pièce visitée, les gardiens lancent des dés pour chaque menace qu'ils y ont placé,
en espérant vaincre les aventuriers sur un ⚅ mais en risquant de voir leurs menaces détruites sur un ⚀.</p>
<h2>HLM & œuf de dragon</h2>
<p>Durant notre partie, nous sommes restés dans la veine <em>Dungeon Keeper</em>, et d'autres inspirations nous sont très vites venues :
la BD <a href="https://fr.wikipedia.org/wiki/Donjon_%28bande_dessin%C3%A9e%29">Donjon</a> de Lewis Trondheim et Joann Sfar,
le <a href="http://www.penofchaos.com/warham/donjon-resume.htm">Donjon de Naheulbeuk</a>,
le manga <a href="images/readings/gloutons-et-dragons.jpg">Gloutons & Dragons</a>
ou encore le JdR <a href="http://www.subasylum.com/brainsalad/?page_id=945">Dragoons</a> de Willy Favre.</p>
<p>Voici le bilan en fin de partie.</p>
<p><strong>Description du donjon</strong></p>
<ul>
<li><strong>Cœur du donjon</strong> : un œuf de dragon</li>
<li><strong>Apparence du donjon</strong> : une tour HLM labyrinthique venue d'une autre dimension</li>
<li><strong>Odeur</strong> : sang, tripes & mort + eau de Javel car un des Gardiens est un peu maniaque</li>
<li><strong>Pièges</strong> : pentacles piégés, sol qui se dérobe sur une fosse, « téléporteur » constitué d'un énorme pendule qui renvoie dans la pièce précédente...</li>
<li><strong>Plan com'</strong> pour faire venir les aventuriers : des tracts distribués dans toute la région,
en promettant une espérance de vie doublée à quiconque mangerai l'œuf de dragon à la coque</li>
</ul>
<p><strong>Les Gardiens</strong></p>
<ul>
<li><strong>Melvin</strong>, un vieux gobelin ayant la charge de <em>Squelette</em> et féru de sa cornemuse familiale</li>
<li><strong>Einhart</strong>, un elfe chasseur mystique faisant office de <em>Moelle</em> et conservant dans une crypte secrète une princesse endormie</li>
<li><strong>Krang</strong>, un cerveau dans un golem ayant le rôle de... <em>Cerveau</em> et tenant énormément à son aquarium</li>
<li><strong>Karth</strong>, un vampire maniaque officiant comme <em>Pancréas</em> et amoureux de son aspirateur Dyson</li>
</ul>
<p><strong>Nos vaillants monstres</strong></p>
<ul>
<li>✝ mouches géantes acides</li>
<li>✝ serpents explosifs</li>
<li>✝ requin des roches</li>
<li>✝ mamies hypnotiques</li>
<li>✝ ours-hibou catcheur (notre meilleur combattant !)</li>
<li>✝ boules poilues</li>
<li>✝ écureuil laser</li>
<li>✝ troll maboul</li>
<li>✝ mouches Piranha</li>
<li>✝ Tentacrush la pieuvre</li>
<li>la sœur jumelle de l'ours-hibou catcheur</li>
<li>✝ <em>gunslinger gorilla</em></li>
<li>sangsues-termites</li>
<li>✝ tyranoeil qui louche</li>
<li>✝ golem de pierre</li>
<li>zombie pirate / grand mère / mousquetaire</li>
<li>sirène zombie</li>
<li>blobs</li>
</ul>
<p><strong>Les vagues d'aventuriers occis</strong></p>
<ol>
<li>Demi-orc + Halfeling + Gnome + Naine</li>
<li>Samouraï + Fée + Paladin + Amazone</li>
<li>Troll + Curé zélote + Une elfe + Un barde</li>
<li>Gorille + Pirate + Mousquetaire + Vieil archéo-explorateur fou</li>
<li>Pyromane + Chat parlant + Nécromancien & sa grand-mère</li>
<li>4 hobbits empilés</li>
</ol>
<figure role="group">
<img alt="Notre donjon au terme de la partie" src="images/2019/05/notre-donjon.jpg">
<figcaption>Notre donjon au terme de la partie</figcaption>
</figure>
<h2>❓ Les questions en suspens</h2>
<p>Quelques points de règles qui nous ont paru peu clairs :</p>
<ul>
<li>Quand les aventuriers entrent dans une pièce, est-ce que chaque joueur lance les dés correspondant à ses menaces puis applique ses effets ?
Ou bien tous les dés sont-ils tous lancés simultanément, les effets de toutes les menaces de la pièce s'appliquant sur chacun ?</li>
</ul>
<p>⇒ Les concepteurs ont répondu sur <code>itch.io</code> qu'il s'agissait plutôt de la première option.</p>
<ul>
<li>Lorsqu'un Gardien se sacrifie, les aventuriers restants sont-ils éjectés du donjon ou vaincus ?</li>
</ul>
<p>⇒ Le cas ne s'est finalement pas produit.</p>
<ul>
<li>Peut-il y avoir plusieurs emplacements "Trésor" dans le donjon ?
Et si oui, est-ce que les aventuriers se content d'un seul avant de se rendre au cœur du donjon ?</li>
</ul>
<p>⇒ Nous avons supposé que la réponse était "oui" aux deux questions mais le cas ne s'est pas présenté.</p>
<ul>
<li>Est-ce que Moelle peut recruter un aventurier vaincu comme monstre si plus aucun emplacement n'est libre ?</li>
</ul>
<p>⇒ On a supposé que non.</p>
<h2>👍 Ce qui a marché</h2>
<p>Le jeu est très fun ! On s'est amusés comme des petits fous à trucider des aventuriers 😜</p>
<p>La mécanique de jeu est assez rapide mais donne plein d'opportunités et d'inspiration pour faire quelques belles descriptions.</p>
<p>On s'attache très vite à nos monstres, et on rage quand un jet de dés malheureux les emporte dans la tombe !</p>
<p>À l'exception de quelques points ci-dessous, j'ai trouvé dans l'ensemble que le jeu était bien équilibré malgré la grande part de hasard.</p>
<p>La majorité des rôles sont funs à jouer, avec des petites règles différentes mais assez complémentaires.</p>
<h2>🤔 Ce qui a moins marché</h2>
<p>Petit retour d'expérience personnelle tout d'abord, j'avais pensé utiliser ce fantastique générateur de PNJs d'univers de fantasy : <a href="http://www.dmheroes.com">http://www.dmheroes.com</a>.
Cependant il s'est avéré paradoxalement trop riche : les descriptions prennent du temps à être lues et les personnages ne sont pas assez
« mémorables ». Dès la 2e vague d'aventurier, nous nous sommes contentés d'un mot pour décrire chaque aventurier,
et ça a beaucoup fluidifié la phase d'invasion.</p>
<ul>
<li>nous n'avons jamais vraiment crains pour le cœur du donjon : aucun Gardien n'a eut à faire de sacrifice car aucun aventurier n'a atteint
la dernière pièce. Et le moment le plus tendu était en réalité au tout début de la partie.</li>
</ul>
<p>💡 pour augmenter progressivement la difficulté sans rendre le jeu impossible, le nombre d'aventurier pourrait augmenter de 1
à chaque nouvelle vague, sauf lorsqu'un aventurier atteint le cœur du donjon et alors on redescend à 4 pour la prochaine invasion</p>
<ul>
<li>nous nous sommes arrêtés après la 6e vague d'aventuriers, car un joueur devait partir mais aussi car le jeu devenait un peu répétitif</li>
</ul>
<p>💡 l'idée précédente pourrait déjà ajouter un peu de piment, et pour ma part j'ai imaginé dans mon extension que des « Héros »,
des aventuriers un peu plus coriaces, apparaissent parfois</p>
<ul>
<li>
<p>en-dehors du fait que j'ai eu la poisse aux dés en le jouant (mes énigmes ont systématiquement été détruite au premier jet de dé),
le rôle Cerveau est clairement le moins intéressant à jouer. Ses énigmes ne peuvent être corsées qu'une fois,
sont plus chères que des pièges et moins dangereuses que les monstres pour un effet spécial peu attractif.
Dans une moindre mesure, le rôle du Squelette est également un peu limité comme il ne jette aucun dé.</p>
</li>
<li>
<p>le piège magique est lui clairement trop puissant.
Il est bien plus intéressant que le piège mécanique (car au final les aventuriers doivent retraverser 2 pièces et non une)
et dans notre partie il s'est avéré être la stratégie de loin la plus efficace pour ralentir les aventuriers,
au point que nous en avons mis un dans chaque pièce !</p>
</li>
</ul>
<p>💡 Nous avions 2 suggestions pour <em>nerfer</em> cette capacité : que le piège se détruise après avoir téléporté les aventuriers;
ou bien que ces pièges doivent fonctionner par paire, obligeant d'en construire 2 pour qu'ils soient utiles.</p>
<ul>
<li>en ce qui concerne les options "bonus" à la mort d'un aventurier, nous n'avons jamais fait le choix de n'avoir que 2 aventuriers
lors de la prochaine vague. Tactiquement ça ne nous paraissait pas intéressant, sans doute encore car la difficulté nous paraissait peu élevée.
Nous avons généralement opté pour le battement de cœur ou le gain de monstre, cette seconde option ayant l'avantage d'être
moins chère que le coup de base d'une créature.
Les trois autres récompenses nous ont paru initialement redondantes avec le gain de battement de cœur,
puis nous avons compris que cela permettait « d'acheter » ces actions en cours d'invasion
là où les battements de cœur ne peuvent être dépensés qu'en phase de préparation.</li>
</ul>
<p>💡 Peut-être que plutôt que de laisser le choix dans les bonus,
la récompense pourrait dépendre de la menace qui a éliminé l'aventurier ?
Par exemple : vaincu par énigme -> +1 monstre / vaincu par piège -> +1 battement de cœur / vaincu par monstre : monstre +1 dé</p>
<ul>
<li>la mécanique des types d'emplacement doublant le nombre de dés ne nous a pas semblé avoir beaucoup d'intérêt.
Durant notre partie, TOUTES les menaces ont toujours bénéficié de ce bonus,
et nous n'avons jamais eu à placer une menace dans un emplacement de type différent.</li>
</ul>
<p>💡 Peut-être qu'il faudrait mieux supprimer les types d'emplacement, mais par contraire permettre au Squelette
d'installer un trésor dans un emplacement pour ❤, et de fournir un bonus de doublement des dés à une menace pour ❤
(ce bonus disparaissant si la menace est détruite).</p>
<ul>
<li>nourrir les monstres / corser les énigmes ne nous est pas apparu comme une tactique très intéressante</li>
</ul>
<p>💡 une variante pourrait être que lorsqu'un ⚀ est obtenu pour un monstre <em>+1 dé</em>, il ne meurt pas mais perd son dé supplémentaire</p>
<h2>En définitive</h2>
<p>La mécanique de ce jeu est franchement fun et bien trouvée.</p>
<p>Autour de la table hier soir, nous étions tous partants pour refaire une partie à l'occasion,
donc je crois que je peux recommander ce jeu à tout amateur de ce type de format court de jeu de rôle !</p>
<p>Pour ma part je vais terminer de concocter ma petite extension intitulée « Héros & Sortilèges » 😉</p>
<hr>
<p>Yesterday, with some friends of mine, all regular RPG players, I had the pleasure to play <a href="https://mare-baixa.itch.io/dungeon-heart">Dungeon Heart</a>
for the first time. It is my favorite game from the recent <a href="https://itch.io/jam/pamphletjam">Pamphlet Dungeon Jam</a>.</p>
<p>Honestly, the game totally met my expectations, and I think we all had a really good time 😊</p>
<p>I was actually so enthralled that I've already written a translation in French and a first draft of a one-page expansion !</p>
<p>In this article I want to introduce the 1.6 version of this game we tested, make a short summary of our session,
and then discuss the game mechanics and explore some alternative variants.</p>
<h2>From the Bottom of Our Heart</h2>
<p><em>Dungeon Heart</em> is a single-sheet pen & paper RPG for up to 4 players with no GM.
It has a "flyer" layout, which was the requested format for the <em>Pamphlet Jam</em> where it was submitted.</p>
<p>The game is largely inspired by the <a href="https://en.wikipedia.org/wiki/Dungeon_Keeper">Dungeon Keeper</a> video game :
players are Keepers whose task is to defend the Dungeon Heart from hordes of greedy adventurers.
They have to stop them using large amounts of traps, monsters and riddles.</p>
<p>Note that you do not have to stick to the licentious humur of the video game to play Dungeon Heart.
I can totally figure an oneiric session where Keepers have to protect a sleeping creature in a Miyazaki-inspired dungeon.
You are free to imagine anything you want for the central part of the game that is the Dungeon Heart, as the rules state:</p>
<blockquote>
<p>It can be a place, a creature, an object. It can also be something immaterial like a word, a ritual, a prophecy or even a dream.</p>
</blockquote>
<p>In terms of game mechanics, every Keeper has an archetype which gives them unique abilities.
Each one contributes to the defense of the dungeon and costs heart beats, the game currency.
Then adventurers come by waves of 4, and progress room by room in the dungeon.
For every visited room Keepers can roll the dice for the threats they have set up.
A ⚅ means an adventurer is defeated, but a ⚀ means a destroyed threat.</p>
<h2>Skyscrapers & dragon egg</h2>
<p>For our game we sticked to the <em>Dungeon Keeper</em> spirit, with extra inspiration from other works :
the <a href="https://en.wikipedia.org/wiki/Dungeon_%28comics%29">Donjon</a> comics by Lewis Trondheim & Joann Sfar,
the Naheulbeuk Dungeon French audio series,
the <a href="https://en.wikipedia.org/wiki/Delicious_in_Dungeon">Delicious in Dungeon</a> manga
or even the French tabletop RPG <a href="http://www.subasylum.com/brainsalad/?page_id=945">Dragoons</a> by Willy Favre.</p>
<p>There is a summary of our session.</p>
<p><strong>Dungeon description</strong></p>
<ul>
<li><strong>Dungeon Heart</strong> : a dragon egg</li>
<li><strong>Dungeon appearance</strong> : a maze skyscraper from another dimension</li>
<li><strong>Smell</strong> : blood, guts & death + bleach because one of the Keepers is a little obsessive with cleanliness</li>
<li><strong>PR plan</strong> to lure adventurers inside : flyers distributed all over the surrounding area,
promising a doubled life expectancy to those who would eat the dragon egg soft-boiled</li>
</ul>
<p><strong>The Keepers</strong></p>
<ul>
<li><strong>Melvin</strong>, an old goblin acting as the <em>Skeleton</em> and keen on his ancestral bagpipes</li>
<li><strong>Einhart</strong>, a mystical elf hunter with the role of the <em>Marrow</em> and secretly guarding a sleeping princess</li>
<li><strong>Krang</strong>, a brain in a golem officiating as... <em>Brain</em>, and crazy about his giant aquarium</li>
<li><strong>Karth</strong>, an obsessive vampire, being the party <em>Pancreas</em> and in love with his Dyson vacuum cleaner</li>
</ul>
<p><strong>Our valiant monsters</strong></p>
<ul>
<li>✝ giant acid flies</li>
<li>✝ explosive snakes</li>
<li>✝ rock shark</li>
<li>✝ hypnotic grannies</li>
<li>✝ bear-owl wrestler (our best fighter !)</li>
<li>✝ hairy balls</li>
<li>✝ laser squirrel</li>
<li>✝ nuts troll</li>
<li>✝ Piranha flies</li>
<li>✝ Tentacrush the squid</li>
<li>the bear-owl wrestler twin sister</li>
<li>✝ gunslinger gorilla</li>
<li>termite-leeches</li>
<li>✝ squinting beholder</li>
<li>✝ rock golem</li>
<li>zombie pirate / granny / musketeer</li>
<li>zombie mermaid</li>
<li>blobs</li>
</ul>
<p><strong>Slayed waves of adventurers</strong></p>
<ol>
<li>Half-ork + halfling + gnome + dwarf</li>
<li>Samurai + fairy + paladin + amazon</li>
<li>Troll + zaelot cleric + elf + bard</li>
<li>gorilla + pirate + musketeer + old crazy archeo-explorer</li>
<li>Pyromaniac + speaking cat + necromancer & his granny</li>
<li>4 hobbits stacked-up</li>
</ol>
<figure role="group">
<img alt="Our dungeon at the end of the session" src="images/2019/05/notre-donjon.jpg">
<figcaption>Our dungeon at the end of the session</figcaption>
</figure>
<h2>❓ The remaining questions</h2>
<p>Some points of the rule that remained slightly unclear :</p>
<ul>
<li>When adventurers enter a room, does each player roll its own threats dice and check if special effects apply, or are all dice rolled at once and all threats effects apply to all dice ?</li>
</ul>
<p>⇒ The author replied on <code>itch.io</code> :</p>
<blockquote>
<p>We are currently playing like this: each player gathers their dice, then everyone rolls the dice and each player checks their threats effects.</p>
</blockquote>
<ul>
<li>When a Keeper make a sacrifice, are the remaining adventurers kicked out or defeated ?</li>
</ul>
<p>⇒ It never happened during our game.</p>
<ul>
<li>Can there be several "Treasure" slots in the dungeon ?
And if yes, are adventurer attracted by the second Treasure room they met ?</li>
</ul>
<p>⇒ We supposed the answer was "yes" to both questions but this situation never occurred in our session.</p>
<ul>
<li>Can a defeated adventurer be turned evil but kept "in stock" if there are no free slots ?</li>
</ul>
<p>⇒ We guessed no.</p>
<h2>👍 What worked well</h2>
<p>The game is really fun ! We had a really good time slaying all those adventurers 😜</p>
<p>The game mechanic is simple & fluid but gives many opportunities to describe memorable scenes.</p>
<p>We quickly start to care about our monsters, and we rage when some bad roll take them to the grave !</p>
<p>Except from some minor points listed below, overall I found the game very balanced given the importance of randomness in the mechanics.</p>
<p>Must archetypes are funs to play, each with its own singular but complementary rules.</p>
<h2>🤔 What worked less well</h2>
<p>First some feedback on an idea I had that failed : I wanted to use this amazing fantasy NPC generator : <a href="http://www.dmheroes.com">http://www.dmheroes.com</a>.
However it revealed to be too rich : descriptions were taking a bit of time to be read and characters weren't striking enough to be easily remembered.
From the second wave onward, we described adventurers with a single word and it made the invasion phase a lot more fluid.</p>
<ul>
<li>we never really feared for our Dungeon Heart : no Keeper had to make a sacrifice because no adventurer reached the last room.
Really it was only a bit tense on the very first invasion phase.</li>
</ul>
<p>💡 to increase the difficulty progressively, the number of adventurers could increase by 1 on every new invasion phase.
However once an adventurer has reached the Dungeon Heart, then on next phase only 4 adventurers would come.</p>
<ul>
<li>we stopped at after the 6th wave of adventurers, as a player had to leave but also because the game started to feel a little repetitive</li>
</ul>
<p>💡 the previous idea could already add some spice to the game as time goes on. Another approach I took in the expansion I drafter was to introduce « Heroes »,
more tenacious adventurers that can sometime lead a party in the dungeon</p>
<ul>
<li>
<p>putting aside the fact that I was very very unlucky with dice (all my riddles felt in pieces on my first roll),
the Brain role is less interesting to play in my opinion. His riddles can only be hardened once,
they are more expansive than traps and less dangerous than monsters for a not-so-attractive special effect.
To a lesser extent, the Skeleton archetype also lacks a bit of interactivity, as he never makes any roll during the game,
and don't use his abilities very often.</p>
</li>
<li>
<p>the magic trap is clearly too powerful !
It is a lot more interesting than the mechanical trap (in the end adventurers will have to go through 2 rooms again, not 1)
and in our game it appeared to be by far the most efficient strategy to slow down adventurers,
to the point that we put one in every room !</p>
</li>
</ul>
<p>💡 We had a few ides to <em>nerf</em> its effect : the trap could auto-destroy if the teleportation is triggered;
or those traps could only work as a pair, forcing Pancreas to build 2 of them to be useful; or it could only work once per phase (simple, my favorite solution).</p>
<ul>
<li>regarding the "bonus" options when an adventurer is defeated, we never picked the <em>flee cowardly</em> option.
Tactically it did not seem interesting to us as it meant less heart beats, but that's maybe because the difficulty / risk seemed relatively low to us.
We generally choose to pick the extra heart beat or the adventurer turned evil, as this option is cheaper than buying a new monster.
The other 3 options initially seemed to us redundant with the gain of a heart beat,
until we realized it allowed to get access to those <em>power ups</em> during the invasion phase,
whereas we could only spend heart beats during the preparation phase. But still we never picked them.</li>
</ul>
<p>💡 As a drastically different approach, maybe instead of letting players choose between 6 options,
some rewards could only be available depending on the threat that vanquished the adventurer ?
For example : defeated by a riddle -> +1 monster / defeated by a trap -> +1 heart beat / defeated by a monster : it is fed.</p>
<ul>
<li>the whole "matching slot types doubling dice" mechanic did not seem to bring much value to the game to me.
In our game, all our threats benefited from this bonus,
we never had to put non-matching threat in a slot of a some type.</li>
</ul>
<p>💡 Maybe slot types could disappear, but the Skeleton could have extra abilities as a replacement :
setup a treasure in a slot for ❤; double the dice of an established threat for ❤, by incorporating it more in the dungeon
(would the threat be destroyed, this bonus would vanish too).</p>
<ul>
<li>feeding monsters / hardening riddles did not seem to us like an attractive tactical choice</li>
</ul>
<p>💡 a variant could be that when ⚀ is rolled for a fed monster (with <em>+1 die</em>), it doesn't die but loose its extra die</p>
<h2>Final word</h2>
<p>I loved the core game mechanic, which I found really fun and well-thought.</p>
<p>Around the table yesterday, we all were willing to play another game one of those days,
so I think I can warmly recommend Dungeon Heart to any player who like those kind of original micro-RPG !</p>
<p>As for myself, I'm going back to my small expansion named « Heroes & Spells » 😉</p>
<style>
hr { margin: 5rem; }
</style>Concours photo « L'eau, entre culture(s) et imaginaire »2019-05-19T16:00:00+02:002019-05-19T16:00:00+02:00Lucas Cimontag:chezsoi.org,2019-05-19:/lucas/blog/concours-photo-cpie-l-eau-entre-culture-s-et-imaginaire.html<p>Je viens d'apprendre que la photo que j'avais soumise à un petit concours de photo avait été sélectionnée pour être affichée en grand format !
La voici :</p>
<p><a href="images/2019/05/photo-concours-cpie-l-eau-entre-culture-s-et-imaginaire.jpg"><img alt="Cliquez sur cette photo pour accéder à la version originale haute résolution" src="images/2019/05/photo-concours-cpie-l-eau-entre-culture-s-et-imaginaire-small.jpg"></a></p>
<p>Il s'agit du concours organisé par le <a href="http://www.cpie-broceliande.fr">CPIE Forêt de Brocéliande</a>
sur le thème « L'eau, entre culture(s) et imaginaire ».</p>
<p>Je crois qu'une petite …</p><p>Je viens d'apprendre que la photo que j'avais soumise à un petit concours de photo avait été sélectionnée pour être affichée en grand format !
La voici :</p>
<p><a href="images/2019/05/photo-concours-cpie-l-eau-entre-culture-s-et-imaginaire.jpg"><img alt="Cliquez sur cette photo pour accéder à la version originale haute résolution" src="images/2019/05/photo-concours-cpie-l-eau-entre-culture-s-et-imaginaire-small.jpg"></a></p>
<p>Il s'agit du concours organisé par le <a href="http://www.cpie-broceliande.fr">CPIE Forêt de Brocéliande</a>
sur le thème « L'eau, entre culture(s) et imaginaire ».</p>
<p>Je crois qu'une petite trentaine de photos ont été soumises,
et les 10 sélectionnées vont être exposées sur le Chemin Buissonnier autour de l'étang de Concoret
pour la 6ème édition du parcours d'art « Élucubrations d'une goutte », du 15 juin au 30 septembre 2019.</p>
<p>Je tiens à remercier le CPIE d'organiser ce concours, d'avoir retenu ma photo et de la mettre ainsi en valeur !
Merci également a Laëtitia Beschus qui m'a poussé à participer 😉</p>
<p>Ne m'estimant pas extrêmement doué en photographie, je suis d'autant plus touché que celle-ci a pu plaire au jury !</p>
<p>J'en profite pour donner un peu de contexte sur ce qui y est représenté :
l'été dernier nous avons réalisé avec ma compagne un voyage à vélo de 74 jours à travers l'Europe.
Nous avons longé le Danube et quelque part entre Straubin et Passau nous avons aperçu cette balancoire suspendue
à un arbre près du fleuve.</p>
<p>La photo n'a pas capturé l'oscillement de l'eau et des feuilles sous le vent, qui rajoutait au charme de la scène.
Il reste tout de même ce petit côté magique d'une balançoire suspendue à quelques centimètre de la surface,
au milieu de nulle part.
Je suppose que, comme à Brocéliande, il existe par là bas des korrigans farceurs,
qui apprécient l'été se balancer par dessus le Danube en regardant les cyclistes passer !</p>
<p>La photo a été très légèrement recadrée et retouchée avec le logiciel libre <a href="https://www.gimp.org">GIMP</a>.</p>
<script>
document.querySelectorAll('article img').forEach(img => img.title = img.alt)
</script>Topoloku2019-05-15T23:00:00+02:002019-05-15T23:00:00+02:00Lucas Cimontag:chezsoi.org,2019-05-15:/lucas/blog/topoloku.html<p><link rel="stylesheet" type="text/css" href="images/enigmes/topoloku.css"></p>
<p>Drôle de nom, n'est-ce pas 😊</p>
<p>C'est ainsi que j'ai décidé de baptiser un puzzle que j'ai conçu le week-end dernier,
comme un cadeau pour pour ma compagne.</p>
<p>Le but est de remplir une grille de lettres :</p>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th>T</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td>S</td>
<td>E</td>
<td></td>
</tr>
</tbody>
</table>
<p>↓</p>
<table>
<thead>
<tr>
<th>T</th>
<th>T</th>
<th>T</th>
<th>E</th>
</tr>
</thead>
<tbody>
<tr>
<td>S</td>
<td>S</td>
<td>E</td>
<td>E</td>
</tr>
</tbody>
</table>
<p>→</p>
<p><strong>Voici les règles …</strong></p><p><link rel="stylesheet" type="text/css" href="images/enigmes/topoloku.css"></p>
<p>Drôle de nom, n'est-ce pas 😊</p>
<p>C'est ainsi que j'ai décidé de baptiser un puzzle que j'ai conçu le week-end dernier,
comme un cadeau pour pour ma compagne.</p>
<p>Le but est de remplir une grille de lettres :</p>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th>T</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td>S</td>
<td>E</td>
<td></td>
</tr>
</tbody>
</table>
<p>↓</p>
<table>
<thead>
<tr>
<th>T</th>
<th>T</th>
<th>T</th>
<th>E</th>
</tr>
</thead>
<tbody>
<tr>
<td>S</td>
<td>S</td>
<td>E</td>
<td>E</td>
</tr>
</tbody>
</table>
<p>→</p>
<p><strong>Voici les règles à respecter :</strong></p>
<ol>
<li>
<p>le nombre d'<strong>extrémités</strong> de chaque lettre indique le <strong>nombre</strong> de fois qu'elle doit apparaître dans <strong>un même groupe de lettres adjacentes</strong> (horizontalement ou verticalement). Une seule ou zéro extrémité signifie que le lettre doit être isolée.</p>
</li>
<li>
<p>le nombre de boucles de chaque lettre indique le <strong>nombre de côtés de la grille</strong> qu'elle doit toucher, <strong>au minimum</strong>.</p>
</li>
</ol>
<p><strong>Exemples :</strong></p>
<table>
<thead>
<tr>
<th>L</th>
<th>2 extrémités<br>0 boucle</th>
<th>T</th>
<th>3 extrémités<br>0 boucle</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
<td>2 extrémités<br>1 boucle</td>
<td>#</td>
<td>8 extrémités<br>1 boucle</td>
</tr>
<tr>
<td>B</td>
<td>0 extrémités<br>2 boucles</td>
<td>%</td>
<td>2 extrémités<br>2 boucles</td>
</tr>
</tbody>
</table>
<p>Deux lettres ayant le même nombre d'extrémités et de boucles sont complètement équivalentes dans ce jeu.
En mathématiques, on dirait qu'elles ont la même <a href="https://fr.wikipedia.org/wiki/Topologie">topologie</a>,
d'où le nom du jeu 😉</p>
<table class="ko-ok"><thead><tr>
<th>KO</th> <th>OK</th>
</tr></thead><tbody><tr><td>
<table><thead><tr>
<th>L</th> <th>A</th>
</tr></thead><tbody><tr>
<td>A</td> <td>L</td>
</tr></tbody></table>
<span class="wrong comment">❌ les L ne sont pas adjacents</span>
</td><td>
<table><thead><tr>
<th>L</th> <th>A</th>
</tr></thead><tbody><tr>
<td>L</td> <td>A</td>
</tr></tbody></table>
<span class="comment">✔️</span>
</td></tr><tr><td>
<table><thead><tr>
<th>A</th> <th>L</th> <th>L</th>
</tr></thead><tbody><tr>
<td>A</td> <td>L</td> <td>L</td>
</tr></tbody></table>
<span class="wrong comment">❌ les L forment un groupe trop grand</span>
</td><td>
<table><thead><tr>
<th>L</th> <th>A</th> <td>L</td>
</tr></thead><tbody><tr>
<td>L</td> <td>A</td> <td>L</td>
</tr></tbody></table>
<span class="comment">✔️</span>
</td></tr><tr><td>
<table><thead><tr>
<th>E</th> <th>E</th>
</tr></thead><tbody><tr>
<td>E</td> <td>L</td>
</tr></tbody></table>
<span class="wrong comment">❌ les L ne forment pas un groupe de 2</span>
</td><td>
<table><thead><tr>
<th>E</th> <th>E</th>
</tr></thead><tbody><tr>
<td>E</td> <td>P</td>
</tr></tbody></table>
<span class="comment">✔️</span>
</td></tr><tr><td>
<table><thead><tr>
<th>H</th> <th>H</th> <th>H</th> <th>H</th>
</tr></thead><tbody><tr>
<th>O</th> <th>A</th> <th>A</th> <th>O</th>
</tr><tr>
<th>I</th> <th>I</th> <th>I</th> <th>I</th>
</tr></tbody></table>
<span class="wrong comment">❌ les A ne touchent pas le bord</span>
</td><td>
<table><thead><tr>
<th>H</th> <th>H</th> <th>O</th> <th>I</th>
</tr></thead><tbody><tr>
<th>H</th> <th>H</th> <th>I</th> <th>I</th>
</tr><tr>
<th>A</th> <th>A</th> <th>I</th> <th>O</th>
</tr></tbody></table>
<span class="comment">✔️</span>
</td></tr></tbody></table>
<p><strong>Quelques puzzles :</strong></p>
<p>(les indications <span class="red">en rouge</span> permettent de lire un mot caché lorsque la grille est résolue)</p>
<p>L'</p>
<!--
|S |_1_ |A |_2_ |
|-----|-----|-----|-----|
|_3_ |_4_ |_6_ |_5_ |
|E |_7_ |C |N |
-->
<table class="topoloku" data-size="[4, 3]"
data-initial-letters='{"0,0": "S", "2,0": "A", "0,2": "E", "2,2": "C", "3,2": "N"}'
data-missing-letters="B"
data-secret-word-pos="[[1, 0], [3, 0], [0, 1], [1, 1], [3, 1], [2, 1], [1, 2]]"
data-display-secret-word-pos="true"></table>
<p>de l'</p>
<!--
| |R | |T |
|-----|-----|-----|-----|
| | |T |E |
| |T |_1_ |_4_ |
|T |_2_ |R |_3_ |
-->
<table class="topoloku" data-size="[4, 4]"
data-initial-letters='{"1,0": "R", "3,0": "T", "2,1": "T", "1,2": "T", "0,3": "T", "2,3": "R", "3,1": "E"}'
data-secret-word-pos="[[2, 2], [1, 3], [3, 3], [3, 2]]"
data-display-secret-word-pos="true"></table>
<p>qu'on</p>
<!--
| | |A |I |M |
|-----|-----|-----|-----|-----|
|_4_ |I | | |_3_ |
| |_2_ | | | |
|I | |E |I |_1_ |
-->
<table class="topoloku" data-size="[5, 4]"
data-initial-letters='{"2,0": "A", "3,0": "I", "4,0": "M", "1,1": "I", "0,3": "I", "2,3": "E", "3,3": "I"}'
data-custom-letters-topo='{"I": {"loops": 0, "ends": 4}}'
data-secret-word-pos="[[4, 3], [1, 2], [4, 1], [0, 1]]"
data-display-secret-word-pos="true"></table>
<p>c'est de l'</p>
<!--
| |N | |U |E |
|-----|-----|-----|-----|-----|
| | | |_2_ |_1_ |
|I | | |_3_ | |
|N | |_5_ I|_4_ |U |
-->
<table class="topoloku" data-size="[5, 4]"
data-initial-letters='{"1,0": "N", "3,0": "U", "4,0": "E", "0,2": "I", "0,3": "N", "2,3": "I", "4,3": "U"}'
data-custom-letters-topo='{"I": {"loops": 0, "ends": 4}}'
data-secret-word-pos="[[4, 1], [3, 1], [3, 2], [3, 3], [2, 3]]"
data-display-secret-word-pos="true"></table>
<p>que</p>
<!--
|I | | |R |_1_ |
|-----|-----|-----|-----|-----|
| | | |I | |
| | |_2_ |E | |
|I |_4_ |N | | |
|E |_3_ | | |I |
-->
<table class="topoloku" data-size="[5, 5]"
data-initial-letters='{"0,0": "I", "3,0": "R", "3,1": "I", "3,2": "E", "0,3": "I", "2,3": "N", "0,4": "E", "4,4": "I"}'
data-custom-letters-topo='{"I": {"loops": 0, "ends": 4}}'
data-secret-word-pos="[[4,0], [2,2], [1,3], [1, 4]]"
data-display-secret-word-pos="true"></table>
<p>ne</p>
<!--
|_3_ |_1_ |_2_ |
|-----|-----|-----|
|U |_4_ | |
|T | |E |
-->
<table class="topoloku" data-size="[3, 3]"
data-initial-letters='{"0,1": "U", "0,2": "T", "2,2": "E"}'
data-missing-letters="P"
data-secret-word-pos="[[1,0], [2,0], [0,0], [1, 1]]"
data-display-secret-word-pos="true"></table>
<!--
| | |E |_1_ |
|-----|-----|-----|-----|
|P _6_|S _3_|_4_ |_7_ |
|_2_ |_5_ | |E |
|I | |R |_8_ |
-->
<table class="topoloku" data-size="[4, 4]"
data-initial-letters='{"2,0": "E", "0,1": "P", "1,1": "S", "3,2": "E", "0,3": "I", "2,3": "R"}'
data-missing-letters="D"
data-custom-letters-topo='{"I": {"loops": 0, "ends": 4}}'
data-secret-word-pos="[[3,0], [0,2], [1,1], [2,1], [1,2], [0,1], [3,0], [3,3]]"
data-display-secret-word-pos="true"></table>
<p><strong>M-J. Riccoboni - 1759</strong></p>
<p><strong>EDIT [2020/06/02]</strong> : plus de grilles sont disponibles dans cet autre article :
<a href="nonograms-topolokus-et-compagnie.html">Nonograms, Topolokus et compagnie</a></p>
<p><strong>EDIT [2020/06/23]</strong> : une amie m'a signalé que certaines grilles étaient invalides,
car elles ne respectaient pas la règles que <strong>chaque</strong> lettre avec une boucle doit toucher un bord.
Je les ai donc modifié pour corriger ça.</p>
<style>
@media screen and (min-width: 40rem) {
table:nth-of-type(1) { float:left; margin-left: 13%; margin-right: 5%; margin-bottom: 15px; }
table:nth-of-type(1) + p { display: none; }
table:nth-of-type(2) { float:right; margin-right: 13%; margin-left: 5%; margin-bottom: 15px; }
table:nth-of-type(2) + p { clear: none; display:block; padding-top: 1.5rem; font-size: 2rem; }
}
p { clear: both; }
@media screen and (max-width: 40rem) {
table:nth-of-type(1) + p { font-size: 2rem; padding-left: 4.65rem; }
table:nth-of-type(2) + p { display: none; }
}
.red { color: #ee0403; }
table:nth-of-type(3) { margin: 0 auto; }
table:nth-of-type(3) td, table:nth-of-type(3) th { border: none; }
table:nth-of-type(3) td:nth-of-type(2), table:nth-of-type(3) th:nth-of-type(2),
table:nth-of-type(3) td:nth-of-type(4), table:nth-of-type(3) th:nth-of-type(4) { font-size: 1rem; text-align: left; width: 8rem; }
table.ko-ok { width: 100%; margin: 2rem 0; }
table.ko-ok > * > tr > th:nth-of-type(1) { color: #ee0403; }
table.ko-ok > * > tr > th:nth-of-type(2) { color: green; }
table.ko-ok > * > tr > td, table.ko-ok > * > tr > th { width: 50%; padding: .5rem; border-top: none; border-bottom: none; }
.comment { color: green; font-size: 1rem; float: right; padding-right: 50%; }
.wrong.comment { color: #ee0403; }
table.topoloku { font-family: Consolas; } /* To display "I" letters with 4 edges */
</style>
<script type="module">
import { renderTopolokuUsingDataAttrs } from './images/enigmes/topoloku.js';
Array.from(document.getElementsByClassName('topoloku')).forEach(renderTopolokuUsingDataAttrs);
document.querySelectorAll('article table').forEach((table) => table.classList.add('topoloku'))
</script>Hiding secret words in Sudokus with Python2019-05-05T15:30:00+02:002019-05-05T15:30:00+02:00Lucas Cimontag:chezsoi.org,2019-05-05:/lucas/blog/hiding-secret-words-in-sudokus-with-python.html<p>Yesterday I was crafting some puzzles for my girlfriends,
and I was looking for letter-based ones where I a secret word
would be revealed once solved.</p>
<p><img alt="Solution of a size-12 Wordoku I created displaying the hidden words: AMOUR PASSION" src="images/2019/05/sudoku-letters-amourpassion-solution.png">
<img alt="Wordoku initial grid for this same puzzle" src="images/2019/05/sudoku-letters-amourpassion-grid.png"></p>
<p>With this same goal, I had already once worked on an open-source JS
<a href="https://en.wikipedia.org/wiki/Word_search">word search</a> generator:
<a href="https://lucas-c.github.io/wordfind/">https://lucas-c.github.io/wordfind/</a></p>
<p><em>(pour les francophones …</em></p><p>Yesterday I was crafting some puzzles for my girlfriends,
and I was looking for letter-based ones where I a secret word
would be revealed once solved.</p>
<p><img alt="Solution of a size-12 Wordoku I created displaying the hidden words: AMOUR PASSION" src="images/2019/05/sudoku-letters-amourpassion-solution.png">
<img alt="Wordoku initial grid for this same puzzle" src="images/2019/05/sudoku-letters-amourpassion-grid.png"></p>
<p>With this same goal, I had already once worked on an open-source JS
<a href="https://en.wikipedia.org/wiki/Word_search">word search</a> generator:
<a href="https://lucas-c.github.io/wordfind/">https://lucas-c.github.io/wordfind/</a></p>
<p><em>(pour les francophones, vous trouverez quelques autres générateurs de jeux de lettres / chiffres dans <a href="https://linuxfr.org/news/generateurs-de-jeux-de-lettres-chiffres-libres">cet article sur linuxfr.org</a>)</em></p>
<p>This time I started crafting <a href="https://fr.wikipedia.org/wiki/Masyu">Masyu</a> puzzles into letter shapes,
but then I realized Sudokus would be a perfect pick, with digits replaced by letters !
Those are sometimes named <a href="https://en.wikipedia.org/wiki/Sudoku#Alphabetical_Sudoku">Wordokus</a>.</p>
<p>Hence I wrote a Wordoku generator in Python,
where the secret word appears in the diagonal starting from the top-left:
<a href="https://github.com/Lucas-C/dotfiles_and_notes/blob/master/languages/python/secret_word_sudokulike_grid_generator.py">secret_word_sudokulike_grid_generator.py</a></p>
<div class="highlight"><pre><span></span><code># ./secret_word_sudokulike_grid_generator.py ensemble
..L.|...J EMLS|BNKJ
BNK.|...L BNKJ|EMSL
--------- ---------
....|.... MLSB|JENK
J..E|.S.M JKNE|LSBM
--------- solution: ---------
S..K|.... SBJK|MLEN
L..N|KB.S LEMN|KBJS
--------- ---------
.J.M|.KLB NJEM|SKLB
.S.L|..ME KSBL|NJME
</code></pre></div>
<p>Because I wanted to be able to generate grids for any size, even for prime number sizes like 7,
this program can drop the constraint of the boxes for those cases:
the generated grid is a pseudo-sudoku, where the only rules are that columns & rows must contain all letters.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># ./secret_word_sudokulike_grid_generator.py --no-boxes-constraint ensemble</span>
<span class="n">E</span> <span class="o">.</span> <span class="n">M</span> <span class="o">.</span> <span class="o">.</span> <span class="n">L</span> <span class="o">.</span> <span class="o">.</span> <span class="n">E</span> <span class="n">D</span> <span class="n">M</span> <span class="n">S</span> <span class="n">N</span> <span class="n">L</span> <span class="n">Q</span> <span class="n">B</span>
<span class="o">.</span> <span class="n">N</span> <span class="o">.</span> <span class="n">M</span> <span class="o">.</span> <span class="o">.</span> <span class="n">S</span> <span class="n">L</span> <span class="n">Q</span> <span class="n">N</span> <span class="n">D</span> <span class="n">M</span> <span class="n">B</span> <span class="n">E</span> <span class="n">S</span> <span class="n">L</span>
<span class="n">B</span> <span class="n">E</span> <span class="o">.</span> <span class="o">.</span> <span class="o">.</span> <span class="o">.</span> <span class="o">.</span> <span class="n">N</span> <span class="n">B</span> <span class="n">E</span> <span class="n">S</span> <span class="n">L</span> <span class="n">Q</span> <span class="n">D</span> <span class="n">M</span> <span class="n">N</span>
<span class="o">.</span> <span class="o">.</span> <span class="o">.</span> <span class="o">.</span> <span class="o">.</span> <span class="o">.</span> <span class="o">.</span> <span class="o">.</span> <span class="n">solution</span><span class="p">:</span> <span class="n">N</span> <span class="n">S</span> <span class="n">B</span> <span class="n">E</span> <span class="n">L</span> <span class="n">Q</span> <span class="n">D</span> <span class="n">M</span>
<span class="n">D</span> <span class="o">.</span> <span class="n">L</span> <span class="n">B</span> <span class="o">.</span> <span class="n">N</span> <span class="o">.</span> <span class="n">S</span> <span class="n">D</span> <span class="n">Q</span> <span class="n">L</span> <span class="n">B</span> <span class="n">M</span> <span class="n">N</span> <span class="n">E</span> <span class="n">S</span>
<span class="o">.</span> <span class="n">M</span> <span class="n">E</span> <span class="o">.</span> <span class="o">.</span> <span class="o">.</span> <span class="n">N</span> <span class="o">.</span> <span class="n">L</span> <span class="n">M</span> <span class="n">E</span> <span class="n">D</span> <span class="n">S</span> <span class="n">B</span> <span class="n">N</span> <span class="n">Q</span>
<span class="n">S</span> <span class="n">B</span> <span class="o">.</span> <span class="o">.</span> <span class="n">E</span> <span class="o">.</span> <span class="o">.</span> <span class="n">D</span> <span class="n">S</span> <span class="n">B</span> <span class="n">Q</span> <span class="n">N</span> <span class="n">E</span> <span class="n">M</span> <span class="n">L</span> <span class="n">D</span>
<span class="o">.</span> <span class="o">.</span> <span class="o">.</span> <span class="n">Q</span> <span class="n">D</span> <span class="n">S</span> <span class="n">B</span> <span class="o">.</span> <span class="n">M</span> <span class="n">L</span> <span class="n">N</span> <span class="n">Q</span> <span class="n">D</span> <span class="n">S</span> <span class="n">B</span> <span class="n">E</span>
</code></pre></div>
<p>You can see that the word <code>ENSEMBLE</code> appears on the diagonal in the solution.</p>
<p>On the technical side, the script is relatively straightforward.
I only needed a fast sudoku solver.
I used Ali Assaf's excellent one, that he describes in this article:
<a href="https://www.cs.mcgill.ca/~aassaf9/python/algorithm_x.html">https://www.cs.mcgill.ca/~aassaf9/python/algorithm_x.html</a></p>
<p>Using the fact that Sudoku puzzles may be described as an <a href="https://en.wikipedia.org/wiki/Exact_cover#Sudoku">exact cover problem</a>,
he implemented Donald Knuth « Algorithm X » with <em>Dancing Links</em> ( how poetic ! )</p>
<p>Kudos to him for writing this. The code is under the GNU GPL license.</p>
<p>At some point I tried to optimize the code using Numpy & Numba,
but I faced many issues with the later lib:
no support for <code>np.argwhere</code> / <code>np.setdiff1d</code> / <code>np.isin</code> / <code>np.in1d</code>,
<a href="https://github.com/numba/numba/issues/4053">new bugs</a>...
I started looking at at contributing by implementing those,
but Numba's internal code is quite complex. 😞</p>
<p>In the end I did not bother improving the performances:
even for a large grid like the size-12 one at the top of this article,
the script execution times is between 10s and 10min,
which is totally OK for my needs.</p>
<p><strong>EDIT [2019/10/05]:</strong> I discovered that this lind of puzzle was sometimes called « SudoLettre » in French.</p>
<script>
document.querySelectorAll('article img').forEach(img => img.title = img.alt)
</script>
<!--
| |S| | |P| |M|R| |O|W| |
|-|-|-|-|-|-|-|-|-|-|-|-|
| | |W|L| |G|O| |I| | |P|
|U| | | |L| | |W| |A| | |
|I| |P| | | | |A| |R| | |
|S|L|A| |R| | |N| | |P|I|
|W| | | |I|P|S| | | | | |
|N| | |S| | | |G| |P| | |
| |P| |O| | | |S|L| | |A|
|G|A| | |U| |I|P| | |R|O|
|O| | |G| | |U| | |I|S| |
| | | | | | | | |M| | |W|
| |W|U| | | | | | |L|A| |
<br>
A|S|G|N|P|I|M|R|U|O|W|L
-|-|-|-|-|-|-|-|-|-|-|-
R|M|W|L|A|G|O|U|I|S|N|P
U|I|O|P|L|S|N|W|R|A|M|G
I|O|P|U|M|W|L|A|N|R|G|S
S|L|A|M|R|U|G|N|O|W|P|I
W|G|N|R|I|P|S|O|A|M|L|U
N|U|R|S|O|L|A|G|W|P|I|M
M|P|I|O|N|R|W|S|L|G|U|A
G|A|L|W|U|M|I|P|S|N|R|O
O|N|M|G|W|A|U|L|P|I|S|R
L|R|S|A|G|N|P|I|M|U|O|W
P|W|U|I|S|O|R|M|G|L|A|N
-->
<style>
article img { display: inline-block; }
article pre { line-height: 1rem; }
td, th {
font-weight: normal;
font-size: 2rem;
font-size: 2rem;
padding: 0;
width: 2.5rem;
height: 2.5rem;
text-align: center;
}
td:nth-of-type(4), th:nth-of-type(4),
td:nth-of-type(8), th:nth-of-type(8) {
border-right: 3px solid black;
}
tr:nth-of-type(3), tr:nth-of-type(6), tr:nth-of-type(9) {
border-top: 3px solid black;
}
th:nth-of-type(1),
tr:nth-child(1) td:nth-of-type(2),
tr:nth-child(2) td:nth-of-type(3),
tr:nth-child(3) td:nth-of-type(4),
tr:nth-child(4) td:nth-of-type(5),
tr:nth-child(5) td:nth-of-type(6),
tr:nth-child(6) td:nth-of-type(7),
tr:nth-child(7) td:nth-of-type(8),
tr:nth-child(8) td:nth-of-type(9),
tr:nth-child(9) td:nth-of-type(10),
tr:nth-child(10) td:nth-of-type(11),
tr:nth-child(11) td:nth-of-type(12) {
background-color: #ffc4c4;
}
</style>Keep a changelog from git commits2019-04-23T12:00:00+02:002019-04-23T12:00:00+02:00Lucas Cimontag:chezsoi.org,2019-04-23:/lucas/blog/keep-a-changelog-from-git-commits.html<p>Over the past years, on software programming projects where my end users where developers (other than myself or my team),
I have tried to follow the advice of this website : <a href="https://keepachangelog.com">keepachangelog(.com)</a></p>
<p>A changelog is <a href="https://en.wikipedia.org/wiki/Changelog">defined by Wikipedia</a> as :</p>
<blockquote>
<p>a log or record of all notable changes made to a …</p></blockquote><p>Over the past years, on software programming projects where my end users where developers (other than myself or my team),
I have tried to follow the advice of this website : <a href="https://keepachangelog.com">keepachangelog(.com)</a></p>
<p>A changelog is <a href="https://en.wikipedia.org/wiki/Changelog">defined by Wikipedia</a> as :</p>
<blockquote>
<p>a log or record of all notable changes made to a project.
[It] usually includes records of changes such as bug fixes, new features, etc.</p>
</blockquote>
<p>It is very simple to add one to a project, and brings a lot of value to its users.
You can even consume them as Atom/RSS feeds, with tools like <a href="https://allmychanges.com">allmychanges.com</a>.</p>
<p>Honestly I haven't tried other standards than <a href="https://keepachangelog.com">keepachangelog.com</a>,
but what I like about it is that it's <strong>aimed at users</strong>.</p>
<p>As a software library or API user, when your are faced with a bug
or when you are considering an upgrade to a more recent version,
you want to be able to rapidly scroll through a changelog and detect useful bug / security fixes,
non backward compatible changes or new useful features.</p>
<p>This simple format standard provides exactly that,
with a very simple Markdown structure.</p>
<figure>
<img src="images/2019/04/AyaMulder_FourSeasonsTree.png">
<figcaption>I could not find an image I liked illustrating <code>changelogs</code> under a CC license,
so I picked this one which represents a tree but reminds me of git branches
- Four Seasons Tree by Aya Mulder <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a></figcaption>
</figure>
<p>The <a href="https://keepachangelog.com">keepachangelog.com</a> website clearly states as its top heading:</p>
<blockquote>
<p>Don’t let your friends dump git logs into changelogs.</p>
</blockquote>
<p>Which is more or less what I am going to advice you do 😏</p>
<p>I think that by making the changelog contribution part of the necessary steps to make code changes,
instead of having to edit a Markdown file separately,
you won't risk to forget about it.</p>
<p>I recently used <a href="https://github.com/vaab/gitchangelog">gitchangelog</a>
which can be configured very easily to follow <a href="https://keepachangelog.com">keepachangelog.com</a> format:</p>
<ul>
<li><a href="https://github.com/voyages-sncf-technologies/hesperides/blob/master/.gitchangelog.rc">.gitchangelog.rc</a>:</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">section_regexps</span> <span class="o">=</span> <span class="p">[</span>
<span class="p">(</span><span class="s1">'Added'</span><span class="p">,</span> <span class="p">[</span><span class="sa">r</span><span class="s1">'^[aA]dded\s*:.*$'</span><span class="p">]),</span>
<span class="p">(</span><span class="s1">'Changed'</span><span class="p">,</span> <span class="p">[</span><span class="sa">r</span><span class="s1">'^[cC]hanged\s*:.*$'</span><span class="p">]),</span>
<span class="p">(</span><span class="s1">'Deprecated'</span><span class="p">,</span> <span class="p">[</span><span class="sa">r</span><span class="s1">'^[dD]eprecated\s*:.*$'</span><span class="p">]),</span>
<span class="p">(</span><span class="s1">'Removed'</span><span class="p">,</span> <span class="p">[</span><span class="sa">r</span><span class="s1">'^[rR]emoved\s*:.*$'</span><span class="p">]),</span>
<span class="p">(</span><span class="s1">'Fixed'</span><span class="p">,</span> <span class="p">[</span><span class="sa">r</span><span class="s1">'^[fF]ix(ed)?\s*:.*$'</span><span class="p">]),</span>
<span class="p">(</span><span class="s1">'Security'</span><span class="p">,</span> <span class="p">[</span><span class="sa">r</span><span class="s1">'^[sS]ecu(rity)?\s*:.*$'</span><span class="p">]),</span>
<span class="p">]</span>
<span class="n">output_engine</span> <span class="o">=</span> <span class="n">mustache</span><span class="p">(</span><span class="s2">".gitchangelog-keepachangelog.tpl"</span><span class="p">)</span>
<span class="n">publish</span> <span class="o">=</span> <span class="n">FileRegexSubst</span><span class="p">(</span>
<span class="s2">"CHANGELOG.md"</span><span class="p">,</span>
<span class="sa">r</span><span class="s1">'(?s)(<!-- gitchangelog START -->\n).*(<!-- gitchangelog END -->\n)'</span><span class="p">,</span>
<span class="sa">r</span><span class="s2">"\1\o\2"</span>
<span class="p">)</span>
</code></pre></div>
<ul>
<li>
<p><code>.gitchangelog-keepachangelog.tpl</code> mustache template simple example: <a href="https://github.com/voyages-sncf-technologies/hesperides/blob/master/.gitchangelog-keepachangelog.tpl">https://github.com/voyages-sncf-technologies/hesperides/blob/master/.gitchangelog-keepachangelog.tpl</a></p>
</li>
<li>
<p>starting <a href="https://github.com/voyages-sncf-technologies/hesperides/blob/master/CHANGELOG.md">CHANGELOG.md</a>:</p>
</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="c1"># CHANGELOG</span>
<span class="k">All</span> <span class="n">notable</span> <span class="n">changes</span> <span class="k">to</span> <span class="n">this</span> <span class="n">project</span> <span class="n">are</span> <span class="n">documented</span> <span class="k">in</span> <span class="n">this</span> <span class="k">file</span><span class="p">.</span>
<span class="n">The</span> <span class="k">format</span> <span class="k">is</span> <span class="n">based</span> <span class="k">on</span> <span class="err">[</span><span class="n">Keep</span> <span class="n">a</span> <span class="n">Changelog</span><span class="err">]</span><span class="p">(</span><span class="n">http</span><span class="o">://</span><span class="n">keepachangelog</span><span class="p">.</span><span class="n">com</span><span class="p">).</span>
<span class="n">It</span> <span class="k">is</span> <span class="k">generated</span> <span class="k">from</span> <span class="n">the</span> <span class="n n-Quoted">`git`</span> <span class="n">commits</span> <span class="n">whose</span> <span class="n">message</span> <span class="k">starts</span> <span class="k">with</span>
<span class="n n-Quoted">`added:`</span> <span class="o">/</span> <span class="n n-Quoted">`changed:`</span> <span class="o">/</span> <span class="n n-Quoted">`deprecated:`</span> <span class="o">/</span> <span class="n n-Quoted">`removed:`</span> <span class="o">/</span> <span class="n n-Quoted">`fixed:`</span> <span class="o">/</span> <span class="n n-Quoted">`security:`</span>
<span class="n">thanks</span> <span class="k">to</span> <span class="err">[</span><span class="n">gitchangelog</span><span class="err">]</span><span class="p">(</span><span class="n">https</span><span class="o">://</span><span class="n">github</span><span class="p">.</span><span class="n">com</span><span class="o">/</span><span class="n">vaab</span><span class="o">/</span><span class="n">gitchangelog</span><span class="p">)</span> <span class="o">:</span>
<span class="n n-Quoted">`pip install gitchangelog pystache`</span>
<span class="n n-Quoted">`gitchangelog`</span>
<span class="n">You</span> <span class="n">can</span> <span class="n">still</span> <span class="k">use</span> <span class="err">[</span><span class="n">conventional</span> <span class="k">commit</span> <span class="n">messages</span><span class="err">]</span><span class="p">(</span><span class="n">https</span><span class="o">://</span><span class="n">www</span><span class="p">.</span><span class="n">conventionalcommits</span><span class="p">.</span><span class="n">org</span><span class="p">),</span> <span class="k">starting</span> <span class="k">for</span> <span class="n">example</span> <span class="k">with</span>
<span class="n n-Quoted">`chore:`</span> <span class="o">/</span> <span class="n n-Quoted">`docs:`</span> <span class="o">/</span> <span class="n n-Quoted">`refactor:`</span> <span class="o">/</span> <span class="n n-Quoted">`style:`</span> <span class="o">/</span> <span class="n n-Quoted">`test:`</span><span class="p">,</span> <span class="n">they</span> <span class="n">just</span> <span class="n">won</span><span class="s1">'t be included in this changelog.</span>
<span class="s1"><!-- gitchangelog START --></span>
<span class="s1"><!-- gitchangelog END --></span>
</code></pre></div>
<p>Then you simply execute the <code>gitchangelog</code> command in a pre-commit hook or in your release CI pipeline and <em>voilà</em> !</p>
<p>Now, I am far from an expert on this subject. There are many other standards for changelogs,
and other tools to use <code>git</code> commits or pull requests to extract such history.</p>
<p>But I see the following advantages to using <code>gitchangelog</code>:</p>
<ul>
<li>it is easy to introduce to an existing code base, while preserving an existing changelog or ignoring old commits</li>
<li>it lets you configure the naming convention you want to use in your commit messages</li>
<li>it lets you choose whether or not to ignore commits without the required prefixes.</li>
<li>it is written in Python 😉</li>
</ul>Le dernier café sur la droite & L'île mystérieuse2019-04-11T22:00:00+02:002019-04-11T22:00:00+02:00Lucas Cimontag:chezsoi.org,2019-04-11:/lucas/blog/ldcslg-et-l-ile-mysterieuse.html<p>Le premier jeu que nous avons testé hier était <a href="http://cheatyourownadventure.co.uk/the-last-coffee-shop-on-the-left"><em>The last coffee shop on the left</em></a>
de <a href="/lucas/blog/tag/shane-mclean.html">Shane McLean</a>, l'auteur de <a href="la-tour-et-cheat-your-own-adventure.html"><em>Cheat your own adventure</em></a> auquel nous avions déjà joué.
J'en propose une traduction en français dans <a href="pages/jeux-de-role.html">la section <em>jeux de rôle</em> de ce blog</a>.</p>
<p><a href="https://en.wikipedia.org/wiki/Nighthawks"><img alt="Tableau Nighthawks de Edward Hopper" src="images/2019/04/Nighthawks_by_Edward_Hopper_1942.jpg"></a></p>
<p>Ce jeu très court - la …</p><p>Le premier jeu que nous avons testé hier était <a href="http://cheatyourownadventure.co.uk/the-last-coffee-shop-on-the-left"><em>The last coffee shop on the left</em></a>
de <a href="/lucas/blog/tag/shane-mclean.html">Shane McLean</a>, l'auteur de <a href="la-tour-et-cheat-your-own-adventure.html"><em>Cheat your own adventure</em></a> auquel nous avions déjà joué.
J'en propose une traduction en français dans <a href="pages/jeux-de-role.html">la section <em>jeux de rôle</em> de ce blog</a>.</p>
<p><a href="https://en.wikipedia.org/wiki/Nighthawks"><img alt="Tableau Nighthawks de Edward Hopper" src="images/2019/04/Nighthawks_by_Edward_Hopper_1942.jpg"></a></p>
<p>Ce jeu très court - la partie a duré a peine plus d'une demi-heure - a pour particularité
d'être prévu pour joué au fur et à mesure qu'il est lu.
L'auteur recommande expressément de ne pas le lire en entier au préalable,
mais de le découvrir au fur et mesure des 5 étapes qui le compose.</p>
<p>Je crois que dans l'ensemble tout le monde a aimé ce jeu,
son format bref et son twist central.
Autour de la table, les avis divergeaient un peu sur la mécanique des personnages tournant.
Certains l'ont trouvé un peu frustrante, d'autres ont apprécié l'occasion de jouer un rôle
imposé et créé collectivement.</p>
<p>Personnellement je me suis beaucoup amusé durant le duel d'arguments inavouables
dans la dernière phase pour convaincre le Gardien !
Il s'agit d'ailleurs peut-être là du seul bémol : son rôle manque un peu d'explications,
sur ses objectifs, pour l'incarner à plusieurs, et pour savoir
dans quelle mesure il peut confronter les personnages à des actes qu'il leur invente.
Et puis j'aurais bien aimé qu'il ait un court texte final pour clôturer la partie !</p>
<hr>
<p>Le « plat de résistance » de la soirée était <a href="http://troplongpaslu.fr/jeux-de-role-court/lile-mysterieuse/">L'Île Mystérieuse</a> de Damien « Rahyll » C.,
auteur du jeu <a href="dans-la-nuit-longue-et-glacee.html">Dans la nuit longue et glacée</a> que nous avons testé la dernière fois.</p>
<blockquote>
Vous faites partie d'une des nombreuses expéditions s'étant donnée pour but de retrouver
les reliques d'une ancienne civilisation basée sur une île mystérieuse perdue au milieu de l'océan.
Alors que l'île était en vue, vous avez été pris dans une tempête et votre navire a sombré.
Vous, ainsi qu'une partie de l'équipage, vous réveillez le lendemain matin sur le rivage.
Il va falloir vous trouver de quoi survivre, explorer cette île, affronter ses dangers
et trouver un moyen de vous en échapper, et peut-être mettre la main sur les précieuses reliques.
</blockquote>
<p>Le jeu nous a énormément plus, et je l'ai trouvé particulièrement original dans sa mécanique
de génération « procédurale » de l'île, au fur et à mesure de la partie,
tout en restant très équilibré.
J'ai adoré par exemple l'idée des cases à cocher dans les table de génération
permettant d'éviter de retomber dessus !</p>
<p>Nous avons pris le parti d'avoir un MJ « tournant » :
c'était au joueur face à celui qui explorait de décrire les lieux visités
et de définir les obstacles auxquels nous faisions face.
Ça s'est révélé très amusant, et petit à petit, de description inspirée en dialogue improvisé,
nous avons esquissé la légende de la brigade B56,
enrôlé le survivant Von Schtafen (avec feuille de PNJ improvisée) et empoisonné la tribu Nagasaki pour leur voler le trésor !</p>
<blockquote>
<p>J'ai essayé de soulever la jeep pour faire cric... mais ça a fait crac !</p>
</blockquote>
<p>Notre score final, avec une relique, le trésor, 13 biomes découverts et tout le monde dans l'hélicoptère : <strong>3880 points</strong> !</p>
<p><img alt="Photo de la table de jeu" src="images/2019/04/lile-mysterieuse.jpg"></p>
<p>Si ce jeu est génial, il n'est pas pour autant exempt de défauts.
Le texte regorge de fautes d'orthographe, et certains points de règles sont franchement flous.
Par exemple, nous n'avons pas arrêté d'utiliser le terme « ressource » de manière ambiguë,
tantôt de manière générique pour désigner toutes les « denrées récoltables » incluant eau & vivres,
tantôt dans le sens spécifique employé dans les règles (mais jamais défini clairement).
Nous avons aussi dû relire en milieu de partie la règle de récolte de « ressources » justement,
en nous mettons finalement d'accord que la difficulté de l'action complexe correspondante
définissait aussi le maximum qui puisse être récolté... Pas simple.</p>
<p>Voici un florilège de toutes les questions que nous nous sommes posés durant la partie :</p>
<ul>
<li>est-ce qu'on ne peut pas essayer de récupérer de l'eau / des vivres en-dehors des lieux sans dangers & infrastructures ?</li>
<li>c'est pas un peu radical d'obtenir une condition dès qu'on rate un jet de récolte ?? (on ne l'a finalement pas appliqué)</li>
<li>l'hélicoptère nous accompagne tout le temps ? Il peut se compacter façon Capsule Corp ? <img alt="Capsule tirée de Dragon Ball" src="images/2019/04/dragonball-capsule.jpg"></li>
<li>avec l'hélicoptère, que se passe-t-il au bout de 6 cases si on ne peut pas se poser ?</li>
<li>est-ce que les objets & reliques prennent de la place dans le sac à dos ?</li>
<li>quel est l'intérêt de la règle de conversion de 1-VIVRE du feu ?</li>
</ul>
<p>Toujours en vrac, voici quelques idées que nous avons eu pour peut-être améliorer le jeu :</p>
<ul>
<li>simplifier la gestion des « ressources » : pourquoi pas seulement 2 ? Et simplifier la règle de récolte.</li>
<li>toujours dans l'idée de simplification, pourquoi pas supprimer les règles de météo et/ou les particularités des biomes ?
Voir même les compteurs d'utilisation / essence.</li>
<li>nous avons eu la chance d'obtenir l'hélicoptère assez vite, mais peut-être serait-il intéressant
d'avoir une option « B » pour quitter l'île qui dépende moins du hasard, comme par exemple collecter X ressources
pour fabriquer un radeau.</li>
<li>un des joueurs n'a pas aimé que le compas nous fasse rebrousser chemin plusieurs fois,
et a imaginé que celui-ci ne permette que de progresser vers un des 3 hexagones dans la direction
où l'on progresse sur l'île... ce qui pourrait éviter d'avoir l'impression d'être complètement nuls en orientation !</li>
</ul>
<p>La maquette du jeu est d'ailleurs très réussie, et au regard de tout ce que le jeu propose en règles et contenu,
c'est vraiment un record d'avoir tout fait tenir en 2 pages recto-verso !</p>
<p>C'est définitivement un jeu pour lequel j'aimerais beaucoup faire une partie d'une "v2"
un peu remaniée.
Chapeau bas à Damien son auteur, et bonne chance si vous vous aventurez vous aussi sur l'Île Mystérieuse !</p>
<figure>
<img alt="Spirou découvre un temple dans la jungle" src="images/2019/04/SpirouEtFantasio_LaValleeDesBannis_Temple.png">
<img alt="Tombes de Siegfried & Maginot" src="images/2019/04/SpirouEtFantasio_LaValleeDesBannis_Tombes.png">
<figcaption>Illustrations tirées de Spirou & Fantasio - La Vallée Des Bannis</figcaption>
</figure>
<style>
article hr { margin: 5rem; }
article figure img { width: 30rem; }
article li img { width: 7rem; float: right; }
</style>Dans la nuit longue et glaciale2019-03-13T20:00:00+01:002019-03-13T20:00:00+01:00Lucas Cimontag:chezsoi.org,2019-03-13:/lucas/blog/dans-la-nuit-longue-et-glacee.html<figure>
<img alt="The Phantom Hunter - William Blair Bruce" src="images/2019/03/William_Blair_Bruce_-_The_Phantom_Hunter.jpg">
<figcaption>The Phantom Hunter - William Blair Bruce</figcaption>
</figure>
<p><a href="http://troplongpaslu.fr/wp-content/uploads/2018/08/DLNLG_web.pdf">Dans la nuit longue et glaciale</a> est un jeu
de <a href="/lucas/blog/tag/damien-rahyll-c.html">Damien “Rahyll” C.</a>, qui n'en est pas à sa première bafouille puisqu'il a gagné le Game Chef 2016 avec <a href="https://rpggeek.com/rpg/37904/matching-hearts">Matching Hearts</a>.</p>
<p>Le jeu place un joueur dans la peau d'un survivant d'un crash d'hélicoptère …</p><figure>
<img alt="The Phantom Hunter - William Blair Bruce" src="images/2019/03/William_Blair_Bruce_-_The_Phantom_Hunter.jpg">
<figcaption>The Phantom Hunter - William Blair Bruce</figcaption>
</figure>
<p><a href="http://troplongpaslu.fr/wp-content/uploads/2018/08/DLNLG_web.pdf">Dans la nuit longue et glaciale</a> est un jeu
de <a href="/lucas/blog/tag/damien-rahyll-c.html">Damien “Rahyll” C.</a>, qui n'en est pas à sa première bafouille puisqu'il a gagné le Game Chef 2016 avec <a href="https://rpggeek.com/rpg/37904/matching-hearts">Matching Hearts</a>.</p>
<p>Le jeu place un joueur dans la peau d'un survivant d'un crash d'hélicoptère, livré à lui même seul dans la montagne.
Je dis bien <em>un survivant</em> car il n'y a bien qu'un unique personnage principal ici,
et tous les autres joueurs autour de la table incarnent l'adversité à laquelle il fait face, tel un MJ collectif.</p>
<p>J'ai découvert le jeu via le fantastique site <a href="http://troplongpaslu.fr">troplongpaslu.fr</a>
et cette proposition ludique m'avait tout de suite séduite.
Nous l'avons finalement testé hier avec 4 amis. Pour l'occasion c'était un novice du jeu de rôle qui s'est porté
volontaire pour endosser le rôle du protagoniste. Ça lui a bien plus, il a même trouvé idéal d'être
“ sous les feux de la rampe ” pour une première partie.</p>
<p>Sans vous faire un résumé complet de la partie, l'histoire s'est tout d'abord orientée vers la recherche de l'épave
de l'hélicoptère et d'un abri pour la nuit.
Globalement, la partie s'est construite en crescendo, et a débuté très doucement.
Après 20min de jeu, une première scène mémorable a vu notre héro assommer, dépouiller puis abandonner aux loups
le pilote rescapé mais mortellement blessé qu'il venait de retrouver.</p>
<figure>
<img alt="The Struggle for Existence - George Bouverie Goddard" src="images/2019/03/George_Bouverie_Goddard_The_Struggle_for_Existence.jpg">
<figcaption>The Struggle for Existence - George Bouverie Goddard</figcaption>
</figure>
<p>Puis un joueur “ MJ ” a introduit dans l'histoire <a href="https://en.wikipedia.org/wiki/UVB-76">une mystérieuse transmission radio constituée d'une succession de nombres</a>.
De mon côté, ça a été le déclic, j'ai pensé à la série de jeux vidéos <a href="https://en.wikipedia.org/wiki/Penumbra_(video_game_series)">Penumbra</a>
et introduit dans la foulée un tunnel minier avec des rails. Tunnel que s'est empressé d'emprunter notre protagoniste,
et le récit s'est petit à petit orienté vers l'horreur...</p>
<p>En conclusion de la partie, notre joueur principal nous a offert un délicieux épilogue,
présentant notre protagoniste rescapé dans une chambre d'hôpital, visiblement traumatisé,
occupé à compter en boucle à haute voix les tours concentriques qu'il fait faire à un petit train sur rails,
en référence aux mystérieux individus nus rencontrés dans la montagne qui ne cessaient de répéter une litanie de nombres.</p>
<p>A noter qu'en milieu de partie nous étions un peu frustrés côté “ MJ ” de notre rôle assez passif,
ne pouvant pas émettre de suggestions narratives. Nous avons donc introduit comme variante
de présenter au joueur protagoniste des petits papiers sur lesquels nous introduisions des événements ou perceptions,
et il était libre de nous interroger à ce propos ensuite.</p>
<p>Alors que j'estimais à une ou deux heures la durée de la partie, elle a durée le double.
Dans l'ensemble, nous avons tous beaucoup apprécié le jeu. Mention spéciale personnelle à la magnifique maquette
ainsi qu'à la simplicité du système et à la clarté des explications.
Le texte à trous initial permet de ne pas avoir deux parties semblables, le très simple et efficace système de jet de d6
est tiré d'Apocalypse World, et la mécanique de jetons d'objectifs / conditions est très bien pensée,
servant de moteur au protagoniste.
Pour notre partie, ce dernier point s'est juste révélé un peu artificiel / hors de propos
lorsque l'histoire a basculé vers la course poursuite dans un bâtiment abandonné :
faim et froid n'étaient plus présents, et la déprime laissait place à la peur.
En tout cas, nous étions tous prêts à refaire une partie à l'occasion pour endosser le rôle du survivant !</p>
<p>Nous étions toutefois plusieurs à trouver le système de questions un peu limitant.
Comme l'un des joueurs l'a formulé, le jeu requiert de la part du protagoniste un dosage délicat
des questions, pour répartir la parole et l'introduction d'éléments dans l'histoire.
Il aurait peut-être été utile que la règle du jeu inclue quelques exemples de questions.</p>
<p>Enfin, un des joueurs a proposé une ingénieuse règle optionnelle pour augmenter la pression sur le protagoniste :
lorsqu'il prend trop son temps, les autres joueurs peuvent poser devant lui un sablier en lien avec une condition :
faim, froid, déprime ou blessure.
S'il n'arrive pas à améliorer cette condition avant qu'il soit écoulé, un jeton est déplacé dessus.</p>
<p><img alt="Hourglass" src="images/2019/03/hourglass.png"></p>
<style>
img { width: 40rem; }
p > img { max-height: 10rem; }
</style>Face au Titan2019-03-04T09:00:00+01:002019-03-04T09:00:00+01:00Lucas Cimontag:chezsoi.org,2019-03-04:/lucas/blog/face-au-titan.html<figure>
<img src="images/2019/03/faceAuTitan-coverAmericanCaptain2-720x1024.jpg" alt="Ébauche de couverture du jeu de rôle Face Au Titan">
<figcaption>Ébauche de couverture du jeu, illustration de Roger Heal</figcaption>
</figure>
<p>La semaine dernière j'ai eu la chance de tester <em>Face au Titan</em>,
le jeu de rôle de Nicolas "Gulix" Ronvel.</p>
<p>Le jeu est déjà bien au-delà du stade de prototype :
la <a href="http://www.gulix.fr/blog/2019/02/21/face-a-la-mise-en-page/">mise en page</a>
est en cours, des illustrations ont été …</p><figure>
<img src="images/2019/03/faceAuTitan-coverAmericanCaptain2-720x1024.jpg" alt="Ébauche de couverture du jeu de rôle Face Au Titan">
<figcaption>Ébauche de couverture du jeu, illustration de Roger Heal</figcaption>
</figure>
<p>La semaine dernière j'ai eu la chance de tester <em>Face au Titan</em>,
le jeu de rôle de Nicolas "Gulix" Ronvel.</p>
<p>Le jeu est déjà bien au-delà du stade de prototype :
la <a href="http://www.gulix.fr/blog/2019/02/21/face-a-la-mise-en-page/">mise en page</a>
est en cours, des illustrations ont été réalisées, les <em>playtests</em> s'enchaînent...
Avec dans l'objectif une campagne de financement participatif pour l'été.</p>
<p>Pour ma part j'ai été plutôt conquis, et j'ai hâte que le projet voit le jour 🤩</p>
<p><em>Face au Titan</em> est un jeu narratif : il n'y a pas de Meneur de Jeu,
les joueurs ont tous autant d'influence sur l'histoire,
la partie suit un déroulement structuré en actes et l'histoire construite collectivement aura une fin annoncée.</p>
<p>Dans ce jeu les héros sont les membres d'une Compagnie vouée à la destruction d'un Titan,
une créature colossale et monstrueuse.
Pour cette partie il s'agissait de Vamakaskan, un bœuf / sanglier / cerf géant.
Dont j'ai systématiquement écorché le nom pendant la partie 😅.
L'univers de "low fantasy" ébauché durant les premières minutes de jeu était habité de peuples nomades
et de colons au passé mystérieux.</p>
<p>Sans rentrer dans les détails, la mécanique de jeu nous a amené à décrire
le monde, nos personnages et le Titan façon portrait chinois à quatre joueurs,
à l'aide de courtes vignettes présentant des morceaux d'histoire comme autant de coups de pinceaux.</p>
<p>Un système de Motifs et d'Échos va créer du lien entre ces éléments narratifs,
et provoquer la transition d'acte en acte.
Enfin, ils serviront de base à la phase finale d'épilogue, que j'ai trouvée efficace et très bien pensée.</p>
<p><img alt="Illustration pour Swords Without Master" src="images/2019/03/swords-without-master.jpg"></p>
<p>Une inspiration importante pour <em>Face au Titan</em> est <a href="https://www.500nuancesdegeek.fr/sword-without-master/">Sword Without Master</a>
d'Epidias Ravachol. Le jeu me tente beaucoup, mais n'y ayant pas encore joué je ne peux pas vous parler de leurs points communs et différences.
A titre personnel toutefois, le thème fort du combat contre un Titan m'a beaucoup emballé,
plus que celle d'un univers <em>à la Conan</em>.</p>
<p>Apparemment issue de SWM, j'ai été conquis par la mécanique de lancer de 2d6 pour déterminer
le Ton de votre narration en tant que joueur.
C'est simple, ça inspire et ça oriente subtilement l'ambiance de chaque acte.</p>
<p>Un de mes regrets est que face à la profusion d'idées que le jeu génère,
j'ai été un peu frustré qu'elles n'ai pas pu être toutes exploitées durant la partie !
Pourquoi ces étranges structures de métal dans le désert se retrouvent-elles sur le dos de Vamakaskan ?
Est-ce que la mort de cette bête signe la fin des sorcières, et notamment de celle qui a été la protégée d'Augure ?
Et Jeroni retrouvera-t-il le Change-Peau qui a massacré sa belle-famille ?
C'est un peu inhérent à ce style de jeu narratif, et c'est un peu le prix à payer pour forcer la partie a avoir une fin,
ce que je trouve très intéressant et agréable pour autant.</p>
<p>En définitive je recommande chaudement <em>Face au Titan</em> pour une partie <em>one-shot</em> épique
avec des joueurs un peu imaginatifs. Pour ma part j'attends la sortie du jeu final avec impatience 😃</p>
<style>
article img { width: 20rem; }
</style>Jeux multijoueurs en local sur PC2019-02-15T09:00:00+01:002019-02-15T09:00:00+01:00Lucas Cimontag:chezsoi.org,2019-02-15:/lucas/blog/local-multiplayer-games.html<p>Je crois que j'envisage d'écrire un article sur ce sujet depuis que j'ai créé ce blog !
J'ai retrouvé ces notes datant d'au moins 5 ans, déposées au fin fond d'un fichier texte :</p>
<blockquote>
<p>Dans tous ces jeux, un des plaisirs principaux provient de la découverte, à deux ou plus, des règles …</p></blockquote><p>Je crois que j'envisage d'écrire un article sur ce sujet depuis que j'ai créé ce blog !
J'ai retrouvé ces notes datant d'au moins 5 ans, déposées au fin fond d'un fichier texte :</p>
<blockquote>
<p>Dans tous ces jeux, un des plaisirs principaux provient de la découverte, à deux ou plus, des règles du jeu.
Aussi je tâcherai de ne pas en dire trop, mais juste assez pour vous allécher un peu.</p>
</blockquote>
<p>Je vais essayer de respecter cette contrainte d'écriture fixée par mon moi d'il y a plusieurs années 😄</p>
<p>Cet article va donc recenser quelques uns de mes coups de cœur vidéo-ludiques de ces 20 dernières années,
parmi les jeux sur PC pouvant se jouer à au moins 2 sur le même écran.
J'espère que certains vous donnerons envie de rassembler quelques amis ou cousins pour les tester !</p>
<h2>Super Bomberman 2 sur SNES</h2>
<p><img class="left sbm2" alt="Screenshot du jeu Super Bomberman 2 sur SNES" src="images/2019/02/super-bomberman-2-snes-screenshot-battle-stage-1-finish.jpg"></p>
<p>Un de ceux auxquels j'ai le plus joué ado, avec mes cousins notamment, <strong>jusqu'à 4 sur un même clavier</strong> !</p>
<p>Je me souviens passer plusieurs minutes avant chaque partie à configurer quelles touches du clavier serait attribuées à quel joueur,
dans l'interface de l'émulateur de Super Nintendo <a href="http://www.snes9x.com/">Snes 9x</a>.
Il fallait se dépêcher car l'impatience montait rapidement :)
A noter qu'il est possible de jouer sous Linux / Mac / Windows.</p>
<p>Cette version du jeu contient 10 niveaux différents et la possibilité de jouer avec des <em>bots</em>.
Le <em>gameplay</em> est ultra classique mais je ne m'en suis jamais lassé !
Surtout que certains niveaux et certains <em>power-ups</em> ouvrent des possibilités tactiques.
Le <a href="https://bomberman.fandom.com/wiki/Power_Glove">gant</a> par exemple est redoutable car il permet de projeter des bombes par-dessus
les murs, et même d'un côté du niveau à l'autre !</p>
<h2>Windjammers</h2>
<p><img class="right" alt="Screenshot du jeu Windjammers" src="images/2019/02/windjammers.jpg"></p>
<p>Toujours sur émulateur Super Nintendo, il s'agit cette fois d'un jeu de <strong>duel</strong> bien particulier :
on s'affronte avec un frisbee super-sonique !</p>
<p>Je crois qu'à première vue il est facile de sous-estimer le <strong>fun</strong> que ce jeu procure :)
Les matchs sont très courts, les échanges rapides et tendus...
La palette de coups est très réduite mais cela rend le jeu facile à prendre en main
et le positionnement sur le terrain un élément clef.</p>
<p>Ayant joué à ce jeu dans mes années collège-lycée, quelle n'a pas été mon (agréable !)
surprise de le retrouver... à mon travail.
C'est en effet un des jeux favoris à l'espace détente de <a href="https://www.oui.sncf">oui.sncf</a> à Nantes 😆
J'en déduis que je ne suis pas le seul a avoir été marqué par ce jeu,
d'autant plus qu'il a été porté sur au moins 5 consoles depuis sa sortie en 1994.</p>
<h2>GLTron</h2>
<p><img class="left" alt="Screenshot du jeu GLTron" src="images/2019/02/gltron.jpg"></p>
<p>Aaaah <a href="http://www.gltron.org">GLTron</a>... Tant d'heures scotchés après les cours devant un jeu si minimaliste...
Parfois, à nouveau, à 4 sur le même clavier ! Ou alors le quatrième jouait <strong>à la souris</strong>, un peu laborieusement...</p>
<p>À ne pas s'y tromper : il ne s'agit absolument pas d'un jeu de course !
Le but est d'être le dernier survivant en lice, et tout le monde joue contre le temps et l'espace qui s'amenuise...
Après quelques parties je vous promets inévitablement quelques commentaires du type
« Mais... Comment t'as fait pour passer là ?? »</p>
<p>Un jeu de réflexes encore une fois, open-source et compatible Windows / Mac / Linux,
sans aucune variété de niveaux mais doté de quelques paramètres permettant de varier les plaisirs :
vitesse du jeu, taille du terrain...</p>
<h2>Jump'n Bump</h2>
<p><img class="right" alt="Screenshot du jeu Jump'n'bump" src="images/2019/02/jump-n-bump.png"></p>
<p>Un jeu de plate-forme cette fois, mais toujours aussi minimaliste et faisant appel à nos instincts primaires.
Jusqu'à 4 joueurs sur le même clavier.
Je ne vous en dit pas plus, mais ce n'est pas parce que vous incarnez de mignons lapins sautant partout que ce jeu est destiné à de jeunes enfants...</p>
<p>Le jeu date de 1998 et a été <em>open-sourcé</em> dès l'année suivante comme <a href="https://en.wikipedia.org/wiki/Jump_'n_Bump"><em>emailware</em></a>.
Il existe sous <a href="https://icculus.org/jumpnbump/">Windows</a> et sous <a href="https://doc.ubuntu-fr.org/jumpnbump">Ubuntu</a>.
A l'époque je le lançais avec DOSBox...</p>
<p><a href="https://github.com/GrahamTheCoder/jump-n-bump">Enno Rehling</a> a porté le code en HTML+Javascript.
Vous pouvez jouer à cette version en ligne ici: <a href="https://lucas-c.github.io/jump-n-bump/">https://lucas-c.github.io/jump-n-bump/</a></p>
<p>Il existe aussi une version <em>remastered</em> permettant jusqu'à 8 joueurs et des manettes de jeux, ainsi qu'un éditeur de niveaux :
<a href="https://domarius-games.itch.io/jump-n-bump-remastered">https://domarius-games.itch.io/jump-n-bump-remastered</a></p>
<h2>Super Mario War</h2>
<p><img class="left" alt="Screenshot du jeu Super Mario War" src="images/2019/02/super-mario-war.jpg"></p>
<p>Exactement le même principe de jeu, mais cette fois dans l'univers de Mario,
avec plus de 1000 niveaux différents, des <em>power-ups</em> à foison et plein de modes de jeux : GetTheChicken, Domination, CTF, ...</p>
<p><a href="http://supermariowar.supersanctuary.net">Super Mario War</a> est <a href="https://github.com/mmatyas/supermariowar"><em>open-source</em> (C++)</a>,
compatible Windows / Linux / Mac, supporte les manettes de jeux... un <em>party game</em> idéal.
Je crois néanmoins y avoir joué moins de parties qu'à Jump'n Bump.</p>
<h2>KRYP</h2>
<p><img class="right" alt="GIF animé du jeu KRYP" src="images/2019/02/kryp.gif"></p>
<p>A la fois OVNI dans cette liste et véritable petit coup de cœur il y a 3 ans,
j'ai découvert <a href="https://ditto.itch.io/kryp">Kryp</a> via le site <a href="http://warpdoor.com">Warp Door</a>.</p>
<p>Ce jeu gratuit de <a href="https://twitter.com/dittomat">ditto</a>, le créateur de <a href="http://www.gonnergame.com/">Gonner</a>,
a comme particularité de ne nécessiter qu'<strong>un bouton par joueur</strong> !
Rassemblez amis, famille ou collègues et demandez-leur d'appuyer sur une touche du clavier sur l'écran d'accueil et c'est parti !</p>
<p>C'est encore un jeu très minimaliste, mais s'il peut paraître un peu brouillon ou aléatoire lors des premières parties,
il se révèle nécessiter réflexes et sens tactique.
La mécanique de jeu très maline est pensée pour limiter une partie à 2 ou 3 minutes,
pour mieux les enchaîner à l'infini.</p>
<p>Enfin et pour ne rien gâcher, je le trouve visuellement assez épatant !</p>
<h2>nidhogg</h2>
<p><img class="left" alt="GIF animé du jeu Nidhogg" src="images/2019/02/nidhogg_1-1.gif"></p>
<p>On revient à du duel avec ce jeu du prolifique créateur <a href="http://messhof.com">Messhof</a>.
C'est aussi le premier jeu payant de cette liste, à 10 € sur Steam ou Humble Bundle hors soldes.
À noter qu'il existe une version "2" que je n'ai pas testé, où les graphismes changent radicalement.</p>
<p>Le GIF animé ci-contre révèle l'essentiel du <em>gameplay</em>, qui s'étoffe de sauts,
de parades haute ou basse et de la possibilité de lancer son arme.
Les combats s'enchaînent de manière frénétique à travers différents tableaux,
jusqu'à ce qu'un joueur obtienne aux termes de plusieurs victoires la récompense ultime...</p>
<p>Très addictif et jubilatoire, ce jeu « indé » mérite vraiment d'être essayé !</p>
<p>A défault de jouer à l'original, inégalable, notez qu'il existe une version parodique
très complète et gratuite, <a href="https://madgarden.itch.io/eggnogg">EGGNOGG+</a>.
Les contrôles sont parfois aléatoires, le <em>level design</em> est moins réussi,
mais le jeu reste très amusant !</p>
<h2>Samurai Gunn</h2>
<p><img class="right" alt="GIF animé du jeu Samurai Gun" src="images/2019/02/Samurai_gunn_slash_step3.gif"></p>
<p>Un jeu assez similaire, avec un <em>gameplay</em> un poil plus épuré (pas de niveaux de parades différents)
mais qui permet cette fois de s'affronter jusqu'à 4, voir <strong>de jouer en co-op</strong> contre des vagues de ninjas.</p>
<p>On retrouve le même équilibre « attaques d'escrime illimitées » / « attaques à distance rationnées »
que dans Nidhogg, et la possibilité de parer des balles est très jouissive !</p>
<p>Et puis c'est bien connu, les samouraïs qui utilisent des armes à feu n'ont pas d'honneur...</p>
<p>La <a href="https://doseone.bandcamp.com/album/the-samurai-gunn-ep">bande son</a> de doseone est très bonne, mélangeant sonorités japonisantes et rap.
J'imagine très bien le thème de Samurai Gunn dans un film de Tarantino par exemple.
Je la préfère même à celle de Nidhogg pourtant excellente, étrange et hypnotique.</p>
<p>La page officielle du jeu n'existe plus, mais il peut être acheté pour 14 € sur Steam.
Création du développeur Beau « Teknopants » Blyth, un deuxième épisode devrait sortit très bientôt sur Nintendo Switch,
accompagné d'une BD dans le même univers du dessinateur français <a href="http://airfortress.tumblr.com">Valentin Seiche</a>.</p>
<h2>Towerfall Ascension</h2>
<p><img class="left" alt="GIF animé du jeu Towerfall" src="images/2019/02/TowerFall_time_stomp.gif"></p>
<p>Que de joyeux souvenirs que ces heures passées à progresser dans le mode « histoire » coopératif
ou bien en arène où les coups bas pleuvent,
devant un vidéo-projecteur avec une manette de Xbox vissée entre les mains.
Avec bien sûr, toujours un bouton pressé pour encocher une flèche dans mon arc tendu...</p>
<p>Multi-plateformes, issu à l'origine d'une <em>game-jam</em>, les superlatifs vont me manquer pour décrire <a href="http://www.towerfall-game.com">Towerfall Ascension</a>,
tant il représente la quintessence des jeux que j'aime.
Direction artistique, action frénétique, ergonomie, <em>level design</em>, durée de vie, fun... tout est « aux petits oignons ».</p>
<p>Il est à 15 € sur Steam, et de tous les jeux commerciaux présentés ici
c'est sans hésiter dans celui-ci que je vous recommanderais de mettre vos biffetons.</p>
<h2>Crawl</h2>
<p><img class="right" alt="Screenshot du jeu Crawl" src="images/2019/02/crawl.jpg"></p>
<p>Dernier des jeux de cette liste que j'ai découvert, <a href="http://www.powerhoof.com/crawl/">Crawl</a> est un jeu
de <em>dungeon crawling</em> compétitif et asymétrique. Avec des Gros Pixels.</p>
<p>En clair, lors d'une partie de Crawl, chaque joueur est associé à un héro ainsi qu'à un panthéon maléfique.
Tandis qu'un seul joueur contrôle un héro à l'écran, tentant de monter de niveau et de s'équiper à travers le donjon,
ses camarades prennent possession dans chaque pièce qu'il traverse des pièges et monstres tentant de le liquider,
en faisant également évoluer en puissance ces derniers petit à petit...</p>
<p>Si notre première partie était franchement bordélique et faite de très mauvais choix tactiques,
le concept est original et je me suis beaucoup amusé dans un rôle comme dans l'autre.
Nous avons un peu râlé lorsque la sélection aléatoire d'équipement vendu par le marchant favorisait un joueur,
ou quand un autre adoptait fourbement une tactique de « grève des monstres »,
mais le jeu n'en reste pas moins drôle et équilibré.
Vous le trouverez à 15 € sur Steam, et y jouer vous donnera l'occasion d'affronter Gabe Newell comme boss final d'un des donjons !</p>
<h2>Conclusion</h2>
<p>Je l'assume complètement, il s'agit avant tout d'un article « madeleine de Proust ».</p>
<p>Mais quel plaisir de replonger dans ces pépites !
Je vous recommande chaudement d'en rassembler plusieurs sur une clef USB à ramener lors votre prochaine soirée vidéo-ludique 😉</p>
<p>Bien sûr je n'ai effleuré que le plus gros de l'iceberg,
mettant de côté d'autres grands classiques que j'ai adoré, comme Puzzle Bobble ou Super Smash Bros sur (émulateur) Nintendo 64.
Ou bien des jeux ayant très mal vieilli comme <a href="https://www.mobygames.com/game/windows/island-wars-2">Island Wars</a>
ou <a href="https://sourceforge.net/projects/blobby/">Blobby Volley</a>, auquel je me souviens avoir joué en cours de Technologie au au collège.
Ou encore de vieux jeux indés de développeurs maintenant reconnus,
comme <a href="https://teknopants.itch.io/0space">0space</a> ou <a href="https://forums.tigsource.com/index.php?topic=3836.0">DasUberleben</a> de Beau « Teknopants » Blyth,
ou <a href="http://cargocollective.com/messhof/Jetpack-Basketball">Jet Pack BasketBall</a> de Messhof.
Il y a enfin de nombreux autres jeux récents que j'ai beaucoup apprécié sans les nommer ici,
comme les excellents <em>BattleBlock Theater</em> & <em>Castle Crashers</em>,
ou <em>Wand Wars</em> qu'il faudra un jour que je compare à <em>Lethal League</em>.</p>
<p>Pour conclure je vais en mentionner un dernier tout de même,
un jeu Flash minimaliste gratuit où certains reconnaîtront l'ancêtre de Spy Party, <a href="https://patkemp.itch.io/puji">Puji</a>.</p>
<p>À venir cette année (peut-être) : un autre article où j'aimerais évoquer quelques bons souvenirs de jeux vidéos coopératifs.</p>
<!--
- Trine 3
- Cowboyana : Après quelques dizaines de parties ces dernières années,
la progression dans ce jeu de plateforme / minigames me paraît toujours aussi incompréhensible et aléatoire.
Pourtant, je m'y suis amusé à chaque fois. Ses forces : un look "Far West géométrique" minimaliste,
un enchainement de phases de duel et d'attaque de train en coopération
(mais il n'y a qu'un seul cheval à la fin ! Et à quoi sert cette bouteille ??)
et un système de rechargement très fun et intuitif.
Et on peut obtenir un revolver qui décharge tout son barillet d'un coup (mais comment exactement, aucune idée).
Peut-être en affrontant le boss cowboy qui s'en sert. Et il y a une autre boss qui lance des boules de feu. Enfin je crois.
-->
<p>En attendant je serais ravis de lire vos propres recommandations dans les commentaires,
où vos propres anecdotes de partie que cet article ont pu rappeler à votre mémoire !</p>
<p><img style="float: left" src="images/2019/02/flat-heroes.webp" alt="Flat Heroes : A minimalistic epic adventures"></p>
<p><strong>EDIT [2019/02/16]</strong> : Je rajoute à cette liste <a href="https://yellowafterlife.itch.io/super-crate-box-together">Super Crate Box Together</a>
testé hier soir : sans être inoubliable, cette version co-op (jusqu'à 4) d'un petit jeu gratuit des hollandais de Vlambeer
est suffisament hypnotique, avec l'envie d'en refaire toujours « une dernière » pour faire un meilleur score,
pour que je vous le recommande ici !</p>
<p><strong>EDIT [2021/03/04]</strong> : Je rajoute également <a href="http://parallelcircles.com/press/sheet.php?p=flat_heroes">Flat Heroes</a>
découvert l'année dernière : un excellent jeu de plateforme jusqu'à 4 joueurs,
semi-coopératif avec même de sympatiques bosses de fin de niveau !
La direction artistique est originale, minimaliste mais réussie, et le gameplay très réactif et simple à prendre en main.</p>
<p><strong>EDIT [2022/07/04]</strong> : mon Shaarli contient également de nombreuses suggestions de jeux multijoueurs "en local", dont beaucoup sont gratuits : <a href="https://chezsoi.org/shaarli/?searchtags=LocalMultiplayer">#LocalMultiplayer</a></p>
<!-- TODO: mention Lethal League & Frog Smashers -->
<style>
article img { width: 28rem; }
.sbm2 { padding: 1rem 0; }
@media screen and (min-width: 40rem) {
article img { margin: 1rem; }
img.left { float: left; }
img.right { float: right; }
article h2 { clear: both; }
}
</style>Scavengers : anomalies nauchoresques2019-01-05T19:00:00+01:002019-01-05T19:00:00+01:00Lucas Cimontag:chezsoi.org,2019-01-05:/lucas/blog/scavengers-anomalies-nauchoresques.html<p>Il y a 2-3 ans, j'ai débuté avec des amis une campagne de <a href="http://awarestudios.blogspot.com/2014/01/scavengers.html">Scavengers</a>, un jeu de rôle de Grégory Pogorzelski.
Nos parties se sont malheureusement arrêtées après 3 sessions suite à mon déménagement à Nantes.</p>
<p>Aujourd'hui je réalise qu'il s'agissait du premier jeu <em>powered by the Apocalypse</em> auquel j'ai …</p><p>Il y a 2-3 ans, j'ai débuté avec des amis une campagne de <a href="http://awarestudios.blogspot.com/2014/01/scavengers.html">Scavengers</a>, un jeu de rôle de Grégory Pogorzelski.
Nos parties se sont malheureusement arrêtées après 3 sessions suite à mon déménagement à Nantes.</p>
<p>Aujourd'hui je réalise qu'il s'agissait du premier jeu <em>powered by the Apocalypse</em> auquel j'ai joué !
Et comme il m'avait beaucoup inspiré, j'ai rassemblé toutes mes idées en une aide de jeu illustrée.</p>
<p><img alt="Couverture du JdR Scavengers" src="images/2018/12/scavengers.png"></p>
<p>Elle se veut fidèle à l'ambiance originale :
jouer un équipage de baroudeurs spatiaux, explorant épave après épave, essuyant coup dur après coup dur,
et rêvant d'un jour trouver LA pépite qui leur permettra de raccrocher, enfin.</p>
<p>Comme elle contient surtout des tables aléatoires, elle peut sans doute également servir à d'autres jeux de rôles,
comme <a href="https://www.evilhat.com/home/scum-and-villainy/">Scum & Villainy</a>, <a href="http://www.tuesdayknightgames.com/mothership/">Mothership</a> de Sean McCoy
ou encore <a href="http://livresdelours.blogspot.com/search/label/Into%20the%20Dark">Into The Dark</a> d'Alexandre 'Kobayashi' Jeannette.</p>
<p>Elle est :</p>
<ul>
<li><a href="https://lucas-c.github.io/jdr/scavengers/">disponible en ligne ici</a></li>
<li><a href="https://github.com/Lucas-C/jdr/releases/download/adj-scavengers-1.2.2/adj-scavengers-1.2.2.pdf">ainsi qu'en PDF là</a></li>
</ul>
<p>Cette aide de jeu contient:</p>
<ul>
<li>de quoi décrire votre vaisseau, avec en particulier une table d'anomalies</li>
<li>des <em>playbooks</em> & TOCs pour vos personnages</li>
<li>une règle optionnelle, le compas d'humeurs, pour varier vos interprétations</li>
<li>des cachotteries de mission, incluant des contrats bonus</li>
<li>et enfin des imprévus de voyages listant des rebondissements aléatoires, certains tendant vers l'horreur cosmique</li>
</ul>
<p>Comme mes précédentes créations, elle utilise des images libres de droits (sous <em>Creative Commons</em>).</p>
<p>A noter également que la feuille de personnage du jeu est disponible sur <a href="https://github.com/Lucas-C/rpg-bonhomme">rpg-bonhomme</a>,
avec par exemple celle d'un de mes PJs : <a href="https://chezsoi.org/lucas/jdr/rpg-bonhomme/?layout=Scavengers&name=yuri_pashlov">Yuri Pashlov</a>.</p>
<p>En termes de ressources pour ces jeux, je vous recommande également <a href="https://bloodntongue.bandcamp.com/album/spaceship-graveyard">l'album Spaceship Graveyard de Mr. Fist</a> sur Bandcamp,
ainsi que ces quelques tables aléatoires :</p>
<ul>
<li><a href="https://imaginaryhallways.blogspot.com/2018/12/d66-sci-fi-horror-room-details.html?m=1">d66 Sci-Fi Horror Room Details</a></li>
<li>sur chartopia, <a href="https://chartopia.d12dev.com/en/chart/2769/"><em>Scum and Villainy Job Generator</em></a> ainsi que toutes celles en lien avec <a href="https://chartopia.d12dev.com/en/search/?q=Spaceship">les vaisseaux spatiaux</a></li>
</ul>
<p>Et puis pour dépeindre à vos joueurs à quoi ressemble Point Central,
je trouve que les illustrations de Paul Chadeisson pour le jeu vidéo <em>Remember Me</em> conviennent parfaitement.
Elle sont consultables <a href="https://www.artstation.com/pao/albums/672547">sur ArtStation</a>.
On y trouve également de magnifiques illustrations de Sergey Grechanyuk,
comme <a href="https://www.artstation.com/artwork/L6Yrl"><em>Smugglers Harbor</em></a> ou sa série <em>Kepler</em> :
<a href="https://www.artstation.com/grekgss/albums/635517">ici</a> et <a href="https://www.artstation.com/artwork/g5NDx">là</a>.</p>
<p>Enfin, j'ai profité de cette occasion pour créer une page sur ce blog dédiée à mes créations de JdR : <a href="pages/jeux-de-role.html">pages/jeux-de-role.html</a>.</p>
<!-- Com'
* [x] https://www.scenariotheque.org/Document/info_doc.php?id_doc=9919
-> référence : https://lucas-c.github.io/jdr/scavengers/
& https://github.com/Lucas-C/jdr/releases/download/adj-scavengers-1.2.2/adj-scavengers-1.2.2.pdf
-->
<style>
article img { max-height: 20rem; }
</style>Mothership Hive Mind2018-12-18T01:00:00+01:002018-12-18T01:00:00+01:00Lucas Cimontag:chezsoi.org,2018-12-18:/lucas/blog/mothership-hive-mind.html<p>In June this year, Sean McCoy published a tabletop RPG called <a href="http://www.tuesdayknightgames.com/mothership/"><strong>Mothership</strong></a>.
It has recieved tons of positive reviews, including <a href="https://www.reddit.com/r/rpg/comments/a2d66a/mothership_by_sean_mccoy_is_decembers_game_of_the/">/r/rpg December's Game of the Month</a> award:</p>
<blockquote>
<p>It's a d100 sci-fi/horror system, perfect for running Alien or Event Horizon, but it is most importantly exceptionally well designed.
Every …</p></blockquote><p>In June this year, Sean McCoy published a tabletop RPG called <a href="http://www.tuesdayknightgames.com/mothership/"><strong>Mothership</strong></a>.
It has recieved tons of positive reviews, including <a href="https://www.reddit.com/r/rpg/comments/a2d66a/mothership_by_sean_mccoy_is_decembers_game_of_the/">/r/rpg December's Game of the Month</a> award:</p>
<blockquote>
<p>It's a d100 sci-fi/horror system, perfect for running Alien or Event Horizon, but it is most importantly exceptionally well designed.
Every subheading is numbered for reference, character sheets have flowchart arrows pointing you towards important points,
and it hits the sweet spot niche of hard sci-fi without overwhelming crunch. Plus it's PWYW.</p>
</blockquote>
<p><img alt="Mothership game cover" src="/lucas/mothership-rpg.jpg"></p>
<p>The game has a very active community, which is very creative on <a href="https://discordapp.com/channels/461670627468771329/511645986288959500">Discord</a>.
On one channel, <em>Hive Mind</em>, numerous awesome d10 tables are collectively created.</p>
<p>With a good friend, Matthieu, we've put those tables on <a href="https://chartopia.d12dev.com/en/search/?q=mothership">Chartopia</a>.
You'll find below a list of most of them, categorized.</p>
<p><strong>Character & spaceship creation</strong>:</p>
<ul>
<li><a href="https://chartopia.d12dev.com/en/chart/4515/">Tics longterm space dwellers gain</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4521/">Spacer Tattoos Freighted with Meaning</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4520/">Things scrawled, stapled, or beautifully painted onto the hull while at port</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4462/">Space pets</a></li>
</ul>
<p><strong>Plot ideas</strong>:</p>
<ul>
<li><a href="https://chartopia.d12dev.com/en/chart/4500/">Contraband items your ship has been asked to smuggle across an interplanetary blockade</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4431/">Odd jobs that pay off in warp cores</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4498/">Ship maintenance chores</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4491/">What the hell were they researching that caused this?</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4434/">Reasons why this colony was abandoned</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4506/">Android secret agenda</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4494/">Geometry Neutral Mundane Spaceship Chase Events</a></li>
</ul>
<p><strong>Places</strong>:</p>
<ul>
<li><a href="https://chartopia.d12dev.com/en/chart/4513/">Hidden ways aboard the space station</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4519/">Reasons why you cant dock with that ship</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4509/">Additional places on the Dead Planet’s moon</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4510/">Additional locations on the surface of the dead planet</a></li>
</ul>
<p><strong>Surprises</strong>:</p>
<ul>
<li><a href="https://chartopia.d12dev.com/en/chart/4514/">Horrifying experiences caused by not going to cryosleep during hyperspace jumps</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4430/">Cryosleep defrosting mishaps</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4460/">Mundane jump drive malfunctions</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4435/">Spacial Anomalies That Broke Our Jump</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4438/">Derelict hazards that are not fightable</a></li>
</ul>
<p><strong>Loot</strong>:</p>
<ul>
<li><a href="https://chartopia.d12dev.com/en/chart/4501/">Alien Artifacts</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4433/">Small strange sculptures found hidden in the dead mans locker</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4458/">Weapon Attachments</a></li>
</ul>
<p><strong>Creatures, pests & diseases</strong>:</p>
<ul>
<li><a href="https://chartopia.d12dev.com/en/chart/4499/">Warning displays blinking on your ship “unidentified life form on board”, What is it?</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4439/">Monstrosities to stalk you in the dark</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4508/">Space diseases</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4522/">Spaceship Pests</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4535/">Domesticated Aliens</a></li>
</ul>
<p><strong>NPCs & encounters</strong>:</p>
<ul>
<li><a href="https://chartopia.d12dev.com/en/chart/4511/">Interstellar Crime Syndicates</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4436/">What's wrong with the Android?</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4432/">Teamsters for hire at the seedy space port</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4516/">Stowaways</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4459/">Legendary 2-person fighter-class ships</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4437/">Ships in docking orbit around this station at the edge of charted space</a></li>
<li><a href="https://chartopia.d12dev.com/en/chart/4461/">Vendor NPCs</a></li>
</ul>
<p><strong>EDIT [2020/03/04]</strong> : a PDF zine has been published on DriveThruRPG that compile those tables :
<a href="https://www.drivethrurpg.com/product/288946/Mothership-Hive-Mind-Issue-01">https://www.drivethrurpg.com/product/288946/Mothership-Hive-Mind-Issue-01</a></p>La Tour & Cheat Your Own Adventure2018-12-08T16:00:00+01:002018-12-08T16:00:00+01:00Lucas Cimontag:chezsoi.org,2018-12-08:/lucas/blog/la-tour-et-cheat-your-own-adventure.html<p>Hier soir a été l'occasion de <em>playtester</em> un très court jeu de rôle que j'ai bricolé ces deux dernières semaines.</p>
<blockquote>
<p>Vous venez d'arriver au sommet de La Tour, l'immeuble le plus dangereux du pays, sous l'emprise du gang Takoyashi.
Vous formez une équipe d'opérations spéciales aguerrie, équipée pour l'assaut et …</p></blockquote><p>Hier soir a été l'occasion de <em>playtester</em> un très court jeu de rôle que j'ai bricolé ces deux dernières semaines.</p>
<blockquote>
<p>Vous venez d'arriver au sommet de La Tour, l'immeuble le plus dangereux du pays, sous l'emprise du gang Takoyashi.
Vous formez une équipe d'opérations spéciales aguerrie, équipée pour l'assaut et experte en arts martiaux.
Votre tactique : sécuriser le bâtiment pièce par pièce, étage après étage, en appréhendant tout individu hostile.</p>
</blockquote>
<p><img alt="Photo d'un immeuble délabré" src="images/2018/12/Fire_Ravaged_Part_-_Nandram_Market_-_Brabourne_Road_-_Kolkata.png"></p>
<p>Ce jeu de rôle <a href="/lucas/blog/tag/monopage.html">monopage</a> vous propose de jouer une courte partie
dans la veine des films d'arts martiaux indonésiens comme <a href="https://www.imdb.com/title/tt1899353/">The</a> <a href="https://www.imdb.com/title/tt2265171/">Raid</a>.</p>
<p>Nous étions 3 autour de la table, la session a duré environ 2h30,
et les retours étaient très positifs 😄</p>
<p>Après plusieurs modifications suites aux retours très bien vus des mes playtesteurs, le voici :</p>
<ul>
<li><a href="https://lucas-c.github.io/jdr/latour/">en ligne ici</a></li>
<li><a href="https://github.com/Lucas-C/jdr/releases/download/latour-v2.0.0/latour-v2.0.0.pdf">en PDF là</a></li>
</ul>
<p>Petit conseil si vous tentez l'expérience comme MJ : comment souvent avec ce type de court jeu d'action intense,
il m'a semblé nécessaire d'intercaler des scènes où la tension retombe un peu, pour ménager le rythme de la partie.</p>
<p>Au passage, si vous souhaitez une ambiance sonore, je vous recommande la bande son du film <em>The Raid</em>.</p>
<p>Enfin, une idée évoquée par un joueur durant la partie : pourquoi pas imprimer quelques
<a href="https://www.pinterest.fr/pin/380694974726961282/">plans</a> d'<a href="https://www.pinterest.fr/pin/380694974726946190/">immeubles</a>
pour se repérer durant la partie ?</p>
<p>Je serais sincèrement ravis de vos retours, que vous l'ayez lu ou carrément testé autour d'une table !</p>
<hr>
<h2>Cheat Your Own Adventure</h2>
<p>Durant l'autre partie de soirée nous avons testé <a href="http://cheatyourownadventure.co.uk/">ce petit jeu an anglais</a>
de Shane McLean datant de 2012.
L'auteur a d'ailleurs écrit un autre jeu de 2 pages, plus sombre, que je compte bien essayer :
<a href="http://cheatyourownadventure.co.uk/the-last-coffee-shop-on-the-left">The Last Coffee Shop On The Left</a>.</p>
<p>Sur 2 pages, ce jeu sans meneur vous invite à retranscrire l'ambiance des Livres-Dont-Vous-Êtes-Le-Héros,
les différentes options qui s'offrent au lecteur et l'amène vers d'autres pages,
et les mauvais choix qui mènent à une mort atroce.</p>
<figure>
<img alt="Intérieur d'un livre-dont-vous-êtes-le-héros" src="images/2018/12/Livrejeuparagraphes.jpg">
<figcaption>Image de <a href="https://upload.wikimedia.org/wikipedia/commons/b/b1/Livrejeuparagraphes.jpg">Wikipedia</a> - Ninjamaster - <a href="https://creativecommons.org/licenses/by-sa/3.0/">CC BY-SA 3.0</a></figcaption>
</figure>
<p>Le terme <em>Cheat</em> dans le nom du jeu se réfère à une pratique bien connue de toute personne qui a déjà
lu / joué à un tel livre : revenir à la page précédemment visitée lorsque votre dernier choix vous a amené vers une fin funeste.
Autrement dit : tricher !</p>
<p>Personnellement j'ai trouvé que c'était une petite perle, ultra simple mais toutefois vraiment original en terme d'ambiance et de mécanique de jeu.
C'est un jeu idéal pour improviser une partie sur le pouce, pendant une balade ou en voiture.
Nous l'avons testé à 3, et je pense qu'il aurait été encore plus amusant d'y jouer à 4.</p>
<p>Notre livre s'appelait <strong>« La Crypte du Mal »</strong> et voici un petit compte-rendu des étapes par lesquelles le héros est passé :</p>
<ol>
<li>face à la crypte :<ul>
<li>[x] tu sonnes à la porte - <em>page 8</em></li>
<li>[x] tu escalades un pilier pour accéder directement à l'étage - <em>page 7</em></li>
</ul>
</li>
<li>tu trouves une torche. Une fois la pièce illuminée :<ul>
<li>[ ] tu fouilles la pièce - <em>page 67</em></li>
<li>[x] tu ouvres une des portes latérales - <em>page 42</em></li>
</ul>
</li>
<li>quelque chose grouille et te monte dessus :<ul>
<li>[ ] tu grimpes sur les meubles pour traverser le couloir - <em>page 25</em></li>
<li>[x] tu mets feu à mes vêtements. Tu y es immunisé de puis que Zaardrak t'as enchanté dans le tome 6. - <em>page 12</em></li>
</ul>
</li>
<li>face à un puits :<ul>
<li>[ ] tu t'élèves, aspiré par une force - <em>page 2</em></li>
<li>[x] tu jettes un caillou, entends un "PLOUF" et décides de plonger - <em>page 83</em></li>
</ul>
</li>
<li>dans l'eau au fond du puits :<ul>
<li>[ ] tu longes le contour pour trouver une issue - <em>page 13</em></li>
<li>[x] tu empreintes le passage que tu as aperçu sous l'eau - <em>page 82</em></li>
</ul>
</li>
<li>dans la grotte :<ul>
<li>[x] tu t'avances prudemment vers le bruit, l'arme à la main - <em>page 24</em> → 💀 des goules te dévorent vivant</li>
<li>[x] tu manges un champignon - <em>page 20</em></li>
</ul>
</li>
<li>tu deviens invisible et marche sur une pierre qui émets un bruit de mécanisme qui s'enclenche :<ul>
<li>[ ] de l'eau jailli du plafond et remplit la grotte - <em>page 37</em></li>
<li>[x] plus loin tu entends le bruit d'une herse métallique s'abattre au sol. Des cris inhumains retentissent - <em>page 66</em></li>
</ul>
</li>
<li>plusieurs goules et une jeune femme ont été écrasés :<ul>
<li>[ ] tu essaies de sauver la dame - <em>page 55</em></li>
<li>[x] tu lui fais les poches - <em>page 80</em></li>
</ul>
</li>
<li>tu trouves une fiole. Tu progresses dans la grotte dans un couloir jonché de cercueils :<ul>
<li>[ ] tu as déjà rencontré un vampire dans le tome 18, tu transperces les cercueils un à un avec un pieu - <em>page 17</em></li>
<li>[x] tu n'as pas lu le tome 18, tu avances bravement en criant « MAIS OÙ EST LA SORTIE ? » - <em>page 77</em></li>
</ul>
</li>
<li>alors que tu progresses, épée en avant, quelque chose approche vers toi dans l'ombre :<ul>
<li>[x] un vampire se jette sur toit en hurlant « CE TOMBEAU SERA VOTRE TOMBEAU ! » - <em>page 19</em> → 💀 il te découpe en morceaux</li>
<li>[x] un doppelgänger te ressemblant trait pour trait (mais pas invisible) se tient devant toi - <em>page 69</em></li>
</ul>
</li>
<li>comment le vaincre ?<ul>
<li>[x] il a la même épée que toi en main, et tu exploites une faille qu'elle a en combat - <em>page 6</em> → 💀 il te suce le sang</li>
<li>[x] la potion dans ta poche te permet de contrer ses pouvoirs et l'incapaciter - <em>page 10</em></li>
</ul>
</li>
<li>tu les reconnais, c'est Vendrak, un vieil ennemi. Reste à trouver le trésor :<ul>
<li>[x] tu t'empares de la clef autour de son cou - <em>page 4</em> → 💀 elle te brûle la main, puis le corps tout entier</li>
<li>[x] tu lui extorques le secret du trésor contre sa vie, mais tu le pourfends quand même - <em>page 3</em></li>
</ul>
</li>
</ol>
<p>Nous n'avons pas été toujours très inspirés, et avons pris beaucoup de liberté avec la règle en proposant
parfois différentes <strong>situations</strong> se présentant au joueur, plutôt que de lui présenter plusieurs choix d'<strong>actions</strong>.
Mais on s'est amusés comme des petits fous!</p>
<p>D'autre part l'idée des numéros de page nous a bien plu.
On s'était dit que si 2 numéros identiques étaient énoncés,
il faudrait faire en sorte que le joueur ait pu arriver à cette scène par plusieurs chemins.</p>
<p>Voici d'ailleurs une petite <strong>règle alternative</strong> que nous avons conçu à la fin de la partie,
pour remplacer l'échelle de difficulté des jets vraiment très punitive à la fin,
et introduire les numéros de pages dans la mécanique de jeu :</p>
<ul>
<li>lorsqu'un joueur propose un choix, il énonce ensuite un numéro de page de deux chiffres compris entre 1 et 6</li>
<li>après avoir fait son choix, le Narrateur lance 3d6 pour déterminer si cette option est mortelle :
si au moins un des chiffres correspond a un de ceux du numéro de page, le joueur survit</li>
</ul>
<p>Cette variante donne un risque de mort constant d'environ une chance sur trois. Et si vous voulez garder l'aspect "c'est plus dur vers la fin", lancez 4d6 dans les 4 premières pages,
et seulement 2d6 dans les 4 dernières.</p>
<p>Si jamais vous testez cette variante, dites-moi ce que vous en avez pensé en commentaire ! 😉</p>
<p><strong>EDIT [2019/03/12]:</strong> La traduction du jeu, incluant cette variante, est désormais disponible dans la section <a href="pages/jeux-de-role.html">JdR</a> de ce blog.</p>
<style>
article hr { margin: 5rem; }
</style>Au revoir Jugger, Machina Meles2018-12-03T08:30:00+01:002018-12-03T08:30:00+01:00Lucas Cimontag:chezsoi.org,2018-12-03:/lucas/blog/au-revoir-jugger-machina-meles.html<p>Pendant 3 ans, j'ai co-organisé à Angers puis à Nantes 27 entraînements d'un sport peu connu : le <strong>Jugger</strong>.</p>
<p>Je voudrais revenir ici sur cette belle expérience, et ce sport fantastique !</p>
<figure>
<img alt="Emblème de l'équipe représentant un blaireau rouge" src="images/2018/11/embleme-machina-meles.jpg">
<figcaption>Le nom que s'est choisi l'équipe, <em>Machina Meles</em>, signifie en latin "Blaireau mécanique".
<br>
Clin d'oeil aux <a href="https://www.lesmachines-nantes.fr">Machines de l'île de …</a></figcaption></figure><p>Pendant 3 ans, j'ai co-organisé à Angers puis à Nantes 27 entraînements d'un sport peu connu : le <strong>Jugger</strong>.</p>
<p>Je voudrais revenir ici sur cette belle expérience, et ce sport fantastique !</p>
<figure>
<img alt="Emblème de l'équipe représentant un blaireau rouge" src="images/2018/11/embleme-machina-meles.jpg">
<figcaption>Le nom que s'est choisi l'équipe, <em>Machina Meles</em>, signifie en latin "Blaireau mécanique".
<br>
Clin d'oeil aux <a href="https://www.lesmachines-nantes.fr">Machines de l'île de Nantes</a>,
et parce qu'on aimait bien le blaireau comme animal totem 😋</figcaption>
</figure>
<hr>
<p>Tout a commencé au début de l'été 2014, lorsque nous habitions Dublin avec ma compagne, Laëtitia.
Via des amis sur place, nous avons découvert ce sport en participant à un entraînement du club <em>Setenta</em> qui avait lieu à Fairview Park,
et qui était ouvert aux débutants complets.</p>
<p>Pour vous donner un aperçu, voici à quoi cela ressemble le Jugger :</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/S3S2s0Xn3NQ?start=36" allow="encrypted-media; picture-in-picture" allowfullscreen></iframe>
<p>Durant un match de Jugger, 2 équipes de 5 personnes s'affrontent pour s'emparer d'une balle, le <em>jugg</em>,
et la placer dans le but adverse, un réceptacle au niveau du sol.</p>
<p>La grosse spécificité du jeu : 4 membres de chaque équipe sont équipés d'armes rembourrées,
leur permettant d'immobiliser un adversaire au sol d'une simple touche,
tandis que le 5e joueur, sans arme, est le seul a pouvoir toucher la balle.</p>
<p><img alt="Matériel de Jugger" src="images/2018/11/Jugger_Matériel.png"></p>
<p>A l'origine, ce sport a été inventé dans un film de science-fiction post-apocalyptique !
Je cite <a href="https://fr.wikipedia.org/wiki/Jugger">wikipedia</a> :</p>
<blockquote>
<p>Le Jugger est un sport inspiré du film de 1989 "Le Sang des héros".
Le réalisateur David Webb Peoples inventa le Jugger spécialement pour ce film.
L'adaptation de ce jeu en sport eut lieu en 1992 en Allemagne par un groupe de fans.</p>
</blockquote>
<p>Et donc, ce même été là en 2014, une dizaine d'amis sont venus nous rendre visite à Dublin,
avant que nous ne plions bagage pour rentrer en France.</p>
<p>Nous en avons profité pour leur faire découvrir le Jugger, qui avait été un véritable coup de coeur.
Enfin quand je dis "nous", c'est grâce aux entraîneurs de l'équipe <em>Setenta</em> de l'époque, qui nous ont tout appris :
Mark, Seamus, Marion, mille fois merci !</p>
<p>Voici deux photos de cet entraînement fondateur :</p>
<div class="uk-grid">
<img class="uk-width-1-1 uk-width-small-1-2" alt="Entrainement de Jugger - été 2014 - Fairview park" src="images/2018/11/P1020849.JPG">
<img class="uk-width-1-1 uk-width-small-1-2" alt="Entrainement de Jugger - été 2014 - Fairview park" src="images/2018/11/JuggerInDublinFairviewPark.JPG">
</div>
<p>Et puis le temps passe...</p>
<p>On retrouve la France. On habite à Nantes, puis à Angers. On débute de nouveaux boulots.</p>
<p>Mais un an plus tard, à l'automne 2015, on se motive avec quelques amis à fabriquer des <em>pompfens</em>,
les armes rembourrées utilisées en Jugger, et c'est parti !</p>
<p>Pendant un peu plus d'un an, on se retrouvera environ une fois par mois au parc Balzac à Angers.
Avec Laëtitia, on s'efforce d'organiser comme on peut ces entraînements.
Et si le niveau technique n'est pas toujours là, que de bons souvenirs sont ces après-midi là !
Des duels, des culbutes, des matchs intenses ponctués de belles actions, où la chaîne était parée adroitement
ou bien le Qwik marquait dans un magnifique plongeon au sol...
Et puis ces soirées qu'on finissait souvent chez nous, autour d'un repas et d'un jeu de société.</p>
<div class="uk-grid">
<img class="uk-width-1-1 uk-width-small-1-2" alt="Entrainement à Angers" src="images/2018/11/DSC00194.JPG">
<img class="uk-width-1-1 uk-width-small-1-2" alt="Entrainement à Angers" src="images/2018/11/DSC00200.JPG">
<img class="uk-width-1-1 uk-width-small-1-2" alt="Entrainement à Angers" src="images/2018/11/P1030987.JPG">
<img class="uk-width-1-1 uk-width-small-1-2" alt="Entrainement à Angers" src="images/2018/11/P1040008.JPG">
</div>
<p>Et puis début 2017, on déménage à Nantes. Cette fois, c'est au parc du Grand Blottereau qu'on se retrouve (presque) tous les mois.
Et au cours de l'année, on décide de notre nom d'équipe, <em>Machina Meles</em>.</p>
<div class="uk-grid">
<img class="uk-width-1-1 uk-width-small-1-2" alt="Entrainement à Nantes" src="images/2018/11/chain_vs_shield_02.JPG">
<img class="uk-width-1-1 uk-width-small-1-2" alt="Entrainement à Nantes" src="images/2018/11/eliot_vs_laetitia_01.JPG">
<img class="uk-width-1-1 uk-width-small-1-2" alt="Entrainement à Nantes" src="images/2018/11/eliot_vs_lucas_02.JPG">
<img class="uk-width-1-1 uk-width-small-1-2" alt="Entrainement à Nantes" src="images/2018/11/eliot_vs_lucas_05.JPG">
</div>
<p>Au cours de ces rendez-vous réguliers, c'est plus d'une trentaine de personnes qui se sont essayé au Jugger avec nous.
Et en dehors, j'ai aussi eu l'occasion de faire découvrir ce sport à des petits cousins,
lors de fêtes organisées par des amis, et plusieurs fois à des collègues de travail.</p>
<p>A chaque fois la réception était très positive.
Bien que ce soit un sport intense physiquement, ponctués de sprints, de fentes d'escrime, d'esquives et même parfois de sauts,
beaucoup de personnes plutôt peu sportives y prennent goût comme moi,
tant l'aspect ludique vous prend au jeu.</p>
<p>C'est un sport où tous les âges y trouvent leur compte : j'ai vu des enfants de 10 ans s'amuser comme des fous,
comme des seniors de plus de 50 ans !
D'ailleurs, le Jugger est un des rares sports entièrement <strong>mixte</strong>.
Et le joueur le plus redoutable que je connaisse... est une joueuse !</p>
<p>Autre valeur importante de ce sport : l'auto-arbitrage.
Si en match officiel 4 arbitres opèrent sur le terrain,
le cœur de ce sport est basé sur ce principe :
c'est la responsabilité de chacun, lorsqu'il est touché par une arme rembourrée,
de se déclarer de lui-même "hors jeu", en lâchant son arme et posant son genou à terre.
C'est certain, le Jugger est un sport compétitif, mais c'est le seul que je connaisse
avec cette dimension de <em>fairplay</em> où chaque duel d'un match se conclut par un joueur concédant la victoire à son adversaire,
simplement.</p>
<figure>
<img alt="Petit match improvisé pour faire découvrir des amis, en septembre 2017" src="images/2018/11/21125793_10214732289961290_4233492605833704924_o.jpg">
<figcaption>Petit match improvisé pour faire découvrir des amis, en septembre 2017</figcaption>
</figure>
<p>A titre personnel, n'ayant jamais fait de sport de compétition, le plus difficile a été d'adopter une casquette d’entraîneur.
Organiser un déroulé d’entraînement, un échauffement, des exercices techniques... Rien de tout ça ne m'était naturel 😅
Heureusement Laëtitia s'en sortait bien mieux que moi !</p>
<p>Et puis il y a quelques semaines, le 3 novembre dernier, <a href="https://www.facebook.com/JuggerParis/">le club de Jugger de Paris</a>
organisait un tournoi amical. Pour <em>Machina Meles</em>, c'était le tout premier !</p>
<p>Au final c'est 5 équipes qui se sont retrouvé, dont 2 allemandes et une basée à Londres.
Avec une petite équipe de 5 joueurs, nous y avons participé avec un peu d’appréhension au vu de notre niveau...
Pour finalement passer un très bon moment, dans la joie et la bonne humeur,
à en prendre plein les mirettes face à des joueurs époustouflants !</p>
<p><img alt="Tournoi de Jugger à Paris du 3 novembre 2018" src="images/2018/11/DSC_0180.jpg"></p>
<p><img alt="Tournoi de Jugger à Paris du 3 novembre 2018" src="images/2018/11/DSC_0199.jpg"></p>
<p>Enfin, il est temps d'expliquer pourquoi le titre de cet article débute par "au revoir" :
j'ai décidé d'arrêter le Jugger.
Je n'ai simplement plus assez de motivation personnelle pour continuer à organiser les entraînements,
motiver les joueurs réguliers où prospecter pour en trouver de nouveaux.
Il ne s'est rien passé de particulier qui m'aurait fait changer de regard sur ce sport.
Au contraire, ce tournoi amical à Paris était génial, et je suis ravi d'avoir pu finir là-dessus.
L'ambiance y était très chaleureuse entre toutes les équipes, et rencontrer des adversaires bien plus forts que nous était très motivant !</p>
<p>Merci à vous les juggeurs parisiens pour avoir organisé cet événement !</p>
<p><img alt="Tous les participants du tournoi parisien réunis" src="images/2018/11/DSC_0397-ANIMATION.gif"></p>
<p>Merci surtout à Laëtitia, Vincent, Sarah et Thomas d'avoir formé cette équipe de choc !
Je suis pas prêt d'oublier ce week-end ensemble, et cet esprit d'équipe partagé.</p>
<p>Merci aussi à tous ceux qui sont venu tester ce sport loufoque,
et nous ont permis d'y jouer pendant ces quelques années :
Nicolas, Victor, Quentin, Cédric, Ellouan, Théo, François, Corentin, Emilie, Camille, Damien, Alexandre, Pierre, Flo, PEP, Elliot, Yann, Vincent, Simon, Philippe, Marion, Antonin, Maxime, Pierre, Orianne, Kevin, Anne-Laure, Thibault, Eliane & Joël</p>
<p><img alt="Couverture du manuel des règles de jeu 2017" src="images/2018/11/jugger-rulebook-cover.png"></p>
<style>
article p {
font-size: 1.2rem;
line-height: 1.5rem;
}
article iframe, article img {
display: block;
margin: 3rem auto;
max-height: 25rem;
}
article figcaption {
margin-top: -2rem;
margin-bottom: 3rem;
}
article hr { margin: 5rem 0; }
</style>
<script>
document.querySelectorAll('article img').forEach(img => {
let a = document.createElement('a');
img.parentNode.prepend(a);
a.appendChild(img);
a.href = img.src;
a.target = '_blank';
a.className = img.className;
img.className = '';
})
</script>Amères Victoires et Glorieuses Défaites2018-12-02T12:00:00+01:002018-12-02T12:00:00+01:00Lucas Cimontag:chezsoi.org,2018-12-02:/lucas/blog/ameres-victoires-et-glorieuses-defaites.html<p><img alt="Crâne couronné" src="images/2018/12/bone-1299051_1280.png" class="small-img"></p>
<p>Depuis une quinzaine d'années déjà, je joue à des jeux de rôles.
J'ai participé à plus d'une centaine de parties, et maîtrisé quelques belles campagnes.
J'ai testé des styles de jeux très différents, et conçu quelques prototypes moi-même.</p>
<p>Aujourd'hui, je suis très heureux et fier de vous présenter mon premier …</p><p><img alt="Crâne couronné" src="images/2018/12/bone-1299051_1280.png" class="small-img"></p>
<p>Depuis une quinzaine d'années déjà, je joue à des jeux de rôles.
J'ai participé à plus d'une centaine de parties, et maîtrisé quelques belles campagnes.
J'ai testé des styles de jeux très différents, et conçu quelques prototypes moi-même.</p>
<p>Aujourd'hui, je suis très heureux et fier de vous présenter mon premier jeu <a href="/lucas/blog/tag/mise-en-page.html">mis en page</a> !</p>
<blockquote>
<p>Dans ce court jeu de rôle, les joueurs incarnent les héros d'une saga épique et tragique, au dernier chapitre de leur périple.
Ils ont traversé bien des aventures ensembles, et arrivent au terme de leurs quêtes respectives,
où ils devront faire des choix cornéliens pour y apporter une conclusion.</p>
</blockquote>
<p><strong>Ameres Victoires & Glorieuses Defaites</strong> :</p>
<ul>
<li><a href="https://lucas-c.github.io/jdr/gdav/">est disponible en ligne ici</a></li>
<li><a href="https://github.com/Lucas-C/jdr/releases/download/gdav-v1.1.1/gdav-v1.1.1.pdf">ainsi qu'en PDF là</a></li>
</ul>
<p>Si vous prennez le temps de le lire, voir de le tester,
dites-moi ce que vous en pensez en commentaire svp !</p>
<figure>
<img alt="Table du jeu lors du tout premier playtest" src="images/2018/12/IMG_20181013_173455.jpg">
<figcaption>Table du jeu lors du tout premier playtest</figcaption>
</figure>
<style>
.small-img { max-height: 16rem; }
article img { max-height: 40rem; }
</style>Monster Chef & Havoc Brigade2018-12-01T22:20:00+01:002018-12-01T22:20:00+01:00Lucas Cimontag:chezsoi.org,2018-12-01:/lucas/blog/double-brigade.html<p>Pour notre partie hebdomadaire, nous avons cette fois testé 2 courts jeux de rôles:
<strong>Monster Chef</strong> de Bruno Bord et <strong>Havoc Brigade</strong> de Grant Howitt.</p>
<p><img alt="Couverture du JdR "Havoc Brigade"" src="images/2018/12/HavocBrigade.png"></p>
<h2>Monster Chef</h2>
<p>Ce premier jeu a été créé par Bruno Bord en juillet 2013.
Le jeu est disponible <a href="http://brunobord.github.io/monster-chef-rpg/">en ligne</a>,
il est également possible de …</p><p>Pour notre partie hebdomadaire, nous avons cette fois testé 2 courts jeux de rôles:
<strong>Monster Chef</strong> de Bruno Bord et <strong>Havoc Brigade</strong> de Grant Howitt.</p>
<p><img alt="Couverture du JdR "Havoc Brigade"" src="images/2018/12/HavocBrigade.png"></p>
<h2>Monster Chef</h2>
<p>Ce premier jeu a été créé par Bruno Bord en juillet 2013.
Le jeu est disponible <a href="http://brunobord.github.io/monster-chef-rpg/">en ligne</a>,
il est également possible de récupérer la version PDF et les sources du jeu
<a href="https://github.com/brunobord/monster-chef-rpg#t%C3%A9l%C3%A9chargements">sur GitHub</a>.</p>
<p>Je crois pouvoir affirmer qu'il reste un prototype inachevé
(la section sur l'équipement par exemple est bien vide et comporte un joli "TODO"),
mais l'idée de base du jeu m'avait suffisamment plu pour vouloir tenter l'aventure :</p>
<blockquote>
<p>Les personnages incarnent la brigade d'une cuisine "Monstresques",
c'est à dire servant uniquement des Monstres ou des Aliens ou des Créatures Étranges.
Saurez-vous satisfaire l'appétit d'un Ogre qui aurait commandé un tartare de Licorne sauce verte,
ou un Globurlien affamé qui attend ses larves de Ver Galactique au chocolat ?</p>
</blockquote>
<p>J'avais déjà évoqué le jeu dans <a href="/lucas/blog/quelques-suggestions-pour-debuter-en-jdr-gratuites-et-en-francais.html">un article précédent</a>,
et je suis bien content d'avoir pu enfin le <em>playtester</em> !</p>
<p>Le concept a bien séduit tous les joueurs autour de la table.
Nous avons rapidement décidé de jouer dans le restaurant d'un château hanté
(...qui flottait dans l'espace...).
Puis les joueurs ont créé leurs personnages.</p>
<p>Dans Monster Chef, chaque PJ a 5 compétences, toutes spécifiques à la cuisine:
<strong>préparer</strong>, <strong>cuire</strong>, <strong>touiller</strong>, <strong>assaisonner</strong> et <strong>dresser</strong>.
Comme au cours de la partie les PJs ont tenté des actions qui n'avaient rien à voir avec la gastronomie,
il était très rigolo de déterminer quelle compétence se rapprochait suffisamment qu'ils effectuent leurs jets de dés avec...
D'ailleurs, en pratique, "<strong>préparer</strong>" a été la plus utilisée !</p>
<p>Comme le jeu ne comporte pas beaucoup d'éléments pour donner corps aux PJs
(pas d'objectif / trait particulier / rôle spécifique en cuisine / équipement / etc.),
j'ai fait quelques tours de tables en posant à chaque joueur une question différente pour définir un peu le cadre :</p>
<ul>
<li>qu'y a-t-il autour du restaurant ?</li>
<li>de quelle manière en es-tu venu à travailler dans ce restaurant ?</li>
<li>quelle est ta pièce préférée dans le château ?</li>
<li>quel est le client régulier que tu aimes le moins ?</li>
<li>pour quelle raison aimes-tu travailler ici ?</li>
<li>mais au fait, qui est le patron du restaurant ?</li>
</ul>
<p>Une fois la partie démarrée, la structure du jeu impose aux PJs des allers-retours dans la terrible <strong>Réserve</strong>,
et de composer des plats à toute vitesse pour suivre le rythme des commandes ininterrompues des clients.</p>
<p>La Réserve est une super idée, qui permet de forcer un PJ à s'éloigner de la cuisine,
et à lui faire rencontrer toute sorte de créatures et défis.
Peut-être d'ailleurs qu'une table de génération d'événements s'y produisant pourrait être utile dans le jeu,
pour donner du grain à moudre au MJ.</p>
<p>Pour l'anecdote, après qu'un des clients du restaurant ait commandé exactement le même plat que les PJs venaient de réaliser,
l'un d'eux a eu la prévoyance de remonter une plus grande quantité d'ingrédients que nécessaire
la fois suivante qu'il descendit dans la Réserve !</p>
<p>Dans l'ensemble, le gros point négatif du jeu est son <strong>système trop punitif</strong>.
Non seulement il se focalise uniquement sur les échecs,
ce qui a beau être comique peut finir par fatiguer les joueurs qui ont l'impression de cumuler les problèmes,
mais en plus il n'est pas assez équilibré. De base, avec ses 5 compétences, un joueur a comme chances de réussites:
50%, 25%, 12.5%, 6.75% et 3.875%. Et c'est sans compter les malus qui s'accumulent très vites,
avec le nombre de clients qui augmente et les Désagréments qui s'accumulent !</p>
<p>Durant notre partie, pour faciliter un peu la tâche aux PJs, j'avais décidé que les Jokers permettaient d'ignorer
<strong>tous</strong> les dés d'échec. Et même avec cette variante c'est resté laborieux pour eux,
et j'ai parfois préféré leur donner des complications pour réaliser leurs actions, que des Désagréments punitifs.</p>
<p>De même, en cuisine, je n'ai pas demandé aux PJs 5 jets pour chaque plat composé.
J'ai préféré les mettre au défi de trouver des solutions de secours quand leurs recettes tournaient au vinaigre,
mais ne pas leur demander de lancer les dés systématiquement.</p>
<p>Par contre j'ai bien aimé qu'on puisse y jouer avec n'importe quels dés, quel que soit leur nombre de faces !
Sans changer la mécanique de jeu et dans un but de simplification, nous avons considéré que les résultats pairs étaient des succès,
et les dés impairs des échecs.</p>
<figure>
<img alt="Couverture du tome 1 de Gloutons & Dragons" src="https://chezsoi.org/lucas/blog/images/readings/gloutons-et-dragons.jpg">
<figcaption>Je vous recommande au passage ce très bon manga, et inspi idéale pour Monster Chef : Gloutons & Dragons de Ryoko Kui</figcaption>
</figure>
<p>Dans l'ensemble, il m'a fallu beaucoup improviser tout au long de la partie:
inventer des clients, des plats tarabiscotés, des surprises dans la Réserve, et toujours plus de Désagréments...
Il pourrait être bien utile que le jeu propose des tables aléatoires pour aider le MJ,
et même introduire des éléments d'histoire :</p>
<ul>
<li>un client retourne un plat en cuisine</li>
<li>un client demande à rencontrer le chef: satisfait ou pas satisfait ?</li>
<li>un appareil de cuisine rend l'âme</li>
<li>un client demande un plat à base de viande... de l'espèce d'un des PJs !</li>
<li>un habitant de la réserve ne consent à leur donner un ingrédient que si les PJs lui ramènent un autre objet</li>
<li>une des serveuses en pince pour un des PJs et l'aider volontiers contre un petit baiser...</li>
<li>un des plats tourne mal : il caille, des grumeaux se forment...</li>
<li>un client veut un plat de son enfance, dont il décrit les saveurs mais ne connaît pas les ingrédients... aux PJs de le reconstituer !</li>
<li>en deuxième partie de soirée, le restaurant se transforme en club et les PJs doivent maintenant servir des cocktails !</li>
</ul>
<p>Une autre proposition pour aider les joueurs, et aussi être un poil réalistes établir qu'en début de service
les PJs ont déjà <strong>tous les ingrédients nécessaires à leurs plats</strong> en cuisine,
et qu'ils ne doivent descendre à la Réserve que pour en chercher lorsqu'il en vient à manquer,
ou qu'un client réclame un ingrédient spécial.</p>
<p>Enfin, comme le jeu repose sur une certaine tension vis-à-vis du temps qui passe,
une petite suggestion de ma part serait d'introduire plusieurs sabliers,
un par commande, pour imposer aux PJs de devoir réaliser une recette rapidement.
A expérimenter 😉</p>
<p>Au final, nous avons conclu la partie après un peu plus de 2h de jeu, et 3 clients servis !</p>
<hr>
<h2>Havoc Brigade</h2>
<p>Deuxième jeu de la soirée, nous avons cette fois testé un JdR de Grant Howitt publié en 2014,
dont le PDF de 25 pages en anglais est disponible à prix libre sur <a href="https://gshowitt.itch.io/havoc-brigade">itch.io</a>.</p>
<p>Voici le pitch du jeu traduit :</p>
<blockquote>
<p>Brigade Massacre est un jeu de rôle conçu pour raconter une histoire en particulier :
celles d'orcs "infiltrant" la ville humaine de Freiberg, pour kidnapper le répugnant Prince Holstein.
C'est un jeu "one-shot" léger; ce n'est pas une histoire profonde de tourments émotionnels et d'introspection torturée.
C'est une histoire d'explosions, de grosses bagarres, de vols de trucs provoquant des problèmes,
et par-dessus tout, de plans vraiment stupides.</p>
</blockquote>
<p>Notez l'usage des guillemets autour d'<strong>infiltrer</strong> 😁</p>
<p>Le jeu donne le choix aux joueurs parmi 6 personnages pré-tirés,
que j'ai trouvé très bien écrits et conçus.</p>
<p>Mes joueurs ont pris un malin plaisir à incarner des orcs "bas du front",
dans un spectre allant de la grosse brute ne vivant que pour le combat,
au demi-orc seul capable de parler la langue des humains,
en passant par le vieux shaman qui n'a jamais vu d'humain et ne cesse de répéter "de mon temps"...</p>
<p>L'univers est tout de suite familier : on retrouve les code de la <em>fantasy</em> classique,
où l'on peut croiser des elfes, des nains, des gnomes et des magiciens.</p>
<p>Une carte de la ville de Freiberg est également fournie,
comportant en son centre le palais princier dans lequel les PJs doivent s'infiltrer.
Mes joueurs ont trouvé que c'était un gros "plus" pour se plonger dans l'ambiance du jeu.</p>
<p>Tout cela donne un scénario à la fois très cadré (objectif clair, 48 heures pour le réaliser),
et très libre, les PJs ayant toute liberté pour accomplir la mission qui leur a été confiée
par le seigneur de guerre orc Fracass'Crânes.</p>
<p>Le système de jeu est une petite perle qui vous demandera plus d'une vingtaine de dés à 6 faces.
Simple et efficace, il encourage les joueurs à s'entraider, et surtout à ramasser
tout le matériel qu'ils peuvent en chemin car leur équipement s'abîme petit à petit !
Tout en leur donnant la possibilité, une fois pas partie, de tout saccager autour d'eux et créer un gros boxon. 👺⚔️👹</p>
<p>En pratique, la mécanique du pool de dés de Suspicion fonctionne très bien,
et pousse les joueurs à temporiser un peu pour limiter la pagaille.</p>
<p>La seule chose qui m'a paru un peu superflue, c'est le score de Morale des groupes de PNJs,
qui n'est jamais beaucoup descendu durant la partie.
Là où les caractéristiques de PNJs fournies à la fin du PDF sont bien utiles et m'ont donné plein d'idées,
gérer ces PNJs systématiquement "en groupe" n'est pas ce que je trouve le plus pratique,
mais c'est probablement plus une question de préférence personnelle de MJ.</p>
<p>Au terme de ces 3h de partie survoltées, je recommande donc chaudement ce jeu
pour une petite partie de baston décomplexée.
Comme indiqué dans les règles, il est idéal pour joueur en convention, ou avec des débutants.</p>
<p><img alt="Illustration d'un personnage du jeu, le "Sergeant" Shivvit Deadeye" src="images/2018/12/HavocBrigade_SergeantShivvitDeadeye.png"></p>
<style>
article hr { margin: 5rem; }
</style>Game of life favicon2018-11-23T22:00:00+01:002018-11-23T22:00:00+01:00Lucas Cimontag:chezsoi.org,2018-11-23:/lucas/blog/game-of-life-favicon.html<p>I just added a <a href="https://it.wikipedia.org/wiki/Favicon">favicon</a> to this site,
representing a <em>glider</em> of <a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life">Conway's Game of Life</a>.</p>
<p><img alt="My website GIF favicon" src="https://chezsoi.org/favicon.ico"></p>
<p>This GIF was made with a Python script.
The source code is <a href="https://github.com/Lucas-C/dotfiles_and_notes/blob/master/languages/python/favicon.py">on GitHub</a>,
and uses <a href="https://github.com/neozhaoliang/pywonderland/blob/master/src/gifmaze/gifmaze.py">Zhao Liang's gifmaze.py</a>.</p>
<p>Feel free to reuse the code to make you own favicons 😉</p>
<!-- Another cool animated GIF favicon: https://i1.wp.com/studinano.com/wp-content/uploads/2018/11/favicon.gif -->
<style>
article img {
transform …</style><p>I just added a <a href="https://it.wikipedia.org/wiki/Favicon">favicon</a> to this site,
representing a <em>glider</em> of <a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life">Conway's Game of Life</a>.</p>
<p><img alt="My website GIF favicon" src="https://chezsoi.org/favicon.ico"></p>
<p>This GIF was made with a Python script.
The source code is <a href="https://github.com/Lucas-C/dotfiles_and_notes/blob/master/languages/python/favicon.py">on GitHub</a>,
and uses <a href="https://github.com/neozhaoliang/pywonderland/blob/master/src/gifmaze/gifmaze.py">Zhao Liang's gifmaze.py</a>.</p>
<p>Feel free to reuse the code to make you own favicons 😉</p>
<!-- Another cool animated GIF favicon: https://i1.wp.com/studinano.com/wp-content/uploads/2018/11/favicon.gif -->
<style>
article img {
transform: scale(8);
image-rendering: optimizeSpeed;
margin: 5rem auto;
}
</style>Génie domestique2018-11-20T21:30:00+01:002018-11-20T21:30:00+01:00Lucas Cimontag:chezsoi.org,2018-11-20:/lucas/blog/genie-domestique.html<blockquote>
<p>Vous êtes un dieu mineur, un <em>genius loci</em>, dans un petit village calme du sud de l'Angleterre dans les années 60.</p>
</blockquote>
<figure>
<img src="images/2018/11/genius_loci.jpg">
<figcaption><a href="https://www.flickr.com/photos/digitalrob70/3736263913/in/photolist-6Gak6k-ndPvyA-oE99K3-U6SUQu-Vcqj8g-qxRXES-MCUqLF-dLfEXY-hCAPS4-dSXX6k-Sb4ZnV-dUUTc8-SLLyFj-ST6Tz1-f1W1Ma-3o9gkV-ekqJKF-3odLbU-pkyUZ1-V49kDN-q6ZK6u-fE6FVr-2cPytgN-Ub1y3y-3o9fMB-efrLux-5Axiq4-qQkLiU-59dDme-8t1zDT-UR5sJ4-bpACnN-q3obA6-dVp8Zz-X4SNoZ-cYBYLw-W5LbT3-pAqSdp-56944f-9f4mTK-ifWsJY-QHt3vf-dCvmf4-raXAQb-s4JuYx-6B3tAL-557Zh7-84LmCH-fdkQnq-NYuz6v">Castle Combe, Wiltshire, England</a> by Robert Lennon - <a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a></figcaption>
</figure>
<p>Voici le pitch de <a href="https://rowanrookanddecard.com/product/genius-loci/">Genius Loci</a>, le JdR <a href="/lucas/blog/tag/monopage.html">monopage</a> de Grant Howitt
auquel nous avons joué lors de …</p><blockquote>
<p>Vous êtes un dieu mineur, un <em>genius loci</em>, dans un petit village calme du sud de l'Angleterre dans les années 60.</p>
</blockquote>
<figure>
<img src="images/2018/11/genius_loci.jpg">
<figcaption><a href="https://www.flickr.com/photos/digitalrob70/3736263913/in/photolist-6Gak6k-ndPvyA-oE99K3-U6SUQu-Vcqj8g-qxRXES-MCUqLF-dLfEXY-hCAPS4-dSXX6k-Sb4ZnV-dUUTc8-SLLyFj-ST6Tz1-f1W1Ma-3o9gkV-ekqJKF-3odLbU-pkyUZ1-V49kDN-q6ZK6u-fE6FVr-2cPytgN-Ub1y3y-3o9fMB-efrLux-5Axiq4-qQkLiU-59dDme-8t1zDT-UR5sJ4-bpACnN-q3obA6-dVp8Zz-X4SNoZ-cYBYLw-W5LbT3-pAqSdp-56944f-9f4mTK-ifWsJY-QHt3vf-dCvmf4-raXAQb-s4JuYx-6B3tAL-557Zh7-84LmCH-fdkQnq-NYuz6v">Castle Combe, Wiltshire, England</a> by Robert Lennon - <a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a></figcaption>
</figure>
<p>Voici le pitch de <a href="https://rowanrookanddecard.com/product/genius-loci/">Genius Loci</a>, le JdR <a href="/lucas/blog/tag/monopage.html">monopage</a> de Grant Howitt
auquel nous avons joué lors de notre session hebdomadaire.</p>
<p>Tout commence avec la création des PJs à partir d'une table aléatoire.
Les protagonistes de cette histoire sont donc :</p>
<ul>
<li><strong>Nelson</strong>, dieu d'un petit parc animé. Légèrement conservateur, il est à l'image de l'amiral éponyme dont la statue trône dans son cher "square"</li>
<li><strong>Lili</strong>, déesse d'une école primaire fermée suite au départ de la ville de tous les professeurs, à l'apparence de petite fille, un ourse en peluche sans tête dans les bras</li>
<li><strong>Glenn</strong>, dieu débonnaire mais dangereux du pub local, dont le sous-sol abrite de sombres activités illicites</li>
</ul>
<p>La création de PJ inclue également de définir ce qu'il aime à la folie et ce dont il a le plus peur, à partir de deux tables dédiées,
ainsi que 3 choses intéressantes à propos d'elle ou lui.
Petite astuce pour ces derniers traits : suggérez-leur, s'ils sont en manque d'inspiration, de poser la question aux autres joueurs.</p>
<p>Pour finir, et c'est la phase qui a eu le plus de succès, les joueurs dessinent leur village.
J'ai tracé le cours d'eau qui passe ici, puis les joueurs ont ajouté à tour de rôle, et avec beaucoup d'enthousiasme,
maisons, commerces, rues, fermes et champs. <strong>Bienvenue à Goldenshire !</strong></p>
<p>En parallèle, je tire dans une table la menace qui plane sur le village et qui servira de moteur dramatique à la partie,
ainsi que 2 autres <em>genius loci</em> PNJs.
Pendant que je note quelques idées de personnages et d'événements à faire intervenir dans l'histoire,
je leur demande de faire un tour de parole pour dire ce que leur dieu pense de celui de leur voisin de gauche.</p>
<p>Et c'est parti ! 3h30 de partie qui débutent doucement sur un dimanche printanier où les PJs vont petit à petit se rendre compte
que la forêt locale - Tom, un autre <em>genius loci</em> - est menacée par un dieu de l'élevage de poulets en batterie.</p>
<p>A posteriori les joueurs m'ont dit qu'ils avaient trouvé un peu laborieux le début de partie,
ne sachant trop quoi faire / dans quelle direction allait le scénario.
Pour ma part j'ai beaucoup aimé planter ce décor de village champêtre et paisible,
ses personnages et ses turpitudes.</p>
<p>Parmi les événements notables de la partie :</p>
<ul>
<li>un fermier interrogé de manière musclée dans le sous-sol du pub de Glenn, puis saoulé jusqu'à plus soif pour lui faire oublier l'interrogatoire</li>
<li>une offrande à la déesse de la rivière pour qu'elle fasse pleuvoir afin d'éteindre un incendie</li>
<li>un tuyau infini à l'extrémité d'une lance à incendie de pompier</li>
<li>un tribunal des petits dieux du village rassemblé dans la cantine de l'école abandonnée pour juger un dieu malveillant,
et qui vote au final pour le dévorer collectivement et rituellement</li>
</ul>
<p><a href="images/2018/11/IMG_20181119_234326.jpg"><img alt="Photo de la table de notre session de jeu" src="images/2018/11/IMG_20181119_234326.jpg"></a></p>
<p>A la fin de la partie, nous étions unanimement très content de ce petit jeu !</p>
<p>Pour un jeu monopage, je pense qu'il a une très bonne rejouabilité.
D'ailleurs le mythe de la <a href="https://fr.wikipedia.org/wiki/Divinit%C3%A9_du_foyer">divinité du foyer</a> est antédiluvien.
On le retrouve dans beaucoup de cultures sous différentes formes, et c'est donc un gros potentiels d'histoires !</p>
<p>J'ai vraiment bien aimé le système de résolution des actions, ultra simple mais moteur d'éléments de narration,
ainsi que le système des "conditions" représentant les différents types de "blessures" que peuvent subir les <em>genius loci</em>.
Même la petite table pour déterminer l'attitude des PNJs face au PJs est bien pensé et bien utile !
Vraiment, ce jeu est un condensé ultra efficace de mécaniques de jeu futées.</p>
<p>Pour avoir un autre son de cloche sur ce jeu, je vous invite à jeter un œil <a href="https://lapartiedulundi.wordpress.com/2017/08/22/the-gods-around-the-corner/">au compte-rendu de lapartidulundi sur ce jeu</a>.</p>
<p>Voici une petite table supplémentaire comme "extension" / "aide de jeu",
pour donner un but supplémentaire à vos PJs, si vous souhaitez jouer plus en mode "sandbox" :</p>
<table>
<thead>
<tr>
<th>d6</th>
<th>For your domain, you crave</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>1</strong></td>
<td>More visitors</td>
</tr>
<tr>
<td><strong>2</strong></td>
<td>To grow</td>
</tr>
<tr>
<td><strong>3</strong></td>
<td>More births & deaths</td>
</tr>
<tr>
<td><strong>4</strong></td>
<td>To have people stay longer</td>
</tr>
<tr>
<td><strong>5</strong></td>
<td>To be the scene of important events</td>
</tr>
<tr>
<td><strong>6</strong></td>
<td>To embellish</td>
</tr>
</tbody>
</table>
<p><strong>EDIT [2019/04/11]:</strong> une traduction en français est disponible dans <a href="pages/jeux-de-role.html">la section <em>jeux de rôle</em> de ce blog</a>.</p>
<style>
article img {
margin: 1rem auto;
max-height: 25rem;
}
table { border-collapse: collapse; margin: 2rem auto; }
td { padding: 5px; }
tbody > tr:nth-of-type(odd) { background-color: #ddd; }
</style>Dog bear2018-11-18T10:30:00+01:002018-11-18T10:30:00+01:00Lucas Cimontag:chezsoi.org,2018-11-18:/lucas/blog/dog-bear.html<p><em>(English version at the bottom)</em></p>
<p>Cette semaine, nous avons testé le jeu de rôle <a href="https://www.invincible.ink/traditional-games/dog-bear/">Dog Bear</a>,
du collectif australien Invincible Ink, où l'on joue des soldats d'élite faisant leur compte rendu de mission à leur supérieur.
Le jeu est paru en 2015, le PDF en anglais coûte 5$ et il …</p><p><em>(English version at the bottom)</em></p>
<p>Cette semaine, nous avons testé le jeu de rôle <a href="https://www.invincible.ink/traditional-games/dog-bear/">Dog Bear</a>,
du collectif australien Invincible Ink, où l'on joue des soldats d'élite faisant leur compte rendu de mission à leur supérieur.
Le jeu est paru en 2015, le PDF en anglais coûte 5$ et il a pour particularité de se jouer avec des cartes à jouer plutôt que des dés.</p>
<p><img alt="Couverture du jeu Dog Bear" src="images/2018/11/dog-bear.png"></p>
<p>Dans l'ensemble, on a adoré ce jeu ! On s'est beaucoup amusé, et ce dès la création minimaliste des personnages :
on tire 5 cartes et on se reporte à une table, 2 cartes définissent son nom de code, et les 3 autres quelques traits de caractère.
Résultat du tirage pour nos PJs : <em>Steel Raptor</em>, <em>Chamelon Cat</em> et <em>Threefold Devil</em>.</p>
<p>Le reste du système est du même acabit : simple et basé sur des tables de génération d'éléments narratifs.
Notre partie, certes jouée à marche forcée, n'a duré qu'1h30. Je pense qu'une partie peut facilement durer 3 ou 4 heures,
en ajustant le nombre de cartes de la Rivière, mais probablement pas plus, et c'est donc un jeu adapté à des sessions courtes.
Il possède néanmoins une grande rejouabilité, et si le temps ne nous manquais pas nous étions tous partant pour en refaire une en fin de partie !</p>
<p>Probablement parce que nous avions évoqué Dwayne Johnson et Jason Statham juste avant le début de la partie,
et aussi en raison de ces noms de code à la fois classes et ridiculement kitsch,
nous avons joué toute la session en usant et abusant des clichés des films de guerre, avec des références allant de Predator à Mozinor.
Et joué comme ça, Dog Bear est un jeu qui donne mal à la gorge 😆
On force sur la voix pour surjouer les soldats vétérans ultra testostéronés... et on se fend bien la poire !</p>
<p>Malgré cette ambiance parodique, et c'est là le tour de force du jeu, le déroulement de la partie était digne du scénario d'un jeu <a href="https://fr.wikipedia.org/wiki/Metal_Gear_(s%C3%A9rie)">Metal Gear</a> !
Clairement l'inspiration principale de Dog Bear, le jeu se prête parfaitement bien à retranscrire l'ambiance d'espionnage tactique,
d'action survoltée et de retournement de situations propre à cette série de jeux vidéos.
D'ailleurs le rôle du MJ est d'incarner le supérieur des PJs nommé... The Boss.</p>
<figure role="group">
<img alt="Punished 'Venom' Snake - Big Boss" src="images/2018/11/punished__venom__snake_big_boss_by_mrsteph06220_d98wl8t-fullview.jpg">
<figcaption><a href="https://www.deviantart.com/mrsteph06220/art/Punished-Venom-Snake-Big-Boss-559153037">Punished 'Venom' Snake - Big Boss</a> de MrSteph06220 - <a href="https://creativecommons.org/licenses/by-nd/3.0/">CC BY-ND 3.0</a></figcaption>
</figure>
<p>A titre personnel, c'est ce que j'ai préféré dans le jeu :
le MJ <strong>incarne</strong> réellement un personnage central de la partie, discutant autour d'une table avec les PJs,
dans cette tente militaire quelque part dans la jungle où ils lui font leur <em>debriefing</em>.
Et sa posture change du tout au tout par rapport à une partie de JdR plus classique :
il ne <strong>décrit pas</strong> aux PJs ce qu'ils voient, il leur <strong>demande</strong> ce qu'ils ont vu !</p>
<p>Concrètement, voici le genre de questions que je posais comme MJ :</p>
<ul>
<li>alors, dites-moi soldats, comment s'est déroulée l'arrivé dans la zone d'opération ?</li>
<li>rappelez-moi, quel était l'objectif de la mission précisément ? Vous savez pourquoi c'était d'une importance capitale n'est-ce pas ?</li>
<li>très bien. Et donc <em>Chamelon Cat</em>, quelle était la première étape majeur de votre plan ? <em>(en pointant la première carte de la Rivière)</em></li>
<li>est-ce que les défenses en place autour de la base correspondait à notre Intel ? Avez-vous eu des surprises ?</li>
<li>ah oui, je vois, et c'est à ce moment là que s'est déclenché l'avalanche que nous avons observé depuis nos satellites espions ?</li>
<li>bien joué les gars. Et ensuite <em>Threefold Devil</em>, comment avez-vous réussi à franchir les scanners génétiques et les drones armés ?</li>
<li>et c'est comme ça, <em>Steel Raptor</em>, que tu as perdu un doigt à la main droite ?</li>
</ul>
<p>En résumé, les joueurs décrivent l'essentiel des lieux et événements,
tandis que le MJ leur "tend la perche" pour introduire des rebondissements.
Et si jamais il veut leur mettre des bâtons dans les roues plus explicitement,
le MJ doit ruser en expliquant comment il a eu vent de certaines infos partielles sur le déroulé de la mission...</p>
<p>C'est très rafraîchissant comme style de jeu !
Et les joueurs étaient très inspirés, donnant lieu à une magnifique scène où le <em>field operative</em> ennemi se révèle être...
le frère d'un des PJs ! Celui-ci est alors tenté de retourner sa veste, mais un autre PJ va lui faire entendre raison,
et lui rappeler son devoir face à sa patrie... au travers d'un duel de <em>close combat</em>, torses nus dans la neige !</p>
<p>Du pour <em>Metal Gear Solid</em> je vous dit 😊</p>
<p>Au passage, le jeu encourageant à donner des <em>fresh cuts</em> aux joueurs proposant des éléments d'histoire ingénieux ou amusants,
je vous recommande également <strong>d'en offrir aux joueurs qui introduisent d'eux même des complications dans leurs descriptions</strong>,
pour les encourager à enrichir l'histoire de péripéties.</p>
<p>Dans l'ensemble, la règle du jeu est très verbeuse. Les 11 pages d'explications pourraient être résumées sur un recto-verso en adoptant un style plus compact.</p>
<p>Le système de la Rivière de cartes est bien pensé, représentant les jalons de la mission et leur difficulté,
avec des tables associées pour définir le lieu, l'objectif et l'ennemi,
et l'ingénieuse mécanique des cartes faces cachées permettant des <em>plot twists</em> imprévus.
A noter que nous avons eu la chance d'en déclencher deux sur les trois tas de cartes lors de notre partie,
et avec le recul je vous recommande de "truquer" votre distribution de cartes pour vous assurer d'en avoir, sans quoi la partie aura beaucoup moins de saveur.</p>
<p>Voici comment je pense m'y prendre la prochaine fois : les retournements de situation étant déclenchés par des têtes (valet, dame, roi),
j'en prendrai 3 et 3 autres cartes de 1 à 10, puis je mélangerai ces 6 cartes pour enfin en distribuer 3 face cachée pour former la rivière.
Ainsi il y a toujours une possibilité (très faible, 1/20) de n'avoir aucun <em>plot twist</em>, et une grande chance d'en avoir 1 ou 2.</p>
<p><a href="images/2018/11/IMG_20181115_212032.jpg"><img alt="Photo de la table de notre session de jeu" src="images/2018/11/IMG_20181115_212032.jpg"></a></p>
<p>Enfin, de manière générale, le système laisse peu de place à l'échec, et peut être perçu comme "trop facile" pour les PJs :
sur notre partie de test, <strong>aucune</strong> action des PJs n'a échoué.</p>
<p>Pour moi ce n'est pas réellement un problème, l'essentiel étant que leur résolution a donné lieu à des rebondissements et des surprises,
mais voici quelques suggestions de règles alternatives pour corser les choses :</p>
<ul>
<li>interdire le <em>swap</em> de cartes par le ou les joueurs réalisant l'action</li>
<li>interdire le <em>swap</em> de cartes lorsqu'une carte de la Rivière est retournée</li>
<li>l'une ou l'autre de ces règles, pour limiter les possibilités des joueurs en fin de partie :<ul>
<li>les 2 cartes du début définissant le nom de code ne sont pas remplacées lorsqu'elles sont utilisées</li>
<li>les cartes utilisées pour battre la Rivière ne sont pas remplacées</li>
</ul>
</li>
</ul>
<hr>
<p>This week, we tried the tabletop RPG <a href="https://www.invincible.ink/traditional-games/dog-bear/">Dog Bear</a>,
from the Australian collective Invincible Ink, in which we played elite soldiers delivering an operational debrief to their commanding officer.
The game was published in 2015, the PDF costs 5$ and it has the distinction of using playing cards instead of dice.</p>
<p><img alt="Dog Bear RPG cover" src="images/2018/11/dog-bear.png"></p>
<p>Overall, we loved the game! We had a lot of fun from the very beginning. At the character creation phase,
you draw 5 cards and look them up in a table, 2 cards defining your code name, while the 3 others hint at some of your quirks.
Result for our PCs : <em>Steel Raptor</em>, <em>Chamelon Cat</em> et <em>Threefold Devil</em>.</p>
<p>The remainder of the game system is similar: straightforward and based on tables which generate elements of the story.
Our session, admittedly played full throttle, lasted only 90min. I think a game can easily last 3 to 4 hours
by adjusting the number of cards in the River, but probably not much longer, and so this is a game that fits short RPG sessions.
However it has great replayability, and if we weren't short of time we were all ready to play another game!</p>
<p>Probably because we evoked Dwayne Johnson and Jason Statham just before the beginning of the session,
and also due to our code names being both classy and ridiculously kitsch,
we made excessive use of war movie clichés.
And played that way, Dog Bear is a game that gives you a sore throat 😆
We overdid the voice of testosterone-laden veteran soldiers... and had a good laugh!</p>
<p>Despite this parody atmosphere, and that is an amazing feat of the game, the session story was worthy of a <a href="https://en.wikipedia.org/wiki/Metal_Gear">Metal Gear Solid</a> scenario !
Clearly the main inspiration of Dog Bear, this tabletop RPG embodies very well the spirit of tactical espionage,
tense action and improbable plot twists of the video games series.
Incidentally the GM role is to personify the PCs commanding officer, aptly named... The Boss.</p>
<figure role="group">
<img alt="Punished 'Venom' Snake - Big Boss" src="images/2018/11/punished__venom__snake_big_boss_by_mrsteph06220_d98wl8t-fullview.jpg">
<figcaption><a href="https://www.deviantart.com/mrsteph06220/art/Punished-Venom-Snake-Big-Boss-559153037">Punished 'Venom' Snake - Big Boss</a> de MrSteph06220 - <a href="https://creativecommons.org/licenses/by-nd/3.0/">CC BY-ND 3.0</a></figcaption>
</figure>
<p>Personally, this is the one thing I loved most about this game:
the GM <strong>plays</strong> really a central character over the whole session;
chatting with the PCs as if sitting round a table in a military tent in the jungle where they are debriefing.
And the GM's position is totally different from their usual role in a tabletop RPG:
<strong>instead of describing</strong> to the PCs what they were seeing, I was <strong>asking</strong> what they saw!</p>
<p>These are some of the questions you might typically find yourself asking them as The Boss:</p>
<ul>
<li>so, tell me soldiers, how did your arrival in the area of operations go?</li>
<li>remind me, what was the precise goal of the mission? Why was it of critical importance?</li>
<li>very good. And so <em>Chamelon Cat</em>, what was the first major stage of your plan ? (pointing to the first card of the River)</li>
<li>did the defensive emplacements around the base match our Intel? Were there any surprises?</li>
<li>ok, I see, and that's when the avalanche we noticed from our spy satellite started?</li>
<li>well done, marines. And then <em>Threefold Devil</em>, how did you cross through the genetic scanners and armed drones?</li>
<li>and is that how, <em>Steel Raptor</em>, you lost a finger on your right hand?</li>
</ul>
<p>To summarize, the non-GM players describe most of the places and events,
while the GM gives hint to introduce unexpected developments.
If ever he wants to throw a wrench in the gears more explicitly,
they have to be cunning and explain how he got some partial information on how the mission went...</p>
<p>This is a very refreshing style of play!
And the other players were very inspired, giving rise to a splendid scene where the main enemy field operative was revealed to be...
the brother of one of the PCs! This one is then tempted to swap sides, but another PC decides to make him listen to reason
and remind him of his duty... through a CQC fight bare-chested in the snow!</p>
<p>Typical <em>Metal Gear Solid</em> I told you 😊</p>
<p>By the way, the game has an incentive system to give "fresh cuts" to players for producing "a particularly good or funny idea",
I recommend also to <strong>offer them to players voluntarily introducing complications in their depictions</strong>,
in order to encourage them to add twists to the story.</p>
<p>Overall, the game rule is quite wordy. The 11 pages of explanation could easily be summarized on only 2 by adopting a more dense style.</p>
<p>The River system is well thought out, symbolizing milestones of the mission and their difficulty,
with linked tables to define the location, goal and enemy,
and the ingenious rule with face-down cards allowing for unexpected plot twists.
It's worth noting we were lucky to have two of them during our session,
and with hindsight I would recommend to "rig" your card distribution to make sure you have some, otherwise the game will lack some flavour.</p>
<p>Here is how I plan to proceed for our next play: the plot twists will be triggered by face cards (Jack, Queen, King),
I'm going to take 3 of them plus 3 other cards, then shuffle them and finally draw 3 face down to form the bottom of the River.
This way there will always be a possibility to get zero plot twist (but very thin, 1/20), and a high probability to get 1 or 2 of them.</p>
<p><img alt="The table during our game session" src="images/2018/11/IMG_20181115_212032.jpg"></p>
<p>Finally, the game system does not give much space for failure, and can be perceived as "too easy" for the PCs:
during our test session, <strong>not one</strong> action of the PCs failed.</p>
<p>To me, this is not really an issue, the most important thing being that their resolution allowed room for twists and surprises,
but here are some rule suggestions to spice things up :</p>
<ul>
<li>forbid card swap for the players carrying the action</li>
<li>forbid card swap from the point when a River card is flipped up</li>
<li>either one of these rules, to limit the player possibilities at the end of the game:<ul>
<li>the 2 cards defining a character code name at the beginning of the game are not replaced upon use</li>
<li>the cards used to fight the River are not replaced</li>
</ul>
</li>
</ul>
<p><strong>EDIT[2018/11/22]:</strong> Many thanks to Danny O'Leary for the re-reading & style checking !</p>
<style>
article img { max-height: 25rem; }
</style>Quelques suggestions pour débuter en jeu de rôle, gratuites et en français2018-11-14T23:45:00+01:002018-11-14T23:45:00+01:00Lucas Cimontag:chezsoi.org,2018-11-14:/lucas/blog/quelques-suggestions-pour-debuter-en-jdr-gratuites-et-en-francais.html<p>Le week-end dernier, un de mes cousins m'a demandé si je pouvais le conseiller pour faire découvrir le jeu de rôle à des enfants / ados dans le cadre de son boulot.</p>
<p>Après lui avoir conseillé quelques noms, je me fais la réflexion que ça pourra peut-être servir à d'autres.
Voici …</p><p>Le week-end dernier, un de mes cousins m'a demandé si je pouvais le conseiller pour faire découvrir le jeu de rôle à des enfants / ados dans le cadre de son boulot.</p>
<p>Après lui avoir conseillé quelques noms, je me fais la réflexion que ça pourra peut-être servir à d'autres.
Voici donc une petite liste de recommandations de jeux de rôle, gratuits et en français,
qui me paraissent assez adaptés pour organiser une petite partie avec de jeunes joueurs débutants.</p>
<p>Et comme <a href="https://www.500nuancesdegeek.fr/initier-au-jdr/">le recommande MaitreSinh</a>,
pas la peine d'employer le terme "initier" lorsqu'il s'agit de faire découvrir ce loisir 😉</p>
<h2>P'tites Sorcières</h2>
<div class="imgs">
<img alt="Une petite sorcière s'envolant dans la nuit au milieu des feuilles mortes" src="images/2018/11/ptites_sorcieres_illus_charme.jpg">
<img alt="Une petite sorcière jouant aux dés avec deux chats" src="images/2018/11/ptites_sorcieres_illus_lesdes.jpg">
</div>
<p><a href="http://toinito.free.fr/fr/jdr/psorcieres.php">P'tites sorcières</a> est un jeu d'Antoine Bauza publié en 2005.
Depuis aussi longtemps que je me souvienne, je l'ai entendu recommandé comme un très bon JdR pour jouer avec des enfants.
Et le thème est idéal pour les fans d'Harry Potter !
Le manuel fait 84 pages et <a href="http://www.scenariotheque.org/Document/info_jeu.php?f_id_jeu=161">la Scénariothèque</a> regorge d'aides de jeu et de scénarios pour ce jeu.</p>
<blockquote>
<p>Ici, pas d'abominations terrifiantes, d'interminables complots interplanétaires ou de sanglantes batailles. P'tites sorcières est un jeu qui respire la bonne humeur et la joie de vivre.</p>
<p>Les règles sont simples pour être accessibles à tous. P'tites sorcières a pour but de vous faire vivre des aventures insolites dans la peau d'une jeune pratiquante de cette art méconnu qu'est la sorcellerie.</p>
<p>L'objectif de votre personnage est de décrocher le diplôme de sorcellerie qui fera de lui une praticienne reconnue. N'attendez plus pour partir à la découverte du Pièce-monde, un univers insolite et merveilleux.</p>
</blockquote>
<p><strong>EDIT [2022/03/07]</strong> : j'ai dédié un article à ce jeu, après l'avoir testé : <a href="ptites-sorcieres.html">P'tites sorcières</a></p>
<h2>Les jeux du Grümph</h2>
<p>Ce célèbre auteur de JdRs a plusieurs jeux parfaits pour débuter :</p>
<div class="uk-grid">
<div class="uk-width-1-1 uk-width-small-1-2">
<img alt="Couverture du jeu représentant 5 combattants" src="images/2018/11/TranchonsEtTraquons.png">
<blockquote>
<a href="https://drive.google.com/file/d/0B4TBit4mrI1tVkNLMGJYVVRnYk0/view">Tranchons & Traquons</a> vous permet de revivre l’excitation des tout premiers jeux de rôle :
des règles bancales, des personnages stéréotypés, de l’or et du sang.
Pas de théories oiseuses ou d’expériences mystiques. Rien que vous, votre claymore et 2D6.
</blockquote>
Le jeu fait 64 pages et encore une fois <a href="http://www.scenariotheque.org/Document/info_jeu.php?f_id_jeu=422">la Scénariothèque</a> regorge de ressources.
</div>
<div class="uk-width-1-1 uk-width-small-1-2">
<img alt="Guerrière une lance à la main" src="images/2018/11/Sventovia.png">
<blockquote>
<a href="http://legrumph.org/Terrier/?Jeux-de-role/Sventovia">Sventovia</a> est un petit jeu de rôle conçu pour l’initiation du grand public au cours des conventions, salons, festivals de jeux et autres manifestations ludiques.
Le scénario proposé peut se jouer en 45 à 60 minutes maximum, présentation du loisir et création des personnages compris, et couvre une grande partie des situations que l’on peut croiser dans une partie habituelle d’un jeu d’aventure : un peu d’interaction sociale, un peu d’observation et de planification, un peu d’intrusion et, éventuellement, un peu de combat.
</blockquote>
Le jeu fait 25 pages et comprends des personnages et des armes à découper.
</div>
<div class="uk-width-1-1 uk-width-small-1-2">
<img alt="Couverture du jeu représentant un roi et ses chevaliers sous forme d'animaux anthropomorphes" src="images/2018/11/Athalame-CourRoyale.jpg">
<a href="http://legrumph.org/Terrier/?Jeux-de-role/Athalame&search=athalame">Athalame</a> est un jeu de 32 pages pour jouer des animaux anthropomorphes dans un monde médiéval-fantastique.
<blockquote>
Ton personnage est un aventurier engagé par le roi Aquila, de la Cité d’Athalame.
Tu appartiens désormais aux gardes du roi. Ta mission consiste à patrouiller dans les environs de la cité,
à mener des enquêtes et à aider ceux qui demandent l’assistance de ton seigneur.
</blockquote>
</div>
</div>
<p>Je dois également mentionner un dernier jeu du Grümph, non gratuit mais au prix très modeste : <a href="http://legrumph.org/Terrier/?Chibi/Donjon-sans-facon">Donjon Sans Façon</a></p>
<h2>Les 13 Reliques</h2>
<p><img alt="Couverture du jeu représentant des gobelins aventureux et sympathiques" src="images/2018/11/Les13Reliques.png"></p>
<p><a href="http://lab00.free.fr/sommaire/lab01.htm">Les 13 Reliques</a> a une place à part dans mon cœur.
Il fait partie d'un recueil de 7 JdRs semi-pros paru en 2003 en 2 volumes, le LAB 01/02.
C'est le premier JdR que j'ai acheté (par correspondance !) et il m'a beaucoup marqué.</p>
<blockquote>
<p>Les personnages incarnent des gobelins travaillant pour la Grande Bibliothèque, un immense bâtiment relié à des centaines de mondes,
parcourant les univers à la recherche de livres rares et combattant les Innommables, des créatures monstrueuses que personne n'est jamais parvenu à décrire.</p>
</blockquote>
<p>Ce jeu d'<a href="http://www.misterfrankenstein.com">Anthony "Yno" Combrexelle</a> de 47 pages, désormais disponible en PDF en ligne,
dégage une ambiance captivante de contes et mystères,
utilise un système de jeu simple et efficace à base de 2d6, et a un univers qui se prête à des aventures dans des mondes différents à chaque partie,
tout comme un autre excellent jeu du LAB 01, <a href="http://lab00.free.fr/dedale/home.htm">Dedale</a>.</p>
<p>Un scénario supplémentaire est disponible <a href="https://www.scenariotheque.org/Document/info_jeu.php?f_id_jeu=207">sur la Scénariothèque</a>.</p>
<h2>Monster Chef</h2>
<figure role="group">
<img alt="Poulpe chef cuistot japonais" src="images/2018/11/SushiChef.jpg">
<figcaption><a href="https://www.flickr.com/photos/bucky1105/9388132634/in/photolist-q6ABa4-6FpgZD-6rmf2d-5hjK1-GFBmhz-2aTCgSc-o4ofAe-9pFcd7-2aTBTyK-qZzRE-fiACKL-9Cse7C-9ZPPDg-9ZSEHN-jR4ZMy-5Smw2W-5NJeji-8vvjj1-5j2zDz-9oJY8J-4NpcP8-ayDXgR-25iXCNW-8aY4Ck-8b2niW-cKb6FJ-TVN1vr-9zvubL-6W7Az9-25pdNMa">Sushi Chef</a> de Kent Buckingham - <a href="https://creativecommons.org/licenses/by-nc/2.0/">CC BY-NC 2.0</a></figcaption>
</figure>
<p><a href="http://brunobord.github.io/monster-chef-rpg/">Monster Chef</a> est un petit jeu de <a href="http://www.legrog.org/biographies/bruno-bord">Bruno Bord</a>
de 11 pages que je compte tester bientôt et dont je trouve le concept diablement rigolo :</p>
<blockquote>
<p>Les personnages incarnent la brigade d'une cuisine "Monstresques",
c'est à dire servant uniquement des Monstres ou des Aliens ou des Créatures Étranges.
Saurez-vous satisfaire l'appétit d'un Ogre qui aurait commandé un tartare de Licorne sauce verte,
ou un Globurlien affamé qui attend ses larves de Ver Galactique au chocolat ?</p>
</blockquote>
<p><strong>EDIT [2018/12/01]</strong>: Après l'<a href="/lucas/blog/double-brigade.html">avoir testé</a>, le jeu est pour le moment à l'état de prototype et le système manque d'équilibrage,
aussi je vous recommande plutôt <a href="la-brigade-du-chaos.html">La Brigade du Chaos</a> de Grant Howitt.</p>
<p><strong>EDIT [2019/10/07]</strong>: Sur un thème assez similaire, <a href="https://supersepia.itch.io/menu-mystere">Menu mystère pour Madame la Duchesse</a>
est un jeu <a href="https://en.wikipedia.org/wiki/Powered_by_the_Apocalypse"><em>Powered by the Apocalypse</em></a> (<a href="/lucas/blog/tag/pbta.html">PbtA</a>) très mignon !</p>
<h2>Wushu</h2>
<p><img alt="Gunfighters formant le symbole Yin/yang" src="images/2018/11/wushu-logo.jpg"></p>
<p><a href="http://www.scriptorium.d100.fr/index.php/archives/jeux-heberges-2/wushu-open/">Wushu</a> est un jeu de Daniel Bayn de 5 pages,
pour imaginer collectivement une histoire centrée sur les combats stylisés, tel un film d'action ou de kung-fu.</p>
<blockquote>
<p>Oubliez les pages et pages de règles, oubliez les modificateurs, tout ce qu’il vous faut pour jouer à Wushu, ce sont des d6 de couleurs différentes et beaucoup d’imagination !
Idéal pour simuler toutes les grandes aventures d’action, comme au cinéma, Wushu repose sur des principes simples : les joueurs réussissent toujours ce qu’ils entreprennent ; et la qualité de leur réussite dépend de leur interprétation.</p>
</blockquote>
<p>Des personnages dont les caractéristiques tiennent sur un coin de feuille, un système ultra-simple et narrativiste :
idéal pour une partie improvisée sur le pouce, mais nécessite une bonne expérience du MJ pour improviser l'histoire
<a href="http://www.jdrp.fr/recherche/wushu.html">JdRP</a> recense pas mal de ressources pour le jeu.</p>
<hr>
<p><strong>EDIT [2019/04/28]</strong> : <a href="http://troplongpaslu.fr/jeux-de-role-court/donjons-et-chatons-version-mini/">Donjons & Châtons</a>
de Clément De Ruyter a l'air très prometeur également ! Et cette version Mini est très courte : 10 pages seulement.</p>
<blockquote>
<p>un jeu de rôle traditionnel mêlant fantaisie, conte et post-apo animalier.
Dans cette version mini vous trouverez un tout petit système, quelques conseils de jeu et des pistes d’aventures !</p>
</blockquote>
<p><img alt="Couverture de Donjons & Châtons" src="images/2018/11/donjons-et-chatons_mini-pdf.jpg"></p>
<p><strong>EDIT [2019/06/24]</strong> : je vous recommande cet article complémentaire, avec même des conseils pour les tous petits,
<a href="https://www.cestpasdujdr.fr/jouer-aux-jeux-de-role-avec-des-enfants/">sur le blog de Matthieu Bé</a></p>
<p><strong>EDIT [2019/08/15]</strong> : un court jeu en anglais, basé sur l'exploration et la création collective d'une carte,
qui pourrait également être amusant de jouer avec des enfants : <a href="https://gmurphy.itch.io/untold-horizons">Untold Horizons de Gordie Murphy</a>.</p>
<p><a href="https://gmurphy.itch.io/untold-horizons"><img alt="Couverture d'Untold Horizons" src="images/2018/11/untold-horizons.png"></a></p>
<p><strong>EDIT [2020/01/15]</strong> : on vient de me recommander <a href="https://www.black-book-editions.fr/catalogue.php?id=553">Tails of Equestria</a>
inspiré de la série animée <em>My Little Pony - Friendship is Magic</em>, pour jouer avec de jeunes enfants.</p>
<p><a href="https://www.black-book-editions.fr/catalogue.php?id=553"><img alt="Couverture de Tails of Equestria" src="images/2018/11/TailsOfEquestria.jpg"></a></p>
<p><strong>EDIT [2020/03/16]</strong> : vous trouverez d'autres suggestions sur la page de <a href="pages/jdr-favoris.html">mes jeux de rôle préférés</a>,
et je viens de rédiger un article sur <a href="faire-decouvrir-le-jdr-et-fete-du-jeu.html">comment faire découvrir le JdR</a>,
incluant <strong>Ori Mushi</strong>, un court jeu complet (système, scénario & illustrations) pour faire découvrir ce loisir :</p>
<p><a href="faire-decouvrir-le-jdr-et-fete-du-jeu.html"><img alt="Couverture d'Ori Mushi" src="https://lucas-c.github.io/jdr/OriMushi/Journey-to-the-West-TysonTan.png"></a></p>
<p><strong>EDIT [2020/03/20]</strong> : <a href="https://lefix.di6dent.fr/archives/10704">une spéciale redif’ de la plupart du matériel gratuit à destination des enfants</a>, Le Fix - Di6dent</p>
<p><strong>EDIT [2020/06/24]</strong> : je viens de découvrir <a href="https://gaet-hoth.itch.io/smallest-adventures-pirates-fr">Smallest adventures… Pirates!</a>,
qui me semble malin, minimaliste et très accessible pour des enfants (de 8 ans et plus).</p>
<p><a href="https://gaet-hoth.itch.io/smallest-adventures-pirates-fr"><img alt="Couverture de Smallest Adventures" src="images/2018/11/SmallestAdventures.png"></a></p>
<p><strong>EDIT [2020/07/05]</strong> : un ami m'a parlé de <a href="https://gusandco.net/2020/03/18/donjons-chenapans-jeu-enfants/">Donjons & Chenapans</a>,
un jeu de rôle pour les enfants dès 4 ans, avec une très jolie feuille de personnage.
Je viens également d'écrire un court scénario pour <em>Run Die Repeat</em> pour jouer avec des enfants :
<a href="un-scenario-pour-run-die-repeat-pour-les-enfants.html"><em>le camembert de la sorcière</em></a>.</p>
<p><a href="https://gusandco.net/2020/03/18/donjons-chenapans-jeu-enfants/"><img alt="Couverture de Donjons & Chenapans" src="images/2018/11/DonjonsEtChenapans.webp"></a></p>
<p><strong>EDIT [2022/08/02]</strong>: un intéressant article de blog de Cyol sur le sujet : <a href="http://cyol.fr/blog/post/faire-du-jeu-de-role-avec-les-enfants-sur-proxi-jeux/">« Faire du Jeu de rôle avec les enfants »</a></p>
<p><strong>Retrouvez tous les articles de ce blog mentionnant des JdR adaptés avec des enfants avec le tag</strong> <a href="/lucas/blog/tag/jdr4kids.html">jdr4kids</a>.</p>
<style>
article img { max-height: 20rem; }
.imgs {
display: flex;
justify-content: center;
}
</style>The red panda intelligence division's revenge2018-11-10T23:55:00+01:002018-11-10T23:55:00+01:00Lucas Cimontag:chezsoi.org,2018-11-10:/lucas/blog/the-red-panda-intelligence-divisions-revenge.html<p>Cette après-midi j'ai eu l'occasion de tester avec trois amis ce micro-JdR d'une page de Manuel Bedouet.</p>
<p>Publié il y a un peu plus d'un an sur <a href="http://www.shamzam.net/blog/2017/09/format-court-des-jeux-pour-jouer-tout-de-suite/">le site du studio GOBZ'INK</a>,
ce jeu vous met dans la peau de... <strong>Pandas Roux Ninjas de l'Espace</strong>.
Capturés sur Terre, vous vous …</p><p>Cette après-midi j'ai eu l'occasion de tester avec trois amis ce micro-JdR d'une page de Manuel Bedouet.</p>
<p>Publié il y a un peu plus d'un an sur <a href="http://www.shamzam.net/blog/2017/09/format-court-des-jeux-pour-jouer-tout-de-suite/">le site du studio GOBZ'INK</a>,
ce jeu vous met dans la peau de... <strong>Pandas Roux Ninjas de l'Espace</strong>.
Capturés sur Terre, vous vous réveillez dans un sous-sol à Las Vegas,
avec l'intention de retrouver et faire payer les responsables.</p>
<p><img alt="Illustration du jeu: estampe représentant 2 pandas ninja" src="images/2018/11/rpid-estampe-768x768.png"></p>
<p>Oui, c'est un jeu absurde et humoristique.
Mais le décalage entre le côté mignon et "badass" de ces créatures est une sacrée bonne idée, et vraiment drôle à jouer !</p>
<p>Petit conseil au passage, car je crois que ça a bien aidé à établir l'ambiance : montrez à vos joueurs une vidéo de pandas roux en début de partie,
pour avoir en tête à quel point cet animal est "kawaï".
On s'est tous beaucoup amusé à jouer de cette dimension "on est des créatures super mignonnes" pendant la partie,
jusqu'à aboutir à une scène où un PJ décide de faire un câlin à un ennemi plutôt que l'attaquer,
et tandis que ce soldat succombe à cet élan de tendresse, un autre PJ en profite pour le transpercer de sang froid de son katana !</p>
<p>Le système est simple et efficace, et j'ai apprécié que les objectifs des PJs soient énoncés clairement dès le début.</p>
<p>L'idée du "choose one skill you are not as good as you think you are at" est également excellente :
les joueurs ont joué à fond le "je me crois invisible / super létal alors qu'en fait non",
ce qui a créé des scènes très rigolotes.</p>
<p>Il y a tout de même quelques points qui ne m'ont pas entièrement convaincu dans le jeu,
à commencer par la règle suivante :</p>
<blockquote>
<p>GM can use what people like about you to get you into trouble.</p>
</blockquote>
<p>Avec des PJs appréciés car ils sont "chanceux", "conciliant" ou "qui font de très belle galipettes",
dur d'utiliser ces critères pour leur causer des ennuis ! :)</p>
<p>L'autre petit point d'insatisfaction : les tables de génération du scénario.
Bien qu'assez délirantes et drôles, je n'ai pas été inspiré par certaines options
(comment introduire des "mamans football" comme grands méchants de l'histoire ???)
et surtout la plupart n'ouvrent pas de portes narratives.
Concrètement, là où je trouve bien trouvés "a machine that need repairing" ou "they have an hostage"
car ces propositions donnent des pistes implicites pour faire rebondir l'histoire,
de manière générale ces tables ne donnent pas beaucoup de matière pour élaborer une intrigue ou des péripéties.</p>
<p>Enfin bien sûr le jeu manque de rejouabilité, mais c'est inhérent au format mono-page.
D'ailleurs, pour plus d'informations sur le concept des JdR en une page, je vous recommande vivement
<a href="https://www.youtube.com/watch?v=tMWsh8ZIA-s">la passionnante conférence de Manuel Bedouet</a> issue du festival Orc'idée 2018.</p>
<p>Néanmoins je recommande vivement le jeu pour une petite partie de JdR décontractée à l'improviste,
à faire jouer à toute allure et sans aucune censure des idées les plus loufoques de tout le monde autour de la table !</p>
<p>Pour notre partie en particulier, nos pandas-ninjas ont retourné un casino sans dessus-dessous,
du sous-sol jusqu'au dernier étage secret du "Big Boss" des cyber-lézards qui les avait capturé,
et finir en apothéose dans un combat avec <strong>deux</strong> séries de méchants clones à leur image,
à grand coups de grappins, de kunaïs et de lance-patates.</p>
<p>Au passage, cette partie a été l'occasion de découvrir <a href="https://www.youtube.com/channel/UCvVWCrxq_aZr7fN_KpaGGTA">The Guild of Ambience</a>,
un "sound designer" australien qui propose sur Youtube de super ambiances musicales pour parties de JdR.
Merci Elliot pour la découverte ;)</p>
<p><strong>EDIT[2018/11/15]:</strong> une alternative (merci Matthieu !) : https://rpg.ambient-mixer.com</p>
<p>Création de pandas comprise, la partie de RPIDR a duré un peu moins de 2 heures.
Nous avons fini la journée à playtester un prototype de JdR de ma conception,
<em>Amères Victoires & Glorieuses Défaites</em>, dont je reparlerai très bientôt...</p>Listing all GitHub security alerts of a user's projects using GraphQL and Python2018-10-19T19:00:00+02:002018-10-19T19:00:00+02:00Lucas Cimontag:chezsoi.org,2018-10-19:/lucas/blog/listing-all-github-security-alerts-of-a-user-s-projects-using-graphql-and-python.html<p><a href="https://blog.github.com/2017-11-16-introducing-security-alerts-on-github/">Almost a year ago</a>, GitHub introduced security alerts. They are an awesome feature.</p>
<p><img alt="Animation displaying the GitHub security alerts web interface" src="images/2018/10/github-security-alerts.gif"></p>
<p>They function as notifications you receive whenever a vulnerability affecting one of your project dependencies.</p>
<p>But long after receiving a notification, <strong>how to list all security alerts affecting your repositories ?</strong></p>
<p>I didn't found an out-of-the box solution …</p><p><a href="https://blog.github.com/2017-11-16-introducing-security-alerts-on-github/">Almost a year ago</a>, GitHub introduced security alerts. They are an awesome feature.</p>
<p><img alt="Animation displaying the GitHub security alerts web interface" src="images/2018/10/github-security-alerts.gif"></p>
<p>They function as notifications you receive whenever a vulnerability affecting one of your project dependencies.</p>
<p>But long after receiving a notification, <strong>how to list all security alerts affecting your repositories ?</strong></p>
<p>I didn't found an out-of-the box solution, so I wrote <a href="https://github.com/Lucas-C/dotfiles_and_notes/blob/master/languages/python/github_graphql_get_reposecurityvulnerabilities.py">a small Python script</a> to perform this.</p>
<p>I wrote it to search for security alerts for all <strong>source repositories</strong> of a <strong>single user</strong>,
but it should be very easy to adapt it for a GitHub org or to also include forks.</p>
<p>Here is an example of output:</p>
<div class="highlight"><pre><span></span><code>$ python3 github_graphql_get_reposecurityvulnerabilities.py
https://github.com/Lucas-C/tablut/network/alerts
- <span class="o">{</span><span class="s2">"packageName"</span>: <span class="s2">"randomatic"</span>, <span class="s2">"affectedRange"</span>: <span class="s2">"< 3.0.0"</span>, <span class="s2">"externalReference"</span>: <span class="s2">"https://nvd.nist.gov/vuln/detail/CVE-2017-16028"</span>, <span class="s2">"fixedIn"</span>: <span class="s2">"3.0.0"</span><span class="o">}</span>
https://github.com/Lucas-C/OuiJam2018/network/alerts
- <span class="o">{</span><span class="s2">"packageName"</span>: <span class="s2">"ssri"</span>, <span class="s2">"affectedRange"</span>: <span class="s2">"< 5.2.2"</span>, <span class="s2">"externalReference"</span>: <span class="s2">"https://nvd.nist.gov/vuln/detail/CVE-2018-7651"</span>, <span class="s2">"fixedIn"</span>: <span class="s2">"5.2.2"</span><span class="o">}</span>
- <span class="o">{</span><span class="s2">"packageName"</span>: <span class="s2">"hoek"</span>, <span class="s2">"affectedRange"</span>: <span class="s2">">= 5.0.0,< 5.0.3"</span>, <span class="s2">"externalReference"</span>: <span class="s2">"https://nvd.nist.gov/vuln/detail/CVE-2018-3728"</span>, <span class="s2">"fixedIn"</span>: <span class="s2">"5.0.3"</span><span class="o">}</span>
- <span class="o">{</span><span class="s2">"packageName"</span>: <span class="s2">"mime"</span>, <span class="s2">"affectedRange"</span>: <span class="s2">"< 1.4.1"</span>, <span class="s2">"externalReference"</span>: <span class="s2">"https://nvd.nist.gov/vuln/detail/CVE-2017-16138"</span>, <span class="s2">"fixedIn"</span>: <span class="s2">"1.4.1"</span><span class="o">}</span>
- <span class="o">{</span><span class="s2">"packageName"</span>: <span class="s2">"parsejson"</span>, <span class="s2">"affectedRange"</span>: <span class="s2">"<=0.0.3"</span>, <span class="s2">"externalReference"</span>: <span class="s2">"https://nvd.nist.gov/vuln/detail/CVE-2017-16113"</span>, <span class="s2">"fixedIn"</span>: <span class="s2">""</span><span class="o">}</span>
- <span class="o">{</span><span class="s2">"packageName"</span>: <span class="s2">"deep-extend"</span>, <span class="s2">"affectedRange"</span>: <span class="s2">"< 0.5.1"</span>, <span class="s2">"externalReference"</span>: <span class="s2">"https://nvd.nist.gov/vuln/detail/CVE-2018-3750"</span>, <span class="s2">"fixedIn"</span>: <span class="s2">"0.5.1"</span><span class="o">}</span>
- <span class="o">{</span><span class="s2">"packageName"</span>: <span class="s2">"randomatic"</span>, <span class="s2">"affectedRange"</span>: <span class="s2">"< 3.0.0"</span>, <span class="s2">"externalReference"</span>: <span class="s2">"https://nvd.nist.gov/vuln/detail/CVE-2017-16028"</span>, <span class="s2">"fixedIn"</span>: <span class="s2">"3.0.0"</span><span class="o">}</span>
</code></pre></div>
<p>The script contacts <a href="https://developer.github.com/v4/">GitHub GraphQL API v4</a>,
and hence you'll need to <a href="https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/">create a <code>GITHUB_OAUTH_TOKEN</code></a>
in order to authenticated yourself with it. The only scope required is <code>repo > public_repo</code>.</p>
<p>The script also enable the <a href="https://developer.github.com/v4/previews/#repository-vulnerability-alerts">Repository Vulnerability Alerts Schema Preview</a>,
through an <code>Accept</code> HTTP header, as this feature is still in beta.</p>
<p>It was the first time I used the GraphQL query language.
It is very simple to grasp, but I have a complaint :
despite a <a href="https://github.com/facebook/graphql/issues/271">proposal</a> discarded recently,
the language currently offers no way to filter the result with basic conditionals.
In my case I needed to process repositories that have vulnerability alerts,
but couldn't do so in GraphQL and had to filter them out in Python.</p>
<p>For a language which aims to <a href="https://graphql.org">gives clients the power to ask for exactly what they need and nothing more</a>,
this is a bit sad...</p>From overblog to a pelican static website2018-10-11T18:00:00+02:002018-10-11T18:00:00+02:00Lucas Cimontag:chezsoi.org,2018-10-11:/lucas/blog/overblog2pelican.html<p>Some time ago, I used the <a href="https://over-blog.com">overblog</a> platform in order to create a blog for a long trip in Ireland.</p>
<p>Despite being sometimes very slow, it was overall a good platform, very easy to grasp for beginners.
The blog is now old and unused, but before destroying it I wanted …</p><p>Some time ago, I used the <a href="https://over-blog.com">overblog</a> platform in order to create a blog for a long trip in Ireland.</p>
<p>Despite being sometimes very slow, it was overall a good platform, very easy to grasp for beginners.
The blog is now old and unused, but before destroying it I wanted to export a copy as a memory.
I also wanted to get my data back, from overblog servers onto my computer.</p>
<p>Because I love Python, I made a script <a href="https://github.com/Lucas-C/dotfiles_and_notes/blob/master/languages/python/overblog2pelican.py">overblog2pelican.py</a>
that performs an export of all texts and images to flat files,
so that it can be powered by <a href="https://blog.getpelican.com/">Pelican</a>.</p>
<p><img alt="Old Pelican logo" src="images/2018/10/pelican-old.png"></p>
<p>I'm just sharing this code in case it can help someone else.
The process to run this script is the following:</p>
<ol>
<li>Ensure you have Python 3 and the libs listed in the script installed</li>
<li>Create a new project using <a href="http://docs.getpelican.com/en/stable/quickstart.html#create-a-project"><code>pelican-quickstart</code></a></li>
<li>Generate an <code>overblog-posts-with-dates.yaml</code> file from the Overblog admin page,
following the instructions at the top of my script</li>
<li>Run <code>python overblog2pelican.py</code> in the directory of your new pelican project</li>
</ol>
<p>Feel free to leave a comment if you need any support.</p>Hearts and clubs.com2018-09-09T18:00:00+02:002018-09-09T18:00:00+02:00Lucas Cimontag:chezsoi.org,2018-09-09:/lucas/blog/hearts-and-clubs.html<p>Being currently on a bike tour in Europe for several week, without computer nor smartphone, I don't have the opportunity to post much on this blog.</p>
<p>Still, I'd like to mention a discovery I just made from an Internet café here in Budapest :
the <a href="http://heartsandclubs.com">Hearts and clubs</a> website.</p>
<p><img alt="Hearts and clubs logo" src="images/2018/09/HeartsandClubs-2.png"></p>
<p>Curated by …</p><p>Being currently on a bike tour in Europe for several week, without computer nor smartphone, I don't have the opportunity to post much on this blog.</p>
<p>Still, I'd like to mention a discovery I just made from an Internet café here in Budapest :
the <a href="http://heartsandclubs.com">Hearts and clubs</a> website.</p>
<p><img alt="Hearts and clubs logo" src="images/2018/09/HeartsandClubs-2.png"></p>
<p>Curated by By Pierre Corbinais, <a href="http://oujevipo.fr">OuJeViPo</a> author's, this site collects board games which attempt to address socio-political questions.</p>__del__ magic method dangers in Python 2 and infinite loops2018-07-04T09:00:00+02:002018-07-04T09:00:00+02:00Lucas Cimontag:chezsoi.org,2018-07-04:/lucas/blog/del-magic-method-dangers-in-python-2-and-infinite-loops.html<p>Yesterday I've stumbled upon a very surprising bug in some Python 2 code,
related to the use of the <code>__del__</code> method in a vendor library we employ at work.</p>
<p>Here is some minimal code that reproduces the issue I met:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">MyClass</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s1">'Woops'</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__del__ …</span></code></pre></div><p>Yesterday I've stumbled upon a very surprising bug in some Python 2 code,
related to the use of the <code>__del__</code> method in a vendor library we employ at work.</p>
<p>Here is some minimal code that reproduces the issue I met:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">MyClass</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s1">'Woops'</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__del__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">raise_recursion_error</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">RuntimeError</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'Recursion error caught'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">raise_recursion_error</span><span class="p">():</span>
<span class="n">raise_recursion_error</span><span class="p">()</span>
<span class="n">MyClass</span><span class="p">()</span>
</code></pre></div>
<p>What do you think will happen ? Take a moment to make a guess.</p>
<hr>
<p>If you execute this code, you will witness a never-ending repetition of the <code>Recursion error caught</code> message in your terminal.</p>
<p>Interestingly, if we replace the recursion error by a simple <code>RuntimeError</code>, the problem vanishes !</p>
<p>I don't quite understand why this recursion error, despite being caught, causes this strange behaviour.
Somehow I think the <code>try</code> / <code>catch</code> does not "entirely" suppress the exception,
and that it still bubbles up "enough" to cause the Python interpreter to call <code>__del__</code> again.</p>
<p><img alt="A guy keeps opening a Matryoshka that seems to never end" src="/lucas/wwcb/photos/Infinite_loop_Matryoshka.gif"></p>
<p>If you remove the <code>try</code> / <code>catch</code> around <code>raise_recursion_error()</code>, you'll get the same behaviour, with a different message:</p>
<div class="highlight"><pre><span></span><code>Exception RuntimeError: 'maximum recursion depth exceeded' in <bound method MyClass.__del__ of <__main__.MyClass instance at 0x6ffffebd758>> ignored
</code></pre></div>
<p>This kind of "exception ignored" messages disappeared in Python 3, which handles this case very smoothly, by halting immediately and displaying this:</p>
<div class="highlight"><pre><span></span><code>Traceback (most recent call last):
File "del_recurse_infinite_loop.py", line 36, in <module>
MyClass()
File "del_recurse_infinite_loop.py", line 24, in __init__
raise RuntimeError('Woops')
RuntimeError: Woops
Recursion error caught
</code></pre></div>
<p>In the real world code where I saw this behaviour, the recursion error was due to a <code>__getattr__</code> method calling itself.
The following class presents the same behaviour:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">MyClass</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s1">'Woops'</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__del__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">foo</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">RuntimeError</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'Recursion error caught'</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__getattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">uninitialized_attribute</span>
</code></pre></div>
<p>Not also that in its original form, I wasn't even able to stop the process with <strong>CTRL+C</strong> !
The <code>KeyboardInterrupt</code> exceptions were ignored.</p>
<p>My key takeaways from this painful deep dive:</p>
<ul>
<li>Python 3 is safer than Python 2</li>
<li>recursion error are a special breed of <code>RuntimeError</code>, and are sometimes handled differently in Python 2
(even if they are not identified by a named subclass)</li>
<li>a typo in the code of a <code>__getattr__</code> method can lead to infinite recursion</li>
<li>stay away from <code>__del__</code></li>
</ul>
<p>I'm not the first to warn about the <code>__del__</code> method by the way:</p>
<ul>
<li><a href="http://www.algorithm.co.il/blogs/programming/python/python-gotchas-1-__del__-is-not-the-opposite-of-__init__/">Python Gotchas 1: <strong>del</strong> is not the opposite of <strong>init</strong></a></li>
<li><a href="http://www.andy-pearce.com/blog/posts/2013/Apr/python-destructor-drawbacks/">Python destructor drawbacks</a></li>
<li><a href="https://www.holger-peters.de/an-interesting-fact-about-the-python-garbage-collector.html">An Interesting Fact About The Python Garbage Collector</a></li>
</ul>
<p>This last one even mentions that it can generate memory leaks.</p>
<p>Let me know in the comments section if ever you have more information to explain this strange quirk !</p>Traduction du Temple du Non, une histoire interactive Twine du studio Crows Crows Crows2018-06-18T09:00:00+02:002018-06-18T09:00:00+02:00Lucas Cimontag:chezsoi.org,2018-06-18:/lucas/blog/traduction-du-temple-du-non-une-histoire-interactive-twine-du-studio-crows-crows-crows.html<p><a href="https://crowscrowscrows.com">Crows Crows Crows</a> est un studio de jeu vidéo créé en 2015,
à l'origine entre autres du jeu complètement déjanté <a href="https://crowscrowscrows.itch.io/dr-langeskov-the-tiger-and-the-terribly-cursed-emerald-a-whirlwind-heist">Dr. Langeskov, The Tiger, and The Terribly Cursed Emerald: A Whirlwind Heist</a> et le créateur du studio, William Pugh, est également un des auteurs de <em>The Stanley Parable</em>.</p>
<p>En 2016 …</p><p><a href="https://crowscrowscrows.com">Crows Crows Crows</a> est un studio de jeu vidéo créé en 2015,
à l'origine entre autres du jeu complètement déjanté <a href="https://crowscrowscrows.itch.io/dr-langeskov-the-tiger-and-the-terribly-cursed-emerald-a-whirlwind-heist">Dr. Langeskov, The Tiger, and The Terribly Cursed Emerald: A Whirlwind Heist</a> et le créateur du studio, William Pugh, est également un des auteurs de <em>The Stanley Parable</em>.</p>
<p>En 2016 ils ont sorti une courte "histoire dont vous êtes le héro", <a href="https://en.wikipedia.org/wiki/The_Temple_of_No">Le Temple du Non</a>.</p>
<figure role="group">
<img alt="Le Temple Du Non" src="images/2018/06/the-temple-of-no.png">
<figcaption>Illustration de <a href="http://www.dominikjohann.de">Dominik Johann</a></figcaption>
</figure>
<p>Ce court jeu - une dizaine de minute suffit pour le finir - est gratuit, jouable en ligne et a été réalisé avec <a href="https://twinery.org">Twine</a>.
Détail important également : l'intégralité des sources du jeu sont <em>open-source</em> et sont <a href="https://github.com/CrowsCrowsCrows/the-temple-of-no/pulls">sur GitHub</a>.</p>
<p>J'ai <strong>adoré</strong> cette histoire loufoque et drôle, dans les lignées des autres jeux du studio.
L'écriture, tant des dialogues que de l'histoire générale, est absolument géniale.</p>
<p>Le 8 mai dernier, j'ai appris par la <em>newsletter</em> de Crows Crows Crows qu'ils avaient fait une petite mise à jour du jeu,
pour introduire une fin cachée, avec même <a href="https://www.youtube.com/watch?v=PYTyGJ2Xk5U">une petite vidéo bande-annonce</a> pour l'occasion.
Ça a été l'occasion de remettre le nez dedans, et je me suis alors mis en tête de traduire ce jeu en français.</p>
<p>Après plusieurs jours à m'arracher les cheveux pour traduire des références intraduisibles (<a href="https://www.youtube.com/watch?v=xZ5cH1Dh2G0"><em>Where's the trigger ?</em></a>),
et après plusieurs relectures de valeureux testeurs (merci Laëtitia, Alexandre et ma maman 🙏 😉),
je suis fier de vous présenter : <a href="https://chezsoi.org/lucas/le-temple-du-non/">Le Temple du Non</a> ! <em>(cliquez sur le lien)</em></p>
<p>J'espère que le jeu vous plaira !
Tout le mérite revient à ses génialissimes créateurs : Dominik Johann, William Pugh, Joe Finegold & Tom Schley.
N'hésitez pas à laisser un commentaire si vous avez apprécié, ou si vous avez repéré une maladresse dans la traduction.</p>
<hr>
<p>Techniquement, comme le moteur des jeux Twine 1 est en Python,
et qu'il n'inclus pas de mécanisme de localisation,
j'ai écrit <a href="https://github.com/Lucas-C/the-temple-of-no/tree/master/l10n">quelques scripts Python</a> pour y ajouter le support de <a href="https://www.gnu.org/software/gettext/manual/gettext.html#PO-Files">fichiers de traduction au format standard "gettext" <code>.po</code></a>. Voici un example d'utilisation :</p>
<div class="highlight"><pre><span></span><code>twine1_localizer.py translate the-temple-of-no.tws l10n/fr-FR.po the-temple-of-no_fr-FR.tws
</code></pre></div>
<p>Le fichier <code>l10n/fr-FR.po</code> contient toutes les traductions en français des phrases utilisées dans le jeu,
en utilisant comme clef l'identifiant de passage Twine. Le fichier <code>.tws</code> en sortie est ainsi localisé, et peu être exporté en HTML pour le rendu final.</p>
<p>Enfin, je souhaite encore faire quelques améliorations pour mettre le jeu aux normes d'accessibilité du web,
afin qu'il puisse être jouable uniquement à l'oreille par exemple. Je vous en reparlerai bientôt :)</p>
<hr>
<p>PS: J'ai découvert qu'il y avait <a href="https://www.reddit.com/r/crowscrowscrows/comments/8kcpbu/easteregg_in_the_temple_of_no/">un secret dans le jeu</a>,
mais je ne l'ai pas encore déchiffré. Des idées ?? :)</p>
<style>
article img { max-height: 20rem; }
</style>Customizing Notepad++ autocompletion with Python2018-06-15T14:00:00+02:002018-06-15T14:00:00+02:00Lucas Cimontag:chezsoi.org,2018-06-15:/lucas/blog/customizing-notepad++-autocompletion-with-python.html<p>When <a href="https://chezsoi.org/lucas/blog/migration-du-blog-de-ghost-a-pelican.html">I migrated this blog to Pelican</a>,
I noted one thing that I missed from Ghost: tags autocompletion, to help reusing tags I already defined in other articles.</p>
<p>Because nowadays I mostly use Notepad++ or <code>vim</code> to write my blog posts,
I found out an easy solution that works for …</p><p>When <a href="https://chezsoi.org/lucas/blog/migration-du-blog-de-ghost-a-pelican.html">I migrated this blog to Pelican</a>,
I noted one thing that I missed from Ghost: tags autocompletion, to help reusing tags I already defined in other articles.</p>
<p>Because nowadays I mostly use Notepad++ or <code>vim</code> to write my blog posts,
I found out an easy solution that works for both: using a <a href="https://en.wikipedia.org/wiki/Ctags">Ctags</a> file.</p>
<p><img alt="Notepad++ logo" src="images/2018/06/notepad++_logo.jpg"></p>
<p>Quoting Wikipedia:</p>
<blockquote>
<p>Ctags is a programming tool that generates an index (or tag) file of names found in source and header files of various programming languages.
These tags allow definitions to be quickly and easily located by a text editor or other utility.</p>
</blockquote>
<p>First off, I wrote <a href="https://github.com/getpelican/pelican-plugins/pull/1038">a simple Pelican plugin</a> to generate
a file containing all articles tags and following <a href="http://ctags.sourceforge.net/FORMAT">the Ctags format spec</a>.</p>
<p><code>vim</code> natively supports Ctags, however Notepad++ does not.
I tried all <a href="http://docs.notepad-plus-plus.org/index.php?title=Plugin_Central">Notepad++ plugins</a> that provide support for Ctags,
and while some work fine (<code>CCompletion</code> Ctags parser however is a bit picky / limited),
none really blended with Notepad++ native autocompletion as well as I wanted.</p>
<p>Then I discovered Dave Brotherstone awesome <a href="https://github.com/bruderstein/PythonScript">PythonScript</a> plugin for Notepad++.</p>
<p>Once installed, the following short code snippet is enough to parse a Ctags file and trigger the editor autocompletion:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">os</span>
<span class="n">ctags_filepath</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">notepad</span><span class="o">.</span><span class="n">getCurrentFilename</span><span class="p">()),</span> <span class="s1">'tags'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">ctags_filepath</span><span class="p">):</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">ctags_filepath</span><span class="p">)</span> <span class="k">as</span> <span class="n">ctags_file</span><span class="p">:</span>
<span class="n">ctags</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">line</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">'</span><span class="se">\t</span><span class="s1">'</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">ctags_file</span><span class="o">.</span><span class="n">readlines</span><span class="p">()</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">'!'</span><span class="p">))</span>
<span class="n">editor</span><span class="o">.</span><span class="n">autoCShow</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="s1">' '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">sorted</span><span class="p">(</span><span class="n">ctags</span><span class="p">)))</span>
</code></pre></div>
<p>You can for example bound this script to <code>CTRL+SHIFT+C</code> in <code>Settings > Shortcut Mapper > Plugins Commands > Run Previous Script</code>.
It will show a typical Notepad++/Scintilla autocompletion list, but based on the content of the <code>tags</code> file in the current file directory.</p>
<p>This script will also work if the <code>tags</code> file simply contains one word per line.</p>
<p>This PythonScript plugin can make Notepad++ really powerful,
able to perform IDE-style refactoring easily.</p>
<p>For example, one could combine it with code refactoring tools like <a href="https://github.com/python-rope/rope">python-rope/rope</a>, <a href="https://github.com/PyCQA/redbaron">PyCQA/redbaron</a>,
<a href="https://github.com/google/pasta">google/pasta</a> or even code style autofixers like <a href="https://github.com/ambv/black">ambv/black</a>,
to modify the code you just selected in Notepad++ on a simple keystroke.</p>
<style>
article img { max-height: 20rem; }
</style>Displaying chained exceptions stacktraces in Python 22018-06-12T14:00:00+02:002018-06-12T14:00:00+02:00Lucas Cimontag:chezsoi.org,2018-06-12:/lucas/blog/displaying-chained-exceptions-stacktraces-in-python-2.html<p>At work we have a component not yet migrated to Python 3,
and we recently had some difficulties diagnosing a problem with the MySQL connector.</p>
<p>Because we were catching the <code>mysql.connector.errors.Error</code> and raising a custom exception,
we were loosing the underlying stacktrace and hence couldn't troubleshoot the …</p><p>At work we have a component not yet migrated to Python 3,
and we recently had some difficulties diagnosing a problem with the MySQL connector.</p>
<p>Because we were catching the <code>mysql.connector.errors.Error</code> and raising a custom exception,
we were loosing the underlying stacktrace and hence couldn't troubleshoot the root cause of the issue.</p>
<p><img alt="Grandman says: Program exit with error -11! But where is the stacktrace ?" src="images/2018/06/program-exit-with-11-but-where-is-the-stacktrace.jpg"></p>
<p>Raising custom exceptions isn't the issue here :
this practice ensure you have a proper <a href="https://en.wikipedia.org/wiki/Separation_of_concerns">separation of concerns</a>,
meaning in practice your code do not raise exceptions coming from third-party libraries <sup><a id="ref1" href="javascript:;" onclick="document.location.hash='fn1';">[1]</a></sup>
and that you control what kind of exception your class or module can raise,
while adding useful contextual information in the new custom one raised.</p>
<p>No, the real solution here is to display the full stacktrace.</p>
<p><img alt="Scene from the film Inception : That's not enough, we have to go deeper" src="images/2018/06/inception-thats-not-enough-we-have-to-go-deeper.png"></p>
<p>In this article, I'll show how to handle such situation in Python 2.</p>
<hr>
<p>Let's take this piece of Python 3 code :</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="n">CustomException</span>(<span class="nb">Exception</span>):
<span class="nb">pass</span>
<span class="n">try:</span>
<span class="n">raise</span> <span class="n">ValueError</span>(<span class="s">'Wooops'</span>)
<span class="n">except</span> <span class="n">ValueError</span> <span class="n">as</span> <span class="n">err:</span>
<span class="n">raise</span> <span class="n">CustomException</span>(<span class="s">'Badaboum'</span>) <span class="nb">from</span> <span class="n">err</span>
</code></pre></div>
<p>If you execute it, you'll get this output :</p>
<div class="highlight"><pre><span></span><code>Traceback (most recent call last):
File "test_reraise_py3.py", line 5, in <module>
raise ValueError('Wooops')
ValueError: Wooops
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "test_reraise_py3.py", line 7, in <module>
raise CustomException('Badaboum') from err
__main__.CustomException: Badaboum
</code></pre></div>
<p>Very handy !</p>
<p>In fact, this behaviour comes from <a href="https://www.python.org/dev/peps/pep-3134/">PEP 3134</a>.</p>
<p>But in Python 2, there is no <code>raise ... from ...</code> construct.</p>
<p>Two packages (at least) provide a backward compatibile workaround :</p>
<ul>
<li><a href="https://pypi.org/project/six/">six</a>, but the implementation is <a href="https://github.com/benjaminp/six/blob/master/six.py#L736">empty</a></li>
<li><a href="https://pypi.org/project/future/">future</a>, bringing more interesting <a href="https://github.com/PythonCharmers/python-future/blob/master/src/future/utils/__init__.py#L422">code</a></li>
</ul>
<p>What happens if we use the function they provide ?</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">future.utils</span> <span class="kn">import</span> <span class="n">raise_from</span>
<span class="k">class</span> <span class="nc">CustomException</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
<span class="k">pass</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Wooops'</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">ValueError</span> <span class="k">as</span> <span class="n">err</span><span class="p">:</span>
<span class="n">raise_from</span><span class="p">(</span><span class="n">CustomException</span><span class="p">(</span><span class="s1">'Badaboum'</span><span class="p">),</span> <span class="n">err</span><span class="p">)</span>
</code></pre></div>
<p>We get this output:</p>
<div class="highlight"><pre><span></span><code>Traceback (most recent call last):
File "test_reraise_py2.py", line 10, in <module>
raise_from(CustomException('Badaboum'), err)
File "/home/lucas_cimon/.local/share/virtualenvs/infralib-py2/lib/python2.7/site-packages/future/utils/__init__.py", line 454, in raise_from
raise e
__main__.CustomException: Badaboum
</code></pre></div>
<p>Hmm... Quite frustrating !</p>
<p>Why that ?</p>
<p>The answer lies in the PEP mentioned above :</p>
<blockquote>
<p>In the traceback module, the <code>format_exception</code>, <code>print_exception</code>, <code>print_exc</code>, and <code>print_last</code> functions will be updated to accept an optional <code>chain</code> argument, <code>True</code> by default.
When this argument is <code>True</code>, these functions will format or display the entire chain of exceptions as just described.
When it is <code>False</code>, these functions will format or display only the outermost exception.</p>
</blockquote>
<p>What this means is that, with the <code>future.utils.raise_from</code> implementation, we miss 2 things :</p>
<ul>
<li>while <code>__cause__</code> & <code>__context__</code> attributes are already set, the <code>__traceback__</code> one isn't</li>
<li>the code that display the <em>tracebacks</em> should use those attributes</li>
</ul>
<p>For the first issue, you can use the patched version of <code>raise_from</code> in <a href="https://github.com/PythonCharmers/python-future/pull/341">this pull request</a>.</p>
<p>For the second one, we cannot safely modify the <em>builtin</em> standard <code>format_exception</code> / <code>print_exception</code> / <code>print_exc</code> / <code>print_last</code> functions.
A workaround is to define a <code>__str__</code> method on your exceptions, as follows:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="n">CustomException</span>(<span class="nb">Exception</span>):
<span class="n">def</span> <span class="n">__str__</span>(<span class="nb">self</span>):
<span class="n">out</span> = <span class="nb">Exception</span>.<span class="n">__str__</span>(<span class="nb">self</span>)
<span class="k">if</span> <span class="n">hasattr</span>(<span class="nb">self</span>, <span class="s">'__cause__'</span>) <span class="o">and</span> <span class="nb">self</span>.<span class="n">__cause__</span> <span class="o">and</span> <span class="n">hasattr</span>(<span class="nb">self</span>.<span class="n">__cause__</span>, <span class="s">'__traceback__'</span>) <span class="o">and</span> <span class="nb">self</span>.<span class="n">__cause__</span>.<span class="n">__traceback__:</span>
<span class="n">out</span> += <span class="s">'\n\nThe above exception was the direct cause of the following exception:\n\n'</span>
<span class="n">out</span> += <span class="s">''</span>.<span class="nb">join</span>(<span class="n">traceback</span>.<span class="n">format_tb</span>(<span class="nb">self</span>.<span class="n">__cause__</span>.<span class="n">__traceback__</span>) + [<span class="s">'{}: {}'</span>.<span class="nb">format</span>(<span class="nb">self</span>.<span class="n">__cause__</span>.<span class="n">__class__</span>.<span class="n">__name__</span>, <span class="nb">self</span>.<span class="n">__cause__</span>)])
<span class="k">return</span> <span class="n">out</span>
</code></pre></div>
<p>With those 2 fixes, there is the stacktrace we get when executing our original code using <code>raise_from</code> in Python 2 :</p>
<div class="highlight"><pre><span></span><code>Traceback (most recent call last):
File "test_reraise_py2.py", line 45, in <module>
raise_from(CustomException('Badaboum'), err)
File "test_reraise_py2.py", line 33, in raise_from
raise e
__main__.CustomException: Badaboum
The above exception was the direct cause of the following exception:
File "test_reraise_py2.py", line 43, in <module>
raise ValueError('Wooops')
ValueError: Wooops
</code></pre></div>
<hr>
<p><a id="fn1" href="javascript:;" onclick="document.location.hash='ref1';">1.</a> Bubbling up external libraries exceptions isn't always a bad practice,
especially for critical ones. But if your code uses various libs that all can raise very common and different exceptions,
this will force the users of your code to <code>import</code> all those exceptions systematically and is a clear violation of S.o.C.</p>
<style>
article img { max-height: 20rem; }
</style>Générateurs de puzzles open source2018-05-06T08:00:00+02:002018-05-06T08:00:00+02:00Lucas Cimontag:chezsoi.org,2018-05-06:/lucas/blog/generateurs-de-puzzles-open-source.html<p>Voici une petite dépêche que j'ai écrite sur le site LinuxFr :
<a href="https://linuxfr.org/news/generateurs-de-puzzles-libres">https://linuxfr.org/news/generateurs-de-puzzles-libres</a></p>
<!--
Il y a quelques temps, j'étais à la recherche de générateurs de puzzles personnalisables (dont la solution serait un petit mot doux romantique). Des puzzles qui ne soient pas uniquement jouables en ligne, mais imprimables, ne nécessitant qu'une feuille et un crayon.
Au final j'ai découvert de nombreux programmes open source permettant de générer des mots croisés, des grilles de mot mystère, des nonogrammes, etc.
Je vais donc dans cette dépêche vous présenter ces projets, en espérant qu'ils vous inspirent à concocter vos propres puzzles pour vos enfants / neveux, compagnon / compagne ou encore grand parents !
# Mots croisés #
Je n'ai testé qu'un seul programme, en Python, de David Whitlock (riverrun) : [genxword](https://github.com/riverrun/genxword)
Voici un exemple de grille qui peut être générées :
![Exemple de grille avec indices sur le thème des Monty Python](https://raw.githubusercontent.com/riverrun/genxword/master/examples/output/Pdf_grid_example.png)
Pour fonctionner le programme nécessite qu'on lui fournisse une liste de mots, qui sont donc entièrement personnalisables. Chaque mot de la liste peut être associé à une définition.
`genxword` peut générer des grilles sous forme de PDFs, de PNGs ou de SVGs.
Il est compatible Python 2 & 3, est basé sur GTK mais fonctionne sous Windows (j'ai testé) et est sous license GNU v3.
# Nonogrammes #
Également appelés ["picross", "griddlers" an anglais ou encore "hanjie" au Japon](https://fr.wikipedia.org/wiki/Picross), ces puzzles sont parmis mes préférés.
J'ai essayé plusieurs programmes pour générer ce type de puzzle, mais mon préféré de loin est celui de Zhou Qi (HandsomeOne) en Javascript : https://github.com/HandsomeOne/Nonogram
![Nonogram par Zhou Qi](http://i.imgur.com/XRs3jk7.gif)
Il inclut une grille cliquable pour y jouer, un éditeur interactif pour confectionner vos propres grilles et même un solveur avec rendu visuel des étapes pas à pas, permettant de valider que la grille a une solution !
Le code est structuré et lisibile facilement, sans dépendances et sous license MIT.
Comme au final un nonogramme n'est qu'une image pixelisée en noir & blanc, j'ai fait un [_fork_](https://github.com/Lucas-C/Nonogram/) du projet pour simplement rajouter 2 boutons permettant d'importer ou exporter [des grilles au format PNG](https://github.com/Lucas-C/Nonogram/tree/master/grids): https://lucas-c.github.io/Nonogram/ (dans la section "Create Your Own Nonogram")
# Mot mystère
Aussi appelé ["mots cachés"](https://fr.wikipedia.org/wiki/Mots_cach%C3%A9s),
ce puzzle est idéal pour camoufler un message secret dans une grille, afin qu'il soit reconstitué une fois résolu !
Bill Scheidel (bunkat) a créé en Javascript [une grille jouable en ligne, avec éditeur intégré](https://github.com/bunkat/wordfind) : https://lucas-c.github.io/wordfind/
![Capture d'écran de wordfind.js](https://chezsoi.org/lucas/wordfind.png)
Vous pouvez y lister les mots à cacher dans la gille, votre mot secret, l'extension maximum de la grille ou encore le nombre de mots qui peuvent être "ignorés" parmi ceux fournis afin que le générateur produise une grille compacte.
Bref, c'est un programme simple d'utilisation, sans dépendance et sous license MIT.
# Sudoku
Je ne l'ai que très peu testé, mais voici un générateur de Sudoku écrit par Rob McGuire (robatron) en Javascript : https://github.com/robatron/sudoku.js
![Capture d'écran de Sudoku.js](https://chezsoi.org/lucas/SudokuJS.png)
Utilisant jQuery et la bibliothèque Bootstrap, ce projet est sous License MIT et vous permettra de générer vos propres grilles.
Pour ceux qui préfèrent d'autres langage que le Javascript, sachez que comme le Sudoku est un puzzle très populaire, vous trouverez de nombreux générateurs et versions jouables sur Github en Python, Ruby, etc.
# Et plein d'autres puzzles originaux !
Pour conclure, je voudrais mentionner la collection de puzzles de Simon Tatham, sous license MIT et disponibles en Java ou Javascript, qui est à la fois immense et regorge de puzzles originaux :
https://www.chiark.greenend.org.uk/~sgtatham/puzzles/
![La collection de puzzle de Simon Tatham](https://chezsoi.org/lucas/SimonTathamPuzzleCollection.png)
-->Python module imports visualization2018-04-24T18:30:00+02:002018-04-24T18:30:00+02:00Lucas Cimontag:chezsoi.org,2018-04-24:/lucas/blog/python-modules-imports-visualization.html<h3>flask</h3>
<div id="modules-flask" style="text-align: center; padding-bottom: 4rem"></div>
<h3>httpie</h3>
<div id="modules-httpie" style="text-align: center; padding-bottom: 4rem"></div>
<h3>requests</h3>
<div id="modules-requests" style="text-align: center; padding-bottom: 4rem"></div>
<h3>simplejson</h3>
<div id="modules-simplejson" style="text-align: center; padding-bottom: 4rem"></div>
<h3>botocore</h3>
<div id="modules-botocore" style="text-align: center; padding-bottom: 4rem"></div>
<h3>scrapy</h3>
<div id="modules-scrapy" style="text-align: center; padding-bottom: 4rem"></div>
<h3>docker-compose</h3>
<div id="modules-docker-compose" style="text-align: center; padding-bottom: 4rem"></div>
<h3>ansible</h3>
<div id="modules-ansible" style="text-align: center; padding-bottom: 4rem"></div>
<h2>What are those diagrams ?</h2>
<p>They show dependencies between the internal modules of various well-known Python libraries.</p>
<p>They goal is to provide a global overview of a Python project architecture, as a map of <strong>modules & packages</strong>, the top-level code abstractions.</p>
<p>Note that all …</p><h3>flask</h3>
<div id="modules-flask" style="text-align: center; padding-bottom: 4rem"></div>
<h3>httpie</h3>
<div id="modules-httpie" style="text-align: center; padding-bottom: 4rem"></div>
<h3>requests</h3>
<div id="modules-requests" style="text-align: center; padding-bottom: 4rem"></div>
<h3>simplejson</h3>
<div id="modules-simplejson" style="text-align: center; padding-bottom: 4rem"></div>
<h3>botocore</h3>
<div id="modules-botocore" style="text-align: center; padding-bottom: 4rem"></div>
<h3>scrapy</h3>
<div id="modules-scrapy" style="text-align: center; padding-bottom: 4rem"></div>
<h3>docker-compose</h3>
<div id="modules-docker-compose" style="text-align: center; padding-bottom: 4rem"></div>
<h3>ansible</h3>
<div id="modules-ansible" style="text-align: center; padding-bottom: 4rem"></div>
<h2>What are those diagrams ?</h2>
<p>They show dependencies between the internal modules of various well-known Python libraries.</p>
<p>They goal is to provide a global overview of a Python project architecture, as a map of <strong>modules & packages</strong>, the top-level code abstractions.</p>
<p>Note that all module names in those diagrams are HTML links to the actual source code on GitHub.</p>
<h2>Why ?</h2>
<p>At work, we did a short <strong>technical-debt review</strong> of one of our Python services,
and a co-worker reported a lack of documentation to provide a clear overview of the code structure,
for first-time contributors to easily jump in.</p>
<p>Hence, last week I searched for some helpful code visualization recipes to provide such insight to our code base,
hoping to find an easy-to-setup Python module that would do the job.</p>
<p>I did not find any off-the-shelf package for my need (although I'd love your suggestions if you know some !),
but discovered Francois Zaninotto's <a href="https://github.com/fzaninotto/DependencyWheel">DependencyWheel</a> visualization of dependencies,
and decided to use it to build a nice diagram and add it to our documentation.</p>
<p>I thought it could be useful to others, hence this blog post to share the recipe online.</p>
<h2>How ?</h2>
<p>Following the spirit of <a href="http://idratherbewriting.com/2016/07/26/modern-technical-writing-review/">"Modern Technical Writing"</a>
/ <a href="https://en.wikipedia.org/wiki/Literate_programming">"Literate programming"</a> / <a href="https://leanpub.com/livingdocumentation">"Living Documentation"</a>,
our documentation for this project at work is written in Markdown and compiled with <a href="http://www.mkdocs.org">mkdocs</a> to provide a static website.
Moreover, the project is built & hosted by <a href="https://about.gitlab.com/features/pages/">GitLab Pages</a>.</p>
<p>This way, the diagram is always up-to-date with the project code.
It also made the addition of this diagram quite easy:</p>
<ol>
<li>I added some code to the GitLab Pages build script to fetch the corresponding git repo and extract the modules dependencies as JSON.</li>
<li>I added some Javascript code to a Markdown page in our documentation to render the dependency wheel based on this JSON</li>
</ol>
<p>The script to extract the modules dependencies is on GitHub: <a href="https://github.com/Lucas-C/dotfiles_and_notes/blob/master/languages/python/gen_modules_graph.py">gen_modules_graph.py</a>.
It is less than 100 lines and use the <a href="https://pypi.org/project/modulegraph/">modulegraph</a> package to parse modules dependencies, taking care to:</p>
<ul>
<li>ignore modules outside of the target project</li>
<li>ignore constants, functions and modules with the zero incoming & outgoing dependencies (like Python packages with an empty <code>__init__.py</code>)</li>
</ul>
<p>Usage example:</p>
<div class="highlight"><pre><span></span><code><span class="n">gen_modules_graph</span><span class="o">.</span><span class="n">py</span> <span class="n">ansible</span><span class="o">.</span><span class="n">inventory</span><span class="o">.</span><span class="n">manager</span> <span class="n">ansible</span><span class="o">.</span><span class="n">playbook</span> <span class="n">ansible</span><span class="o">.</span><span class="n">executor</span><span class="o">.</span><span class="n">task_queue_manager</span> <span class="o">></span> <span class="n">modules</span><span class="o">-</span><span class="n">ansible</span><span class="o">.</span><span class="n">json</span>
</code></pre></div>
<p>For the rendering, I used <a href="http://www.redotheweb.com/DependencyWheel/">fzaninotto/DependencyWheel</a>,
originally written to display the <strong>external</strong> dependencies of a project (e.g. links between PHP composer packages).
I made 2 small patches / PRs to the latest version of this project:</p>
<ul>
<li><a href="https://github.com/fzaninotto/DependencyWheel/pull/15">a single-line code change to allow for colors customization</a></li>
<li><a href="https://github.com/fzaninotto/DependencyWheel/pull/16">another minor change to make the chart adaptive to the parent DOM element width</a></li>
</ul>
<p>I also used some additional JS code to:</p>
<ul>
<li>ensure the dependencies matrix is square (to get prettier graphs)</li>
<li>customize the colors (cf. below)</li>
<li>add HTML anchor links</li>
</ul>
<p>The code is available in this page source. Like the Python script, you are free to reuse it at will.</p>
<p>It is relatively straightforward, with a single notable trick:
the conversion from a Python module path to a <a href="https://en.wikipedia.org/wiki/Hue">hue</a> color value on a 360 degrees scale.</p>
<h2>A little bit of maths</h2>
<p>In order for modules with a shared ancestor to have close colors (like <code>http.response.html</code> and <code>http.response.text</code> in the <code>scrapy</code> wheel above),
I used a simple mathematical concept: decomposing the hue value with a <a href="https://en.wikipedia.org/wiki/Bijective_numeration">bijective numeration</a>
into a fixed-size string of digits.</p>
<p>This idea is similar to the binary numeral system, notably with the same concept of most / least significant digits,
except that the final range covered is <code>[0, 360]</code> and we want as many digits as the module tree depth.</p>
<p>Once this numeral system <a href="https://en.wikipedia.org/wiki/Radix">base radix</a> is computed from those 2 constraints,
computing the hue value is simply a matter of a basic <a href="https://en.wikipedia.org/wiki/Positional_notation#Exponentiation">exponentiation</a> :</p>
<figure role="group">
<img alt="Python module tree" src="images/2018/04/PythonModuleTree.png">
<figcaption>Python module tree, with module names positions for module path <code>output.formatters.headers</code> of <code>httpie</code>
<br>(made with <a href="https://www.draw.io">draw.io</a> - <a href="images/2018/04/PythonModuleTree.xml">source xml</a>)</figcaption>
</figure>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=AM_HTMLorMML,Safe"></script>
<p class="formula">
`"Let's consider a module tree of depth " D "."`
`"Then the base radix to use in our decomposition is " R = 360^(1 / D)`
`"Now, let " m " be a module path, constituted of " d " modules names " m_i ", with " d <= D "."`
`"We can define " pos(m_i) " to be the position of the module name " m_i " in the sorted list of its parent module children,"`
`" and " parentModCount(m_i) " to be the number of children modules for its parent."`
`"We can now compute the digits of " m " in our decomposition: " d_(m_i) = (pos(m_i)) / (parentModCount(m_i)) * (R - 1)`
`"And then " hue(m) = sum_(i=1)^D d_(m_i)*R^(D-i)`
</p>
<script src="images/2018/04/d3.v4.min.js"></script>
<script src="images/2018/04/d3.dependencyWheel.js"></script>
<script>
function buildModuleTree(modulePaths) {
var tree = {};
modulePaths.forEach(modulePath => {
modulePath.split('.').reduce((parent, moduleName) => (parent[moduleName] = parent[moduleName] || {}), tree);
});
return tree;
}
function modulePath2Degrees(modulePath, moduleTree, moduleTreeDepth) {
var parentModule = moduleTree, result = 0, baseRadix = Math.pow(360, 1 / moduleTreeDepth);
for (var i = 0; i < moduleTreeDepth && modulePath[i]; i++) {
var parentModuleChildren = Object.keys(parentModule);
parentModuleChildren.sort();
var moduleRatioInParent = parentModuleChildren.indexOf(modulePath[i]) / parentModuleChildren.length;
var weight = Math.pow(baseRadix, moduleTreeDepth - 1 - i);
result += weight * (moduleRatioInParent * (baseRadix - 1));
parentModule = parentModule[modulePath[i]];
}
return result;
}
function renderDependencyWheel(dependencyGraphJsonUrl, htmlElementSelector, moduleUrlTemplate) {
d3.json(dependencyGraphJsonUrl, function(data) {
// Ensuring matrix is symmetrical to make chords more regular, thick
var originalMatrix = JSON.parse(JSON.stringify(data.matrix));
data.matrix.forEach((row, i) => {
row.forEach((value, j) => {
if (value && !data.matrix[j][i]) {
data.matrix[j][i] = value;
}
});
});
// Custom chords & path colors:
var moduleTree = buildModuleTree(data.packageNames);
console.log(moduleTree);
var moduleTreeDepth = Math.max(...data.packageNames.map(p => p.split('.').length));
var chart = d3.chart.dependencyWheel({fill: function (d) {
var modulePath = data.packageNames[d.index].split('.');
if (d.subindex && !originalMatrix[d.index][d.subindex]) {
modulePath = data.packageNames[d.subindex].split('.');
}
var hue = modulePath2Degrees(modulePath, moduleTree, moduleTreeDepth);
return 'hsl(' + hue + ', 90%, 70%)';
}});
d3.select(htmlElementSelector).datum(data).call(chart).call(function(selection) {
// Insert <a> links on module names:
d3.selectAll(htmlElementSelector + ' text').each(function() {
var oldParent = this.parentNode;
var newParentAnchor = document.createElementNS('http://www.w3.org/2000/svg', 'a');
newParentAnchor.setAttributeNS(null, 'href', moduleUrlTemplate(this.textContent.replace('.', '/') + '.py'));
newParentAnchor.setAttributeNS(null, 'target', '_blank');
oldParent.replaceChild(newParentAnchor, this);
newParentAnchor.appendChild(this);
});
});
});
}
renderDependencyWheel('images/2018/04/modules-flask.json', '#modules-flask', (modPath) => `https://github.com/pallets/flask/blob/master/flask/${modPath}`)
renderDependencyWheel('images/2018/04/modules-httpie.json', '#modules-httpie', (modPath) => `https://github.com/jakubroztocil/httpie/blob/master/httpie/${modPath}`)
renderDependencyWheel('images/2018/04/modules-requests.json', '#modules-requests', (modPath) => `https://github.com/requests/requests/blob/master/requests/${modPath}`)
renderDependencyWheel('images/2018/04/modules-simplejson.json', '#modules-simplejson', (modPath) => `https://github.com/simplejson/simplejson/blob/master/simplejson/${modPath}`)
renderDependencyWheel('images/2018/04/modules-botocore.json', '#modules-botocore', (modPath) => `https://github.com/boto/botocore/blob/master/botocore/${modPath}`)
renderDependencyWheel('images/2018/04/modules-scrapy.json', '#modules-scrapy', (modPath) => `https://github.com/scrapy/scrapy/blob/master/scrapy/${modPath}`)
renderDependencyWheel('images/2018/04/modules-docker-compose.json', '#modules-docker-compose', (modPath) => `https://github.com/docker/compose/blob/master/compose/${modPath}`)
renderDependencyWheel('images/2018/04/modules-ansible.json', '#modules-ansible', (modPath) => `https://github.com/ansible/ansible/blob/master/lib/ansible/${modPath}`)
</script>
<style>
h3 { text-align: center; }
.formula {
font-size: larger;
text-align: center;
}
.MathJax { line-height: 3rem; }
</style>The Salem Conspiracy2018-04-21T16:00:00+02:002018-04-21T16:00:00+02:00Lucas Cimontag:chezsoi.org,2018-04-21:/lucas/blog/the-salem-conspiracy.html<p>Découvert grâce au <a href="https://pnpfrance.wordpress.com/2016/08/26/the-salem-conspiracy/">blog "Du papier et des jeux"</a>,
j'ai testé ces derniers jours le jeu <em>print & play</em> pour 2 joueurs <a href="https://boardgamegeek.com/boardgame/142233/salem-conspiracy">The Salem Conspiracy</a>.</p>
<p>Et je vous le recommande vivement si vous cherchez un petit jeu de duel tactique court (~15min) pour 2 joueurs,
ne requiérant que très peu de …</p><p>Découvert grâce au <a href="https://pnpfrance.wordpress.com/2016/08/26/the-salem-conspiracy/">blog "Du papier et des jeux"</a>,
j'ai testé ces derniers jours le jeu <em>print & play</em> pour 2 joueurs <a href="https://boardgamegeek.com/boardgame/142233/salem-conspiracy">The Salem Conspiracy</a>.</p>
<p>Et je vous le recommande vivement si vous cherchez un petit jeu de duel tactique court (~15min) pour 2 joueurs,
ne requiérant que très peu de matériel : 3 pages à imprimer, 4 dés à 6 faces et un paquet de cartes à jouer.</p>
<p><img alt="Matériel de jeu de The Salem Conspiracy" src="images/2018/04/the-salem-conspiracy.jpg"></p>
<p>Pour autant, ce jeu de <a href="https://boardgamegeek.com/boardgamedesigner/68316/javier-martin">Javier Martin</a> est très bien pensé et équilibré,
lanssant beaucoup d'options aux joueurs à chaque tour tout en incluant une part amusante de hasard.</p>
<p>De mon point de vue, le pouvoir du personnage du <em>Trickster</em> n'est pas des plus utiles et <a href="https://boardgamegeek.com/article/28884493#28884493">pourrait être amélioré</a>, mais au final le jeu invite presque de par sa conception
à y ajouter ses propres cartes de personnage, et n'en reste pas moins un très bon jeu gratuit auquel je rejouerai avec plaisir !</p>Conference sur l'open source en entreprise a l'Ecole IMT Atlantique2018-03-02T18:30:00+01:002018-03-02T18:30:00+01:00Lucas Cimontag:chezsoi.org,2018-03-02:/lucas/blog/conference-sur-l-open-source-en-entreprise-a-l-ecole-imt-atlantique.html<p>Une courte présentation que j'ai donné ce matin à l'école d'ingénieurs <a href="https://www.imt-atlantique.fr">IMT Atlantique</a> (ex Ecole des Mines) via <a href="https://www.oui.sncf">oui.sncf</a> :</p>
<p>(la navigation est meilleure avec les flêches gauche / droite du clavier)</p>
<div style="text-align:center;"><iframe src="https://voyages-sncf-technologies.github.io/conf-open-source-en-entreprise/" width="600" height="400">
<p>Iframes non supportées. Cliquez sur le lien dans le paragraphe ci-dessous pour accéder directement aux slides.</p>
</iframe></div>
<p>Le code source …</p><p>Une courte présentation que j'ai donné ce matin à l'école d'ingénieurs <a href="https://www.imt-atlantique.fr">IMT Atlantique</a> (ex Ecole des Mines) via <a href="https://www.oui.sncf">oui.sncf</a> :</p>
<p>(la navigation est meilleure avec les flêches gauche / droite du clavier)</p>
<div style="text-align:center;"><iframe src="https://voyages-sncf-technologies.github.io/conf-open-source-en-entreprise/" width="600" height="400">
<p>Iframes non supportées. Cliquez sur le lien dans le paragraphe ci-dessous pour accéder directement aux slides.</p>
</iframe></div>
<p>Le code source des slides est disponible <a href="https://github.com/voyages-sncf-technologies/conf-open-source-en-entreprise">ici</a>.</p>Glitch art and image processing with Python2018-02-18T23:00:00+01:002018-02-18T23:00:00+01:00Lucas Cimontag:chezsoi.org,2018-02-18:/lucas/blog/glitch-art-and-image-processing-with-python.html<p>This week I discovered the fantastic <a href="https://www.reddit.com/r/glitch_art">glitch art</a> Reddit community
(for a little more context on glitch art, <a href="https://en.wikipedia.org/wiki/Glitch_art">wikipedia has a page</a>).
These are the pieces I love the most (click on them to find the source):</p>
<!-- The a > div > noscript seems not valid in terms of HTML syntax -->
<p><a href="https://www.reddit.com/r/glitch_art/comments/7x3ps0/dream_girl_gaze/">
<img loading="lazy" alt="dream_girl_gaze art by misteach" src="images/2018/02/pd1gx9xucuf01.jpg">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/75ok6f/walk_it_off/">
<img loading="lazy" alt="Walk it off by skybrian" src="images/2018/02/zqvSQdO.png">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/5wbei8/pxl_rain/">
<img loading="lazy" alt="pxl_rain by GutturalEcho" src="images/2018/02/ljc76jy3s8iy.jpg">
</a></p>
<p><a href="https://www.reddit.com/r/pixelsorting/comments/757ung/a_bit_of_rain/">
<img loading="lazy" alt="A bit of rain by cirodoggy" src="images/2018/02/31t7i2m9rrqz.jpg">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/7glph4/the_heavens_opened_up_when_the_rain_came/">
<img loading="lazy" alt="The heavens opened up When the rain came by txchick1983" src="images/2018/02/fBb1s47.png">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/8n8xnu/sakura/">
<img loading="lazy" alt="Sakura by piromantic" src="images/2018/02/sakura.png">
</a></p>
<p><a href="https://www.reddit.com/r/pixelsorting/comments/61iwka/liquid_metal/">
<img loading="lazy" alt="Liquid metal by Tw1gz666" src="images/2018/02/tap4xb93enny.jpg">
</a>
This one above reminds me of <a href="https://en.wikipedia.org/wiki/The_Great_Wave_off_Kanagawa">The Great Wave off Kanagawa</a>.
Like many …</p><p>This week I discovered the fantastic <a href="https://www.reddit.com/r/glitch_art">glitch art</a> Reddit community
(for a little more context on glitch art, <a href="https://en.wikipedia.org/wiki/Glitch_art">wikipedia has a page</a>).
These are the pieces I love the most (click on them to find the source):</p>
<!-- The a > div > noscript seems not valid in terms of HTML syntax -->
<p><a href="https://www.reddit.com/r/glitch_art/comments/7x3ps0/dream_girl_gaze/">
<img loading="lazy" alt="dream_girl_gaze art by misteach" src="images/2018/02/pd1gx9xucuf01.jpg">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/75ok6f/walk_it_off/">
<img loading="lazy" alt="Walk it off by skybrian" src="images/2018/02/zqvSQdO.png">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/5wbei8/pxl_rain/">
<img loading="lazy" alt="pxl_rain by GutturalEcho" src="images/2018/02/ljc76jy3s8iy.jpg">
</a></p>
<p><a href="https://www.reddit.com/r/pixelsorting/comments/757ung/a_bit_of_rain/">
<img loading="lazy" alt="A bit of rain by cirodoggy" src="images/2018/02/31t7i2m9rrqz.jpg">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/7glph4/the_heavens_opened_up_when_the_rain_came/">
<img loading="lazy" alt="The heavens opened up When the rain came by txchick1983" src="images/2018/02/fBb1s47.png">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/8n8xnu/sakura/">
<img loading="lazy" alt="Sakura by piromantic" src="images/2018/02/sakura.png">
</a></p>
<p><a href="https://www.reddit.com/r/pixelsorting/comments/61iwka/liquid_metal/">
<img loading="lazy" alt="Liquid metal by Tw1gz666" src="images/2018/02/tap4xb93enny.jpg">
</a>
This one above reminds me of <a href="https://en.wikipedia.org/wiki/The_Great_Wave_off_Kanagawa">The Great Wave off Kanagawa</a>.
Like many other pieces, it uses a "glitching" technique called Pixed Sorting.</p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/6nsxna/city_lights/">
<img loading="lazy" alt="City Lights by Sarxasm" src="images/2018/02/DKgMD0A.jpg">
</a></p>
<p><a href="https://www.reddit.com/r/pixelsorting/comments/7b3u4x/have_you_ever_retired_a_human_by_mistake/">
<img loading="lazy" alt="Have you ever retired a human by mistake? by pixelated_spliffs" src="images/2018/02/nxmkk00scbwz.jpg">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/79bm2k/lost_city/">
<img loading="lazy" alt="Lost City by Kek_Snek" src="images/2018/02/ep2fjwlh3muz.jpg">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/22dk1u/wakegif/">
<img loading="lazy" alt="wake.gif by HopelessPerson" src="images/2018/02/SFVf5ov.gif">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/6ylvyw/you_cant_handle_the_glitch_by_arsikere/">
<img loading="lazy" alt="You can't handle the Glitch by @Arsikere" src="images/2018/02/AWlGFnu.gif">
</a></p>
<p>This other glitch video by MarshmellowNinja is very funny, but better viewed online: <a href="https://www.reddit.com/r/glitch_art/comments/7xhbrf/woah/">https://www.reddit.com/r/glitch_art/comments/7xhbrf/woah/</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/26w188/a_landscape_piece/">
<img loading="lazy" alt="A landscape piece. by vvdr12" src="images/2018/02/vvdr12_14116705239_cd7ae031e4_k.jpg">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/1kztnf/japanization_python_pixel_editing_bug/">
<img loading="lazy" alt="Japanization: Python pixel editing bug by vvdr12" src="images/2018/02/vvdr12_9655801399_c763157694_o.png">
</a></p>
<p><a href="https://www.flickr.com/photos/vvdr12/10343539123/">
<img loading="lazy" alt="elephant hill by vvdr12" src="images/2018/02/vvdr12_10343539123_128a0c8375_k.jpg">
</a></p>
<p>Those 4 last ones are from Reddit user <a href="https://www.reddit.com/user/vvdr12">vvdr12</a>, whose <a href="https://www.flickr.com/photos/vvdr12/">flickr gallery</a> also includes great intentionnaly made pieces. He <a href="https://www.reddit.com/r/glitch_art/comments/1kztnf/japanization_python_pixel_editing_bug/">kindly explained</a> <a href="https://www.reddit.com/r/glitch_art/comments/1p5mno/elephant_hill/">how he made this "japanify" effect in Python</a>, and specified the <a href="http://imgur.com/TDZSJMs">source image</a>.</p>
<p>He did not provide the code for the palette substitution, so I re-rewrote it: <a href="https://github.com/Lucas-C/dotfiles_and_notes/blob/master/languages/python/img_processing/japanify.py">japanify.py</a> + <a href="https://github.com/Lucas-C/dotfiles_and_notes/blob/master/languages/python/img_processing/steal_colors_with_same_brightness.py">steal_colors_with_same_brightness.py</a></p>
<div class="highlight"><pre><span></span><code>./japanify.py TDZSJMs.jpg
./steal_colors_with_same_brightness.py --palette-img edJl3YU.jpg japanified_TDZSJMs.jpg
</code></pre></div>
<p>(I'd love to also replicate <a href="https://imgur.com/Jcs4BMw">this animated Joy Division effect by vvdr12</a>, but he only provided some code to produce a static image - maybe a GIF could be created using <a href="https://github.com/neozhaoliang/pywonderland/blob/master/src/wilson/encoder.py">Zhao Liang GIFWriter</a>...)</p>
<p>The Glasgow band <a href="https://soundcloud.com/killthewaves">Kill the Waves</a> even used his idea for their album cover:</p>
<p><img alt="Kill The Waves band album cover" src="http://i.imgur.com/JrPsI1y.jpg"></p>
<p>It was nice to stumble on this band, I especially like their <a href="https://soundcloud.com/tonguesmusic/anymore">Anymore</a> & <a href="https://soundcloud.com/killthewaves/vow">Vow</a> songs.</p>
<p>What do you think of those glitches ? ?? Are there other ones you known and like ? ??</p>
<p><strong>EDIT [2018/03/05]</strong> : I learned thanks to <a href="http://rhizome.org">http://rhizome.org</a> about the name of one of those technics, <em>datamoshing</em>,
of which this video is an example : <a href="https://www.youtube.com/watch?v=TxFeesWL5OI">https://www.youtube.com/watch?v=TxFeesWL5OI</a>.</p>
<p>There is also this tumblr collecting glitch GIFs: <a href="http://glitchgifs.tumblr.com">http://glitchgifs.tumblr.com</a>.</p>
<p><strong>EDIT [2018/11/16]</strong> : another outstanding one</p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/5qlthr/pixel_sort_rotation_gif_oc/">
<img loading="lazy" alt="Pixel sort Rotation by HI_IM_DR_PHIL" src="images/2018/02/ls5etz2fodcy.gif">
</a></p>
<p><strong>EDIT [2020/07/17]</strong> : a couple of new ones</p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/hpvuxe/7122020/">
<img loading="lazy" alt="7/12/2020 by YTChyme"
src="images/2018/02/998zibjfzfa51.jpg">
</a></p>
<p><a href="https://www.reddit.com/r/glitch_art/comments/hfk749/im_glitching_out/">
<img loading="lazy" alt="Im glitching out by Cruel_Coppinger"
src="images/2018/02/7wr3m65mg1751.jpg">
</a></p>
<p>Check also the <a href="https://imgur.com/a/ruqeUJV">sǝʌᴉʇɐuɹǝʇlɐ</a>.</p>
<style>
article img { max-height: 80vh; }
</style>
<script>
function setTitles() {
document.querySelectorAll('article img').forEach(img => img.title = img.alt)
setTimeout(setTitles, 2000);
}
setTitles();
</script>Free alerting-as-a-service drop-in replacement for mail command2018-02-16T22:30:00+01:002018-02-16T22:30:00+01:00Lucas Cimontag:chezsoi.org,2018-02-16:/lucas/blog/free-alerting-as-a-service-drop-in-replacement-for-mail-command.html<p>On my personnal server, I used to send myself alerts by email using the handy standard <a href="https://linux.die.net/man/1/mail"><code>mail</code></a> command.
However, recently it appeared that my server became categorized as "spammer" by some online service providers,
due to the alerts frequency (a little bit more than one per day).</p>
<p>Hence, I got …</p><p>On my personnal server, I used to send myself alerts by email using the handy standard <a href="https://linux.die.net/man/1/mail"><code>mail</code></a> command.
However, recently it appeared that my server became categorized as "spammer" by some online service providers,
due to the alerts frequency (a little bit more than one per day).</p>
<p>Hence, I got rid of the postfix package and cumbersome configuration and decided to find an alternative solution.
I hesitated between <a href="https://sentry.io">Sentry</a>, a big player in the alerting-as-a-service domain that has the big advantage to be open source
(and developped in Python !), and <a href="https://rollbar.com">rollbar</a>, for which I opted for because of its simplicty (and by curiosity).</p>
<p>Then, I only had to <code>pip install rollbar</code> on my server and create a <code>/usr/local/bin/mail</code> executable file with the following code:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal">1</span>
<span class="normal">2</span>
<span class="normal">3</span>
<span class="normal">4</span>
<span class="normal">5</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><code><span class="ch">#!/usr/bin/python</span>
<span class="kn">import</span> <span class="nn">rollbar</span><span class="o">,</span> <span class="nn">socket</span><span class="o">,</span> <span class="nn">sys</span>
<span class="n">rollbar</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="s1">'rollbar token'</span><span class="p">)</span>
<span class="n">rollbar</span><span class="o">.</span><span class="n">report_message</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">gethostname</span><span class="p">()</span> <span class="o">+</span> <span class="s1">' sent an email:'</span> <span class="o">+</span> <span class="s1">' '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">:]),</span>
<span class="n">extra_data</span><span class="o">=</span><span class="p">{</span><span class="s1">'stdin'</span><span class="p">:</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">read</span><span class="p">()})</span>
</code></pre></div>
</td></tr></table>
<p>Et voilà !</p>
<p>All my pre-existing scripts now send alerts to the rollbar web API,
I still receive email notifications from this service
<strong>and</strong> I now have an online web dashboard and extra functionnalities like alerts aggregation 🎉</p>
<p><strong>EDIT [2022/05/03]</strong>: in case of <a href="https://docs.rollbar.com/docs/rate-limits">rate-limit exceeded</a>, the <code>pyrollbar</code> Python client will raise a warning, not an error:
<a href="https://github.com/rollbar/pyrollbar/blob/master/rollbar/__init__.py#L1700"><code>rollbar/__init__.py</code> line 1700</a> 😔</p>
<div class="highlight"><pre><span></span><code><span class="n">WARNING</span><span class="p">:</span><span class="n">rollbar</span><span class="p">:</span><span class="n">Rollbar</span><span class="p">:</span> <span class="n">over</span> <span class="n">rate</span> <span class="n">limit</span><span class="p">,</span> <span class="n">data</span> <span class="n">was</span> <span class="n">dropped</span><span class="o">.</span> <span class="n">Payload</span> <span class="n">was</span><span class="p">:</span><span class="o">...</span>
</code></pre></div>
<p>There is how to transform this warning into a proper <code>mail</code> command failure, because <a href="https://en.wikipedia.org/wiki/Fail-fast">fail-fast</a> is <strong>The Way</strong>:</p>
<div class="highlight"><pre><span></span><code><span class="ch">#!/usr/bin/python3</span>
<span class="kn">import</span> <span class="nn">logging</span><span class="o">,</span> <span class="nn">rollbar</span><span class="o">,</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">logging.handlers</span> <span class="kn">import</span> <span class="n">BufferingHandler</span>
<span class="n">log_handler</span> <span class="o">=</span> <span class="n">BufferingHandler</span><span class="p">(</span><span class="n">capacity</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
<span class="n">rollbar</span><span class="o">.</span><span class="n">log</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">log_handler</span><span class="p">)</span>
<span class="n">rollbar</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="s1">'rollbar token'</span><span class="p">,</span> <span class="n">handler</span><span class="o">=</span><span class="s1">'blocking'</span><span class="p">)</span>
<span class="n">rollbar</span><span class="o">.</span><span class="n">report_message</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">gethostname</span><span class="p">()</span> <span class="o">+</span> <span class="s1">' sent an email:'</span> <span class="o">+</span> <span class="s1">' '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">:]),</span>
<span class="n">extra_data</span><span class="o">=</span><span class="p">{</span><span class="s1">'stdin'</span><span class="p">:</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">read</span><span class="p">()})</span>
<span class="k">if</span> <span class="nb">any</span><span class="p">(</span><span class="s1">'data was dropped'</span> <span class="ow">in</span> <span class="n">record</span><span class="o">.</span><span class="n">message</span> <span class="k">for</span> <span class="n">record</span> <span class="ow">in</span> <span class="n">log_handler</span><span class="o">.</span><span class="n">buffer</span><span class="p">):</span>
<span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div>Reverse engineering de l'API HTTP derrière une app android2018-02-14T23:30:00+01:002018-02-14T23:30:00+01:00Lucas Cimontag:chezsoi.org,2018-02-14:/lucas/blog/reverse-engineering-the-http-api-behind-an-android-app.html<p>Ces dernières 24h, j'ai eu cette lubie stupide de vouloir utiliser l'API Tickets Restaurant.
Une API qui n'est pas documentée. Dont le site est en ASP.NET (beurk). Qui a une app officielle android.</p>
<p>Ahah !</p>
<p>Bref, j'ai tenté de <em>reverse-engineer</em> un APK alors que je n'ai même pas de <em>smartphone …</em></p><p>Ces dernières 24h, j'ai eu cette lubie stupide de vouloir utiliser l'API Tickets Restaurant.
Une API qui n'est pas documentée. Dont le site est en ASP.NET (beurk). Qui a une app officielle android.</p>
<p>Ahah !</p>
<p>Bref, j'ai tenté de <em>reverse-engineer</em> un APK alors que je n'ai même pas de <em>smartphone</em> 🤦</p>
<p><img alt="reverse engineer" src="images/2018/02/reverse_engineer.jpg"></p>
<p>Au passage, je recommande vivement <a href="https://ibotpeaches.github.io/Apktool/">apktool</a>,
pour extraire le contenu d'un APK,
et <a href="https://github.com/skylot/jadx">jadx</a> pour décompiler le bytecode Dex en du code Java à peu près correct.
En tout cas, même s'il fait un peu plus d'erreurs (oublis de déclarations de variables par exemple),
il produit un code bien plus clair que <a href="http://jd.benow.ca">JD-GUI</a> (il extrait même les noms de variables !),
et l'interface graphique est bien pratique.</p>
<p>Le résultat au final ?</p>
<p><img alt="Ninja smoke bomb fail" src="images/2018/02/fail.gif"></p>
<p>Un bon gros <em>fail</em>.</p>
<p>Alors oui, j'ai eu la joie de réduire plusieurs milliers de lignes de Java très répétitives et pleines de fautes à <a href="https://github.com/Lucas-C/dotfiles_and_notes/blob/master/languages/python/edenred.py">une centaine de lignes de Python</a>.
Et c'était super fun de voir mes appels à cette fichue API SOAP retourner des erreurs incompréhensibles d'un <em>backend</em> visiblement en Ruby On Rails.</p>
<p>Mais au final, pas moyen de récupérer mon solde programmatiquement 😢</p>
<p>Au final j'en viens vraiment à douter que cette app fonctionne, surtout vu les avis dessus sur le store Google Play.
Donc si quelqu'un avec un ordinateur de poche pouvait me le confirmer, ça me ferait bien plaisir ! :D</p>
<p><strong>[EDIT]</strong> : un intéressant article récent sur le sujet : <a href="https://yasoob.me/posts/reverse-engineering-nike-run-club-using-frida-android/">Reverse Engineering Nike Run Club Android App Using Frida - Yasoob Khalid</a></p>
<style>
article img { max-height: 15rem; }
</style>Post-mortem de la Global Game Jam 20182018-02-03T14:00:00+01:002018-02-03T14:00:00+01:00Lucas Cimontag:chezsoi.org,2018-02-03:/lucas/blog/post-mortem-de-la-global-game-jam-2018.html<p>Le week-end dernier, j'ai participé à ma première <a href="https://globalgamejam.org">Global Game Jam</a>, à Nantes.
Dans cet article, je vais vous présenter comment elle s'est déroulée,
et faire le point sur ce qui a plus ou moins bien marché pour notre projet,
<a href="https://globalgamejam.org/2018/games/king-must-know">The King Must Know</a>, dont voici l'écran d'accueil :</p>
<p><a href="https://lucas-c.github.io/OuiJam2018/build/"><img alt="Écran d'accueil du jeu" src="images/2018/01/GGJ2018_GameIntroScreenshot.png"></a></p>
<p>J'avais déjà …</p><p>Le week-end dernier, j'ai participé à ma première <a href="https://globalgamejam.org">Global Game Jam</a>, à Nantes.
Dans cet article, je vais vous présenter comment elle s'est déroulée,
et faire le point sur ce qui a plus ou moins bien marché pour notre projet,
<a href="https://globalgamejam.org/2018/games/king-must-know">The King Must Know</a>, dont voici l'écran d'accueil :</p>
<p><a href="https://lucas-c.github.io/OuiJam2018/build/"><img alt="Écran d'accueil du jeu" src="images/2018/01/GGJ2018_GameIntroScreenshot.png"></a></p>
<p>J'avais déjà participé à quelques game jams durant mes études à l'<a href="http://ensimag.grenoble-inp.fr">Ensimag</a>.
Contraintes de colocation obligent, nous nous retrouvions à 8 dans ma chambre d'étudiant d'une quinzaine de mètres carrés, entre 11h du matin et 22h.
On codait <a href="https://github.com/Lucas-C/ImagGameJams">comme des pieds</a>, on faisait nos <em>sprites</em> sous Paint, et on s'amusaient comme des petits fous 😋 🎮 😵</p>
<p>Cette fois, la Global Game Jam était organisée par les écoles E-art Sup & Epitech dans leurs locaux à Nantes.
Merci à eux pour la super organisation, et pour nous avoir prêté leurs locaux le temps du week-end !
Avec deux collègues de <a href="https://www.oui.sncf">oui.sncf</a>, Henri & Loïc, nous avions déjà décidé de former une équipe ensemble pour l'occasion.</p>
<figure role="group">
<img alt="Photo de l'équipe" src="images/2018/01/GGJ2018_TheTeam.jpg">
<figcaption>Une équipe qui roxe du poney bleu !</figcaption>
</figure>
<p>Voici grosso-modo comment s'est déroulé le week-end:</p>
<ul>
<li><strong>vendredi 18h30</strong> : environ une centaine de participants se tassent dans un amphi d'E-art Sup, pour donner le coup d'envoi.
Nous sommes clairement parmi les moins jeunes des participants, la plupart étant des étudiants.
Une introduction par <a href="http://www.casusludi.com">Florent De Grissac</a> nous invite à réfléchir à la raison pour laquelle nous faisons des jeux,
et nous détaille le programme de la GGJ ainsi que quelques conseils.
La <a href="https://www.youtube.com/watch?v=3Roxls_2W2M"><em>keynote</em> officielle de la GGJ</a> est ensuite diffusée,
assez hallucinante par moments, avec une session d'aérobic en collants roses fluos.
Enfin, <strong>le thème est révélé : TRANSMISSION</strong></li>
<li>nous avons alors <strong>10 à 15min</strong> pour imaginer un concept de jeu !
Avec l'équipe, nous aboutissons à une demi-douzaine d'idées, parmi lesquelles:<ul>
<li>un "party game" de reconnaissance vocale pour téléphone mobile,
où chaque participant doit prononcer les mots d'une phrase, chacun son tour, dans des langues étrangères</li>
<li>un jeu d'évaluation des distances où des personnages s'envoient des messages télépathiques</li>
<li>un "sokoban/minesweeper" en temps limité où le joueur incarne le personnage du démineur de Windows et progresse dans le niveau sans faire exploser de bombe</li>
<li>un "party game" TIC-TAC-BOUM ou 2 à 4 joueurs devant l'écran se renvoient une bombe</li>
<li>un jeu de déduction dans un graphe de relations inspiré du modèle Alice/Bob/César de cryptographie,
où le joueur doit déterminer qui est l'espion après X tentatives de transmission de messages dans le réseau en sachant lesquelles ont été interceptées</li>
<li>un "puzzle-game" avec une balle suivant une trajectoire de folie en rebondissant sur des "tilt" de flipper</li>
</ul>
</li>
</ul>
<p>Une restitution des idées a alors lieux dans l'amphi, où chaque projet décrit brièvement son concept et exprime ses besoins de compétences (dev, graphiste, sound designer, etc.).
Comme nous avons une équipe mais pas encore de consensus entre nous sur une idée, nous séchons cette étape 😳</p>
<ul>
<li><strong>vendredi 21h</strong> : l'équipe investit une petite salle de l'Epitech, après quelques parts de pizzas offertes par les oganisateurs.
Nous partons finalement sur une idée d'Henri (très prolifique en concepts !) de transmission de message dans une prison.
Nous nous répartissions brièvement les tâches pour la soirée, en partant d'un exemple utilisant le framework <a href="https://phaser.io">PhaserJS</a>
afin d'essayer d'avoir un prototype au plus vite. Plusieurs dizaines de schémas et de dessins sont gribouillés et commentés.
Vu le concept sur lequel on est parti, ce serait génial d'utiliser les <em>sprites</em> de <a href="http://store.steampowered.com/app/233450/Prison_Architect/">PrisonArchitect</a> !
Comme c'est un jeu commercial, j'envoie un email <a href="http://ryansumo.blogspot.fr">au graphiste qui les a réalisés</a> ainsi qu'à <a href="https://www.introversion.co.uk/introversion/#about">l'éditeur du jeu</a>.
Dans l'immédiat, nous décidons d'utiliser un ensemble de <em>spritesheets</em> de <a href="https://kenney.nl">kenney.nl</a>.</li>
<li><strong>vendredi dans la soirée</strong> : Lucas Fleurance vient nous proposer de réaliser une bande son pour le jeu !</li>
<li><strong>vendredi minuit et demi</strong> : fin de la journée, on rentre se coucher</li>
<li><strong>samedi vers 9h30</strong> : nous nous retrouvons dans la salle pour continuer à coder.
Henri réalise le superbe écran d'intro</li>
<li><strong>samedi 14h30</strong> : toujours pas de prototype jouable !
Nous participons tout de même à un échange très sympa et motivant avec deux autres équipes participantes,
pour faire le point sur nos avancées et avoir quelques premiers retours.</li>
<li><strong>samedi 19h</strong> : alors que nous avons enfin un prototype jouable,
Chris Delay dIntroversion Software nous donne très aimablement l'autorisation par email d'utiliser les <em>sprites</em> de Prison Architect pour notre jeu !
Il est malheureusement trop tard pour nous pour les utiliser 😞</li>
<li><strong>samedi minuit</strong> : au dodo pour moi et Loïc, Henri lui poursuit jusqu'au bout de la nuit 💤</li>
<li><strong>dimanche 7h</strong> : dernière ligne droite: on corrige quelques bugs;
on ajoute 2 niveaux et une page de crédits; on intègre la musique et les bruitages;
on réussit à intégrer une fonctionnalité dans la dernière heure (le "Hurry Up!");
on prend en compte les retours des testeurs en essayant d'expliquer mieux les mécaniques de jeu dans le premier niveau :</li>
</ul>
<p><img alt="Premier niveau du jeu" src="images/2018/01/GGJ2018_lvl_1.png"></p>
<ul>
<li><strong>dimanche 15h</strong> : nous soumettons le jeu en ligne sur <a href="https://globalgamejam.org/2018/games/king-must-know">le site de la GGJ</a>, qui a du mal à tenir la charge.
Après avoir pris le temps de faire le tour des projets des autres équipes et tester plusieurs jeux,
c'est la fin de la jam pour nous. Bien fatigués, on rentre se reposer !</li>
</ul>
<p>En définitive, c'était une belle aventure !</p>
<p>Je crois pouvoir dire qu'on a tous les trois bien apprécié l'expérience.
Et personnellement je n'ai vraiment pas vu le week-end passer.</p>
<p>Pour les curieux, tout le code source du jeu est disponible sur GitHub: <a href="https://github.com/Lucas-C/OuiJam2018">https://github.com/Lucas-C/OuiJam2018</a></p>
<p>Dimanche, après avoir mis en ligne le jeu, nous avons fait un rapide "post-mortem" ensemble.
Voici ce qui ressortait de notre état d'esprit à la fin:</p>
<ul>
<li>envie d'en refaire une ! (mais pas tout de suite 💤)</li>
<li>plutôt content du gameplay final pour un jeu en 48h</li>
<li>content de l'avoir fait "sans se presser", et sans trop d'ambition,
et de ne pas s'être usé les nerfs les uns les autres</li>
<li>ravis du résultat en termes de musique et de sons</li>
<li>content du lieu où s'est déroulé la jam, de l'orga et de la bonne ambiance entre équipes</li>
<li>plutôt satisfaits de l'adéquation du jeu au thème</li>
</ul>
<p>Nous avons aussi listé les obstacles qui nous ont cassé les pieds durant cette jam:</p>
<ul>
<li>perdre du temps sur des "git merge" suite à des changements d'indentation automatique car nos IDEs étaient configurés différemment 😬</li>
<li>un bug de Phaser JS sur le chargement des <em>spritesheets</em>
(<a href="https://github.com/photonstorm/phaser-ce/issues/448">rapporté</a> depuis et en cours de correction)</li>
<li>retrouver la position des sprites que l'on voulait utiliser dans les grandes <em>spritesheets</em> → très laborieux</li>
<li>perdre du temps sur la gestion de la caméra, de la grille de <em>sprites</em> et de leur mise à l'échelle à l'écran</li>
</ul>
<p>Les trucs auxquels penser la prochaine fois:</p>
<ul>
<li>prévoir d'utiliser une éditeur de "tilemaps" comme Tiled, compatible avec Phaser JS, pour gagner du temps</li>
<li>préparer un peu plus notre "template" de code de départ, en prévoyant comment structurer les entités Phaser JS,
et en configurant un "linter" ES6</li>
</ul>
<p>Fonctionnalités qu'on aurait aimé ajouter:</p>
<ul>
<li>faire se déplacer les prisonniers dans une cellule quand ils le peuvent (et en termes de code, détecter automatiquement les <code>exits</code>)</li>
<li>faire se déplacer les gardes, et indiquer leur ligne de vue en changeant la couleur du sol des cellules</li>
<li>mieux gérer le niveau de zoom et le déplacement de la caméra</li>
<li>ajouter des items : WCs, cuiller, revolver...</li>
<li>ajouter un chargement de niveaux au format JSON/Tiled</li>
<li>utiliser un générateur aléatoire basé sur une <em>seed</em> exportable & importable</li>
</ul>
<p>Enfin, au-delà du moteur de jeu, <a href="http://phaser.io">Phaser JS</a>, et des <a href="https://github.com/Lucas-C/OuiJam2018#external-resources">assets libres de droit</a>,
voici les principaux outils que nous avons utilisé:</p>
<ul>
<li><a href="https://www.jetbrains.com/idea/">IntelliJ IDEA</a></li>
<li><a href="https://www.gimp.org/fr/">Gimp</a></li>
<li><a href="https://framateam.org">Framateam</a></li>
<li><code>git</code>, GitHub et <a href="https://pages.github.com">GitHub pages</a> pour héberger une version en ligne</li>
</ul>
<p>Pour conclure, merci encore aux organisateurs de cette fantastique jam !</p>
<p>Je vous encourage à jeter un œil à tous les jeux créés ce week-end là à <a href="https://globalgamejam.org/2018/jam-sites/epitech-nantes">Nantes</a>,
ainsi que partout ailleurs dans le monde !</p>
<p>Et pour tester le notre en ligne, <a href="https://lucas-c.github.io/OuiJam2018/build/">cliquez ici</a>.</p>
<p>On aimerait beaucoup vos avis / suggestions dessus, donc laissez nous un commentaire si vous le testez !</p>Daniel Linssen latest wonderful games2018-01-08T23:30:00+01:002018-01-08T23:30:00+01:00Lucas Cimontag:chezsoi.org,2018-01-08:/lucas/blog/daniel-linssen-latest-wonderful-games.html<p><img alt="managore logo" src="images/2018/01/managore.png"></p>
<p>Daniel Linssen, aka <a href="https://managore.itch.io"><em>managore</em></a>, is a fantastic indie game developer. I already mentioned one of his games, HopSlide, in <a href="https://chezsoi.org/lucas/blog/til-cows-tear-us-apart-et-hop-slide.html">a previous blog post (FR)</a>.</p>
<p>Now that it's clear that I am totally unbiased about this incredible game maker, lets talk about his latest games !</p>
<h3>WalkieTalkie</h3>
<p>This first one simply amazes …</p><p><img alt="managore logo" src="images/2018/01/managore.png"></p>
<p>Daniel Linssen, aka <a href="https://managore.itch.io"><em>managore</em></a>, is a fantastic indie game developer. I already mentioned one of his games, HopSlide, in <a href="https://chezsoi.org/lucas/blog/til-cows-tear-us-apart-et-hop-slide.html">a previous blog post (FR)</a>.</p>
<p>Now that it's clear that I am totally unbiased about this incredible game maker, lets talk about his latest games !</p>
<h3>WalkieTalkie</h3>
<p>This first one simply amazes me in terms of originality. <a href="https://managore.itch.io/walkie-talkie">WalkieTalkie</a> concept is just awesome:
you exchange messages in a chat room, <strong>which are all playable levels of a platform game !</strong></p>
<p>Plus, the artistic direction is lovingly pixelated :</p>
<figure role="group">
<img alt="WalkieTalkie gameplay glipse" src="images/2018/01/WalkieTalkie.gif">
<figcaption>Gameplay glipse - gif made with <a href="http://www.screentogif.com">ScreenToGif</a></figcaption>
</figure>
<p>Daniel made it a year ago during the <a href="http://ludumdare.com/compo/ludum-dare-37/?action=preview&uid=3479">LudumDare 37</a> game jam, which had for theme "One Room".
Looking <a href="http://data.takorii.com/wk/get.php?from=0">behind the scenes</a> and given the first chat comment, I'm pretty confident to say that another game developer, <a href="https://tak.itch.io">takorii</a>, gave him a little hand in this project :)</p>
<p>Walkie Talkie reminds me of <a href="http://store.steampowered.com/app/258890/TypeRider/">Type:Rider</a>,
a French game I enjoyed playing very much, as they share a same theme, typography, and platformer gameplay based on letters and text.</p>
<h3>windowframe</h3>
<p><a href="https://managore.itch.io/windowframe">windowframe</a> has a little bit in common with his previous game <a href="https://chezsoi.org/lucas/blog/til-cows-tear-us-apart-et-hop-slide.html">HopSlide</a>, as it revolves around moving windows <strong>in your operating systems</strong>.</p>
<p>The window resizing mechanics are truly ingenious, but the game puzzles are his real strength: they are well balanced,
extremely smart and most of all very fun !</p>
<p><img alt="WindowFame gameplay glimpse" src="images/2018/01/WindowFrame.gif"></p>
<p>One thing that stroke most is how the game use window resizing & positioning in some scenes as "camera",
to focus the player sight on one character in one corner of the screen. It's a bit like an animated comics !</p>
<p>It was made for <a href="http://ludumdare.com/compo/ludum-dare-35/?action=preview&uid=3479">Ludum Dare 35</a> game jam.
I urge you to test it and brave the terrible 6 vampire bosses !</p>Psi*Run dans l'univers de Blades in the Dark2017-12-28T18:00:00+01:002017-12-28T18:00:00+01:00Lucas Cimontag:chezsoi.org,2017-12-28:/lucas/blog/psi-run-dans-l-univers-de-blades-in-the-dark.html<p><img alt="Illustration de Blades In The Dark" src="images/2017/12/BitD_running.png"></p>
<p><a href="https://chezsoi.org/lucas/jdr/psirun/CR_2016-12-18.html">Un an plus tard</a>, deuxième partie de <a href="http://nightskygames.com/welcome/game/PsiRun">Psi*Run de Meguey Baker</a>,
cette fois dans une ambiance inspirée du jeu de rôles <a href="https://www.evilhat.com/home/blades-in-the-dark/">Blades In The Dark</a>.</p>
<p>Et encore une fois, c'était génial !</p>
<p>Voici le <a href="/lucas/blog/tag/compte-rendu.html">compte-rendu</a> : <a href="https://chezsoi.org/lucas/jdr/psirun/CR_2017-12-27.html">https://chezsoi.org/lucas/jdr/psirun/CR_2017-12-27.html</a></p>Pencil park2017-12-05T23:30:00+01:002017-12-05T23:30:00+01:00Lucas Cimontag:chezsoi.org,2017-12-05:/lucas/blog/pencil-park.html<p>Ce soir, je viens de tester <strong>Pencil Park</strong>, la dernière création de <a href="http://danielsolisblog.blogspot.fr/search/label/Pencil%20Park">Daniel Solis</a>,
disponbile en <em>print & play</em> sur <a href="https://boardgamegeek.com/filepage/147337/pencil-park-print-and-playtest">boardgamegeek</a>.</p>
<p>Et je le recommande chaudement !</p>
<p><img alt="Couverture du jeu Pencil Park" src="images/2017/12/pencil-park.jpg"></p>
<p>Le jeu m'a fait beaucoup pensé à <strong>La route des vignes</strong>, que j'avais agréablement découvert au festival "Paris est ludique" en juin dernier.</p>
<p><img alt="Couverture du jeu La route des vignes" src="images/2017/12/la-route-des-vignes.jpg"></p>
<p>Il partage …</p><p>Ce soir, je viens de tester <strong>Pencil Park</strong>, la dernière création de <a href="http://danielsolisblog.blogspot.fr/search/label/Pencil%20Park">Daniel Solis</a>,
disponbile en <em>print & play</em> sur <a href="https://boardgamegeek.com/filepage/147337/pencil-park-print-and-playtest">boardgamegeek</a>.</p>
<p>Et je le recommande chaudement !</p>
<p><img alt="Couverture du jeu Pencil Park" src="images/2017/12/pencil-park.jpg"></p>
<p>Le jeu m'a fait beaucoup pensé à <strong>La route des vignes</strong>, que j'avais agréablement découvert au festival "Paris est ludique" en juin dernier.</p>
<p><img alt="Couverture du jeu La route des vignes" src="images/2017/12/la-route-des-vignes.jpg"></p>
<p>Il partage la même mécanique de tirage aléatoire imposant à chaque joueur un nouvel élement à placer sur la feuille de papier quadrillée devant lui,
le plongeant ainsi dans un casse-tête géométrique compétitif.</p>
<p>On se triture les méninges, on se plaint du tirage, on commente les choix tactiques des autres...
Petit à petit, alors que la grille se remplit, la pression monte pour les apprentis urbanistes autour de la table !</p>
<p>Bien sûr, comme tous les jeux de se genre, il manque un peu d'interactivité entre joueurs.
Mais la rejouabilité est grande, surtout que nous n'avons pas testé les règles supplémentaires des villes "face B".</p>
<p>A noter qu'à cause d'une erreur de lecture des règles, nous avons joué avec une <strong>variante</strong> lors de notre première partie:
plutôt que chacun choisisse comment arranger les dés au mieux pour lui, le joueur les ayant lancé impose sa répertation à tout le monde !
Cela créé un peu plus d'interactivité au final, au prix d'un peu plus de ressemblance des villes à la fin de la partie.</p>Certification développeur RGAA Access422017-11-16T18:00:00+01:002017-11-16T18:00:00+01:00Lucas Cimontag:chezsoi.org,2017-11-16:/lucas/blog/certification-developpeur-rgaa-access42.html<p><img alt="" src="images/2017/11/accessibilite_numerique.jpg"></p>
<p>Connaissez-vous l'<strong>accessibilité numérique</strong> ?</p>
<p>En très bref:</p>
<blockquote>
<p>L'accessibilité numérique est la mise à la disposition de tous les individus, quels que soient leur matériel ou logiciel, leur infrastructure réseau,
leur langue maternelle, leur culture, leur localisation géographique, ou leurs aptitudes physiques ou mentales, des ressources numériques.</p>
</blockquote>
<p>Pour plus de détails …</p><p><img alt="" src="images/2017/11/accessibilite_numerique.jpg"></p>
<p>Connaissez-vous l'<strong>accessibilité numérique</strong> ?</p>
<p>En très bref:</p>
<blockquote>
<p>L'accessibilité numérique est la mise à la disposition de tous les individus, quels que soient leur matériel ou logiciel, leur infrastructure réseau,
leur langue maternelle, leur culture, leur localisation géographique, ou leurs aptitudes physiques ou mentales, des ressources numériques.</p>
</blockquote>
<p>Pour plus de détails, je vous recommande:</p>
<ul>
<li>la page d'introduction au sujet sur le site officiel du gouvernement: <a href="http://references.modernisation.gouv.fr/accessibilite-numerique">http://references.modernisation.gouv.fr/accessibilite-numerique</a></li>
<li>cet article très clair et complet: <a href="http://www.ipedis.com/definition-accessibilite-numerique/">http://www.ipedis.com/definition-accessibilite-numerique/</a></li>
</ul>
<p>J'ai découvert l'accessibilité numérique lors d'une formation via mon travail à <a href="https://www.voyages-sncf.com">voyages-sncf.com</a>.
Elle était passionnante, et dispensée par Jean-Pierre Villain, co-fondateur de la société Access42.</p>
<p><img alt="Logo d'Access42" src="images/2017/11/access42.jpg"></p>
<p><a href="https://access42.net/prestations">Access42</a> est une <a href="https://fr.wikipedia.org/wiki/Soci%C3%A9t%C3%A9_coop%C3%A9rative_et_participative">SCOP</a> spécialisée dans l'accessibilité numérique,
autrice du <a href="https://references.modernisation.gouv.fr/rgaa-accessibilite/">référentiel d’accessibilité numérique de l'état français</a>.</p>
<p>Après avoir eu entre-temps l'occasion de développer <a href="https://github.com/Lucas-C/pre-commit-hooks-lxml#fr-accessibilité-rgaa">quelques</a> <a href="https://github.com/Lucas-C/pre-commit-hooks-java#fr-accessibilité-rgaa">hooks de pre-commit git</a> pour valider des critèrs d'accessibilité
(à savoir: seul 20% environ des critères du RGAA sont automatisables), j'ai passé l'examen de certification de la formation à la fin du mois d'octobre.</p>
<p><strong>Et au final, lundi 6 novembre dernier, j'ai été très fier d'obtenir ma certification
<em>Développer des sites web avec le RGAA 3.0</em> !!</strong></p>
<p>Merci à Jean-Pierre et Audrey d'Access42, ainsi qu'à <voyages-sncf.com> pour m'avoir permis d'obtenir cette certification.</p>
<p>L'enjeu maintenant pour moi va être d'appliquer au quotidien ce que j'ai appris,
de me maintenir à jour et de transmettre ces bonnes pratiques.
J'aimerais beaucoup aussi, si j'ai le temps, contribuer au projet <a href="https://github.com/nvaccess/nvda">NVDA</a>,
le lecteur d'écran open-source écrit en Python.</p>
<p><strong>Bonus</strong>:</p>
<ul>
<li>la liste des resources RGAA d'Access42: <a href="http://gta2017.access42.net/2/ressources/">http://gta2017.access42.net/2/ressources/</a></li>
<li>la présentation de Tara Voelker au GDC 2017 décrivant comment ils ont rendu accessibile le jeu vidéo Evolve : <a href="https://www.youtube.com/watch?v=K56VO28WGfA">https://www.youtube.com/watch?v=K56VO28WGfA</a></li>
</ul>
<style>
article img { max-height: 30vh; }
</style>Quelques courts métrages des Utopiales 20172017-11-06T14:00:00+01:002017-11-06T14:00:00+01:00Lucas Cimontag:chezsoi.org,2017-11-06:/lucas/blog/quelques-courts-metrages-des-utopiales-2017.html<p>La semaine dernière se tenait à Nantes le festival de science-fiction des <a href="https://www.utopiales.org">Utopiales</a>.</p>
<p>Voici une sélection personnelle de quelques court-métrages qu'y ont été diffusés et que j'ai particulièrement aimé.</p>
<p><img alt="Affiche des Utopiales 2017" src="images/2017/11/utopiales.jpg"></p>
<h2>Courts métrages entièrement visionnables en ligne</h2>
<h3>Icarus</h3>
<iframe width="560" height="315" src="https://www.youtube.com/embed/kGlm1Fq73Og" allowfullscreen></iframe>
<p>Un film émouvant et immersif, aux faux airs de "Seul sur Mars".</p>
<h3>Black Holes …</h3><p>La semaine dernière se tenait à Nantes le festival de science-fiction des <a href="https://www.utopiales.org">Utopiales</a>.</p>
<p>Voici une sélection personnelle de quelques court-métrages qu'y ont été diffusés et que j'ai particulièrement aimé.</p>
<p><img alt="Affiche des Utopiales 2017" src="images/2017/11/utopiales.jpg"></p>
<h2>Courts métrages entièrement visionnables en ligne</h2>
<h3>Icarus</h3>
<iframe width="560" height="315" src="https://www.youtube.com/embed/kGlm1Fq73Og" allowfullscreen></iframe>
<p>Un film émouvant et immersif, aux faux airs de "Seul sur Mars".</p>
<h3>Black Holes : How Embarrassing To Be Human</h3>
<iframe src="https://player.vimeo.com/video/188075559?color=ffffff&title=0&byline=0&portrait=0" width="640" height="291" allowfullscreen></iframe>
<p>Ce court complètement barré et bourré de références SF est en fait le pilote d'une série financée via <a href="https://www.kickstarter.com/projects/blackholes/black-holes-0?lang=fr">KickStarter</a>.</p>
<h3>MeTube 2 : August sings Carmina Burana</h3>
<iframe width="560" height="315" src="https://www.youtube.com/embed/o19cnfIOuU0" allowfullscreen></iframe>
<p>De loin le court métrage le plus déjanté, <strong>gagnant du prix du jury à égalité</strong>.</p>
<h2>Les bandes annonces des autres courts métrages</h2>
<h3>The Last Schintzel</h3>
<iframe src="https://player.vimeo.com/video/197017494" width="640" height="268" allowfullscreen></iframe>
<p>Gagnant du prix du public, très drôle, financé sur <a href="https://www.indiegogo.com/projects/the-last-schnitzel#/">Indiegogo</a>.</p>
<h3>Hybrids</h3>
<iframe src="https://player.vimeo.com/video/223520703" width="640" height="268" allowfullscreen></iframe>
<p>Réalisé par des étudiants de l'école MOPA, <strong>gagnant du prix du jury à égalité</strong>.</p>
<h3>Real Artists</h3>
<iframe src="https://player.vimeo.com/video/183600832?color=858282&byline=0&portrait=0" width="640" height="360" allowfullscreen></iframe>
<p>Un court métrage d'<a href="https://fr.wikipedia.org/wiki/Anticipation_(fiction)">anticipation</a> sur le thème du métier d'artiste à l'ère numérique, glaçant de réalisme.</p>
<h3>Caronte</h3>
<iframe src="https://player.vimeo.com/video/203857821" width="640" height="268" allowfullscreen></iframe>
<p>Un mélange surprenant mais réussi de deux genres, avec des très beaux effets spéciaux.</p>
<h3>Last tree standing</h3>
<iframe width="560" height="315" src="https://www.youtube.com/embed/sfNq_PrRrm0" allowfullscreen></iframe>
<p>Une histoire assez poétique dans une ambiance post-apocalyptique à la David Lynch.</p>
<h3>The Meltdown</h3>
<iframe src="https://player.vimeo.com/video/196111458" width="640" height="360" allowfullscreen></iframe>
<p>Un court métrage humoristique sur le quotidien d'une équipe en charge d'une centrale nucléaire que j'ai trouvé assez original en terme de direction artistique.</p>
<h3>Voyagers</h3>
<p><a href="http://www.ecole-mopa.fr/portfolio-items/voyagers/"><img alt="Personnage du court-métrage Voyagers" src="images/2017/11/VOYAGERS.png"></a></p>
<p>Mon préféré personnellement, très drôle, également réalisé par des étudiants de l'école MOPA.</p>
<style>
article iframe, article img { display: block; margin: 0 auto; }
</style>Compte-rendu de la PyConFr 20172017-09-25T19:00:00+02:002017-09-25T19:00:00+02:00Lucas Cimontag:chezsoi.org,2017-09-25:/lucas/blog/compte-rendu-de-la-pyconfr-2017.html<p><img src="images/2017/09/pyconfr-2017-logo.png" alt="Logo PyConFr 2017"></p>
<p>Cette année, <a href="https://open.voyages-sncf.com">voyages-sncf.com</a> m'a permis d'aller à la conférence annuelle Python à Toulouse.</p>
<p><img src="images/2017/09/logo_voyages-sncf.com.png" alt="Logo voyages-sncf.com"></p>
<p>En vrac, voici un petit résumé personnel de cette PyConFr.</p>
<p>J'y étais présent 3 jours sur 4 (sprint le premier et conférences les deux autres),
et j'y ai donné donné 2 présentations, dont le contenu est …</p><p><img src="images/2017/09/pyconfr-2017-logo.png" alt="Logo PyConFr 2017"></p>
<p>Cette année, <a href="https://open.voyages-sncf.com">voyages-sncf.com</a> m'a permis d'aller à la conférence annuelle Python à Toulouse.</p>
<p><img src="images/2017/09/logo_voyages-sncf.com.png" alt="Logo voyages-sncf.com"></p>
<p>En vrac, voici un petit résumé personnel de cette PyConFr.</p>
<p>J'y étais présent 3 jours sur 4 (sprint le premier et conférences les deux autres),
et j'y ai donné donné 2 présentations, dont le contenu est disponible sur la pages <a href="pages/slides.html">slides</a>.</p>
<!-- toc -->
<ul>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#jour-1---vendredi-22-septembre">Jour 1 - vendredi 22 septembre</a><ul>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#python-docs-fr">python-docs-fr</a></li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#ideascube">ideascube</a></li>
</ul>
</li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#jour-2---samedi-23-septembre">Jour 2 - samedi 23 septembre</a><ul>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#10h30--plone-15-ans-dexperience-feront-toujours-la-difference---eric-brehault---makina-corpus">10h30 : Plone, 15 ans d'expérience feront toujours la différence - Eric Bréhault - Makina Corpus</a></li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#11h30--les-aventuriers-du-packaging-perdu---joachim-jablon--stephane-angel">11h30 : Les Aventuriers du Packaging Perdu - Joachim Jablon & Stéphane Angel</a></li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#14h--enseigner-est-apprendre---celine-martinet-sanchez">14h : Enseigner est apprendre - Céline Martinet Sanchez</a></li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#15h30--linterpreteur-python-quel-sale-type---serge-sans-paille">15h30 : L'interprêteur Python, quel sale type - serge-sans-paille</a></li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#16h30--scalable--distributed-applications-in-python---julien-danjou---red-hat">16h30 : Scalable & distributed applications in Python - Julien Danjou - Red Hat</a></li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#17h30--as-tu-deja-pense-a-contribuer-a-cpython---stephane-wirtel">17h30 : As-tu déjà pensé à contribuer à CPython - Stéphane Wirtel</a></li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#18h30--cest-quoi-etre-different----haikel-guemar">18h30 : C'est quoi être différent ? - Haïkel Guémar</a></li>
</ul>
</li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#jour-3---dimanche-24-septembre">Jour 3 - dimanche 24 septembre</a><ul>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#11h--frets-on-fire-x-et-son-ecosysteme-apres-11-ans---francois-magimel">11h : Frets On Fire (X) et son écosystème après 11 ans - François Magimel</a></li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#12h--nuka---liberez-le-vilain-devops-qui-est-en-vous---gael-pasgrimaud">12h : nuka - libérez le vilain devops qui est en vous - Gael Pasgrimaud</a></li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#14h--construire-et-gerer-un-operateur-internet-en-python---florian-vichot---wifirst">14h : Construire et gérer un opérateur Internet en Python - Florian Vichot - Wifirst</a></li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#16h30--organisez-vos-conferences-avec-ponyconf---elie-bouttier">16h30 : Organisez vos conférences avec PonyConf - Elie Bouttier</a></li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#17h--python-and-poland-directories---christopher-lozinski">17h : Python and Poland Directories - Christopher Lozinski</a></li>
</ul>
</li>
<li><a href="/compte-rendu-de-la-pyconfr-2017.html#bilan">Bilan</a></li>
</ul>
<!-- tocstop -->
<h1>Jour 1 - vendredi 22 septembre</h1>
<p>La conférence était hébergée par l'ENSEEIHT à Toulouse.
Durant les deux jours de sprints nous étions tous rassemblés dans une grande salle classe.</p>
<p>Il était proposé de contribuer à des projets Python parmi <a href="https://www.pycon.fr/2017/programme.html#sprints">une dizaine de présents</a>.
A vue de nez, il y avait dans l'après-midi une soixante de personne concentrées sur leurs ordinateurs portables,
regroupées par petits groupes autour de quelques tables.
Un développeur connaissant le projet était sur place pour le présenter,
indiquer quelques bugs faciles et accompagner les contributeurs.</p>
<p>J'ai personnellement effectué quelques contributions mineures à deux projets:</p>
<h2>python-docs-fr</h2>
<p>Cette initiative de <a href="https://mdk.fr">Julien Palard</a> vise à traduire la documentation de la bibliothèque standard Python en français.
Il m'a dit s'être attelé à ce chantier pour passer le temps lors de longs trajets en RER :)
C'est un sacré chantier, chapeau bas à lui de l'avoir entamé !</p>
<p><span style="font-size: 3rem">🙏</span></p>
<p>Ça a été l'occasion pour moi de découvrir <a href="https://poedit.net">Poedit</a>, un éditeur de fichiers <code>.po</code> de traductions (standard <a href="https://fr.wikipedia.org/wiki/GNU_gettext">gettext</a>).</p>
<p>Suite à un rapide échange avec Julien, j'ai soumis une <em>pull-request</em> pour remplacer tous les usages du verbe "retourner" par "renvoyer" ou "revenir" afin d'éviter tout ambiguïté: <a href="https://github.com/python/python-docs-fr/pull/29">https://github.com/python/python-docs-fr/pull/29</a></p>
<p>A noter qu'il est possible, et encore plus simple, de contribuer à cette traduction via <a href="https://www.transifex.com/python-doc/public/">https://www.transifex.com/python-doc/public/</a></p>
<p>J'ai ensuite commencé à jeter un oeil à un bug concernant le changement de langage dans l'interface web de la doc Python (<a href="http://bugs.python.org/issue31146">http://bugs.python.org/issue31146</a>), mais je ne suis pas allé au bout au cours de ce sprint.</p>
<h2>ideascube</h2>
<p>Avec beaucoup de patience, <a href="https://mathieu.daitauha.fr/blog/">Mathieu Bridon</a> et <a href="https://mgautier.fr">Matthieu Gauthier</a> de <a href="https://kymeria.fr">Kymeria</a> m'ont guidé à travers <a href="https://framagit.org/ideascube/ideascube/merge_requests?scope=all&utf8=%E2%9C%93&state=all&author_username=Lucas-C">quelques corrections de bugs</a> sur le projet <strong>ideascube</strong>.</p>
<p>Ce projet de <em>Bibliothèques Sans Frontières</em> est une application Django motorisant les <a href="http://www.ideas-box.org">Ideas Box</a>.</p>
<p><img alt="IdeasBox: portable media-center for refugee and vulnerable populations" src="images/2017/09/IdeasBox.jpg"></p>
<p>Mathieu Bridon en parle plus en détails sur son blog: <a href="https://mathieu.daitauha.fr/blog/2017/09/25/de-retour-de-pycon-fr-et-du-sprint-ideascube/">https://mathieu.daitauha.fr/blog/2017/09/25/de-retour-de-pycon-fr-et-du-sprint-ideascube/</a></p>
<p>Pour en savoir un peu plus sur ces projets de BSF, je vous invite à écoute l'émission récente de la radio Nova sur le sujet:
<a href="http://www.nova.fr/podcast/neo-geo/neo-geo-gren-seme-de-la-reunion-et-jeremy-lachal-de-bibliotheque-sans-frontieres">http://www.nova.fr/podcast/neo-geo/neo-geo-gren-seme-de-la-reunion-et-jeremy-lachal-de-bibliotheque-sans-frontieres</a></p>
<p>J'ai également découvert à cette occasion <a href="http://wiki.kiwix.org">Kiwix</a>, un beau projet à but humanitaire dont bénéficient plus d'1 million d'utilisateurs chaque année:</p>
<blockquote>
<p><strong>Tout le monde n'a pas accès à Internet</strong></p>
<p>Et si c'est le cas, il y a de grandes chances que celui-ci soit lent, erratique ou simplement censuré. Kiwix est une solution hors-ligne qui permet à tout un chacun de consulter des contenus éducatifs tels que Wikipédia, le Wiktionnaire, la bibliothèque Gutenberg et bien d'autres encore - ce sur n'importe quel ordinateur ou smartphone, et sans qu'il y ait besoin d'avoir une connexion permanente à Internet.</p>
<p><strong>Kiwix est présent dans des écoles, des universités, et même des prisons</strong></p>
<p>Et bien sûr à la maison. Plus rapide qu'une connexion internet, il permet d'économiser de la bande passante et du temps de chargement sur les connexions lentes. Facile à installer et peu gourmand en ressources, Kiwix fonctionne sur les vieux ordinateurs et ceux à faible puissance. Il est disponible sur la plupart des plateformes, d'Android et iOS à Microsoft Windows, macOS et bien sûr GNU/Linux.</p>
</blockquote>
<h1>Jour 2 - samedi 23 septembre</h1>
<p>Début des conférences !</p>
<p>Je donne mon premier <em>talk</em> à 10h.</p>
<p>A noter que cette année, de nombreuses conférences étaient signées en simultané par des traducteurs en langue des signes.</p>
<h2>10h30 : Plone, 15 ans d'expérience feront toujours la différence - Eric Bréhault - Makina Corpus</h2>
<p>Présentation de <a href="http://plone.fr">plone</a>, "seul" CMS Python, et de ses très nombreuses fonctionnalités <em>built-in</em> (ex: breadcrumbs depuis 2001).</p>
<p>Très sécuritaire, il est notamment utilisé par le FBI.</p>
<p>En cours de refonte pour Python 3 (Zope 4), avec un nouveau projet : <strong>Guillotina</strong>, version purement <em>headless</em> se basant sur <code>aiohttp</code>, Elasticsearch et une base donnée similaire à ZODB par-dessus de PostgreSQL ou Cockroach.</p>
<p>Ce CMS est déployable sur Heroku et apparemment extrêmement simple à prendre en main.</p>
<p>Vidéo: <a href="https://www.youtube.com/watch?v=FFIK3neBhMI&list=PLetYPqNT2qjAinIBr976XSjJObaa-zUy5&index=1">https://www.youtube.com/watch?v=FFIK3neBhMI&list=PLetYPqNT2qjAinIBr976XSjJObaa-zUy5&index=1</a></p>
<h2>11h30 : Les Aventuriers du Packaging Perdu - Joachim Jablon & Stéphane Angel</h2>
<p>Présentation très complète des bonnes pratiques de packaging en Python.</p>
<p>Ils m'ont convaincu à transformer le <code>setup.py</code> de mes projets en <code>setup.cfg</code>,
ce que j'ai fait en parallèle de leur <em>talk</em> ^^</p>
<p>La liste d'outils à la fin de leurs <em>slides</em> vaut vraiment le coup d'être récupérée.</p>
<h2>14h : Enseigner est apprendre - Céline Martinet Sanchez</h2>
<p>Un retour d'expérience très intéressant de cette formatrice et rédactrice de cours sur <a href="https://openclassrooms.com/membres/celinemartinet">OpenClassrooms</a> portant sur tout ce que peut apporter l'enseignement.</p>
<p>Elle prêchait un convaincu dans mon cas, mais elle a aussi donné quelques très bons conseils sur comment créer un bon cours :</p>
<ul>
<li>fournir des exemples concrets et des définitions abstraites</li>
<li>le bonheur est dans le chemin et dans la finalité</li>
<li>contenu différenciant</li>
<li>détaillez toutes les étapes, même les + petites</li>
<li>Soyez drôle ! Donnez envie !</li>
</ul>
<p>Enfin, elle a apporté des éléments de réponse à la passionnante question "Qu'est-ce qu'être développeur senior ?"</p>
<h2>15h30 : L'interprêteur Python, quel sale type - serge-sans-paille</h2>
<p>En très bref, une belle démonstration des limites de la validation statique de code Python typé via des annotations, via <code>mypy</code> par exemple.</p>
<p>Personnellement il m'a convaincu.</p>
<p>Au passage, selon les critères que l'on choisit pour définir la notion d'objet itérable (existence de la méthode <code>__iter__</code>, <code>isinstance(obj, Iterable)</code>, etc.),
des objects comme celui-ci peuvent donner des résultats surprenants:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="n">Infinity</span>(<span class="n">objet</span>):
<span class="n">def</span> <span class="n">__getitem__</span>(<span class="nb">self</span>, <span class="n">_</span>)
<span class="k">return</span> <span class="mi">0</span>
</code></pre></div>
<p>La présentation était basée sur un <em>notebook</em> Jupyter permettant d'évaluer du code Python en <em>live</em>, avec un <em>plugin</em> de présentation <code>reveal.js</code>.</p>
<p>Ce <em>talk</em> s'est conclu par une très intéressante discussion avec Serge et Victor Stinner, ou j'ai découvert <a href="https://www.python.org/dev/peps/pep-0436/">Argument Clinic</a>, un outil CPython visant à:</p>
<ul>
<li>fournir des information sur la signature des fonctions <em>builtin</em></li>
<li>permettre à des implémentations alternatives de Python de créer des tests de compatibilité automatisés</li>
<li>améliorer les performances du code gérant la lecture de paramètres de fonctions</li>
</ul>
<h2>16h30 : Scalable & distributed applications in Python - Julien Danjou - Red Hat</h2>
<p>Une présentation très complète détaillant beaucoup de conseils pour concevoir des applications distribuées en Python,
par le développeur de <a href="https://github.com/gnocchixyz/gnocchi">Gnocchi</a>.</p>
<p><img src="images/2017/09/gnocchi-logo.png" alt="Logo Gnocchi" style="width: 20rem"></p>
<p>Les <em>slides</em> de ce <em>talk</em> étaient très complets. Voici quelques notes en pagaille qui en sont issues.</p>
<p><em>Threads: how to</em></p>
<ul>
<li><strong>Concurrent.futures</strong></li>
<li><strong>Futurist</strong>: provide backlog control & statistics</li>
</ul>
<p><strong>GIL</strong> => surtout utile pour:</p>
<ul>
<li>async I/O</li>
<li>computation without Python datastructures</li>
<li>running C extensions in parallel</li>
</ul>
<p><em>Multi-processes: how to</em></p>
<ul>
<li>pas intéressant pour exécutions courtes</li>
<li><strong>Concurrent.futures</strong></li>
<li><strong>Cotyledon</strong>: baby-sitting de daemons</li>
</ul>
<p>Système distribué ⚠ => plein de nouveaux failure modes
- node failure
- network failure
- latency</p>
<p><strong>Use queue based systems</strong>: Celery, <code>rq</code></p>
<p>Projet Python implémentant plein de locks ou de coordinateurs: <code>Tooz</code></p>
<p>⚠ Attention! à ce que le gestionnaire de locks ne devienne pas un SPOF</p>
<p>Caching:</p>
<ul>
<li><code>cachetools</code> : gère TTL, fourni stats</li>
<li><code>functools.lru_cache</code></li>
<li><code>dogpile.cache</code> : agnostique de l'outil derrière -> Redis, memcache, (Varnish?)</li>
</ul>
<p>Questions:</p>
<ul>
<li>quid. antirez/disque ? (vs <code>rq</code>)</li>
<li>dead letter queue ?</li>
</ul>
<h2>17h30 : As-tu déjà pensé à contribuer à CPython - Stéphane Wirtel</h2>
<p>Stéphane, organisateur du FOSDEM Python, a démystifié toutes les appréhensions qu'on pouvait avoir sur le sujet.
Il nous a expliqué pas à pas comment se déroulent les contributions sur CPYthon,
les personnes qui peuvent nous aider et toutes les ressources à disposition :</p>
<ul>
<li><a href="https://www.python.org/dev/core-mentorship/">https://www.python.org/dev/core-mentorship/</a></li>
<li>le <a href="https://docs.python.org/devguide/">devguide</a></li>
<li>les <a href="https://www.python.org/community/lists/">mailing-lists</a></li>
</ul>
<p>Il nous a mentionné une phrase de Brett Cannon:</p>
<blockquote>
<p>Je suis venu pour le langage, et je suis resté pour la communauté.</p>
</blockquote>
<p>Quelques anecdotes découvertes au passage:</p>
<ul>
<li>
<p>l'intégration continue de CPYthon se fait via des instances <a href="http://buildbot.net">buildbot</a> hébergées par des bénévoles,
dont l'exécution est déclenchée à distance, ce qui peut mener des <a href="https://vstinner.github.io/python-buildbots-2017q2.html#the-vacuum-cleaner">échanges assez drôles</a> sur la mailing-list.</p>
</li>
<li>
<p>suite à l'incident de sécurité sur <a href="https://pypi.org">https://pypi.org</a> (paquets typosquattés) qui a refait surface ce mois-ci,
de nombreux commentaires violemment critiques ont été postés sur des sites comme Hacker News, accusant directement
les mainteneurs de pypi et les devs CPython.</p>
</li>
</ul>
<p>Quand on sait que, quel que soit le sondage, Python est systématiquement dans le top 5 des langages les plus utilisés au monde,
c'est assez fou de réaliser que les <em>core devs</em> Python sont quasi-tous de volontaires.</p>
<p><strong>Une seule personne au monde est rémunérée par la PSF</strong> (pour administrer les sites web Python officiels).
Un <em>core dev</em> est également autorisé par son employeur à consacrer la majeure partie de son temps à Python.
En dehors de ces deux personnes, <strong>Python est entièrement développé et maintenu par des bénévoles sur leur temps libre !!</strong></p>
<p>Vidéo: <a href="https://www.youtube.com/watch?v=iEDFRCNDZNE&list=PLetYPqNT2qjAinIBr976XSjJObaa-zUy5&index=6">https://www.youtube.com/watch?v=iEDFRCNDZNE&list=PLetYPqNT2qjAinIBr976XSjJObaa-zUy5&index=6</a></p>
<h2>18h30 : C'est quoi être différent ? - Haïkel Guémar</h2>
<p>J'ai raté le début de ce <em>talk</em>, dont le but était de faire prendre conscience des discriminations existantes,
ainsi que des moyens à notre disposition pour les contrecarrer. J'ai aimé en particulier le passage sur l'illusion de la méritocratie.</p>
<p>Voici quelques unes de ses recommandations:</p>
<p>Que faire concrètement ?</p>
<ul>
<li>admettre l'existence du biais</li>
<li>mesurer les inégalités (salaire, postes)</li>
</ul>
<p>Théorie de la fenêtre brisée:</p>
<ul>
<li>certains propos n'ont pas leur place dans le lieu de travail</li>
<li>rendre l'environnement accueillant pour tous</li>
<li>ne pas se taire</li>
</ul>
<p>Ne pas reproduire les schémas:</p>
<ul>
<li>éducation</li>
<li>remettre en question les choix établis</li>
<li>charité bien ordonnée commence par soi même</li>
</ul>
<p>Briser les barrières mentales:</p>
<ul>
<li>donner un cadre sécuritaire pour les minorités de s'exprimer et apprendre</li>
<li>donner des exemples</li>
<li>ne pas confondre considération et condescendances</li>
</ul>
<h1>Jour 3 - dimanche 24 septembre</h1>
<p>Le matin avait lieu l'AG annuelle de l'AFPY, dont voici le CR : <a href="https://www.afpy.org/news/pv-de-lassemblee-generale-ordinaire-2017">https://www.afpy.org/news/pv-de-lassemblee-generale-ordinaire-2017</a></p>
<h2>11h : Frets On Fire (X) et son écosystème après 11 ans - François Magimel</h2>
<p>Projet gagnant de l'<em>Assembly demo party</em> 2006, issue à l'origine d'une boîte suédoise,
l'aventure continue pour clone de GuitarHero libre, <em>cloné</em> de nombreuses fois.</p>
<p><img src="images/2017/09/frets-on-fire.png" alt="Illustration du jeu"
style="max-height: 15rem"></p>
<p>La dernière mouture activement maintenue s'appelle <a href="http://fretsonfire.wikidot.com/fofix-install">FoFiX</a>.</p>
<p>Les technos employées sous le capot: pygame, OpenGL, NumPy, Cython. Et pour les musiques: midi, OGG, Vorbis.</p>
<p>Et, chose que je trouve absolument géniale, un chercheur canadien a adapté <em>Frets On Fire</em> pour les personnes en situation de handicap visuel:
<a href="http://www.eelke.com/projects/blindhero-guitarhero-visual-impairment.html">http://www.eelke.com/projects/blindhero-guitarhero-visual-impairment.html</a></p>
<h2>12h : nuka - libérez le vilain devops qui est en vous - Gael Pasgrimaud</h2>
<p>Présentation d'un outil de déploiement fonctionnellement proche d'Ansible: <a href="https://github.com/bearstech/nuka">nuka</a></p>
<p><img src="images/2017/09/bearstech-logo.jpg" alt="Logo bearstech"
style="max-height: 10rem"></p>
<p>A bearstech ils utilisaient initialement un outil maison, <a href="https://github.com/bearstech/pussh">pussh</a>, très robuste,
mais leurs clients souhaitaient quelque chose de plus moderne / sexy.</p>
<p>Ansible ne convenait pas:</p>
<ul>
<li>ils ne voulaient pas d'agent sur les serveurs</li>
<li>il comporte des limitations en terme de performance pas solubles facilement,
par exemple l'utilisation du module <code>mulitprocessing</code> générant un nouveau <em>process</em> à chaque commande exécutée</li>
</ul>
<p>Cette solution:</p>
<ul>
<li>est compatible avec des déploiement dans le "cloud" via l'utilisation de la lib <code>libcloud</code></li>
<li>nécessite Python3 localement, mais le serveur à distance peut se contenter d'un Python 2.6+</li>
<li>inclus un système d’introspection de la <em>stack</em> d'appels Python pour éviter les redites de configuration 😱</li>
<li>supporte <code>gpg</code></li>
</ul>
<p><em>Benchmark</em> indicatif: 296 exécution de la commande <code>ls</code> en 15s (contre ~40s avant)</p>
<p>Astuce: pour gagner en vitesse, changer la priorité système du processus <code>ssh-agent</code></p>
<h2>14h : Construire et gérer un opérateur Internet en Python - Florian Vichot - Wifirst</h2>
<p>Une des présentations les plus détaillées et pointue techniquement que j'ai vu de la conférence.
Je n'ai pas tout saisi des aspects "infrastructure physique du réseau", mais dans l'ensemble
c'était un retour d'expérience très complet et très clair.</p>
<p>Je n'ai par contre pris que très peu de notes.</p>
<p>Ils utilisent extensivement <code>pyroute2</code>, une <em>lib</em> permettant de se connecter à une socket Netlink et de configurer tout ce que permet <code>iproute2</code>.</p>
<p>Ils ont atteint les limites d'Ansible:</p>
<ul>
<li>→ lenteur 😞</li>
<li>→ ont développé un module de <em>callback</em> listant des résultats d'exécution plus complets</li>
<li>→ quelques problèmes avec les mauvais réseaux (type satellite) pour SSH</li>
<li>→ <code>/bin/true</code> 4min30 vs <code>/bin/false</code> 22min</li>
</ul>
<h2>16h30 : Organisez vos conférences avec PonyConf - Élie Bouttier</h2>
<p>Une présentation de l'appli libre Django qu'ils ont développé et utilisé pour organiser PyCon:
<a href="https://github.com/toulibre/ponyconf">https://github.com/toulibre/ponyconf</a></p>
<p>La liste de fonctionnalités est assez impressionnante ! (elle est détaillée dans ses <em>slides</em>)
Ils ont fait un sacré beau boulot, et prévoient encore pas mal d'améliorations.</p>
<p>Clairement l'outil que j'utiliserais pour organiser une conf.</p>
<h2>17h : Python and Poland Directories - Christopher Lozinski</h2>
<p>Présentation de <a href="https://pythonlinks.info">https://pythonlinks.info</a>.</p>
<p>Plutôt que de répéter son contenu, je vous invite à regarder la vidéo d'introduction au projet sur la page d’accueil du site.</p>
<p>Vidéo: <a href="https://www.youtube.com/watch?v=eZynCkvjjhE&list=PLetYPqNT2qjAinIBr976XSjJObaa-zUy5&index=4">https://www.youtube.com/watch?v=eZynCkvjjhE&list=PLetYPqNT2qjAinIBr976XSjJObaa-zUy5&index=4</a></p>
<h1>Bilan</h1>
<p>Il y a au moins deux conférences qui m'avaient l'air très intéressants mais auxquelles, absence d'ubiquité oblige, je n'ai pas pu assister:
celle sur <a href="https://github.com/Z3Prover/z3">le solver Z3</a> de Michael Scherer, et celle sur l'histoire du standard Unicode de Guillaume Ayoub.</p>
<p>Les présentations ont été filmées: <a href="https://www.youtube.com/playlist?list=PLetYPqNT2qjAinIBr976XSjJObaa-zUy5">https://www.youtube.com/playlist?list=PLetYPqNT2qjAinIBr976XSjJObaa-zUy5</a></p>
<p>A noter qu'en parallèle de la conférence, un "Crypto challenge" était organisé.
Je ne me suis essayé qu'aux premières étapes (un bot <a href="https://riot.im">https://riot.im</a> était impliqué),
mais à ce qu'on m'a dit il était tordu à souhait !</p>
<p>Enfin, ça a surtout été l'occasion de faire de chouettes rencontres: Stéphane, Kevin, Damien, Florian, Hugo, Ludo, Antoine, Victor...
Au plaisir de vous revoir à la prochaine convention ;)</p>
<script>
['h1', 'h2'].forEach(function (selector) {
document.querySelectorAll(selector).forEach(function (title) {
if (!title.classList.length) {
title.id = title.textContent
.toLowerCase()
.replace(/[()?!:,'&@]/g, '')
.replace(/[à]/g, 'a')
.replace(/[ç]/g, 'c')
.replace(/[éêè]/g, 'e')
.replace(/[ï]/g, 'i')
.replace(/ /g, '-');
}
});
});
</script>Using a different palette as admin in cmder2017-09-18T12:00:00+02:002017-09-18T12:00:00+02:00Lucas Cimontag:chezsoi.org,2017-09-18:/lucas/blog/using-a-different-palette-as-admin-in-cmder.html<p>In the last years, <a href="http://cmder.net">cmder</a> became my default console when I needed a <code>cmd.exe</code>-compatible Windows console.</p>
<p>Very often, I have a <code>cmder</code> window with a set of tabs under my standard Windows user, and another that I launch as admi, to switch on/off some services, e.g …</p><p>In the last years, <a href="http://cmder.net">cmder</a> became my default console when I needed a <code>cmd.exe</code>-compatible Windows console.</p>
<p>Very often, I have a <code>cmder</code> window with a set of tabs under my standard Windows user, and another that I launch as admi, to switch on/off some services, e.g. MySQL daemon. And in the last weeks, I happened to confuse both a few times, leading me to create a few files owned by the Windows admin :(</p>
<p>Hence I decided to find a way to change the palette when in an admin <code>cmder</code> console.</p>
<p>I found what I was looking for among <a href="https://github.com/cmderdev/cmder/issues/370#issuecomment-263082897">its Github project issues</a>. Simply create a wrapping <code>cmder.bat</code> batch script in the your <code>cmder</code> root directory with this :</p>
<div class="highlight"><pre><span></span><code><span class="err">@</span><span class="n">echo</span> <span class="n">off</span>
<span class="n">set</span> <span class="n">CMDER_ROOT</span><span class="o">=%~</span><span class="n">dp0</span>
<span class="n">start</span> <span class="o">%</span><span class="n">CMDER_ROOT</span><span class="o">%</span>\<span class="n">vendor</span>\<span class="n">conemu</span><span class="o">-</span><span class="n">maximus5</span>\<span class="n">ConEmu</span><span class="o">.</span><span class="n">exe</span> <span class="o">/</span><span class="n">icon</span> <span class="s2">"%CMDER_ROOT%\cmder.exe"</span> <span class="o">/</span><span class="n">single</span> <span class="o">/</span><span class="n">title</span> <span class="n">Cmder</span> <span class="o">/</span><span class="n">loadcfgfile</span> <span class="s2">"%CMDER_ROOT%\config\ConEmu-%USERNAME%.xml"</span> <span class="o">/</span><span class="n">cmd</span> <span class="n">cmd</span> <span class="o">/</span><span class="n">k</span> <span class="s2">"%CMDER_ROOT%</span><span class="se">\v</span><span class="s2">endor\init.bat cd %CD%</span>
</code></pre></div>
<p>Notice that the name of the <code>ConEmu</code> configuration file (the underlying console emulator used by <code>cmder</code>) is <code>ConEmu-%USERNAME%.xml</code>.</p>
<p>Now, in the <code>config/</code> sub-directory, make a copy of the default <code>ConEmu.xml</code> file for every user you want to launch <code>cmder</code> as and name like that: <code>ConEmu-%USERNAME%.xml</code>.</p>
<p>Finally, launch <code>cmder</code> as admin, right click on the title bar of the window and choose "Settings", go to "Features > Colors" and choose another palette, like "Solarized Light". "Save settings" and you're done !</p>
<p><a href="images/2017/09/cmder-2-consoles-screenshot.png"><img alt="Screenshot of 2 cmder consoles with different users" src="images/2017/09/cmder-2-consoles-screenshot.png"></a>)</p>Meetup Python à Nantes le 3 octobre2017-09-11T12:00:00+02:002017-09-11T12:00:00+02:00Lucas Cimontag:chezsoi.org,2017-09-11:/lucas/blog/meetup-python-a-nantes-le-3-octobre.html<p><img src="images/2017/09/afpy-nantes.jpg" alt="Logo AFPY à Nantes" class="column-img"></p>
<p>Avec Thomas Durey, nous organisons un meetup à Nantes mardi 3 octobre dans les locaux de <a href="https://open.voyages-sncf.com/groupe/qui-sommes-nous">Voyages-Sncf.com</a> avec l'<a href="http://nantes.afpy.org/meetup-du-mois-doctobre-2017-a-voyages-sncfcom-technologies-nantes.html">AFPY</a> pour échanger autour du langage Python !</p>
<p>Au programme :</p>
<ul>
<li>un <em>talk</em> sur les pipelines <a href="https://github.com/spotify/luigi">luigi</a> et comment les tester avec <a href="https://behave.readthedocs.io/en/latest/">behave</a></li>
<li>nous vous proposerons ensuite de participer à un barcamp …</li></ul><p><img src="images/2017/09/afpy-nantes.jpg" alt="Logo AFPY à Nantes" class="column-img"></p>
<p>Avec Thomas Durey, nous organisons un meetup à Nantes mardi 3 octobre dans les locaux de <a href="https://open.voyages-sncf.com/groupe/qui-sommes-nous">Voyages-Sncf.com</a> avec l'<a href="http://nantes.afpy.org/meetup-du-mois-doctobre-2017-a-voyages-sncfcom-technologies-nantes.html">AFPY</a> pour échanger autour du langage Python !</p>
<p>Au programme :</p>
<ul>
<li>un <em>talk</em> sur les pipelines <a href="https://github.com/spotify/luigi">luigi</a> et comment les tester avec <a href="https://behave.readthedocs.io/en/latest/">behave</a></li>
<li>nous vous proposerons ensuite de participer à un barcamp autours de Python, dont vous proposerez les sujets. L'idée est simplement de se retrouver et de décider sur place des sujets de discussions qui vous intéressent, de les aborder ensemble en différents groupes, puis de mettre en commun ce qui s'est dit pendant les ateliers.</li>
</ul>
<p>Nous souhaiterions avoir plus de sujets, donc n'hésitez pas à proposer votre talk !</p>
<p>Un buffet dinatoire sera servi pour l'occasion. RDV dès 18h30 pour débuter les talks à 19h.</p>
<p>Si vous avez des questions ou des remarques concernant le meetup, contactez-nous sur la liste de diffusion : <a href="http://lists.afpy.org/mailman/listinfo/nantes">http://lists.afpy.org/mailman/listinfo/nantes</a></p>
<p>Le meetup est gratuit mais il faut s'inscrire sur <a href="http://www.meetup.com/Nantes-Python-Meetup">http://www.meetup.com/Nantes-Python-Meetup</a></p>
<p><br></p>
<p><u>Accès:</u> Voyages-Sncf.com est situé au 5e étage du bâtiment Jalais dont l'entrée principale se trouve au 34 rue du Pré Gauchet :</p>
<p><a href="https://www.openstreetmap.org/search?query=34%20rue%20du%20Pr%C3%A9%20Gauchet%2C%20nantes">
<img alt="Plan d'accès au 34 rue du Pré Gauchet, Nantes"
src="https://chezsoi.org/lucas/PlanAcces_VoyagesSncfTechnologies_34rueDuPreGauchetNantes.png"
style="max-width: 100%">
</a></p>
<p><br></p>
<p><strong>EDIT [2017/10/12]</strong>: un compte-rendu de ce meetup est disponible sur le site de l'AFPY nantaise:
http://nantes.afpy.org/compte-rendu-du-meetup-du-mois-doctobre-2017.html</p>
<style>
@media screen and (min-width: 40rem) {
.column-img {
max-width: 40%;
float: left;
padding: 2rem;
}
}
@media screen and (max-width: 40rem) {
.column-img {
max-width: 60%;
margin: 0 auto;
display: block;
}
}
.clear-floats {
clear: both;
}
article li {
position: relative;
left: 1em;
}
</style>Première partie test de Blades In The Dark2017-09-09T16:00:00+02:002017-09-09T16:00:00+02:00Lucas Cimontag:chezsoi.org,2017-09-09:/lucas/blog/premiere-partie-test-de-blades-in-dark.html<p>J'avais déjà évoqué ce jeu de manière très rapide dans <a href="/lucas/blog/jdr-notes-de-lectures-du-quickstart-de-blades-in-the-dark.html">un post précédent</a>.</p>
<p>Voici un petit compte-rendu de notre première partie d'il y a quelques semaines, qui a été un franc succès je crois :)</p>
<h2>Le gang du Harpon</h2>
<p><a href="images/2017/09/f8023ad16d6734862e3899c200b0d612.jpg"><img src="images/2017/09/f8023ad16d6734862e3899c200b0d612.jpg" alt="Ludius Dalmore" title="Ludius Dalmore"></a></p>
<ul>
<li><strong>Ludius Dalmore</strong> : chef du gang; ancien capitaine de navire chasseur de léviathans …</li></ul><p>J'avais déjà évoqué ce jeu de manière très rapide dans <a href="/lucas/blog/jdr-notes-de-lectures-du-quickstart-de-blades-in-the-dark.html">un post précédent</a>.</p>
<p>Voici un petit compte-rendu de notre première partie d'il y a quelques semaines, qui a été un franc succès je crois :)</p>
<h2>Le gang du Harpon</h2>
<p><a href="images/2017/09/f8023ad16d6734862e3899c200b0d612.jpg"><img src="images/2017/09/f8023ad16d6734862e3899c200b0d612.jpg" alt="Ludius Dalmore" title="Ludius Dalmore"></a></p>
<ul>
<li><strong>Ludius Dalmore</strong> : chef du gang; ancien capitaine de navire chasseur de léviathans; très sec, avec une grande barbe; connu pour avoir fracassé des bâteaux dans le port de Duskwall; champion du vol de marchandise en cours de transport; se bat avec 2 haches</li>
<li><strong>Ethnos</strong> (PJ: <em>Hound</em>) : noble des "Dagger Isles"; très pieux et repentant; ancien freelance recruté dans le gang par la soeur de Ludius</li>
<li><strong>Eric Stamp</strong> (PJ: <em>Cutter</em>) : féroce combattant originaire de Tycheros, dont la violence est à la hauteur de la réputation de démons qu'ont ces "étrangers"; recruté après avoir détruit un bâtiment entier alors que le gang opérait un casse à proximité</li>
<li><strong>Elaria</strong> (PJ: <em>Lurk</em>) : voleuse originaire d'une famille pauvre de travailleurs d'Akoros, recueillie par Ludius très jeune; a fait de la prison pour cambriolage; toxicomane</li>
<li><strong>Melvir</strong> : <em>physicker</em>, vieil ami de Ludius du temps où ils naviguaient</li>
</ul>
<figure role="group">
<a href="images/2017/09/PZO8500Dargley_500.jpeg"><img src="images/2017/09/PZO8500Dargley_500.jpeg" alt="Melvir"></a>
<figcaption>Melvir - Artwork: "Dargley" par <a href="https://athayar.deviantart.com/art/Pathfinder-Dargley-400349532">CavalierediSpade</a></figcaption>
</figure>
<p><strong>Type de gang:</strong> Thugs</p>
<p><strong>Réputation:</strong> audacieux</p>
<p><strong>Planque:</strong> vieux gallion en calle sèche à Crow's Foot</p>
<p><strong>Gang nemesis:</strong> l'Oeil blanc</p>
<h2>Résumé de partie</h2>
<p>(pour la bande son: <a href="https://www.youtube.com/watch?v=pxKq0eQaSk0">Dishonored 2 OST</a>)</p>
<p>Tout commence par une mission assez simple pour le gang: s'infiltrer sur un navire de la flotte impériale,
afin d'y dérober un très vieux et précieux journal de bord, détaillant les routes maritimes empruntées par les léviathans.</p>
<figure role="group">
<a href="images/2017/09/ship-infiltration.jpg"><img src="images/2017/09/ship-infiltration.jpg" alt="Infiltration sur le navire de Drav Walund"></a>
<figcaption>Concept art de <a href="http://conceptartworld.com/news/thief-concept-art-by-mathieu-latour-duhaime/">Mathieu Latour-Duhaime</a></figcaption>
</figure>
<p>Ethnos & Elvira abordent discrètement le navire en barque, de nuit, tandis qu'Eric se charge de créer une diversion sur le ponton des docks,
déguisé en homme de la milice de Duskwall.
De son côté, Ludius a trouvé un prétexte pour être à bord : via ses anciens contacts marins, il a demandé à rencontrer le capitaine,
<strong>Drav Walund</strong>, en prétendant vouloir être recruté.</p>
<figure role="group">
<a href="images/2017/09/f7e12590ecc0f025dc93f885c6fc92cc.jpg"><img src="images/2017/09/f7e12590ecc0f025dc93f885c6fc92cc.jpg" alt="Drav Walund"></a>
<figcaption>Drav Walund - Artwork de <a href="https://www.artstation.com/crazybrush">Goran Bukvic</a></figcaption>
</figure>
<blockquote>
<p>Alors comme ça, tu veux rejoindre notre équipage comme officier ?</p>
</blockquote>
<p>Ethnos & Elvira escaladent le gaillard arrière du galion, et éliminent discrètement une sentinelle,
quand tout d'un coup des éclats de voix se font entendre sur le pont principal, où l'équipage est rassemble autour de Ludius & Drav.</p>
<blockquote>
<p>Croyais-tu vraiment que je te reconnaîtrais pas ? Traître ! Tu vas payer !</p>
</blockquote>
<p>Visiblement Ludius est tombé sur une vieille connaissance, et sa couverture n'a pas fait long feu.
Il est rapidement désarmé, jeté sol, et un filet électrostatique est jeté sur lui.</p>
<blockquote>
<p>La garde ne devrait plus tarder, mais avant je vais m'amuser avec toi...</p>
</blockquote>
<p>Comprennant que le plan tourne au vinaigre, Eric monte sur le galion par la passerelle le reliant aux docks,
après avoir habilement bluffé les deux gardes en faction. Il tente de s'interposer pour faire cesser cette scène de torture,
mais le jeune quartier-maître du bateau, <strong>Branon Grime</strong>, s'interpose.</p>
<p>Un peu plus loin dans le navire, Elvira décide d'accomplir tout de même la mission et s'introduit dans la cabine du capitaine.
Elle finit par découvrir un coffre en fonte caché derrière un tableau, malheureusement impossible à ouvrir.</p>
<p>Sur le pont, on pousse Ludius, gravement électrocuté par le filet, pour subir le traditionnel supplice de la planche.
Drav écourte cependant la chose, voyant la milice arriver (Eric déguisé), et l'abat d'une balle de mousquet.</p>
<p>Ethnos, qui avait anticipé la situation, est déjà dans l'eau sous la planche.
Il réceptionne Ludius, à demi-mort, et le ramène à la barque.</p>
<p>L'instant suivant, c'est Elvira qui fait basculer le coffre en fonte depuis la cabine du capitaine dans la barque.
Elle les rejoint ensuite d'un saut, en décochant dans sa chute un couteau fatal à un homme d'équipage qui se jettait sur elle.</p>
<p>Ils sont finalement rejoint par Eric, qui après être sorti de son rôle en attaquant violement Drav Walund,
s'est jeté à la mer pour les rejoindre, alors que sur le port une explosion retentit :
les tonneaux de poudre que la gang avait prévu de faire exploser pour couvrir leur fuite en cas de mauvaise surprise.</p>
<hr>
<p>Les PJs retrouvent Melvir à la planque, où il devient très vite évident que Ludius risque d'y passer.</p>
<p>Ils montent rapidement un plan pour kidnapper un médecin de la caste des nobles.
Elvira contacte son amie noble <strong>Roselyn Koellis</strong>, qui accepte de lui donner les renseignements nécessaire:
tous les matins à la même heure, <strong>Stravul Comber</strong>, un chirurgien renommé, prend un téléphérique électrostatique de son domicile
à l'hôpital où il travaille.</p>
<p>Les PJs décident d'en profiter pour lui mettre le grapin dessus.</p>
<p>En résumé, l'opération est un succès, malgré une victime collatérale (un garde du corps un peu trop zêlé dans le téléphérique),
et une chute d'une dizaine de mètres de haut de Ethnos dans une fumerie d'opium du bidonville.</p>
<hr>
<p>Tandis que Stravul, à qui on a fait comprendre qu'il n'avait pas le choix, tente de rafistoler Ludius à la planque,
les PJs rejoignent le gang des Crows dans un hangar des docks pour le deal prévu : la vente du journal de bord.</p>
<figure role="group">
<a href="images/2017/09/Thief_Game_Concept_Art_MLD_30.jpg"><img src="images/2017/09/Thief_Game_Concept_Art_MLD_30.jpg" alt="Extérieur de l'entrpôt sur les quais"></a>
<figcaption>Concept art de <a href="http://conceptartworld.com/news/thief-concept-art-by-mathieu-latour-duhaime/">Mathieu Latour-Duhaime</a></figcaption>
</figure>
<figure role="group">
<a href="images/2017/09/Thief-city-hub-101.jpg"><img src="images/2017/09/Thief-city-hub-101.jpg" alt="Intérieur de l'entrpôt sur les quais"></a>
<figcaption>Concept art de <a href="http://conceptartworld.com/news/thief-concept-art-by-mathieu-latour-duhaime/">Mathieu Latour-Duhaime</a></figcaption>
</figure>
<p>Les PJs semblent les premiers au RDV. Après une demi-heure, toujours pas de trace des Crows, lorsque surgit un de leurs hommes de main. Il les prévient de déguerpir car la milice est en train de faire une raffle dans le quartier.</p>
<p>Le groupe se scinde alors : Melvir et Eric fuient en direction du quartier d'habitation le plus proche, tandis qu'Elvira et Ethnos échappent de peu
à une pluie de balles d'un char de la milice sur les docks, et trouvent leur salut en plongeant dans l'eau.
De leur côté, Eric et Melvir manquent de peu de tomber sur la milice, et Eric se planque finalement dans les quartiers des domestiques d'une résidence pour la nuit.</p>
<hr>
<p>De retour à la planque, Stravul leur révèle qu'il a fait ce qu'il a pu. Laudius est mort.
Enfin... partiellement.</p>
<p>Laudius, horrifié de ce qu'il est devenu, quite la planque en rage.</p>
<p>Toute aussi désemparée, Elvira, furieuse, élimine brutalement Stravul. C'est à ce moment que Melvir débarque, affolé : il y a une rixe générale à l'auberge de la <strong>Goule Fendue</strong>, sur le territoire du gang du Harpon.
On accuse le combat de lutte d'être truqué, et des membres du gang de l'<strong>Oeil Blanc</strong> seraient en train de saccager l'endroit !</p>
<p>Tandis qu'Elvira part à la poursuite de Ludius, Eric se rend à l'auberge, avec Ethnos qui, très mal en point, se poste avec un fusil sur un toit proche.
En quelques minutes Eric règle leur compte aux quelques agitateurs. Il affronte également <strong>Marlene</strong>, son ennemi personnel, et un incendie se déclare.
Ethnos se charge d'abattre les quelques membres du gang de l'Oeil Blanc qui tentent de s'enfuir, en laissant un vivant pour porter son message :
ici c'est <strong>leur territoire</strong>.</p>
<p>De son côté, Elvira découvre que Ludius s'est réfugié chez <strong>Grell Jayan</strong>, un prêtre vaudou qui pourrait l'aider à maîtriser sa nouvelle nature.</p>
<h2>Retour d'expérience</h2>
<p>Ce fut une partie très rythmée, où la tension est montée plusieurs fois pour les PJs.
Côté MJ, j'ai dû pas mal faire preuve d'impro face à des joueurs foisonnant d'idées,
mais c'était véritablement jouissif de les voir monter, en l'espace d'une séance, deux plans d'action et de mener ces opérations à bien.</p>
<p>En termes de règles, la création de persos était rapide et fun, et le système s'est révêlé simple et inituitif à prendre en main.</p>
<p>Pour les jets, nous avons joué avec le système de "posture" mais pas celui "d'effet", par simplicité.
Pour le moment, nous n'avons pas non plus eu l'opportunité de tester le système de "teamwork", ni d'avoir de "Devil's bargain".
Enfin, les PJs n'ont pas encore joué avec le système de flashbacks, mais se sont par contre gavés avec les points de stress (prévisible).</p>
<p>Comme je ne disposais que des règles de démo en anglais,
la compréhension des points de règles et la traduction des termes en français a parfois un peu ralentit le jeu.</p>
<p>En définitive, hâte de remettre ça !</p>
<p><strong>EDIT [20/12/2017]</strong> : la suite de la campagne est consignée sur <a href="pages/jdr-blades-in-the-dark.html">cette page</a>.</p>
<style>
article img { max-height: 80vh; }
</style>Street art and hedonogeolostism in London2017-09-07T09:00:00+02:002017-09-07T09:00:00+02:00Lucas Cimontag:chezsoi.org,2017-09-07:/lucas/blog/street-art-and-hedonogeolostism-in-london.html<p>This post may just look like a good excuse to show some holiday pictures (and really it is), sorry about that :)</p>
<p>I was in London last week, and I had the chance to make a tour of some great street art spots around Liverpool Street station, thanks to Fabrizio Gallozzi …</p><p>This post may just look like a good excuse to show some holiday pictures (and really it is), sorry about that :)</p>
<p>I was in London last week, and I had the chance to make a tour of some great street art spots around Liverpool Street station, thanks to Fabrizio Gallozzi.
This post is just a galery to share some great art pieces I liked.</p>
<p>What's about "<strong>hedonogeolostism</strong>" you may ask ? It's a neologism I made up.
It means: <strong>pleasure of feeling geographically lost</strong>.
I love visiting a new city and not beeing able to tell in which direction is my starting point.
Feeling like each new street I walk in is a new place to discover.</p>
<p>I <a href="https://www.onelook.com/thesaurus/?s=pleasure%20of%20feeling%20geographically%20lost">looked up</a> for a word meaning that, but could not find one
(I learned a little about how those "reverse dictionaries" work though, with interesting maths stuff like <a href="https://en.wikipedia.org/wiki/Semantic_similarity">semantic distance</a>).
Hence, because it is a concept I like, I just made up a word from approximative greek roots ^^</p>
<p><img loading="lazy" src="images/2017/09/p1050890.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050889.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050891.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050896.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050901.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050903.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050904.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050906.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050908.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050912.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050915.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050917.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050921.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050922.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050923.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050926.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050927.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050928.JPG"></p>
<figure role="group">
<img loading="lazy" alt="Mirror photo" src="images/2017/09/p1050929.JPG">
<figcaption>I found this one above quite funny and original: instead of a sprayed painting or a stencil,
it consisted simply of a mirror on the wall,where you could see your reflection. Hence the group photo :)</figcaption>
</figure>
<p><img loading="lazy" src="images/2017/09/p1050930.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050937.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050942.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050953.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050963.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050965.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050966.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050967.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050968.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050970.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050971.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050972.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050974.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050976.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050977.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050978.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050979.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050980.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050981.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050982.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050983.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050984.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050988.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050990.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050991.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050994.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050996.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050997.JPG"></p>
<p><img loading="lazy" src="images/2017/09/p1050998.JPG"></p>
<style>
img { max-height: 80vh; }
</style>Aux marches du pouvoir2017-08-17T21:00:00+02:002017-08-17T21:00:00+02:00Lucas Cimontag:chezsoi.org,2017-08-17:/lucas/blog/aux-marches-du-pouvoir.html<p><a href="https://melville.itch.io/aux-marches-du-pouvoir"><img alt="¨Page du studio Gobz'Ink dédiée" src="images/2017/08/AuxMarchesDuPouvoir.png"></a></p>
<p>J'ai découvert ce jeu via la critique de Steve Jakoubovitch sur <a href="http://hu-mu.blogspot.fr/2017/07/aux-marches-du-pouvoir.html">Hugin & Munin</a>.</p>
<p>Malgré cette critique partagée, j'ai été conquis par le concept. J'ai commandé le jeu et nous avons fait une première partie samedi dernier.</p>
<p>Et personnellement, j'ai pris mon pied !</p>
<p>Déjà à la lecture des quelques paragraphes succins …</p><p><a href="https://melville.itch.io/aux-marches-du-pouvoir"><img alt="¨Page du studio Gobz'Ink dédiée" src="images/2017/08/AuxMarchesDuPouvoir.png"></a></p>
<p>J'ai découvert ce jeu via la critique de Steve Jakoubovitch sur <a href="http://hu-mu.blogspot.fr/2017/07/aux-marches-du-pouvoir.html">Hugin & Munin</a>.</p>
<p>Malgré cette critique partagée, j'ai été conquis par le concept. J'ai commandé le jeu et nous avons fait une première partie samedi dernier.</p>
<p>Et personnellement, j'ai pris mon pied !</p>
<p>Déjà à la lecture des quelques paragraphes succins détaillant la cité et les différentes idoles,
leur description m'a évoqué de nombreuses images et donné plein d'idées de scénarios.
Un grand bravo à <a href="/lucas/blog/tag/manuel-bedouet.html">Manuel Bedouet</a> <em>aka</em> <a href="http://melville-games.com/jeux-de-roles/">Mellville</a> pour avoir su créer une ambiance si évocatrice en si peu de mots !</p>
<p>Durant la partie, les mécaniques se sont révélées bien huilées, légères mais suffisamment directrices pour donner à l'histoire un début et une fin.
J'ai beaucoup aimé manipuler les dominos. Je trouve qu'on se rapproche beaucoup de l'<a href="http://tvtropes.org/pmwiki/pmwiki.php/Main/TheChessmaster">image</a>
des joueurs d'échecs symbolisant les luttes d'influence et les manipulations dans l'ombre.</p>
<p>Je n'ai pas trouvé qu'un MJ manquait au jeu, comme le mentionnait Steve.
Par contre, dès le choix de vocabulaire du jeu, on est immédiatement en compétition entre "Ambitieux".
Selon la dynamique de groupe et ce que cherchent les joueurs, il faut savoir doser la part de guéguerre entre joueurs
par rapport à l'aspect "écriture collective d'une histoire intéressante".</p>
<h2>Le bouleversement</h2>
<blockquote>
<p>Erickson, le marchand marié à la fille aînée du roi barbare, orateur populaire et dénonciateur de la corruption de la classe politique,
a été retrouvé mort dans la Volière. On murmure que le Sénat l'aurait fait assassiner...</p>
</blockquote>
<h2>Les Ambitieux</h2>
<h3>Amaury, l'imprimeur</h3>
<p>Pour l'occasion, j'ai incarné un Ambitieux dans la plus pure veine de LittleFinger dans Game Of Thrones.
J'avoue qu'il m'a paru difficile au premier abord de joueur un personnage vertueux :)
(une bonne raison pour essayer la prochaine fois !)</p>
<figure role="group">
<img alt="Artwork représentant LittleFinger" src="images/2017/08/LittleFinger.jpg">
<figcaption>Artwork de <a href="http://josuoh.cl/post/80332450316">Josu Hernaiz</a></figcaption>
</figure>
<p><strong>Ambition</strong>: Contrôle</p>
<p><strong>Attitude</strong>: Menteur</p>
<p><strong>Je suis</strong> intendant des Presses Souterraines</p>
<p><strong>Je suis</strong> initié des Architectes</p>
<h3>Elisabeth De Nacros</h3>
<p>Une aristocrate influente et très imbue d'elle même, qui s'est révélée très retorse :)</p>
<figure role="group">
<img alt="Artwork représentant Lady Jessica Atreides" src="images/2017/08/LadyJessicaAtreides.jpg">
<figcaption>Artwork de <a href="http://markzug.com/dune/dune-the-card-game/5425994">Mark Zug</a></figcaption>
</figure>
<p><strong>Ambition</strong>: Noblesse</p>
<p><strong>Attitude</strong>: Infaillible</p>
<p><strong>Je suis</strong> la muse du Roi Barbare</p>
<p><strong>Je suis</strong> exclue du Sénat</p>
<h3>Hugo Warthen</h3>
<p>L'Ambitieux le plus réglo de la partie, il s'est attelé à trouver le vrai coupable,
et calmer les tensions entre le peuple barbare et les autres habitants de Naëscence.</p>
<figure role="group">
<img alt="Artwork représentant Unferth dans Beowulf" src="images/2017/08/BeowulfUnferth.jpg">
<figcaption>Artwork de <a href="http://colinfix.blogspot.fr/2007/11/i-am-beowulf.html">Colin Fix</a></figcaption>
</figure>
<p><strong>Ambition</strong>: Respect</p>
<p><strong>Attitude</strong>: Amicale</p>
<p><strong>Je suis</strong> une étoile montante des Architectes</p>
<p><strong>Je suis</strong> héritier du Roi Barbare</p>
<h3>Erwann</h3>
<p>L'Ambitieux que j'ai trouvé le plus croustillant: membre de la milice impériale infiltré chez les Sceptiques,
véritable meurtrier du marchand Erickson au cours d'une bavure !</p>
<p><img alt="Leonardo DiCaprio dans le film Les infiltrés de Scorsese" src="images/2017/08/TheDeparted.jpg" title="Oui, Infernal Affairs était mieux"></p>
<p><strong>Ambition</strong>: Illumination</p>
<p><strong>Attitude</strong>: Prudent</p>
<p><strong>Je suis</strong> milicien à la Pyramide</p>
<p><strong>Je suis</strong> infiltré au sein des Sceptiques</p>
<h2>Conclusion</h2>
<p>Mon seul regret: j'avais en tête de laisser la très belle carte au milieu de la table,
mais la taille réduite de celle-ci, la nécessité de placer les dominos au centre et les fréquents retours aux règles durant cette première partie
ont fait qu'on l'a rarement eu devant nous :(</p>
<p>Quelques retours des 3 autres joueurs:</p>
<ul>
<li><strong>-</strong> trop long (la partie a duré de 19h30 à 3h du matin)</li>
<li><strong>-</strong> la "durée" d'un chapitre est au final au choix de chacun, et ça peut entraîner des déséquilibres de temps de parole</li>
<li><strong>+</strong> sentiment de liberté totale</li>
<li><strong>+</strong> mécaniques de jeu très bien pensées</li>
<li><strong>?</strong> peut-être jouable en plusieurs séances</li>
<li><strong>!</strong> attention à bien respecter la règle des complications, et faire intervenir l'icône, pas un dérivé</li>
</ul>
<h2>Seconde partie - juin 2023</h2>
<p>Un très bon moment en compagnie d'Olivier, Laetitia & Laurence
de l'<a href="https://laubergedesreveurs.forumactif.com">Auberge des Rêveurs</a> !</p>
<p>Et cette fois nous avons réussi à faire tenir la partie en seulement 4h30 \o/ ^^
En sautant par contre le second tour de table de l'acte 2.
Par contre j'avais stupidement oublié un élément essentiel du jeu...
la carte de Naëscence 🗺️🤦♂️
Voici quelques notes en vrac sur cette session :</p>
<p>Nous avons opté pour une carte dessinée collectivement, et c'était très bien aussi !
D'ailleurs il me semblerait intéressant d'établir une unité de lieu pour chaque chapitre,
qui devrait se dérouler dans un lieu précis de la ville, désigné sur la carte.</p>
<p>Il y a une mécanique de jeu de que j'ai trouvé en définitive pas géniale :
l'octroi d'un domino supplémentaire à chaque joueur en fin de chapitre.
Choisir de ne <strong>PAS</strong> octroyer de domino à un joueur est délicat,
car facilement perceptible comme un jugement / une "sanction"
vis à vis de la narration que le joueur vient de réaliser
(qui n'est d'ailleurs pas vraiment la sienne, comme elle est collective).</p>
<p>Les règles du jeu manquent parfois un peu de clarté concernant ces dominos :
comment doit s'effectuer le choix initial des deux dominos sur trois ;
les joueurs doivent favoriser la conservation de dominos de valeur élevée
s'ils souhaitent optimiser les chances pour leur Ambitieux d'avoir une fin heureuse...</p>
<p>Peut-être aussi qu'il serait au final préférable de laisser entièrement le choix aux joueurs
concernant les tables qui définissent leurs Ambitieux.
La contrainte des dominos apporte finalement peu de chose,
et peut parfois mener à créer des Ambitieux que les joueurs ont du mal à s'approprier.</p>
<p>En termes d'aides de jeux, celles-ci me sembleraient bien utiles pour une prochaine partie :</p>
<ul>
<li>une feuille d'Ambitieux, incluant une section où chaque joeur peut indiquer les nom et Ambition/Attitude des autres Ambitieux</li>
<li>une feuille récapitulative des 7 thèmes, associés aux chiffres et Icônes correspondants</li>
</ul>
<p>Enfin, voici une suggestion qui me semble très intéressante d'un joueur :
effectuer en début de partie un choix de visuels,
à la façon des illustrations dans <em>Pour la Reine</em>,
pour les Figures ou les Ambitieux par exemple,
afin d'aider à l'immersion.</p>
<!--
Vu les circonstances, avoir la version PDF du jeu m'a bien dépanné.
J'étais par contre un peu déçu qu'elle diffère légèrement de la version imprimé sur certains points.
-->Migration du blog de ghost à pelican2017-08-15T21:30:00+02:002017-08-15T21:30:00+02:00Lucas Cimontag:chezsoi.org,2017-08-15:/lucas/blog/migration-du-blog-de-ghost-a-pelican.html<p><a href="https://www.artlimited.net/image/fr/219453"><img alt="Pelikan Man 009 by Francesco Sambo" src="images/2017/08/ghost2pelican.png"></a></p>
<p>Ça y est ! Bascule effectuée !</p>
<p>Ce blog est désormais un blog <strong>statique</strong>, généré avec <a href="https://blog.getpelican.com/">Pelican</a>.</p>
<p>Et au passage: c'est son anniversaire ! 3 ans :)</p>
<h2>Pourquoi migrer vers un blog statique avec Pelican ?</h2>
<ul>
<li>par souci de simplicité: <code>make publish</code> et il n'y a plus qu'à servir les fichiers HTML générés</li>
<li>par sécurité …</li></ul><p><a href="https://www.artlimited.net/image/fr/219453"><img alt="Pelikan Man 009 by Francesco Sambo" src="images/2017/08/ghost2pelican.png"></a></p>
<p>Ça y est ! Bascule effectuée !</p>
<p>Ce blog est désormais un blog <strong>statique</strong>, généré avec <a href="https://blog.getpelican.com/">Pelican</a>.</p>
<p>Et au passage: c'est son anniversaire ! 3 ans :)</p>
<h2>Pourquoi migrer vers un blog statique avec Pelican ?</h2>
<ul>
<li>par souci de simplicité: <code>make publish</code> et il n'y a plus qu'à servir les fichiers HTML générés</li>
<li>par sécurité, les blogs statiques permettent d'éviter toute une classe de failles: <a href="https://github.com/TryGhost/Ghost/issues?utf8=%E2%9C%93&q=security">https://github.com/TryGhost/Ghost/issues?utf8=%E2%9C%93&q=security</a></li>
<li>je préfère versionner mes articles sous <code>git</code> plutôt que d'avoir à gérer une base de données</li>
<li>Pelican est en <strong>Python</strong> ! Et les templates sont en <code>Jinja2</code>: je suis bien plus familier et je préfère grandement ces outils</li>
<li>pour pouvoir <code>grep</code>-er mes articles</li>
<li>manipuler de simple fichiers pour écrire des articles permet bien plus de flexibilité, comme celle d'utiliser le correcteur orthographique de <code>vim</code> par exemple, ou de faire du <a href="https://github.com/Lucas-C/ludochaordic/blob/master/tasks.py#L94">postprocessing</a></li>
</ul>
<h2>Les étapes de la migration</h2>
<ul>
<li>convertir l'export de contenu de Ghost, au format JSON, en fichiers markdowns: <a href="https://gist.github.com/Lucas-C/7bd26443669bfe369107c03be8b05bb2">gist</a></li>
<li>établir des régles de redirection pour <code>nginx</code>: <a href="https://gist.github.com/Lucas-C/d3ff24ca636e09241eb5eea18e5a4c72">gist</a></li>
<li>chosisir un thème et l'installer: je l'ai forké pour y apporter quelques modifications: <a href="https://github.com/Lucas-C/pelican-mg">https://github.com/Lucas-C/pelican-mg</a><ul>
<li>commentaires isso (+ cron pour recevoir des emails lorsque des commentaires sont ajoutés: <a href="https://gist.github.com/Lucas-C/42373e6451a28e4c59026c129c1abb73">gist</a>)</li>
<li>gestion des tags, manquante</li>
<li>correction de bugs en passant les templates au validateur HTML W3C</li>
<li>ajout du blogroll et de liens/icônes dans la barre latérale</li>
<li>ajout d'un tag cloud</li>
<li>ajout d'images miniatures pour caque article dans l'index, avec redimensionnement automatique pour ne pas avoir une page trop lourde</li>
<li>plus d'hébergement de resources sur des CDNs</li>
<li>boutons de navigation permettant de filtrer les articles par tags</li>
</ul>
</li>
</ul>
<h2>Bilan</h2>
<p>Je suis assez content du résultat, mais <strong>dites-moi ce que VOUS en pensez !</strong> :)</p>
<p>Le blog dispose désormais d'une fonction de recherche, déjà présente dans le thème initial.
Je me demande si c'est utile à conserver, surtout que la lib "Tipue-Search" derrière n'est clairement plus maintenue (le projet a même disparu de GitHub)</p>
<p>Je ne vois qu'une fonctionnalité utile de Ghost qui va me manquer: l'auto-complétion des tags, pour ajouter à un article des tags déjà employés.</p>
<p>Et enfin, cette migration a été l'occasion de faire une 1ère contribution au projet Pelican: <a href="https://github.com/getpelican/pelican/pull/2204">https://github.com/getpelican/pelican/pull/2204</a>,
et d'ajouter la gestion des templates Jinja2 à mes pre-commit hooks git permettant de valider du HTML: <a href="https://github.com/Lucas-C/pre-commit-hooks-html">https://github.com/Lucas-C/pre-commit-hooks-html</a></p>Dungeon Janitor2017-08-15T12:00:00+02:002017-08-15T12:00:00+02:00Lucas Cimontag:chezsoi.org,2017-08-15:/lucas/blog/dungeon-janitor.html<p><img alt="Carte de donjon tirée de DungeonJanitor" src="images/2017/08/DungeonJanitor.jpg"></p>
<p>Je ne veux pas vous parler du jeu de <a href="http://farbs.org">farbs</a> en Puzzle Script: <a href="http://www.puzzlescript.net/play.html?p=6866423">http://www.puzzlescript.net/play.html?p=6866423</a>
(je n'ai toujours pas compris comment il se jouait)</p>
<p>Non, le concierge de donjon dont je souhaite vanter les mérites est celui d'<a href="http://auntiepixelante.com">Anna Anthropy</a>,
disponible sur la plateforme …</p><p><img alt="Carte de donjon tirée de DungeonJanitor" src="images/2017/08/DungeonJanitor.jpg"></p>
<p>Je ne veux pas vous parler du jeu de <a href="http://farbs.org">farbs</a> en Puzzle Script: <a href="http://www.puzzlescript.net/play.html?p=6866423">http://www.puzzlescript.net/play.html?p=6866423</a>
(je n'ai toujours pas compris comment il se jouait)</p>
<p>Non, le concierge de donjon dont je souhaite vanter les mérites est celui d'<a href="http://auntiepixelante.com">Anna Anthropy</a>,
disponible sur la plateforme <code>itch.io</code> : <a href="https://w.itch.io/dungeon-janitors-apprentice">https://w.itch.io/dungeon-janitors-apprentice</a></p>
<p><br></p>
<p>Il s'agit d'un petit jeu de rôle d'improvistion, pour deux joueurs.</p>
<p>Les règles tiennent en une page, comme certains jeux de <a href="http://onesevendesign.com">John Harper</a> ou de <a href="http://lookrobot.co.uk/games/">Grant Howitt</a>.</p>
<p>Les mécaniques sont simplissimes: il s'agit simplement d'un dialogue improvisé entre deux personnages:</p>
<ul>
<li>le concierge énonce une corvée à effectuer dans le donjon</li>
<li>l'apprenti doit constament trouver un nouvelle excuse pour ne pas l'accomplir</li>
<li>le concierge lui renvoie la balle en trouvant à chaque fois un contournement au problème</li>
</ul>
<p>Une manche s'arrête lorsqu'un joueur consent la victoire à l'autre, parce qu'il est à court d'inspiration
ou qu'il s'esclaffe à la dernière énormité proférée par son partenaire.</p>
<p>Au final, ça ressemble pas à mal à "Oui, Seigneur des Ténèbres", pour 2 joueurs et en plus simple :)</p>
<p>La partie que nous avons faite avec un ami a durée environ une heure,
et ce fut une belle tranche de rigolade.
Mon adversaire était redoutable pour trouver des prétexte, je lui tire mon chapeau !</p>
<p>Voici quelques unes des corvées énoncées:
- nettoyer les os des squelettes
- laver les outils du bourreau
- récurer le gouffre sans fond
- aller me chercher un crayon et une plume
- apporter cette lettre au roi des gobelins
- aller chercher de la viande dans le séchoir
- laver le vomi de gobelin dans leur salle de jeu suite à au banquet d'hier soir (ils étaient déchâinés)
- donner aux fourmis rouges les cadavres d'aventuriers qui trainent dans le couloir des orcs
- apporter sa paye au dragon : voici une bourse de pièces d'or</p>
<p>Je vous recommande l'expérience, pourquoi pas lors d'un long trajet en voiture pour égayer un peu l'ambiance :)
J'ai toutefois trouvé <strong>très</strong> utile d'avoir la carte du donjon sous les yeux pour l'insipration.</p>
<p>Pour avoir un peu de contexte sur la création du jeu, je vous conseille de jeter un oeil à l'article de blog où Anna l'a présenté:
<a href="http://auntiepixelante.com/?p=2437">http://auntiepixelante.com/?p=2437</a></p>Notes de lectures du QuickStart de Blades In The Dark2017-07-29T17:07:00+02:002017-07-29T17:07:00+02:00Lucas Cimontag:chezsoi.org,2017-07-29:/lucas/blog/jdr-notes-de-lectures-du-quickstart-de-blades-in-the-dark.html<p><a href="http://www.evilhat.com/home/blades-in-the-dark/"><img alt="Banière de Blades in the dark" src="images/2017/07/EHP_Blades_Pageheader.jpg"></a></p>
<p>Voici quels notes en vrac suite à ma lecture du QuickStart guide v3 (beaucoup de choses ont donc pu changer dans la version finale).</p>
<p>Résumé:</p>
<ul>
<li>vous jouez des crapules dans les rues de Duskwall,
une ville dans un univers fantasy-industriel. Il y a de l’électricité primitive, la machine à …</li></ul><p><a href="http://www.evilhat.com/home/blades-in-the-dark/"><img alt="Banière de Blades in the dark" src="images/2017/07/EHP_Blades_Pageheader.jpg"></a></p>
<p>Voici quels notes en vrac suite à ma lecture du QuickStart guide v3 (beaucoup de choses ont donc pu changer dans la version finale).</p>
<p>Résumé:</p>
<ul>
<li>vous jouez des crapules dans les rues de Duskwall,
une ville dans un univers fantasy-industriel. Il y a de l’électricité primitive, la machine à imprimer, une magie nécromancienne et des armes à feu</li>
<li>vous et les autres joueurs créez votre gang et enchaînez les opérations illégales pour grossir votre butin, agrandir votre territoire et étendre votre réputation</li>
<li>le jeu se concentre sur les moments d'action intense durant une mission, avec d'occasionnels flashbacks vers les préparatifs en amont pour améliorer le travail d'équipe</li>
</ul>
<p>Inspis:</p>
<ul>
<li>Dishonored, by Arkane Studios</li>
<li>Vlad Taltos series of novels, by Steven Brust</li>
<li>stories of Fafhrd and the Grey Mouser, by Fritz Leiber.</li>
<li>The Lies of Locke Lamora, by Scott Lynch.</li>
<li>Thief: The Dark Project and its sequels, by Looking Glass Studios.</li>
<li>The Wire, by David Simon et al.</li>
</ul>
<p>Autant dire... du lourd ! :D</p>
<p>Ce que j'ai aimé:</p>
<ul>
<li>définir la posture du personnage durant un jet d'action: dominante, audacieuse, désespérée</li>
<li>le mécanisme des flashbacks en cours de run</li>
<li>la gestion de gang: territoires, possessions et réputation, "crew upgrades"...</li>
<li>le système de base, qui a l'air simple, avec des attributs originaux</li>
<li>le système facultatif de teamwork, donnant des rôles de "leader" / "backup" aux joueurs</li>
<li>la gestion de l'XP : +1 pour jets désespérés & lorsqu'on progresse dans ses objectifs persos</li>
<li>les "facultés spéciales", très JV-esques</li>
<li>les 6 types de plans, simples- la gestion de l'XP : +1 pour jets désespérés & lorsqu'on progresse dans ses objectifs persos</li>
<li>"the Devil's Bargain"</li>
<li>GM reference sheet !!</li>
<li>la table de "score generator"</li>
</ul>
<p>Ce que j'ai moins aimé:</p>
<ul>
<li>la gestion des "tiers" qui remettent la réputation à 0, et nécessitent de dépenser des sous pour passer au "tier" suivant : ça fait très artificiel</li>
<li>la gestion des "claims": l'idée est originale, mais si chaque claim = une mission, ça donne une orientation très "PMT" au jeu là où je préfère l'aspect narrativisé d'une campagne, ou alors un enchaînement de runs + orienté sur le level up du gang. Donc au final, un pan de règles que je n'utiliserai pas je pense</li>
<li>la gestion des "clocks" : l'idée est super et bien expliquée, mais le livre semble suggérer d'utiliser ce système la majorité du temps - ce qui me paraît lourd - pour gérer tout et n'importe quoi. Mais j'aime beaucoup l'idée des "linked clocks" : ça doit faire des jolis diagrammes pour représenter des conséquences en chaîne !</li>
<li>peu d'infos sur la gestion des PNJs du gang (dans ce QuickStart, mais ils apparaissent sur la feuille de Crew)</li>
<li>pas de scénario dans ce QuickStart</li>
</ul>
<p>Traduction approximative des actions/attributs:</p>
<ul>
<li><em>Insight</em>: perspicacité<ul>
<li><strong>Hunt</strong>: chasser une cible -> rassemble des infos sur sa position et ses mouvements ; attaquer avec précision ; faire feu à distance</li>
<li><strong>Study</strong>: étudier un document, une personne ou un objet attentivement ; améliorer ses connaissances ; faire des recherches</li>
<li><strong>Survey</strong>: enquêter sur un lieu ou une situation ; anticiper un danger ; rassembler des infos sur des opportunités ou des vulnérabilités</li>
<li><strong>Tinker</strong>: bricoler pour créer des objets, les modifier, les désactiver ou les réparer ; désactiver un piège, crocheter une serrure ou ouvrir un coffre ; utiliser les appareils steampunks et électroplasmiques de la ville à votre avantage</li>
</ul>
</li>
<li><em>Prowess</em>:<ul>
<li><strong>Finesse</strong> : vol à la tire, dextérité, tours de passe-passe ; diriger un véhicule ou une monture</li>
<li><strong>Prowl</strong> : être discret et franchir les obstacles ; escalader, nager, courir, sauter, retomber sur ses pieds ; embuscades au corps-à-corps - poignarder, trancher la gorge, etc.</li>
<li><strong>Skirmish</strong> : combat avec un adversaire au corps-à-corps ; attaquer ou défendre une position ; rixes & bagarres</li>
<li><strong>Wreck</strong> : détruire un endroit, un objet ou un obstacle avec sauvagerie ou un sabotage minutieux ; enfoncer des défenses avec force ; créer une distraction ou provoquer le chaos</li>
</ul>
</li>
<li><em>Resolve</em>:<ul>
<li><strong>Attune</strong> : le domaine des esprits ; concentrer l'énergie électroplasmique ; percevoir et communiquer avec les fantômes ; comprendre la spectrologie</li>
<li><strong>Command</strong> : obtenir l'obéissance via la force de votre personnalité ; intimider et menacer ; diriger une action coordonnée en équipe</li>
<li><strong>Consort</strong> : grâce à vos connexions (héritage, passé, amis, rivaux...) gagner accès à des ressources, des informations, des contacts, ou des endroits</li>
<li><strong>Sway</strong> : influencer quelqu'un par le charme, la tromperie, le déguisement, ou le bluff ; faire changer les attitudes ou les comportements par la manipulation ou la séduction</li>
</ul>
</li>
</ul>
<p>Toutes les feuilles de perso / gang sont téléchargeables sur le site officiel en anglais: <a href="http://www.evilhat.com/home/blades-in-the-dark-downloads/">http://www.evilhat.com/home/blades-in-the-dark-downloads/</a></p>
<p>De plus il semble y avoir de chouettes scenarios ici: <a href="https://fictivefantasies.wordpress.com/blades-in-the-dark/">https://fictivefantasies.wordpress.com/blades-in-the-dark/</a></p>
<p>Et comme j'ai finalement craqué pour commander "Aux Marches du Pouvoir", ce sera l'occasion de comparer d'ici quelques semaines avec un autre jeu dans la même ambiance, mais qui prend un angle plus narrativiste :)</p>Variante 2 joueurs pour BANG! - Le jeu de dés2017-07-21T07:07:00+02:002017-07-21T07:07:00+02:00Lucas Cimontag:chezsoi.org,2017-07-21:/lucas/blog/variante-2-joueurs-pour-bang-le-jeu-de-des.html<p><em>(English version: <a href="https://boardgamegeek.com/article/26459280">on boardgamegeek</a> - there is also <a href="https://boardgamegeek.com/thread/1176865/2-player-variant-hidden-roles-shootout-mode">another 2 players variant</a> there)</em></p>
<p><img alt="Photo du jeu BANG! Le jeu de dés" src="images/2017/07/bang-jeudedes-contenu.jpg"></p>
<p><a href="https://www.trictrac.net/jeu-de-societe/bang-le-jeu-de-des">BANG! - Le jeu de dés</a> est un de mes jeux favoris. Je trouve sa mécanique de Yams revisitée, combinée à une version épurée du jeu <a href="https://www.trictrac.net/jeu-de-societe/wanted">WANTED!</a> original, avec rôles cachés et portées de tir, très réussi.</p>
<p>Il existe …</p><p><em>(English version: <a href="https://boardgamegeek.com/article/26459280">on boardgamegeek</a> - there is also <a href="https://boardgamegeek.com/thread/1176865/2-player-variant-hidden-roles-shootout-mode">another 2 players variant</a> there)</em></p>
<p><img alt="Photo du jeu BANG! Le jeu de dés" src="images/2017/07/bang-jeudedes-contenu.jpg"></p>
<p><a href="https://www.trictrac.net/jeu-de-societe/bang-le-jeu-de-des">BANG! - Le jeu de dés</a> est un de mes jeux favoris. Je trouve sa mécanique de Yams revisitée, combinée à une version épurée du jeu <a href="https://www.trictrac.net/jeu-de-societe/wanted">WANTED!</a> original, avec rôles cachés et portées de tir, très réussi.</p>
<p>Il existe une variante pour 3 joueurs du jeu, mais une partie prend toute sa saveur à partir de 4 ou 5 joueurs.</p>
<p>J'ai donc imaginé une version alternative pour 2 joueurs, en réutilisant uniquement les éléments du jeu original.</p>
<h2>Mise en situation</h2>
<p>Les joueurs incarnent le shérif ou son adjoint, saouls des barriques, terriblement en colère l'un contre l'autre à cause d'une sombre histoire de dynamite, et barricadé chacun d'un côté de la rue principale, dans une maison en bois.</p>
<p>Leur différent cessera seulement lorsque la résidence de l'un de ces 2 réprésentants des forces de l'ordre aura été réduite en miettes par la poudre !</p>
<h2>Mise en place</h2>
<p>Chaque joueur reçoit:</p>
<ul>
<li>8 cartes personnages face cachée, disposées en rectangle horizontal de 4 par 2. Ces cartes symbolisent la planque du joueur</li>
<li>3 jetons "balle" représentant... ses munitions</li>
<li>3 cartes personnages: hors-la-loi, renégat et shérif ou adjoint, ce qui définit si le joueur est l'un ou l'autre (ce n'est qu'un détail)</li>
</ul>
<p>Conservez le reste des jetons munitions à portée de main pour servir de réserve.</p>
<p><img src="images/2017/07/bang-2p-variant-setup.png" style="max-width: 60%"></p>
<h2>But du jeu</h2>
<p>Être le premier à détruire les 8 cartes "planque" de son adversaire.</p>
<h2>Déroulement</h2>
<p>La partie se déroule comme dans le jeu standard, chaque joueur effectuant chacun son tour un lancer de 5 dés avec 2 relances possibles.</p>
<p>Les combinaisons possibles changent cependant:</p>
<h3>Combinaisons</h3>
<ul>
<li>
<p><img src="images/2017/07/bang-2p-variant-dynamite.png" class="dice-face"> <img src="images/2017/07/bang-2p-variant-dynamite.png" class="dice-face"> <img src="images/2017/07/bang-2p-variant-dynamite.png" class="dice-face"> : de la même manière que dans le jeu original, les dynamites ne peuvent être relancées, et obtenir 3 dynamites signifie <strong>perdre 1 carte planque</strong>, au choix</p>
</li>
<li>
<p><img src="images/2017/07/bang-2p-variant-shot1.png" class="dice-face"> <img src="images/2017/07/bang-2p-variant-shot2.png" class="dice-face"> : les tirs vous permettent, <strong>en défaussant un jeton munition</strong>,
de détruire une carte planque de l'adversaire, sur la ligne la plus proche de vous (1) ou la plus loin (2)</p>
</li>
<li>
<p><img src="images/2017/07/bang-2p-variant-arrow.png" class="dice-face"> <img src="images/2017/07/bang-2p-variant-arrow.png" class="dice-face"> : vous plantez ces 2 flèches dans une planche pour la fixer devant une fenêtre, et <strong>récupérez ainsi 1 carte planque dans une colonne où il vous en reste encore une</strong></p>
</li>
<li>
<p><img src="images/2017/07/bang-2p-variant-beer.png" class="dice-face"> : après vos relances, <strong>pour chaque dé bière que vous retirez du résultat, vous pouvez changer la face d'un autre dé</strong>. Cet action s'effectue avant de résoudre les effets de tous les dés, et permet de changer la face d'un dé dynamite, mais ne vous empêchera pas d'exploser sur un trio de dynamites.</p>
</li>
<li>
<p><img src="images/2017/07/bang-2p-variant-gatling.png" class="dice-face"> / <img src="images/2017/07/bang-2p-variant-gatling.png" class="dice-face"> <img src="images/2017/07/bang-2p-variant-gatling.png" class="dice-face"> : lorsque vous obtenez un dé munition, vous <strong>récupérez immédiatement un jeton munition</strong> (comme les flèches dans le jeu stantard) et <strong>pouvez relancer ce dé</strong>. De plus, s'il vous reste <strong>2 dés munitions après vos relances, vous déclenchez un duel</strong>.</p>
</li>
<li>
<p><img src="images/2017/07/bang-2p-variant-beer.png" class="dice-face"> <img src="images/2017/07/bang-2p-variant-beer.png" class="dice-face"> + <img src="images/2017/07/bang-2p-variant-dynamite.png" class="dice-face"> + <img src="images/2017/07/bang-2p-variant-beer.png" class="dice-face"> ou <img src="images/2017/07/bang-2p-variant-dynamite.png" class="dice-face"> + <img src="images/2017/07/bang-2p-variant-shot1.png" class="dice-face"> ou <img src="images/2017/07/bang-2p-variant-shot2.png" class="dice-face"> : <strong>cocktail-molotov de la muerte</strong> !!</p>
</li>
</ul>
<h3>Duel</h3>
<p><img alt="Cartes rôles du jeu BANG! Le jeu de dés" src="images/2017/07/Bang-role-cards.jpg"></p>
<p>Le duel se base sur le principe du feuille / papier / ciseau.</p>
<p>Le joueur qui a déclenché le duel choisit une de ces 3 cartes rôles et la pose face cachée, chacune ayant un effet différent:</p>
<ul>
<li>le hors-la-loi <strong>détruit 2 cartes planque</strong> de son adversaire, au choix de l'attaquant</li>
<li>le sherif/adjoint <strong>récupère 2 cartes planques</strong> de son choix, sans contrainte de colonne</li>
<li>le rénégat <strong>vole toutes les munitions</strong> de son opposant</li>
</ul>
<p>L'autre joueur place également une carte face cachée, pour <strong>contrer son adversaire</strong>:
- le shérif/adjoint bloque le hors-la-loi
- le hors-la-loi bloque le rénégat
- le rénégat bloque le shérif/adjoint</p>
<p>Un moyen mnémotechnique pour retenir cet ordre est de se souvenir qu'il suit l'ordre alphabétique: <strong>A</strong>djoint/<strong>S</strong>hérif <strong>></strong> <strong>H</strong>ors-la-loi <strong>></strong> <strong>R</strong>énégat <strong>></strong> <strong>A</strong>djoint/<strong>S</strong>hérif</p>
<p>Une fois une carte rôle choisie par chaque joueur, on les révêle et on détermine si l'effet s'applique ou est bloqué. Si les 2 cartes sont identiques, on recommence le duel.</p>
<p>Note: seul le joueur initiant le duel peut déclencher un effet, pas le joueur défenseur</p>
<h3>Cocktail-molotov de la muerte</h3>
<p><img src="images/2017/07/molotov-cocktail.jpg" style="height: 12rem"></p>
<p>Il s'agit d'une combinaison à 5 dés:</p>
<ul>
<li>2 ou 3 bières</li>
<li>1 ou 2 dynamites</li>
<li>1 dé "tir" indique quelle ligne de l'adversaire est visée</li>
</ul>
<p><strong>Toutes les cartes planques de la ligne sont alors détruites !</strong></p>
<p><em>Merci à Matthieu & Laëtitia pour les playtests, et Zemeckis pour ses retours</em></p>
<p><strong>[EDIT 2018-09-19]</strong> : Bishop19 sur le site boardgamegeek a mis en page cette règle au format des cartes du jeu, en anglais :
<a href="https://boardgamegeek.com/filepage/169243/2-player-variant">https://boardgamegeek.com/filepage/169243/2-player-variant</a> Merci à lui !</p>
<p>J'ai découvert par la meme occasion cette autre variante pour 2 joueurs : <a href="https://boardgamegeek.com/filepage/169173/shootout-mode-2-player-variant">https://boardgamegeek.com/filepage/169173/shootout-mode-2-player-variant</a></p>
<style>
.dice-face {display:inline !important; width: 5rem}
</style>Extracting setup_requires dependencies out of a setup.py2017-07-04T12:07:00+02:002017-07-04T12:07:00+02:00Lucas Cimontag:chezsoi.org,2017-07-04:/lucas/blog/extracting-setup_requires-dependencies-out-of-a-setup-py.html<p>I ended up not using this code, but it may be useful to others:</p>
<ul>
<li><code>mock_setup_provider.py</code>:</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">Mock</span>
<span class="k">class</span> <span class="nc">MockSetupProvider</span><span class="p">(</span><span class="n">Mock</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">captured_setup_requires</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">setup</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">captured_setup_requires</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'setup_requires'</span><span class="p">))</span>
</code></pre></div>
<ul>
<li><code>setup_extractor …</code></li></ul><p>I ended up not using this code, but it may be useful to others:</p>
<ul>
<li><code>mock_setup_provider.py</code>:</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">unittest.mock</span> <span class="kn">import</span> <span class="n">Mock</span>
<span class="k">class</span> <span class="nc">MockSetupProvider</span><span class="p">(</span><span class="n">Mock</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">captured_setup_requires</span> <span class="o">=</span> <span class="nb">set</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">setup</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">captured_setup_requires</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">kwargs</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'setup_requires'</span><span class="p">))</span>
</code></pre></div>
<ul>
<li><code>setup_extractor.py</code>:</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">.mock_setup_provider</span> <span class="kn">import</span> <span class="n">MockSetupProvider</span>
<span class="k">def</span> <span class="nf">extract_setup_requires</span><span class="p">(</span><span class="n">setup_dir_path</span><span class="p">):</span>
<span class="n">real_setuptools</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'setuptools'</span><span class="p">)</span>
<span class="n">real_distutils_core</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'distutils.core'</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">setup_dir_path</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">mock_setup_provider</span> <span class="o">=</span> <span class="n">MockSetupProvider</span><span class="p">()</span>
<span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="p">[</span><span class="s1">'setuptools'</span><span class="p">]</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="p">[</span><span class="s1">'distutils.core'</span><span class="p">]</span> <span class="o">=</span> <span class="n">mock_setup_provider</span>
<span class="kn">import</span> <span class="nn">setup</span>
<span class="k">return</span> <span class="n">mock_setup_provider</span><span class="o">.</span><span class="n">captured_setup_requires</span>
<span class="k">finally</span><span class="p">:</span>
<span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'distutils.core'</span><span class="p">)</span> <span class="o">=</span> <span class="n">real_distutils_core</span>
<span class="n">sys</span><span class="o">.</span><span class="n">modules</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'setuptools'</span><span class="p">)</span> <span class="o">=</span> <span class="n">real_setuptools</span>
</code></pre></div>
<ul>
<li><code>test_setup_extractor.py</code>:</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">setup_extractor</span> <span class="kn">import</span> <span class="n">extract_setup_requires</span>
<span class="k">def</span> <span class="nf">test_ok</span><span class="p">():</span>
<span class="k">assert</span> <span class="n">extract_setup_requires</span><span class="p">(</span><span class="s1">'tests-fixtures'</span><span class="p">)</span> <span class="o">==</span> <span class="nb">set</span><span class="p">([</span><span class="s1">'damn42'</span><span class="p">])</span>
</code></pre></div>
<ul>
<li><code>test-fixtures/setup.py</code>:</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">setuptools</span> <span class="kn">import</span> <span class="n">setup</span><span class="p">,</span> <span class="n">find_packages</span>
<span class="n">setup</span><span class="p">(</span>
<span class="n">name</span><span class="o">=</span><span class="s1">'toto'</span><span class="p">,</span>
<span class="n">version</span><span class="o">=</span><span class="s1">'1.2.3'</span><span class="p">,</span>
<span class="n">install_requires</span><span class="o">=</span><span class="p">[</span><span class="s1">'dummy'</span><span class="p">],</span>
<span class="n">setup_requires</span><span class="o">=</span><span class="p">[</span><span class="s1">'damn42'</span><span class="p">],</span>
<span class="p">)</span>
</code></pre></div>
<p><strong>EDIT[2017/09/11]</strong> : many more <code>setup.py</code> parsing alternatives can be found here:
https://github.com/nexB/scancode-toolkit/issues/253#issuecomment-270905807</p>Is there any equitable dumbphone or laptop out there ??2017-07-01T08:07:00+02:002017-07-01T08:07:00+02:00Lucas Cimontag:chezsoi.org,2017-07-01:/lucas/blog/is-there-any-equitable-dump-phone-out-there.html<p><img alt="False cellphone in wood" src="images/2017/07/CargoCultFeaturePhone.jpg" style="max-height: 300px"></p>
<p>Hello all.</p>
<p>This is an open request : <strong>does anyone know about equitable non-smart phones or laptops</strong>, ideally long lasting and for the laptops: easily repairable with open-source componants ?</p>
<hr>
<p>I know about <a href="https://www.fairphone.com">Fairphone</a>, but I couldn't find anything similar for simpler "feature phones".</p>
<p>By the way, I stumbled upon their cost …</p><p><img alt="False cellphone in wood" src="images/2017/07/CargoCultFeaturePhone.jpg" style="max-height: 300px"></p>
<p>Hello all.</p>
<p>This is an open request : <strong>does anyone know about equitable non-smart phones or laptops</strong>, ideally long lasting and for the laptops: easily repairable with open-source componants ?</p>
<hr>
<p>I know about <a href="https://www.fairphone.com">Fairphone</a>, but I couldn't find anything similar for simpler "feature phones".</p>
<p>By the way, I stumbled upon their cost breakdowns, which are very interesting and well made:</p>
<ul>
<li><a href="https://www.fairphone.com/fr/2013/09/12/costbreakdown/">Fairphone 1 cost breakdown</a></li>
<li><a href="https://www.fairphone.com/fr/2015/09/09/cost-breakdown-of-the-fairphone-2/">Fairphone 2 cost breakdown</a></li>
</ul>
<hr>
<p>I'm just going to put down and share my findings here:</p>
<ul>
<li>
<p><a href="https://forum.fairphone.com/t/fair-non-smart-phone/655/7">a thread on the Fairphone forum about feature phones</a> : started in 2014, resurrected in 2016, they point to Nokia being the "most" fair company by rankabrand.org & Greenpeace</p>
</li>
<li>
<p><a href="https://forum.fairphone.com/t/which-computer-do-you-recommend/6964">another thread focuses on laptops</a> : some very interesting suggestions there</p>
</li>
<li>
<p><a href="https://whyopencomputing.ch">why!</a> is a Swiss company providing ecological laptops (low consumption, label, repairability) with Ubuntu installed.</p>
</li>
<li>
<p>there is an Ecolabel for electronics products : <a href="http://tcocertified.com">TCO certified</a></p>
</li>
<li>
<p>for repairability, Greenpeace launched a comparator for laptops, tablets & smartphones recently: <a href="https://www.rethink-it.org">https://www.rethink-it.org</a></p>
</li>
</ul>
<p><img src="images/2017/07/naga44.png" alt="Naga44 logo" title="Naga44 logo"></p>
<ul>
<li>currently the best solution seems to be refurbished phones and computers. Living in Nantes, I'm going to look into <a href="http://www.alis44.org">Alis44</a> & <a href="http://www.naga44.org">Naga44</a> for laptops, and <a href="https://www.leboncoin.fr">LeBonCoin</a> for dumbphones</li>
</ul>
<p><strong>EDIT[2017/11/06]</strong>: a very interesting initiative: <a href="https://commown.fr">Commown</a></p>
<p><strong>EDIT[2018/06/13]</strong>: <a href="https://www.backmarket.fr/ordinateur-portable-reconditionne.html">another one</a>, repackaged computers in France</p>Nouvelles variantes pour The Game2017-06-22T11:06:00+02:002017-06-22T11:06:00+02:00Lucas Cimontag:chezsoi.org,2017-06-22:/lucas/blog/nouvelles-variantes-pour-the-game.html<p>Pour faire suite à <a href="https://chezsoi.org/lucas/blog/une-variante-pour-the-game.html">mon précédent post sur ce jeu génial</a>, voici 2 autres variantes que nous avons inventé et testé avec ma compagne:</p>
<ul>
<li><strong>"The Game: Constrained"</strong> : inspiré de l'extension officielle <a href="http://www.nsv.de/spielregeln/the-game-extreme-f.pdf">The Game: Extreme (PDF, 560Ko)</a>, l'idée est d'associer une règle supplémentaire à certaines cartes lorsqu'elles sont visibles au dessus …</li></ul><p>Pour faire suite à <a href="https://chezsoi.org/lucas/blog/une-variante-pour-the-game.html">mon précédent post sur ce jeu génial</a>, voici 2 autres variantes que nous avons inventé et testé avec ma compagne:</p>
<ul>
<li><strong>"The Game: Constrained"</strong> : inspiré de l'extension officielle <a href="http://www.nsv.de/spielregeln/the-game-extreme-f.pdf">The Game: Extreme (PDF, 560Ko)</a>, l'idée est d'associer une règle supplémentaire à certaines cartes lorsqu'elles sont visibles au dessus d'une pile:<ul>
<li>les 6 cartes bleues "On Fire" (22, 33, 44, 55, 66, 77) empêchent de piocher de nouvelles cartes à la fin du tour</li>
<li>les 9 cartes des dizaines (10, 20, 30, 40, 50, 60, 70, 80, 90) empêchent de faire des "sauts" en arrière de 10</li>
<li>les 10 cartes finissant par "5" (5, 15, 25, 35, 45, 55, 65, 75, 85, 95) forcent les joueurs à jouer 3 cartes au minimum par tour</li>
</ul>
</li>
</ul>
<p>Dans le cas où un joueur n'a pas assez de cartes en main pour jouer le minimum de cartes requis : la partie n'est <strong>pas</strong> perdue tant qu'il arrive à jouer toutes ses cartes à son tour. De même si un joueur n'a plus <strong>aucune</strong> carte, il se contente de suivre les règles de pioche en vigueur et la partie continue.</p>
<p><img alt="Fausse couverture de jeu The game - Hanabi" src="images/2017/06/TheGameHanabi-2.png"></p>
<ul>
<li><strong>"The Game: Hanabi"</strong> : cette variante pour 2 joueurs change un certain nombre de règles:<ul>
<li>les joueurs ont désormais en main à la fois des cartes visibles (uniquement d'eux, comme dans le jeu classique), et <strong>des cartes tournées vers le partenaire</strong> (comme dans Hanabi).
Le 1er joueur commence avec 5 cartes visibles et 3 tournées vers l'autre, et le 2e joueur avec 3 cartes visibles et 5 tournées vers son partenaire</li>
<li>les joueurs doivent <strong>jouer strictement 3 cartes par tour</strong>, parmi les 5 cartes qu'il voit</li>
<li>après avoir joué, chaque joueur <strong>indique à son partenaire de retourner 2 cartes</strong> vers lui</li>
<li>enfin, il <strong>pioche 3 cartes: 1 face visible et 2 tournées</strong> vers l'autre joueur</li>
</ul>
</li>
</ul>
<p>Avec cette mécanique de jeu, au début de son tour, chaque joueur doit toujours avoir <strong>5 cartes face visible et 3 tournées vers l'autre</strong>.</p>
<p>Comme d'habitude avec The Game / Hanabi, la difficulté varie énormément selon le tirage des cartes. Pour l'instant, nous n'avons pas réussi à battre le jeu avec cette variante, mais ça s'est parfois joué à un cheveu !</p>
<p>Pour 3 joueurs:</p>
<ul>
<li>chaque joueur à 7 cartes en main</li>
<li>le premier joueur commence avec 5 cartes visibles, les deux autres avec 4</li>
<li>chaque joueur peut à son tour répartir les 2 indices (cartes retournées) comme il le souhaite entre ses partenaires</li>
<li>lorsque la pioche est vide, les joueurs peuvent jouer moins de cartes, mais donnent alors d'autant moins d'indices: 3 cartes => 2 indices; 2 cartes => 1 indice; 1 carte => 0 indice.</li>
</ul>JSONP & exceptions with Spring web 42017-05-19T12:05:00+02:002017-05-19T12:05:00+02:00Lucas Cimontag:chezsoi.org,2017-05-19:/lucas/blog/properly-handling-exceptions.html<p>Since Spring 4.1, it is really easy to enable <a href="https://en.wikipedia.org/wiki/JSONP">JSONP</a> on an API controller:</p>
<div class="highlight"><pre><span></span><code><span class="nd">@RestController</span>
<span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/"</span><span class="p">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyController</span> <span class="p">{</span>
<span class="nd">@ControllerAdvice</span>
<span class="kd">static</span> <span class="kd">class</span> <span class="nc">JsonpAdvice</span> <span class="kd">extends</span> <span class="n">AbstractJsonpResponseBodyAdvice</span> <span class="p">{</span>
<span class="kd">public</span> <span class="nf">JsonpAdvice</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">super</span><span class="p">(</span><span class="s">"callback"</span><span class="p">);</span> <span class="c1">// name of the query parameter to use</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/"</span><span class="p">,</span> <span class="n">method</span> <span class="o">=</span> <span class="n">RequestMethod</span><span class="p">.</span><span class="na">GET</span><span class="p">)</span>
<span class="kd">public</span> <span class="n">MyAPIResult</span> <span class="nf">getStuff</span><span class="p">(...)</span> <span class="p">{</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>There is no RFC …</p><p>Since Spring 4.1, it is really easy to enable <a href="https://en.wikipedia.org/wiki/JSONP">JSONP</a> on an API controller:</p>
<div class="highlight"><pre><span></span><code><span class="nd">@RestController</span>
<span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/"</span><span class="p">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyController</span> <span class="p">{</span>
<span class="nd">@ControllerAdvice</span>
<span class="kd">static</span> <span class="kd">class</span> <span class="nc">JsonpAdvice</span> <span class="kd">extends</span> <span class="n">AbstractJsonpResponseBodyAdvice</span> <span class="p">{</span>
<span class="kd">public</span> <span class="nf">JsonpAdvice</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">super</span><span class="p">(</span><span class="s">"callback"</span><span class="p">);</span> <span class="c1">// name of the query parameter to use</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nd">@RequestMapping</span><span class="p">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/"</span><span class="p">,</span> <span class="n">method</span> <span class="o">=</span> <span class="n">RequestMethod</span><span class="p">.</span><span class="na">GET</span><span class="p">)</span>
<span class="kd">public</span> <span class="n">MyAPIResult</span> <span class="nf">getStuff</span><span class="p">(...)</span> <span class="p">{</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>There is no RFC for JSONP. It is simply a recipe, without any reference document for implementation best practices.</p>
<p>Specifically, error handling can be difficult: <strong>if an API return a 4XX or 5XX error, the JSONP callback cannot catch them</strong> and will simply never be called, whereas the API actually responded.</p>
<p>In order to fix this, a solution is to make your API handle exceptions differently when a <code>callback</code> parameter is provided, and else to respond with 4XX/5XX HTTP codes as a REST API should do.</p>
<p>For example, <strong>in case of an error your API can pass to the Javascript <code>callback</code> an <code>Error</code> instead of a JSON object</strong>. The calling code can then react to errors as it wishes:</p>
<div class="highlight"><pre><span></span><code><span class="nb">window</span><span class="p">.</span><span class="nx">jsonp_callback</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">response</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">response</span> <span class="ow">instanceof</span> <span class="ne">Error</span><span class="p">)</span> <span class="p">{</span>
<span class="p">...</span><span class="nx">handle</span> <span class="nx">API</span> <span class="nx">error</span> <span class="nx">response</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="p">...</span><span class="nx">handle</span> <span class="nx">API</span> <span class="nx">successful</span> <span class="nx">response</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>On the server side, with Spring, this can be accomplished with an <code>@ExceptionHandler</code> annotation:</p>
<div class="highlight"><pre><span></span><code><span class="nd">@ControllerAdvice</span> <span class="c1">// Handle exceptions raised in all controllers</span>
<span class="kd">class</span> <span class="nc">GlobalControllerExceptionHandler</span> <span class="p">{</span>
<span class="nd">@ExceptionHandler</span><span class="p">(</span><span class="n">NotFoundException</span><span class="p">.</span><span class="na">class</span><span class="p">)</span> <span class="c1">// 404</span>
<span class="kd">public</span> <span class="n">ResponseEntity</span> <span class="nf">handleNotFoundException</span><span class="p">(</span><span class="n">NotFoundException</span> <span class="n">exception</span><span class="p">,</span>
<span class="n">HttpServletRequest</span> <span class="n">request</span><span class="p">)</span> <span class="p">{</span>
<span class="n">LOGGER</span><span class="p">.</span><span class="na">error</span><span class="p">(</span><span class="s">"Error 404"</span><span class="p">,</span> <span class="n">exception</span><span class="p">);</span>
<span class="k">return</span> <span class="n">createErrorResponse</span><span class="p">(</span><span class="n">HttpStatus</span><span class="p">.</span><span class="na">NOT_FOUND</span><span class="p">,</span>
<span class="n">request</span><span class="p">.</span><span class="na">getParameterMap</span><span class="p">(),</span>
<span class="n">exception</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span>
<span class="p">}</span>
<span class="nd">@ExceptionHandler</span><span class="p">(</span><span class="n">Exception</span><span class="p">.</span><span class="na">class</span><span class="p">)</span> <span class="c1">// Catch any other exception -> 500</span>
<span class="kd">public</span> <span class="n">ResponseEntity</span> <span class="nf">handleException</span><span class="p">(</span><span class="n">Exception</span> <span class="n">exception</span><span class="p">,</span>
<span class="n">HttpServletRequest</span> <span class="n">request</span><span class="p">)</span> <span class="p">{</span>
<span class="n">LOGGER</span><span class="p">.</span><span class="na">error</span><span class="p">(</span><span class="s">"Error 500"</span><span class="p">,</span> <span class="n">exception</span><span class="p">);</span>
<span class="k">return</span> <span class="n">createErrorResponse</span><span class="p">(</span><span class="n">HttpStatus</span><span class="p">.</span><span class="na">INTERNAL_SERVER_ERROR</span><span class="p">,</span>
<span class="n">request</span><span class="p">.</span><span class="na">getParameterMap</span><span class="p">(),</span>
<span class="n">exception</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span>
<span class="p">}</span>
<span class="kd">static</span> <span class="kd">private</span> <span class="n">ResponseEntity</span> <span class="nf">createErrorResponse</span><span class="p">(</span><span class="n">HttpStatus</span> <span class="n">errorStatus</span><span class="p">,</span>
<span class="n">Map</span><span class="o"><</span><span class="n">String</span><span class="p">,</span> <span class="n">String</span><span class="o">[]></span> <span class="n">params</span><span class="p">,</span>
<span class="n">String</span> <span class="n">errorMsg</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">params</span><span class="p">.</span><span class="na">containsKey</span><span class="p">(</span><span class="s">"callback"</span><span class="p">))</span> <span class="p">{</span>
<span class="kd">final</span> <span class="n">String</span> <span class="n">jsError</span> <span class="o">=</span> <span class="n">String</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="s">"%s(new Error('%d: %s'))"</span><span class="p">,</span>
<span class="n">params</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"callback"</span><span class="p">)</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span><span class="p">,</span>
<span class="n">errorStatus</span><span class="p">.</span><span class="na">value</span><span class="p">(),</span>
<span class="n">escapeEcmaScript</span><span class="p">(</span><span class="n">errorMsg</span><span class="p">));</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">ResponseEntity</span><span class="o"><></span><span class="p">(</span><span class="n">jsError</span><span class="p">,</span> <span class="n">HttpStatus</span><span class="p">.</span><span class="na">OK</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">ResponseEntity</span><span class="o"><></span><span class="p">(</span><span class="n">createJsonResponse</span><span class="p">(</span><span class="n">errorMsg</span><span class="p">),</span> <span class="n">errorStatus</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">static</span> <span class="kd">private</span> <span class="n">String</span> <span class="nf">createJsonResponse</span><span class="p">(</span><span class="n">String</span> <span class="n">errorMsg</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">final</span> <span class="n">Map</span><span class="o"><</span><span class="n">String</span><span class="p">,</span> <span class="n">Object</span><span class="o">></span> <span class="n">jsonData</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HashMap</span><span class="o"><></span><span class="p">();</span>
<span class="n">jsonData</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="s">"error"</span><span class="p">,</span> <span class="n">errorMsg</span><span class="p">);</span>
<span class="k">return</span> <span class="n">StringFormatUtils</span><span class="p">.</span><span class="na">multilinesPrettyFormat</span><span class="p">(</span><span class="n">jsonData</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>Python gevent terrible failure mode under Windows2017-05-18T14:05:00+02:002017-05-18T14:05:00+02:00Lucas Cimontag:chezsoi.org,2017-05-18:/lucas/blog/python-gevent-terrible-failure-mode.html<p>What do you think of the following innocuous Python code ?</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">gevent</span> <span class="kn">import</span> <span class="n">monkey</span>
<span class="n">monkey</span><span class="o">.</span><span class="n">patch_all</span><span class="p">(</span><span class="n">thread</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">select</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'http://i-do-not-exist.com'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'THIS WILL NEVER BE PRINTED !!!'</span><span class="p">)</span>
</code></pre></div>
<p>Guess what ? The string message will never get printed :(</p>
<p><img alt="Mister Bean meme image - Silent But Deadly" src="images/2017/05/96cb6a3bd576058ccc3ca0442099c9f7_silent-memes-image-memes-at-relatablycom-silent-meme_400-400.jpeg"></p>
<p>Simply remove the <code>monkey.patch_all</code> line and you'll …</p><p>What do you think of the following innocuous Python code ?</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">gevent</span> <span class="kn">import</span> <span class="n">monkey</span>
<span class="n">monkey</span><span class="o">.</span><span class="n">patch_all</span><span class="p">(</span><span class="n">thread</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">select</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'http://i-do-not-exist.com'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'THIS WILL NEVER BE PRINTED !!!'</span><span class="p">)</span>
</code></pre></div>
<p>Guess what ? The string message will never get printed :(</p>
<p><img alt="Mister Bean meme image - Silent But Deadly" src="images/2017/05/96cb6a3bd576058ccc3ca0442099c9f7_silent-memes-image-memes-at-relatablycom-silent-meme_400-400.jpeg"></p>
<p>Simply remove the <code>monkey.patch_all</code> line and you'll get a pretty <code>socket.gaierror: [Errno 8] Name or service not known</code>.</p>
<p>I tested this with <code>gevent==1.2.1</code> and Windows 10, with the <code>gevent</code> & <code>greenlet</code> packages built from source by <code>pip install</code>.</p>Intro à Lua et ses utilisations dans Nginx & Redis2017-05-17T16:05:00+02:002017-05-17T16:05:00+02:00Lucas Cimontag:chezsoi.org,2017-05-17:/lucas/blog/intro-a-lua-et-ses-utilisations-dans-nginx.html<p>Une courte présentation que j'ai donné cette après-midi à <a href="http://jobs.voyages-sncf.com">Voyages-sncf.com Technologies</a> :</p>
<div style="text-align:center;"><iframe src="/lucas/slides/intro-lua/" width="600" height="400">
<p>Iframes non supportées. Cliquez sur le lien dans le paragraphe ci-dessous pour accéder directement aux slides.</p>
</iframe></div>
<p>Les slides sont accessibles <a href="/lucas/slides/intro-lua/">ici</a> en plein écran.</p>Record an existing voicemail message with Python & Twilio2017-05-17T16:05:00+02:002017-05-17T16:05:00+02:00Lucas Cimontag:chezsoi.org,2017-05-17:/lucas/blog/record-an-existing-voicemail-message-with-python.html<p>Last week, my father asked me if I could make a backup of an old lovely voicemail message he had,
recorded by my sister when she was a kid.</p>
<p>I wrote a <a href="https://github.com/Lucas-C/linux_configuration/blob/master/languages/python/record_voicemail_with_twilio.py">short Python script</a>
to accomplish this using <a href="https://www.twilio.com">Twilio</a>:</p>
<div class="highlight"><pre><span></span><code><span class="n">twiml_url</span> <span class="o">=</span> <span class="s1">'https://handler.twilio.com/twiml/EH9515e9e0d2fb81f27d75a493225ae703'</span>
<span class="n">client</span> <span class="o">=</span> <span class="n">Client</span><span class="p">(</span><span class="n">os …</span></code></pre></div><p>Last week, my father asked me if I could make a backup of an old lovely voicemail message he had,
recorded by my sister when she was a kid.</p>
<p>I wrote a <a href="https://github.com/Lucas-C/linux_configuration/blob/master/languages/python/record_voicemail_with_twilio.py">short Python script</a>
to accomplish this using <a href="https://www.twilio.com">Twilio</a>:</p>
<div class="highlight"><pre><span></span><code><span class="n">twiml_url</span> <span class="o">=</span> <span class="s1">'https://handler.twilio.com/twiml/EH9515e9e0d2fb81f27d75a493225ae703'</span>
<span class="n">client</span> <span class="o">=</span> <span class="n">Client</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'TWILIO_ACCOUNT_SID'</span><span class="p">],</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'TWILIO_AUTH_TOKEN'</span><span class="p">])</span>
<span class="n">client</span><span class="o">.</span><span class="n">calls</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">to</span><span class="o">=</span><span class="s1">'+33241XXXXXX'</span><span class="p">,</span>
<span class="n">from_</span><span class="o">=</span><span class="n">client</span><span class="o">.</span><span class="n">incoming_phone_numbers</span><span class="o">.</span><span class="n">list</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">phone_number</span><span class="p">,</span>
<span class="n">record</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">url</span><span class="o">=</span><span class="n">twiml_url</span><span class="p">)</span>
</code></pre></div>
<p>The twiml URL refers to a short XML file I created online on Twilio web console at <a href="https://www.twilio.com/console/dev-tools/twiml-bins">https://www.twilio.com/console/dev-tools/twiml-bins</a>:</p>
<div class="highlight"><pre><span></span><code><span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><Response></span>
<span class="nt"><Pause</span> <span class="na">length=</span><span class="s">"30"</span><span class="nt">/></span>
<span class="nt"></Response></span>
</code></pre></div>
<p>After executing the script, I found the recorded audio message at <a href="https://www.twilio.com/console/voice/dashboard">https://www.twilio.com/console/voice/dashboard</a>, where I was able to download it as MP3 or WAV:</p>
<p><img alt="Screenshot of Twilio web console" src="images/2017/05/2017-05-17-18_40_04-Twilio-Console---Voice-Logs-Calls.png"></p>Variantes pour Hanabi2017-05-05T19:05:00+02:002017-05-05T19:05:00+02:00Lucas Cimontag:chezsoi.org,2017-05-05:/lucas/blog/variantes-pour-hanabi.html<p><a href="http://net1204.deviantart.com/art/Utakata-Hanabi-212226463"><img alt="Illustration représentant des feux d'artifices aux couleur d'Hanabi" src="images/2017/05/utakata_hanabi___sasuke_ver__by_net1204-d3icqzj.jpg"></a></p>
<p><a href="https://www.trictrac.net/jeu-de-societe/hanabi">Hanabi</a> d'Antoine Bauza est un de nos jeux préférés, à moi et ma compagne. Et certainement celui auquel on a joué le plus de fois. Et on ne l'a toujours pas complètement battu !</p>
<p>Voici quelque idées de variantes pour renouveler un peu le plaisir :</p>
<ul>
<li><strong>Variante rapide pour 2 joueurs</strong> <em>(testé …</em></li></ul><p><a href="http://net1204.deviantart.com/art/Utakata-Hanabi-212226463"><img alt="Illustration représentant des feux d'artifices aux couleur d'Hanabi" src="images/2017/05/utakata_hanabi___sasuke_ver__by_net1204-d3icqzj.jpg"></a></p>
<p><a href="https://www.trictrac.net/jeu-de-societe/hanabi">Hanabi</a> d'Antoine Bauza est un de nos jeux préférés, à moi et ma compagne. Et certainement celui auquel on a joué le plus de fois. Et on ne l'a toujours pas complètement battu !</p>
<p>Voici quelque idées de variantes pour renouveler un peu le plaisir :</p>
<ul>
<li><strong>Variante rapide pour 2 joueurs</strong> <em>(testé : un peu trop aléatoire)</em> Source: <a href="https://boardgamegeek.com/thread/1122247/quick-hanabi-2-experienced-players">BoardGameGeek</a>.
On restreint le matériel à :<ul>
<li><strong>cartes</strong>: trois 1, deux 2, et un 3 dans trois couleurs, soit 18 cartes au total.</li>
<li><strong>mains</strong>: deux cartes par joueur (oui c'est tout !)</li>
<li><strong>indices</strong>: 3</li>
<li><strong>pétards</strong>: 2</li>
</ul>
</li>
</ul>
<p>Les règles sont identiques. En particulier, on obtient un indice bonus en posant le 3 d'une couleur, et lorsqu'on défausse une carte alors qu'il y a déjà 3 indices, on n'en récupère pas.</p>
<ul>
<li><strong>Manabi</strong> <em>(testé : très sympa !)</em> Source: <a href="https://boardgamegeek.com/thread/1237441/manabi-magical-hanabi-variant">BoardGameGeek</a>. Le nom de cette variante est la contraction de "Magical Hanabi". L'idée est simple : poser un 5 ne permet plus de récupérer un indice, mais déclenche un effet selon sa couleur :<ul>
<li><strong>bleu</strong> : après avoir pioché une nouvelle carte, le joueur qui a joué le 5 prend les 3 premières cartes de la pioche, les regarde en secret et les remet dans l'ordre de son choix.</li>
<li><strong>vert</strong> : après avoir pioché une nouvelle carte, le joueur qui a joué le 5 choisit une carte dans la défausse et la mélange dans la pioche.</li>
<li><strong>rouge</strong> : après avoir pioché une nouvelle carte, le joueur qui a joué le 5 choisit une carte dans la main de tous les joueurs, et les mélange avec une carte de la pioche. Il redistribue ensuite les cartes entre les joueurs, et mélange la carte restante dans la pioche.</li>
<li><strong>jaune</strong> : tous les indices sont défaussés, puis le joueur qui a joué le 5 récupère une carte de la défausse dans sa main.</li>
<li><strong>blanc</strong> : permet de récupérer tous les indices</li>
</ul>
</li>
</ul>
<p>Sinon les règles sont identiques, mais on ne joue qu'avec 6 indices !</p>
<ul>
<li>
<p><strong>Mélange</strong> <em>(testé: une chouette variante un peu + difficile!)</em> Une idée de variante rigolote, inspirée d'une suggestion sur <a href="https://boardgamegeek.com/thread/1757782/blended-variant">BoardGameGeek</a>. Les joueurs doivent systématique donner un indice sur 2 cartes à la fois, en indiquant :</p>
<ul>
<li>soit leur somme de valeurs</li>
<li>soit la couleur la plus foncée : bleu > vert > rouge > jaune > blanc</li>
</ul>
</li>
<li>
<p><strong>Haut ou bas</strong> <em>(testé: super, renouvelle bien le jeu!)</em> Source: <a href="https://boardgamegeek.com/thread/1213533/or-down-variant-experienced-players">BoardGameGeek</a>. Pour y jouer il vous faudra retirer deux "1" de chaque couleur, et rajouter une carte "DEBUT" dans chacune. Le plus simple pour cela est de coller une gomette pour marquer un des deux "1" retirés dans chaque famille.
Au final, vous devez obtenir dans chaque couleur les cartes suivantes : 1, 2, 2, 3, 3, 4, 4, 5 et DEBUT.
Règles modifiées :</p>
<ul>
<li>chaque pile de cartes doit commencer avec un "1" ou un "5". Les cartes s'enchaînent ensuite dans l'ordre croissant ou décroissant.</li>
<li>la carte "DEBUT" peut remplacer un "1" ou un "5" en début de pile. Un "2" ou un "4" peuvent ensuite être joués dessus, puis l'ordre des cartes sera fixé (croissant ou décroissant). Une carte "DEBUT" jouée alors que la pile correspondante est déjà commencé vous fait prendre un jeton rouge.</li>
<li>on ne peut donner d'indice de valeur sur les cartes "DEBUT", uniquement des indices de couleur</li>
<li>finir une pile de 5 cartes permet bien toujours de récupérer un indice</li>
</ul>
</li>
<li>
<p><strong>Personnages</strong> <em>(non encore testé)</em> Source: <a href="https://boardgamegeek.com/thread/1688194/hanabi-characters-variant">BoardGameGeek</a>. Cette variante consiste en une vingtaine de cartes <em>Personnage</em>, tirées au hasard en début de partie par chaque joueur. Si vous souhaiter la tester, je vous invite à vous rendre sur BoardGameGeek pour y télécharger le fichier PDF contenant les cartes en anglais.</p>
</li>
<li>
<p><strong>Jetons de la honte</strong> <em>(non encore testé)</em> : distribués pour éviter trop de "méta-jeu" et d'informations divulguées intentionnellement à demi-mot. cf. <a href="https://boardgamegeek.com/thread/867083/shame-token">https://boardgamegeek.com/thread/867083/shame-token</a></p>
</li>
</ul>Rendering deep text-based mindmaps with WiseMapping and Python2017-03-15T20:03:00+01:002017-03-15T20:03:00+01:00Lucas Cimontag:chezsoi.org,2017-03-15:/lucas/blog/rendering-deep-text-based-mindmaps-with-wisemapping-and-python.html<p>In this blog post, I'm going to demonstrate how to reuse <a href="http://wisemapping.com">WiseMapping</a> HTML+JS rendering engine to easily visualize...</p>
<pre>text-based mindmaps
like this one
have many benefits
they are readable as-it-is
they don't require any tool to be edited
[they follow the UNIX tenets](http://www.ru.j-npcs.org/usoft …</pre><p>In this blog post, I'm going to demonstrate how to reuse <a href="http://wisemapping.com">WiseMapping</a> HTML+JS rendering engine to easily visualize...</p>
<pre>text-based mindmaps
like this one
have many benefits
they are readable as-it-is
they don't require any tool to be edited
[they follow the UNIX tenets](http://www.ru.j-npcs.org/usoft/WWW/LJ/Articles/unixtenets.html)
</pre>
<p>For the impatient ones, <a href="/lucas/mindmap/mindmap-viewer/?text_based_mindmaps">here</a> is the result (click & drag to center the mindmap):</p>
<iframe src="/lucas/mindmap/mindmap-viewer/?text_based_mindmaps" height="200" width="800"></iframe>
<p>And you'll find <a href="https://github.com/Lucas-C/brain_dump">here</a> all that is required to launch this viewer yourself:</p>
<div class="highlight"><pre><span></span><code>./build_viewer.sh
./wisemapping_txt2xml.py text_based_mindmaps.txt > wise-editor/src/main/webapp/samples/text_based_mindmaps.xml
python3 -m http.server
</code></pre></div>
<p>You only need: <code>git</code>, <code>python3</code>, <code>rsync</code> and <code>sed</code>.</p>
<p><strong>EDIT [2017/08/02]</strong>: in the end I put the viewer in a separate repo: <a href="https://github.com/Lucas-C/wisemapping-mindmap-viewer">https://github.com/Lucas-C/wisemapping-mindmap-viewer</a></p>
<h3>Genesis of the project</h3>
<p>Perfect time to slip in <a href="https://www.youtube.com/watch?v=ZujuYiweht8">one of their songs</a>.</p>
<p>The reason behind this experiment (and <a href="/lucas/blog/solarized-mindmaps-with-python-and-graphviz.html">this previous one with graphviz</a>) is that I wanted to leave the excellent <a href="http://www.freeplane.org">Freeplane</a> mindmapping software for a text file based solution, in order to store my mindmaps in Gitlab.</p>
<p>Using graphviz has some severe limitations : big mindmaps are hard to read and navigate due to the absence of a folding mechanism, links are not clickable, etc.</p>
<p>Then I realized an Open-Source mindmap software, <a href="http://wisemapping.com">WiseMapping</a>, was in fact a web interface built on top of a Java REST API. So I planned to write a mock API in Python in order to answer the required HTTP calls to display a mindmap based on a text file.</p>
<h3>Poking around</h3>
<p>To begin with, I followed the project README instructions:</p>
<div class="highlight"><pre><span></span><code>cd wise-webapp
mvn jetty:run-war -Dmaven.test.skip=true
cd -
mvn assembly:assembly -Dmaven.test.skip=true
python3 -m http.server
</code></pre></div>
<p>The tests systematically failed, hence I passed <code>-Dmaven.test.skip=true</code> to all the Maven commands.
I'more Python than Ruby, so I replaced the command they used to serve the static files by a Python one.</p>
<p>Quickly, I realized that it would be even easier than I thought: most of the code logic was in Javascript, the API only serving as a mindmap save/restore mechanism. Zero call to the Java process is made when loading the default <code>welcome.xml</code> mindmap in <a href="https://bitbucket.org/wisemapping/wisemapping-open-source/src/v4.0.3/wise-editor/src/main/webapp/html/viewmode.html"><code>viewmode.html</code></a> !</p>
<h3>Extracting the HTML+JS mindmap renderer from WiseMapping</h3>
<p>If you look at <code>build_viewer.sh</code> you'll see that I use <code>rsync</code> to extract from the repo only a few files required to run the viewer. They are mostly JS & image files, and not even all of them are needed !</p>
<p>Looking at my browser Network tab, I figured out that the <code>mapId = 'welcome'</code> in <a href="https://bitbucket.org/wisemapping/wisemapping-open-source/src/v4.0.3/wise-editor/src/main/webapp/html/viewmode.html#viewmode.html-25"><code>viewmode.html</code></a> was the name of an XML file in the [samples/] (https://bitbucket.org/wisemapping/wisemapping-open-source/src/v4.0.3/wise-editor/src/main/webapp/samples/) directory that was loaded and parsed by the app.</p>
<p>Instead of this default value, I just changed the code to load the XML file corresponding to the query string:</p>
<div class="highlight"><pre><span></span><code>mapId = location.search.substr(1) || 'welcome'
</code></pre></div>
<h3>WTF is JSPomLoader ???</h3>
<p>Strangely, loading a mindmap was really slow. Here is what Firefox Network tab showed when loading the viewser in a new private window:</p>
<p><img alt="Firefox screenshot: 287 requests, 1 292,48 KB, 30,26s" src="images/2017/03/Firefox_WiseMapping_RequestCount1.png"></p>
<p>I realized that for some crazy reason, when the JS editor component was loading, it retrieved the Maven <code>pom.xml</code> file from disk, parsed it and then loaded those files one per one ! And with jQuery adding a <code>?_=${timestamp}</code> query parameter each time, to avoid browser caches !! <code>JSPomLoader</code> is the class responsible for this absurdity this in <a href="https://bitbucket.org/wisemapping/wisemapping-open-source/src/v4.0.3/wise-editor/src/main/webapp/js/mindplot-min.js">mindplot-min.js</a>.</p>
<p>I decided to build a JS bundle instead. After some experiments with <code>xmlstartlet</code> and <code>browserify</code>, I realized it was doable in just 3 simple commands:</p>
<div class="highlight"><pre><span></span><code>cd mindplot/src/main/javascript
sed -n '/<span class="nt"><includes></span>/,/<span class="err"><</span>\/includes>/{/<span class="nt"><includes></span>/d;/<span class="err"><</span>\/includes>/d;p}' ../../../../wisemapping-open-source/mindplot/pom.xml | sed -e 's~<span class="cp">${</span><span class="n">basedir</span><span class="cp">}</span>~../../..~' -e 's~\s*<span class="nt"><include></span>~~' -e 's~<span class="nt"></include></span>$~~' > js_sources
cat js_sources > ../../../../wise-editor/src/main/webapp/js/mindplot-bundle.js
cd -
sed -i '<span class="nv">$d</span>' wise-editor/src/main/webapp/js/editor.js
cat wise-editor/src/main/webapp/js/mindplot-bundle.js >> wise-editor/src/main/webapp/js/editor.js
</code></pre></div>
<p>The second command makes <code>editor.js</code> use our new <code>mindplot-bundle.js</code> instead of <code>mindplot-min.js</code> by replacing its last JS line.</p>
<p>The result:</p>
<p><img alt="Firefox screenshot: 170 requests, 808,26 KB, 7,28s" src="images/2017/03/Firefox_WiseMapping_RequestCount2.png"></p>
<h3>Avoiding unwanted LocalStorage cache</h3>
<p>At this stage, I could add XML mindmaps in <code>wise-editor/src/main/webapp/samples/</code> and visualize them with <code>viewmode.html?file_base_name</code>.</p>
<p>However, when modifying an XML file after rendering it once, changes to the mindmap were not taken into consideration when refreshing the page.</p>
<p>This happened because the WiseMapping editor agressively caches mindmaps in your browser LocalStorage and does not expect changes on those XML files outside its saving mechanism. Hence I had to add this line to disable the cache:</p>
<div class="highlight"><pre><span></span><code>persistence.discardChanges(mapId);
</code></pre></div>
<h3>Converting text mindmaps to XML</h3>
<p>Last but not least, remained to convert textual mindmaps into WiseMapping XML format.</p>
<p>In order to do so I wrote a Python script named <code>wisemapping_txt2xml.py</code> that uses a <code>pyparsing</code> to parse the pseudo-markdown syntax:</p>
<ul>
<li>Markdown:</li>
</ul>
<pre>Markdown styles
Font styles
**bold**
__italic__
__**bold italic**__
Hyperlinks
[My blog](https://chezsoi.org/lucas/blog)
![](https://chezsoi.org/lucas/blog/images/2014/Jul/bw-2.jpg)
</pre>
<div class="highlight"><pre><span></span><code>./wisemapping_txt2xml.py --images-size 100,100 --no-shrink markdown_example.txt > wise-editor/src/main/webapp/samples/markdown_example.xml
</code></pre></div>
<p>The result:</p>
<iframe src="/lucas/mindmap/mindmap-viewer/?markdown_example" height="300" width="800"></iframe>
<ul>
<li>WiseMaping icons (they are all in <code>wise-editor/src/main/webapp/icons/</code>)</li>
</ul>
<pre>Blah blah blah !icon=${icon_name}</pre>
<div class="highlight"><pre><span></span><code>./wisemapping_txt2xml.py icons_example.txt > wise-editor/src/main/webapp/samples/icons_example.xml
</code></pre></div>
<p>The result:</p>
<iframe src="/lucas/mindmap/mindmap-viewer/?icons_example" height="450" width="800"></iframe>
<ul>
<li>and with inline attributes like <code><!--fontStyle=";;#dfcfe6;;;" bgColor="#0a0a08"--></code>, full support for all the features in the default <a href="/lucas/mindmap/mindmap-viewer/samples/welcome.xml"><code>welcome.xml</code></a> defined in an <code>welcome.txt</code>:</li>
</ul>
<iframe src="/lucas/mindmap/mindmap-viewer/?welcomeFromText" height="600" width="800"></iframe>
<h3>Final touches</h3>
<p>In the end, I noticed the links preview, when you hover over a node with an anchor, was broken: it uses <a href="http://pagepeeker.com">pagepeeker.com</a> to create website screenshots, but oviously that doesn't work on localhost.
Comparing with <a href="https://framindmap.org">Framindmap</a> fork of WiseMapping, I realized they also fixed in their own way the JS loader hell.
And in their version, they replaced the pagepeeker screenshot by a simple button, which I preferred:</p>
<div class="highlight"><pre><span></span><code><span class="n">git</span> <span class="n">clone</span> <span class="nl">https:</span><span class="c1">//framagit.org/framasoft/framindmap.git</span>
<span class="n">cp</span> <span class="n">framindmap</span><span class="o">/</span><span class="n">webapps</span><span class="o">/</span><span class="n">wisemapping</span><span class="o">/</span><span class="n">js</span><span class="o">/</span><span class="n">mindplot</span><span class="o">-</span><span class="n">min</span><span class="p">.</span><span class="n">js</span> <span class="n">wise</span><span class="o">-</span><span class="n">editor</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">main</span><span class="o">/</span><span class="n">webapp</span><span class="o">/</span><span class="n">js</span><span class="o">/</span><span class="n">mindplot</span><span class="o">-</span><span class="n">bundle</span><span class="p">.</span><span class="n">js</span>
<span class="p">#</span> <span class="n">puis</span> <span class="n">regénérer</span> <span class="n">wise</span><span class="o">-</span><span class="n">editor</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">main</span><span class="o">/</span><span class="n">webapp</span><span class="o">/</span><span class="n">js</span><span class="o">/</span><span class="n">editor</span><span class="p">.</span><span class="n">js</span>
</code></pre></div>Solarized mindmaps with Python and graphviz2017-02-22T12:02:00+01:002017-02-22T12:02:00+01:00Lucas Cimontag:chezsoi.org,2017-02-22:/lucas/blog/solarized-mindmaps-with-python-and-graphviz.html<p>This week I wrote <a href="https://github.com/Lucas-C/brain_dump">a small Python script</a>, that can generate a mindmap from a simple indented text input like this:</p>
<pre>Winter
december
january
february
Spring
march
april
may
Summer
june
july
august
Autumn
september
october
november
</pre>
<p>The command: <code>./graphviz_mindmap.py seasons.txt</code>.</p>
<p>The results, with various <code>layout</code> parameters:</p>
<p><img alt="default layout" src="images/2017/02/seasons_twopi.png"></p>
<p><img alt="sfdp layout" src="images/2017/02/seasons_sfdp.png"></p>
<p><img alt="dot layout" src="images/2017/02/seasons_dot.png"></p>
<p>Another …</p><p>This week I wrote <a href="https://github.com/Lucas-C/brain_dump">a small Python script</a>, that can generate a mindmap from a simple indented text input like this:</p>
<pre>Winter
december
january
february
Spring
march
april
may
Summer
june
july
august
Autumn
september
october
november
</pre>
<p>The command: <code>./graphviz_mindmap.py seasons.txt</code>.</p>
<p>The results, with various <code>layout</code> parameters:</p>
<p><img alt="default layout" src="images/2017/02/seasons_twopi.png"></p>
<p><img alt="sfdp layout" src="images/2017/02/seasons_sfdp.png"></p>
<p><img alt="dot layout" src="images/2017/02/seasons_dot.png"></p>
<p>Another, deeper, example:</p>
<pre>Types of Chocolate
Baking Chocolate
contains nothing but chocolate liquor
Cocoa Powder
cocoa solids remain when most of the pressed into liquor.
Chocolate liquor
center(nib) of the cocoa bean, ground until liquifies, no alcohol.
Milk Chocolate-
contains 10% chocolate Liquor, most commonly eaten.
White Chocolate
contains no cocoa powder or chocolate liquor.
Dutch-Pressed Cocoa
has a darker color but milder chocolate flavor.
Bittersweet Chocolate
the darkest of eating chocolate liquor, has the highest %
may contain extra cocoa butter
Semi Sweet Chocolate
often referred to as dark chocolate, 35-45% Chocolate liquor
</pre>
<p><img alt="default layout" src="images/2017/02/chocolates_twopi.png"></p>
<p>A last one:</p>
<pre>The Bubonic Plague
What is it?
Some may know it as 'The Black Death'
It is a disease that once hit Europe in the Middle Ages
In just two years, 25 million people died of the plague. In ten years, the plague had killed over 1/3 of Europe's population.
Religious beliefs
Many people had thought that it was God punishing them for being wicked.
How did it spread?
Infected rats carried the disease.
The Bubonic Plague started in Asia and then struck Africa and Europe.
How did people react to this disease?
People locked their doors to protect themselves from the awful disease.
Others had burnt the hours filled with the dead and the sky was filled with ashes.
Sarah Bernal 8AE
1348-1349
</pre>
<p><img alt="default layout" src="images/2017/02/plague_twopi.png"></p>Making HTTPS calls in a pure Groovy shared lib for Jenkins pipeline2017-02-10T15:02:00+01:002017-02-10T15:02:00+01:00Lucas Cimontag:chezsoi.org,2017-02-10:/lucas/blog/making-https-calls-in-a-pure-groovy-shared-lib-for-jenkins-pipeline.html<p><img alt="Demonic version of the Jenkins logo" src="images/2017/02/butler-devil.png"></p>
<p>Recently I lost a lot of time on this. Hence I want to share a working solution, even if i cannot take the time to detail the issue.</p>
<p>I'm taking about writing reusable code for Jenkinsfiles : <a href="https://jenkins.io/doc/book/pipeline/shared-libraries/">https://jenkins.io/doc/book/pipeline/shared-libraries/</a></p>
<p>One cannot simply use Groovy <code>HTTPBuilder</code>, because …</p><p><img alt="Demonic version of the Jenkins logo" src="images/2017/02/butler-devil.png"></p>
<p>Recently I lost a lot of time on this. Hence I want to share a working solution, even if i cannot take the time to detail the issue.</p>
<p>I'm taking about writing reusable code for Jenkinsfiles : <a href="https://jenkins.io/doc/book/pipeline/shared-libraries/">https://jenkins.io/doc/book/pipeline/shared-libraries/</a></p>
<p>One cannot simply use Groovy <code>HTTPBuilder</code>, because of Jenkins 2 custom Groovy interpreter that follow the <em>Continuation Passing Style</em> paradigm : <a href="https://github.com/jenkinsci/workflow-cps-plugin#technical-design">https://github.com/jenkinsci/workflow-cps-plugin#technical-design</a></p>
<p>Hence the solution is to invoke the <a href="https://jenkins.io/doc/pipeline/steps/http_request/">http_request</a> plugin :</p>
<div class="highlight"><pre><span></span><code><span class="nv">def</span> <span class="nv">jenkinsHttpGet</span><span class="ss">(</span><span class="nv">Map</span> <span class="nv">args</span><span class="ss">)</span> {
<span class="nv">def</span> <span class="nv">response</span> <span class="o">=</span> <span class="nv">args</span>.<span class="nv">jenkinsWorkflowScript</span>.<span class="nv">invokeMethod</span> <span class="s1">'</span><span class="s">httpRequest</span><span class="s1">'</span>, [[<span class="nv">args</span>.<span class="nv">url</span>: <span class="nv">url</span>]] <span class="nv">as</span> <span class="nv">Object</span>[]
<span class="k">if</span> <span class="ss">(</span><span class="nv">response</span>.<span class="nv">status</span> <span class="o">!=</span> <span class="mi">200</span><span class="ss">)</span> {
<span class="nv">jenkinsWorkflowScript</span>.<span class="nv">invokeMethod</span> <span class="s1">'</span><span class="s">echo</span><span class="s1">'</span>, [<span class="nv">response</span>.<span class="nv">content</span>] <span class="nv">as</span> <span class="nv">Object</span>[]
<span class="nv">throw</span> <span class="nv">new</span> <span class="nv">HttpResponseException</span><span class="ss">(</span><span class="nv">response</span>.<span class="nv">status</span>, <span class="s1">'</span><span class="s">HTTP error</span><span class="s1">'</span><span class="ss">)</span>
}
<span class="nv">response</span>.<span class="nv">content</span>
}
</code></pre></div>
<p>This function can be used like this in a Jenkinsfile :</p>
<div class="highlight"><pre><span></span><code>jenkinsHttpGet jenkinsWorkflowScript:this, url:'https://httpbin.org/get'
</code></pre></div>
<p>The <code>invokeMethod</code> trick makes it possible to invoke builtin functions from classes defined in shared libs.</p>
<p>Beware ! This code can trigger a <code>javax.net.ssl.SSLException: java.lang.RuntimeException: Could not generate DH keypair</code> if you are using Java 7. Upgrading to an 1.8 JVM for Jenkins fixed the issue.</p>
<p>You'll also have to take good care to make all your Groovy classes <code>Serializable</code>.</p>
<p>At <a href="https://github.com/voyages-sncf-technologies">Voyages-Sncf</a>, we are in the process of building a Groovy library of common tools that can be used both in CLI and in Jenkinsfiles.</p>Analyse statique de code Java : Google Error Prone, Findbugs et PMD2017-01-18T13:01:00+01:002017-01-18T13:01:00+01:00Lucas Cimontag:chezsoi.org,2017-01-18:/lucas/blog/analyse-statique-de-code-java-google-error-prone-findbugs-et-pmd.html<p><img alt="Illustration of a bug insect under a magnifying glass" src="images/2017/01/img_3069.jpg"></p>
<p>Ce court article détaille comment mettre en place simplement 3 analyseurs de code statique avec Maven.</p>
<h2>Contexte</h2>
<p>Au sein de mon équipe à <a href="http://jobs.voyages-sncf.com">Voyages-Sncf</a>, le nombre de composants Java est en train d'augmenter.</p>
<p>Nous avons déjà toute une batterie de tests pour valider notre code, ainsi qu'un Sonar en place …</p><p><img alt="Illustration of a bug insect under a magnifying glass" src="images/2017/01/img_3069.jpg"></p>
<p>Ce court article détaille comment mettre en place simplement 3 analyseurs de code statique avec Maven.</p>
<h2>Contexte</h2>
<p>Au sein de mon équipe à <a href="http://jobs.voyages-sncf.com">Voyages-Sncf</a>, le nombre de composants Java est en train d'augmenter.</p>
<p>Nous avons déjà toute une batterie de tests pour valider notre code, ainsi qu'un Sonar en place.</p>
<p>Néanmoins, aucune <strong><em>analyse<strong> </strong>statique du code à la compilation</em></strong>, avant de commiter.</p>
<p>La mise en place de tels outils de validation a permis de<em> <strong>détecter un bug majeur</strong></em> : Findbugs a repéré qu'un accesseur de DAO retournait le mauvais champ.</p>
<p>Pour l'occasion, j'ai fais un petit tour d'horizon des analyseurs de code Java utilisés aujourd'hui, et j'ai en testé 3.</p>
<h2>Google Error Prone</h2>
<p><a href="http://errorprone.info/">Error Prone</a> est un petit nouveau (2012) parmi les analyseurs de code statique. Il est implémenté comme un "hook" du compileur <code>javac</code>, et est très facile à mettre en place avec Maven :</p>
<div class="highlight"><pre><span></span><code><span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.apache.maven.plugins<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>maven-compiler-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>3.3<span class="nt"></version></span>
<span class="nt"><configuration></span>
<span class="nt"><compilerId></span>javac-with-errorprone<span class="nt"></compilerId></span>
<span class="nt"><forceJavacCompilerUse></span>true<span class="nt"></forceJavacCompilerUse></span>
<span class="nt"><source></span>8<span class="nt"></source></span>
<span class="nt"><target></span>8<span class="nt"></target></span>
<span class="nt"></configuration></span>
<span class="nt"><dependencies></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.codehaus.plexus<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>plexus-compiler-javac-errorprone<span class="nt"></artifactId></span>
<span class="nt"><version></span>2.8.1<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="c"><!-- override plexus-compiler-javac-errorprone's dependency on Error Prone with the latest version --></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.google.errorprone<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>error_prone_core<span class="nt"></artifactId></span>
<span class="nt"><version></span>2.0.15<span class="nt"></version></span>
<span class="nt"></dependency></span>
<span class="nt"></dependencies></span>
<span class="nt"></plugin></span>
</code></pre></div>
<p>
En terme de config, on peut noter les tags <code>source</code> et <code>target</code> indiquant la version de Java utilisée.</p>
<h2>Findbugs</h2>
<p>Bien que le projet soit <a href="https://garygregory.wordpress.com/2016/11/07/of-the-demise-of-findbugs-and-monty-python/">actuellement au point mort</a>, Findbugs reste un outil très utilisé et très puissant. Tout un ensemble d'analyseurs supplémentaires peut même lui être ajouté via le plugin <a href="https://github.com/mebigfatguy/fb-contrib">fb-contrib</a>.</p>
<p>Voici comment le mettre en place avec Maven :</p>
<div class="highlight"><pre><span></span><code><span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.codehaus.mojo<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>findbugs-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>3.0.4<span class="nt"></version></span>
<span class="nt"><configuration></span>
<span class="nt"><failOnError></span>true<span class="nt"></failOnError></span>
<span class="nt"><threshold></span>Default<span class="nt"></threshold></span>
<span class="nt"><effort></span>Max<span class="nt"></effort></span>
<span class="nt"><omitVisitors></span>ExceptionSoftening,FieldCouldBeLocal,OverlyConcreteParameter,PossiblyRedundantMethodCalls,SuspiciousJDKVersionUse,UnnecessaryStoreBeforeReturn<span class="nt"></omitVisitors></span>
<span class="nt"><plugins></span>
<span class="nt"><plugin></span>
<span class="nt"><groupId></span>com.mebigfatguy.fb-contrib<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>fb-contrib<span class="nt"></artifactId></span>
<span class="nt"><version></span>6.8.1<span class="nt"></version></span>
<span class="nt"></plugin></span>
<span class="nt"></plugins></span>
<span class="nt"></configuration></span>
<span class="nt"><executions></span>
<span class="nt"><execution></span>
<span class="nt"><phase></span>compile<span class="nt"></phase></span>
<span class="nt"><goals></span>
<span class="nt"><goal></span>check<span class="nt"></goal></span>
<span class="nt"></goals></span>
<span class="nt"></execution></span>
<span class="nt"></executions></span>
<span class="nt"></plugin></span>
</code></pre></div>
<p>Ici, l'exécution du plugin se fera en phase Maven <code>compile</code>. Si vous ne souhaitez pas utiliser <code>fb-contrib</code>, il suffit de supprimer la section <code>plugins</code>.</p>
<p>Le degré de sévérité des analyseurs, peut être contrôlé via les tags <code>threshold</code> et <code>effort</code>. Pour supprimer des erreurs, on peut éliminer entièrement un analyseur en l'incluant dans le champ <code>omitVisitors</code>, ou bien en utilisant l'annotation <a href="http://findbugs.sourceforge.net/api/edu/umd/cs/findbugs/annotations/SuppressFBWarnings.html"><code>@SuppressFBWarning</code></a>.</p>
<p>Enfin, cet analyseur peut également produire des rapports HTML (même si ça peut être un peu <a href="http://stackoverflow.com/a/10365954/636849">alambiqué</a> à mettre en place), mais si on veut conserver le comportement failOnError il faut alors configurer <a href="http://stackoverflow.com/a/38655823/636849">deux phases d'exécution</a>.</p>
<h2>PMD</h2>
<p><a href="https://pmd.github.io/">PMD</a> est un outil très complet, capable d'analyser de nombreux languages.
Encore une fois, il est assez simple à mettre en place avec Maven:</p>
<div class="highlight"><pre><span></span><code><span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.apache.maven.plugins<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>maven-pmd-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>3.7<span class="nt"></version></span>
<span class="nt"><configuration></span>
<span class="nt"><failOnViolation></span>true<span class="nt"></failOnViolation></span>
<span class="nt"><failurePriority></span>5<span class="nt"></failurePriority></span>
<span class="nt"><printFailingErrors></span>true<span class="nt"></printFailingErrors></span>
<span class="nt"><linkXRef></span>false<span class="nt"></linkXRef></span>
<span class="nt"></configuration></span>
<span class="nt"><executions></span>
<span class="nt"><execution></span>
<span class="nt"><phase></span>compile<span class="nt"></phase></span>
<span class="nt"><goals></span>
<span class="nt"><goal></span>check<span class="nt"></goal></span>
<span class="nt"></goals></span>
<span class="nt"></execution></span>
<span class="nt"></executions></span>
<span class="nt"></plugin></span>
</code></pre></div>
<p>Encore une fois, l'exécution du plugin se fera en phase Maven <code>compile</code>.</p>
<p><code>printFailingErrors</code> permet d'avoir une liste d'erreur en terminal.</p>
<p>Pour <a href="http://pmd.sourceforge.net/pmd-4.3.0/suppressing.html">supprimer des erreurs</a>, on peut utiliser une annotation ou bien un simple commentaire "<code>NOPMD</code>" en fin de ligne de code.</p>
<h2>Infer</h2>
<p>Facebook a développé un outil très prometteur nommé <a href="http://fbinfer.com/">infer</a>, mais malheureusement il ne tourne pas sous Windows.</p>
<p>Si malgré tout vous souhaitez le tester, il est possible de <a href="https://www.lolware.net/2016/02/12/argon2-code-review.html">le lancer via Docker</a>.</p>
<p><img alt="Comic strip: Oh no! The robotos are KILLING us!!! - Bu WHY?!? We never programmed them to do THIS!!! - <code snippet:> if (isCrazyMurderingRobot = true) kill(humans)" src="images/2017/01/oh-no-the-robots.jpg"></p>Homepage pour rpg-bonhomme2017-01-09T22:01:00+01:002017-01-09T22:01:00+01:00Lucas Cimontag:chezsoi.org,2017-01-09:/lucas/blog/homepage-pour-rpg-bonhomme.html<p>Nouvelle feature pour mon petit projet d'éditeur/visualisateur de feuille de perso de jdr: <a href="https://github.com/Lucas-C/rpg-bonhomme">rpg-bonhomme</a> : une <a href="https://chezsoi.org/lucas/jdr/rpg-bonhomme">homepage</a> liste désormais tous les layouts et les persos créés !</p>
<iframe style="width:100%" height="500" src="https://chezsoi.org/lucas/jdr/rpg-bonhomme/"></iframe>Psi*Run : résumé de partie et avis personnel2017-01-04T19:01:00+01:002017-01-04T19:01:00+01:00Lucas Cimontag:chezsoi.org,2017-01-04:/lucas/blog/psi-run-resume-de-partie-et-avis-personnel.html<p><img alt="Couverture du jeu de rôle PsiRun" src="images/2017/01/psirun-couverture.png"></p>
<p>Cela fait des années que je fais du jeu de rôle avec des amis, et à chaque fin de séance c'est la même chose :
on se dit que ce serait quand même vachement bien d'écrire le compte rendu de la partie, mais tout le monde perd la motivation au final …</p><p><img alt="Couverture du jeu de rôle PsiRun" src="images/2017/01/psirun-couverture.png"></p>
<p>Cela fait des années que je fais du jeu de rôle avec des amis, et à chaque fin de séance c'est la même chose :
on se dit que ce serait quand même vachement bien d'écrire le compte rendu de la partie, mais tout le monde perd la motivation au final.</p>
<p>Alors cette fois je m'y suis collé !</p>
<p>Et voici le résultat:
<a href="https://chezsoi.org/lucas/jdr/psirun/CR_2016-12-18.html">https://chezsoi.org/lucas/jdr/psirun/CR_2016-12-18.html</a></p>
<p>Spoiler: la partie était super sympa, ça a été l'occasion de faire découvrir le jdr à une amie.
Le jeu est bien équilibré, nerveux et original.
Et très axé narration collective. Parfait pour improviser un <a href="/lucas/blog/tag/one-shot.html">One-Shot</a> !</p>
<p>Chapeau à son auteur, <a href="http://nightskygames.com/welcome/game/PsiRun">Meguey Baker</a>, et merci à Alexis Lamiable pour <a href="https://electric-goat.net/products/1">la VF</a>.</p>
<p>Et pour un autre avis sur ce jeu, je vous conseille l'<a href="http://awarestudios.blogspot.com/2014/12/psi-run.html">article de Grégory Pogorzelski</a>.</p>svg PITA bug of the day2017-01-04T14:01:00+01:002017-01-04T14:01:00+01:00Lucas Cimontag:chezsoi.org,2017-01-04:/lucas/blog/svg-pita-bug-of-the-day.html<p>Today I've been struggling to understand why this does not work in Firefox, but is OK in Chrome:</p>
<div class="highlight"><pre><span></span><code><span class="cp"><!DOCTYPE html></span>
<span class="p"><</span><span class="nt">html</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">"UTF-8"</span><span class="p">></span>
<span class="p"><</span><span class="nt">base</span> <span class="na">href</span><span class="o">=</span><span class="s">"/"</span><span class="p">></span>
<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">svg</span><span class="p">></span>
<span class="p"><</span><span class="nt">symbol</span> <span class="na">id</span><span class="o">=</span><span class="s">"pretty-circle"</span><span class="p">></span>
<span class="p"><</span><span class="nt">circle</span> <span class="na">cx</span><span class="o">=</span><span class="s">"15"</span> <span class="na">cy</span><span class="o">=</span><span class="s">"15"</span> <span class="na">r</span><span class="o">=</span><span class="s">"10"</span><span class="p">/></span>
<span class="p"></</span><span class="nt">symbol</span><span class="p">></span>
<span class="p"><</span><span class="nt">use</span> <span class="na">xlink:href</span><span class="o">=</span><span class="s">"#pretty-circle"</span><span class="p">></</span><span class="nt">use</span><span class="p">></span>
<span class="p"></</span><span class="nt">svg</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</code></pre></div>
<p>Here …</p><p>Today I've been struggling to understand why this does not work in Firefox, but is OK in Chrome:</p>
<div class="highlight"><pre><span></span><code><span class="cp"><!DOCTYPE html></span>
<span class="p"><</span><span class="nt">html</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">"UTF-8"</span><span class="p">></span>
<span class="p"><</span><span class="nt">base</span> <span class="na">href</span><span class="o">=</span><span class="s">"/"</span><span class="p">></span>
<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">svg</span><span class="p">></span>
<span class="p"><</span><span class="nt">symbol</span> <span class="na">id</span><span class="o">=</span><span class="s">"pretty-circle"</span><span class="p">></span>
<span class="p"><</span><span class="nt">circle</span> <span class="na">cx</span><span class="o">=</span><span class="s">"15"</span> <span class="na">cy</span><span class="o">=</span><span class="s">"15"</span> <span class="na">r</span><span class="o">=</span><span class="s">"10"</span><span class="p">/></span>
<span class="p"></</span><span class="nt">symbol</span><span class="p">></span>
<span class="p"><</span><span class="nt">use</span> <span class="na">xlink:href</span><span class="o">=</span><span class="s">"#pretty-circle"</span><span class="p">></</span><span class="nt">use</span><span class="p">></span>
<span class="p"></</span><span class="nt">svg</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</code></pre></div>
<p>Here is a <a href="http://codepen.io/anon/pen/WRNqxg">codepen snippet</a> to test if your browser display a circle or not.</p>
<p>I found the answer here: <a href="http://stackoverflow.com/a/18265336/636849">http://stackoverflow.com/a/18265336/636849</a></p>
<p>Yes, that innocuous <code><base></code> tag is the culprit.</p>
<p><img alt="Angry frustrated face" src="images/wwcb/angry-must-resist.jpeg"></p>
<p>Beware of this bug, as now with Angular 2 a <code>base</code> meta tag has to be there by default.</p>Une variante pour The Game2016-11-13T18:11:00+01:002016-11-13T18:11:00+01:00Lucas Cimontag:chezsoi.org,2016-11-13:/lucas/blog/une-variante-pour-the-game.html<p><img alt="Boîte du jeu The Game" src="images/2016/11/TheGame.png"></p>
<p><a href="//www.trictrac.net/jeu-de-societe/the-game-1">The Game</a> est un très chouette jeu de cartes coopératif. Voici une variante que nous avons testé hier soir à trois, sur une idée originale d'un ami (merci Nicolas !).</p>
<p>Le principe est d'ajouter une dimension tactique en rajoutant des jetons au jeu (n'importe quels pions feront l'affaire) :</p>
<ul>
<li>la main de …</li></ul><p><img alt="Boîte du jeu The Game" src="images/2016/11/TheGame.png"></p>
<p><a href="//www.trictrac.net/jeu-de-societe/the-game-1">The Game</a> est un très chouette jeu de cartes coopératif. Voici une variante que nous avons testé hier soir à trois, sur une idée originale d'un ami (merci Nicolas !).</p>
<p>Le principe est d'ajouter une dimension tactique en rajoutant des jetons au jeu (n'importe quels pions feront l'affaire) :</p>
<ul>
<li>la main de chaque joueur est réduite d'une carte</li>
<li>la partie débute avec 2 jetons par joueur et aucun au centre de la table</li>
<li>à son tour, un joueur peut choisir de jouer normalement ou bien de ne jouer qu'<strong>une</strong> carte en plaçant au centre de la table <strong>un de ses jeton</strong>; ou bien encore il peut passer entièrement son tour en y plaçant <strong>deux jetons</strong></li>
<li>lorsqu'un joueur pose une carte en effectuant <strong>un saut</strong>, <strong>il récupère immédiatement un jeton</strong> s'il y en a au centre de la table</li>
</ul>
<p>Une dernière règle peut être utilisée pour éviter <a href="//boardgamegeek.com/article/24207068">cette situation bloquante</a> :</p>
<ul>
<li>pour un jeton, un joueur peut déclencher un échange d'une carte avec un autre joueur (faces cachées)</li>
</ul>
<p>Les parties de test que nous avons effectué avec cette règle se sont révélées délicates mais passionnantes !</p>
<p>Quelques autres variantes sur <a href="//boardgamegeek.com/boardgame/173090/game/forums/0">BoardGameGeek</a> :</p>
<ul>
<li>
<p>permettre de <a href="//boardgamegeek.com/thread/1372665/single-card-sharing-add-more-cooperation">poser devant soi une unique carte de sa main face visible</a></p>
</li>
<li>
<p>plein d'idées intéressantes sur <a href="//boardgamegeek.com/thread/1397090/ideas-variantsexpansionschanges-game">ce thread</a> : un deck alternatif coloré en dégradé de couleurs; un rôle de "traître" qui doit finir le jeu dès que la pioche est terminée; la possibilité de récupérer des cartes de la défausse avec l'objectif compétitif pour un joueur de poser toutes ses cartes d'un coup pour avoir un point bonus...</p>
</li>
</ul>
<p><strong>EDIT [05/05/207]</strong> Une autre variante intéressante: jouer à jeu ouvert. Le jeu change énormément : plus de communication à demi-mots, il devient exclusivement tactique. Bien sûr il perd un peu de son sel, mais c'est très amusant à essayer en réduisant le nombre de cartes en main de 1 ou 2 pour corser l'affaire !</p>
<hr>
<div style="font-size: small">Au passage, sachez que si vous avez un jeu de <a href="//www.trictrac.net/jeu-de-societe/6-qui-prend-0">6 qui prend</a> alors vous avez déjà tout le nécessaire pour joueur à The Game ;)</div>Til Cows Tear Us Apart et Hop Slide2016-11-02T20:11:00+01:002016-11-02T20:11:00+01:00Lucas Cimontag:chezsoi.org,2016-11-02:/lucas/blog/til-cows-tear-us-apart-et-hop-slide.html<p>Ce soir, je veux vous parler de petits jeux <strong>coup de coeur</strong>.
Deux petits jeux gratuits, créés par des développeurs indépendants.</p>
<hr>
<p>Le premier se nomme <a href="//pierrec.itch.io/til-the-cows-tear-us-apart">'Til Cows Tear Us Apart</a>.
Il a été conçu par <a href="//oujevipo.fr/qui-suis-je/">Pierrec</a>, le fantastique auteur de l'Oujevipo, au cours d'une GameJam sur le thème <a href="//itch.io/jam/space-cowboy-jam">"Space Cowboy …</a></p><p>Ce soir, je veux vous parler de petits jeux <strong>coup de coeur</strong>.
Deux petits jeux gratuits, créés par des développeurs indépendants.</p>
<hr>
<p>Le premier se nomme <a href="//pierrec.itch.io/til-the-cows-tear-us-apart">'Til Cows Tear Us Apart</a>.
Il a été conçu par <a href="//oujevipo.fr/qui-suis-je/">Pierrec</a>, le fantastique auteur de l'Oujevipo, au cours d'une GameJam sur le thème <a href="//itch.io/jam/space-cowboy-jam">"Space Cowboy"</a>.</p>
<p><img alt="Capture d'écran du jeu Til Cows Tear Us Apart" src="images/2016/11/Screenshot-1.png"></p>
<p>Je dois avouer que ce thème à lui tout seul me séduit déjà beaucoup, étant un très grand fan de <a href="//www.imdb.com/title/tt0213338/">Cowboy Beebop</a> et de <a href="//www.imdb.com/title/tt0303461/">Firefly</a> par exemple.
Et au delà du thème, ce jeu partage un point commun avec ces deux séries (<a href="http://oujevipo.fr/general/4406-til-cows-tear-us-apart-web/">dont il s'inspire !</a>): <a href="//tilcowstearusapart.bandcamp.com/album/til-cows-tear-us-apart-ost">une bande son magnifique</a> <sup><a href="#fn1" id="ref1" style="font-size: small">[1]</a></sup>.</p>
<p>Il s'agit d'un jeu créé avec le vénérable <a href="http://www.adventuregamestudio.co.uk">Adventure Game Studio</a>.
Les graphismes sont simplissimes: une seule pièce, le cockpit d'un vaisseau spatial, toute en gros pixels.
Le gameplay, lui, est limité à un simple choix de dialogues: ne vous attendez pas à autre chose, il s'agit purement et simplement d'une histoire à choix multiples.
Aucun réflexe n'est requis, et pas de puzzles à résoudre.
Une histoire simple, une histoire courte, mais tellement bien racontée !</p>
<p>Je ne veux pas vous gâcher la surprise, mais je vous encourage vivement à tenter l'aventure:
le jeu se finit en 20 minutes, mais ça m'a suffit pour me prendre d'affection pour les personnages.</p>
<p>Sachez également qu'une préquelle existe: <a href="//pierrec.itch.io/stuck-in-a-muddle-with-you">Stuck in a muddle with you</a>.</p>
<hr>
<p><a href="//managore.itch.io/hopslide">Hop Slide</a> a lui aussi été conçu lors d'une Game Jam, la <a href="//ludumdare.com/compo/ludum-dare-30/">Ludum Dare 30</a> sur le thème des "Mondes Connectés", par <a href="//managore.itch.io/">Daniel Linssen</a>, dont j'avais déjà beaucoup apprécié le précédent jeu <a href="//managore.itch.io/haemo">Haemo</a>.</p>
<p><img alt="Capture d'écran du jeu Hop Slide" src="images/2016/11/Map.png"></p>
<p>Je vous recommande de jouer à ce jeu sans chercher ni explications ni d'indices sur le web.
Sachez juste qu'il s'agit d'un puzzle constitué de 2 exécutables, <strong>Hop.exe</strong> et <strong>Slide.exe</strong>, qu'il faut lancer tous 2 ensemble.</p>
<p>C'est un de ces jeux très malin, qu'il vaut mieux découvrir et comprendre par soi-même. Nul texte d'introduction ou d'explications, seuls de petits détails visuels pour vous aider à résoudre les 6 énigmes du jeu.</p>
<p>Mais quelle satisfaction à résoudre ces énigmes ô combien originales !</p>
<hr>
<p><sup id="fn1">1. Téléchargeable avec <a href="//github.com/iheanyi/bandcamp-dl">bandcamp-dl</a>. <a href="#ref1">↩</a></sup></p>Resources pour Mr Jack et Pandémie2016-11-02T19:11:00+01:002016-11-02T19:11:00+01:00Lucas Cimontag:chezsoi.org,2016-11-02:/lucas/blog/resources-pour-mr-jack-et-pandemie.html<p><img alt="Boîtes de ces deux jeux de société" src="images/2016/11/P1040810_small.JPG"></p>
<p>Un petit article pour partager quelques trouvailles de variantes et de scénarios pour ces deux excellents jeux.</p>
<p>Nous avons profité de soldes la semaine dernière pour les acheter, et nous les avons testé à deux, avec ma compagne, durant ce week-end étendu de la Toussaint.</p>
<hr>
<p>Nous connaissions déjà <a href="//www.trictrac.net/jeu-de-societe/pandemie-0">Pandémie</a>, mais …</p><p><img alt="Boîtes de ces deux jeux de société" src="images/2016/11/P1040810_small.JPG"></p>
<p>Un petit article pour partager quelques trouvailles de variantes et de scénarios pour ces deux excellents jeux.</p>
<p>Nous avons profité de soldes la semaine dernière pour les acheter, et nous les avons testé à deux, avec ma compagne, durant ce week-end étendu de la Toussaint.</p>
<hr>
<p>Nous connaissions déjà <a href="//www.trictrac.net/jeu-de-societe/pandemie-0">Pandémie</a>, mais c'était la première fois que nous le testions à deux joueurs. Nous n'avons pas été déçu ! Et il est coriace: sur 4 parties en difficulté standard, nous n'en avons gagné qu'une.</p>
<p>Personnellement, j'ai juste été un peu frustré par le gros facteur aléatoire dû au tirage des cartes des 2 paquets: j'ai l'impression que la difficulté d'une partie tient beaucoup plus à ce paramètre qu'à la capacité de réflexion et d'anticipation des joueurs.
Nous avons tous deux également trouvé que le jeu avait beaucoup de points communs avec <a href="//www.trictrac.net/jeu-de-societe/le-desert-interdit">Le Désert Interdit</a>, ce qui n'est absolument pas gênant comme c'est un de nos jeux favoris !</p>
<p>Je suis ensuite parti en quête de contenu additionnel créé par des fans sur le net.
Voici donc quelques chouettes resources:</p>
<ul>
<li>
<p><a href="https://www.zmangames.com/en/games/#/P">tous les livres de règles en anglais sur le site de l'éditeur américain Z-Man</a>.
On peut notament y trouver 2 scénarios prometteurs, <a href="https://www.google.fr/search?q=site%3Azmangames.com+filetype%3Apdf+pandemic+isolation"><strong>Isolation</strong></a> & <a href="https://www.google.fr/search?q=site%3Azmangames.com+filetype%3Apdf+pandemic+government+shutdown"><strong>Government Shutdown</strong></a>, que nous comptons tester bientôt.
Il y a également toutes les règles de l'extension <strong>On The Brink</strong>, y compris la très intéressante règle du <strong>BioTerroriste</strong>.</p>
</li>
<li>
<p>j'ai également trouvé sur le site BoardGameGeek une <a href="//boardgamegeek.com/filepage/33653/bioterrorist-variant-pdf-rules-v09">règle alternative de <strong>BioTerroriste</strong> (PDF, 87 Ko)</a>, imaginé par un fan avant la sortie de l'extension.</p>
</li>
<li>
<p>ce site comporte également <a href="//boardgamegeek.com/filepage/64531/all-pandemic-roles-official-custom">une liste exhaustive de tous les rôles possibles</a>, sous forme de page web "standalone" avec un bouton "génération d'une sélection aléatoire de rôles".</p>
</li>
<li>
<p>enfin, j'ai aussi trouvé sur le forum de BoardGameGeek <a href="//boardgamegeek.com/thread/382345/4-exciting-new-role-variants">4 rôles supplémentaires très intéressants</a> imaginé par un fan :</p>
<ul>
<li>
<p><strong>Aide-soignant</strong>: lorsque vous passez par une ville ne comportant qu'un seul cube, ce cube est retiré automatiquement.
Cet effet s'applique également aux villes adjacentes pour les maladies qui ont été guéries.</p>
</li>
<li>
<p><strong>Coordinateur</strong> des transports: les actions "Traiter une maladie", "Construire une station de recherche", "Partage de connaissances", et "Vol charteur" peuvent être appliquées dans des villes adjacentes pour 1 action.</p>
</li>
<li>
<p><strong>Météorologiste</strong>: à n'importe quel moment de votre tour, regardez les 3 cartes du dessus du paquet Infection. Ceci ne nécessite pas de dépenser d'action. Vous pouvez également regarder les 3 cartes du dessus du paquet de cartes Joueur pour 1 action.</p>
</li>
<li>
<p><strong>Ingénieur</strong>: échangez n'importe quelle carte de votre main avec celle du dessus de la défausse des cartes Joueur pour 1 action.
Note: vous ne pouvez pas récupérer une carte "Epidémie" ou "Evênement Spécial" ainsi.</p>
</li>
</ul>
</li>
</ul>
<p><strong>EDIT [2019/09/02]</strong> : Nous venons de tester le scénario <a href="https://boardgamegeek.com/filepage/138412/scenario-zombies"><strong>Zombies</strong></a>
et il est vraiment excellent ! La variante de règle qui est introduite change de manière très intéressante le jeu.</p>
<p><strong>EDIT [2019/11/23]</strong> : Je viens de découvrir sur BGG une prometteuse campagne gratuite <a href="https://boardgamegeek.com/filepage/191584/pandemic-story-mode-season-1">Pandemic Story Mode</a>
Je viens également de publier deux scénarios de mon cru:
<a href="pandemic-worldwide-research-program-and-mass-migrations.html">Pandemic: Worldwide Research Program & Mass Migrations</a></p>
<p><strong>EDIT [2020/06/05]</strong> : Nous avons testé à 2 un scénario COVID-19 pour ce jeu, conçu par Trevor Bender,
et nous l'avons très amusant et bien équilibré :
<a href="https://www.c3iopscenter.com/pages/wargame-room-store/#!/COVID-19-A-Pandemic-Scenario-C3i-eBook-Edition/p/185408244/category=33205167">COVID-19: A Pandemic Scenario</a>.
Une traduction française par Antoine Bourguilleau : <a href="https://drive.google.com/file/d/1_kFzvSi-cD7ikxeg-ymfmNbQZzhDOemu/view">fichier .docx via Google Drive</a></p>
<hr>
<p><a href="//www.trictrac.net/jeu-de-societe/mr-jack">Mr Jack</a> a été une super découverte ! J'en avais seulement entendu parler jusqu'à présent, et le jeu se révèle un très bon défi tactique asymétrique, savamment équilibré.</p>
<p>Selon BoardGameGeek (toujours), les statistiques de parties en ligne du jeu semblent indiquer un léger avantage à l'inspecteur, qui gagnerait dans 60% des parties.
<a href="//boardgamegeek.com/thread/288086/tweaking-mr-jack-towards-5050-balance">Une variante intéressante</a> pour équilibrer ça a été proposée sur le forum: durant les 2 premiers tours, laisser le choix à Jack au début du tour de la répartition des cartes: Jack-Inspecteur-Inspecteur-Jack ou bien Inspecteur-Jack-Jack-Inspecteur.</p>
<p>Enfin, <a href="//boardgamegeek.com/thread/131844/coach-expansion">une intéressante extension</a> a été proposée: <strong>la calèche</strong></p>
<ol>
<li>la nouvelle pièce, la calèche, débute sur la case de la statue, au milieu de la carte</li>
<li>un joueur peut déplacer la calèche au lieu du personnage qu'il a selectionné</li>
<li>la calèche ne peut jamais être utilisée à la place du 4e personnage</li>
<li>la calèche ne peut être déplacée qu'une seule fois par tour</li>
<li>la calèche peut se déplacer jusqu'à 8 cases</li>
<li>durant son 1er déplacement, il doit quitter le parc par une des 2 cases directement au Nord et au Sud</li>
<li>elle peut ensuite se déplacer à travers les rues comme tout autre personnage, mais ne peut pas traverser les maisons, utiliser les égouts ou sortir du quartier</li>
<li>si la calèche entre dans une case qui contient un personnage, elle peut le faire monter à bord</li>
<li>la calèche ne peut transporter qu'un seul personnage</li>
<li>à la fin du déplacement, le personnage dans la calèche doit descendre sur une case adjacente</li>
<li>un personnage peut descendre sur un autre pour faire une accusation, mais pas directement sortir du quartier ainsi</li>
</ol>Server-less reveal.js slides2016-10-12T11:10:00+02:002016-10-12T11:10:00+02:00Lucas Cimontag:chezsoi.org,2016-10-12:/lucas/blog/server-less-reveal-js-slides.html<p>I love <a href="http://lab.hakim.se/reveal-js">reveal.js</a>. I've been using it for years. But the other day, I was badly bitten by its requirement on a local HTTP server.</p>
<p>What happenned was that I was invited to make a short presentation in a youth and cultural center. I had prepared some slides with …</p><p>I love <a href="http://lab.hakim.se/reveal-js">reveal.js</a>. I've been using it for years. But the other day, I was badly bitten by its requirement on a local HTTP server.</p>
<p>What happenned was that I was invited to make a short presentation in a youth and cultural center. I had prepared some slides with reveal.js, but once in their computer room, I realized a firewall was installed on each and every computer there, blocking any attempt to launch a simple local server with <code>python -m http.server</code> or a simple <a href="https://www.cesanta.com/products/binary">Mongoose server</a>. And no one had the admin rights over this firewall !</p>
<p><img alt="Daffy Duck slamming itself on a tree" src="images/wwcb/daffy_tree_slam.gif"></p>
<p>There is currently <a href="https://github.com/hakimel/reveal.js/issues/610">no</a> <a href="https://github.com/hakimel/reveal.js/issues/673">plan</a> from the devs behind reveal.js to get rid of this requirement. Hence I developed a workaround !</p>
<p>With the markdown plugin, a typical usage of reveal.js looks like this (taken from their <a href="https://github.com/hakimel/reveal.js/blob/master/index.html">base index.html</a>) :</p>
<div class="highlight"><pre><span></span><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"reveal"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"slides"</span><span class="nt">></span>
<span class="nt"><section</span> <span class="na">data-markdown=</span><span class="s">"MySlides.md"</span> <span class="na">data-separator=</span><span class="s">"^\n\n\n"</span> <span class="na">data-separator-vertical=</span><span class="s">"^\n\n"</span> <span class="na">data-notes=</span><span class="s">"^Note:"</span> <span class="na">data-charset=</span><span class="s">"utf-8"</span><span class="nt">></section></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"><script</span> <span class="na">src=</span><span class="s">"js/reveal.js"</span><span class="nt">></script></span>
<span class="nt"><script></span>
Reveal.initialize({
...
dependencies: [
{ src: 'plugin/markdown/marked.js' },
{ src: 'plugin/markdown/markdown.js' },
...
});
<span class="nt"></script></span>
</code></pre></div>
<p>The requirement for a local HTTP server actually comes from the <a href="https://github.com/hakimel/reveal.js/blob/master/plugin/markdown/markdown.js#L222">markdown plugin that performs an AJAX request</a> to retrieve <code>MySlides.md</code>.</p>
<p>Hence, my idea for a workaround : convert <code>MySlides.md</code> into a <code>MySlides.js</code> script, so that no AJAX is needed any more !</p>
<p>Here is a 30-lines Bash script that does exactly that : <a href="https://github.com/Lucas-C/linux_configuration/blob/master/bin/reveal_md2js.sh"><code>reveal_md2js.sh</code></a></p>
<p>In our exemple, you would just use it like this :</p>
<div class="highlight"><pre><span></span><code>./reveal_md2js.sh MySlides.md
MySlides.js has been successfully generated
</code></pre></div>
<p>Then, in your <code>index.html</code>, just replace the line :</p>
<div class="highlight"><pre><span></span><code><span class="nt"><section</span> <span class="na">data-markdown=</span><span class="s">"MySlides.md"</span> <span class="err">...</span><span class="nt">></section></span>
</code></pre></div>
<p>by :</p>
<div class="highlight"><pre><span></span><code><span class="nt"><script</span> <span class="na">src=</span><span class="s">"MySlides.js"</span> <span class="err">...</span><span class="nt">></script></span>
</code></pre></div>
<p>And TADAAA ! You now have some reveal.js slides that are fully-functional with the <code>file://</code> procotol, e.g. if you directly open <code>index.html</code> with your browser.</p>
<p><strong>EDIT 2016/10/13</strong> : actually an even simpler solution is to put your Markdown <strong>directly</strong> in a <code><section></code> in you <code>index.html</code>.</p>Problème de maths de la lady du lac2016-09-03T11:09:00+02:002016-09-03T11:09:00+02:00Lucas Cimontag:chezsoi.org,2016-09-03:/lucas/blog/probleme-de-maths-de-la-lady-du-lac.html<p>Dans <a href="http://www.tangente-mag.com/numero.php?id=131">le numéro 171 du magazine tangente</a>, page 47, un intéressant petit problème mathématique est posé au lecteur.</p>
<blockquote>
<p>Une jeune femme était en vacances au bord du lac Circulaire, un grand plan d’eau artificiel ainsi nommé pour sa forme circulaire précise.
Pour échapper à un soupirant envahissant qui la …</p></blockquote><p>Dans <a href="http://www.tangente-mag.com/numero.php?id=131">le numéro 171 du magazine tangente</a>, page 47, un intéressant petit problème mathématique est posé au lecteur.</p>
<blockquote>
<p>Une jeune femme était en vacances au bord du lac Circulaire, un grand plan d’eau artificiel ainsi nommé pour sa forme circulaire précise.
Pour échapper à un soupirant envahissant qui la harcelait, elle monta dans une barque et rama jusqu’à un radeau ancré au centre du lac.
Son poursuivant décida de l’attendre sur le rivage, sachant qu’elle devrait revenir à terre.
Il pouvait marcher quatre fois plus vite qu’elle pouvait ramer, et pensait pouvoir la joindre dès que son bateau toucherait le bord du lac.
Mais la jeune femme, major de mathématiques au Radcliffe College et sportive de haut niveau, savait qu’une fois à terre,
elle pourrait distancer le fâcheux. Il était seulement nécessaire de mettre au point une stratégie pour accoster à un point du rivage avant qu’il ne puisse y arriver.
Elle trouva rapidement un plan assez simple.
<strong>Quelle était la stratégie de la jeune femme ?</strong>
On suppose qu’elle connaît à tout moment sa position exacte sur le lac.</p>
</blockquote>
<p>Cependant, la solution proposée par <em>tangente</em> ne me satisfait pas vraiment. L'énoncé de la question laisse un peu de liberté d'interprétation,
et la solution du magazine comporte, je crois, une petite incohérence.</p>
<p>Afin de visualiser les différentes solutions possibles selon les règles que l'on choisit de fixer,
j'ai développé <strong>un petit simulateur web</strong>: <a href="https://chezsoi.org/lucas/maths/lady_of_the_lake.html">https://chezsoi.org/lucas/maths/lady_of_the_lake.html</a>
(incluant la description de la solution de Tangente).</p>
<p>La configuration par défaut correspond à la solution de <em>tangente</em> :
<img alt="Capture d'écran de la simulation correspondant à la solution de Tangente" src="images/2016/09/SolutionTangente.png" style="width: 50%"></p>
<p>Cete solution prend comme postulat que la direction du soupirant est <strong>invariante</strong>, c'est-à-dire qu'il choisit une direction pour tourner autour du lac au départ, et <strong>qu'il n'en change jamais ensuite</strong>.</p>
<p>Néanmoins, avec un tel postulat, une solution bien <strong>plus simple</strong> est de prendre le soupirant <strong>à contre-pied</strong> :
<img alt="Capture d'écran de la simulation correspondant à la solution à contre-pied" src="images/2016/09/ContrePied.png" style="width: 50%"></p>
<p>Au-contraire, si on considère que le poursuivant <strong>adapte sa direction</strong> afin de se rapprocher systématiquement de la lady, alors <strong>la solution de <em>tangente</em> ne fonctionne pas</strong>:
<img alt="Capture d'écran de la simulation correspondant à la solution de Tangente avec soupirant changeant de direction" src="images/2016/09/SolutionTangenteAvecSoupirantChangeantDeDirection.png" style="width: 50%"></p>
<p>Essayons maintenant de simuler une lady qui <strong>s'adapte véritablement et dynamiquement</strong> à la position de son poursuivant,
<cite>"de sorte que le centre du lac soit toujours compris entre elle et son poursuivant sur le rivage, les trois points étant alignés"</cite>, comme propose la solution de <em>tangente</em> :
<img alt="Capture d'écran de la simulation correspondant à un soupirant dynamique 4 fois plus rapide" src="images/2016/09/FullAdaptiveWithRatio4.png" style="width: 50%"></p>
<p><strong>Que se passe-t-il ??</strong></p>
<p>Avec un rapport de vitesses de 4, la lady <strong>ne peut pas échapper à son poursuivant</strong>. Elle est bloquée au centre du lac, sans pouvoir le prendre de vitesse.
Il est intéressant de remarquer que sa trajectoire semble converger vers un cercle de rayon <em>r</em> / 4 (où <em>r</em> est le rayon du lac).</p>
<p>Enfin, on peut essayer des rapports de vitesses plus petits, pour essayer de déterminer quand il devient possible pour la lady de gagner.
Les valeurs obtenues avec le simulateur web sont sensibles au "pas" de simulation, mais avec un rapport de <strong>3.6</strong> on peut par exemple observer que la lady bat son poursuivant de vitesse :
<img alt="Capture d'écran de la simulation correspondant à un soupirant dynamique 3.6 fois plus rapide" src="images/2016/09/FullAdaptiveWithRatio3.6.png" style="width: 50%"></p>
<p>Qu'en pensez-vous ? N'hésitez pas à laisser votre avis sur ce problème en commentaire !</p>
<p><strong>[EDIT 2019/06/15]</strong> le mois dernier la chaîne Youtube Numberphile a sorti une vidéo au sujet de ce problème :
<a href="https://www.youtube.com/watch?v=vF_-ob9vseM">Game of Cat and Mouse</a>.
Le mathématicien Ben Sparks développe son raisonnement et démontre d'une part que j'avais tord,
car il existe une solution, mais d'autre part que ce n'est pas celle de Tangente non plus !
Il existe en effet une zone circulaire où la lady peut progressivement se placer à l'opposé de son poursuivant,
pour ensuite sprinter vers le rivage.</p>
<p><strong>[EDIT 2022/10/25]</strong> : <a href="https://www.youtube.com/channel/UCgkhWgBGRp0sdFy2MHDWfSg">El Jj</a> évoque ce problème dans l'une de se vidéos pour Mathctober, et le problème aurait été étudié par Leonard De Vinci :</p>
<iframe width="320" height="439" src="https://www.youtube.com/embed/cWhCVLLfBA8" title="21 - Bad Dog - #mathctober" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<style>
article iframe { display: block; margin: 0 auto; }
</style>Command history in a Python 3 virtualenv2016-06-30T11:06:00+02:002016-06-30T11:06:00+02:00Lucas Cimontag:chezsoi.org,2016-06-30:/lucas/blog/command-history-in-a-python-3-virtualenv.html<p>Due to a <a href="https://github.com/pypa/virtualenv/issues/355">long standing bug</a>, no history file will be kept of the commands you enter in an interactive shell when using a Python 3 virtualenv.</p>
<p>I found out a simple workaround. Simply put the following in your <code>~/.pythonrc</code> :</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">atexit</span><span class="o">,</span> <span class="nn">os</span><span class="o">,</span> <span class="nn">readline</span><span class="o">,</span> <span class="nn">sys</span>
<span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">version_info</span> <span class="o">>=</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">0 …</span></code></pre></div><p>Due to a <a href="https://github.com/pypa/virtualenv/issues/355">long standing bug</a>, no history file will be kept of the commands you enter in an interactive shell when using a Python 3 virtualenv.</p>
<p>I found out a simple workaround. Simply put the following in your <code>~/.pythonrc</code> :</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">atexit</span><span class="o">,</span> <span class="nn">os</span><span class="o">,</span> <span class="nn">readline</span><span class="o">,</span> <span class="nn">sys</span>
<span class="k">if</span> <span class="n">sys</span><span class="o">.</span><span class="n">version_info</span> <span class="o">>=</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="ow">and</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">sys</span><span class="p">,</span> <span class="s1">'real_prefix'</span><span class="p">):</span> <span class="c1"># in a VirtualEnv</span>
<span class="n">PYTHON_HISTORY_FILE</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'HOME'</span><span class="p">],</span> <span class="s1">'.python_history'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">PYTHON_HISTORY_FILE</span><span class="p">):</span>
<span class="n">readline</span><span class="o">.</span><span class="n">read_history_file</span><span class="p">(</span><span class="n">PYTHON_HISTORY_FILE</span><span class="p">)</span>
<span class="n">atexit</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">readline</span><span class="o">.</span><span class="n">write_history_file</span><span class="p">,</span> <span class="n">PYTHON_HISTORY_FILE</span><span class="p">)</span>
</code></pre></div>
<p><code>.pythonrc</code> files are loaded at startup by defining a <code>PYTHONSTARTUP</code> environment variable pointing to them. I prefer to load mine only when launching a REPL, but never when passing parameters to the <code>python</code> command (like script names), so I use the following function definion in my <code>.bashrc</code> :</p>
<div class="highlight"><pre><span></span><code>python <span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$#</span><span class="s2">"</span> -eq <span class="m">0</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
<span class="nv">PYTHONSTARTUP</span><span class="o">=</span>~/.pythonrc <span class="k">$(</span><span class="nb">type</span> -P python<span class="k">)</span>
<span class="k">else</span>
<span class="k">$(</span><span class="nb">type</span> -P python<span class="k">)</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
<span class="k">fi</span>
<span class="o">}</span>
</code></pre></div>Retrieving a file version (git commit & tag) based on its hash2016-06-27T19:06:00+02:002016-06-27T19:06:00+02:00Lucas Cimontag:chezsoi.org,2016-06-27:/lucas/blog/retrieving-a-file-version-git-commit-tag-based-on-its-hash.html<p>Today, I did some tests on a server where an old version of our project was deployed. At some point, I needed to identify which version of the code was there, and I wrote a pretty shell function to figure this out.</p>
<p>Yeah, I know what your thinking : <em>there must …</em></p><p>Today, I did some tests on a server where an old version of our project was deployed. At some point, I needed to identify which version of the code was there, and I wrote a pretty shell function to figure this out.</p>
<p>Yeah, I know what your thinking : <em>there must be another way</em>. Surely this information should be somewhere in your deployment system.
Probably. But that's no excuse to skip a good git scripting exercise !</p>
<p><img src="images/wwcb/you-got-it-wrong-alphabet.jpg" title="You're doing it wrong"></p>
<p>Ok, so here is the situation :</p>
<ul>
<li>a git repository cloned on my development machine</li>
<li>a remote server without git installed</li>
<li>a readable file on this server that originates from the git repository</li>
</ul>
<p>Now, the idea is to compute the file hash similarly to git, and then find a commit for which <code>git rev-parse</code> returns the same hash.</p>
<p>First thing first, lets build a function that does the same as <code>git hash-object</code> but does not require git. You can check <a href="http://stackoverflow.com/questions/7225313/how-does-git-compute-file-hashes">this SO answer</a> or the details on how git compute blob hashes. Here is my solution:</p>
<div class="highlight"><pre><span></span><code>git-hash-object <span class="o">()</span> <span class="o">{</span> <span class="c1"># USAGE: git-hash-object [-t type] file</span>
<span class="nb">local</span> <span class="nv">type</span><span class="o">=</span>blob
<span class="o">[</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">=</span> <span class="s2">"-t"</span> <span class="o">]</span> <span class="o">&&</span> <span class="nb">shift</span> <span class="o">&&</span> <span class="nv">type</span><span class="o">=</span><span class="nv">$1</span> <span class="o">&&</span> <span class="nb">shift</span>
<span class="c1"># depending on your git eol/autocrlf settings,</span>
<span class="c1"># you may want to substitute CRLFs by LFs first,</span>
<span class="c1"># by using `perl -pe 's/\r$//g'` instead of `cat` in the next 2 commands</span>
<span class="nb">local</span> <span class="nv">size</span><span class="o">=</span><span class="k">$(</span>cat <span class="nv">$1</span> <span class="p">|</span> wc -c <span class="p">|</span> sed <span class="s1">'s/ .*$//'</span><span class="k">)</span>
<span class="o">(</span> <span class="nb">echo</span> -en <span class="s2">"</span><span class="nv">$type</span><span class="s2"> </span><span class="nv">$size</span><span class="s2">\0"</span><span class="p">;</span> cat <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">)</span> <span class="p">|</span> sha1sum <span class="p">|</span> sed <span class="s1">'s/ .*$//'</span>
<span class="o">}</span>
</code></pre></div>
<p>Ok, lets write our search function now:</p>
<div class="highlight"><pre><span></span><code>git-identify-filehash <span class="o">()</span> <span class="o">{</span> <span class="c1"># USAGE: git-identify file hash</span>
<span class="nb">local</span> <span class="nv">file</span><span class="o">=</span><span class="si">${</span><span class="nv">1</span><span class="p">?</span><span class="si">}</span>
<span class="nb">local</span> <span class="nv">hash</span><span class="o">=</span><span class="si">${</span><span class="nv">2</span><span class="p">?</span><span class="si">}</span>
git log --format<span class="o">=</span><span class="s2">"%h %s"</span> <span class="nv">$file</span> <span class="p">|</span> <span class="k">while</span> <span class="nb">read</span> commit msg<span class="p">;</span> <span class="k">do</span>
<span class="k">if</span> <span class="o">[</span> <span class="k">$(</span>git rev-parse <span class="nv">$commit</span>:<span class="nv">$file</span><span class="k">)</span> <span class="o">=</span> <span class="s2">"</span><span class="nv">$hash</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
<span class="nb">echo</span> <span class="nv">$commit</span> <span class="nv">$msg</span> - oldest tag including this commit: <span class="se">\</span>
<span class="k">$(</span>git tag --contains <span class="nv">$commit</span> <span class="p">|</span> head -n <span class="m">1</span><span class="k">)</span>
<span class="nb">break</span>
<span class="k">fi</span>
<span class="k">done</span>
<span class="o">}</span>
</code></pre></div>
<p>Ok, I'm going to try this on the second-to-last version of <code>pre-commit</code> <code>main.py</code>, that is from commit <a href="https://github.com/pre-commit/pre-commit/blob/c3c98af/pre_commit/main.py">c3c98af</a> at the time I'm writing :</p>
<div class="highlight"><pre><span></span><code>$ wget http://github.com/pre-commit/pre-commit/raw/c3c98af/pre_commit/main.py
$ git hash-object main.py
e292c72c6c86e8809bd792a630e7f90ac811c385
<span class="c1"># Lets pretend we are on a remote server without git:</span>
$ git-hash-object main.py
e292c72c6c86e8809bd792a630e7f90ac811c385
<span class="c1"># Now lets identify this file hash in our repository clone</span>
$ git clone git@github.com:pre-commit/pre-commit.git
$ git-identify-filehash pre_commit/main.py e292c72c6c86e8809bd792a630e7f90ac811c385
c3c98af Support pre-commit from inside submodules - oldest tag including this commit: v0.7.1
</code></pre></div>
<p><img alt="Oh My God" src="images/wwcb/OMG.gif"></p>Battledev de RegionJobs : Python vs Java2016-04-14T20:04:00+02:002016-04-14T20:04:00+02:00Lucas Cimontag:chezsoi.org,2016-04-14:/lucas/blog/battledev-de-regionjobs-python-vs-java.html<p>En mars dernier, j'ai participé à la Battledev de RegionJobs.</p>
<p>Et c'était super fun.</p>
<p>Pour ceux qui ne connaissent pas, c'est une compétition de programmation en ligne, où l'on doit résoudre 5 questions de difficulté croissante en 2 heures.</p>
<p>J'ai eu l'occasion d'y participer avec quelques collègues, et ça a …</p><p>En mars dernier, j'ai participé à la Battledev de RegionJobs.</p>
<p>Et c'était super fun.</p>
<p>Pour ceux qui ne connaissent pas, c'est une compétition de programmation en ligne, où l'on doit résoudre 5 questions de difficulté croissante en 2 heures.</p>
<p>J'ai eu l'occasion d'y participer avec quelques collègues, et ça a été à la fois un chouette challenge et moment sympa à passer ensemble.</p>
<p>Le 8 novembre prochaine, une nouvelle édition à lieu, alors <a href="https://battledev.blogdumoderateur.com">inscrivez-vous</a> !
J'en serai :)</p>
<hr>
<p>Toutefois, il y a un petit quelque chose qui m'est resté en travers de la gorge suite à cette battle : <strong>pour la dernière question, les solutions étaient de complexité différente selon les langages</strong>.</p>
<p>Je m'explique: lors de la BattleDev, chaque participant à le choix de formuler sa réponse dans la langue de son choix. Personnellement, j'ai participé en Python.</p>
<p>En fin de concours, la société Isograd qui a mit en place le challenge révèle des solutions possibles à chaque question. Celles-ci sont disponibles sur cette page: <a href="http://www.isograd.com/FR/solutionconcours.php">http://www.isograd.com/FR/solutionconcours.php</a> (choisir "Battle Dev RegionsJob - Mars 2016").</p>
<p>Si vous allez y jeter un oeil, vous pourrez constater que selon les langages, la solution de la question 5 est plus ou moins complexe:</p>
<ul>
<li><code>C</code>, <code>NodeJS</code>, <code>Ruby</code> : pas de solution proposée</li>
<li><code>C#</code>, <code>C++</code>, <code>Java</code> : solution "simple" (<a href="https://github.com/Lucas-C/linux_configuration/blob/master/languages/python/battledev_regionsjobs_isograd_2016-03-22/challenge5_soluce.java">exemple de la solution Java</a>).</li>
<li><code>Python</code>, <code>PHP</code> : solution complexe à base de <code>RangeMinQuery</code> (<a href="https://github.com/Lucas-C/linux_configuration/blob/master/languages/python/battledev_regionsjobs_isograd_2016-03-22/challenge5_soluce.py">exemple de la solution Python</a>).</li>
</ul>
<p>Pourquoi ?
Je n'ai trouvé aucune justification officielle.
Mon hypothèse est la suivante: je pense que les interprêteurs des langages <code>PHP</code> et <code>Python</code> utilisé par Isograd dans son service d'évaluation des réponses sont bien moins performants que ceux pour <code>C#</code> / <code>C++</code> / <code>Java</code>. Probablement à cause la différence fondamentale entre ces 2 familles de langages : les uns sont compilés, les autres interprétés<sup><a href="#fn1" id="ref1" style="font-size: small">[1]</a></sup>.
Cette limitation impose d'être plus "malin" en terme d'algorithme pour les langages plus lents, d'où le recours à une classe <code>RangeMaxQuery</code> <strong>très</strong> bien pensée.</p>
<p>Néanmoins, il reste indéniable que les participants étaient fortement favorisés selon le langage choisi.</p>
<p>J'espère sincèrement que la prochaine édition de la BattleDev ne comportera pas ce genre d'inégalités linguistiques.</p>
<p><img alt="Animation tirée de The Big Lebowsky" src="images/wwcb/OnlyOneToGiveAShitAboutRules.gif" style="width: 60%"></p>
<hr>
<p><sup id="fn1">1. Même si en fait un code Python est transcris en bytecode de la même manière qu'en Java. <a href="#ref1">↩</a></sup></p>A Python iterator to list all UTF8 characters2016-04-14T12:04:00+02:002016-04-14T12:04:00+02:00Lucas Cimontag:chezsoi.org,2016-04-14:/lucas/blog/a-python-iterator-to-list-all-utf8-characters.html<p>Last week, I made up a basic TCP server in Python, to receive log lines. To split log lines, I used the ascii line feed ascii character : \n aka 0xa in hexadecimal.</p>
<p>But then I wondered : could this byte appear elsewhere in the UTF8-encoded strings of text I was sending …</p><p>Last week, I made up a basic TCP server in Python, to receive log lines. To split log lines, I used the ascii line feed ascii character : \n aka 0xa in hexadecimal.</p>
<p>But then I wondered : could this byte appear elsewhere in the UTF8-encoded strings of text I was sending ?</p>
<p>To find out, I wrote a <a href="//github.com/Lucas-C/linux_configuration/blob/master/languages/python/utf8_iterator.py">small Python script</a> that list all possible UTF8 characters. Here is its output:</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">time</span> python3 utf8_iterator.py
<span class="m">4</span>-bytes-max UTF8 potential characters count <span class="m">2164864</span>
<span class="m">4</span>-bytes-max UTF8 decodable characters count <span class="m">1112064</span>
UTF8 characters containing the 0x0a newline byte: <span class="o">[</span>b<span class="s1">'\n'</span><span class="o">]</span>
real 0m8.003s
</code></pre></div>
<p>So, there is no other UTF8 character containing the 0x0a byte !
In fact, all 128 ASCII characters are encoded in the same way in UTF8. More important: <strong>the bytes corresponding to the 128 ASCII characters never appear elsewhere in UTF8</strong>.</p>
<p>Nice property isn't it ?
Now go get a glance on the <a href="//en.wikipedia.org/wiki/UTF-8#History">Wikipedia page</a> to find out about this encoding intersting history.</p>
<p><a href="//en.wikipedia.org/wiki/Office_Space"><img alt="Animation tirée du film Office Space" src="images/wwcb/YeahThanks-IfWeCouldGetbackToWorkNowThatdBeGreat.jpg"></a></p>
<p>The Python script is certainly not the fastest for the task, but simple enough for my need, and could be useful to write tests for Python code processing byte strings: it can generate all the 1112064 valid UTF8 code points, plus some extra invalid ones.</p>Intro à Python & les frameworks web2016-04-14T11:04:00+02:002016-04-14T11:04:00+02:00Lucas Cimontag:chezsoi.org,2016-04-14:/lucas/blog/intro-a-python.html<p>Le mois dernier j'ai rapidement présenté le language Python et les frameworks web existants à mon équipe à <a href="http://jobs.voyages-sncf.com">Voyages-Sncf.com</a>.</p>
<p>Les slides sont accessibles <a href="/lucas/slides/python_frameworks_web_2016-02-26">ici</a>, ainsi que <a href="/lucas/slides/python_frameworks_web_2016-02-26/tks_2016-02-26.md">les sources Markdown</a> pour <a href="http://lab.hakim.se/reveal-js">Reveal.js</a>.</p>
<div style="text-align:center;"><iframe src="/lucas/slides/python_frameworks_web_2016-02-26/" width="600" height="400">
<p>Iframes non supportées. Cliquez sur le lien dans le paragraphe au-dessus pour accéder directement aux slides.</p>
</iframe></div>
<p>Il …</p><p>Le mois dernier j'ai rapidement présenté le language Python et les frameworks web existants à mon équipe à <a href="http://jobs.voyages-sncf.com">Voyages-Sncf.com</a>.</p>
<p>Les slides sont accessibles <a href="/lucas/slides/python_frameworks_web_2016-02-26">ici</a>, ainsi que <a href="/lucas/slides/python_frameworks_web_2016-02-26/tks_2016-02-26.md">les sources Markdown</a> pour <a href="http://lab.hakim.se/reveal-js">Reveal.js</a>.</p>
<div style="text-align:center;"><iframe src="/lucas/slides/python_frameworks_web_2016-02-26/" width="600" height="400">
<p>Iframes non supportées. Cliquez sur le lien dans le paragraphe au-dessus pour accéder directement aux slides.</p>
</iframe></div>
<p>Il manque toutefois quelque chose à cette présentation : une mention du formidable article <a href="https://www.paypal-engineering.com/2014/12/10/10-myths-of-enterprise-python/">10 Myths of Enterprise Python</a> et de ses arguments chocs.</p>Show me a hero2016-04-13T03:04:00+02:002016-04-13T03:04:00+02:00Lucas Cimontag:chezsoi.org,2016-04-13:/lucas/blog/show-me-a-hero.html<p>Depuis une dizaine d'années, j'ai cette petite idée dans un tiroir. Une série télé qui présenterait le quotidien de politiciens, présentés comme des super héros. Peut-être en insérrant des passages où ils se verraient les uns les autres en justaucorps et collans multicolores. Ou alors par un habile jeu de …</p><p>Depuis une dizaine d'années, j'ai cette petite idée dans un tiroir. Une série télé qui présenterait le quotidien de politiciens, présentés comme des super héros. Peut-être en insérrant des passages où ils se verraient les uns les autres en justaucorps et collans multicolores. Ou alors par un habile jeu de mise en scène, suggérer des regards lasers, ou des costumes moulants cachés sous les costards. Mais avec, au final, l'idée de mettre en évidence leurs egos, leurs ambitions, leurs capacités parfois hors du commun.</p>
<p>Une fois n'est pas coutume, je vais vous parler d'une série télé.</p>
<p>Les 6 épisodes de <em>Show me a hero</em> ont été diffusés sur HBO en 2015, et conçus par David Simon. Si ce nom ne vous dit rien, vous avez de la chance. Vous avez devant vous 5 saisons de ma série favorite à découvrir : <a href="//fr.wikipedia.org/wiki/Sur_%C3%A9coute"><em>The Wire</em></a>.</p>
<p>Ces deux séries ont beaucoup en commun. On retrouve des thèmes similaires, une construction <a href="//fr.wikipedia.org/wiki/Film_choral">chorale</a>, un réalisme quasi-historique.
J'ai été très ému en les regardant. Et j'ai ressenti un élan d'inspiration, un frissonnement, une envie de prendre ces personnages pour modèle. Quelque chose que je retrouve dans les séries d'<a href="//fr.wikipedia.org/wiki/Aaron_Sorkin#S.C3.A9ries_t.C3.A9l.C3.A9vis.C3.A9es">Aaron Sorkin</a>.</p>
<p>Comme je déteste les "spoilers", je vais me livrer - en quelques mots - à ce délicat exercice d'équilibriste qui consiste à essayer de donner envie de visionner cette série, tout en en parlant le moins possible.</p>
<p><img alt="Nick Wasicko et sa femme admirant la vue sur le balcon de leur nouvelle maison" src="images/2016/04/Capture-d--cran-de-2016-04-13-06-00-18.png"></p>
<p>En bref, il est question de la vie politique à la fin des années 80 à Yonkers, une petite ville des Etats-Unis, et notamment du débat autour de la construction de logements sociaux.</p>
<p>Des le premier épisode, deux morceaux de Bruce Springsteen débutent la <a href="//www.youtube.com/playlist?list=PLkLimRXN6NKx58EYzH7Yxrr-HCbIzdMQT">bande son</a> et installent l'ambiance.
Les accents des personnages sont marqués. La lumière sur chaque plan magnifiquement maîtrisée. Style vestimentaire, coupes de cheveux, intérieurs d'appartements et extérieurs urbains... tout est d'époque, impeccable.</p>
<p><img alt="Doreen Henderson quittant son appartement avec ses enfants" src="images/2016/04/Capture-d--cran-de-2016-04-13-05-56-09.png"></p>
<p>Petit à petit, on suit les trajectoires croisées d'une vingtaine de personnages. Tantôt des habitants des "projects", les cités HLM américaines, tantôt des politiciens, conseillers municipaux ou membres de l'administration.</p>
<p>En terme de narration, cette minisérie utilise une astuce redoutable : au fil des épisodes, des pistes (et parfois des fausses pistes !) sont discrètement amenées pour expliquer la toute première scène de la série, quelque peu énigmatique.</p>
<p><img alt="Nick Wasicko, pensif, assis seul dans un escalier, le verso d'uncadran d'horloge au-dessus de lui" src="images/2016/04/Capture-d--cran-de-2016-04-13-05-53-32---2.png"></p>
<p>Un personnage central se détache : Nick Wasicsko, jeune candidat au poste de maire. Ambitieux, idéaliste, il évoluera du tout au tout au cour de la série.</p>
<p>Une chose qui m'a frappé : tous comme les autres personnages politiques de la série, on le voit assez régulièrement désoeuvré, en totale opposition avec les personnages féminins issus des citées.</p>
<p><img alt="Nick Wasicko s'observant dans un miroir" src="images/2016/04/I_am_a_hero.png"></p>
<p>Bien plus que Nick Wasicko, trop prévisible à mon goût, l'histoire de Doreen Henderson m'a véritablement scotché.</p>
<p>Sans rien déflorer, elle joue un rôle discret mais incroyablement poignant, en miroir complet de Nick.</p>
<p><img alt="Doreen Henderson & Mary Dorman en voiture" src="images/2016/04/Capture-d--cran-de-2016-04-13-05-48-53.png"></p>
<p>Bref, je ne peux que vous recommander chaudement cette série.
Sans être exempt de défauts - l'intrigue centrale monopolise vraiment excessivement la vie politique de la ville - c'est une petite perle à mettre entre toutes mains.</p>
<p>Et cérise sur le gateau, le dernier épisode se conclut avec des photos d'époques, faisant échos à des moments clefs de la série.</p>
<p><img alt="Doreen Henderson speaking at a Canopy meeting" src="images/2016/04/Capture-d--cran-de-2016-04-13-05-44-29.png"></p>
<p>Quant à mon idée de série, je vais la garder dans un coin de tiroir pour le moment ;)</p>Display Chuck Norris facts when you git pull !2016-03-10T12:03:00+01:002016-03-10T12:03:00+01:00Lucas Cimontag:chezsoi.org,2016-03-10:/lucas/blog/display-chuck-norris-facts-when-you-git-pull.html<p>...in just one command :</p>
<div class="highlight"><pre><span></span><code><span class="nt">cd</span> <span class="nt">path</span><span class="o">/</span><span class="nt">to</span><span class="o">/</span><span class="nt">your</span><span class="o">/</span><span class="nt">git</span><span class="o">/</span><span class="nt">repo</span>
<span class="nt">cat</span> <span class="o"><<</span><span class="nt">EOF</span> <span class="o">></span><span class="p">.</span><span class="nc">git</span><span class="o">/</span><span class="nt">hooks</span><span class="o">/</span><span class="nt">pre-rebase</span>
<span class="err">#</span><span class="o">!/</span><span class="nt">bin</span><span class="o">/</span><span class="nt">sh</span>
<span class="nt">echo</span> <span class="nt">-n</span> <span class="err">\</span><span class="o">$</span><span class="err">'\</span><span class="nt">x1b</span><span class="cp">[</span><span class="mi">36</span><span class="nx">m</span><span class="s1">' # start coloration (cyan)</span>
<span class="s1">curl -s https://raw.githubusercontent.com/jenkinsci/chucknorris-plugin/master/src/main/java/hudson/plugins/chucknorris/FactGenerator.java | sed '</span><span class="mi">1</span><span class="p">,/</span><span class="nx">FACTS</span> <span class="o">=</span> <span class="p">{/</span><span class="nx">d</span><span class="p">;</span><span class="nx">s</span><span class="o">/</span><span class="p">^</span> <span class="o">\+</span><span class="s2">"//;s/"</span><span class="nx">..</span><span class="o">\?</span><span class="err">$</span><span class="c1">//;/^$/,$d' | shuf …</span></code></pre></div><p>...in just one command :</p>
<div class="highlight"><pre><span></span><code><span class="nt">cd</span> <span class="nt">path</span><span class="o">/</span><span class="nt">to</span><span class="o">/</span><span class="nt">your</span><span class="o">/</span><span class="nt">git</span><span class="o">/</span><span class="nt">repo</span>
<span class="nt">cat</span> <span class="o"><<</span><span class="nt">EOF</span> <span class="o">></span><span class="p">.</span><span class="nc">git</span><span class="o">/</span><span class="nt">hooks</span><span class="o">/</span><span class="nt">pre-rebase</span>
<span class="err">#</span><span class="o">!/</span><span class="nt">bin</span><span class="o">/</span><span class="nt">sh</span>
<span class="nt">echo</span> <span class="nt">-n</span> <span class="err">\</span><span class="o">$</span><span class="err">'\</span><span class="nt">x1b</span><span class="cp">[</span><span class="mi">36</span><span class="nx">m</span><span class="s1">' # start coloration (cyan)</span>
<span class="s1">curl -s https://raw.githubusercontent.com/jenkinsci/chucknorris-plugin/master/src/main/java/hudson/plugins/chucknorris/FactGenerator.java | sed '</span><span class="mi">1</span><span class="p">,/</span><span class="nx">FACTS</span> <span class="o">=</span> <span class="p">{/</span><span class="nx">d</span><span class="p">;</span><span class="nx">s</span><span class="o">/</span><span class="p">^</span> <span class="o">\+</span><span class="s2">"//;s/"</span><span class="nx">..</span><span class="o">\?</span><span class="err">$</span><span class="c1">//;/^$/,$d' | shuf -n 1</span>
<span class="nx">echo</span> <span class="na">-n</span> <span class="o">\</span><span class="err">$</span><span class="s1">'</span><span class="se">\x1b</span><span class="s1">[0m'</span> <span class="err">#</span> <span class="nx">end</span> <span class="nx">coloration</span>
<span class="nx">EOF</span>
</code></pre></div>
<p>And enjoy the message next time you <code>git pull</code> !</p>
<p><img alt="Chuck Norris approuve" src="images/wwcb/chuck_norris_approve.gif"></p>
<p>Thanks to <a href="https://github.com/jenkinsci/chucknorris-plugin">jenkinsci/chucknorris-plugin</a> developpers for collecting those facts.</p>Solving a painful browserify limitation : portable source files selection with a wildcard pattern2016-02-25T15:02:00+01:002016-02-25T15:02:00+01:00Lucas Cimontag:chezsoi.org,2016-02-25:/lucas/blog/solving-a-painful-browserify-limitation.html<p>In any UNIX shell, the following will always work out of the box:</p>
<div class="highlight"><pre><span></span><code>browserify src/main/lib/js/*.js > out-bundle.js
</code></pre></div>
<p>But of course, <strong>not under Windows</strong>.</p>
<p>And <code>browserify</code> does not accept directory names as primary parameter, nor wildcard globbing patterns. There is a pending <a href="https://github.com/substack/node-browserify/issues/1170">issue & pull request</a> aiming to …</p><p>In any UNIX shell, the following will always work out of the box:</p>
<div class="highlight"><pre><span></span><code>browserify src/main/lib/js/*.js > out-bundle.js
</code></pre></div>
<p>But of course, <strong>not under Windows</strong>.</p>
<p>And <code>browserify</code> does not accept directory names as primary parameter, nor wildcard globbing patterns. There is a pending <a href="https://github.com/substack/node-browserify/issues/1170">issue & pull request</a> aiming to solve this, but it doesn't seem it's going to get merged soon. So lets solve this.</p>
<p>Put the following code in a file named <code>browserify_glob.js</code> :</p>
<div class="highlight"><pre><span></span><code><span class="k">var</span> <span class="n">browserify</span> <span class="o">=</span> <span class="n">require</span><span class="p">(</span><span class="s1">'browserify'</span><span class="p">),</span>
<span class="n">glob</span> <span class="o">=</span> <span class="n">require</span><span class="p">(</span><span class="s1">'glob'</span><span class="p">);</span>
<span class="k">var</span> <span class="n">bundler</span> <span class="o">=</span> <span class="n">browserify</span><span class="p">();</span>
<span class="n">console</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">'Called with: '</span> <span class="o">+</span> <span class="n">process</span><span class="o">.</span><span class="n">argv</span><span class="o">.</span><span class="n">slice</span><span class="p">(</span><span class="mi">2</span><span class="p">));</span>
<span class="k">var</span> <span class="n">glob_pattern</span> <span class="o">=</span> <span class="n">process</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="n">glob_pattern</span><span class="o">.</span><span class="n">charAt</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">===</span> <span class="s2">"'"</span><span class="p">)</span> <span class="p">{</span> <span class="o">//</span> <span class="n">Needed</span> <span class="k">for</span> <span class="n">Windows</span> <span class="n">compatibility</span>
<span class="n">glob_pattern</span> <span class="o">=</span> <span class="n">glob_pattern</span><span class="o">.</span><span class="n">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">glob</span><span class="p">(</span><span class="n">glob_pattern</span><span class="p">,</span> <span class="n">function</span> <span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="n">srcFiles</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span> <span class="n">throw</span> <span class="n">error</span><span class="p">;</span> <span class="p">}</span>
<span class="n">srcFiles</span><span class="o">.</span><span class="n">forEach</span><span class="p">(</span><span class="n">function</span> <span class="p">(</span><span class="n">srcFile</span><span class="p">)</span> <span class="p">{</span>
<span class="n">console</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">'- Adding'</span><span class="p">,</span> <span class="n">srcFile</span><span class="p">);</span>
<span class="n">bundler</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">srcFile</span><span class="p">);</span>
<span class="p">});</span>
<span class="n">bundler</span><span class="o">.</span><span class="n">bundle</span><span class="p">()</span><span class="o">.</span><span class="n">pipe</span><span class="p">(</span><span class="n">process</span><span class="o">.</span><span class="n">stdout</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div>
<p>And in your <code>package.json</code> :</p>
<div class="highlight"><pre><span></span><code>{
"devDependencies": {
"browserify": "latest",
"glob": "latest"
},
"scripts": {
"build:js": "node browserify_glob.js 'src/main/lib/js/**/*.js' > out-bundle.js"
}
}
</code></pre></div>
<p>And now you can <code>npm install && npm run build:js</code> and it will work under Windows !</p>
<p>In fact, a <code>**/*.js</code> glob pattern is even more powerful that a typical UNIX-style wildcard, as it will list files recursively.</p>
<p><strong>EDIT [2016/05/17]</strong> : I realized this trick is uterly dumb and useless : <code>browserify</code> is able to <a href="https://github.com/substack/browserify-handbook#how-browserify-works">find dependancies using static analysis from a single JS file entry point</a>.</p>Fixing fonts that raise a &DFLT table doesn't satisfy the spec. LangSysCount is not zero& error in Firefox2016-02-11T14:02:00+01:002016-02-11T14:02:00+01:00Lucas Cimontag:chezsoi.org,2016-02-11:/lucas/blog/fixing-fonts-that-raise-a-dflt-table-error-in-firefox.html<p>Did you ever got this infamous error message in Firefox debug console (with CSS error messages enabled) ?</p>
<div class="highlight"><pre><span></span><code><span class="n">downloadable</span> <span class="n">font</span><span class="p">:</span> <span class="n">Layout</span><span class="p">:</span> <span class="n">DFLT</span> <span class="n">table</span> <span class="n">doesn</span><span class="s1">'t satisfy the spec. for script tag DFLT (font-family: "MyBeautifulFont" style:normal weight:normal stretch:normal src index:1) source: http://W.X.Y.Z/fonts/myfont …</span></code></pre></div><p>Did you ever got this infamous error message in Firefox debug console (with CSS error messages enabled) ?</p>
<div class="highlight"><pre><span></span><code><span class="n">downloadable</span> <span class="n">font</span><span class="p">:</span> <span class="n">Layout</span><span class="p">:</span> <span class="n">DFLT</span> <span class="n">table</span> <span class="n">doesn</span><span class="s1">'t satisfy the spec. for script tag DFLT (font-family: "MyBeautifulFont" style:normal weight:normal stretch:normal src index:1) source: http://W.X.Y.Z/fonts/myfont.woff2</span>
<span class="n">downloadable</span> <span class="n">font</span><span class="p">:</span> <span class="n">Layout</span><span class="p">:</span> <span class="n">Failed</span> <span class="n">to</span> <span class="n">parse</span> <span class="n">script</span> <span class="n">table</span> <span class="mi">0</span> <span class="p">(</span><span class="n">font</span><span class="o">-</span><span class="n">family</span><span class="p">:</span> <span class="s2">"MyBeautifulFont"</span> <span class="n">style</span><span class="p">:</span><span class="n">normal</span> <span class="n">weight</span><span class="p">:</span><span class="n">normal</span> <span class="n">stretch</span><span class="p">:</span><span class="n">normal</span> <span class="n">src</span> <span class="n">index</span><span class="p">:</span><span class="mi">1</span><span class="p">)</span> <span class="n">source</span><span class="p">:</span> <span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">W</span><span class="o">.</span><span class="n">X</span><span class="o">.</span><span class="n">Y</span><span class="o">.</span><span class="n">Z</span><span class="o">/</span><span class="n">fonts</span><span class="o">/</span><span class="n">myfont</span><span class="o">.</span><span class="n">woff2</span>
<span class="n">downloadable</span> <span class="n">font</span><span class="p">:</span> <span class="n">GSUB</span><span class="p">:</span> <span class="n">Failed</span> <span class="n">to</span> <span class="n">parse</span> <span class="n">script</span> <span class="n">list</span> <span class="n">table</span> <span class="p">(</span><span class="n">font</span><span class="o">-</span><span class="n">family</span><span class="p">:</span> <span class="s2">"MyBeautifulFont"</span> <span class="n">style</span><span class="p">:</span><span class="n">normal</span> <span class="n">weight</span><span class="p">:</span><span class="n">normal</span> <span class="n">stretch</span><span class="p">:</span><span class="n">normal</span> <span class="n">src</span> <span class="n">index</span><span class="p">:</span><span class="mi">1</span><span class="p">)</span> <span class="n">source</span><span class="p">:</span> <span class="n">http</span><span class="p">:</span><span class="o">//</span><span class="n">W</span><span class="o">.</span><span class="n">X</span><span class="o">.</span><span class="n">Y</span><span class="o">.</span><span class="n">Z</span><span class="o">/</span><span class="n">fonts</span><span class="o">/</span><span class="n">myfont</span><span class="o">.</span><span class="n">woff2</span>
</code></pre></div>
<p>In my case it was accompanied by an obvious visual bug: all of the text on my website was rendered with default "fallback" fonts (Arial, Helvetica...), not the ones specified in the CSS.</p>
<p>Why ? First, lets understand the origin of this message: under the hood, Firefox use <a href="//github.com/khaledhosny/ots">ot-sanitize</a> to check fonts. Hence you get a similar error message using this tool:</p>
<div class="highlight"><pre><span></span><code>$ ot-sanitise myfont.otf
ERROR: Layout: DFLT script doesn<span class="err">'</span>t satisfy the spec. LangSysCount is not zero: <span class="m">1</span>
ERROR: Layout: Failed to parse script table <span class="m">0</span>
ERROR: GSUB: Failed to parse script list table
Failed to sanitise file!
</code></pre></div>
<p>Miracle of the Internets, I found an explanation for this specific error <a href="//github.com/khaledhosny/ots/blob/master/docs/HowToFix.md">here</a>, in the spare documentation of this very same project.
Alas, without any suggestion for a fix :(</p>
<p><img alt="Strip 979 de xkcd: Wisdom of the ancients" src="images/2016/02/xkcd_979_wisdom_of_the_ancients.png"></p>
<p>But now I will demonstrate how to fix an <code>.otf</code> file presenting this problem.
The following also works with <code>.ttf</code> / <code>.woff</code> / <code>.woff2</code> files, and by the way, <a href="//github.com/zoltan-dulac/css3FontConverter">css3FontConverter</a> is <strong>magic</strong> to convert your font files to all the formats needed to be displayed nicely in old browsers as well as modern ones : it even generates the CSS declaration !</p>
<p>Here comes the magic <code>ttx</code> command (from the awesome <a href="//github.com/behdad/fonttools">fonttools</a> Python package) and good old <code>sed</code> to the rescue:</p>
<div class="highlight"><pre><span></span><code>pip install fonttools
ttx -o myfont.ttx myfont.otf
tr '\n' ' ' <span class="nt">< myfont.ttx</span> <span class="err">|</span> <span class="err">sed</span> <span class="err">'s~\(<GSUB</span><span class="nt">></span>.\<span class="nt"><ScriptTag</span> <span class="na">value=</span><span class="s">"DFLT"</span><span class="nt">/></span>.\++<span class="nt"></DefaultLangSys></span>\)\s\+<span class="c"><!-- LangSysCount=1 --></span>\s\+<span class="nt"><LangSysRecord.</span><span class="err">\+</LangSysRecord</span><span class="nt">></span>~\1~' > myfont_fixed.ttx
ttx -o myfont_fixed.otf myfont_fixed.ttx
</code></pre></div>
<p>You can now test the output file with <code>ot-sanitise</code> : the check should pass !</p>
<p>Now the explanation on what this <code>sed</code> voodoo does: it deletes any <code><LangSysRecord></code> tag under <code>ttFont > GSUB > ScriptList > ScriptRecord[ScriptTag="DFLT"] > Script</code> in the <code>.ttx</code> file, in order to conform to the spec.</p>
<p>It would be waaay cleaner to this with <code>xml2</code> / <a href="http://xmlstar.sourceforge.net/docs.php"><code>xmlstarlet</code></a> / <a href="//stackoverflow.com/a/91801">any other CLI tool that manipulate XML</a>, but <code>sed</code> is very portable and can be found on any Unix system.</p>Useful short Python decorator to convert generators into lists2016-02-11T12:02:00+01:002016-02-11T12:02:00+01:00Lucas Cimontag:chezsoi.org,2016-02-11:/lucas/blog/useful-short-python-decorator-to-convert-generators-into-lists.html<p>Python generators are awesome. Why ?</p>
<ul>
<li>their syntax is simple an concise</li>
<li>they lazily generate values and hence are very memory efficient</li>
<li>bonus point: since Python 3 you can chain them with <code>yield from</code></li>
</ul>
<p>Their drawback ? They can be iterated only once, and they hide the iterable length.</p>
<p>I took an …</p><p>Python generators are awesome. Why ?</p>
<ul>
<li>their syntax is simple an concise</li>
<li>they lazily generate values and hence are very memory efficient</li>
<li>bonus point: since Python 3 you can chain them with <code>yield from</code></li>
</ul>
<p>Their drawback ? They can be iterated only once, and they hide the iterable length.</p>
<p>I took an habit of making a generator of every function I write that generates an iterable. Basically, it simply means using <code>yield</code>:</p>
<div class="highlight"><pre><span></span><code><span class="nv">def</span> <span class="nv">list_vowels_before</span><span class="ss">(</span><span class="nv">char</span><span class="ss">)</span>:
<span class="k">for</span> <span class="nv">vowel</span> <span class="nv">in</span> <span class="ss">(</span><span class="s1">'</span><span class="s">a</span><span class="s1">'</span>, <span class="s1">'</span><span class="s">e</span><span class="s1">'</span>, <span class="s1">'</span><span class="s">i</span><span class="s1">'</span>, <span class="s1">'</span><span class="s">o</span><span class="s1">'</span>, <span class="s1">'</span><span class="s">u</span><span class="s1">'</span>, <span class="s1">'</span><span class="s">y</span><span class="s1">'</span><span class="ss">)</span>:
<span class="k">if</span> <span class="nv">vowel</span> <span class="o">>=</span> <span class="nv">char</span>:
<span class="k">return</span>
<span class="nv">yield</span> <span class="nv">vowel</span>
</code></pre></div>
<p>But now, if I want an iterable that I can iterate several times, I need to convert to a list the generator returned by this function <strong>in every piece of code that invoke it</strong>:</p>
<div class="highlight"><pre><span></span><code>selected_vowels = list(list_vowels_before('t'))
</code></pre></div>
<p>But this isn't very good in terms of code readibility & maintainability: if one use this function but forget to do the conversion, the resulting object won't behave as expected. E.g. a simple conditional that test if the list is empty will evaluate to <code>True</code> :</p>
<div class="highlight"><pre><span></span><code><span class="nv">selected_vowels</span> <span class="o">=</span> <span class="nv">list_vowels_before</span><span class="ss">(</span><span class="s1">'</span><span class="s">a</span><span class="s1">'</span><span class="ss">)</span>
<span class="k">if</span> <span class="nv">selected_vowels</span>:
<span class="nv">print</span><span class="ss">(</span><span class="s1">'</span><span class="s">AMAZING: there are vowels BEFORE the letter "a" !!</span><span class="s1">'</span><span class="ss">)</span>
</code></pre></div>
<p>In case you want to use the generators syntax, but ensure that a function always return a list, here is a simple recipe you can use:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># This @decorator is totally optional, but it is a recommended best practice</span>
<span class="k">try</span><span class="p">:</span> <span class="c1"># We try to import GrahamDumpleton/wrapt if available</span>
<span class="kn">from</span> <span class="nn">wrapt</span> <span class="kn">import</span> <span class="n">decorator</span>
<span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span> <span class="c1"># fallback to the standard, less complete equivalent</span>
<span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">wraps</span> <span class="k">as</span> <span class="n">decorator</span>
<span class="nd">@decorator</span>
<span class="k">def</span> <span class="nf">aslist</span><span class="p">(</span><span class="n">generator</span><span class="p">):</span>
<span class="s2">"Function decorator to transform a generator into a list"</span>
<span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">list</span><span class="p">(</span><span class="n">generator</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">))</span>
<span class="k">return</span> <span class="n">wrapper</span>
</code></pre></div>
<p>And now you just have to add a single line of code to your previously defined function:</p>
<div class="highlight"><pre><span></span><code><span class="nv">@aslist</span><span class="w"></span>
<span class="n">def</span><span class="w"> </span><span class="n">list_vowels_before</span><span class="p">(</span><span class="nc">char</span><span class="p">)</span><span class="err">:</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">vowel</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="p">(</span><span class="s1">'a'</span><span class="p">,</span><span class="w"> </span><span class="s1">'e'</span><span class="p">,</span><span class="w"> </span><span class="s1">'i'</span><span class="p">,</span><span class="w"> </span><span class="s1">'o'</span><span class="p">,</span><span class="w"> </span><span class="s1">'u'</span><span class="p">,</span><span class="w"> </span><span class="s1">'y'</span><span class="p">)</span><span class="err">:</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">vowel</span><span class="w"> </span><span class="o">>=</span><span class="w"> </span><span class="nc">char</span><span class="err">:</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"></span>
<span class="w"> </span><span class="n">yield</span><span class="w"> </span><span class="n">vowel</span><span class="w"></span>
<span class="n">selected_vowels</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">list_vowels_before</span><span class="p">(</span><span class="s1">'a'</span><span class="p">)</span><span class="w"></span>
<span class="k">if</span><span class="w"> </span><span class="nl">selected_vowels</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="k">print</span><span class="p">(</span><span class="s1">'This will never be printed ;)'</span><span class="p">)</span><span class="w"></span>
</code></pre></div>
<hr>
<p><a href="https://lucas-c.itch.io/undying-dusk"><img alt="Undying Dusk game cover" src="https://chezsoi.org/lucas/undying-dusk/cover.png"></a></p>New release for genealogic-d32016-02-03T13:02:00+01:002016-02-03T13:02:00+01:00Lucas Cimontag:chezsoi.org,2016-02-03:/lucas/blog/new-release-for-genealogic-d3.html<p><a href="/lucas/blog/genealogy-tree-visualization-with-d3-js">A year ago</a>, I built a small JS lib using D3.js to visualize JSON-defined genealogy trees.</p>
<p>At the beginning of the year, I added a new feature using <a href="https://github.com/Russian60/flex-calendar">flex-calendar</a> and <a href="https://github.com/DepthFrance/moment-ferie-fr">moment-ferie-fr</a> : a birthday calendar using the same JSON genealogy definition and miniature images.</p>
<p>I added this calendar to the …</p><p><a href="/lucas/blog/genealogy-tree-visualization-with-d3-js">A year ago</a>, I built a small JS lib using D3.js to visualize JSON-defined genealogy trees.</p>
<p>At the beginning of the year, I added a new feature using <a href="https://github.com/Russian60/flex-calendar">flex-calendar</a> and <a href="https://github.com/DepthFrance/moment-ferie-fr">moment-ferie-fr</a> : a birthday calendar using the same JSON genealogy definition and miniature images.</p>
<p>I added this calendar to the original demo : <a href="https://chezsoi.org/lucas/genealogic-d3">https://chezsoi.org/lucas/genealogic-d3</a>
And here is the github project page : <a href="https://github.com/Lucas-C/genealogic-d3">https://github.com/Lucas-C/genealogic-d3</a></p>
<p>If like me you have a large family, it is handy and useful for newcomers to have such a web page, listing everyone's birthdays & photos.</p>pre-commit under Windows2016-01-12T19:01:00+01:002016-01-12T19:01:00+01:00Lucas Cimontag:chezsoi.org,2016-01-12:/lucas/blog/pre-commit-under-windows.html<p>First, lets mention <a href="//msysgit.github.com">Git Bash</a> (aka <em>msysgit</em>) : the old version was a PITA to extend with additional packages (e.g. adding common C libs like libxml), and the new one (renamed <a href="//git-for-windows.github.io">Git for Windows</a>), is based on MSYS2, but does not include a package manager.</p>
<p>Hence, we were left with …</p><p>First, lets mention <a href="//msysgit.github.com">Git Bash</a> (aka <em>msysgit</em>) : the old version was a PITA to extend with additional packages (e.g. adding common C libs like libxml), and the new one (renamed <a href="//git-for-windows.github.io">Git for Windows</a>), is based on MSYS2, but does not include a package manager.</p>
<p>Hence, we were left with 2 alternatives, both very good :</p>
<ul>
<li><a href="//www.cygwin.com">Cygwin</a> (my personal favourite) and its great & simple package manager <a href="//github.com/transcode-open/apt-cyg">apt-cyg</a>. Make sure you use a recent enough Cygwin, with <code>$BASH_VERSION</code> at least 4.2 (because of <a href="//github.com/transcode-open/apt-cyg/issues/71">this bug</a>). Then, there is how to install <code>apt-cyg</code> and the <code>pre-commit</code> command :</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="nv">lynx</span> <span class="o">-</span><span class="nv">source</span> <span class="nv">rawgit</span>.<span class="nv">com</span><span class="o">/</span><span class="nv">transcode</span><span class="o">-</span><span class="nv">open</span><span class="o">/</span><span class="nv">apt</span><span class="o">-</span><span class="nv">cyg</span><span class="o">/</span><span class="nv">v1</span><span class="o">/</span><span class="nv">apt</span><span class="o">-</span><span class="nv">cyg</span> <span class="o">></span> <span class="nv">apt</span><span class="o">-</span><span class="nv">cyg</span>
<span class="nv">sudo</span> <span class="nv">install</span> <span class="nv">apt</span><span class="o">-</span><span class="nv">cyg</span> <span class="o">/</span><span class="nv">usr</span><span class="o">/</span><span class="nv">local</span><span class="o">/</span><span class="nv">bin</span> <span class="o">&&</span> <span class="nv">rm</span> <span class="nv">apt</span><span class="o">-</span><span class="nv">cyg</span>
<span class="nv">apt</span><span class="o">-</span><span class="nv">cyg</span> <span class="nv">install</span> <span class="nv">wget</span>
<span class="nv">apt</span><span class="o">-</span><span class="nv">cyg</span> <span class="nv">install</span> <span class="nv">git</span> <span class="nv">python</span> <span class="nv">gcc</span><span class="o">-</span><span class="nv">g</span><span class="o">++</span> <span class="nv">libxml2</span><span class="o">-</span><span class="nv">devel</span> <span class="nv">libxslt</span><span class="o">-</span><span class="nv">devel</span>
<span class="nv">curl</span> <span class="o">--</span><span class="nv">silent</span> <span class="o">--</span><span class="k">show</span><span class="o">-</span><span class="nv">error</span> <span class="nv">https</span>:<span class="o">//</span><span class="nv">bootstrap</span>.<span class="nv">pypa</span>.<span class="nv">io</span><span class="o">/</span><span class="nv">get</span><span class="o">-</span><span class="nv">pip</span>.<span class="nv">py</span> <span class="o">|</span> <span class="nv">python</span>
<span class="nv">pip</span> <span class="nv">install</span> <span class="nv">pre</span><span class="o">-</span><span class="nv">commit</span>
</code></pre></div>
<ul>
<li><a href="//msys2.github.io">MSYS2</a> and its built-in package manager <code>pacman</code> :</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="nv">pacman</span> <span class="o">-</span><span class="nv">S</span> <span class="nv">git</span> <span class="nv">python</span> <span class="nv">gcc</span> <span class="nv">libxml2</span><span class="o">-</span><span class="nv">devel</span> <span class="nv">libxslt</span><span class="o">-</span><span class="nv">devel</span>
<span class="nv">curl</span> <span class="o">--</span><span class="nv">silent</span> <span class="o">--</span><span class="k">show</span><span class="o">-</span><span class="nv">error</span> <span class="nv">https</span>:<span class="o">//</span><span class="nv">bootstrap</span>.<span class="nv">pypa</span>.<span class="nv">io</span><span class="o">/</span><span class="nv">get</span><span class="o">-</span><span class="nv">pip</span>.<span class="nv">py</span> <span class="o">|</span> <span class="nv">python</span>
<span class="nv">pip</span> <span class="nv">install</span> <span class="nv">pre</span><span class="o">-</span><span class="nv">commit</span>
</code></pre></div>
<p><strong>EDIT [2016/02/11]</strong>: sadly, Stephen Jungels transcode-open/apt-cyg has been subject to an <a href="//github.com/github/dmca/blob/master/2016-01-26-apt-cyg.md">DMCA complaint</a>. Incidentally, Github user <a href="//github.com/svnpenn">@svnpenn</a> created a fork named <a href="//github.com/svnpenn/sage">sage</a>, but legal issues remain as can be observed in this <a href="//github.com/github/dmca/blob/master/2016-01-27-apt-cyg-counternotice.md">DMCA counter-claim</a> & the sage project <a href="//github.com/svnpenn/sage/issues">issues</a>.</p>TIL zip files can contain comments2016-01-08T12:01:00+01:002016-01-08T12:01:00+01:00Lucas Cimontag:chezsoi.org,2016-01-08:/lucas/blog/til-zip-files-can-contain-comments.html<p>... and the standard UNIX tool <code>zipinfo</code> cannot display them !</p>
<p><img alt="Photo of a zipper" src="images/2016/01/pythonchallenge-channel.jpg"></p>
<p>So here is Python one-liner to extract them, and other useful meta informations:</p>
<div class="highlight"><pre><span></span><code><span class="n">python</span> <span class="o">-</span><span class="n">c</span> <span class="s2">"import json, sys, zipfile; json.dump([{k: str(getattr(i, k)) for k in zipfile.ZipInfo.__slots__} for i in zipfile.ZipFile(sys.argv[1]).infolist …</span></code></pre></div><p>... and the standard UNIX tool <code>zipinfo</code> cannot display them !</p>
<p><img alt="Photo of a zipper" src="images/2016/01/pythonchallenge-channel.jpg"></p>
<p>So here is Python one-liner to extract them, and other useful meta informations:</p>
<div class="highlight"><pre><span></span><code><span class="n">python</span> <span class="o">-</span><span class="n">c</span> <span class="s2">"import json, sys, zipfile; json.dump([{k: str(getattr(i, k)) for k in zipfile.ZipInfo.__slots__} for i in zipfile.ZipFile(sys.argv[1]).infolist()], sys.stdout)"</span> <span class="err">$</span><span class="n">file</span><span class="o">.</span><span class="n">zip</span> <span class="o">|</span> <span class="n">jq</span> <span class="o">.</span>
</code></pre></div>
<p>Here, the outpout of the Python command is piped to <a href="https://stedolan.github.io/jq/"><code>jq</code></a>, a great tool to manipulate JSON on the command line. E.g. to extract only the filenames you can use: <code>jq -r .[].filename</code>.</p>
<p>Of course, one can also refactor this unreadable one-liner in a proper small script, or not use <code>jq</code> and do the field selection in pure Python, but that's good starting point I guess :)</p>Generating SRI hashes with grunt-usemin2016-01-05T12:01:00+01:002016-01-05T12:01:00+01:00Lucas Cimontag:chezsoi.org,2016-01-05:/lucas/blog/generating-sri-hashes-with-grunt-usemin.html<p>I'm not really crazy about Yeoman's <a href="//github.com/yeoman/grunt-usemin">grunt-usemin</a> : I find painful the way it enforces a unique pipeline, with its preliminary <code>useminPrepare</code> task and <code>:generated</code> targets.</p>
<p>But on the project I'm working on, we made the choice to use it early on, and we're sticking with it for now. And this …</p><p>I'm not really crazy about Yeoman's <a href="//github.com/yeoman/grunt-usemin">grunt-usemin</a> : I find painful the way it enforces a unique pipeline, with its preliminary <code>useminPrepare</code> task and <code>:generated</code> targets.</p>
<p>But on the project I'm working on, we made the choice to use it early on, and we're sticking with it for now. And this time we wanted to add <a href="//developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity">subresource integrity (SRI) checks</a> to our project. Here is a quick and easy way to do so, assuming you already use <code>grunt-usemin</code>.</p>
<p>This was tested with NodeJs version 0.12.2 (versions inferior to 0.12 won't have <code>execSync</code>), <code>grunt</code> version 0.4.5 and <code>grunt-usemin</code> version 3.1.1. We also use <a href="//github.com/firstandthird/load-grunt-config"><code>load-grunt-config</code></a> (v0.19.0), so the following is the exact content of <code>grunt/usemin.js</code> :</p>
<div class="highlight"><pre><span></span><code><span class="k">var</span> <span class="n">execSync</span> <span class="o">=</span> <span class="n">require</span><span class="p">(</span><span class="s1">'child_process'</span><span class="p">)</span><span class="o">.</span><span class="n">execSync</span><span class="p">;</span>
<span class="k">var</span> <span class="n">compute_sri_hash</span> <span class="o">=</span> <span class="n">function</span> <span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">algo</span><span class="p">)</span> <span class="p">{</span>
<span class="n">algo</span> <span class="o">=</span> <span class="n">algo</span> <span class="o">||</span> <span class="s1">'sha256'</span><span class="p">;</span>
<span class="k">return</span> <span class="n">algo</span> <span class="o">+</span> <span class="s1">'-'</span> <span class="o">+</span> <span class="n">execSync</span><span class="p">(</span><span class="s1">'openssl dgst -'</span> <span class="o">+</span> <span class="n">algo</span> <span class="o">+</span> <span class="s1">' -binary '</span> <span class="o">+</span> <span class="n">filename</span>
<span class="o">+</span> <span class="s1">' | openssl enc -base64 -A'</span><span class="p">);</span>
<span class="p">};</span>
<span class="n">module</span><span class="o">.</span><span class="n">exports</span> <span class="o">=</span> <span class="n">function</span> <span class="p">(</span><span class="n">grunt</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="n">html</span><span class="p">:</span> <span class="p">[</span><span class="s1">'grunt-target/index.html'</span><span class="p">],</span>
<span class="n">options</span><span class="p">:</span> <span class="p">{</span>
<span class="n">assetsDirs</span><span class="p">:</span> <span class="p">[</span><span class="s1">'grunt-target'</span><span class="p">],</span>
<span class="n">blockReplacements</span><span class="p">:</span> <span class="p">{</span>
<span class="n">css</span><span class="p">:</span> <span class="n">function</span> <span class="p">(</span><span class="n">block</span><span class="p">)</span> <span class="p">{</span>
<span class="k">var</span> <span class="n">media</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">media</span> <span class="err">?</span> <span class="s1">' media="'</span> <span class="o">+</span> <span class="n">block</span><span class="o">.</span><span class="n">media</span> <span class="o">+</span> <span class="s1">'"'</span> <span class="p">:</span> <span class="s1">''</span><span class="p">;</span>
<span class="n">grunt</span><span class="o">.</span><span class="n">log</span><span class="o">.</span><span class="n">writeln</span><span class="p">(</span><span class="s1">'Generating SRI hash for '</span> <span class="o">+</span> <span class="n">block</span><span class="o">.</span><span class="n">dest</span><span class="p">);</span>
<span class="k">var</span> <span class="n">sri_hash</span> <span class="o">=</span> <span class="n">compute_sri_hash</span><span class="p">(</span><span class="s1">'grunt-target/'</span> <span class="o">+</span> <span class="n">block</span><span class="o">.</span><span class="n">dest</span><span class="p">);</span>
<span class="k">return</span> <span class="s1">'<link rel="stylesheet" href="'</span> <span class="o">+</span> <span class="n">block</span><span class="o">.</span><span class="n">dest</span> <span class="o">+</span> <span class="s1">'"'</span> <span class="o">+</span> <span class="n">media</span>
<span class="o">+</span> <span class="s1">' integrity="'</span> <span class="o">+</span> <span class="n">sri_hash</span> <span class="o">+</span> <span class="s1">'" crossorigin="anonymous">'</span><span class="p">;</span>
<span class="p">},</span>
<span class="n">js</span><span class="p">:</span> <span class="n">function</span> <span class="p">(</span><span class="n">block</span><span class="p">)</span> <span class="p">{</span>
<span class="k">var</span> <span class="n">defer</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">defer</span> <span class="err">?</span> <span class="s1">'defer '</span> <span class="p">:</span> <span class="s1">''</span><span class="p">;</span>
<span class="k">var</span> <span class="n">async</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">async</span> <span class="err">?</span> <span class="s1">'async '</span> <span class="p">:</span> <span class="s1">''</span><span class="p">;</span>
<span class="n">grunt</span><span class="o">.</span><span class="n">log</span><span class="o">.</span><span class="n">writeln</span><span class="p">(</span><span class="s1">'Generating SRI hash for '</span> <span class="o">+</span> <span class="n">block</span><span class="o">.</span><span class="n">dest</span><span class="p">);</span>
<span class="k">var</span> <span class="n">sri_hash</span> <span class="o">=</span> <span class="n">compute_sri_hash</span><span class="p">(</span><span class="s1">'grunt-target/'</span> <span class="o">+</span> <span class="n">block</span><span class="o">.</span><span class="n">dest</span><span class="p">);</span>
<span class="k">return</span> <span class="s1">'<script '</span> <span class="o">+</span> <span class="n">defer</span> <span class="o">+</span> <span class="n">async</span> <span class="o">+</span> <span class="s1">'src="'</span> <span class="o">+</span> <span class="n">block</span><span class="o">.</span><span class="n">dest</span> <span class="o">+</span> <span class="s1">'"'</span>
<span class="o">+</span> <span class="s1">' integrity="'</span> <span class="o">+</span> <span class="n">sri_hash</span> <span class="o">+</span> <span class="s1">'"'</span>
<span class="o">+</span> <span class="s1">' crossorigin="anonymous"><\/script>'</span><span class="p">;</span>
<span class="p">},</span>
<span class="n">nominjs</span><span class="p">:</span> <span class="n">function</span> <span class="p">(</span><span class="n">block</span><span class="p">)</span> <span class="p">{</span>
<span class="k">var</span> <span class="n">defer</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">defer</span> <span class="err">?</span> <span class="s1">'defer '</span> <span class="p">:</span> <span class="s1">''</span><span class="p">;</span>
<span class="k">var</span> <span class="n">async</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">async</span> <span class="err">?</span> <span class="s1">'async '</span> <span class="p">:</span> <span class="s1">''</span><span class="p">;</span>
<span class="n">grunt</span><span class="o">.</span><span class="n">log</span><span class="o">.</span><span class="n">writeln</span><span class="p">(</span><span class="s1">'Generating SRI hash for '</span> <span class="o">+</span> <span class="n">block</span><span class="o">.</span><span class="n">dest</span><span class="p">);</span>
<span class="k">var</span> <span class="n">sri_hash</span> <span class="o">=</span> <span class="n">compute_sri_hash</span><span class="p">(</span><span class="s1">'grunt-target/'</span> <span class="o">+</span> <span class="n">block</span><span class="o">.</span><span class="n">dest</span><span class="p">);</span>
<span class="k">return</span> <span class="s1">'<script '</span> <span class="o">+</span> <span class="n">defer</span> <span class="o">+</span> <span class="n">async</span> <span class="o">+</span> <span class="s1">'src="'</span> <span class="o">+</span> <span class="n">block</span><span class="o">.</span><span class="n">dest</span> <span class="o">+</span> <span class="s1">'"'</span>
<span class="o">+</span> <span class="s1">' integrity="'</span> <span class="o">+</span> <span class="n">sri_hash</span> <span class="o">+</span> <span class="s1">'"'</span>
<span class="o">+</span> <span class="s1">' crossorigin="anonymous"><\/script>'</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="p">};</span>
</code></pre></div>
<p>With <code>load-grunt-tasks</code>, <code>module.exports</code> can be either an object hash or a function. I use the later form here to gain access to <code>grunt.log</code>.
Then I redefine <a href="//github.com/yeoman/grunt-usemin/blob/master/lib/fileprocessor.js#L107"><code>grunt-usemin</code> default block replacements</a> so that they include the SRI hash.
Note that I also define a <code>nominjs</code> target : this is a useful trick so that I can define different <code>usemin</code> flow steps for JS files I don't want to <code>uglify</code>. I then use it like this in my <code>index.html</code> :</p>
<div class="highlight"><pre><span></span><code><span class="c"><!-- build:nominjs compile/scripts/app.js --></span>
<span class="nt"><script</span> <span class="na">src=</span><span class="s">"../../grunt-target/scripts/app.js"</span><span class="nt">></script></span>
<span class="nt"><script</span> <span class="na">src=</span><span class="s">"../../grunt-target/scripts/ngtemplates.js"</span><span class="nt">></script></span>
<span class="c"><!-- endbuild --></span>
</code></pre></div>
<p>In thgis example, <code>grunt-target/scripts/app.js</code> & <code>grunt-target/scripts/ngtemplates.js</code> have been generated by an earlier <code>grunt</code> target when the <code>usemin</code> piepline kicks in.</p>
<p>That's it !</p>Colored diff output with Python2015-12-16T12:12:00+01:002015-12-16T12:12:00+01:00Lucas Cimontag:chezsoi.org,2015-12-16:/lucas/blog/colored-diff-output-with-python.html<p>Say you are generating a colored diff output with the standard <code>difflib</code> Python package:</p>
<div class="highlight"><pre><span></span><code><span class="gh">diff = difflib.ndiff(file1_lines, file2_lines)</span>
print('\n'.join(diff))
</code></pre></div>
<p>Now, I'll show you how to write a simple <code>color_diff</code> function that you can use to color your diff like this:</p>
<div class="highlight"><pre><span></span><code><span class="gh">diff = difflib.ndiff(file1_lines, file2_lines)</span>
<span class="gh">diff …</span></code></pre></div><p>Say you are generating a colored diff output with the standard <code>difflib</code> Python package:</p>
<div class="highlight"><pre><span></span><code><span class="gh">diff = difflib.ndiff(file1_lines, file2_lines)</span>
print('\n'.join(diff))
</code></pre></div>
<p>Now, I'll show you how to write a simple <code>color_diff</code> function that you can use to color your diff like this:</p>
<div class="highlight"><pre><span></span><code><span class="gh">diff = difflib.ndiff(file1_lines, file2_lines)</span>
<span class="gh">diff = color_diff(diff)</span>
print('\n'.join(diff))
</code></pre></div>
<p>Here is the code:</p>
<div class="highlight"><pre><span></span><code><span class="k">try</span><span class="p">:</span>
<span class="kn">from</span> <span class="nn">colorama</span> <span class="kn">import</span> <span class="n">Fore</span><span class="p">,</span> <span class="n">Back</span><span class="p">,</span> <span class="n">Style</span><span class="p">,</span> <span class="n">init</span>
<span class="n">init</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">ImportError</span><span class="p">:</span> <span class="c1"># fallback so that the imported classes always exist</span>
<span class="k">class</span> <span class="nc">ColorFallback</span><span class="p">():</span>
<span class="fm">__getattr__</span> <span class="o">=</span> <span class="k">lambda</span> <span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="s1">''</span>
<span class="n">Fore</span> <span class="o">=</span> <span class="n">Back</span> <span class="o">=</span> <span class="n">Style</span> <span class="o">=</span> <span class="n">ColorFallback</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">color_diff</span><span class="p">(</span><span class="n">diff</span><span class="p">):</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">diff</span><span class="p">:</span>
<span class="k">if</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">'+'</span><span class="p">):</span>
<span class="k">yield</span> <span class="n">Fore</span><span class="o">.</span><span class="n">GREEN</span> <span class="o">+</span> <span class="n">line</span> <span class="o">+</span> <span class="n">Fore</span><span class="o">.</span><span class="n">RESET</span>
<span class="k">elif</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">'-'</span><span class="p">):</span>
<span class="k">yield</span> <span class="n">Fore</span><span class="o">.</span><span class="n">RED</span> <span class="o">+</span> <span class="n">line</span> <span class="o">+</span> <span class="n">Fore</span><span class="o">.</span><span class="n">RESET</span>
<span class="k">elif</span> <span class="n">line</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s1">'^'</span><span class="p">):</span>
<span class="k">yield</span> <span class="n">Fore</span><span class="o">.</span><span class="n">BLUE</span> <span class="o">+</span> <span class="n">line</span> <span class="o">+</span> <span class="n">Fore</span><span class="o">.</span><span class="n">RESET</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">yield</span> <span class="n">line</span>
</code></pre></div>Disabling error stack traces with SpringBoot, but only in prod2015-10-29T12:10:00+01:002015-10-29T12:10:00+01:00Lucas Cimontag:chezsoi.org,2015-10-29:/lucas/blog/disabling-error-stack-traces-with-springboot-but-only-in-prod.html<p>Simply add the following class to your project. It will be automatically registered at start-up if you use the <a href="http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html"><code>@EnableAutoConfiguration</code> annotation</a> :</p>
<div class="highlight"><pre><span></span><code><span class="nv">@ControllerAdvice</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Makes</span><span class="w"> </span><span class="n">this</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">behaviour</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="ow">all</span><span class="w"> </span><span class="n">controllers</span><span class="w"></span>
<span class="nv">@ConditionalOnProperty</span><span class="p">(</span><span class="k">prefix</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ss">"app"</span><span class="p">,</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ss">"disable-default-exception-handling"</span><span class="p">)</span><span class="w"></span>
<span class="k">class</span><span class="w"> </span><span class="n">GlobalControllerExceptionHandler</span><span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span><span class="nv">@ExceptionHandler</span><span class="p">(</span><span class="k">Exception</span><span class="p">.</span><span class="k">class</span><span class="p">)</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="k">Catch</span><span class="w"> </span><span class="ow">any</span><span class="w"> </span><span class="k">exception</span><span class="w"></span>
<span class="w"> </span><span class="nv">@ResponseStatus</span><span class="p">(</span><span class="n">HttpStatus</span><span class="p">.</span><span class="n">INTERNAL_SERVER_ERROR</span><span class="p">)</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="k">Returns</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="n">code …</span></code></pre></div><p>Simply add the following class to your project. It will be automatically registered at start-up if you use the <a href="http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-auto-configuration.html"><code>@EnableAutoConfiguration</code> annotation</a> :</p>
<div class="highlight"><pre><span></span><code><span class="nv">@ControllerAdvice</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="n">Makes</span><span class="w"> </span><span class="n">this</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="k">default</span><span class="w"> </span><span class="n">behaviour</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="ow">all</span><span class="w"> </span><span class="n">controllers</span><span class="w"></span>
<span class="nv">@ConditionalOnProperty</span><span class="p">(</span><span class="k">prefix</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ss">"app"</span><span class="p">,</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ss">"disable-default-exception-handling"</span><span class="p">)</span><span class="w"></span>
<span class="k">class</span><span class="w"> </span><span class="n">GlobalControllerExceptionHandler</span><span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span><span class="nv">@ExceptionHandler</span><span class="p">(</span><span class="k">Exception</span><span class="p">.</span><span class="k">class</span><span class="p">)</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="k">Catch</span><span class="w"> </span><span class="ow">any</span><span class="w"> </span><span class="k">exception</span><span class="w"></span>
<span class="w"> </span><span class="nv">@ResponseStatus</span><span class="p">(</span><span class="n">HttpStatus</span><span class="p">.</span><span class="n">INTERNAL_SERVER_ERROR</span><span class="p">)</span><span class="w"> </span><span class="o">//</span><span class="w"> </span><span class="k">Returns</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="n">code</span><span class="w"> </span><span class="mi">500</span><span class="w"></span>
<span class="w"> </span><span class="k">public</span><span class="w"> </span><span class="n">void</span><span class="w"> </span><span class="n">handleException</span><span class="p">()</span><span class="w"> </span><span class="err">{</span><span class="w"></span>
<span class="w"> </span><span class="err">}</span><span class="w"></span>
<span class="err">}</span><span class="w"></span>
</code></pre></div>
<p>The nice trick is that you can the define your production settings as the default properties in your <code>application.yml</code> :</p>
<div class="highlight"><pre><span></span><code><span class="n">app</span><span class="o">:</span>
<span class="n">disable</span><span class="o">-</span><span class="k">default</span><span class="o">-</span><span class="n">exception</span><span class="o">-</span><span class="n">handling</span><span class="o">:</span> <span class="kc">true</span>
<span class="n">error</span><span class="o">:</span>
<span class="n">whitelabel</span><span class="o">:</span>
<span class="n">enabled</span><span class="o">:</span> <span class="kc">false</span>
</code></pre></div>
<p>Then, when you run your SpringBoot application on your local machine, select the dev Spring profile with <code>-Dspring.profiles.active=dev</code> and put the following in <code>application-dev.yml</code> :</p>
<div class="highlight"><pre><span></span><code><span class="n">app</span><span class="o">:</span>
<span class="n">disable</span><span class="o">-</span><span class="k">default</span><span class="o">-</span><span class="n">exception</span><span class="o">-</span><span class="n">handling</span><span class="o">:</span> <span class="kc">false</span>
<span class="n">error</span><span class="o">:</span>
<span class="n">whitelabel</span><span class="o">:</span>
<span class="n">enabled</span><span class="o">:</span> <span class="kc">true</span>
</code></pre></div>
<p>I tested it with a <a href="//en.wikipedia.org/wiki/WAR_(file_format)"><code>.war</code></a> deployed under Apache Tomcat 7.0.52. Most of the code comes straight from the <a href="//spring.io/blog/2013/11/01/exception-handling-in-spring-mvc">official documentation</a>, I only added the <code>@ConditionalOnProperty</code>.</p>
<hr>
<p><a href="https://lucas-c.itch.io/undying-dusk"><img alt="Undying Dusk game cover" src="https://chezsoi.org/lucas/undying-dusk/cover.png"></a></p>Citations : comédiens & Terror Network2015-10-28T18:10:00+01:002015-10-28T18:10:00+01:00Lucas Cimontag:chezsoi.org,2015-10-28:/lucas/blog/fren-citations-artistes.html<p>Deux lectures complètement décorellées à partager:</p>
<blockquote>
<p>Aujourd'hui, pour justifier leurs [les comédiens] droits, on parle d'économie. En 2004, le rapport Guillot remis au ministre de la culture et de la communication, dans l'émotion suscitée par l'annulation du festival d'Avignon, souligne qu'en 2003 "la valeur ajoutée dégagée par le secteur du …</p></blockquote><p>Deux lectures complètement décorellées à partager:</p>
<blockquote>
<p>Aujourd'hui, pour justifier leurs [les comédiens] droits, on parle d'économie. En 2004, le rapport Guillot remis au ministre de la culture et de la communication, dans l'émotion suscitée par l'annulation du festival d'Avignon, souligne qu'en 2003 "la valeur ajoutée dégagée par le secteur du spectacle vivant et enregistré (...) équivalait à celle de la construction aéraunotique, navale et ferroviaire". Depuis, l'argument comptable est régulièrement brandi comme légitimation... Triste réponse, qui accepte la domination de la rentabilité comme raison d'être. Pauvre excuse, qui refuse d'affirmer la nécessité du jeu, du travail gratuit, de l'imaginaire à partager, du luxe de la représentation des rêves humains, autant de facteurs de... désordre.</p>
</blockquote>
<p>"Saltimbanques, fauteurs de troubles" - Evelyne Pieiller, Le monde diplomatique d'Octobre 2015</p>
<blockquote>
<p>"William Casey, the new head of the CIA, after reading the Terror Network book, called a meeting with the soviet analysts at the CIA headquarters, and told them to produce a report for the president that proved this hidden network [between all terrorist groups in the world] existed. But the analysts told him this would be impossible. Because much of the information in the book came from black propaganda the CIA themselves had invented, to smeer the soviet union. They new the terror network didn't exist, because themselves had made it up."</p>
</blockquote>
<p>"The Power of Nightmares: The Rise of the Politics of Fear" BBC documentary by Adam Curtis</p>SpyFall en print&play2015-10-20T19:10:00+02:002015-10-20T19:10:00+02:00Lucas Cimontag:chezsoi.org,2015-10-20:/lucas/blog/spyfall-en-print.html<p><img alt="The Spy character from Team Fortress 2" src="images/2015/10/spy-2.jpg"></p>
<p>La semaine dernière, j'ai enfin offert à un ami le petit jeu de cartes que j'ai bricolé ces dernières semaines.</p>
<p>Comme je pense qu'ils pourra en amuser plus d'un, voici une version prête à imprimer !</p>
<p><a href="https://chezsoi.org/lucas/spyfall">https://chezsoi.org/lucas/spyfall</a> (<a href="/lucas/spyfall/spyfall_print-and-play.pdf">PDF, 3.47Mo</a>)</p>
<p>Aperçu :</p>
<p><img alt="Photo du jeu de cartes imprimé & découpé" src="/lucas/spyfall/printed_deck.jpg"></p>
<h1>Le jeu</h1>
<p>Pour une description complète …</p><p><img alt="The Spy character from Team Fortress 2" src="images/2015/10/spy-2.jpg"></p>
<p>La semaine dernière, j'ai enfin offert à un ami le petit jeu de cartes que j'ai bricolé ces dernières semaines.</p>
<p>Comme je pense qu'ils pourra en amuser plus d'un, voici une version prête à imprimer !</p>
<p><a href="https://chezsoi.org/lucas/spyfall">https://chezsoi.org/lucas/spyfall</a> (<a href="/lucas/spyfall/spyfall_print-and-play.pdf">PDF, 3.47Mo</a>)</p>
<p>Aperçu :</p>
<p><img alt="Photo du jeu de cartes imprimé & découpé" src="/lucas/spyfall/printed_deck.jpg"></p>
<h1>Le jeu</h1>
<p>Pour une description complète, je vous recommande <a href="http://www.vindjeu.eu/2015/01/23/spyfall-agent-trouble/">la review de Swatsh sur VindJeu</a>.</p>
<p>Cette version à pour particularité que chaque lieu est une référence à un jeu vidéo
(avant l'ajout de 27 illustrations supplémentaires grâce à Lukas).
Retrouverez-vous tous les clins d'oeil ? :)</p>
<p>Les réponses : <a href="https://chezsoi.org/lucas/spyfall/img-srcs-answers.txt">https://chezsoi.org/lucas/spyfall/img-srcs-answers.txt</a></p>
<p>Les règles complètes en anglais: <a href="http://international.hobbyworld.ru/download/rules/international/Spyfall_rules_ENG.pdf">PDF, 1,58Mo</a></p>
<p><img alt="Animation tirée de The Big Lebowsky" src="images/wwcb/OnlyOneToGiveAShitAboutRules.gif"></p>
<h1>Comment l'imprimer</h1>
<p>Tout simplement avec Chrome, en imprimant directement sur papier cartonné ou bien en choisissant "imprimer au format PDF" comme destination dans la fenêtre d'impression (CTRL+P).</p>
<h1>Création de cartes au format SVG</h1>
<p>Cette version a été entièrement réalisé avec <a href="http://snapsvg.io">Adobe Snap.svg</a>, en prennant exemple sur <a href="http://www.slideshare.net/kevinhakanson/make-your-own-print-play-card-game-using-svg-and-java-script">le travail de Kevin Hakanson sur Fluxx</a> (<a href="https://github.com/hakanson/tccc16">son repo GitHub</a>). Un grand merci à lui pour l'idée !</p>
<p>Pour les devs, je recommande vivement le procédé, qui permet du "templating" de cartes très puissant, de la gestion de versions et du partage sur le web très simplement.</p>
<p>Bien mieux que me précédent bricolages de cartes sour Word...</p>
<p><img alt="Sa place est dans un musée !" src="images/wwcb/SaPlaceEstDansUnMusée.gif"></p>
<p>L'idée originale de réaliser une version maison de ce jeu revient à mon ami, qui m'a fait découvrir l'existence de 2 apps, au code open-source, pour jouer sur smartphone:</p>
<ul>
<li><s><a href="http://spyfall.meteor.com">http://spyfall.meteor.com</a></s> → <a href="https://spyfall.tannerkrewson.com">https://spyfall.tannerkrewson.com</a></li>
<li><a href="http://spyfall.adrianocola.com">http://spyfall.adrianocola.com</a></li>
<li><a href="https://www.spyfall.app">https://www.spyfall.app</a></li>
</ul>
<p>Comme le travail de localisation avait déjà été fait sur ces apps, je l'ai simplement réutilisé dans cette version print & play. Merci à Evan Brumley et tous les gens qui ont contribué aux traductions !</p>
<p>Suite du développement de cette page web, j'en ai retiré 2 petites leçons :</p>
<ul>
<li>
<p><strong>ne pas essayer de créer des éléments HTML avec <code>paper.el</code> ou <code>Snap._.$</code></strong>. C'est très technique, mais je m'en suis mordu les doigts: ces fonctions peuvent uniquement créer des éléments SVG car ils utilient <a href="//github.com/adobe-webplatform/Snap.svg/blob/master/src/svg.js#L93">le namespace XML</a>.</p>
</li>
<li>
<p>le style CSS appliqué aux éléments, et plus spécifiquement la propriété <code>text-shadow</code>, est ignoré par le navigateur lors de l'export en PDF.</p>
</li>
</ul>
<p>Pour cette fois, pas de repo GitHub dédié au projet : l'ensemble du code source nécessaire pour reproduire ce résultat sont accessible dans le code source de la page web !</p>
<p><img alt="Tatouage d'une balise HTML head" src="images/wwcb/head_body_tatoo.jpg"></p>
<p>Enfin, un remerciement à Dan Hetteix (membre de TheNounProject), qui a créé l'icône d'espion utilisé sur les cartes.</p>
<p><strong>[EDIT 2020/04/26]</strong> : Thanks to Lukas, I added illustrations for all cards that missed one, using the following images :</p>
<ul>
<li><em>art museum</em> (<a href="https://www.piqsels.com/en/public-domain-photo-sjsit">image source</a>)</li>
<li><em>baseball stadium</em> (<a href="https://pxhere.com/en/photo/64540">image source</a>)</li>
<li><em>candy factory</em> (<a href="https://www.needpix.com/photo/291294/candy-candy-store-chocolate-m-ms-sweet">image source</a>)</li>
<li><em>cat show</em> (<a href="https://publicdomainpictures.net/fr/view-image.php?image=76777&picture=dog-show-affiche-de-bande-dessinee">image source</a>)</li>
<li><em>cemetery</em> (<a href="https://www.pexels.com/photo/selective-focus-photo-of-cemetery-lantern-720730/">image source</a>)</li>
<li><em>coal mine</em> (<a href="https://www.pxfuel.com/en/free-photo-oxhud">image source</a>)</li>
<li><em>construction site</em> (<a href="https://pxhere.com/en/photo/89214">image source</a>)</li>
<li><em>day spa</em> (<a href="https://www.pikrepo.com/fvgmv/trevi-fountain-rome-italy">image source</a>)</li>
<li><em>embassy</em> (<a href="https://pxhere.com/en/photo/757223">image source</a>)</li>
<li><em>gas station</em> (<a href="https://www.piqsels.com/en/public-domain-photo-spaqi">image source</a>)</li>
<li><em>harbor docks</em> (<a href="https://fr.wikipedia.org/wiki/Fichier:Docks_and_shipping,_Hamburg,_Germany-LCCN2002713698.jpg">image source</a>)</li>
<li><em>jail</em> (<a href="https://pxhere.com/en/photo/1095378">image source</a>)</li>
<li><em>jazz club</em> (<a href="https://pxhere.com/en/photo/1084769">image source</a>)</li>
<li><em>library</em> (<a href="https://pxhere.com/en/photo/707871">image source</a>)</li>
<li><em>movie studio</em> (<a href="https://pxhere.com/en/photo/1588369">image source</a>)</li>
<li><em>ocean liner</em> (<a href="https://pxhere.com/en/photo/1233488">image source</a>)</li>
<li><em>retirement home</em> (<a href="https://pxhere.com/en/photo/782117">image source</a>)</li>
<li><em>race track</em> (<a href="https://www.pickpik.com/transfagarasan-drone-road-green-forest-winding-36518">image source</a>)</li>
<li><em>rock concert</em> (<a href="https://www.pickpik.com/man-guitar-in-concert-lights-people-music-76510">image source</a>)</li>
<li><em>school</em> (<a href="https://pxhere.com/en/photo/1147031">image source</a>)</li>
<li><em>sightseeing bus</em> (<a href="https://www.pexels.com/photo/red-tower-hill-bus-1837590/">image source</a>)</li>
<li><em>subway</em> (<a href="https://pxhere.com/en/photo/2767">image source</a>)</li>
<li><em>supermarket</em> (<a href="https://unsplash.com/photos/53SEwmFQLqU">image source</a>)</li>
<li><em>the united nations</em> (<a href="https://www.pickpik.com/microphone-active-talk-conference-meeting-audio-38534">image source</a>)</li>
<li><em>university</em> (<a href="https://unsplash.com/photos/TJIF_x88tVk">image source</a>)</li>
<li><em>vineyard</em> (<a href="https://www.pxfuel.com/en/free-photo-jepqq">image source</a>)</li>
<li><em>wedding</em> (<a href="https://www.pexels.com/photo/woman-holding-white-calla-lily-flowers-on-sitting-beside-man-wearing-black-suit-265871/">image source</a>)</li>
</ul>
<p>I sincerely appreciate the contribution, thanks again Lukas !</p>Visualisation des votes sur republique-numerique.fr2015-10-18T23:10:00+02:002015-10-18T23:10:00+02:00Lucas Cimontag:chezsoi.org,2015-10-18:/lucas/blog/visualisation-des-votes-sur-republique-numeriquefr.html<p>Quelques essais de visualisation de données pour célébrer la fin de cette expérimentation de débat citoyen en ligne.</p>
<p>Première tentative, utilisant <a href="//www.charted.co">Charted</a>, pour visualiser la répartition des nombre de votes toutes propositions confondues :</p>
<iframe width="853" height="480" src="https://www.charted.co/?%7B%22dataUrl%22%3A%22http%3A%2F%2Fchezsoi.org%2Flucas%2Frepublique-numerique%2Fvotes_counts_frequencies_histogram.csv%22%2C%22seriesNames%22%3A%7B%221%22%3A%22freq_no%22%7D%2C%22charts%22%3A%5B%7B%22type%22%3A%22line%22%2C%22rounding%22%3A%22off%22%2C%22title%22%3A%22Positive%2FNegative%2FReserved%20votes%20count%20frequencies%20distribution%22%2C%22note%22%3A%22Data%20from%20republique-numerique.fr%20on%202015%2F10%2F18%20at%20midnight%22%7D%5D%7D" allowfullscreen></iframe>
<p>Exemple de lecture de ce graph: 33 propositions ont reçu exactement 8 votes positifs.</p>
<p>Ce graph n'apporte …</p><p>Quelques essais de visualisation de données pour célébrer la fin de cette expérimentation de débat citoyen en ligne.</p>
<p>Première tentative, utilisant <a href="//www.charted.co">Charted</a>, pour visualiser la répartition des nombre de votes toutes propositions confondues :</p>
<iframe width="853" height="480" src="https://www.charted.co/?%7B%22dataUrl%22%3A%22http%3A%2F%2Fchezsoi.org%2Flucas%2Frepublique-numerique%2Fvotes_counts_frequencies_histogram.csv%22%2C%22seriesNames%22%3A%7B%221%22%3A%22freq_no%22%7D%2C%22charts%22%3A%5B%7B%22type%22%3A%22line%22%2C%22rounding%22%3A%22off%22%2C%22title%22%3A%22Positive%2FNegative%2FReserved%20votes%20count%20frequencies%20distribution%22%2C%22note%22%3A%22Data%20from%20republique-numerique.fr%20on%202015%2F10%2F18%20at%20midnight%22%7D%5D%7D" allowfullscreen></iframe>
<p>Exemple de lecture de ce graph: 33 propositions ont reçu exactement 8 votes positifs.</p>
<p>Ce graph n'apporte pas vraiment d'éclairage intéressant, mais il a été amusant a réaliser :)</p>
<p>En voici deux autres, plus simples et plus clairs, utilisant <a href="http://canvasjs.com/">CanvaJS</a> :</p>
<iframe width="800" height="640" src="/lucas/republique-numerique/arguments_counts.html" allowfullscreen></iframe>
<p>Pour une véritable analyse des votes sur la plateforme republique-numerique.fr en cette fin de consultation citoyenne en ligne, <a href="http://rue89.nouvelobs.com/2015/10/18/loi-numerique-dernier-jour-sursaut-lobbys-261722">cf. ce passionant article de Rue89</a>.</p>
<p>Les sources de ces tests de visualisation sont accessibles <a href="//github.com/Lucas-C/republique-numerique-stats">ici</a>.</p>Recyclage rigolo2015-09-26T15:09:00+02:002015-09-26T15:09:00+02:00Lucas Cimontag:chezsoi.org,2015-09-26:/lucas/blog/recyclage-rigolo.html<p>Petit bricolage du week-end dernier donc je suis assez content :</p>
<p><img alt="Blendix Lux" src="images/2015/09/blendix-lux.jpg"></p>
<p>Un blender hors d'usage + une douille & son ampoule + 2 petits dominos à fils électrique = une magnifique lampe !</p>
<p>Et le bouton à vitesses du blender fait office d'interrupteur à intensités variables !</p>How to type in your damn PIN to exit Family View on Steam website2015-09-24T23:09:00+02:002015-09-24T23:09:00+02:00Lucas Cimontag:chezsoi.org,2015-09-24:/lucas/blog/how-to-type-in-your-damn-pin-to-exit-family-on-steam-website.html<p>This evening I faced a really annoying bug: while able to exit Family View by entering my PIN in <code>Steam.exe</code>, I could not do so when trying to access the Steam website.
I simply could not pass the page "Adults, enter your PIN below to exit Family View." Neither …</p><p>This evening I faced a really annoying bug: while able to exit Family View by entering my PIN in <code>Steam.exe</code>, I could not do so when trying to access the Steam website.
I simply could not pass the page "Adults, enter your PIN below to exit Family View." Neither with Chrome nor Firefox: the input field just silently ignored anything I typed in, and the "Submit" button stayed un-clickable.</p>
<p><img alt="Angry face trying to resist" src="images/wwcb/angry-must-resist.jpeg"></p>
<p>After a quick look at the HTML, I simply used the following Javascript code in <a href="http://webmasters.stackexchange.com/questions/8525/how-to-open-the-javascript-console-in-different-browsers">my browser console</a> to programatically enter my PIN and submit the form on this page :</p>
<div class="highlight"><pre><span></span><code>$('steam_parental_password_box').value = '<your-4-digits-pin>'; SubmitForm()
</code></pre></div>Quickly finding where you PHP script is stuck2015-09-03T10:09:00+02:002015-09-03T10:09:00+02:00Lucas Cimontag:chezsoi.org,2015-09-03:/lucas/blog/quickly-finding-where-you-php-script-is-stuck.html<p>First, install PHP debugging extensions for <code>gdb</code>, for example:</p>
<div class="highlight"><pre><span></span><code><span class="nv">debuginfo</span><span class="o">-</span><span class="nv">install</span> <span class="nv">php</span><span class="o">-</span><span class="mi">5</span>.<span class="mi">6</span>.<span class="mi">8</span> # <span class="k">if</span> <span class="nv">you</span> <span class="nv">use</span> <span class="nv">yum</span>
<span class="nv">aptitude</span> <span class="nv">install</span> <span class="nv">php5</span><span class="o">-</span><span class="nv">dbg</span> # <span class="k">if</span> <span class="nv">you</span> <span class="nv">use</span> <span class="nv">aptitude</span>
</code></pre></div>
<p>Then simply:</p>
<div class="highlight"><pre><span></span><code>php_version=5.6.8
php_script_pid=$(pgrep -f $php_script_name)
curl https://raw.githubusercontent.com/php/php-src/PHP-$php_version/.gdbinit >> ~/.gdbinit
gdb -p …</code></pre></div><p>First, install PHP debugging extensions for <code>gdb</code>, for example:</p>
<div class="highlight"><pre><span></span><code><span class="nv">debuginfo</span><span class="o">-</span><span class="nv">install</span> <span class="nv">php</span><span class="o">-</span><span class="mi">5</span>.<span class="mi">6</span>.<span class="mi">8</span> # <span class="k">if</span> <span class="nv">you</span> <span class="nv">use</span> <span class="nv">yum</span>
<span class="nv">aptitude</span> <span class="nv">install</span> <span class="nv">php5</span><span class="o">-</span><span class="nv">dbg</span> # <span class="k">if</span> <span class="nv">you</span> <span class="nv">use</span> <span class="nv">aptitude</span>
</code></pre></div>
<p>Then simply:</p>
<div class="highlight"><pre><span></span><code>php_version=5.6.8
php_script_pid=$(pgrep -f $php_script_name)
curl https://raw.githubusercontent.com/php/php-src/PHP-$php_version/.gdbinit >> ~/.gdbinit
gdb -p $php_script_pid
dump_bt executor_globals.current_execute_data
</code></pre></div>
<p>This will print <strong>a stacktrace of PHP function calls</strong>.</p>
<p>In my case, I found out my <code>drush</code> command was stuck waiting for text from <code>stdin</code>, because I gave it the wrong cli flag: <code>–y</code> instead of <code>-y</code>.</p>
<p>...</p>
<p>You noticed the difference ?
Yes, it's subttle and god damn confusing: the first argument contains the <a href="http://www.fileformat.info/info/unicode/char/2013/index.htm">unicode character EN-DASH</a>.</p>
<p><img alt="So surprise he's spitting out his cereals" src="images/wwcb/cereal-guy-cereal-guy-spitting.png"></p>Détection d'une archive gzip corrompue en Python2015-08-23T16:08:00+02:002015-08-23T16:08:00+02:00Lucas Cimontag:chezsoi.org,2015-08-23:/lucas/blog/detection-dune-archive-gzip-corrompue-en-python.html<p>Il y a quelques jours, un ami m'a demandé de jeter un oeil à un problème qu'il recontrait: alors qu'il analysait des données de <a href="http://donneespubliques.meteofrance.fr">donneespubliques.meteofrance.fr</a> dans un <a href="http://ipython.org/notebook.html">IPython notebook</a>, en utilisant la fantastique lib <a href="http://docs.python-requests.org">requests</a>, il a été confronté à un bug étrange:</p>
<blockquote>
<p>Mon soucis c'est que ma …</p></blockquote><p>Il y a quelques jours, un ami m'a demandé de jeter un oeil à un problème qu'il recontrait: alors qu'il analysait des données de <a href="http://donneespubliques.meteofrance.fr">donneespubliques.meteofrance.fr</a> dans un <a href="http://ipython.org/notebook.html">IPython notebook</a>, en utilisant la fantastique lib <a href="http://docs.python-requests.org">requests</a>, il a été confronté à un bug étrange:</p>
<blockquote>
<p>Mon soucis c'est que ma requête fonctionne très bien de 1996 à février 2015, mais après de mars 2015 à août 2015 ma requête ne me retourne que les données des premiers jours de chaque mois inexplicablement.</p>
</blockquote>
<p>Après quelques essais infructueux à bases d'examens d'entêtes HTTP et de <code>python -m trace</code>, j'ai finalement trouvé une piste intéressante:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">requests</span><span class="o">,</span> <span class="nn">urllib2</span><span class="o">,</span> <span class="nn">zlib</span>
<span class="kn">from</span> <span class="nn">sh</span> <span class="kn">import</span> <span class="n">gunzip</span>
<span class="n">ERRONEOUS_CSV_URL</span> <span class="o">=</span> <span class="s1">'https://donneespubliques.meteofrance.fr'</span>\
<span class="s1">'/donnees_libres/Txt/Synop/Archive/synop.201503.csv.gz'</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">ERRONEOUS_CSV_URL</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'[requests] Response Content-Encoding: </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'content-encoding'</span><span class="p">]))</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'[requests] => Uncompressed CSV lines count: </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="nb">len</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">content</span><span class="o">.</span><span class="n">splitlines</span><span class="p">())))</span>
<span class="n">response_content</span> <span class="o">=</span> <span class="n">urllib2</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">ERRONEOUS_CSV_URL</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="n">csv_content</span> <span class="o">=</span> <span class="n">zlib</span><span class="o">.</span><span class="n">decompress</span><span class="p">(</span><span class="n">response_content</span><span class="p">,</span> <span class="n">zlib</span><span class="o">.</span><span class="n">MAX_WBITS</span><span class="o">|</span><span class="mi">16</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'[urllib2] + zlib => Uncompressed CSV lines count: </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="nb">len</span><span class="p">(</span><span class="n">csv_content</span><span class="o">.</span><span class="n">splitlines</span><span class="p">())))</span>
<span class="n">csv_content</span> <span class="o">=</span> <span class="n">gunzip</span><span class="p">(</span><span class="n">_in</span><span class="o">=</span><span class="n">response_content</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'[urllib2] + gunzip => Uncompressed CSV lines count: </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="nb">len</span><span class="p">(</span><span class="n">csv_content</span><span class="o">.</span><span class="n">splitlines</span><span class="p">())))</span>
</code></pre></div>
<p>Et le résultat:</p>
<div class="highlight"><pre><span></span><code><span class="o">[</span><span class="n">requests</span><span class="o">]</span><span class="w"> </span><span class="n">Response</span><span class="w"> </span><span class="n">Content</span><span class="o">-</span><span class="nl">Encoding</span><span class="p">:</span><span class="w"> </span><span class="n">gzip</span><span class="w"></span>
<span class="o">[</span><span class="n">requests</span><span class="o">]</span><span class="w"> </span><span class="n">Uncompressed</span><span class="w"> </span><span class="n">CSV</span><span class="w"> </span><span class="n">lines</span><span class="w"> </span><span class="nf">count</span><span class="err">:</span><span class="w"> </span><span class="mi">61</span><span class="w"></span>
<span class="o">[</span><span class="n">urllib2</span><span class="o">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">zlib</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="n">Uncompressed</span><span class="w"> </span><span class="n">CSV</span><span class="w"> </span><span class="n">lines</span><span class="w"> </span><span class="nf">count</span><span class="err">:</span><span class="w"> </span><span class="mi">61</span><span class="w"></span>
<span class="o">[</span><span class="n">urllib2</span><span class="o">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">gunzip</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="n">Uncompressed</span><span class="w"> </span><span class="n">CSV</span><span class="w"> </span><span class="n">lines</span><span class="w"> </span><span class="nf">count</span><span class="err">:</span><span class="w"> </span><span class="mi">13516</span><span class="w"></span>
</code></pre></div>
<p>Le problème vient donc de <code>zlib</code> !
En effet, <code>requests</code> décompresse automatiquement le fichier téléchargé avec le module standard <code>zlib</code> lorsqu'il détecte l'entête HTTP <code>Content-Encoding: gzip</code>.
Mais lorsqu'on utilise la bibliothèque standard <code>urllib2</code> avec la command unix <code>gunzip</code>, on récupère bien bien le contenu décompressé correct !</p>
<p>Je me suis donc mis en tête de vérifier le contenu de l'archive <code>.gz</code> pour vérifier si elle n'était pas corrompue, et plus précisément au niveau de ses entêtes <code>gzip</code>.
Après quelques recherches sur le web, à part un très peu loquace <code>gzip -t</code> qui vérifie les archives, je n'ai trouvé aucune commande qui afficherait simplement les en-têtes <code>gzip</code> d'une archive compressée.</p>
<p>En définitve, j'ai donc bricolé un <a href="https://github.com/Lucas-C/linux_configuration/blob/master/languages/python/gzip_headers_reader.py">petit script</a> pour examiner ça:</p>
<div class="highlight"><pre><span></span><code>$ <span class="nv">BASE_REPO_URL</span><span class="o">=</span>https://rawgit.com/Lucas-C/linux_configuration/master
$ <span class="nv">BASE_CSV_URL</span><span class="o">=</span>https://donneespubliques.meteofrance.fr/donnees_libres
$ wget -q <span class="se">\</span>
<span class="nv">$BASE_REPO_URL</span>/languages/python/gzip_headers_reader.py <span class="se">\</span>
<span class="nv">$BASE_CSV_URL</span>/Txt/Synop/Archive/synop.201502.csv.gz <span class="se">\</span>
<span class="nv">$BASE_CSV_URL</span>/Txt/Synop/Archive/synop.201503.csv.gz
$ <span class="k">for</span> f <span class="k">in</span> synop*.csv.gz<span class="p">;</span> <span class="k">do</span> <span class="nb">echo</span> <span class="nv">$f</span><span class="p">;</span> python2.7 gzip_headers_reader.py <span class="nv">$f</span> <span class="p">|</span> tail -n <span class="m">8</span><span class="p">;</span> <span class="k">done</span>
</code></pre></div>
<p>Et le résultat:</p>
<div class="highlight"><pre><span></span><code><span class="n">synop</span><span class="mf">.201502</span><span class="p">.</span><span class="kr">csv</span><span class="p">.</span><span class="n">gz</span>
<span class="n">CRC32</span><span class="o">:</span> <span class="mi">694988689</span>
<span class="o">-></span> <span class="n">CRC32</span> <span class="n">COMPUTED</span> <span class="n">FROM</span> <span class="n">DECOMPRESSED</span> <span class="n">DATA</span><span class="o">:</span> <span class="mi">694988689</span>
<span class="n">ISIZE</span><span class="o">:</span> <span class="mi">3575777</span> <span class="n">bytes</span>
<span class="p">(</span><span class="n">size</span> <span class="kr">of</span> <span class="n">the</span> <span class="n">original</span> <span class="p">(</span><span class="n">uncompressed</span><span class="p">)</span> <span class="n">input</span> <span class="kt">data</span> <span class="n">modulo</span> <span class="mi">2</span><span class="o">^</span><span class="mi">32</span><span class="p">)</span>
<span class="o">-></span> <span class="n">ACTUAL</span> <span class="n">COMPRESSED</span> <span class="kt">DATA</span> <span class="nf">LENGTH</span><span class="o">:</span> <span class="mi">564016</span> <span class="n">bytes</span>
<span class="o">-></span> <span class="n">ACTUAL</span> <span class="n">DECOMPRESSED</span> <span class="kt">DATA</span> <span class="nf">LENGTH</span><span class="o">:</span> <span class="mi">3575777</span> <span class="n">bytes</span>
<span class="o">-></span> <span class="n">ZLIB</span><span class="o">:</span> <span class="n">len</span><span class="p">(</span><span class="n">unused_data</span><span class="p">)</span><span class="o">=</span><span class="mi">0</span>
<span class="n">synop</span><span class="mf">.201503</span><span class="p">.</span><span class="kr">csv</span><span class="p">.</span><span class="n">gz</span>
<span class="n">CRC32</span><span class="o">:</span> <span class="mi">856966149</span>
<span class="o">-></span> <span class="n">CRC32</span> <span class="n">COMPUTED</span> <span class="n">FROM</span> <span class="n">DECOMPRESSED</span> <span class="n">DATA</span><span class="o">:</span> <span class="mi">2470910129</span>
<span class="n">ISIZE</span><span class="o">:</span> <span class="mi">14979</span> <span class="n">bytes</span>
<span class="p">(</span><span class="n">size</span> <span class="kr">of</span> <span class="n">the</span> <span class="n">original</span> <span class="p">(</span><span class="n">uncompressed</span><span class="p">)</span> <span class="n">input</span> <span class="kt">data</span> <span class="n">modulo</span> <span class="mi">2</span><span class="o">^</span><span class="mi">32</span><span class="p">)</span>
<span class="o">-></span> <span class="n">ACTUAL</span> <span class="n">COMPRESSED</span> <span class="kt">DATA</span> <span class="nf">LENGTH</span><span class="o">:</span> <span class="mi">682100</span> <span class="n">bytes</span>
<span class="o">-></span> <span class="n">ACTUAL</span> <span class="n">DECOMPRESSED</span> <span class="kt">DATA</span> <span class="nf">LENGTH</span><span class="o">:</span> <span class="mi">16436</span> <span class="n">bytes</span>
<span class="o">-></span> <span class="n">ZLIB</span><span class="o">:</span> <span class="n">len</span><span class="p">(</span><span class="n">unused_data</span><span class="p">)</span><span class="o">=</span><span class="mi">678973</span>
</code></pre></div>
<p>On voit donc bien que le champ <code>ISIZE</code> de l'archive de mars 2015 est incorrect, et que 99.5% des données compressées n'ont pas été extraites par <code>zlib</code>.</p>
<p>Pour ce week-end je vais m'arrêter là, mais il serait intéressant de poursuivre un peu plus loin:</p>
<ul>
<li>contacter <a href="http://donneespubliques.meteofrance.fr">donneespubliques.meteofrance.fr</a> pour les prévenir du problème de corruption d'archive</li>
<li>regarder dans le <a href="https://hg.python.org/cpython/file/tip/Modules/zlibmodule.c">code source de <code>zlib</code></a> pourquoi la vérification du <code>CRC32</code> et du <code>ISIZE</code> n'est pas faite, et peut-être proposer un patch sur le code source CPython pour l'ajouter optionnellement. Et si <code>gunzip</code> peut le faire, <code>zlib</code> devrait aussi être capable de décompresser un <code>.gz</code> aux entêtes incorrectes !!</li>
</ul>
<p><strong>EDIT</strong>: Voici une solution rapide à ce problème, utilisant uniquement des modules standards, si jamais vous êtes coincé avec ce type d'archive <code>.gz</code> corrompue:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">gzip</span><span class="o">,</span> <span class="nn">requests</span><span class="o">,</span> <span class="nn">StringIO</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">URL</span><span class="p">,</span> <span class="n">stream</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">gzipped_file_obj</span> <span class="o">=</span> <span class="n">StringIO</span><span class="o">.</span><span class="n">StringIO</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">raw</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
<span class="k">with</span> <span class="n">gzip</span><span class="o">.</span><span class="n">GzipFile</span><span class="p">(</span><span class="n">fileobj</span><span class="o">=</span><span class="n">gzipped_file_obj</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">gunzipped_file_obj</span><span class="p">:</span>
<span class="n">decoded_content</span> <span class="o">=</span> <span class="n">gunzipped_file_obj</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</code></pre></div>
<p>Comme <code>gzip</code> utilise <code>zlib</code> et ne semble pas présenter ce problème de décompression, le bug initial provient peut-être d'une mauvaise utilisation de zlib par <code>requests</code>.
À investiguer...</p>youtube_playlist_watcher2015-07-24T11:07:00+02:002015-07-24T11:07:00+02:00Lucas Cimontag:chezsoi.org,2015-07-24:/lucas/blog/youtube_playlist_watcher.html<p>Depuis plusieurs années, je ruminais contre Youtube: chaque fois qu'une vidéo est supprimée d'une playlist (parce que l'utilisateur qui l'avait uploadé l'a supprimé par exemple), son nom n'est même pas conservé, seul un lien mort persiste dans la liste.</p>
<p>Et la seule solution est alors de googler le nom de …</p><p>Depuis plusieurs années, je ruminais contre Youtube: chaque fois qu'une vidéo est supprimée d'une playlist (parce que l'utilisateur qui l'avait uploadé l'a supprimé par exemple), son nom n'est même pas conservé, seul un lien mort persiste dans la liste.</p>
<p>Et la seule solution est alors de googler le nom de la vidéo pour retrouver, si vous êtes chanceux, de quelle chanson il s'agissait.</p>
<p>C'est pourquoi j'ai écrit un petit script Python pour y remédier : <a href="https://github.com/Lucas-C/youtube_playlist_watcher">youtube_playlist_watcher</a>.</p>
<p>Une fois installé dans un <em>cron job</em>, il effectue une sauvegarde journalière de votre playlist au format JSON, et vous alerte par email lorsqu'une vidéo est supprimée ou devient inaccessible.</p>
<p><img alt="Les tortues ninja serent la main des Power Rangers" src="images/wwcb/NinjaTurtlesPowerRangers.gif"></p>Mettre à jour de Python 3.2 à 3.4 sous Cygwin2015-07-14T11:07:00+02:002015-07-14T11:07:00+02:00Lucas Cimontag:chezsoi.org,2015-07-14:/lucas/blog/mettre-a-jour-de-python-3-2-a-3-4-sous-cygwin.html<p>Depuis mai 2015, <a href="https://www.cygwin.com/ml/cygwin/2015-05/msg00080.html">Python 3.4 est disponoble sous Cygwin</a>.</p>
<p>Si vous aviez précédement installé Python 3.2, voici comment passer à la version 3.4.</p>
<p>Tout d'abord, je vous recommande l'excellent gestionnaire de package <a href="https://github.com/transcode-open/apt-cyg">apt-cyg</a>. C'est tout simplement une interface en ligne de commande équivalente au <code>setup-x86.exe</code> (ou …</p><p>Depuis mai 2015, <a href="https://www.cygwin.com/ml/cygwin/2015-05/msg00080.html">Python 3.4 est disponoble sous Cygwin</a>.</p>
<p>Si vous aviez précédement installé Python 3.2, voici comment passer à la version 3.4.</p>
<p>Tout d'abord, je vous recommande l'excellent gestionnaire de package <a href="https://github.com/transcode-open/apt-cyg">apt-cyg</a>. C'est tout simplement une interface en ligne de commande équivalente au <code>setup-x86.exe</code> (ou <code>setup-x86_64.exe</code>) classique de Cygwin, servant à l'installation et la mise à jours de package.
Voici comment l'installer simplement:</p>
<div class="highlight"><pre><span></span><code><span class="n">mkdir</span> <span class="o">~/</span><span class="n">bin</span> <span class="o">&&</span> <span class="n">echo</span> <span class="s1">'export PATH=$PATH:~/bin'</span> <span class="o">>></span> <span class="o">~/.</span><span class="n">bashrc</span>
<span class="n">lynx</span> <span class="o">-</span><span class="n">source</span> <span class="n">rawgit</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">transcode</span><span class="o">-</span><span class="n">open</span><span class="o">/</span><span class="n">apt</span><span class="o">-</span><span class="n">cyg</span><span class="o">/</span><span class="k">master</span><span class="o">/</span><span class="n">apt</span><span class="o">-</span><span class="n">cyg</span> <span class="o">></span> <span class="n">apt</span><span class="o">-</span><span class="n">cyg</span>
<span class="n">install</span> <span class="n">apt</span><span class="o">-</span><span class="n">cyg</span> <span class="o">~/</span><span class="n">bin</span><span class="o">/</span> <span class="o">&&</span> <span class="n">rm</span> <span class="n">apt</span><span class="o">-</span><span class="n">cyg</span>
</code></pre></div>
<p>Maintenant il vous suffit simplement de désinstaller puis de réinstaller le package <code>python3</code>, qui se réinstallera avec la nouvelle version 3.4 par défaut:</p>
<div class="highlight"><pre><span></span><code>apt-cyg remove python3
apt-cyg install python3
</code></pre></div>
<p>Enfin, pour obtenir la commande <code>pip3.4</code> :</p>
<div class="highlight"><pre><span></span><code>python3 -m ensurepip
</code></pre></div>
<p>Et je vous recommande chaudement de supprimer également les fichiers <code>/usr/bin/pip3.2</code> désormais inutilisable, et <code>/usr/bin/pip</code> dont l'usage peut porter à confusion.</p>Handy recipe for timing Python code execution time2015-06-20T15:06:00+02:002015-06-20T15:06:00+02:00Lucas Cimontag:chezsoi.org,2015-06-20:/lucas/blog/handy-recipe-for-timing-python-code-execution-time.html<p>The <code>timeit</code> module is useful for micro benchmarks, but does not allow to measure execution time of large snippets, as it requires the code tested to fit in a string.
Hence, I went looking for a context or decorator-based solution. And I found <a href="https://bugs.python.org/issue19495">this bug report</a> commented by Guido van …</p><p>The <code>timeit</code> module is useful for micro benchmarks, but does not allow to measure execution time of large snippets, as it requires the code tested to fit in a string.
Hence, I went looking for a context or decorator-based solution. And I found <a href="https://bugs.python.org/issue19495">this bug report</a> commented by Guido van Rossum, recommending a few recipes.</p>
<p>Here is another one, that can be used either as a decorator or as a "with-context", combining the following inspirations:</p>
<ul>
<li><a href="https://hg.python.org/cpython/file/tip/Lib/timeit.py#l165">timeit.Timer.timeit</a> from the standard library</li>
<li><a href="http://dabeaz.blogspot.it/2010/02/function-that-works-as-context-manager.html">http://dabeaz.blogspot.it/2010/02/function-that-works-as-context-manager.html</a></li>
<li><a href="http://code.activestate.com/recipes/577896/">http://code.activestate.com/recipes/577896/</a></li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">gc</span>
<span class="kn">from</span> <span class="nn">contextlib</span> <span class="kn">import</span> <span class="n">contextmanager</span>
<span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="n">wraps</span>
<span class="kn">from</span> <span class="nn">time</span> <span class="kn">import</span> <span class="n">perf_counter</span> <span class="c1"># used by timeit module</span>
<span class="k">def</span> <span class="nf">trace_exec_time</span><span class="p">(</span><span class="n">end_callback</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="nd">@contextmanager</span>
<span class="k">def</span> <span class="nf">benchmark</span><span class="p">(</span><span class="o">**</span><span class="n">ctx_kwargs</span><span class="p">):</span>
<span class="n">gc_was_enabled</span> <span class="o">=</span> <span class="n">gc</span><span class="o">.</span><span class="n">isenabled</span><span class="p">()</span>
<span class="n">gc</span><span class="o">.</span><span class="n">disable</span><span class="p">()</span>
<span class="n">start</span> <span class="o">=</span> <span class="n">perf_counter</span><span class="p">()</span>
<span class="k">yield</span> <span class="n">ctx_kwargs</span>
<span class="n">end</span> <span class="o">=</span> <span class="n">perf_counter</span><span class="p">()</span>
<span class="n">ctx_kwargs</span><span class="p">[</span><span class="s1">'exec_duration'</span><span class="p">]</span> <span class="o">=</span> <span class="n">end</span> <span class="o">-</span> <span class="n">start</span>
<span class="k">if</span> <span class="n">end_callback</span><span class="p">:</span>
<span class="n">end_callback</span><span class="p">(</span><span class="n">ctx_kwargs</span><span class="p">)</span>
<span class="k">if</span> <span class="n">gc_was_enabled</span><span class="p">:</span>
<span class="n">gc</span><span class="o">.</span><span class="n">enable</span><span class="p">()</span>
<span class="k">if</span> <span class="n">end_callback</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">decorator</span><span class="p">(</span><span class="n">func</span><span class="p">):</span>
<span class="nd">@wraps</span><span class="p">(</span><span class="n">func</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">wrapper</span><span class="p">(</span><span class="o">*</span><span class="n">func_args</span><span class="p">,</span> <span class="o">**</span><span class="n">func_kwargs</span><span class="p">):</span>
<span class="k">with</span> <span class="n">benchmark</span><span class="p">(</span><span class="n">func_args</span><span class="o">=</span><span class="n">func_args</span><span class="p">,</span> <span class="n">func_kwargs</span><span class="o">=</span><span class="n">func_kwargs</span><span class="p">,</span>
<span class="n">func_name</span><span class="o">=</span><span class="n">func</span><span class="o">.</span><span class="vm">__name__</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">return</span> <span class="n">func</span><span class="p">(</span><span class="o">*</span><span class="n">func_args</span><span class="p">,</span> <span class="o">**</span><span class="n">func_kwargs</span><span class="p">)</span>
<span class="k">return</span> <span class="n">wrapper</span>
<span class="k">return</span> <span class="n">decorator</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="n">benchmark</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</code></pre></div>
<p>The triple args/kwargs variable shadowing is ugly, but that keeps the code short.</p>
<p>There are some usage examples:</p>
<div class="highlight"><pre><span></span><code><span class="nv">@trace_exec_time</span><span class="p">(</span><span class="n">end_callback</span><span class="o">=</span><span class="k">print</span><span class="p">)</span><span class="w"></span>
<span class="n">def</span><span class="w"> </span><span class="k">add</span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">)</span><span class="err">:</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">b</span><span class="w"></span>
<span class="k">add</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="err">{</span><span class="s1">'func_kwargs'</span><span class="err">:</span><span class="w"> </span><span class="err">{</span><span class="s1">'b'</span><span class="err">:</span><span class="w"> </span><span class="mi">3</span><span class="err">}</span><span class="p">,</span><span class="w"> </span><span class="s1">'exec_duration'</span><span class="err">:</span><span class="w"> </span><span class="mf">1.9</span><span class="o">-</span><span class="mi">06</span><span class="p">,</span><span class="w"> </span><span class="s1">'func_args'</span><span class="err">:</span><span class="w"> </span><span class="p">(</span><span class="mi">2</span><span class="p">,),</span><span class="w"> </span><span class="s1">'func_name'</span><span class="err">:</span><span class="w"> </span><span class="s1">'add'</span><span class="err">}</span><span class="w"></span>
<span class="k">with</span><span class="w"> </span><span class="n">trace_exec_time</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Heavy calculation'</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="nl">report</span><span class="p">:</span><span class="w"></span>
<span class="w"> </span><span class="k">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">big_compute</span><span class="p">(</span><span class="n">list_of_things</span><span class="p">)</span><span class="w"></span>
<span class="k">print</span><span class="p">(</span><span class="n">report</span><span class="o">[</span><span class="n">'name'</span><span class="o">]</span><span class="p">,</span><span class="w"> </span><span class="ss">"%0.3fs"</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="n">report</span><span class="o">[</span><span class="n">'exec_duration'</span><span class="o">]</span><span class="p">)</span><span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">Heavy</span><span class="w"> </span><span class="n">calculation</span><span class="w"> </span><span class="mf">10.576</span><span class="n">s</span><span class="w"></span>
</code></pre></div>
<p><strong>[UPDATED] on 2016/08/04</strong></p>
<p>With an aggregated list of all your timings, if you don't have <code>numpy</code>/<code>scipy</code> at hand, here is a handy recipe to compute some useful statistics out of them (requires Python 3 <code>statistics</code> module for <code>pstdev</code>):</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">statistics</span>
<span class="k">def</span> <span class="nf">compute_timing_stats</span><span class="p">(</span><span class="n">timings_in_ms</span><span class="p">):</span>
<span class="n">timings_in_ms</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">timings_in_ms</span><span class="p">)</span>
<span class="n">total</span> <span class="o">=</span> <span class="nb">sum</span><span class="p">(</span><span class="n">timings_in_ms</span><span class="p">)</span>
<span class="k">return</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'count'</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">timings_in_ms</span><span class="p">),</span>
<span class="s1">'mean'</span><span class="p">:</span> <span class="n">total</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">timings_in_ms</span><span class="p">),</span>
<span class="s1">'p00_min'</span><span class="p">,</span> <span class="n">timings_in_ms</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
<span class="s1">'p01'</span><span class="p">,</span> <span class="n">percentile</span><span class="p">(</span><span class="n">timings_in_ms</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
<span class="s1">'p10'</span><span class="p">,</span> <span class="n">percentile</span><span class="p">(</span><span class="n">timings_in_ms</span><span class="p">,</span> <span class="mi">10</span><span class="p">),</span>
<span class="s1">'p50_median'</span><span class="p">,</span> <span class="n">percentile</span><span class="p">(</span><span class="n">timings_in_ms</span><span class="p">,</span> <span class="mi">50</span><span class="p">),</span>
<span class="s1">'p90'</span><span class="p">,</span> <span class="n">percentile</span><span class="p">(</span><span class="n">timings_in_ms</span><span class="p">,</span> <span class="mi">90</span><span class="p">),</span>
<span class="s1">'p99'</span><span class="p">,</span> <span class="n">percentile</span><span class="p">(</span><span class="n">timings_in_ms</span><span class="p">,</span> <span class="mi">99</span><span class="p">),</span>
<span class="s1">'p100_max'</span><span class="p">,</span> <span class="n">timings_in_ms</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">],</span>
<span class="s1">'pstdev'</span><span class="p">:</span> <span class="n">statistics</span><span class="o">.</span><span class="n">pstdev</span><span class="p">(</span><span class="n">timings_in_ms</span><span class="p">),</span>
<span class="s1">'sum'</span><span class="p">:</span> <span class="n">total</span>
<span class="p">}</span>
<span class="k">def</span> <span class="nf">percentile</span><span class="p">(</span><span class="n">sorted_data</span><span class="p">,</span> <span class="n">percent</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Find the percentile of a list of values.</span>
<span class="sd"> @parameter sorted_data - is an ALREADY SORTED list of values</span>
<span class="sd"> @parameter percent - a float value from 0.0 to 1.0.</span>
<span class="sd"> @return - the percentile of the values</span>
<span class="sd"> """</span>
<span class="k">assert</span> <span class="mi">0</span> <span class="o"><=</span> <span class="n">percent</span> <span class="o"><</span> <span class="mi">1</span>
<span class="n">index</span> <span class="o">=</span> <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">sorted_data</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">*</span> <span class="n">percent</span>
<span class="k">return</span> <span class="n">sorted_data</span><span class="p">[</span><span class="nb">int</span><span class="p">(</span><span class="n">index</span><span class="p">)]</span>
</code></pre></div>Inceptionism : des images générées par des réseaux neuronaux2015-06-20T14:06:00+02:002015-06-20T14:06:00+02:00Lucas Cimontag:chezsoi.org,2015-06-20:/lucas/blog/inceptionism-des-images-generees-par-des-reseaux-neuronaux.html<p><a href="https://photos.google.com/share/AF1QipPX0SCl7OzWilt9LnuQliattX4OUCj_8EP65_cTVnBmS1jnYgsGQAieQUc1VQWdgQ?key=aVBxWjhwSzg2RjJWLWRuVFBBZEN1d205bUdEMnhB">Voici la galerie en question</a>, et voici une bande son pour accompagner la lecture de cet article:
<a href="http://auditiveescape.bandcamp.com/album/noted-wind">http://auditiveescape.bandcamp.com/album/noted-wind</a></p>
<p>Maintenant quelques explications: ce que vous admirez, ce sont des images générées par une forme d'intelligence artificielle appelée "réseau neuronal".</p>
<p>Il s'agit tout simplement d'un type d'algorithme …</p><p><a href="https://photos.google.com/share/AF1QipPX0SCl7OzWilt9LnuQliattX4OUCj_8EP65_cTVnBmS1jnYgsGQAieQUc1VQWdgQ?key=aVBxWjhwSzg2RjJWLWRuVFBBZEN1d205bUdEMnhB">Voici la galerie en question</a>, et voici une bande son pour accompagner la lecture de cet article:
<a href="http://auditiveescape.bandcamp.com/album/noted-wind">http://auditiveescape.bandcamp.com/album/noted-wind</a></p>
<p>Maintenant quelques explications: ce que vous admirez, ce sont des images générées par une forme d'intelligence artificielle appelée "réseau neuronal".</p>
<p>Il s'agit tout simplement d'un type d'algorithme capable d'APPRENDRE. Et par exemple, d'apprendre à reconnaître des images.</p>
<p>En quelques mots, des gentils développeurs ont éduqué ce réseau neuronal en leur montrant plein de photos, comme un livre d'images à un enfant, en leur disant à chaque fois "ça c'est une FEU-ILLE, ça c'est un OI-SEAU".
Et, au terme de l'apprentissage, ils ont demandé au programme de produire une image, <strong>à partir de rien d'autre qu'un mot</strong>.
Par exemple, "banane" :
<img alt="Illustration du processus et des images générées" src="images/2015/06/noise-to-banana.png"></p>
<p>Ou bien ils ont demandé au réseau neuronal de transformer une photo de quelque chose en autre chose:
<img alt="Dream map" src="images/2015/06/image-dream-map.png" style="width: 60%"/></p>
<p>Et ils ont même demandé au programme "qu'est-ce que tu vois dans les nuages" :
<a href="/lucas/blog/images/2015/06/sky_bright.jpg"><img alt="Nuages" src="images/2015/06/sky_bright.jpg"/></a></p>
<p>Et je trouve ça juste magique.</p>
<p>Pour ceux qui veulent lire l'article original, c'est <a href="http://googleresearch.blogspot.co.uk/2015/06/inceptionism-going-deeper-into-neural.html">ici</a>.</p>Solving BeanCreationException: Error creating bean with name 'entityManagerFactory'2015-06-03T12:06:00+02:002015-06-03T12:06:00+02:00Lucas Cimontag:chezsoi.org,2015-06-03:/lucas/blog/solving-beancreationexception-error-creating-bean-with-name-entitymanagerfactory.html<p>I've lost quite some time on this error recently :</p>
<div class="highlight"><pre><span></span><code><span class="n">Caused</span> <span class="nl">by:</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">beans</span><span class="p">.</span><span class="n">factory</span><span class="p">.</span><span class="nl">BeanCreationException:</span> <span class="n">Error</span> <span class="n">creating</span> <span class="n">bean</span> <span class="n">with</span> <span class="n">name</span> <span class="p">'</span><span class="n">entityManagerFactory</span><span class="p">'</span> <span class="n">defined</span> <span class="n">in</span> <span class="n">class</span> <span class="n">path</span> <span class="n">resource</span> <span class="p">[</span><span class="n">org</span><span class="o">/</span><span class="n">springframework</span><span class="o">/</span><span class="n">boot</span><span class="o">/</span><span class="n">autoconfigure</span><span class="o">/</span><span class="n">orm</span><span class="o">/</span><span class="n">jpa</span><span class="o">/</span><span class="n">HibernateJpaAutoConfiguration</span><span class="p">.</span><span class="n">class</span><span class="p">]</span><span class="o">:</span> <span class="n">Invocation</span> <span class="n">of</span> <span class="n">init</span> <span class="n">method</span> <span class="n">failed</span><span class="p">;</span> <span class="n">nested</span> <span class="n">exception</span> <span class="n">is</span> <span class="n">java</span><span class="p">.</span><span class="n">lang</span><span class="p">.</span><span class="nl">AbstractMethodError:</span> <span class="n">org</span><span class="p">.</span><span class="n">hibernate</span><span class="p">.</span><span class="n">jpa</span><span class="p">.</span><span class="n">boot …</span></code></pre></div><p>I've lost quite some time on this error recently :</p>
<div class="highlight"><pre><span></span><code><span class="n">Caused</span> <span class="nl">by:</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">beans</span><span class="p">.</span><span class="n">factory</span><span class="p">.</span><span class="nl">BeanCreationException:</span> <span class="n">Error</span> <span class="n">creating</span> <span class="n">bean</span> <span class="n">with</span> <span class="n">name</span> <span class="p">'</span><span class="n">entityManagerFactory</span><span class="p">'</span> <span class="n">defined</span> <span class="n">in</span> <span class="n">class</span> <span class="n">path</span> <span class="n">resource</span> <span class="p">[</span><span class="n">org</span><span class="o">/</span><span class="n">springframework</span><span class="o">/</span><span class="n">boot</span><span class="o">/</span><span class="n">autoconfigure</span><span class="o">/</span><span class="n">orm</span><span class="o">/</span><span class="n">jpa</span><span class="o">/</span><span class="n">HibernateJpaAutoConfiguration</span><span class="p">.</span><span class="n">class</span><span class="p">]</span><span class="o">:</span> <span class="n">Invocation</span> <span class="n">of</span> <span class="n">init</span> <span class="n">method</span> <span class="n">failed</span><span class="p">;</span> <span class="n">nested</span> <span class="n">exception</span> <span class="n">is</span> <span class="n">java</span><span class="p">.</span><span class="n">lang</span><span class="p">.</span><span class="nl">AbstractMethodError:</span> <span class="n">org</span><span class="p">.</span><span class="n">hibernate</span><span class="p">.</span><span class="n">jpa</span><span class="p">.</span><span class="n">boot</span><span class="p">.</span><span class="n">internal</span><span class="p">.</span><span class="n">EntityManagerFactoryBuilderImpl</span><span class="err">$</span><span class="mf">4.</span><span class="n">getConfigurationValues</span><span class="p">()</span><span class="n">Ljava</span><span class="o">/</span><span class="n">util</span><span class="o">/</span><span class="n">Map</span><span class="p">;</span>
<span class="n">at</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">beans</span><span class="p">.</span><span class="n">factory</span><span class="p">.</span><span class="n">support</span><span class="p">.</span><span class="n">AbstractAutowireCapableBeanFactory</span><span class="p">.</span><span class="n">initializeBean</span><span class="p">(</span><span class="n">AbstractAutowireCapableBeanFactory</span><span class="p">.</span><span class="nl">java:</span><span class="mh">1574</span><span class="p">)</span>
<span class="n">at</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">beans</span><span class="p">.</span><span class="n">factory</span><span class="p">.</span><span class="n">support</span><span class="p">.</span><span class="n">AbstractAutowireCapableBeanFactory</span><span class="p">.</span><span class="n">doCreateBean</span><span class="p">(</span><span class="n">AbstractAutowireCapableBeanFactory</span><span class="p">.</span><span class="nl">java:</span><span class="mh">539</span><span class="p">)</span>
<span class="n">at</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">beans</span><span class="p">.</span><span class="n">factory</span><span class="p">.</span><span class="n">support</span><span class="p">.</span><span class="n">AbstractAutowireCapableBeanFactory</span><span class="p">.</span><span class="n">createBean</span><span class="p">(</span><span class="n">AbstractAutowireCapableBeanFactory</span><span class="p">.</span><span class="nl">java:</span><span class="mh">476</span><span class="p">)</span>
<span class="n">at</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">beans</span><span class="p">.</span><span class="n">factory</span><span class="p">.</span><span class="n">support</span><span class="p">.</span><span class="n">AbstractBeanFactory</span><span class="err">$</span><span class="mf">1.</span><span class="n">getObject</span><span class="p">(</span><span class="n">AbstractBeanFactory</span><span class="p">.</span><span class="nl">java:</span><span class="mh">303</span><span class="p">)</span>
<span class="n">at</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">beans</span><span class="p">.</span><span class="n">factory</span><span class="p">.</span><span class="n">support</span><span class="p">.</span><span class="n">DefaultSingletonBeanRegistry</span><span class="p">.</span><span class="n">getSingleton</span><span class="p">(</span><span class="n">DefaultSingletonBeanRegistry</span><span class="p">.</span><span class="nl">java:</span><span class="mh">230</span><span class="p">)</span>
<span class="n">at</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">beans</span><span class="p">.</span><span class="n">factory</span><span class="p">.</span><span class="n">support</span><span class="p">.</span><span class="n">AbstractBeanFactory</span><span class="p">.</span><span class="n">doGetBean</span><span class="p">(</span><span class="n">AbstractBeanFactory</span><span class="p">.</span><span class="nl">java:</span><span class="mh">299</span><span class="p">)</span>
<span class="n">at</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">beans</span><span class="p">.</span><span class="n">factory</span><span class="p">.</span><span class="n">support</span><span class="p">.</span><span class="n">AbstractBeanFactory</span><span class="p">.</span><span class="n">getBean</span><span class="p">(</span><span class="n">AbstractBeanFactory</span><span class="p">.</span><span class="nl">java:</span><span class="mh">194</span><span class="p">)</span>
<span class="n">at</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">context</span><span class="p">.</span><span class="n">support</span><span class="p">.</span><span class="n">AbstractApplicationContext</span><span class="p">.</span><span class="n">getBean</span><span class="p">(</span><span class="n">AbstractApplicationContext</span><span class="p">.</span><span class="nl">java:</span><span class="mh">956</span><span class="p">)</span>
<span class="n">at</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">context</span><span class="p">.</span><span class="n">support</span><span class="p">.</span><span class="n">AbstractApplicationContext</span><span class="p">.</span><span class="n">finishBeanFactoryInitialization</span><span class="p">(</span><span class="n">AbstractApplicationContext</span><span class="p">.</span><span class="nl">java:</span><span class="mh">747</span><span class="p">)</span>
<span class="n">at</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">context</span><span class="p">.</span><span class="n">support</span><span class="p">.</span><span class="n">AbstractApplicationContext</span><span class="p">.</span><span class="n">refresh</span><span class="p">(</span><span class="n">AbstractApplicationContext</span><span class="p">.</span><span class="nl">java:</span><span class="mh">480</span><span class="p">)</span>
<span class="n">at</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">boot</span><span class="p">.</span><span class="n">SpringApplication</span><span class="p">.</span><span class="n">refresh</span><span class="p">(</span><span class="n">SpringApplication</span><span class="p">.</span><span class="nl">java:</span><span class="mh">686</span><span class="p">)</span>
<span class="n">at</span> <span class="n">org</span><span class="p">.</span><span class="n">springframework</span><span class="p">.</span><span class="n">boot</span><span class="p">.</span><span class="n">SpringApplication</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="n">SpringApplication</span><span class="p">.</span><span class="nl">java:</span><span class="mh">320</span><span class="p">)</span>
</code></pre></div>
<p>Bottom line: it was due to a mismatch between Spring Boot Hibernate version, and the one I used in my project, defined in <code>pom.xml</code>.</p>
<p>Simply make sure that they match, e.g by <a href="https://github.com/spring-projects/spring-boot/blob/24a791898c44087943fe8662354f0de1c41cc108/spring-boot-dependencies/pom.xml#L72">looking here for version 1.2.3.RELEASE</a>.</p>
<p><strong>EDIT 14/04/2016:</strong> adding a couple of comments that an anonymous visitor tried to post:</p>
<blockquote>
<p>I faced the same exception when including hibernate-search ontop of spring-boot + hibernate + spring-mvc project! What is the exact resolution?</p>
<p>Resolution: use 4.5.x hibernate-search-orm artifact. works fine with the latest spring-boot. atleast the unit tests are starting to run! :-)</p>
</blockquote>
<hr>
<p><a href="https://lucas-c.itch.io/undying-dusk"><img alt="Undying Dusk game cover" src="https://chezsoi.org/lucas/undying-dusk/cover.png"></a></p>AngularJS console debugging tips + pre-commit hooks2015-05-30T14:05:00+02:002015-05-30T14:05:00+02:00Lucas Cimontag:chezsoi.org,2015-05-30:/lucas/blog/angularjs-console-debugging-tips-and-pre-commit-hooks.html<p>Just some handy accessors for the brower console :</p>
<div class="highlight"><pre><span></span><code><span class="k">var</span> <span class="n">myScope</span> <span class="o">=</span> <span class="o">$</span><span class="p">(</span><span class="s1">'#directive > select.or'</span><span class="p">)</span><span class="o">.</span><span class="n">scope</span><span class="p">()</span>
<span class="k">var</span> <span class="o">$</span><span class="n">rootScope</span> <span class="o">=</span> <span class="o">$</span><span class="p">(</span><span class="s1">'body'</span><span class="p">)</span><span class="o">.</span><span class="n">scope</span><span class="p">()</span> <span class="o">//</span> <span class="k">if</span> <span class="o"><</span><span class="n">body</span><span class="o">></span> <span class="n">has</span> <span class="n">the</span> <span class="s1">'ng-app'</span> <span class="n">attribute</span>
<span class="k">var</span> <span class="n">myController</span> <span class="o">=</span> <span class="o">$</span><span class="p">(</span><span class="s1">'#directive > select.or'</span><span class="p">)</span><span class="o">.</span><span class="n">controller</span><span class="p">()</span>
<span class="k">var</span> <span class="n">injector</span> <span class="o">=</span> <span class="o">$</span><span class="p">(</span><span class="n">document</span><span class="o">.</span><span class="n">body</span><span class="p">)</span><span class="o">.</span><span class="n">injector</span><span class="p">()</span>
<span class="k">var</span> <span class="n">myService</span> <span class="o">=</span> <span class="n">injector</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'myServiceName'</span><span class="p">)</span>
</code></pre></div>
<p><br></p>
<p>And there are 3 handy <a href="https://chezsoi.org/lucas/blog/git-pre-commit-hooks.html">pre-commit</a> hooks :</p>
<div class="highlight"><pre><span></span><code>- repo: local
hooks …</code></pre></div><p>Just some handy accessors for the brower console :</p>
<div class="highlight"><pre><span></span><code><span class="k">var</span> <span class="n">myScope</span> <span class="o">=</span> <span class="o">$</span><span class="p">(</span><span class="s1">'#directive > select.or'</span><span class="p">)</span><span class="o">.</span><span class="n">scope</span><span class="p">()</span>
<span class="k">var</span> <span class="o">$</span><span class="n">rootScope</span> <span class="o">=</span> <span class="o">$</span><span class="p">(</span><span class="s1">'body'</span><span class="p">)</span><span class="o">.</span><span class="n">scope</span><span class="p">()</span> <span class="o">//</span> <span class="k">if</span> <span class="o"><</span><span class="n">body</span><span class="o">></span> <span class="n">has</span> <span class="n">the</span> <span class="s1">'ng-app'</span> <span class="n">attribute</span>
<span class="k">var</span> <span class="n">myController</span> <span class="o">=</span> <span class="o">$</span><span class="p">(</span><span class="s1">'#directive > select.or'</span><span class="p">)</span><span class="o">.</span><span class="n">controller</span><span class="p">()</span>
<span class="k">var</span> <span class="n">injector</span> <span class="o">=</span> <span class="o">$</span><span class="p">(</span><span class="n">document</span><span class="o">.</span><span class="n">body</span><span class="p">)</span><span class="o">.</span><span class="n">injector</span><span class="p">()</span>
<span class="k">var</span> <span class="n">myService</span> <span class="o">=</span> <span class="n">injector</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'myServiceName'</span><span class="p">)</span>
</code></pre></div>
<p><br></p>
<p>And there are 3 handy <a href="https://chezsoi.org/lucas/blog/git-pre-commit-hooks.html">pre-commit</a> hooks :</p>
<div class="highlight"><pre><span></span><code>- repo: local
hooks:
- id: angular-forbid-apply
name: In AngularJS, use $digest over $apply
language: pcre
entry: $apply
files: \.js$
- id: angular-forbid-ngrepeat-without-trackby
name: In AngularJS, ALWAYS use 'track by' with ng-repeat
language: pcre
entry: ng-repeat(?!.*track by)
files: \.html$
- id: angular-forbid-ngmodel-with-no-dot
name: In AngularJS, "Whenever you have ng-model there’s gotta be a dot in there somewhere"
language: pcre
entry: ng-model="?[^.]+[" ]
files: \.html$
</code></pre></div>
<p>You can find the justification for the two first hooks in <a href="http://fr.slideshare.net/nirkaufman/angularjs-performance-production-tips">those great slides by Nir Kaufman</a>.</p>
<p>The second will help you to detect <a href="http://jimhoskins.com/2012/12/14/nested-scopes-in-angularjs.html">a nested scope gotcha</a> in AngularJS identified by Miško Hevery:</p>
<blockquote>
<p>"Whenever you have ng-model there’s gotta be a dot in there somewhere. If you don’t have a dot, you’re doing it wrong"</p>
</blockquote>God I hate Java Regex API2015-05-19T16:05:00+02:002015-05-19T16:05:00+02:00Lucas Cimontag:chezsoi.org,2015-05-19:/lucas/blog/god-i-hate-java-regex-api.html<p>Pourquoi, mais pourquoi faut-il <strong>3 lignes</strong> en Java pour juste extraire un groupe d'une expression régulière qui "match" ???</p>
<div class="highlight"><pre><span></span><code>Matcher matcher = Pattern.compile("o?k(b|i)s+").matcher("kiss");
matcher.matches();
assert matcher.group(1) == "i";
</code></pre></div>
<p><img alt="Angry face" src="images/wwcb/rage-comic-angry.jpg"></p>
<p>En Python:</p>
<div class="highlight"><pre><span></span><code>assert re.match("o?k(b|i)s+", "kiss").group(1) == "i …</code></pre></div><p>Pourquoi, mais pourquoi faut-il <strong>3 lignes</strong> en Java pour juste extraire un groupe d'une expression régulière qui "match" ???</p>
<div class="highlight"><pre><span></span><code>Matcher matcher = Pattern.compile("o?k(b|i)s+").matcher("kiss");
matcher.matches();
assert matcher.group(1) == "i";
</code></pre></div>
<p><img alt="Angry face" src="images/wwcb/rage-comic-angry.jpg"></p>
<p>En Python:</p>
<div class="highlight"><pre><span></span><code>assert re.match("o?k(b|i)s+", "kiss").group(1) == "i";
</code></pre></div>
<p>La cerise sur le gateau: il existe en Java une méthode <code>.groupCount()</code> qui retourne <a href="http://docs.oracle.com/javase/7/docs/api/java/util/regex/Matcher.html#groupCount()">"le nombre de groupes de capture dans Pattern associé au Matcher"</a>.
<strong>Mais pourquoi diable est-ce que cette méthode est définie dans la classe <code>Matcher</code> et PAS dans la classe <code>Pattern</code> ???</strong></p>
<p><img alt="Aaaargh" src="images/wwcb/Aaaargh.gif"></p>
<p>Voilà, c'était le coup de gueule du jour. Même en Javascript l'API est plus pratique ! Alors OK 2 lignes de plus c'est pas la fin du monde, mais je trouve que ça illustre bien la différence entre ces deux langages en terme d'élégance. Et comme on dit: "The Devil is in the detail"...</p>Problème avec Windows Update & .NET, Sysinternals et l'enfer des DLLs2015-05-16T16:05:00+02:002015-05-16T16:05:00+02:00Lucas Cimontag:chezsoi.org,2015-05-16:/lucas/blog/towerfall-windows-sysinternals-et-lenfer-des-dlls.html<p>Cette après-midi, j'ai enfin résolu un problème d'installation de Microsoft .NET Framework qui datait de presque un an.</p>
<p>Dans l'espoir que ça puisse aider quelqu'un qui rencontre la même erreur, et parce que j'ai appris à utiliser un outil intéressant au passage, je me fends d'un petit article de blog …</p><p>Cette après-midi, j'ai enfin résolu un problème d'installation de Microsoft .NET Framework qui datait de presque un an.</p>
<p>Dans l'espoir que ça puisse aider quelqu'un qui rencontre la même erreur, et parce que j'ai appris à utiliser un outil intéressant au passage, je me fends d'un petit article de blog pour expliquer cette mésaventure.</p>
<p>J'ai Windows Seven en dual-boot, et ne l'utilise plus très souvent désormais, essentiellement pour jouer à des jeux vidéos de temps en temps.
En relisant mes notes de debug d'août 2014, où ce problème m'a explosé à la tronche pour la première fois, je vois <strong>beaucoup</strong> de détresse.</p>
<p><img alt="Mark Wahlberg angry smashes a computer" src="images/wwcb/computer-smash-Mark-Wahlberg-angry.gif"></p>
<p>J'ai passé plusieurs dizaines d'heures à essayer de résoudre le souci, pour finalement abandonner.
Tout a commencé par une erreur 0x80073712 systématique de Windows Update, empêchant toute mise à jour du sytème. Je retranscris ici l'intégralité de mes périgrinations, peut-être que quelqu'un y reconnaîtra des symptômes:</p>
<ul>
<li><code>Store corruption detected in function [...] MissingWinningComponentKey</code> dans le <code>Windows\Logs\CBS\CBS.log</code></li>
<li><a href="http://www.microsoft.com/security/scanner/en-us/default.aspx">Microsoft Safety Scanner</a>, <a href="http://www.bleepingcomputer.com/download/farbar-service-scanner/dl/62">Farbar Service Scanner</a>, <code>sfc /scannow</code> et Microsoft <a href="https://www.microsoft.com/en-us/download/details.aspx?id=20858">CheckSUR/SURT</a> ne détectent rien</li>
<li>des threads sur <a href="http://www.sysnative.com">sysnative.com</a> et <a href="http://www.sevenforums.com">sevenforums.com</a> me poussent à soupçonner <code>mscorwks.dll</code> et le framework .NET.</li>
<li>j'essaye <a href="http://support.microsoft.com/kb/2698555">l'outil de réparation du framework .NET de Microsoft</a> ainsi que <a href="http://blogs.msdn.com/b/astebner/archive/2008/10/13/8999004.aspx">celui d'Aaron Stebner</a>, chaudement recommandé sur plusieurs forums, sans succès</li>
<li>après bien d'autres essais infructueux, je finis par identifier et résoudre le problème initial de Windows Update ! <a href="http://support.microsoft.com/kb/957310/fr">Le manifeste Services à base de composants (CBS) était endommagé</a>. Solution: mettre à jour Windows à partir du CD d'installation, ce qui nécessite 17Go d'espace libre, me prend 2 heures puis nécessite une MAJ de GRUB pour rétablir le dual-boot.</li>
<li><strong>mais</strong> toujours pas moyen de mettre à jour .NET en version 4 :(</li>
</ul>
<p>Mais cette après-midi donc, voulant pouvoir jouer à <a href="http://store.steampowered.com/app/251470">Towerfall Ascension</a> que je venais d'acquérir, je me repenche sur le problème, et trouve un article de blog expliquant la solution : <a href="http://blogs.msdn.com/b/vsnetsetup/archive/2013/09/30/error-25003-error-occurred-while-initializing-fusion.aspx">MERCI mille fois Soumitra Mondal !!!</a>.</p>
<p><a href="https://xkcd.com/979/"><img src="http://imgs.xkcd.com/comics/wisdom_of_the_ancients.png" title="xkcd/979 : Wisdom of the Ancients"></a></p>
<p>L'origine du problème ? Des DLLs corompues (ou aux permissions incorrectes, allez savoir).
Mais le plus intéressant dans cette histoire, ça a été la réponse de <code>atverweij</code> sur <a href="https://social.msdn.microsoft.com/Forums/vstudio/en-US/ae70d0f8-2dcb-4ff5-9d9f-94efd30455c3/incorrect-function-during-install-of-net-40-on-windows-2008-x64-sp2">ce thread du support Microsoft</a>, indiquant qu'il avait identifié les fichiers posant problème grâce au Process Monitor des <strong>Sysinternals</strong>.</p>
<p>Les Sysinternals sont de fantastiques outils, <a href="https://technet.microsoft.com/en-us/sysinternals/bb545021.aspx">fournis par Microsoft</a> mais pas installés par défaut, permettant de véritablement <strong>comprendre</strong> et <strong>résoudre</strong> de nombreux comportements obscurs de Windows. Le genre de bugs incompréhensibles pour lesquels mes seuls alliés auparavant étaient Google et les fantastiques communautés d'internautes des forums d'aide Windows.</p>
<p>En voici quelques uns (j'utilise les 3 régulièrement comme dev ou gamer) :</p>
<ul>
<li><a href="https://technet.microsoft.com/en-us/sysinternals/bb896653">Process Explorer</a> a depuis longtemps remplacé l'explorateurs de processus Windows de base sur mon PC. Il est bien plus complet, fournissant entre autres des graphes d'utilisation CPU / IO et <strong>une arborescence des processus</strong> (à la <code>pstree</code> sous Linux). Il permet ausi de lister les fichiers ouverts par des programmes, et ainsi de trouver cet enfoiré de @#%£&§ de processus qui empêche la suppression d'un fichier.</li>
<li><a href="https://technet.microsoft.com/en-us/sysinternals/bb897437">TCPView</a> est l'équivalent d'un <code>netstat</code> sous Linux. Très utile pour déterminer quel processus utilise quel port !</li>
<li><a href="https://technet.microsoft.com/en-us/sysinternals/bb896645">Processus Monitor</a> enfin, permet entre autres de déterminer les tentatives d'ouverture de fichier râtées d'un processus. C'est un peu l'équivalent d'un <code>strace</code> sous Linux.</li>
</ul>
<p>Grâce à ce dernier outil, j'ai bien vérifié qu'il m'aurait été possible de déterminer à cause de quel fichier récalcitrant mon installateur de Microsoft .NET Framework 4 plantait systématiquement.</p>
<p>Ce post n'a pas pour but de décrire comment se servir de tous les outils, mais je vous recommande <a href="http://www.howtogeek.com/school/sysinternals-pro/lesson1/">cet article de HowToGeek</a> qui est une bonne introduction aux Sysinternals.</p>Une journée comme DevOps à Amazon Web Services2015-05-12T21:05:00+02:002015-05-12T21:05:00+02:00Lucas Cimontag:chezsoi.org,2015-05-12:/lucas/blog/une-journee-comme-devops-a-amazon-web-services.html<p>Ce soir aux HumanTalks de MaineLabs, j'ai eu la plaisir de faire une petite présentation sur mon boulot comme DevOps à Amazon Web Services lors de mes 2 années à Dublin.</p>
<p>L'intégralité du talk a été filmé et mise en ligne sur Youtube :</p>
<iframe width="853" height="480" src="https://www.youtube.com/embed/M1y_1tMlll4" allowfullscreen></iframe>
<p>Les slides sont accessibles <a href="/lucas/HTML_2015-05-12">ici</a>, ainsi que …</p><p>Ce soir aux HumanTalks de MaineLabs, j'ai eu la plaisir de faire une petite présentation sur mon boulot comme DevOps à Amazon Web Services lors de mes 2 années à Dublin.</p>
<p>L'intégralité du talk a été filmé et mise en ligne sur Youtube :</p>
<iframe width="853" height="480" src="https://www.youtube.com/embed/M1y_1tMlll4" allowfullscreen></iframe>
<p>Les slides sont accessibles <a href="/lucas/HTML_2015-05-12">ici</a>, ainsi que <a href="/lucas/HTML_2015-05-12/HTML_2015-05-12.md">les sources Markdown</a> pour <a href="http://lab.hakim.se/reveal-js">Reveal.js</a>. Pour ceux qui ont assisté au talk, j'ai ajouté quelques slides bonus et des références de lecture.</p>
<div style="text-align:center;"><iframe src="/lucas/slides/HTML_2015-05-12/" width="600" height="400">
<p>Iframes non supportées. Cliquez sur le lien dans le paragraphe au-dessus pour accéder directement aux slides.</p>
</iframe></div>
<p>Je vous encourage également a visionner les 3 autres talks du jour, listés <a href="http://mainelabs.fr/evenement/humantalks-mai-2015">sur la page de l'évênement</a>, tous réellement passionant !</p>
<p>Et merci à toute l'équipe de MaineLabs pour organiser ce super évênement mensuel.</p>
<style>
article iframe { display: block; margin: 1rem auto; }
</style>Git pre-commit hooks2015-05-01T13:05:00+02:002015-05-01T13:05:00+02:00Lucas Cimontag:chezsoi.org,2015-05-01:/lucas/blog/git-pre-commit-hooks.html<p>I'd like to introduce you to an <strong>awesome git companion</strong> : <a href="http://pre-commit.com"><code>pre-commit</code> hooks by Yelp</a>.</p>
<p><img alt="Project logo" src="images/2015/05/pre-commit-logo.png"></p>
<p>Git hooks are scripts that <code>git</code> executes before or after events such as: commit, push, and receive.</p>
<p>Git hooks are a built-in feature, but <code>git</code> does not offer much support for them: if there is a …</p><p>I'd like to introduce you to an <strong>awesome git companion</strong> : <a href="http://pre-commit.com"><code>pre-commit</code> hooks by Yelp</a>.</p>
<p><img alt="Project logo" src="images/2015/05/pre-commit-logo.png"></p>
<p>Git hooks are scripts that <code>git</code> executes before or after events such as: commit, push, and receive.</p>
<p>Git hooks are a built-in feature, but <code>git</code> does not offer much support for them: if there is a <code>.git/hooks/pre-commit</code> file, it just executes it. It's up to you to do all the wiring with existing checkers, run them consecutively and do some pretty console reporting.</p>
<p><a href="http://githooks.com">Many projects exist to do this</a>, but in my humble opinion the Yelp one is simply awesome :</p>
<ul>
<li>it's really simple to install & use</li>
<li>it's written in Python</li>
<li>it already has <a href="http://pre-commit.com/hooks.html">a huge list of supported checks</a></li>
<li>the project is mature, has a nice & supporting community, and is open to improvements</li>
</ul>
<p>To install it on your machine, you just need <a href="https://docs.python.org/3/installing/"><code>pip</code></a>, Python package manager : <code>pip install --user git+git://github.com/pre-commit/pre-commit.git@b68261c#egg=pre-commit</code></p>
<p>Then you can install Yelp <code>pre-commit</code> hooks into your repository: <code>cd path/to/my/git/repo && pre-commit install</code>. This will simply create <code>.git/hooks/pre-commit</code> so that it invokes the <code>pre-commit</code> commands.</p>
<p>Now all your hooks configuration fits in one file: <code>.pre-commit-config.yaml</code>. For example:</p>
<div class="highlight"><pre><span></span><code>- repo: git://github.com/pre-commit/pre-commit-hooks
sha: cedcea550c495d536247ca23115035b17074cac7
hooks:
- id: trailing-whitespace
- id: check-json
</code></pre></div>
<p>This config tells <code>pre-commit</code> to use 2 predefined hooks, that check for trailing whitespaces and malformed <code>.json</code> files.</p>
<p>The checks will automagically be executed whenever you do a commit, preventing you to commit files that don't pass the checks.
Here is an exemple of the resulting terminal output in case of a hook failure:</p>
<p><img alt="Screenshot of the tool terminal output" src="images/2015/08/pre-commit.png"></p>
<p>You can also invoke the hooks manually:</p>
<div class="highlight"><pre><span></span><code>pre-commit run $hook_id # run one specific hook on all files ready to be commited
pre-commit run --files $file1 $file2 # run all hooks on some specific files
pre-commit run --all-files # run all hooks on all the files in the repo
</code></pre></div>
<p>The checks defined above live in git repositories. <a href="http://pre-commit.com/hooks.html">Many are already available</a> for you tu use.
They are associated with a sha signature, used to determine which version of the hook to use. It looks a bit verbose, but as <a href="https://github.com/pre-commit/pre-commit/issues/158">this post</a> explains in details, it's mandatory and you can update all your hooks to the latest version with <code>pre-commit autoupdate</code>.</p>
<p>And finally, after a <a href="https://github.com/pre-commit/pre-commit/pull/226">small contribution</a> from your servitor, simple hooks defined directly in the config file are now supported !</p>
<p>Example:</p>
<div class="highlight"><pre><span></span><code><span class="o">-</span> <span class="n">repo</span><span class="p">:</span> <span class="n">local</span>
<span class="n">hooks</span><span class="p">:</span>
<span class="o">-</span> <span class="n">id</span><span class="p">:</span> <span class="n">make</span> <span class="n">check</span>
<span class="n">name</span><span class="p">:</span> <span class="n">Run</span> <span class="n">the</span> <span class="n">Makefile</span> <span class="n">checks</span>
<span class="n">language</span><span class="p">:</span> <span class="n">system</span>
<span class="n">entry</span><span class="p">:</span> <span class="n">make</span> <span class="n">check</span>
<span class="n">files</span><span class="p">:</span> <span class="s1">''</span>
<span class="o">-</span> <span class="n">id</span><span class="p">:</span> <span class="n">pyflakes</span>
<span class="n">name</span><span class="p">:</span> <span class="n">Run</span> <span class="n">Pyflakes</span>
<span class="n">language</span><span class="p">:</span> <span class="n">system</span>
<span class="n">entry</span><span class="p">:</span> <span class="n">pyflakes</span>
<span class="n">files</span><span class="p">:</span> <span class="o">.</span><span class="n">py</span><span class="o">$</span>
<span class="o">-</span> <span class="n">id</span><span class="p">:</span> <span class="n">vnu</span><span class="o">-</span><span class="n">html</span><span class="o">-</span><span class="n">checker</span>
<span class="n">name</span><span class="p">:</span> <span class="n">Download</span> <span class="o">&</span> <span class="n">execute</span> <span class="n">VNU</span> <span class="n">HTML</span> <span class="n">checker</span>
<span class="n">language</span><span class="p">:</span> <span class="n">script</span>
<span class="n">entry</span><span class="p">:</span> <span class="n">bin</span><span class="o">/</span><span class="n">vnu_html_checker</span><span class="o">.</span><span class="n">py</span>
<span class="n">files</span><span class="p">:</span> <span class="o">.</span><span class="n">html</span><span class="o">$</span>
</code></pre></div>
<p><a href="http://www.commitstrip.com/fr/2012/03/06/pre-commit-hook-irl/"><img alt="CommitStrip : IRL hook" src="images/2015/05/Strip-SVN-800-final.jpg"></a></p>Contre le Patriot Act à la française et l'interception des photos de pénis2015-04-08T20:04:00+02:002015-04-08T20:04:00+02:00Lucas Cimontag:chezsoi.org,2015-04-08:/lucas/blog/contre-le-patriot-act-a-la-francaise-et-linterception-des-photos-de-penis.html<p>Il reste 7 jours pour se mobiliser :
<a href="http://sous-surveillance.fr">http://sous-surveillance.fr</a></p>
<p>Aux USA, les américains en ont assez de ce Patriot Act initiallement approuvé par George Bush, et veulent le réformer. Pourquoi répéter leurs erreurs avec ce projet de loi relatif au Renseignement ??</p>
<p>Pour une explication drôle de la situation là-bas …</p><p>Il reste 7 jours pour se mobiliser :
<a href="http://sous-surveillance.fr">http://sous-surveillance.fr</a></p>
<p>Aux USA, les américains en ont assez de ce Patriot Act initiallement approuvé par George Bush, et veulent le réformer. Pourquoi répéter leurs erreurs avec ce projet de loi relatif au Renseignement ??</p>
<p>Pour une explication drôle de la situation là-bas, à base de photos de pénis et d'une interview d'Edward Snowden :</p>
<iframe width="853" height="480" src="https://www.youtube.com/embed/XEVlyP4_11M" allowfullscreen></iframe>
<p><br></p>
<p>En France, le Syndicat de la Magistrature, Reporters sans frontières, Amnesty International et la Ligue des Droits de l'Homme sont opposé au texte.</p>
<p>Et vous ?</p>
<style>
article iframe { display: block; margin: 1rem auto; }
</style>Django tips & tricks2015-04-06T20:04:00+02:002015-04-06T20:04:00+02:00Lucas Cimontag:chezsoi.org,2015-04-06:/lucas/blog/django-tips.html<p>I recently worked on a short website project using Django & Heroku. It was my very time using this Python framework, and I really liked it !</p>
<p>This is a compendium of tips & tricks that I, as a Django beginner, found quite useful :</p>
<h1><strong>Django enhanced shell</strong></h1>
<p>To install it :</p>
<div class="highlight"><pre><span></span><code>pip install django-extensions …</code></pre></div><p>I recently worked on a short website project using Django & Heroku. It was my very time using this Python framework, and I really liked it !</p>
<p>This is a compendium of tips & tricks that I, as a Django beginner, found quite useful :</p>
<h1><strong>Django enhanced shell</strong></h1>
<p>To install it :</p>
<div class="highlight"><pre><span></span><code>pip install django-extensions
</code></pre></div>
<p>I defined a useful shell alias to invoke with my custom <code>.pythonrc</code> :</p>
<div class="highlight"><pre><span></span><code>alias djshell='PYTHONSTARTUP=$HOME/.pythonrc ./manage.py shell_plus --use-pythonrc'
</code></pre></div>
<p>The <code>.pythonrc</code> I use is available <a href="//github.com/Lucas-C/linux_configuration/blob/master/.pythonrc">on github</a>. It provides two useful features :</p>
<ul>
<li>it enables <strong>command history</strong>, stored in <code>~/.django_history</code></li>
<li>it defines a handy <code>load_fixture(filename)</code> function to load test data from the shell, data that will be automatically deleted when you exit it
<br><br></li>
</ul>
<h1><strong>djangocolors_formatter.py</strong></h1>
<p>A simple drop-in one-file solution to get pretty colored logs output: <a href="//github.com/tiliv/django-colors-formatter/blob/master/djangocolors_formatter/__init__.py">tiliv/django-colors-formatter</a>
<br><br></p>
<h1><strong>A handy 'hasattribute' template filter</strong></h1>
<p>Put the following code in <code>${my_app}/templatetags/hasattribute.py</code> :</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">django</span> <span class="kn">import</span> <span class="n">template</span>
<span class="n">register</span> <span class="o">=</span> <span class="n">template</span><span class="o">.</span><span class="n">Library</span><span class="p">()</span>
<span class="nd">@register</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">is_safe</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">hasattribute</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">attr_name</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">obj</span><span class="p">,</span> <span class="n">attr_name</span><span class="p">)</span>
</code></pre></div>
<p>Then you can use it as an <code>if</code> condition in your templates :</p>
<div class="highlight"><pre><span></span><code><span class="p">{</span><span class="o">%</span> <span class="nb">load</span> <span class="n">hasattribute</span> <span class="o">%</span><span class="p">}</span>
<span class="p">{</span><span class="o">%</span> <span class="k">if</span> <span class="n">user</span><span class="o">|</span><span class="n">hasattribute</span><span class="p">:</span><span class="s1">'homeaddress'</span> <span class="o">%</span><span class="p">}</span>
<span class="p">{{</span> <span class="n">user</span><span class="o">.</span><span class="n">homeaddress</span> <span class="p">}}</span>
<span class="p">{</span><span class="o">%</span> <span class="n">endif</span> <span class="o">%</span><span class="p">}</span>
</code></pre></div>
<p><br><br></p>
<h1><strong>debug.py</strong></h1>
<p>I find it very useful to enable a <em>debug</em> mode when I'm developping, e.g. to get full stack traces when an exception arise. To enable it, I simply run <code>./manage.py runserver --settings debug</code></p>
<p>For the magic to work, simply define a <code>debug.py</code> file in your project root directory, with your debug configuration. For example :</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">path.to.your.project.settings</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">DEBUG</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">TEMPLATE_DEBUG</span> <span class="o">=</span> <span class="kc">True</span>
<span class="k">class</span> <span class="nc">InvalidVarException</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__mod__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">missing</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">missing_str</span><span class="o">=</span><span class="n">unicode</span><span class="p">(</span><span class="n">missing</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">missing_str</span><span class="o">=</span><span class="s1">'Failed to create string representation'</span>
<span class="k">raise</span> <span class="ne">Exception</span><span class="p">(</span><span class="s1">'Unknown template variable </span><span class="si">%r</span><span class="s1"> </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="p">(</span><span class="n">missing</span><span class="p">,</span> <span class="n">missing_str</span><span class="p">))</span>
<span class="k">def</span> <span class="fm">__contains__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">search</span><span class="p">):</span>
<span class="k">if</span> <span class="n">search</span><span class="o">==</span><span class="s1">'</span><span class="si">%s</span><span class="s1">'</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">True</span>
<span class="k">return</span> <span class="kc">False</span>
<span class="n">TEMPLATE_STRING_IF_INVALID</span> <span class="o">=</span> <span class="n">InvalidVarException</span><span class="p">()</span> <span class="c1"># WARNING: do not catch undefined for-loops variables</span>
</code></pre></div>
<p><br><br></p>
<h1><strong>Simply dealing with Heroku DATABASE_URL</strong></h1>
<p>I wanted to use a local sqlite database in my developpement environment. But, when deployed on Heroku, the app has to pick up & follow the DB configuration provided by the <code>DATABASE_URL</code> environment variable.</p>
<p>Here is how to do it very easily :</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">dj_database_url</span> <span class="kn">import</span> <span class="n">parse</span> <span class="k">as</span> <span class="n">parse_db_url</span>
<span class="n">BASE_DIR</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">))</span>
<span class="n">TEST_DB_URL</span> <span class="o">=</span> <span class="s1">'sqlite:///'</span> <span class="o">+</span> <span class="n">BASE_DIR</span> <span class="o">+</span> <span class="s1">'/local_test_db.sqlite3'</span>
<span class="n">DATABASE_URL</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'DATABASE_URL'</span><span class="p">,</span> <span class="n">TEST_DB_URL</span><span class="p">)</span>
<span class="n">DATABASES</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'default'</span><span class="p">:</span> <span class="n">parse_db_url</span><span class="p">(</span><span class="n">DATABASE_URL</span><span class="p">)}</span>
</code></pre></div>
<p><br><br></p>
<h1><strong>Easily making constants available in templates</strong></h1>
<p>I used some pre-defined constants in the server-side logic, that I wanted to be accessible in the client-side HTML/javascript output.
As an example, one use case was that I needed to do some journey duration calculation on the server, and some visual map rendering of those journeys in JS. Both codes shared the same map boundaries, and I wanted to define those in only one place.</p>
<p>My solution was to create the following <code>${my_geo_app}/constants.py</code> file :</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="n">BORDER_MAP</span><span class="p">(</span><span class="n">object</span><span class="p">):</span>
<span class="n">lat_nw</span> <span class="o">=</span> <span class="mf">49.0930256</span>
<span class="n">lon_nw</span> <span class="o">=</span> <span class="mf">1.9477209</span>
<span class="n">lat_se</span> <span class="o">=</span> <span class="mf">48.5442711</span>
<span class="n">lon_se</span> <span class="o">=</span> <span class="mf">2.9736786</span>
<span class="c1"># expose all the constants defined in this module in the templates</span>
<span class="n">def</span> <span class="n">context_processor</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">return</span> <span class="n">globals</span><span class="p">()</span>
</code></pre></div>
<p>Those constants can then be easily imported and used in the server-logic Python code.</p>
<p>And then, to make same available as constants in my templates, I simply had to tell django to parse this file for contexts, by adding the following in the project <code>settings.py</code> :</p>
<div class="highlight"><pre><span></span><code><span class="n">TEMPLATE_CONTEXT_PROCESSORS</span> <span class="o">=</span> <span class="n">DEFAULT_SETTINGS</span><span class="o">.</span><span class="n">TEMPLATE_CONTEXT_PROCESSORS</span> <span class="o">+</span> <span class="p">(</span>
<span class="s2">"my_geo_app.constants.context_processor"</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div>
<p>Credits for the original idea: <a href="//stackoverflow.com/a/433209/636849">this SO answer</a>.
<br><br></p>
<h1><strong>Templates checking</strong></h1>
<p>I like to be able to check my HTML files for various kind of errors. As explained in <a href="//chezsoi.org/lucas/blog/html-validation-and-converting-a-bash-script-to-python/">a previous post</a>, this is a process easy to automate, and one way to deal with "mustaches" template variables is simply to ignore them.</p>
<p>But I wanted to ensure that my templates did not use mistyped or undefined variables. Hence I built a small script that :</p>
<ul>
<li>given a dictionary of variables test values, check that no template contains undefined variable.</li>
<li>render the templates with those test values, so that the output HTML can be then analyzed by other checkers</li>
</ul>
<p>The script is <a href="//github.com/Lucas-C/linux_configuration/blob/master/languages/python/render_all_django_templates.py">on github</a>.</p>
<p>And here is an example of <code>tplt_test_context.py</code> configuration file defining some variables values :</p>
<div class="highlight"><pre><span></span><code><span class="n">DJANGO_SETTINGS_MODULE</span> <span class="o">=</span> <span class="s1">'my_app.settings'</span>
<span class="k">def</span> <span class="nf">get_context_dict</span><span class="p">():</span>
<span class="c1"># Project-specific imports must be done after django.setup()</span>
<span class="kn">from</span> <span class="nn">profiles.models</span> <span class="kn">import</span> <span class="n">User</span>
<span class="kn">from</span> <span class="nn">startup_pages.forms</span> <span class="kn">import</span> <span class="n">ContactAndRegisterForm</span>
<span class="n">users</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="n">User</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">select_related</span><span class="p">(</span><span class="s1">'user__homeaddress'</span><span class="p">))</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s1">'users'</span><span class="p">:</span> <span class="n">users</span><span class="p">,</span>
<span class="s1">'contactform'</span><span class="p">:</span> <span class="n">ContactAndRegisterForm</span><span class="p">(),</span>
<span class="s1">'src_username'</span><span class="p">:</span> <span class="n">users</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">username</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div>
<p>And an example of output:</p>
<pre>/path/to/project/templates/templates/allusers.html
<span style="color:green;"> -> ok: users, STATIC_URL</span>
/path/to/project/templates/entrer-en-contact.html
<span style="color:green;"> -> ok: contactform, STATIC_URL</span>
/path/to/project/templates/dest-summary.html
<span style="color:green;"> -> ok: STATIC_URL</span>
<span style="color:red;"> -> missing: dest_username</span>
</pre>
<p><br><br></p>
<h1><strong>Generate a pretty graph of your models relations</strong></h1>
<div class="highlight"><pre><span></span><code>pip install pyparsing==1.5.7 && pip install pydot && ./manage.py graph_models -a -g -o pretty_models_visualization.png
</code></pre></div>
<p><br><br></p>
<h1><strong>More tips & tricks for the Frenchies</strong></h1>
<ul>
<li><a href="http://sametmax.com/mieux-que-python-virtualenvwrapper-pew/">http://sametmax.com/mieux-que-python-virtualenvwrapper-pew/</a></li>
<li><a href="http://www.miximum.fr/checklist-bonnes-pratiques-django.html.html">http://www.miximum.fr/checklist-bonnes-pratiques-django.html.html</a></li>
</ul>
<p><strong>EDIT [16/05/2015]</strong>: another good read: <a href="http://docs.quantifiedcode.com/python-code-patterns/django/index.html">Django anti-patterns</a></p>HTML validation and converting a shell script to Python2015-03-25T21:03:00+01:002015-03-25T21:03:00+01:00Lucas Cimontag:chezsoi.org,2015-03-25:/lucas/blog/html-validation-and-converting-a-bash-script-to-python.html<p><img alt="Angry shouting Rafiki in the Lion King animated movie" src="images/wwcb/AngryShoutingRafikiFromLionKing.gif"></p>
<h3>Die shell script, DIE !</h3>
<p>In this post, I'll show how easy it ease to convert fragile shell scripts to Python scripts, using <a href="http://amoffat.github.io/sh/">sh.py</a>.
I'll use as an example a simple script to check your HTML code from the command-line, using the <a href="http://validator.w3.org/">W3C validator</a>.</p>
<p>Now you ask me, why the …</p><p><img alt="Angry shouting Rafiki in the Lion King animated movie" src="images/wwcb/AngryShoutingRafikiFromLionKing.gif"></p>
<h3>Die shell script, DIE !</h3>
<p>In this post, I'll show how easy it ease to convert fragile shell scripts to Python scripts, using <a href="http://amoffat.github.io/sh/">sh.py</a>.
I'll use as an example a simple script to check your HTML code from the command-line, using the <a href="http://validator.w3.org/">W3C validator</a>.</p>
<p>Now you ask me, why the hell would Python be better than shell scripts ?
I love the simplicty of a short Bash script. But they are simply too brittle to be safely used in production:</p>
<ul>
<li>shell scripts miss some very useful features: exceptions and try/catch blocks, stack traces displayed in case of failure, classes and modules, built-in dictionaries, assertions, list-comprehensions...</li>
<li>Python has more built-in static checks: uninitialized variables and undefined functions can be detected before execution, and horrific syntax errors like <code>foo () { = 42; }</code> (this is valid Bash code !) are not tolerated</li>
<li>Python has a more easy to read & understand syntax, and Python code is generally easier to maintain</li>
<li>Python comes with "batteries included", a HUGE community and has tens of thousands of PyPI packages, so you don't need to reinvent the wheel. On the other end, c ode reuse among shell scripts tends to be difficult.</li>
<li>many Unix utility commands (awk, grep, sed...) can be replaced by simple Python code, meaning less commands invocations and faster execution</li>
<li>finally, shell scripts can cause <a href="//github.com/valvesoftware/steam-for-linux/issues/3671">this kind of issue</a></li>
</ul>
<p>To begin with, there is our initial Bash script:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal"> 1</span>
<span class="normal"> 2</span>
<span class="normal"> 3</span>
<span class="normal"> 4</span>
<span class="normal"> 5</span>
<span class="normal"> 6</span>
<span class="normal"> 7</span>
<span class="normal"> 8</span>
<span class="normal"> 9</span>
<span class="normal">10</span>
<span class="normal">11</span>
<span class="normal">12</span>
<span class="normal">13</span>
<span class="normal">14</span>
<span class="normal">15</span>
<span class="normal">16</span>
<span class="normal">17</span>
<span class="normal">18</span>
<span class="normal">19</span>
<span class="normal">20</span>
<span class="normal">21</span>
<span class="normal">22</span>
<span class="normal">23</span>
<span class="normal">24</span>
<span class="normal">25</span>
<span class="normal">26</span>
<span class="normal">27</span>
<span class="normal">28</span>
<span class="normal">29</span>
<span class="normal">30</span>
<span class="normal">31</span>
<span class="normal">32</span>
<span class="normal">33</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><code><span class="ch">#!/usr/bin/env bash</span>
<span class="c1"># USAGE: ./vnu_html_checker.sh $file.html</span>
<span class="c1"># OR: ./vnu_html_checker.sh < $file.html</span>
<span class="nb">set</span> -o pipefail -o errexit -o nounset
<span class="nv">VNU_GITHUB</span><span class="o">=</span>https://github.com/validator/validator
<span class="nv">VNU_VERSION</span><span class="o">=</span><span class="m">20150207</span>
download_vnu_jar <span class="o">()</span> <span class="o">{</span>
<span class="nb">local</span> <span class="nv">zip_filename</span><span class="o">=</span>vnu-<span class="nv">$VNU_VERSION</span>.jar.zip
wget <span class="nv">$VNU_GITHUB</span>/releases/download/<span class="nv">$VNU_VERSION</span>/<span class="nv">$zip_filename</span>
unzip <span class="nv">$zip_filename</span>
mv vnu/vnu.jar .
rm -r vnu/ <span class="nv">$zip_filename</span>
<span class="o">}</span>
filter_out_XUAcompat_line <span class="o">()</span> <span class="o">{</span> <span class="c1"># seen as an error by VNU</span>
grep -vF <span class="s1">'meta http-equiv="X-UA-Compatible"'</span>
<span class="o">}</span>
filter_out_mustaches <span class="o">()</span> <span class="o">{</span>
sed -e <span class="s1">'s/{[{%][^{}]\+[%}]}/DUMMY_MUSTACHE/g'</span>
<span class="o">}</span>
<span class="k">if</span> ! <span class="o">[</span> -e vnu.jar <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
download_vnu_jar
<span class="k">fi</span>
cat <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span> <span class="p">|</span> filter_out_XUAcompat_line <span class="se">\</span>
<span class="p">|</span> filter_out_mustaches <span class="se">\</span>
<span class="p">|</span> java -jar vnu.jar -
</code></pre></div>
</td></tr></table>
<p>To paraphrase the code, this script retrieves the <a href="//github.com/validator/validator"><code>vnu.jar</code> validator from Github</a> on the first run, and use it to check the HTML code passed by standard input or filename.
Hence, the script downloads and uncompresses a ZIP archive file. Moreover, it filters out from the HTML file any <a href="//mustache.github.io">mustache</a> string pattern.</p>
<p>Now, compare it to the following equivalent Python code:</p>
<table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span class="normal"> 1</span>
<span class="normal"> 2</span>
<span class="normal"> 3</span>
<span class="normal"> 4</span>
<span class="normal"> 5</span>
<span class="normal"> 6</span>
<span class="normal"> 7</span>
<span class="normal"> 8</span>
<span class="normal"> 9</span>
<span class="normal">10</span>
<span class="normal">11</span>
<span class="normal">12</span>
<span class="normal">13</span>
<span class="normal">14</span>
<span class="normal">15</span>
<span class="normal">16</span>
<span class="normal">17</span>
<span class="normal">18</span>
<span class="normal">19</span>
<span class="normal">20</span>
<span class="normal">21</span>
<span class="normal">22</span>
<span class="normal">23</span>
<span class="normal">24</span>
<span class="normal">25</span>
<span class="normal">26</span>
<span class="normal">27</span>
<span class="normal">28</span>
<span class="normal">29</span>
<span class="normal">30</span>
<span class="normal">31</span>
<span class="normal">32</span>
<span class="normal">33</span>
<span class="normal">34</span>
<span class="normal">35</span>
<span class="normal">36</span>
<span class="normal">37</span>
<span class="normal">38</span>
<span class="normal">39</span>
<span class="normal">40</span>
<span class="normal">41</span>
<span class="normal">42</span>
<span class="normal">43</span></pre></div></td><td class="code"><div class="highlight"><pre><span></span><code><span class="ch">#!/usr/bin/env python2.7</span>
<span class="c1"># INSTALL: python2.7 -m pip install --user sh</span>
<span class="c1"># USAGE: ./vnu_html_checker.py $file.html</span>
<span class="c1"># OR: ./vnu_html_checker.py < $file.html</span>
<span class="kn">import</span> <span class="nn">os.path</span><span class="o">,</span> <span class="nn">re</span>
<span class="kn">from</span> <span class="nn">sys</span> <span class="kn">import</span> <span class="n">argv</span><span class="p">,</span> <span class="nb">exit</span><span class="p">,</span> <span class="n">stderr</span><span class="p">,</span> <span class="n">stdin</span>
<span class="kn">import</span> <span class="nn">sh</span>
<span class="n">sh</span> <span class="o">=</span> <span class="n">sh</span><span class="p">(</span><span class="n">_err</span><span class="o">=</span><span class="n">stderr</span><span class="p">)</span>
<span class="n">VNU_GITHUB</span> <span class="o">=</span> <span class="s1">'https://github.com/validator/validator'</span>
<span class="n">VNU_VERSION</span> <span class="o">=</span> <span class="s1">'20150207'</span>
<span class="k">def</span> <span class="nf">download_vnu_jar</span><span class="p">():</span>
<span class="n">zip_filename</span> <span class="o">=</span> <span class="s1">'vnu-{}.jar.zip'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">VNU_VERSION</span><span class="p">)</span>
<span class="n">download_url</span> <span class="o">=</span> <span class="s1">'{}/releases/download/{}/{}'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">VNU_GITHUB</span><span class="p">,</span> <span class="n">VNU_VERSION</span><span class="p">,</span> <span class="n">zip_filename</span><span class="p">)</span>
<span class="n">sh</span><span class="o">.</span><span class="n">wget</span><span class="p">(</span><span class="n">download_url</span><span class="p">)</span>
<span class="n">sh</span><span class="o">.</span><span class="n">unzip</span><span class="p">(</span><span class="n">zip_filename</span><span class="p">)</span>
<span class="n">sh</span><span class="o">.</span><span class="n">mv</span><span class="p">(</span><span class="s1">'vnu/vnu.jar'</span><span class="p">,</span> <span class="s1">'.'</span><span class="p">)</span>
<span class="n">sh</span><span class="o">.</span><span class="n">rm</span><span class="p">(</span><span class="s1">'-r'</span><span class="p">,</span> <span class="s1">'vnu/'</span><span class="p">,</span> <span class="n">zip_filename</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">filter_out_xuacompat_line</span><span class="p">(</span><span class="n">input_pipe</span><span class="p">):</span> <span class="c1"># seen as an error by VNU</span>
<span class="k">return</span> <span class="p">(</span><span class="n">l</span> <span class="k">for</span> <span class="n">l</span> <span class="ow">in</span> <span class="n">input_pipe</span> <span class="k">if</span> <span class="s1">'meta http-equiv="X-UA-Compatible"'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">l</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">filter_out_mustaches</span><span class="p">(</span><span class="n">input_pipe</span><span class="p">):</span>
<span class="k">return</span> <span class="p">(</span><span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="s1">'{[{%][^{}]+[%}]}'</span><span class="p">,</span> <span class="s1">'DUMMY_MUSTACHE'</span><span class="p">,</span> <span class="n">l</span><span class="p">)</span> <span class="k">for</span> <span class="n">l</span> <span class="ow">in</span> <span class="n">input_pipe</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="s1">'vnu.jar'</span><span class="p">):</span>
<span class="n">download_vnu_jar</span><span class="p">()</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">argv</span><span class="p">)</span> <span class="o">></span> <span class="mi">1</span><span class="p">:</span>
<span class="n">pipe</span> <span class="o">=</span> <span class="n">sh</span><span class="o">.</span><span class="n">cat</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">_iter</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">pipe</span> <span class="o">=</span> <span class="n">sh</span><span class="o">.</span><span class="n">cat</span><span class="p">(</span><span class="n">_in</span><span class="o">=</span><span class="n">stdin</span><span class="p">,</span> <span class="n">_iter</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">pipe</span> <span class="o">=</span> <span class="n">filter_out_xuacompat_line</span><span class="p">(</span><span class="n">pipe</span><span class="p">)</span>
<span class="n">pipe</span> <span class="o">=</span> <span class="n">filter_out_mustaches</span><span class="p">(</span><span class="n">pipe</span><span class="p">)</span>
<span class="n">parsed_html</span> <span class="o">=</span> <span class="s1">''</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">pipe</span><span class="p">)</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">'utf8'</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">sh</span><span class="o">.</span><span class="n">java</span><span class="p">(</span><span class="s1">'-jar'</span><span class="p">,</span> <span class="s1">'vnu.jar'</span><span class="p">,</span> <span class="s1">'-'</span><span class="p">,</span> <span class="n">_in</span><span class="o">=</span><span class="n">parsed_html</span><span class="p">)</span>
<span class="k">except</span> <span class="n">sh</span><span class="o">.</span><span class="n">ErrorReturnCode</span> <span class="k">as</span> <span class="n">error</span><span class="p">:</span>
<span class="nb">exit</span><span class="p">(</span><span class="n">error</span><span class="o">.</span><span class="n">exit_code</span><span class="p">)</span>
</code></pre></div>
</td></tr></table>
<p>First observation: the code is definitively lengthier. 88% more characters exactly.
But look at the benefits !</p>
<ul>
<li>the script keeps its general structure: pipe-based, functional, applying simple filters on the input stream</li>
<li>all commands are checked at import time so they are guaranteed to exist: <code>java</code>, <code>wget</code>... By the way, you are not limited to Linux <em>coreutils</em>, you can use <strong>any</strong> command on your system.</li>
<li><strong>try/catch blocks</strong> !!!</li>
<li>in case of failure, you can inspect the objects at runtime with <code>pdb</code></li>
<li>finally, two commands invocation (<code>grep</code> and <code>awk</code>) have been replaced by native Python inline generators. I prefer avoiding unnecessary command calls, but in the process of migrating this script I initially sticked with them:</li>
</ul>
<p><code>return grep('-vF', 'meta http-equiv="X-UA-Compatible"', _in=input_pipe, _iter=True)</code></p>
<p><code>return sed('-e' 's/{[{%][^{}]\+[%}]}/DUMMY_MUSTACHE/g', _in=input_pipe, _iter=True)</code></p>
<p>I hope I convinced you: next time you write a script, <strong>think about Python</strong> !</p>
<p>But BEWARE, even Python scripts can become <a href="images/wwcb/Ill_just_write_a_quick_script...-catacrac.net.png">spaghetti code monsters</a>.</p>
<p><strong>EDIT[17/08/2015]</strong> : as long as the interpreter is installed on your system, you can run a standalone Python script just like a Bash script and easily benefit from the THOUSANDS libraries in Pypy, including <code>sh.py</code>, by invoking <code>pip</code> FROM YOUR SCRIPT !</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">pip</span>
<span class="n">pip</span><span class="o">.</span><span class="n">main</span><span class="p">([</span><span class="s1">'install'</span><span class="p">,</span> <span class="s1">'--user'</span><span class="p">,</span> <span class="s1">'retrying==1.3.3'</span><span class="p">,</span> <span class="s1">'requests==2.7.0'</span> <span class="s1">'sh==1.11'</span><span class="p">])</span>
<span class="c1"># if you're using the logging module & a pip version newer or equal to 6.0, you'll need this bugfix (cf. https://github.com/pypa/pip/issues/3043) :</span>
<span class="n">logging</span><span class="o">.</span><span class="n">root</span><span class="o">.</span><span class="n">handlers</span> <span class="o">=</span> <span class="p">[]</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="kn">from</span> <span class="nn">retrying</span> <span class="kn">import</span> <span class="n">retry</span>
<span class="kn">from</span> <span class="nn">sh</span> <span class="kn">import</span> <span class="n">grep</span><span class="p">,</span> <span class="n">sed</span>
</code></pre></div>Convert source code to PDF with syntax coloring2015-02-25T23:02:00+01:002015-02-25T23:02:00+01:00Lucas Cimontag:chezsoi.org,2015-02-25:/lucas/blog/convert-code-to-pdf-with-syntax-coloring.html<p>Sometimes, it's useful to print some source code on paper. And PDF is a very common file format, that you can be sure your printer will accept, and that will let you preview the final page layout.
But how to quickly perform syntax-coloring and export to PDF ?</p>
<p>I've been experimenting …</p><p>Sometimes, it's useful to print some source code on paper. And PDF is a very common file format, that you can be sure your printer will accept, and that will let you preview the final page layout.
But how to quickly perform syntax-coloring and export to PDF ?</p>
<p>I've been experimenting with <code>pandoc</code>, GNU <code>highlight</code> and Latex/Xetex. Finally, I wrote the following simple bash function to do the job, using <code>pygmentize</code> and <code>wkhtmltopdf</code> :</p>
<div class="highlight"><pre><span></span><code>src2pdf () {
local noext="<span class="cp">${</span><span class="mi">1</span><span class="o">%.*</span><span class="cp">}</span>"
pygmentize -O full -o "<span class="nv">$noext.html</span>" "$1"
# enabling line wrapping in <span class="nt"><pre></span> blocks
perl -i -wpe '/<span class="nt"><style.</span><span class="err">*</span><span class="nt">></span>$/<span class="ni">&&(</span><span class="nv">$_.</span><span class="ni">="pre{white-space:pre-wrap;</span>}\n")' "<span class="nv">$noext.html</span>"
wkhtmltopdf "<span class="nv">$noext.html</span>" "<span class="nv">$noext.pdf</span>"
rm "<span class="nv">$noext.html</span>"
}
</code></pre></div>
<p>There is an example of <a href="images/2015/Fev/UnixUsefulCmds.pdf">PDF result, 133Ko</a>.</p>
<p>Alternatively, if you have syntax coloring enabled in vim, e.g. with the great <code>syntastic</code> plugin, export to PostScript is trivial :</p>
<div class="highlight"><pre><span></span><code>TERM=xterm-256color vim '+hardcopy >out.ps' +q code.src
</code></pre></div>
<p>Because with other values of <code>$TERM</code> environment variable the output colors can change, I prefer to set it explicitly.</p>
<p>You also may need to tweak your <em>.vimrc</em> a little, here is my config :</p>
<div class="highlight"><pre><span></span><code>set printfont=:h9
set printoptions=number:y,left:5pc
</code></pre></div>
<p>And <a href="/lucas/blog/images/2015/Fev/UnixUsefulCmds.ps">the final .ps file</a>.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/wudV1c9jLKM" allowfullscreen></iframe>
<style>
article iframe { display: block; margin: 1rem auto; }
</style>Colored logs in Python2015-02-17T08:02:00+01:002015-02-17T08:02:00+01:00Lucas Cimontag:chezsoi.org,2015-02-17:/lucas/blog/colored-logs-in-python.html<p><u><em>Disclaimer</em>:</u> this post was heaviliy inspired by the 2 following gists:</p>
<ul>
<li><a href="https://gist.github.com/brainsik/1238935"><code>color_log.py</code></a> using <code>termcolor</code> recipes</li>
<li><a href="https://gist.github.com/kergoth/813057"><code>colorlog.py</code></a> using <code>colorama</code></li>
</ul>
<p>I loved the simplicity of the first, not having to manipulate any of the logging internal API, but I prefer <code>colorama</code> over <code>termcolor</code>.</p>
<p>Without further ado, there is my solution …</p><p><u><em>Disclaimer</em>:</u> this post was heaviliy inspired by the 2 following gists:</p>
<ul>
<li><a href="https://gist.github.com/brainsik/1238935"><code>color_log.py</code></a> using <code>termcolor</code> recipes</li>
<li><a href="https://gist.github.com/kergoth/813057"><code>colorlog.py</code></a> using <code>colorama</code></li>
</ul>
<p>I loved the simplicity of the first, not having to manipulate any of the logging internal API, but I prefer <code>colorama</code> over <code>termcolor</code>.</p>
<p>Without further ado, there is my solution :</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">logging</span><span class="o">,</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">colorama</span> <span class="kn">import</span> <span class="n">Back</span><span class="p">,</span> <span class="n">Fore</span><span class="p">,</span> <span class="n">Style</span>
<span class="n">LOG_FORMAT</span> <span class="o">=</span> <span class="s2">"</span><span class="si">%(asctime)s</span><span class="s2"> - pid:</span><span class="si">%(process)s</span><span class="s2"> </span><span class="si">%(filename)s</span><span class="s2">:</span><span class="si">%(lineno)d</span><span class="s2"> </span><span class="si">%(levelname)8s</span><span class="s2">| </span><span class="si">%(message)s</span><span class="s2">"</span>
<span class="k">class</span> <span class="nc">ColorLogsWrapper</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
<span class="n">COLOR_MAP</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'debug'</span><span class="p">:</span> <span class="n">Fore</span><span class="o">.</span><span class="n">CYAN</span><span class="p">,</span>
<span class="s1">'info'</span><span class="p">:</span> <span class="n">Fore</span><span class="o">.</span><span class="n">GREEN</span><span class="p">,</span>
<span class="s1">'warning'</span><span class="p">:</span> <span class="n">Fore</span><span class="o">.</span><span class="n">YELLOW</span><span class="p">,</span>
<span class="s1">'error'</span><span class="p">:</span> <span class="n">Fore</span><span class="o">.</span><span class="n">RED</span><span class="p">,</span>
<span class="s1">'critical'</span><span class="p">:</span> <span class="n">Back</span><span class="o">.</span><span class="n">RED</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">logger</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">logger</span> <span class="o">=</span> <span class="n">logger</span>
<span class="k">def</span> <span class="fm">__getattr__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">attr_name</span><span class="p">):</span>
<span class="k">if</span> <span class="n">attr_name</span> <span class="o">==</span> <span class="s1">'warn'</span><span class="p">:</span>
<span class="n">attr_name</span> <span class="o">=</span> <span class="s1">'warning'</span>
<span class="k">if</span> <span class="n">attr_name</span> <span class="ow">not</span> <span class="ow">in</span> <span class="s1">'debug info warning error critical'</span><span class="p">:</span>
<span class="k">return</span> <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="p">,</span> <span class="n">attr_name</span><span class="p">)</span>
<span class="n">log_level</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">logging</span><span class="p">,</span> <span class="n">attr_name</span><span class="o">.</span><span class="n">upper</span><span class="p">())</span>
<span class="c1"># mimicking logging/__init__.py behaviour</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">isEnabledFor</span><span class="p">(</span><span class="n">log_level</span><span class="p">):</span>
<span class="k">return</span>
<span class="k">def</span> <span class="nf">wrapped_attr</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">style_prefix</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">COLOR_MAP</span><span class="p">[</span><span class="n">attr_name</span><span class="p">]</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">style_prefix</span> <span class="o">+</span> <span class="n">msg</span> <span class="o">+</span> <span class="n">Style</span><span class="o">.</span><span class="n">RESET_ALL</span>
<span class="c1"># We call _.log directly to not increase the callstack</span>
<span class="c1"># so that Logger.findCaller extract the corrects filename/lineno</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">logger</span><span class="o">.</span><span class="n">_log</span><span class="p">(</span><span class="n">log_level</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">return</span> <span class="n">wrapped_attr</span>
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">stream</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="n">LOG_FORMAT</span><span class="p">,</span> <span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
<span class="n">LOGGER</span> <span class="o">=</span> <span class="n">ColorLogsWrapper</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">))</span>
<span class="n">LOGGER</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">'Debug'</span><span class="p">)</span>
<span class="n">LOGGER</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Info'</span><span class="p">)</span>
<span class="n">LOGGER</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="s1">'Warning'</span><span class="p">)</span>
<span class="n">LOGGER</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">'Error'</span><span class="p">)</span>
<span class="n">LOGGER</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="s1">'Critical'</span><span class="p">)</span>
</code></pre></div>
<p>Output:</p>
<pre style="font-family: monospace;">$ py colored_logger.py
2015-02-17 10:38:04,802 - pid:16089 colored_logger.py:39 DEBUG| <span style="color:darkcyan;">Debug</span>
2015-02-17 10:38:04,802 - pid:16089 colored_logger.py:40 INFO| <span style="color:darkgreen;">Info</span>
2015-02-17 10:38:04,802 - pid:16089 colored_logger.py:41 WARNING| <span style="color:gold;">Warning</span>
2015-02-17 10:38:04,802 - pid:16089 colored_logger.py:42 ERROR| <span style="color:darkred;">Error</span>
2015-02-17 10:38:04,802 - pid:16089 colored_logger.py:43 CRITICAL| <span style="background-color:red;color:white;">Critical</span>
</pre>
<p><img src="images/wwcb/CouldWork_Creeper.jpg" alt="Could Work. You won't know if you don't try."></p>Quickly deleting all your old SauceLabs jobs2015-02-17T08:02:00+01:002015-02-17T08:02:00+01:00Lucas Cimontag:chezsoi.org,2015-02-17:/lucas/blog/quickly-deleting-all-your-old-saucelabs-jobs.html<p>In a nutshell:</p>
<div class="highlight"><pre><span></span><code>SUN=$SAUCE_USERNAME; SAK=$SAUCE_ACCESSKEY
curl -u $SUN:$SAAK https://saucelabs.com/rest/v1/$SUN/jobs?format=csv \
| perl -wpe 's/\r$//' \
| xargs -I{} curl -u $SUN:$SAK -X DELETE "https://saucelabs.com/rest/v1/$SUN/jobs/{}"
</code></pre></div>
<p><img src="images/wwcb/Climate-global_warming_level-_Earth_on_Fire.jpg" alt="Earth Kaboom"></p>Pytest AST-modification : getting the tests final code2015-02-15T08:02:00+01:002015-02-15T08:02:00+01:00Lucas Cimontag:chezsoi.org,2015-02-15:/lucas/blog/pytest-ast-modification-getting-the-tests-final-code.html<p><a href="http://pytest.org"><code>Pytest</code></a> is a very complete test framework for Python. I like how you can write a basic <code>unittest.TestCase</code> and the <code>py.test</code> test runner command will inject all its magic at runtime, without you having to directly <code>import</code> anything: awesome separation of concerns.</p>
<p>This modularity comes at a cost …</p><p><a href="http://pytest.org"><code>Pytest</code></a> is a very complete test framework for Python. I like how you can write a basic <code>unittest.TestCase</code> and the <code>py.test</code> test runner command will inject all its magic at runtime, without you having to directly <code>import</code> anything: awesome separation of concerns.</p>
<p>This modularity comes at a cost though: <code>py.test</code> actually preprocess tests before running them, by parsing their <a href="//docs.python.org/2/library/ast.html">AST tree</a> and replacing <code>assert</code> calls by custom exceptions "manually "raised. The final compiled <code>.pyc</code> binaries are then cached in a <code>__pycache__/</code> directory.</p>
<p>My curiosity got aroused by <a href="http://pybites.blogspot.fr/2011/07/behind-scenes-of-pytests-new-assertion.html">this blog post from 2011</a> : wouldn't that be nice to peek into this process and check what the modified code look like exactly ?</p>
<p>I considered 2 solutions:</p>
<ul>
<li>either decompile the cached <code>.pyc</code> files, but I couldn't feed them to <code>uncompyle2</code> nor <code>pycdc</code> without raising bytecode format errors.</li>
<li>take a glance at Pytest code base and find a way to invoke its custom AST-parsing method, then "AST-unparse" the resulting AST tree instead of compiling it down to bytecode.</li>
</ul>
<p>This second solution revealed to be very easy to implement. I hesitated for a moment between two good-looking AST-unparser, namely <a href="https://github.com/berkerpeksag/astor"><code>astor</code></a> and <a href="https://github.com/simonpercivall/astunparse"><code>astunparse</code></a>, and ended up with the following code:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">_pytest.assertion.rewrite</span> <span class="kn">import</span> <span class="n">rewrite_asserts</span>
<span class="kn">import</span> <span class="nn">ast</span><span class="o">,</span> <span class="nn">astunparse</span><span class="o">,</span> <span class="nn">sys</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s1">'r'</span><span class="p">)</span> <span class="k">as</span> <span class="n">open_file</span><span class="p">:</span>
<span class="n">ast_tree</span> <span class="o">=</span> <span class="n">ast</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">open_file</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
<span class="n">rewrite_asserts</span><span class="p">(</span><span class="n">ast_tree</span><span class="p">)</span>
<span class="nb">print</span> <span class="n">astunparse</span><span class="o">.</span><span class="n">unparse</span><span class="p">(</span><span class="n">ast_tree</span><span class="p">)</span>
</code></pre></div>
<p>And that's it !
To test it, just write a dummy <strong>stupid_test.py</strong> file with:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">dummy_test</span><span class="p">():</span>
<span class="k">assert</span> <span class="kc">False</span>
</code></pre></div>
<p>And then <code>python pytest_rewrite.py stupid_test.py</code> :</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">__builtin__</span> <span class="k">as</span> <span class="nd">@py_builtins</span>
<span class="kn">import</span> <span class="nn">_pytest.assertion.rewrite</span> <span class="k">as</span> <span class="nd">@pytest_ar</span>
<span class="k">def</span> <span class="nf">dummy_test</span><span class="p">():</span>
<span class="k">if</span> <span class="p">(</span><span class="ow">not</span> <span class="kc">False</span><span class="p">):</span>
<span class="nd">@py_format1</span> <span class="o">=</span> <span class="p">((</span><span class="s1">''</span> <span class="o">+</span> <span class="s1">'assert </span><span class="si">%(py0)s</span><span class="s1">'</span><span class="p">)</span> <span class="o">%</span> <span class="p">{</span><span class="s1">'py0'</span><span class="p">:</span> <span class="p">(</span><span class="nd">@pytest_ar</span><span class="o">.</span><span class="n">_saferepr</span><span class="p">(</span><span class="kc">False</span><span class="p">)</span> <span class="k">if</span> <span class="p">((</span><span class="s1">'False'</span> <span class="ow">in</span> <span class="nd">@py_builtins</span><span class="o">.</span><span class="n">locals</span><span class="p">())</span> <span class="ow">or</span> <span class="nd">@pytest_ar</span><span class="o">.</span><span class="n">_should_repr_global_name</span><span class="p">(</span><span class="kc">False</span><span class="p">))</span> <span class="k">else</span> <span class="s1">'False'</span><span class="p">)})</span>
<span class="k">raise</span> <span class="ne">AssertionError</span><span class="p">(</span><span class="nd">@pytest_ar</span><span class="o">.</span><span class="n">_format_explanation</span><span class="p">(</span><span class="nd">@py_format1</span><span class="p">))</span>
</code></pre></div>
<p><img src="images/wwcb/Mowgli-and-Kaa.jpg" alt="Mowgli hypnotized by Kaa"></p>
<p>Now, I want to conclude on a more nuanced tone: not everything is perfect in the Pytest world, and I have a few pain points to mention:</p>
<ul>
<li>Pytest terminal reports are rendered character by character, making it impossible to write log messages to stdout without messing everything</li>
<li>Pytest wraps every module/class/object in your tests into <a href="//github.com/pytest-dev/pytest/blob/master/_pytest/python.py">custom wrappers</a> and use generic callback hooks on at least 3 invocation levels : I had to scratch my head for some time to debug minor errors stacktraces and hack around it</li>
<li>Pytest code base is very dense and not always following PEP code standards, making it very difficult to understand and contribute</li>
<li>finally, I have a last minor complaint: when using <code>pytest-xdist</code> to parallelize tests, <a href="//bitbucket.org/hpk42/pytest/issue/680/cannot-disable-capturing-with-dist">you cannot write to <em>stdout</em></a>. I guess it'd be difficult to collect the standard outputs of every process spawned by <code>pytest-xdist</code>, but it's still a PITA.</li>
</ul>Cross-browsers testing : zuul or sauce-tap-runner to replace testling by SauceLabs ?2015-02-09T16:02:00+01:002015-02-09T16:02:00+01:00Lucas Cimontag:chezsoi.org,2015-02-09:/lucas/blog/cross-browsers-testing-zuul-or-brtapsauce-to-replace-testling-by-saucelabs.html<p><a href="//github.com/substack/testling/issues/88">Since July 2014</a>, <strong>Substack</strong> great cross-browsers testing tool <code>testling</code> has been unavailable.</p>
<p>Today I was looking for an alternative to use with <a href="https://github.com/Lucas-C/ecovoit"><strong>ecovoit</strong></a>, my carpooling search engine. <a href="//saucelabs.com/docs/onboarding">Saucelabs</a> is a very interesting solution, and is free for open-source projects.</p>
<p>Now I found 2 tools to easily launch your Javascript TAP …</p><p><a href="//github.com/substack/testling/issues/88">Since July 2014</a>, <strong>Substack</strong> great cross-browsers testing tool <code>testling</code> has been unavailable.</p>
<p>Today I was looking for an alternative to use with <a href="https://github.com/Lucas-C/ecovoit"><strong>ecovoit</strong></a>, my carpooling search engine. <a href="//saucelabs.com/docs/onboarding">Saucelabs</a> is a very interesting solution, and is free for open-source projects.</p>
<p>Now I found 2 tools to easily launch your Javascript TAP tests to SauceLabs:</p>
<ul>
<li><a href="//github.com/defunctzombie/zuul">zuul</a></li>
<li><a href="//github.com/conradz/sauce-tap-runner">sauce-tap-runner</a> + potentially <a href="//github.com/rvagg/brtapsauce">brtapsauce</a></li>
</ul>
<p>There is a comparison table to choose betwen the two :</p>
<table>
<thead>
<tr>
<th></th>
<th>zuul</th>
<th>sauce-tap-runner</th>
</tr>
</thead>
<tbody>
<tr>
<td>Project maturity</td>
<td>352 commits<br>20 contributors</td>
<td><< 50 commits<br>1 contributor</td>
</tr>
<tr>
<td>Project vitality</td>
<td>last commit 7 days ago</td>
<td>last commit one year ago</td>
</tr>
<tr>
<td>Run tests locally</td>
<td><code>zuul -- test</code></td>
<td><code>npm run-script test-local</code></td>
</tr>
<tr>
<td>Configuration file</td>
<td><code>.zuul.yml</code> + <code>.zuulrc</code></td>
<td><code>test/sauce.js</code></td>
</tr>
<tr>
<td>Supported test frameworks</td>
<td>mocha, tape, qunit, jasmine</td>
<td>TAP</td>
</tr>
<tr>
<td>Module to interact with SauceLabs</td>
<td>included <a href="https://github.com/defunctzombie/zuul/blob/master/lib/SauceBrowser.js">SauceBrowser.js</a></td>
<td><a href="https://github.com/conradz/sauce-tap-runner">sauce-tap-runner</a> downloads & launches the Sauce Connect JAR</td>
</tr>
<tr>
<td>Bonus points</td>
<td><a href="https://github.com/defunctzombie/zuul/wiki/quickstart">great tutorial</a> including how to setup Travis CI with secured credentials</td>
<td></td>
</tr>
</tbody>
</table>Equivalent for Linux of Windows Python launcher 'py'2015-01-12T19:01:00+01:002015-01-12T19:01:00+01:00Lucas Cimontag:chezsoi.org,2015-01-12:/lucas/blog/equivalent-for-linux-of-windows-python-launcher-py.html<p>Under Windows, CPython is shipped with a <a href="https://docs.python.org/3/using/windows.html#python-launcher-for-windows">very useful <code>py</code> command</a>.</p>
<p><a href="https://www.python.org/dev/peps/pep-0397/">PEP-397</a> describes in details its behaviour, and its C implementation can be found <a href="https://hg.python.org/cpython/file/8b3c609f3f73/PC/launcher.c">in the CPYthon Mercurial repository</a>.</p>
<p><img src="images/wwcb/IronMan_INeedIt.gif" alt="IronMan_INeedIt.gif" title="python -m antigravity"></p>
<p>There has already been a lenghty discussion <a href="https://mail.python.org/pipermail/python-ideas/2014-April/thread.html#27633">in the "python-ideas" mailing list</a> to write a Linux equivalent. I agree with what …</p><p>Under Windows, CPython is shipped with a <a href="https://docs.python.org/3/using/windows.html#python-launcher-for-windows">very useful <code>py</code> command</a>.</p>
<p><a href="https://www.python.org/dev/peps/pep-0397/">PEP-397</a> describes in details its behaviour, and its C implementation can be found <a href="https://hg.python.org/cpython/file/8b3c609f3f73/PC/launcher.c">in the CPYthon Mercurial repository</a>.</p>
<p><img src="images/wwcb/IronMan_INeedIt.gif" alt="IronMan_INeedIt.gif" title="python -m antigravity"></p>
<p>There has already been a lenghty discussion <a href="https://mail.python.org/pipermail/python-ideas/2014-April/thread.html#27633">in the "python-ideas" mailing list</a> to write a Linux equivalent. I agree with what Éric Araujo wrote, that there isn't really a need for such a tool under Linux.</p>
<p>But I find it useful to use a <code>py -3.4 -m pip install $pkg</code> or <code>py 27 $file</code> shorthand, and hence I wrote a basic bash script equivalent : <a href="https://github.com/Lucas-C/linux_configuration/blob/master/bin/py">https://github.com/Lucas-C/linux_configuration/blob/master/bin/py</a></p>
<p><strong>EDIT [2018/06/18]</strong>: there is also <a href="https://crates.io/crates/python-launcher">Brett Cannon python-launcher written in Rust</a></p>Bonne année ! A vos dons, prêt ? Partez !2015-01-10T16:01:00+01:002015-01-10T16:01:00+01:00Lucas Cimontag:chezsoi.org,2015-01-10:/lucas/blog/bonne-annee-a-vos-dons-pret-partez.html<p><img src="images/wwcb/SuperDupont_OuiNideIou.jpg" style="height:400px" alt="Oui Nide Iou" title="SuperDupont par Gotlib"></p>
<p>L'année dernière, j'avais été touché par l'article de @nicolasy "<a href="http://artimuses.be/2013/12/noel-est-passe-mais-il-vous-reste-quelques-cadeaux-a-faire/">Noël est passé, mais il vous reste quelques cadeaux à faire !</a>" En cette rentrée 2015, je me permets de lui piquer l'idée.</p>
<p>Certes, la motivation de fin d'année de faire ses derniers dons défisqualisés est passée. Certes, il est un peu …</p><p><img src="images/wwcb/SuperDupont_OuiNideIou.jpg" style="height:400px" alt="Oui Nide Iou" title="SuperDupont par Gotlib"></p>
<p>L'année dernière, j'avais été touché par l'article de @nicolasy "<a href="http://artimuses.be/2013/12/noel-est-passe-mais-il-vous-reste-quelques-cadeaux-a-faire/">Noël est passé, mais il vous reste quelques cadeaux à faire !</a>" En cette rentrée 2015, je me permets de lui piquer l'idée.</p>
<p>Certes, la motivation de fin d'année de faire ses derniers dons défisqualisés est passée. Certes, il est un peu tard pour des cadeaux de Noël. Et certes, <a href="http://www.nextinpact.com/news/91483-menacee-disparition-la-quadrature-net-lance-appel-aux-dons.htm">la Quadrature du Net est sauvée pour 2015</a>.</p>
<p>Mais peu importe : ce début d'année est le moment idéal pour prendre une bonne résolution concrète. Et il n'y a rien de mieux pour se sentir bien dans ses baskets et surmonter le blues post-fêtes-de-fin-d'année !</p>
<p>Cette article se veut donc une liste de suggestions, pour peut-être vous faire découvrir une nouvelle bonne cause à soutenir, ou dans laquelle vous impliquer.</p>
<p><br>
<strong>Aider le logiciel libre de notre quotidien</strong></p>
<p>Quel langage de programmation utilisez-vous le plus ? Quel éditeur de texte ou IDE ? Quel débogueur, système de tests unitaires ou outil de déploiement automatisé ?</p>
<p>Les chances sont grandes pour que la réponse à au moins l'une de ces questions soit un <a href="http://fr.wikipedia.org/wiki/Logiciel_libre">projet ou logiciel libre</a> et/ou <a href="http://fr.wikipedia.org/wiki/Open_source">open source</a>. Surtout si vous utilisez Linux. Des développeurs ont bénévolement conçu et maintenu nombre d'outils que nous utilisons.</p>
<p>Et il est important de les aider:</p>
<ul>
<li>pour les remercier</li>
<li>pour soutenir leurs efforts constants d'amélioration et de réparation de bugs de ces outils</li>
<li>pour encourager cette philosophie et l'éclosion de nouveaux projets</li>
</ul>
<p>Voici donc quelques pistes pour leur faire un don, de temps ou d'argent :</p>
<ul>
<li>
<p>trouvez l'organisme dédié à supporter <strong>votre langage de programmation favori</strong>, par exemple la <a href="//www.python.org/psf/donations/">Python Software Foundation</a>.</p>
</li>
<li>
<p>vous utilisez <strong>Linux</strong> ? Vous avez probablement déjà tapé quelques commandes dans un terminal console : <a href="http://en.wikipedia.org/wiki/GNU_Core_Utilities"><code>ls</code>, <code>cp</code></a> ? Et peut être aussi utilisé <code>Gimp</code>, <code>Pidgin</code>, <code>bash</code>, <code>emacs</code>, <code>gdb</code>, <code>less</code>, <code>make</code>, <code>screen</code>, <code>sed</code>, <code>gzip</code> ou <code>tar</code> ? Tous ces programmes sont développés et maintenus par le projet <a href="http://www.gnu.org">GNU</a>, sponsorisé par la <a href="http://www.fsf.org/">Free Software Fundation</a>. La FSF accepte les donations, vous pouvez trouver les détails de leurs actions <a href="http://www.fsf.org/campaigns/">ici</a>, et il existe une <a href="//fsfe.org/work.fr.html">branche européenne</a>, mais vous pouvez aussi <a href="http://www.gnu.org/server/takeaction.html">contribuer de plein d'autres façons</a>. Enfin, si vous voulez devenir le Jango Fett du logiciel libre (ou son sponsor), GNU <a href="http://www.gnu.org/software/hurd/donate.html">suggère</a> le système de primes de la <a href="http://www.fossfactory.org">FOSS factory</a>.</p>
</li>
<li>
<p>vous utilisez <strong>Apache</strong> ? Cassandra DB, Couch DB, Hadoop, Lucene, Maven, Open Office ou Subversion ? Pensez à l'<a href="http://www.apache.org/foundation/contributing.html">Apache Software Foundation</a>.</p>
</li>
<li>
<p>grand utilisateur de <strong>Libre Office</strong> ? Donnez à la <a href="http://www.documentfoundation.org/">Document Foundation</a>.</p>
</li>
<li>
<p>amateur de <strong><code>git</code></strong>, Mercurial, Boost libraries, Gevent, Pypy, Twisted, Selenium ou Wine ? Aidez la <a href="http://sfconservancy.org/donate/">Software Freedom Conservancy</a>.</p>
</li>
<li>
<p>enfin, les développeurs d'<strong>Eclipse</strong> acceptent <a href="http://www.eclipse.org/donate/">donations</a> et <a href="http://www.eclipse.org/contribute/">toute bonne volonté</a>.</p>
</li>
</ul>
<p><br>
<strong>Défendre Internet</strong></p>
<p><img src="images/wwcb/nsa_killed_Internet_have_to_build_a_gnu_one.jpg" alt="Grumpy cat : NSA killed the Internet - Now I have to build a new one"></p>
<ul>
<li>
<p><strong><a href="//framasoft.org">Framasoft</a></strong> est une mon organisation favorite. Cette association française promouvoit le logiciel libre depuis 2001. La liste de leurs projets est impressionnante: <a href="//framapad.org">framapad</a>, <a href="//framadate.org">framadate</a>, <a href="//framindmap.org">framamindmap</a>, <a href="//www.framapack.org/">framapack</a>, <a href="//www.framakey.org">framakey</a>... Vous pouvez <a href="//soutenir.framasoft.org">leur faire un don</a> ou <a href="//contact.framasoft.org/participer/">contribuer directement</a>.</p>
</li>
<li>
<p>la <strong><a href="//www.laquadrature.net/fr">Quadrature du Net</a></strong> est une autre association française se consacrant à la défense des droits et libertés des citoyens sur Internet. Sa page de <a href="//www.laquadrature.net/fr/propositions">propositions</a> illustre assez bien les principes de neutralité du net, de partage de la culture et d'accès à un Internet libre qu'elle promouvoit. Vous pouvez <a href="//soutien.laquadrature.net/">leur faire un don</a> ou <a href="//www.laquadrature.net/en/how-to-participate">contribuer directement</a>.</p>
</li>
<li>
<p>la <strong><a href="//www.mozilla.org/en-US/foundation/">Fondation Mozilla</a></strong> est non seulement la maman du navigateur Firefox et du logiciel de courriels Thunderbird, c'est aussi une association à but non lucratif <a href="//www.mozilla.org/fr/mission/">oeuvrant à améliorer Internet</a>, et vous pouvez l'<a href="//www.mozilla.org/fr/contribute/">y aider de bien des manières</a>.</p>
</li>
<li>
<p>l'<strong><a href="//www.eff.org">Electronic Frontier Foundation</a></strong> est une ONG défendant la liberté d'expression sur Internet. Ses actions sont <a href="//www.eff.org/issues">nombreuses</a>, mais ce sont notamment des champions législatifs ayant <a href="https://www.eff.org/victories">défendus nos droits</a> dans des procès contre des multinationnales et même des gouvernements. Pour l'anecdote, l'EFF a été fondée suite au procès en 1993 de <em>Steve Jackson Games</em>, la fameuse compagnie de JDR et jeux de plateaux américaine, contre les services secrets nationnaux qui avaient confisqué leur matériel informatique sans raison valable ! L'histoire est tout bonnement <a href="http://kotaku.com/5801427/the-day-the-secret-service-raided-a-role-playing-game-company">incroyable</a>. Aidez-les <a href="//supporters.eff.org/donate">avec un don</a> ou en <a href="//act.eff.org/">s'inscrivant à leur mailing-list</a>.</p>
</li>
<li>
<p>la <strong><a href="https://wikimediafoundation.org">Fondation Wikimedia</a></strong> est une organisation à but non lucratif qui héberge entres autres Wikipédia et le Wiktionnaire, et a pour objectif le partage libre de l'ensemble des connaissances humaines. Rien que ça. <a href="//donate.wikimedia.org/w?title=Special:FundraiserLandingPage&country=FR">Faites lui un don.</a></p>
</li>
</ul>
<p><br>
<strong>Aider en tant que développeur</strong></p>
<p>Vous pouvez aussi aider en dehors milieu du logiciel et d'Internet, mais en mettant à profit vos compétences.</p>
<p>Les sites ci-dessous ont pour vocation de vous mettre en relation avec des associations qui ont besoin de vous :</p>
<ul>
<li><a href="http://www.betobe.org/php/?language_code=fr">beTobe</a></li>
<li><a href="http://www.tousbenevoles.org/trouver-une-mission-benevole?cp=&id_action_type=24&id_public=&q=&age_minimum=0">tousbenevoles.org</a></li>
<li><a href="http://www.francebenevolat.org/benevoles/recherche/assoc-annonce?search%5Btype_mission%5D=16&search%5Bdomaine_action%5D=&search%5Blocalisation%5D=Ville%2C+code+postal%2C+...&search%5Bkeywords%5D=Mots+cl%C3%A9s%2C+nom+association%2C+...">francebenevolat.org</a></li>
<li><a href="//www.onlinevolunteering.org/fr/org/opportunity_search/?typ=5">onlinevolunteering.org</a></li>
<li><a href="http://socialcoder.org">socialcoder</a></li>
</ul>
<p><br>
<strong>Aider au-delà du numérique</strong></p>
<p>Les causes sont nombreuses et tout aussi importantes : <a href="https://www.unicef.org">UNICEF</a>, <a href="http://www.wwf.fr/">WWF</a>, <a href="http://www.msf.org/">MSF</a>, <a href="http://www.amnesty.org/fr/how-you-can-help">Amnesty</a>, <a href="http://www.leriremedecin.org/">Rire médecin</a>... Tous acceptent les dons, mais vous pouvez aussi <a href="//campaigns.amnesty.org/fr/actions">signer une pétition</a> ou <a href="http://www.amnesty-marseille.fr/spip.php?article25">envoyer une lettre</a>.</p>
<p>Personnellement, j'ai pris une habitude toute bête : passer donner mon sang à l'<strong><a href="http://www.dondusang.net">Etablissement Français du Sang</a></strong>. C'est une chouette balade : ça ne prend pas longtemps, vous serez acceuilli par des gens charmants et reconnaissants, et vous pouvez même bavarder avec d'autres donneurs ou le personnel médical. En plus une petite collation est offerte ! Pour trouver où donner en quelques clics, <a href="http://www.dondusang.net/rewrite/heading/1000/ou-donner/rechercher-une-collecte.htm">cliquez ici</a>.</p>
<p><br>
<strong>Parlez-en autour de vous !</strong></p>
<p>Et c'est sans doute le plus important. Si vous êtes développeur, vous êtes une porte d'entrée pour tous vos proches dans le monde fabuleux du numérique.
Vantez-leur les incroyables bénéfices d'Internet et prévenez-les des risques. Vulgarisez le fonctionnement des ordinateurs et d'Internet ! Discutez de l'évolution de la législation française sur la neutralité du net et la propriété intellectuelle. Débatez de l'actualité politique et technologique, des innovations importantes et des causes à défendre.
Et si vous manquez d'idées pour troller en repas de familler, <a href="http://www.nesblog.com/la-quadrature-du-net/">le NesBlog a des suggestions pour vous</a> ;)</p>Le centre de la France selon Google2014-12-18T11:12:00+01:002014-12-18T11:12:00+01:00Lucas Cimontag:chezsoi.org,2014-12-18:/lucas/blog/le-centre-de-la-france-selon-google.html<p>C'était la prise de conscience rigolote de ce matin :)</p>
<p>Petite explication: plusieurs des services web de géolocalisation gracieusement fournis par Google utilisent la même interface (API). Cette interface requiert de définir une "zone de travail" géographique dans laquelle on va ensuite pouvoir interroger Google sur des adresses, des itinéraries, etc …</p><p>C'était la prise de conscience rigolote de ce matin :)</p>
<p>Petite explication: plusieurs des services web de géolocalisation gracieusement fournis par Google utilisent la même interface (API). Cette interface requiert de définir une "zone de travail" géographique dans laquelle on va ensuite pouvoir interroger Google sur des adresses, des itinéraries, etc.</p>
<p>La façon la plus simple de spécifier cette zone est d'indiquer au service les coordonnées du centre d'un carré géographique. Exemple pour la France:</p>
<p><img alt="Carte de la France entière" src="images/2014/Dec/le_centre_de_la_France_square.png"></p>
<p>Or Google fournit un valeur par défaut précise pour ces coordonnées...</p>
<div class="highlight"><pre><span></span><code>$ curl -s https://maps.googleapis.com/maps/api/geocode/json?address<span class="o">=</span>France <span class="p">|</span> jq <span class="s1">'..|objects|.location//empty'</span>
<span class="o">{</span>
<span class="s2">"lat"</span>: <span class="m">46</span>.227638,
<span class="s2">"lng"</span>: <span class="m">2</span>.213749
<span class="o">}</span>
</code></pre></div>
<p>Où se trouve donc le centre de la France alors ? Pas très loin de Montluçon il semblerait :</p>
<p><img alt="Carte Googlemaps centrée à côté de Montluçon" src="images/2014/Dec/le_centre_de_la_France_map.jpg"></p>
<p>Vous seriez pas curieux de creuser dans cette clairière vous ? Je parie qu'un trésor nous attend...</p>
<p><img alt="Carte Googlemaps avec rendu photo zoomée sur une clairière" src="images/2014/Dec/le_centre_de_la_France_satelite.jpg"></p>Replicating PHPSESSID and srctoken session authentication with mitmproxy2014-12-11T09:12:00+01:002014-12-11T09:12:00+01:00Lucas Cimontag:chezsoi.org,2014-12-11:/lucas/blog/replicating-phpsessid-and-srctoken-session-authentication-with-mitmproxy.html<p>A month ago, I wanted to automate queries to a website that is using the PHPSESSID cookie to keep track of sessions. I struggled a lot and couldn't find any documentation covering the behaviour I was observing. But yesterday I finally found a solution !</p>
<p><img alt="Meme image: a guy is going down some stairs swaying happily" src="images/wwcb/Going_down_the_stairs_happily.gif"></p>
<p>In hope it could help others …</p><p>A month ago, I wanted to automate queries to a website that is using the PHPSESSID cookie to keep track of sessions. I struggled a lot and couldn't find any documentation covering the behaviour I was observing. But yesterday I finally found a solution !</p>
<p><img alt="Meme image: a guy is going down some stairs swaying happily" src="images/wwcb/Going_down_the_stairs_happily.gif"></p>
<p>In hope it could help others, I'm going to expose my findings here. I won't detail all my initial attempts and only focus on how to use the amazing <a href="http://mitmproxy.org"><code>mitmproxy</code></a> command.</p>
<p>First, some exposition : the PHPSESSID cookie is used by PHP to keep track of sessions. It is generated when first accessing the website and sent to the client in the initial response headers. For the session to "stick", the client must include that cookie in every later request to the server.</p>
<p>But on the server side, the PHPSESSID cookie has an expiration date (in my case after 20 minutes). So in order to automate queries to the website I had to find a way to automatically extract and reuse that cookie.</p>
<p>Now, quoting the official documentation, <code>mitmproxy</code> is <cite>an interactive console program that allows traffic flows to be intercepted, inspected, modified and replayed.</cite></p>
<p>So first, it's a traffic inspection tool, like <a href="http://www.telerik.com/fiddler">Fiddler</a>, <a href="http://wiki.wireshark.org/Tools"><code>wireshark</code></a> or <code>tcpdump</code>. To enable it, there are only 2 steps :</p>
<ul>
<li>start the proxy so it listens on 0.0.0.0:8080 : <code>mitmproxy --host</code>. The interactive window opened should be empty, you can get the list of available commands with <code>?</code>.</li>
<li>configure your browser to use this adress as a proxy to access the Internet.</li>
</ul>
<p>Now you can browse to the website you want to interract with, and <code>mitmproxy</code> will record the traffic "flows" generated.</p>
<p>Once you are done, save the record in a file : <code>w</code> <code>a</code> <code>traffic.mitm</code>. And exit : <code>q</code> <code>y</code>.</p>
<p>To experience the full capabilities of <code>mitmproxy</code>, launch the command again without parameters and re-open & replay the "traffic flows" file with <code>c</code> <code>traffic.mitm</code>. The arrow keys will let you navigate between the flows, and you'll be able to selectively replay a flow with <code>r</code> or inspect one by pressing <code>Enter</code>. In the flow view, you'll see the request headers and can switch to the response details by pressing <code>Tab</code>.</p>
<p><img alt="mitmproxy terminal output screenshot" src="images/2014/Dec/mitmproxy_screenshot.png"></p>
<p>Not only <code>mitmproxy</code> let you replay recorded traffic, you can also programmatically modify your requests using scripts: <a href="https://docs.mitmproxy.org/stable/addons-scripting/">https://docs.mitmproxy.org/stable/addons-scripting/</a>.</p>
<p>That's an awesome feature, and a few weeks ago I was able in no time to write a basic script that recorded the PHPSESSID cookie generated on the first request to the website, and inject it in the following requests.</p>
<p>But that did not work. Given the mostly empty HTML responses generated by the server, the bare cookie wasn't enough for the session to properly "stick" and the website ro recognize me... :(</p>
<p><img alt="Gandalf road sign: You shall not pass" src="images/wwcb/YouShallNotPass.jpg"></p>
<p>What I only realized yesterday is that I missed one key element: <strong>the srctoken input value</strong>. This doesn't seem like a widely used method, but at each request my PHP website was generating a form input tag with a different predefined value. This form parameter was then sent in the next POST request query url.</p>
<p>To explain more graphically, my initial mental model of the PHPSESSID cookie exchange was the following :</p>
<p><img src="images/2014/Dec/phpsessid_diagram1.png" alt="PHPSESSID exchange diagram" title="Made with zwibbler.com"/></p>
<p>But this is a more complete picture :</p>
<p><img src="images/2014/Dec/phpsessid_diagram2.png" alt="PHPSESSID & srctoken exchange diagram" title="Made with zwibbler.com"/></p>
<p>With that final bit of information I was able to craft the following <code>mitmproxy</code> script :</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">print_function</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">from</span> <span class="nn">libmproxy.protocol.http</span> <span class="kn">import</span> <span class="n">decoded</span>
<span class="k">def</span> <span class="nf">log</span><span class="p">(</span><span class="n">string</span><span class="p">):</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">'./mitmproxy.log'</span><span class="p">,</span> <span class="s1">'a'</span><span class="p">)</span> <span class="k">as</span> <span class="n">output_log</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">string</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">output_log</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">response</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">flow</span><span class="p">):</span>
<span class="n">log</span><span class="p">(</span><span class="s2">"Entering 'response' hook"</span><span class="p">)</span>
<span class="k">if</span> <span class="s1">'Set-Cookie'</span> <span class="ow">in</span> <span class="n">flow</span><span class="o">.</span><span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">:</span>
<span class="k">for</span> <span class="n">cookie</span> <span class="ow">in</span> <span class="n">flow</span><span class="o">.</span><span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'Set-Cookie'</span><span class="p">]:</span>
<span class="k">if</span> <span class="ow">not</span> <span class="s1">'PHPSESSID'</span> <span class="ow">in</span> <span class="n">cookie</span><span class="p">:</span>
<span class="k">continue</span>
<span class="c1"># storing values in the 'context' as globals wouldn't persist</span>
<span class="n">context</span><span class="o">.</span><span class="n">phpsessid</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="s1">'PHPSESSID=(.*);'</span><span class="p">,</span> <span class="n">cookie</span><span class="p">)</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">log</span><span class="p">(</span><span class="s1">'New PHPSESSID cookie set: </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">context</span><span class="o">.</span><span class="n">phpsessid</span><span class="p">))</span>
<span class="k">with</span> <span class="n">decoded</span><span class="p">(</span><span class="n">flow</span><span class="o">.</span><span class="n">response</span><span class="p">):</span>
<span class="n">match</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="s1">'<input id="srctoken" name="srctoken" type="hidden" value="(.*)" />'</span><span class="p">,</span> <span class="n">flow</span><span class="o">.</span><span class="n">response</span><span class="o">.</span><span class="n">content</span><span class="p">)</span>
<span class="k">if</span> <span class="n">match</span><span class="p">:</span>
<span class="n">context</span><span class="o">.</span><span class="n">srctoken</span> <span class="o">=</span> <span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">log</span><span class="p">(</span><span class="s1">'srctoken found: </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">context</span><span class="o">.</span><span class="n">srctoken</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">request</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">flow</span><span class="p">):</span>
<span class="n">log</span><span class="p">(</span><span class="s2">"Entering 'request' hook"</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s1">'phpsessid'</span><span class="p">):</span>
<span class="n">log</span><span class="p">(</span><span class="s2">"ERROR: no PHPSESSID extracted - aborting"</span><span class="p">)</span>
<span class="k">return</span>
<span class="n">phpsessid_str</span> <span class="o">=</span> <span class="s1">'PHPSESSID=</span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">context</span><span class="o">.</span><span class="n">phpsessid</span><span class="p">)</span>
<span class="k">if</span> <span class="s1">'Cookie'</span> <span class="ow">in</span> <span class="n">flow</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">headers</span><span class="p">:</span>
<span class="n">cookie_substituted</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">def</span> <span class="nf">process_cookie</span><span class="p">(</span><span class="n">cookie</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="s1">'PHPSESSID'</span> <span class="ow">in</span> <span class="n">cookie</span><span class="p">:</span>
<span class="k">return</span> <span class="n">cookie</span>
<span class="n">cookie_substituted</span> <span class="o">=</span> <span class="n">cookie</span>
<span class="k">return</span> <span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="s1">'PHPSESSID=[^;]*'</span><span class="p">,</span> <span class="n">phpsessid_str</span><span class="p">,</span> <span class="n">cookie</span><span class="p">)</span>
<span class="n">flow</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'Cookie'</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">process_cookie</span><span class="p">(</span><span class="n">cookie</span><span class="p">)</span> <span class="k">for</span> <span class="n">cookie</span> <span class="ow">in</span> <span class="n">flow</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'Cookie'</span><span class="p">]]</span>
<span class="k">if</span> <span class="n">cookie_substituted</span><span class="p">:</span>
<span class="n">log</span><span class="p">(</span><span class="s1">'Substituted </span><span class="si">{}</span><span class="s1"> in existing cookie </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">phpsessid_str</span><span class="p">,</span> <span class="n">cookie_substituted</span><span class="p">))</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">flow</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'Cookie'</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">phpsessid_str</span><span class="p">)</span>
<span class="n">log</span><span class="p">(</span><span class="s1">'Added cookie </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">phpsessid_str</span><span class="p">))</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">flow</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'Cookie'</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">phpsessid_str</span><span class="p">]</span>
<span class="n">log</span><span class="p">(</span><span class="s1">'Added unique cookie </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">phpsessid_str</span><span class="p">))</span>
<span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="s1">'srctoken'</span><span class="p">)</span> <span class="ow">and</span> <span class="s2">"application/x-www-form-urlencoded"</span> <span class="ow">in</span> <span class="n">flow</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"content-type"</span><span class="p">]:</span>
<span class="n">form</span> <span class="o">=</span> <span class="n">flow</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">get_form_urlencoded</span><span class="p">()</span>
<span class="n">log</span><span class="p">(</span><span class="s2">"Modifying srctoken: </span><span class="si">{}</span><span class="s2"> -> </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">form</span><span class="p">[</span><span class="s2">"srctoken"</span><span class="p">],</span> <span class="p">[</span><span class="n">context</span><span class="o">.</span><span class="n">srctoken</span><span class="p">]))</span>
<span class="n">form</span><span class="p">[</span><span class="s2">"srctoken"</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="n">context</span><span class="o">.</span><span class="n">srctoken</span><span class="p">]</span>
<span class="n">flow</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">set_form_urlencoded</span><span class="p">(</span><span class="n">form</span><span class="p">)</span>
</code></pre></div>
<p>And then use it to replay my full traffic flow :</p>
<div class="highlight"><pre><span></span><code>$ mitmproxy -s sticky_phpsession.py -c traffic.mitm
</code></pre></div>Python 3 non consistent set & dict iteration gotcha2014-12-08T19:12:00+01:002014-12-08T19:12:00+01:00Lucas Cimontag:chezsoi.org,2014-12-08:/lucas/blog/python-3-non-consistent-set-dict-iteration-gotcha.html<p>Consider the following Python expression:</p>
<div class="highlight"><pre><span></span><code><span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="s2">"ABCDE"</span><span class="p">)))</span>
</code></pre></div>
<p>What do you think it produces ?</p>
<p><mark>Not necessarily "ABCDE".</mark>
Right, but you would expect the result to be consistent, isn't it ?</p>
<div class="highlight"><pre><span></span><code>$ <span class="k">for</span> i <span class="k">in</span> <span class="o">{</span><span class="m">1</span>..3<span class="o">}</span><span class="p">;</span> <span class="k">do</span> python2.7 -c <span class="s1">'print("".join(set("ABCDE")))'</span><span class="p">;</span> <span class="k">done</span>
ACBED
ACBED
ACBED
</code></pre></div>
<p>Great !</p>
<p>...</p>
<p>But with …</p><p>Consider the following Python expression:</p>
<div class="highlight"><pre><span></span><code><span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">set</span><span class="p">(</span><span class="s2">"ABCDE"</span><span class="p">)))</span>
</code></pre></div>
<p>What do you think it produces ?</p>
<p><mark>Not necessarily "ABCDE".</mark>
Right, but you would expect the result to be consistent, isn't it ?</p>
<div class="highlight"><pre><span></span><code>$ <span class="k">for</span> i <span class="k">in</span> <span class="o">{</span><span class="m">1</span>..3<span class="o">}</span><span class="p">;</span> <span class="k">do</span> python2.7 -c <span class="s1">'print("".join(set("ABCDE")))'</span><span class="p">;</span> <span class="k">done</span>
ACBED
ACBED
ACBED
</code></pre></div>
<p>Great !</p>
<p>...</p>
<p>But with Python 3 :</p>
<div class="highlight"><pre><span></span><code>$ <span class="k">for</span> i <span class="k">in</span> <span class="o">{</span><span class="m">1</span>..3<span class="o">}</span><span class="p">;</span> <span class="k">do</span> python3.4 -c <span class="s1">'print("".join(set("ABCDE")))'</span><span class="p">;</span> <span class="k">done</span>
DECAB
CDBEA
DBACE
</code></pre></div>
<p>WHY ???</p>
<p>I found the answer in <a href="http://stackoverflow.com/a/14959001/636849">this StackOverflow answer</a> :</p>
<blockquote>This is the result of a security fix from 2012, which was enabled by default in Python 3.3. [...]
Hash randomization causes the iteration order of dicts and sets to be unpredictable and differ across Python runs.</blockquote>
<p><img src="images/wwcb/ACourtDArgument.gif" alt="[A court d'argument]" title="Fireflyyyyy !"></p>
<p><strong>EDIT [9/12/2014]</strong> by curiosity, I checked if the hashes are uniformly distributed :</p>
<div class="highlight"><pre><span></span><code>$ <span class="k">for</span> i <span class="k">in</span> <span class="o">{</span><span class="m">1</span>..10000<span class="o">}</span><span class="p">;</span> <span class="k">do</span> python3.4 -c <span class="s1">'print("".join(set("ABCDE")))'</span><span class="p">;</span> <span class="k">done</span> > python3_set_iteration_abcde_10000
$ csvstat -H python3_set_iteration_abcde_10000
Unique values: <span class="m">120</span>
<span class="m">5</span> most frequent values:
ABCDE: <span class="m">317</span>
EABCD: <span class="m">195</span>
BCDEA: <span class="m">192</span>
BACDE: <span class="m">192</span>
CDEAB: <span class="m">165</span>
</code></pre></div>
<p>Conclusion : all 120 possible ordering are generated, but the "ABCDE" ordering appears twice as often as any other.</p>
<p>And finally a minimalist terminal visualization of the distribution, using <a href="https://github.com/thorduri/ministat"><code>ministat</code></a> and a perl one-liner to convert the "ABCDE" strings into integers :</p>
<div class="highlight"><pre><span></span><code>$ perl -ne <span class="s1">'if(!exists($s{$_})) {$s{$_}=scalar keys %s} print "$s{$_}\n"'</span> python3_set_iteration_abcde_10000 <span class="p">|</span> ministat -H <span class="m">0</span>.05 -w <span class="m">60</span>
x <stdin>
+------------------------------------------------------------+
<span class="p">|</span> x <span class="p">|</span>
<span class="p">|</span> x <span class="p">|</span>
<span class="p">|</span> x <span class="p">|</span>
<span class="p">|</span> x <span class="p">|</span>
<span class="p">|</span> x <span class="p">|</span>
<span class="p">|</span> x <span class="p">|</span>
<span class="p">|</span> x x <span class="p">|</span>
<span class="p">|</span> x x <span class="p">|</span>
<span class="p">|</span> xx x x <span class="p">|</span>
<span class="p">|</span> xx x x x x x <span class="p">|</span>
<span class="p">|</span> xx x x x x x x <span class="p">|</span>
<span class="p">|</span> xx x x x xxx x xx <span class="p">|</span>
<span class="p">|</span>xxx x x x x x xxxxx x xx x xx xx <span class="p">|</span>
<span class="p">|</span>xxx xx x x x x xxxxxxxx x xxxx xx x xx x x x <span class="p">|</span>
<span class="p">|</span>xxxxxx x x x x xxxxxxxxxxxx x xxxx xxxx x xxx x x xx <span class="p">|</span>
<span class="p">|</span>xxxxxxxxxxx xxx xxxxxxxxxxxx x xxxxx xxxxxxxxxx x xxxx <span class="p">|</span>
<span class="p">|</span>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxx xxxxxxxxxxx xxxx xxxx <span class="p">|</span>
<span class="p">|</span>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<span class="p">|</span>
<span class="p">|</span>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<span class="p">|</span>
<span class="p">|</span>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<span class="p">|</span>
<span class="p">|</span>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<span class="p">|</span>
<span class="p">|</span> <span class="p">|</span>--------------M-A---------------<span class="p">|</span> <span class="p">|</span>
+------------------------------------------------------------+
N Min Max Median Avg Stddev
x <span class="m">10000</span> <span class="m">0</span> <span class="m">119</span> <span class="m">53</span> <span class="m">55</span>.4782 <span class="m">33</span>.728675
</code></pre></div>
<p>(the graph is slightly skewed here because of the 60 characters width, but it follows the "x2 more frequent" initial observation if you use a 120 characters width)</p>La tête dans le guidon2014-11-29T16:11:00+01:002014-11-29T16:11:00+01:00Lucas Cimontag:chezsoi.org,2014-11-29:/lucas/blog/la-tete-dans-le-guidon.html<p>En anglais, le titre pourrait se traduire par <em>"The head to the grindstone"</em>, soit littéralement "la tête sur la meule à aiguiser".
Rigolo non ?</p>
<p>Au vu du sujet cependant, <em>"Tales of a lost afternoon"</em> sonnerait mieux je trouve. En effet, cet article va traiter d'organisation personnelle du temps. Le sujet …</p><p>En anglais, le titre pourrait se traduire par <em>"The head to the grindstone"</em>, soit littéralement "la tête sur la meule à aiguiser".
Rigolo non ?</p>
<p>Au vu du sujet cependant, <em>"Tales of a lost afternoon"</em> sonnerait mieux je trouve. En effet, cet article va traiter d'organisation personnelle du temps. Le sujet est vaste, et il ne s'agira pas ici de parler de <a href="/lucas/blog/images/2014/Dec/Levels_of_Procrastination.jpg">procrastination</a> ou d'outils de gestion de projet (j'ai déjà écris à propos d'<a href="/lucas/blog/setting-up-etherpad-in-a-server-subdirectory-aka-apache-config-hell">Etherpad</a>). Il s'agit simplement de partager mon expérience personnelle, et de suggérer quelques astuces pour tout ceux qui ont déjà perdu un peu trop de temps à coder "la tête dans le guidon" comme moi.</p>
<p>Pas forcément passionnant à priori donc. Mais bon, quitte à perdre son temps, autant en profiter pour apprendre de ses erreurs !</p>
<h3>Un Makefile récalcitrant</h3>
<p>Vendredi après-midi, alors que je refactorisais le contenu du Makefile de mon projet actuel, je me suis mis en tête de créer une cible <code>help</code>, de manière à pouvoir détailler le rôle de chaque cible sur un simple <code>make help $targetname</code>.</p>
<p>Un <a href="http://fr.wikipedia.org/wiki/Makefile">Makefile</a> est un outil d'automatisation assez ancien, mais toujours très utile et souvent employé, et je pensais que le temps investi sur ce problème serait rentabilisé en réutilisant cette nouvelle "recette" dans mes projets futurs...</p>
<p><img src="images/2014/Dec/JonHamm_YeahSure.gif" title="Ouiii, c'est ça..." alt="JonHamm"/></p>
<p>Cette idée faisait suite à ma trouvaille pour afficher les messages de Makefile en couleur, utilisée dans <a href="https://chezsoi.org/lucas/blog/javascript-testing-adapting-testling-in-browser-tap-rendering.html">mon article précédent</a>.
Cela peut sembler un peu vain, mais je trouve qu'un peu de couleur améliore énormément la lisibilité en fenêtre console.</p>
<p>Pour me conformer au principe DRY (<a href="http://fr.wikipedia.org/wiki/Ne_vous_r%C3%A9p%C3%A9tez_pas">Don't Repeat Yourself</a>), je voulais réutiliser le même message pour <code>help</code> et pour la cible elle-même, pour éviter une duplication redondante :</p>
<div class="highlight"><pre><span></span><code><span class="nf">COMPILER</span> <span class="p">:</span><span class="err">=</span> <span class="no">bob</span>
<span class="nf">SRC_FILES</span> <span class="p">:</span><span class="err">=</span> <span class="no">roley.c</span> <span class="no">travis.c</span> <span class="no">lofty.c</span> <span class="no">scoop.c</span>
<span class="nf">OUT_FILE</span> <span class="p">:</span><span class="err">=</span> <span class="no">home_sweet_home</span>
<span class="nf">print</span> <span class="err">=</span> <span class="err">/</span><span class="no">bin</span><span class="err">/</span><span class="no">echo</span> <span class="p">-</span><span class="no">e</span> <span class="err">"\</span><span class="no">x1b</span><span class="p">[</span><span class="mi">36</span><span class="no">m</span><span class="err">\</span><span class="c1">#\# $(1)\x1b[0m"</span>
<span class="nl">.PHONY:</span> <span class="nf">build</span>
<span class="nl">build:</span> <span class="nf">$</span><span class="p">(</span><span class="no">OUT_FILE</span><span class="p">)</span>
<span class="err">@:</span>
<span class="nf">$</span><span class="p">(</span><span class="no">OUT_FILE</span><span class="p">):</span> <span class="no">$</span><span class="p">(</span><span class="no">SRC_FILES</span><span class="p">)</span>
<span class="err">@</span><span class="nf">$</span><span class="p">(</span><span class="no">call</span> <span class="no">print</span><span class="p">,</span><span class="err">"</span><span class="no">Bob</span> <span class="no">is</span> <span class="no">starting</span> <span class="no">building</span><span class="err">"</span><span class="p">)</span>
<span class="nf">$</span><span class="p">(</span><span class="no">COMPILER</span><span class="p">)</span> <span class="no">$</span><span class="p">(</span><span class="no">SRC_FILES</span><span class="p">)</span>
<span class="nl">help:</span>
<span class="err">@</span><span class="nf">$</span><span class="p">(</span><span class="no">call</span> <span class="no">print</span><span class="p">,</span><span class="err">"</span><span class="no">build</span><span class="p">:</span> <span class="no">Guess</span> <span class="no">what</span> <span class="err">?</span> <span class="no">Bob</span><span class="err">'</span><span class="no">s</span> <span class="no">gonna</span> <span class="no">build</span> <span class="p">!</span><span class="err">"</span><span class="p">)</span>
</code></pre></div>
<p>Ma première approche fut d'essayer de définir une variable d'environnement dans la cible <code>help</code> qui rendrait inactives les autres commandes, puis d'invoquer la cible passée en argument. Comme les Makefiles ne semblent pas posséder de telle option, j'ai songé à préfixer toutes les commandes à passer sous silence par une variable qui serait transformée en <code>#</code> par la cible <code>help</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nf">build</span><span class="o">:</span> <span class="k">$(</span><span class="nv">SRC_FILES</span><span class="k">)</span>
@<span class="k">$(</span>call print,<span class="s2">"Bob is starting building"</span><span class="k">)</span>
<span class="k">$(</span>_<span class="k">)$(</span>COMPILER<span class="k">)</span> <span class="k">$(</span>SRC_FILES<span class="k">)</span>
<span class="nf">help</span><span class="o">:</span> <span class="n">_</span>=\<span class="c">#</span>
<span class="nf">help</span><span class="o">:</span> <span class="k">$(</span><span class="nv">wordlist</span> <span class="nv">2</span>,<span class="nv">3</span>,<span class="k">$(</span><span class="nv">MAKECMDGOALS</span><span class="k">))</span>
@:
</code></pre></div>
<p>Comme ça me semblait excessivement complexe et un peu fastidieux de préfixer toutes les commandes par <code>$(_)</code>, j'ai envisagé une autre solution.</p>
<p>J'ai réalisé qu'un simple <code>grep -A1 $targetname Makefile</code> était suffisant pour récupérer le message associé à chaque cible :</p>
<div class="highlight"><pre><span></span><code><span class="nf">help</span><span class="o">:</span>
@grep -A1 <span class="k">$(</span>wordlist <span class="m">2</span>,3,<span class="k">$(</span>MAKECMDGOALS<span class="k">))</span> Makefile <span class="p">|</span> tail -n <span class="m">1</span>
</code></pre></div>
<p>Les inconvénients ? Pas de couleurs, il fallait encore supprimer le hideux préfixe <code>@$(call print</code> de la ligne extraite, et cela forçait à rajouter des messages aux cibles "façades" comme <code>build</code>.</p>
<p>Dernière alternative: invoquer récursivement <code>$(MAKE) --dry-run</code>, ce qui résulte en une sortie inutilement verbeuse et sans couleurs.</p>
<p>Quelle que soit la solution adoptée, il restait un gros problème: comment désactiver l'interprétation des arguments en ligne commande par <code>make</code>, considérés comme des cibles supplémentaire à exécuter ?</p>
<p>C'est après avoir implémenté <a href="http://stackoverflow.com/a/14061796/636849">ce magnifique hack trouvé sur StackOverflow</a>, que j'ai enfin réalisé que je perdais complètement mon temps.</p>
<h3>5 raisonnements qui font perdre du temps</h3>
<p>Je vais essayer de disséquer ce qui me passe par la tête lorsque je perds plusieurs heures de mon temps ainsi:</p>
<ol>
<li>
<p><strong>"maintenant ou jamais"</strong> : je refuse de reporter la tâche à plus tard, en me disant que si je ne le fais pas MAINTENANT, je ne le ferai jamais.</p>
</li>
<li>
<p><strong>"ça ne prendra que 10min"</strong> : j'ai tendance à sous-estimer l'étendue des ramifications du problème, et le temps qu'il va me falloir pour arriver à une solution acceptable.</p>
</li>
<li>
<p><strong>"autant commencer par ça puisque je suis motivé"</strong>, plutôt que les tâches bien plus urgentes que je rechigne à faire. Et puis je prends ce bout de code qui refuse de fonctionner comme un vrai défi personnel...</p>
</li>
<li>
<p><strong>"c'est toujours utile à savoir faire"</strong> : ça me resservira sûrement dans le futur de savoir ça. Il faut toujours continuer à apprendre non ?</p>
</li>
<li>
<p>quitte à la faire proprement, <strong>"autant faire une solution générique"</strong>.
<img src="images/2014/Dec/xkcd_the_general_problem.png" title="xkcd.com/974" alt="xkcd.com/974"/></p>
</li>
</ol>
<h3>Quelques astuces pour éviter ces écueils</h3>
<p>Ces cinq points sont des exemples parfaits de biais cognitifs. Je ne suis pas le premier développeur à penser qu'<a href="http://www.kitchensoap.com/2012/10/25/on-being-a-senior-engineer/">il est important de savoir les reconnaître</a> :</p>
<ol>
<li>
<p><strong>"maintenant ou jamais"</strong> : il y a deux aspects ici:</p>
<ul>
<li>on surévalue l'importance d'une tâche mineure : est-ce si grave de ne pas la réaliser ? Lorsqu'on a la tête dans le guidon, on met de côté l'importance d'accomplir l'objectif global : quel que soit son urgence et la satisfaction personnelle bien plus grande qu'on en retirera, on ne voit qu'à court terme le défi technique qui nous fait face.
Pour s'en prévenir, une règle d'or: <strong>YAGNI</strong>, <a href="//fr.wikipedia.org/wiki/YAGNI">You Ain't Gonna Need It</a>. Si cette tâche n'est <strong>pas nécessaire</strong> à la réalisation de l'objectif, <strong>ne perdez pas votre temps</strong> avec !</li>
<li>on a peur d'oublier, de ne pas avoir le temps plus tard. Pour éviter ça, rien de plus simple : <strong>notez-la dans une liste de tâches annexes</strong>. Plus tard, avec le recul nécessaire, vous pourrez relire cette liste et reprioriser les plus urgentes. C'est aussi un très bon moyen de l'exorciser: la savoir notée quelque part me donne la tranquilité d'esprit nécessaire pour la chasser de mes pensées. Salvatore Sanfilippo, l'auteur de <a href="http://redis.io">Redis</a>, a écrit un très intéressant article sur ce sujet : <a href="http://antirez.com/news/51">"la programmation par report de tâche"</a>.</li>
</ul>
</li>
<li>
<p><strong>"ça ne prendra que 10min"</strong> :</p>
<ul>
<li>ce biais cognitif là est dénommé <em>"<a href="//en.wikipedia.org/wiki/Planning_fallacy">Planning fallacy</a>"</em> chez nos confrères anglophones. Estimer la durée d'une tâche, c'est difficile. Et de manière générale, on a tendance à sous-estimer.</li>
<li>si on ne fait pas attention au temps qui passe, 10min deviennent facilement 60.</li>
<li>ce schéma peut se répéter à la prochaine difficulté rencontrée, et entraîner une succession de "tâches digressives" façon boule de neige.
<img src="images/2014/Dec/TeteDansLeGuidon_spiral.png" title="Réalisé avec zwibbler.com" alt="Spiral of side-tasks digression"/>
Pour éviter cet écueil, il faut se forcer à ne pas partir dans la moindre "tâche digressive" : souvenez vous, utilisez un journal de tâches annexes, et YAGNI !
Et pour éviter les sous-estimations, une seule solution: devenir meilleur à estimer les durées de tâches. Le livre <em>"<a href="http://blog.codinghorror.com/a-pragmatic-quick-reference/">The Pragmatic Programmer</a>"</em> recommande de garder un suivi de ses estimations, pour apprendre de ses erreurs.
Une solution didactique pour s'y entraîner : la <a href="//fr.wikipedia.org/wiki/Technique_Pomodoro">technique <em>Pomodoro</em></a>.</li>
</ul>
</li>
<li>
<p><strong>"autant commencer par ça puisque je suis motivé"</strong> : là, je prétends qu'il s'agit d'une tâche de grande importante, voir même "bloquante", alors qu'en vérité je trouve juste ça bien plus passionnant que d'autres choses que j'ai à faire. De plus, et je me réfère là à l'excellent <em>"Reality is Broken"</em> de Jane McGonigal, une telle "tâche digressive" se compose souvent d'une succession de petits défis techniques, stimulants intellectuellement et générateur d'adrénaline, et formant au final une spirale addictive. Chaque solution trouvée entrouvre la porte d'un autre problème annexe, que l'on s'empresse d'attaquer par curiosité, goût du défi et envie de peaufiner notre solution.
Au fond, il s'agit essentiellement d'un problème de <strong>motivation</strong> et de <strong>discipline</strong>. Pour garder l'envie de programmer, il faut se garder du temps pour écrire du code plus <em>fun</em>; mais il faut aussi être conscient qu'atteindre ses objectifs requiert de la discipline personnelle, et de parfois faire des choses moins intéressantes : "d'abord le boulot, après les loisirs" comme disait mon père !</p>
</li>
<li>
<p><strong>"c'est toujours utile à apprendre"</strong> : là, c'est illusion de l'apprentissage comme une fin et pas comme un moyen. Comprenez-moi bien : je suis un partisant 100% convaincu de l'apprentissage continu tout au long de la vie; mais apprendre peut devenir une addiction, et il est trompeur de penser que toutes les sources de connaissance sont aussi enrichissantes et utiles les unes que les autres.
En pratique : deux douzaines de réponses détaillées sur <em>StackOverflow</em> ont bien moins de valeur que de suivre un cours entier de <em>Coursera</em> ou <em>Codeademy</em> sur un sujet nouveau.
Il n'est pas utile de connaître tous les <em>hacks</em> du monde, parfois mieux vaut réfléchir par soi-même à une solution plus simple.
<img src="images/2014/Dec/A_mindless_worker_is_a_happy_worker.jpg" alt="Futurama: shut up and do your job !"></p>
</li>
<li>
<p><strong>"autant faire une solution générique"</strong> : là, c'est l'<em><a href="//en.wikipedia.org/wiki/Overengineering">Over-engineering</a></em> qui guète.
La solution la plus simple et rapide est parfois la meilleure, pas besoin de tomber dans l'écueil de la solution inutilement générique.
Autrement dit : KISS, <a href="//fr.wikipedia.org/wiki/Principe_KISS">Keep It Simple, Stupid</a> !
Et puis YAGNI aussi !
Au passage, ce <em>webcomic</em> XKCD a d'ailleurs une "suite" intéressante: <a href="http://xkcd.com/1205">xkcd.com/1205</a>.</p>
</li>
</ol>
<p>En conclusion, il vaux mieux s'arrêter quelques minutes pour prendre du recul, s'extraire de la frénésie du <a href="//en.wikipedia.org/wiki/Flow_%28psychology%29"><em>flow</em></a> et reconsidérer l'importance de chaque tâche.
<strong>Dans le doute, prenez une pause !</strong></p>
<p>Il faut être préparé à ressentir un peu de frustration à abandonner une tâche passionnante, mais je vous mets au défi d'essayer, vous ne regretterez pas !</p>
<h3><a href="http://fr.wikipedia.org/wiki/Liste_de_termes_d%27argot_Internet#T.2C_U.2C_V">Tl;dr</a></h3>
<p>Mon humble avis:</p>
<ul>
<li>rester concentré requiert <a href="http://www.edudemic.com/age-distraction/">un minimum d'organisation</a></li>
<li>KISS & YAGNI + noter les idées d'amélioration non-urgentes dans un fichier</li>
<li>se forcer à prendre une pause de 10 minutes toutes les 2 heures, ou alors de changer de tâche</li>
</ul>
<h3>Epilogue</h3>
<p>Quant au Makefile, j'ai opté pour le strict minimum :</p>
<div class="highlight"><pre><span></span><code><span class="n">build</span><span class="p">:</span> <span class="o">$</span><span class="p">(</span><span class="n">OUT_FILE</span><span class="p">)</span>
<span class="err">@</span><span class="p">:</span>
<span class="o">$</span><span class="p">(</span><span class="n">OUT_FILE</span><span class="p">):</span> <span class="o">$</span><span class="p">(</span><span class="n">SRC_FILES</span><span class="p">)</span>
<span class="c1"># Bob is starting building</span>
<span class="o">$</span><span class="p">(</span><span class="n">COMPILER</span><span class="p">)</span> <span class="o">$</span><span class="p">(</span><span class="n">SRC_FILES</span><span class="p">)</span>
<span class="n">help</span><span class="p">:</span>
<span class="c1"># make -n target # --dry-run : get targets description</span>
<span class="c1"># make -B target # --always-make : force execution of targets commands, even if dependencies are satisfied</span>
<span class="c1"># make DEBUG=0 # variable override</span>
<span class="c1"># make --debug[=abijmv] # enable variants of make verbose output</span>
</code></pre></div>
<p>Pas de <code>make help $targetname</code>: juste de simples commentaires affichés à l'exécution et sur un <code>make --dry-run $targetname</code>.</p>
<p>Au revoir également l'appel au <code>print</code> coloré : la coloration se fera désormais côté console par <a href="http://michaelheap.com/grc/"><code>grc</code></a>, configuré dans <em>/usr/share/grc/conf.gcc</em> pour mettre en couleur les commentaires :</p>
<div class="highlight"><pre><span></span><code><span class="p">.........</span>
<span class="p">#</span> <span class="n">comments</span>
<span class="n">regexp</span><span class="o">=^</span><span class="p">#.</span><span class="o">*</span>
<span class="n">colours</span><span class="o">=</span><span class="n">green</span>
<span class="n">count</span><span class="o">=</span><span class="n">stop</span>
</code></pre></div>
<p><br></p>
<hr>
<p><br></p>
<h3>Digression n°1 : une commande <code>pomodoro</code> ?</h3>
<p>Suite à la découverte de la technique <em>Pomodoro</em> et de la lecture de <em>"Reality is Broken"</em> de Jane McGonigal, les doigts me brûlent de créer un utilitaire en ligne de commande <code>pomodoro-karma</code> avec les caractéristiques suivantes :</p>
<ul>
<li>tâche configurée au départ avec un objectif + un délai (par exemple 1h)</li>
<li>au bout du délai impartit, l'application s'affiche au premier plan en plein écran (bloque même le bureau ?) pour vérifier si la tâche a été réalisée</li>
<li>inspiré par les idées de <em>ludification</em> de Jane McGonigal : score de Karma: +1 si l'utilisateur fait bien une pause de 5min; +1 s'il a accompli sa tâche; +0 sinon</li>
<li>on peut indiquer à l'application que la tâche a été effectuée plus tôt</li>
<li>l'application fournira également des statistiques sur l'exactitude et l'évolution des estimations de l'utilisateur</li>
</ul>
<p>Techniquement:</p>
<ul>
<li>Python 3</li>
<li>persitence des données basique : <a href="https://docs.python.org/3/library/persistence.html">https://docs.python.org/3/library/persistence.html</a></li>
<li>rendu à travers le navigateur ? Pros: portable, <em>user-friendly</em>. Cons: plus complexe à coder que d'autres solutions basées sur des bibliothèques existantes d'interaction-utilisateur ?</li>
</ul>
<p>Maaaais... il existe déjà des centaines de projets similaires, donc je ne le coderai pas :)
<a href="https://github.com/search?q=pomodoro">https://github.com/search?q=pomodoro</a> : 1273 résultats, 148 en Python.
Je vais juste essayer <a href="http://askubuntu.com/a/190675/185582">ce package sous Ubuntu</a>.</p>
<p><br></p>
<hr>
<p><br></p>
<h3>Digression n°2 : qu'est-ce qui a sa place sur un blog ?</h3>
<p>Je suis loin d'être le premier à écrire ce type de <em>mea culpa</em>. Des tas de développeurs s'y sont essayé, y compris des "personnalités" du domaine. Niveau originalité donc, on repassera. Pourquoi alors, pondre un énième pamphlet dégoulinant de recommandations déjà entendues mille fois ?</p>
<ul>
<li>est-ce que ça excuse les heures de boulot perdues ?</li>
<li>est-ce un sujet intéressant, utile à mes lecteurs ? Selon quels critères ?</li>
<li>quelle image ça donne de moi ?</li>
</ul>
<p>Je n'ai pas de réponse à ces questions, mais voilà quelques unes de mes raisons :</p>
<ul>
<li>c'est un excellent exercice de communication & de didactique</li>
<li>de manière générale, coucher mes idées par écrit m'aide à clarifier mes idées, et me force à justifier certaines hypothèses que je tenais pour acquises</li>
<li>je ne veux pas que ce blog soit exclusivement technique. La programmation a de nombreux aspects humains, et devenir un meilleur développeur signifie aussi se pencher sur les aspects de communication, de travail d'équipe et d'organisation.</li>
<li>en écrivant à propos de ces erreurs, je m'engage en quelque sorte à ne pas les reproduire</li>
</ul>
<p><strong>[EDIT 2018/03/6]</strong> Un concept a rapprocher de ces observations: le <em>yak shaving</em></p>Mimicking testling HTML rendering of TAP javascript tests2014-11-26T18:11:00+01:002014-11-26T18:11:00+01:00Lucas Cimontag:chezsoi.org,2014-11-26:/lucas/blog/javascript-testing-adapting-testling-in-browser-tap-rendering.html<p>This post is only relevant to you if you use <code>browserify</code>. For a good introduction to this powerful Javascript bundling tool, check <a href="//github.com/substack/browserify-handbook">this doc</a>.</p>
<p>First of all, I'd like to take my hat off to <a href="http://substack.net/code">James Hallyday</a> and <a href="http://www.catonmat.net/blog/top-10-browserling-inventions/">Peteris Krumins</a> who have built such amazing tools as <a href="http://browserify.org"><code>browserify</code></a>, <a href="//www.testling.com"><code>testling</code></a> and …</p><p>This post is only relevant to you if you use <code>browserify</code>. For a good introduction to this powerful Javascript bundling tool, check <a href="//github.com/substack/browserify-handbook">this doc</a>.</p>
<p>First of all, I'd like to take my hat off to <a href="http://substack.net/code">James Hallyday</a> and <a href="http://www.catonmat.net/blog/top-10-browserling-inventions/">Peteris Krumins</a> who have built such amazing tools as <a href="http://browserify.org"><code>browserify</code></a>, <a href="//www.testling.com"><code>testling</code></a> and <a href="//www.browserling.com/"><code>browserling</code></a>.</p>
<p>Now, the problem : I want an HTML page that execute and display the results of my Javascript unit tests.</p>
<p>Actually, when using <a href="//en.wikipedia.org/wiki/Test_Anything_Protocol">TAP</a> tests, <code>browserify</code> has a handy companion : <code>testling</code>. With him, we can get such HTML output :</p>
<div class="highlight"><pre><span></span><code>browserify my_test.js | testling --html > tests.html
</code></pre></div>
<p>However, the issue is that <code>testling</code> doesn't provide any option to include custom HTML content in its output. Alas, the Javascript code I was testing in my case relied on some HTML tags to exist.</p>
<p>To get around this limitation, and automate the process, I crafted the following <em>Makefile</em> that mimics the logic of <a href="//github.com/substack/testling/blob/master/bin/cmd.js#L307">the original <code>testling</code> source code</a> :</p>
<div class="highlight"><pre><span></span><code>HTML_INDEX := index.html
OUT_BUNDLE := bundle.js
TESTS_HTML := tests.html
TESTS_BUNDLE:= tests-bundle.js
PRELUDE := browserified_testling_prelude.js
TESTS_DIR := tests/
TESTS_FILES := $(wildcard $(TESTS_DIR)*.js)
print = /bin/echo -e "\x1b[36m\#\# $(1)\x1b[0m"
.PHONY: tests view-tests
tests: $(TESTS_HTML)
$(TESTS_HTML): $(HTML_INDEX) $(TESTS_BUNDLE) $(PRELUDE)
@$(call print,'Building the HTML file for rendering tests in a browser')
perl -p -e "s/$(OUT_BUNDLE)/$(TESTS_BUNDLE)/ <span class="err">&&</span> print '\
<span class="nt"><pre</span> <span class="na">id=</span><span class="s">\"__testling_output\"</span><span class="nt">></span>\
<span class="nt"></pre></span>\
<span class="nt"><script</span> <span class="na">type=</span><span class="s">\"text/javascript\"</span> <span class="na">src=</span><span class="s">\"$(PRELUDE)\"defer</span><span class="nt">></span>\
<span class="nt"></script></span>\
'" $(HTML_INDEX) > $(TESTS_HTML)
$(TESTS_BUNDLE): $(TESTS_FILES)
@$(call print,'Generating JS tests bundle')
browserify $(TESTS_FILES) > $(TESTS_BUNDLE)
$(PRELUDE):
@$(call print,'Retrieving <span class="err">&</span> browserifying testling prelude')
wget https://raw.githubusercontent.com/substack/testling/master/browser/prelude.js
browserify prelude.js > $(PRELUDE)
rm prelude.js
view-tests: $(TESTS_HTML)
@$(call print,'Creating a local server <span class="err">&</span> opening the tests HTML page in a local browser')
python -m webbrowser http://localhost:8080/$(TESTS_HTML)
python -m SimpleHTTPServer 8080
</code></pre></div>
<p>Basically, here is what happens when running <code>make tests</code> :</p>
<ol>
<li>the unit tests files are bundled by <code>browserify</code> into <em>tests-bundle.js</em>.</li>
<li>the script required to display the tests is generated by browserifying <code>testling</code> <em>prelude.js</em> source file.</li>
<li>an ugly sed-like perl one-liner generates the tests viewer HTML file by copying our base HTML <em>index.html</em> and applying one substitution : it replaces the line originally including our <em>bundle.js</em> by 3 others :<ul>
<li>one including the <em>tests-bundle.js</em> script</li>
<li>one including the <em>browserified_testling_prelude.js</em> script</li>
<li>one defining a <code><pre></code> block where the tests output will be displayed</li>
</ul>
</li>
</ol>
<p>Et voilà !</p>Genealogy tree visualization with d3.js2014-11-18T14:11:00+01:002014-11-18T14:11:00+01:00Lucas Cimontag:chezsoi.org,2014-11-18:/lucas/blog/genealogy-tree-visualization-with-d3-js.html<p>I'm happy to introduce you with <a href="https://github.com/Lucas-C/genealogic-d3">genealogic-d3</a>, a Javascript visualization library to nicely display genealogy trees that I've been working on during the past 3 days.</p>
<p>I'm quite satisfied by the result. You'll find a live demo you can play with at <a href="https://chezsoi.org/lucas/genealogic-d3/skywalker.html">https://chezsoi.org/lucas/genealogic-d3/skywalker.html</a></p>
<p>I …</p><p>I'm happy to introduce you with <a href="https://github.com/Lucas-C/genealogic-d3">genealogic-d3</a>, a Javascript visualization library to nicely display genealogy trees that I've been working on during the past 3 days.</p>
<p>I'm quite satisfied by the result. You'll find a live demo you can play with at <a href="https://chezsoi.org/lucas/genealogic-d3/skywalker.html">https://chezsoi.org/lucas/genealogic-d3/skywalker.html</a></p>
<p>I used the Skywalker family as an example, but it is really very easy to adapt to <a href="http://mentalfloss.com/article/27833/11-fictional-family-trees">any other family</a>.</p>
<p>On an <!-- NOT SO --> unrelated note : go watch <a href="https://www.youtube.com/watch?v=jcQacCfi_pw">Predestination</a>, this time-travelling movie is awesome ! <!-- (SPOILER) but no way genealogic-d3 could display its family tree :) --></p>Fun with Javascript string obfuscation2014-11-14T01:11:00+01:002014-11-14T01:11:00+01:00Lucas Cimontag:chezsoi.org,2014-11-14:/lucas/blog/fun-with-javascript-string-obfuscation.html<p>Inspired by <a href="//twitter.com/lagergren/statuses/337484475204255744">this tweeter post by Marcus Lagergren</a> and the <a href="http://minddotout.wordpress.com/2013/04/28/js1k-tips-writing-tiny-javascript/">JS1K competition</a>, here is a valid Javascript code snippet for you obfuscated code lovers:</p>
<div class="highlight"><pre><span></span><code><span class="c">ー=!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ߺ=ー</span><span class="nb">+</span><span class="c">ー</span><span class="nt">,</span><span class="c">ǀ=ߺ</span><span class="nb">+</span><span class="c">ߺ</span><span class="nt">,</span><span class="c">ꓹ=!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ǃ=!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ᚐ=ꓹ</span><span class="k">[</span><span class="nb">+</span><span class="k">[]]</span><span class="nt">,</span><span class="c">ꓹ=ᚐ</span><span class="nb">+</span><span class="c">ꓹ</span><span class="k">[</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="c">(!</span><span class="k">[]</span><span class="nb">+</span><span class="k">[]</span><span class="c">)</span><span class="k">[</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="c">ᚐ</span><span class="nt">,</span><span class="c">ᚐ=/</span><span class="nt">,</span><span class="c">/</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ꓹ=ǀ</span><span class="nb">+</span><span class="c">ߺ</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ꓹ=ᚐ</span><span class="k">[</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="c">ᚐ …</span></code></pre></div><p>Inspired by <a href="//twitter.com/lagergren/statuses/337484475204255744">this tweeter post by Marcus Lagergren</a> and the <a href="http://minddotout.wordpress.com/2013/04/28/js1k-tips-writing-tiny-javascript/">JS1K competition</a>, here is a valid Javascript code snippet for you obfuscated code lovers:</p>
<div class="highlight"><pre><span></span><code><span class="c">ー=!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ߺ=ー</span><span class="nb">+</span><span class="c">ー</span><span class="nt">,</span><span class="c">ǀ=ߺ</span><span class="nb">+</span><span class="c">ߺ</span><span class="nt">,</span><span class="c">ꓹ=!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ǃ=!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ᚐ=ꓹ</span><span class="k">[</span><span class="nb">+</span><span class="k">[]]</span><span class="nt">,</span><span class="c">ꓹ=ᚐ</span><span class="nb">+</span><span class="c">ꓹ</span><span class="k">[</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="c">(!</span><span class="k">[]</span><span class="nb">+</span><span class="k">[]</span><span class="c">)</span><span class="k">[</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="c">ᚐ</span><span class="nt">,</span><span class="c">ᚐ=/</span><span class="nt">,</span><span class="c">/</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ꓹ=ǀ</span><span class="nb">+</span><span class="c">ߺ</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ꓹ=ᚐ</span><span class="k">[</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="c">ᚐ</span><span class="k">[</span><span class="c">ー</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]]</span><span class="nt">,</span><span class="c">ǃ=</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ᚐ=</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ǃ=ꓹ</span><span class="k">[</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="c">ꓹ</span><span class="k">[</span><span class="c">ᚐ</span><span class="k">]</span><span class="nb">+</span><span class="c">(</span><span class="k">[][[]]</span><span class="nb">+</span><span class="k">[]</span><span class="c">)</span><span class="k">[</span><span class="c">ᚐ</span><span class="k">]</span><span class="nb">+</span><span class="c">(!</span><span class="k">[]</span><span class="nb">+</span><span class="k">[]</span><span class="c">)</span><span class="k">[</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ᚐ</span><span class="k">]</span><span class="nb">+</span><span class="c">(!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="k">[]</span><span class="c">)</span><span class="k">[</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="c">(!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="k">[]</span><span class="c">)</span><span class="k">[</span><span class="c">ᚐ</span><span class="k">]</span><span class="nb">+</span><span class="c">(</span><span class="k">[][[]]</span><span class="nb">+</span><span class="k">[]</span><span class="c">)</span><span class="k">[</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="c">ꓹ</span><span class="k">[</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="c">(!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="k">[]</span><span class="c">)</span><span class="k">[</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="c">ꓹ</span><span class="k">[</span><span class="c">ᚐ</span><span class="k">]</span><span class="nb">+</span><span class="c">(!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="k">[]</span><span class="c">)</span><span class="k">[</span><span class="c">ᚐ</span><span class="k">]</span><span class="nt">,</span><span class="c">ᚐ=ǃ</span><span class="k">[</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ǃ=!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nt">,</span><span class="c">ꓹ=(!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="k">[]</span><span class="c">)</span><span class="k">[</span><span class="nb">+</span><span class="k">[]]</span><span class="nb">+</span><span class="c">ꓹ</span><span class="k">[</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]]</span><span class="nb">+</span><span class="c">ᚐ</span><span class="k">[</span><span class="c">ߺ</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]]</span><span class="nb">+</span><span class="c">ᚐ</span><span class="k">[</span><span class="c">ߺ</span><span class="nb">+</span><span class="c">ǃ</span><span class="k">]</span><span class="nb">+</span><span class="c">ᚐ</span><span class="k">[</span><span class="c">ߺ</span><span class="nb">+</span><span class="c">ǃ</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]]</span><span class="nb">+</span><span class="c">ᚐ</span><span class="k">[</span><span class="c">ߺ</span><span class="nb">+</span><span class="c">ー</span><span class="k">]</span><span class="nb">+</span><span class="c">ᚐ</span><span class="k">[</span><span class="c">ߺ</span><span class="nb">+</span><span class="c">ー</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]]</span><span class="nb">+</span><span class="c">ᚐ</span><span class="k">[</span><span class="c">ߺ</span><span class="nb">+</span><span class="c">ー</span><span class="nb">+</span><span class="c">ǃ</span><span class="k">]</span><span class="nt">,</span><span class="c">ǃ=ǀ</span><span class="nb">+</span><span class="c">ǀ</span><span class="nb">+</span><span class="c">ー</span><span class="nt">,</span><span class="c">(</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ー</span><span class="nb">+</span><span class="c">ߺ)</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="c">(ǃ)</span><span class="nb">+</span><span class="c">(</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ー</span><span class="nb">+</span><span class="c">ǀ)</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="c">(ǃ)</span><span class="nb">+</span><span class="c">(!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ߺ)</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="c">(ǃ)</span><span class="nb">+</span><span class="c">(</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ߺ)</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="c">(ǃ)</span><span class="nb">+</span><span class="c">(</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ー</span><span class="nb">+</span><span class="c">ߺ</span><span class="nb">+</span><span class="c">ǀ)</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="c">(ǃ)</span><span class="nb">+</span><span class="c">(</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ー</span><span class="nb">+</span><span class="c">ߺ)</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="c">(ǃ)</span><span class="nb">+</span><span class="c">(!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ǀ)</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="c">(ǃ)</span><span class="nb">+</span><span class="c">(</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ー</span><span class="nb">+</span><span class="c">ߺ)</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="c">(ǃ)</span><span class="nb">+</span><span class="c">(!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ǀ</span><span class="nb">+</span><span class="c">ǀ)</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="c">(ǃ)</span><span class="nb">+</span><span class="c">ᚐ</span><span class="k">[</span><span class="c">ߺ</span><span class="k">]</span><span class="nb">+</span><span class="c">(</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ー</span><span class="nb">+</span><span class="c">ǀ)</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="c">(ǃ)</span><span class="nb">+</span><span class="c">(</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ߺ</span><span class="nb">+</span><span class="c">ǀ)</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="c">(ǃ)</span><span class="nb">+</span><span class="c">(</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">!</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ǀ)</span><span class="k">[</span><span class="c">ꓹ</span><span class="k">]</span><span class="c">(ǃ)</span><span class="nb">+</span><span class="c">ᚐ</span><span class="k">[</span><span class="c">ߺ</span><span class="k">]</span><span class="nb">+</span><span class="c">(</span><span class="nb">+</span><span class="k">[]</span><span class="nb">+</span><span class="c">ߺ)</span><span class="nb">+</span><span class="c">ᚐ</span><span class="k">[</span><span class="c">ǀ</span><span class="k">]</span><span class="c"></span>
</code></pre></div>
<p>Just <a href="http://repl.it/9yC">click here</a> to check on <a href="http://repl.it">http://repl.it</a> what this snippet evaluates to.</p>
<p>This snippet has been generated by an home-made algorithm, whose goal is to produce Javascript code <strong>containing ZERO character</strong>, that outputs a string when evaluated. Maybe not very useful, but it's a kind of <a href="//en.wikipedia.org/wiki/Constrained_writing">constrained code writing</a> I guess :)
I won't reveal all the tricks I used here, maybe in a later post. There is another one for you:</p>
<div class="highlight"><pre><span></span><code>ー=!+[]+!+[]+!+[]+!+[],ߺ=ー+ー,ǀ=ߺ+ߺ,ꓹ=!+[]+[],ǃ=!+[]+!+[]+!+[],ᚐ=ꓹ[+[]],ꓹ=ᚐ+ꓹ[ǃ]+(![]+[])[ǃ]+ᚐ,ᚐ=/,/[ꓹ]+[],ꓹ=ǀ+ߺ+!+[]+!+[],ꓹ=ᚐ[ǃ]+ᚐ[ー+!+[]+!+[]],ǃ=+[],ᚐ=+!+[],ǃ=ꓹ[ǃ]+ꓹ[ᚐ]+([][[]]+[])[ᚐ]+(![]+[])[!+[]+!+[]+ᚐ]+(!+[]+[])[ǃ]+(!+[]+[])[ᚐ]+([][[]]+[])[ǃ]+ꓹ[ǃ]+(!+[]+[])[ǃ]+ꓹ[ᚐ]+(!+[]+[])[ᚐ],ᚐ=ǃ[ǃ]+[],ǃ=!+[]+!+[],ꓹ=(!+[]+[])[+[]]+ꓹ[+!+[]]+ᚐ[ߺ+!+[]]+ᚐ[ߺ+ǃ]+ᚐ[ߺ+ǃ+!+[]]+ᚐ[ߺ+ー]+ᚐ[ߺ+ー+!+[]]+ᚐ[ߺ+ー+ǃ],ǃ=ǀ+ǀ+ー,(!+[]+ǀ)[ꓹ](ǃ)+(!+[]+!+[]+ー+ߺ)[ꓹ](ǃ)+(!+[]+ー+ǀ)[ꓹ](ǃ)+(!+[]+ー+ǀ)[ꓹ](ǃ)+(ߺ+ǀ)[ꓹ](ǃ)+ᚐ[ߺ]+(ǀ+ǀ)[ꓹ](ǃ)+(ߺ+ǀ)[ꓹ](ǃ)+(!+[]+!+[]+!+[]+ߺ+ǀ)[ꓹ](ǃ)+(!+[]+ー+ǀ)[ꓹ](ǃ)+(!+[]+ー+ߺ)[ꓹ](ǃ)
</code></pre></div>
<p>Again, <a href="http://repl.it/9yC/1">click here</a> to check its execution on <a href="http://repl.it">http://repl.it</a></p>
<p><br><hr><br></p>
<p><strong>EDIT [26/11/2014]</strong> : Actually, I found out this is not breaking ground at all, there are actually plenty of similar crazy "NoAlnum" hacks & contests : <a href="http://security.stackexchange.com/a/8265">http://security.stackexchange.com/a/8265</a>. Thanks to <a href="https://fr.linkedin.com/pub/thibault-toledano/27/468/4b8">Thibault</a> for finding out !</p>
<p>Now, the commented source code of my own generic ASCII-to-NoAlnum translator can be found on <a href="https://github.com/Lucas-C/linux_configuration/blob/master/languages/web-d3/nochar_obfuscate.js">GitHub</a>.</p>
<p><strong>EDIT[19/12/2014]</strong> : there is an equivalent one for Python : <a href="http://benkurtovic.com/2014/06/01/obfuscating-hello-world.html">http://benkurtovic.com/2014/06/01/obfuscating-hello-world.html</a></p>
<p><strong>EDIT[3/03/2016]</strong> : funnily, the recent vulnerability <a href="https://blog.checkpoint.com/2016/02/02/ebay-platform-exposed-to-severe-vulnerability/">Ebay has been exposed to</a> relies on the very same trick, except that with <a href="http://www.jsfuck.com">JSFuck</a> or <a href="http://patriciopalladino.com/blog/2012/08/09/non-alphanumeric-javascript.html?utm_content=buffer489d0&utm_source=buffer&utm_medium=twitter&utm_campaign=Buffer">hieroglyphy</a> you only need 6 non-alphanumeric characters !</p>
<p><strong>EDIT[27/11/2017]</strong> : more creative coding : http://aem1k.com</p>gdb Python macros2014-11-07T11:11:00+01:002014-11-07T11:11:00+01:00Lucas Cimontag:chezsoi.org,2014-11-07:/lucas/blog/gdb-python-macros.html<p>This post aims to introduce a very useful tool to debug low-level issues in Python, how to enhance it and finally how to solve two annoying common problems.</p>
<h3>1. Debugging Python with gdb</h3>
<p>All the basics are there : <a href="https://wiki.python.org/moin/DebuggingWithGdb">https://wiki.python.org/moin/DebuggingWithGdb</a></p>
<p>Tl;dr :</p>
<div class="highlight"><pre><span></span><code>gdb -p $(pgrep -f …</code></pre></div><p>This post aims to introduce a very useful tool to debug low-level issues in Python, how to enhance it and finally how to solve two annoying common problems.</p>
<h3>1. Debugging Python with gdb</h3>
<p>All the basics are there : <a href="https://wiki.python.org/moin/DebuggingWithGdb">https://wiki.python.org/moin/DebuggingWithGdb</a></p>
<p>Tl;dr :</p>
<div class="highlight"><pre><span></span><code>gdb -p $(pgrep -f your_running_python_program_name)
</code></pre></div>
<h3>2. Installing the macros for Python</h3>
<p><q>A set of GDB macros are distributed with Python that aid in debugging the Python process.</q> (from: <a href="https://wiki.python.org/moin/DebuggingWithGdb#GDB_Macros">https://wiki.python.org/moin/DebuggingWithGdb#GDB_Macros</a>).</p>
<p>One-liner to install those macros:</p>
<div class="highlight"><pre><span></span><code>curl --silent http://svn.python.org/projects/python/trunk/Misc/gdbinit >> ~/.gdbinit
</code></pre></div>
<p>There is a minor fix to the <code>lineno</code> macro to make:</p>
<div class="highlight"><pre><span></span><code>sed -i 's/printf "%d", $__li/printf "%d", $__li + 1/' ~/.gdbinit
</code></pre></div>
<p>Usage example taken from the comments:</p>
<blockquote>
<pre><code>(gdb) pyo apyobjectptr
<module 'foobar' (built-in)>
refcounts: 1
address : 84a7a2c
$1 = void
</code></pre>
Quoted from: <cite><a href="http://svn.python.org/projects/python/trunk/Misc/gdbinit">Python SVN Misc/.gdbinit</a></cite>
</blockquote>
<p><br></p>
<p>Some other useful macros: <code>pystack</code>, <code>printframe</code>, <code>pylocals</code>.
List them all with: <code>grep define ~/.gdbinit</code>.</p>
<h3>3. Persistent history & colored prompt</h3>
<p>The following command will append some lines to your <em>~/.gdbinit</em> configuration file in order to keep a persistent history between your gdb sessions, and to set a very useful colored prompt:</p>
<div class="highlight"><pre><span></span><code><span class="nt">cat</span> <span class="o"><<</span><span class="nt">END</span> <span class="o">>></span> <span class="o">~/</span><span class="p">.</span><span class="nc">gdbinit</span>
<span class="err">#</span> <span class="nt">Custom</span> <span class="nt">configuration</span> <span class="nt">by</span> <span class="nt">USER</span><span class="o">=$</span><span class="nt">USER</span>
<span class="err">##</span> <span class="nt">Persistent</span> <span class="nt">history</span><span class="o">:</span>
<span class="nt">set</span> <span class="nt">history</span> <span class="nt">save</span>
<span class="nt">set</span> <span class="nt">history</span> <span class="nt">filename</span> <span class="o">~/</span><span class="p">.</span><span class="nc">gdb_history</span>
<span class="err">##</span> <span class="nt">Colored</span> <span class="nt">prompt</span><span class="o">:</span>
<span class="nt">set</span> <span class="nt">prompt</span> <span class="err">\</span><span class="nt">001</span><span class="err">\</span><span class="nt">033</span><span class="cp">[</span><span class="mi">1</span><span class="p">;</span><span class="mi">32</span><span class="nx">m</span><span class="o">\</span><span class="mi">002</span><span class="p">(</span><span class="nx">gdb</span><span class="p">)</span><span class="o">\</span><span class="mi">001</span><span class="o">\</span><span class="mi">033</span><span class="err">[</span><span class="mi">0</span><span class="nx">m</span><span class="o">\</span><span class="mi">002</span><span class="o">\</span><span class="mi">040</span>
<span class="nx">END</span>
</code></pre></div>
<p>All the credit for this trick goes to <a href="http://web.archive.org/web/20140831120136/http://dirac.org/linux/gdb/03-Initialization,_Listing,_And_Running.php#the%3Ctt%3E.gdbinit%3C/tt%3Efile">Peter Jay Salzman</a>.
Here is also a more complete improved <em>.gdbinit</em> : <a href="http://reverse.put.as/gdbinit/">http://reverse.put.as/gdbinit/</a>.</p>
<h3>4. Solving: 'ptrace: Operation not permitted'</h3>
<p>DO <strong>NOT</strong> SET THE SUID/SGID BIT :
<del><code>sudo chmod +s /usr/bin/gdb</code></del></p>
<p>As explained in the comments (thanks to Romain Geissler), this solution I initially recommended actually creates a <strong>security vulnerability</strong>.</p>
<p>A better solution looks to be :</p>
<div class="highlight"><pre><span></span><code>echo 0 > /proc/sys/kernel/yama/ptrace_scope # to execute as root
</code></pre></div>
<p>Or setting <code>kernel.yama.ptrace_scope = 0</code> in <em>/etc/sysctl.d/10-ptrace.conf</em>.</p>
<h3>5. Solving: 'No symbol "co" in current context'</h3>
<p>This one is trickier. It's a known issue described in <a href="https://wiki.python.org/moin/DebuggingWithGdb#GDB_Macros">https://wiki.python.org/moin/DebuggingWithGdb#GDB_Macros</a>. The recommended solution is to <q>Recompile python with make CFLAGS=-g -fno-inline -fno-strict-aliasing</q> (the alternative patch did not work for me).</p>
<p>Yeah, I know, <em>SIGH</em>...</p>
<p>But it's in fact not so difficult to built a stand-alone python interpreter:</p>
<div class="highlight"><pre><span></span><code>dbg_py_v=2.7.8; dbg_py=Python-$dbg_py_v
mkdir /opt; cd /opt
curl -L http://python.org/ftp/python/$dbg_py_v/$dbg_py.tar.xz | tar xJvf -
cd $dbg_py
./configure --prefix=/usr/local --enable-unicode=ucs4 --disable-shared LDFLAGS="-Wl,-rpath /usr/local/lib"
make -j3 "CFLAGS=-g -fno-inline -fno-strict-aliasing"
</code></pre></div>
<p>Then simply:</p>
<div class="highlight"><pre><span></span><code><span class="k">export</span> <span class="n">PYTHONPATH</span><span class="o">=...</span> <span class="c1"># can be extracted from the original script with print('\n'.join(sys.path))</span>
<span class="o">/</span><span class="n">opt</span><span class="o">/$</span><span class="n">dbg_py</span><span class="o">/</span><span class="n">python</span> <span class="n">your_python_program</span>
</code></pre></div>
<p>And no more "undefined symbol" error message in <code>gdb</code> !</p>
<hr>
<p><strong>EDIT</strong>: Actually, if your <code>gdb</code> version has been compiled with python support (<code>gdb --batch --quiet -ex 'show configuration' | grep with-python</code>), no need to download any <em>.gdbinit</em> file !
You can replace all those macros definition by this single line in your <em>.gdbinit</em> (after manually replacing the <em>$dbg_py</em> variable) :</p>
<div class="highlight"><pre><span></span><code><span class="n">add</span><span class="o">-</span><span class="n">auto</span><span class="o">-</span><span class="nb">load</span><span class="o">-</span><span class="n">safe</span><span class="o">-</span><span class="n">path</span> <span class="o">/</span><span class="n">opt</span><span class="o">/$</span><span class="n">dbg_py</span><span class="o">/</span><span class="n">python</span><span class="o">-</span><span class="n">gdb</span><span class="o">.</span><span class="n">py</span>
</code></pre></div>
<p>You now have access to all the following commands:</p>
<ul>
<li>py-bt</li>
<li>py-list</li>
<li>py-down / py-up</li>
<li>py-locals</li>
<li>py-print</li>
</ul>
<p>On the other hand, the latest version of the <em>gdbinit</em> file on the new Mercurial repository doesn't seem to work: <a href="https://hg.python.org/cpython/raw-file/default/Misc/gdbinit">https://hg.python.org/cpython/raw-file/default/Misc/gdbinit</a>.
I get a <em>'No symbol "_PyUnicode_AsString" in current context.'</em> error message.</p>
<p><strong>EDIT (2016/04/15):</strong> a more recent article on the subject : <a href="http://podoliaka.org/2016/04/10/debugging-cpython-gdb/">http://podoliaka.org/2016/04/10/debugging-cpython-gdb/</a></p>
<p>The following commands taken from this article helped me once:</p>
<div class="highlight"><pre><span></span><code>yum install python33-python-debuginfo glibc-debuginfo
# from base-debuginfo repository - can also be installed with: debuginfo-install ...
</code></pre></div>
<p>In <code>gdb</code> :</p>
<div class="highlight"><pre><span></span><code>source /usr/lib/debug/opt/rh/python33/root/usr/lib64/libpython3.3m.so.1.0.debug-gdb.py
</code></pre></div>Overriding the Enter keydown behaviour in bootstrap-datepicker.js2014-10-27T22:10:00+01:002014-10-27T22:10:00+01:00Lucas Cimontag:chezsoi.org,2014-10-27:/lucas/blog/overriding-the-enter-keydown-behaviour-in-bootstrap-datepicker-js.html<p>I've got many post ideas in the past two weeks, but none of them made it to the blog yet. While those scribbles are maturing, there is a short hack for the wonderful <a href="//eternicode.github.io/bootstrap-datepicker">datepicker</a> pluging for <a href="http://getbootstrap.com">bootstrap.js</a>.</p>
<p>Given a datepicker initialized on a <code><div id="date-picker"></code> like this:</p>
<div class="highlight"><pre><span></span><code><span class="nx">$</span><span class="p">(</span><span class="s1">'#date-picker …</span></code></pre></div><p>I've got many post ideas in the past two weeks, but none of them made it to the blog yet. While those scribbles are maturing, there is a short hack for the wonderful <a href="//eternicode.github.io/bootstrap-datepicker">datepicker</a> pluging for <a href="http://getbootstrap.com">bootstrap.js</a>.</p>
<p>Given a datepicker initialized on a <code><div id="date-picker"></code> like this:</p>
<div class="highlight"><pre><span></span><code><span class="nx">$</span><span class="p">(</span><span class="s1">'#date-picker'</span><span class="p">).</span><span class="nx">datepicker</span><span class="p">({</span>
<span class="nx">language</span><span class="o">:</span> <span class="s1">'fr'</span><span class="p">,</span>
<span class="nx">startDate</span><span class="o">:</span> <span class="ow">new</span> <span class="nb">Date</span><span class="p">(),</span>
<span class="nx">todayHighlight</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">});</span>
</code></pre></div>
<p>Now that's how to override the default behaviour of the <em><strong>Enter</strong></em> keydown event, that very annoyingly toggle on & off the user input:</p>
<div class="highlight"><pre><span></span><code><span class="nx">$</span><span class="p">(</span><span class="s1">'#date-selected'</span><span class="p">).</span><span class="nx">keydown</span><span class="p">(</span><span class="kd">function</span> <span class="p">(</span><span class="nx">ev</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">keycode</span> <span class="o">=</span> <span class="p">(</span><span class="nx">ev</span><span class="p">.</span><span class="nx">keyCode</span> <span class="o">?</span> <span class="nx">ev</span><span class="p">.</span><span class="nx">keyCode</span> <span class="o">:</span> <span class="nx">ev</span><span class="p">.</span><span class="nx">which</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">keycode</span> <span class="o">==</span> <span class="s1">'13'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">dp</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="s1">'#date-picker'</span><span class="p">).</span><span class="nx">data</span><span class="p">(</span><span class="s1">'datepicker'</span><span class="p">);</span>
<span class="c1">// 1: we manually restore the input date so it's not toggled on/off</span>
<span class="nx">dp</span><span class="p">.</span><span class="nx">dates</span><span class="p">.</span><span class="nx">pop</span><span class="p">();</span> <span class="c1">// idempotent if no dates</span>
<span class="nx">dp</span><span class="p">.</span><span class="nx">dates</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">dp</span><span class="p">.</span><span class="nx">viewDate</span><span class="p">);</span>
<span class="nx">dp</span><span class="p">.</span><span class="nx">setValue</span><span class="p">();</span>
<span class="c1">// 2: we move to the next input field & close the picker</span>
<span class="nx">$</span><span class="p">(</span><span class="s1">'input[id!="date-selected"]'</span><span class="p">).</span><span class="nx">first</span><span class="p">().</span><span class="nx">focus</span><span class="p">();</span>
<span class="nx">dp</span><span class="p">.</span><span class="nx">hide</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div>Shrink down a GIF by reducing its frames count2014-10-10T03:10:00+02:002014-10-10T03:10:00+02:00Lucas Cimontag:chezsoi.org,2014-10-10:/lucas/blog/shrinking-down-a-gif-by-reducing-its-frames-count.html<p>To illustrate my <a href="https://chezsoi.org/lucas/blog/setting-up-etherpad-in-a-server-subdirectory-aka-apache-config-hell.html">previous post</a> and keep images at a reasonable size, I had to shrink down the top GIF from <a href="https://lh3.googleusercontent.com/-W4wSdhJ2O3A/UfgjKpNKBCI/AAAAAAAAAac/UGbf_GaXjA4/w400-h225-no/Cloudberry+Kingdom+difficulty.gif">14Mb</a> to <a href="/lucas/blog/images/2014/Oct/Cloudberry-Kingdom-difficulty_reduced_x3.gif">3.2Mb</a>.</p>
<p>Usually, online GIF converters will provide the ability to lower down the image dimensions. Another solution to reduce its size is to simply skip some …</p><p>To illustrate my <a href="https://chezsoi.org/lucas/blog/setting-up-etherpad-in-a-server-subdirectory-aka-apache-config-hell.html">previous post</a> and keep images at a reasonable size, I had to shrink down the top GIF from <a href="https://lh3.googleusercontent.com/-W4wSdhJ2O3A/UfgjKpNKBCI/AAAAAAAAAac/UGbf_GaXjA4/w400-h225-no/Cloudberry+Kingdom+difficulty.gif">14Mb</a> to <a href="/lucas/blog/images/2014/Oct/Cloudberry-Kingdom-difficulty_reduced_x3.gif">3.2Mb</a>.</p>
<p>Usually, online GIF converters will provide the ability to lower down the image dimensions. Another solution to reduce its size is to simply skip some frames.</p>
<p>To do so, I used <a href="http://imagemagick.org/">ImageMagick</a> wrapped in the following script:</p>
<div class="highlight"><pre><span></span><code>gif_framecount_reducer <span class="o">()</span> <span class="o">{</span> <span class="c1"># args: $gif_path $frames_reduction_factor</span>
<span class="nb">local</span> <span class="nv">orig_gif</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">1</span><span class="p">?</span><span class="s1">'Missing GIF filename parameter'</span><span class="si">}</span><span class="s2">"</span>
<span class="nb">local</span> <span class="nv">reduction_factor</span><span class="o">=</span><span class="si">${</span><span class="nv">2</span><span class="p">?</span><span class="s1">'Missing reduction factor parameter'</span><span class="si">}</span>
<span class="c1"># Extracting the delays between each frames</span>
<span class="nb">local</span> <span class="nv">orig_delay</span><span class="o">=</span><span class="k">$(</span>gifsicle -I <span class="s2">"</span><span class="nv">$orig_gif</span><span class="s2">"</span> <span class="p">|</span> sed -ne <span class="s1">'s/.*delay \([0-9.]\+\)s/\1/p'</span> <span class="p">|</span> uniq<span class="k">)</span>
<span class="c1"># Ensuring this delay is constant</span>
<span class="o">[</span> <span class="k">$(</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$orig_delay</span><span class="s2">"</span> <span class="p">|</span> wc -l<span class="k">)</span> -ne <span class="m">1</span> <span class="o">]</span> <span class="se">\</span>
<span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"Input GIF doesn't have a fixed framerate"</span> ><span class="p">&</span><span class="m">2</span> <span class="se">\</span>
<span class="o">&&</span> <span class="k">return</span> <span class="m">1</span>
<span class="c1"># Computing the current and new FPS</span>
<span class="nb">local</span> <span class="nv">new_fps</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">"(1/</span><span class="nv">$orig_delay</span><span class="s2">)/</span><span class="nv">$reduction_factor</span><span class="s2">"</span> <span class="p">|</span> bc<span class="k">)</span>
<span class="c1"># Exploding the animation into individual images in /var/tmp</span>
<span class="nb">local</span> <span class="nv">tmp_frames_prefix</span><span class="o">=</span><span class="s2">"/var/tmp/</span><span class="si">${</span><span class="nv">orig_gif</span><span class="p">%.*</span><span class="si">}</span><span class="s2">_"</span>
convert <span class="s2">"</span><span class="nv">$orig_gif</span><span class="s2">"</span> -coalesce +adjoin <span class="s2">"</span><span class="nv">$tmp_frames_prefix</span><span class="s2">%05d.gif"</span>
<span class="nb">local</span> <span class="nv">frames_count</span><span class="o">=</span><span class="k">$(</span>ls <span class="s2">"</span><span class="nv">$tmp_frames_prefix</span><span class="s2">"</span>*.gif <span class="p">|</span> wc -l<span class="k">)</span>
<span class="c1"># Creating a symlink for one frame every $reduction_factor</span>
<span class="nb">local</span> <span class="nv">sel_frames_prefix</span><span class="o">=</span><span class="s2">"/var/tmp/sel_</span><span class="si">${</span><span class="nv">orig_gif</span><span class="p">%.*</span><span class="si">}</span><span class="s2">_"</span>
<span class="k">for</span> i <span class="k">in</span> <span class="k">$(</span>seq <span class="m">0</span> <span class="nv">$reduction_factor</span> <span class="k">$((</span>frames_count-1<span class="k">)))</span><span class="p">;</span> <span class="k">do</span>
<span class="nb">local</span> <span class="nv">suffix</span><span class="o">=</span><span class="k">$(</span><span class="nb">printf</span> <span class="s2">"%05d.gif"</span> <span class="nv">$i</span><span class="k">)</span>
ln -s <span class="s2">"</span><span class="nv">$tmp_frames_prefix$suffix</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$sel_frames_prefix$suffix</span><span class="s2">"</span>
<span class="k">done</span>
<span class="c1"># Assembling the new animated GIF from the selected frames</span>
convert -delay <span class="nv">$new_fps</span> <span class="s2">"</span><span class="nv">$sel_frames_prefix</span><span class="s2">"</span>*.gif <span class="s2">"</span><span class="si">${</span><span class="nv">orig_gif</span><span class="p">%.*</span><span class="si">}</span><span class="s2">_reduced_x</span><span class="si">${</span><span class="nv">reduction_factor</span><span class="si">}</span><span class="s2">.gif"</span>
<span class="c1"># Cleaning up</span>
rm <span class="s2">"</span><span class="nv">$tmp_frames_prefix</span><span class="s2">"</span>*.gif <span class="s2">"</span><span class="nv">$sel_frames_prefix</span><span class="s2">"</span>*.gif
<span class="o">}</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code>$ <span class="k">for</span> i <span class="k">in</span> <span class="o">{</span><span class="m">2</span>..5<span class="o">}</span><span class="p">;</span> <span class="k">do</span> gif_framecount_reducer Cloudberry-Kingdom-difficulty.gif <span class="nv">$i</span><span class="p">;</span> <span class="k">done</span>
$ ls -sh *.gif <span class="p">|</span> sort -r
<span class="m">8</span>.7M Cloudberry-Kingdom-difficulty.gif
<span class="m">4</span>.7M Cloudberry-Kingdom-difficulty_reduced_x2.gif
<span class="m">3</span>.2M Cloudberry-Kingdom-difficulty_reduced_x3.gif
<span class="m">2</span>.4M Cloudberry-Kingdom-difficulty_reduced_x4.gif
<span class="m">1</span>.9M Cloudberry-Kingdom-difficulty_reduced_x5.gif
</code></pre></div>
<p>There are the results:</p>
<ul>
<li><a href="/lucas/blog/images/2014/Oct/Cloudberry-Kingdom-difficulty.gif">Cloudberry-Kingdom-difficulty.gif</a></li>
<li><a href="/lucas/blog/images/2014/Oct/Cloudberry-Kingdom-difficulty_reduced_x2.gif">Cloudberry-Kingdom-difficulty_reduced_x2.gif</a></li>
<li><a href="/lucas/blog/images/2014/Oct/Cloudberry-Kingdom-difficulty_reduced_x3.gif">Cloudberry-Kingdom-difficulty_reduced_x3.gif</a></li>
<li><a href="/lucas/blog/images/2014/Oct/Cloudberry-Kingdom-difficulty_reduced_x4.gif">Cloudberry-Kingdom-difficulty_reduced_x4.gif</a></li>
<li><a href="/lucas/blog/images/2014/Oct/Cloudberry-Kingdom-difficulty_reduced_x5.gif">Cloudberry-Kingdom-difficulty_reduced_x5.gif</a></li>
</ul>
<p>This function also rely on the <code>gifsicle</code> command, that is dedicated to creating and editing GIFs. It can be more useful and reliable than ImageMagick <code>identify</code> to extract information. E.g. you can extract the frames count this way:</p>
<div class="highlight"><pre><span></span><code>gifsicle $gif -I | sed -ne 's/.* \([0-9]\+\) images/\1/p'
</code></pre></div>Setting-up Etherpad in a server subdirectory -aka- Apache config hell2014-10-09T22:10:00+02:002014-10-09T22:10:00+02:00Lucas Cimontag:chezsoi.org,2014-10-09:/lucas/blog/setting-up-etherpad-in-a-server-subdirectory-aka-apache-config-hell.html<p><img alt="Animation showing a difficult as hell platformer video game" src="images/2014/Oct/Cloudberry-Kingdom-difficulty_reduced_x3.gif"></p>
<p>I truly think <a href="//github.com/ether/etherpad-lite">Etherpad</a> is an amazing piece of software. Not so much for its code base quality than for its extraordinary range of usages.</p>
<p>Now, while I'm still unsure if todo-lists are useful <a href="//blog.codinghorror.com/todont/">or a complete waste of energy</a>, I'm convinced that keeping a developer logbook / diary / journal has …</p><p><img alt="Animation showing a difficult as hell platformer video game" src="images/2014/Oct/Cloudberry-Kingdom-difficulty_reduced_x3.gif"></p>
<p>I truly think <a href="//github.com/ether/etherpad-lite">Etherpad</a> is an amazing piece of software. Not so much for its code base quality than for its extraordinary range of usages.</p>
<p>Now, while I'm still unsure if todo-lists are useful <a href="//blog.codinghorror.com/todont/">or a complete waste of energy</a>, I'm convinced that keeping a developer logbook / diary / journal has <a href="http://coliveira.net/software/day-3-keep-a-programming-diary/">many</a> <a href="//programmers.stackexchange.com/a/3499">benefits</a> (even <a href="http://en.wikipedia.org/wiki/The_Pragmatic_Programmer">The Pragmatic Programmer</a> and <a href="http://antirez.com/news/51">Redis creator</a> recommend it).</p>
<p>Then, don't get me wrong: paper notebooks are <a href="https://gist.github.com/sent-hil/3444793">GREAT</a>. I love them, and always carry one with me. But for the sake of durability, safety and hypertextuality, I often end up transfering most of my hand-written notes on my computer. And Etherpad is just the perfect tool for this. Why ?</p>
<ul>
<li>its interface is slick, easy to grasp quickly, almost simplistic at first but with a bit of <a href="//en.wikipedia.org/wiki/Progressive_disclosure">progressive disclosure</a></li>
<li>it provides the flexibility to add, remove or rearrange ideas way more easily than on paper</li>
<li>it allows quick copy & paste of URLs, code snippets, quotes, input text for a temporary backup copy... like some kind of cross-applications scratchpad/buffer</li>
<li>it has various font sizes, styles and colors : it is well-known in <a href="en.wikipedia.org/wiki/Mind_map">mind-mapping</a> that this will help make your notes more stimulating, engaging & easy to remember</li>
<li>it allows simultaneous multi-users access</li>
<li>it has plenty of plugins : <a href="//github.com/JohnMcLear/ep_previewimages">images preview</a>, <a href="//github.com/spruce/ep_small_list">list of pads</a>, <a href="//github.com/etinquis/etherpad-plugins/tree/master/ep_syntaxhighlighting">syntax highlighting</a>...</li>
<li>and above all: <strong>it provides a persistent history of changes</strong></li>
</ul>
<p>Hence my will to install a local instance on my server.</p>
<p>It all started off on the right foot: installing Etherpad on my Ubuntu laptop revealed to be <a href="//github.com/ether/etherpad-lite/blob/master/README.md#gnulinux-and-other-unix-like-systems">easy as a pie</a>.</p>
<p>But things became nasty when I had to serve it through Apache. The <a href="//github.com/ether/etherpad-lite/wiki/How-to-put-Etherpad-Lite-behind-a-reverse-Proxy">wiki guidelines</a> did not work for me.</p>
<p>After hours of googling (more precisely <a href="//duckduckgo.com">duckduckgoing</a>) and a stupid attempt to add an additional helpful entry to the Etherpad config (I was about to send a pull request on github when I realized how useless it was), I found the <a href="//xkcd.com/979/">wisdom of the ancients</a> on good old <a href="//stackoverflow.com/a/13385407">StackOverflow</a>.</p>
<p>First, assuming you have an Etherpad node.js process ready & listening on port 9001, you'll have to configure Apache to use mod_proxy_html:</p>
<div class="highlight"><pre><span></span><code>sudo apt-get install libapache2-mod-proxy-html libxml2-dev
sudo a2enmod proxy_html xml2enc
</code></pre></div>
<p>Then, there is the magic config:</p>
<div class="highlight"><pre><span></span><code><span class="o"><</span><span class="n">Location</span><span class="w"> </span><span class="o">/</span><span class="k">pad</span><span class="o">></span><span class="w"></span>
<span class="w"> </span><span class="n">ProxyPass</span><span class="w"> </span><span class="nl">http</span><span class="p">:</span><span class="o">//</span><span class="nl">localhost</span><span class="p">:</span><span class="mi">9001</span><span class="w"> </span><span class="n">retry</span><span class="o">=</span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="err">#</span><span class="w"> </span><span class="n">retry</span><span class="o">=</span><span class="mi">0</span><span class="w"> </span><span class="o">=></span><span class="w"> </span><span class="n">avoid</span><span class="w"> </span><span class="mi">503</span><span class="err">'</span><span class="n">s</span><span class="w"> </span><span class="k">when</span><span class="w"> </span><span class="n">restarting</span><span class="w"> </span><span class="n">etherpad</span><span class="o">-</span><span class="n">lite</span><span class="w"></span>
<span class="w"> </span><span class="n">ProxyPassReverse</span><span class="w"> </span><span class="nl">http</span><span class="p">:</span><span class="o">//</span><span class="nl">localhost</span><span class="p">:</span><span class="mi">9001</span><span class="w"></span>
<span class="w"> </span><span class="n">SetOutputFilter</span><span class="w"> </span><span class="n">proxy</span><span class="o">-</span><span class="n">html</span><span class="w"></span>
<span class="w"> </span><span class="n">ProxyHTMLURLMap</span><span class="w"> </span><span class="nl">http</span><span class="p">:</span><span class="o">//</span><span class="nl">localhost</span><span class="p">:</span><span class="mi">9001</span><span class="w"></span>
<span class="o"></</span><span class="n">Location</span><span class="o">></span><span class="w"></span>
<span class="n">RewriteRule</span><span class="w"> </span><span class="o">^/</span><span class="k">pad</span><span class="err">$</span><span class="w"> </span><span class="o">/</span><span class="k">pad</span><span class="o">/</span><span class="w"> </span><span class="o">[</span><span class="n">R</span><span class="o">]</span><span class="w"></span>
</code></pre></div>
<p>Now simply restart Apache, and Bob's your uncle !</p>
<div class="highlight"><pre><span></span><code><span class="n">sudo</span> <span class="n">service</span> <span class="n">apache2</span> <span class="n">restart</span>
<span class="n">sudo</span> <span class="n">tail</span> <span class="o">-</span><span class="n">F</span> <span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="nb">log</span><span class="o">/</span><span class="n">apache2</span><span class="o">/*.</span><span class="n">log</span> <span class="c1"># watch that everything is OK</span>
</code></pre></div>
<p><img alt="Animation showing a Powerrangers character stunt" src="images/2014/Oct/PowerRanger_stunt_GotYou.gif"></p>
<p><br/><br/><br/><br/><br/><br/><br/></p>
<p>Still here ? Ok, there is a last tip, in case you use either ep_small_list or ep_syntaxhighlighting plugins, to convert their bogus absolute URLs into relative ones:</p>
<div class="highlight"><pre><span></span><code>grep -FIlR '"/static/' path/to/etherpad-lite/node_modules/ep_{small_list,syntaxhighlighting} | xargs sed -i 's~"/static/~"../static/~'
</code></pre></div>Follow a process progress to read a file2014-10-08T05:10:00+02:002014-10-08T05:10:00+02:00Lucas Cimontag:chezsoi.org,2014-10-08:/lucas/blog/follow-a-progress-progress-to-read-a-file.html<p>Do you know the <code>pv</code> command ? It's really nifty.</p>
<p>There are a few use cases:</p>
<div class="highlight"><pre><span></span><code><span class="nv">echo</span> <span class="s2">"</span><span class="s">You can simulate on-screen typing just like in the movies</span><span class="s2">"</span> <span class="o">|</span> <span class="nv">pv</span> <span class="o">-</span><span class="nv">qL</span> <span class="mi">10</span>
# <span class="k">Show</span> <span class="nv">the</span> <span class="nv">incoming</span> <span class="nv">TCP</span> <span class="nv">packets</span> <span class="nv">rate</span>:
<span class="nv">tcpdump</span> <span class="nv">tcp</span> <span class="o">-</span><span class="nv">w</span> <span class="o">-</span> <span class="o">|</span> <span class="nv">pv</span> <span class="o">-</span><span class="nv">btr</span> <span class="o">>/</span><span class="nv">dev</span><span class="o">/</span><span class="nv">null</span>
# <span class="k">Show</span> <span class="nv">the</span> <span class="nv">maximum</span> <span class="nv">throughput</span> <span class="nv">between</span> <span class="nv">two</span> <span class="nv">machines</span>:
# <span class="ss">(</span><span class="nv">provided …</span></code></pre></div><p>Do you know the <code>pv</code> command ? It's really nifty.</p>
<p>There are a few use cases:</p>
<div class="highlight"><pre><span></span><code><span class="nv">echo</span> <span class="s2">"</span><span class="s">You can simulate on-screen typing just like in the movies</span><span class="s2">"</span> <span class="o">|</span> <span class="nv">pv</span> <span class="o">-</span><span class="nv">qL</span> <span class="mi">10</span>
# <span class="k">Show</span> <span class="nv">the</span> <span class="nv">incoming</span> <span class="nv">TCP</span> <span class="nv">packets</span> <span class="nv">rate</span>:
<span class="nv">tcpdump</span> <span class="nv">tcp</span> <span class="o">-</span><span class="nv">w</span> <span class="o">-</span> <span class="o">|</span> <span class="nv">pv</span> <span class="o">-</span><span class="nv">btr</span> <span class="o">>/</span><span class="nv">dev</span><span class="o">/</span><span class="nv">null</span>
# <span class="k">Show</span> <span class="nv">the</span> <span class="nv">maximum</span> <span class="nv">throughput</span> <span class="nv">between</span> <span class="nv">two</span> <span class="nv">machines</span>:
# <span class="ss">(</span><span class="nv">provided</span> <span class="nv">the</span> <span class="nv">correct</span> <span class="nv">iptables</span> <span class="nv">OUTPUT</span><span class="o">/</span><span class="nv">INPUT</span> <span class="nv">rules</span> <span class="nv">allowing</span> <span class="nv">traffic</span> <span class="nv">through</span> <span class="nv">port</span> <span class="mi">7070</span> <span class="nv">are</span> <span class="nv">set</span><span class="ss">)</span>
<span class="nv">socat</span> <span class="o">-</span> <span class="nv">tcp</span><span class="o">-</span><span class="nv">listen</span>:<span class="mi">7070</span> <span class="o">></span> <span class="o">/</span><span class="nv">dev</span><span class="o">/</span><span class="nv">null</span> # <span class="nv">on</span> <span class="nv">the</span> <span class="nv">receiving</span> <span class="nv">host</span> <span class="nv">side</span>
<span class="nv">pv</span> <span class="o">-</span><span class="nv">btr</span> <span class="o">/</span><span class="nv">dev</span><span class="o">/</span><span class="nv">zero</span> <span class="o">|</span> <span class="nv">socat</span> <span class="o">-</span> <span class="nv">tcp</span>:$<span class="nv">host</span>:<span class="mi">7070</span> # <span class="nv">on</span> <span class="nv">the</span> <span class="nv">sending</span> <span class="nv">server</span> <span class="nv">side</span>
# <span class="nv">Monitor</span> <span class="nv">progress</span> <span class="nv">of</span> <span class="nv">a</span> <span class="nv">command</span>
<span class="nv">gzip</span> <span class="nv">access</span>.<span class="nv">log</span> <span class="o">|</span> <span class="nv">pv</span> <span class="o">></span> <span class="nv">access</span>.<span class="nv">log</span>.<span class="nv">gz</span>
# <span class="nv">Copy</span> <span class="nv">a</span> <span class="nv">file</span> <span class="nv">and</span> <span class="nv">watch</span> <span class="nv">its</span> <span class="nv">progress</span>
<span class="nv">pv</span> <span class="nv">sourcefile</span> <span class="o">></span> <span class="nv">destfile</span>
</code></pre></div>
<p>Actually the last two examples are the most common usages.</p>
<p>But what if you already started a command without <code>pv</code> but still want to watch the file processing progress ? Or if the file reading is done <strong>inside</strong> your command, without using Unix pipes ?</p>
<p>Well I've got a solution for you, heavily inspired by <a href="//blogs.oracle.com/ksplice/entry/solving_problems_with_proc">Keegan McAllister article on Ksplace Oracle blog</a>:</p>
<div class="highlight"><pre><span></span><code><span class="nv">FIFTY_NON_SHADY_NOR_GREY_HASHES</span><span class="o">=</span><span class="s1">'##################################################'</span>
progress_bar <span class="o">()</span> <span class="o">{</span> <span class="c1"># receive a serie of integers in {1..100} as input and update a unique progress bar line accordingly</span>
<span class="nb">local</span> percent
<span class="k">while</span> <span class="nb">read</span> percent<span class="p">;</span> <span class="k">do</span>
<span class="nb">printf</span> <span class="s2">"\r%-50s (%-3s%%)"</span> <span class="si">${</span><span class="nv">FIFTY_NON_SHADY_NOR_GREY_HASHES</span><span class="p">:</span><span class="nv">0</span><span class="p">:</span><span class="k">$((</span>percent <span class="o">/</span> <span class="m">2</span><span class="k">))</span><span class="si">}</span> <span class="nv">$percent</span>
<span class="k">done</span>
<span class="nb">echo</span>
<span class="o">}</span>
proc_read_fd_progress <span class="o">()</span> <span class="o">{</span> <span class="c1"># args: $pid [$fd]</span>
<span class="nb">local</span> <span class="nv">pid</span><span class="o">=</span><span class="si">${</span><span class="nv">1</span><span class="p">:?</span><span class="s1">'Missing pid first argument'</span><span class="si">}</span>
! <span class="o">[</span> -e /proc/<span class="nv">$pid</span>/ <span class="o">]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"No process found with PID=</span><span class="nv">$pid</span><span class="s2">"</span> ><span class="p">&</span><span class="m">2</span> <span class="o">&&</span> <span class="k">return</span> <span class="m">1</span>
<span class="nb">local</span> <span class="nv">fd</span><span class="o">=</span><span class="nv">$2</span>
<span class="k">if</span> <span class="o">[</span> -z <span class="s2">"</span><span class="si">${</span><span class="nv">fd</span><span class="k">:-</span><span class="si">}</span><span class="s2">"</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
readlink /proc/<span class="nv">$pid</span>/fd/* <span class="p">|</span> nl -v <span class="m">0</span>
<span class="nb">echo</span> -n <span class="s2">"Choose a file descriptor: "</span>
<span class="nb">read</span> fd
<span class="k">fi</span>
<span class="nb">local</span> <span class="nv">proc_fd</span><span class="o">=</span>/proc/<span class="nv">$pid</span>/fd/<span class="nv">$fd</span>
! <span class="o">[</span> -e <span class="nv">$proc_fd</span> <span class="o">]</span> <span class="o">&&</span> <span class="nb">echo</span> <span class="s2">"fd=</span><span class="nv">$fd</span><span class="s2"> is not a valid file descriptor in /proc/</span><span class="nv">$pid</span><span class="s2">/fd/"</span> ><span class="p">&</span><span class="m">2</span> <span class="o">&&</span> <span class="k">return</span> <span class="m">2</span>
<span class="nb">local</span> <span class="nv">fd_size</span><span class="o">=</span><span class="k">$(</span>wc -c <span class="nv">$proc_fd</span> <span class="p">|</span> awk <span class="s1">'{print $1}'</span><span class="k">)</span>
<span class="nb">echo</span> <span class="s2">"Progress reading '</span><span class="k">$(</span>readlink <span class="nv">$proc_fd</span><span class="k">)</span><span class="s2">':"</span>
<span class="nb">local</span> <span class="nv">percent_progress</span><span class="o">=</span><span class="m">0</span>
<span class="k">while</span> <span class="o">[</span> -e <span class="nv">$proc_fd</span> <span class="o">]</span> <span class="o">&&</span> <span class="o">[</span> <span class="s2">"</span><span class="nv">$percent_progress</span><span class="s2">"</span> -ne <span class="m">100</span> <span class="o">]</span> <span class="o">&&</span> ! <span class="nb">read</span> -n <span class="m">1</span> -t <span class="m">1</span> dummy<span class="p">;</span> <span class="k">do</span>
<span class="nb">local</span> <span class="nv">file_read_progress</span><span class="o">=</span><span class="k">$(</span>grep ^pos /proc/<span class="nv">$pid</span>/fdinfo/<span class="nv">$fd</span> <span class="p">|</span> awk <span class="s1">'{print $2}'</span><span class="k">)</span>
<span class="nv">percent_progress</span><span class="o">=</span><span class="k">$((</span><span class="m">100</span> <span class="o">*</span> <span class="nv">$file_read_progress</span> <span class="o">/</span> <span class="nv">$fd_size</span><span class="k">))</span>
<span class="nb">echo</span> <span class="nv">$percent_progress</span>
<span class="k">done</span> <span class="p">|</span> progress_bar
<span class="o">}</span>
</code></pre></div>
<p>Simply put the code above in your <em>.bashrc</em>, and you'll be able to test it immediately:</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">cat</span> <span class="n">slow</span><span class="o">-</span><span class="n">reader</span><span class="o">.</span><span class="n">py</span>
<span class="kn">import</span> <span class="nn">sys</span><span class="o">,</span> <span class="nn">time</span>
<span class="n">f</span> <span class="o">=</span> <span class="n">file</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="s1">'r'</span><span class="p">)</span>
<span class="k">while</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mi">1024</span><span class="p">):</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.01</span><span class="p">)</span>
<span class="err">$</span> <span class="n">python</span> <span class="n">slow</span><span class="o">-</span><span class="n">reader</span><span class="o">.</span><span class="n">py</span> <span class="n">bigfile</span> <span class="o">&</span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="mi">18589</span>
<span class="err">$</span> <span class="n">proc_read_fd_progress</span> <span class="mi">18589</span>
<span class="mi">0</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">pts</span><span class="o">/</span><span class="mi">25</span>
<span class="mi">1</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">pts</span><span class="o">/</span><span class="mi">25</span>
<span class="mi">2</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">pts</span><span class="o">/</span><span class="mi">25</span>
<span class="mi">3</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">lucas</span><span class="o">/</span><span class="n">bigfile</span>
<span class="n">Choose</span> <span class="n">a</span> <span class="n">file</span> <span class="n">descriptor</span><span class="p">:</span> <span class="mi">3</span>
<span class="n">Progress</span> <span class="n">reading</span> <span class="s1">'/home/lucas/bigfile'</span><span class="p">:</span>
<span class="c1">############################################ (88 %)</span>
</code></pre></div>
<p>There are a few small differences with the original <em>phantom-progress.bash</em>:</p>
<ul>
<li>if none is provided, the command interactively asks which file descriptor to use from the list of files opened by the process</li>
<li>it doesn't use the terminal-invasive <code>dialog</code> interface (nor <code>zenity</code>), but a bare simple one-line progress bar</li>
<li>the command can be interrupted by any keystroke or <em>CTRL+C</em></li>
</ul>TheSheepest - Grenoble street-art2014-10-03T23:10:00+02:002014-10-03T23:10:00+02:00Lucas Cimontag:chezsoi.org,2014-10-03:/lucas/blog/thesheepest-grenoble-street-art.html<p><img src="images/2014/Oct/thesheepest_windowhead.jpg" alt="Un mouton à la fenêtre" width="200" style="display:block;"></p>
<p>Très court post ce soir, juste pour mentionner cet artiste qui me tient à coeur. Pourquoi ? Parce que ses moutons narquois ont contribué à me faire tomber amoureux de Grenoble. De passage là-bas la semaine dernière, ces stickers laineux dissimulés un peu partout en ville ont été ma madeleine nostalgique …</p><p><img src="images/2014/Oct/thesheepest_windowhead.jpg" alt="Un mouton à la fenêtre" width="200" style="display:block;"></p>
<p>Très court post ce soir, juste pour mentionner cet artiste qui me tient à coeur. Pourquoi ? Parce que ses moutons narquois ont contribué à me faire tomber amoureux de Grenoble. De passage là-bas la semaine dernière, ces stickers laineux dissimulés un peu partout en ville ont été ma madeleine nostalgique le temps de quelques trajets en tram.</p>
<ul>
<li><a href="http://flickrhivemind.net/Tags/thesheepest/Interesting">une galerie de photos</a></li>
<li><a href="//thesheepest.blogspot.fr/">son blog</a></li>
<li><a href="//www.glazedmag.fr/actualites/the-sheepest-dessine-moi-des-moutons.html">une interview</a></li>
</ul>
<p>Voici un petit jeu la prochaine fois que vous passerez dans la ville aux trois roses: combien de moutons saurez vous compter sans vous endormir ?</p>
<p><img src="images/2014/Oct/thesheepest_chimney.jpg" alt="Bêêêh-revoir !" width="300" style="display:block;"></p>Bash remote code execution vulnerability2014-09-25T10:09:00+02:002014-09-25T10:09:00+02:00Lucas Cimontag:chezsoi.org,2014-09-25:/lucas/blog/bash-remote-code-execution-vulnerability.html<p>Just relaying the information about this "<em>ShellShock</em>" vulnerability:</p>
<ul>
<li><a href="//securityblog.redhat.com/2014/09/24/bash-specially-crafted-environment-variables-code-injection-attack/">RedHat security blog post</a></li>
<li><a href="http://seclists.org/oss-sec/2014/q3/650">the full disclosure on seclists.org</a></li>
</ul>
<p>This seems to affect Apache, <code>sshd</code>, DHCP clients and even potentially <code>git</code>.</p>
<p>TL;DR here is how to check your Bash version</p>
<div class="highlight"><pre><span></span><code>env x='() { echo Never called; }; echo YOUR BASH IS VULNERABLE …</code></pre></div><p>Just relaying the information about this "<em>ShellShock</em>" vulnerability:</p>
<ul>
<li><a href="//securityblog.redhat.com/2014/09/24/bash-specially-crafted-environment-variables-code-injection-attack/">RedHat security blog post</a></li>
<li><a href="http://seclists.org/oss-sec/2014/q3/650">the full disclosure on seclists.org</a></li>
</ul>
<p>This seems to affect Apache, <code>sshd</code>, DHCP clients and even potentially <code>git</code>.</p>
<p>TL;DR here is how to check your Bash version</p>
<div class="highlight"><pre><span></span><code>env x='() { echo Never called; }; echo YOUR BASH IS VULNERABLE' bash -c 'echo This is a test'
</code></pre></div>
<p>The "YOUR BASH IS VULNERABLE" message should NOT appear in your terminal.</p>
<p>Source: <a href="http://taint.org/2014/09/24/235802a.html">Justin Mason's Weblog</a></p>
<p><strong>EDIT</strong>[29/09/2014]: <a href="//twitter.com/taviso/status/514887394294652929">Tavis Ormandy</a> noticed that the first patch doesn't seem to correct the full vulnerability:</p>
<div class="highlight"><pre><span></span><code>env X='() { (a)=>\' sh -c "echo date"; cat echo
</code></pre></div>
<p>I the current date is printed, your <code>bash</code> version is still vulnerable.</p>
<p><strong>EDIT</strong>[30/09/2014]:</p>
<ul>
<li><a href="http://www.troyhunt.com/2014/09/everything-you-need-to-know-about.html">everything you need to know about the Shellshock Bash bug</a></li>
<li><a href="http://www.dwheeler.com/essays/shellshock.html">an even more detailed essay on Shellshock</a></li>
</ul>Extracting images/photos from a diaporama video2014-09-14T13:09:00+02:002014-09-14T13:09:00+02:00Lucas Cimontag:chezsoi.org,2014-09-14:/lucas/blog/extracting-imagesphotos-from-a-diaporama-video.html<p>I was asked the other day by my grand-mother in law to try to recover her photos from a diaporama video she created years ago.</p>
<p>The diaporama was made of a succession of photos, occupying the whole screen for a moment and then transitioning with various effects to the next …</p><p>I was asked the other day by my grand-mother in law to try to recover her photos from a diaporama video she created years ago.</p>
<p>The diaporama was made of a succession of photos, occupying the whole screen for a moment and then transitioning with various effects to the next photo.</p>
<p>To avoid the manual approach of taking successive screenshots, I decided to build a simple pipeline out of some existing Linux tools to carry out the following plan:</p>
<ul>
<li>extract frames from the video as image files</li>
<li>find the <em>static</em> frames, i.e. consecutive identical ones, in opposition to frames that are part of transition effects</li>
<li>select only one image for each batch of static frames</li>
</ul>
<p>For the first phase I used the powerful <code>avconv</code> command (formerly known as <code>ffmpeg</code>), getting inspiration from <a href="http://ubuntuforums.org/showthread.php?t=2014630&p=12099770#post12099770">this forum post</a>:</p>
<div class="highlight"><pre><span></span><code>sudo aptitude install avconv
mkdir <span class="cp">${</span><span class="n">video_file</span><span class="cp">}</span>_extracted
avconv -i <span class="nv">$video_file.VOB</span> -r 1 -an "<span class="cp">${</span><span class="n">video_file</span><span class="cp">}</span>_extracted/videoframe%03d.png"
</code></pre></div>
<p><code>-r 1</code> sets the <a href="//en.wikipedia.org/wiki/Frame_rate">FPS</a> to 1 second and <code>-an</code> disables the audio recording. Because the output file pattern contains <em>"%0Nd"</em>, the <strong>image2</strong> file muxer is used by <code>avconv</code>.</p>
<p>At this point, I had more than 700 images. The next step was to find which ones were visually identical.</p>
<p>While there are many tools available to do the job, including the very interesting <a href="http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html">perceptual hash</a> algorithm in use at <a href="//www.tineye.com/">TinEye</a> <sup><a href="#fn1" id="ref1"><small>1</small></a></sup>, my first pick was for a Perl-command available in the Ubuntu repositories, and it did the trick perfectly:</p>
<div class="highlight"><pre><span></span><code><span class="n">sudo</span><span class="w"> </span><span class="n">aptitude</span><span class="w"> </span><span class="n">install</span><span class="w"> </span><span class="n">findimagedupes</span><span class="w"></span>
<span class="n">mkdir</span><span class="w"> </span><span class="n">triaged</span><span class="o">/</span><span class="w"></span>
<span class="n">findimagedupes</span><span class="w"> </span><span class="o">--</span><span class="n">threshold</span><span class="o">=</span><span class="mi">99</span><span class="o">%</span><span class="w"> </span><span class="o">--</span><span class="n">include</span><span class="w"> </span><span class="err">'</span><span class="n">VIEW</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">middle_index</span><span class="o">=</span><span class="n">$</span><span class="p">((</span><span class="n">$</span><span class="err">#</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">2</span><span class="p">));</span><span class="w"> </span><span class="n">middle_img</span><span class="o">=</span><span class="n">$</span><span class="p">(</span><span class="n">echo</span><span class="w"> </span><span class="s">"$@"</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">sort</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">cut</span><span class="w"> </span><span class="o">-</span><span class="n">d</span><span class="s">" "</span><span class="w"> </span><span class="o">-</span><span class="n">f</span><span class="w"> </span><span class="n">$middle_index</span><span class="p">);</span><span class="w"> </span><span class="n">cp</span><span class="w"> </span><span class="n">$middle_img</span><span class="w"> </span><span class="n">triaged</span><span class="o">/</span><span class="p">;</span><span class="w"> </span><span class="p">}</span><span class="err">'</span><span class="w"> </span><span class="n">$</span><span class="p">{</span><span class="n">video_file</span><span class="p">}</span><span class="n">_extracted</span><span class="o">/</span><span class="w"></span>
<span class="n">mv</span><span class="w"> </span><span class="n">triaged</span><span class="w"> </span><span class="n">$</span><span class="p">{</span><span class="n">video_file</span><span class="p">}</span><span class="n">_triaged</span><span class="o">/</span><span class="w"></span>
<span class="n">rm</span><span class="w"> </span><span class="o">-</span><span class="n">r</span><span class="w"> </span><span class="n">$</span><span class="p">{</span><span class="n">video_file</span><span class="p">}</span><span class="n">_extracted</span><span class="o">/</span><span class="w"></span>
</code></pre></div>
<p>The <code>--threshold</code> argument sets the similarity level required to group images in batches. In my case, with the default <strong>90%</strong> value, transition images were sometimes considered similar to static images; while with <strong>100%</strong>, some images that were visually identical were placed in separate batches.
The <code>VIEW</code> shell function defined in-line by <code>--include</code> is invoked for each batch of duplicate image file names. In this command it simply moves the select file of one batch in a separate directory. The selected image is the one in the middle of a serie of frames considered identical.</p>
<p>Et <em>voilà</em>, 60 static images properly extracted !</p>
<p><strong>EDIT [5/01/2014]</strong> : to also extract the soundtrack of your .VOB file :</p>
<div class="highlight"><pre><span></span><code>avconv -i /path/to/$video_file.VOB -vn -c:a libmp3lame -b:a 128k $video_file.mp3
</code></pre></div>
<p><strong>EDIT [15/02/2015]</strong> : for a generic approach to extracting frames, check this very detailed post on <a href="http://zulko.github.io/blog/2015/02/01/extracting-perfectly-looping-gifs-from-videos-with-python-and-moviepy/">MoviePy</a> Python library.</p>
<p><br><hr><br></p>
<p><sup id="fn1">1. Among other examples of <em>pHash</em> usage: <a href="//pypi.python.org/pypi/ImageHash">ImageHash</a> a generic Python library, and an <a href="//github.com/mk-fg/image-deduplication-tool">image-deduplication-tool</a>. <a href="#ref1">↩</a></sup></p>Quick stats on a stream of values in the console2014-09-09T15:09:00+02:002014-09-09T15:09:00+02:00Lucas Cimontag:chezsoi.org,2014-09-09:/lucas/blog/quick-stats-on-a-stream-of-values-in-the-console.html<p>I often find myself <code>grep</code>-ing for information in system or application log files. And often, by combining pipes, I end up generating a flow of values that is sometimes difficult to interpret.</p>
<p>In this post I'll show you a quick-and-dirty but handy solution to get basic statistical quantities from …</p><p>I often find myself <code>grep</code>-ing for information in system or application log files. And often, by combining pipes, I end up generating a flow of values that is sometimes difficult to interpret.</p>
<p>In this post I'll show you a quick-and-dirty but handy solution to get basic statistical quantities from a UNIX text stream of values.</p>
<p>There is the <code>bash</code> function using <code>awk</code> to put in your <em>.bashrc</em>:</p>
<div class="highlight"><pre><span></span><code><span class="n">stats</span> <span class="p">()</span> <span class="p">{</span> <span class="c1"># --no-header | awk '{print $3}'</span>
<span class="p">[</span> <span class="s2">"$1"</span> <span class="o">=</span> <span class="s2">"--no-header"</span> <span class="p">]</span> <span class="o">||</span> <span class="n">printf</span> <span class="s2">"</span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="se">\n</span><span class="s2">"</span>\
<span class="mi">1</span><span class="o">-</span><span class="n">SUM</span> <span class="mi">2</span><span class="o">-</span><span class="n">COUNT</span> <span class="mi">3</span><span class="o">-</span><span class="n">MEAN</span> <span class="mi">4</span><span class="o">-</span><span class="n">STD_DEV</span> <span class="mi">5</span><span class="o">-</span><span class="n">MIN</span> <span class="mi">6</span><span class="o">-</span><span class="n">TP01</span> <span class="mi">7</span><span class="o">-</span><span class="n">TP10</span> <span class="mi">8</span><span class="o">-</span><span class="n">MEDIAN</span> <span class="mi">9</span><span class="o">-</span><span class="n">TP90</span> <span class="mi">10</span><span class="o">-</span><span class="n">TP99</span> <span class="mi">11</span><span class="o">-</span><span class="n">MAX</span>
<span class="n">sort</span> <span class="o">-</span><span class="n">n</span> <span class="o">|</span> <span class="n">awk</span> <span class="s1">'BEGIN{n=0;sum=0;mean=0;M2=0}</span><span class="se">\</span>
<span class="s1"> /^[^#]/{a[n++]=$1;sum+=$1;delta=$1-mean;mean+=delta/n;M2+=delta*($1-mean)}</span><span class="se">\</span>
<span class="s1"> function tp(ratio){i=n*ratio-1;if(i<0){return a[0];}else{return a[int(i)];}}</span>
<span class="n">END</span><span class="p">{</span><span class="n">unbiased_variance</span><span class="o">=</span><span class="n">M2</span><span class="o">/</span><span class="p">(</span><span class="n">n</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="n">std_dev</span><span class="o">=</span><span class="nb">sqrt</span><span class="p">(</span><span class="n">unbiased_variance</span><span class="p">);</span>
<span class="k">if</span><span class="p">((</span><span class="n">n</span><span class="o">%</span><span class="mi">2</span><span class="p">)</span><span class="o">==</span><span class="mi">1</span><span class="p">){</span><span class="n">median</span><span class="o">=</span><span class="n">a</span><span class="p">[</span><span class="nb nb-Type">int</span><span class="p">(</span><span class="n">n</span><span class="o">/</span><span class="mi">2</span><span class="p">)];}</span>\
<span class="k">else</span><span class="p">{</span><span class="n">median</span><span class="o">=</span><span class="p">(</span><span class="n">a</span><span class="p">[</span><span class="n">n</span><span class="o">/</span><span class="mi">2</span><span class="p">]</span><span class="o">+</span><span class="n">a</span><span class="p">[</span><span class="n">n</span><span class="o">/</span><span class="mi">2</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span><span class="o">/</span><span class="mi">2</span><span class="p">;}</span>\
<span class="n">printf</span> <span class="s2">"</span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="s2"> </span><span class="si">%-10s</span><span class="se">\n</span><span class="s2">"</span><span class="p">,</span>\
<span class="n">sum</span><span class="p">,</span><span class="n">n</span><span class="p">,</span><span class="n">mean</span><span class="p">,</span><span class="n">std_dev</span><span class="p">,</span><span class="n">a</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="n">tp</span><span class="p">(</span><span class="o">.</span><span class="mi">01</span><span class="p">),</span><span class="n">tp</span><span class="p">(</span><span class="o">.</span><span class="mi">1</span><span class="p">),</span><span class="n">median</span><span class="p">,</span><span class="n">tp</span><span class="p">(</span><span class="o">.</span><span class="mi">9</span><span class="p">),</span><span class="n">tp</span><span class="p">(</span><span class="o">.</span><span class="mi">99</span><span class="p">),</span><span class="n">a</span><span class="p">[</span><span class="n">n</span><span class="o">-</span><span class="mi">1</span><span class="p">]}</span><span class="s1">'</span>
<span class="p">}</span>
</code></pre></div>
<p>Now, a very basic example:</p>
<div class="highlight"><pre><span></span><code># seq 1 10 | stats
1-SUM 2-COUNT 3-MEAN 4-STD_DEV 5-MIN 6-TP01 7-TP10 8-MEDIAN 9-TP90 10-TP99 11-MAX
55 10 5.5 3.02765 1 1 1 5.5 9 9 10
</code></pre></div>
<p>Then, what about the min & max hop length (in ms) when pinging <em>google.com</em> ?</p>
<div class="highlight"><pre><span></span><code><span class="gh">#</span> traceroute google.com | sed '1d;/\*/d' | awk '{print $(NF-1)}'
1-SUM 2-COUNT 3-MEAN 4-STD_DEV 5-MIN 6-TP01 7-TP10 8-MEDIAN 9-TP90 10-TP99 11-MAX
377.292 9 41.9213 14.8838 3.101 3.101 3.101 46.556 48.472 48.472 52.235
</code></pre></div>
<p>Finally, let's look at the sizes (in bytes) of all the files in <em>/var/log</em>:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># stat -c '%s' /var/log/* | stats --no-header</span>
<span class="mi">8129626</span> <span class="mi">119</span> <span class="mf">68316.2</span> <span class="mi">201958</span> <span class="mi">0</span> <span class="mi">0</span> <span class="mi">218</span> <span class="mi">5140</span> <span class="mi">168528</span> <span class="mi">704151</span> <span class="mi">1693462</span>
</code></pre></div>
<p>Of course, this little function is not meant to be used in production, where Python <a href="http://pandas.pydata.org">pandas</a> library would do a far better job.</p>
<p>Inspiration taken from <a href="//unix.stackexchange.com/a/13779/48906">a StackOverflow answer by Bruced Ediger</a> and <a href="//en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm">this Wikipedia online algorithm</a> to compute variance.</p>
<p><strong>EDIT</strong> [22/09/2014] : I found two very useful existing commands that do similar things: <a href="https://github.com/thorduri/ministat"><code>ministat</code></a> and <a href="//github.com/codahale/tinystat/blob/master/cmd/tinystat/main.go"><code>tinystat</code></a> written in Go.</p>
<p><strong>EDIT</strong> [8/12/2014] : Another great one : <code>csvstat</code> (install it with <code>pip install csvkit</code>).</p>Web scraping with Scrapy2014-09-09T11:09:00+02:002014-09-09T11:09:00+02:00Lucas Cimontag:chezsoi.org,2014-09-09:/lucas/blog/web-scraping-with-scrapy.html<p>As a tabletop RPG game master, whenever I need to imagine a universe background for a scenario, I need illustrations to picture myself the atmosphere, and get some inspiration.
I usually simply surf the web from blog to blog, or spend some time on inspirational websites like <a href="//www.deviantart.com">DeviantArt</a>, <a href="//www.tumblr.com">Tumblr</a>, <a href="http://unurth.com">Unurth …</a></p><p>As a tabletop RPG game master, whenever I need to imagine a universe background for a scenario, I need illustrations to picture myself the atmosphere, and get some inspiration.
I usually simply surf the web from blog to blog, or spend some time on inspirational websites like <a href="//www.deviantart.com">DeviantArt</a>, <a href="//www.tumblr.com">Tumblr</a>, <a href="http://unurth.com">Unurth</a> or <a href="http://www.darkroastedblend.com/">DarkRoastedBlend</a>.</p>
<p>But last week, I stumbled upon <a href="http://crpp0001.uqtr.ca/w4/campagne/images">Sylvain Robert great illustration gallery</a>.
It has thousands of fantasy images, from D&D to comics.</p>
<p>But... I was only looking for goblin illustrations.</p>
<p>Hence, I decided to scrape all the file names from the Apache directory index pages.</p>
<p>My first attemps involved <a href="http://linux.die.net/man/1/wget"><code>wget</code></a> and <a href="http://www.httrack.com"><code>httrack</code></a>, but I found no way to use their <em>spider</em> mode to only list the file names <strong>AND</strong> ignore urls containing the string <em>"fichiers/"</em>.</p>
<p>Hence, my fallback solution was <a href="http://scrapy.org">Scrapy</a>, which leverage all the flexibility and simplicty of Python for web crawling.</p>
<p>In a matter of minutes, I was able to write a very basic but functional scraper.
There is a refined version:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">Counter</span><span class="p">,</span> <span class="n">OrderedDict</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">scrapy</span>
<span class="kn">import</span> <span class="nn">urlparse</span>
<span class="k">class</span> <span class="nc">FileUrl</span><span class="p">(</span><span class="n">scrapy</span><span class="o">.</span><span class="n">Item</span><span class="p">):</span>
<span class="n">url</span> <span class="o">=</span> <span class="n">scrapy</span><span class="o">.</span><span class="n">Field</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">HtmlDirectoryCrawler</span><span class="p">(</span><span class="n">scrapy</span><span class="o">.</span><span class="n">Spider</span><span class="p">):</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="vm">__file__</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">url</span><span class="o">=</span><span class="s1">''</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">start_urls</span> <span class="o">=</span> <span class="p">[</span><span class="n">url</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">ext_counter</span> <span class="o">=</span> <span class="n">Counter</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">parse</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">response</span><span class="p">):</span>
<span class="k">for</span> <span class="n">href</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">xpath</span><span class="p">(</span><span class="s1">'//a/@href'</span><span class="p">)[</span><span class="mi">5</span><span class="p">:]:</span>
<span class="c1"># Skipping the 5 first hrefs: Name, Last modified, Size, Description, Parent Folder</span>
<span class="n">child_url</span> <span class="o">=</span> <span class="n">urlparse</span><span class="o">.</span><span class="n">urljoin</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">url</span><span class="p">,</span> <span class="n">href</span><span class="o">.</span><span class="n">extract</span><span class="p">())</span>
<span class="n">is_garbage</span> <span class="o">=</span> <span class="n">child_url</span><span class="o">.</span><span class="n">endswith</span><span class="p">(</span><span class="s1">'_fichiers/'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">is_garbage</span><span class="p">:</span>
<span class="k">continue</span>
<span class="n">is_folder</span> <span class="o">=</span> <span class="p">(</span><span class="n">child_url</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'/'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">is_folder</span><span class="p">:</span>
<span class="k">yield</span> <span class="n">scrapy</span><span class="o">.</span><span class="n">Request</span><span class="p">(</span><span class="n">child_url</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">parse</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">ext</span> <span class="o">=</span> <span class="n">child_url</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">'.'</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">ext_counter</span><span class="p">[</span><span class="n">ext</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">yield</span> <span class="n">FileUrl</span><span class="p">(</span><span class="n">url</span><span class="o">=</span><span class="n">child_url</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">closed</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">reason</span><span class="p">):</span>
<span class="n">ordered_ext_counter</span> <span class="o">=</span> <span class="n">OrderedDict</span><span class="p">(</span><span class="nb">sorted</span><span class="p">(</span>
<span class="bp">self</span><span class="o">.</span><span class="n">ext_counter</span><span class="o">.</span><span class="n">iteritems</span><span class="p">(),</span>
<span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="p">(</span><span class="n">k</span><span class="p">,</span><span class="n">v</span><span class="p">):</span> <span class="p">(</span><span class="n">v</span><span class="p">,</span><span class="n">k</span><span class="p">)))</span>
<span class="bp">self</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Stats on extensions found:</span><span class="se">\n</span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">ordered_ext_counter</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">4</span><span class="p">)),</span>
<span class="n">level</span><span class="o">=</span><span class="n">scrapy</span><span class="o">.</span><span class="n">log</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span>
</code></pre></div>
<p>At the end of the scraping, I wanted to know the count of files per extension. To do so, I incremented a <code>collections.Counter</code> for every file processed. Then, at the end, I used a <code>collections.OrderedDict</code> and <code>json.dumps</code> to pretty-print the dictionnary of extensions frequencies.</p>
<p>The following command runs the scraper and dumps its output in a file named <em>crpp0001.uqtr.ca.json</em> :</p>
<div class="highlight"><pre><span></span><code># time scrapy runspider --pdb -L INFO html_dir_crawler.py -a url=http://crpp0001.uqtr.ca/w4/campagne/images -o crpp0001.uqtr.ca.json
...
2014-09-09 13:47:48+0200 [html_dir_crawler.pyc] INFO: Spider opened
2014-09-09 13:47:48+0200 [html_dir_crawler.pyc] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2014-09-09 13:48:48+0200 [html_dir_crawler.pyc] INFO: Crawled 284 pages (at 284 pages/min), scraped 11343 items (at 11343 items/min)
2014-09-09 13:49:48+0200 [html_dir_crawler.pyc] INFO: Crawled 957 pages (at 673 pages/min), scraped 29275 items (at 17932 items/min)
2014-09-09 13:50:43+0200 [html_dir_crawler.pyc] INFO: Closing spider (finished)
2014-09-09 13:50:43+0200 [html_dir_crawler.pyc] INFO: Stats on extensions found:
...
"PNG": 60,
"URL": 69,
"jpe": 70,
"pdf": 134,
"html": 162,
"FCW": 194,
"zip": 216,
"BMP": 235,
"htm": 419,
"psd": 608,
"bmp": 657,
"jpeg": 854,
"fcw": 892,
"db": 1051,
"JPG": 1192,
"gif": 1872,
"png": 2989,
"jpg": 27162
}
2014-09-09 13:50:43+0200 [html_dir_crawler.pyc] INFO: Stored json feed (39340 items) in: crpp0001.uqtr.ca.json
...
real 0m45.489s
user 0m27.370s
sys 0m0.553s
</code></pre></div>
<p>And now I can search all those JPEG file names with a simple <code>grep</code> !</p>
<div class="highlight"><pre><span></span><code># grep -ic goblin crpp0001.uqtr.ca.json
35
# grep -i space crpp0001.uqtr.ca.json | grep -iv espace | wc -l
56
</code></pre></div>
<p><br></p>
<hr>
<p><br>
<strong>EDIT</strong>[8/10/2014]: as an exercise, you could try to scrape all the tags from <a href="http://taint.org/">Justin Mason Weblog</a> and then use <a href="http://tagcrowd.com">tagcrowd.com</a> (or the fantastic Javascript library <a href="http://www.jasondavies.com/wordcloud">d3-cloud</a>) to generate the following tags cloud:</p>
<p><img src="images/2014/Oct/jmason_weblog_tagscloud.png" alt="Justin Mason Weblog PNG Tags Cloud"/></p>Colorez les sorties de vos Makefile & scripts shell unix2014-07-24T16:07:00+02:002014-07-24T16:07:00+02:00Lucas Cimontag:chezsoi.org,2014-07-24:/lucas/blog/colorez-les-sorties-de-vos-scripts-shell-unix.html<p>Malgré leurs nombreux défauts <sup><a href="#fn1" id="ref1">[1]</a></sup>, les scripts <em>shell</em> restent bien pratiques. Pourquoi ? Parce qu'ils fonctionnent <strong>exactement</strong> comme l'interpréteur de ligne de commande de votre terminal Linux. Ils sont donc la solution la plus simple et rapide pour automatiser des enchaînement de commandes <sup><a href="#fn2" id="ref2">[2]</a></sup>.</p>
<p>Comme les scripts <em>shell</em> sont très sujets …</p><p>Malgré leurs nombreux défauts <sup><a href="#fn1" id="ref1">[1]</a></sup>, les scripts <em>shell</em> restent bien pratiques. Pourquoi ? Parce qu'ils fonctionnent <strong>exactement</strong> comme l'interpréteur de ligne de commande de votre terminal Linux. Ils sont donc la solution la plus simple et rapide pour automatiser des enchaînement de commandes <sup><a href="#fn2" id="ref2">[2]</a></sup>.</p>
<p>Comme les scripts <em>shell</em> sont très sujets aux erreurs d'exécution, il leur faudrait un article dédié pour détailler toutes les bonnes pratiques de programmation qui leur sont associées. Pour débuter, voici simplement comment ajouter rapidement et facilement de la couleur aux sorties de vos scripts.</p>
<p>Copiez le code suivant dans un fichier <em>bash_colors.sh</em> :</p>
<div class="highlight"><pre><span></span><code><span class="o">#</span> <span class="n">This</span> <span class="n">is</span> <span class="n">a</span> <span class="n">minimal</span> <span class="n">set</span> <span class="n">of</span> <span class="n">ANSI</span><span class="o">/</span><span class="n">VT100</span> <span class="n">color</span> <span class="n">codes</span>
<span class="n">_END</span><span class="o">=$</span><span class="s">'\x1b[0m'</span>
<span class="n">_BOLD</span><span class="o">=$</span><span class="s">'\x1b[1m'</span>
<span class="n">_UNDER</span><span class="o">=$</span><span class="s">'\x1b[4m'</span>
<span class="n">_REV</span><span class="o">=$</span><span class="s">'\x1b[7m'</span>
<span class="o">#</span> <span class="n">Colors</span>
<span class="n">_GREY</span><span class="o">=$</span><span class="s">'\x1b[30m'</span>
<span class="n">_RED</span><span class="o">=$</span><span class="s">'\x1b[31m'</span>
<span class="n">_GREEN</span><span class="o">=$</span><span class="s">'\x1b[32m'</span>
<span class="n">_YELLOW</span><span class="o">=$</span><span class="s">'\x1b[33m'</span>
<span class="n">_BLUE</span><span class="o">=$</span><span class="s">'\x1b[34m'</span>
<span class="n">_PURPLE</span><span class="o">=$</span><span class="s">'\x1b[35m'</span>
<span class="n">_CYAN</span><span class="o">=$</span><span class="s">'\x1b[36m'</span>
<span class="n">_WHITE</span><span class="o">=$</span><span class="s">'\x1b[37m'</span>
<span class="o">#</span> <span class="n">Inverted</span><span class="p">,</span> <span class="n">i</span><span class="p">.</span><span class="n">e</span><span class="p">.</span> <span class="n">colored</span> <span class="n">backgrounds</span>
<span class="n">_IGREY</span><span class="o">=$</span><span class="s">'\x1b[40m'</span>
<span class="n">_IRED</span><span class="o">=$</span><span class="s">'\x1b[41m'</span>
<span class="n">_IGREEN</span><span class="o">=$</span><span class="s">'\x1b[42m'</span>
<span class="n">_IYELLOW</span><span class="o">=$</span><span class="s">'\x1b[43m'</span>
<span class="n">_IBLUE</span><span class="o">=$</span><span class="s">'\x1b[44m'</span>
<span class="n">_IPURPLE</span><span class="o">=$</span><span class="s">'\x1b[45m'</span>
<span class="n">_ICYAN</span><span class="o">=$</span><span class="s">'\x1b[46m'</span>
<span class="n">_IWHITE</span><span class="o">=$</span><span class="s">'\x1b[47m'</span>
</code></pre></div>
<p>Ensuite, utilisez votre nouvelle palette de couleurs ainsi:</p>
<div class="highlight"><pre><span></span><code>source ./bash_colors.sh
echo "<span class="cp">${</span><span class="n">_BOLD</span><span class="cp">}${</span><span class="n">_UNDER</span><span class="cp">}${</span><span class="n">_ICYAN</span><span class="cp">}</span>Hello World<span class="cp">${</span><span class="n">_END</span><span class="cp">}</span>"
</code></pre></div>
<p>Et voici la sortie obtenue:</p>
<pre><span style="font-weight:bold;"></span><span style="text-decoration:underline;font-weight:bold;"></span><span style="background-color:teal;text-decoration:underline;font-weight:bold;">Hello World</span>
</pre>
<p>Cette astuce est complètement indépendante du type de <em>shell</em>, et devrait fonctionner avec <a href="http://hyperpolyglot.org/unix-shells">tous les descendants du vénérable <code>sh</code></a>, y compris <a href="//wiki.ubuntu.com/DashAsBinSh"><code>dash</code></a>.</p>
<p>Bien que très simple, cette technique est quelque peu limitée: pas moyen d'obtenir du texte <del>barré</del> ou <em>italique</em> car certains terminaux supportent ces styles, mais pas tous <sup><a href="#fn3" id="ref3">[3]</a></sup>.</p>
<p>Si vous avez des besoin plus complexes, je vous invite à jeter un oeil au manuel de la commande <code>tput</code> qui est bien plus expressive et portable. Vous obtiendrez le même résultat que ci-desssus en l'utilisant ainsi:</p>
<div class="highlight"><pre><span></span><code>echo "$(tput bold)$(tput smul)$(tput setab 6)Hello World$(tput sgr0)"
</code></pre></div>
<p>Dernière astuce: la commande <code>aha</code> permet de convertir les couleurs ANSI en HTML !</p>
<div class="highlight"><pre><span></span><code># echo "<span class="cp">${</span><span class="n">_BOLD</span><span class="cp">}${</span><span class="n">_UNDER</span><span class="cp">}${</span><span class="n">_ICYAN</span><span class="cp">}</span>Hello World<span class="cp">${</span><span class="n">_END</span><span class="cp">}</span>" | aha --no-header
<span class="nt"><span</span> <span class="na">style=</span><span class="s">"font-weight:bold;"</span><span class="nt">></span><span</span> <span class="na">style=</span><span class="s">"text-decoration:underline;font-weight:bold;"</span><span class="nt">></span><span</span> <span class="na">style=</span><span class="s">"background-color:teal;text-decoration:underline;font-weight:bold;"</span><span class="nt">></span>Hello World<span class="nt"></span></span>
</code></pre></div>
<p><br><hr><br></p>
<p><strong>EDIT [26/11/2014]</strong> : En complément, voici comment appliquer cette astuce à <code>make</code>. Vous pouvez tester le code ci-dessous en le plaçant dans une fichier <em>Makefile</em>, puis en exécutant <code>make</code> :</p>
<div class="highlight"><pre><span></span><code>cyan = /bin/echo -e "\x1b[36m\#\# $1\x1b[0m"
all:
@$(call cyan,"Hello world !")
</code></pre></div>
<p><br><hr><br></p>
<p><sup id="fn1">1. Entre autres limitations, les scripts <em>shell</em> ne fournissent pas de gestion sûre des exceptions/erreurs d'execution, ne sont pas portables sous Windows nativement (vive <a href="//www.cygwin.com">Cygwin</a> ! Une autre alternative : <a href="//github.com/BYVoid/Batsh">Batsh</a>), ne fournissent pas de concepts de programmation de "haut niveau" (classes, modules, closures...) et sont notoirement difficile à maintenir dès que le code devient un peu long. <a href="#ref1">↩</a></sup></p>
<p><sup id="fn2">2. Préférez néanmoins d'autres language pour des scripts robustes. <a href="//amoffat.github.io/sh">sh.py</a> par exemple est une excellente solution pour bénéficier à la fois de la simplicité des scripts shells et de tous les avantages du language Python. <a href="#ref2">↩</a></sup></p>
<p><sup id="fn3">3. <code>gnome-terminal</code> supporte par exemple ces deux styles, mais pas <a href="//github.com/lanoxx/tilda"><code>tilda</code></a>. Pour plus de détails sur les séquences de contrôle ANSI/VT100, jettez un oeil à l'article de <a href="http://misc.flogisoft.com/bash/tip_colors_and_formatting">FLOZz' MISC</a> et au <code>man terminfo</code> <a href="#ref3">↩</a></sup></p>Bonjour, monde !2014-07-16T13:07:00+02:002014-07-16T13:07:00+02:00Lucas Cimontag:chezsoi.org,2014-07-16:/lucas/blog/bonjour-monde.html<p>Ça y est, c'est ouvert !</p>
<p>Bienvenu sur mon petit coin d'Internet :)</p>
<p>Tout est encore en travaux, mais je compte rédiger rapidement quelques premiers articles.</p>
<p>Au passage:</p>
<ul>
<li>ce blog tourne sous <a href="//ghost.org">Ghost 0.5.8</a></li>
<li>le thème actuel est <a href="//github.com/semeano/DayAndNight">Day & Night</a>, réalisé par Pedro Semeano et sous License MIT</li>
<li><del>j'y ai …</del></li></ul><p>Ça y est, c'est ouvert !</p>
<p>Bienvenu sur mon petit coin d'Internet :)</p>
<p>Tout est encore en travaux, mais je compte rédiger rapidement quelques premiers articles.</p>
<p>Au passage:</p>
<ul>
<li>ce blog tourne sous <a href="//ghost.org">Ghost 0.5.8</a></li>
<li>le thème actuel est <a href="//github.com/semeano/DayAndNight">Day & Night</a>, réalisé par Pedro Semeano et sous License MIT</li>
<li><del>j'y ai ajouté un moteur de recherche en utilisant <a href="//github.com/i11ume/ghostHunter">ghostHunter</a> de Jamal Neufeld, qui ne cherche pas dans les tags malheureusement</del></li>
<li>et <a href="//highlightjs.org/static/test.html">highlight.js</a> fournit la coloration syntaxique</li>
<li>le logo "Little Pixel" a été réalisé par <a href="//www.behance.net/mythostasis">Elliot Jolivet</a>, un ami</li>
<li>la couverture du blog provient d'une <a href="//www.flickr.com/photos/t_e_brown/8677750589">photo</a> de Tom Brown sous License CC-BY-2.0</li>
<li>pour les backups, j'utilise un <a href="//github.com/dan-v/ghost-backup">script</a> de Dan Vittegleo</li>
</ul>
<p>Et un indice pour comprendre le nom du blog: <a href="//fr.wikipedia.org/wiki/Chaordique">la définition de "chaordique" sur Wikipédia</a>.</p>
<p>Enfin, un petit truc : vous pouvez basculer le contraste en mode "nuit" en appuyant sur le bouton noir en haut.</p>
<hr/>
<p><strong>EDIT</strong> [21/09/2014] :</p>
<ul>
<li>Ghost v0.4 -> v0.5.1</li>
<li>commentaires <a href="http://posativ.org/isso">ISSO</a> ajoutés. J'ai utilisé <a href="//tobrunet.ch/articles/comments-for-a-static-website-with-isso/">ce tuto</a> mais avec une configuration Apache légèrement différente:</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">WSGIPythonHome</span> <span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">www</span><span class="o">/</span><span class="n">lucas</span><span class="o">/</span><span class="n">python</span><span class="o">-</span><span class="n">virtualenv</span>
<span class="o">...</span> <span class="c1"># VirtualHost specific configuration</span>
<span class="n">WSGIScriptAlias</span> <span class="o">/</span><span class="n">lucas</span><span class="o">/</span><span class="n">isso</span> <span class="o">/</span><span class="k">var</span><span class="o">/</span><span class="n">www</span><span class="o">/</span><span class="n">lucas</span><span class="o">/</span><span class="n">isso</span><span class="o">/</span><span class="n">isso</span><span class="o">.</span><span class="n">wsgi</span>
<span class="n">WSGIDaemonProcess</span> <span class="n">isso</span><span class="o">-</span><span class="n">blog</span><span class="o">-</span><span class="n">lucas</span> <span class="n">user</span><span class="o">=</span><span class="n">isso</span> <span class="n">group</span><span class="o">=</span><span class="n">isso</span> <span class="n">threads</span><span class="o">=</span><span class="mi">5</span>
<span class="n">WSGIProcessGroup</span> <span class="n">isso</span><span class="o">-</span><span class="n">blog</span><span class="o">-</span><span class="n">lucas</span>
<span class="n">WSGIApplicationGroup</span> <span class="o">%</span><span class="p">{</span><span class="n">GLOBAL</span><span class="p">}</span>
</code></pre></div>