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ì.
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"
)
type ruleset1 struct{}
var Ruleset1 ruleset1
func (ruleset1) Start(config PartitaConfig) PartitaState {
var Ruleset1 = Ruleset{
Start: func(config PartitaConfig) PartitaState {
state := PartitaState{
Players: make([]Player, len(config.Players)),
Time: 0,
Actions: []Action{},
}
ruoli := []Ruolo{}
// 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)...)
}
// ed i rimanenti sono contadini
for len(ruoli) < len(config.Players) { // riempi il resto dei ruoli con contadini
ruoli = append(ruoli, Contadino)
}
util.Shuffle(ruoli) // mischia la lista di ruoli
// mischio la lista di ruoli
util.Shuffle(ruoli)
for _, username := range config.Players {
var ruolo Ruolo
ruolo, ruoli = ruoli[0], ruoli[1:]
for i, username := range config.Players {
log.Printf(`Al giocatore %q è stato assegnato il ruolo di %q`, username, ruoli[i].Nome)
log.Printf(`Al giocatore %q è stato assegnato il ruolo di %q`, username, ruolo.Nome)
state.Players = append(state.Players, Player{
state.Players[i] = Player{
Username: username,
Ruolo: ruolo,
Ruolo: ruoli[i],
Vivo: true,
})
}
}
return state
},
Update: func(state PartitaState, responses []PlayerMessage) (PartitaState, []PlayerCommand) {
if state.Time == 0 {
ruoliAgenti := map[Ruolo]struct{}{
Fattucchiera: {},
Veggente: {},
}
playerCommands := []PlayerCommand{}
for _, player := range state.Players {
if _, present := ruoliAgenti[player.Ruolo]; present {
if cmd, noop := RuoloMainCmd(player, state); !noop {
playerCommands = append(playerCommands, cmd)
}
}
}
state.Time++
return state, playerCommands
} else {
}
return state, []PlayerCommand{}
},
}

Loading…
Cancel
Save