Groundwork Groundwork

Practice: a launch console

The model rocket club will not fly unless four readings are within limits, and at the moment somebody checks them on a clipboard. You are going to build the console that does it, one reading at a time, ending with a program that reads all four and gives a verdict.

The limits are:

ReadingSafe
Windunder 30 mph
Temperature2 to 35 °C, ends included
Cloud ceiling2000 feet or more
Fuel95 percent or more

Everything here uses Chapters 1 to 4: variables, IO.readln, Double.parseDouble and Integer.parseInt, arithmetic, comparisons, &&, || and !, String.format, if, else if and else, and try and catch. No loops—so each program asks for each reading exactly once, in the order wind, temperature, ceiling, fuel.

Each step names the exact line it must print. The wording around it is yours.

Steps

Step 1. Ask for the wind speed in mph and print it back to one decimal place, as Wind: 12.5 mph. Remember that IO.readln gives back a String.

Show a solution
void main() {
    double wind = Double.parseDouble(IO.readln("Wind speed in mph? ").strip());
    IO.println("Wind: " + String.format("%.1f", wind) + " mph");
}

Double.parseDouble rather than Integer.parseInt, because a wind speed has a decimal point. %.1f keeps the display steady at one decimal place, whatever was typed: 7.46 shows as 7.5, and 8 as 8.0.

Step 2. Now judge it. Print Wind: OK when the speed is under 30, and Wind: TOO STRONG otherwise. 30 exactly is too strong.

Show a solution
void main() {
    double wind = Double.parseDouble(IO.readln("Wind speed in mph? ").strip());

    if (wind < 30) {
        IO.println("Wind: OK");
    } else {
        IO.println("Wind: TOO STRONG");
    }
}

Under 30 means <, not <=. At exactly 30 the club stays on the ground.

Step 3. Ask for the temperature in °C and print Temperature: OK when it is from 2 to 35 with both ends allowed, and Temperature: OUT OF RANGE otherwise. One if, with &&.

Show a solution
void main() {
    double temperature = Double.parseDouble(IO.readln("Temperature in C? ").strip());

    if (temperature >= 2 && temperature <= 35) {
        IO.println("Temperature: OK");
    } else {
        IO.println("Temperature: OUT OF RANGE");
    }
}

Two comparisons joined with &&. Both ends count as safe, so both are >= and <=.

Step 4. Ask for the cloud ceiling in feet and sort it into three bands with an else if chain: Ceiling: CLEAR at 5000 or above, Ceiling: MARGINAL from 2000 to 4999, and Ceiling: TOO LOW below 2000. Put the narrow test before the broad one, or one branch will never run.

Show a solution
void main() {
    double ceiling = Double.parseDouble(IO.readln("Cloud ceiling in feet? ").strip());

    if (ceiling >= 5000) {
        IO.println("Ceiling: CLEAR");
    } else if (ceiling >= 2000) {
        IO.println("Ceiling: MARGINAL");
    } else {
        IO.println("Ceiling: TOO LOW");
    }
}

The narrow test goes first. With 2000 tested before 5000, a clear sky reads as marginal and the CLEAR branch never runs.

Step 5. Ask for the fuel as a whole-number percentage, but do not trust it: convert it with Integer.parseInt inside a try, and print Fuel: 97% with the number that was typed. If Integer.parseInt throws, catch the NumberFormatException and print Fuel: NOT A NUMBER instead.

Show a solution
void main() {
    String typed = IO.readln("Fuel percentage? ");

    try {
        int fuel = Integer.parseInt(typed.strip());
        IO.println("Fuel: " + fuel + "%");
    } catch (NumberFormatException e) {
        IO.println("Fuel: NOT A NUMBER");
    }
}

The conversion is the check: when Integer.parseInt cannot read what was typed, it throws, and the catch prints the warning. Without the try, Integer.parseInt on full would stop the program before it could print anything.

Step 6. Put all four together. Ask for all four readings in order, then print one verdict line: LAUNCH: GO when every reading is safe, LAUNCH: NO GO when any of them is not. Fuel that is not a number counts as not safe.

