PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Cet article est trop gay

samedi 3 mai 2014 à 15:44

Je fréquente plus de LBG que la moyenne. Encore plus cette année, puisque deux de mes colocs ne sont pas hétéros, donc forcément avec les amis d’amis, ça charge mes statistiques.

Du coup, j’ai l’occasion de vivre du gay quotidien, et pas juste en soirée, fiesta ou autre événement. Bref, en mode caleçon, bière, pizza, film, sport en plein air et achat de pomme de terre.

Comme toujours, les minorités se tapent leurs lots de stéréotypes, que je peux confronter à mon petit échantillon non représentatif.

Plus d’activité sexuelle

C’est biaisé. Ce n’est pas vrai pour la majorité.

Répartition du nombre de personnes par nombre de partenaires sexuels et orientation sexuelle

Piqué à l'article linké plus haut

Par contre, c’est vrai que dans mon entourage, proportionnellement, un plus gros pourcentage de gays ont une vie sexuelle très active que parmi les hétéros. Je ne vais pas m’aventurer sur une explication du pourquoi, mais c’est un constat, que ce soit pour les filles et les mecs. D’ailleurs, ce sont les plus à l’aise pour en parler, même si généralement chez nous, ça parle de cul de toute façon et que j’ai des amis franchement délurés (sado-masos, partouseurs, stripteasers, etc). Finalement je suis super classique comme mec comparé à mes fréquentations.

Les homos essayent de convertir les hétéros

Non, mais par contre ils les draguent. Et certains font régulièrement des tentatives malgré un “non” explicite précédent. Ce n’est pas méchant, c’est parfois relou, mais envoyer chier un boulet collant est de bonne guerre pour tous les sexes. En prime, ça vous permet de vous mettre à la place des nanas qui se font draguer par des boulets.

Dans tous les cas, ils essayent, car parfois ça marche. La théorie d’un de mes potes c’est “un hétéro bourré est un hétéro suçable”. Il a par ailleurs rencontré son petit ami actuel en le pompant dans ma cuisine lors d’une soirée, ce qui fait qu’on a pas pu accéder aux petits fours pendant quelques temps.

Dessin de batman enculant Robin

Dans certains comics, Robin à 10 ans...

Même parmi ceux qui ne tentent pas, ils en a qui sont juste naturellement très tactiles, et si on est pas à l’aise avec le fait de faire calin-calin avec un gayzounours velus (les miens sont italienne et espagnol…), ça peut mettre mal à l’aise. Ca n’a rien à voir avec le côté homo/hétéro, c’est chiant y compris avec des straights.

Ce qui est important c’est d’être très simple et direct : oui ou non. Ici est la limite. Pour certain, la limite c’est “tu m’approches pas à moins de 2 m”, pour d’autre, c’est “ok pour le bisou sur la joue”. On s’en fout, tout est acceptable. Mais il faut le dire, et ne pas ruminer dans son coin, c’est idiot. Votre virilité/féminité n’est pas en jeu, votre confort, oui.

Qu’on se le dise, vous ne tomberez pas sur un gang bang surprise, et comme tout le monde, il y a les introvertis et les extravertis, donc ne pas partir sur une idée reçue. Les filles sont moins rentre dedans que les garçons en milieu mixte, par contre en boîte, tout est permis. C’est un peu le but.

On me signale dans la gayreillette : “nan mais moi je fais pas ça”. C’est vrai. Lui il fait pas ça. Vous voulez dire que les gays peuvent être des gens ordinnaires et classiques ? Noooooon ? Surpris ?

Les gays ont de l’argent

Encore une fois pas forcément, mais la majorité que je connais vivent bien. Genre, bien, bien. Mon pote qui lit l’article est en train de me dire “bon, ben tu me fileras mon chèque de gay alors, je l’attends toujours”. Mais il est de mauvaise foi, il vit avec moi dans un 200m2 avec 1000m2 de jardin. A 10m du tram, en ville. Y a pire.

En même temps, quand on a pas d’enfant à 30 ans, c’est généralement plus facile. Après j’ai une amie lesbienne qui galère tout le temps, elle est prof et elle roule à vélo, faut pas déconner, il n’y a pas un fond de financement secret des gays dont la mission est de les maintenir dans un état d’opulence. C’est pas con comme idée ça, je vais breveter.

Gir animé du film zombiland montrant un personnage essuyant ses larmes avec des billets

MRW I'm single with no children

Les gays sont sociables

J’ai envie de dire, pas plus que les autres. Mais en réfléchissant deux secondes, j’ai un couple qui est toujours fourré en soirée, un lesbienne qui est la fille la plus caline du monde, deux colocs qui font des soirées toutes les semaines, occasionnellement à 200 invités… Alors je vais rien dire… C’est peut être une coincidence…

Dessin de bisounours mangeant un humain

Ce qu'on fait vraiment dans les soirées à la maison

Les homosexuels sont des folles

Je n’en ai rencontré qu’un dans ma vie, donc c’est plutôt rare. Oui il y a des trais parfois particuliers, mais pas nécessairement le côté éphéminé pour les mecs ou camioneur pour les nanas. J’ai un très mauvais gaydar donc je ne saurais pas vous faire une liste, cela dit. Un gay n’est pas forcément artiste, honête, joyeux, fêtard, ou autre. Par contre, je note que tous ont une forme de sensibilité plus assumée. Bon, je note, je note, sur ceux que je connais, hein. Je note ce que je peux noter.

Le truc c’est que quand un gay est discret, vous ne saurez juste jamais qu’il l’est. C’est pas écrit sur sa tronche. Il y a les timides, ceux qui ne s’assument pas, ceux qui font comme tout le monde, etc.

Comprenez en tout cas, que la gay pride, c’est juste un événement festif, ce n’est pas représentatif d’un mode de vie. La fistinière non plus. Ca ne veut pas dire que ça ne peut pas l’être, évidement, mais perso notre dernière activité mixte, c’était une partie de magic bi VS lesbienne vs hétéro. Pas un défilé de cuir.

