나노 코어 전쟁


21

이것은 20 세기로 거슬러 올라가는 KOTH 프로그램 인 Core War에 대한 적응입니다 . 보다 구체적으로, 그것은 원래 제안에 기초하여 매우 단순화 된 명령 세트를 사용하고 있습니다.

배경

Core War에는 컴퓨터를 제어하기 위해 싸우는 두 가지 프로그램이 있습니다. 각 프로그램의 목표는 반대 프로그램을 찾아 종료함으로써 승리하는 것입니다.

전투는 컴퓨터의 메인 메모리 내에서 이루어집니다. 이 메모리를 코어라고하며 8192 개의 주소가 포함되어 있습니다. 전투가 시작되면 각 경쟁자 (전사라고 함)의 코드가 임의의 메모리 덩어리에 배치됩니다. 프로그램 실행은 전사마다 번갈아 가며 각각의 명령을 수행합니다. 각 명령은 Core의 일부를 수정할 수 있으므로 자체 수정 프로그램이 가능합니다.

목표는 반대 프로그램을 종료하는 것입니다. 프로그램이 유효하지 않은 명령 (모든 DAT명령) 을 실행하려고하면 종료됩니다 .

명령어 세트

각 프로그램은 일련의 하위 레벨 명령으로 구성되며 각 명령에는 A 및 B 필드라는 두 개의 필드가 필요합니다.

이 인스트럭션 세트는 원래 사양과 크게 다릅니다. 주요 변경 사항은 1) 명령 추가 / 빼기에 대한 설명 및 2) #어드레싱 모드를 어디에서나 사용할 수 있도록 변경하는 것입니다 . 대부분의 Core Wars 버전에는 20 개가 넘는 opcode, 8 개의 주소 지정 모드 및 일련의 "명령 수정 자"가 있습니다.

오피 코드

각 명령어에는 7 개의 서로 다른 opcode 중 하나가 있어야합니다.

  • DAT A B- (데이터) - 이것은 단순히 숫자를 보유 A하고 B. 중요한 것은 프로세스가 DAT 명령을 실행하려고 할 때 죽는 것입니다.
  • MOV A B-(이동)-메모리 위치의 내용을 메모리 위치 A로 옮깁니다 B. 이전과 이후의 데모는 다음과 같습니다.

    MOV 2 1
    ADD @4 #5
    JMP #1 -1
    
    MOV 2 1
    JMP #1 -1
    JMP #1 -1
    
  • ADD A B-(추가)-메모리 위치의 내용을 메모리 위치 A에 추가합니다 B. 두 필드 중 첫 번째 두 필드가 추가되고 두 ​​번째 필드가 추가됩니다.

    ADD 2 1
    MOV @4 #5
    JMP #1 -1
    
    ADD 2 1
    MOV @5 #4
    JMP #1 -1
    
  • SUB A B-(빼기)-메모리 위치 A에서 메모리 위치의 내용을 빼고 결과를 메모리 위치에 저장합니다 B.

    SUB 2 1
    MOV @4 #5
    JMP #1 -1
    
    SUB 2 1
    MOV @3 #6
    JMP #1 -1
    
  • JMP A B-(점프)-위치로 이동하여 A다음 사이클에서 실행됩니다. B숫자 여야하지만 아무것도하지 않습니다 (정보를 저장하는 데 사용할 수 있음).

    JMP 2 1337
    ADD 1 2
    ADD 2 3
    

    점프는 ADD 2 3다음 사이클에서 실행될 것임을 의미합니다 .

  • JMZ A B-(0 인 경우 점프)-두 행의 필드가 모두 B0이면 프로그램이 location으로 이동합니다 A.

    JMZ 2 1
    SUB 0 @0
    DAT 23 45
    

    명령 1의 두 필드가 0이므로 DAT 명령이 다음 차례에 실행되어 임박한 사망으로 이어집니다.

  • CMP A B- (비교하고 동일하지 않을 경우 생략) - 지시 필드 경우 A와는 B동일하지, 다음 명령을 건너 뜁니다.

    CMP #1 2
    ADD 2 #3
    SUB @2 3
    

    명령어 1과 2의 두 필드는 값이 같으므로 ADD 명령은 건너 뛰지 않으며 다음 번에 실행됩니다.

두 개의 명령어를 더하거나 빼면 두 필드 (A와 B)가 한 쌍씩 더해집니다. 주소 지정 모드와 opcode는 변경되지 않습니다.

주소 지정 모드

주소 지정 모드에는 세 가지 종류가 있습니다. 명령어의 두 필드 각각에는이 세 가지 주소 지정 모드 중 하나가 있습니다.

  • 즉시#X - X계산에 직접 사용될 라인입니다. 예를 들어, #0프로그램의 첫 번째 줄입니다. 네거티브 라인은 프로그램 시작 전 코어의 라인을 나타냅니다.

    ... //just a space-filler
    ...
    ADD #3 #4
    DAT 0 1
    DAT 2 4
    

    두 줄의 첫 번째 줄이 두 번째 줄에 추가됩니다. 두 줄은 각각 줄 3과 4에 있기 때문입니다. 그러나 DAT가 다음주기에 봇을 죽일 것이기 때문에이 코드를 사용하고 싶지 않습니다.

  • 상대X -숫자 X는 현재 주소를 기준으로 대상 메모리 주소의 위치를 ​​나타냅니다. 이 위치의 숫자는 계산에 사용됩니다. line #35이 실행 중이고를 포함 -5하면 line #30이 사용됩니다.

    ... //just a space-filler
    ...
    ADD 2 1
    DAT 0 1
    DAT 2 4
    

    두 번째 DAT 줄이 첫 번째 줄에 추가됩니다.

  • 간접@X -숫자 X는 상대 주소를 나타냅니다. 해당 위치의 내용은 숫자 X에 임시로 추가되어 새로운 상대 주소를 형성하며, 이로부터 숫자를 검색합니다. line #35이 실행 중이고 두 번째 필드가 @4이고 두 번째 필드가 #39number를 포함하면 -7line #32이 사용됩니다.

    ... //just a space-filler
    ...
    ADD @1 @1
    DAT 0 1
    DAT 2 4
    

    이렇게하면 첫 번째 DAT가 두 번째에 추가되지만 더 복잡한 방식으로 추가됩니다. 첫 번째 필드는 @ 1이며 첫 번째 DAT의 첫 번째 필드 인 상대 주소에서 0을 가져옵니다.이 위치에서 두 번째 상대 주소로 해석되므로 1 + 0 = 1은 원래 명령에서 오프셋. 두 번째 필드의 경우 @ 1은 해당 상대 주소 (첫 번째 DAT의 두 번째 필드에있는 1)에서 값을 가져와 같은 방식으로 자체에 추가합니다. 총 오프셋은 1 + 1 = 2입니다. 따라서이 명령어는와 비슷하게 실행됩니다 ADD 1 2.

