기능적 언어의 2 차원 보드 게임을위한 데이터 구조


9

함수형 프로그래밍 언어 Elixir에서 간단한 MiniMax 구현을 만들고 있습니다. 완벽한 지식 게임 (tic tac toe, connect-four, checker, chess 등)이 많으므로이 구현은 이러한 게임 중 하나에 대한 게임 AI를 만들기위한 프레임 워크가 될 수 있습니다.

그러나 내가 직면하고있는 한 가지 문제는 기능적 언어로 게임 상태를 올바르게 저장하는 방법입니다. 이러한 게임은 주로 다음과 같은 작업이 빈번한 2 차원 게임 보드를 처리합니다.

  • 특정 보드 위치의 내용을 읽습니다.
  • 특정 보드 위치의 내용을 업데이트합니다 (새로운 이동 가능성을 반환 할 때)
  • 현재 위치 (즉, 다음 또는 이전의 수평, 수직 또는 대각선 위치)에 연결된 하나 이상의 위치의 내용을 고려
  • 여러 방향으로 연결된 여러 위치의 내용을 고려합니다.
  • 전체 파일, 순위 및 대각선의 내용을 고려합니다.
  • 보드 회전 또는 미러링 (이미 계산 된 것과 동일한 결과를 제공하는 대칭을 확인하기 위해)

대부분의 기능적 언어는 링크 된 목록 및 튜플을 다중 요소 데이터 구조의 기본 빌딩 블록으로 사용합니다. 그러나 이것들은 일을 위해 매우 나쁘게 보입니다.

  • 연결된 목록 에는 O (n) (선형) 조회 시간이 있습니다. 또한 보드 위의 단일 스윕으로 '보드를 스캔하고 업데이트'할 수 없으므로 목록을 사용하는 것은 매우 비현실적입니다.
  • 튜플 에는 O (1) (일정한) 조회 시간이 있습니다. 그러나 보드를 고정 크기 튜플로 표시하면 순위, 파일, 대각선 또는 다른 종류의 연속 정사각형을 반복하는 것이 매우 어렵습니다. 또한 Elixir와 Haskell (내가 알고있는 두 가지 기능 언어) 은 튜플 의 n 번째 요소 를 읽는 구문이 부족합니다 . 이로 인해 임의 크기의 보드에 적합한 동적 솔루션을 작성할 수 없습니다.

Elixir에는 O (log n) (logarithmic) 요소에 대한 액세스를 허용 하는 내장 Map 데이터 구조 (And Haskell has Data.Map)가 있습니다. 지금 x, y은 위치를 키로 나타내는 튜플이 있는 맵을 사용 합니다.

이 '작동'하지만 왜 그런지 정확히 알지 못하지만 이런 식으로 맵을 남용하는 것은 잘못된 느낌입니다. 함수형 프로그래밍 언어로 2 차원 게임 보드를 저장하는 더 좋은 방법을 찾고 있습니다.


1
나는 실천에 대해 말할 수는 없지만, 두 가지 하스켈에서 내 마음에 와서 : 지퍼 , 이해가 제대로 기억하지 않고 어느 쪽도 일부 이론에 의해 지퍼 관련된 데이터 구조 및 comonads, 일정한 시간 "의 단계를"허용; )
phipsgabler 2016 년

이 보드는 얼마나 큰가요? Big O는 알고리즘이 얼마나 빠르지 않고 확장되는지를 나타냅니다. 작은 보드 (예 : 각 방향으로 100 미만)에서 각 사각형을 한 번만 터치하면 O (1) 대 O (n)은 별 문제가되지 않습니다.
Robert Harvey

@RobertHarvey 다를 수 있습니다. 그러나 예를 들어 : 체스에는 64x64 보드가 있지만 어떤 움직임이 가능한지 확인하고 현재 위치의 휴리스틱 값 (재료의 차이, 왕의 여부, 폰의 통과 등)을 결정하는 모든 계산 모두 보드의 사각형에 접근해야합니다.
Qqwy

1
체스에는 8x8 보드가 있습니다. C와 같은 메모리 매핑 언어에서는 셀의 정확한 주소를 얻기 위해 수학 계산을 수행 할 수 있지만 메모리 관리 언어 (서수 주소 지정이 구현 세부 정보)에서는 그렇지 않습니다. 14 개의 노드를 뛰어 넘는 것이 메모리 관리 언어로 배열 요소를 처리하는 것과 거의 같은 시간이 걸리더라도 놀라지 않을 것입니다.
Robert Harvey