Affiche de la série "the new normal"

Je trouve génial que quand les médias essayent de rendre quelque chose ordinaire, ils le rendent tout de suite caricatural

Les bis aiment autant les mecs que les filles

Généralement, bien qu’ils puissent avoir des aventures avec les deux, il y a toujours un sexe qui prédomine. Il n’y a pas de régle, car il y autant de degré de biitude qu’il y a de bis, mais quand on aime la vanille et le chocolat, on a souvent une préférence pour un parfum, particulièrement pour les relations à long terme. Ce n’est pas forcément ce qu’ils vous diront, mais ça se voit dans les stats de dating, comme ont dit dans les milieux autorisés.

Si on essaye dresser un profile type, on va se planter dans tous les cas. On a aussi les asexuels, les asexués, les trans opérés, les trans non opérés, et je soupçonne un de mes amis d’aimer beaucoup son chien…

Ils / elles ont envie de me baiser

C’est possible. Moi y a plein de nana que j’ai envie de baiser.

Fille à gros seins et godzilla

Moi non plus

L’inverse est aussi tout à fait courant : aimer quelqu’un sans avoir envie de le baiser. Wow. Shoker.

Ils / elles ont le sida

Meh.

Le logo SEGA parodié en SIDA

Le sida, communément représenté par une boule pleine de pique qui court et ramasse des anneaux. Heu...

Ce sont des drogués

Beaucoup de mes amis consomment au moins une drogue (au minimum clope ou alcool), parfois 2/3 voir 4. Personnellement je ne fume pas et bois peu. Mais il n’y aucune différence entre les homos et les hétéros. C’est tout pareil.

flattr this!

La martingale à la roulette en Python

jeudi 1 mai 2014 à 12:17

Sur internet, c’est bien connu, on peut perdre du poids, trouver une fille facile près de chez soit, et gagner beaucoup d’argent rapidement et sans risque.

Une de ces techniques qu’on vous vend est la fameuse martingale, mais appliquée aux casinos en ligne. Outre le fait que vous ne pouvez pas vous fier à un serveur distant pour ne pas tricher, les jeux de hasard sont de toute façon toujours en faveur de la banque.

Nous allons nous intéresser à la martingale la plus simple, celle appliquée à la roulette. Quand j’étais à l’université, entre deux parties de Munchkins, nous cherchions un moyen de faire du pognon. Et on a failli s’organiser une session au casino de notre ville pour appliquer la technique suivante :

  1. Jouer une petite mise sur une couleur;
  2. Si on perd, revenir en 1 mais avec une mise équivalent au double de ses pertes.
  3. Si on gagne, encaisser, retourner en 1, mais avec la toute petite mise de départ.

L’idée était que si tu perdais, tu doublais ta mise jusqu’à ce que ça te rembourse tes pertes. Sinon, tu gagnes, et tu empoches. Seulement voilà, la roulette à la couleur, ce n’est pas 50% de chance, car il y a le zéro, qui n’est ni rouge, ni noir.

Comme on était des petits cons nuls en probas, mais des petits cons programmeurs, on avait simulé le jeu avec un programme Python, juste au cas où :

import random
 
# Une roulette (en France), c'est un 0 vert, et des numéros de
# 1 à 36 alternativement rouges et noirs.
roulette = ["green"]
roulette.extend("red" for i in range(1, 37, 2))
roulette.extend("black" for i in range(2, 37, 2))
 
# Regardons ce que donne la proba de choper le rouge avec le générateur
# de nombre pseudo-aléatoires de Python.
count = 0
for i in range(100000):
    count += random.choice(roulette) == "red"
print("Average chance of picking red: %s" % (count / 100000 * 100))
 
# Time to play ! Insérer ici la musique d'un film américain ambiance Las Vegas.
def play(rounds, budget=10000, color="red", start_bet=5):
    initial_budget = budget
    max_bet = start_bet
    bet = start_bet
    loss = 0
    # On limite le nombre de paris
    for round in range(rounds):
        # On mise
        budget -= bet
        if random.choice(roulette) != color:
            # On a perdu, on mise le double de ses pertes.
            loss += bet
            bet = loss * 2
            # Si jamais c'est plus que notre pognon, on se couche et on chiale.
            if bet > budget:
                break
            # On garde une trace de notre mise max pour évaluer le budget max.
            if (max_bet < bet):
                max_bet = bet
        else:
            # Si on gagne, on récupère notre mise et le gain, et on recommence.
            budget += bet * 2
            bet = start_bet
            loss = 0
 
    return budget - initial_budget, max_bet
 
print("10 rounds | balance: %s (max=%s)" % play(10))
print("100 rounds | balance: %s (max=%s)" % play(100))
print("1000 rounds | balance: %s (max=%s)" % play(1000))
print("10000 rounds | balance: %s (max=%s)" % play(1000))

Là le budget est large, mais nous on avait 800 euros… Bref, le résultat :

$ python3 roulette.py
Average chance of picking red: 48.78
10 rounds | balance: 10 (max=90)
100 rounds | balance: 1835 (max=2430)
1000 rounds | balance: -3585 (max=2430)
10000 rounds | balance: -3495 (max=2430)
 
$ python3 roulette.py
Average chance of picking red: 48.67
10 rounds | balance: 15 (max=30)
100 rounds | balance: -3620 (max=2430)
1000 rounds | balance: -3180 (max=2430)
10000 rounds | balance: -4670 (max=7290)
 
$ python3 roulette.py
Average chance of picking red: 48.507
10 rounds | balance: 45 (max=90)
100 rounds | balance: 1800 (max=2430)
1000 rounds | balance: -3370 (max=2430)
10000 rounds | balance: -3455 (max=2430)
 
$ python3 roulette.py
Average chance of picking red: 48.4
10 rounds | balance: 50 (max=30)
100 rounds | balance: 1940 (max=2430)
1000 rounds | balance: 1435 (max=7290)
10000 rounds | balance: -970 (max=7290)
 
$ python3 roulette.py
Average chance of picking red: 48.853
10 rounds | balance: -115 (max=270)
100 rounds | balance: -3535 (max=2430)
1000 rounds | balance: -2100 (max=7290)
10000 rounds | balance: -2120 (max=7290)

C’est pas vraiment la fortune assurée et en prime, dès qu’on veut jouer un peu sérieusement, il faut un budget max énorme. Mais surtout, il y a une grosse contrainte de temps, puisque 10 rounds c’est une bonne heure de jeu dans un casino réel.


Télécharger le code de l’article.

flattr this!

Input number et spin buttons

mercredi 30 avril 2014 à 11:43

Avec HTML5 on a plein de nouveaux widgets, et notamment la possibilité de définir plus de types pour les inputs. Le truc sympa par exemple, c’est qu’on peut dire :

<input type="number" />

Et là, le navigateur ne vous laisse saisir que des nombres, sans avoir à coder de JS. Mieux, les machines avec claviers virtuels (téléphones, tablettes, etc) affichent généralement un clavier spécial qui permet de saisir facilement des nombres.

Seulement dernièrement, il y a une nouvelle tendance, certains navigateurs affichent aussi des boutons pour incrémenter et décrémenter le champ.

Ça vous bousille tout votre design car c’est inconsistant selon selon les navs, et en plus ces spin buttons sont inutilisables sur mobiles car bien trop petits.

C’est quoi déjà les pas de la salsa ? Un pas en avant, un pas sur place, un pas en arrière, un pas sur place ?

Bref, j’ai d’abord essayé d’appliquer une solution CSSisante :

input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
 
input[type=number]{
    -moz-appearance:textfield;
}

Mais bien entendu ça ne marche pas partout, partout. Ça serait trop facile.

En bon dev Web, j’ai du coup utilisé un contournement tout pourri, c’est la marque de fabrique du métier :

<input type="tel" />

Le browser considère ainsi qu’il s’agit d’un numéro de téléphone. Il check donc si c’est un numéro (ou quelques signes comme “.”, “-”, etc, mais c’est acceptable) et fait apparaitre un clavier virtuel presque similaire au clavier pour type=number.

“On s’en contentera”, la devise de notre profession.

flattr this!

Service, factory et provider dans AngularJS

mardi 29 avril 2014 à 11:07

AngularJS est un framework difficile à prendre en main. Pas parce qu’il est particulièrement compliqué, mais parce que ses concepts sont vraiment différents de ceux qu’on a l’habitude de rencontrer dans les frameworks habituels. Le pire, c’est quand on vient de jQuery, car Angular est un peu l’anti-jQuery et il faut littéralement désapprendre ses habitudes.

Généralement, les gens s’en sortent avec les contrôleurs. Ils ne mettent pas le bon code dedans, ils ne savent pas comment rendre les bouts de code indépendants et réutilisables, mais ils arrivent à en faire quelque chose. Les directives, ils n’y touchent pas, mais ils peuvent s’en passer pendant un certain temps et juste réutiliser du code trouvé sur Github.

Par contre le côté service/factory/provider, ça c’est un gros problème. On ne peut pas faire sans, mais peu de gens savent faire avec. En codant All that counts, j’ai réalisé que c’était un bon tuto à faire. Donc en avant.

Article long = musique, évidement.

On m’a très justement demandé les prérequis pour suivre cet article : il faut avoir fait des tutos de base sur Angular et notamment comprendre l’injection de dépendance et le data binding.

Que font ces trucs là ?

Techniquement, un service, une factory ou un provider, dans AngularJS, ça sert à la même chose. Un service est juste une manière plus facile d’écrire une factory, qui est juste une manière plus simple d’écrire un provider.

Les 3 servent à créer un objet Javascript ordinaire, c’est tout.

Oui, oui, c’est vraiment tout. C’est le seul et unique but de cela. Vous allez me dire, mais alors pourquoi se faire chier à les utiliser alors qu’on peut écrire un objet à la main ? Tout simplement parce qu’ils permettent d’encapsuler cette création, l’isoler du reste du code, un problème toujours difficile en Javascript.

Ils sont là pour éviter de pourrir le namespace global, tout en n’utilisant pas la technique bien connu de “je met un gros conteneur avec tout dedans et tout le monde y accède”, qu’on a tendance à voir dans la plupart des projets JS. Le code est ainsi plus maintenable et testable.

Chaque service/factory/provider permet d’avoir un groupe de fonctionnalités séparé du reste. On peut utiliser celui-ci facilement partout ailleurs en utilisant l’injection de dépendance. Si vous ne savez pas ce que c’est, arrêtez-vous tout de suite, c’est le principe de fonctionnement de base d’Angular, il faut quitter cet article et prendre un tuto pour débutant. Revenez ensuite.

Dans un service/factory/provider, on va donc mettre du code métier et/ou des données de l’app, mais groupés par thème.

Par exemple, sur une app video, on peut en faire un qui va contenir le code pour gérer le profile utilisateur et un pour gérer la playlist.

On aura donc deux services/factories/providers, qui vont créer au final deux objets. Chaque objet va contenir des méthodes et des données. Pour le profile, on peut imaginer qu’il va contenir le nom d’utilisateur, son ID, et une méthode de login et de logout :

{
    "name": "Hodor",
    "Id": 7898,
    "login": function(){
        // faire un truc pour se loger
    },
    "logout": function(){
        // faire un truc pour se deco
    }
}

Pour la playlist, on peut imaginer que ça va contenir la liste des videos, la video en cours de lecture et de quoi passer à la suivante et précédente :

{
    "name" : "Super playlist"
    "videos": [
        {"title": "video 1", "url": "http://..."},
        {"title": "video 2", "url": "http://..."}
    ],
    "currentVideo": 1,
    "next": function(){
        // passer à la vid suivante
    },
    "previous": function(){
        // passer à la vid précédente
    }
 
}

