روبوت لعبة إكس-أو (تيك-تاك-تو)
درس الفيديو: دروس فيديو من شركة Singtown Technology حول OpenMV - روبوت الذراع الآلي للعبة إكس-أو، مسابقة الإلكترونيات الجامعية 2024
ينقسم روبوت لعبة إكس-أو إلى ثلاث خطوات: 1- التعرف على الصور للحصول على مصفوفة قطع اللعبة. 2- خوارزمية استراتيجية اللعب. 3- التحكم بالذراع الآلية لالتقاط القطع ووضعها.
من ناحية العتاد، استخدمنا OpenMV4 H7 من شركة Singtown Technology، وجميع الأكواد تعمل على OpenMV نفسه! بما في ذلك التحكم بالذراع الآلية، والتحكم بالمرحل (relay)، وخوارزمية استراتيجية اللعب، وخوارزمية التعرف على الصور.
الذراع الآلية مطبوعة مباشرة بتقنية الطباعة ثلاثية الأبعاد، وتستخدم 3 محركات سيرفو، ويمكن التحكم بها مباشرة عبر لوحة توسعة السيرفو الخاصة بـ OpenMV.
يتم التقاط القطع باستخدام مغناطيس كهربائي، حيث استخدمت 10 بطاريات زر (أزرارية) كقطع للعبة بشكل عملي. يمكن لـ OpenMV التحكم بالمغناطيس الكهربائي عبر المرحل.
1- التعرف على الصور بسيط جدًا: أولًا يتم الحصول على صورة بتدرج رمادي، ثم يتم الحصول على معلومات اللون داخل 9 مناطق من رقعة اللعب، ويتم ذلك في OpenMV باستخدام دالة الإحصائيات statistics. ومن خلال معلومات التدرج الرمادي يمكن معرفة ما إذا كانت القطعة سوداء أو بيضاء أو أن المربع فارغ. 2- لحساب الخطوة التالية، أخبرني صديقي المعلّم ChatGPT باستخدام خوارزمية minimax، وقام بكتابة هذا الجزء من الكود لي. 3- التحكم بالذراع الآلية بسيط نسبيًا أيضًا؛ حيث يتم تحديد موضع الذراع في منطقة انتظار التقاط القطع، وكذلك في كل مربع من مربعات رقعة اللعب التسعة. وعادةً يمكن إنجاز ذلك عبر وظيفة التعليم (Teach)، لكن نظرًا لأن ذراعي الآلية صُنعت خلال يوم واحد فقط وكان الوقت ضيقًا جدًا، فقد حددت المواضع مباشرة من خلال الكود.
# robot.py
import time
from servo import Servos
from machine import SoftI2C, Pin
import math
# التحكم بالمرحل، والمرحل يتحكم بالمغناطيس الكهربائي
pin1 = Pin('P1', Pin.OUT_PP, Pin.PULL_NONE)
pin1.value(0)
# لوحة توسعة السيرفو PCA9685
i2c = SoftI2C(sda=Pin('P5'), scl=Pin('P4'))
servo = Servos(i2c, address=0x40, freq=50, min_us=650, max_us=2800, degrees=180)
# المواضع الابتدائية للمحركات الثلاثة
servo.position(0, 0)
servo.position(1, 90)
servo.position(2, 90)
# متغير عام، يُستخدم للحركة البطيئة
servo_positions = [0,90,90]
# التحكم بحركة محرك سيرفو واحد
def move(index, angle):
servo.position(index, angle)
servo_positions[index] = angle
# التحكم بحركة المحركات الثلاثة
def move_list(angle_list):
print(angle_list)
move(0, int(angle_list[0]))
move(1, int(angle_list[1]))
move(2, int(angle_list[2]))
# التحكم بالحركة البطيئة للمحركات الثلاثة
def slow_move_to(angle_list):
init_positions = servo_positions.copy()
d0 = angle_list[0] - init_positions[0]
d1 = angle_list[1] - init_positions[1]
d2 = angle_list[2] - init_positions[2]
dm = int(max(abs(d0), abs(d1), abs(d2)))
if dm == 0:
return
for i in range(dm+1):
move_list([init_positions[0]+i*d0/dm,
init_positions[1]+i*d1/dm,
init_positions[2]+i*d2/dm])
time.sleep_ms(40)
# تحديد مواضع منطقة التقاط القطع
PICK = [[74,128,19], [82,128,17], [90,125,14], [98,128,17], [106,128,19]]
# موضع رفع الذراع في منطقة الالتقاط
HIGH_PICK = [90,95,55]
# مواضع وضع القطع على رقعة اللعب
BOARD = [
[[82,150,55], [82,140,40], [81,133,30]],
[[90,150,55], [90,140,40], [90,133,30]],
[[98,150,55], [98,140,40], [99,133,30]]
]
# الموضع فوق رقعة اللعب عند وضع القطعة
HIGH_BOARD = [90,120,70]
# التقاط قطعة ووضعها عند الإحداثيين x,y
def pick_and_place(x,y):
slow_move_to(HIGH_PICK)
time.sleep_ms(500)
slow_move_to(PICK[2])
time.sleep_ms(500)
slow_move_to(HIGH_PICK)
time.sleep_ms(500)
slow_move_to(HIGH_BOARD)
time.sleep_ms(500)
slow_move_to(BOARD[y][x])
time.sleep_ms(500)
pin1.value(1) # فتح المرحل
time.sleep_ms(500)
slow_move_to(HIGH_PICK)
pin1.value(0) # إغلاق المرحل
time.sleep_ms(500)
slow_move_to([0,90,90])
if __name__ == "__main__":
# تُستخدم للمعايرة والاختبار
time.sleep_ms(1)
for order in [
BOARD[1][1], BOARD[0][0], BOARD[1][0],
BOARD[2][0], BOARD[2][1], BOARD[2][2],
BOARD[1][2], BOARD[0][2], BOARD[0][1],
]:
slow_move_to(HIGH_BOARD)
time.sleep_ms(500)
slow_move_to(order)
time.sleep_ms(500)
#slow_move_to(BOARD[0][2])
#for x in PICK:
#slow_move_to(HIGH_PICK)
#time.sleep_ms(500)
#slow_move_to(x)
#time.sleep_ms(500)
slow_move_to([0,90,90])
# chess.py
SIZE = 3
# التحقق من الفوز
def check_win(board, player):
# Check rows and columns
for i in range(SIZE):
if all(board[i][j] == player for j in range(SIZE)) or \
all(board[j][i] == player for j in range(SIZE)):
return True
# Check diagonals
if all(board[i][i] == player for i in range(SIZE)) or \
all(board[i][SIZE - 1 - i] == player for i in range(SIZE)):
return True
return False
# التحقق من التعادل
def check_draw(board):
return all(board[i][j] != ' ' for i in range(SIZE) for j in range(SIZE))
# حساب نقاط الاستراتيجية
def minimax(board, depth, is_maximizing):
computer = 'X'
player = 'O'
if check_win(board, computer):
return 10 - depth
if check_win(board, player):
return depth - 10
if check_draw(board):
return 0
if is_maximizing:
best_score = float('-inf')
for i in range(SIZE):
for j in range(SIZE):
if board[i][j] == ' ':
board[i][j] = computer
score = minimax(board, depth + 1, False)
board[i][j] = ' '
best_score = max(score, best_score)
return best_score
else:
best_score = float('inf')
for i in range(SIZE):
for j in range(SIZE):
if board[i][j] == ' ':
board[i][j] = player
score = minimax(board, depth + 1, True)
board[i][j] = ' '
best_score = min(score, best_score)
return best_score
# حساب موضع الخطوة التالية
def computer_move(board):
if board == [
[" "," "," "],
[" "," "," "],
[" "," "," "]
]:
return 1,1
best_score = float('-inf')
move = (-1, -1)
for i in range(SIZE):
for j in range(SIZE):
if board[i][j] == ' ':
board[i][j] = 'X'
score = minimax(board, 0, False)
board[i][j] = ' '
if score > best_score:
best_score = score
move = (i, j)
if move != (-1, -1):
# board[move[0]][move[1]] = 'X'
print(f"Computer places X at ({move[0]}, {move[1]})")
return move[0], move[1]
# التحقق من دور من يلعب الآن
def check_turn(board):
x_count = sum(row.count("X") for row in board)
o_count = sum(row.count("O") for row in board)
return "X" if x_count == o_count else "O"
# main.py
import csi, image, time
csi0 = csi.CSI()
from pyb import Pin
import robot
import chess
csi0.reset()
csi0.pixformat(csi.GRAYSCALE)
csi0.framesize(csi.QVGA)
csi0.snapshot(time = 2000)
clock = time.clock()
# مفتاح لمسي (زر ضغط)
pin0 = Pin('P0', Pin.IN, Pin.PULL_UP)
distance = 43
block = 10
# توليد مواضع مناطق الشبكة 3×3
def generate_centered_rois(width, height, b, k):
rois = []
# حساب إزاحة موضع مركز كل منطقة اهتمام (ROI)
offset = (b - k) // 2
# حساب عرض وارتفاع مصفوفة 3×3 بالكامل
total_width = 3 * b
total_height = 3 * b
# حساب نقطة البداية في الزاوية العلوية اليسرى لتوسيط المصفوفة
start_x = (width - total_width) // 2
start_y = (height - total_height) // 2
for i in range(3):
row = []
for j in range(3):
x_center = start_x + j * b + b // 2
y_center = start_y + i * b + b // 2
x = x_center - k // 2
y = y_center - k // 2
row.append((x, y, k, k))
rois.append(row)
return rois
# مواضع مناطق الشبكة 3×3
rois = generate_centered_rois(csi0.width(), csi0.height(), distance, block)
# مصفوفة رقعة اللعب
# القطعة السوداء: X
# القطعة البيضاء: O
# لا توجد قطعة: سلسلة فارغة
board = [
[" "," "," "],
[" "," "," "],
[" "," "," "],
]
# الانتظار حتى يُضغط المفتاح ثم يُترك
def wait_key():
while pin0.value():
img = csi0.snapshot().lens_corr(1.8)
for y in range(len(rois)):
for x in range(len(rois[y])):
img.draw_rectangle(rois[y][x])
while not pin0.value():
time.sleep_ms(1)
while(True):
clock.tick()
wait_key()
img = csi0.snapshot().lens_corr(1.8)
# الحصول على مصفوفة رقعة اللعب عبر التعرف على الصورة
for y in range(len(rois)):
for x in range(len(rois[y])):
gray = img.get_statistics(roi=rois[y][x]).mean
if gray < 100:
board[y][x] = "X"
elif gray > 200:
board[y][x] = "O"
else:
board[y][x] = " "
# طباعة مصفوفة رقعة اللعب الحالية
for line in board:
print(line)
print()
# رسم مصفوفة رقعة اللعب
for y in range(len(rois)):
for x in range(len(rois[y])):
if board[y][x] == "X":
color = 255
elif board[y][x] == "O":
color = 0
elif board[y][x] == " ":
color = 127
img.draw_rectangle(rois[y][x], color=color)
# استراتيجية اللعب
if chess.check_win(board, 'O'):
print("你赢啦!")
elif chess.check_win(board, 'X'):
print("我赢啦!")
elif chess.check_draw(board):
print("平局啦!")
elif chess.check_turn(board) == "X":
# حساب موضع القطعة التالية
line,row = chess.computer_move(board)
# رسم علامة زائد على الموضع الهدف في رقعة اللعب
img.draw_cross((int(rois[line][row][0]+block/2), int(rois[line][row][1]+block/2)), size=block, color=0)
csi0.flush()
# الروبوت يلتقط القطعة ويضعها
robot.pick_and_place(row, line)
csi0.flush()
elif chess.check_turn(board) == "O":
print("该你下了!")