Lösningsförslag till några uppgifter

Omkastade ortsnamn

import se.lth.cs.pt.io.*;
import se.lth.cs.pt.clock.*;

import java.util.*;
import java.io.*;

class Towns {

    public static void main(String[] args) {
        new Towns().run();
    }

    List<String> towns = new LinkedList<String>();
    StopWatch sw = new StopWatch();
    Random rng = new Random();

    void run() {
        inputTowns();
        shuffleTowns();
        query();
    }

    void inputTowns() {
        Scanner s = getScanner();
        while (s.hasNextLine()) {
            String town = s.nextLine();
            if (town.length() > 0) {
                towns.add(town);
            }
        }
    }

    Scanner getScanner() {
        String filename = Keyboard.nextLine("Landskap: ");
        try {
            return new Scanner(new File(filename));
        } catch (Exception e) {
            System.out.println("Det finns ingen sådan fil.");
            System.exit(1);
            return null;   // för kompilatorns skull (vi kommer aldrig hit!)
        }
    }

    void shuffleTowns() {
        for (int k = 0; k < towns.size(); k++) {
            int index = rng.nextInt(towns.size());
            towns.add(towns.remove(index));
        }
    }

    void query() {
        for (String town : towns) {
            queryOne(town);
        }
    }

    void queryOne(String town) {
        sw.restart();
        String guess = Keyboard.nextLine(scramble(town) + ": ");
        sw.stop();
        if (guess.toUpperCase().equals(town.toUpperCase())) {
            System.out.printf("Rätt! (%.1f s)\n", sw.getValue()/1000.0);
        } else {
            System.out.println("Fel, rätt svar är " + town);
        }
    }

    String scramble(String str) {
        StringBuilder sb = new StringBuilder(str.toUpperCase());
        for (int i = sb.length()-1; i > 0; i--) {
            int r = rng.nextInt(i+1);
            char saved = sb.charAt(i);
            sb.setCharAt(i,sb.charAt(r));
            sb.setCharAt(r,saved);
        }
        return sb.toString();
    }
}

Vi skulle naturligtvis även kunna använda klasser, exempelvis:

import se.lth.cs.pt.io.*;
import se.lth.cs.pt.clock.*;

import java.util.*;
import java.io.*;

class Towns {

    public static void main(String[] args) {
        new Towns().run();
    }

    List<Town> towns = new LinkedList<Town>();
    double bestTime = Double.POSITIVE_INFINITY;
    double worstTime = 0;
    StopWatch sw = new StopWatch();

    void run() {
        inputTowns();
        test();
        report();
    }

    void inputTowns() {
        String filename = Keyboard.nextLine("Landskap: ");
        Scanner infile = null;
        try {
            infile = new Scanner(new File(filename));
        } catch (Exception e) {
            System.out.println("Fel på indatafilen.");
            return;
        }
        for (;;) {
            String town = infile.nextLine();
            if (town.length() == 0) {
                break;
            }
            towns.add(new Town(town));
        }
        Collections.shuffle(towns);    // standardklass (överkurs, se 
                                       // annars shuffle() ovan).
    }

    void test() {
        for (Town town : towns) {
            askOne(town);
        }
    }

    void askOne(Town town) {
        sw.reset();
        sw.start();
        String guess = Keyboard.nextLine(town.getScrambled() + ": ");
        sw.stop();
        double time = sw.getValue()/1000.0;
        worstTime = Math.max(worstTime, time);
        if (guess.toUpperCase().equals(town.getName().toUpperCase())) {
            bestTime = Math.min(bestTime, time);
            System.out.printf("Rätt! (%.1f s)\n", time);
        } else {
            System.out.println("Nix, rätt svar är " + town.getName());
        }
    }

    void report() {
        System.out.printf("Bästa tid: %.1f s\n", bestTime);
        System.out.printf("Sämsta tid: %.1f s\n", worstTime);
    }
}

class Town {

    private String name;
    private String scrambled;

    public Town  (String name) {
        this.name = name;
        scrambled = scramble(name);
    }

    public String getName() {
        return name;
    }

    public String getScrambled() {
        return scrambled;
    }

    private static Random rng = new Random();

    private static String scramble(String name) {
        StringBuilder sb = new StringBuilder(name.toUpperCase());
        for (int i = sb.length()-1; i > 0; i--) {
            int r = rng.nextInt(i);
            char tmp = sb.charAt(i);
            sb.setCharAt(i, sb.charAt(r));
            sb.setCharAt(r, tmp);
        }
        return sb.toString();
    }
}