Et c’est tout. Ce n’est ni plus ni moins que ça, des objets normaux dans lesquels ont va mettre nos données et nos méthodes. Ce qui est “special”, c’est comment ils sont créés et utilisés. Et c’est ça qu’on va voir tout de suite.

Créer un objet via un service/factory/provider

Voyons le plus compliqué d’abord : le provider. Les deux autres ne sont que des raccourcis pour écrire un provider, de toute façon.

Le rôle du provider, c’est de créer et mettre à disposition un objet. C’est tout. Il n’y a rien de plus. Je répète, ça ne sert qu’à ça. “provider”, ça veut dire “fournisseur” en anglais. Et c’est ce que ça fait : ça fournit un objet. Supposons que vous avez déjà créé une app “monApp”. Écrire un provider pour créer l’objet “profile” ressemble à ça :

// On appelle notre objet qui sera créé "profile". Ce qui signifie que l'on
// pourra ensuite utiliser le nom "profile" pour annoncer quand
// on souhaite utiliser l'objet issu de ce provider.
monApp.provider("profile", function(){
 
    // Un provider, au final, c'est juste une fonction anonyme qu'on relie
    // à un nom :)
 
    // Cette fonction DOIT avoir une méthode nommé "$get" attachée à son "this"
    this.$get = function() {
 
        // Et la méthode "$get" DOIT retourner l'objet qu'on veut créer.
        return {
            "name": "Anonymous",
            "Id": null,
            "login": function(){
                // faire un truc pour se loger
            },
            "logout": function(){
                // faire un truc pour se deco
            }
        }
    }
});

C’est tout. Je vous jure que c’est tout. C’est juste une boîte qu’Angular va prendre, et appeler la méthode $get pour obtenir un objet. En plus, un seul objet, car la méthode ne sera appelée qu’une fois : l’objet sera un singleton.

Plus tard dans le code, quand vous ferez un controller :

mymonAppApp.controller('UnControlleurCommeUnAutre', function($scope, profile) {
    $scope.profile = profile;
});

Vous utiliserez profile en paramètre, il va donc être injecté automatiquement. C’est tout le principe d’angular, des tas de boîtes qui déclarent quelles autres boîtes elles utilisent via l’injection de dépendance.

Ici, l’objet va être créé (si ce n’est pas déjà fait ailleurs), et c’est tout. Le boulot d’un controller, c’est essentiellement de mettre des objets issus des providers dans le scope pour que le HTML puisse les utiliser. Je vous promet, c’est 90% des use cases.

Votre objet sera le conteneur que l’on va manipuler pour changer le nom du profile, se logger, etc. C’est juste une boîte qui rend un service. D’ailleurs on appelle souvent les objets issus des providers des “services”.

Utiliser des providers évite de créer tout une logique de classes, de gérer des namespaces, des instanciations : on est obligé de faire de l’encapsulation et de la composition. Ca rend le code (javascript) plus propre et plus testable, bien décomposé et découplé. Avoir un code propre est le but principal des providers, ils ne fournissent rien comme fonctionnalités qu’on ne puisse faire autrement.

Maintenant voyons ce qu’est une factory. En fait, c’est juste un raccourci pour écrire un provider :

monApp.factory('profile', function() {
    return {
        "name": "Anonymous",
        "Id": null,
        "login": function(){
            // faire un truc pour se loger
        },
        "logout": function(){
            // faire un truc pour se deco
        }
    }
});

Ça fait exactement la même chose, et on l’utilise exactement pareil. C’est juste plus court. Pas de méthode $get à écrire, on retourne l’objet cash pistache.

Pourquoi on utilise un provider alors ? Parce que c’est plus pratique à configurer, comme on le verra plus bas. Mais la factory est suffisante la plupart du temps. Ce qui est marrant, c’est qu’en terme informatique, un provider est un design pattern “factory”, ce qui m’a vachement rendu confus au début…

Et le service alors ? C’est une syntaxe alternative :

monApp.service('profile', function() {
    this.name = "Anonymous";
    this.id = null;
    this.login = function(){
        // faire un truc pour se loger
    }
    this.logout = function(){
        // faire un truc pour se deco
    }
});

La fonction va être utilisée directement (avec new) pour créer l’objet, donc pas besoin de return : on attache tout à this. Mais le résultat est strictement le même. Le choix entre une factory et un service est vraiment une question de goût. Personnellement je vous conseille d’utiliser des factories car ça vous évite le casse-tête de la portée de this, grande cause d’erreurs en JS.

Dépendances et injections

Souvenez-vous qu’on peut faire ça :

mymonAppApp.controller('UnControlleurCommeUnAutre', function($scope, profile) {
    $scope.profile = profile;
});

Pour dire “mon controller dépend de profile, donc s’il te plait Angular, crée l’objet et passe le moi”.

Et bien ça marche aussi entre les providers/factories/services. Par exemple, si je fais un provider pour ma playlist, mais que la playlist dépend du profile :

monApp.provider("playlist", function(){
 
    // En utilisant le nom de l'autre provider ici, je demande à ce qu'il
    // soit injecté ici, et donc disponible ici. Je dis "ce provider
    // est dépendant de l'objet fournit par cet autre provider."
    this.$get = function(profile) {
 
        return {
            // Et du coup je peux accéder aux attributs de l'objet.
            "name": "Playlist de " + profile.name
            "videos": [],
            "currentVideo": 1,
            "next": function(){
                // passer à la vid suivante
            },
            "previous": function(){
                // passer à la vid précédente
            }
        }
    }
});

Si je le faisais avec une factory, se serait pareil, mais en plus simple :

// l'injection de dépendance se fait au niveau de la fonction anonyme
// car il n'y a pas de méthode "$get"
monApp.factory("playlist", function(profile){
    return {
        "name": "Playlist de " + profile.name
        "videos": [],
        "currentVideo": 1,
        "next": function(){
            // passer à la vid suivante
        },
        "previous": function(){
            // passer à la vid précédente
        }
    }
});

