Search code examples
javascriptpythonflaskjinja2

letters do not display correctly after flask redirect


Live site: https://charlie-project3-aebf005f6451.herokuapp.com/viewlib repo: https://github.com/Tropicalbunny/FLASK

i am having an issue where my hangman page is not displaying the revealed letters (or even not revealed) within the center of the screen, but it seems every time /gameboard reloads it removes the updated revealed letters.

i originally had the variable send to HTML through the gameboard route, but it was updating to the preivous session['correct_letters']. affectively "lagging" one behind each guess.

so i tried this approach to update through the JS. unfortunately causing this. i have added the repo and live site incase any of that helps.

Python Code thats relevant:

@app.route('/button', methods=["POST"])
def buttonpress():
    data = request.json
    value = data['value']
    hangman(value, session)
    
    session.modified = True
    
    return jsonify({
        'status': 'success',
        'correct_letters': session['correct_letters'],
        'guessed_letters': session['guessed_letters'],
        'guesses_left': session['guesses_left'],
        'game_over': session['game_over']
    })

@app.route('/gameboard')
def gameboard():
    return render_template('gameboard.html')

def hangman(guess, session):
    guesses_left = session['guesses_left']
    guessed_letters = session['guessed_letters']
    correct_letters = session['correct_letters']
    game_over = session['game_over']
    database_word = session['database_word']

    guessed_letters = guessed_letters + guess
    correct_letters = ""
    if guess not in database_word:
        guesses_left -= 1
        session['guesses_left'] = guesses_left
        if guesses_left == 0:
            session['game_over'] = 1
            return game_over

    for letter in database_word:
        if letter in guessed_letters:
            correct_letters += f"{letter} "
        else:
            correct_letters += "_ "

    session['correct_letters'] = correct_letters
    session['guessed_letters'] = guessed_letters
    print("cookie", session['correct_letters'])
    if all(letter in guessed_letters for letter in database_word):
        session['game_over'] = 2

the JS Function:

function sendValue(buttonValue) {
    fetch('/button', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ value: buttonValue }),
    })
    .then(response => response.json())
    .then(data => {
        if (data.status === 'success') {
            document.querySelector('#displayedLetters').textContent = data.correct_letters;
        }else {
            console.error('Error processing guess:', data);
        }
    })
}

snip of HTML with button so you can see whats calling:

    <div id="word-guess"><p id="displayedLetters"></p></div>
    <div id="letter-guesses">
        <form>
        <button onclick="sendValue(this.value)" class="guess-button" id="a" value ="a">A</button>

....

ive tried a few different things: redirecting the gameboard page(when i was using jinja to show the guessed letters) within the hangman function. tried time.sleep to slow the function until the session had been updated. now trying to update the js side to reveal the guessed letters. i have used Print and console.log to narrow the issue down to here. intrestingly the function hangman updates the session['correct_letters'] correctly. but it looks like /gameboard is running before the update can take place.


