4
\$\begingroup\$

I would like to hear opinions from people with bigger experience about KeyboardTeacher application. It fetches text from a text file, and shows the first line in console. If user types the whole line correctly, then it shows the next line from the file, and so on till the end of the text file. Otherwise it shows the first bad index and show again line which has been written badly. At the end it shows the result in seconds and save the result in the text file.

I added ranking system by which people can see who was the fastest.

I would like to know if I can change there anything, improve etc.

Here are some of the main classes. The entire project is on GitHub: https://github.com/must1/KeyBoardTeacher

Main:

import conditionchecker.ConditionChecker;
import conditionchecker.ConditionCheckerService;
import contentfile.ContentFileRetriever;
import contentfile.ContentFileRetrieverService;
import rankingsystem.RankingSystemFacade;
import rankingsystem.RankingSystemService;
import view.GameMessages;
import view.KeyBoardTeacherView;

public class Main {

    public static void main(String[] args) {

        ConditionChecker conditionChecker = new ConditionCheckerService();
        ContentFileRetriever contentFileRetriever = new ContentFileRetrieverService();
        GameMessages gameMessages = new KeyBoardTeacherView();
        RankingSystemService rankingSystemService = new RankingSystemService(contentFileRetriever);
        RankingSystemFacade rankingSystemFacade = new RankingSystemFacade(rankingSystemService, gameMessages);

        KeyBoardTeacherEngine keyBoardTeacherEngine = new KeyBoardTeacherEngine(contentFileRetriever,
                conditionChecker, gameMessages, rankingSystemFacade);
        keyBoardTeacherEngine.startKeyBoardTeacherEngine();
    }
}

KeyboardEngine

import conditionchecker.ConditionChecker;
import contentfile.ContentFileRetriever;
import rankingsystem.RankingSystemFacade;
import view.GameMessages;

import java.util.Arrays;
import java.util.Scanner;

class KeyBoardTeacherEngine {

    private static final int NANOSECONDS_IN_SECOND = 1000000000;
    private ConditionChecker conditionChecker;
    private ContentFileRetriever contentFileRetriever;
    private GameMessages gameMessages;
    private RankingSystemFacade rankingSystemFacade;
    private Scanner scanner;

    KeyBoardTeacherEngine(ContentFileRetriever contentFileRetriever, ConditionChecker conditionChecker,
                          GameMessages gameMessages, RankingSystemFacade rankingSystemFacade) {
        this.contentFileRetriever = contentFileRetriever;
        this.conditionChecker = conditionChecker;
        this.gameMessages = gameMessages;
        this.rankingSystemFacade = rankingSystemFacade;
        scanner = new Scanner(System.in);
    }

    void startKeyBoardTeacherEngine() {
        String name = getName();
        String[] contentFileArray = contentFileRetriever.getContent(getPathFileOfGame());
        String lineGivenByUser;
        long startTime = System.nanoTime();
        for (String modelLine : contentFileArray) {
            do {
                System.out.println(modelLine);
                lineGivenByUser = scanner.nextLine();

                if (modelLine.equals(lineGivenByUser)) {
                    break;
                }

                for (int index = 0; index < modelLine.length(); index++) {
                    int modelLineNumber = Arrays.asList(contentFileArray).indexOf(modelLine);

                    if (conditionChecker.checkIfCharactersAreUnequal(modelLine.charAt(index), lineGivenByUser.charAt(index))) {
                        gameMessages.executeBadIndexMessage(index);
                        break;
                    } else if ((lineGivenByUser.length() < contentFileArray[modelLineNumber].length())) {
                        if (conditionChecker.checkIfIndexEqualsToLengthOfLineGivenByUser(index, lineGivenByUser)) {
                            gameMessages.executeMessageOfCaseWhenGivenLineIsShorterThanProper(lineGivenByUser);
                            break;
                        }
                    } else if ((lineGivenByUser.length() > contentFileArray[modelLineNumber].length())) {
                        gameMessages.executeMessageOfCaseWhenGivenLineIsLongerThanProper();
                        break;
                    }
                }
            }
            while (true);
        }

        long endTime = System.nanoTime();
        long durationTimeInSeconds = (endTime - startTime) / NANOSECONDS_IN_SECOND;
        rankingSystemFacade.executeRankingSystemProcess(name, durationTimeInSeconds);
        gameMessages.executeCongratulationsAboutEndingGame(durationTimeInSeconds, name);
    }

