Оцініть гру Kingdom Builder


16

Я хочу спробувати нову форму кодового гольфу тут. Подібно до бонусів, не всі частини завдання повинні бути виконані, але кожна відповідь має реалізувати підмножину певного розміру (і є ядро, яке має відповідати кожна відповідь). Тому, крім гольфу, цей виклик також включає вибір набору функцій, які добре поєднуються.

Правила

Kingdom Builder - це настільна гра, що грається на шестигранній сітці. Дошка складається з чотирьох (рандомізованих) чотирикутників, у кожному з яких є 10 шістнадцяткових комірок 10x10 (тому повна дошка буде 20x20). Для цілей цього виклику кожна шестигранна клітина містить або воду ( W), гори ( M) місто ( T), замок ( C), або порожню ( .). Так може виглядати квадрант

. . W . . . . . . .
 . M W W . . . . . .
. M . . W . . . T .
 M M . W . . . . . .
. . M . W W . . . .
 . . . . . W W W W W
. T . . . . . . . .
 . . W . . C . . . .
. . W W . . . . M . 
 . . . . . . . M M .

Другий ряд завжди буде зміщений праворуч від першого ряду. Гравці 1в 4можна розмістити до 40 пунктів по кожній з порожніх клітин (після деяких правил , які ми будемо ігнорувати для цього завдання). Можлива дошка в кінці гри:

3 3 W . . . 4 . 4 . . 2 W . 4 . . 4 . 4
 3 M W W . 1 1 . . 4 2 W . 3 C 4 4 . . 4
3 M 2 2 W 1 1 1 T 3 2 W 4 3 . 1 4 . 4 .
 M M . W 2 2 . . . 2 2 W 3 . 1 1 1 . . .
. 4 M . W W 2 2 2 2 W W 3 . 1 4 . T . .
 . . . . . W W W W W . 3 C 1 . . 2 2 2 2
. T 1 1 1 1 . . 2 . . 4 . . . 2 2 M M M
 4 . W 4 . C 4 4 . . . . . . 2 M M M M M
. 4 W W . . . 4 M . . W . W . 2 2 2 M M
 . . . . . . . M M . . W W . . . . 2 M .
. . . 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 . 1
 M 3 3 . . . . . . . . 4 . T 2 . 2 4 1 .
M M . C . 4 . 4 . . . . . 1 2 4 2 1 1 .
 M . . 1 . 4 . . . . M M 1 2 . . 2 1 . .
. . . W 1 1 4 1 1 . . . 1 2 . . 2 W W W
 . . 1 1 W 1 T . 1 1 1 1 T . . 2 W . 4 .
. 1 1 W . 3 3 . . . . . . . . 2 W 4 C 3
 C 1 3 3 3 . 3 . 4 . 4 . 4 . . 2 W 1 1 M
4 3 3 4 . M 4 3 . . . . . . . 2 W . . .
 . . . 4 . M M 3 . . 4 4 . 4 . 2 W W . .

Ми позначимо такі квадрати як

1 2
3 4

Вашим завданням буде забити таку дошку. Завжди використовується один основний рахунок, і 8 необов'язкових балів, 3 з яких вибираються для кожної гри. У подальшому я опишу всі 9 балів та використаю наведене вище налаштування як приклад того, скільки очок отримає кожен гравець.

† У дійсній грі є 10 балів, але я залишу два, тому що ніхто не хоче їх грати.

Основна оцінка. Гравець отримує 3 очки за кожного Cастела, який має поселення поруч. Приклад балів: 18, 0, 15, 12.

