Jeu du pendu - Fortran

a marqué ce sujet comme résolu.

Bonjour à tous,

Voilà je me suis mis à un langage que je pensais jamais apprendre, mais petit défi, je m’y suis mis et ça se passe plutôt pas mal, malgré des débuts extrêmement difficile dans une syntaxe assez déroutante.

J’ai tenté un jeu du pendu, avec allocation mémoire, utilisation des boucles et tableau. Je voulais savoir si ma façon de faire était correcte, dans le sens bonnes et mauvaises pratiques, car malgré des tutoriels lus et relus, n’ayant pas d’expérience dans ce langage, je préfère vous laisser juger.

J’utilise Fortran 90. Voici 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
PROGRAM pendu

    CHARACTER(len=32) :: secret
    CHARACTER, DIMENSION(:), ALLOCATABLE :: word
    CHARACTER :: let
    LOGICAL :: trouve

    secret = "coucou"

    ALLOCATE(word(LEN_TRIM(secret)))
    CALL star(word, LEN_TRIM(secret)) ! Transforme mot secret en une liste d'étoiles *

    trouve = .FALSE.
    DO WHILE (.NOT. trouve)
        print*, word
        print*, "Proposer une lettre: "
        read (*, '(A)') let
        CALL check(secret, word, let)
        trouve = check_win(word)
    END DO

    print*, word

    DEALLOCATE(word)

CONTAINS
    SUBROUTINE star(w, n)
        INTEGER, INTENT(IN) :: n
        CHARACTER, DIMENSION(n), INTENT(OUT) :: w

        w = '*'

    END SUBROUTINE star

    SUBROUTINE check(s, w, letter)
        !Fonction qui vérifie que letter est dans s
        !Si oui, on l'à place dans w
        CHARACTER, DIMENSION(:) :: w
        CHARACTER(len=*), INTENT(IN) :: s
        CHARACTER :: letter
        INTEGER :: i

        DO i=1, LEN_TRIM(s)
            IF (s(i:i) == letter) THEN
                w(i) = letter
            END IF
        END DO
    END SUBROUTINE

    LOGICAL FUNCTION check_win(w)
        ! Vérifie que l'on a plus d'* dans w, si oui c'est gagné
        CHARACTER, DIMENSION(:) :: w
        INTEGER :: i
        LOGICAL :: win

        win = .TRUE.
        DO i=1, SIZE(w)
            IF (w(i) == '*') THEN
                win = .FALSE.
                EXIT
            END IF
        END DO

        check_win = win

    END FUNCTION

END PROGRAM pendu

Pour les balises code, je me doutais que Fortran était aux abonnés absents.

EDIT1: J’ai modifié la signature de check_win par

1
FUNCTION check_win(w) RESULT (win)

Ce qui donne ce nouveau bloc de fonction, ne changeant pas l’aspect du code principal:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
FUNCTION check_win(w) RESULT (win)
        ! Vérifie que l'on a plus d'* dans w, si oui c'est gagné
        CHARACTER, DIMENSION(:) :: w
        INTEGER :: i
        LOGICAL :: win

        win = .TRUE.
        DO i=1, SIZE(w)
            IF (w(i) == '*') THEN
                win = .FALSE.
                EXIT
            END IF
        END DO

END FUNCTION
+0 -0

Coucou1,

Je ne suis pas expert du Fortran, notamment au niveau des bonnes pratiques (si elles existent, ce dont je commence à douter à force de voir des codes tous plus horribles les uns que les autres), mais j’en fait de temps à autre.

  • Pourquoi une fonction star ? Un simple word = '*' ne suffit pas ?
  • Je précise au cas où qu’il existe les module pour créer des fonctions dans d’autre fichier plutôt que de tout faire dans le même.
  • check_win(w) est inutile. Il y a la fonction intrinsèque any avec laquelle tu peux faire le boulot.
  • s(i:i) ? Pourquoi pas s(i) ?
  • Je ne sais pas si c’est une convention personnelle ou non, mais j’ai tendance à n’utiliser les subroutines que lorsque j’ai besoin d’un effet de bord. En effet, entre avant et après l’appel à une subroutine, tu ne sais pas comment ton programme a été modifié (effet de bord). Les fonctions peuvent travailler sans effet de bords (fonctions pures, on peut même demander à Fortran de s’assurer que c’est le cas en les déclarant intrinsèques), ce qui permet de savoir en lisant le code que l’état avant/après est le même (à une variable près). Je trouve ça beaucoup plus confortable au débogage.
  • Juste pour signaler : tu n’as pas besoin de tout mettre en majuscule. Fortran n’est pas sensible à la casse : WHILE, while et wHiLe sont identiques.
  • Dans tes subroutines, met les intent pour chaque variable. Fortran est très permissif par défaut, le jour où tu as un bogue, bonne chance pour le trouver si tu restes en permissif.
  • Mets implicit none au début de ton programme (en deuxième ligne), sans ça, Fortran te permet de travailler avec des variables non déclarées. Je ne sais pas ce que ça fait, mais je vois poindre les soucis. ;)

  1. J’ai gagné ? 

+1 -0

