Le « Plus / Moins » le plus horrible du siècle !

a marqué ce sujet comme résolu.

Plus que le code en lui meme, une explication par leurs auteurs des elements horribles serait la bienvenue, comme dans ce message de superlama. N'etant pas familier avec tous les langages utilises ci-dessus, j'aimerais pouvoir comprendre et apprendre. :)

+2 -0

Vive les lambda fonctions :p

1
2
3
from random import randint
v = randint(0,99)
while "Gagné !" != (lambda r : r if not print(r) else r)((lambda v1, v2 : ("+" if v1 < v2 else None) or ("-" if v1 > v2 else None) or ("Gagné !" if v1 == v2 else None))(int(input(">>")), v)):pass

Edit (pour les explications) : J'ai un peu caricaturé une tendance que j'ai à vouloir gagner du temps en faisant au plus court sur les fonctions "simple" de mes programmes dans le but de m'occuper des points plus intéressants ^^ .

+0 -0

J'ai clarifié le premier message et je re-poste la clarification ici :

Le formattage ne compte pas, l'idée est plutôt de faire un code intelligent, mais horriblement mal conçu et/ou inutilement compliqué. Et surtout, expliquez ce qui est mal fait et pourquoi.

L'un des buts derrière cet atelier, c'est aussi et surtout de réfléchir aux différentes « bonnes pratiques », possibilités et limitations des langages ; et de leurs raisons d'être.

Je mettrai à jour le reste ce soir.

Python est un langage très expressif et souvent les utilisateur intermédiaires cherche à factoriser leur code pour le rendre le plus court possible, au détriment de la lisibilité.

Donc voila un code relativement logique mais en one-liner :

1
(lambda randrange: (lambda nombrePropose, nombreMystere: (lambda _, nombrePropose, nombreMystere: (lambda nombrePropose, nombreMystere, while_loop : while_loop(nombrePropose, nombreMystere, while_loop))(nombrePropose, nombreMystere, (lambda nombrePropose, nombreMystere, me: (lambda _, nombreMystere, me : (lambda nombrePropose, nombreMystere, me :(lambda _, nombrePropose, nombreMystere, me : me(nombrePropose, nombreMystere, me))((lambda nombrePropose, nombreMystere : print("C'est trop petit !\n") if nombrePropose < nombreMystere else (print("C'est trop grand !\n") if nombrePropose > nombreMystere else print("Félicitations, vous avez trouvé le nombre mystère !!!\n")))(nombrePropose, nombreMystere), nombrePropose, nombreMystere, me))(int(input()), nombreMystere, me))(print("Quel est le nombre ?"), nombreMystere, me) if nombrePropose != nombreMystere else None)))(print("=== LE JEU DU PLUS OU MOINS ==="), nombrePropose, nombreMystere))(0, randrange(1, 100)))(__import__("random", fromlist=("randrange",)).randrange)

Et je vois que klafyvel a eu la même idée que moi sauf que manifestement il a pas voulu poussé le concept à fond.

J'ai les étapes interméidaires si ça interesse du monde, c'est juste que ça me prendra un peu de temps à expliquer

edit:

Le code est strictement equivalent à celui-ci, trouvé sur le net

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from random import randrange

#On initie la variable nombrePropose à n'importe quelle valeur sauf entre 1 et 100 sinon il se pourrait que se soit précisement le nombre mystère !.
nombrePropose = 0

#Un titre pour faire joli.
print("=== LE JEU DU PLUS OU MOINS ===")

#On génère le nombre mystère.
nombreMystere = randrange(1, 100)

#Boucle qui continuera tant que l'utilisateur n'aura pas trouvé le nombre.
while nombrePropose != nombreMystere:

    print("Quel est le nombre ?")

    #L'utilisateur propose son nombre.
    nombrePropose = int(input())

    #Si le nombre est trop petit...
    if nombrePropose < nombreMystere:
        print("C'est trop petit !\n")

    #Sinon si le nombre est trop grand...
    elif nombrePropose > nombreMystere:
        print("C'est trop grand !\n")

    #Sinon (sous-entendu : si on a trouvé le nombre)...
    else:
        print("Félicitations, vous avez trouvé le nombre mystère !!!\n")