Inverso

För att göra programmet lite kortare kan vi använda klassen GameTemplate för DotWindow - vi slipper då skriva en egen händelseloop.

När vi anropar run()-operationen i GameTemplate kommer först setup() att anropas, och därefter anropas mouseClicked() varje gång användaren klickar med musen. Vi ärver ett DotWindow-attribut w från GameTemplate:

import se.lth.cs.pt.dotwindow.*;
import se.lth.cs.pt.game.dotwindow.*;
import java.util.*;

class InversoGame extends GameTemplate {

    protected int tries = 0;
    protected int size = 3;
    protected int dotSize = 40;
    protected Color
        backgroundColor = new Color("#98BF7C"),
        lightColor = new Color("#CAFFA5"),
        darkColor = new Color("#627C4F");

    public void setup() {
        w = new DotWindow(size, size, dotSize);
        w.useCircularDots(backgroundColor);
        w.checkMouse(true, false, false, false, false);
        randomize();
    }

    private void randomize() {
        Random rng = new Random();
        for (int x = 0; x < size; x++) {
            for (int y = 0; y < size; y++) {
                if (rng.nextBoolean()) {
                    w.setDot(x,y,lightColor);
                } else {
                    w.setDot(x,y,darkColor);
                }
            }
        }
    }

    public void mouseClicked(int xPos, int yPos) {
        tries++;
        for (int x = xPos-1; x <= xPos+1; x++) {
            for (int y = yPos-1; y <= yPos+1; y++) {
                if (isInside(x,y)) {
                    w.setDot(x,y,oppositeOf(w.getDot(x,y)));
                }
            }
        }
        if (allLight()) {
            System.out.printf("Du klarade det på %d försök.\n", tries);
            quit();
        }
    }

    private Color oppositeOf(Color color) {
        if (color.equals(lightColor)) {
            return darkColor;
        }
        return lightColor;
    }

    private boolean isInside(int x, int y) {
        return between(0,x,size) && between(0,y,size);
    }

    private boolean between(int min, int value, int max) {
        // Kollar om min <= value < max. Denna lite egendomliga
        // asymmetri är faktiskt något slags standard i C och Java.
        return min <= value && value < max;
    }

    private boolean allLight() {
        for (int x = 0; x < size; x++) {
            for (int y = 0; y < size; y++) {
                if (w.getDot(x,y).equals(darkColor)) {
                    return false;
                }
            }
        }
        return true;
    }
}

Vi kan starta programmet från följande huvudprogram:

class Inverso {

    public static void main(String[] args) {
        new Inverso().run();
    }

    void run() {
        new InversoGame().run();
    }
}

eller bara:

class Inverso {

    public static void main(String[] args) {
        new InversoGame().run();
    }
}

Sänka skepp

Även här kan vi använda GameTemplate-klassen för DotWindow, som vi stötte på i föregående uppgift:

class Battleship extends GameTemplate {

    Random rng = new Random();
    Ship[][] board;
    int size;
    Color
        backgroundColor = new Color("#000668"),
        emptyColor = new Color("#171FB7"),
        aircraftCarrierColor = new Color("#C10811"),
        battleshipColor = new Color("#E44048"),
        cruiserColor = new Color("#FD7D83"),
        patrolBoatColor = new Color("#E1979A"),
        submarineColor = new Color("#BD8183"),
        missColor = new Color("#0B1292");


    Ship[] ships = {
        new Ship(5, "an Aircraft Carrier", aircraftCarrierColor),
        new Ship(4, "a Battleship", battleshipColor),
        new Ship(3, "a Cruiser", cruiserColor),
        new Ship(2, "a Patrol Boat", patrolBoatColor),
        new Ship(3, "a Submarine", submarineColor)
    };

    void setup() {
        int size = Keyboard.nextInt("Storlek på planen: ");
        board = new Ship[size][size];
        w = new DotWindow(size,size,30);
        w.useCircularDots(backgroundColor);
        w.fillWith(emptyColor);
        placeShips();
        w.checkMouse(true,false,false,false,false);
    }

    void placeShips() {
        for (Ship ship : ships) {
            ship.place();
        }
    }