L’interêt des providers

L’interêt principal d’utiliser un provider plutôt qu’une factory, c’est qu’on peut le rendre configurable.

Par exemple, je veux que ma playlist ne soit jamais vide, alors si elle l’en, on met deux vidéos par défaut dedans :

monApp.factory("playlist", function(profile){
 
    // je ne retourne pas l'objet tout de suite, je vais le modifier
    var playlist = {
        "name": "Playlist de " + profile.name
        "videos": [],
        "currentVideo": 1,
        "next": function(){
            // passer à la vid suivante
        },
        "previous": function(){
            // passer à la vid précédente
        }
    };
 
    // hop, je m'assure que la playlist n'est jamais vide en modifiant
    // l'objet
    if (playlist.videos.length === 0){
        playlist.videos = [
            {"title": "video 1", "url": "http://..."},
            {"title": "video 2", "url": "http://..."}
        ]
    }
 
    // ne pas oublier de retourner l'objet à la fin quand même :)
    return playlist;
});

Tout ça sera largement suffisant dans la plupart des cas. Mais dans le rare cas où je veux faire une lib réutilisable, je veux que ces vidéos par défaut soient configurables. Comment alors permettre que l’utilisateur de ma lib les choisisse ?

En utilisant un provider :

monApp.provider("playlist", function(){
 
    // On déclare des données attachée sur le provider. PAS sur l'objet que
    // le provider va créer, attention. Sur le provider lui-même.
    // Le provider, je le rappelle, c'est cette fonction anonyme qui retourne
    // un objet via sa méthode "$get" (oui, les fonctions sont des objets
    // en JS, et peuvent avoir des méthodes. Ce langage est très clair.)
    this.defaultVideos = [
            {"title": "video 1", "url": "http://..."},
            {"title": "video 2", "url": "http://..."}
    ]
 
    // ensuite je fais ma méthode "$get" qui va retourner l'objet voulu,
    // comme d'hab
    this.$get = function(profile) {
 
        var playlist = {
            "name": "Playlist de " + profile.name
            "videos": [],
            "currentVideo": 1,
            "next": function(){
                // passer à la vid suivante
            },
            "previous": function(){
                // passer à la vid précédente
            }
        }
 
        // Au moment de la création de l'objet, j'utilise les données
        // attachées au provider pour fournir la valeur par défaut.
        if (playlist.videos.length === 0){
            playlist.videos = this.defaultVideos;
        }
 
        return playlist;
    }
});

Jusqu’ici, vous allez me dire, mais quelle est la différence avec le précédent ? Et bien il se trouve qu’Angular met à votre disposition automatiquement playlist, mais également playlistProvider, une référence sur le provider qui va créer l’objet playlist.

ATTENTION : quand vous déclarez ici “playlist”, “playlist” est le nom de l’objet créé par le provider. Angular, lui, va automagiquement attacher le nom “playlistProvider” au provider qui a créé l’objet. Vous n’avez rien à faire pour cela, il va automatiquement s’appeler “nomDeVotreObjetProvider” et sera mis à votre disposition.

Du coup :

// On peut utiliser "config" pour lancer du code avant la création des objets
// par les providers. Tout le code dans des blocs "config" est toujours lancé en premier,
// avant tous les providers/factories/services de cette app.
monApp.config(function(playlistProvider){
    // ici l'utilisateur de votre lib a accès au provider avant la création de
    // l'objet, et à donc le loisir de changer les valeurs par défaut
    playlistProvider.defaultVideos = [
            {"title": "Autre video", "url": "http://..."},
    ]
});

Ce n’est pas un cas courant, et la plupart du temps, utiliser une factory marchera très bien.

Et tout ça, je m’en sers pour quoi ?

On met dans les providers/services/factories, tout le code qui n’est pas lié à la navigation ou à la manipulation de DOM. Bref, tout le code de la logique de votre app. Le code qui n’est pas lié au Web. Un profile n’a rien à avoir avec le Web. Une playlist n’a rien à voir avec le Web. Mais il faut un code pour les gérer, et des variables vont devoir êtres mises quelque part.

Généralement, ces providers/services/factories sont utilisés :

Mais aussi, directement dans le HTML.

En effet, quand vous allez faire ça :

// le boulot du controller, c'est essentiellement d'attacher les bons
// services au scope, je vous dis !
mymonAppApp.controller('VideoCtrl', function($scope, playlist) {
    $scope.playlist = playlist;
});

Vous allez ensuite pouvoir déclarer votre controller dans votre HTML. Dans un
bloc de HTML couvert par un controller, vous avez accès à toutes les attributs
du scope, donc à votre service “playlist”. Et du coup, paf, à tout son code :

<div ng-controller="VideoCtrl">
    ...
    <p>Playlist :</p>
    <ul>
       <li ng-repeat="vid for playlist.videos">{{ vid.title }}</li>
    </ul>
    <p>
    <button ng-click="playlist.next()">Next</button>
    </p>
</div>

Et c’est ça la beauté d’Angular : tout est bien séparé, et bien rangé. Votre code métier dans les providers, votre code d’interface dans le HTML, votre liaison entre le HTML et les providers à travers les controllers… Enfin du code JS qui ne ressemble pas à des spaghetti trop cuites par un enfant de 6 ans scatoman.

flattr this!

Un Tic-Tac-Toe en HTML écrit en Python, sans framework, mais avec les pieds.

dimanche 27 avril 2014 à 10:28

Ceci est un post invité de 01ivier posté sous licence creative common 3.0 unported.

Mea Sam&Maxima Culpa

Tout d’abord, je me dois de vous présenter mes excuses.

Je vous avais laissés miroiter, lors de ma première intervention, des articles avec du code bien dégueulasse afin de décomplexer les plus nuls d’entre vous, mais, quand je me suis penché sur la rédaction de mon troisième article ainsi que sur le script que je vais vous présenter aujourd’hui, j’étais tellement embarrassé de ne pas comprendre moi-même ce que j’avais fait à l’époque que j’ai repoussé cette publication à un jour où j’aurais le temps de décortiquer mon propre sale travail.

