Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Currently Dicio answers questions about:
- **media**: play, pause, previous, next song
- **translation**: translate from/to any language with **Lingva** - _How do I say Football in German?_
- **wake word control**: turn on/off the wakeword - _Stop listening_
- **random**: Ask for a random number, flip a coin, or roll dice - _Pick a number between 1 and 100_

## Speech to text

Expand Down
2 changes: 2 additions & 0 deletions app/src/main/kotlin/org/stypox/dicio/eval/SkillHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.stypox.dicio.skills.lyrics.LyricsInfo
import org.stypox.dicio.skills.media.MediaInfo
import org.stypox.dicio.skills.navigation.NavigationInfo
import org.stypox.dicio.skills.open.OpenInfo
import org.stypox.dicio.skills.random.RandomInfo
import org.stypox.dicio.skills.search.SearchInfo
import org.stypox.dicio.skills.telephone.TelephoneInfo
import org.stypox.dicio.skills.timer.TimerInfo
Expand Down Expand Up @@ -53,6 +54,7 @@ class SkillHandler @Inject constructor(
CurrentTimeInfo,
MediaInfo,
JokeInfo,
RandomInfo,
ListeningInfo(dataStore),
TranslationInfo,
)
Expand Down
32 changes: 32 additions & 0 deletions app/src/main/kotlin/org/stypox/dicio/skills/random/RandomInfo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.stypox.dicio.skills.random

import android.content.Context
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Casino
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.Skill
import org.dicio.skill.skill.SkillInfo
import org.stypox.dicio.R
import org.stypox.dicio.sentences.Sentences

object RandomInfo : SkillInfo("random") {
override fun name(context: Context) =
context.getString(R.string.skill_name_random)

override fun sentenceExample(context: Context) =
context.getString(R.string.skill_sentence_example_random)

@Composable
override fun icon() =
rememberVectorPainter(Icons.Default.Casino)

override fun isAvailable(ctx: SkillContext): Boolean {
return Sentences.Random[ctx.sentencesLanguage] != null
}

override fun build(ctx: SkillContext): Skill<*> {
return RandomSkill(RandomInfo, Sentences.Random[ctx.sentencesLanguage]!!)
}
}
79 changes: 79 additions & 0 deletions app/src/main/kotlin/org/stypox/dicio/skills/random/RandomOutput.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.stypox.dicio.skills.random

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.SkillOutput
import org.stypox.dicio.R
import org.stypox.dicio.io.graphical.Body
import org.stypox.dicio.io.graphical.Headline
import org.stypox.dicio.io.graphical.Subtitle
import org.stypox.dicio.util.getString

sealed interface RandomOutput : SkillOutput {

data class CoinFlip(private val result: String) : RandomOutput {
override fun getSpeechOutput(ctx: SkillContext): String =
ctx.getString(R.string.skill_random_coin_flip, result)

@Composable
override fun GraphicalOutput(ctx: SkillContext) {
Headline(text = result.uppercase())
}
}

data class DiceRoll(private val results: List<Int>, private val sides: Int) : RandomOutput {
override fun getSpeechOutput(ctx: SkillContext): String {
val total = results.sum()
return if (results.size == 1) {
ctx.getString(R.string.skill_random_dice_roll, results[0], sides)
} else {
ctx.getString(R.string.skill_random_dice_roll_multiple, total, results.size, sides)
}
Comment on lines +29 to +34
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val total = results.sum()
return if (results.size == 1) {
ctx.getString(R.string.skill_random_dice_roll, results[0], sides)
} else {
ctx.getString(R.string.skill_random_dice_roll_multiple, total, results.size, sides)
}
return if (results.size == 1) {
ctx.getString(R.string.skill_random_dice_roll, results[0], sides)
} else {
ctx.getString(R.string.skill_random_dice_roll_multiple, results.sum(), results.size, sides)
}

}

@Composable
override fun GraphicalOutput(ctx: SkillContext) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Headline(
text = if (results.size == 1) results[0].toString() else results.sum().toString()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
text = if (results.size == 1) results[0].toString() else results.sum().toString()
text = results.sum().toString()

)
if (results.size == 1) {
Subtitle(text = ctx.getString(R.string.skill_random_dice_sides, sides))
} else {
Subtitle(text = ctx.getString(R.string.skill_random_dice_sides, sides))
Body(text = results.joinToString(" + ") + " = ${results.sum()}")
}
Comment on lines +43 to +48
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (results.size == 1) {
Subtitle(text = ctx.getString(R.string.skill_random_dice_sides, sides))
} else {
Subtitle(text = ctx.getString(R.string.skill_random_dice_sides, sides))
Body(text = results.joinToString(" + ") + " = ${results.sum()}")
}
Subtitle(text = ctx.getString(R.string.skill_random_dice_sides, sides))
if (results.size != 1) {
Body(text = results.joinToString(" + ") + " = ${results.sum()}")
}

}
}
}

