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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions