NB: WORK IN PROGRESS
Dette eksemplet handler om ConnectFour-spillet med en tydelig oppdeling i klasser for brikke, brett og spill-logikk.
Oppgavebeskrivelse
I spillet ConnectFour skal to spillere legge hver sine brikker i et rutenett på 7x7 ruter og prøve å få fire på rad før brettet er fullt. Oppgaven fokuserer på å realisere dette ved bruk av tre klasser, med en tydelig rollefordering: Piece-klassen inneholder verdien til en brikke på brettet, ConnectFour-klassen håndterer selve brettet og hvem sin tur det er, mens ConnectFourProgram-klassen håndterer tekst-basert interaksjon med spillerne gjennom konsollet. Denne tydelige oppdelingen i logikk og interaksjon (også kalt brukergrensesnitt) vil gjøre det lettere å senere lage et grafisk brukergrensesnitt uten å måtte programmere alt på nytt, siden logikk-klassen vil kunne gjenbrukes. For å gjøre klassene noenlunde uavhengig av hverandre brukes prinsippet om Innkapsling.
Oppgaven er åpen i den forstand at den ikke spesifiserer akkurat hvilke metoder hver klasse skal ha, men sier noe om hvordan det skal ta seg ut for brukeren. Dette gjør det vanskeligere å teste enkeltmetoder, så istedenfor testes teksten som kommer ut, basert på hva brukeren (eller testprogrammet) gir inn.
Det er ofte lurt å løse slike oppgaver i mindre trinn, og derfor har vi nedenfor spesifisert hvilke funksjoner vi tror er lurt å lage i hvert trinn.
- Trinn 1 - kunne vise frem brettet med og uten brikker. I dette trinnet lager du ConnectFour-klassen med en toString()-metode som viser brettet med de brikkene som er satt (f.eks. av et enkelt test-hovedprogram du lager selv). ConnectFour-klassen skal være ordentlig innkapslet. Du må også lage Piece-klassen for å representere brikker.
- Trinn 2 - spillerne kan legge brikker. I dette trinnet lages en enkel versjon av ConnectFourProgram-klassen, slik at spillerne etter tur kan legge brikker ved å angi kolonnen de ønsker å slippe en brikke ned i. ConnectFourProgram-klassen skal ta seg av all input og utskrift. For interaksjon med brukeren kan det være lurt å bruker Scanner-klassen.
- Trinn 3 - et helt fungerende spill, hvor ConnectFour-klassen kan si fra til ConnectFourProgram-klassen hvilken spiller som har turen, om spillet er ferdig og hvilken spiller som evt. har vunnet.
Nedenfor har vi vist en mulig spillsekvens som både illustrerer brett-formatet og dialogen mellom spillet og spillerne. Output til brukeren er i svart, mens input fra brukeren er i grønt.
Eksempelløsning
Klassen lagrer radiusen i et attributt (kalt felt i Java) av typen double. Dette feltet initialiseres av konstruktøren, som tar inn et double-argumentet og setter radius-feltet til denne verdien. Her brukes this.radius for å referere til attributtet (og "hoppe over" argumentet) og kun radius for å referere til argumentet.
Metodene getCircumference() og getArea() bruker radius-attributtet i de vanlige formlene for omkrets og areal. Her er bruken av this i this.radius strengt tatt ikke nødvendig, siden radius alene også vil blir tolket som en referanse til attributtet, da det ikke er argumenter eller lokale variable "i veien".
toString()-metoden lager en String med ved å skjøte sammen mange deler med +. En alternativ variant med bruk av String.format er også vist. Den tar inn en String med formatteringsdirektiver som forteller hvor de påfølgende argumentene skal spleises inn i teksten. %.2f brukes som direktiv siden verdiene er desimaltall (f for floating point-verdier) og vi bare vil at tallene skal vises med to desimaler.
package connectfour; public class Piece { private char value; public Piece(char value) { this.value = value; } public char getValue() { return value; } public void setValue(char value) { this.value = value; } public String toString() { return "" + getValue(); } }
something here
package connectfour; import java.util.ArrayList; public class ConnectFour { private ArrayList<ArrayList<Piece>> board; private char player; public ConnectFour() { board = new ArrayList<ArrayList<Piece>>(); for (int r = 0; r < 7; r++) { board.add(new ArrayList<Piece>()); for (int c = 0; c < 7; c++) { board.get(r).add(new Piece(' ')); } } player = 'o'; } private Piece getPiece(int r, int c) { return board.get(r).get(c); } private void setPiece(int r, int c, Piece piece) { board.get(r).set(c, piece); } public boolean drop(int c) { if (getPiece(0, c).getValue() != ' ') { return false; } else { for (int r = 6; r >= 0; r--) { if (getPiece(r, c).getValue() == ' ') { setPiece(r, c, new Piece(player)); return true; } } return true; } } public boolean hasWon() { for (int r = 0; r < 7; r++) { for (int c = 0; c < 7; c++) { if (getPiece(r,c).getValue() != ' ' && hasWonFromPosition(r,c)) { return true; } } } return false; } private boolean hasWonFromPosition(int r, int c) { for (int dr = -1; dr <= 1; dr++) { for (int dc = -1; dc <= 1; dc++) { if (dr != 0 || dc != 0) { if (hasWonFromPositionWithDirection(r,c,getPiece(r,c), dr, dc)) { return true; } } } } return false; } private boolean hasWonFromPositionWithDirection(int r, int c, Piece piece, int dr, int dc) { int counter = 0; while (0 <= r && r < 7 && 0 <= c && c < 7 && getPiece(r, c).getValue() == piece.getValue()) { r += dr; c += dc; counter++; } return counter >= 4; } public char getPlayer() { return player; } public void changePlayer() { if (player == 'o') { player = 'x'; } else { player = 'o'; } } public String toString() { String str = ""; for (int r = 0; r < 7; r++) { str += "| "; for (int c = 0; c < 7; c++) { str += getPiece(r, c) + " "; } str += "|\n"; } return str; } }
something here
package connectfour; import java.util.Scanner; public class ConnectFourProgram { ConnectFour cf; public void init() { cf = new ConnectFour(); } public void run() { Scanner scanner = new Scanner(System.in); while (! cf.hasWon()) { System.out.println(cf); System.out.println("Player " + cf.getPlayer() + ", enter index of column to drop next piece: "); int c = scanner.nextInt(); if (cf.drop(c) && ! cf.hasWon()) { cf.changePlayer(); } } System.out.println(cf); System.out.println("Congratulations player " + cf.getPlayer() + "! You have won the game."); } public static void main(String[] args) { ConnectFourProgram cfp = new ConnectFourProgram(); cfp.init(); cfp.run(); } }
For å prøve ut koden lager vi en hovedprogramklasse kalt CircleProgram. Det vanlige er å ha en init()- og en run()-metode, men vi utelater init() her, siden programmet er så lite og enkelt. Det er tross alt vi som velger hva de heter, basert på hva som er ryddig og bekvemt.
I run()-metoden opprettes to Circle-objekter med new. Disse er illustrert i figuren til høyre (id'ene #1 og #2 er kun med for å illustrere at dette er forskjellige objekter). Disse objektene skrives så ut med System.out.println-metoden. Dette vil implisitt kalle toString()-metoden, siden System.out.println bruker denne internt, for å gjøre om argumentet til en String, før det skrives ut.
Merk at main-metoden må være deklarert akkurat slik for å bli kalt av Java, når klassen som helhet skal utføres. Den lager en instans av programmet og kaller run()-metoden som gjør "jobben".