Bien entendu, ce jour n’est jamais venu et vous n’avez plus entendu parler de moi.

Quelle misère.
Alors que j’avais sous les yeux un code illisible, d’une inutilité profonde et surtout particulièrement long pour ce qu’il avait à faire, je faisais la fine bouche et vous privais d’une véritable leçon de vie qui pourrait se résumer par “Comment faire n’importe quoi n’importe comment.”

Me voilà donc devant vous aujourd’hui pour tenter de me rattraper.

L’idée géniale.

En 1998, poussé par une intuition qui ne survient sur Terre qu’une fois tous les 69 ans, mon ami Frédéric s’est lancé dans l’écriture informatique d’un Tic-Tac-Toe afin de rendre un hommage appuyé à l’accadémie des neufs. Il a donc pondu, à la main, en HTML, toutes les combinaisons possibles pour pouvoir jouer au jeu.

Chez moi, qui découvrais à peine les homepages sur fond gris et les GIF animés “Work in progress…”, je peux vous assurer que cette initiative eut l’effet d’une bombe. Mon ami était un génie.

Une dizaine d’années plus tard, pour son anniversaire, je me suis dit que j’allais faire la même chose, mais en Python. Compte-tenu de mon niveau et de l’intérêt de la démarche, j’étais convaincu d’arriver à un résultat tout aussi absurde. Je n’ai pas été déçu.

Le résultat.

Avant d’aborder la manière dont je m’y suis pris et d’essuyer, de fait, les ricanements des plus véloces d’entre vous, je vous propose d’admirer le résultat. Car, c’est n’importe quoi, c’est fait n’importe comment, mais ça marche :

Cliquez ici pour être certain de ne pas gagner.

La mise en Œuvre.

Le script s’appuie sur la méthode .format() qui permet de faire des choses émouvantes comme :

>>> toto = "J'ha{1} Orleans pour son cor{0} artis{3} suc{2}ent."
>>> toto.format("bite", "nichon", "anal", "cul")
"J'hanichon Orleans pour son corbite artiscul sucanalent."

À noter que si vous faites…

>>> toto = "J'ha{1} Orleans pour son cor{0} artis{3} suc{2}ent."
>>> titi = ("bite", "nichon", "anal", "cul")
>>> toto.format(titi)

…vous allez récolter un IndexError parce que, même si titi contient 4 éléments, vous ne filez qu’un seul argument à .format() alors qu’il en a besoin de 4. Il faut donc faire…

>>> toto.format(*titi)

… où l’étoile vous dépackagera votre tuple bien comme il faut (mais ne me demandez pas pourquoi).

Armé de cette subtilité, j’ai créé une page HTML avec un petit tableau de 3×3 puis j’ai écrit dans chacune des cellules {0}, {1}, {2}… pour pouvoir les remplacer par le texte

<image src="rond.png" />

si j’avais besoin d’y placer un rond, ou par

<image src="croix.png" />

si j’avais besoin d’y mettre une croix.

Ici, par exemple, je vois bien que j’ai numéroté les cellules en faisant un petit escargot et non pas ligne par ligne, mais je n’ai aucune idée de pourquoi j’ai fait ce choix. J’ai dû vouloir faire une blague. Si ça vous fait rire, c’est qu’elle est réussie. Moi, je suis resté de marbre.

<table >
  <tr>
    <td>{0}</td>
    <td>{1}</td>
    <td>{2}</td>
  </tr>
  <tr>
    <td>{7}</td>
    <td>{8}</td>
    <td>{3}</td>
  </tr>
  <tr>
    <td>{6}</td>
    <td>{5}</td>
    <td>{4}</td>
  </tr>
</table>

Puis, pour exporter chaque page HTML, j’ai écrit cette petite fonction…

  def ecriturePage (namePage, contenuPage):
    with open ('{0}.html'.format(namePage), 'w') as new_file:
        new_file.write (contenuPage)

…qui récupère le nom de la page, son contenu et l’enregistre dans le dossier courant.

Ensuite…

Ensuite, c’est plus compliqué.
De ce que je comprends, j’initialise une liste que je réécris partiellement en fonction des combinaisons recherchées.

C’est un peu flou, mais, ce qui est sûr c’est que je n’ai pas automatisé grand chose, surtout sur la fin…
J’ai rempli des tas de feuilles de brouillon pour trouver les bonnes positions pour les croix et les ronds à chaque étape… et je les ai retranscrites avec des for des if et des else

Pour la dernière étape, je suis sûr qu’il eut été plus rapide pour moi d’écrire les pages HTML directement à la main, mais, j’aurais alors lamentablement échoué dans ma mission et mon ami m’aurait sans nul doute frappé violemment au visage si je lui avais présenté ce résultat médiocre comme cadeau pour son anniversaire.

J’ai donc persévéré.

La récompense.

Vous l’aurez compris, cette décision absurde s’est révélée extrêmement judicieuse.

En effet, concentré que j’étais dans l’obtention de toutes les pages nécessaires pour jouer, je n’ai absolument pas prêté attention à la non-obtention des pages inutiles…

Et je me suis retrouvé avec ce joyau.

J’avais fait écrire une combinaison qui n’était pas possible.
J’avais créé un futur pour le jeu qui, bien qu’existant, ne lui était pas accessible.

MON DIEU QUEL PUR MOMENT DE BONHEUR !!!

Le code

L’archive avec l’index.html, les images, le CSS… est disponible ici.

Pour créer toutes les pages (dont ces bijoux de pages inutiles) il suffit de lancer le fichier GO.py.

Mais pour les plus impatients, voici l’intégralité du code :

# -*- coding: utf-8 -*-
# Ce programme écrit toutes les pages html nécessaires pour jouer au Tic-Tac-Toe 
# En partant du principe que l'ordinateur ne doit pas perdre, donc commence...
 
