Välised failid

Tekstifailide kasutamisel võib olla teatud olukordades eeliseid andmebaaside ees:

  1. Lihtsus: Tekstifailid on andmebaasidega võrreldes palju lihtsamad kasutada. Puuduvad keerulised päringud või tabelid, ning andmeid on lihtne lugeda ja muuta tavalise tekstiredaktoriga.

  2. Kättesaadavus: Tekstifailid on erinevate platvormide ja operatsioonisüsteemide vahel lihtsasti liigutatavad ilma keerukate konverteerimisvahendite või tarkvarata.

  3. Ühilduvus: Enamus programmeerimiskeeli toetavad sisseehitatud tekstifailide lugemist ja kirjutamist, mistõttu on nad laialdaselt aktsepteeritud standardiks andmete salvestamiseks ja vahetamiseks.

  4. Madal koormus: Tekstifailidel on väga madal koormus, mis tähendab, et need võtavad vähe ruumi kettal ja kasutavad minimaalselt süsteemiressursse.

  5. Paindlikkus: Tekstifailid võivad olla kasulikud erinevat tüüpi andmete salvestamiseks ja vahetamiseks, lihtsatest seadistusfailidest kuni keerukate andmestruktuurideni nagu JSON ja XML.

Siiski on andmebaasidel omad eelised, näiteks võimekus hakkama saada suurte andmemahtude, keerukate päringute ja tagada andmete järjepidevus ja terviklikkus. Lõplik valik tekstifailide ja andmebaaside vahel sõltub teie rakenduse konkreetsetest nõuetest ja sellest, millist tüüpi andmeid vajate haldamiseks.

JSON-i parsimine

JSON on andmeformaat, mis sobib hästi dünaamiliste ja/või mahukate andmete hoidmiseks. Näiteks eri mängutasemel erinevate tegelaste atribuudid, tegelaste dialoogid, jutuvestija räägitav narratiiv.

Järgnevas koodinäites loome keeruka JSON-i struktuuri, defineerime ühe JSON-i atribuudi põhjal enumi, parsime JSON-i, loome sellest Java objektid ning kasutame casting'ut, et määrata objektid vastavatesse pärivatesse klassidesse.

Loome kolme eri kaarditüüpi JSON-i objektiks:
  • DECISION: mängija valikust sõltub, kas tema skoor suureneb või väheneb.

  • NOTHING: kaardi saamisel ei juhtu midagi.

  • FREE_POINTS: mängija skoor automaatselt suureneb või väheneb.

type võtme asemel võib ka mõne muu välja valida ja selle väärtused Enum-klassis defineerida.

[
    {
        "type": "DECISION",
        "id": 190,
        "question": "Are you going on a trip to Yemen or Oman?",
        "badAnswer": "Oman",
        "goodAnswer": "Yemen",
        "badAnswerScore": -100,
        "goodAnswerScore": 100,
        "badAnswerOutput": "Oh man",
        "goodAnswerOutput": "Ye men"
    },
    {
        "type": "NOTHING",
        "id": 200
    },
    {
        "type": "FREE_POINTS",
        "id": 360,
        "text": "You win the Nobel Peace Prize.",
        "score": 100
    }
]

Defineerime enumi iga kaarditüübi jaoks. See lihtsustab hiljem põhilises mänguklassis kaarditüüpide erikäitlemise ning ka Screenide vahetamise kaarditüübi järgi.

public enum CardType  {

    DECISION,
    FREE_POINTS,
    NOTHING
}

Loome abstraktse klassi, kuhu paigutame kõik muutujad ja nende get-meetodid, mida iga päriv klass vajab. set-meetodid pole selles näites vaja.

public abstract class BaseCard {

    private CardType  type;
    private int id;

    public int getId() {
        return id;
    }

    public CardType  getType() {
        return type;
    }
}

NothingCard on ainus kaarditüüp, mis vastab täpselt abstraktsele klassile, teised kaarditüübid vajavad lisamuutujaid ja get-meetodeid nendele muutujatele.

public class NothingCard extends BaseCard {
}

public class FreePointsCard extends BaseCard {

    private String text;
    private int score;

    public String getText() {
        return text;
    }
    public int getScore() {
        return score;
    }
}

public class DecisionCard extends BaseCard {

   private String question;
   private String badAnswer;
   private String goodAnswer;
   private String badAnswerOutput;
   private String goodAnswerOutput;
   private int badAnswerScore;
   private int goodAnswerScore;