data class RandomNumber(
private val result: Int,
private val min: Int,
private val max: Int
) : RandomOutput {
override fun getSpeechOutput(ctx: SkillContext): String =
ctx.getString(R.string.skill_random_random_number, result, min, max)

@Composable
override fun GraphicalOutput(ctx: SkillContext) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Headline(text = result.toString())
Subtitle(text = ctx.getString(R.string.skill_random_range, min, max))
}
}
}

data object InvalidRange : RandomOutput {
override fun getSpeechOutput(ctx: SkillContext): String =
ctx.getString(R.string.skill_random_invalid_range)

@Composable
override fun GraphicalOutput(ctx: SkillContext) {
Headline(text = ctx.getString(R.string.skill_random_invalid_range))
}
}
}
65 changes: 65 additions & 0 deletions app/src/main/kotlin/org/stypox/dicio/skills/random/RandomSkill.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.stypox.dicio.skills.random

import org.dicio.numbers.unit.Number
import org.dicio.skill.context.SkillContext
import org.dicio.skill.skill.SkillInfo
import org.dicio.skill.skill.SkillOutput
import org.dicio.skill.standard.StandardRecognizerData
import org.dicio.skill.standard.StandardRecognizerSkill
import org.stypox.dicio.R
import org.stypox.dicio.sentences.Sentences.Random
import org.stypox.dicio.util.getString
import kotlin.random.Random as KotlinRandom
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a randomly-seeded RNG, or does it always have the same seed?


class RandomSkill(
correspondingSkillInfo: SkillInfo,
data: StandardRecognizerData<Random>
) : StandardRecognizerSkill<Random>(correspondingSkillInfo, data) {

private fun extractNumber(ctx: SkillContext, text: String?): Int? {
return text?.let {
ctx.parserFormatter?.extractNumber(it)?.mixedWithText
?.filterIsInstance<Number>()
?.firstOrNull()
?.integerValue()
?.toInt()
Comment on lines +21 to +25
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it.isInteger is false, then integerValue() is just 0.

Suggested change
ctx.parserFormatter?.extractNumber(it)?.mixedWithText
?.filterIsInstance<Number>()
?.firstOrNull()
?.integerValue()
?.toInt()
ctx.parserFormatter?.extractNumber(it)
?.first
?.takeIf { it.isInteger }
?.integerValue()
?.toInt()

Since this appeared often recently, maybe you could change the Number class into a sealed interface with two distinct implementations for integer and decimal, and make integerValue() return null if the value is a decimal. And to go further, I'd also add a firstInteger function to ExtractNumberParams that returns Long? and basically does the operations below. This changes will have to go in https://github.com/Stypox/dicio-numbers

}
}

override suspend fun generateOutput(ctx: SkillContext, inputData: Random): SkillOutput {
return when (inputData) {
is Random.CoinFlip -> {
val result = if (KotlinRandom.nextBoolean()) {
ctx.getString(R.string.skill_random_heads)
} else {
ctx.getString(R.string.skill_random_tails)
}
RandomOutput.CoinFlip(result)
}

is Random.DiceRoll -> {
val sides = extractNumber(ctx, inputData.sides) ?: 6
val amount = extractNumber(ctx, inputData.amount) ?: 1

if (sides <= 0 || amount <= 0 || amount > 100) {
return RandomOutput.InvalidRange
}

val results = List(amount) { KotlinRandom.nextInt(1, sides + 1) }
RandomOutput.DiceRoll(results, sides)
}

is Random.RandomNumber -> {
val min = extractNumber(ctx, inputData.min) ?: 1
val max = extractNumber(ctx, inputData.max) ?: 100

if (min > max) {
return RandomOutput.InvalidRange
}

val result = KotlinRandom.nextInt(min, max + 1)
RandomOutput.RandomNumber(result, min, max)
}
}
}
}
11 changes: 11 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@
<string name="skill_sentence_example_timer">Set a timer for five minutes</string>
<string name="skill_name_current_time">Current time</string>
<string name="skill_sentence_example_current_time">What time is it?</string>
<string name="skill_name_random">Random</string>
<string name="skill_sentence_example_random">Roll a 20-sided die</string>
<string name="skill_fallback_name_text">Text message</string>
<string name="skill_search_here_is_what_i_found">Here is what I found</string>
<string name="skill_search_no_results">The search returned no results, try telling me again what you want to search for</string>
Expand Down Expand Up @@ -246,4 +248,13 @@
<string name="failed_to_copy">Failed to copy to clipboard</string>
<string name="skill_translation_auto">Auto</string>
<string name="skill_search_duckduckgo_recaptcha">DuckDuckGo did not provide results, asking for a Captcha to be solved</string>
<string name="skill_random_coin_flip">The coin landed on %1$s</string>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather have two skill_random_coin_flip_heads and skill_random_coin_flip_tails strings, so for languages other than english the strings can be translated naturally in full. Then make the others directly uppercase.

