Segna una partita a Kingdom Builder


16

Voglio provare una nuova forma di codice golf qui. Simile ai bonus, non tutte le parti della sfida devono essere completate, ma ogni risposta deve implementare un sottoinsieme di determinate dimensioni (e c'è un nucleo che ogni risposta deve implementare). Quindi, oltre al golf, questa sfida implica anche la scelta di una serie di funzionalità che vanno bene insieme.

Le regole

Kingdom Builder è un gioco da tavolo, giocato su una griglia esagonale (a punta). La scheda è composta da quattro quadranti (randomizzati), ognuno dei quali ha 10x10 celle esadecimali (quindi una scheda completa sarà 20x20). Ai fini di questa sfida, ogni cella esadecimale contiene water ( W), mountain ( M) una città ( T), un castello ( C) o è vuota ( .). Quindi potrebbe apparire un quadrante

. . 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 seconda riga sarà sempre spostata a destra rispetto alla prima riga. I giocatori 1di 4possibile inserire fino a 40 insediamenti ciascuno su celle vuote (di seguito alcune regole che ignoreremo per questa sfida). Una possibile tavola alla fine del gioco è la seguente:

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

Etichettiamo i quadranti come

1 2
3 4

Il tuo compito sarà quello di segnare una tale tavola. C'è sempre un punteggio base che viene sempre usato e 8 punteggi opzionali, 3 dei quali sono scelti per ogni partita. Di seguito, descriverò tutti e 9 i punteggi e userò la configurazione sopra come esempio per quanti punti otterrebbe ciascun giocatore.

† Nel gioco attuale ci sono 10 punteggi, ma ne tralascerò due perché nessuno vuole giocarli a golf.

Il punteggio principale. Un giocatore ottiene 3 punti per ogni Cascia a cui ha un insediamento vicino. Punteggi di esempio: 18, 0, 15, 12.

I punteggi opzionali.

  1. Un giocatore ottiene 1 punto per ogni fila orizzontale su cui ha almeno un insediamento.

    Punteggi di esempio: 14, 20, 12, 16.

  2. Per ogni giocatore, trova la fila orizzontale su cui si trova la maggior parte dei suoi insediamenti (scegline uno in caso di pareggio). Un giocatore ottiene 2 punti per ogni insediamento su quella fila.

    Esempi di punteggi: 14 (riga 16), 8 (riga 4, 5 o 6), 28 (riga 11), 10 (riga 1).

  3. Un giocatore ottiene 1 punto per ogni insediamento che si trova accanto Wall'aster.

    Punteggi di esempio: 13, 21, 10, 5.

  4. Un giocatore ottiene 1 punto per ogni insediamento vicino a una Mmontagna.

    Punteggi di esempio: 4, 12, 8, 4.

  5. Conta gli insediamenti di ciascun giocatore in ciascun quadrante. Per quadrante, i giocatori con il maggior numero di insediamenti ottengono 12 punti ciascuno, i giocatori con il secondo maggior numero di insediamenti ottengono 6 punti ciascuno.

    Punteggi di esempio: 18 (6 + 0 + 6 + 6), 36 (12 + 12 + 0 + 12), 12 (0 + 0 + 12 + 0), 18 (12 + 6 + 0 + 0).

  6. Per ogni giocatore determinare il quadrante in cui hanno il minor numero di insediamenti. Un giocatore ottiene 3 punti per ogni insediamento in quel quadrante.

    Punteggi di esempio: 18 (quadrante 2), 0 (quadrante 3), 15 (quadrante 1 o 2), 27 (quadrante 3).

  7. Un giocatore ottiene 1 punto per ogni gruppo collegato di insediamenti.

    Punteggi di esempio: 7, 5, 6, 29.

  8. Un giocatore ottiene 1 punto per ogni 2 insediamenti nel più grande gruppo di insediamenti collegati del giocatore.

    Punteggi di esempio: 4, 10, 8, 2.

La sfida

Come nel gioco si sceglierà 3 dei punteggi opzionali, e segnare un dato bordo in base al punteggio di base e questi tre punteggi. Il tuo codice dovrebbe produrre un elenco di 4 punteggi. C'è una limitazione sulla scelta però: ho raggruppato i punteggi in 3 gruppi e devi implementare uno di ciascun gruppo:

  • Implementa uno di 1 e 2 .
  • Implementa uno di 3, 4, 5 e 6 .
  • Implementa uno di 7 e 8 .

È possibile scrivere un programma o una funzione, prendendo input tramite STDIN, argomento della riga di comando, prompt o parametro della funzione. È possibile restituire il risultato o stamparlo su STDOUT.

È possibile scegliere qualsiasi formato elenco / stringa 1D o 2D conveniente per l'input. Si può non utilizzare un grafico con informazioni complete adiacenza. Ecco qualche buona lettura su griglie esadecimali se hai bisogno di ispirazione.

L'output può anche trovarsi in qualsiasi elenco o formato stringa comodo e inequivocabile.

Questo è il golf del codice, quindi vince la risposta più breve (in byte).

Ulteriori ipotesi

Puoi presumere che ...

  • ... ogni giocatore ha almeno 1 insediamento e non ci sono più di 40 insediamenti per ogni giocatore.
  • ... ogni quadrante contiene una città e due castelli, oppure due città e un castello.
  • ... città e castelli sono abbastanza distanti, in modo tale che nessun insediamento possa essere adiacente a due di essi.

Casi test

Usando ancora la scheda sopra, ecco i punteggi individuali per tutte le possibili scelte dei meccanismi di punteggio:

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

C'è un tabellone in cui vince sempre un giocatore, indipendentemente dalla combinazione?
ThreeFx

@ThreeFx Poiché il limite inferiore del numero di insediamenti per giocatore è 1, è abbastanza semplice da configurare. ;) Ma con lo stesso numero di insediamenti per ogni giocatore, in realtà non lo so.
Martin Ender,

