Skip to main content
added 1 character in body
Source Link
Veedrac
  • 9.8k
  • 23
  • 38

As Caridorc says, return None is much better than # Implicit return None at the end of the'return functionNone' at the end of the function. f_positive should also use try's else clause to minimize the area under it:

As Caridorc says, return None is much better than # Implicit return None at the end of the function. f_positive should also use try's else clause to minimize the area under it:

As Caridorc says, return None is much better than # Implicit 'return None' at the end of the function. f_positive should also use try's else clause to minimize the area under it:

deleted 5 characters in body
Source Link
Veedrac
  • 9.8k
  • 23
  • 38
"""
Prompt the user with 'question'.

Await until check_func(<user_input>) returns a non-None value and returnsreturn it
"""
"""Check if there is a win condition, returnsreturn who won or False."""
def lines(width, height):    
    for y in range(width):
        yield [(x, y) for x in range(height)]

    for x in range(height):
        yield [(x, y) for y in range(width)]

    if width == height:
        yield [(x,  x) for x in range(width)]
        yield [(x, -x) for x in range(width)]

def check_win(board):
    """Check if there is a win condition, returnsreturn who won or False."""
    width = len(board[0])
    height = len(board)

    for line in lines(width, height):
        x, y = line[0]
        first = board[x][y]

        if first and all(board[i][j] == first for i, j in line):
            return first

    return False
# encoding: utf-8

## Tic-tac-toe game by Mateon1

from __future__ import print_function

from itertools import chain, cycle, islice

try:
    input = raw_input
    range = range
except NameError:
    pass

def get_shape(board):
    return len(board[0]), len(board)

def prompt(question, check_func=lambda i: i.lower() == "y"):
    """
    Prompt the user with 'question'.

    Await until check_func(<user_input>) returns a non-None value and returnsreturn it
    """
    while True:
        value = check_func(input(question))
        if value is not None:
            return value

def lines(width, height):    
    for y in range(width):
        yield [(x, y) for x in range(height)]

    for x in range(height):
        yield [(x, y) for y in range(width)]

    if width == height:
        yield [(x,  x) for x in range(width)]
        yield [(x, -x) for x in range(width)]

def check_win(board):
    """Check if there is a win condition, returnsreturn who won or None otherwise."""
    width, height = get_shape(board)

    for line in lines(width, height):
        x, y = line[0]
        first = board[x][y]

        if first and all(board[i][j] == first for i, j in line):
            return first

    return None

def f_positive(string):
    try:
        val = int(string)
    except ValueError:
        return

    if val <= 0:
        return

    return val

def f_coords(board):
    """Get function handling user input for board coordinates."""
    width, height = get_shape(board)

    def inner(user_input):
        try:
            x, y = map(int, user_input.split(" "))
        except ValueError:
            return

        x -= 1; y -= 1

        if not (0 <= x < width and 0 <= y < height):
            return

        if board[y][x]:
            return

        return x, y

    return inner

def print_board(board):
    """
    Print the game board.

    Output example:

          1  2  3
        +--+--+--+
      1 |()|  |()|
        +--+--+--+
      2 |()|><|  |
        +--+--+--+
      3 |><|  |  |
        +--+--+--+
    """

    def cell(value):
        """Get a 2-character representation of a cell."""
        return {0: "  ", 1: "()", 2: "><"}[value]

    width, height = get_shape(board)

    numbers = ["{:2d}".format(i) for i in range(1, width+1)]
    separator = "    " + "+--" * width + "+"

    # Output
    print("    ", *numbers)

    for i, row in enumerate(board, 1):
        print(separator)
        print("{:3d} |{}|".format(i, "|".join(map(cell, row))))

    print(separator)

