完成组合掷骰功能和 Android 支持
This commit is contained in:
@@ -26,17 +26,17 @@
|
|||||||
|
|
||||||
- [x] 50分 至少可以 roll D6 任意次
|
- [x] 50分 至少可以 roll D6 任意次
|
||||||
|
|
||||||
- [ ] 30分 可以实现 roll D4,D6,D8,D12(或者更多)任意切换
|
- [x] 30分 可以实现 roll D4,D6,D8,D12(或者更多)任意切换
|
||||||
|
|
||||||
- [ ] 20分 可以实现 roll n D6(指同时掷出 n 个六面骰并将点数加和)
|
- [x] 20分 可以实现 roll n D6(指同时掷出 n 个六面骰并将点数加和)
|
||||||
|
|
||||||
- [ ] 20分 可以实现 roll n D m(指同时掷出 n 个 m 面骰并将点数加和)
|
- [x] 20分 可以实现 roll n D m(指同时掷出 n 个 m 面骰并将点数加和)
|
||||||
|
|
||||||
- [ ] 20分 可以实现 roll $n_1$ D $m_1$ , $n_2$ D $m_2$ ...... 任意组合,最后点数取和
|
- [x] 20分 可以实现 roll $n_1$ D $m_1$ , $n_2$ D $m_2$ ...... 任意组合,最后点数取和
|
||||||
|
|
||||||
### 额外 共35分
|
### 额外 共35分
|
||||||
|
|
||||||
- [ ] 5分 支持 Android 平台的编译与安装
|
- [x] 5分 支持 Android 平台的编译与安装
|
||||||
|
|
||||||
- [ ] 5分 支持浏览器 WASM 编译
|
- [ ] 5分 支持浏览器 WASM 编译
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
- [ ] 5分 有应用图标
|
- [ ] 5分 有应用图标
|
||||||
|
|
||||||
- [ ] 5分 好看,达到我的审美
|
- [?] 5分 好看,达到我的审美
|
||||||
|
|
||||||
- [ ] 5分 根据实现方法,代码格式化等的特殊得分
|
- [ ] 5分 根据实现方法,代码格式化等的特殊得分
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.androidApplication)
|
||||||
|
alias(libs.plugins.composeMultiplatform)
|
||||||
|
alias(libs.plugins.composeCompiler)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "cn.itouc.git.diceroller.android"
|
||||||
|
compileSdk = 37
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "cn.itouc.git.diceroller"
|
||||||
|
minSdk = 24
|
||||||
|
targetSdk = 37
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget.set(JvmTarget.JVM_17)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":shared"))
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
|
implementation(libs.compose.runtime)
|
||||||
|
implementation(libs.compose.foundation)
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<application
|
||||||
|
android:label="组合掷骰子"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||||
|
<activity
|
||||||
|
android:name="cn.itouc.git.diceroller.android.MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package cn.itouc.git.diceroller.android
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import cn.itouc.git.diceroller.App
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.safeDrawingPadding()
|
||||||
|
.imePadding()
|
||||||
|
) {
|
||||||
|
App()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
plugins {
|
plugins {
|
||||||
|
alias(libs.plugins.androidApplication) apply false
|
||||||
|
alias(libs.plugins.androidKmpLibrary) apply false
|
||||||
// this is necessary to avoid the plugins to be loaded multiple times
|
// this is necessary to avoid the plugins to be loaded multiple times
|
||||||
// in each subproject's classloader
|
// in each subproject's classloader
|
||||||
alias(libs.plugins.composeMultiplatform) apply false
|
alias(libs.plugins.composeMultiplatform) apply false
|
||||||
|
|||||||
@@ -6,3 +6,5 @@ kotlin.daemon.jvmargs=-Xmx3072M
|
|||||||
org.gradle.jvmargs=-Xmx3072M -Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Xmx3072M -Dfile.encoding=UTF-8
|
||||||
org.gradle.configuration-cache=true
|
org.gradle.configuration-cache=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.overridePathCheck=true
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
agp = "9.4.0"
|
||||||
|
androidx-activity = "1.10.1"
|
||||||
androidx-lifecycle = "2.11.0"
|
androidx-lifecycle = "2.11.0"
|
||||||
composeMultiplatform = "1.12.0"
|
composeMultiplatform = "1.12.0"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
@@ -7,6 +9,7 @@ kotlinx-coroutines = "1.11.0"
|
|||||||
material3 = "1.12.0-alpha03"
|
material3 = "1.12.0-alpha03"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" }
|
||||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||||
kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||||
junit = { module = "junit:junit", version.ref = "junit" }
|
junit = { module = "junit:junit", version.ref = "junit" }
|
||||||
@@ -21,6 +24,8 @@ compose-uiToolingPreview = { module = "org.jetbrains.compose.ui:ui-tooling-previ
|
|||||||
kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutinesSwing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
androidKmpLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
|
||||||
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
|
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
|
||||||
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=bafc141b619ad6350fd975fc903156dd5c151998cc8b058e8c1044ab5f7b031f
|
distributionSha256Sum=acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
retries=0
|
retries=0
|
||||||
|
|||||||
@@ -35,3 +35,4 @@ plugins {
|
|||||||
|
|
||||||
include(":desktopApp")
|
include(":desktopApp")
|
||||||
include(":shared")
|
include(":shared")
|
||||||
|
include(":androidApp")
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
|
alias(libs.plugins.androidKmpLibrary)
|
||||||
alias(libs.plugins.kotlinMultiplatform)
|
alias(libs.plugins.kotlinMultiplatform)
|
||||||
alias(libs.plugins.composeMultiplatform)
|
alias(libs.plugins.composeMultiplatform)
|
||||||
alias(libs.plugins.composeCompiler)
|
alias(libs.plugins.composeCompiler)
|
||||||
@@ -7,6 +10,20 @@ plugins {
|
|||||||
kotlin {
|
kotlin {
|
||||||
jvm()
|
jvm()
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "cn.itouc.git.diceroller.shared"
|
||||||
|
compileSdk = 37
|
||||||
|
minSdk = 24
|
||||||
|
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget.set(JvmTarget.JVM_17)
|
||||||
|
}
|
||||||
|
|
||||||
|
androidResources {
|
||||||
|
enable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
implementation(libs.compose.runtime)
|
implementation(libs.compose.runtime)
|
||||||
|
|||||||
@@ -1,56 +1,229 @@
|
|||||||
package cn.itouc.git.diceroller
|
package cn.itouc.git.diceroller
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.safeContentPadding
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.unit.dp
|
||||||
import org.jetbrains.compose.resources.painterResource
|
|
||||||
|
|
||||||
import diceroller.shared.generated.resources.Res
|
|
||||||
import diceroller.shared.generated.resources.compose_multiplatform
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
data class DiceGroup(
|
||||||
|
val count: Int,
|
||||||
|
val sides: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DiceGroupResult(
|
||||||
|
val group: DiceGroup,
|
||||||
|
val points: List<Int>
|
||||||
|
) {
|
||||||
|
val total: Long
|
||||||
|
get() = points.sumOf { it.toLong() }
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
|
||||||
fun App() {
|
fun App() {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
var showContent by remember { mutableStateOf(false) }
|
var sidesInput by remember { mutableStateOf("6") }
|
||||||
var randomInt by remember { mutableStateOf(Random.nextInt()) }
|
var countInput by remember { mutableStateOf("1") }
|
||||||
|
|
||||||
|
var groups by remember {
|
||||||
|
mutableStateOf(emptyList<DiceGroup>())
|
||||||
|
}
|
||||||
|
|
||||||
|
var results by remember {
|
||||||
|
mutableStateOf(emptyList<DiceGroupResult>())
|
||||||
|
}
|
||||||
|
|
||||||
|
val diceSides = sidesInput.toIntOrNull()
|
||||||
|
val diceCount = countInput.toIntOrNull()
|
||||||
|
|
||||||
|
val validSides = diceSides != null &&
|
||||||
|
diceSides >= 1 &&
|
||||||
|
diceSides < Int.MAX_VALUE
|
||||||
|
|
||||||
|
val validCount = diceCount != null &&
|
||||||
|
diceCount in 1..1000
|
||||||
|
|
||||||
|
val totalDiceCount = groups.sumOf { it.count }
|
||||||
|
|
||||||
|
val withinLimit = diceCount != null &&
|
||||||
|
diceCount in 1..1000 &&
|
||||||
|
totalDiceCount + diceCount <= 1000
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colorScheme.primaryContainer)
|
.fillMaxSize()
|
||||||
.safeContentPadding()
|
.verticalScroll(rememberScrollState())
|
||||||
.fillMaxSize(),
|
.padding(24.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
|
Text(
|
||||||
|
text = "组合掷骰子",
|
||||||
|
style = MaterialTheme.typography.headlineMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = sidesInput,
|
||||||
|
onValueChange = { sidesInput = it },
|
||||||
|
label = { Text("骰子面数") },
|
||||||
|
singleLine = true,
|
||||||
|
isError = !validSides
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!validSides) {
|
||||||
|
Text(
|
||||||
|
text = "面数请输入 1~2147483646 的整数",
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = countInput,
|
||||||
|
onValueChange = { countInput = it },
|
||||||
|
label = { Text("骰子数量") },
|
||||||
|
singleLine = true,
|
||||||
|
isError = !validCount
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!validCount) {
|
||||||
|
Text(
|
||||||
|
text = "数量请输入 1~1000 的整数",
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
} else if (!withinLimit) {
|
||||||
|
Text(
|
||||||
|
text = "添加后将超过总数限制,最多允许 1000 个骰子",
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
|
enabled = validSides && validCount && withinLimit,
|
||||||
onClick = {
|
onClick = {
|
||||||
showContent = !showContent
|
val sides = diceSides
|
||||||
if (showContent) randomInt = Random.nextInt()
|
val count = diceCount
|
||||||
|
|
||||||
|
if (
|
||||||
|
sides != null &&
|
||||||
|
sides >= 1 &&
|
||||||
|
sides < Int.MAX_VALUE &&
|
||||||
|
count != null &&
|
||||||
|
count in 1..1000 &&
|
||||||
|
groups.sumOf { it.count } + count <= 1000
|
||||||
|
) {
|
||||||
|
groups = groups + DiceGroup(
|
||||||
|
count = count,
|
||||||
|
sides = sides
|
||||||
|
)
|
||||||
|
|
||||||
|
results = emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text("Click me!")
|
Text("添加骰子组")
|
||||||
}
|
}
|
||||||
AnimatedVisibility(showContent) {
|
|
||||||
Column(
|
Text(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
text = "当前组合",
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
style = MaterialTheme.typography.titleLarge
|
||||||
) {
|
)
|
||||||
Image(painterResource(Res.drawable.compose_multiplatform), null)
|
|
||||||
Text("a random int: $randomInt")
|
if (groups.isEmpty()) {
|
||||||
|
Text("还没有骰子组,请先添加")
|
||||||
|
} else {
|
||||||
|
groups.forEachIndexed { index, group ->
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "第 ${index + 1} 组:${group.count}D${group.sides}",
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
groups = groups.filterIndexed { groupIndex, _ ->
|
||||||
|
groupIndex != index
|
||||||
|
}
|
||||||
|
results = emptyList()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("删除")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text("共 ${groups.size} 组,$totalDiceCount / 1000 个骰子")
|
||||||
|
|
||||||
|
Button(
|
||||||
|
enabled = groups.isNotEmpty(),
|
||||||
|
onClick = {
|
||||||
|
results = groups.map { group ->
|
||||||
|
DiceGroupResult(
|
||||||
|
group = group,
|
||||||
|
points = List(group.count) {
|
||||||
|
Random.nextInt(1, group.sides + 1)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("全部掷骰")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = "掷骰结果",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
|
||||||
|
results.forEachIndexed { index, result ->
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "第 ${index + 1} 组:" +
|
||||||
|
"${result.group.count}D${result.group.sides}",
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
|
||||||
|
val pointsText = result.points
|
||||||
|
.take(20)
|
||||||
|
.joinToString(" + ")
|
||||||
|
|
||||||
|
val remainingHint =
|
||||||
|
if (result.points.size > 20) {
|
||||||
|
" + …"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("点数:$pointsText$remainingHint")
|
||||||
|
Text("小计:${result.total}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val grandTotal = results.sumOf { it.total }
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "总和:$grandTotal",
|
||||||
|
style = MaterialTheme.typography.headlineMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user