6 각형 타일링에서 생물 계산


18

이 도전은 타일 ​​게임 Palago에서 "생물" 을 세게 할 것입니다.

생물체는 육각형 격자에 일치하는 색상의 팔라고 타일로 형성 할 수있는 닫힌 모양입니다.

Palago 게임은 다음과 같은 타일로 구성됩니다.

팔라고 타일

이 타일은 , 로 회전 하거나 전혀 회전 하지 않고 6 각형 격자의 어느 곳에 나 배치 할 수 있습니다. 예를 들어 12 개의 타일이 필요한 (빨간색) 생물입니다.12024012 개의 타일 생물.

도전

이 과제의 목표는 정수 n를 입력으로 사용하고 n타일 을 필요로하는 생물 수 (회전 및 반사까지)를 계산 하는 프로그램을 작성하는 것 입니다. 이 프로그램은 최대 처리 할 수 있어야 n=10TIO . 이것은 이므로 가장 적은 바이트가 이깁니다.

데이터 예

값은 제작자 웹 사이트 의 "생물 수 및 추정치"섹션에있는 데이터와 일치해야합니다 . 즉

 n | output
---+-------
 1 | 0
 2 | 0
 3 | 1 
 4 | 0
 5 | 1
 6 | 1
 7 | 2
 8 | 2
 9 | 9
10 | 13
11 | 37
12 | 81

"프로그램은 n=10TIO 를 처리 할 수 ​​있어야합니다 ." -이것이 실행 속도 요구 사항이라면 code-golf 대신 code-challenge 를 사용 하십시오. 후자는 순수한 바이트 최적화 작업을 나타냅니다.
Jonathan Frech

10
여기 의 토론을 바탕으로 점수가 바이트 수인 한 코드 골프 질문 에 실행 속도 요구 사항을 갖는 것이 좋습니다.
Peter Kagey

2
+1 나선형 시퀀스 와 마찬가지로이 과제는 이해하기 쉽고 해결하기가 정말 흥미 롭습니다. 그러나 약간의 코드가 필요합니다. : p
Arnauld

1
그래서 .... 우리는 단지 입력을 받아서 위의 목록에서 1에서 10 사이의 n에 대해 출력을 반환합니까? 조회 테이블을 사용할 수 있습니까?
BradC

6
=10

답변:


5

자바 스크립트 (Node.js) , 480 417 바이트

@Arnauld 덕분에 -63 바이트. 와.

n=>(E=(x,y,d,k,h)=>V[k=[x+=1-(d%=3),y+=~d%3+1,d]]?0:(V[k]=1,h=H.find(h=>h[0]==x&h[1]==y))?(d^(t=2-h[2])?E(x,y,t)||E(x,y,h[2]*2):E(x,y,t+2)):[x,y,0],I=c=>c.map(([x,y,t])=>[x-g(0),y-g(1),t],g=p=>Math.min(...c.map(h=>h[p]))).sort(),S=e=>(V={},e=E(0,0,0))?(--n&&H.pop(H.push(e),S(),S(e[2]=1),S(e[2]=2)),++n):n-1||E[I(c=H)]||[0,0,0,++N,0,0].map(r=>E[I(c=c.map(([x,y,t])=>[-x-y,r?y:x,(r?t*2:t+1)%3]))]=1))(H=[[N=0,0,1]])&&N

온라인으로 사용해보십시오!

첫째, Arnauld에게 답을 보내면 답이 더 깊이 파고들 것입니다. 코드를 좀 더 쉽게 비교할 수 있도록 Arnauld와 동일한 변수를 사용하도록 일부 코드를 의도적으로 변경했지만 알고리즘을 사용하여 독창적이려고 노력했습니다.

빈 육각형 검색