Pourquoi une fonction star ? Un simple word = ’*’ ne suffit pas ?

Si tout à fait, à force de m’entraîner avec subroutine et function, je ne fais que ça ! :D

Je précise au cas où qu’il existe les module pour créer des fonctions dans d’autre fichier plutôt que de tout faire dans le même.

Je n’en suis pas encore là, c’est mon 3ème jour de Fortran, je découvre petit à petit, mais effectivement, j’avais entre aperçu cette possibilité dans certains codes exemples.

check_win(w) est inutile. Il y a la fonction intrinsèque any avec laquelle tu peux faire le boulot.

C’est une autre manière de penser, j’ai pas l’impression qu’il y a un plus, mais merci pour l’information… La difficulté est de mettre des expressions logiques dans word, mais ensuite comment afficher word sans passer par une boucle de comparaison entre secret et word, ça deviendrait tout aussi lourd.

s(i:i) ? Pourquoi pas s(i) ?

J’ai une erreur à la compilation, il aime pas !

PROCEDURE attribute conflicts with INTENT attribute in ‘s’

Juste pour signaler : tu n’as pas besoin de tout mettre en majuscule. Fortran n’est pas sensible à la casse : WHILE, while et wHiLe sont identiques.

Oui je sais, mais ça me déplaît pas ces majuscules, je distingue variable et type de cette manière.

Dans tes subroutines, met les intent pour chaque variable. Fortran est très permissif par défaut, le jour où tu as un bogue, bonne chance pour le trouver si tu restes en permissif.

J’ai du mal avec ces INTENT, j’utilise IN pour éviter de modifier le paramètre, OUT pour modifier un paramètre de la fonction (référence ?)

Mets implicit none au début de ton programme (en deuxième ligne), sans ça, Fortran te permet de travailler avec des variables non déclarées. Je ne sais pas ce que ça fait, mais je vois poindre les soucis.

Ça n’a absolument rien changé chez moi. Le code modifié après tes indications

 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
PROGRAM pendu

    IMPLICIT NONE
    CHARACTER(len=32) :: secret
    CHARACTER, DIMENSION(:), ALLOCATABLE :: word
    CHARACTER :: let
    LOGICAL :: trouve
    INTEGER :: n

    secret = "coucou"

    ALLOCATE(word(LEN_TRIM(secret)))
    word = '*' ! Transforme mot secret en une liste d'étoiles *

    trouve = .FALSE.
    DO WHILE (.NOT. trouve)
        print*, word
        print*, "Proposer une lettre: "
        read (*, '(A)') let
        CALL check(secret, word, let)
        trouve = check_win(word)
    END DO

    print*, word

    DEALLOCATE(word)

CONTAINS

    SUBROUTINE check(s, w, letter)
        !Fonction qui vérifie que letter est dans s
        !Si oui, on l'à place dans w
        IMPLICIT NONE
        CHARACTER, DIMENSION(:) :: w
        CHARACTER(len=*), INTENT(IN) :: s
        CHARACTER :: letter
        INTEGER :: i, j

        DO i=1, LEN_TRIM(s)
            IF (s(i:i) == letter) THEN
                w(i) = letter
            END IF
        END DO
    END SUBROUTINE

    FUNCTION check_win(w) RESULT (win)
        ! Vérifie que l'on a plus d'* dans w, si oui c'est gagné
        IMPLICIT NONE
        CHARACTER, DIMENSION(:) :: w
        INTEGER :: i
        LOGICAL :: win

        win = .TRUE.
        DO i=1, SIZE(w)
            IF (w(i) == '*') THEN
                win = .FALSE.
                EXIT
            END IF
        END DO

    END FUNCTION

END PROGRAM pendu
+0 -0

C’est une autre manière de penser, j’ai pas l’impression qu’il y a un plus, mais merci pour l’information…

Ça t’évite de coder une fonction, c’est générique, c’est facilement parallélisable, c’est optimisé par le compilateur, c’est garantie sans effet de bord. ^^

Ce dont je parle, c’est de faire trouve = .not. any(word == '*'). C’est effectivement un peu différent de ce que tu peux avoir l’habitude de faire, mais, et là j’en suis sûr, c’est la bonne manière de procéder en Frotran.

PROCEDURE attribute conflicts with INTENT attribute in ‘s’

Tu as raison, j’ai confondu le comportement entre chaine et tableaux.

Ça n’a absolument rien changé chez moi.

Heureusement. C’est une option qui permet de gueuler à la compilation quand tu fais des choses bizarres. Ainsi,

1
2
3
PROGRAM pendu
    print*, word
END PROGRAM pendu

Compile (et renvoie un truc assez aléatoire). Par contre,

1
2
3
4
PROGRAM pendu
implicit none
    print*, word
END PROGRAM pendu

ne compile pas (car Symbol 'word' at (1) has no IMPLICIT type). Et c’est mieux ainsi. ^^

J’ai du mal avec ces INTENT, j’utilise IN pour éviter de modifier le paramètre, OUT pour modifier un paramètre de la fonction (référence ?)