각 프로그램은 최대 64 개의 명령어를 포함 할 수 있습니다.

라운드가 시작되면 두 프로그램은 8192 개의 위치가있는 메모리 뱅크에 무작위로 배치됩니다. 각 프로그램의 명령 포인터는 프로그램 시작시 시작되며 각 실행주기 후에 증가합니다. 명령 포인터가 DAT명령 실행을 시도하면 프로그램이 종료 됩니다.

핵심의 매개 변수

코어 크기는 8192이며 시간 제한은 8192 * 8 = 65536 틱입니다. 코어는 주기적이므로 주소 8195에 쓰는 것은 주소 3에 쓰는 것과 같습니다 DAT #0 #0. 사용하지 않는 모든 주소는로 초기화됩니다 .

각 경쟁 업체는 64 줄을 넘지 않아야합니다. 정수는 부호있는 32 비트 정수로 저장됩니다.

파싱

경쟁 업체의 프로그래밍을 쉽게하기 위해 파서에 라인 레이블 기능을 추가하겠습니다. opcode 앞의 줄에 나오는 단어는 줄 레이블로 해석됩니다. 예를 들어 tree mov 4 6라인 레이블이 tree있습니다. 프로그램의 어느 곳에 tree #tree나 또는 이 포함 된 필드가 있으면 @tree숫자가 대체됩니다. 또한 대문자는 무시됩니다.

다음은 라인 레이블을 대체하는 방법의 예입니다.

labelA add labelB @labelC
labelB add #labelC labelC
labelC sub labelA @labelB

여기서 레이블 A, B 및 C는 줄 0, 1 및 2에 있습니다. 인스턴스 #label는 레이블의 줄 번호로 대체됩니다. 라벨의 상대 위치 label또는 인스턴스로 @label대체됩니다. 주소 지정 모드가 유지됩니다.

ADD 1 @2
ADD #2 1
SUB -2 @-1

채점

각 참가자 쌍마다 가능한 모든 전투가 수행됩니다. 전투 결과는 두 프로그램의 상대적인 오프셋에 의존하기 때문에 가능한 모든 오프셋 (약 8000 개)이 시도됩니다. 또한 각 프로그램은 각 오프셋에서 먼저 이동할 수 있습니다. 이 오프셋의 대부분을 차지하는 프로그램이 쌍의 승자입니다.

전사가 승리 할 때마다 2 점을 얻습니다. 동점마다 1 점을 얻는다.

둘 이상의 전사를 제출할 수 있습니다. 태그 팀 구성, 협력 금지, 왕 제작 금지 등과 같은 다중 제출에 대한 일반적인 규칙이 적용됩니다. 어쨌든 Core War에는 실제로 이러한 공간이 없기 때문에 큰 문제가되지 않습니다.

컨트롤러

컨트롤러에 대한 코드는 2 개의 쉬운 예제 봇과 함께 여기에 있습니다 . 이 경쟁 (공식 설정을 사용하여 실행할 때)은 완전히 결정적이므로 생성 한 리더 보드는 공식 리더 보드와 정확히 동일합니다.

봇 예

다음은 언어의 일부 기능을 보여주는 봇 예제입니다.

main mov bomb #-1
     add @main main
     jmp #main 0
bomb dat 0 -1

이 봇은 코어의 다른 모든 메모리를 "폭탄"으로 교체하여 천천히 지 웁니다. 폭탄은 DAT명령 이므로 폭탄에 도달하는 모든 프로그램은 파괴됩니다.

숫자를 대체하는 "main"과 "bomb"의 두 가지 라인 레이블이 있습니다. 전처리 후 프로그램은 다음과 같습니다.

MOV 3 #-1
ADD @-1 -1
JMP #0 0
DAT 0 -1

첫 번째 줄은 폭탄을 프로그램 바로 위의 줄에 복사합니다. 다음 줄 0 -1은 이동 명령에 폭탄 값 ( )을 추가 하고 @주소 지정 모드를 사용하는 방법도 보여줍니다 . 이 추가로 이동 명령이 새 대상을 가리 킵니다. 다음 명령은 무조건 프로그램 시작으로 되돌아갑니다.


현재 리더 보드

24-Turbo
22-DwarvenEngineer
20-HanShotFirst
18-Dwarf
14-ScanBomber
10-Paranoid
10-FirstTimer
10-Janitor
10-Evolved
6-EasterBunny
6-CopyPasta
4-Imp
2-슬러그

쌍별 결과 :

Dwarf > Imp
CopyPasta > Imp
Evolved > Imp
FirstTimer > Imp
Imp > Janitor
Imp > ScanBomber
Slug > Imp
DwarvenEngineer > Imp
HanShotFirst > Imp
Turbo > Imp
EasterBunny > Imp
Paranoid > Imp
Dwarf > CopyPasta
Dwarf > Evolved
Dwarf > FirstTimer
Dwarf > Janitor
Dwarf > ScanBomber
Dwarf > Slug
DwarvenEngineer > Dwarf
HanShotFirst > Dwarf
Turbo > Dwarf
Dwarf > EasterBunny
Dwarf > Paranoid
Evolved > CopyPasta
FirstTimer > CopyPasta
Janitor > CopyPasta
ScanBomber > CopyPasta
CopyPasta > Slug
DwarvenEngineer > CopyPasta
HanShotFirst > CopyPasta
Turbo > CopyPasta
CopyPasta > EasterBunny
Paranoid > CopyPasta
Evolved > FirstTimer
Evolved > Janitor
ScanBomber > Evolved
Evolved > Slug
DwarvenEngineer > Evolved
HanShotFirst > Evolved
Turbo > Evolved
EasterBunny > Evolved
Paranoid > Evolved
Janitor > FirstTimer
ScanBomber > FirstTimer
FirstTimer > Slug
DwarvenEngineer > FirstTimer
HanShotFirst > FirstTimer
Turbo > FirstTimer
FirstTimer > EasterBunny
FirstTimer > Paranoid
ScanBomber > Janitor
Janitor > Slug
DwarvenEngineer > Janitor
HanShotFirst > Janitor
Turbo > Janitor
Janitor > EasterBunny
Janitor > Paranoid
ScanBomber > Slug
DwarvenEngineer > ScanBomber
HanShotFirst > ScanBomber
Turbo > ScanBomber
ScanBomber > EasterBunny
ScanBomber > Paranoid
DwarvenEngineer > Slug
HanShotFirst > Slug
Turbo > Slug
EasterBunny > Slug
Paranoid > Slug
DwarvenEngineer > HanShotFirst
Turbo > DwarvenEngineer
DwarvenEngineer > EasterBunny
DwarvenEngineer > Paranoid
Turbo > HanShotFirst
HanShotFirst > EasterBunny
HanShotFirst > Paranoid
Turbo > EasterBunny
Turbo > Paranoid
Paranoid > EasterBunny