def play(board_size=(3, 3)):
    width, height = board_size
    board = [[0] * width for y in range(height)]
    # board[y][x]:
    # * 0 - empty
    # * 1 - () // circle
    # * 2 - >< // cross
    for player_turn in islice(cycle((1, 2)), width * height):
        if check_win(board) is not None:
            break

        print("Player {}'s ({}) turn!".format(player_turn, "circle" if player_turn == 1 else "cross"))

        print_board(board)
        x, y = prompt(
            "Please enter board coordinates to place your piece on.\n"
            "(example: `2 3` for 2nd column 3rd row): ",
            f_coords(board)
        )

        board[y][x] = player_turn

    print("=== Game over! ===")
    print_board(board)

    winning_player = check_win(board)
    if winning_player is None:
        print("The game was a tie!")
    else:
        print("Player {} ({}) won!".format(winning_player, "circle" if winning_player == 1 else "cross"))

def main():
    while True:
        board_size = (prompt("Please enter tic-tac-toe board width:  ", f_positive),
                      prompt("Please ented tic-tac-toe board height: ", f_positive))
        play(board_size=board_size)

        if not prompt("Would you like to play again? (y/N) "):
            break

if __name__ == "__main__":
    main()
"""
Prompt the user with 'question'.

Await until check_func(<user_input>) returns a non-None value and returns it
"""
"""Check if there is a win condition, returns who won or False."""
def lines(width, height):    
    for y in range(width):
        yield [(x, y) for x in range(height)]

    for x in range(height):
        yield [(x, y) for y in range(width)]

    if width == height:
        yield [(x,  x) for x in range(width)]
        yield [(x, -x) for x in range(width)]

def check_win(board):
    """Check if there is a win condition, returns who won or False."""
    width = len(board[0])
    height = len(board)

    for line in lines(width, height):
        x, y = line[0]
        first = board[x][y]

        if first and all(board[i][j] == first for i, j in line):
            return first

    return False
# encoding: utf-8

## Tic-tac-toe game by Mateon1

from __future__ import print_function

from itertools import chain, cycle, islice

try:
    input = raw_input
    range = range
except NameError:
    pass

def get_shape(board):
    return len(board[0]), len(board)

def prompt(question, check_func=lambda i: i.lower() == "y"):
    """
    Prompt the user with 'question'.

    Await until check_func(<user_input>) returns a non-None value and returns it
    """
    while True:
        value = check_func(input(question))
        if value is not None:
            return value

def lines(width, height):    
    for y in range(width):
        yield [(x, y) for x in range(height)]

    for x in range(height):
        yield [(x, y) for y in range(width)]

    if width == height:
        yield [(x,  x) for x in range(width)]
        yield [(x, -x) for x in range(width)]

def check_win(board):
    """Check if there is a win condition, returns who won or None otherwise."""
    width, height = get_shape(board)

    for line in lines(width, height):
        x, y = line[0]
        first = board[x][y]

        if first and all(board[i][j] == first for i, j in line):
            return first

    return None

def f_positive(string):
    try:
        val = int(string)
    except ValueError:
        return

    if val <= 0:
        return

    return val

def f_coords(board):
    """Get function handling user input for board coordinates."""
    width, height = get_shape(board)

    def inner(user_input):
        try:
            x, y = map(int, user_input.split(" "))
        except ValueError:
            return

        x -= 1; y -= 1

        if not (0 <= x < width and 0 <= y < height):
            return

        if board[y][x]:
            return

        return x, y

    return inner

def print_board(board):
    """
    Print the game board.

    Output example:

          1  2  3
        +--+--+--+
      1 |()|  |()|
        +--+--+--+
      2 |()|><|  |
        +--+--+--+
      3 |><|  |  |
        +--+--+--+
    """

    def cell(value):
        """Get a 2-character representation of a cell."""
        return {0: "  ", 1: "()", 2: "><"}[value]

    width, height = get_shape(board)

    numbers = ["{:2d}".format(i) for i in range(1, width+1)]
    separator = "    " + "+--" * width + "+"

    # Output
    print("    ", *numbers)

    for i, row in enumerate(board, 1):
        print(separator)
        print("{:3d} |{}|".format(i, "|".join(map(cell, row))))

    print(separator)

