Skip to content

Update and add chess piece style, prevent repeated falling, victory &… #123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion docs/tictactoe.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,44 @@
Tic Tac Toe
===========

A paper-and-pencil game for two players.
A single-player vs computer Tic Tac Toe game with the following features:

- **Colored and styled pieces**:
X in blue, O in red, with thicker lines for clarity.
- **Move validation**:
Prevents placing a piece on an occupied square.
- **Win and draw detection**:
Automatically detects and displays a result message when someone
wins or when the board is full (draw).
- **Smart AI opponent**:
Uses the Minimax algorithm for optimal play, ensuring a
challenging game.

Gameplay
--------

1. **Starting the game**:

.. code-block:: python

python tictactoe.py

2. **Player moves**:
The human player uses X (blue) and always moves first. Click on
any empty square in the 3×3 grid to place your X.

3. **Computer response**:
After your move, the computer (O in red) will calculate its optimal
move and place an O after a brief delay.

4. **Winning**:
The first player to align three of their own pieces in a row
(horizontal, vertical, or diagonal) wins.

5. **Draw**:
If all nine squares are filled without a winner, the game ends
in a draw.

.. literalinclude:: ../src/freegames/tictactoe.py
:language: python
:linenos:
153 changes: 123 additions & 30 deletions src/freegames/tictactoe.py
Original file line number Diff line number Diff line change
@@ -1,64 +1,157 @@
"""Tic Tac Toe

Exercises

1. Give the X and O a different color and width.
2. What happens when someone taps a taken spot?
3. How would you detect when someone has won?
4. How could you create a computer player?
"""

from turtle import *

from freegames import line

# ——— 全局状态 ———
state = {
'player': 0, # 0 = X(人),1 = O(电脑)
'board': [[None]*3 for _ in range(3)],
'game_over': False,
}

# 绘制格子
def grid():
"""Draw tic-tac-toe grid."""
line(-67, 200, -67, -200)
line(67, 200, 67, -200)
line( 67, 200, 67, -200)
line(-200, -67, 200, -67)
line(-200, 67, 200, 67)

line(-200, 67, 200, 67)

# 画 X
def drawx(x, y):
"""Draw X player."""
"""Draw X player in blue, width=4."""
color('blue')
width(4)
line(x, y, x + 133, y + 133)
line(x, y + 133, x + 133, y)


# 画 O
def drawo(x, y):
"""Draw O player."""
"""Draw O player in red, width=4."""
up()
goto(x + 67, y + 5)
down()
color('red')
width(4)
circle(62)


# 将点击坐标归整到格子左下角
def floor(value):
"""Round value down to grid with square size 133."""
return ((value + 200) // 133) * 133 - 200

# 检测胜利
def check_win():
b = state['board']
# 行、列检测
for i in range(3):
if b[i][0] is not None and b[i][0] == b[i][1] == b[i][2]:
return b[i][0]
if b[0][i] is not None and b[0][i] == b[1][i] == b[2][i]:
return b[0][i]
# 对角线
if b[0][0] is not None and b[0][0] == b[1][1] == b[2][2]:
return b[0][0]
if b[0][2] is not None and b[0][2] == b[1][1] == b[2][0]:
return b[0][2]
# 平局
if all(all(cell is not None for cell in row) for row in b):
return -1
return None

def evaluate():
"""胜利时得分:电脑 O 赢 +1,人 X 赢 -1,平局或未结束 0"""
winner = check_win()
if winner == 1: # O (电脑)
return +1
if winner == 0: # X (人)
return -1
return 0 # 平局 or 未结束

def minimax(is_maximizing):
"""Minimax 递归,返回 (最佳分值, (row, col) 最佳落子)"""
score = evaluate()
if score != 0 or all(all(cell is not None for cell in row) for row in state['board']):
# 终止:有人赢或满盘
return score, None

if is_maximizing:
best = (-2, None) # 初始化为比最小值还低
for r in range(3):
for c in range(3):
if state['board'][r][c] is None:
state['board'][r][c] = 1 # 电脑落 O
val, _ = minimax(False)
state['board'][r][c] = None
if val > best[0]:
best = (val, (r, c))
return best
else:
worst = (2, None) # 初始化为比最大值还高
for r in range(3):
for c in range(3):
if state['board'][r][c] is None:
state['board'][r][c] = 0 # 玩家落 X
val, _ = minimax(True)
state['board'][r][c] = None
if val < worst[0]:
worst = (val, (r, c))
return worst

# 落子回调
def tap(x, y):
if state['game_over']:
return

state = {'player': 0}
players = [drawx, drawo]
# 计算格子索引
fx = floor(x)
fy = floor(y)
col = int((fx + 200) // 133)
row = int((fy + 200) // 133)

# 已有落子则忽略
if state['board'][row][col] is not None:
return

def tap(x, y):
"""Draw X or O in tapped square."""
x = floor(x)
y = floor(y)
# 当前玩家落子
player = state['player']
draw = players[player]
draw(x, y)
draw = drawx if player == 0 else drawo
draw(fx, fy)
update()
state['player'] = not player


state['board'][row][col] = player

# 检查胜利
result = check_win()
if result is not None:
state['game_over'] = True
penup()
goto(0, 0)
color('green')
if result == 0:
write("You Win!", align='center', font=('Arial', 24, 'bold'))
elif result == 1:
write("Computer Wins!", align='center', font=('Arial', 24, 'bold'))
else:
write("Draw!", align='center', font=('Arial', 24, 'bold'))
return

# 切换玩家
state['player'] = 1 - player

# 如果轮到电脑,就随机落子
if state['player'] == 1:
_, move = minimax(True) # True 表示电脑层 (maximizing)
r, c = move
# 计算该格左下角坐标
fx_ai = c*133 - 200
fy_ai = r*133 - 200
# 延迟一点,看起来更自然
ontimer(lambda: tap(fx_ai + 1, fy_ai + 1), 200)

# 主流程
setup(420, 420, 370, 0)
hideturtle()
tracer(False)
grid()
update()
onscreenclick(tap)
done()
done()