최신 업데이트 (Turbo 및 Paranoid의 새 버전)는 오래된 랩톱에서 실행하는 데 약 5 분이 걸렸습니다. Ilmari Karonen 이 컨트롤러 를 개선해 주셔서 감사 합니다 . 컨트롤러의 로컬 사본이있는 경우 파일을 업데이트해야합니다.


경쟁하는 두 봇이 동일한 레이블을 사용하려고하면 어떻게됩니까?
mbomb007

1
@ mbomb007 레이블은 전처리 작업이며 봇의 소스 파일을 구문 분석 할 때 계산됩니다. 라벨은 경쟁사 라벨과 상호 작용하지 않습니다.
PhiNotPi

1
@ mbomb007 프로그램이 겹치지 않도록합니다. 또한이 버전에 더 이상 기능을 추가 할 계획이 없으며 Micro Core War를 위해 기능을 저장하십시오.
PhiNotPi

1
@ mbomb007 간접 주소 지정은 참조를 만드는 동일한 필드 (1 또는 2)를 참조합니다. 명령어 수정자가 없습니다. 나는이 도전을 '94 표준에 근거하지 않습니다.
PhiNotPi

2
@Thrax 나는 한 번의 제출에만 국한되지 않는다고 말하려고한다. 어쨌든 핵심 전쟁에서 협력 할 여지는 많지 않지만 일반적인 다중 제출 규칙이 적용됩니다 (태그 팀 구성 등).
PhiNotPi

답변:


9

드워프 엔지니어

새롭게 개선 된 드워프. 지금까지 제출 된 다른 모든 것에 대해 이깁니다. 멋진 코어 스텝 최적화 스텝 크기는 아마도 여기서 과잉 일 것입니다.

        MOV bomb    @aim
aim     MOV bomb    @-6326
        SUB step    aim
step    JMZ #0      6328
        MOV 0       1
bomb    DAT 0       3164

주목할만한 기능으로는 오래된 코어 전쟁 전문 용어에서 평균 폭격 속도가 0.5c이고 JMZ폭격이 완료된 시점을 감지하고 계획 B ( 여기, Imp).


나는 90 년대에 핵심 전쟁을하던 적이 있었는데 (97 년에 쓴 기본 가이드 북 을 보았을 수도있다 ), RedCode '88 / '94 세계에서 어떤 오래된 전략이 이 변형에 유용합니다.

나의 첫 생각은 :

  • SPL없으므로 복제기 (및 임프 링 / 스파이럴)가 없습니다. 이것은 폭격기를 강하게 만들어야합니다. (또한 복제기 및 나선을 다루기 위해 설계된 모든 멋진 폭격 전략? 여기에서 전혀 불필요하고 쓸모가 없습니다 DAT.

  • 다시 한 번, CMP스캔은 폭탄보다 잠재적으로 빠르기 때문에 빠른 스캐너 는 기회를 가질 있습니다.

  • 감소 / 감소가 없으면 코어 클리어가 매우 느려집니다. 실제로,이 변형에서 분명한 핵심은 (차선의) 단계 크기가 ± 1 인 폭격기 일뿐입니다. 다시 말하지만 이것은 또한 스캐너를 손상시킵니다. 그러나 원샷 스캐너 → 폭격기 전략 효과적 일 수 있습니다 .

  • Quickscanners / quickbombers (Unrolled scan / bombing loop를 사용하는 초기 게임 전략, Core War 전문 용어가없는 가족을위한 초기 게임 전략)는 여전히 잠재적으로 유용하지만 긴 프로그램 (그 자체가 있으므로 일종의 피드백이 있음)에 대해서만 유용합니다. 여기에 효과). 그것이 정말로 문제의 가치가 있는지 말하기는 어렵습니다.

  • 점수 시스템은 흥미 롭습니다. 동점은 전통적인 코어 전쟁에서와 같이 1/3이 아닌 승리로 절반의 점수를 얻어 더 매력적으로 만듭니다. 다시 한번,이 규칙에 따라 많은 유대를 득점 할 수있는 유일한 프로그램에 대해서는 임프가 있습니다. (또한, 드의 부재는 / 단위는 꼬마 도깨비 게이트 하드, 너무도 간단한 임프 실제로하게 그들의 상대가 살아 도달하면 넥타이 득점의 기회가있다.)

  • 또한, 최종 순위는 당신이 어떤 프로그램을이기는 지에 달려 있고 얼마나 많은 프로그램을이기는 지에 달려 있기 때문에 일반적인 항목을 선호합니다. 절반을 완전히 파괴하고 나머지는 거의 잃지 않는 것보다 모든 상대를 간신히 물리 치는 것이 좋습니다 .

  • 코드가 공개되어 있기 때문에, 그건 항상 그들의 가능성도 몇 - - 주어진 이전의 제출을 이길 수있는 프로그램을 찾을 수 그들은 일반적으로 상관없이 얼마나 잘. 하지만 그러한 트릭 (스텝 크기를 조정하여 상대방을 때리기 직전)은 쉽게 저렴 해 보일 수 있습니다. 물론 대상 플레이어는 항상 상수가 다른 새 버전을 제출할 수 있습니다.

어쨌든,이 모든의 결말은 내가 빠른 폭격기 또는 중 하나를 작성하려고한다고 결정한다는 것입니다 매우 빠른 스캐너, 그리고 어쩌면 그 위에 압정 quickscanner / 폭격기. 이러한 옵션 중에서 빠른 폭격기는 가장 단순하고 가장 효과가있는 것으로 보였습니다.

