Gagnez une partie de Kingdom Builder

16

Je veux essayer une nouvelle forme de code golf ici. Comme pour les bonus, toutes les parties du défi ne doivent pas être terminées, mais chaque réponse doit implémenter un sous-ensemble d'une certaine taille (et il y a un noyau que chaque réponse doit implémenter). Donc, en plus du golf, ce défi implique également de choisir un ensemble de fonctionnalités qui vont bien ensemble.

Les règles

Kingdom Builder est un jeu de plateau, joué sur une grille hexagonale (pointue). La carte est composée de quatre quadrants (randomisés), chacun ayant 10x10 cellules hex (donc une carte complète sera de 20x20). Aux fins de ce défi, chaque cellule hexadécimale contient de l'eau ( W), une montagne ( M) une ville ( T), un château ( C) ou est vide ( .). Donc, un quadrant pourrait ressembler

. . 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 .

La deuxième ligne sera toujours décalée vers la droite de la première ligne. Les joueurs 1de 4peuvent placer jusqu'à 40 colonies chacune sur les cellules vides (quelques règles que nous ignorerons pour relever ce défi). Un tableau possible à la fin du jeu est le suivant:

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 . .

Nous étiquetons les quadrants comme

1 2
3 4

Votre tâche sera de marquer un tel tableau. Il y a un score de base qui est toujours utilisé et 8 scores optionnels, dont 3 sont choisis pour chaque match. Dans ce qui suit, je décrirai les 9 scores et utiliserai la configuration ci-dessus comme exemple pour combien de points chaque joueur gagnerait.

† Il y a 10 scores dans le jeu réel, mais je vais en laisser deux parce que personne ne veut les jouer au golf.

Le score de base. Un joueur obtient 3 points pour chaque Castle à côté duquel il a un règlement. Exemples de scores: 18, 0, 15, 12.

Les scores optionnels.

  1. Un joueur obtient 1 point pour chaque ligne horizontale sur laquelle il a au moins un règlement.

    Exemples de scores: 14, 20, 12, 16.

  2. Pour chaque joueur, trouvez la ligne horizontale sur laquelle ils ont le plus de leurs colonies (choisissez-en en cas d'égalité). Un joueur obtient 2 points pour chaque règlement sur cette ligne.

    Exemples de scores: 14 (ligne 16), 8 (ligne 4, 5 ou 6), 28 (ligne 11), 10 (ligne 1).

  3. Un joueur obtient 1 point pour chaque colonie qui est construite à côté d' WAter.

    Exemples de scores: 13, 21, 10, 5.

  4. Un joueur obtient 1 point pour chaque règlement à côté d'une Mfontaine.

    Exemples de scores: 4, 12, 8, 4.

  5. Comptez les colonies de chaque joueur dans chaque quadrant. Par quadrant, les joueurs avec le plus grand nombre de colonies obtiennent 12 points chacun, les joueurs avec le deuxième plus grand nombre de colonies obtiennent 6 points chacun.

    Exemples de scores: 18 (6 + 0 + 6 + 6), 36 (12 + 12 + 0 + 12), 12 (0 + 0 + 12 + 0), 18 (12 + 6 + 0 + 0).

  6. Pour chaque joueur, déterminez le quadrant dans lequel ils ont le moins de colonies. Un joueur obtient 3 points pour chaque règlement dans ce quadrant.

    Exemples de scores: 18 (quadrant 2), 0 (quadrant 3), 15 (quadrant 1 ou 2), 27 (quadrant 3).

  7. Un joueur obtient 1 point pour chaque groupe de colonies connecté.

    Exemples de scores: 7, 5, 6, 29.

  8. Un joueur obtient 1 point pour 2 colonies dans le plus grand groupe de colonies connectées du joueur.

    Exemples de scores: 4, 10, 8, 2.

Le défi

Comme dans le jeu, vous choisirez 3 des scores optionnels et marquerez un tableau donné en fonction du score de base et de ces trois scores. Votre code devrait produire une liste de 4 scores. Il y a cependant une restriction au choix: j'ai groupé les scores en 3 groupes, et vous devez mettre en œuvre un de chaque groupe:

  • Implémentez l'un des 1 et 2 .
  • Mettez en œuvre l'un des 3, 4, 5 et 6 .
  • Implémentez l'un des 7 et 8 .

Vous pouvez écrire un programme ou une fonction, en prenant une entrée via STDIN, un argument de ligne de commande, une invite ou un paramètre de fonction. Vous pouvez retourner le résultat ou l'imprimer à STDOUT.

Vous pouvez choisir n'importe quel format de liste / chaîne 1D ou 2D pour l'entrée. Vous ne pouvez pas utiliser un graphique avec des informations d'adjacence complètes. Voici une bonne lecture sur les grilles hexadécimales si vous avez besoin d'inspiration.

Votre sortie peut également être dans n'importe quel format de liste ou de chaîne pratique et sans ambiguïté.

Il s'agit du code golf, donc la réponse la plus courte (en octets) l'emporte.

Autres hypothèses

Vous pouvez supposer que ...

  • ... chaque joueur a au moins 1 règlement et il n'y a pas plus de 40 règlements de chaque joueur.
  • ... chaque quadrant contient soit une ville et deux châteaux, soit deux villes et un château.
  • ... les villes et les châteaux sont suffisamment éloignés, de sorte qu'aucune colonie ne peut être adjacente à deux d'entre eux.

Cas de test

Toujours en utilisant le tableau ci-dessus, voici les scores individuels pour tous les choix possibles de mécanismes de notation:

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
Martin Ender
la source
Existe-t-il un plateau dans lequel un joueur gagne toujours, quelle que soit la combinaison?
ThreeFx
@ThreeFx Étant donné que la limite inférieure du nombre de colonies par joueur est 1, c'est assez simple à configurer. ;) Mais avec le même nombre de colonies pour chaque joueur, je ne sais pas vraiment.
Martin Ender

Réponses:

5

Python 2, 367 octets

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"]

Le programme utilise les scores 1, 3, 7. L'entrée est une liste de listes de caractères représentant chaque cellule. Pour tester l'exemple de carte facilement, nous pouvons faire:

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]