    private String getPathFileOfGame() {
        gameMessages.executeGettingContentPathMessage();
        return scanner.nextLine();
    }

    private String getName() {
        gameMessages.askAboutName();
        return scanner.nextLine();
    }
}

KeyboardTeacherView - it implements GameMessage which is of course interface.

package view;

public class KeyBoardTeacherView implements GameMessages {

    @Override
    public void executeCongratulationsAboutEndingGame(long durationTimeInSeconds, String name) {
        System.out.println("Congratulations " + name + ", you have ended in " + durationTimeInSeconds + " seconds.");
    }

    @Override
    public void executeBadIndexMessage(int index) {
        System.out.println("Bad " + index + " index. (starting from 0)");
    }

    @Override
    public void executeGettingContentPathMessage() {
        System.out.println("Provide path of the file in which we can find text for the game(if the file is in the project" +
                " folder, then just write a name of the file) i.e tekst.txt");
    }

    @Override
    public void executeMessageOfCaseWhenGivenLineIsShorterThanProper(String lineGivenByUser) {
        System.out.println("Bad character at " + lineGivenByUser.length() + " index. (starting from 0)");
    }

    @Override
    public void askAboutName() {
        System.out.println("Please, put your name");
    }

    @Override
    public void executeMessageOfCaseWhenGivenLineIsLongerThanProper() {
        System.out.println("Your line is longer than proper, please try again!");
    }

    @Override
    public void executeMessageAboutPlaceInRankingOfPlayer(int position, int size) {
        System.out.println("You are " + position + " of " + size + " players in ranking!!!!");
    }

    @Override
    public void executeGettingRankingFilePathMessage() {
        System.out.println("Provide path of the ranking file(if the file is in the project folder, " +
                "then just write a name of the file) i.e ranking.txt");
    }
}

RankingSystem

package rankingsystem;

import contentfile.ContentFileRetriever;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

public class RankingSystemService {

    private static final int FIRST_PART = 0;
    private static final int SECOND_PART = 1;
    private ContentFileRetriever contentFileRetriever;

    public RankingSystemService(ContentFileRetriever contentFileRetriever) {
        this.contentFileRetriever = contentFileRetriever;
    }

    int findGivenNameInRanking(String name, Map<String, Long> sortedRankingArray) {
        return new ArrayList<>(sortedRankingArray.keySet()).indexOf(name);
    }

