0

While coding a simple command-line tictactoe game to learn Python (here's a link to the entire script on repl.it), I was not happy with my game_over function, since a lot of code is repeated:

Edit: fixed the code by putting the [deck_is_full] if last instead of first

Edit 2: changed the code back to original for clarity

def game_over(deck):
    if deck_is_full(deck):
        return deck_is_full(deck)
    if three_in_line(deck, "X"):
        return three_in_line(deck, "X")
    if three_in_line(deck, "O"):
        return three_in_line(deck, "O")
    return False

Is there a pythonic, repetition-free way to code that? I have found this answer on SO, but it doesn't get rid of the repetition, just gets it into a one-liner.

2 Answers2

5
def game_over(deck):
    return deck_is_full(deck) or three_in_line(deck, "X") or three_in_line(deck, "O")

Edit: Had a look at your whole code and see that three_in_line returns true strings or the default None. So if you do need False here instead of None, then add it explicitly:

def game_over(deck):
    return deck_is_full(deck) or three_in_line(deck, "X") or three_in_line(deck, "O") or False

Edit 2: Hmm, just a note: If your functions had side effects, my version wouldn't be equivalent to yours, as I only call them once, not twice like you (when they return a true value). But as expected, and as they in my opinion should, they don't have side effects.

superb rain
  • 5,300
  • 2
  • 11
  • 25
  • The prople who just copied your post and reposted downvoted this, propped you back up.. – maor10 Aug 10 '20 at 15:43
  • 1
    note that contrary to what you wrote on a deleted answer, `any()` does short circuit on the first `False` condition – Chris_Rands Aug 10 '20 at 15:48
  • 1
    @Chris_Rands `any` does, but the tuple/list-building doesn't. – superb rain Aug 10 '20 at 15:49
  • 1
    @Chris_Rands Just removing parentheses wouldn't make it a generator expression. It would make it three separate arguments being passed to `any`. – khelwood Aug 10 '20 at 15:51
  • @khelwood yes, i got that wrong, would actually require an awkard construct like `any(func() for func in (lambda: deck_is_full(deck), lambda: three_in_line(deck, "X"), lambda: three_in_line(deck, "O")))` – Chris_Rands Aug 10 '20 at 16:00
  • @Chris_Rands And even that would change the returned strings to `True`. – superb rain Aug 10 '20 at 16:05
  • @superbrain Yes, actually `next()` with a default of `False` would be better, anyway i prefer `or` on refection! – Chris_Rands Aug 10 '20 at 16:07
  • @Chris_Rands `next` would also need a filter for true values, so I guess maybe `next(filter(bool, (func() for func in (lambda: deck_is_full(deck), lambda: three_in_line(deck, "X"), lambda: three_in_line(deck, "O")))), default=False)` :-) – superb rain Aug 10 '20 at 16:27
  • Impressive! Your 1st edit about explicitly returning `False` got me wondering wether I should have also explicitly returned `False` in `three_in_line` and `deck_is_full` instead of silently returning the falsey `None` by default. I read about the Python Zen "explicit is better than implicit" --> I guess better if I explicitly return `False` in those 2 functions. – Arnaud Bouffard Aug 11 '20 at 10:39
  • tdelaney walrus operator answer made me realize my code was bugged --> I edited my question. @superbrain your answer replicated that bug (deck_is_full being evaluated 1st instead of last) --> I submitted an edit of your answer for peer-review. – Arnaud Bouffard Aug 11 '20 at 10:43
  • @ArnaudBouffard I don't think it's a good idea to change the question for that and invalidate all the answers and try to update them. I'd say that change is not relevant to the question. I suggest you change the question back. – superb rain Aug 11 '20 at 12:30
  • I understand and have edited the question back to original for clarity. – Arnaud Bouffard Aug 11 '20 at 13:19
0

Since python 3.8 you can use the walrus operator to assign a variable in the if clause

def game_over(deck):
    if not (result:=deck_is_full(deck)):
        if not (result:=three_in_line(deck, "X")):
            if not (result:=three_in_line(deck, "O")):
                return False
    return result
tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • Interesting, I didn't know about the walrus operator. By the way, you have fixed an error in my original code (edited since), which if a player won while filling the last remaining space, would declare the game a draw (because the deck becomes full) instead of a victory. – Arnaud Bouffard Aug 11 '20 at 10:14
  • @ArnaudBouffard What do you mean this fixed that? This is equivalent to your original and I even just tested it, it did report a draw in that case. – superb rain Aug 11 '20 at 12:35
  • @superbrain original function checked `deck_is_full` then `three_in_line(deck, "X")` then `three_in_line(deck, "X")`, thus reporting a draw instead of a victory in this case: [O O X] [O X X] [O X O]. Fixed by checking `deck_is_full` last. – Arnaud Bouffard Aug 11 '20 at 13:14
  • @ArnaudBouffard Yes, but this code checks `deck_is_full` *first*. – superb rain Aug 11 '20 at 13:27
  • and reports a draw in that case, you are right @superbrain, I got mixed up – Arnaud Bouffard Aug 11 '20 at 13:56