생물체 검색은 다음과 같습니다.

  • 0,0에서 타일 1로 타일 목록 초기화
  • 재귀 적으로 :
    • 생물을 완성하는 데 필요한 빈 16 진수를 찾으십시오.
    • 빈 16 진수가 발견되면
      • 16 진수를 비우고 재귀하기 위해 각 유형의 타일 0,1,2 추가
    • 빈 16 진수를 찾지 못한 경우
      • 생물이 올바른 크기이고 아직 동물원에 있지 않은 경우
        • 하나에 의해 발견 된 독특한 생물의 수 증가
        • 동물원에 생물의 모든 회전과 반사를 추가

빈 육각형을 검색 한 결과 흥미로운 대칭이 발견되었습니다. Arnauld는 6 가지 방향 중 하나는 무시해도되지만 실제로 6 개 중 3 개는 무시할 수 있음을 발견했습니다!

Arnauld의 원래 방향과 타일 키는 다음과 같습니다.

Arnauld의 방향과 타일 키

파란색 점에서 유형 1의 타일 A에서 시작한다고 상상해보십시오. d = 0과 d = 5로 되풀이해야 할 것 같습니다. 그러나 d = 0에 배치 된 타일은 반드시 d = 4로 종료되며, d = 5에서 타일 A를 종료하는 것과 같은 16 진수를 방문합니다. 그것이 Arnauld의 발견이며, 제가 생각하기 시작한 것입니다.

그것을주의해라:

  • d = 0에 이탈이있는 모든 타일에는 d = 5에 이탈이 있습니다.
  • d = 2에 출구가있는 모든 타일은 d = 1에 출구가 있습니다
  • d = 4에 출구가있는 모든 타일에는 d = 3에 출구가 있습니다

  • d = 0에서 입력 할 수있는 모든 타일에는 d = 4의 이탈이 있습니다.

  • d = 2에서 입력 할 수있는 모든 타일은 d = 0으로 종료됩니다.
  • d = 4에서 입력 할 수있는 모든 타일은 d = 2에 이탈이 있습니다.

즉, 방향 0,2,4 만 고려하면됩니다. 방향 1, 3, 5에 도달 할 수있는 육각형은 방향 0, 2 또는 4를 사용하여 인접한 16 진수에서 도달 할 수 있기 때문에 방향 1, 3, 5의 출구는 무시할 수 있습니다.

얼마나 멋진가요?!

레이블이 지정된 방향

따라서 다음과 같이 방향과 타일의 레이블을 다시 지정합니다 (Arnauld의 이미지 편집).

단순화 된 방향

이제 우리는 타일, 입구 및 출구 사이에 다음과 같은 관계가 있습니다.

    |  t=0  |  t=1  |  t=2
----+-------+-------+-------
d=0 |  0,2  |  1,2  |    2
d=1 |  0,2  |    0  |  0,1
d=2 |    1  |  1,2  |  0,1

따라서 종료는 다음과 같습니다. d + t == 2? (4-t) % 3 : 2-t 및 2 * t % 3

6 각형 회전 및 반사

회전과 반사 를 위해 x, y, z 큐브 좌표 대신 x, y 육각 축 좌표를 사용 하기로 결정했습니다 .

-1,2   0,2   1,2   2,2
    0,1   1,1   2,1
 0,0   1,0   2,0   3,0

이 시스템에서는 회전과 반사가 예상보다 간단했습니다.

120 Rotation:   x=-x-y   y=x   t=(t+1)%3
Reflection:     x=-x-y   y=y   t=(t*2)%3

내가 수행 한 모든 조합을 얻으려면 : 썩음, 썩음, 썩음, 반영, 썩음, 썩음

코드 (원본 480 바이트)

