Skip to content

Redstone DSL #1

@rollrat

Description

@rollrat
model Redstone2L {
    // ============================================================
    // Redstone2L DSL: 2층(ground/top) 조합회로(PnR/합성)용 선언형 모델
    // - "place"  : 배치 변수(블록을 놓을지)
    // - "state"  : 상태 변수(시나리오별 전기적 상태: 파워/온오프)
    // - "shape"  : 모양/연결 같은 파생 구조(더스트 축, 연결 방향 등)
    // - "sources": “어디서 파워가 들어오는가”를 집합으로 누적하는 개념(OR-원인 집합)
    // - "rule"   : 제약 묶음(ILP로 내려갈 논리식/등가식/함의/합 제약 등)
    // ============================================================

    // -------------------------
    // 0) Index / Topology
    // -------------------------

    // 격자상의 한 칸을 (x,z)로 인덱싱한다. (y는 Layer로 분리)
    index Cell = (x,z) in Grid;

    // 2층 레이어: 바닥(GROUND)과 그 위 1칸(TOP)
    enum Layer { GROUND=0, TOP=1 }

    // 4방향
    enum Dir { N,E,S,W }

    // c에서 방향 d로 인접한 셀(격자 밖이면 null 가능)
    fn neigh(c:Cell, d:Dir) -> Cell?;

    // 벽토치가 붙을 "지지 블록" 위치를 계산
    // (예: 벽토치가 c의 면 d에 붙으면, 실제로는 d방향 반대쪽 블록을 지지로 삼는 식)
    fn supportForWallTorch(c:Cell, d:Dir) -> Cell?;

    // 리피터 방향 d를 기준으로, 입력(back) 셀 위치
    fn back(c:Cell, d:Dir) -> Cell?;

    // 리피터 방향 d를 기준으로, 출력(front) 셀 위치
    fn front(c:Cell, d:Dir) -> Cell?;

    // -------------------------
    // 1) 시나리오 / 핀
    // -------------------------

    // 시나리오(진리표 검증용): IN=0인 세계(s=0)와 IN=1인 세계(s=1)를 동시에 풀도록 복제
    scenario Sc in {0,1};

    // 외부에서 관찰/강제하는 관측 지점(입력/출력 핀)
    pin IN  : ObsPoint;
    pin OUT : ObsPoint;

    // -------------------------
    // 2) 배치 변수 (place)
    // -------------------------
    // "place X[...] : Type" 는
    // "그 위치에 해당 블록/부품을 둘지"를 나타내는 0/1 결정변수.

    place S[Cell] : Solid;          // 해당 Cell에 '고체 블록(솔리드 블록)'을 둘지
    place D[Layer, Cell] : Dust;    // 해당 Layer/Cell에 레드스톤 더스트를 둘지
    place T_stand[Cell] : Torch;    // 해당 Cell에 '스탠딩 토치(위에 올리는 토치)'를 둘지
    place T_wall[Cell, Dir] : Torch;// Cell에 방향 Dir로 벽토치를 둘지(어느 면에 붙는지)
    place R[Cell, Dir] : Repeater;  // Cell에 방향 Dir로 리피터를 둘지

    // -------------------------
    // 3) 상태 변수 (state) - 시나리오별
    // -------------------------
    // "state"는 배치가 고정된 뒤, 시나리오 s마다 전기 상태(파워)를 나타냄.
    // 보통 place가 1이어야만 state가 1이 될 수 있게 R_DOMAIN에서 묶는다.

    state BP[Sc, Cell] : BlockPower;         // 블록이 파워(간접파워 포함) 되었는지
    state DP[Sc, Layer, Cell] : DustOn;      // 더스트가 켜졌는지(신호를 띄는지)
    state TO_stand[Sc, Cell] : TorchOn;      // 스탠딩 토치가 ON인지
    state TO_wall[Sc, Cell, Dir] : TorchOn;  // 벽토치가 ON인지
    state RO[Sc, Cell, Dir] : RepOn;         // 리피터 출력이 ON인지(활성화) — “리피터가 켜짐”

    // -------------------------
    // 4) 모양/연결(Shape) 변수
    // -------------------------
    // 레드스톤 더스트는 “연결 후보”에 따라 모양(축/크로스)이 결정되고,
    // 모양이 Conn에 반영되어 어떤 방향으로 전력이 전달되는지 컷(cutoff)된다.

    shape Conn[Layer, Cell, Dir] : Conn; // 더스트가 해당 방향 Dir로 "연결되어 있음"을 의미
    shape AxisNS[Layer, Cell] : Axis;    // 더스트가 NS축 모양(남북 직선)
    shape AxisEW[Layer, Cell] : Axis;    // 더스트가 EW축 모양(동서 직선)
    shape Cross[Layer, Cell] : Axis;     // 더스트가 4방향 교차 모양(크로스) 허용 여부/선택

    // -------------------------
    // 5) Source-set(원인 집합) 변수
    // -------------------------
    // “이 셀이 파워를 받는 원인들이 뭐냐”를 원인 집합으로 누적하고,
    // 나중에 OR{SrcSet} 형태로 "하나라도 있으면 켜짐"을 만든다.
    // (백엔드에서는 결국 OR-선형화로 내려갈 것)

    sources BlockSrc[Sc, Cell] : SrcSet;       // 블록을 파워시키는 원인들의 집합
    sources DustSrc[Sc, Layer, Cell] : SrcSet; // 더스트를 켜는 원인들의 집합

    // -------------------------
    // 6) 파생 헬퍼(derived helper) 선언
    // -------------------------
    // 네가 def로 쓰던 파생식들이 DSL에선 변수로 선언되어야 ILP로 내릴 수 있음.

    shape Cand[Layer, Cell, Dir] : Bool;                   // 해당 방향으로 연결 후보가 있는지(더스트/주변 환경 기반)
    shape DustPowersBlock[Sc, Layer, Cell, Dir] : Bool;    // 더스트가 그 방향 이웃 블록을 파워하는지(Conn 컷 포함)
    state RepIn[Sc, Cell, Dir] : Bool;                     // 리피터 입력이 들어왔는지(백 방향에서 신호가 들어오는지)

    // ============================================================
    // A) placement 제약
    // ============================================================
    rule R_PLACEMENT_BASE {
        forall(c in Cell) {
            // TOP 레이어에 더스트를 놓으려면, 그 아래(해당 Cell)에 솔리드 블록이 필요
            // (마인크래프트에서 더스트는 블록 위에만 올려둘 수 있다는 규칙)
            require D[TOP, c] -> S[c];

            // 스탠딩 토치도 블록 위에만 올려둘 수 있으므로, 블록이 있어야 한다
            require T_stand[c] -> S[c];
        }

        forall((c,d) in Cell * Dir) {
            // 벽토치 배치 규칙:
            // - 벽토치 자체는 "빈 공간" 위치(c)에 존재해야 하고(!S[c])
            // - 지지 블록(supportForWallTorch(c,d))은 솔리드여야 한다
            require T_wall[c,d] -> (!S[c] and S[supportForWallTorch(c,d)]);
        }

        forall(c in Cell) {
            // 한 칸에는 벽토치를 최대 1개 방향만 달 수 있게 제한
            require sum(d in Dir) T_wall[c,d] <= 1;

            // 한 칸에는 리피터도 최대 1개 방향만 둘 수 있게 제한
            require sum(d in Dir) R[c,d] <= 1;
        }

        feature PACKING_ONEHOT {
            forall(c in Cell) {
                // 패킹(배타) 제약:
                // 동일 Cell(동일 좌표)에 "서로 겹쳐서" 놓이면 안 되는 것들을 한 번에 제한
                // - ground 더스트 / top 더스트 / 스탠딩 토치 / 벽토치(4방 중 1) / 리피터(4방 중 1)
                // - 합이 1을 넘지 않게 해서 한 칸에는 최대 하나만 두도록 강제
                require D[GROUND,c] + D[TOP,c] + T_stand[c]
                    + sum(d in Dir) T_wall[c,d]
                    + sum(d in Dir) R[c,d] <= 1;
            }
        }
    }

    // ============================================================
    // B) domain linking (state <= placement)
    // ============================================================
    rule R_DOMAIN {
        forall(s in Sc) {
            forall(c in Cell) {
                // 상태 변수는 배치가 있어야만 켜질 수 있다(도메인 링크)
                // 예: 블록파워 BP가 1이면 그 위치에 블록 S가 반드시 존재
                require BP[s,c] -> S[c];

                // 더스트 상태 DP가 1이면 해당 더스트가 배치돼 있어야 함
                require DP[s,GROUND,c] -> D[GROUND,c];
                require DP[s,TOP,c]    -> D[TOP,c];

                // 토치 ON 상태는 토치가 배치돼 있어야 함
                require TO_stand[s,c]  -> T_stand[c];
            }
            forall((c,d) in Cell * Dir) {
                // 벽토치 ON 상태는 벽토치 배치가 있어야 함
                require TO_wall[s,c,d] -> T_wall[c,d];

                // 리피터 ON 상태는 리피터 배치가 있어야 함
                require RO[s,c,d]      -> R[c,d];
            }
        }
    }

    // ============================================================
    // C) torch inverter (토치 인버터 동작)
    // ============================================================
    rule R_TORCH_INVERTER {
        forall(s in Sc) {
            forall(c in Cell) {
                // 스탠딩 토치의 기본 동작:
                // - 토치가 설치되어 있고(T_stand=1)
                // - 붙어있는 블록이 파워되지 않았으면(!BP)
                // => 토치는 ON
                // 즉, 토치는 “부착 블록의 BP”를 반전한다(인버터)
                def TO_stand[s,c] <-> (T_stand[c] and !BP[s,c]);
            }
            forall((c,d) in Cell * Dir) {
                let sup = supportForWallTorch(c,d);

                // 벽토치도 마찬가지로, 지지 블록(sup)의 BP를 반전한다
                def TO_wall[s,c,d] <-> (T_wall[c,d] and !BP[s,sup]);
            }
        }
    }

    rule R_TORCH_ANTICHEAT_EXCLUDE_ATTACHED_BLOCK {
        // 토치 규칙의 중요한 예외:
        // "토치는 자신이 붙어있는 블록(attached/support)을 파워하지 않는다"
        // 즉, 토치 출력이 BlockSrc에 들어가더라도 attached 블록으로는 들어가면 안 됨.
        // 여기서는 sources 집합에서 특정 원인을 "exclude"로 빼는 형태로 표현.

        forall(s in Sc) {
            forall(c in Cell) {
                // 스탠딩 토치 출력이 BlockSrc[s,c]에 (자동으로) 들어가는 상황이 있더라도,
                // "자기 부착 블록(c)"에 대해서는 제외한다는 의미
                exclude BlockSrc[s,c] += TorchOut(stand,c);
            }
            forall((c,d) in Cell * Dir) {
                let sup = supportForWallTorch(c,d);

                // 벽토치도 지지 블록(sup)에는 파워 원인으로 추가되지 못하게 exclude
                exclude BlockSrc[s,sup] += TorchOut(wall,c,d);
            }
        }
    }

    // ============================================================
    // D) repeater (리피터 스켈레톤)
    // ============================================================
    rule R_REPEATER {
        forall(s in Sc) {
            forall((c,d) in Cell * Dir) {
                let b = back(c,d);   // 입력 방향(뒤)
                let f = front(c,d);  // 출력 방향(앞)

                // 리피터 입력 조건(대충의 OR):
                // - back쪽 셀의 더스트가 켜져 있거나
                // - back쪽 셀의 블록이 파워되어 있거나
                // - back쪽 셀에 토치 등이 파워를 주고 있거나(추상 헬퍼)
                // 실제 마크 리피터 입력 규칙을 더 정확히 모델링하려면 이 부분을 확장해야 함.
                def RepIn[s,c,d] <-> OR{
                    DP[s,GROUND,b],
                    DP[s,TOP,b],
                    BP[s,b],
                    TorchPowersCell(s,b)
                };

                // 리피터 ON: 리피터가 설치되어 있고, 입력이 들어오면 출력 ON
                def RO[s,c,d] <-> (R[c,d] and RepIn[s,c,d]);

                // 리피터 출력은 앞쪽 셀의 더스트/블록을 파워 소스로 추가한다
                // (DustSrc/BlockSrc에 원인으로 누적)
                add DustSrc[s,GROUND,f] += RepOut(c,d);
                add DustSrc[s,TOP,f]    += RepOut(c,d);
                add BlockSrc[s,f]       += RepOut(c,d);
            }
        }
    }

    // ============================================================
    // E) dust shape/conn (더스트 모양/연결)
    // ============================================================
    rule R_DUST_CONN_AXIS_CUT {
        forall((l,c,d) in Layer * Cell * Dir) {
            // Cand: 더스트가 있고(D=1), 그 방향으로 "연결 후보"가 존재하면 후보 true
            // ExistsConnectionCandidate는 주변에 더스트/리피터/토치/블록 등
            // 연결될 만한 것이 있는지를 보는 built-in(현재 백엔드 매핑 필요)
            def Cand[l,c,d] <-> (D[l,c] and ExistsConnectionCandidate(l,c,d));
        }

        feature DUST_AUTO_AXIS {
            forall((l,c) in Layer * Cell) {
                // 축 선택:
                // - NS 후보가 하나라도 있으면 AxisNS 가능
                // - EW 후보가 하나라도 있으면 AxisEW 가능
                // 여기서는 "자동으로" 축을 고르게 만들되,
                // 둘 다 동시에 선택은 금지(<=1)로 해서 직선 모양을 강제한다.
                def AxisNS[l,c] <-> OR{ Cand[l,c,N], Cand[l,c,S] };
                def AxisEW[l,c] <-> OR{ Cand[l,c,E], Cand[l,c,W] };
                require AxisNS[l,c] + AxisEW[l,c] <= 1;

                // Conn은 선택된 축에 따라 2방향만 true가 된다.
                // (AxisNS면 N/S만 연결, AxisEW면 E/W만 연결)
                def Conn[l,c,N] <-> AxisNS[l,c];
                def Conn[l,c,S] <-> AxisNS[l,c];
                def Conn[l,c,E] <-> AxisEW[l,c];
                def Conn[l,c,W] <-> AxisEW[l,c];
            }
        }

        feature DUST_ALLOW_CROSS {
            forall((l,c) in Layer * Cell) {
                // 교차(크로스) 모양을 허용하는 확장점:
                // AllowCrossChoice가 true면 Cross를 선택 가능하게 하고
                // Conn을 4방향으로 열어주는 식으로 확장할 수 있다.
                // (현재는 Cross 선언만 있고 Conn 반영은 아직 스켈레톤)
                def Cross[l,c] <-> AllowCrossChoice(l,c);
            }
        }
    }

    // ============================================================
    // F) dust on (더스트 상태)
    // ============================================================
    rule R_DUST_ON {
        forall(s in Sc) {
            forall((l,c) in Layer * Cell) {
                // 더스트가 ON이라는 것은:
                // - 더스트가 설치되어 있고(D=1)
                // - DustSrc(원인 집합) 중 하나라도 존재하면(OR{...})
                // => 켜진다
                def DP[s,l,c] <-> (D[l,c] and OR{ DustSrc[s,l,c] });
            }
        }
    }

    // ============================================================
    // G) dust -> block (더스트가 블록을 파워, Conn cutoff 반영)
    // ============================================================
    rule R_DUST_POWERS_BLOCK {
        forall(s in Sc) {
            forall((l,c,d) in Layer * Cell * Dir) {
                let b = neigh(c,d); // c에서 d방향 이웃 블록 셀

                // 더스트가 블록을 파워하는 조건:
                // - 더스트가 켜져 있고(DP)
                // - 해당 방향으로 연결이 열려 있고(Conn)
                // - 이웃 칸에 솔리드 블록이 실제로 존재해야(S[b])
                // => 그 블록은 파워 원인으로 "더스트"를 하나 가진다
                def DustPowersBlock[s,l,c,d] <-> (DP[s,l,c] and Conn[l,c,d] and S[b]);

                // 그 결과를 BlockSrc에 원인으로 추가(“이 블록은 저 더스트 때문에 파워됨”)
                add BlockSrc[s,b] += DustPowersBlock(s,l,c,d);
            }
        }
    }

    // ============================================================
    // H) block power (블록 파워)
    // ============================================================
    rule R_BLOCK_POWER {
        feature MINIMAL_NOT_STRONG {
            forall(s in Sc) {
                forall(c in Cell) {
                    // (단순화/근사) 블록 파워를 "TOP 더스트가 켜져 있느냐"로만 정의
                    // 즉, BP는 더스트 전파 규칙(토치/리피터/간접파워 등)을
                    // 강하게 모델링하지 않고 최소한으로 쓰겠다는 설정.
                    // 실제로는: BP[s,c] <-> OR{ BlockSrc[s,c] } 같은 형태가 더 일반적.
                    def BP[s,c] <-> DP[s,TOP,c];
                }
            }
        }
        // else: 블록은 OR(BlockSrc)로 정의하는 정식 버전을 넣을 수 있음
    }

    // ============================================================
    // J) truth table (NOT 게이트 진리표 강제)
    // ============================================================
    rule R_TRUTH_TABLE_NOT {
        // Observe(PIN, s=k)는 "시나리오 k에서 그 핀의 관측값"을 의미.
        // 여기서는 시나리오 0에서는 IN=0, 시나리오 1에서는 IN=1로 강제하고,
        // OUT은 그 반대로 나오도록 강제해서 NOT 게이트를 합성하게 만든다.

        force Observe(IN,  s=0) == 0;
        force Observe(IN,  s=1) == 1;
        force Observe(OUT, s=0) == 1;
        force Observe(OUT, s=1) == 0;
    }

    // ============================================================
    // K) objective (목적함수)
    // ============================================================
    objective minimize {
        // 가중치 기반 비용 최소화:
        // - 더스트 개수 * wD
        // - 토치 개수 * wT
        // - 솔리드 블록 개수 * wS
        // - 리피터 개수 * wR
        // 이렇게 하면 “가능하면 적은 부품으로” NOT을 만들도록 유도된다.

        wD * sum((l,c) in Layer * Cell) D[l,c]
      + wT * (sum(c in Cell) T_stand[c] + sum((c,d) in Cell * Dir) T_wall[c,d])
      + wS * sum(c in Cell) S[c]
      + wR * sum((c,d) in Cell * Dir) R[c,d]
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions