Цель игры состоит в том, чтобы угадать пятибуквенное слово за пять попыток или менее. Когда пользователь (игрок) вводит слово, приложение информирует пользователя о том, было ли его предположение правильным или нет. Если пользователь угадывает правильное слово, приложение просто «переходит на следующий уровень» и предлагает пользователю угадать другое слово. Если пользователь неправильно угадает слово, приложение снимет попытку с пользователя и даст подсказку, исходя из того, какие буквы он угадал правильно. Буквы, угаданные пользователем правильно, могут отображаться двумя способами: (Зеленый — правильная буква, правильный индекс) (Желтый — правильная буква, неправильный индекс)
Я хочу выяснить, эффективна ли моя логика для приложения. Буду признателен за любые отзывы. Спасибо.
fun main(args: Array<String>) {
Menu()
}
// Game Menu
fun Menu() {
println("******************************\n" +
"Welcome to WordGame!\n" +
"******************************")
println("Guess the correct word in\n" +
"five attempts or less\n" +
"******************************")
println("Type your name to play")
val player = readln()
WordGame(player)
}
// Game Function
fun WordGame(player : String) {
var level : Int = 1
var attempts : Int = 5
// Array to hold random words
val words: Array<String> = arrayOf("mayor", "train", "watch", "movie", "photo");
var word : String = words.random() // Gets random word in array words
// Loops around the game
while (true) {
println("******************************")
println(player + " - Level " + level + " - Attempts " + attempts)
println("******************************")
println("Guess the word:")
val answer : String = readln()
// Checks for correct word
if(CheckIndex(word, answer).size == 5) {
level = level + 1 // adds level
println("Correct!")
attempts = 5 // resets the players attempts
word = words.random()
// Ends game when level 6 is reached
if (level == 6) {
println("Well done! You have beaten WordGame.\n" +
"Press enter to return to menu")
readln()
Menu()
}
}
else {
attempts = attempts - 1 // Removes an attempt
// Ends game when attempts reach 0
if (attempts == 0) {
println("You've Lost at WordGame!\n" +
"Press enter to return to menu")
readln()
Menu()
}
var index = CheckIndex(word, answer).joinToString()
var letters = CheckLetters(word, answer).joinToString()
println("******************************\n" +
"Green: " + index + "\n" +
"Yellow: " + letters + "\n" +
"******************************")
println("Remaing attempts: " + attempts)
println("Press enter to try again")
readln()
}
}
}
// Function to check index
fun CheckIndex(word: String, answer: String) : ArrayList<Char> {
val index = ArrayList<Char>()
var i = 0
// Loops around the length of the guessed word
while (i < answer.length) {
// Checks against the char of guessed word
// and correct word
if(answer.get(i) == word.get(i)) {
index.add(answer.get(i)) // if a char is equal, adds to an arraylist
}
i++
}
return index
}
// Function to check correct letters
fun CheckLetters(word: String, answer: String) : ArrayList<Char> {
val letters = ArrayList<Char>()
var i = 0
while (i < answer.length) {
// Ensures the 'Green' letters are
// not seen as 'Yellow'
if(answer.get(i) != word.get(i)) {
// Checks against each letter in the correct word
// I feel that this is a bit clunky as I would prefer
// to not hard-code indexe
when (answer.get(i)) {
word.get(0) -> letters.add(answer.get(i))
word.get(1) -> letters.add(answer.get(i))
word.get(2) -> letters.add(answer.get(i))
word.get(3) -> letters.add(answer.get(i))
word.get(4) -> letters.add(answer.get(i))
}
}
i++
}
return letters
}
Брэд
3 ответа
Во-первых, это неплохая попытка, однако есть некоторые ошибки:
Что происходит, когда пользователь вводит слово, состоящее менее чем из 5 букв? Что делать, если игрок вводит заглавные буквы?
Также в некоторых случаях возвращаемые подсказки могут быть бесполезными/неправильными. Например, с секретным словом movie: если игрок входит where игра будет отображать e как зеленый без какой-либо информации, которая e верно. И дополнительно отобразит e желтым, что неверно, поскольку movie содержит только один e который уже отображается зеленым цветом.
Вы непоследовательно следуете за Kotlin соглашения о коде.
- Имена функций должно начинаться с маленькой буквы.
- Не должно быть пробела перед двоеточие при объявлении типов.
- Должен быть пробел между ключевыми словами (
if,whileи т. д.) и следующие скобки. - и т. д.
Ваша IDE (в частности, IntelliJ) может форматировать код для тебя.
Вы используете исключительно процедурный стиль программирования и на самом деле не используете функции Kotlin, например, объектно-ориентированное и функциональное программирование. Это не совсем неправильно, но вы упускаете его сильные стороны.
Вы могли бы, например, использовать многострочные (необработанные) строковые литералы и строковые шаблоны:
println("""
|******************************
|${player} - Level ${level} - Attempts ${attempts}
|******************************
|Guess the word
""".trimMargin())
Другим предложением было бы использовать классы Kotlin, например, его коллекциивместо специфичных для Java (таких как ArrayList).
Это может быть слишком сложно, но специфичный для Kotlin способ реализации checkIndex может быть:
fun checkIndex(word: String, answer: String): List<Char> =
word.zip(answer).filter { (a, b) -> a == b }.map { it.first }
Не совсем оптимально звонить Menu() изнутри WordGame(), особенно с бесконечным циклом. После нескольких раундов у вас будет Menu() позвонить внутри WordGame() позвонить внутри Menu() позвонить внутри WordGame() позвонить внутри Menu() позвонить внутри WordGame() позвонить внутри Menu() позвонить внутри WordGame() звонок и т.д.
Лучше бы break из петли в WordGame() и вместо этого иметь дополнительный цикл внутри Menu() (желательно тот, из которого пользователь может выйти):
fun Menu() {
while (true) {
// ...
println("Type your name to play")
val player = readln()
if (player.isBlank()) break
WordGame(player)
}
}
В WordGame() затем используйте break (или return для выхода из функции напрямую) вместо вызова Menu().
Вместо when просто добавьте еще одну петлю над буквами word и сравните каждый с answer.get(i).
Тоби Спейт
Как @RoToRa указал на проблему,
Например, с секретным словом
movieесли игрок входитwhereигра будет отображатьeкак зеленый без какой-либо информации, котораяeверно. И дополнительно отобразитeжелтым, что неверно, посколькуmovieсодержит только одинeкоторый уже отображается зеленым цветом.
Я хотел бы предложить решение для улучшения вашей логики, как насчет того, чтобы вы снова напечатали точное слово, которое в приведенном выше примере where но с правильными буквами, окрашенными в соответствии с вашим цветовым кодом. Чтобы сделать это, вам нужно будет проверить каждую букву ответа (поэтому он должен быть в цикле) и соответствующим образом распечатать, используя escape-последовательность ANSI, например, такую:
//сохранение управляющих кодов в переменных для простоты val yellow: String = "\e[33m"
val green: String = "\e[32m"
//"\e[m" resets text color
val colorOff: String = "\e[m"
//if(letter is correct but wrong index)
print(yellow+letter+colorOff)
//else if(letter is correct && correct index)
print(green+letter+colorOff)
//else
print(letter) //printing the wrong letter without any color
However, it’s worth noting that the escape code «\e» may not be recognized by all terminals or operating systems. So the behavior of the code that uses this variable may not be consistent across all environments.
Also, make sure you print the color code beforehand in the execution of the program explaining it to the user. By the way, I think the code you provided lacks the hints to guess the word. Glad if I could help.
I’m not checking for bugs in your logic. I suspect you are not handling green and yellow letters correctly when there’s more than one of the same letter in the target word, but I just wanted to focus on the checkLetters() function since you commented that you think it might be clunky, and you’re right about that. So this answer is about how this function could be more elegantly written without changing its behavior.
The below will get you the exact same behavior for the function, any existing bugs still included!
- It’s Kotlin convention to start function names with a lower-case letter. This is kind of important for code readability, since Kotlin doesn’t have a
newkeyword that helps distinguish between constructor calls and regular function calls. If you follow convention, constructor calls stand out because they start with a capital letter. - You can use the
buildListfunction to more elegantly write this function. It allows you to directly call MutableList functions within your lambda, and then at the end simply returns a List. You don’t need to specifyArrayListsince you are not further mutating this list after this function returns.- Also, in idiomatic Kotlin or Java, you would rarely ever use ArrayList as a return type instead of just List/MutableList. The specific type ArrayList generally would be useless extra information to whoever is calling this function. This is related to the principle of encapsulation.)
string.get(i)is less idiomatic thanstring[i].- Ваш цикл while с объявленным извне
iкоторый внутренне увеличивается, запутан. Вы можете получить ту же функциональность, используяfor (i in answer.indices). - Ты используешь
answer.get(i)много раз. Когда это происходит, вы должны сначала вытащить его в переменную, чтобы не вызывать этот геттер повторно.
Итак, реализовав вышеперечисленные пункты перед тем, как двигаться дальше, мы имеем:
fun checkLetters(word: String, answer: String): List<Char> = buildList {
for (i in answer.indices) {
// Ensures the 'Green' letters are
// not seen as 'Yellow'
val thisChar = answer[i]
if(thisChar != word[i]) {
when (thisChar) {
word.get(0) -> add(thisChar)
word.get(1) -> add(thisChar)
word.get(2) -> add(thisChar)
word.get(3) -> add(thisChar)
word.get(4) -> add(thisChar)
}
}
}
}
А теперь внимательно посмотрите на свою when заявление. Вы делаете то же самое во всех филиалах! Мы можем сформулировать логику этого раздела кода как «добавить thisChar в список, если он соответствует любому из символов в word. Это может быть более просто достигнуто с помощью word.containsчто также может быть выражено как in word. Таким образом, мы можем обновить функцию следующим образом, чтобы получить точно такой же результат:
fun checkLetters(word: String, answer: String): List<Char> = buildList {
for (i in answer.indices) {
// Ensures the 'Green' letters are
// not seen as 'Yellow'
val thisChar = answer[i]
if(thisChar != word[i] && thisChar in word) {
add(thisChar)
}
}
}
В качестве альтернативного способа сделать это вместо использования buildList и вручную повторяя и добавляя в список, вы можете использовать операторы Iterable Kotlin (иногда называемые функциональным программированием). Это эквивалентно приведенному выше коду:
fun checkLetters(word: String, answer: String): List<Char> =
answer.filterIndexed { i, c -> c != word[i] && c in word }.toList()
Точно так же ваш checkIndex() функция также может быть однострочной:
fun checkIndex(word: String, answer: String): List<Char> =
answer.filterIndexed { i, c -> c == word[i] }.toList()