f=n=>(
    // H:list of filled hexes [x,y,tile] during search for a complete creature
    // N:number of distinct creatures of size n
    // B:record of all orientations of all creatures already found
    H=[[0,0,1]],N=0,B={},

// E: find an empty hex required to complete creature starting in direction d from x,y
    E=(x,y,d,k,h)=>(
        x+=1-d,
        y+=1-(d+1)%3,
        // V: list of visited hexes during this search in E
        V[k=[x,y,d]] ? 
            0
        : (V[k]=1, h=H.find(h=>h[0]==x&&h[1]==y)) ? 
            // this hex is filled, so continue search in 1 or 2 directions
            (d==2-h[2] ? E(x,y,(4-h[2])%3) : (E(x,y,2-h[2]) || E(x,y,h[2]*2%3))) 
        : [x,y,0] // return the empty hex 
    ),

    // I: construct unique identifier for creature c by moving it so x>=0 and y>=0
    I=c=>(
        M=[0,1].map(p=>Math.min(...c.map(h=>h[p]))),
        c.map(([x,y,t])=>[x-M[0],y-M[1],t]).sort()
    ),

    // A: add complete creature c to B
    A=c=>{
        n==1&&!B[I(c)]&&(
            // creature is correct size and is not already in B
            N++,
            [0,0,0,1,0,0].map(
                // Add all rotations and reflections of creature into B
                // '0' marks a rotation, '1' marks a (vertical) reflection
                // rotation:   x=-x-y   y=x   t=(t+1)%3
                // reflection: x=-x-y   y=y   t=(t*2)%3
                r=>B[I(c=c.map(([x,y,t])=>[-x-y,r?y:x,(r?t*2:t+1)%3]))]=1)          
        )
    },

    // S: recursively search for complete creatures starting with hexes H
    S=e=>{
        V={};
        (e=E(0,0,0)) ?
            // e is a required empty hex, so try filling it with tiles 0,1,2
            (--n && (H.push(e),S(),S(e[2]=1),S(e[2]=2),H.pop()), ++n)
        : A(H) // creature is complete, so add it to B
    },

    S(),
    N
)

코드 (Arnauld 417 바이트)

Arnauld는 친절하게 63 바이트 절약을 제출하여 트릭을 사용하여 머릿속을 감싸는 데 상당한 시간이 걸렸습니다. 그것은 많은 흥미로운 편집이 있기 때문에, 나는 그의 코드를 아래에 넣을 것이라고 생각했습니다 (내 의견을 추가했습니다). 내 버전과 대조 될 수 있습니다.

f=n=>(
    // E:find an empty hex required to complete creature starting in direction d from x,y
    E=(x,y,d,k,h)=>
      V[k=[x+=1-(d%=3),y+=~d%3+1,d]] ?
        0
      :(V[k]=1,h=H.find(h=>h[0]==x&h[1]==y)) ?
        (d^(t=2-h[2]) ? E(x,y,t) || E(x,y,h[2]*2) : E(x,y,t+2))
      :[x,y,0],

    // I: construct unique identifier for creature c by moving it so x>=0 and y>=0
    I=c=>c.map(([x,y,t])=>[x-g(0),y-g(1),t],g=p=>Math.min(...c.map(h=>h[p]))).sort(),

    // S: recursively search for complete creatures starting with hexes H
    S=e=>
      (V={},e=E(0,0,0)) ?
        (--n&&H.pop(H.push(e),S(),S(e[2]=1),S(e[2]=2)),++n)
      :n-1
        ||E[I(c=H)] 
        // creature is the correct size and has not been seen before
        // so record all rotations and reflections of creature in E[]
        ||[0,0,0,++N,0,0].map(r=>E[I(c=c.map(([x,y,t])=>[-x-y,r?y:x,(r?t*2:t+1)%3]))]=1)
)
// This wonderfully confusing syntax initializes globals and calls S()
(H=[[N=0,0,1]]) && N

방향에 대한 좋은 통찰력! 그리고 나는 이것이 내 대답의 크기 아래로 골프를 칠 수 있다고 생각합니다.
Arnauld


@Arnauld 대단해! 나는 지금 나보다 큰 작업 일을 가지고 있지만 내일 이것을 확인하기를 기대합니다. 감사.
John Rees

20