답변:


6

여기서 A Map는 정확한 기본 데이터 구조입니다. 왜 당신을 불안하게 만들지 잘 모르겠습니다. 조회 및 업데이트 시간이 뛰어나고 크기가 동적이며 파생 데이터 구조를 만드는 것이 매우 쉽습니다. 예를 들어 (haskell에서) :

filterWithKey (\k _ -> (snd k) == column) -- All pieces in given column
filterWithKey (\k _ -> (fst k) == row)    -- All pieces in given row
mapKeys (\(x, y) -> (-x, y))              -- Mirror

프로그래머가 불변성을 가지고 프로그래밍을 처음 시작할 때 파악하기 어려운 다른 점은 하나의 데이터 구조 만 고수 할 필요가 없다는 것입니다. 일반적으로 하나의 데이터 구조를 "진실의 원천"으로 선택하지만 원하는만큼 파생물을 만들 수 있으며 파생물 파생물까지 만들 수 있으며 필요한만큼 오래 동기화 될 수 있습니다.

Map, 최상위 수준에서을 사용한 다음 행 분석으로 전환 Lists하거나 Arrays행 분석을 Arrays위해 다른 방법으로 열 분석을 위해 색인 을 생성 한 다음 패턴 분석을 Strings위해 비트 마스크 를 표시 한 다음 표시 할 수 있습니다. 잘 설계된 기능적 프로그램은 단일 데이터 구조를 전달하지 않습니다. 그것들은 하나의 데이터 구조를 취하여 다음 단계에 적합한 새로운 데이터 구조를 방출하는 일련의 단계입니다.

최상위 수준에서 이해할 수있는 형식으로 이동하여 다른 쪽을 도출 할 수있는 한, 그 사이에 데이터를 얼마나 많이 재구성할지 걱정할 필요가 없습니다. 불변이므로 최상위 레벨에서 진실의 근원으로 돌아가는 경로를 추적하는 것이 가능합니다.


4

최근에 F #에서이 작업을 수행했으며 1 차원 목록 (F #에서는 단일 연결 목록)을 사용했습니다. 실제로, O (n) 목록 인덱서의 속도는 사람이 사용할 수있는 보드 크기의 병목 현상이 아닙니다. 나는 2d 배열과 같은 다른 유형을 실험했지만 결국 내 자체 가치 평등 검사 코드를 작성하거나 순위와 파일을 색인으로 변환하는 것이 절충되었습니다. 후자는 더 간단했다. 먼저 작동시킨 다음 필요할 경우 나중에 데이터 유형을 최적화하십시오. 문제에 대해 큰 차이를 만들지 않을 것입니다.

결국 보드 작업이 보드 유형 및 작업으로 올바르게 캡슐화되어있는 한 구현이 중요하지 않아야합니다. 예를 들어, 다음은 테스트 중 일부가 보드를 설정하는 방법입니다.

let pos r f = {Rank = r; File = f} // immutable record type
// or
let pos r f = OnBoard (r, f) // algebraic type
...
let testBoard =
    Board.createEmpty ()
    |> Board.setPiece p (pos 1 2)
    |> ...

이 코드 (또는 모든 호출 코드)에는 보드가 어떻게 표현되었는지는 중요하지 않습니다. 보드는 기본 구조 이상의 작업으로 표시됩니다.


안녕하세요, 코드가 어딘가에 게시되어 있습니까? 나는 현재 F # (재미있는 프로젝트)에서 체스와 같은 게임을하고 있으며 심지어 맵을 표현하기 위해 Map <Square, Piece>를 사용하고 있습니다. 유형과 모듈.
asibahi

아니요, 어디에도 게시되지 않았습니다.
Kasey Speakman

현재 구현을 살펴보고 개선 할 수있는 방법에 대해 조언 해 주시겠습니까?
asibahi

유형을 살펴보면 Position 유형에 도달 할 때까지 매우 유사한 구현을 시작했습니다. 나중에 Code Review에 대해 더 철저한 분석을 수행 할 것입니다.
Kasey Speakman
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.