commit e2edd3b83afa2ab602e6a79cd141c7d4684018d1 Author: bytedream Date: Thu Apr 28 19:47:11 2022 +0200 Initial commit diff --git a/QuizzleDizzle.iml b/QuizzleDizzle.iml new file mode 100644 index 0000000..be6e9d0 --- /dev/null +++ b/QuizzleDizzle.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6ed1c94 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# A quiz written in java11 for school + +Has full singleplayer and hotseat support. +Multiplayer via sockets are also implemented but not working + diff --git a/lib/JColor-5.0.1.jar b/lib/JColor-5.0.1.jar new file mode 100644 index 0000000..2042bc6 Binary files /dev/null and b/lib/JColor-5.0.1.jar differ diff --git a/lib/json-20210307.jar b/lib/json-20210307.jar new file mode 100644 index 0000000..6583ea5 Binary files /dev/null and b/lib/json-20210307.jar differ diff --git a/out/artifacts/QuizzleDizzle_jar/QuizzleDizzle.jar b/out/artifacts/QuizzleDizzle_jar/QuizzleDizzle.jar new file mode 100644 index 0000000..d0795ac Binary files /dev/null and b/out/artifacts/QuizzleDizzle_jar/QuizzleDizzle.jar differ diff --git a/out/artifacts/QuizzleDizzle_jar/questions.csv b/out/artifacts/QuizzleDizzle_jar/questions.csv new file mode 100644 index 0000000..74782a4 --- /dev/null +++ b/out/artifacts/QuizzleDizzle_jar/questions.csv @@ -0,0 +1,3 @@ +# question;right answer index;answers... +Wie toll ist Microsoft;3;Ganz toll; nicht so toll;schlecht;*kotzgeräusche* +Was ist das beste Betriebsystem;0;Linux;Mac;Windows \ No newline at end of file diff --git a/questions.csv b/questions.csv new file mode 100644 index 0000000..74782a4 --- /dev/null +++ b/questions.csv @@ -0,0 +1,3 @@ +# question;right answer index;answers... +Wie toll ist Microsoft;3;Ganz toll; nicht so toll;schlecht;*kotzgeräusche* +Was ist das beste Betriebsystem;0;Linux;Mac;Windows \ No newline at end of file diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 0000000..1dc58b9 --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: org.bytedream.quizzledizzle.Main + diff --git a/src/org/bytedream/quizzledizzle/Main.java b/src/org/bytedream/quizzledizzle/Main.java new file mode 100644 index 0000000..e530f79 --- /dev/null +++ b/src/org/bytedream/quizzledizzle/Main.java @@ -0,0 +1,51 @@ +package org.bytedream.quizzledizzle; + +import org.bytedream.quizzledizzle.console.Console; +import org.bytedream.quizzledizzle.options.*; +import org.bytedream.quizzledizzle.question.Questions; + +import java.io.File; + +public class Main { + + public static void main(String[] args) throws Exception { + Console console = new Console(); + Questions questions = Questions.loadFromCSV(new File("questions.csv"), ";"); + + while (true) { + Option option; + + console.info("1 - Singleplayer"); + console.info("2 - Hotseat"); + console.info("3 - Host multiplayer game (2 players)"); + console.info("4 - Join multiplayer game (2 players)"); + switch (console.askInt("Choice: ")) { + case -1: + console.info("Bye, see ya later"); + return; + case 1: + option = new Singleplayer(console, questions); + break; + case 2: + option = new Hotseat(console, questions); + break; + case 3: + option = new MultiplayerHost(console, questions); + break; + case 4: + option = new MultiplayerClient(console, questions); + break; + default: + console.error("Please enter a number between 1 and 4 (see options above) or 'q' to exit"); + continue; + } + console.clearScreen(); + String exitMessage = option.start(); + console.clearScreen(); + if (exitMessage != null) { + console.error(exitMessage); + } + } + } + +} diff --git a/src/org/bytedream/quizzledizzle/console/Console.java b/src/org/bytedream/quizzledizzle/console/Console.java new file mode 100644 index 0000000..78a1aa3 --- /dev/null +++ b/src/org/bytedream/quizzledizzle/console/Console.java @@ -0,0 +1,141 @@ +package org.bytedream.quizzledizzle.console; + +import com.diogonunes.jcolor.Ansi; +import com.diogonunes.jcolor.Attribute; +import org.bytedream.quizzledizzle.question.Question; + +import java.util.Scanner; + +public class Console { + + public static final boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win"); + private final Scanner scanner = new Scanner(System.in); + + public String ask(String question) { + System.out.print(question); + return scanner.next(); + } + + public int askInt(String question) { + String maybeNum; + while (true) { + System.out.print(question); + maybeNum = scanner.next(); + + try { + return Integer.parseInt(maybeNum); + } catch (NumberFormatException e) { + if (maybeNum.equalsIgnoreCase("q")) { + return -1; + } else { + System.out.println(Ansi.colorize("Please enter a number or 'q' to exit", Attribute.RED_TEXT())); + } + } + } + } + + public void title(String content) { + System.out.println(content); + } + + public void error(String content) { + System.out.println(Ansi.colorize(content, Attribute.RED_TEXT())); + } + + public void info(String content) { + System.out.println(Ansi.colorize(content, Attribute.BRIGHT_BLUE_TEXT())); + } + + /** + * shows a question + * + * @param question + * @return the answer index + */ + public int showQuestion(Question question) { + System.out.println(Ansi.colorize(question.getQuestion(), Attribute.BLUE_TEXT())); + + String[] answers = question.getAnswers(); + for (int i = 0; i < answers.length; i++) { + System.out.printf(" -> %d) %s\n", i + 1, answers[i]); + } + while (true) { + System.out.println(""); + System.out.print("Enter a number: "); + String answer = scanner.next(); + if (answer.equals("q")) { + return -1; + } else { + try { + int chosenAnswer = Integer.parseInt(answer) - 1; + if (chosenAnswer >= 0 && chosenAnswer < answers.length) { + return chosenAnswer; + } + } catch (NumberFormatException ignore) {} + clearLastLine(); + clearLastLine(); + System.err.println("Please enter a valid number"); + } + } + } + + /** + * shows the results from a question + * + * @param question + * @param chosenAnswer + */ + public void showResult(Question question, int chosenAnswer) { + String[] answers = question.getAnswers(); + + char answerEmoji; + for (int i = 0; i < answers.length; i++) { + if (i == chosenAnswer) { + answerEmoji = '✔'; + } else { + answerEmoji = '✘'; + } + if (i == question.getRightAnswerIndex()) { + System.out.println(Ansi.colorize(String.format("»» %d) %s [%s] ««", i + 1, answers[i], answerEmoji), Attribute.GREEN_TEXT())); + } else { + System.out.println(Ansi.colorize(String.format(" %d) %s [%s]", i + 1, answers[i], answerEmoji), Attribute.RED_TEXT())); + } + } + } + + /** + * clears the screen and shows the total questions and the count of right questions + * + * @param totalQuestions + * @param rightQuestions + */ + public void showClearScreen(int totalQuestions, int rightQuestions) { + clearScreen(); + System.out.println(Ansi.colorize(String.format("Total questions: %d - Right questions: %d\n", totalQuestions, rightQuestions), Attribute.BLUE_TEXT())); + } + + /** + * clears the entire screen. + * repeats \n if the plaform is windows, ansi codes are used on all other plaforms + */ + public void clearScreen() { + if (!isWindows) { + System.out.print("\033[H\033[2J"); + } else { + // because windows is windows, ansi codes are not working by default and this has to be used + System.out.println("\n".repeat(1000)); + } + } + + /** + * clears the last line. + * only works on linux + */ + public void clearLastLine() { + // yep, windows again... + if (!isWindows) { + System.out.print("\033[1F\33[2K"); + } + } + +} diff --git a/src/org/bytedream/quizzledizzle/multiplayer/Client.java b/src/org/bytedream/quizzledizzle/multiplayer/Client.java new file mode 100644 index 0000000..bf42d19 --- /dev/null +++ b/src/org/bytedream/quizzledizzle/multiplayer/Client.java @@ -0,0 +1,69 @@ +package org.bytedream.quizzledizzle.multiplayer; + +import org.bytedream.quizzledizzle.question.Question; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.Socket; +import java.util.Arrays; +import java.util.Random; + +public class Client { + private final Socket socket; + private final BufferedReader reader; + private final PrintWriter writer; + private final long seed; + private final Random random; + private final boolean isServer; + + public Client(Socket socket, BufferedReader reader, PrintWriter writer, long seed, boolean isServer) { + this.socket = socket; + this.reader = reader; + this.writer = writer; + this.seed = seed; + this.random = new Random(seed); + this.isServer = isServer; + } + + public void sendQuestion(Question question) { + writer.printf("%s;%s;%s\n", question.getQuestion(), question.getRightAnswerIndex(), String.join(";", question.getAnswers())); + writer.flush(); + } + + public Question receiveQuestion() throws IOException { + String[] rawQuestion = reader.readLine().split(";"); + return new Question(rawQuestion[0], Integer.parseInt(rawQuestion[1]), Arrays.copyOfRange(rawQuestion, 2, rawQuestion.length)); + } + + public void sendAnswer(int answerIndex) { + writer.print(answerIndex); + writer.flush(); + } + + public int receiveAnswer() throws IOException { + return Integer.parseInt(reader.readLine()); + } + + public void updateSeed(int count) { + this.random.setSeed(seed + count); + } + + /** + * Returns if the client has to send the next question to the other player + * + * @return + */ + public boolean requestSend() { + int result = random.nextInt(2); + if (isServer) { + return result == 1; + } else { + return result == 0; + } + } + + public void close() throws IOException { + socket.close(); + } +} diff --git a/src/org/bytedream/quizzledizzle/multiplayer/Connection.java b/src/org/bytedream/quizzledizzle/multiplayer/Connection.java new file mode 100644 index 0000000..7957905 --- /dev/null +++ b/src/org/bytedream/quizzledizzle/multiplayer/Connection.java @@ -0,0 +1,32 @@ +package org.bytedream.quizzledizzle.multiplayer; + +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; + +public class Connection { + public static Client client(String host, int port) throws IOException { + // creates a new socket an connects directly to the client + Socket socket = new Socket(host, port); + + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); + + long seed = Long.parseLong(reader.readLine()); + return new Client(socket, reader, writer, seed, false); + } + + public static Client server(int port) throws IOException { + // creates new socket server and waits until a client connects + Socket socket = new ServerSocket(port).accept(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); + + // generates and sends a seed which is used to choose if the server or the client sends the questions + long seed = System.nanoTime(); + writer.println(seed); + writer.flush(); + return new Client(socket, reader, writer, seed, true); + } +} diff --git a/src/org/bytedream/quizzledizzle/options/Hotseat.java b/src/org/bytedream/quizzledizzle/options/Hotseat.java new file mode 100644 index 0000000..06b314c --- /dev/null +++ b/src/org/bytedream/quizzledizzle/options/Hotseat.java @@ -0,0 +1,47 @@ +package org.bytedream.quizzledizzle.options; + +import org.bytedream.quizzledizzle.console.Console; +import org.bytedream.quizzledizzle.question.Question; +import org.bytedream.quizzledizzle.question.Questions; + +public class Hotseat extends Option { + + public Hotseat(Console console, Questions defaultQuestions) { + super(console, defaultQuestions); + } + + @Override + public String start() throws Exception { + int playerCount = console.askInt("Player count> "); + + int count = 1; + while (true) { + int[] answers = new int[playerCount]; + + Question question = defaultQuestions.randomQuestion(); + for (int i = 0; i < playerCount; i++) { + console.clearScreen(); + console.title("Question " + count); + console.title("Player " + (i + 1)); + int answer = console.showQuestion(question); + if (answer == -1) { + return null; + } + answers[i] = answer; + } + + console.clearScreen(); + console.info("Results"); + for (int i = 0; i < playerCount; i++) { + console.info("Player " + (i + 1)); + console.showResult(question, answers[i]); + } + + if (!console.ask("Continue? [y/N]").toLowerCase().startsWith("y")) { + return null; + } + + count++; + } + } +} diff --git a/src/org/bytedream/quizzledizzle/options/MultiplayerClient.java b/src/org/bytedream/quizzledizzle/options/MultiplayerClient.java new file mode 100644 index 0000000..180a016 --- /dev/null +++ b/src/org/bytedream/quizzledizzle/options/MultiplayerClient.java @@ -0,0 +1,34 @@ +package org.bytedream.quizzledizzle.options; + +import org.bytedream.quizzledizzle.console.Console; +import org.bytedream.quizzledizzle.multiplayer.Client; +import org.bytedream.quizzledizzle.multiplayer.Connection; +import org.bytedream.quizzledizzle.question.Questions; + +import java.io.IOException; + +public class MultiplayerClient extends MultiplayerOption { + public MultiplayerClient(Console console, Questions defaultQuestions) { + super(console, defaultQuestions); + } + + @Override + public String start() throws Exception { + String ip = console.ask("IP address to connect to> "); + int port = console.askInt("Port to connect> "); + + Client client; + try { + client = Connection.client(ip, port); + console.info("Connected to server"); + } catch (IOException e) { + return "Could not connect to the given ip address. Maybe the port or ip is wrong?"; + } + + while (true) { + if (!this.request(defaultQuestions.randomQuestion(), client)) { + return null; + } + } + } +} diff --git a/src/org/bytedream/quizzledizzle/options/MultiplayerHost.java b/src/org/bytedream/quizzledizzle/options/MultiplayerHost.java new file mode 100644 index 0000000..688d386 --- /dev/null +++ b/src/org/bytedream/quizzledizzle/options/MultiplayerHost.java @@ -0,0 +1,36 @@ +package org.bytedream.quizzledizzle.options; + +import com.diogonunes.jcolor.Ansi; +import org.bytedream.quizzledizzle.console.Console; +import org.bytedream.quizzledizzle.multiplayer.Client; +import org.bytedream.quizzledizzle.multiplayer.Connection; +import org.bytedream.quizzledizzle.question.Questions; + +import java.io.IOException; + +public class MultiplayerHost extends MultiplayerOption { + public MultiplayerHost(Console console, Questions defaultQuestions) { + super(console, defaultQuestions); + } + + @Override + public String start() throws Exception { + int port = console.askInt("Hosting port> "); + + console.info(Ansi.colorize("Waiting for new connection on port " + port)); + + Client client; + try { + client = Connection.server(port); + console.info("Client connected"); + } catch (IOException e) { + return "Could not create socket. Maybe the port is out of bounce or already taken?"; + } + + while (true) { + if (!this.request(defaultQuestions.randomQuestion(), client)) { + return null; + } + } + } +} diff --git a/src/org/bytedream/quizzledizzle/options/MultiplayerOption.java b/src/org/bytedream/quizzledizzle/options/MultiplayerOption.java new file mode 100644 index 0000000..bfb7e71 --- /dev/null +++ b/src/org/bytedream/quizzledizzle/options/MultiplayerOption.java @@ -0,0 +1,41 @@ +package org.bytedream.quizzledizzle.options; + +import org.bytedream.quizzledizzle.console.Console; +import org.bytedream.quizzledizzle.multiplayer.Client; +import org.bytedream.quizzledizzle.question.Question; +import org.bytedream.quizzledizzle.question.Questions; + +import java.io.IOException; + +public abstract class MultiplayerOption extends Option { + + public MultiplayerOption(Console console, Questions defaultQuestions) { + super(console, defaultQuestions); + } + + public boolean request(Question question, Client client) throws IOException { + int ownAnswer; + int otherAnswer; + // possible bug: the server could send the question before the client / other player is listening for it + if (client.requestSend()) { + ownAnswer = console.showQuestion(question); + client.sendQuestion(question); + otherAnswer = client.receiveAnswer(); + client.sendAnswer(ownAnswer); + } else { + question = client.receiveQuestion(); + ownAnswer = console.showQuestion(question); + client.sendAnswer(ownAnswer); + otherAnswer = client.receiveAnswer(); + } + + if (ownAnswer == -1 || otherAnswer == -1) { + client.close(); + return true; + } + + this.showResult(question, ownAnswer); + + return false; + } +} diff --git a/src/org/bytedream/quizzledizzle/options/Option.java b/src/org/bytedream/quizzledizzle/options/Option.java new file mode 100644 index 0000000..1e29895 --- /dev/null +++ b/src/org/bytedream/quizzledizzle/options/Option.java @@ -0,0 +1,36 @@ +package org.bytedream.quizzledizzle.options; + +import org.bytedream.quizzledizzle.console.Console; +import org.bytedream.quizzledizzle.question.Question; +import org.bytedream.quizzledizzle.question.Questions; + +public abstract class Option { + + protected final Console console; + protected final Questions defaultQuestions; + + private int totalQuestions = 0; + private int rightQuestions = 0; + + public Option(Console console, Questions defaultQuestions) { + this.console = console; + this.defaultQuestions = defaultQuestions; + } + + public void showResult(Question question, int chosenAnswer) { + if (chosenAnswer == question.getRightAnswerIndex()) { + rightQuestions++; + } + console.showResult(question, chosenAnswer); + console.showClearScreen(++totalQuestions, rightQuestions); + } + + /** + * Entrypoint when the user has chosen this option + * + * @throws Exception + * @return + */ + public abstract String start() throws Exception; + +} diff --git a/src/org/bytedream/quizzledizzle/options/Singleplayer.java b/src/org/bytedream/quizzledizzle/options/Singleplayer.java new file mode 100644 index 0000000..1cd533a --- /dev/null +++ b/src/org/bytedream/quizzledizzle/options/Singleplayer.java @@ -0,0 +1,23 @@ +package org.bytedream.quizzledizzle.options; + +import org.bytedream.quizzledizzle.console.Console; +import org.bytedream.quizzledizzle.question.Question; +import org.bytedream.quizzledizzle.question.Questions; + +public class Singleplayer extends Option { + public Singleplayer(Console console, Questions defaultQuestions) { + super(console, defaultQuestions); + } + + @Override + public String start() throws Exception { + while (true) { + Question question = defaultQuestions.randomQuestion(); + int answer = console.showQuestion(question); + if (answer == -1) { + return null; + } + this.showResult(question, answer); + } + } +} diff --git a/src/org/bytedream/quizzledizzle/question/Question.java b/src/org/bytedream/quizzledizzle/question/Question.java new file mode 100644 index 0000000..3f5b726 --- /dev/null +++ b/src/org/bytedream/quizzledizzle/question/Question.java @@ -0,0 +1,30 @@ +package org.bytedream.quizzledizzle.question; + +public class Question { + + private final String question; + private final int rightAnswerIndex; + private final String[] answers; + + public Question(String question, int rightAnswerIndex, String... answers) { + this.question = question; + this.rightAnswerIndex = rightAnswerIndex; + this.answers = answers; + } + + public String getQuestion() { + if (!question.endsWith("?")) { + return question + '?'; + } + return question; + } + + public int getRightAnswerIndex() { + return rightAnswerIndex; + } + + public String[] getAnswers() { + return answers; + } + +} diff --git a/src/org/bytedream/quizzledizzle/question/Questions.java b/src/org/bytedream/quizzledizzle/question/Questions.java new file mode 100644 index 0000000..8e84ef4 --- /dev/null +++ b/src/org/bytedream/quizzledizzle/question/Questions.java @@ -0,0 +1,49 @@ +package org.bytedream.quizzledizzle.question; + +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Random; + +public class Questions { + + private final String[][] questions; + + public Questions(String[][] questions) { + this.questions = questions; + } + + /** + * shows a random question + * + * @return + */ + public Question randomQuestion() { + String[] chosenQuestion = questions[new Random().nextInt(questions.length)]; + return new Question(chosenQuestion[0], Integer.parseInt(chosenQuestion[1]), Arrays.copyOfRange(chosenQuestion, 2, chosenQuestion.length)); + } + + /** + * loads questions from a .csv file + * + * @param file the file to read from + * @param delimiter the used delimiter + * @return + * @throws IOException if the file does not exist + */ + public static Questions loadFromCSV(File file, String delimiter) throws IOException { + BufferedReader reader = new BufferedReader(new FileReader(file)); + ArrayList items = new ArrayList<>(); + + String line; + while ((line = reader.readLine()) != null) { + // if a comment is in the file + if (line.startsWith("#")) { + continue; + } + items.add(line.split(delimiter)); + } + + return new Questions(items.toArray(String[][]::new)); + } +}