Definire la logica senza istruzioni if-else annidate

4

Sto lavorando a un gioco un po 'semplice. Attualmente sto cercando di implementare la logica di gioco per spostare i pezzi intorno.

La logica è qualcosa del genere:

does player have pieces in inventory?
    if yes:
        did they try to move it to an empty location
            if yes: ...
            if no: ...
    if no:
        ...

Come puoi vedere si tratta di un gruppo di istruzioni if if nidificate. La mia domanda è: come si può rifattorizzare il codice (o magari anche avvicinarsi al progetto) per evitare grandi blocchi annidati di istruzioni if-else.

Il mio gioco non è scacchi, ma se prendi gli scacchi per esempio, come dovresti avvicinarti al design per evitare condizionali in cui la logica di gioco è piuttosto complessa.

Dalla ricerca molto breve che ho fatto polimorfismo sembra essere uno (1)

(1) link

    
posta francium 21.03.2016 - 17:36
fonte

4 risposte

4

Un'alternativa all'inclusione di una logica decisionale complessa direttamente nel codice è creare una struttura dati personalizzata che rappresenti la complessa serie / raccolta di decisioni e / o stati che potresti altrimenti collegare al tuo codice.

Questa tecnica è talvolta nota come Programmazione basata su regole , che ha qualche associazione con forme deboli di IA, dove le strutture che incapsulano la tua logica / le decisioni sono conosciute come "Regole" .

La logica per fare questo è che le strutture di dati sono dichiarative piuttosto che procedurali; a volte è molto più facile (e più espressivo / idiomatico) rappresentare decisioni complesse in una struttura di dati dichiarativa piuttosto che rappresentare quelle stesse decisioni nel codice procedurale.

Usando alcune delle regole di un scacchi secondo la tua domanda; considera una struttura basata su XML per i diversi tipi di schemi di movimento consentiti per pezzi diversi:

<ChessPieceMoves>

  <Piece type="Rook">
    <Moves capture="true" move="true">
      <CanMove pattern="Horizontal" />
      <CanMove pattern="Vertical" />
    </Moves>
  </Piece>

  <Piece type="King">
    <Moves capture="true" move="true">
      <CanMove pattern="Horizontal" rangeLimit="1" />
      <CanMove pattern="Vertical" rangeLimit="1"  />
      <CanMove pattern="ForwardDiagonal" rangeLimit="1" />
      <CanMove pattern="BackwardDiagonal" rangeLimit="1" />
    </Moves>
    <Moves capture="false" move="true">
      <CanMove pattern="Castle" firstMoveOnly="true" />
    </Moves>
  </Piece>

  <Piece type="Pawn">
    <Moves capture="true" move="false">
      <CanMove pattern="ForwardDiagonal" rangeLimit="1" />
    </Moves>

    <Moves capture="false" move="true">
      <CanMove pattern="Forward" firstMoveOnly="true" rangeLimit="2" />
      <CanMove pattern="Forward" rangeLimit="1" />
    </Moves>
  </Piece>

</ChessPieceMoves>

(Nota: quanto sopra può o non può essere una buona struttura per le mosse di scacchi, ho passato meno di 5 minuti a pensarci su).

Ovviamente, devi scrivere il codice che associa quelle "regole" nel tuo codice (cioè il codice che legge l'XML e rappresenta il codice procedurale per ogni comportamento / decisione differente in quei dati ); e questo non è necessariamente un compito banale (anche se qualcosa come XmlSerializer di C # lo rende molto più semplice). Il codice di associazione dovrebbe essere significativamente meno complesso della logica per incorporare quelle decisioni / regole in codice procedurale.

Inoltre, esistono linguaggi di dichiarazioni dichiarative esistenti e vari tipi di motori di regole già esistenti; quindi potresti scegliere di imparare un linguaggio logico come Prolog .

Se la logica del codice finisce per essere più complessa, allora la struttura dei dati è probabilmente cattiva e richiede un ripensamento; è probabile che avrebbe bisogno di più informazioni (elementi / attributi) per coprire tutte le diverse mosse possibili negli scacchi, e probabilmente avrebbe bisogno anche di una ristrutturazione.

    
risposta data 21.03.2016 - 19:42
fonte
2

Tenderei ad avvicinarlo a qualcosa del tipo:

val legalMoves = generateAllMovesWithin8()
  .filter(inBounds)
  .filterNot(passesThroughFriendlyPiece)
  .filterNot(passesEnemyPieceAfterTaking)
  .filterNot(putsOwnKingInCheck)

Se scegli attentamente le tue strutture dati, per lo più solo la prima riga dovrebbe essere specifica per il pezzo, con un paio di piccole eccezioni per i cavalieri e l'arrocco. Il trucco è separare le preoccupazioni in modo che ogni funzione sia estremamente semplice.

    
risposta data 21.03.2016 - 22:20
fonte
1

Io sostengo la via di mezzo. Non c'è niente di sbagliato in affermazioni, ma la nidificazione profonda è qualcosa che si vuole evitare. Come regola generale, se si arriva al terzo livello di nidificazione, è consigliabile ristrutturare il codice. Il primo e più basilare approccio è creare nuovi metodi. Quindi nel tuo esempio avresti il livello più alto se e poi chiamerai i metodi che gestiscono il secondo livello. Questo è un approccio perfetto, ma potresti incontrare nuovi problemi. Il problema principale è se si dispone di una serie di calcoli che è necessario utilizzare in tutti gli strati di nidificazione. Resistere alla tentazione di spostarli in un ambito superiore (come l'oggetto) semplicemente per evitare di passare i parametri. In questo modo giace la follia.

Il secondo approccio è usare il polimorfismo. Un buon esempio crea una classe per ogni pezzo degli scacchi. Ognuno di loro implementerebbe un metodo (o metodi) che determina le proprie mosse legali. Eviti di avere una dichiarazione se dice qualcosa come se la regina ..., se il re ... se pedina ... Andando ancora più in profondità, prendi una mano sul modello di strategia. È uno dei migliori esempi di progettazione OO efficace e molto rilevante per i giochi.

    
risposta data 21.03.2016 - 22:45
fonte
0

potresti utilizzare OOP per risolvere questo problema.

var piece = GetPiece(..);
if (piece != null)
{
   board.Move(piece,location);
}
else
{
   //do something
}

Ora la prima riga, piece != null gestisce il

does player have pieces in inventory?

Il board.Move(piece,location) avrebbe la logica per vedere

did they try to move it to an empty location.

Ovviamente il pezzo ha la sua logica come il modello di movimento.

    
risposta data 22.03.2016 - 02:32
fonte

Leggi altre domande sui tag