<string name="skill_random_dice_roll">You rolled a %1$d on a %2$d-sided die</string>
<string name="skill_random_dice_roll_multiple">You rolled %1$d total on %2$d %3$d-sided dice</string>
<string name="skill_random_dice_sides">%1$d-sided die</string>
<string name="skill_random_random_number">Your random number is %1$d</string>
<string name="skill_random_range">Range: %1$d to %2$d</string>
<string name="skill_random_invalid_range">Invalid range, please specify valid numbers</string>
<string name="skill_random_heads">heads</string>
<string name="skill_random_tails">tails</string>
</resources>
25 changes: 25 additions & 0 deletions app/src/main/sentences/en/random.yml
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#384 (comment) also applies here: you could separate specific_dice_roll and specific_random_number sentences and disable those if NumberParserFormatter is not available.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
coin_flip:
- flip (a|the) coin<s?>
- heads or tails

dice_roll:
# Simple dice roll (defaults to 6 sides)
- roll (a|the|some)? (die|dice)
- throw (a|the|some)? (die|dice)

# Dice with specific sides
- (roll|throw) (a|an)? .sides. sided? (die|dice)
- (roll|throw) a? d .sides.

# RPG notation: "roll 1 d 20", "roll 4 d 10", etc
- (roll|throw) .amount. d .sides.

random_number:
# Generic random number (no range specified)
- (give me|pick|choose|select|generate|get) (a|any)? random? number
- random number

# Random number with specific range
- pick (a|any)? number (from|between)? .min. (to|and|through) .max.
- random number (from|between)? .min. (to|and|through) .max.
- (choose|select|give me|generate|get) (a|any)? random? number (from|between)? .min. (to|and|through) .max.
25 changes: 25 additions & 0 deletions app/src/main/sentences/en/rng.yml
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probablt a leftover and should be removed

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
coin_flip:
- flip (a|the) coin<s?>
- heads or tails

dice_roll:
# Simple dice roll (defaults to 6 sides)
- roll (a|the|some)? (die|dice)
- throw (a|the|some)? (die|dice)

# Dice with specific sides
- (roll|throw) (a|an)? .sides. sided? (die|dice)
- (roll|throw) a? d .sides.

# RPG notation: "roll 1 d 20", "roll 4 d 10", etc
- (roll|throw) .amount. d .sides.

random_number:
# Generic random number (no range specified)
- (give me|pick|choose|select|generate|get) (a|any)? random? number
- random number

# Random number with specific range
- pick (a|any)? number (from|between)? .min. (to|and|through) .max.
- random number (from|between)? .min. (to|and|through) .max.
- (choose|select|give me|generate|get) (a|any)? random? number (from|between)? .min. (to|and|through) .max.
17 changes: 17 additions & 0 deletions app/src/main/sentences/skill_definitions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,20 @@ skills:
type: string
- id: target
type: string

- id: random
specificity: high
sentences:
- id: coin_flip
- id: dice_roll
captures:
- id: sides
type: string
- id: amount
type: string
- id: random_number
captures:
- id: min
type: string
- id: max
type: string
1 change: 1 addition & 0 deletions fastlane/metadata/android/en-US/full_description.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Dicio answers questions about:
<li><b>jokes</b>: tells you a joke - <i>Tell me a joke</i></li>
<li><b>media</b>: play, pause, previous, next song - <i>Next Song</i></li>
<li><b>translation</b>: translate from/to any language with <b>Lingva</b> - <i>How do I say Football in German?</i></li>
<li><b>random</b>: Ask for a random number, flip a coin, or roll dice - <i>Pick a number between 1 and 100</i></li>
</ul>

Dicio can receive input through a text box or through <a href="https://github.com/alphacep/vosk-api/">Vosk</a> <i>speech to text</i>, and can talk using toasts or the Android <i>speech synthesis</i> engine. <b>Interactive graphical output</b> is provided by skills when they answer a question.
Expand Down