Gestion de la grille hexadécimale

Puisque nous sommes sur une grille hexagonale, nous devons traiter les voisins un peu différemment. Si nous utilisons une grille 2D traditionnelle comme représentation, alors (1, 1)nous avons:

. 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)

En y regardant de plus près, nous réalisons que les décalages dépendent de la parité de la ligne sur laquelle vous vous trouvez. L'exemple ci-dessus concerne les lignes impaires, mais sur les lignes paires, les décalages sont

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

La seule chose qui a changé, c'est que les 1ère, 2e, 5e et 6e paires ont vu leur deuxième coordonnée décrémentée de 1.

La fonction lambda Nprend une paire de coordonnées (row, col)et renvoie tous les voisins de la cellule dans la grille. La compréhension interne génère les décalages ci-dessus en les extrayant d'un simple codage en base 3, en incrémentant la deuxième coordonnée si la ligne est impaire et en ajoutant les décalages à la cellule en question pour donner aux voisins. La compréhension extérieure filtre ensuite, ne laissant que les voisins qui se trouvent dans les limites de la grille.

Non golfé

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
Sp3000
la source
Ne peut-il pas def Fs'agir d'une fonction distincte plutôt que d'une fonction interne? Vous ne pouvez pas kêtre supprimé de def F:?
Justin
@Quincunx Fest la fonction de remplissage d'inondation et doit être accessible J, il est donc à l'intérieur pour économiser sur le passage en Jtant que paramètre (je vais expérimenter un peu pour voir si je peux contourner la copie en profondeur). Vous avez raison k, merci :) (le nouveau code a l'air un peu génial cependant, en raison de la dépendance aux courts-circuits)
Sp3000
2

Programmation du jeu de réponses, 629 octets

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 appartient à la famille des langages de programmation logique, incarnée ici par le framework Potassco , en particulier Clingo (Grounder Gringo + Solver Clasp). En raison de la limitation du paradigme, il ne peut pas prendre directement une carte donnée en sortie, donc un prétraitement des données est nécessaire (ici effectué en python). Ce prétraitement n'est pas comptabilisé dans le score d'octets total.

C'est mon premier code de golf, et l'objectif est plus de montrer une langue que j'aime que je n'ai jamais vue auparavant dans le golf, que de vraiment gagner le match. De plus, je suis loin d'être un expert en ASP, donc de nombreuses optimisations du code peuvent certainement être effectuées pour des résultats en moins d'octets.

représentation des connaissances

Il y a le code python qui convertit la carte en atomes:

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'
        ]

Par exemple, les atomes b (pour __b__oard) donnés pour la première ligne de l'exemple de carte sont les suivants:

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).

Où b (0,0,3) est un atome qui décrit que le joueur 3 a un règlement aux coordonnées (0; 0).

Résolution ASP

Il y a le code ASP, avec de nombreux scores optionnels implémentés:

% 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

Ce programme peut être lancé avec la commande:

clingo board.lp golf.lp 

Et ne trouvera qu'une seule solution (c'est une preuve qu'il n'y a qu'une seule façon de répartir les points):

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)

Où s (7,3,6) dit que le joueur 3 gagne 6 points avec un score optionnel 7, et s (t, 4,62) dit que le joueur 4 gagne 62 points au total (core + 1 + 3 + 7).

Facile à analyser pour avoir une table de fantaisie!

aluriak
la source