Необов’язкові бали.

  1. Гравець отримує 1 бал за кожен горизонтальний ряд, на якому має принаймні одне розрахункове.

    Приклад балів: 14, 20, 12, 16.

  2. Для кожного гравця знайдіть горизонтальний ряд, на якому вони перебувають у більшості своїх поселень (виберіть будь-який випадок, якщо буде нічия). Гравець отримує 2 очки за кожний населений пункт у цьому ряду.

    Приклад балів: 14 (рядок 16), 8 (рядок 4, 5 або 6), 28 (рядок 11), 10 (рядок 1).

  3. Гравець отримує 1 бал за кожне поселення, яке будується поруч з Wатером.

    Приклад балів: 13, 21, 10, 5.

  4. Гравець отримує 1 бал за кожний населений пункт поруч із Mсусідом.

    Приклад балів: 4, 12, 8, 4.

  5. Порахуйте розрахунки кожного гравця у кожному квадранті. За квадрант гравці з найбільшою кількістю населених пунктів отримують по 12 очок , гравці з другою за кількістю населених пунктів отримують по 6 очок .

    Приклад балів: 18 (6 + 0 + 6 + 6), 36 (12 + 12 + 0 + 12), 12 (0 + 0 + 12 + 0), 18 (12 + 6 + 0 + 0).

  6. Для кожного гравця визначте квадрант, у якому вони мають найменшу кількість поселень. Гравець отримує 3 очки за кожний населений пункт у цьому квадранті.

    Приклад балів: 18 (Квадрант 2), 0 (Квадрант 3), 15 (Квадрант 1 або 2), 27 (Квадрант 3).

  7. Гравець отримує 1 бал за кожну підключену групу населених пунктів.

    Приклад балів: 7, 5, 6, 29.

  8. Гравець отримує 1 бал за кожні 2 населених пункти у найбільшій групі пов'язаних населених пунктів.

    Приклад балів: 4, 10, 8, 2.

Змагання

Як і в грі, ви виберете 3 з необов'язкових балів, і зарахуєте дану дошку на основі основного бала та цих трьох балів. Ваш код повинен скласти список із 4 балів. Однак є одне обмеження щодо вибору: я згрупував оцінки в 3 групи, і ви повинні реалізувати одну з кожної групи:

  • Виконайте один із 1 та 2 .
  • Виконайте одну з 3, 4, 5 і 6 .
  • Виконайте одну із 7 та 8 .

Ви можете написати програму або функцію, взявши введення через STDIN, аргумент командного рядка, рядок або параметр функції. Ви можете повернути результат або роздрукувати його в STDOUT.

Ви можете вибрати будь-який зручний 1D або 2D список / формат списку для введення. Ви не можете використовувати графік із повною інформацією про суміжність. Ось хороше читання на шестигранних сітках, якщо вам потрібно натхнення.

Ваш вихід може також бути у будь-якому зручному, однозначному списку чи рядковому форматі.

Це кодовий гольф, тому найкоротша відповідь (у байтах) виграє.

Подальші припущення

Ви можете припустити, що ...

  • ... кожен гравець має принаймні 1 населений пункт, і не більше 40 розрахунків кожного гравця.
  • ... кожен квадрант містить або одне місто та два замки, або два міста та один замок.
  • ... міста і замки розташовані досить далеко один від одного, так що жодне поселення не може бути сусіднім з двома з них.

Випробування

Все ще використовуючи вищевказану дошку, ось окремі бали для всіх можливих варіантів вибору бальних механізмів:

Chosen Scores      Total Player Scores
1 3 7              52 46 43 62
1 3 8              49 51 45 35
1 4 7              43 37 41 61
1 4 8              40 42 43 34
1 5 7              57 61 45 75
1 5 8              54 66 47 48
1 6 7              57 25 48 84
1 6 8              54 30 50 57
2 3 7              52 34 59 56
2 3 8              49 39 61 29
2 4 7              43 25 57 55
2 4 8              40 30 59 28
2 5 7              57 49 61 69
2 5 8              54 54 63 42
2 6 7              57 13 64 78
2 6 8              54 18 66 51

Чи є дошка, на якій завжди грає один гравець, незалежно від комбінації?
ThreeFx

@ThreeFx Оскільки нижня межа кількості розрахунків на одного гравця дорівнює 1, налаштувати це досить просто. ;) Але з однаковою кількістю розрахунків для кожного гравця я насправді не знаю.
Мартін Ендер

Відповіді:


5

Python 2, 367 байт

T=range(20)
N=lambda r,c:{(a,b)for a,b in{(r+x/3-1,c+x%3-1+(x/3!=1)*r%2)for x in[0,1,3,5,6,7]}if-1<b<20>a>-1}
def S(B):
 def F(r,c):j=J[r][c]!=i;J[r][c]*=j;j or map(F,*zip(*N(r,c)));return j
 J=map(list,B);X=lambda r,c,x,y:x+y in{B[r][c]+B[a][b]for a,b in N(r,c)};return[sum((i in B[r])+20*(3*X(r,c,"C",i)-~X(r,c,i,"W")-F(r,c))for r in T for c in T)/20for i in"1234"]

