Primi prototipi del funzionamento dei regolamenti e spiegazioni di cose nel README

main
Antonio De Lucreziis 2 years ago
parent 39c793583b
commit 5aad886817

@ -61,3 +61,99 @@ Ed infine c'è il modulo che si occupa del far avanzare le partite e della risol
Al massimo potrebbe contenere alcune informazioni su come serializzare lo stato delle partite però per ora tutte le strutture qui dentro sono annotate per essere serializzate a JSON ed anche quando passeremo ad SQLite forse converrà fare sempre così. Al massimo potrebbe contenere alcune informazioni su come serializzare lo stato delle partite però per ora tutte le strutture qui dentro sono annotate per essere serializzate a JSON ed anche quando passeremo ad SQLite forse converrà fare sempre così.
TODO: Al massimo potrebbe dipendere da `/model` ma forse non serve. TODO: Al massimo potrebbe dipendere da `/model` ma forse non serve.
## Architettura Lupus (1)
Lo stato della partita è contenuto in `lupus.PartitaState` e viene aggiornato da una struct di tipo `lupus.Ruleset`. Il lifecycle di una partita è
- `Ruleset.Start(PartitaConfig) PartitaState`
Questa funzione prende un config e genera il primo stato della partita.
- `Ruleset.Update(PartitaState, []PlayerMsg) (PartitaState, []PlayerCmd)`
Dopo la chiamata di `Ruleset.Start(...)` viene chiamata questa funzione senza nessun messaggio da processare in input.
Questa funzione viene chiamata quando arriva con tutti i risultati dei comandi inviati dalla funzione precedente (o almeno per quei comandi che ritornano un risultato).
**Esempio.**
Supponiamo ad esempio che sia la prima notte a questo punto, intanto aggiorniamo, gli unici ruoli che agiscono sono il _veggente_ e la _fattucchiera_ quindi come nuovo stato ritorniamo lo stesso stato solo con solo cambiato `state.Time = 1` e come lista di comandi ritorniamo i form per agire con le liste di tutti i giocatori (per ora sono tutti vivi quindi è la stessa lista).
```go
[]PlayerCmd{
ChoosePlayerCmd{
Target: "player-i", // veggente
Message: "Chi vuoi puntare?",
Options: allPlayers,
},
ChoosePlayerCmd{
Target: "player-j", // fattucchiera
Message: "Chi vuoi puntare?",
Options: allPlayers,
},
}
```
A questo punto il codice relativo alla partita va in pausa ed una volta che entrambi i giocatori hanno compilato i propri form la risposta sarà (ad esempio diciamo che sia la _fattucchiera_ che il _veggente_ hanno puntato `player-1` che è un _contadino_)
```go
[]PlayerMsg{
ChoosePlayerMsg{
Target: "player-i", // veggente
Answer: "player-1",
},
ChoosePlayerMsg{
Target: "player-j", // fattucchiera
Answer: "player-1",
},
}
```
a questo punto lo stato avrà `state.Time = 1` quindi sappiamo che è giorno e usiamo i messaggi precedenti per risolvere la partita facendo prima cambiare l'aura alla fattucchiera al giocatore puntato e poi facciamo agire il veggente quindi ci basterà ritornare qualcosa del tipo
```go
DisplayMsg{
Target: "player-i", // veggente
Message: "@player-1 è stato visto nero"
},
```
ora però è giorno ed i giocatori dovranno anche votare per il rogo quindi dobbiamo anche inviare una lista di messaggio di scelta per il voto come segue
```go
ChoosePlayerCmd{
Target: "player-1",
Message: "Chi vuoi bruciare al rogo?",
Options: allAlivePlayers,
},
ChoosePlayerCmd{
Target: "player-2",
Message: "Chi vuoi bruciare al rogo?",
Options: allAlivePlayers,
},
...
ChoosePlayerCmd{
Target: "player-n",
Message: "Chi vuoi bruciare al rogo?",
Options: allAlivePlayers,
},
```
inoltre se è stato incluso il kamikaze bisogna anche aggiungere un messaggio per far agire il kamikaze durante il giorno
```go
OptionalCmd{
ChoosePlayerCmd{
Target: "player-?", // kamikaze
Message: "Su chi vuoi farti esplodere?",
Options: allAlivePlayers,
}
}
```
e l'idea è che un comando di tipo `OptionalCmd` semplicemente wrappa un'altro `Cmd` e lo rende "opzionale" ovvero fa sì che non sia strettamente necessario per processare gli altri comandi.
## Architettura Lupus (2)
TODO: Boh forse si può fare proprio qualcosa ad "eventi".