c = range (10)
rond = '<image src="rond.png" />'
croix = '<image src="croix.png" />'
croixR = '<image src="croixR.png" />'
back = """<a href="index.html"> Pour recommencer, c'est ici... </a>"""
 
def page (grille):
    return('''
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <link rel="stylesheet" type="text/css" href="style.css" />
  <link rel="icon" type="image/png" href="favicon.png" />
  <title>Y a matière à... l académie des neuf</title>
 </head>  
 
<body> 
 
<div class="grille">
<table >
<tr>
<td>{0}</td>
<td>{1}</td>
<td>{2}</td>
</tr>
<tr>
<td>{7}</td>
<td>{8}</td>
<td>{3}</td>
</tr>
<tr>
<td>{6}</td>
<td>{5}</td>
<td>{4}</td>
</tr>
</table>
</div>
 
<div class="redo">
{9}
</div>
 
 
</body>
 
</html>
'''.format(*grille))
 
def lien (link):
    return ('<a href="{0}.html"><image src="vide.png" /></a>'.format(link))
 
 
def ecriturePage (namePage, contenuPage):
    with open ('{0}.html'.format(namePage), 'w') as new_file:
        new_file.write (contenuPage)
 
def grilleVide (num):
 
    for i in range (8):
        c[i] = lien ("{0}{1}".format(num, i))
        c[8] = croix
        c[9] = ""
 
def grilleFin ():
 
    for i in range (8):
        c[i] = '<image src="vide.png" />'
        c[8] = croix
        c[9] = back
 
 
####### 1er clic
 
for i in range (8):
 
    grilleVide(i)
    c[i] = rond
 
    if i !=1 and i !=5:    
        c[1] = croix        
    else:
        c[7] = croix
 
    ecriturePage(i, page(c))        
 
####### 2eme clic
 
for i in range (8):
 
    if i == 0:
 
        for j in range (8):
 
            if j !=5:
                grilleFin()
                c[1] = croixR
                c[i] = rond
                c[j] = rond
                c[5] = croixR
                c[8] = croixR   
 
            else:
                grilleVide("{0}{1}".format(i, j))
                c[1] = croix
                c[i] = rond
                c[j] = rond
                c[6] = croix
 
            ecriturePage("{0}{1}".format(i, j), page(c))
 
    if i == 2:
 
        for j in range (8):
 
            if j !=5:
                grilleFin()
                c[1] = croixR
                c[i] = rond
                c[j] = rond
                c[5] = croixR
                c[8] = croixR 
 
            else:
                grilleVide("{0}{1}".format(i, j))
                c[1] = croix
                c[i] = rond
                c[j] = rond
                c[4] = croix
 
            ecriturePage("{0}{1}".format(i, j), page(c))
 
    if i == 7:
 
        for j in range (8):
 
            if j !=5:
                grilleFin()
                c[1] = croixR
                c[i] = rond
                c[j] = rond
                c[5] = croixR
                c[8] = croixR 
 
            else:
                grilleVide("{0}{1}".format(i, j))
                c[1] = croix
                c[i] = rond
                c[j] = rond
                c[2] = croix
 
            ecriturePage("{0}{1}".format(i, j), page(c))
 
    if i == 3:
 
        for j in range (8):
 
            if j !=5:
                grilleFin()
                c[1] = croixR
                c[i] = rond
                c[j] = rond
                c[5] = croixR
                c[8] = croixR 
 
            else:
                grilleVide("{0}{1}".format(i, j))
                c[1] = croix
                c[i] = rond
                c[j] = rond
                c[0] = croix
 
            ecriturePage("{0}{1}".format(i, j), page(c))
 
 
    elif i == 1:
 
        for j in range (8):
 
 
            if j !=3:
                grilleFin()
                c[7] = croixR
                c[i] = rond
                c[j] = rond
                c[3] = croixR
                c[8] = croixR 
            else:
                grilleVide("{0}{1}".format(i, j))
                c[7] = croix
                c[i] = rond
                c[j] = rond
                c[6] = croix
 
            ecriturePage("{0}{1}".format(i, j), page(c))
 
    elif i == 5:
 
        for j in range (8):
 
            if j !=3:
                grilleFin()
                c[7] = croixR
                c[i] = rond
                c[j] = rond
                c[3] = croixR
                c[8] = croixR 
            else:
                grilleVide("{0}{1}".format(i, j))
                c[7] = croix
                c[i] = rond
                c[j] = rond
                c[6] = croix
 
            ecriturePage("{0}{1}".format(i, j), page(c))
 
    elif i == 6:
 
        for j in range (8):
 
            if j !=5:
                grilleFin()
                c[1] = croixR
                c[i] = rond
                c[j] = rond
                c[5] = croixR
                c[8] = croixR 
            else:
                grilleVide("{0}{1}".format(i, j))
                c[1] = croix
                c[i] = rond
                c[j] = rond
                c[4] = croix
 
            ecriturePage("{0}{1}".format(i, j), page(c))
 
    elif i == 4:
 
        for j in range (8):
 
            if j !=5:
                grilleFin()
                c[1] = croixR
                c[i] = rond
                c[j] = rond
                c[5] = croixR
                c[8] = croixR 
            else:
                grilleVide("{0}{1}".format(i, j))
                c[1] = croix
                c[i] = rond
                c[j] = rond
                c[6] = croix
 
            ecriturePage("{0}{1}".format(i, j), page(c))
 
 
#######3eme clic                
 