그 시점에서 나는 PhiNotPi의 인터프리터 코드를 조정하고 최적화하는 데 너무 많은 시간을 보냈습니다. 왜냐하면 상수를 최적화하기 위해 많은 무차별 대입 시험을 실행하고 있다고 생각했기 때문입니다. 이런 일이 일어나지 않았을 때, 위의 코드는 실제로 작동 한 첫 번째 버전과 거의 같지 않습니다 (사소한 버그로 인해 자살 한 몇 번의 시도 실패 후).


내 폭격기를 빠르게 만드는 비결은 간접 주소 지정을 사용하여 각각에 대해 두 개의 폭탄을 던지는 것 ADD입니다. 작동 방식은 다음과 같습니다.

  1. 첫 번째 사이클에서을 실행 MOV bomb @aim합니다. 이렇게 bomb하면 B 필드의 핵심 부분이있는 곳 어디에서나 명령어가 복사 aim됩니다 (처음에는 정확히 6326 명령어 이전 aim또는 6328 명령어 전에 step해당 숫자가 나중에 중요한 이유를 알 수 있습니다).

  2. 다음 단계에서는 aim명령어 자체를 실행합니다 ! 첫 번째 패스에서는 다음과 같습니다 MOV bomb @-6326.. 따라서 bomb6326 라인에서 명령의 B 필드가 가리키는 위치로 복사 됩니다.

    그렇다면 6326 줄에는 무엇이 aim있습니까? 왜, bomb우리가 방금 1주기 더 일찍 복사 한 사본입니다 ! 그리고 우리는 방금 B 필드가 bomb0이 아닌 값을 갖도록 물건을 배열했습니다. 그래서 새 폭탄은 오래된 폭탄 ​​위에 복사되지 않지만 약간 거리가 있습니다 (사실 거리는 3164입니다. 이것은 공칭 스텝 크기 6328의 절반이지만 다른 오프셋은 아마도 더 좋을 수 있습니다).

  3. 다음 사이클에서는로 목표를 조정 SUB step aim합니다.이 step명령 은 명령어 의 값을 뺍니다 (이것은 어딘가 단순한 것일 수도 있지만 다음에 실행할 점프입니다 DAT) aim.

    (여기에서 주목할 세부 사항 은 A 값 이 0 되기를 원 step하므로 다음 반복에서 여전히 동일한 폭탄을 던질 것입니다. 그럼에도 불구하고 엄격하게 필요하지는 않습니다. 에 의해 처음으로 3164 동등한 자신의 B-필드를 가지고 명령 필요, 나머지는 아무것도 할 수 있습니다.)

  4. 다음으로, JMZ명령 6328이 지시로부터 멀어 지도록 점검하고 여전히 0이면, 코드의 맨 위로 이동합니다. 이제 6328은 폭격기의 걸음 수이며 8로 나눌 수 있지만 16은 아닙니다. 따라서 만약 우리가 6328 단계마다 폭탄을 계속 던졌다면 결국 우리는 코어에서 8 번째 명령마다 폭탄을 터 뜨리고 시작한 곳으로 돌아갈 것입니다. 우리는 모든 공격 한 것이다 네번째 ) 명령을.

    그러나 우리는 6328 개의 지시에 우리의 폭격 실행을 시작 하기 전에JMZ , 우리는 위치를 6328 단계에 폭탄을거야, 그래서 모든 반복에 -6328에 의해 뒤로 강화 JMZ 우리가 공격 할 전에 한 반복 JMZ자체. JMZ6328 번 지시에 따라 폭탄 이 감지 되면, 우리가 공격하지 않고 가능한 한 많은 코어를 다뤘으 며, 우리를 죽이기 전에 백업 전략으로 전환해야한다는 신호입니다.

  5. 백업 전략에 관해서는, MOV 0 1지금은 더 나은 것을 생각할 수 없기 때문에 오래된 오래된 임프입니다. 내가 본 방식으로, 만약 우리가 코어의 네 번째 위치마다 폭격을했지만 여전히 이기지 못했다면, 우리는 아마도 아주 작거나 매우 방어적인 무언가와 싸울 것입니다. 그런 작거나 방어적인 프로그램은 일반적으로 다른 것을 죽이는 데별로 좋지 않기 때문에 우연히 몇 번의 싸움 만이기는 경우에도 여전히 앞서 나올 것입니다.


추신. 다른 누군가가 그것을 원한다면 여기에 약간의 PhiNotPi 토너먼트 코드 포크가 있습니다 . 약 두 배 빠르며 오래된 전투 결과를 저장하여 다시 실행할 필요가 없으며 전투 결과 계산에서 사소한 버그 라고 생각되는 것을 수정합니다 . PhiNotPi에 의해 변경 사항이 기본 버전 으로 병합되었습니다 . 감사!


1
스코어링은 프로그램 시작 위치의 가능한 모든 조합과 테스트에서 가장 많은 승점을 얻습니다. 이것은 프로그램이 결코 자신을 죽이지 않고 적어도 한 번의 주소를 폭격하지 않는 한, 임프를 이기고, 한 번의 승리를 거두고, 나머지 는 유대하기 때문에 유대를 불가능 하거나 완전히 바람직하지 않게 만듭니다 .
mbomb007

9

그래프보기

이것은 디버깅 도구로 사용될 수 있습니다. 코어를 표시하고 플레이어의 위치를 ​​보여줍니다. 그것을 사용하려면 코드에서 호출해야합니다. 또한 Game.javaGraphView를 자동으로 표시 하는 수정 을 제공했습니다 .

PhiNotPi와 Ilmari Karonen은 최근 컨트롤러를 변경했습니다. Ilmari Karonen 은이 위치 에서 업데이트 된 GameView를 제공 할만큼 친절했습니다 .

import javax.swing.*;
import java.awt.*;

public class GameView extends JComponent{

    final static Color[] commandColors = new Color[]{
            Color.black, //DAT
            Color.blue,  //MOV
            Color.blue,  //ADD
            Color.blue,  //SUB
            Color.blue,  //JMP
            Color.blue,  //JMZ
            Color.blue,  //CMP
    };

