~macaptain/ghostspeller

979d23dc1d7e0023bc3919b43ba93053861896a4 — Michael Captain 9 months ago d4eff35 master
Refactor CLI commands code into a subpackage
3 files changed, 104 insertions(+), 100 deletions(-)

M src/main/Main.kt
A src/main/cli/GhostCommand.kt
A src/main/cli/Play.kt
M src/main/Main.kt => src/main/Main.kt +1 -100
@@ 1,12 1,7 @@
import cli.Play
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.versionOption
import com.github.ajalt.clikt.parameters.types.file
import com.github.ajalt.clikt.parameters.types.int
import java.io.File

const val GHOSTSPELLER_VERSION = "0.1.0"



@@ 15,100 10,6 @@ class Ghostspeller : CliktCommand() {
}

/**
 * Command line subcommand for pitting the player against the computer at Ghost.
 */
class Play : CliktCommand() {
    // Command line options
    private val dictionary by option(
        "-d",
        "--dictionary",
        help = "Path to a new-line separated list of words to be used as the word list (default /usr/share/dict/words)"
    )
        .file()
        .default(File("/usr/share/dict/words"))
    private val minWordLength by option(
        "-m",
        "--min-word-length",
        help = "Minimum word length for a word to be considered in the dictionary (default 4)"
    )
        .int()
        .default(4)
    private val initialStem by option(
        "-i",
        "--initial-stem",
        help = "Start the game midway through a word"
    ).default("")
    private val excludeProperNouns by option(
        "--use-proper-nouns",
        help = "Use words in the dictionary that begin with a capital letter (default false)"
    ).flag(default = false)

    /**
     * Starts the Ghost play loop in which the player and computer take turns spelling.
     */
    override fun run() {
        echo("\uD83D\uDC7B Play Ghost! \uD83D\uDC7B")
        print("Loading word list... ")
        val wordList = dictionary.readLines()
            .filter { it.isNotEmpty() }
            .filter { excludeProperNouns || it.first().isLowerCase() }
            .map { it.toLowerCase() }
            .toSet()
        val trie = GhostTrieNode.fromWordList(wordList, minWordLength, initialStem)
        var node = trie
        echo("Done!")
        while (true) {
            echo("\uD83E\uDE84 Spell! \uD83E\uDE84")
            if (initialStem.isNotEmpty()) echo(initialStem)
            while (true) {
                val input = readLine() ?: break
                if (input == "") continue
                if (input.count() > 1) {
                    echo("Spell one letter at at time!")
                    continue
                }

                val c = input.first()
                if (node.children.containsKey(c)) {
                    node = node.children[c]!!
                } else {
                    echo("There are no words beginning \"${node.s + c}\" in the dictionary. You lose!")
                    echo("Words beginning with \"${node.s}\":")
                    wordList.filter { it.startsWith(node.s) }.forEach {
                        echo("$it - https://www.merriam-webster.com/dictionary/$it")
                    }
                    break
                }

                if (node.isWord) {
                    echo("The word \"${node.s}\" is in the dictionary! You lose!")
                    echo("Meaning: https://www.merriam-webster.com/dictionary/${node.s}")
                    break
                } else {
                    if (node.s.count() > 1) echo(node.s) // reminder of word so far

                    node = when {
                        node.winningChildren.isNotEmpty() -> node.winningChildren.values.random()
                        node.notWordChildren.isNotEmpty() -> node.notWordChildren.values.random()
                        else -> node.children.values.random()
                    }
                }
                if (node.isWord) {
                    echo("Congratulations! You win with \"${node.s}\".")
                    echo("https://www.merriam-webster.com/dictionary/${node.s}")
                    break
                } else {
                    echo(node.s)
                }
            }
            echo("Play again? (Ctrl + C to quit)")
            readLine()
            node = trie
        }
    }
}

/**
 * Entrypoint for running ghostspeller.
 *
 * @param args command line arguments passed to the Clikt command.

A src/main/cli/GhostCommand.kt => src/main/cli/GhostCommand.kt +39 -0
@@ 0,0 1,39 @@
package cli

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.file
import com.github.ajalt.clikt.parameters.types.int
import java.io.File

/**
 * Abstract class for common command line flags between Ghost subcommands.
 */
abstract class GhostCommand : CliktCommand() {
    // Command line options
    protected val dictionary by option(
        "-d",
        "--dictionary",
        help = "Path to a new-line separated list of words to be used as the word list (default /usr/share/dict/words)"
    )
        .file()
        .default(File("/usr/share/dict/words"))
    protected val minWordLength by option(
        "-m",
        "--min-word-length",
        help = "Minimum word length for a word to be considered in the dictionary (default 4)"
    )
        .int()
        .default(4)
    protected val initialStem by option(
        "-i",
        "--initial-stem",
        help = "Start the game midway through a word"
    ).default("")
    protected val excludeProperNouns by option(
        "--use-proper-nouns",
        help = "Use words in the dictionary that begin with a capital letter (default false)"
    ).flag(default = false)
}

A src/main/cli/Play.kt => src/main/cli/Play.kt +64 -0
@@ 0,0 1,64 @@
package cli

class Play : GhostCommand() {
    override fun run() {
        echo("\uD83D\uDC7B Play Ghost! \uD83D\uDC7B")
        print("Loading word list... ")
        val wordList = dictionary.readLines()
            .filter { it.isNotEmpty() }
            .filter { excludeProperNouns || it.first().isLowerCase() }
            .map { it.toLowerCase() }
            .toSet()
        val trie = GhostTrieNode.fromWordList(wordList, minWordLength, initialStem)
        var node = trie
        echo("Done!")
        while (true) {
            echo("\uD83E\uDE84 Spell! \uD83E\uDE84")
            if (initialStem.isNotEmpty()) echo(initialStem)
            while (true) {
                val input = readLine() ?: break
                if (input == "") continue
                if (input.count() > 1) {
                    echo("Spell one letter at at time!")
                    continue
                }

                val c = input.first()
                if (node.children.containsKey(c)) {
                    node = node.children[c]!!
                } else {
                    echo("There are no words beginning \"${node.s + c}\" in the dictionary. You lose!")
                    echo("Words beginning with \"${node.s}\":")
                    wordList.filter { it.startsWith(node.s) }.forEach {
                        echo("$it - https://www.merriam-webster.com/dictionary/$it")
                    }
                    break
                }

                if (node.isWord) {
                    echo("The word \"${node.s}\" is in the dictionary! You lose!")
                    echo("Meaning: https://www.merriam-webster.com/dictionary/${node.s}")
                    break
                } else {
                    if (node.s.count() > 1) echo(node.s) // reminder of word so far

                    node = when {
                        node.winningChildren.isNotEmpty() -> node.winningChildren.values.random()
                        node.notWordChildren.isNotEmpty() -> node.notWordChildren.values.random()
                        else -> node.children.values.random()
                    }
                }
                if (node.isWord) {
                    echo("Congratulations! You win with \"${node.s}\".")
                    echo("https://www.merriam-webster.com/dictionary/${node.s}")
                    break
                } else {
                    echo(node.s)
                }
            }
            echo("Play again? (Ctrl + C to quit)")
            readLine()
            node = trie
        }
    }
}