+1 -0

Ta version n'en est pas loin. Il faut que tu utilise __import__ pour permettre de faire l'import comme une expression et donc pouvoir le passer en paramètre d'une lambda.

Le point un peu technique c'est le while. Il faut faire une fonction récursive et faire en sorte de la passer en paramètre à elle-même… C'est un pattern connu, le fixed-point combinator ou Y-combinator. C'est vraiment ça le plus chiant à faire.

Personnellement j'ai joué avec la sur-ingénérie qu'on trouve hélas trop souvent en Java en entreprise.

Donc, comme dans tout projet qui se respecte, le code doit être Souple et Maintenable, mais aussi Adaptable aux Besoins du client. Et pour ce faire, on va respecter les Bonnes Pratiques et utiliser des Design Pattern :

  1. On se base évidemment sur une architecture Modèle / Vue / Contrôleur
  2. Pour pouvoir facilement faire évoluer les implémentations, donc on définit presque systématiquement des interfaces et des implémentations (ici les versions par défaut)
  3. On utilise des factory pour créer ces implémentations
  4. Ces factory sont des singletons
  5. En cas de crash de l'appli, par mesure de sécurité, on doit pouvoir revenir à l'état avant le crash, donc on sauvegarde l'état du jeu au fur et à mesure.
  6. Comme dans tout projet réaliste, la fonction qui permet de restaurer cet état n'existe pas
  7. Cette sauvegarde utilise un DAO (backend de fichiers par défaut)
  8. Pour plus de souplesse, les actions « métier » sont gérées par un pattern Task / Command (et j'ai été sympa, je ne vous ai fait que des commandes).
  9. Lequel pattern utilise évidemment tout ce qui a été dit plus haut en terme d'interfaces et de factory.
  10. Les traductions sont dans un fichier de traductions
  11. Toutes les constantes sont centralisées dans un fichier de constantes
  12. Il y a des generics

On pourrait encore améliorer le système des façons suivantes, mais j'ai la flemme :

  • Utiliser un système pour configurer les implémentations à utiliser via des fichiers de conf
  • Mettre des commentaires © Captain Obvious
  • Maven et un framework / des tas de libs bien lourdes
  • Utiliser de l'asynchrone
  • Je suis sûr qu'on peut caler un pattern Observer, des spécificités Java 8 (la version actuelle devrait compiler avec Java 7, sans garantie) et de annotations personnalisées quelque part.

Vous comprendrez donc que comme c'est du Java, le code est verbeux (et il est évident que cette verbosité ne vient que du langage), et donc que je ne le colle pas ici. Le code est disponible sur Github.

Comme je suis sympa, je vous donne quand même l'arborescence :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
spacefox@azathoth:~/IdeaProjects/PlusMoins$ tree src
src
├── info
│   └── kisai
│       └── plusmoins
│           ├── command
│           │   ├── Command.java
│           │   ├── impl
│           │   │   ├── InitGameCommandDefaultImpl.java
│           │   │   ├── MainLoopCommandDefaultImpl.java
│           │   │   └── ProcessAttemptCommandDefaultImpl.java
│           │   ├── InitGameCommand.java
│           │   ├── MainLoopCommand.java
│           │   └── ProcessAttemptCommand.java
│           ├── controller
│           │   ├── Game.java
│           │   └── impl
│           │       └── GameDefaultImpl.java
│           ├── factory
│           │   ├── Factory.java
│           │   ├── GameFactory.java
│           │   ├── impl
│           │   │   ├── GameDefaultFactory.java
│           │   │   ├── InitGameCommandDefaultFactory.java
│           │   │   ├── InputDefaultFactory.java
│           │   │   ├── MainLoopCommandDefaultFactory.java
│           │   │   ├── OutputDefaultFactory.java
│           │   │   └── ProcessAttemptCommandDefaultFactory.java
│           │   ├── InitGameCommandFactory.java
│           │   ├── InputFactory.java
│           │   ├── MainLoopCommandFactory.java
│           │   ├── OutputFactory.java
│           │   └── ProcessAttemptCommandFactory.java
│           ├── Main.java
│           ├── model
│           │   ├── bean
│           │   │   ├── Attempt.java
│           │   │   ├── Bean.java
│           │   │   ├── Guess.java
│           │   │   └── impl
│           │   │       ├── AttemptDefaultImpl.java
│           │   │       ├── BeanDefaultImpl.java
│           │   │       └── GuessDefaultImpl.java
│           │   └── dao
│           │       ├── AttemptDao.java
│           │       ├── DaoFactory.java
│           │       ├── exception
│           │       │   └── DAOException.java
│           │       ├── FileDaoImplementation.java
│           │       ├── GuessDao.java
│           │       └── impl
│           │           ├── AttemptDaoDefaultImpl.java
│           │           ├── DaoFactoryDefaultImpl.java
│           │           └── GuessDaoDefaultImpl.java
│           ├── util
│           │   ├── Labels.java
│           │   ├── Status.java
│           │   └── Utils.java
│           └── view
│               ├── impl
│               │   ├── InputDefaultImpl.java
│               │   └── OutputDefaultImpl.java
│               ├── Input.java
│               └── Output.java
└── LabelsBundle_fr.properties

Toute l'astuce réside dans le fait que à priori aucun des points soulevés n'est en soit mauvais (sauf le 6). Par contre, leur accumulation au sein d'un même projet si petit…

PS : le truc le plus effrayant avec ce code ? Sa ressemblance avec des projets que j'ai croisés dans ma vie professionnelle…

Salut,
Je viens de faire ma version de ce projet en C#… (dépôt GitHub)

Moyens utilisés
  • Les magnifiques dialogues du jeu sont enregistrés dans un fichier .resx (ressource C#), avec pour entrées des noms d'agrumes.
  • Les variables aussi ont des noms d'agrumes.
  • Utilisation des if goto à la place de if lambdas.
  • Commentaires inutiles au début de chaque "bloc".
  • Utilisation des noms complets pour toutes les classes (System.String et non String)
  • Parenthèses, points-virgule et accolades inutiles.

@SpaceFox : Si je puis me permettre, il manque quand même la génération automatique des 9/10 du code via des outils basés par exemple sur des diagrammes UML.

Et puis, je suis désolé, mais il y a bien trop de constantes magiques dans le code. Par exemple, dans le fichier src/info/kisai/plusmoins/model/bean/impl/GuessDefaultImpl.java, je ne comprends pas du tout à quoi correspond le 100.

En tout cas, super bon boulot. Faut quand même une énorme motivation pour pondre autant de code, vu l'utilité finale.

+5 -0

Ma proposition :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/bin/python3
# coding: utf-8

import os
import random


# define exceptions
class TryAgain(Exception):
    def __init__(self, smaller):
        if smaller:
            super().__init__('Smaller than that')
        else:
            super().__init__('Higher than that')


class AchievementUnlocked(Exception):
    def __init__(self):
        super().__init__('Congratulation, you found it!')


class Gibberish(Exception):
    def __init__(self):
        super().__init__('Unintelligible!')


# define the two functions
def test(number, guess):
    try:
        guess = int(guess)
    except ValueError:
        raise Gibberish

    if number == guess:
        raise AchievementUnlocked
    else:
        raise TryAgain(number < guess)


def ask(number):
    guess = input('Your proposition ? ')

    try:
        test(number, guess)
    except (TryAgain, Gibberish) as e_:
        print(e_)

    ask(number)

# pick a random number
random_ = 0
if os.path.exists('/dev/urandom'):
    random_ = 1 + ord(open('/dev/urandom', 'rb').read(1)) % 99
else:  # waaaay less funny OS spotted there !
    random_ = random.randrange(1, 99)

print('I pick a number between 1 and 99, guess it !')

# make the program run
try:
    ask(random_)
except AchievementUnlocked as e:
    print(e)

Pourquoi c'est mal ? Principalement parce que je détourne le mécanisme des exceptions pour faire à peu de chose près ce que font les goto des "anciens" langages de programmation, et que du coup, je fait la boucle en appelant récursivement la même fonction.

Bonus: aller chercher la valeur aléatoire dans /dev/urandom.

+2 -0

il est où le binding JNI pour gagner des perfs sur le goulet d'étranglement de l'application : la comparaison entre deux entiers. Non parce que clairement l'autoboxing ça bouffe des perfs quoi.

artragis

J'aime bien l'idée, mais il faudrait que j'introduise le boxing pour pouvoir l'optimiser :-°

Enfin, c'est du java quoi…

Davidbrcz

Soit c'est du troll anti-java, soit tu n'as rien compris à mon code et à ce qu'il critique. Dans les deux cas, je ne comprends pas l'intérêt de ce genre de remarque.

@SpaceFox : Si je puis me permettre, il manque quand même la génération automatique des 9/10 du code via des outils basés par exemple sur des diagrammes UML.

Berdes

Je ne connais pas d'outil qui fait ça, et je n'ai pas vu ce genre de gag depuis l'école… quoique j'imagine que ça doit exister, mais c'est moins courant que ce qu'on pourrait imaginer.

Et puis, je suis désolé, mais il y a bien trop de constantes magiques dans le code. Par exemple, dans le fichier src/info/kisai/plusmoins/model/bean/impl/GuessDefaultImpl.java, je ne comprends pas du tout à quoi correspond le 100.

Berdes

Ha ! Je savais que j'en avais oublié !

En tout cas, super bon boulot. Faut quand même une énorme motivation pour pondre autant de code, vu l'utilité finale.

Berdes

Un peu moins de 2 heure. C'est beaucoup de copier/coller et d'abus des fonctionnalités de IntelliJ (refactoring, auto-complétion, …).

Le plus difficile, c'était de ne pas se perdre dans le code à la fin.

Bonjour,

Voici ma contribution en C. Ce code est horrible parce qu'il est factorisé de façon abusive. En fait, j'ai non seulement factorisé le code mais également factorisé la pile. L'idée de base est de considérer le jeu du plus ou moins comme une sorte d'automate représenté de façon absolument pas rigoureuse de la façon suivante :

Les différents états du jeu du plus ou moins.

Chaque noeud représente un état et chaque état est implémenté par une fonction. La transition entre les états se fait avec les fonctions setjmp et longjmp. Setjmp et longjump permettent de faire des sauts en dehors d'une fonction. Setjmp définit un point vers lequel on saute, et longjump permet de sauter vers un point précédemment sauvegardé. Les prototypes de ces fonctions sont :

1
2
int setjmp (jmp_buf env);
void longjmp (jmp_buf env, int val);

Quand on appelle setjmp directement la fonction sauvegarde l'état des registres au moment de l'appel dans la variable env et retourne 0. Quand longjmp est appelé il retourne à l'état sauvegardé dans env et l'appel à setjmp correspondant retourne la valeur val. Si une fonction qui a appelé setjmp se termine, l'état de la pile change et la variable env n'est plus un point de retour valide. L'utilisation de setjmp et longjmp n'est pas recommandée pour la même raison que les goto, ils rendent les programmes difficiles à comprendre, de plus il est très facile de se retrouver avec des bugs vraiment pénibles à corriger.

Chaque fonction d'état à la forme suivante :

1
2
3
4
5
6
void f (volatile int a, volatile int b) {
    if (setjmp(env)){
        fais_ton_travail()
        long_jmp(env_suivant, val);
    }
}

Le programme passe d'abord dans chaque état, exécute setjmp et sort de la fonction. Le but est d'avoir un ensemble de jmp_buf avec des pointeurs d'instructions différents mais le même pointeur de pile. Ainsi les variables a et b deviennent partagés entre toutes les fonctions d'états. Elles sont déclarées volatiles pour s'assurer qu'elles sont bien mises sur la pile et non pas dans des registres, ce qui est nécessaire afin de conserver la propriété qui nous intéresse ici. En d'autres termes, toutes les fonctions partagent le même cadre de pile, ce qui me permet de dire que j'ai factorisé la pile dans ce programme :D.

Comme les fonctions se ressemblaient toutes plus ou moins, j'ai factorisé le code de façon à éviter les répétitions qui sont évidemment source d'erreurs. J'ai utilisé pour cela des macros afin de générer complètement les fonctions d'états, ce qui permet d'avoir un code compact, incompréhensible et dont la moindre coquille sera un calvaire à corriger par ce que le compilateur donnera des messages d'erreurs quelque peu obscurs (ce que j'ai bien évidemment vécu en écrivant ce programme).

En résumé :

  • Une solution inutilement compliquée pour un problème aussi simple. Bien que tuer une mouche avec un marteau ou allumer une bougie au lance-flamme soit très amusant, une tapette à mouches et une allumette font le travail tout aussi bien.
  • Le code utilise les fonctions setjmp et longjmp d'une façon non standard afin d'écraser la pile. En général il vaut mieux éviter les trucs cochons bas niveaux sauf si on aime vraiment se faire mal.
  • Utilisation intensive de macros avec des noms pas clairs afin de factoriser le code, ce qui rend le programme incompréhensible et impossible à maintenir. Les macros donnent souvent des messages peu clairs qui rendent la correction d'erreurs difficile.

Et le code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <setjmp.h>

jmp_buf bfs [6];
const char *s [] = {
    "Veuillez entrer un nombre entre O et 99 : ",
    NULL,
    "C'est plus.",
    "C'est moins.",
    "Youhou !"
};

typedef  volatile int v_int;
typedef void (*fn_t) (v_int, v_int);

#define _(x)  setjmp(bfs[x])
#define __(x,y) longjmp(bfs[x], y)
#define ___(x,y,z) if (_(x))__(y,z)
#define ____(x,y,z) _(x);__(y,z)
#define _____(x,y)  if (!_(x)) y

#define __a(x) (!x) ? (puts(s[x]) + scanf("%d",&b)) : (x == 1) ? 1 : puts(s[x])
#define __nn ((b < 0 || b > 99) ? 0 : (b < a) ? 2 : (b > a) ? 3 : 4)
#define __n(x) (x == 0) ? 1 : (x == 1) ? __nn\
             : (x == 2 || x == 3) ? 0 : 5
#define __f(x) if (!x) {____(x,__n(x),__a(x));} else ___(x,__n(x),__a(x))

#define f(x) void f ## x (v_int a, v_int b) {__f(x);}
#define g(x) x[1](0,0);x[2](0,0);x[3](0,0);x[4](0,0);\
             _____(5,x[0](rand() % 100,0))

f(0) // Demande un nombre entre 0 et 99
f(1) // Teste ce nombre
f(2) // Affiche "C'est plus."
f(3) // Affiche "C'est moins."
f(4) // Affiche "Youhou !"

int main () {
    fn_t fns [] = {f0,f1,f2,f3,f4};
    srand (time(NULL));
    g(fns);

    return 0;
}

+4 -0

L'utilisation de setjmp et longjmp n'est pas recommandée pour la même raison que les goto, ils rendent les programmes difficiles à comprendre, de plus il est très facile de se retrouver avec des bugs vraiment pénibles à corriger.

Souksouk

Et encore, les goto sont utiles pour la gestion d'erreurs (en C bien sûr) et dans quelques très rares cas, alors que longjmp - setjmp n'ont pas vraiment d'intérêt en C, à la limite des coroutines / système d'exceptions mais bon…

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte