Affichage en double avec `putStr`

et *character-break*

L'auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Salut,

Après avoir mis longtemps à comprendre le principe de pureté en Haskell, je me suis attaqué à un jeu du pendu. Le jeu est fonctionnel (pas fait exprès désolé :P ) mais comporte des problèmes d'affichages. Lorsque je demande au joueur d'entrer un caractère, j'affiche "Enter a letter: " et je retourne le résultat de getChar si il appartient à l'alphabet, autrement je recommence. Cependant, dans tous les cas, que j'entre un bon caractère ou non, le "Enter a letter: " est affiché en double. Voici le code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
askLetter :: IO Char
askLetter = do
    putStr "Enter a letter: ";
    char <- getChar

    if (toLower char) `elem` ['a'..'z']
    then return (toLower char)
    else do
      putStr "\n"
      rec <- askLetter
      return rec

Et voici la fonction dans lequel je l'appelle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
hangmanGame :: Word -> TryOutInfo -> IO GameStatus
hangmanGame word tryOuts = do
    view <- stateView tryOuts
    putStrLn view

    if revealed word
    then return Won
    else do
        if remaining tryOuts == 0
        then return Failed
        else do
            putStrLn $ show word

            letter <- askLetter
            if letter `elem` (actual word)
            then do
                status <- hangmanGame (revealChar word letter) tryOuts
                return status
            else do
                status <- hangmanGame word (TryOutInfo (initial tryOuts) ((remaining tryOuts) - 1))
                return status

Voici plus exactement le rendu:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Enter a letter:
Enter a letter: e

 |
 |
 |
 |
 |
 |
_|___
pe__h
Enter a letter:
Enter a letter: a

 |
 |
 |
 |
 |
 |
_|___
pea_h
Enter a letter:
Enter a letter: c

Il est à noter que seul le putStr est répété, le programme ne me redemande pas le caractère.

Voilà, merci d'avance,

AZ.

PS: J'aimerais aussi savoir comment faire en sorte de ne pas avoir à faire un retour à la ligne, mis que le programme continue dès que la touche est appuyée, j'ai regardé du côté de HSCurses mais une librairie/fonction builtin m'arrangerai. J'ai déjà essayé un truc foireux du genre hSetBuffering stdin NoBuffering mais j'ai pas eu l'impression que ça marchait. Merci pour tout.

Édité par felko

Anciennement AlphaZeta

+0 -0

Cette réponse a aidé l'auteur du sujet

Aah, les entrées-sorties, comme c'est rigolo…

Je ne suis pas très sûr du fonctionnement de tous ces trucs, pardon si je dis une bêtise : lorsque tu utilises getChar et que tu saisis, par exemple, une espace, le retour à la ligne reste dans le tampon, et est consommé par le deuxième getChar. Comme ça n'est pas un caractère alphabétique, ton code boucle une deuxième fois. Tu peux réécrire les choses ainsi pour t'en convaincre :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import Data.Char
import System.IO

askLetter = do
    putStr "Enter a letter: "
    char <- getChar

    if (toLower char) `elem` ['a'..'z']
    then return (toLower char)
    else do
      putStr "\n"
      putStrLn $ "Received: " ++ (show $ ord char)
      askLetter

main :: IO ()
main = do
  hSetBuffering stdout NoBuffering
  hSetBuffering stdin NoBuffering
  foo <- askLetter
  return ()

Je ne suis pas très compétent sur le sujet, et je ne sais pas comment résoudre ton problème exactement (sans rajouter les hSetBuffering). En tout cas, c'est moins un problème de Haskell que de buffers d'entrées/sorties. Je n'arrive pas à une solution satisfaisante avec hFlush stdin. La version ci-dessus marche en tout cas correctement chez moi.

Au passage, est-ce que tu pourrais s'il te plaît nous donner des codes minimaux exécutables à l'avenir ? Je ne programme pas en Haskell, et des trucs tout cons style savoir dans quel module aller chercher lower me prennent un certain temps.

Édité par anonyme

+1 -0
Auteur du sujet

Une fois de plus merci beaucoup, du coup voici mon code pour askLetter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
askLetter = do
    putStr "Enter a letter: "
    char     <- getChar
    _newline <- getChar

    if isLetter char
    then return (toLower char)
    else do
      putStr "\n"
      askLetter

Merci pour tout @katana, je tâcherai la prochaine fois de metter un code exécutable :)

Par contre le character break ne fonctionne pas, mais je verrai plus tard, c'est un détail.

Édité par felko

Anciennement AlphaZeta

+0 -0
Auteur du sujet

Hum c'est ce que j'avais commencé à faire mais si l'utilisateur n'entre rien (s'il appuie direct sur entrée), alors je vais devoir traiter ça et donc rajouter 5 lignes, là je règle tout en une seule ligne.

Édité par felko

Anciennement AlphaZeta

+0 -0

Cette réponse a aidé l'auteur du sujet

Bonjour,

Ton dernier code n'est pas satisfaisant, il faudrait vider le tampon d'entrée en lisant jusqu'à rencontrer un retour à la ligne si tu ne veux pas avoir de surprises (d'où getLine proposé par katana). Ici si l'utilisateur rentre plus de deux caractères tu n'en consommeras que deux, et tu laisseras les autres dans le tampon. C'est en effet plutôt un problème de gestion des entrées-sorties qu'un problème spécifique à Haskell. Je t'invite à lire un cours traitant des entrées-sorties et de leur mise en tampon (pas forcément en Haskell). Je crois que ceci correspond à ce que tu cherches à faire :

 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
import Data.Char
import System.IO

askLetter :: IO Char
askLetter =
    do putStr "Enter a letter: "
       -- on n'a pas envoyé de retour à la ligne qui forcerait à vider
       -- le tampon, donc il faut le vider manuellement avec hFlush
       hFlush stdout

       line <- getLine
       case line of
         -- une ligne commençant par une lettre
         c : _ | isLetter c ->
            return (toLower c)
         -- autre chose
         _ ->
            do putChar '\n'
               askLetter

main :: IO ()
main =
    do c <- askLetter
       putStr "Result: "
       putChar c
       putChar '\n'

Mais est-ce satisfaisant si l'utilisateur rentre plusieurs lettres, de prendre la première au lieu de l'inviter à faire une nouvelle saisie ? Pour moi non, à toi de voir et de corriger mon code si nécessaire.

Je n'arrive pas à une solution satisfaisante avec hFlush stdin.

katana

hFlush est prévue pour les flux de sortie et non d'entrée, pour vider le tampon d'un flux d'entrée il faut lire ce dernier.

Édité par dentuk

+0 -0

hFlush est prévue pour les flux de sortie et non d'entrée, pour vider le tampon d'un flux d'entrée il faut lire ce dernier.

Ouais, ça fait partie des trucs que je sais mais que je ne comprends pas. Pourquoi est-ce qu'on ne pourrait pas faire ça ?

+1 -0

Cette réponse a aidé l'auteur du sujet

J'avais sauté ce passage :

PS: J'aimerais aussi savoir comment faire en sorte de ne pas avoir à faire un retour à la ligne, mis que le programme continue dès que la touche est appuyée, j'ai regardé du côté de HSCurses mais une librairie/fonction builtin m'arrangerai. J'ai déjà essayé un truc foireux du genre hSetBuffering stdin NoBuffering mais j'ai pas eu l'impression que ça marchait. Merci pour tout.

AlphaZeta

hSetBuffering stdin NoBuffering devrait fonctionner si bien exécuté avant les appels à getChar. Cependant il y a peu de programmes qui fonctionnent sur ce principe donc cela risque de dérouter l'utilisateur, qui n'aura au passage pas l'occasion de corriger son entrée s'il s'est trompé (dans le mode normal je peux taper "A RetourArrière B Entrée" et ton programme recevra la même chose que si j'avais tapé "B Entrée").

hFlush est prévue pour les flux de sortie et non d'entrée, pour vider le tampon d'un flux d'entrée il faut lire ce dernier.

Ouais, ça fait partie des trucs que je sais mais que je ne comprends pas. Pourquoi est-ce qu'on ne pourrait pas faire ça ?

katana

Il faudrait demander aux gens qui ont spécifié Haskell. Si tu veux mon avis, c'est une bonne chose notamment quand on considère ce passage :

The default buffering mode when a handle is opened is implementation-dependent and may depend on the file system object which is attached to that handle. For most implementations, physical files will normally be block-buffered and terminals will normally be line-buffered.

Haskell 2010 Language Report

Si tu t'attends à avoir un flux stdin line-buffered et qu'il se trouve avoir été initialisé en block-buffered pour améliorer les performances (par exemple parce que l'utilisateur a dirigé l'entrée vers un fichier), les hFlush stdin n'auraient pas du tout l'effet attendu / intuitif. Pour régler ça, tu pourrais alors forcer tous tes flux en line-buffered, mais hFlush n'apporterait dans ce cas rien par rapport à hGetLine, et cette approche dégraderait les performances pour les flux qui auraient pu rester en block-buffered.

En bref, en préférant hGetLine on gère de la même façon les flux d'entrée quel que soit leur type de tampon. Si hFlush avait été étendu aux flux d'entrée avec le comportement que tu préconises, il serait un outil difficile à appréhender, et ce serait une mauvaise idée de l'utiliser dans un cas comme celui qu'on a ici. Aussi, oublier les données ne serait pas cohérent avec le comportement sur les flux de sortie, qui est de les envoyer à leur destinataire et non nulle part.

Edit : reformulation.

Édité par dentuk

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

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