   public String getQuestion() {
       return question;
   }

   public String getBadAnswer() {
       return badAnswer;
   }

   public String getGoodAnswer() {
       return goodAnswer;
   }

   public String getBadAnswerOutput() {
       return badAnswerOutput;
   }

   public String getGoodAnswerOutput() {
       return goodAnswerOutput;
   }

   public int getBadAnswerScore() {
       return badAnswerScore;
   }

   public int getGoodAnswerScore() {
       return goodAnswerScore;
   }
  1. Põhilises mänguklassis kasutame Map<Integer, BaseCard> muutujat, et kaardid nende ID-de järgi leida. Kuigi BaseCard klassis on ID muutuja primitiivne andmetüüp int, vajab Map võtmeks Integer andmetüüpi.

private final Map<Integer, BaseCard> cardId = new HashMap<>();

Loome põhilises mänguklassi meetodi parseJson, mis:

  1. Loeb assets kaustast JSON-faili string-iks.

  2. Salvestab iga JSON-i objekti (loogeliste sulgude sisu) eraldi objektiks ja lisab need massiivi.

  3. Käib läbi massiivi ja otsib type võtme abil väärtusi, mis on ka Enum-klassis.

  4. Kasutab ObjectMapper -it, et teisendada JSON-i objektid Java objektideks.

  5. Loob switch-case tingimuslause abil iga kaarditüübi jaoks vastava BaseCardi päriva klassi objekti.

  6. Lisab loodud objektid Map -i, kasutades kaardi ID-d võtmena.

private void parseJson() {

String jsonData = Gdx.files.internal("cards.json").readString();
try {
                JSONArray jsonArray = new JSONArray(jsonData);
                for (int i = 0; i < jsonArray.length(); i++) {
                        JSONObject jsonObject = jsonArray.getJSONObject(i);
                        CardType  type = CardType .valueOf(jsonObject.getString("type"));

                        ObjectMapper mapper = new ObjectMapper();

                        BaseCard card = switch (type) {
                                case FREE_POINTS -> mapper.readValue(jsonObject.toString(), FreePointsCard.class);
                                case NOTHING -> mapper.readValue(jsonObject.toString(), NothingCard.class);
                                case DECISION -> mapper.readValue(jsonObject.toString(), DecisionCard.class);
        };
                        cardId.put(card.getId(), card);
                }
        } catch (IOException e) {
                e.printStackTrace();
        }
}

Nüüd peame parseJson(); põhilises mänguklassi konstruktoris välja kutsuma.

Lõpetuseks peame sama klassi render meetodis määrama mängija saadud kaarditüübi ning suunama mängija screenManager abiga kaarditüübile vastavale Screen'ile, et eri muutujatega erinevaid nuppe näiteks kasutada. Et anda uute Screen'ide konstruktoritele päritava klassi muutujad, peame tegema casting-ut, sest andmeid hoidvas Map-is on kõik väärtused abstraktse klassina. Casting muudab meie Map-i väärtuse õigeks päritavaks klassiks. Uuele Screen-ile mängija viimine tähendab ka seda, et järgnevat koodi peab kirjutama enne batch.begin() osa. Uues Screen-is anname peale igale kaarditüübile omasele muutujale kaasa ka hetkese klassi ja mängija objekti.

BaseCard card = cardId.get(currentCardId);
    if (card.getType() == CardType.NOTHING) {
        break;

    } else if (card.getType() == CardType.FREE_POINTS) {
        int score = ((FreePointsCard) card).getScore();
        String text = ((FreePointsCard) card).getText();

        screenManager.switchToFreePointsScreen(text, score, player, this);

    } else if (card.getType() == CardType.DECISION) {
        String card = ((DecisionCard) card).getQuestion();
        String goodAnswer = ((DecisionCard) card).getGoodAnswer();
        String badAnswer = ((DecisionCard) card).getBadAnswer();
        String goodAnswerOutput = ((DecisionCard) card).getGoodAnswerOutput();
        String badAnswerOutput = ((DecisionCard) card).getBadAnswerOutput();
        int goodScore = ((DecisionCard) card).getGoodAnswerScore();
        int badScore = ((DecisionCard) card).getBadAnswerScore();

        screenManager.switchToDecisionsScreen(card, goodAnswer, badAnswer, goodScore, badScore, goodAnswerOutput, badAnswerOutput, player, this);
    }