StackBots/game_plat.py

661 lines
24 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
import sys
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)
# 定义屏幕尺寸
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
# 定义按钮尺寸
BUTTON_WIDTH = 200
BUTTON_HEIGHT = 200
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, blueprint, tower_image=None):
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.is_upper = False
self.blueprint = blueprint
self.center_x, self.center_y = 300, 300 # 定义塔的中心点
self.ideal_positions = self.get_ideal_positions()
self.position_tolerance = 20
self.angle_tolerance = 15
self.tower_image = None
if tower_image:
self.tower_image = pygame.transform.scale(tower_image, (150, 150)) # 调整图片大小
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):
# 将相对坐标转换为绝对坐标
absolute_positions = []
for layer in self.blueprint:
layer_positions = []
for rel_x, rel_y, angle in layer:
abs_x = self.center_x + rel_x
abs_y = self.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, 0
possible_positions = self.generate_possible_positions()
best_position, similarity_score = 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), similarity_score
else:
print("无法找到合适的放置位置")
return None, 0
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')
best_similarity_score = 0
blocks_in_current_layer = sum(1 for block in self.blocks if block.layer == self.current_layer)
ideal_positions = self.ideal_positions[self.current_layer]
if blocks_in_current_layer < len(ideal_positions):
ideal_x, ideal_y, ideal_angle = ideal_positions[blocks_in_current_layer]
else:
print("警告:当前层的所有位置都已被填满")
return None, 0
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(self.blueprint)
temp_tower.blocks = self.blocks.copy()
temp_tower.add_block(new_block)
if temp_tower.check_stability():
distance = np.sqrt((x - ideal_x)**2 + (y - ideal_y)**2) + abs(angle - ideal_angle)
similarity_score = 100 * (1 - distance / (np.sqrt(WIDTH**2 + HEIGHT**2) + 360))
if distance < min_distance:
min_distance = distance
best_position = (x, y, angle)
best_similarity_score = similarity_score
print(f"选择的最佳位置: {best_position},相似性评分: {best_similarity_score:.2f}")
return best_position, best_similarity_score
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 draw_tower_image(self, screen):
if self.tower_image:
image_rect = self.tower_image.get_rect()
image_rect.topright = (SCREEN_WIDTH - 10, 10) # 放置在右上角留出10像素的边距
screen.blit(self.tower_image, image_rect)
def show_tower_selection(screen):
# 加载塔型图片
tower_images = [
pygame.image.load("tower1.png"),
pygame.image.load("tower2.png"),
pygame.image.load("tower3.png")
]
# 调整图片大小
tower_images = [pygame.transform.scale(img, (BUTTON_WIDTH, BUTTON_HEIGHT)) for img in tower_images]
# 计算按钮位置
button_y = (SCREEN_HEIGHT - BUTTON_HEIGHT) // 2
button_x_start = (SCREEN_WIDTH - (BUTTON_WIDTH * 3 + 40)) // 2
# 创建按钮矩形
buttons = [
pygame.Rect(button_x_start + i * (BUTTON_WIDTH + 20), button_y, BUTTON_WIDTH, BUTTON_HEIGHT)
for i in range(3)
]
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
for i, button in enumerate(buttons):
if button.collidepoint(mouse_pos):
return i # 返回选择的塔型索引
screen.fill(WHITE)
# 绘制按钮和图片
for i, (button, image) in enumerate(zip(buttons, tower_images)):
screen.blit(image, button.topleft)
pygame.draw.rect(screen, BLACK, button, 2)
# 添加标题
font = pygame.font.Font(None, 36)
title = font.render("选择塔型", True, BLACK)
title_rect = title.get_rect(center=(SCREEN_WIDTH // 2, 50))
screen.blit(title, title_rect)
pygame.display.flip()
def polar_to_cartesian(r, theta, angle):
"""
将极坐标转换为直角坐标
参数:
r: 半径
theta: 极角(度)
angle: 方块的旋转角度(度)
返回:
(x, y, angle): 直角坐标和方块旋转角度
"""
# 将角度转换为弧度
theta_rad = math.radians(theta)
# 计算 x 和 y
x = r * math.cos(theta_rad)
y = r * math.sin(theta_rad)
# 四舍五入到整数
x = round(x)
y = round(y)
return (x, y, angle)
def get_tower_blueprint_1():
# 第一种塔型的蓝图(相对坐标)
return [
[(-40, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-35, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-30, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-25, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-20, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-15, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-10, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-5, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(0, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-5, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-10, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-15, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-20, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-25, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-30, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-35, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
[(-40, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
]
def get_tower_blueprint_2():
# 使用极坐标定义第二种塔型的蓝图
return [
[polar_to_cartesian(20, 180, 90), polar_to_cartesian(20, 0, 90)], # (20, 0, 90)
[polar_to_cartesian(20, 90, 0), polar_to_cartesian(20, 270, 0)], # (0, -20, 0)
[polar_to_cartesian(20, 165, 75), polar_to_cartesian(20, -15, 75)],
]
def get_tower_blueprint_3():
# 第三种塔型的蓝图(相对坐标)
return [
[(-60, -60, 45), (60, -60, 135), (-60, 60, 135), (60, 60, 45)],
[(0, -80, 0), (80, 0, 90), (0, 80, 0), (-80, 0, 90)],
[(-55, -55, 45), (55, -55, 135), (-55, 55, 135), (55, 55, 45)],
[(0, -75, 0), (75, 0, 90), (0, 75, 0), (-75, 0, 90)],
[(-50, -50, 45), (50, -50, 135), (-50, 50, 135), (50, 50, 45)],
[(0, -70, 0), (70, 0, 90), (0, 70, 0), (-70, 0, 90)],
[(-45, -45, 45), (45, -45, 135), (-45, 45, 135), (45, 45, 45)],
[(0, -65, 0), (65, 0, 90), (0, 65, 0), (-65, 0, 90)],
[(-40, -40, 45), (40, -40, 135), (-40, 40, 135), (40, 40, 45)],
[(0, -60, 0), (60, 0, 90), (0, 60, 0), (-60, 0, 90)],
[(-40, 0, 90), (40, 0, 90)],
[(0, 40, 0), (0, -40, 0)],
]
def main():
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("积木塔仿真")
# 加载塔型图片
tower_images = [
pygame.image.load("tower1.png"),
pygame.image.load("tower2.png"),
pygame.image.load("tower3.png")
]
# 显示塔选择界面
selected_tower = show_tower_selection(screen)
# 根据选择的塔型设置相应的蓝图和图片
if selected_tower == 0:
tower_blueprint = get_tower_blueprint_1()
elif selected_tower == 1:
tower_blueprint = get_tower_blueprint_2()
else:
tower_blueprint = get_tower_blueprint_3()
tower = Tower(tower_blueprint, tower_images[selected_tower])
clock = pygame.time.Clock()
current_block = None
similarity_score = 0 # 初始化相似性评分
font = pygame.font.Font(None, 36) # 创建字体对象
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, new_similarity_score = 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}")
similarity_score = new_similarity_score # 更新相似性评分
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)
# 显示相似性评分,将 Y 坐标从 10 改为 50
score_text = font.render(f"Similarity Score: {similarity_score:.2f}", True, (0, 0, 0))
screen.blit(score_text, (10, 50)) # 这里将 Y 坐标从 10 改为 50
# 计算并绘制当前层的重心
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))
tower.draw_tower_image(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
if __name__ == "__main__":
main()