Morse code practice machine
モールス信号の練習アプリを作ってみました。
トン(Space短押し)で左に、ツー(Space長押し)で右に枝分かれします。

環境
- Arch Linux
- pygame
- Windows 11
- pygame
構築
- pygameをインストール
- Arch Linuxの場合
sudo pacman -S python-pygame
- Windows 11の場合
pip install pygame
- Arch Linuxの場合
- Geminiにお願いしたプロンプト
./images/morse_code_image.png この画像ファイルにあるモールス信号練習機に似たアプリケーションを作りたいです。LinuxとWindowsで動かしたいです。スペースキーの押している時間で信号を発生します。上部中央から始まって、短い信号や長い信号の発生に応じて枝分かれして光が点灯していき、モールス信号に該当するアルファベットにたどり着きます。
一度の長押しが長押し+短押しに判定されやすいです。
ESCキー押下でテキストをクリアしたいです。
- 生成されたコード
morse_code.py import pygame
import time
# --- 設定 ---
WIDTH, HEIGHT = 800, 600
BG_COLOR = (30, 30, 30)
NODE_COLOR = (80, 80, 80)
HIGHLIGHT_COLOR = (0, 255, 0)
TEXT_COLOR = (255, 255, 255)
DOT_THRESHOLD = 0.2 # 0.2秒以下なら「・」
# モールス信号の二分木データ (Level 0 to 4)
# (文字, 左の子Index, 右の子Index)
MORSE_TREE = {
"": {"left": "E", "right": "T", "pos": (0, 0)},
"E": {"left": "I", "right": "A", "pos": (-1, 1)},
"T": {"left": "N", "right": "M", "pos": (1, 1)},
"I": {"left": "S", "right": "U", "pos": (-1.5, 2)},
"A": {"left": "R", "right": "W", "pos": (-0.5, 2)},
"N": {"left": "D", "right": "K", "pos": (0.5, 2)},
"M": {"left": "G", "right": "O", "pos": (1.5, 2)},
"S": {"left": "H", "right": "V", "pos": (-1.75, 3)},
"U": {"left": "F", "right": "", "pos": (-1.25, 3)},
"R": {"left": "L", "right": "", "pos": (-0.75, 3)},
"W": {"left": "P", "right": "J", "pos": (-0.25, 3)},
"D": {"left": "B", "right": "X", "pos": (0.25, 3)},
"K": {"left": "C", "right": "Y", "pos": (0.75, 3)},
"G": {"left": "Z", "right": "Q", "pos": (1.25, 3)},
"O": {"left": "", "right": "", "pos": (1.75, 3)},
# 末端ノード(簡略化のため一部のみ)
"H": {"pos": (-1.87, 4)}, "V": {"pos": (-1.63, 4)}, "F": {"pos": (-1.37, 4)},
"L": {"pos": (-0.87, 4)}, "P": {"pos": (-0.37, 4)}, "J": {"pos": (-0.13, 4)},
"B": {"pos": (0.13, 4)}, "X": {"pos": (0.37, 4)}, "C": {"pos": (0.63, 4)},
"Y": {"pos": (0.87, 4)}, "Z": {"pos": (1.13, 4)}, "Q": {"pos": (1.37, 4)}
}
class MorseApp:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Morse Code Visualizer")
self.font = pygame.font.SysFont("Arial", 24)
self.clock = pygame.time.Clock()
self.current_node = ""
self.path = [""]
self.last_input_time = time.time()
self.press_start_time = 0
self.is_pressing = False
self.decoded_sentence = ""
def get_screen_pos(self, pos_tuple):
# ツリーの座標を画面上のピクセルに変換
x_factor, y_level = pos_tuple
x = WIDTH // 2 + int(x_factor * 180)
y = 80 + y_level * 100
return (x, y)
def draw_tree(self):
for char, data in MORSE_TREE.items():
start_pos = self.get_screen_pos(data["pos"])
# 子ノードへの線を描画
for side in ["left", "right"]:
child_char = data.get(side)
if child_char and child_char in MORSE_TREE:
end_pos = self.get_screen_pos(MORSE_TREE[child_char]["pos"])
color = NODE_COLOR
# パス上の線はハイライト
if char in self.path and child_char in self.path:
if self.path.index(child_char) == self.path.index(char) + 1:
color = HIGHLIGHT_COLOR
pygame.draw.line(self.screen, color, start_pos, end_pos, 3)
# ノードの円を描画
is_active = char == self.current_node
color = HIGHLIGHT_COLOR if char in self.path else NODE_COLOR
pygame.draw.circle(self.screen, color, start_pos, 20)
# 文字を描画
text_surf = self.font.render(char if char else "Start", True, TEXT_COLOR)
self.screen.blit(text_surf, (start_pos[0] - 8, start_pos[1] - 12))
def run(self):
running = True
while running:
self.screen.fill(BG_COLOR)
current_time = time.time()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
self.decoded_sentence = ""
self.current_node = ""
self.path = [""]
# スペースキーの物理的な状態を直接チェック(OSのキーリピートによる誤動作を防止)
keys = pygame.key.get_pressed()
space_pressed = keys[pygame.K_SPACE]
if space_pressed and not self.is_pressing:
# キーが押された瞬間
self.press_start_time = current_time
self.is_pressing = True
elif not space_pressed and self.is_pressing:
# キーが離された瞬間
duration = current_time - self.press_start_time
self.is_pressing = False
self.last_input_time = current_time
# 短押し(left)か長押し(right)かを判定
direction = "left" if duration < DOT_THRESHOLD else "right"
next_node = MORSE_TREE.get(self.current_node, {}).get(direction)
if next_node and next_node in MORSE_TREE:
self.current_node = next_node
self.path.append(next_node)
# 一定時間(1.5秒)入力がなければリセット
if not self.is_pressing and current_time - self.last_input_time > 1.5:
if self.current_node != "":
self.decoded_sentence += self.current_node
self.current_node = ""
self.path = [""]
self.draw_tree()
# 確定した文字列の表示
sentence_surf = self.font.render(f"Text: {self.decoded_sentence}", True, HIGHLIGHT_COLOR)
self.screen.blit(sentence_surf, (20, HEIGHT - 50))
pygame.display.flip()
self.clock.tick(60)
pygame.quit()
if __name__ == "__main__":
MorseApp().run()
利用
- 起動
python ./morse_code.py
- スペースキーで信号入力
Space短押し:左に分岐
Space長押し:右に分岐
ESC:テキストをクリア
Comments