Skip to content

Commit 385e76b

Browse files
committed
Project 21 solution
1 parent abbd319 commit 385e76b

File tree

2 files changed

+180
-37
lines changed

2 files changed

+180
-37
lines changed

21_tictactoe/test.py

+44-37
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,24 @@
77
import re
88
import string
99

10-
prg = './tictactoe.py'
10+
PRG = "./tictactoe.py"
1111

1212

1313
# --------------------------------------------------
1414
def test_exists():
1515
"""exists"""
1616

17-
assert os.path.isfile(prg)
17+
assert os.path.isfile(PRG)
1818

1919

2020
# --------------------------------------------------
2121
def test_usage():
2222
"""usage"""
2323

24-
for flag in ['-h', '--help']:
25-
rv, out = getstatusoutput(f'{prg} {flag}')
24+
for flag in ["-h", "--help"]:
25+
rv, out = getstatusoutput(f"python {PRG} {flag}")
2626
assert rv == 0
27-
assert out.lower().startswith('usage')
27+
assert out.lower().startswith("usage")
2828

2929

3030
# --------------------------------------------------
@@ -42,7 +42,7 @@ def test_no_input():
4242
No winner.
4343
""".strip()
4444

45-
rv, out = getstatusoutput(prg)
45+
rv, out = getstatusoutput(f"python {PRG}")
4646
assert rv == 0
4747
assert out.strip() == board
4848

@@ -53,8 +53,8 @@ def test_bad_board():
5353

5454
expected = '--board "{}" must be 9 characters of ., X, O'
5555

56-
for bad in ['ABC', '...XXX', 'XXXOOOXX']:
57-
rv, out = getstatusoutput(f'{prg} --board {bad}')
56+
for bad in ["ABC", "...XXX", "XXXOOOXX"]:
57+
rv, out = getstatusoutput(f"python {PRG} --board {bad}")
5858
assert rv != 0
5959
assert re.search(expected.format(bad), out)
6060

@@ -63,8 +63,8 @@ def test_bad_board():
6363
def test_bad_player():
6464
"""dies on bad player"""
6565

66-
bad = random.choice([c for c in string.ascii_uppercase if c not in 'XO'])
67-
rv, out = getstatusoutput(f'{prg} -p {bad}')
66+
bad = random.choice([c for c in string.ascii_uppercase if c not in "XO"])
67+
rv, out = getstatusoutput(f"python {PRG} -p {bad}")
6868
assert rv != 0
6969
expected = f"-p/--player: invalid choice: '{bad}'"
7070
assert re.search(expected, out)
@@ -75,17 +75,17 @@ def test_bad_cell_int():
7575
"""dies on bad cell"""
7676

7777
for bad in [0, 10]:
78-
rv, out = getstatusoutput(f'{prg} --cell {bad}')
78+
rv, out = getstatusoutput(f"python {PRG} --cell {bad}")
7979
assert rv != 0
80-
assert re.search(f'-c/--cell: invalid choice: {bad}', out)
80+
assert re.search(f"-c/--cell: invalid choice: {bad}", out)
8181

8282

8383
# --------------------------------------------------
8484
def test_bad_cell_str():
8585
"""dies on bad cell string value"""
8686

8787
bad = random.choice(string.ascii_letters)
88-
rv, out = getstatusoutput(f'{prg} --cell {bad}')
88+
rv, out = getstatusoutput(f"python {PRG} --cell {bad}")
8989
assert rv != 0
9090
assert re.search(f"-c/--cell: invalid int value: '{bad}'", out, re.I)
9191

@@ -94,10 +94,10 @@ def test_bad_cell_str():
9494
def test_both_player_and_cell():
9595
"""test for both --player and --cell"""
9696

97-
player = random.choice('XO')
98-
rv, out = getstatusoutput(f'{prg} --player {player}')
97+
player = random.choice("XO")
98+
rv, out = getstatusoutput(f"python {PRG} --player {player}")
9999
assert rv != 0
100-
assert re.search('Must provide both --player and --cell', out)
100+
assert re.search("Must provide both --player and --cell", out)
101101

102102

103103
# --------------------------------------------------
@@ -115,7 +115,7 @@ def test_good_board_01():
115115
No winner.
116116
""".strip()
117117