Show a solution
void main() {
    double wind = Double.parseDouble(IO.readln("Wind speed in mph? ").strip());
    double temperature = Double.parseDouble(IO.readln("Temperature in C? ").strip());
    double ceiling = Double.parseDouble(IO.readln("Cloud ceiling in feet? ").strip());
    String typed = IO.readln("Fuel percentage? ");

    boolean windOk = wind < 30;
    boolean temperatureOk = temperature >= 2 && temperature <= 35;
    boolean ceilingOk = ceiling >= 2000;
    boolean fuelOk;
    try {
        fuelOk = Integer.parseInt(typed.strip()) >= 95;
    } catch (NumberFormatException e) {
        fuelOk = false;
    }

    if (windOk && temperatureOk && ceilingOk && fuelOk) {
        IO.println("LAUNCH: GO");
    } else {
        IO.println("LAUNCH: NO GO");
    }
}

Naming each condition keeps the verdict line readable, and the names are reused in the next step. fuelOk is created with no value, and the try and the catch each give it one, as every branch of the grade chain gives grade one: fuel that is not a number is not safe.

Step 7. A bare refusal is no use to anyone. After a LAUNCH: NO GO, print the first thing that failed, checked in the order wind, temperature, ceiling, fuel, as HOLD: wind, HOLD: temperature, HOLD: ceiling or HOLD: fuel. Print nothing extra when the verdict is LAUNCH: GO.

Show a solution
void main() {
    double wind = Double.parseDouble(IO.readln("Wind speed in mph? ").strip());
    double temperature = Double.parseDouble(IO.readln("Temperature in C? ").strip());
    double ceiling = Double.parseDouble(IO.readln("Cloud ceiling in feet? ").strip());
    String typed = IO.readln("Fuel percentage? ");

    boolean windOk = wind < 30;
    boolean temperatureOk = temperature >= 2 && temperature <= 35;
    boolean ceilingOk = ceiling >= 2000;
    boolean fuelOk;
    try {
        fuelOk = Integer.parseInt(typed.strip()) >= 95;
    } catch (NumberFormatException e) {
        fuelOk = false;
    }

    if (windOk && temperatureOk && ceilingOk && fuelOk) {
        IO.println("LAUNCH: GO");
    } else {
        IO.println("LAUNCH: NO GO");
        if (!windOk) {
            IO.println("HOLD: wind");
        } else if (!temperatureOk) {
            IO.println("HOLD: temperature");
        } else if (!ceilingOk) {
            IO.println("HOLD: ceiling");
        } else {
            IO.println("HOLD: fuel");
        }
    }
}

else if rather than four separate ifs, so only the first failure is reported. Fuel is the else because it is the only one left.

Step 8. Finish the console: ask for the four readings, print the four status lines from steps 2 to 5, then the verdict, then the HOLD: line if there is one.

Show a solution
// The launch console: four readings, four status lines, a verdict and its reason.

void main() {
    double wind = Double.parseDouble(IO.readln("Wind speed in mph? ").strip());
    double temperature = Double.parseDouble(IO.readln("Temperature in C? ").strip());
    double ceiling = Double.parseDouble(IO.readln("Cloud ceiling in feet? ").strip());
    String typed = IO.readln("Fuel percentage? ");

    boolean windOk = wind < 30;
    boolean temperatureOk = temperature >= 2 && temperature <= 35;
    boolean ceilingOk = ceiling >= 2000;

    if (windOk) {
        IO.println("Wind: OK");
    } else {
        IO.println("Wind: TOO STRONG");
    }

    if (temperatureOk) {
        IO.println("Temperature: OK");
    } else {
        IO.println("Temperature: OUT OF RANGE");
    }

    if (ceiling >= 5000) {
        IO.println("Ceiling: CLEAR");
    } else if (ceilingOk) {
        IO.println("Ceiling: MARGINAL");
    } else {
        IO.println("Ceiling: TOO LOW");
    }

    boolean fuelOk;
    try {
        int fuel = Integer.parseInt(typed.strip());
        IO.println("Fuel: " + fuel + "%");
        fuelOk = fuel >= 95;
    } catch (NumberFormatException e) {
        IO.println("Fuel: NOT A NUMBER");
        fuelOk = false;
    }

    if (windOk && temperatureOk && ceilingOk && fuelOk) {
        IO.println("LAUNCH: GO");
    } else {
        IO.println("LAUNCH: NO GO");
        if (!windOk) {
            IO.println("HOLD: wind");
        } else if (!temperatureOk) {
            IO.println("HOLD: temperature");
        } else if (!ceilingOk) {
            IO.println("HOLD: ceiling");
        } else {
            IO.println("HOLD: fuel");
        }
    }
}

Nothing new, only assembly: the status lines from steps 2 to 5, then the verdict, then the reason. The fuel's try both prints its status line and decides fuelOk, so the fuel is converted once.