def play(board_size=(3, 3)):
    width, height = board_size
    board = [[0] * width for y in range(height)]
    # board[y][x]:
    # * 0 - empty
    # * 1 - () // circle
    # * 2 - >< // cross
    for player_turn in islice(cycle((1, 2)), width * height):
        if check_win(board) is not None:
            break

        print("Player {}'s ({}) turn!".format(player_turn, "circle" if player_turn == 1 else "cross"))

        print_board(board)
        x, y = prompt(
            "Please enter board coordinates to place your piece on.\n"
            "(example: `2 3` for 2nd column 3rd row): ",
            f_coords(board)
        )

        board[y][x] = player_turn

    print("=== Game over! ===")
    print_board(board)

    winning_player = check_win(board)
    if winning_player is None:
        print("The game was a tie!")
    else:
        print("Player {} ({}) won!".format(winning_player, "circle" if winning_player == 1 else "cross"))

def main():
    while True:
        board_size = (prompt("Please enter tic-tac-toe board width:  ", f_positive),
                      prompt("Please ented tic-tac-toe board height: ", f_positive))
        play(board_size=board_size)

        if not prompt("Would you like to play again? (y/N) "):
            break

if __name__ == "__main__":
    main()
"""
Prompt the user with 'question'.

Await until check_func(<user_input>) returns a non-None value and return it
"""
"""Check if there is a win condition, return who won or False."""
def lines(width, height):    
    for y in range(width):
        yield [(x, y) for x in range(height)]

    for x in range(height):
        yield [(x, y) for y in range(width)]

    if width == height:
        yield [(x,  x) for x in range(width)]
        yield [(x, -x) for x in range(width)]

def check_win(board):
    """Check if there is a win condition, return who won or False."""
    width = len(board[0])
    height = len(board)

    for line in lines(width, height):
        x, y = line[0]
        first = board[x][y]

        if first and all(board[i][j] == first for i, j in line):
            return first

    return False
# encoding: utf-8

## Tic-tac-toe game by Mateon1

from __future__ import print_function

from itertools import chain, cycle, islice

try:
    input = raw_input
    range = range
except NameError:
    pass

def get_shape(board):
    return len(board[0]), len(board)

def prompt(question, check_func=lambda i: i.lower() == "y"):
    """
    Prompt the user with 'question'.

    Await until check_func(<user_input>) returns a non-None value and return it
    """
    while True:
        value = check_func(input(question))
        if value is not None:
            return value

def lines(width, height):    
    for y in range(width):
        yield [(x, y) for x in range(height)]

    for x in range(height):
        yield [(x, y) for y in range(width)]

    if width == height:
        yield [(x,  x) for x in range(width)]
        yield [(x, -x) for x in range(width)]

def check_win(board):
    """Check if there is a win condition, return who won or None otherwise."""
    width, height = get_shape(board)

    for line in lines(width, height):
        x, y = line[0]
        first = board[x][y]

        if first and all(board[i][j] == first for i, j in line):
            return first

    return None

def f_positive(string):
    try:
        val = int(string)
    except ValueError:
        return

    if val <= 0:
        return

    return val

def f_coords(board):
    """Get function handling user input for board coordinates."""
    width, height = get_shape(board)

    def inner(user_input):
        try:
            x, y = map(int, user_input.split(" "))
        except ValueError:
            return

        x -= 1; y -= 1

        if not (0 <= x < width and 0 <= y < height):
            return

        if board[y][x]:
            return

        return x, y

    return inner

def print_board(board):
    """
    Print the game board.

    Output example:

          1  2  3
        +--+--+--+
      1 |()|  |()|
        +--+--+--+
      2 |()|><|  |
        +--+--+--+
      3 |><|  |  |
        +--+--+--+
    """

    def cell(value):
        """Get a 2-character representation of a cell."""
        return {0: "  ", 1: "()", 2: "><"}[value]

    width, height = get_shape(board)

    numbers = ["{:2d}".format(i) for i in range(1, width+1)]
    separator = "    " + "+--" * width + "+"

    # Output
    print("    ", *numbers)

    for i, row in enumerate(board, 1):
        print(separator)
        print("{:3d} |{}|".format(i, "|".join(map(cell, row))))

    print(separator)

