Flutter: kako sestaviti kviz igro

UPDATE (01.01.2019): alternativno različico lahko najdete s pomočjo paketa za obnovitev tukaj.

Uvod

V tem članku bi vam rad pokazal, kako sem sestavil ta primer trivia igre s Flutterjem in paketom frideos (oglejte si ta dva primera, če želite izvedeti, kako deluje example1, example2). Je precej preprosta igra, vendar zajema različne zanimive argumente.

Aplikacija ima štiri zaslone:

  • Glavna stran, kjer uporabnik izbere kategorijo in začne igro.
  • Stran z nastavitvami, kjer lahko uporabnik izbere število vprašanj, vrsto baze podatkov (lokalno ali oddaljeno), rok za vsako vprašanje in težave.
  • Spletna stran, na kateri so prikazana vprašanja, rezultat, število popravkov, napak in odgovorov.
  • Stran s povzetkom, ki prikazuje vsa vprašanja s pravilnimi / napačnimi odgovori.

To je končni rezultat:

Tu lahko vidite boljši gif.
  • 1. del: Nastavitev projekta
  • 2. del: Arhitektura aplikacij
  • Del 3: API in JSON
  • 4. del: Domača stran in drugi zasloni
  • 5. del: TriviaBloc
  • Del 6: Animacije
  • Del 7: Stran s povzetkom
  • Zaključek
1. del - Nastavitev projekta

1 - Ustvarite nov projekt flutter:

flutter ustvarite svoje ime_projekta

2 - Uredite datoteko "pubspec.yaml" in dodajte pakete http in frideos:

odvisnosti:
  trepetanje:
    sdk: flutter
  http: ^ 0,12.0
  frideos: ^ 0.6.0

3- Izbrišite vsebino datoteke main.dart

4- Ustvarite strukturo projekta kot naslednjo sliko:

Podrobnosti strukture

  • API: tukaj bodo dart datoteke za obdelavo API-ja »Odprta baza podatkov» in API-ja za lokalno testiranje: api_interface.dart, mock_api.dart, trivia_api.dart.
  • Blocks: kraj edinega BLoC-ja aplikacije trivia_bloc.dart.
  • Modeli: appstate.dart, kategorija.dart, models.dart, question.dart, theme.dart, trivia_stats.dart.
  • Zasloni: main_page.dart, settings_page.dart, sum_page.dart, trivia_page.dart.
2. del - Arhitektura aplikacij

V svojem zadnjem članku sem pisal o različnih načinih pošiljanja in skupne rabe podatkov na več gradnikov in strani. V tem primeru bomo uporabili nekoliko naprednejši pristop: primerek enotonskega razreda z imenom appState bo drevesu pripomočkov zagotovljen z uporabo ponudnika InheritedWidget (AppStateProvider), to bo imelo stanje aplikacije, nekaj poslov logika in primer edinega BLoC-ja, ki obravnava "kvizni del" aplikacije. Torej bo na koncu to nekakšna mešanica med singleton in BLoC vzorcem.

Znotraj vsakega pripomočka je mogoče dobiti primerek razreda AppState s klicem:

končni appState = AppStateProvider.of  (kontekst);

1 - glavni.dart

To je vstopna točka aplikacije. Razred Appis widget brez stanja, kjer je razglašen primerek razreda AppState in kjer se z uporabo AppStateProvider ta poda v drevo gradnikov. Primerek appState bo odstranjen tako, da zapre vse tokove v metodi odstranjevanja razreda AppStateProvider.

Pripomoček MaterialApp je zavit v pripomoček ValueBuilder, tako da se vsakič, ko je izbrana nova tema, celotno drevo gradnikov obnovi in ​​temo posodobi.

2 - Državno upravljanje

Kot že rečeno, primerek appState drži stanje aplikacije. Ta razred se bo uporabljal za:

  • Nastavitve: trenutno uporabljena tema, jo naložite / shranite s skupnimi nastavitvami. Izvedba API-ja, posmeh ali oddaljeno (z uporabo API-ja od opentdb.com). Čas, določen za vsako vprašanje.
  • Prikaz trenutnega zavihka: glavna stran, malenkosti, povzetek.
  • Nalaganje vprašanj.
  • (če je na oddaljenem API-ju) Shranite nastavitve kategorije, številke in težavnosti vprašanj.

