Exercise index of this lecture   Alphabetic index   Course home   

Exercises and solutions
Collections og streams


11.1   Klassen Kortbunke  

I forlængelse af opgaven om Spillekort fra en tidligere lektion vil vi i denne opgave programmere en klasse Kortbunke, som repræsenterer en bunke af kort. En instans af denne klasse kan bruges til at repræsentere et komplet spil kort ligesom den kan bruges til at repræsentere de kort, en spiller har på hånden.

Ideen med opgaven er at benytte en Java Collection klasse som grundlaget for implementationen. Overvej hvilken, og overvej om en Kortbunke skal udvide/specialisere en Collection klasse, være klient af en Collection klasse, eller blot implementere et Collection interface.

Vi ønsker at at kunne repræsentere en bestemt ordning mellem kortene, således at vi f.eks. kan tale om det øverste kort og det nederste kort.

Følgende operationer skal være mulige på instanser af Kortbunke (hverken flere eller færre):

  • Fjern og returner øverste kort.
  • Læg et nyt kort oven på bunken.
  • Læg et nyt kort nederst i bunken.
  • Returner om bunken er tom
  • Bland bunken tilfældigt.
  • Sorter bunken i 'naturlig orden' (lad os sige ruder før hjerter før klør før spar).
  • Returner en Iterator, som gennemløber kortene i bunken.

Datarepræsentationen skal være privat i klassen.

Det vil være hensigtsmæssigt at have følgende udvalg af konstruktorer:

  • En konstruktor som laver et tomt kortspil.
  • En konstruktor som laver et spil kort med bestemte, givne kort.
  • En konstruktor som laver et komplet spil kort (med 52 kort).

Overvej hvorledes du ønsker at skelne mellem disse tre konstruktorer parametermæssigt.

Solution
Vi vælger at basere klassen Kortbunke på List interfacet og ArrayList klassen. Man kunne på en enkel måde blot anvende lister af kort, gennem List interfacet, men denne opgave går ud på at lave en meget specifik klasse, som repræsenterer en bunke af spillekort. Der bliver endvidere bedt om en helt bestemt grænseflade fra klassen. Derfor vælger vi at lave en klasse Kortbunke, som er en klient af java.util.List (eller mere præcist, af java.util.ArrayList). Klassen Kortbunke har en privat instansvariabel kaldet bunke af typen ArrayList. En del operationer på kortbunken kalder blot en naturlig List operation på bunke (f.eks. tagØverte). Man kan sige, at dette er en forholdsvis omstændelig måde at lave klassen på, imodsætning til blot at bruge ArrayList, eller måske lave en specialisering af ArrayList. Man kan dog pege på, at vi netop er i stand til at realisere en bestemt grænsefalde i Kortbunke, og at vi kan indkapsle en del (ellers nødvendige) casts (typekonverteringer) i Kortbunke's metoder (f.eks. i metoden tagØverste).

Læg også mærke til operationen sorter, som sorterer en kortbunke i en bestemt rækkefølge. Sorteringen foregår ved brug af den statiske metode Collections.sort(List,Comparator). Den anden parameter er et objekt, som indkapsler en kort compare funktion.

Der er tre konstruktorer i klassen Kortbunke. Konstruktoren der laver en tom bunke har, helt naturligt, ingen parametre. Konstruktoren der laver en bunke med bestemte kort tager en liste af kort af typen List som parameter. Konstruktoren som laver en bunke med et komplet kortspil har vi valgt at given en boolsk parameter. Gennem konstanten HELT_KORTSPIL falder også denne konstruktor helt fornuftig på plads (se selv herunder).

Her følger så min udgave af klassen Kortbunke, udvidet med nogle ekstra nyttige operationer i forhold til opgavens ordlyd (lavet med henblik på smidig programmering af kortspillet Krig i en efterfølgende opgave):

import java.util.*;

class Kortbunke {
  private List bunke; 

  /** Lav en tom kortbunke */
  public Kortbunke (){
    bunke = new ArrayList();
  }

  /** Lav en kortbunk med bestemte kort, repræsenteret som en liste */
  public Kortbunke (List listeAfKort){
    // Her burde vi nok checke typen af kort
    bunke = (ArrayList)((ArrayList)listeAfKort).clone();
  }

  /** En konstant der symboliserer et fuldt spil kort */
  public static final boolean HELT_KORTSPIL = true;

  /** Lav et komplet spil kort hvis parameteren er den boolske værdi true. Hvis false, lav et tomt spil */
  public Kortbunke (boolean kompletKortSpil){
    bunke = new ArrayList();
    if (kompletKortSpil){
     for(int v = 1; v <= Spillekort.KONGE; v++)
       for (int f = Spillekort.RUDER; f <= Spillekort.SPAR; f++){
         Spillekort kort = new Spillekort(f,v);
         bunke.add(kort);
       }}
  }

  /** Fjern og returner øverste kort. Prebetingelse: bunken er ikke tom */
  public Spillekort tagØverste(){
    return (Spillekort)bunke.remove(0);
  }