def play(board_size=(3, 3)):
    width, height = board_size
    board = [[0] * width for y in range(height)]
    # board[y][x]:
    # * 0 - empty
    # * 1 - () // circle
    # * 2 - >< // cross
    for player_turn in islice(cycle((1, 2)), width * height):
        if check_win(board) is not None:
            break

        print("Player {}'s ({}) turn!".format(player_turn, "circle" if player_turn == 1 else "cross"))

        print_board(board)
        x, y = prompt(
            "Please enter board coordinates to place your piece on.\n"
            "(example: `2 3` for 2nd column 3rd row): ",
            f_coords(board)
        )

        board[y][x] = player_turn

    print("=== Game over! ===")
    print_board(board)

    winning_player = check_win(board)
    if winning_player is None:
        print("The game was a tie!")
    else:
        print("Player {} ({}) won!".format(winning_player, "circle" if winning_player == 1 else "cross"))

def main():
    while True:
        board_size = (prompt("Please enter tic-tac-toe board width:  ", f_positive),
                      prompt("Please ented tic-tac-toe board height: ", f_positive))
        play(board_size=board_size)

        if not prompt("Would you like to play again? (y/N) "):
            break

if __name__ == "__main__":
    main()
Source Link
Veedrac
  • 9.8k
  • 23
  • 38

This won't work:

try:
    raw_input
except NameError:
    import sys
    print >> sys.stderr, "This program supports Python 2 only."
    sys.exit(1)

The program would crash with a SyntaxError during compilation before being able run this. I get

  File "p.py", line 104
    print "".join(text)
           ^
SyntaxError: invalid syntax

One way of doing this check is to have a file like

from __future__ import print_function

try:
    raw_input
except NameError:
    import sys
    print("This program supports Python 2 only.", file=sys.stderr)
    sys.exit(1)

import the_real_program
the_real_program.main()

This program has to support both versions syntactically but can be much smaller.

Personally, though, it looks trivial to get this running on Python 3; use

from __future__ import print_function

try:
    input = raw_input
    range = xrange
except NameError:
    pass

swap the usages and you're done. I suggest you do so; Python 3 is way better anyway.

prompt's docstring should be indented properly and IMHO you shouldn't use backticks. Being pedantic, it should also be phrased as an imperative. Out of the several valid styles, I prefer:

"""
Prompt the user with 'question'.

Await until check_func(<user_input>) returns a non-None value and returns it
"""

check_win's docstring should use tripple-quotes:

"""Check if there is a win condition, returns who won or False."""

IMHO diagonals shouldn't only work for square boards; this should be a valid win

      1  2  3  4 
    +--+--+--+--+
  1 |<>|  |  |><|
    +--+--+--+--+
  2 |  |<>|  |><|
    +--+--+--+--+
  3 |  |  |<>|  |
    +--+--+--+--+

given that this is also a valid win:

      1  2  3  4 
    +--+--+--+--+
  1 |<>|  |  |><|
    +--+--+--+--+
  2 |<>|  |  |><|
    +--+--+--+--+
  3 |<>|  |  |  |
    +--+--+--+--+

Although this is a gameplay issue so I won't mess with it.

As written, the checks are largely repetitive so I'd suggest extracting it into an operation over indices:

def lines(width, height):    
    for y in range(width):
        yield [(x, y) for x in range(height)]

    for x in range(height):
        yield [(x, y) for y in range(width)]

    if width == height:
        yield [(x,  x) for x in range(width)]
        yield [(x, -x) for x in range(width)]

def check_win(board):
    """Check if there is a win condition, returns who won or False."""
    width = len(board[0])
    height = len(board)

    for line in lines(width, height):
        x, y = line[0]
        first = board[x][y]

        if first and all(board[i][j] == first for i, j in line):
            return first

    return False

