CATIAでテトリスGUI版。
まずはPydroidで実験。
import tkinter as tk
from tkinter import ttk, messagebox
import random
import traceback
try:
import win32com.client
except ImportError:
win32com = None
TETROMINOS = {
"I": [[1, 1, 1, 1]],
"O": [[1, 1], [1, 1]],
"T": [[0, 1, 0], [1, 1, 1]],
"S": [[0, 1, 1], [1, 1, 0]],
"Z": [[1, 1, 0], [0, 1, 1]],
"J": [[1, 0, 0], [1, 1, 1]],
"L": [[0, 0, 1], [1, 1, 1]],
}
class CATIAV5R21Tetris3D:
def __init__(self, logger):
self.log = logger
self.catia = None
self.documents = None
self.part_document = None
self.part = None
self.selection = None
self.shape_factory = None
self.hybrid_shape_factory = None
self.origin_elements = None
self.xy_plane = None
self.bodies = None
self.main_body = None
self.grid_body = None
self.locked_body = None
self.current_body = None
self.board_width = 10
self.board_height = 20
self.block_size = 10.0
self.block_height = 5.0
self.board = [[0 for _ in range(self.board_width)] for _ in range(self.board_height)]
self.current_piece = None
self.current_shape = None
self.current_x = 0
self.current_y = 0
self.score = 0
self.lines_cleared_total = 0
self.game_over = False
self.reframed_once = False
# -----------------------------
# 基本ユーティリティ
# -----------------------------
def safe_log(self, msg):
try:
self.log(msg)
except Exception:
pass
def connect(self):
if win32com is None:
raise RuntimeError("pywin32 が未インストールです。pip install pywin32 を実行してください。")
self.safe_log("CATIA接続中...")
self.catia = win32com.client.Dispatch("CATIA.Application")
self.catia.Visible = True
self.catia.DisplayFileAlerts = False
self.documents = self.catia.Documents
try:
version = self.catia.SystemConfiguration.Version
self.safe_log(f"CATIA接続成功: {version}")
except Exception:
self.safe_log("CATIA接続成功")
def create_part(self):
self.safe_log("新規 Part 作成中...")
self.part_document = self.documents.Add("Part")
self.part = self.part_document.Part
self.selection = self.part_document.Selection
self.shape_factory = self.part.ShapeFactory
try:
self.hybrid_shape_factory = self.part.HybridShapeFactory
except Exception:
self.hybrid_shape_factory = None
self.origin_elements = self.part.OriginElements
self.xy_plane = self.origin_elements.PlaneXY
self.bodies = self.part.Bodies
self.main_body = self.bodies.Item(1)
self.main_body.Name = "MainBody"
self.grid_body = self.add_body("GridBody")
self.locked_body = self.add_body("LockedBlocksBody")
self.current_body = self.add_body("CurrentPieceBody")
self.safe_log("Part 作成完了")
def add_body(self, name):
body = self.bodies.Add()
body.Name = name
return body
def set_inwork(self, obj):
try:
self.part.InWorkObject = obj
except Exception:
pass
def update_part(self):
self.part.Update()
def clear_selection(self):
try:
self.selection.Clear()
except Exception:
pass
def delete_with_selection(self, obj):
"""
V5R21向け Selection ベース削除
"""
try:
self.clear_selection()
self.selection.Add(obj)
self.selection.Delete()
self.clear_selection()
return True
except Exception:
return False
def delete_all_shapes_in_body(self, body):
"""
Body配下のShapesを後ろから消す
"""
try:
shapes = body.Shapes
count = shapes.Count
deleted = 0
for i in range(count, 0, -1):
try:
shp = shapes.Item(i)
if self.delete_with_selection(shp):
deleted += 1
except Exception:
pass
return deleted
except Exception:
return 0
def delete_all_sketches_in_body(self, body):
try:
sketches = body.Sketches
count = sketches.Count
deleted = 0
for i in range(count, 0, -1):
try:
sk = sketches.Item(i)
if self.delete_with_selection(sk):
deleted += 1
except Exception:
pass
return deleted
except Exception:
return 0
def cleanup_body_completely(self, body):
s1 = self.delete_all_shapes_in_body(body)
s2 = self.delete_all_sketches_in_body(body)
return s1 + s2
# -----------------------------
# ゲーム状態
# -----------------------------
def reset_game_state(self):
self.board = [[0 for _ in range(self.board_width)] for _ in range(self.board_height)]
self.current_piece = None
self.current_shape = None
self.current_x = 0
self.current_y = 0
self.score = 0
self.lines_cleared_total = 0
self.game_over = False
def spawn_piece(self):
self.current_shape = random.choice(list(TETROMINOS.keys()))
self.current_piece = [row[:] for row in TETROMINOS[self.current_shape]]
self.current_x = self.board_width // 2 - len(self.current_piece[0]) // 2
self.current_y = 0
if self.check_collision():
self.game_over = True
return False
return True
def check_collision(self, offset_x=0, offset_y=0, piece=None):
if piece is None:
piece = self.current_piece
for y, row in enumerate(piece):
for x, cell in enumerate(row):
if not cell:
continue
nx = self.current_x + x + offset_x
ny = self.current_y + y + offset_y
if nx < 0 or nx >= self.board_width:
return True
if ny >= self.board_height:
return True
if ny >= 0 and self.board[ny][nx]:
return True
return False
def move_left(self):
if not self.check_collision(offset_x=-1):
self.current_x -= 1
return True
return False
def move_right(self):
if not self.check_collision(offset_x=1):
self.current_x += 1
return True
return False
def move_down(self):
if not self.check_collision(offset_y=1):
self.current_y += 1
return True
return self.lock_piece()
def hard_drop(self):
moved = False
while not self.check_collision(offset_y=1):
self.current_y += 1
moved = True
self.lock_piece()
return moved
def rotate(self):
if not self.current_piece:
return False
rotated = [
[self.current_piece[y][x] for y in range(len(self.current_piece) - 1, -1, -1)]
for x in range(len(self.current_piece[0]))
]
if not self.check_collision(piece=rotated):
self.current_piece = rotated
return True
return False
def lock_piece(self):
for y, row in enumerate(self.current_piece):
for x, cell in enumerate(row):
if cell:
by = self.current_y + y
bx = self.current_x + x
if 0 <= by < self.board_height and 0 <= bx < self.board_width:
self.board[by][bx] = 1
self.clear_lines()
return self.spawn_piece()
def clear_lines(self):
lines_cleared = 0
y = self.board_height - 1
while y >= 0:
if all(self.board[y]):
del self.board[y]
self.board.insert(0, [0 for _ in range(self.board_width)])
lines_cleared += 1
else:
y -= 1
if lines_cleared > 0:
self.lines_cleared_total += lines_cleared
gained = lines_cleared * lines_cleared * 100
self.score += gained
self.safe_log(f"{lines_cleared} ライン消去 / +{gained} 点")
# -----------------------------
# CATIA描画系
# -----------------------------
def initialize_scene(self):
self.connect()
self.create_part()
self.safe_log("既存表示を初期化中...")
self.cleanup_body_completely(self.grid_body)
self.cleanup_body_completely(self.locked_body)
self.cleanup_body_completely(self.current_body)
self.reset_game_state()
self.build_fixed_grid_3d()
self.spawn_piece()
self.rebuild_locked_blocks_3d()
self.rebuild_current_piece_3d()
self.safe_reframe_once()
self.update_part()
self.safe_log("初期化完了")
def safe_reframe_once(self):
if self.reframed_once:
return
try:
viewer = self.catia.ActiveWindow.ActiveViewer
viewer.Reframe()
self.reframed_once = True
self.safe_log("初回のみ Reframe 実行")
except Exception:
self.safe_log("Reframe はスキップ")
def build_fixed_grid_3d(self):
"""
グリッドは固定スケッチ + 細いPadで3D風の床面表示
"""
self.safe_log("固定グリッド作成中...")
self.cleanup_body_completely(self.grid_body)
self.set_inwork(self.grid_body)
sketch = self.grid_body.Sketches.Add(self.xy_plane)
sketch.Name = "GridSketch"
factory2d = sketch.OpenEdition()
total_w = self.board_width * self.block_size
total_h = self.board_height * self.block_size
# 外枠
factory2d.CreateLine(0, 0, total_w, 0)
factory2d.CreateLine(total_w, 0, total_w, total_h)
factory2d.CreateLine(total_w, total_h, 0, total_h)
factory2d.CreateLine(0, total_h, 0, 0)
# グリッド線
for i in range(1, self.board_width):
x = i * self.block_size
factory2d.CreateLine(x, 0, x, total_h)
for i in range(1, self.board_height):
y = i * self.block_size
factory2d.CreateLine(0, y, total_w, y)
sketch.CloseEdition()
self.set_inwork(sketch)
try:
pad = self.shape_factory.AddNewPad(sketch, 0.5)
pad.Name = "GridPad"
except Exception:
# V5R21で失敗時はスケッチだけ残す
self.safe_log("GridPad 作成に失敗したためスケッチのみ表示")
self.update_part()
def create_block_pad(self, body, grid_x, grid_y, name_prefix):
"""
単一セルを3Dブロック化
"""
self.set_inwork(body)
sketch = body.Sketches.Add(self.xy_plane)
x1 = grid_x * self.block_size
y1 = grid_y * self.block_size
x2 = x1 + self.block_size
y2 = y1 + self.block_size
factory2d = sketch.OpenEdition()
factory2d.CreateLine(x1, y1, x2, y1)
factory2d.CreateLine(x2, y1, x2, y2)
factory2d.CreateLine(x2, y2, x1, y2)
factory2d.CreateLine(x1, y2, x1, y1)
sketch.CloseEdition()
self.set_inwork(sketch)
pad = self.shape_factory.AddNewPad(sketch, self.block_height)
try:
pad.Name = f"{name_prefix}_{grid_x}_{grid_y}"
except Exception:
pass
return sketch, pad
def rebuild_locked_blocks_3d(self):
"""
固定ブロックはライン消去時に全再構築
"""
self.cleanup_body_completely(self.locked_body)
self.set_inwork(self.locked_body)
for y in range(self.board_height):
for x in range(self.board_width):
if self.board[y][x]:
self.create_block_pad(self.locked_body, x, y, "Locked")
self.update_part()
def rebuild_current_piece_3d(self):
"""
現在ピースは毎回完全再作成
"""
self.cleanup_body_completely(self.current_body)
self.set_inwork(self.current_body)
if not self.current_piece:
self.update_part()
return
for py, row in enumerate(self.current_piece):
for px, cell in enumerate(row):
if cell:
gx = self.current_x + px
gy = self.current_y + py
if gy >= 0:
self.create_block_pad(self.current_body, gx, gy, "Current")
self.update_part()
def refresh_view(self):
self.rebuild_current_piece_3d()
def after_piece_locked_refresh(self):
self.rebuild_locked_blocks_3d()
self.rebuild_current_piece_3d()
# -----------------------------
# 表示レイヤー管理風
# -----------------------------
def show_body_only(self, show_grid=True, show_locked=True, show_current=True):
"""
CATIAの本当の画層APIを厳密運用するとV5R21で環境差が大きいので、
ここでは Selection の可視/不可視でグループ管理的に扱う。
"""
try:
self._set_visibility(self.grid_body, show_grid)
self._set_visibility(self.locked_body, show_locked)
self._set_visibility(self.current_body, show_current)
self.update_part()
except Exception as e:
self.safe_log(f"表示切替警告: {e}")
def _set_visibility(self, obj, visible=True):
try:
self.clear_selection()
self.selection.Add(obj)
vis = self.selection.VisProperties
if visible:
vis.SetShow(1)
else:
vis.SetShow(0)
self.clear_selection()
except Exception:
pass
# -----------------------------
# ゲーム実行
# -----------------------------
def step_auto(self):
if self.game_over:
return False
action = random.choices(
["left", "right", "rotate", "down"],
weights=[1, 1, 1, 6]
)[0]
locked_before = [row[:] for row in self.board]
if action == "left":
self.move_left()
elif action == "right":
self.move_right()
elif action == "rotate":
self.rotate()
else:
self.move_down()
if self.game_over:
self.after_piece_locked_refresh()
return False
if locked_before != self.board:
self.after_piece_locked_refresh()
else:
self.refresh_view()
return True
def manual_action(self, action):
if self.game_over:
return False
locked_before = [row[:] for row in self.board]
if action == "left":
self.move_left()
elif action == "right":
self.move_right()
elif action == "down":
self.move_down()
elif action == "rotate":
self.rotate()
elif action == "drop":
self.hard_drop()
if self.game_over:
self.after_piece_locked_refresh()
return False
if locked_before != self.board:
self.after_piece_locked_refresh()
else:
self.refresh_view()
return True
class CATIATetrisManualGUI:
def __init__(self, root):
self.root = root
self.root.title("CATIA V5R21 手動操作GUI版 3Dテトリス")
self.root.geometry("980x720")
self.engine = None
self.running = False
self.after_id = None
self.auto_step_count = 0
self._build_ui()
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
def _build_ui(self):
top = ttk.Frame(self.root, padding=8)
top.pack(fill="x")
cfg = ttk.LabelFrame(top, text="設定", padding=8)
cfg.pack(fill="x", pady=4)
ttk.Label(cfg, text="自動速度(ms)").grid(row=0, column=0, sticky="w")
self.speed_var = tk.StringVar(value="300")
ttk.Entry(cfg, textvariable=self.speed_var, width=10).grid(row=0, column=1, padx=5, sticky="w")
ttk.Label(cfg, text="自動最大手数").grid(row=0, column=2, sticky="w", padx=(20, 0))
self.max_steps_var = tk.StringVar(value="300")
ttk.Entry(cfg, textvariable=self.max_steps_var, width=10).grid(row=0, column=3, padx=5, sticky="w")
btns = ttk.LabelFrame(top, text="制御", padding=8)
btns.pack(fill="x", pady=4)
ttk.Button(btns, text="CATIA初期化", command=self.init_catia).pack(side="left", padx=4)
ttk.Button(btns, text="開始", command=self.start_auto).pack(side="left", padx=4)
ttk.Button(btns, text="停止", command=self.stop_auto).pack(side="left", padx=4)
ttk.Button(btns, text="リセット", command=self.reset_game).pack(side="left", padx=4)
ttk.Button(btns, text="終了", command=self.on_close).pack(side="right", padx=4)
manual = ttk.LabelFrame(top, text="手動操作", padding=8)
manual.pack(fill="x", pady=4)
ttk.Button(manual, text="← 左", command=lambda: self.manual("left"), width=12).pack(side="left", padx=4)
ttk.Button(manual, text="→ 右", command=lambda: self.manual("right"), width=12).pack(side="left", padx=4)
ttk.Button(manual, text="↓ 下", command=lambda: self.manual("down"), width=12).pack(side="left", padx=4)
ttk.Button(manual, text="↻ 回転", command=lambda: self.manual("rotate"), width=12).pack(side="left", padx=4)
ttk.Button(manual, text="⤓ 一気に落下", command=lambda: self.manual("drop"), width=14).pack(side="left", padx=4)
vis = ttk.LabelFrame(top, text="表示切替", padding=8)
vis.pack(fill="x", pady=4)
self.show_grid_var = tk.BooleanVar(value=True)
self.show_locked_var = tk.BooleanVar(value=True)
self.show_current_var = tk.BooleanVar(value=True)
ttk.Checkbutton(vis, text="Grid", variable=self.show_grid_var, command=self.apply_visibility).pack(side="left", padx=8)
ttk.Checkbutton(vis, text="Locked", variable=self.show_locked_var, command=self.apply_visibility).pack(side="left", padx=8)
ttk.Checkbutton(vis, text="Current", variable=self.show_current_var, command=self.apply_visibility).pack(side="left", padx=8)
status = ttk.LabelFrame(self.root, text="状態", padding=8)
status.pack(fill="x", padx=8, pady=6)
self.status_var = tk.StringVar(value="未初期化")
self.score_var = tk.StringVar(value="Score: 0")
self.lines_var = tk.StringVar(value="Lines: 0")
self.steps_var = tk.StringVar(value="Auto Steps: 0")
ttk.Label(status, textvariable=self.status_var, font=("", 11, "bold")).pack(anchor="w")
row = ttk.Frame(status)
row.pack(fill="x", pady=4)
ttk.Label(row, textvariable=self.score_var).pack(side="left", padx=10)
ttk.Label(row, textvariable=self.lines_var).pack(side="left", padx=10)
ttk.Label(row, textvariable=self.steps_var).pack(side="left", padx=10)
logframe = ttk.LabelFrame(self.root, text="ログ", padding=8)
logframe.pack(fill="both", expand=True, padx=8, pady=6)
self.log_text = tk.Text(logframe, wrap="word")
self.log_text.pack(side="left", fill="both", expand=True)
sy = ttk.Scrollbar(logframe, orient="vertical", command=self.log_text.yview)
sy.pack(side="right", fill="y")
self.log_text.configure(yscrollcommand=sy.set)
self.root.bind("", lambda e: self.manual("left"))
self.root.bind("", lambda e: self.manual("right"))
self.root.bind("", lambda e: self.manual("down"))
self.root.bind("", lambda e: self.manual("rotate"))
self.root.bind("", lambda e: self.manual("drop"))
def log(self, msg):
self.log_text.insert("end", msg + "\n")
self.log_text.see("end")
self.root.update_idletasks()
def update_status_labels(self):
if self.engine is None:
self.score_var.set("Score: 0")
self.lines_var.set("Lines: 0")
self.steps_var.set("Auto Steps: 0")
return
self.score_var.set(f"Score: {self.engine.score}")
self.lines_var.set(f"Lines: {self.engine.lines_cleared_total}")
self.steps_var.set(f"Auto Steps: {self.auto_step_count}")
def init_catia(self):
self.stop_auto()
try:
self.engine = CATIAV5R21Tetris3D(self.log)
self.engine.initialize_scene()
self.auto_step_count = 0
self.update_status_labels()
self.status_var.set("CATIA初期化完了")
self.apply_visibility()
except Exception as e:
self.status_var.set("初期化失敗")
self.log(f"初期化エラー: {e}")
self.log(traceback.format_exc())
messagebox.showerror("エラー", f"初期化に失敗しました。\n\n{e}")
def apply_visibility(self):
if self.engine is None:
return
try:
self.engine.show_body_only(
show_grid=self.show_grid_var.get(),
show_locked=self.show_locked_var.get(),
show_current=self.show_current_var.get()
)
except Exception as e:
self.log(f"表示切替エラー: {e}")
def reset_game(self):
self.stop_auto()
if self.engine is None:
return
try:
self.engine.reset_game_state()
self.engine.rebuild_locked_blocks_3d()
self.engine.spawn_piece()
self.engine.rebuild_current_piece_3d()
self.auto_step_count = 0
self.update_status_labels()
self.apply_visibility()
self.status_var.set("リセット完了")
self.log("ゲームをリセットしました")
except Exception as e:
self.status_var.set("リセット失敗")
self.log(f"リセットエラー: {e}")
self.log(traceback.format_exc())
def manual(self, action):
if self.engine is None:
messagebox.showwarning("未初期化", "先に CATIA初期化 を押してください。")
return
self.stop_auto()
try:
alive = self.engine.manual_action(action)
self.update_status_labels()
self.apply_visibility()
if not alive:
self.status_var.set("ゲームオーバー")
self.log("ゲームオーバー")
else:
self.status_var.set(f"手動操作: {action}")
except Exception as e:
self.status_var.set("手動操作エラー")
self.log(f"手動操作エラー: {e}")
self.log(traceback.format_exc())
def start_auto(self):
if self.engine is None:
messagebox.showwarning("未初期化", "先に CATIA初期化 を押してください。")
return
self.running = True
self.status_var.set("自動実行中")
self.schedule_next()
def stop_auto(self):
self.running = False
if self.after_id is not None:
try:
self.root.after_cancel(self.after_id)
except Exception:
pass
self.after_id = None
def schedule_next(self):
if not self.running:
return
try:
delay = int(float(self.speed_var.get().strip()))
except Exception:
delay = 300
self.after_id = self.root.after(delay, self.run_auto_loop)
def run_auto_loop(self):
if not self.running or self.engine is None:
return
try:
try:
max_steps = int(self.max_steps_var.get().strip())
except Exception:
max_steps = 300
if self.auto_step_count >= max_steps:
self.running = False
self.status_var.set("指定手数で終了")
self.log("指定手数に到達しました")
return
alive = self.engine.step_auto()
self.auto_step_count += 1
self.update_status_labels()
self.apply_visibility()
if not alive:
self.running = False
self.status_var.set("ゲームオーバー")
self.log("ゲームオーバー")
return
self.schedule_next()
except Exception as e:
self.running = False
self.status_var.set("自動実行エラー")
self.log(f"自動実行エラー: {e}")
self.log(traceback.format_exc())
messagebox.showerror("実行エラー", str(e))
def on_close(self):
self.stop_auto()
self.root.destroy()
def main():
root = tk.Tk()
try:
style = ttk.Style()
if "vista" in style.theme_names():
style.theme_use("vista")
except Exception:
pass
app = CATIATetrisManualGUI(root)
root.mainloop()
if __name__ == "__main
コメント
コメントを投稿