Програма використовує оцінки 1, 3, 7. Введення - це список списків символів, що представляють кожну клітинку. Щоб легко протестувати прикладну плату, ми можемо зробити:

board = """
3 3 W . . . 4 . 4 . . 2 W . 4 . . 4 . 4
 3 M W W . 1 1 . . 4 2 W . 3 C 4 4 . . 4
3 M 2 2 W 1 1 1 T 3 2 W 4 3 . 1 4 . 4 .
 M M . W 2 2 . . . 2 2 W 3 . 1 1 1 . . .
. 4 M . W W 2 2 2 2 W W 3 . 1 4 . T . .
 . . . . . W W W W W . 3 C 1 . . 2 2 2 2
. T 1 1 1 1 . . 2 . . 4 . . . 2 2 M M M
 4 . W 4 . C 4 4 . . . . . . 2 M M M M M
. 4 W W . . . 4 M . . W . W . 2 2 2 M M
 . . . . . . . M M . . W W . . . . 2 M .
. . . 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 . 1
 M 3 3 . . . . . . . . 4 . T 2 . 2 4 1 .
M M . C . 4 . 4 . . . . . 1 2 4 2 1 1 .
 M . . 1 . 4 . . . . M M 1 2 . . 2 1 . .
. . . W 1 1 4 1 1 . . . 1 2 . . 2 W W W
 . . 1 1 W 1 T . 1 1 1 1 T . . 2 W . 4 .
. 1 1 W . 3 3 . . . . . . . . 2 W 4 C 3
 C 1 3 3 3 . 3 . 4 . 4 . 4 . . 2 W 1 1 M
4 3 3 4 . M 4 3 . . . . . . . 2 W . . .
 . . . 4 . M M 3 . . 4 4 . 4 . 2 W W . .
"""

board = [row.split() for row in board.strip().split("\n")]
print S(board)

# [52, 46, 43, 62]

Обробка шестигранної сітки

Оскільки ми перебуваємо на шестигранній сітці, ми маємо мати справу з сусідами трохи інакше. Якщо ми використовуємо традиційну 2D сітку в якості нашого представлення, то для (1, 1)нас є:

. N N . .       . N N . .                (0, 1), (0, 2)            (-1, 0), (-1, 1)
 N X N . .  ->  N X N . .  -> Neighbours (1, 0), (1, 2) -> Offsets (0, -1), (0, 1)
. N N . .       . N N . .                (2, 1), (2, 2)            (1, 0), (1, 1)

При детальному огляді ми розуміємо, що компенсації залежать від паритету рядка, на якому ви знаходитесь. Наведений вище приклад - для непарних рядків, але для парних рядів - це зрушення

(-1, -1), (-1, 0), (0, -1), (0, 1), (1, -1), (1, 0)

Єдине, що змінилося, - це те, що у 1-ї, 2-ї, 5-ої та 6-ї пар була зменшена друга координата на 1.

Функція лямбда Nприймає пару координат(row, col) і повертає всіх сусідів комірки всередині сітки. Внутрішнє розуміння генерує вищезазначені зсуви, витягуючи їх з простого базового кодування 3, збільшуючи другу координату, якщо рядок непарний, і додає зсуви у відповідну комірку для надання сусідів. Потім зовнішнє розуміння фільтрує, залишаючи лише сусідів, які знаходяться в межах сітки.

Безумовно

def neighbours(row, col):
    neighbour_set = set()

    for dr, dc in {(-1,-1), (-1,0), (0,-1), (0,1), (1,-1), (1,0)}:
        neighbour_set.add((row + dr, col + dc + (1 if dr != 0 and row%2 == 1 else 0)))

    return {(r,c) for r,c in neighbour_set if 20>r>-1 and 20>c>-1}