자바 스크립트 (Node.js) ,  578 ... 433  431 바이트

f=(n,T=[B=[N=0,0,0,1,1]])=>!n||T.some(([x,y,q,m])=>B.some((p,d)=>m>>d&1&&((p=x+~-s[d],q=y+~-s[d+2],t=T.find(([X,Y])=>X==p&Y==q))?(q=t[3])&(p=D[d*3+t[4]])^p?t[f(n,T,t[3]|=p),3]=q:0:[0,1,2].map(t=>f(n-1,[...T,[p,q,-p-q,D[d*3+t],t]])))),s="2100122",D=Buffer("160).(9!'8 &$<%"))|n>1||[0,1,2,1,2,0].some((_,d,A)=>B[k=T.map(a=>[(h=n=>Math.min(...T.map(R=a=>a[A[(d+n)%6]]))-R(a))(0),h(3),(x=(a[4]+d*2)%3,d>2)*x?3-x:x]).sort()])?N:B[k]=++N

=1=13

어떻게?

방향과 타일

우리는 6 방향 나침반과 타일에 다음 코드를 사용합니다.

방향 및 타일

우리는 그 생물이 파란색이라고 가정합니다.

사이

주어진 방향으로 주어진 타일에 들어갈 때 생물의 어떤 부분이 다른 타일에 연결되어야 하는지를 알기 위해 테이블이 필요합니다.

     |  T=0  |  T=1  |  T=2
-----+-------+-------+-------
 d=0 | 0,4,5 | 1,2,4 |   4
 d=1 | 0,3,5 | 1,2,3 |   3
 d=2 | 0,3,4 |   0   | 0,1,2
 d=3 | 3,4,5 |   5   | 1,2,5
 d=4 |   2   | 2,3,4 | 0,2,5
 d=5 |   1   | 1,3,4 | 0,1,5

예:

방향이 5 인 유형 1 의 타일을 입력하면 방향 1 로 연결해야합니다.514

사이

5

     |  T=0  |  T=1  |  T=2
-----+-------+-------+-------
 d=0 |  0,4  | 1,2,4 |   4
 d=1 |  0,3  | 1,2,3 |   3
 d=2 | 0,3,4 |   0   | 0,1,2
 d=3 |  3,4  |   -   |  1,2
 d=4 |   2   | 2,3,4 |  0,2

+32

     |  T=0  |  T=1  |  T=2              |  T=0  |  T=1  |  T=2
-----+-------+-------+-------       -----+-------+-------+-------
 d=0 |   17  |   22  |   16          d=0 |  "1"  |  "6"  |  "0"
 d=1 |    9  |   14  |    8          d=1 |  ")"  |  "."  |  "("
 d=2 |   25  |    1  |    7    -->   d=2 |  "9"  |  "!"  |  "'"
 d=3 |   24  |    0  |    6          d=3 |  "8"  |  " "  |  "&"
 d=4 |    4  |   28  |    5          d=4 |  "$"  |  "<"  |  "%"

평평해진 후에는 다음을 제공합니다.

D = Buffer("160).(9!'8 &$<%")

좌표

타일의 위치를 ​​설명하기 위해 x + y 와 함께 큐브 좌표를 사용합니다엑스+와이+=0

큐브 좌표

크레딧 : www.redblobgames.com

알고리즘의 마지막 단계에서 회전 및 반사를 쉽게 처리 할 수 ​​있습니다.

타일 ​​인코딩

타일은 특정 순서없이 목록에 저장됩니다. 동적 2D 할당에 대해 걱정할 필요가 없으며 기존 타일에서 쉽게 반복 할 수 있습니다. 단점은 특정 좌표가 주어지면 find()목록의 해당 타일이 필요하다는 것 입니다.