It's also more typical to return None on failure.

As Caridorc says, return None is much better than # Implicit return None at the end of the function. f_positive should also use try's else clause to minimize the area under it:

def f_positive(string):
    try:
        val = int(string)
    except ValueError:
        pass
    else:
        if val > 0: return val

    return

Sadly there is not try ... elif ;). I would personally invert the logic to an early return:

def f_positive(string):
    try:
        val = int(string)
    except ValueError:
        return

    if val <= 0:
        return

    return val

f_coords is too tightly spaced. Lines are cheap, here are a few for free:

def f_coords(board):
    """Get function handling user input for board coordinates."""
    def f(i):
        i = i.split(" ")
        if len(i) != 2:
            return

        x = f_positive(i[0])
        y = f_positive(i[1])

        if x is None or y is None:
            return

        if board[y - 1][x - 1] != 0:
            return

        return (x - 1, y - 1)
    return f

i is to short, I suggest user_input. finner would be better, traditional naming. You can shorten this to

def f_coords(board):
    """Get function handling user input for board coordinates."""
    def inner(user_input):
        try:
            x, y = map(int, user_input.split(" "))
        except ValueError:
            return

        x -= 1; y -= 1

        if x < 0 or y < 0 or board[y][x]:
            return

        return x, y

    return inner

although you're not checking for large numbers; board[y][x] can crash. Add that check:

def f_coords(board):
    """Get function handling user input for board coordinates."""
    def inner(user_input):
        try:
            x, y = map(int, user_input.split(" "))
        except ValueError:
            return

        x -= 1; y -= 1

        if not (0 <= x < len(board) and 0 <= y < len(board[0])):
            return

        if board[y][x]:
            return

        return x, y
    return inner

print_board's docstring needs indenting too.

Personally I'd use () for a circle; it looks much rounded and easier to distinguish. Your comments for

elif value == 1:
    return "<>" # circle
elif value == 2:
    return "><" # cross

are bad; if the programmer needs commenting then the user is going to be confused.

The cell function should just use a dictionary:

def cell(value):
    """Get a 2-character representation of a cell."""
    return {0: "  ", 1: "()", 2: "><"}[value]

I don't know what you mean by print is annoying. If you have encountered difficulty with something, a good comment will explain what is problematic - not just that it is. Hopefully you'll find the new one more capable:

def cell(value):
    """Get a 2-character representation of a cell."""
    return {0: "  ", 1: "()", 2: "><"}[value]

width = len(board[0])
height = len(board)

numbers = ["{:2}".format(i) for i in range(1, width+1)]
separator = "    " + "+--" * width + "+"

# Output
print("    ", *numbers)

for i, row in enumerate(board, 1):
    print(separator)
    print("{:3} |{}|".format(i, "|".join(map(cell, row))))

print(separator)

In play, board's initialization can be simplified:

board = [[0] * board_size[0] for y in range(board_size[1])]

I would also do

width, height = board_size
board = [[0] * width for y in range(height)]

while turn < width * height and check_win(board) is None: should be a for and a check:

for turn in range(width * height):
    if check_win(board) is not None:
        break

Your %s formatting in print would be better using .format. Also, use %s over %d unless you actually need the specific %d operator.

("circle", "cross")[turn % 2]

should be

"cross" if turn % 2 else "circle"

turn % 2 should be extracted into a player variable since it's used quite a bit. I would even change the loop to

for player_turn in islice(cycle((1, 2)), width * height):

Your prompt could use some line wrapping

    x, y = prompt(
        "Please enter board coordinates to place your piece on.\n"
        "(example: `2 3` for 2nd column 3rd row): ",
        f_coords(board)
    )

Finally, the

# Python needs a damn do..while construct
# Can't avoid repeating myself here.

is trivially solved with break. Look at this isomorphism:

while True:           do {
    things()              things();
    if not x: break   } while (x)