def solve(board):
    def flood_fill(char, row, col):
        # Logic negated in golfed code to save a few bytes
        is_char = (dummy[row][col] == char)
        dummy[row][col] = "" if is_char else dummy[row][col]

        if is_char:
            for neighbour in neighbours(row, col):
                flood_fill(char, *neighbour)

        return is_char

    def neighbour_check(row, col, char1, char2):
        return board[row][col] == char1 and char2 in {board[r][c] for r,c in neighbours(row, col)}

    dummy = [row[:] for row in board] # Need to deep copy for the flood fill
    scores = [0]*4

    for i,char in enumerate("1234"):
        for row in range(20):
            for col in range(20):
                scores[i] += (char in board[row])                        # Score 1
                scores[i] += 20 * 3*neighbour_check(row, col, "C", char) # Core score
                scores[i] += 20 * neighbour_check(row, col, char, "W")   # Score 3
                scores[i] += 20 * flood_fill(char, row, col)             # Score 7

        # Overcounted everything 20 times, divide out
        scores[i] /= 20

    return scores

Чи не може def Fбути окремою функцією, а не внутрішньою функцією? Неможливо kвидалити їх def F:?
Джастін

@Quincunx F- це функція заповнення затоплення і потребує доступу до неї J, тому з внутрішньої сторони можна економити при проходженні Jв якості параметра (я трохи експериментую, щоб побачити, чи зможу я обійти глибоке копіювання). Ти маєш рацію, kхоча, дякую :) (однак новий код виглядає дещо химерним, оскільки покладається на коротке замикання)
Sp3000

2

Програмування набору відповідей, 629 байт

d(X,Y):-b(X,Y,_).p(1;2;3;4).n(X,Y,(((X-2;X+2),Y);((X-1;X+1),(Y-1;Y+1)))):-d(X,Y).n(X,Y,I,J):-n(X,Y,(I,J));d(I,J).t(X,Y,P):-n(X,Y,I,J);b(I,J,P).s(c,P,S*3):-S={t(X,Y,P):b(X,Y,"C")};p(P).s(1,P,S*1):-S=#count{r(Y):b(_,Y,P)};p(P).s(3,P,S):-S={b(X,Y,P):t(X,Y,"W")};p(P).o(X,Y,Y+X*100):-d(X,Y).h(P,X,Y,I,J):-o(X,Y,O);o(I,J,Q);O<Q;n(X,Y,I,J);b(X,Y,P);b(I,J,P);p(P).h(P,X,Y,I,J):-o(X,Y,O);o(I,J,Q);O<Q;h(P,X,Y,K,L);n(K,L,I,J);b(I,J,P);p(P).c(P,X,Y):-h(P,X,Y,_,_);not h(P,_,_,X,Y).c(P,X,Y):-{h(P,X,Y,_,_);h(P,_,_,X,Y)}0;b(X,Y,P);p(P).s(7,P,S):-S=#count{c(P,X,Y):c(P,X,Y)};p(P).s(t,P,C+S+T+U):-s(c,P,C);s(1,P,S);s(3,P,T);s(7,P,U).#shows/3.

ASP належить до сімейства мов логічного програмування, тут втілена рамка Potassco , зокрема Clingo (Grounder Gringo + solver Clasp). Через обмеження парадигми він не може приймати задану плату безпосередньо як вихід, тому необхідна попередня обробка даних (тут виконується в python). Ця попередня обробка не враховується в загальному байтовому балі.

Це мій перший гольф-код, і мета - це більше показати мову, яку я люблю, яку я ніколи не бачив у гольфі, ніж справді виграти гру. Більше того, я далеко не експерт у галузі ASP, тому багато оптимізацій коду, безумовно, можна виконати для отримання результатів у меншій кількості байтів.

представлення знань

Є код пітона, який перетворює плату в атоми:

def asp_str(v):
    return ('"' + str(v) + '"') if v not in '1234' else str(v)

with open('board.txt') as fd, open('board.lp', 'w') as fo:
        [fo.write('b('+ str(x) +','+ str(y) +','+ asp_str(v) +').\n')
         for y, line in enumerate(fd)
         for x, v in enumerate(line) if v not in ' .\n'
        ]

Наприклад, атоми b (для __b__oard), наведені для першого рядка дошки прикладу, наступні:

b(0,0,3).
b(2,0,3).
b(4,0,"W").
b(12,0,4).
b(16,0,4).
b(22,0,2).
b(24,0,"W").
b(28,0,4).
b(34,0,4).
b(38,0,4).

Де b (0,0,3) - атом, який описує, що гравець 3 має поселення за координатами (0; 0).