  /** Læg kortet kort øverst i denne bunke */
  public void lægØverst (Spillekort kort){
    bunke.add(0,kort);
  }

  /** Læg alle kort i andenBunke øverst i denne bunke */
  public void lægØverst(Kortbunke andenBunke){
    for(Iterator kortIterator = andenBunke.bunkeIterator(); kortIterator.hasNext();){
      this.lægØverst((Spillekort)kortIterator.next());
    }
  }

  /** Læg kort nederst i denne bunke */
  public void lægNederst (Spillekort kort){
    bunke.add(kort);
  }

  /** Læg alle kort i andenBunke nederst i denne bunke */
  public void lægNederst(Kortbunke andenBunke){
    for(Iterator kortIterator = andenBunke.bunkeIterator(); kortIterator.hasNext();){
      this.lægNederst((Spillekort)kortIterator.next());
    }
  }

  /** Returner om denne bunke er tom */
  public boolean erTom(){
    return bunke.isEmpty();
  }

  /** Bland denne bunke tilfældigt */
  public void bland(){
    Collections.shuffle(bunke);
  }

  /* En klasse der indkapsler en compare funktion på to kort */
  private static class KortSortering implements Comparator{
    public int compare(Object o1, Object o2){
      Spillekort kort1 = (Spillekort) o1;
      Spillekort kort2 = (Spillekort) o2;
      if (kort1.ligMed(kort2))
        return 0;
      else if (kort1.totalMindreEnd(kort2))
        return -1;
      else
        return 1;
    }
  }

  /** Sorter denne bunke ved brug af KortSortering's compare operation */
  public void sorter(){
    Collections.sort(bunke,new KortSortering());
  }

  /** Returner en iterator, som tillader et gennemløb af bunken */
  public Iterator bunkeIterator(){
    return bunke.listIterator();
  }

  /** Returner en streng præsentation af denne kortbunke */
  public String toString(){
    return bunke.toString();
  }

  /** Tøm denne bunke */
  public void tøm(){
    bunke.clear();
  }

  /** Returner antallet af kort i denne bunke */
  public int antalKort(){
   return bunke.size();
  }
}

Her er endvidere et link til det rene Java program med alle nødvendige klasser samlet på én fil .


11.2   Krig - et simpelt kortspil  

I forlængelse af opgaven om Spillekort og opgaven om Kortbunke vil vi i denne opgave programmere en applikation, som anvender et spil kort til at gennemføre et kortspil, som mange børn kalder 'Krig'

Spillet spilles af to spillere. I denne opgave spiller Computeren begge spillere. Der er altså tale om ultimativ effektivisering, idet vi nu kan spille Krig på under et sekund...

Kortene deles ligeligt mellem to spillere. Hver spiller har en bunke kort foran sig. Spillet går ud på at erobre alle modspillerens kort. Spillet forløber således:

Hver spiller tager det øverste kort. Den spiller, som har det største kort vinder begge kort. Vinderen lægger de vundne kort i bunden af sin bunke. Hvis to kort er af samme størrelse er der 'krig'. Hver spiller putter de to ens kort i en pulje, som forstørres med yderligere to kort fra hver spiller (øverst fra bunken). Dernæst sammenlignes de øverste kort fra de to resterende bunker igen, og den spiller der har det største vinder hele puljen. Er der igen lighed gentages krigen, med det resultat at puljen bliver større og større. Den spiller der først mister alle sine kort taber spillet.

Skriv programmet således at der bliver udskrevet et spor af spillet, der fortæller om hver duel og hver krig. Det er en fordel at man løbende kan se hvor mange kort hver spiller har tilbage.

Solution
Her følger min udgave af spillet 'Krig':

import java.util.*;

class Krig {

  /* Et program som adminstrerer et spil kort og to spillere med hver sine kort.
     Programmet gennemfører et simpelt spil, som mange børn kalder 'Krig'.
     Denne version af programmet er basert på klassen Kortbunke
  */

  // Stakkene af kort repræsenteres som Kortbunkt objekter
  static Kortbunke kortSpil = new Kortbunke(Kortbunke.HELT_KORTSPIL);  // et fuld spil kort
  static Kortbunke spillerEt = new Kortbunke();
  static Kortbunke spillerTo = new Kortbunke();

  // En stak af kort der bruges når der opstår en krigssituation.
  static Kortbunke krigsPulje = new Kortbunke();
 
  // En variabel der peger på næste spillers Kortbunke.
  // Dette for at holde styr på, hvem der skal have næste kort når kortene deles ud mv.
  static Kortbunke næsteSpiller; 

  // Skift fra en spiller til den anden
  private static void skift(){
    if (næsteSpiller == spillerEt)
       næsteSpiller = spillerTo;
    else næsteSpiller = spillerEt;
  }

  // Udskriv situtationen for spillerne i termer af øverste kort og antal kort
  private static void situation(Spillekort k1, int n1, Spillekort k2, int n2){
    System.out.print("Spiller 1: " + k1 + "(" + n1 + ")" +  "   " + "Spiller 2: " + k2 + "(" + n2 + ")" + "     ");
  }
  
