From 26163d3029d15fda78298f3a9debd5825c54f352 Mon Sep 17 00:00:00 2001 From: Andreas Omholt Olsen Date: Thu, 22 Jan 2026 22:01:08 +0100 Subject: [PATCH] Add oving3 --- oppgavetekster/oving3/Card.md | 38 ++++++ oppgavetekster/oving3/CoffeeCup.md | 40 ++++++ oppgavetekster/oving3/Nim.md | 25 ++++ oppgavetekster/oving3/README.md | 45 +++++++ oppgavetekster/oving3/RPNCalc.md | 68 ++++++++++ src/main/java/oving3/card/Card.java | 64 +++++++++ src/main/java/oving3/debugging/CoffeeCup.java | 59 +++++++++ .../oving3/debugging/CoffeeCupProgram.java | 53 ++++++++ src/test/java/oving3/NimTest.java | 81 ++++++++++++ src/test/java/oving3/RPNCalcTest.java | 123 ++++++++++++++++++ src/test/java/oving3/card/CardDeckTest.java | 42 ++++++ src/test/java/oving3/card/CardTest.java | 46 +++++++ 12 files changed, 684 insertions(+) create mode 100644 oppgavetekster/oving3/Card.md create mode 100644 oppgavetekster/oving3/CoffeeCup.md create mode 100644 oppgavetekster/oving3/Nim.md create mode 100644 oppgavetekster/oving3/README.md create mode 100644 oppgavetekster/oving3/RPNCalc.md create mode 100644 src/main/java/oving3/card/Card.java create mode 100644 src/main/java/oving3/debugging/CoffeeCup.java create mode 100644 src/main/java/oving3/debugging/CoffeeCupProgram.java create mode 100644 src/test/java/oving3/NimTest.java create mode 100644 src/test/java/oving3/RPNCalcTest.java create mode 100644 src/test/java/oving3/card/CardDeckTest.java create mode 100644 src/test/java/oving3/card/CardTest.java diff --git a/oppgavetekster/oving3/Card.md b/oppgavetekster/oving3/Card.md new file mode 100644 index 0000000..08babd8 --- /dev/null +++ b/oppgavetekster/oving3/Card.md @@ -0,0 +1,38 @@ +# Innkapsling - Card-oppgave + +Denne oppgaven handler om to klasser for kortspill: `Card` (kort) og `CardDeck` (kortstokk), der den siste inneholder ett eller flere `Card`-objekter. + +I mange sammenhenger vil objekter av en klasse inneholde eller "eie" objekter av andre klasser. Når en klasse er assosiert med én instans av en (annen) klasse er dette en [1-1-assosiasjon](https://www.ntnu.no/wiki/display/tdt4100/Koding+av+1-1-assosiasjoner) og når en klasse er assosiert med flere instanser av en annen klasse er dette en [1-n-assosiasjon](https://www.ntnu.no/wiki/display/tdt4100/Koding+av+1-n-assosiasjoner). I denne oppgaven skal du implementere logikk for en kortstokk som inneholder kort. Nedenfor beskrives begge klassene og metodene disse skal inneholde. + +Filene i denne oppgaven skal lagres i [oving3/card](../../src/main/java/oving3/card). + +## Card + +Oppgavebeskrivelsen for denne deloppgaven er annerledes enn oppgavene dere har møtt tidligere. Istedenfor å få gitt oppgavene her i `README`-en, skal dere lese og forstå den såkalte Javadoc-dokumentasjonen som er skrevet i koden. Javadoc er en måte å skrive dokumentasjon på i Java, og er en standard måte å skrive dokumentasjon på i Java-verdenen. Dette er ofte slik oppgavene blir gitt på eksamen, så det er viktig å kunne lese og forstå Javadoc-dokumentasjonen. Her gjelder det å lese beskrivelsen nøye, og det er ofte fordelaktig å lese testkoden for å forstå hva som forventes av klassen. + +> Kjapt tips: Hold musen over metoden/klassen for å lese Javadoc-dokumentasjonen på et fint format. + +Den påbegynte koden for `Card`-klassen finner du i [oving3/card/Card.java](../../src/main/java/oving3/card/Card.java). + +## CardDeck + +Denne klassen må dere lage selv slik som på tidligere øvinger. `CardDeck`-objekter inneholder initielt et visst antall kort av de fire kortfargene `'S'`, `'H'`, `'D'` og `'C'`. Klassen inneholder standardmetoder for å lese hvor mange og hvilke kort, og en metode for å endre tilstand. + +Konstruktør: + +- `CardDeck(int n)` - fyller kortstokken med de `n` første kortene av hver kortfarge, totalt `n*4` kort, med spar $1$ som første kort, spar $2$ som andre, spar $3$ som tredje, spar $4$ som fjerde, ..., hjerter $1$ som fjortende, hjerter $2$ som femtende, osv. Med andre ord, først alle spar, så hjerter, så ruter og så kløver, alle i stigende rekkefølge. + +Lesemetoder: + +- `int getCardCount()` - returnerer hvor mange Card-objekter som `CardDeck`-objektet inneholder. +- `Card getCard(int n)` - returnerer kort nr. `n` eller utløser et `IllegalArgumentException` hvis `n` ikke er gyldig. Kort nr. $0$ er det første kortet i kortstokken. + +Endringsmetode: + +- `void shufflePerfectly()` - stokker kortstokken ved å dele den i to like store deler og flette de to delene perfekt, slik at kortet på toppen forblir på toppen og kortet på bunnen forblir på bunnen (se [http://en.wikipedia.org/wiki/Out_shuffle](http://en.wikipedia.org/wiki/Out_shuffle)). + +## Oppgave: Java-kode + +Skriv `Card`- og `CardDeck`-klassene, slik at de har ønsket oppførsel og er skikkelig innkapslet. Det kan være lurt å skrive og teste `Card`-klassen først. + +Testkode for denne oppgaven finner du i [oving3/card/CardDeckTest.java](../../src/test/java/oving3/card/CardDeckTest.java) og [oving3/card/CardTest.java](../../src/test/java/oving3/card/CardTest.java). diff --git a/oppgavetekster/oving3/CoffeeCup.md b/oppgavetekster/oving3/CoffeeCup.md new file mode 100644 index 0000000..664c598 --- /dev/null +++ b/oppgavetekster/oving3/CoffeeCup.md @@ -0,0 +1,40 @@ +# Debugging - CoffeeCup-oppgave + +Oppgaven handler om feilsøking i en **CoffeeCup**- og en **CoffeeCupProgram**-klasse ved bruk av [debuggeren i VS Code](https://www.ntnu.no/wiki/pages/viewpage.action?pageId=235996724). + +Et **CoffeeCup**-objekt inneholder kapasitet og nåværende volum av kaffe. + +- Kapasitet må være et ikke-negativt flyttall, som til enhver tid må være større enn nåværende volum av kaffe. +- Nåværende volum av kaffe er et ikke-negativt flyttall som til enhver tid må være mindre enn kapasiteten til koppen. + +**CoffeeCup**-klassen har følgende metoder og konstruktører: + +- `CoffeeCup()` - konstruktør som setter standard initialverdier til $0.0$ kapasitet og $0.0$ nåværende kaffe i koppen. +- `CoffeeCup(double, double)` - konstruktør som setter initialverdier til de oppgitte verdiene. +- `void drinkCoffee(double)` og `void fillCoffee(double)` - henholdsvis drikker og fyller koppen med kaffe. Om man prøver å drikke mer enn det finnes i koppen, eller fyller for mye i koppen blir en `IllegalArgumentException` utløst. +- `void increaseCupSize(double)` - øker størrelsen på koppen. Om man prøver å gjøre koppen mindre skjer ingenting. +- I tillegg har klassen noen private hjelpefunksjoner som man kan identifisere selv. + +**CoffeeCupProgram**-klassen er en hovedprogramklasse som oppretter en instans av **CoffeeCup**, og utfører en sekvens av kall til koppens ulike metoder. + +I denne oppgaven blir en implementasjon av **CoffeeCup** sammen med et hovedprogram utdelt, men i implementasjonen av hovedprogrammet har vi plantet noen feil. Det er to oppgaver som må løses. + +CoffeeCup-koden finner du i [oving3/debugging/CoffeeCup.Java](../../src/main/java/oving3/debugging/CoffeeCup.java). CoffeeCupProgram finner du i [oving3/debugging/CoffeeCupProgram.java](../../src/main/java/oving3/debugging/CoffeeCupProgram.java). + +## Leseliste + +- [Debuggeren i VS Code](https://www.ntnu.no/wiki/pages/viewpage.action?pageId=235996724) - Debuggeren er et verktøy som brukes til å analysere kjørende kode, noe som kan være svært nyttig når man vil forstå og evt. rette feil i et program. +- [Hovedprogramklasser](https://www.ntnu.no/wiki/display/tdt4100/Hovedprogramklasser) - Hovedprogramklasser er klasser som aktiveres når programmer starter opp og som initialiserer og kontrollerer/koordinerer de andre objekter i applikasjonen. +- [`java.util.Random`](https://docs.oracle.com/javase/8/docs/api/java/util/Random.html) - En pseudotilfeldig nummergenerator (PRNG) i Java. + +### Del 1 + +Målet for oppgaven er å finne en feil i funksjonen `part1()` ved hjelp av debuggeren i VS Code. Kjør hovedprogrammet i debug-modus, og bruk dette til å finne hva kapasiteten og nåværende volum av kaffe er like før programmet utløser et unntak. + +Finn også ut hvilken metode i **CoffeeCup** som utløser unntaket. + +### Del 2 + +Du fant feilen i oppgave 1, bra! Kommentér ut kallet til `part1()` i hovedprogrammet, så slipper vi å ha mer med det å gjøre. + +Du skal nå finne ut hvordan nåverende volum av kaffe endrer seg i `part2()`, før programmet utløser et unntak. Lag en liste over hvilke verdier nivået har. Hvilken metode i **CoffeeCup** utløser et unntak denne gangen? Hvilken type unntak blir utløst? diff --git a/oppgavetekster/oving3/Nim.md b/oppgavetekster/oving3/Nim.md new file mode 100644 index 0000000..fa13817 --- /dev/null +++ b/oppgavetekster/oving3/Nim.md @@ -0,0 +1,25 @@ +# Innkapsling - Nim-oppgave + +Denne oppgaven handler om en `Nim`-klasse, som implementerer en variant av spillet [Nim](https://en.wikipedia.org/wiki/Nim). + +Spillet Nim består av **tre** hauger med brikker. To spillere velger på tur én av haugene, og fjerner så mange brikker som ønskelig, men minimum én brikke. Spillet er over når en av haugene er helt tom for brikker. + +`Nim`-klassen har enkel tilstand: En spillomgang består av tre hauger med et antall brikker. `Nim`-klassen må dermed kunne representere hvor mange brikker som er i hver haug, og oppdatere dette ettersom det fjernes brikker. Hvordan dette gjøres er åpent, så lenge det er tilstrekkelig for å oppfylle oppførselen definert for klassen. + +`Nim`-klassen har følgende metoder: + +- `void removePieces(int number, int targetPile)` - fjerner `number` ($>= 1$) antall brikker fra haugen `targetPile`. `IllegalArgumentException` utløses hvis `number` eller `targetPile` er ugyldig. Siden man ikke kan gjøre trekk etter at spillet er over, må `IllegalStateException` utløses dersom metoden kalles etter det. +- `boolean isValidMove(int number, int targetPile)` - returnerer `true` dersom argumentene representerer et lovlig trekk, og `false` dersom argumentene ikke ville ført til et lovlig trekk. Her skal man kun sjekke om et trekk er lovlig å utføre, ikke faktisk endre på tilstanden. **PS**: Husk at å flytte når et spill er over ikke er et lovlig trekk. +- `boolean isGameOver()` - returnerer `true` dersom en av haugene har $0$ brikker. +- `int getPile(int targetPile)` - returnerer antall brikker i haugen `targetPile`. +- `String toString()` - returnerer en tekststreng som oppsummerer spillets tilstand. + +Haugene skal identifiseres ved tall, altså at gyldige verdier for `targetPile` er $0$, $1$ og $2$. + +`Nim`-klassen skal ha en [konstruktør](https://www.ntnu.no/wiki/display/tdt4100/Klasser+i+java#Klasserijava-constructor) `Nim(int pileSize)` som lar en bestemme hvor mange brikker som skal være i hver haug, og i tillegg en tom konstruktør som initialiserer `Nim`-objektet til å ha $10$ brikker i hver haug. + +## Oppgave: Java-kode + +Skriv `Nim`-klassen, slik at den har ønsket oppførsel og er skikkelig innkapslet. + +Testkode for denne oppgaven finner du i [oving3/NimTest.java](../../src/test/java/oving3/NimTest.java). diff --git a/oppgavetekster/oving3/README.md b/oppgavetekster/oving3/README.md new file mode 100644 index 0000000..46a8434 --- /dev/null +++ b/oppgavetekster/oving3/README.md @@ -0,0 +1,45 @@ +# Øving 3: Klasser og testing + +## Øvingsmål + +- Lære å lage enkle Java-klasser og -programmer +- Lære å bruke debuggeren i VS Code + +## Øvingskrav + +- Kunne tegne enkle klassediagrammer +- Kunne deklarere klasser og metoder ihht. oppgavespesifikasjon + +## Dette må du gjøre + +### Del 1: Programmering + +Velg minst to av oppgavene under, der én av de må være [Card-oppgaven](./Card.md). Merk at denne bygges videre på i øving 4, 5 og 7, og er derfor veldig gunstig å gjøre, slik at man har flere oppgaver å velge mellom senere. + +- [Nim](./Nim.md) (Lett) +- **[Card-oppgave](./Card.md)** (Medium) +- [RPN-kalkulator](./RPNCalc.md) (Vanskelig) + +Oppgavene for denne øvingen skal du lagre i [`src/main/java/oving3`](../../src/main/java/oving3). Test-filene ligger i [`src/test/java/oving3`](../../src/test/java/oving3). + +### Del 2: Klassediagram + +Tegn et [klassediagram](https://www.ntnu.no/wiki/display/tdt4100/Klassediagrammer) for oppgaven du velger i del 1. Klassediagrammet kan for eksempel skrives på papir eller tegnes i et valgfritt program. Diagrammet skal demonstrerer på sal, men det er ikke nødvendig å levere det på Blackboard. + +### Del 3: Debugger + +I denne oppgaven skal [debuggeren i VS Code](https://www.ntnu.no/wiki/pages/viewpage.action?pageId=235996724) brukes. Oppgaven skal demonstreres for en læringsassistent på sal. + +- [CoffeeCup](./CoffeeCup.md) (Medium) + +## Vanskelighetsgrad + +Oppgavene er merket med en vanskelighetsgrad relativt til hverandre. Det er en god idé å begynne med de lettere oppgavene dersom du ikke er komfortabel med pensum så langt, men det er anbefalt å prøve seg på de vanskeligere oppgavene om du synes de første oppgavene er uproblematiske. Dersom du allerede føler deg trygg på punktene i øvingskravene kan du forsøke å gå rett på de vanskeligere oppgavene. Du er selvfølgelig velkommen til å løse flere oppgaver enn minstekravet, noe som er lurt med tanke på eksamen og et langt liv som programmerer. + +## Hjelp/Mistanke om bugs + +Ved spørsmål eller behov for hjelp konsulter læringsassistenten din i saltiden hans/hennes. Du kan også be om hjelp over Teams i Fellesveiledning eller legge ut et innlegg på [Piazza](https://piazza.com/ntnu.no/spring2025/tdt4100). + +## Godkjenning + +Last opp kildekode på Blackboard innen den angitte innleveringsfristen. Innlevert kode skal demonstreres for læringsassistent innen én uke etter innleveringsfrist. Se Blackboard-sidene for mer informasjon rundt organisering av øvingsopplegget og det tilhørende øvingsreglementet. diff --git a/oppgavetekster/oving3/RPNCalc.md b/oppgavetekster/oving3/RPNCalc.md new file mode 100644 index 0000000..39ec580 --- /dev/null +++ b/oppgavetekster/oving3/RPNCalc.md @@ -0,0 +1,68 @@ +# Innkapsling - RPN-kalkulator-oppgave + +Oppgaven handler om å lage en RPN-kalkulator (RPN = Reverse Polish Notation). I denne oppgaven kommer du til å ta i bruk en _stack_, og utføre matematiske operasjoner. + +Her er en forklaring av hvordan en RPN-kalkulator fungerer: + +> The RPN calculator differs from a normal calculator in that its syntax is completely different: It uses postfix notation instead of infix notation. +> +> Reverse polish notation, or postfix notation, is a notation where the operators (`+`, `-`, `*`, `/`, ...) appear after the operands (`1.0`, `3.14`, `2.187`, `42`, ...). As an example, consider the expressions `2 – 1` and `9 + 3*4`. In RPN, this would be `2 1 -` and `9 3 4 * +`, as the operator shall appear after the operands. If the last example is confusing, think of it as `9 (3 4 *) +` instead. +> +> The main advantage of this is notation is that we can avoid parentheses and avoid determining which calculation to perform first. In infix, operator precedence is a problem: In the expression `9 + 3*4`, we first have to multiply, even though the plus operator appears first. In RPN, we simply take the first operator we see and apply it on the last two numbers we've seen, and replace the operator and operands with the result. For the multiplication and addition example: +> +> `9 3 4 * +` - the first operator we see is `*`, so we apply that to the two last seen values, `3` and `4`, and replace those elements with the result, `12`. +> +> `9 12 +` - the next operator we see is `+`, so we apply that to `9` and `12`. +> +> `21` - We are finished, as there are no more operators in the expression. +> +> For the more complex expression `(9 + 7) / (5 – 3)`, this will be written as `9 7 + 5 3 - /` in RPN. Notice how we can avoid parentheses and still calculate what we want without any issues: +> +> `9 7 + 5 3 - /` - the first operator is `+`, so we perform `9 + 7` and replace those elements with the result. +> +> `16 5 3 - /` - the next operator is `-`, so we perform the operation `5 – 3`, as those are the last two elements. +> +> `16 2 /` - we perform the last operation, division: +> +> `8` - We are done, as there are no more operators left. +> +> Most RPN calculators have a stack of numbers, giving them control over which numbers to do operations on when they see the next operand. Whenever they see an operand, they push it on the stack. If they see an operator, they pop off the numbers required, perform the operation, and push the result on top of the stack again. +> +> As an example of how this will work, consider the example `9 3 4 * +` once more. Here, an RPN calculator will first push `9`, `3` and `4` on the stack. The stack will now look like this: `[9, 3, 4]`, where `4` is the top of the stack. Then, when the calculator see the operator `_`, it then pops `4` off the stack, pops `3` off the stack, and pushes `3 _ 4` on top of the stack. The stack which now contains `[9, 12]`. Then, when the RPN calculator sees the operator `*`, it pops off `12` and `9` off the stack, and performs the operation `9 + 12`, and pushes it back on top of the (now empty) stack. The stack will now contain `[21]`. + +Tilstanden i `RPNCalc`-objekter velger du selv, men det er naturlig å bruke en `Stack` eller `ArrayList` med `Double`-objekter. + +`RPNCalc`-klassen skal ha følgende metoder: + +- `void push(double)` - legg argumentet på toppen av stacken. +- `double pop()` - returner verdien på toppen av stacken. Verdien skal også fjernes fra stacken. Dersom stacken er tom, så skal `Double.NaN` returneres. +- `double peek(int)` - returner verdien i stacken som står på plassen gitt i argumentet, telt fra toppen. Det vil si, `peek(0)` skal returnere verdien på toppen av stacken, `peek(1)` skal returnere verdien nest øverst i stacken osv. Verdien skal ikke fjernes av stacken. Dersom det er for få elementer på stacken, så skal `Double.NaN` returneres. +- `int getSize()` - returner antallet elementer i stacken. +- `void performOperation(char)` - utfør den angitte operasjonen på de to øverste verdiene i stacken. De to verdiene skal fjernes fra stacken og resultatet skal legges øverst. Bruk eksisterende metoder for å utføre dette der det er mulig. Metoden må støtte `'+'` (pluss), `'-'` (minus), `'*'` (multiplikasjon) og `'/'` (divisjon), men kan også støtte andre operatorer, f.eks. `'~'` (swap) for å bytte de to øverste operandene, `'p'` eller `'π'` (pi) for å legge pi på stacken (bruker ingen operander), `'|'` (absolutt-verdi, bruker én operand). Prøv å håndtere manglende operander på en måte som gir mening for operasjonen. + +**(Valgfritt)** Det kan være nyttig å tegne [objekttilstandsdiagram](https://www.ntnu.no/wiki/display/tdt4100/Objekttilstandsdiagrammer) for en tenkt bruk av `RPNCalc`-klassen. Velg selv passende start-tilstand og sekvens av kall. + +## Del 1 - Java-kode + +Skriv Java-kode for `RPNCalc`-klassen med oppførsel som er beskrevet over. Du skal bruke synlighetsmodifikatorer på metoder og felt for å gjøre innkapslingen "vanntett". + +Lag en [main-metode](https://www.ntnu.no/wiki/display/tdt4100/Main-metoden), hvor du tester sekvenser av operander og operatorer, og kaller henholdsvis `push` og `performOperation`-metodene på et `RPNCalc`-objekt og skriver ut stacken. Test `RPNCalc`-klassen og sjekk at oppførselen stemmer med tilstandsdiagrammet. + +## Del 2 - Teori + +Til nå har det blitt spesifisert at `peek()` og `pop()`-metodene skal returnere `Double.NaN` hvis stacken er tom. Alternativet er å utløse et unntak. + +Svar på følgende: + +- Hvilken type unntak vil det være naturlig å bruke? +- Hvilke fordeler og ulemper ser du for dette alternativet? + +Det er også spesifisert at en skal "håndtere manglende operander på en måte som gir mening for operasjonen". Hvis `+`-operasjonen ble utført på kun én operand, så kan en f.eks. velge å la den manglende operanden være `0`. + +Svar på følgende: + +- Hva vil tilsvarende verdi for manglende operand for `*`-operasjonen (multiplikasjon) være? Hva med for `/` (divisjon)? +- Hvordan kan du endre (evt. har du endret) grensesnittet for stack-operasjonene for å gjøre implementasjonen av disse enklere? +- Også her er et alternativ å utløse unntak. Hva tror du om det? + +Testkode for denne oppgaven finner du i [oving3/RPNCalcTest.java](../../src/test/java/oving3/RPNCalcTest.java). diff --git a/src/main/java/oving3/card/Card.java b/src/main/java/oving3/card/Card.java new file mode 100644 index 0000000..361a7fb --- /dev/null +++ b/src/main/java/oving3/card/Card.java @@ -0,0 +1,64 @@ +package oving3.card; + +/** + * The {@code Card} class is a so-called value-based class, which is coded in + * such a way that its + * objects cannot be modified after they are created. A {@code Card} object has + * a suit and a face. + */ +public class Card { + + // TODO: Add fields here + + /** + * The constructor of the {@code Card} class initializes the suit and face of + * the card with the + * first and second arguments, respectively. + * + * @param suit the suit of the card, one of {@code 'S'} (spades), {@code 'H'} + * (hearts), + * {@code 'D'} (diamonds), or {@code 'C'} (clubs) + * @param face the face of the card, an integer between {@code 1} (ace) and + * {@code 13} (king) + * (both inclusive) + * @throws IllegalArgumentException if the suit or face is illegal + * + * @see CardTest#testConstructor() + */ + public Card(char suit, int face) { + // TODO: Implement this constructor + } + + /** + * @return the suit of the card + */ + public char getSuit() { + // TODO: Implement this method + return '\0'; + } + + /** + * @return the face of the card + */ + public int getFace() { + // TODO: Implement this method + return 0; + } + + /** + * @return the value of the card of the form {@code }. For example, + * the ace of + * spades should return {@code "S1"} + * + * @see CardTest#testToString() + */ + @Override + public String toString() { + // TODO: Implement this method + return null; + } + + public static void main(String[] args) { + + } +} diff --git a/src/main/java/oving3/debugging/CoffeeCup.java b/src/main/java/oving3/debugging/CoffeeCup.java new file mode 100644 index 0000000..b52fd56 --- /dev/null +++ b/src/main/java/oving3/debugging/CoffeeCup.java @@ -0,0 +1,59 @@ +package oving3.debugging; + +public class CoffeeCup { + + private double capacity; + private double currentVolume; + + public CoffeeCup() { + this.capacity = 0.0; + this.currentVolume = 0.0; + } + + public CoffeeCup(double capacity, double currentVolume) { + if (!CoffeeCup.isValidCapacity(capacity)) { + throw new IllegalArgumentException("Illegal capacity given."); + } + this.capacity = capacity; + + if (!this.isValidVolume(currentVolume)) { + throw new IllegalArgumentException("Illegal volume given."); + } + this.currentVolume = currentVolume; + } + + private static boolean isValidCapacity(double capacity) { + return capacity >= 0.0; + } + + public void increaseCupSize(double biggerCapacity) { + if (CoffeeCup.isValidCapacity(biggerCapacity)) { + this.capacity += biggerCapacity; + } + } + + private boolean isValidVolume(double volume) { + return volume <= this.capacity && volume >= 0.0; + } + + private boolean canDrink(double volume) { + return volume <= this.currentVolume; + } + + public void drinkCoffee(double volume) { + if (!this.isValidVolume(volume) || !this.canDrink(volume)) { + throw new IllegalArgumentException("You cannot drink that much coffee!"); + } + + this.currentVolume -= volume; + } + + public void fillCoffee(double volume) { + if (!this.isValidVolume(this.currentVolume + volume)) { + throw new IllegalArgumentException( + "You just poured coffee all over the table. Good job."); + } + + this.currentVolume += volume; + } +} diff --git a/src/main/java/oving3/debugging/CoffeeCupProgram.java b/src/main/java/oving3/debugging/CoffeeCupProgram.java new file mode 100644 index 0000000..26ddb6b --- /dev/null +++ b/src/main/java/oving3/debugging/CoffeeCupProgram.java @@ -0,0 +1,53 @@ +package oving3.debugging; + +import java.util.Random; + +public class CoffeeCupProgram { + + private CoffeeCup cup; + private Random r; + + public void run() { + this.part1(); + this.part2(); + } + + private void part1() { + this.cup = new CoffeeCup(); + this.r = new Random(123_456_789L); + this.cup.increaseCupSize(40.0); + this.cup.fillCoffee(20.5); + this.cup.drinkCoffee(Math.floor(this.r.nextDouble() * 20.5)); + this.cup.fillCoffee(32.5); + this.cup.drinkCoffee(Math.ceil(this.r.nextDouble() * 38.9)); + this.cup.drinkCoffee(Math.ceil(this.r.nextDouble() * 42)); + this.cup.increaseCupSize(17); + this.cup.drinkCoffee(40); + this.cup.drinkCoffee(Math.ceil(this.r.nextDouble() * 42)); + this.cup.drinkCoffee(Math.floor(this.r.nextDouble() * 20.5)); + this.cup.fillCoffee(32.5); + this.cup.drinkCoffee(Math.ceil(this.r.nextDouble() * 38.9)); + this.cup.drinkCoffee(Math.ceil(this.r.nextDouble() * 42)); + this.cup.increaseCupSize(17); + } + + private void part2() { + this.cup = new CoffeeCup(40.0, 20.5); + this.r = new Random(987_654_321L); + this.cup.drinkCoffee(Math.floor(this.r.nextDouble() * 20.5)); + this.cup.fillCoffee(Math.floor(this.r.nextDouble() * 30)); + this.cup.drinkCoffee(Math.ceil(this.r.nextDouble() * 38.9)); + this.cup.drinkCoffee(Math.ceil(this.r.nextDouble() * 42)); + this.cup.increaseCupSize(Math.floor(this.r.nextDouble() * 26)); + this.cup.fillCoffee(Math.ceil(this.r.nextDouble() * 59)); + this.cup.drinkCoffee(Math.ceil(this.r.nextDouble() * 42)); + this.cup.increaseCupSize(Math.floor(this.r.nextDouble() * 35)); + this.cup.fillCoffee(Math.floor(this.r.nextDouble() * 30)); + this.cup.increaseCupSize(Math.floor(this.r.nextDouble() * 26)); + } + + public static void main(String[] args) { + CoffeeCupProgram program = new CoffeeCupProgram(); + program.run(); + } +} diff --git a/src/test/java/oving3/NimTest.java b/src/test/java/oving3/NimTest.java new file mode 100644 index 0000000..9b2e4aa --- /dev/null +++ b/src/test/java/oving3/NimTest.java @@ -0,0 +1,81 @@ +package oving3; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class NimTest { + + private Nim nim; + + private static boolean checkValidMove(Nim game, int pieces, boolean legal) { + return (legal == game.isValidMove(pieces, 0) && (legal == game.isValidMove(pieces, 1)) + && (legal == game.isValidMove(pieces, 2))); + } + + @BeforeEach + public void setUp() { + nim = new Nim(5); + } + + @Test + @DisplayName("Constructor") + public void testConstructor() { + assertEquals(5, nim.getPile(0)); + assertEquals(5, nim.getPile(1)); + assertEquals(5, nim.getPile(2)); + } + + @Test + @DisplayName("Remove pieces") + public void testRemovePieces() { + nim.removePieces(3, 0); + nim.removePieces(2, 1); + nim.removePieces(1, 2); + assertEquals(2, nim.getPile(0)); + assertEquals(3, nim.getPile(1)); + assertEquals(4, nim.getPile(2)); + + assertThrows(IllegalArgumentException.class, () -> { + nim.removePieces(-1, 0); + }, "Cannot remove negative number of pieces"); + + assertThrows(IllegalArgumentException.class, () -> { + nim.removePieces(0, 0); + }, "Cannot remove too few pieces"); + + assertThrows(IllegalArgumentException.class, () -> { + nim.removePieces(6, 0); + }, "Cannot remove too many pieces"); + } + + @Test + @DisplayName("Game over") + public void testGameOver() { + assertFalse(nim.isGameOver()); + + nim.removePieces(5, 0); + assertEquals(0, nim.getPile(0)); + assertTrue(nim.isGameOver()); + + assertThrows(IllegalStateException.class, () -> { + nim.removePieces(5, 0); + }, "Cannot remove pieces when game is over"); + } + + @Test + @DisplayName("Valid moves") + public void testIsValidMove() { + assertTrue(NimTest.checkValidMove(nim, 2, true)); + assertTrue(NimTest.checkValidMove(nim, -2, false)); + assertTrue(NimTest.checkValidMove(nim, 0, false)); + assertTrue(NimTest.checkValidMove(nim, 6, false)); + + nim.removePieces(5, 0); + assertTrue(NimTest.checkValidMove(nim, 2, false)); + } +} diff --git a/src/test/java/oving3/RPNCalcTest.java b/src/test/java/oving3/RPNCalcTest.java new file mode 100644 index 0000000..6b5b20b --- /dev/null +++ b/src/test/java/oving3/RPNCalcTest.java @@ -0,0 +1,123 @@ +package oving3; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class RPNCalcTest { + + private RPNCalc calc; + + @BeforeEach + public void setUp() { + calc = new RPNCalc(); + } + + @Test + @DisplayName("Push") + public void testPush() { + calc.push(1.0); + assertEquals(1.0, calc.peek(0)); + + calc.push(2.0); + assertEquals(2.0, calc.peek(0)); + + calc.push(3.0); + assertEquals(3.0, calc.peek(0)); + } + + @Test + @DisplayName("Pop") + public void testPop() { + calc.push(1.0); + calc.push(2.0); + calc.push(3.0); + assertEquals(3.0, calc.peek(0)); + assertEquals(3.0, calc.pop()); + + assertEquals(2.0, calc.peek(0)); + assertEquals(2.0, calc.pop()); + + assertEquals(1.0, calc.peek(0)); + + calc.push(2.0); + assertEquals(2.0, calc.peek(0)); + assertEquals(2.0, calc.pop()); + + assertEquals(1.0, calc.peek(0)); + assertEquals(1.0, calc.pop()); + + assertEquals(0, calc.getSize()); + } + + @Test + @DisplayName("Peek") + public void testPeek() { + calc.push(0.0); + calc.push(1.0); + calc.push(2.0); + assertEquals(2.0, calc.peek(0)); + assertEquals(1.0, calc.peek(1)); + assertEquals(0.0, calc.peek(2)); + } + + @Test + @DisplayName("Empty stack") + public void testEmptyStack() { + assertEquals(Double.NaN, calc.peek(3)); + assertEquals(Double.NaN, calc.peek(-1)); + } + + @Test + @DisplayName("Size") + public void testGetSize() { + assertEquals(0, calc.getSize()); + + calc.push(1.0); + assertEquals(1, calc.getSize()); + + calc.push(2.0); + assertEquals(2, calc.getSize()); + } + + @Test + @DisplayName("Addition") + public void testAddOperation() { + calc.push(3.0); + calc.push(4.0); + calc.performOperation('+'); + assertEquals(1, calc.getSize()); + assertEquals(7.0, calc.peek(0)); + } + + @Test + @DisplayName("Subtraction") + public void testSubOperation() { + calc.push(7.0); + calc.push(2.0); + calc.performOperation('-'); + assertEquals(1, calc.getSize()); + assertEquals(5.0, calc.peek(0)); + } + + @Test + @DisplayName("Multiplication") + public void testMultOperation() { + calc.push(5.0); + calc.push(2.0); + calc.performOperation('*'); + assertEquals(1, calc.getSize()); + assertEquals(10.0, calc.peek(0)); + } + + @Test + @DisplayName("Division") + public void testDivOperation() { + calc.push(10.0); + calc.push(4.0); + calc.performOperation('/'); + assertEquals(1, calc.getSize()); + assertEquals(2.5, calc.peek(0)); + } +} diff --git a/src/test/java/oving3/card/CardDeckTest.java b/src/test/java/oving3/card/CardDeckTest.java new file mode 100644 index 0000000..8499b65 --- /dev/null +++ b/src/test/java/oving3/card/CardDeckTest.java @@ -0,0 +1,42 @@ +package oving3.card; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class CardDeckTest { + + private static void checkDeck(CardDeck deck, List expected) { + assertEquals(expected.size(), deck.getCardCount(), "Wrong number of cards in deck"); + + for (int i = 0; i < expected.size(); i++) { + String expectedCard = expected.get(i); + Card actual = deck.getCard(i); + + String cardString = String.valueOf(actual.getSuit()) + actual.getFace(); + assertEquals(expectedCard, cardString); + } + } + + @Test + @DisplayName("Constructor") + public void testConstructor() { + assertThrows(IllegalArgumentException.class, () -> new CardDeck(-1)); + assertThrows(IllegalArgumentException.class, () -> new CardDeck(14)); + + CardDeckTest.checkDeck(new CardDeck(0), Collections.emptyList()); + CardDeckTest.checkDeck(new CardDeck(2), + List.of("S1", "S2", "H1", "H2", "D1", "D2", "C1", "C2")); + } + + @Test + @DisplayName("#shufflePerfectly()") + public void testShufflePerfectly() { + CardDeck cardDeck = new CardDeck(2); + cardDeck.shufflePerfectly(); + CardDeckTest.checkDeck(cardDeck, List.of("S1", "D1", "S2", "D2", "H1", "C1", "H2", "C2")); + } +} diff --git a/src/test/java/oving3/card/CardTest.java b/src/test/java/oving3/card/CardTest.java new file mode 100644 index 0000000..a99f86d --- /dev/null +++ b/src/test/java/oving3/card/CardTest.java @@ -0,0 +1,46 @@ +package oving3.card; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class CardTest { + + private static boolean checkState(Card card, char suit, int face) { + return card.getSuit() == suit && card.getFace() == face; + } + + @Test + @DisplayName("Constructor") + public void testConstructor() { + assertTrue(CardTest.checkState(new Card('S', 1), 'S', 1)); + assertTrue(CardTest.checkState(new Card('S', 13), 'S', 13)); + assertTrue(CardTest.checkState(new Card('H', 1), 'H', 1)); + assertTrue(CardTest.checkState(new Card('H', 13), 'H', 13)); + assertTrue(CardTest.checkState(new Card('D', 1), 'D', 1)); + assertTrue(CardTest.checkState(new Card('D', 13), 'D', 13)); + assertTrue(CardTest.checkState(new Card('C', 1), 'C', 1)); + assertTrue(CardTest.checkState(new Card('C', 13), 'C', 13)); + + assertThrows(IllegalArgumentException.class, () -> { + new Card('X', 1); + }); + + assertThrows(IllegalArgumentException.class, () -> { + new Card('S', 0); + }); + + assertThrows(IllegalArgumentException.class, () -> { + new Card('C', 14); + }); + } + + @Test + @DisplayName("#toString()") + public void testToString() { + assertEquals("S1", new Card('S', 1).toString()); + assertEquals("H13", new Card('H', 13).toString()); + } +}