Рішення ASP

Існує код ASP, в якому реалізовано багато необов'язкових балів:

% input : b(X,Y,V) with X,Y the coordinates of the V value

domain(X,Y):- b(X,Y,_).
player("1";"2";"3";"4").

% neighbors of X,Y
neighbors(X,Y,((X-2,Y);(X+2,Y);((X-1;X+1),(Y-1;Y+1)))) :- domain(X,Y).
neighbors(X,Y,I,J):- neighbors(X,Y,(I,J)) ; domain(I,J).

% Player is next to X,Y iff has a settlement next to.
next(X,Y,P):- neighbors(X,Y,I,J) ; b(I,J,P).


% SCORES

% Core score : 3 point for each Castle "C" with at least one settlement next to.
score(core,P,S*3):- S={next(X,Y,P): b(X,Y,"C")} ; player(P).

% opt1: 1 point per settled row
score(opt1,P,S*1):- S=#count{row(Y): b(_,Y,P)} ; player(P).

% opt2: 2 point per settlement on the most self-populated row
% first, defines how many settlements have a player on each row
rowcount(P,Y,H):- H=#count{col(X): b(X,Y,P)} ; domain(_,Y) ; player(P).
score(opt2,P,S*2):- S=#max{T: rowcount(P,Y,T)} ; player(P).

% opt3: 1 point for each settlements next to a Water "W".
score(opt3,P,S):- S={b(X,Y,P): next(X,Y,"W")} ; player(P).

% opt4: 1 point for each settlements next to a Mountain "M".
score(opt4,P,S):- S={b(X,Y,P): next(X,Y,"M")} ; player(P).

% opt5:
%later…

% opt6:
%later…

% opt7: 1 point for each connected component of settlement
% first we need each coord X,Y to be orderable.
% then is defined path/5, that is true iff exists a connected component of settlement of player P
%   that links X,Y to I,J
% then is defined the connected component atom that give the smaller coords in each connected component
% then computing the score.
order(X,Y,Y+X*100):- domain(X,Y).
path(P,X,Y,I,J):- order(X,Y,O1) ; order(I,J,O2) ; O1<O2 ; % order
                  neighbors(X,Y,I,J) ; b(X,Y,P) ; b(I,J,P) ; player(P). % path iff next to
path(P,X,Y,I,J):- order(X,Y,O1) ; order(I,J,O2) ; O1<O2 ; % order
                  path(P,X,Y,K,L) ; neighbors(K,L,I,J) ; % path if path to next to
                  b(I,J,P) ; player(P).
concomp(P,X,Y):- path(P,X,Y,_,_) ; not path(P,_,_,X,Y). % at least two settlements in the connected component
concomp(P,X,Y):- 0 { path(P,X,Y,_,_) ; path(P,_,_,X,Y) } 0 ; board(X,Y,P) ; player(P). % concomp of only one settlements
score(opt7,P,S):- S=#count{concomp(P,X,Y): concomp(P,X,Y)} ; player(P).

% opt8: 0.5 point for each settlement in the bigger connected component
%later…


% total score:
score(total,P,C+S1+S2+S3):- score(core,P,C) ; score(opt1,P,S1) ; score(opt3,P,S2) ; score(opt7,P,S3).

#show. # show nothing but the others show statements
#show total_score(P,S): score(total,P,S).
%#show score/3. % scores details

Цю програму можна запустити за допомогою команди:

clingo board.lp golf.lp 

І знайдемо лише одне рішення (це є доказом того, що існує лише один спосіб розподілу балів):

s(c,1,18) s(c,2,0) s(c,3,15) s(c,4,12) s(1,1,14) s(1,2,20) s(1,3,12) s(1,4,16) s(3,1,13) s(3,2,21) s(3,3,10) s(3,4,5) s(7,1,7) s(7,2,5) s(7,3,6) s(7,4,29) s(t,1,52) s(t,2,46) s(t,3,43) s(t,4,62)

Де s (7,3,6) говорить, що гравець 3 набирає 6 очок з необов'язковим балом 7, а s (t, 4,62) говорить, що гравець 4 набирає 62 бали (ядро + 1 + 3 + 7).

Легко розібратися, щоб мати вигадливий стіл!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.