  public static void main (String[] args){

    // Bland kortene i hele spillet...
    kortSpil.bland();

    næsteSpiller = spillerEt;
    
    // Skriv dem lige ud så vi kan se dem...
    System.out.println("Kortbunken:"); 
    System.out.println(kortSpil);

    // Del kortene ud til spiller et og to:
    for (int i = 1; i <= 52; i++){
      Spillekort etKort = kortSpil.tagØverste();
      næsteSpiller.lægØverst(etKort);
      skift();
    }

    System.out.println("Spiller et's kort:");
    System.out.println(spillerEt);

    System.out.println("Spiller to's kort:");
    System.out.println(spillerTo);

    // Skriv dem lige ud så vi kan se at bunken nu er tom...
    System.out.println("Kortbunken:");
    System.out.println(kortSpil);


    // Og nu igang med spillet:
    while (!spillerEt.erTom() && !spillerTo.erTom()){
      Spillekort spillerEtsKort = spillerEt.tagØverste();
      Spillekort spillerTosKort = spillerTo.tagØverste();

      situation(spillerEtsKort, spillerEt.antalKort()+1, spillerTosKort, spillerTo.antalKort()+1); 

      if (spillerEtsKort.mindreEnd(spillerTosKort)){
         // Spiller to vinder de to kort. Læg de to kort nederst i bunken.
         spillerTo.lægNederst(spillerEtsKort);  spillerTo.lægNederst(spillerTosKort);
         System.out.println("Spiller to vinder");}
      else if (spillerTosKort.mindreEnd(spillerEtsKort)){
         // Spiller et vinder de to kort. Læg de to kort nederst i bunken.
         spillerEt.lægNederst(spillerTosKort);  spillerEt.lægNederst(spillerEtsKort);
         System.out.println("Spiller et vinder");}
      else {
         // Krig:
         System.out.println("KRIG KRIG KRIG KRIG KRIG KRIG KRIG");
 
         // Læg de to kort i krigsPuljen
         krigsPulje.lægØverst(spillerEtsKort);  krigsPulje.lægØverst(spillerTosKort); 
          
         // Hver spiller putter to kort mere i krigsPuljen (hvis de har to kort, vel at mærke)...
         if (!spillerEt.erTom()) krigsPulje.lægØverst(spillerEt.tagØverste());
         if (!spillerEt.erTom()) krigsPulje.lægØverst(spillerEt.tagØverste());
         if (!spillerTo.erTom()) krigsPulje.lægØverst(spillerTo.tagØverste());
         if (!spillerTo.erTom()) krigsPulje.lægØverst(spillerTo.tagØverste());


         // Afgør krigen ved at trække et kort mere og sammenlign:
         if (!spillerEt.erTom() && !spillerTo.erTom()){
            Spillekort spillerEtsAfgørendeKort = spillerEt.tagØverste();
            Spillekort spillerTosAfgørendeKort = spillerTo.tagØverste();
            
            if (spillerEtsAfgørendeKort.mindreEnd(spillerTosAfgørendeKort)){
               // Spiller to får krigsPuljen og de afgørende kort
               krigsPulje.lægØverst(spillerEtsAfgørendeKort); krigsPulje.lægØverst(spillerTosAfgørendeKort); 
               krigsPulje.bland();
               spillerTo.lægNederst(krigsPulje);
               krigsPulje.tøm();
               System.out.println("Spiller to vinder krigen");}
            else if (spillerTosAfgørendeKort.mindreEnd(spillerEtsAfgørendeKort)){
               // Spiller et får krigsPuljen og de afgørende kort
               krigsPulje.lægØverst(spillerEtsAfgørendeKort); krigsPulje.lægØverst(spillerTosAfgørendeKort); 
               krigsPulje.bland();
               spillerEt.lægNederst(krigsPulje);
               krigsPulje.tøm();
               System.out.println("Spiller et vinder krigen");}
            else {
               System.out.print("GENTAGEN KRIG: "); 
               System.out.println(spillerEtsAfgørendeKort + " vs " + spillerTosAfgørendeKort);

               // Vi laver en iterativ løsning på dobbeltkrigsproblemet.
               // De to afgørende kort lægges tilbage til spiller et og to.
               // Når spillet kører næste runde vil der umiddelbar opstå krig igen
               // Bemærk at puljen kun nulstilles når en af spillerne har vundet krigen.

               spillerEt.lægØverst(spillerEtsAfgørendeKort);
               spillerTo.lægØverst(spillerTosAfgørendeKort);
            }
         }

      }

   }

   if (spillerEt.erTom()) System.out.println("SPILLER TO ER VINDER");
   else System.out.println("SPILLER ET ER VINDER");
         
  }
}

Her er endvidere et link til det rene Java program med alle klasser samlet på én fil .


Generated: Monday March 31, 2008, 12:09:20
on the system cs-unix