    final static Color[] specialColors = new Color[]{
            new Color(0,0,0),
            new Color(190, 255, 152),
            Color.yellow,
            new Color(0, 93, 14),
            new Color(96, 92, 4),
            new Color(0, 93, 14),
            new Color(96, 92, 4),
            new Color(0, 93, 14),
            new Color(96, 92, 4)
    };

    final static Color playerOneColor = Color.green;
    final static Color playerTwoColor = Color.white;

    final Game game;

    int playerOneLocation;
    int playerTwoLocation;

    final static int width = 128;
    final static int height = 64;

    public GameView(Game game) {
        this.game = game;
    }

    @Override
    public void paint(Graphics g) {
        int pixelWidth = getSize().width;
        int pixelHeight = getSize().height;
        if (width > pixelWidth){
            pixelWidth = width;
            setSize(width, pixelHeight);
        }
        if (height > pixelHeight){
            pixelHeight = height;
            setSize(pixelWidth, height);
        }
        int squareWidth = Math.min(pixelWidth / width, pixelHeight / height);
        for (int x = 0; x < squareWidth * width; x += squareWidth){
            for (int y = 0; y < squareWidth * height; y += squareWidth){
                int index = (y / squareWidth) * width + (x / squareWidth);
                Color color = commandColors[game.core[index][0]];
                if (game.coreData[index] != 0){
                    color = specialColors[game.coreData[index]];
                }
                if (index == playerOneLocation){
                    color = playerOneColor;
                }
                if (index == playerTwoLocation){
                    color = playerTwoColor;
                }
                g.setColor(color);
                g.fillRect(x, y, squareWidth, squareWidth);
            }
        }
    }

    public void setLocations(int p1loc, int p2loc){
        this.playerOneLocation = p1loc;
        this.playerTwoLocation = p2loc;
    }
}

수정 된 Game.java :

import javax.swing.*;
import java.util.Random;
import java.util.ArrayList;
import java.util.Arrays;
/**
 * This runs a game of Core Wars between two players.  It can be called mutiple times.
 * 
 * @author PhiNotPi 
 * @version 3/10/15
 */
public class Game
{
    final Player p1;
    final Player p2;
    final int coreSize;
    final int coreSizeM1;
    final int maxTime;
    final int debug;
    public int[][] core;
    public int[] coreData; //Used in debugging.
    int offset1;
    int offset2;
    Random rand;
    ArrayList<int[]> p1code;
    ArrayList<int[]> p2code;
    int p1size;
    int p2size;
    GameView gameView;
    int time = 1000000; //Time in nanoseconds between frames
    public Game(Player A, Player B, int coreSize, int maxTime, int debug)
    {
        p1 = A;
        p2 = B;

        coreSize--;
        coreSize |= coreSize >> 1;
        coreSize |= coreSize >> 2;
        coreSize |= coreSize >> 4;
        coreSize |= coreSize >> 8;
        coreSize |= coreSize >> 16;
        coreSize++;

        this.coreSize = coreSize;
        this.coreSizeM1 = coreSize - 1;
        this.maxTime = maxTime / 2;
        this.debug = debug;
        core = new int[coreSize][5];
        rand = new Random();
        p1code =  p1.getCode();
        p1size = p1code.size();
        p2code =  p2.getCode();
        p2size = p2code.size();
        if (debug == 1){
            gameView = new GameView(this);
            JFrame frame = new JFrame("Game");
            frame.add(gameView);
            frame.setVisible(true);
            frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
            frame.setSize(128, 64);
            coreData = new int[coreSize];
        }
    }

    public int runAll()
    {
        int sum = 0;
        for(int i = 0; i < coreSize - p1size - p2size; i++)
        {
            sum += run(i) - 1;
        }
        if(sum > 0)
        {
            return 1;
        }
        if(sum < 0)
        {
            return -1;
        }
        return 0;
    }

    public int run()
    {
        return run(rand.nextInt(coreSize - p1size - p2size + 1));
    }