Solution

  • I can't test it on full code but when you press <button> then browser sends full <form> to server and later it get response and it reloads full page.

    You have to send event to your function

    onclick="sendValue(event, this.value)"
    

    and use preventDefault() to stop sending full <form>

    function sendValue(event, buttonValue) {
        event.preventDefault(); 
        
        // ... rest ...
    }
    

    You can even skip this.value because you can get it from event.target.value

    onclick="sendValue(event)"
    
    function sendValue(event) {
        event.preventDefault();
        
        buttonValue = event.target.value;
    
        // ... rest ...
    }
    

    EDIT:

    You can also use

    onclick="event.preventDefault(); sendValue(this.value)"
    

    without changing code in sendValue()


    EDIT:

    If you use JavaScript to send data then you can keep <button> without <form>
    and it should work without event.preventDefault()


    BTW:

    In template you can use for-loop to create buttons

    <form>
        {% for char in "abcdefghijklmnopqrstuvwxyz" %}
            <button onclick="event.preventDefault(); sendValue(this.value)" 
                    class="guess-button" 
                    id="{{ char }}" 
                    value="{{ char }}">{{ char | upper }}</button>
        {% endfor %}   
    </form>
    

    EDIT:

    Full working code with three versions for buttons

    1. with event.preventDefault() in sendValue()
    2. with event.preventDefault() in onclick=".."
    3. without <form>

    (and with one word abracadabra)

    I uses render_template_string so all code, JS and HTML is in one file
    and everyone can simply copy and test it.

    #!/usr/bin/env python3
    
    from flask import Flask, render_template_string, request, session, jsonify
    
    app = Flask(__name__)
    app.secret_key = "Hello World!"
    
    @app.route('/button', methods=["POST"])
    def buttonpress():
        data = request.json
        print(data)
        value = data['value']
        hangman(value, session)
        
        session.modified = True
        
        return jsonify({
            'status': 'success',
            'correct_letters': session['correct_letters'],
            'guessed_letters': session['guessed_letters'],
            'guesses_left': session['guesses_left'],
            'game_over': session['game_over']
        })
    
    @app.route('/')
    def gameboard():
    
        session['guesses_left'] = 7
        session['guessed_letters'] = ""
        session['correct_letters'] = ""
        session['game_over'] = 0
        session['database_word'] = "abracadabra"
    
        placeholder = '_ ' * len(session['database_word'])
        
        return render_template_string('''
    <!DOCTYPE html>
    
    <html>
    
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0">
    <title>Example</title>
    </head>
    
    <body>
    
    
    <form>
        version 1: 
        {% for char in "abcdefghijklmnopqrstuvwxyz" %}
            <button onclick="sendValueEvent(event)" class="guess-button" id="{{ char }}" value ="{{ char }}">{{ char | upper }}</button>
        {% endfor %}    
    </form>
        
        <br/>
    
    <form>    
        version 2: 
        {% for char in "abcdefghijklmnopqrstuvwxyz" %}
            <button onclick="event.preventDefault(); sendValue(this.value)" class="guess-button" id="{{ char }}" value ="{{ char }}">{{ char | upper }}</button>
        {% endfor %}    
    </form>
    
    <br/>
    
    <!-- without <form> -->
    version 3: 
    {% for char in "abcdefghijklmnopqrstuvwxyz" %}
        <button onclick="sendValue(this.value)" class="guess-button" id="{{ char }}" value ="{{ char }}">{{ char | upper }}</button>
    {% endfor %}    
    
    <br/>
    
    <br/>
    
    WORD: <span id="displayedLetters">{{ placeholder }}</span>
    
    <script>
    function sendValueEvent(event) {
        event.preventDefault();
        buttonValue = event.target.value;
        sendValue(buttonValue);
    }
        
    function sendValue(buttonValue) {
        fetch('/button', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ value: buttonValue }),
        })
        .then(response => response.json())
        .then(data => {
            if (data.status === 'success') {
                document.querySelector('#displayedLetters').textContent = data.correct_letters;
            }else {
                console.error('Error processing guess:', data);
            }
        })
    }
    </script>
    </body>
    
    </html>''', placeholder=placeholder)
    
    def hangman(guess, session):
        guesses_left = session['guesses_left']
        guessed_letters = session['guessed_letters']
        correct_letters = session['correct_letters']
        game_over = session['game_over']
        database_word = session['database_word']
    
        guessed_letters = guessed_letters + guess
        correct_letters = ""
        if guess not in database_word:
            guesses_left -= 1
            session['guesses_left'] = guesses_left
            if guesses_left == 0:
                session['game_over'] = 1
                return game_over
    
        for letter in database_word:
            if letter in guessed_letters:
                correct_letters += f"{letter} "
            else:
                correct_letters += "_ "
    
        session['correct_letters'] = correct_letters
        session['guessed_letters'] = guessed_letters
        print("cookie", session['correct_letters'])
        if all(letter in guessed_letters for letter in database_word):
            session['game_over'] = 2
    
    if __name__ == '__main__':
        app.debug = True
        app.run()
    

    enter image description here