Fizz Buzz問題を出題するサンプルをJavaFXで作った


Fizz Buzz問題というプログラミングの練習問題があります。番号に従い、以下のように返事をするという問題です。

| case  |  番号i        |  返事      |
|:------|:--------------|:-----------|
| 1     | 3かつ5の倍数  |  FizzBuzz  |
| 2     | 3の倍数       |  Fizz      |
| 3     | 5の倍数       |  Buzz      |
| 4     | 上記以外      |  i         |

この記事は出題側として考えてプログラミングしてみましたという一例です。

#出題の仕方

次のように出題するようにしました。

+ 出題数は5問。
+ 出題する番号は2から99までランダム。
+ 2問目以降は、前問と異なるcaseの問題を出題する。

JavaFXを使い、次のように動くものにしました。

+ ダイアログを出力し、答えを選択してもらう形式にする。
+ 5問おわったら、結果を表に出力する。
+ 正答率を円グラフで表示する。

#プログラミング

サンプルをJavaFXでプログラミングしました。出題するためのクラスでIterator<E>を実装しました。

以下のような画面です。

![start.png](https://qiita-image-store.s3.amazonaws.com/0/133921/1fcbb40e-8e49-94f5-eb9d-0a6a72495f69.png)


![dialog.png](https://qiita-image-store.s3.amazonaws.com/0/133921/ea157f7e-dd06-7708-8949-cc464804f465.png)


![result.png](https://qiita-image-store.s3.amazonaws.com/0/133921/f1b02605-fd33-4274-de92-afcb3d40c37a.png)


##fizzbuzzパッケージ

###Const.java

Fizz Buzzに関する数値などを定義したクラスです。

```java:Const.java
package fizzbuzz;

public class Const {

    private Const() {}

    public static final int NUMBER_OF_PROBLEMS = 5;

    public static final int NUMBER_OF_FIZZBUZZ = 15;
    public static final int NUMBER_OF_FIZZ = 3;
    public static final int NUMBER_OF_BUZZ = 5;

    public static final String STR_OF_FIZZBUZZ = "FizzBuzz";
    public static final String STR_OF_FIZZ = "Fizz";
    public static final String STR_OF_BUZZ = "Buzz";

    public static final String SYMBOL_JUDGMENT_RESULT_CORRECT = "○";
    public static final String SYMBOL_JUDGMENT_RESULT_INCORRECT = "×";

}
```

###FizzBuzz.java

Fizz Buzz問題の答えを取得するクラスです。

```java:FizzBuzz.java
package fizzbuzz;

import static fizzbuzz.Const.*;

public class FizzBuzz {

    private FizzBuzz() {}

    public static String of(int i) {

        String s = null;

        if (i % NUMBER_OF_FIZZBUZZ == 0) {
            s = "FizzBuzz";
        } else if (i % NUMBER_OF_FIZZ == 0) {
            s = "Fizz";
        } else if (i % NUMBER_OF_BUZZ == 0) {
            s = "Buzz";
        } else {
            s = String.valueOf(i);
        }

        return s;

    }

}
```

###FizzBuzzProblem.java

Fizz Buzz問題のひとつひとつを定義するクラスです。

```java:FizzBuzzProblem.java
package fizzbuzz;

import static fizzbuzz.Const.*;

public class FizzBuzzProblem {

    /** 問題番号 */
    private int problemNumber;

    /** 回答 */
    private String answer;

    /** 正解 */
    private String correctAnswer;

    /** 判定結果 */
    private String judgmentResult;

    public FizzBuzzProblem(int problemNumber, String answer) {
        this.problemNumber = problemNumber;
        this.answer = answer;
        judge();
    }

    private void judge() {
        correctAnswer = FizzBuzz.of(problemNumber);

        if (correctAnswer.equals(answer)) {
            judgmentResult = SYMBOL_JUDGMENT_RESULT_CORRECT;
        } else {
            judgmentResult = SYMBOL_JUDGMENT_RESULT_INCORRECT;
        }
    }

    public int getProblemNumber() {
        return problemNumber;
    }

    public String getAnswer() {
        return answer;
    }

    public String getCorrectAnswer() {
        return correctAnswer;
    }

    public String getJudgmentResult() {
        return judgmentResult;
    }

}
```

###FizzBuzzProblemIterator.java

問題を出題するためのクラスです。Iterator<String>を実装しています。フィールドに定義しているリストは正答率の集計で使用します。

```java:FizzBuzzProblemIterator.java
package fizzbuzz;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

public class FizzBuzzProblemIterator implements Iterator<String> {

    private int maxCount;
    private int count;

    private int currentProblemNumber;

    private List<FizzBuzzProblem> problems = new ArrayList<>();

    public FizzBuzzProblemIterator(int maxCount) {
        this.maxCount = maxCount;
    }

    public List<FizzBuzzProblem> getProblems() {
        return problems;
    }

    public void setAnswer(String answer) {
        problems.add(new FizzBuzzProblem(currentProblemNumber, answer));
    }

    @Override
    public boolean hasNext() {
        return count < maxCount;
    }

    @Override
    public String next() {
        count ++;

        int problemNumber;

        Random random = new Random();

        if (currentProblemNumber == 0) {
            // 初出題
            problemNumber = random.nextInt(98) + 2;

        } else {
            // 2問目以降
            FizzBuzzProblem p = problems.get(problems.size() - 1);
            do {
                problemNumber = random.nextInt(98) + 2;
            } while (p.getCorrectAnswer().equals(FizzBuzz.of(problemNumber)));
        }

        // setAnswer()メソッド実行時に使うため、保持する。
        currentProblemNumber = problemNumber;

        return String.valueOf(problemNumber);
    }

    public int getMaxCount() {
        return maxCount;
    }

    public int getCount() {
        return count;
    }

    public int getCurrentProblemNumber() {
        return currentProblemNumber;
    }

}
```

##applicationパッケージ

###Main.java

画面を出力するクラスです。

```java:Main.java
package application;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        BorderPane root;
        try {
            root = (BorderPane)FXMLLoader.load(getClass().getResource("app.fxml"));
            Scene scene = new Scene(root);
            primaryStage.setTitle("FizzBuzz");
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
```

###app.fxml

コンポーネントの配置です。PieChart要素を定義しています。回答が終わった後データを追加する方式です。

```xml:app.fxml
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.chart.PieChart?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.Pane?>

<BorderPane xmlns="http://javafx.com/javafx/" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.AppController">
  <center>
    <Pane prefHeight="460.0" prefWidth="350.0" BorderPane.alignment="CENTER">
      <children>
        <Button layoutX="15.0" layoutY="15.0" mnemonicParsing="false" onAction="#start" text="開始" />
        <TableView fx:id="tableView" layoutX="15.0" layoutY="45.0" prefHeight="146.0" prefWidth="320.0">
          <columns>
            <TableColumn fx:id="tableColumProblemNumber" prefWidth="75.0" text="番号" />
            <TableColumn fx:id="tableColumnCorrectAnswer" prefWidth="75.0" text="正解" />
            <TableColumn fx:id="tableColumAnswer" prefWidth="75.0" text="回答" />
            <TableColumn fx:id="tableColumnJudgmentResult" prefWidth="75.0" text="判定" />
          </columns>
        </TableView>
        <PieChart fx:id="pieChart" layoutX="15.0" layoutY="200.0" prefHeight="240.0" prefWidth="320.0"
            title="正答率" style="-fx-border-width: 2; -fx-border-color: white;" />
      </children>
    </Pane>
  </center>
</BorderPane>
```

###AppController.java

ボタンを押下したときの動作を定義するクラスです。出題の箇所ではChoiceDialogを使っています。コンボボックスを表示することができます。

```java:AppController.java
package application;

import static fizzbuzz.Const.*;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.chart.PieChart;
import javafx.scene.control.ChoiceDialog;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import fizzbuzz.FizzBuzzProblem;
import fizzbuzz.FizzBuzzProblemIterator;

public class AppController implements Initializable {

    @FXML TableView<FizzBuzzProblem> tableView;
    @FXML TableColumn<FizzBuzzProblem, Integer> tableColumProblemNumber;
    @FXML TableColumn<FizzBuzzProblem, String> tableColumnCorrectAnswer;
    @FXML TableColumn<FizzBuzzProblem, String> tableColumAnswer;
    @FXML TableColumn<FizzBuzzProblem, String> tableColumnJudgmentResult;

    @FXML PieChart pieChart;

    @Override
    public void initialize(java.net.URL location, java.util.ResourceBundle resources) {

        tableColumProblemNumber.setCellValueFactory(new PropertyValueFactory<FizzBuzzProblem, Integer>("problemNumber"));
        tableColumnCorrectAnswer.setCellValueFactory(new PropertyValueFactory<FizzBuzzProblem, String>("correctAnswer"));
        tableColumAnswer.setCellValueFactory(new PropertyValueFactory<FizzBuzzProblem, String>("answer"));
        tableColumnJudgmentResult.setCellValueFactory(new PropertyValueFactory<FizzBuzzProblem, String>("judgmentResult"));

    }

    @FXML
    private void start(ActionEvent event) {

        // 出題
        FizzBuzzProblemIterator iterator = new FizzBuzzProblemIterator(NUMBER_OF_PROBLEMS);
        while (iterator.hasNext()) {
            String[] data = { STR_OF_FIZZBUZZ, STR_OF_FIZZ, STR_OF_BUZZ, iterator.next() };
            List<String> dialogData = Arrays.asList(data);

            ChoiceDialog<String> dialog = new ChoiceDialog<String>(dialogData.get(0), dialogData);
            dialog.setTitle("Fizz Buzz問題");
            dialog.setHeaderText(String.format("%d/%d問目 %d", iterator.getCount(), iterator.getMaxCount(), iterator.getCurrentProblemNumber()));
            Optional<String> answer = dialog.showAndWait();
            iterator.setAnswer(answer.isPresent() ? answer.get() : "");
        }

        // 結果取得
        List<FizzBuzzProblem> fizzBuzzProblems  =iterator.getProblems();

        // 表に反映
        ObservableList<FizzBuzzProblem> listOfTableView = tableView.getItems();
        listOfTableView.clear();
        listOfTableView.addAll(fizzBuzzProblems);

        // グラフに反映
        int numOfCorrect = 0;
        int numOfIncorrect = 0;
        for (FizzBuzzProblem p : fizzBuzzProblems) {
            if (p.getAnswer().equals(p.getCorrectAnswer())) {
                numOfCorrect ++;
            } else {
                numOfIncorrect ++;
            }
        }

        ObservableList<PieChart.Data> pieChartData =
                FXCollections.observableArrayList(
                new PieChart.Data("正解", numOfCorrect),
                new PieChart.Data("不正解", numOfIncorrect));
        pieChart.setData(pieChartData);

    }

}
```