@ -0,0 +1,80 @@
package lupus
var (
AuraBianca = "bianca"
AuraNera = "nera"
)
var (
FazioneBuoni = "buoni"
FazioneCattivi = "cattivi"
)
var (
Contadino = Ruolo{
Uid: "contadino",
Nome: "Contadino",
Fazione: FazioneBuoni,
Aura: AuraBianca,
}
Lupo = Ruolo{
Uid: "lupo",
Nome: "Lupo",
Fazione: FazioneCattivi,
Aura: AuraNera,
}
Fattucchiera = Ruolo{
Uid: "fattucchiera",
Nome: "Fattucchiera",
Fazione: FazioneCattivi,
Aura: AuraNera,
}
Indemoniato = Ruolo{
Uid: "indemoniato",
Nome: "Indemoniato",
Fazione: FazioneCattivi,
Aura: AuraBianca,
}
Kamikaze = Ruolo{
Uid: "kamikaze",
Nome: "Kamikaze",
Fazione: FazioneBuoni,
Aura: AuraBianca,
}
Guardia = Ruolo{
Uid: "guardia",
Nome: "Guardia",
Fazione: FazioneBuoni,
Aura: AuraBianca,
}
Cacciatore = Ruolo{
Uid: "cacciatore",
Nome: "Cacciatore",
Fazione: FazioneBuoni,
Aura: AuraBianca,
}
Medium = Ruolo{
Uid: "medium",
Nome: "Medium",
Fazione: FazioneBuoni,
Aura: AuraBianca,
}
Veggente = Ruolo{
Uid: "veggente",
Nome: "Veggente",
Fazione: FazioneBuoni,
Aura: AuraBianca,
}
)
// Ruoli è una lista di ruoli comuni, l'ordine è puramente casuale
var RuoliMap = map[string]Ruolo{
Contadino.Uid: Contadino,
Lupo.Uid: Lupo,
Fattucchiera.Uid: Fattucchiera,
Indemoniato.Uid: Indemoniato,
Guardia.Uid: Guardia,
Cacciatore.Uid: Cacciatore,
Medium.Uid: Medium,
Veggente.Uid: Veggente,
}

@ -1,132 +0,0 @@
package lupus
// Ruleset si occupa di far avanzare la partita, per ora un'implementazione di questa interfaccia è meglio se non ha stato (TODO: Forse sarebbe meglio come struct di funzioni)
type Ruleset interface {
// Start viene chiamata all'inizio di una partita per inizializzare una partita partendo da alcune info di configurazione
Start(PartitaConfig) PartitaState
// Update prende lo stato della partita e ne ritorna uno nuovo eventualmente utilizzando delle "UserResponse" fatte precedentemente all'utente (alla prima chiamata questa lista è vuota), successivamente c'è una corrispondenza tra lo slice di "[]UserRequest" ritornato ed il successivo slice "[]UserResponse" ricevuto.
Update(state PartitaState, responses []UserResponse) (PartitaState, []UserRequest)
}
// PartitaConfig è un config minimale per creare il primo stato della partita
type PartitaConfig struct {
Players []string
RoleCounts map[Ruolo]int
}
// PartitaState rappresenta lo stato della partita
type PartitaState struct {
// Players è una mappa da username a giocatore
Players []Player `json:"players"`
// Time indica la fase corrente del gioco (la parità indica notte/giorno e si inizia da "Notte 0")
Time uint `json:"time"`
// PhaseActions indica quali azioni sono state fatte in una certa fase
Actions []Action `json:"actions"`
// Won inizialmente è nil e diventa &"FazioneBuoni" o &"FazioneCattivi" quando una delle due fazioni viene dichiarata vincitrice
Won *string
}
type Player struct {
Username string `json:"username"`
Ruolo Ruolo `json:"ruolo"`
Vivo bool `json:"vivo"`
}
type Ruolo struct {
Uid string `json:"uid"`
Nome string `json:"nome"`
Fazione string `json:"fazione"`
Aura string `json:"aura"`
}
type Action struct {
Uid string `json:"uid"`
Time uint `json:"time"` // Time indica in quale fase è stata compiuta l'azione
Player string `json:"player"`
TargetPlayer string `json:"targetPlayer"`
}
var (
AuraBianca = "bianca"
AuraNera = "nera"
)
var (
FazioneBuoni = "buoni"
FazioneCattivi = "cattivi"
)
var (
Contadino = Ruolo{
Uid: "contadino",
Nome: "Contadino",
Fazione: FazioneBuoni,
Aura: AuraBianca,
}
Lupo = Ruolo{
Uid: "lupo",
Nome: "Lupo",
Fazione: FazioneCattivi,
Aura: AuraNera,
}
Fattucchiera = Ruolo{
Uid: "fattucchiera",
Nome: "Fattucchiera",
Fazione: FazioneCattivi,
Aura: AuraNera,
}
Indemoniato = Ruolo{
Uid: "indemoniato",
Nome: "Indemoniato",
Fazione: FazioneCattivi,
Aura: AuraBianca,
}
Guardia = Ruolo{
Uid: "guardia",
Nome: "Guardia",
Fazione: FazioneBuoni,
Aura: AuraBianca,
}
Cacciatore = Ruolo{
Uid: "cacciatore",
Nome: "Cacciatore",
Fazione: FazioneBuoni,
Aura: AuraBianca,
}
Medium = Ruolo{
Uid: "medium",
Nome: "Medium",
Fazione: FazioneBuoni,
Aura: AuraBianca,
}
Veggente = Ruolo{
Uid: "veggente",
Nome: "Veggente",
Fazione: FazioneBuoni,
Aura: AuraBianca,
}
)
// Ruoli è una lista di ruoli comuni, l'ordine è puramente casuale
var RuoliMap = map[string]Ruolo{
Contadino.Uid: Contadino,
Lupo.Uid: Lupo,
Fattucchiera.Uid: Fattucchiera,
Indemoniato.Uid: Indemoniato,
Guardia.Uid: Guardia,
Cacciatore.Uid: Cacciatore,
Medium.Uid: Medium,
Veggente.Uid: Veggente,
}
type UserRequest struct {
TargetPlayer string
Request any // TODO: Work in progress
}
type UserResponse struct {
TargetPlayer string
Response any // TODO: Work in progress
}