Risposte:


5

Python 2, 367 byte

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

Il programma utilizza i punteggi 1, 3, 7. L'input è un elenco di elenchi di caratteri che rappresentano ciascuna cella. Per testare facilmente la scheda di esempio, possiamo fare:

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]

Gestire la griglia esadecimale

Dato che siamo su una griglia esadecimale, dobbiamo trattare i vicini in modo leggermente diverso. Se utilizziamo una griglia 2D tradizionale come nostra rappresentazione, allora per (1, 1)abbiamo:

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

Ad un esame più attento, ci rendiamo conto che gli offset dipendono dalla parità della riga in cui ti trovi. L'esempio sopra è per le righe dispari, ma sulle righe pari sono gli offset

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

L'unica cosa che è cambiata è che la prima, seconda, quinta e sesta coppia hanno avuto la loro seconda coordinata decrementata di 1.

La funzione lambda Nprende una coppia di coordinate (row, col)e restituisce tutti i vicini della cella all'interno della griglia. La comprensione interiore genera gli offset di cui sopra estraendoli da una semplice codifica base 3, incrementando la seconda coordinata se la riga è dispari e aggiunge gli offset alla cella in questione per dare ai vicini. La comprensione esterna quindi filtra, lasciando solo i vicini che si trovano entro i limiti della griglia.

Ungolfed

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

Non può def Fessere una funzione separata piuttosto che una funzione interna? Non può kessere rimosso da def F:?
Justin,

@Quincunx Fè la funzione di riempimento inondazione e ha bisogno dell'accesso J, quindi è all'interno per risparmiare sul passaggio Jcome parametro (sperimenterò un po 'per vedere se riesco a aggirare la copia profonda). Hai ragione k, grazie :) (il nuovo codice sembra un po 'strano, a causa del fatto di fare affidamento sul corto circuito)
Sp3000,

2

Programmazione set di risposte, 629 byte

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 appartiene alla famiglia di linguaggi di programmazione logica, qui incarnata dal framework Potassco , in particolare Clingo (grounder Gringo + solver Clasp). A causa della limitazione del paradigma, non può prendere la scheda fornita direttamente come output, quindi è necessaria una preelaborazione dei dati (qui eseguita in Python). Questa preelaborazione non viene conteggiata nel punteggio totale in byte.

È il mio primo codice golf, e l'obiettivo è più quello di mostrare una lingua che amo che non avevo mai visto prima nel golf, che di vincere davvero il gioco. Inoltre, sono ben lungi dall'essere un esperto di ASP, quindi molte ottimizzazioni del codice possono certamente essere eseguite per risultati in meno byte.

rappresentazione della conoscenza

C'è il codice Python che converte la scheda in atomi:

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

Ad esempio, gli atomi b (per __b__oard) forniti per la prima riga della scheda di esempio sono i seguenti:

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

Dove b (0,0,3) è un atomo che descrive che il giocatore 3 ha un insediamento alle coordinate (0; 0).

Risoluzione ASP

C'è il codice ASP, con molti punteggi opzionali implementati:

% 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

Questo programma può essere avviato con il comando:

clingo board.lp golf.lp 

E troverà solo una soluzione (è una prova che esiste un solo modo per distribuire i punti):

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)

Dove s (7,3,6) dice che il giocatore 3 guadagna 6 punti con il punteggio opzionale 7, e s (t, 4,62) dice che il giocatore 4 guadagna 62 punti in totale (core + 1 + 3 + 7).

Facile da analizzare per avere un tavolo elegante!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.