    public int run(int deltaOffset)
    {
        core = new int[coreSize][5];
        //offset1 = rand.nextInt(coreSize);
        offset1 = 0;
        for(int i = 0; i != p1size; i++)
        {
            //System.arraycopy(p1.getCode().get(i), 0, core[(offset1 + i) % coreSize], 0, 5 );
            int[] line = p1code.get(i);
            int loc = (offset1 + i) & coreSizeM1;
            core[loc][0] = line[0];
            core[loc][1] = line[1];
            core[loc][2] = line[2];
            core[loc][3] = line[3];
            core[loc][4] = line[4];
            if (debug != 0){
                coreData[loc] = 1;
            }
        }
        offset2 = offset1 + p1size + deltaOffset;
        for(int i = 0; i != p2size; i++)
        {
            //System.arraycopy(p2.getCode().get(i), 0, core[(offset2 + i) % coreSize], 0, 5 );
            int[] line = p2code.get(i);
            int loc = (offset2 + i) & coreSizeM1;
            core[loc][0] = line[0];
            core[loc][1] = line[1];
            core[loc][2] = line[2];
            core[loc][3] = line[3];
            core[loc][4] = line[4];
            if (debug != 0){
                coreData[loc] = 2;
            }
        }

        int p1loc = offset1 & coreSizeM1;
        int p2loc = offset2 & coreSizeM1;
        for(int time = 0; time != maxTime; time++)
        {
            if(debug != 0)
            {
                //printCore(p1loc,p2loc);
                //System.out.println("p1loc " + p1loc);
                //System.out.println("offset " + offset1);
                gameView.setLocations(p1loc, p2loc);
                gameView.repaint();
                try {
                    Thread.sleep(time / 1000000, time % 1000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            if(core[p1loc][0] == 0)
            {
                return 0;
            }
            p1loc = execute(p1loc, offset1, 1);

            if(debug != 0)
            {
                //printCore(p1loc,p2loc);
                //System.out.println("p2loc " + p2loc);
                //System.out.println("offset " + offset2);
                gameView.setLocations(p1loc, p2loc);
                gameView.repaint();
                /*try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
            }
            if(core[p2loc][0] == 0)
            {
                return 2;
            }
            p2loc = execute(p2loc, offset2, 2);

        }
        return 1;
    }
    public int execute(int ploc, int offset, int player)
    {
        int line1 = offset + core[ploc][3];
        if(core[ploc][1] != 0)
        {
            line1 += ploc - offset;
        }
        if(core[ploc][1] == 2)
        {
            line1 += core[line1 & coreSizeM1][3];
        }
        int line2 = offset + core[ploc][4];
        if(core[ploc][2] != 0)
        {
            line2 += ploc - offset;
        }
        if(core[ploc][2] == 2)
        {
            line2 += core[line2 & coreSizeM1][4];
        }
        line1 = line1 & coreSizeM1;
        line2 = line2 & coreSizeM1;
        int opcode = core[ploc][0];
        ploc = (ploc + 1) & coreSizeM1;
        //String opDescription = "";
        if(opcode == 1)
        {
            core[line2][0] = core[line1][0];
            core[line2][1] = core[line1][1];
            core[line2][2] = core[line1][2];
            core[line2][3] = core[line1][3];
            core[line2][4] = core[line1][4];
            if (debug != 0) {
                coreData[line2] = player + 2;
            }
            return ploc;
            //opDescription = "Moved from " + line1 + " to " + line2;
        }
        if(opcode == 2)
        {
            core[line2][3] += core[line1][3];
            core[line2][4] += core[line1][4];
            if (debug != 0) {
                coreData[line2] = player + 4;
            }
            return ploc;
            //opDescription = "Added " + line1 + " to " + line2;
        }
        if(opcode == 3)
        {
            core[line2][3] -= core[line1][3];
            core[line2][4] -= core[line1][4];
            if (debug != 0) {
                coreData[line2] = player + 6;
            }
            return ploc;
                //opDescription = "Subtracted " + line1 + " to " + line2;
        }
        if(opcode == 4)
        {
            ploc = line1;
            return ploc;
                //opDescription = "Jumped to " + line1;
        }
        if(opcode == 5)
        {
                if(core[line2][3] == 0 && core[line2][4] == 0)
                {
                    ploc = line1;
                    //opDescription = "Jumped to " + line1;
                }
                else
                {
                    //opDescription = "Did not jump to " + line1;
                }
                return ploc;
        }
        if(opcode == 6)
        {
            if(core[line1][3] == core[line2][3] && core[line1][4] == core[line2][4])
            {
                //opDescription = "Did not skip because " + line1 + " and " + line2 + " were equal.";
            }
            else
            {
                ploc = (ploc + 1) & coreSizeM1;
                //opDescription = "Skipped because " + line1 + " and " + line2 + " were not equal.";
            }
            return ploc;
        }
        if(debug != 0)
        {
            //System.out.println(opDescription);
        }
        return ploc;
    }
    /*public void printCore(int p1loc, int p2loc)
    {
        int dupCount = 0;
        int[] dupLine = new int[]{0,0,0,0,0};
        for(int i = 0; i < core.length; i++)
        {
            int[] line = core[i];
            if(Arrays.equals(line, dupLine) && i != p1loc && i != p2loc)
            {
                if(dupCount == 0)
                {
                    System.out.println(Player.toString(line));
                }
                dupCount++;
            }
            else
            {
                if(dupCount == 2)
                {
                    System.out.println(Player.toString(dupLine));
                }
                else if(dupCount > 2)
                {
                    System.out.println("    " + (dupCount - 1) + " lines skipped.");
                }
                System.out.println(Player.toString(line));
                if(i == p1loc)
                {
                    System.out.print(" <- 1");
                }
                if(i == p2loc)
                {
                    System.out.print(" <- 2");
                }
                dupLine = line;
                dupCount = 1;
            }
        }
        if(dupCount == 2)
        {
            System.out.println(Player.toString(dupLine));
        }
        else if(dupCount > 2)
        {
            System.out.println("    " + (dupCount - 1) + " lines skipped.");
        }
    }*/
}

Player도 수정 한 것 같습니다. 다음을 얻습니다./Game.java:275: error: method toString in class Object cannot be applied to given types; System.out.println(Player.toString(line)); ^ required: no arguments found: int[]
AShelly

@AShelly 죄송합니다. 나는 그 printCore()방법 에 대해 언급해야한다 .
TheNumberOne

9

터보

main   add three target
test   jmz -1 @target
bomb   mov three @target
       sub j1 target 
       mov jump @target
       sub j1 target 
       mov copy @target
       sub j1 target
two    mov decr @target
j1     jmp @target 1
target dat -8 -8   
decr   sub #two 3
copy   mov 2 @2
jump   jmp -2 0
three dat -9 -9

나의 두 번째 CoreWar 시도. 난쟁이를 이길 수 있도록 설계되었습니다. 3을 기준으로 데이터를 스캔 한 후 2마다 폭탄을 넣습니다. 각 단계는 3 개의 명령으로 만 실행됩니다. 드워프의 폭탄이 놓치기를 바랍니다.

새로운 Turbo ++ : 이제 향상되었습니다. 데이터를 찾을 때까지 뒤로 스캔 한 다음 그곳으로 이동 한 다음 뒤로 폭탄을 발사합니다. 희망은 이동이 상대방을 방해하거나 이미 폭파되어 안전한 장소에 있다는 것입니다.

... 그리고 스캔을 좀 더 드물게 만들도록 편집하면 모든 사람을 이길 수 있습니다!


난쟁이보다 훨씬 더 많이 이길 것 같습니다. 축하합니다! 임프를 이길 수만 있다면 3 위에 도달 할 수 있다고 생각합니다.
Ilmari Karonen

나는 이것을 업데이트했지만 실제로 이전의 것에서 상당히 큰 진화입니다. 대신 새 항목을 만들어야합니까?
AShelly

나는 PhiNotPi를 말하는 것으로 생각하지 않지만 그것이 당신에게 달려 있다고 생각합니다. 전체 업데이트는 기본적으로 기존 항목을 철회하는 것을 의미합니다. 어쨌든, 3 위로 성공적으로 폭탄을 피하는 데 더 많은 축하를드립니다! 나는 DwarvenEngineer를 쌍으로 이길 수있는 유일한 항목이라고 생각합니다.
Ilmari Karonen

잘 했어 ;). 당신은 지금 이길 사람입니다!
히트

8

난쟁이

드워프 던지기 돌을 나타내는 일반적이고 간단한 프로그램입니다. 그것은 배치 DAT명령을 매 4 개 주소를.

add 2 3
mov 2 @2
jmp -2 #4
dat #0 #4

편집 : 주소 지정을 수정합니다. 분명히 주소 지정 모드는 OP가 연결된 사양과 다릅니다.


첫 번째 줄에 "add # 3 3"이라고 생각합니까?
히트

@ 히트 4 번째 주소마다 적중하고 싶습니다. 을 사용할 수는 add 3 3있지만 추가하는 대신 각 루프를 두 배로 늘리면 유용하지 않습니다. #4즉, 현재 주소 다음 4에 오는 주소의 두 번째 값에 숫자 를 더합니다 3.
mbomb007

#도전 과제에서 주소 지정 모드를 잘못 해석하고 있다고 생각합니다 . 사양에서 언급했듯이 #주소 지정 모드를 변경했습니다 .
PhiNotPi

"2 JMP -2 4 날엔 0 @ 4 2 3 MOV 2를 추가"당신은 같이 가야
히트

올바른 동작과 함께 심지어 진화 패배
히트

7

진화

나는 솔직히 그것이 어떻게 작동하는지 얻지 못한다. 아무것도하기 전에 소스 코드를 작성하는 것 같습니다. 누군가 나에게 그것이 어떻게 작동하는지에 대한 설명을 주면 좋아할 것입니다.

그것을 연구 한 후에, 나는 그것이 단지 경비원과 함께 수정 된 난쟁이라는 것을 발견했다. DAT지시 사항으로 적을 폭격하는 대신 적 코드를 섞습니다. 또한 4 개의 레지스터 대신 2 개의 레지스터마다 폭탄을 터뜨립니다. 충분한 시간이 주어지면 의심 할 여지없이 스스로를 파괴 할 것입니다.

MOV -2 #-1
MOV #4 -9
SUB -5 #6
MOV #1 1
MOV #-6 #4
SUB @8 @7
JMP -3 @4
DAT #-4 8
JMP -1 9
JMP 5 #-10
CMP @-1 #0
SUB 3 #-10
JMP @10 #-9
JMZ #1 10
MOV #3 2
ADD @9 @-3
CMP #-3 @7
DAT @0 @-2
JMP @-7 #6
DAT @-8 -6
MOV @0 #9
MOV #2 1
DAT @6882 #-10
JMP @3 4
CMP @8 2
ADD -7 @11
ADD @1 #-9
JMZ @-5 7
CMP 11 5526
MOV @8 6
SUB -6 @0
JMP 1 11
ADD @-3 #-8
JMZ @-14 @-5
ADD 0 @-8
SUB #3 @9
JMP #-1 5
JMP #9 @1
CMP -9 @0
SUB #4 #-2
JMP #-8 5
DAT -1 @-10
MOV 6 #2
CMP @-11 #-14
ADD @4 @-3
MOV @5 #-6
SUB -3 -2
DAT @-10 #-1
MOV #-13 #-6
MOV #1 5
ADD 5 #-5
MOV -8 @-1
DAT 0 10
DAT #5 #7
JMZ 6 -5
JMZ -12 -11
JMP 5 @-7
MOV #7 -3
SUB #-7 @-3
JMP -4 @-11
CMP @-5 #-2
JMZ @-1 #0
ADD #3 #2
MOV #5 @-6

1
그럼 어디서 구했어?
PyRulez

4
@PyRulez 유전자 알고리즘을 통해 생성 된 컴퓨터입니다.
TheNumberOne

1
실행이 실제로 6 번 라인보다 더 멀리 진행되지 않는 것처럼 보입니다. 프로그램에서 다시 점프하기 때문입니다. 나는 그것이 성공한 이유는 경쟁사보다 더 많은 움직임 / 루프가 있기 때문이라고 생각합니다.
PhiNotPi

6

FirstTimer

그것이 작동한다면, 핵심의 시작 부분에서 입장을 취하고 방어를 만들어야합니다.

main MOV 5 #0
     ADD #data #main
     CMP #main #max
     JMP #0 0
     JMP #main 0
     MOV #data #100
     ADD #data -1
     JMP -2 0
data DAT 1 1
max  DAT 8 3

그것은 내가 생각했던 방식으로 작동하지 않습니다 : 핵심의 시작이 아닌 프로그램#0 의 시작 (즉,와 동일 )을 의미합니다 (어쨌든 의미있는 개념은 아닙니다-핵심은 순환하는 경우 코드에서 시작 또는 끝을 알 수 없습니다). 첫 번째 명령어 ( ) 가을 (를 ) 덮어 쓰고, 그 후 코드가 효과적으로 0.25c (= 4 사이클 당 하나의 명령어) 순방향 코어로 바뀝니다. #mainmainMOV #data #100
Ilmari Karonen

@IlmariKaronen 아, 설명해 주셔서 감사합니다. 나는 #0핵심의 시작으로 착각 했다. 첫 번째 5 가지 지침은 완전히 쓸모가 없습니다.
Thrax

6

복사

CoreWar에 참여한 적이없는이 간단한 프로그램은 자신을 복사하여 붙여 넣은 다음 복사를 시도합니다. 올바르게 작동하지 않을 수 있습니다. 해당되는 경우 알려주십시오.

너무 평화 주의적이며 실제로 이길 수 없습니다.

MOV 6 0
MOV @-1 @-1
CMP @-2 3
JMP 4242 0
SUB -3 -4
JMP -4 0
DAT 0 4244

이 현재 수정 사항은 다음 순위표 업데이트에 포함되지 않을 것입니다 (현재 토너먼트를 진행하고 있습니다). 그러나 이전 버전은 (작은 코어 크기) 예비 결과를 얻었습니다.
PhiNotPi

좋아 :) 이전 버전은 loop1에서 나오지 않습니다. 실제로 원하는 동작이 아닙니다. 수정하려고합니다.
히트

현재 버전이 고장난 것 같습니다. 아직 이유를 모르겠습니다.
PhiNotPi

1
디버깅 도구를 개선하여 이제 문제를 진단 할 수 있습니다. 현재 프로그램은에서 시작하여 후반부 만 복사합니다 JMP loop 0. 그런 다음 복사본의 시작 위치로 건너 뛰면 빈 공간이어서 잃어 버립니다.
PhiNotPi

2
이전의 (현재 삭제 된) 주석을 무시하십시오. 잘못된 버전의 코드를 테스트했습니다 (사 전적으로 붙여 넣기 실수로 인해).
Ilmari Karonen

6

관리인

다음 주소가 비어 있는지 확인하고 그렇지 않은 경우 정리하십시오 (따라서 상대방 봇 지우기).

편집 : 이 새로운 버전은 더 빨라야합니다 (이제 JMZ명령과 @참조를 올바르게 이해했습니다 ).

JMZ 2 6
MOV 4 @-1
ADD 2 -2
JMP -3 0
DAT 0 1
DAT 0 0

관리인이 첫 번째 JMZ로 자살하지 않습니까? 적어도 JMZ 2 8이어야합니다. @를 사용하면 두 개의 합을 하나만 줄일 수 있습니다. 같은 뭔가 "JMZ 2 @ 5 MOV 5 @ 4 ADD 2 3 JMP -3 0 DAT 0 1 DAT 0 2 DAT 0 0"(안된)
히트

@Hit 거기에서 주소 2가 있기 때문에 뛰어 오르지 ADD 3 -2않지만 그가 변경해야한다고 생각합니다.
mbomb007

예, 나는 지시를 잘못 읽었 JMZ으며 생각 JMZ A B이 반대 일 때 0 으로 확인 A하고 점프하는 것으로 생각 B했습니다. :)
plannapus

5

ScanBomber

컴파일하기 전에 내 의견을 제거하십시오. 잠시 동안 스캔 한 다음 프로그램을 찾으면 폭탄이 터집니다. 그래도 여전히 내 난쟁이에게 잃을 것입니다.

scan add #eight #range  ; scan
jmz #scan @range
sub #six #range
fire mov #zero @range   ; bombs away! (-6)
add #two #range
mov #zero @range
add #two #range
mov #zero @range
add #two #range
mov #zero @range        ; (+0)
add #two #range
mov #zero @range
add #two #range
mov #zero @range
add #two #range
mov #zero @range
add #two #range
mov #zero @range        ; (+8)
range jmp #scan 6
two dat 0 2
six dat 0 6
zero dat 0 0
eight dat 0 8

OP #는 사양과 완전히 다르게 정의 되어 (그가 링크 한 링크 읽기) 아직이 프로그램을 수정하지 않았습니다.
mbomb007

@ TheBestOne 나는 그것을 고쳤다 고 생각합니다. 이제 말이 되나요? 아니면 #모든 참조 전에 넣어야 zero합니까? 그래, 내가해야 할 것 같아요
mbomb007

지금은 잘 작동합니다. 드워프와 임프를 제외한 모든 로봇을 이깁니다.
TheNumberOne

@TheBestOne Dwarf가 너무 작으며 가능한 프로그램 배치의 50 %에서만 감지됩니다. 메모리 전체를 돌고 나면 폭탄 자체를 쏘기 때문에 Imp 만 잃게 될 것입니다.
mbomb007

5

한 샷 퍼스트 (v2)

경쟁에서 좀 더 다양한 다양성을 사용할 수 있다고 생각 했으므로 여기에 두 번째 항목 인 원샷 CMP스캐너가 있습니다.

버전 은 향상된 안티 임프 방어 기능을 갖춘 버전 2 입니다. 이제 한 지점 만 있으면 Imp를 이길 수 있습니다. 그것은 여전히 ​​Dwarven Engineer에게지는 것이지만 지금까지 다른 모든 것을 능가하여 현재 묶인 1 위를 차지했습니다.

scan    ADD bomb    aim
aim     CMP 17      12
        JMZ scan    #-3
loop    MOV bomb    @aim
        ADD step    aim
step    JMP loop    #2
bomb    DAT 10      10

차이가 발견 될 때까지 10 단계 간격으로 5 단계 간격으로 인접한 코어 위치를 비교하여 작동합니다. 적을 처치하면 적을 죽이거나 코어 주위를 돌면서 자신에게 도달 할 때까지 2 단계 간격으로 폭탄을 던지기 시작합니다.

스캔이 다른 것을 찾지 못하면 결국 루프를 돌면서 자체 코드를 찾아 공격합니다. 이것은 자살 적이지만, 첫 번째 폭탄이aim 줄에 닿아 다음 폭탄이 코어 아래로 12 위치 (일반 2가 아닌)에 던져져 코드를 편리하게 건너 뛸 수 있습니다. (이는 스캔이 무언가를 찾지 만 상대방을 죽이지 않으면 50 % 확률로 발생합니다.) 코어 크기가 2의 배수이기 때문에, 폭격이 반복 될 경우에도 계속 발생할 수 있습니다. 추가 백업 전략.

(이 자체 폭탄 테러는 원래 순수한 우연의 일치였습니다. 아무것도 발견되지 않으면 스캐닝에서 폭격 모드로 전환하는 완전히 다른 방법을 계획했지만 코드를 처음 테스트했을 때 상수는 올바른 것으로 나타났습니다. 이 방법으로 일하고 나는 그것을 고수하기로 결정했습니다.)



4

강타

     mov    ones    @-1024
     mov    from    -3
     mov    here    -3
loop mov    @-5 @-4
     add    ones  -5
     jmz    -17 -6
     add    ones  -8    
     jmp    loop    42
ones dat    1   1
from dat    2   2
here dat    -11 -11

메모리 공간을 뒤로 탐색합니다. 때때로 폭탄을 멀리 떨어 뜨립니다.


3

부활절 토끼

그는 거꾸로 호핑을 즐긴다 :)

loop mov 0 -10
     add data loop
     cmp -7 data
     jmp -13 0
     jmp loop 0
data dat 1 1

3

편집증

복사 파스타의 종류이지만 폭탄에 의해 코드가 수정되었는지 확인합니다. 그렇다면 난쟁이를지나 복사하고 실행하십시오. GameView를 다시 만들면 일부 상수를 변경하려고 시도합니다.

copy    MOV data copy
loop    MOV @-1 @-1
    CMP @copy end
out JMP check 0
    SUB loop copy
    JMP loop 0
data    DAT 0 4109
check   MOV data copy
loop2   CMP @copy @copy
    JMP ok 0
    MOV aah 2
ok  CMP @copy end
    JMP 4098 0
    SUB loop copy
    JMP loop2 0
panic   MOV end copy
    MOV jump out
    JMP loop 0
jump    JMP 4124 0
dwarf   ADD 2 bomb
    MOV bomb @bomb
    JMP dwarf 4
bomb    DAT 0 4
aah JMP 3 0
end DAT 19 4127

좋아, 사실 그것은 작동하고 난 그냥, 새로운 실행에 대한 감사를 주위 messign되었다)
히트
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.