for i in range (8):
 
    if i in (0, 4):
 
        j = 5
 
        for k in range (8):
 
            if k !=2:
                grilleFin()
                c[1] = croix
                c[i] = rond
                c[j] = rond
                c[6] = croixR
                c[k] = rond
                c[2] = croixR
                c[8] = croixR 
 
            else:
                grilleVide("{0}{1}{2}".format(i, j, k))
                c[1] = croix
                c[i] = rond
                c[j] = rond
                c[6] = croix
                c[k] = rond
                c[3] = croix
 
            ecriturePage("{0}{1}{2}".format(i, j, k), page(c))
 
    if i in (2, 6):
 
        j = 5
 
        for k in range (8):
 
            if k !=0:
                grilleFin()
                c[1] = croix
                c[i] = rond
                c[j] = rond
                c[4] = croixR
                c[k] = rond
                c[0] = croixR
                c[8] = croixR 
 
            else:
                grilleVide("{0}{1}{2}".format(i, j, k))
                c[1] = croix
                c[i] = rond
                c[j] = rond
                c[4] = croix
                c[k] = rond
                c[7] = croix
 
            ecriturePage("{0}{1}{2}".format(i, j, k), page(c))
 
 
    if i == 7:
 
        j = 5
 
        for k in range (8):
 
            if k in (0, 3, 4):
                grilleFin()
                c[1] = croix
                c[i] = rond
                c[j] = rond
                c[2] = croixR
                c[k] = rond
                c[6] = croixR
                c[8] = croixR 
 
            elif k == 6:
                grilleFin()
                c[1] = croixR
                c[i] = rond
                c[j] = rond
                c[2] = croixR
                c[k] = rond
                c[0] = croixR
 
            ecriturePage("{0}{1}{2}".format(i, j, k), page(c))
 
    if i == 3:
 
        j = 5
 
        for k in range (8):
 
            if k in (2, 6, 7):
                grilleFin()
                c[1] = croix
                c[i] = rond
                c[j] = rond
                c[0] = croixR
                c[k] = rond
                c[4] = croixR
                c[8] = croixR 
 
            elif k == 4:
                grilleFin()
                c[1] = croixR
                c[i] = rond
                c[j] = rond
                c[0] = croixR
                c[k] = rond
                c[2] = croixR
 
            ecriturePage("{0}{1}{2}".format(i, j, k), page(c))
 
 
    if i == 1:
 
        j = 3
 
        for k in range (8):
 
            if k in (0, 4, 5):
                grilleFin()
                c[7] = croix
                c[i] = rond
                c[j] = rond
                c[6] = croixR
                c[k] = rond
                c[2] = croixR
                c[8] = croixR 
 
            elif k == 2:
                grilleFin()
                c[7] = croixR
                c[i] = rond
                c[j] = rond
                c[6] = croixR
                c[k] = rond
                c[0] = croixR
 
            ecriturePage("{0}{1}{2}".format(i, j, k), page(c))
 
    if i == 5:
 
        j = 3
 
        for k in range (8):
 
            if k in (0, 1, 4):
                grilleFin()
                c[7] = croix
                c[i] = rond
                c[j] = rond
                c[6] = croixR
                c[k] = rond
                c[2] = croixR
                c[8] = croixR 
 
            elif k == 2:
                grilleFin()
                c[7] = croixR
                c[i] = rond
                c[j] = rond
                c[6] = croixR
                c[k] = rond
                c[0] = croixR
 
            ecriturePage("{0}{1}{2}".format(i, j, k), page(c))
 
 
######4eme clic
 
for i in range (8):
 
    if i == 0:
 
        grilleFin()
        j = 5
        k = 2
        l = 4
        c[1] = croix
        c[i] = rond
        c[j] = rond
        c[6] = croix
        c[k] = rond
        c[3] = croixR
        c[l] = rond
        c[7] = croixR
        c[8] = croixR
 
        ecriturePage("{0}{1}{2}{3}".format(i, j, k, l), page(c))
 
        l = 7
        c[3] = croix
        c[l] = rond
        c[7] = croix
        c[8] = croix
        c[l] = rond
        c[4] = croix
 
        ecriturePage("{0}{1}{2}{3}".format(i, j, k, l), page(c))
 
    if i == 2:
 
        grilleFin()
        j = 5
        k = 0
        l = 3
        c[1] = croix
        c[i] = rond
        c[j] = rond
        c[4] = croix
        c[k] = rond
        c[7] = croix
        c[l] = rond
        c[6] = croix
 
        ecriturePage("{0}{1}{2}{3}".format(i, j, k, l), page(c))
 
        l = 6
        c[l] = rond
        c[3] = croixR
        c[7] = croixR
        c[8] = croixR
 
        ecriturePage("{0}{1}{2}{3}".format(i, j, k, l), page(c))
 
 
    if i == 4:
 
        grilleFin()
        j = 5
        k = 2
        l = 7
        c[1] = croix
        c[i] = rond
        c[j] = rond
        c[6] = croix
        c[k] = rond
        c[3] = croix
        c[l] = rond
        c[0] = croix
 
        ecriturePage("{0}{1}{2}{3}".format(i, j, k, l), page(c))
 
        l = 0
        c[l] = rond
        c[7] = croixR
        c[3] = croixR
        c[8] = croixR
 
        ecriturePage("{0}{1}{2}{3}".format(i, j, k, l), page(c))
 
    if i == 6:
 
        grilleFin()
        j = 5
        k = 0
        l = 3
        c[1] = croix
        c[i] = rond
        c[j] = rond
        c[4] = croix
        c[k] = rond
        c[7] = croix
        c[l] = rond
        c[2] = croix
 
        ecriturePage("{0}{1}{2}{3}".format(i, j, k, l), page(c))
 
        l = 2
        c[l] = rond
        c[3] = croixR
        c[7] = croixR
        c[8] = croixR
 
        ecriturePage("{0}{1}{2}{3}".format(i, j, k, l), page(c))

Je ne doute pas un seul instant que ces lignes vont vous ouvrir les yeux sur le sens de la Vie et que tout le reste n’aura plus trop d’importance, mais retenez tout de même que la première personne à être concernée par les commentaires dans vos codes, c’est vous. Et que si vous ne voulez pas vous retrouver, comme moi, avec des scripts imbitables, prenez le temps de les commenter (avec des commentaires à jour, bien entendu, sinon, autant écrire en JavaScript :-p )

À bientôt…

flattr this!