V konstruktorju razreda:

  • _createThemes gradi teme aplikacije.
  • _loadKategorije naloži kategorije vprašanj, ki jih izberete na spustnem meniju glavne strani.
  • odštevanje je StreamedTransformed iz frideos paketa vrste , ki se uporablja za pridobivanje iz besedilnega polja vrednosti za nastavitev odštevanja.
  • questionsAmount vsebuje število vprašanj, ki bodo prikazana med igro trivia (privzeto 5).
  • Primerek razredaTriviaBloc se inicializira, se vanj pretakajo ročaji odštevanja, seznam vprašanj in stran za prikaz.
Del 3 - API in JSON

Da uporabnik lahko izbira med lokalno in oddaljeno bazo podatkov, sem ustvaril vmesnik QuestionApi z dvema metodama in dvema razredoma, ki ga izvajata: MockApi in TriviaApi.

abstraktni razred VprašanjaAPI {
  Prihodnost  getCategorije (StreamedList  kategorije);
  
  Prihodnji  getQuestions (
    {StreamedList  vprašanja,
     int število,
     Kategorija kategorije,
     Vprašanje Težavnost,
     Vrsta vprašanja});
}

Izvedba MockApi je privzeto nastavljena (lahko jo spremenite na strani z nastavitvami aplikacije) v appState:

// API
VprašanjaAPI api = MockAPI ();
končni apiType = StreamedValue  (začetni podatki: ApiType.mock);

Čeprav je apiTypeis le enum za obravnavo sprememb baze podatkov na strani z nastavitvami:

enum ApiType {mock, remote}

mock_api.dart:

trivia_api.dart:

1 - izbira API-ja

Na strani z nastavitvami lahko uporabnik izbere, katero bazo podatkov uporabiti s spustnega menija:

ValueBuilder  (
  pretočeno: appState.apiType,
  builder: (kontekst, posnetek) {
    vrni DropdownButton  (
      vrednost: snapshot.data,
      onChanged: appState.setApiType,
      izdelki: [
        const DropdownMenuItem  (
          vrednost: ApiType.mock,
          otrok: Besedilo („Demo“),
        ),
        const DropdownMenuItem  (
          vrednost: ApiType.remote,
          otrok: Besedilo („opentdb.com“),
       ),
    ]);
}),

Vsakič, ko je izbrana nova baza podatkov, bo metoda setApiType spremenila izvajanje API-ja in kategorije bodo posodobljene.

void setApiType (vrsta ApiType) {
  če (apiType.value! = vrsta) {
    apiType.value = vrsta;
    če (vnesite == ApiType.mock) {
      api = MockAPI ();
    } else {
      api = TriviaAPI ();
    }
    _loadKategorije ();
  }
}

2 - kategorije

Če želite dobiti seznam kategorij, ki jim rečemo ta URL:

https://opentdb.com/api_category.php

Izvleček odgovora:

{"trivia_categories": [{"id": 9, "ime": "Splošno znanje"}, {"id": 10, "ime": "Zabava: Knjige"}]

Po dekodiranju JSON-a s funkcijo jsonDecode kod dart: pretvorite knjižnico:

končni jsonResponse = convert.jsonDecode (response.body);

imamo to strukturo:

  • jsonResponse ['trivia_categories']: seznam kategorij
  • jsonResponse ['trivia_categories'] [INDEX] ['id']: id kategorije
  • jsonResponse ['trivia_categories'] [INDEX] ['name']: ime kategorije

Model bo torej:

kategorija razreda {
  Kategorija ({this.id, this.name});
  tovarna Kategorija.fromJson (Zemljevid  json) {
    vrne kategorija (id: json ['id'], ime: json ['ime']);
  }
  int id;
  Ime niza;
}

3 - Vprašanja

Če pokličemo ta URL:

https://opentdb.com/api.php?amount=2&difficffic=medium&type=multiple

to bo odgovor:

{"response_code": 0, "results": [{"kategorija": "Zabava: Glasba", "vrsta": "več", "težavnost": "srednje", "vprašanje": "Kaj francoski izvajalec \ / band je znan po igranju na instrument midi & quot; Launchpad & quot ;? Šport "," vrsta ":" več "," težavnost ":" srednje "," vprašanje ":" Kdo je zmagal na državnem prvenstvu v nogometnem plezalstvu (2015)? " nepravilno_answers ": [" Alabama Crimson Tide "," Clemson Tigers "," Wisconsin Baders "]}]}

V tem primeru imamo za dekodiranje JSON to strukturo:

  • jsonResponse ['rezultati']: seznam vprašanj.
  • jsonResponse ['rezultati'] [INDEX] ['kategorija']: kategorija vprašanja.
  • jsonResponse ['rezultati'] [INDEX] ['type']: vrsta vprašanja, večkratna ali logična.
  • jsonResponse ['rezultati'] [INDEX] ['vprašanje']: vprašanje.
  • jsonResponse ['rezultati'] [INDEX] ['true_answer']: pravilen odgovor.
  • jsonResponse ['rezultati'] [INDEX] ['napačen_answer']: seznam napačnih odgovorov.

Model:

razred VprašanjeModel {
  QuestionModel ({this.question, this.correctAnswer, this.incorrectAnswers});
  tovarna QuestionModel.fromJson (Zemljevid  json) {
    vrne VprašanjeModel (
      vprašanje: json ['vprašanje'],
      pravilnoAnswer: json ['correct_answer'],
      nepravilnoAnswers: (json ['napačno_answer'] kot Seznam)
        .map ((odgovor) => answer.toString ())
        .našteti());
  }
  String vprašanje;
  String pravilnoAnswer;
  Seznam  napačenAnswers;
}

4 - razred TriviaApi

Razred izvaja dve metodi vmesnika QuestionsApi, getCategorije in getQuestions:

  • Pridobivanje kategorij

V prvem delu je JSON dekodiran nato z uporabo modela, razčlenjen je, tako da dobi seznam kategorij tipa, na koncu pa se dodeli kategorijam (StreamedList of type type, ki se uporablja za popuščanje seznama kategorij na glavni strani ).

končni jsonResponse = convert.jsonDecode (response.body);
končni rezultat = (jsonResponse ['trivia_categories'] kot Seznam)
.map ((kategorija) => Kategorija.odJson (kategorija));
kategorije.value = [];
kategorije
..addAll (rezultat)
..addElement (Kategorija (id: 0, ime: „Katera koli kategorija“));
  • Pridobivanje vprašanj

Nekaj ​​podobnega se dogaja pri vprašanjih, toda v tem primeru uporabljamo model (Vprašanje), s katerim lahko prvotno strukturo (QuestionModel) JSON-a pretvorimo v bolj priročno strukturo, ki jo bomo uporabili v aplikaciji.

končni jsonResponse = convert.jsonDecode (response.body);
končni rezultat = (jsonResponse ['rezultati'] kot seznam)
.map ((vprašanje) => QuestionModel.fromJson (vprašanje));
questions.value = rezultat
.map ((vprašanje) => Question.fromQuestionModel (vprašanje))
.našteti();

5 - Vprašalni razred

Kot rečeno v prejšnjem odstavku, aplikacija za vprašanja uporablja drugačno strukturo. V tem razredu imamo štiri lastnosti in dva načina:

Vprašanje o razredu {
  Vprašanje ({this.question, this.answers, this.correctAnswerIndex});
  tovarna Question.fromQuestionModel (model QuestionModel) {
    končni seznam  odgovori = []
      ..add (model.correctAnswer)
      ..addAll (model.incorrectAnswers)
      ..shuffle ();
    končni indeks = odgovori.indexOf (model.correctAnswer);
    povratno vprašanje (vprašanje: model.praševanje, odgovori: odgovori, pravilnoAnswerIndex: kazalo);
  }
  String vprašanje;
  Seznam  odgovorov;
  int pravilnoAnswerIndex;
  int izbranAnswerIndex;
  bool isCorrect (strunski odgovor) {
    vrne odgovore.indexOf (odgovor) == pravilnoAnswerIndex;
  }
  bool isChosen (strunski odgovor) {
    vrne odgovore.indexOf (odgovor) == izbranAnswerIndex;
  }
}

V tovarni je seznam odgovorov najprej napolnjen z vsemi odgovori in nato premešan, tako da je vrstni red vedno drugačen. Tu dobimo celo indeks pravilnega odgovora, tako da ga lahko prek konstruktorja QuestionA dodelimo točniAnswerIndex. Obe metodi se uporabljata za določitev, ali je odgovor, ki je bil poslan kot parameter, pravilen ali izbran (bolje bo razložen v enem od naslednjih odstavkov).

4. del - Domača stran in drugi zasloni

1 - gradnik HomePage

V theAppState lahko vidite lastnost z imenom tabControllerthat je StreamedValue tipa AppTab (enum), ki se uporablja za pretakanje strani za prikaz v pripomočku HomePage (stanje brez državljanstva). Deluje na ta način: vsakič, ko drugačen set AppTabis, pripomoček ValueBuilder obnovi zaslon, ki prikazuje novo stran.

  • Razred HomePage:
Gradnja gradnikov (kontekst BuildContext) {
  končni appState = AppStateProvider.of  (kontekst);
  
  vrniti ValueBuilder (
    pretočeno: appState.tabController,
    builder: (kontekst, posnetek) => Odri (
      appBar: snapshot.data! = AppTab.main? null: AppBar (),
      predal: DrawerWidget (),
      telo: _switchTab (snapshot.data, appState),
      ),
  );
}

N.B. V tem primeru bo appBar prikazan samo na glavni strani.

  • _switchTab metoda:
Gradnik _switchTab (zavihek AppTab, aplikacija AppState) {
  stikalo (zavihek) {
    Primer AppTab.main:
      vrne MainPage ();
      odmor;
    Primer AppTab.trivia:
      vrni TriviaPage ();
      odmor;
    Primer AppTab.summary:
      vrne Povzetek strani (statistika: appState.triviaBloc.stats);
      odmor;
    privzeto:
    vrne MainPage ();
  }
}

2 - Stran z nastavitvami

Na strani z nastavitvami lahko izberete število vprašanj, ki jih želite prikazati, težavnost, čas odštevanja in vrsto baze podatkov. Na glavni strani lahko nato izberete kategorijo in na koncu začnete igro. Za vsako od teh nastavitev uporabim StreamedValue, tako da lahko pripomoček ValueBuilder osveži stran vsakič, ko je nastavljena nova vrednost.

5. del - TriviaBloc

Poslovna logika aplikacije je v edinem BLoC-ju z imenom TriviaBloc. Preučimo ta razred.

V konstruktorju imamo:

TriviaBloc ({this.countdownStream, this.questions, this.tabController}) {
// Pridobivanje vprašanj iz API-ja
  questions.on Change ((podatki) {
    če (data.isNotEmpty) {
      končna vprašanja = podatki..shuffle ();
     _startTrivia (vprašanja);
    }
  });
  countdownStream.outTransformed.listen ((podatki) {
     odštevanje = int.parse (podatki) * 1000;
  });
}

Tukaj lastnost vprašanj (StreamedList tipa Vprašanje) posluša spremembe, ko se pošlje seznam vprašanj v tok, ki se začne z metodo _startTrivia, zažene igro.

Namesto tega countdownStream samo posluša spremembe vrednosti odštevanja na strani z nastavitvami, tako da lahko posodobi lastnost odštevanja, uporabljeno v razredu TriviaBloc.

  • _startTrivia (seznam podatki)

Ta metoda začne igro. V bistvu ponastavi stanje lastnosti, nastavi prvo vprašanje in po eni sekundi pokliče metodo playTrivia.

void _startTrivia (seznam  podatki) {
  indeks = 0;
  triviaState.value.questionIndex = 1;
  // Prikaže glavno stran in povzetke
  triviaState.value.isTriviaEnd = napačno;
  // Ponastavite statistiko
  stats.reset ();
  // Nastaviti začetno vprašanje (v tem primeru odštevanje)
  // animacija vrstice se ne bo začela).
  currentQuestion.value = podatki.prvi;
  Časovnik (trajanje (milisekunde: 1000), () {
    // Nastavitev te zastave na true pri spremembi vprašanja
    // Zažene se animacija vrstice za odštevanje.
    triviaState.value.isTriviaPlaying = res;
  
    // Prvo vprašanje sprostite z odštevalno vrstico
    // animacija.
    currentQuestion.value = podatki [indeks];
  
    playTrivia ();
  });
}

triviaState je StreamedValue tipa TriviaState, razred, ki se uporablja za stanje stanja.

razred TriviaState {
  bool isTriviaPlaying = napačno;
  bool isTriviaEnd = napačno;
  bool isAnswerChosen = napačno;
  int questionIndex = 1;
}
  • playTrivia ()

Ko se pokliče ta metoda, časovnik občasno posodobi timer in preveri, ali je pretekel čas večji od nastavitve odštevanja, v tem primeru prekliče časovnik, označi trenutno vprašanje kot neodgovorjeno in pokliče _nextQuestionmethod, da prikaže novo vprašanje .

void playTrivia () {
  timer = Timer.periodic (Trajanje (milisekunde: refreshTime), (Timer t) {
    currentTime.value = refreshTime * t.tick;
    če (currentTime.value> odštevanje) {
      currentTime.value = 0;
      timer.cancel ();
      notAnswered (trenutna vprašanja. vrednost);
     _nextQuestion ();
    }
  });
}
  • notAnswered (Vprašanje za vprašanje)

Ta metoda pokliče metodo addNoAnswer statističnega primerka razreda TriviaStats za vsako vprašanje brez odgovora, da bi posodobila statistiko.

ni razveljavljenoodgovorjeno (vprašanje za vprašanja) {
  stats.addNoAnswer (vprašanje);
}
  • _nextQuestion ()

Pri tej metodi se indeks vprašanj poveča in če so na seznamu še druga vprašanja, se v tok currentQuestion pošlje novo vprašanje, tako da ValueBuilder stran posodobi z novim vprašanjem. V nasprotnem primeru se prikliče metoda _endTriva, ki konča igro.

void _nextQuestion () {
  indeks ++;
   če (indeks 
  • endTrivia ()

Tu se števec prekliče in zastava jeTriviaEnd nastavljena na true. Po 1,5 sekunde po koncu igre se prikaže stran s povzetkom.

void _endTrivia () {
  // PONASTAVITI
  timer.cancel ();
  currentTime.value = 0;
  triviaState.value.isTriviaEnd = res;
  triviaState.refresh ();
  stopTimer ();
  Časovnik (trajanje (milisekunde: 1500), () {
     // Tu se ponastavi, da se ne sproži začetek
     // animacija odštevanja med čakanjem na stran s povzetkom.
     triviaState.value.isAnswerChosen = napačno;
     // Pokaži stran s povzetkom po 1,5s
     tabController.value = AppTab.summary;
     // Počistite zadnje vprašanje, da se ne prikaže
     // v naslednji igri
     currentQuestion.value = null;
  });
}
  • checkAnswer (Vprašanje za vprašanja, string string)

Ko uporabnik klikne na odgovor, ta metoda preveri, ali je pravilen, in pokliče metodo, da v statistiko doda pozitivno ali negativno oceno. Nato se časovnik ponastavi in ​​naloži novo vprašanje.

void checkAnswer (Vprašanje za vprašanja, string string) {
  if (! triviaState.value.isTriviaEnd) {
     question.chosenAnswerIndex = question.answers.indexOf (odgovor);
     če (vprašanje.izpravi pravilno (odgovor)) {
       stats.addCorrect (vprašanje);
     } else {
       stats.addWrong (vprašanje);
     }
     timer.cancel ();
     currentTime.value = 0;
    _nextQuestion ();
  }
}
  • stopTimer ()

Ko se prikliče ta metoda, se čas prekliče in zastavica jeAnswerChosen nastavljena na true, da pove CountdownWidget, naj animacijo ustavi.

void stopTimer () {
  // Ustavi merilnik časa
  timer.cancel ();
  // Z nastavitvijo te zastave na resnično se animacija odštevanja ustavi
  triviaState.value.isAnswerChosen = res;
  triviaState.refresh ();
}
  • onChosenAnswer (String answer)

Ko je izbran odgovor, se števec prekliče in indeks odgovora shrani v lastnost izbranegaAnswerIndex v primeru answerAnimation razreda AnswerAnimation. S tem indeksom se ta odgovor zadnja postavi na kup pripomočkov, da se prepreči, da ga pokrivajo vsi drugi odgovori.

void onChosenAnswer (String answer) {
  izbranAnswer = odgovor;
  stopTimer ();
  // Nastavite izbrani odgovor tako, da ga bo pripomoček za odgovor postavil zadnji na
  // sklad.
  
  odgovoriAnimation.value.chosenAnswerIndex =
  currentQuestion.value.answers.indexOf (odgovor);
  odgovoriAnimation.refresh ();
}

AnswerAnimation class:

razred AnswerAnimation {
  AnswerAnimation ({this.chosenAnswerIndex, this.startPlaying});
  int izbranAnswerIndex;
  bool startPlaying = napačno;
}
  • onChosenAnswerAnimationEnd ()

Ko se animacija odgovorov konča, je zastava isAnswerChosen nastavljena na napačno, da se lahko animacija odštevanja ponovno zažene, nato pa pokliče metoda checkAnswer, da preveri, ali je odgovor pravilen.

void onChosenAnwserAnimationEnd () {
  // Ponastavite zastavico, da se lahko začne animacija odštevanja
  triviaState.value.isAnswerChosen = napačno;
  triviaState.refresh ();
  checkAnswer (currentQuestion.value, izbranAnswer);
}
  • TriviaStats razred

Metode tega razreda se uporabljajo za dodelitev ocene. Če uporabnik izbere pravilen odgovor, se rezultat poveča za deset točk in trenutna vprašanja doda na seznam popravkov, tako da jih je mogoče prikazati na strani s povzetkom, če je odgovor napačen, pa se rezultat zmanjša za štiri, nazadnje če brez odgovora se rezultat zmanjša za dve točki.

razred TriviaStats {
  TriviaStats () {
    popravi = [];
    narobe = [];
    noAnswered = [];
    rezultat = 0;
  }
Seznam  popravi;
  Seznam  napake;
  Seznam  brezAnswered;
  int rezultat;
neveljaven addCorrect (vprašanje z vprašanjem) {
    fixcts.add (vprašanje);
    rezultat + = 10;
  }
void addWrong (Vprašanje za vprašanja) {
    errors.add (vprašanje);
    rezultat - = 4;
  }
neveljaven addNoAnswer (vprašanje z vprašanjem) {
    noAnswered.add (vprašanje);
    rezultat - = 2;
  }
razveljavitev ničnosti () {
    popravi = [];
    narobe = [];
    noAnswered = [];
    rezultat = 0;
  }
}
Del 6 - Animacije

V tej aplikaciji imamo dve vrsti animacij: animirana vrstica pod odgovori označuje čas, ki je potreben za odgovor, in animacija, ki se predvaja, ko je izbran odgovor.

1 - animacija vrstice odštevanja

To je precej preprosta animacija. Gradnik kot parameter vzame širino palice, trajanje in stanje igre. Animacija se začne vsakič, ko se gradnik obnovi, in se ustavi, če izberemo odgovor.

Začetna barva je zelena in postopoma postane rdeča, kar pomeni, da se čas bliža.

2 - Odgovori na animacijo

Ta animacija se začne vsakič, ko je izbran odgovor. S preprostim izračunom položaja odgovorov se vsak izmed njih postopoma premakne na položaj izbranega odgovora. Če želite, da izbrani odgovor ostane na vrhu zložbe, je to zamenjano z zadnjim elementom seznama pripomočkov.

// Zamenjajte zadnjo postavko z izbranim nagovorom, tako da lahko
// naj bo prikazan kot zadnji v skladbi.
final last = widgets.last;
končni izbrani = gradniki [widget.answerAnimation.chosenAnswerIndex]; končno izbranIndex = widgets.indexOf (izbrano);
widgets.last = izbrano;
widgeti [izbranIndex] = zadnji;
vrniti posodo (
   otrok: skladanje (
      otroci: pripomočki,
   ),
);

Če je odgovor pravilen, barva škatel postane zelena in rdeča, če je napačna.

var newColor;
če je (pravilno)
  newColor = Colors.green;
} else {
  newColor = Barve.red;
}
colorAnimation = ColorTween (
  začnite: answerBoxColor,
  konec: nova barva,
) .animate (krmilnik);
čakajo controller.forward ();
Del 7 - Stran s povzetkom

1 - Povzetek strani

Ta stran kot parameter vzame primerek razreda TriviaStats, ki vsebuje seznam popravljenih vprašanj, napak in tistih brez izbranega odgovora ter sestavi ListView, ki prikazuje vsako vprašanje na pravem mestu. Trenutno vprašanje se pošlje v pripomoček SummaryAnswers, ki sestavi seznam odgovorov.

2 - Povzetek odgovorov

Ta pripomoček kot parameter vzame indeks vprašanja in samo vprašanje ter sestavi seznam odgovorov. Pravilen odgovor je obarvan z zeleno barvo, medtem ko je uporabnik izbral napačen odgovor, je ta označen z rdečo barvo in prikazuje pravilne in napačne odgovore.

Zaključek

Ta primer še zdaleč ni popoln ali dokončen, lahko pa je dobro izhodišče za sodelovanje. Na primer, lahko ga izboljšate tako, da ustvarite stran s statistiko z rezultati vsake igrane igre ali odsek, kjer lahko uporabnik ustvari vprašanja in kategorije po meri (to je lahko odlična vaja za vadbo z bazami podatkov). Upam, da je to lahko koristno, lahko predlagate izboljšave, predloge ali drugo.

Izvirno kodo najdete v tem skladišču GitHub.