    Map<String, Long> getSortedLinkedHashMappedRankingArray(String[] rankingArray) {
        return Arrays
                .stream(rankingArray)
                .map(it -> it.split("\\s+"))
                .collect(Collectors.toMap(it -> it[FIRST_PART], it -> Long.valueOf(it[SECOND_PART])))
                .entrySet()
                .stream()
                .sorted(Map.Entry.comparingByValue())
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
                        (oldValue, newValue) -> oldValue, LinkedHashMap::new));
    }

    String[] retrieveRankingData(String rankingPathFile) {
        return contentFileRetriever.getContent(rankingPathFile);
    }

    void overwriteFileWithGivenResult(String name, long timeOfFinishingGame, String rankingPathFile) {

        try (FileWriter writer = new FileWriter(rankingPathFile, true);
             BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
            bufferedWriter.write(name + " " + timeOfFinishingGame);
            bufferedWriter.newLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

RankingSystemFacade

package rankingsystem;

import view.GameMessages;

import java.util.Map;
import java.util.Scanner;

public class RankingSystemFacade {

    private RankingSystemService rankingSystemService;
    private GameMessages gameMessages;
    private Scanner scanner;

    public RankingSystemFacade(RankingSystemService rankingSystem, GameMessages gameMessages) {
        this.rankingSystemService = rankingSystem;
        this.gameMessages = gameMessages;
        scanner = new Scanner(System.in);
    }

    public void executeRankingSystemProcess(String name, long timeOfFinishGame) {
        String rankingPathFile = getPathOfRankingFile();
        rankingSystemService.overwriteFileWithGivenResult(name, timeOfFinishGame,rankingPathFile);
        String[] rankingArray = rankingSystemService.retrieveRankingData(rankingPathFile);
        Map<String, Long> linkedSortedHashMappedRankingArray = rankingSystemService.getSortedLinkedHashMappedRankingArray(rankingArray);
        int position = rankingSystemService.findGivenNameInRanking(name, linkedSortedHashMappedRankingArray);

        gameMessages.executeMessageAboutPlaceInRankingOfPlayer(++position, linkedSortedHashMappedRankingArray.size());
    }

    private String getPathOfRankingFile()
    {
        gameMessages.executeGettingRankingFilePathMessage();
        return scanner.nextLine();
    }
}

ContentFileRetiever:

package contentfile;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class ContentFileRetrieverService implements ContentFileRetriever {

    @Override
    public String[] getContent(String pathName) {

        try (Stream<String> contentFileStream = Files.lines(Paths.get(pathName))) {
            return contentFileStream.toArray(String[]::new);
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }
    }
}

ConditionCheckerService

package conditionchecker;

public class ConditionCheckerService implements ConditionChecker {

    @Override
    public boolean checkIfCharactersAreUnequal(char firstCharacter, char secondCharacter) {
        return firstCharacter != secondCharacter;
    }

    //if index+1 equals to length of line given by user (which is shorter than proper),
    // then we know that there is a bad index at the end of given line.
    @Override
    public boolean checkIfIndexEqualsToLengthOfLineGivenByUser(int index, String lineGivenByUser) {
        return index + 1 == lineGivenByUser.length();
    }
}

ContentFileServiceTest

package contentfile;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import static org.junit.Assert.*;

public class ContentFileRetrieverServiceTest {

    private ContentFileRetriever contentFileRetriever = new ContentFileRetrieverService();

    @Rule
    public TemporaryFolder tempFolder = new TemporaryFolder();

    @Test
    public void getContentFile() throws IOException {
        File textFile = tempFolder.newFile("testText.txt");
        String pathFile = textFile.getPath();
        FileWriter fileWriter = new FileWriter(textFile);
        fileWriter.write("Line1 a\nLine2 b c\nLine 3");
        fileWriter.close();

        String[] testedContent = contentFileRetriever.getContent(pathFile);
        String[] expected = {"Line1 a", "Line2 b c", "Line 3"};

        textFile.deleteOnExit();
        assertArrayEquals(expected, testedContent);
    }

    @Test(expected = IllegalArgumentException.class)
    public void getContentFileWhenFileDoesNotExist() {
        String pathFile = "unknown";
        String[] testedContent = contentFileRetriever.getContent(pathFile);
    }
}

RankingSystemServiceTest

package rankingsystem;

import contentfile.ContentFileRetrieverService;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;

import static org.junit.Assert.*;

public class RankingSystemServiceTest {

    @Rule
    public TemporaryFolder tempFolder = new TemporaryFolder();

    private ContentFileRetrieverService contentFileRetrieverService = new ContentFileRetrieverService();
    private RankingSystemService rankingSystemService = new RankingSystemService(contentFileRetrieverService);

    @Test
    public void getLinkedHashMappedRankingArray() {
        String[] testedArray = {"Piotr 3", "Anna 1", "Andrzej 2"};
        Map<String, Long> linkedSortedHashMappedRankingArray = rankingSystemService.getSortedLinkedHashMappedRankingArray(testedArray);
        Map<String, Long> expectedLinkedSortedHashMappedRankingArray = new LinkedHashMap<>();
        expectedLinkedSortedHashMappedRankingArray.put("Anna", 1L);
        expectedLinkedSortedHashMappedRankingArray.put("Andrzej", 2L);
        expectedLinkedSortedHashMappedRankingArray.put("Piotr", 3L);

        int actualPosition = rankingSystemService.findGivenNameInRanking("Piotr", linkedSortedHashMappedRankingArray);
        int exceptedPosition = rankingSystemService.findGivenNameInRanking("Piotr", expectedLinkedSortedHashMappedRankingArray);

        assertEquals(exceptedPosition, actualPosition);
    }

    @Test
    public void overwriteFileWithGivenResult() throws IOException {
        File rankingFile = tempFolder.newFile("rankingText.txt");
        String rankingFilePath = rankingFile.getPath();

        String[] actualResultBeforeOverwriting = rankingSystemService.retrieveRankingData(rankingFilePath);
        String[] expectedResultBeforeOverwriting = {};

        assertArrayEquals(expectedResultBeforeOverwriting, actualResultBeforeOverwriting);

        rankingSystemService.overwriteFileWithGivenResult("Piotr", 1L, rankingFilePath);

        String[] afterOverwriting = rankingSystemService.retrieveRankingData(rankingFilePath);
        String[] expectedResultAfterOverwriting = {"Piotr 1"};

        rankingFile.deleteOnExit();
        assertArrayEquals(expectedResultAfterOverwriting, afterOverwriting);
    }
}
\$\endgroup\$
1
\$\begingroup\$

Here are some minor suggestions relating to Java/programming standards etc:

ConditionChecker Might not be a good name as it's not very descriptive. However you should be able to remove this entirely as the two methods are very simple and you don't really need a method for either of them.

Negative names such as checkIfCharactersAreUnequal can lead to double negatives. For example, to check if the characters are unequal you'd have to check if they are not unequal. I'd suggest either refactoring the method, or removing it entirely since it simply checks if two characters are equal.

checkIfIndexEqualsToLengthOfLineGivenByUser If you ever use this method to check a different String, you'd have to rename the method. When naming public methods, you should only think in terms of the method itself. In other words, the ConditionCheckerService class doesn't care if it's a String given by the user or not. Also try to use explanatory names, instead of check use is. For example, isIndexEqualToLength(int, String)

KeyBoardTeacherEngine startKeyboardTeacherEngine should be public. Currently it's in the same package as the calling method Main, but I don't think it actually relates to Main.

General suggestions:

Consider having the user able to select the path of the file via a 'file chooser'. Example being that File Explorer like window that pops up to allow the user to select a location.

Instead of displaying the index of the failed character, display the failed line with a '^' underneath the unlatching character.

Different modes, such as allowing the user to make mistakes but keeping a record throughout the duration of the test, and printing the results at the end. You could look at online typing tests for ideas.

In your console: Provide path of the file in which we can find text for the game(if the file is in the project folder, then just write a name of the file) i.e tekst.txt

Change the example file name to "text.txt" or "file.txt"

\$\endgroup\$
  • \$\begingroup\$ About ConditionChecker.1) I agree that I should remove checkIfCharactersAreUnequal as it is simple, and it's done.I do not understand suggestion about checkIfIndexEqualsToLengthOfLineGivenByUser . Should it be named as checkIfIndexEqualsToLengthOfLine ? Also why startKeyboardTeacherEngine should be public? It is not public as I dont have to make it public as it is in the same package. \$\endgroup\$ – pipilam May 12 at 8:49
  • \$\begingroup\$ Could you explain suggestions at General Suggestions? I mean, what do you mean by file chooser and why it is better? "Different modes, such as allowing the user to make mistakes but keeping a record throughout." what do you mena by that? \$\endgroup\$ – pipilam May 12 at 8:54
  • \$\begingroup\$ @pipilam Yeah or maybe isEqualToSize. I'll update my answer. I believe startKeyboardTeacherEngine should be public since it does not relate to the Main method. A file chooser opens a file-explorer like window for the user to select a file, it's easier to use than manually entering the file path \$\endgroup\$ – dustytrash May 12 at 14:26
  • \$\begingroup\$ Okay, got it. Anything else? Do you think that this code is in good quality? Btw. Is there any file chooser that I can use or I need to make it on my own? \$\endgroup\$ – pipilam May 12 at 14:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.