A great thing about the while True version is that it's more flexible! Python doesn't need do ... while.

Here's the code:

while True:
    board_size = (prompt("Please enter tic-tac-toe board width:  ", f_positive),
                  prompt("Please ented tic-tac-toe board height: ", f_positive))
    play(board_size=board_size)

    if not prompt("Would you like to play again? (y/N) "):
        break

Finally, throw that in main and make a get_shape(board) -> width, height function and I'm done.

Here's the code

# encoding: utf-8

## Tic-tac-toe game by Mateon1

from __future__ import print_function

from itertools import chain, cycle, islice

try:
    input = raw_input
    range = range
except NameError:
    pass

def get_shape(board):
    return len(board[0]), len(board)

def prompt(question, check_func=lambda i: i.lower() == "y"):
    """
    Prompt the user with 'question'.

    Await until check_func(<user_input>) returns a non-None value and returns it
    """
    while True:
        value = check_func(input(question))
        if value is not None:
            return value

def lines(width, height):    
    for y in range(width):
        yield [(x, y) for x in range(height)]

    for x in range(height):
        yield [(x, y) for y in range(width)]

    if width == height:
        yield [(x,  x) for x in range(width)]
        yield [(x, -x) for x in range(width)]

def check_win(board):
    """Check if there is a win condition, returns who won or None otherwise."""
    width, height = get_shape(board)

    for line in lines(width, height):
        x, y = line[0]
        first = board[x][y]

        if first and all(board[i][j] == first for i, j in line):
            return first

    return None

def f_positive(string):
    try:
        val = int(string)
    except ValueError:
        return

    if val <= 0:
        return

    return val

def f_coords(board):
    """Get function handling user input for board coordinates."""
    width, height = get_shape(board)

    def inner(user_input):
        try:
            x, y = map(int, user_input.split(" "))
        except ValueError:
            return

        x -= 1; y -= 1

        if not (0 <= x < width and 0 <= y < height):
            return

        if board[y][x]:
            return

        return x, y

    return inner

def print_board(board):
    """
    Print the game board.

    Output example:

          1  2  3
        +--+--+--+
      1 |()|  |()|
        +--+--+--+
      2 |()|><|  |
        +--+--+--+
      3 |><|  |  |
        +--+--+--+
    """

    def cell(value):
        """Get a 2-character representation of a cell."""
        return {0: "  ", 1: "()", 2: "><"}[value]

    width, height = get_shape(board)

    numbers = ["{:2d}".format(i) for i in range(1, width+1)]
    separator = "    " + "+--" * width + "+"

    # Output
    print("    ", *numbers)

    for i, row in enumerate(board, 1):
        print(separator)
        print("{:3d} |{}|".format(i, "|".join(map(cell, row))))

    print(separator)

def play(board_size=(3, 3)):
    width, height = board_size
    board = [[0] * width for y in range(height)]
    # board[y][x]:
    # * 0 - empty
    # * 1 - () // circle
    # * 2 - >< // cross
    for player_turn in islice(cycle((1, 2)), width * height):
        if check_win(board) is not None:
            break

        print("Player {}'s ({}) turn!".format(player_turn, "circle" if player_turn == 1 else "cross"))

        print_board(board)
        x, y = prompt(
            "Please enter board coordinates to place your piece on.\n"
            "(example: `2 3` for 2nd column 3rd row): ",
            f_coords(board)
        )

        board[y][x] = player_turn

    print("=== Game over! ===")
    print_board(board)

    winning_player = check_win(board)
    if winning_player is None:
        print("The game was a tie!")
    else:
        print("Player {} ({}) won!".format(winning_player, "circle" if winning_player == 1 else "cross"))

def main():
    while True:
        board_size = (prompt("Please enter tic-tac-toe board width:  ", f_positive),
                      prompt("Please ented tic-tac-toe board height: ", f_positive))
        play(board_size=board_size)

        if not prompt("Would you like to play again? (y/N) "):
            break

if __name__ == "__main__":
    main()