118-
rv, out = getstatusoutput(f'{prg} -b .........')
118+
rv, out = getstatusoutput(f"python {PRG} -b .........")
119119
assert rv == 0
120120
assert out.strip() == board
121121

@@ -135,7 +135,7 @@ def test_good_board_02():
135135
No winner.
136136
""".strip()
137137

138-
rv, out = getstatusoutput(f'{prg} --board ...OXX...')
138+
rv, out = getstatusoutput(f"python {PRG} --board ...OXX...")
139139
assert rv == 0
140140
assert out.strip() == board
141141

@@ -155,7 +155,7 @@ def test_mutate_board_01():
155155
No winner.
156156
""".strip()
157157

158-
rv, out = getstatusoutput(f'{prg} -b ......... --player X -c 1')
158+
rv, out = getstatusoutput(f"python {PRG} -b ......... --player X -c 1")
159159
assert rv == 0
160160
assert out.strip() == board
161161

@@ -175,7 +175,7 @@ def test_mutate_board_02():
175175
O has won!
176176
""".strip()
177177

178-
rv, out = getstatusoutput(f'{prg} --board XXO...OOX --p O -c 5')
178+
rv, out = getstatusoutput(f"python {PRG} --board XXO...OOX --p O -c 5")
179179
assert rv == 0
180180
assert out.strip() == board
181181

@@ -184,11 +184,11 @@ def test_mutate_board_02():
184184
def test_mutate_cell_taken():
185185
"""test for a cell already taken"""
186186

187-
rv1, out1 = getstatusoutput(f'{prg} -b XXO...OOX --player X --cell 9')
187+
rv1, out1 = getstatusoutput(f"python {PRG} -b XXO...OOX --player X --cell 9")
188188
assert rv1 != 0
189189
assert re.search('--cell "9" already taken', out1)
190190

191-
rv2, out2 = getstatusoutput(f'{prg} --board XXO...OOX --p O -c 1')
191+
rv2, out2 = getstatusoutput(f"python {PRG} --board XXO...OOX --p O -c 1")
192192
assert rv2 != 0
193193
assert re.search('--cell "1" already taken', out2)
194194

@@ -197,30 +197,37 @@ def test_mutate_cell_taken():
197197
def test_winning():
198198
"""test winning boards"""
199199

200-
wins = [('PPP......'), ('...PPP...'), ('......PPP'), ('P..P..P..'),
201-
('.P..P..P.'), ('..P..P..P'), ('P...P...P'), ('..P.P.P..')]
200+
wins = [
201+
("PPP......"),
202+
("...PPP..."),
203+
("......PPP"),
204+
("P..P..P.."),
205+
(".P..P..P."),
206+
("..P..P..P"),
207+
("P...P...P"),
208+
("..P.P.P.."),
209+
]
202210

203-
for player in 'XO':
204-
other_player = 'O' if player == 'X' else 'X'
211+
for player in "XO":
212+
other_player = "O" if player == "X" else "X"
205213

206214
for board in wins:
207-
board = board.replace('P', player)
208-
dots = [i for i in range(len(board)) if board[i] == '.']
215+
board = board.replace("P", player)
216+
dots = [i for i in range(len(board)) if board[i] == "."]
209217
mut = random.sample(dots, k=2)
210-
test_board = ''.join([
211-
other_player if i in mut else board[i]
212-
for i in range(len(board))
213-
])
214-
out = getoutput(f'{prg} -b {test_board}').splitlines()
215-
assert out[-1].strip() == f'{player} has won!'
218+
test_board = "".join(
219+
[other_player if i in mut else board[i] for i in range(len(board))]
220+
)
221+
out = getoutput(f"python {PRG} -b {test_board}").splitlines()
222+
assert out[-1].strip() == f"{player} has won!"
216223

217224

218225
# --------------------------------------------------
219226
def test_losing():
220227
"""test losing boards"""
221228

222-
losing_board = list('XXOO.....')
229+
losing_board = list("XXOO.....")
223230
for i in range(10):
224231
random.shuffle(losing_board)
225-
out = getoutput(f'{prg} -b {"".join(losing_board)}').splitlines()
226-
assert out[-1].strip() == 'No winner.'
232+
out = getoutput(f'python {PRG} -b {"".join(losing_board)}').splitlines()
233+
assert out[-1].strip() == "No winner."

21_tictactoe/tictactoe.py

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Author : Luke McGuire <[email protected]>
4+
Date : 2024-07-03
5+
Purpose: Tic-Tac-Toe
6+
"""
7+
8+
import argparse
9+
10+
11+
# --------------------------------------------------
12+
def get_args():
13+
"""Get command-line arguments"""
14+
15+
parser = argparse.ArgumentParser(
16+
description="Tic-Tac-Toe",
17+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
18+
)
19+
20+
parser.add_argument(
21+
"-b",
22+
"--board",
23+
help="The state of the board",
24+
metavar="board",
25+
type=str,
26+
default="." * 9,
27+
)
28+
29+
parser.add_argument(
30+
"-p",
31+
"--player",
32+
help="Player",
33+
metavar="player",
34+
type=str,
35+
choices=["X", "O"],
36+
default=None,
37+
)
38+
39+
parser.add_argument(
40+
"-c",
41+
"--cell",
42+
help="Cell 1-9",
43+
metavar="cell",
44+
type=int,
45+
choices=range(1, 10),
46+
default=None,
47+
)
48+
49+
args = parser.parse_args()
50+
51+
if len(args.board) != 9 or any(c not in "XO." for c in args.board):
52+
parser.error(f'--board "{args.board}" must be 9 characters of ., X, O')
53+
54+
if any([args.player, args.cell]) and not all([args.player, args.cell]):
55+
parser.error("Must provide both --player and --cell")
56+
57+
if args.cell and args.board[args.cell - 1] != ".":
58+
parser.error(f'--cell "{args.cell}" already taken')
59+
60+
return args
61+
62+
63+
# --------------------------------------------------
64+
def format_board(board: str) -> str:
65+
"""Formats the board string as a tic-tac-toe grid"""
66+
67+
row_divider = "-------------"
68+
cells = [str(i) if c == "." else c for i, c in enumerate(board, start=1)]
69+
70+
cells_templ = "| {} | {} | {} |"
71+
72+
return "\n".join(
73+
[
74+
row_divider,
75+
cells_templ.format(*cells[:3]),
76+
row_divider,
77+
cells_templ.format(*cells[3:6]),
78+
row_divider,
79+
cells_templ.format(*cells[6:9]),
80+
row_divider,
81+
]
82+
)
83+
84+
85+
# --------------------------------------------------
86+
def find_winner(board):
87+
"""Determine if there is a winner"""
88+
89+
wins = [
90+
("PPP......"),
91+
("...PPP..."),
92+
("......PPP"),
93+
("P..P..P.."),
94+
(".P..P..P."),
95+
("..P..P..P"),
96+
("P...P...P"),
97+
("..P.P.P.."),
98+
]
99+
100+
for player in "XO":
101+
for winning_board in wins:
102+
if is_winning(board, winning_board, player):
103+
return player
104+
105+
return None
106+
107+
108+
# --------------------------------------------------
109+
def is_winning(board, condition, player):
110+
"""Check if the board matches a win condition"""
111+
condition = condition.replace("P", player)
112+
for win_cell, board_cell in zip(condition, board):
113+
if win_cell not in (".", board_cell):
114+
return False
115+
return True
116+
117+
118+
# --------------------------------------------------
119+
120+
121+
def main():
122+
"""Make a jazz noise here"""
123+
124+
args = get_args()
125+
board = args.board
126+
if args.cell:
127+
board = board[: args.cell - 1] + args.player + board[args.cell :]
128+
129+
winner = find_winner(board)
130+
print(format_board(board))
131+
print(f"{winner} has won!" if winner else "No winner.")
132+
133+
134+
# --------------------------------------------------
135+
if __name__ == "__main__":
136+
main()

0 commit comments

Comments
 (0)