@ -0,0 +1,85 @@
package lupus
// PartitaConfig è un config minimale per creare il primo stato della partita
type PartitaConfig struct {
Players []string
RoleCounts map[Ruolo]int
}
// PartitaState rappresenta lo stato della partita
type PartitaState struct {
// Players è una mappa da username a giocatore
Players []Player `json:"players"`
// Time indica la fase corrente del gioco (la parità indica notte/giorno e si inizia da "Notte 0")
Time uint `json:"time"`
// PhaseActions indica quali azioni sono state fatte in una certa fase
Actions []Action `json:"actions"`
// Won inizialmente è nil e diventa &"FazioneBuoni" o &"FazioneCattivi" quando una delle due fazioni viene dichiarata vincitrice
Won *string
}
func (p PartitaState) IsNotte() bool { return p.Time%2 == 0 }
func (p PartitaState) IsGiorno() bool { return p.Time%2 == 1 }
func (p PartitaState) NumeroGiorno() uint { return (p.Time + 1) / 2 }
type Player struct {
Username string `json:"username"`
Ruolo Ruolo `json:"ruolo"`
Vivo bool `json:"vivo"`
}
type Action struct {
Uid string `json:"uid"`
Time uint `json:"time"` // Time indica in quale fase è stata compiuta l'azione
Player string `json:"player"`
TargetPlayer string `json:"targetPlayer"`
}
type Ruolo struct {
Uid string `json:"uid"`
Nome string `json:"nome"`
Fazione string `json:"fazione"`
Aura string `json:"aura"`
}
func RuoloMainCmd(player Player, state PartitaState) (cmd PlayerCommand, noop bool) {
switch player.Ruolo {
case Veggente, Lupo, Cacciatore, Guardia, Kamikaze:
alivePlayers := []string{}
for _, p := range state.Players {
if p.Vivo {
alivePlayers = append(alivePlayers, p.Username)
}
}
return PlayerCommand{
TargetPlayer: player.Username,
Request: ChoosePlayerCmd{alivePlayers},
}, false
case Medium:
deadPlayers := []string{}
for _, p := range state.Players {
if !p.Vivo {
deadPlayers = append(deadPlayers, p.Username)
}
}
return PlayerCommand{
TargetPlayer: player.Username,
Request: ChoosePlayerCmd{deadPlayers},
}, false
case Fattucchiera:
allPlayers := []string{}
for _, p := range state.Players {
allPlayers = append(allPlayers, p.Username)
}
return PlayerCommand{
TargetPlayer: player.Username,
Request: ChoosePlayerCmd{allPlayers},
}, false
default:
return PlayerCommand{}, true
}
}