in -> pas modifiable, que lu.
out -> ne devrait pas être lu du tout, seulement écrit. Je me souviens de cas tordus où le contenu originel était écrasé (car pas de in = on s’en fiche de ce qu’il y a quand ça arrive !). C’est plus qu’une simple question de références vs valeur.
inout -> on peux lire et modifier.
rien de préciser -> comme inout, je pense. Mais Fortran est assez traitre au débogage, donc je le met explicitement.

+1 -0

Ce dont je parle, c’est de faire trouve = .not. any(word == ’*’). C’est effectivement un peu différent de ce que tu peux avoir l’habitude de faire, mais, et là j’en suis sûr, c’est la bonne manière de procéder en Frotran.

Oui c’est bien vu !

Merci pour les autres infos, j’enregistre ;)

 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
PROGRAM pendu

    IMPLICIT NONE
    CHARACTER(len=32) :: secret
    CHARACTER, DIMENSION(:), ALLOCATABLE :: word
    CHARACTER :: let
    LOGICAL :: trouve
    INTEGER :: n

    secret = "coucou"

    ALLOCATE(word(LEN_TRIM(secret)))
    word = '*' ! Transforme mot secret en une liste d'étoiles *

    trouve = .FALSE.
    DO WHILE (.NOT. trouve)
        print*, word
        print*, "Proposer une lettre: "
        read (*, '(A)') let
        CALL check(secret, word, let)
        trouve = .NOT. ANY(word=='*')
    END DO

    print*, word

    DEALLOCATE(word)

CONTAINS

    SUBROUTINE check(s, w, letter)
        !Fonction qui vérifie que letter est dans s
        !Si oui, on l'à place dans w
        IMPLICIT NONE
        CHARACTER, DIMENSION(:) :: w
        CHARACTER(len=*), INTENT(IN) :: s
        CHARACTER :: letter
        INTEGER :: i, j

        DO i=1, LEN_TRIM(s)
            IF (s(i:i) == letter) THEN
                w(i) = letter
            END IF
        END DO
    END SUBROUTINE

END PROGRAM pendu
+1 -0

Bonjour,

Voilà le jeu du pendu à peu près finaliser, je ne pense pas aller plus loin dans la conception du jeu, car que très peu d’intérêt dans l’avancement de mon apprentissage.

J’ai ajouté le choix aléatoire d’un mot dans un fichier, ainsi que la conversion automatique des lettres majuscules en minuscules.

  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
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
program pendu

    ! Initialize variables
    implicit none
    character(len=32) :: secret
    character, dimension(:), allocatable :: word
    character :: let
    logical :: trouve
    integer :: i, msec, n, time_info(8)
    integer :: lines

    ! Initialize seed
    call date_and_time(values=time_info)
    msec = 1000 * time_info(7) + time_info(8)
    call random_seed(size=n)
    call random_seed(put=(/ ( i * msec, i = 1, n ) /))

    ! Get random number / line search in file
    lines = get_lines("dico.txt")
    n = my_rand(1, lines)
    call search_line("dico.txt", n, secret)

    ! Get word to completed
    allocate(word(len_trim(secret)))
    word = '*'

    trouve = .false.
    do while (.not. trouve)
        print*, word
        print*, "proposer une lettre: "
        read (*, '(a)') let
        call check(secret, word, let)
        trouve = all(word/='*')
    end do

    print*, word

    deallocate(word)

contains
    subroutine check(s, w, letter)
        character, dimension(:), intent(out) :: w
        character(len=*), intent(in) :: s
        character, intent(out) :: letter
        integer :: i, j

        call lower(letter)

        do i=1, len_trim(s)
            if (s(i:i) == letter) then
                w(i) = letter
            end if
        end do
    end subroutine check

    subroutine lower(l)
        character, intent(inout) :: l
        if (ichar(l) >= 65 .and. ichar(l) < 90) then
            l = char(ichar(l)+32)
        end if
    end subroutine lower

    integer function my_rand(mini, maxi)
        integer, intent(in) :: mini, maxi
        real :: r
        call random_number(r)
        my_rand = int(r*maxi+mini)
    end function

    integer function get_lines(s)
        character(len=*), intent(in) :: s
        integer :: n, io

        open(11,file=s, iostat=io, status='old')
        if (io/=0) stop 'Cannot open file!'

        do
            read(11,*,iostat=io)
            if (io/=0) exit
            n = n + 1
        end do

        close(11)

        get_lines = n

    end function get_lines

    subroutine search_line(s, n, l)
        character(len=*), intent(in) :: s
        character(len=*), intent(out) :: l
        integer, intent(in) :: n
        integer :: i, io

        open(11,file=s, iostat=io, status='old')
        if (io/=0) stop 'Cannot open file!'

        do i=1, n
            read(11,'(A)',iostat=io) l
            if (io/=0) exit
        end do

        close(11)

    end subroutine search_line

end program pendu

Ma prochaine étape sera de créer des modules indépendants, d’ailleurs ça me sera fort utile en ce qui concerne une de mes principales difficultés sur ce projet, avoir de la documentation fiable afin de trouver un entier aléatoire entre un minimum et un maximum.

Bonne journée…

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