각 타일은 (엑스,와이,,미디엄,)

  • (엑스,와이,)
  • 미디엄
  • 는 유형입니다 (0 ,1 또는2

연산

우리 는 ( 0 , 0 , 0 ) 에서 유형 1 의 단일 타일로 시작하고 방향 0에 필요한 연결을 갖습니다.(0,0,0)0 :

초기 타일

따라서이 타일은로 인코딩됩니다 [0,0,0,1,1].

반복 할 때마다 다음을 찾습니다.

  • 연결이 누락 된 타일 :이 경우 각 유형의 타일과의 연결을 연속적으로 시도합니다.

  • 이미 연결되었지만 다른 방향으로 도달했기 때문에 새 연결을 추가해야하는 타일 :이 경우 방향 마스크 (비트 OR로)를 업데이트하고 새 반복을 강제합니다.

모든 연결이 유효 하고 요청 된 타일 수에 도달 한 경우 여전히 새로운 생물인지 또는 기존 생물체의 수정 된 버전인지 테스트해야합니다.

  1. 다음과 같은 변환을 적용합니다.

    • (엑스,와이)(엑스,와이)(와이,)(,엑스)

    • (엑스,와이)(와이,엑스)(,와이)(엑스,)

  2. (0,0)

  3. 좌표와 유형에 따라 타일을 정렬합니다. (이 정렬은 사전 식 순서대로 처리되므로 괜찮습니다.)

  4. 마지막으로 결과 목록을 다른 키와 비교할 수있는 키 문자열로 강제 변환합니다.

  5. 알려진 키가 일치하자마자 중단하거나 새로운 키를 저장하고 알려진 키로 이어지는 변환이 없으면 최종 결과를 증가시킵니다.

댓글

f = (n, T = [B = [N = 0, 0, 0, 1, 1]]) =>
  // abort if n = 0
  !n ||
  // for each tile in T
  T.some(([x, y, q, m]) =>
    // for d = 0 to d = 4
    B.some((p, d) =>
      // if this tile requires a connection in this direction
      m >> d & 1 && (
        // look for a connected tile t at the corresponding position (p, q)
        (
          p = x + ~-s[d],
          q = y + ~-s[d + 2],
          t = T.find(([X, Y]) => X == p & Y == q)
        ) ?
          // if t exists, make sure that its direction mask is up-to-date
          (q = t[3]) & (p = D[d * 3 + t[4]]) ^ p ?
            // if it's not, update it and force a new iteration
            t[f(n, T, t[3] |= p), 3] = q
          :
            0
        :
          // if t does not exist, try each type of tile at this position
          [0, 1, 2].map(t => f(n - 1, [...T, [p, q, -p - q, D[d * 3 + t], t]]))
      )
    ),
    // s is used to apply (dx, dy)
    s = "2100122",
    // D holds the direction masks for the connections
    D = Buffer("160).(9!'8 &$<%")
  ) |
  // stop here if the above some() was truthy or we have more tiles to add
  n > 1 ||
  // otherwise, apply the transformations
  [0, 1, 2, 1, 2, 0].some((_, d, A) =>
    B[
      // compute the key k
      k =
        // by generating the updated tuples [x, y, type] and sorting them
        T.map(a =>
          [
            // transform the 1st coordinate
            (h = n => Math.min(...T.map(R = a => a[A[(d + n) % 6]])) - R(a))(0),
            // transform the 2nd coordinate
            h(3),
            // update the type
            (x = (a[4] + d * 2) % 3, d > 2) * x ? 3 - x : x
          ]
        ).sort()
    ]
  ) ?
    // if the key was found, just return N
    N
  :
    // if this is a new creature, store its key and increment N
    B[k] = ++N

이 답변을 좋아하십시오. 주말에 총을 쏘기 위해 해고 당했어!
존리스

흥미로운 답변을 게시하려고합니다. 내 설명을 돕기 위해 이미지 중 하나를 사용해도 괜찮습니까? 물론 당신을 신용하겠습니다.
John Rees

@JohnRees 물론, 문제 없습니다.
Arnauld
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.