@ -0,0 +1,43 @@
package lupus
// Ruleset si occupa di far avanzare la partita
type Ruleset struct {
// Start viene chiamata all'inizio di una partita per inizializzare una partita partendo da alcune info di configurazione
Start func(PartitaConfig) PartitaState
// Update prende lo stato della partita e ne ritorna uno nuovo eventualmente utilizzando delle "UserResponse" fatte precedentemente all'utente (alla prima chiamata questa lista è vuota), successivamente c'è una corrispondenza tra lo slice di "[]UserRequest" ritornato ed il successivo slice "[]UserResponse" ricevuto.
Update func(state PartitaState, responses []PlayerMessage) (PartitaState, []PlayerCommand)
}
type Cmd interface{ Cmd() }
type Msg interface{ Msg() }
// PlayerCommand rappresenta un "comando" per un certo giocatore, nella fattispecie può dire di mostrare al giocatore un messaggio o anche un prompt che chiede qualcosa all'utente
type PlayerCommand struct {
TargetPlayer string
Request Cmd // TODO: Work in progress
}
// PlayerMessage è una risposta da parte del giocatore ad un comando
type PlayerMessage struct {
TargetPlayer string
Response Msg // TODO: Work in progress
}
//
// Cmd & Msg
//
type ChoosePlayerCmd struct {
Players []string
}
func (ChoosePlayerCmd) Cmd() {}
type ChoosePlayerMsg struct {
Player string
}
func (ChoosePlayerMsg) Msg() {}

@ -6,40 +6,63 @@ import (
"github.com/aziis98/lupus-lite/util" "github.com/aziis98/lupus-lite/util"
) )
type ruleset1 struct{} var Ruleset1 = Ruleset{
Start: func(config PartitaConfig) PartitaState {
state := PartitaState{
Players: make([]Player, len(config.Players)),
Time: 0,
Actions: []Action{},
}
var Ruleset1 ruleset1 // Creo uno slice con il numero di ruoli in base al numero corrispondente per ruolo in "config.RoleCounts"
ruoli := make([]Ruolo, 0, len(config.Players))
for ruolo, count := range config.RoleCounts {
ruoli = append(ruoli, util.RepeatedSlice(ruolo, count)...)
}
func (ruleset1) Start(config PartitaConfig) PartitaState { // ed i rimanenti sono contadini
state := PartitaState{ for len(ruoli) < len(config.Players) { // riempi il resto dei ruoli con contadini
Players: make([]Player, len(config.Players)), ruoli = append(ruoli, Contadino)
Time: 0, }
Actions: []Action{},
}
ruoli := []Ruolo{} // mischio la lista di ruoli
for ruolo, count := range config.RoleCounts { util.Shuffle(ruoli)
ruoli = append(ruoli, util.RepeatedSlice(ruolo, count)...)
}
for len(ruoli) < len(config.Players) { // riempi il resto dei ruoli con contadini for i, username := range config.Players {
ruoli = append(ruoli, Contadino) log.Printf(`Al giocatore %q è stato assegnato il ruolo di %q`, username, ruoli[i].Nome)
}
util.Shuffle(ruoli) // mischia la lista di ruoli state.Players[i] = Player{
Username: username,
Ruolo: ruoli[i],
Vivo: true,
}
}
for _, username := range config.Players { return state
var ruolo Ruolo },
ruolo, ruoli = ruoli[0], ruoli[1:] Update: func(state PartitaState, responses []PlayerMessage) (PartitaState, []PlayerCommand) {
if state.Time == 0 {
ruoliAgenti := map[Ruolo]struct{}{
Fattucchiera: {},
Veggente: {},
}
log.Printf(`Al giocatore %q è stato assegnato il ruolo di %q`, username, ruolo.Nome) playerCommands := []PlayerCommand{}
state.Players = append(state.Players, Player{ for _, player := range state.Players {
Username: username, if _, present := ruoliAgenti[player.Ruolo]; present {
Ruolo: ruolo, if cmd, noop := RuoloMainCmd(player, state); !noop {
Vivo: true, playerCommands = append(playerCommands, cmd)
}) }
} }
}
return state state.Time++
return state, playerCommands
} else {
}
return state, []PlayerCommand{}
},
} }

Loading…
Cancel
Save