    void mouseClicked(int x, int y) {
        Ship s = board[x][y];
        if (s != null) {
            s.hit();
            board[x][y] = null;
            if (s.sunk()) {
                System.out.printf("You sank %s!\n", s.getName());
                s.display();
            }
        } else {
            w.setDot(x,y,missColor);
        }
    }


    class Ship {
        
        final int[] dx = {1, 0};
        final int[] dy = {0, 1};

        int size, originalSize;
        String name;
        Color color;
        int dir, x, y;

        Ship  (int size, String name, Color color) {
            this.size = size;
            originalSize = size;
            this.name = name;
            this.color = color;
        }

        String getName() {
            return name;
        }

        void hit() {
            size--;
        }

        boolean sunk() {
            return size == 0;
        }

        void display() {
            for (int i = 0; i < originalSize; i++) {
                w.setDot(x + dx[dir]*i, y + dy[dir]*i, color);
            }
        }

        void place() {
            outer:
            for (;;) {
                dir = rng.nextInt(2);
                x = rng.nextInt(board.length - size*dx[dir]);
                y = rng.nextInt(board.length - size*dy[dir]);
                for (int i = 0; i < size; i++) {
                    if (board[x + dx[dir]*i][y + dy[dir]*i] != null) {
                        continue outer;
                    }
                }
                for (int i = 0; i < size; i++) {
                    board[x + dx[dir]*i][y + dy[dir]*i] = this;
                }
                return;
            }
        }
    }
}

Översätt tal

Uppgiften blir enklare att lösa om vi utnyttar det faktum att vi alltid bryter ner tal i grupper om 3 siffror:

import se.lth.cs.pt.io.*;

class Translate {

    public static void main(String[] args) {
        new Translate().run();
    }

    void run() {
        for (;;) {
            int number = Keyboard.nextInt("Mata in ett tal: ");
            if (number <= 0) {
                break;
            }
            System.out.println(translate(number));
        }
    }

    final int
        TEN = 10,
        HUNDRED = 10*TEN,
        THOUSAND = 10*HUNDRED,
        MILLION = 1000*THOUSAND,
        BILLION = 1000*MILLION;
    final String[]
        TENS = {"tjugo", "trettio", "fyrtio", "femtio",
                "sextio", "sjuttio", "åttio", "nittio"},
        TEENS = {"tio", "elva", "tolv", "tretton", "fjorton",
                 "femton", "sexton", "sjutton", "arton", "nitton"},
        DIGITS = {"", "ett", "två", "tre", "fyra", "fem",
                  "sex", "sju", "åtta", "nio"};

    String translate(int number) {
        StringBuilder text = new StringBuilder();
        int billions = number / BILLION;
        if (billions > 0) {
            text.append(translateThreeDigits(billions) + "miljarder");
            number %= BILLION;
        }
        int millions = number / MILLION;
        if (millions > 0) {
            text.append(translateThreeDigits(millions) + "miljoner");
            number %= MILLION;
        }
        int thousands = number / THOUSAND;
        if (thousands > 0) {
            text.append(translateThreeDigits(thousands) + "tusen");
            number %= THOUSAND;
        }
        text.append(translateThreeDigits(number));
        return text.toString();
    }

    String translateThreeDigits(int number) {
        StringBuilder text = new StringBuilder();
        int hundreds = number / HUNDRED;
        if (hundreds > 0) {
            text.append(DIGITS[hundreds] + "hundra");
            number %= HUNDRED;
        }
        int tens = number / TEN;
        int ones = number % TEN;
        if (tens == 1) {
            text.append(TEENS[number-10]);
        } else {
            if (tens > 1) {
                text.append(TENS[tens-2]);
            }
            if (ones > 0) {
                text.append(DIGITS[ones]);
            }
        }
        return text.toString();
    }
}

Operationen translate innehåller ganska mycket upprepad kod, vi kan parametrisera det som skiljer sig åt och skriva ett underprogram istället:

    String translate(int number) {
        StringBuilder text = new StringBuilder();
        number = translateGroup(number, text, BILLION, "miljarder");
        number = translateGroup(number, text, MILLION, "miljoner");
        number = translateGroup(number, text, THOUSAND, "tusen");
        number = translateGroup(number, text, 1, "");
        return text.toString();
    }        

    int translateGroup(int number, StringBuilder text, 
                       int factor, String factorName) {
        int times = number / factor;
        if (times > 0) {
            text.append(translateThreeDigits(times) + factorName);
        }
        return number % factor;
    }