StackBots/game_plat.py

479 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import pygame
import math
import numpy as np
import random
from utils.cv_marker import cap_and_mark
# from utils.stack_exe import Stackbot
import cv2
pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("积木塔仿真")
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
LAYER_COLORS = [
(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 0, 255),
(0, 255, 255), (128, 0, 0), (0, 128, 0), (0, 0, 128), (128, 128, 0),
]
cap = cv2.VideoCapture(1)
cap.set(6,cv2.VideoWriter.fourcc('M','J','P','G'))
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
class Block:
def __init__(self, x, y, width, height, angle, layer):
self.x = x
self.y = y
self.width = width
self.height = height
self.angle = angle
self.layer = layer
self.color = LAYER_COLORS[layer % len(LAYER_COLORS)]
def draw(self, surface):
rotated_surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA)
rotated_surface.set_alpha(125)
pygame.draw.rect(rotated_surface, self.color, (0, 0, self.width, self.height))
rotated_surface = pygame.transform.rotate(rotated_surface, self.angle)
surface.blit(rotated_surface, (self.x - rotated_surface.get_width() // 2, self.y - rotated_surface.get_height() // 2))
def get_corners(self):
w, h = self.width / 2, self.height / 2
corners = [(-w, -h), (w, -h), (w, h), (-w, h)]
angle_rad = np.deg2rad(self.angle)
rotation_matrix = np.array([
[np.cos(angle_rad), -np.sin(angle_rad)],
[np.sin(angle_rad), np.cos(angle_rad)]
])
rotated_corners = np.dot(corners, rotation_matrix)
translated_corners = rotated_corners + np.array([self.x, self.y])
# print(translated_corners)
rotated_corners = [(float(point[0]), float(point[1])) for point in translated_corners]
return rotated_corners
class Tower:
def __init__(self):
self.blocks = []
self.current_layer = 0
self.layer_centroids = [] # 存储每层的重心
self.stability_threshold = 10 # 稳定性阈值(像素)
self.rotation_angle = 15 # 预设每层旋转角度
self.angle_tolerance = 5 # 允许的角度偏移
self.position_tolerance = 10 # 允许的位置偏移
self.sim_to_robot_matrix = np.array([
[1, 0, 0, -300], # 假设的转换矩阵,需要根据实际情况调整
[0, -1, 0, -200],
[0, 0, 1, 0],
[0, 0, 0, 1]
])
self.robot_to_sim_matrix = np.linalg.inv(self.sim_to_robot_matrix)
# self.stack_bot = Stackbot()
self.is_upper = False
self.ideal_positions = self.get_ideal_positions()
self.position_tolerance = 20 # 允许的位置偏差(像素)
self.angle_tolerance = 15 # 允许的角度偏差(度)
def sim_to_robot_coords(self, x, y, angle):
sim_coords = np.array([x, y, 0, 1])
robot_coords = self.sim_to_robot_matrix.dot(sim_coords)
robot_angle = (angle + 360) % 360 # 确保角度在0-359范围内
return robot_coords[0], robot_coords[1], robot_angle
def robot_to_sim_coords(self, x, y, angle):
robot_coords = np.array([x, y, 0, 1])
sim_coords = self.robot_to_sim_matrix.dot(robot_coords)
sim_angle = (angle + 360) % 360
return sim_coords[0], sim_coords[1], sim_angle
def add_block_from_robot(self, x, y, angle):
"""
这个函数根据机器人的坐标和角度,在模拟世界中添加一个方块
参数:
x (float): 机器人的 x 坐标
y (float): 机器人的 y 坐标
angle (float): 机器人的角度
返回值:
bool如果方块添加成返回 True否则返回 False
注意:这个函数需要先调用 robot_to_sim_coords 来将机器人坐标转换为模拟坐标,并且需要一个名为 Block 的类来创建方块对象
"""
sim_x, sim_y, sim_angle = self.robot_to_sim_coords(x, y, angle)
new_block = Block(sim_x, sim_y, 100, 20, sim_angle, self.current_layer)
if self.add_block(new_block):
print(f"Block added from robot at ({sim_x:.2f}, {sim_y:.2f}) with angle {sim_angle}")
return True
else:
print(f"Failed to add block from robot at ({sim_x:.2f}, {sim_y:.2f}) with angle {sim_angle}")
return False
def add_block(self, block):
self.add_block_from_sim(block)
return True
# if self.current_layer == 5:
# self.is_upper = True
# print("is upper")
# if not self.check_interference(block):
# self.blocks.append(block)
# self.stack_bot.pick_stack()
# robo_xyz = self.sim_to_robot_coords(block.x, block.y, block.angle)
# print(block.x, block.y, block.angle)
# if self.is_upper:
# self.stack_bot.place_stack(robo_xyz[0], robo_xyz[1], robo_xyz[2], self.current_layer - 5)
# else:
# self.stack_bot.place_stack(robo_xyz[0], robo_xyz[1], robo_xyz[2], self.current_layer)
# return True
# return False
def add_block_from_sim(self, block):
self.blocks.append(block)
return True
def draw(self, surface):
for block in self.blocks:
block.draw(surface)
def check_stability(self):
current_layer_centroid = self.calculate_current_layer_centroid()
tower_centroid = self.calculate_tower_centroid()
if current_layer_centroid is None or tower_centroid is None:
return True # 如果没有足够的块来计算重心,我们假设它是稳定的
horizontal_distance = abs(current_layer_centroid[0] - tower_centroid[0])
return horizontal_distance <= self.stability_threshold
def blocks_overlap(self, block1, block2):
corners1 = block1.get_corners()
corners2 = block2.get_corners()
# 检查 block1 的角是否在 block2 内部
for corner in corners1:
if self.point_inside_polygon(corner, corners2):
return True
# 检查 block2 的角是否在 block1 内部
for corner in corners2:
if self.point_inside_polygon(corner, corners1):
return True
# 检查边是否相交
for i in range(4):
for j in range(4):
if self.line_intersects(corners1[i], corners1[(i+1)%4], corners2[j], corners2[(j+1)%4]):
return True
return False
def point_inside_polygon(self, point, polygon):
x, y = point
n = len(polygon)
inside = False
p1x, p1y = polygon[0]
for i in range(n + 1):
p2x, p2y = polygon[i % n]
if y > min(p1y, p2y):
if y <= max(p1y, p2y):
if x <= max(p1x, p2x):
if p1y != p2y:
xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
if p1x == p2x or x <= xinters:
inside = not inside
p1x, p1y = p2x, p2y
return inside
def line_intersects(self, p1, p2, p3, p4):
def ccw(A, B, C):
return (C[1]-A[1]) * (B[0]-A[0]) > (B[1]-A[1]) * (C[0]-A[0])
return ccw(p1,p3,p4) != ccw(p2,p3,p4) and ccw(p1,p2,p3) != ccw(p1,p2,p4)
def check_interference(self, new_block):
# 检查是否与其他木块重叠
for block in self.blocks:
if block.layer == new_block.layer and self.blocks_overlap(block, new_block):
return True
return False
def calculate_current_layer_centroid(self):
current_layer_blocks = [block for block in self.blocks if block.layer == self.current_layer]
if not current_layer_blocks:
return None
total_area = 0
weighted_x = 0
weighted_y = 0
for block in current_layer_blocks:
area = block.width * block.height
total_area += area
weighted_x += block.x * area
weighted_y += block.y * area
if total_area == 0:
return None
centroid_x = weighted_x / total_area
centroid_y = weighted_y / total_area
return (centroid_x, centroid_y)
def calculate_tower_centroid(self):
if not self.blocks:
return None
total_area = 0
weighted_x = 0
weighted_y = 0
for block in self.blocks:
area = block.width * block.height
total_area += area
weighted_x += block.x * area
weighted_y += block.y * area
if total_area == 0:
return None
centroid_x = weighted_x / total_area
centroid_y = weighted_y / total_area
return (centroid_x, centroid_y)
def increase_layer(self):
centroid = self.calculate_current_layer_centroid()
if centroid:
self.layer_centroids.append(centroid)
self.current_layer += 1
def get_ideal_positions(self):
# 定义中心原点
center_x, center_y = 400, 450
# 定义相对坐标和角度
relative_positions = [
[(0, 40, 0), (0, -40, 0)], # 第一层
[(-40, 0, 90), (40, 0, 90)], # 第二层
[(0, 40, 0), (0, -40, 0)], # 第三层
[(-40, 0, 90), (40, 0, 90)], # 第四层
[(0, 40, 0), (0, -40, 0)], # 第五层
# ... 可以继续添加更多层 ...
]
# 转换为绝对坐标
absolute_positions = []
for layer in relative_positions:
layer_positions = []
for rel_x, rel_y, angle in layer:
abs_x = center_x + rel_x
abs_y = center_y + rel_y
layer_positions.append((abs_x, abs_y, angle))
absolute_positions.append(layer_positions)
return absolute_positions
def auto_place_block(self):
if self.current_layer >= len(self.ideal_positions):
print("塔已经完成")
return None
possible_positions = self.generate_possible_positions()
best_position = self.find_best_position(possible_positions)
if best_position:
x, y, angle = best_position
print(f"选择的最佳位置: ({x}, {y}) 角度为 {angle}") # 添加这行来调试
return Block(x, y, 100, 20, angle, self.current_layer)
else:
print("无法找到合适的放置位置")
return None
def generate_possible_positions(self):
ideal_positions = self.ideal_positions[self.current_layer]
possible_positions = []
for ideal_x, ideal_y, ideal_angle in ideal_positions:
for dx in range(-self.position_tolerance, self.position_tolerance + 1, 2):
for dy in range(-self.position_tolerance, self.position_tolerance + 1, 2):
for dangle in range(-self.angle_tolerance, self.angle_tolerance + 1, 5):
x = ideal_x + dx
y = ideal_y + dy
angle = (ideal_angle + dangle) % 360
possible_positions.append((x, y, angle))
print(f"生成的可能位置数量: {len(possible_positions)}")
return possible_positions
def find_best_position(self, possible_positions):
best_position = None
min_distance = float('inf')
for x, y, angle in possible_positions:
new_block = Block(x, y, 100, 20, angle, self.current_layer)
if self.has_support(new_block) and not self.check_interference(new_block):
temp_tower = Tower()
temp_tower.blocks = self.blocks.copy()
temp_tower.add_block(new_block)
if temp_tower.check_stability():
ideal_x, ideal_y, ideal_angle = self.ideal_positions[self.current_layer][len(self.blocks) % 2]
distance = np.sqrt((x - ideal_x)**2 + (y - ideal_y)**2) + abs(angle - ideal_angle)
if distance < min_distance:
min_distance = distance
best_position = (x, y, angle)
print(f"选择的最佳位置: {best_position},距离理想位置的距离: {min_distance}")
return best_position
def has_support(self, block):
if self.current_layer == 0:
return True # 第一层总是有支撑
block_corners = block.get_corners()
for lower_block in [b for b in self.blocks if b.layer == self.current_layer - 1]:
lower_corners = lower_block.get_corners()
for corner in block_corners:
if self.point_inside_polygon(corner, lower_corners):
return True
return False
def auto_place_layer(self):
center_x, center_y = 359, 90
block_width, block_height = 100, 20
square_size = 110 # 正方形的边长
# 基于当前层数计算旋转角度
layer_rotation = (self.current_layer * 30) % 360
# 计算正方形四个角的坐标
base_positions = [
(-square_size/2, -square_size/2), # 左上
(square_size/2, -square_size/2), # 右上
(square_size/2, square_size/2), # 右下
(-square_size/2, square_size/2) # 左下
]
# 旋转基础位置
rotated_positions = []
for x, y in base_positions:
angle_rad = math.radians(layer_rotation)
rotated_x = x * math.cos(angle_rad) - y * math.sin(angle_rad)
rotated_y = x * math.sin(angle_rad) + y * math.cos(angle_rad)
rotated_positions.append((center_x + rotated_x, center_y + rotated_y))
base_angles = [45, 135, 225, 315]
angles = [(angle - layer_rotation) % 360 for angle in base_angles]
for (x, y), angle in zip(rotated_positions, angles):
new_block = Block(x, y, block_width, block_height, angle, self.current_layer)
if self.add_block(new_block):
print(f"Block placed at ({x:.2f}, {y:.2f}) with angle {angle}")
else:
print(f"Failed to place block at ({x:.2f}, {y:.2f}) with angle {angle}")
def clear_current_layer(self):
self.blocks = [block for block in self.blocks if block.layer!= self.current_layer]
def scan_current_layer(self):
current_block_pos = cap_and_mark(cap=cap)
for block in current_block_pos:
print(block)
self.add_block_from_robot(block[0][0], block[0][1], block[1])
def main():
tower = Tower()
clock = pygame.time.Clock()
current_block = None
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
x, y = pygame.mouse.get_pos()
current_block = Block(x, y, 100, 20, 0, tower.current_layer)
elif event.type == pygame.MOUSEMOTION:
if current_block:
current_block.x, current_block.y = pygame.mouse.get_pos()
elif event.type == pygame.MOUSEBUTTONUP:
if event.button == 1 and current_block:
if tower.add_block_from_sim(current_block):
current_block = None
else:
print("CANNOT place block here, there is already a block there.")
elif event.type == pygame.KEYDOWN:
# 处理所有按键事件
if event.key == pygame.K_r and current_block: # 按 'R' 键旋转当前块
current_block.angle += 15
elif event.key == pygame.K_SPACE: # 按 'Space' 键确认当前层完成放置
tower.increase_layer()
elif event.key == pygame.K_a: # 按 'A' 键自动放置
auto_block = tower.auto_place_block()
if auto_block:
if tower.add_block_from_sim(auto_block):
print(f"成功放置积木在 ({auto_block.x}, {auto_block.y}) 角度为 {auto_block.angle}")
else:
print("无法放置积木")
else:
print("无法找到合适的放置位置")
elif event.key == pygame.K_n: # 按 'N' 键自动放置新的一层
tower.auto_place_layer()
elif event.key == pygame.K_s: # 按 'S' 键扫描当前层
tower.scan_current_layer()
screen.fill(WHITE)
tower.draw(screen)
if current_block:
current_block.draw(screen)
# 计算并绘制当前层的重心
current_centroid = tower.calculate_current_layer_centroid()
if current_centroid:
pygame.draw.circle(screen, RED, (int(current_centroid[0]), int(current_centroid[1])), 5)
font = pygame.font.Font(None, 24)
# 绘制之前层的重心
for i, centroid in enumerate(tower.layer_centroids):
pygame.draw.circle(screen, BLACK, (int(centroid[0]), int(centroid[1])), 3)
font = pygame.font.Font(None, 24)
text = font.render(f"{i+1}", True, BLACK)
screen.blit(text, (int(centroid[0]) + 10, int(centroid[1]) - 10))
if not tower.check_stability():
font = pygame.font.Font(None, 36)
text = font.render("WARNING: Tower is NOT stable", True, RED)
screen.blit(text, (WIDTH // 2 - text.get_width() // 2, 20))
font = pygame.font.Font(None, 36)
layer_text = font.render(f"Current layer: {tower.current_layer + 1}", True, BLACK)
screen.blit(layer_text, (10, 10))
pygame.display.flip()
clock.tick(60)
pygame.quit()
if __name__ == "__main__":
main()