人工意識参号

現象的意識を生み出す最小モデル

画面説明

実行方法

画面構成

操作

動作仕様

動作解説

ソースコード


"""
人工意識参号 - 現象的意識の最小モデル
第1.0版 2025.3.22 初版

MIT License

Copyright (c) 2025  Current Color Co. Ltd.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import pygame
import pygame.midi
import numpy as np
import math
import random

# グローバル定数

N_MAX = 30 #最大層(底辺ノードの数)
N_CYCLE = 20 #入力情報のサイクル
C_MAX_IMAGE = 10 #入力画像の最大値

THRESHOLD = 10  # ノードの発火閾値
INFO_MAX = 13 #ノードの最大蓄積情報量
DECAY_RATE = 0.92  # ノード情報の通常減衰率
DISCHARGE_RATE = 0.6 # ノード情報の強制放電率
FATIGUE_MAX = 3 # ノードの疲労最大値

STRENGTHEN_RATE = 1.3  # リンク強化率
LINK_MIN = 0.28 # リンク強度の最低保証値
LINK_MAX = 1.28 #リンク強度の最大値
LINK_SUM_MAX = 2.1 # リンク強度の合計の最大値(正規化)
WEAKEN_RATE = 0.9999  # リンクの減衰率
FORGET_RATE = 0.8 # リンクの強制忘却率

INPUT_RATE = 1.0 #入力情報の入力ノード加算率
DECAY_RATE_OUTPUT = 0.9 # 出力ノードの情報減衰

C_RANDOM_TIMES = 3 #ランダム刺激の一回あたり発生数

#ウィンドウ
W_WIDTH = 1000
W_HEIGHT = 700

#シミュレーション領域
G_WIDTH = 600
G_HEIGHT = 700
G_UNIT = G_WIDTH / N_MAX
G_HIT = G_UNIT

C_QUALIA_LINE = G_HEIGHT * 0.25 #クオリア線のY座標
C_QUALIA_AREA = G_HEIGHT * 0.15 #クオリア発生域の幅

#インプット情報領域
IN_X = 600
IN_Y = 50
IN_WIDTH = 300
IN_HEIGHT = 200
IN_N = N_MAX
IN_M = N_CYCLE

#アウトプット情報領域
OUT_X = 600
OUT_Y = 260
OUT_WIDTH = 300
OUT_HEIGHT = 400
OUT_N = N_MAX
OUT_M = N_CYCLE * 2

# グローバル変数
time = 0
nodes = []
input_nodes = []
output_nodes = []
links = []
qualia = []

inputting = False
inputting_random = False
osound = False
qsound = False

ac_time = 0

#################################
# ノードクラス
class Node:
    def __init__(self, x, y):
        self.info = 0
        self.info_next = self.info
        self.state = 1
        self.state_next = self.state
        self.threshold = THRESHOLD
        self.fatigue = 0
        self.in_links = []
        self.out_links = []
        self.max_links = 10
        self.x, self.y = x, y
        self.clicked = False

    def add_info(self, amount):
        self.info_next += amount
        self.info_next = min(self.info_next, INFO_MAX)

    #接続リンク群の強度正規化
    def normalize(self, links, sum_max):
        sum = 0
        for link in links:
            if link.fixed == False:
                sum += link.get_strength()
        if sum > 0:
            norm_rate = min(sum, LINK_MAX * sum_max) / sum
            for link in links:
                if link.fixed == False:
                    link.set_strength(link.get_strength() * norm_rate)

    #状態遷移
    def exec(self):
        # 無反応期
        if self.state == 0:
            self.normalize(self.in_links, LINK_SUM_MAX)
            self.normalize(self.out_links, LINK_SUM_MAX)
            self.fatigue = 0
            self.state_next = 1

        # 通常期
        elif self.state == 1:
            if self.info >= self.threshold:
                for link in self.out_links:
                    x = self.info * link.get_strength()
                    if link.dest.state == 1 or link.dest.state == 3:
                        link.dest.add_info(x)
                self.state_next = 2

        # 発火期
        elif self.state == 2:
            self.info_next = 0
            self.fatigue += 1
            self.state_next = 1 if self.fatigue < FATIGUE_MAX else 0

    def discharge(self):
        self.info = self.info * DISCHARGE_RATE
        self.info_next = self.info

    def commit(self):
        self.info = self.info_next
        self.state = self.state_next
        if self.state == 1:
            self.info = self.info * DECAY_RATE
            self.info_next = self.info
        elif self.state == 3:
            self.info = self.info * DECAY_RATE_OUTPUT
            self.info_next = self.info

    def get_state(self):
        return self.state

    def set_state(self, state):
        self.state = state
        self.state_next = self.state

    def add_outbound_link(self, link):
        if len(self.out_links) < self.max_links:
            self.out_links.append(link)

    def add_inbound_link(self, link):
        if len(self.in_links) < self.max_links:
            self.in_links.append(link)

    def get_info(self):
        return self.info

    #描画処理
    def draw_node(self):
        color = (255, 0, 0) if self.state == 2 else (255, 255, 255)
        pygame.draw.circle(screen, color, (int(self.x), int(self.y)), max(5,self.info*0.8))

    def draw_inode(self):
        color = (255, 0, 0) if self.state == 2 else (0, 128, 255)
        pygame.draw.circle(screen, color, (int(self.x), int(self.y)), max(5,self.info))
        if self.clicked:
            pygame.draw.circle(screen, (255, 255, 0), (int(self.x), int(self.y)), 10, 4)
            self.clicked = False

    def draw_onode(self):
        color = (255, 0, 0) if self.state == 2 else (255, 165, 0)
        pygame.draw.circle(screen, color, (int(self.x), int(self.y)), max(5,self.info))

#################################
# リンククラス
class Link:
    def __init__(self, src, dest):
        self.src = src
        self.dest = dest
        self.strength = LINK_MIN  # 伝播力
        self.strength_next = self.strength
        self.fixed = False

    def get_strength(self):
        return self.strength

    def set_strength(self, str):
        self.strength = str
        self.strength_next = self.strength
        return

    def fix(self):
        self.strength = 1
        self.strength_next = self.strength
        self.fixed = True

    def exec(self):
        global qualia
        if self.src.get_state() == 2 and self.dest.get_state() == 2:
            #ヘブ則によるリンク強化
            self.strength_next = min(LINK_MAX, self.strength * STRENGTHEN_RATE)
            #クオリアの発生判定
            if self.dest.y < self.src.y:
                qy = (self.src.y + self.dest.y) / 2
                if  ( qy > C_QUALIA_LINE - C_QUALIA_AREA and
                      qy < C_QUALIA_LINE + C_QUALIA_AREA ):
                    str = self.strength * ((C_QUALIA_AREA - abs(qy - C_QUALIA_LINE) ) /
                                                     C_QUALIA_AREA)
                    quale = Quale((self.src.x + self.dest.x)/2,
                                          (self.src.y + self.dest.y)/2,
                                          str)
                    qualia.append(quale) 
               
        elif self.fixed == False:
            #リンクの弱化
            self.strength = self.strength * WEAKEN_RATE
            self.strength_next = max(self.strength, LINK_MIN)

    def forget(self):
        if self.fixed == False:
            #強制忘却
            self.strength = self.strength * FORGET_RATE
            self.strength_next = max(self.strength, LINK_MIN)

    def commit(self):
        self.strength = self.strength_next

    def draw(self, screen):
        Draw.parallel_line(screen, (128, 128, 128), 
                         (int(self.src.x), int(self.src.y)), 
                         (int(self.dest.x), int(self.dest.y)), 
                         int(self.strength * 6 / LINK_MAX), 3)

#################################
# 描画ツールクラス
class Draw:

    #平行移動した線を描画
    def parallel_line(screen, color, start_point, end_point, width, distance):
        # distance -- 平行移動する距離(正の値なら左側、負の値なら右側)

        # 始点と終点の座標を取得
        x1, y1 = start_point
        x2, y2 = end_point
        # 線の方向ベクトルを計算
        dx = x2 - x1
        dy = y2 - y1
        # ベクトルの長さを計算
        length = math.sqrt(dx**2 + dy**2)
        # 長さがゼロの場合は描画しない(点になるため)
        if length == 0:
            return
        # 単位ベクトルを計算
        unit_dx = dx / length
        unit_dy = dy / length    
        # 90度回転させた単位ベクトル(法線ベクトル)を計算
        # 左向きの法線ベクトル
        normal_dx = -unit_dy
        normal_dy = unit_dx
        # 平行移動の距離を適用
        offset_x = distance * normal_dx
        offset_y = distance * normal_dy
        # 平行移動した始点と終点を計算
        parallel_x1 = x1 + offset_x
        parallel_y1 = y1 + offset_y
        parallel_x2 = x2 + offset_x
        parallel_y2 = y2 + offset_y    
        # 平行移動した線を描画
        pygame.draw.line(screen, color, (parallel_x1, parallel_y1), (parallel_x2, parallel_y2), width)
        return None

#################################
# 入力情報クラス(右上部)
class InputInfo:

    #生成
    def __init__(self):
        # 入力情報(モザイク画像)用の2次元配列を作成
        self.input_array = np.zeros((IN_M, IN_N))
        # 入力情報のポインタ
        self.pointer = -1

    # 画像ファイルの読み込み
    def read_input_image(self, num):
        try:
            image_path = "image"+str(num)+".jpg"
            original_image = pygame.image.load(image_path)
        except pygame.error as e:
            print(f"画像の読み込みに失敗しました: {e}")
            return
    
        # 画像のサイズを取得
        width_S = original_image.get_width()
        height_S = original_image.get_height()
    
        # トリミング領域のサイズを計算
        source_ratio = width_S / height_S
        target_ratio = IN_WIDTH / IN_HEIGHT
  
        if source_ratio > target_ratio:
            # 元画像が横長の場合
            height_T = height_S
            width_T = int(height_S * target_ratio)
        else:
            # 元画像が縦長の場合
            width_T = width_S
            height_T = int(width_S / target_ratio)
    
        # トリミング開始位置の計算(画像の中央からトリミング)
        x_offset = (width_S - width_T) // 2
        y_offset = (height_S - height_T) // 2
        
        # 画像のピクセル情報を取得
        pixel_array = pygame.surfarray.array3d(original_image)
    
        # サンプリングしてグレースケール値を計算し二次元配列に格納
        for j in range(IN_M):
            for i in range(IN_N):
                x = x_offset + int(width_T * i / IN_N)
                y = y_offset + int(height_T * j / IN_M)
            
                # 画像の範囲内かチェック
                if 0 <= x < width_S and 0 <= y < height_S:
                    r, g, b = pixel_array[x, y]
                    # RGBをグレースケールに変換
                    gray = r * 0.2989 + g * 0.5870 + b * 0.1140
                    self.input_array[j, i] = gray
    
    # 入力ファイルの表示
    def draw_input_image(self, screen):

        # モザイク状の画像を描画
        rect_width = IN_WIDTH // IN_N
        rect_height = IN_HEIGHT // IN_M
        for j in range(IN_M):
            for i in range(IN_N):
                # モザイク1つ分の矩形を計算
                rect_x = IN_X + i * IN_WIDTH // IN_N
                rect_y = IN_Y + j * IN_HEIGHT // IN_M
                # グレースケール値を取得して塗りつぶし色を決定
                gray_value = int(self.input_array[j, i])
                color = (gray_value, gray_value, gray_value)
                # 矩形を描画
                pygame.draw.rect(screen, color, (rect_x, rect_y, rect_width, rect_height))

    # 入力場所の表示と更新
    def draw_input_line(self, screen):

        global inputting

        #表示
        if inputting == True:
            y = IN_Y + (self.pointer + 0.5) * IN_HEIGHT // IN_M
            pygame.draw.line(screen, (255,0,0),
                                       (IN_X, y), (IN_X + IN_WIDTH, y), 1)
        #更新
        self.pointer += 1
        if self.pointer >= N_CYCLE:
            self.pointer = 0

    # 入力情報の返却
    def get_input(self, i):
        return self.input_array[self.pointer, i] / 256

#################################
# 出力情報クラス(右下部)
class OutputInfo:

    #生成
    def __init__(self):
        # 出力情報用の2次元配列を作成
        self.output_array = np.zeros((OUT_M, OUT_N))
        # 出力情報のポインタ
        self.pointer = 0

    # 出力履歴の描画
    def draw_output_history(self, screen):
        rect_width = OUT_WIDTH // OUT_N
        rect_height = OUT_HEIGHT // OUT_M
        for j in range(OUT_M):
            k = (self.pointer + j + 1) % OUT_M
            rect_y = OUT_Y + j * OUT_HEIGHT // OUT_M
            for i in range(OUT_N):
                rect_x = OUT_X + i * OUT_WIDTH // OUT_N
                gray_value = int(self.output_array[k, i])
                color = (gray_value, gray_value, gray_value)
                pygame.draw.rect(screen, color, (rect_x, rect_y, rect_width, rect_height))

        self.pointer += 1
        if self.pointer >= OUT_M:
            self.pointer = 0

    # 出力情報の設定
    def set_output(self, i, info):
        self.output_array[self.pointer, i] = min(255, info / INFO_MAX * 256)

#################################
# クオリアクラス
class Quale:

    C_color_quale = (128, 230, 255)

    #生成
    def __init__(self, x, y, intensity):
        self.intensity = intensity
        self.x = int(x)
        self.y = int(y)
        self.cnt = int(intensity * 8)+1
        self.rad = 0
        self.stat = 2  #初期状態

    #実行
    def exec(self):
        self.cnt -= 1
        if self.cnt <= 0:
            self.stat = 0  #消滅
        elif self.rad > 0:
            self.stat = 1  #通常状態
        self.rad += 3
        return self.stat

    #描画
    def draw(self, screen):
        pygame.draw.circle(screen, self.C_color_quale, 
                        (self.x, self.y), self.rad, self.cnt)

#################################
# ボタンクラス
class Button:

    C_color_button = (218, 218, 218)
    C_color_button_clicked = (255, 80, 0)
    C_color_button_text = (20, 20, 20)
    C_clicked_disp = 5

    #生成
    def __init__(self, x,y,w,h,xt,yt,txt):
        self.rec = pygame.Rect(x,y,w,h)
        self.pos = (xt, yt)
        self.txt = txt
        self.clicked = 0
        self.font = pygame.font.SysFont(None, 25)

    #表示
    def draw(self,screen):
        color = self.C_color_button_clicked if self.clicked > 0 else self.C_color_button
        pygame.draw.rect(screen, color, self.rec)
        text = self.font.render(self.txt, False, self.C_color_button_text)
        screen.blit(text,  self.pos)
        if self.clicked > 0:
           self.clicked -= 1

    #クリック判定
    def collide(self, pos):
        if self.rec.collidepoint(pos):
            self.clicked = self.C_clicked_disp
            return True
        else:
            return False

#################################
# UIクラス
class UI:

    C_color_text = (218, 218, 218)

    t_num_pos = (145,15)
    t_input_pos = (97,45)
    t_random_pos = (97,105)
    t_osound_pos = (97,135)
    t_qsound_pos = (97,165)
    t_time_txt_pos = (400,15)
    t_time_pos = (450,15)

    #生成
    def __init__(self):
        self.input_image = 0
        self.selecting = False
        self.font = pygame.font.SysFont(None, 25)
        self.b_down = Button(10,10,60,27, 13,15, "DOWN")
        self.b_up = Button(80,10,60,27, 98,15, "UP")
        self.b_set = Button(180,10,60,27, 195,15, "SET")
        self.b_input = Button(10,40,82,27, 25,45, "INPUT")
        self.b_discharge = Button(10,70,110,27, 15,75, "DISCHARGE")
        self.b_forget = Button(130,70,90,27, 140,75, "FORGET")
        self.b_random = Button(10,100,82,27, 12,105, "RANDOM")
        self.b_osound = Button(10,130,82,27, 15,135, "oSOUND")
        self.b_qsound = Button(10,160,82,27, 15,165, "qSOUND")

    # UIの表示
    def draw(self, screen):

        global osound, qsound, ac_gtime

        self.b_up.draw(screen)
        self.b_down.draw(screen)
        self.b_set.draw(screen)
        self.b_input.draw(screen)
        self.b_discharge.draw(screen)
        self.b_forget.draw(screen)
        self.b_osound.draw(screen)
        self.b_random.draw(screen)
        self.b_qsound.draw(screen)

        txt = self.font.render("TIME", False, self.C_color_text)
        screen.blit(txt,  self.t_time_txt_pos)

        txt = self.font.render(str(ac_time), False, self.C_color_text)
        screen.blit(txt,  self.t_time_pos)

        if self.selecting == True:
            txt = self.font.render(str(self.input_image), False, self.C_color_text)
            screen.blit(txt,  self.t_num_pos)

        txt = "ON" if osound==True else "OFF"
        fnt = self.font.render(txt, False, self.C_color_text)
        screen.blit(fnt,  self.t_osound_pos)

        txt = "ON" if qsound==True else "OFF"
        fnt = self.font.render(txt, False, self.C_color_text)
        screen.blit(fnt,  self.t_qsound_pos)

        txt = "ON" if inputting==True else "OFF"
        fnt = self.font.render(txt, False, self.C_color_text)
        screen.blit(fnt,  self.t_input_pos)

        txt = "ON" if inputting_random==True else "OFF"
        fnt = self.font.render(txt, False, self.C_color_text)
        screen.blit(fnt,  self.t_random_pos)

    # ボタン押下処理
    def click(self, event, input_info):

        global inputting, nodes, links, osound, qsound, player, inputting_random

        if self.b_up.collide(event.pos):
            self.selecting = True
            self.input_image += 1
            if self.input_image > C_MAX_IMAGE:
                self.input_image = 0
        if self.b_down.collide(event.pos):
            self.selecting = True
            self.input_image -= 1
            if self.input_image < 0:
                self.input_image = C_MAX_IMAGE
        if self.b_set.collide(event.pos):
            self.selecting = False
            input_info.read_input_image(self.input_image)
        if self.b_input.collide(event.pos):
            inputting = True if inputting == False else False
        if self.b_discharge.collide(event.pos):
            for node in nodes:
                node.discharge()
        if self.b_forget.collide(event.pos):
            for link in links:
                link.forget()
        if self.b_osound.collide(event.pos):
           if osound == True:
               osound = False
               player.stop_osound()
           else:
               osound = True
        if self.b_qsound.collide(event.pos):
           if qsound == True:
               qsound = False
               player.stop_qsound()
           else:
               qsound = True
        if self.b_random.collide(event.pos):
            inputting_random = True if inputting_random == False else False

#################################
# 音声クラス
class Player:

    CHANNEL_1 = 0
    CHANNEL_2 = 1
    INSTRUMENT_1 = 82
    INSTRUMENT_2 = 0

    #生成
    def __init__(self):
        pygame.midi.init()
        self.midi = pygame.midi.Output(pygame.midi.get_default_output_id())
        self.midi.set_instrument(self.INSTRUMENT_1, self.CHANNEL_1)
        self.midi.set_instrument(self.INSTRUMENT_2, self.CHANNEL_2)

    # 出力情報の音声化
    def play_output(self, output_nodes):
        for onode in output_nodes:
            note = int((onode.x - G_WIDTH / 2) / G_UNIT ) * 2 +60
            vol = int((127 - (INFO_MAX - onode.info)*10) * 0.5)
            if onode.info > INFO_MAX * 0.85:
                self.midi.note_on(note, vol, self.CHANNEL_1)
            if onode.info < INFO_MAX * 0.8:
                self.midi.note_off(note, vol, self.CHANNEL_1)

    # クオリア音の開始
    def quale_on(self, quale):
        note = int((quale.x - G_WIDTH / 2) / G_UNIT ) * 2 +60
        if quale.intensity > 0.8:
            vol = int(quale.intensity * 127)
            self.midi.note_on(note, vol, self.CHANNEL_2)

    # クオリア音の終了
    def quale_off(self, quale):
        note = int((quale.x - G_WIDTH / 2) / G_UNIT ) * 2 +60
        if quale.intensity > 0.8:
            vol = int(quale.intensity * 127)
            self.midi.note_off(note, vol, self.CHANNEL_2)

    # 音声の停止
    def stop_osound(self):
        self.midi.write_short(0xB0, 0x7B, 0x00)

    def stop_qsound(self):
        self.midi.write_short(0xB1, 0x7B, 0x00)

#################################
# 初期化
def ac3_init():

    global nodes, input_nodes, output_nodes, links,top_node

    # 三角形配置(層ごとにノードを管理)
    node_layers = [[] for _ in range(N_MAX + 1)]  # 0層は未使用、1~Nmax層を使用
    for layer in range(N_MAX, 0, -1):  # 底辺(N_MAX)から頂点(1)へ
        for i in range(layer):
            x = G_WIDTH / 2 - (layer - 1) * G_UNIT / 2 + i * G_UNIT
            y = G_HEIGHT - 100 - (N_MAX - layer) * G_UNIT
            node = Node(x, y)
            nodes.append(node)
            node_layers[layer].append(node)

    # リンク生成
    # 抽象化方向リンク
    # N層i番目 → N-1層i-1番目(i>0)と N-1層i番目(i≦N-1)
    for layer in range(N_MAX, 1, -1): 
        for i, src_node in enumerate(node_layers[layer]):            
            if i > 0:
                dest_node = node_layers[layer - 1][i-1]
                make_link(src_node, dest_node)

            if i < len(node_layers[layer - 1]):
                dest_node = node_layers[layer - 1][i]
                make_link(src_node, dest_node)

    # 具体化方向リンク
    # N層i番目 → N+1層i番目 と N+1層i+1番目
    for layer in range(N_MAX-1, 0, -1): 
        for i, src_node in enumerate(node_layers[layer]):

            dest_node = node_layers[layer + 1][i]
            make_link(src_node, dest_node)

            dest_node = node_layers[layer + 1][i+1]
            make_link(src_node, dest_node)
  
    # 同層内リンク
    # N層i番目 → N層i-1番目(i<0の時N番目)とN層i+1番目(i>Nの時0番目)
    for layer in range(N_MAX, 1, -1): 
        for i, src_node in enumerate(node_layers[layer]): 
            #左隣
            if i < 0:
                dest_node = node_layers[layer][layer-1]
            else:
                dest_node = node_layers[layer][i-1]
            make_link(src_node, dest_node)

            #右隣
            if i >= layer-1:
                dest_node = node_layers[layer][0]
            else:
                dest_node = node_layers[layer][i+1]
            make_link(src_node, dest_node)

    # 入力ノード(底辺ノードの真下に描画)
    input_nodes = [Node(node_layers[N_MAX][i].x, G_HEIGHT - 80) for i in range(N_MAX)]
    for i, inode in enumerate(input_nodes):
        dest_node = node_layers[N_MAX][i]
        make_fix_link(inode, dest_node)

    # 出力ノード(入力ノードの真下に描画)
    output_nodes = [Node(node_layers[N_MAX][i].x, G_HEIGHT - 60) for i in range(N_MAX)]
    for i, onode in enumerate(output_nodes):
        onode.set_state(3)
        src_node = node_layers[N_MAX][i]
        make_fix_link(src_node, onode)

    return None

def make_link(src_node, dest_node):
    link = Link(src_node, dest_node)
    src_node.add_outbound_link(link)
    dest_node.add_inbound_link(link)
    links.append(link)

def make_fix_link(src_node, dest_node):
    link = Link(src_node, dest_node)
    link.fix()
    src_node.add_outbound_link(link)
    dest_node.add_inbound_link(link)
    links.append(link)

#################################
# 主処理

#初期化
pygame.init()

screen = pygame.display.set_mode((W_WIDTH, W_HEIGHT))
clock = pygame.time.Clock()

ac3_init()
print("Num of Nodes:", len(nodes))
print("Num of Links:",len(links))

input_info = InputInfo()
input_info.read_input_image(0)

output_info = OutputInfo()

ui = UI()
player = Player()

running = True
input_active = True
osound = False
qsound = False

#メインループ
while running:

    #イベント処理
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.MOUSEMOTION and input_active:
            mx, my = pygame.mouse.get_pos()
            for inode in input_nodes:
                dist = ((inode.x - mx) ** 2 + (inode.y - my) ** 2) ** 0.5
                if dist < G_HIT:
                    inode.add_info(INFO_MAX)
                    inode.clicked = True
        if event.type == pygame.MOUSEBUTTONDOWN:
            ui.click(event, input_info)

    # 更新処理
    if inputting == True:
        for i, inode in enumerate(input_nodes):
            inode.add_info(INFO_MAX * input_info.get_input(i) * INPUT_RATE)

    if inputting_random == True:
        for _ in range(C_RANDOM_TIMES):
            i = random.randint(0, len(nodes) - 1)
            nodes[i].add_info(INFO_MAX)

    for i, onode in enumerate(output_nodes):
        output_info.set_output(i, onode.get_info())

    all_nodes = input_nodes + nodes + output_nodes
    for node in all_nodes:
        node.exec()
    for link in links:
        link.exec()

    for quale in qualia:
        ret = quale.exec()
        if ret == 0:
#            if qsound == True:
#                player.quale_off(quale)
            qualia.remove(quale)
        if ret == 2:
            if qsound == True:
                player.quale_on(quale)

    # 次の計算ステップへの移行
    all_nodes = input_nodes + nodes + output_nodes
    for node in all_nodes:
        node.commit()
    for link in links:
        link.commit()

    # 描画処理
    screen.fill((10, 10, 10))
    input_info.draw_input_image(screen)
    input_info.draw_input_line(screen)
    output_info.draw_output_history(screen)
    ui.draw(screen)

    for link in links:
        link.draw(screen)

    for node in nodes:
        node.draw_node()
    for inode in input_nodes:
        inode.draw_inode()
    for onode in output_nodes:
        onode.draw_onode()

    for quale in qualia:
        quale.draw(screen)

    # 音声処理
    if osound == True:
        player.play_output(output_nodes)

    ac_time += 1

    pygame.display.flip()
    clock.tick(30)

pygame.quit()