Initial commit (works somehow, at least)

This commit is contained in:
Oliver Rümpelein 2021-08-17 20:36:49 +02:00
commit ada58149f0
25 changed files with 1213 additions and 0 deletions

46
.gitignore vendored Normal file
View file

@ -0,0 +1,46 @@
.gradle
**/build/
!src/**/build/
gradle-app.setting
!gradle-wrapper.jar
.gradletasknamecache
*.class
*.log
*.ctxt
.mtj.tmp/
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
hs_err_pid*
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea/**/contentModel.xml
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
.idea/**/gradle.xml
.idea/**/libraries
cmake-build-*/
.idea/**/mongoSettings.xml
*.iws
out/
.idea_modules/
atlassian-ide-plugin.xml
.idea/replstate.xml
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
.idea/httpRequests
.idea/caches/build_file_checksums.ser

48
build.gradle.kts Normal file
View file

@ -0,0 +1,48 @@
import org.jetbrains.compose.compose
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.5.21"
id("org.jetbrains.compose") version "1.0.0-alpha1"
}
group = "de.pheerai"
version = "0.0.1-SNAPSHOT"
repositories {
jcenter()
mavenCentral()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
dependencies {
implementation(compose.desktop.currentOs)
testImplementation(kotlin("test"))
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile>() {
kotlinOptions.jvmTarget = "11"
kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
}
compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Deb, TargetFormat.Rpm)
packageName = "jeopardy"
val fullVersion = (project.version as String)
packageVersion = if (fullVersion.contains("-SNAPSHOT")) {
fullVersion.removeSuffix("-SNAPSHOT")
} else {
fullVersion
}
}
}
}

1
gradle.properties Normal file
View file

@ -0,0 +1 @@
kotlin.code.style=official

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Executable file
View file

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View file

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

9
settings.gradle.kts Normal file
View file

@ -0,0 +1,9 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
}
rootProject.name = "jeopardy"

150
src/main/kotlin/Main.kt Normal file
View file

@ -0,0 +1,150 @@
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import components.EndCard
import components.GameHeader
import components.PlayerBar
import components.QuestionGrid
import data.*
val game = Game(
title = "j-EP-ardy!",
doubleAfter = 5,
endGameAfter = 2,
players = listOf(
Player(name = "Oli"),
Player(name = "Gesina")
),
topics = listOf(
Topic(
color = ColorData(
red = 0x44,
green = 0x44,
blue = 0xAA
),
topic = "Wer MACKt's?",
questions = listOf(
QuestionData(
hint = "Mack",
answer = "Thomas",
points = 100u,
isDeferredDouble = true
),
QuestionData(
hint = "Mack",
answer = "Michael",
points = 200u
),
QuestionData(
hint = "Mack",
answer = "Ann-Kathrin",
points = 300u
)
)
),
Topic(
color = ColorData(
red = 0x22,
green = 0x22,
blue = 0xCC
),
topic = "Topic2",
questions = listOf(
QuestionData(
hint = "Wildcard",
answer = "any",
points = 1000u
),
QuestionData(
hint = "foo",
answer = "bar",
points = 5u
),
QuestionData(
hint = "foo",
answer = "bar",
points = 5u
)
)
),
Topic(
topic = "Topic3",
questions = listOf(
QuestionData(
hint = "Wildcard",
answer = "any",
points = 1000u
),
QuestionData(
hint = "foo",
answer = "bar",
points = 5u
),
QuestionData(
hint = "foo",
answer = "bar",
points = 5u
)
)
)
)
)
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "J-EP-ardy"
) {
MaterialTheme {
val playerPointMap: MutableMap<Player, Long> = game.players.map { it to 0L }.toMutableStateMap()
var questionsPlayed by remember { mutableStateOf(0) }
val doublePoints by remember { derivedStateOf { questionsPlayed >= game.doubleAfter } }
val gameEnded by remember { derivedStateOf { questionsPlayed >= game.endGameAfter } }
if (!gameEnded) {
Column(modifier = Modifier.fillMaxHeight().fillMaxWidth()) {
Row(
modifier = Modifier.fillMaxHeight(0.2f)
) {
GameHeader(
title = game.title,
color = game.headerColor
)
}
Spacer(modifier = Modifier.height(5.dp))
Row(
modifier = Modifier.fillMaxHeight()
) {
Column(Modifier.fillMaxWidth(0.8f)) {
QuestionGrid(
game,
doublePoints,
onResolveQuestion = {
questionsPlayed++
println("Questions: $questionsPlayed, Double: $doublePoints")
},
onPointsChange = { player, points ->
playerPointMap[player] = playerPointMap[player]?.plus(points) ?: 0
}
)
}
Spacer(modifier = Modifier.width(5.dp))
Column(Modifier.fillMaxWidth()) {
PlayerBar(players = playerPointMap)
}
}
}
} else {
EndCard(
playerPointMap = playerPointMap,
close = ::exitApplication
)
}
}
}
}

View file

@ -0,0 +1,49 @@
package components
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import data.Player
import data.toColor
@Suppress("FunctionName")
@Composable
fun EndCard(
playerPointMap: MutableMap<Player, Long>,
close: () -> Unit
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxWidth()
.fillMaxHeight()
) {
val firstThreePlaces = playerPointMap.entries
.sortedBy { it.value }
.map { it.key }
.take(3)
val firstPlayer = firstThreePlaces.first()
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Congratulations, ${firstPlayer.name}!")
for ((index, player) in firstThreePlaces.withIndex()) {
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Text(text = "Place ${index + 1}")
Surface(color = player.color.toColor()) {
Text(text = player.name)
}
}
}
Button(
onClick = close
) {
Text(text = "Close")
}
}
}
}

View file

@ -0,0 +1,30 @@
package components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.em
import data.ColorData
import data.toColor
@Suppress("FunctionName")
@Composable
fun GameHeader(title: String, color: ColorData?) {
Surface(
color = color.toColor(),
modifier = Modifier.fillMaxSize(1f)
) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = title, color = Color.LightGray, fontSize = 8.em)
}
}
}

View file

@ -0,0 +1,26 @@
package components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import data.Player
@Suppress("FunctionName")
@Composable
fun PlayerBar(players: MutableMap<Player, Long>) {
val numberOfCards = players.size
Column(
modifier = Modifier.fillMaxHeight()
.fillMaxWidth()
) {
for (
(index, playerEntry) in players.entries.sortedBy { it.key.name }.sortedByDescending { it.value }
.withIndex()
) {
val maxHeightFraction = 1f / (numberOfCards - index)
PlayerCard(playerEntry.key, maxHeightFraction, playerEntry.value)
}
}
}

View file

@ -0,0 +1,46 @@
package components
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import data.Player
import data.toColor
@Suppress("FunctionName")
@Composable
fun PlayerCard(
player: Player,
maxHeightFraction: Float,
points: Long
) {
Surface(
color = player.color.toColor(),
modifier = Modifier.fillMaxHeight(maxHeightFraction)
.fillMaxWidth()
.padding(5.dp),
shape = MaterialTheme.shapes.small
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceAround
) {
Text(
text = player.name,
fontSize = 2.5.em,
color = Color.LightGray
)
Text(
text = points.toString(),
fontSize = 5.em,
color = Color.White
)
}
}
}

View file

@ -0,0 +1,84 @@
package components
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerMoveFilter
import androidx.compose.ui.unit.dp
import components.questiondialog.QuestionDialogue
import data.Player
import data.QuestionData
import data.Topic
private fun Modifier.setButtonSize(heightFraction: Float) = this.fillMaxWidth(1f)
.fillMaxHeight(heightFraction)
.padding(5.dp)
@Composable
@Suppress("FunctionName")
fun Question(
questionData: QuestionData,
doublePoints: Boolean,
players: List<Player>,
topic: Topic,
heightFraction: Float,
color: Color,
onPointsChange: (Player, Long) -> Unit,
onResolve: () -> Unit
) {
var questionVisible by remember { mutableStateOf(false) }
var questionResolved by remember { mutableStateOf(false) }
var pointsButtonActive by remember { mutableStateOf(false) }
if (questionResolved) {
Surface(
shape = MaterialTheme.shapes.small,
modifier = Modifier
.setButtonSize(heightFraction),
color = Color.LightGray
) { }
} else {
Button(
shape = MaterialTheme.shapes.small,
colors = ButtonDefaults.buttonColors(
backgroundColor = color
),
onClick = { questionVisible = true },
modifier = Modifier
.setButtonSize(heightFraction)
.pointerMoveFilter(
onEnter = {
pointsButtonActive = true
false
},
onExit = {
pointsButtonActive = false
false
}
)
) {
Text(
text = "${questionData.actualDisplayPoints(doublePoints)}",
color = if (pointsButtonActive) Color.Gray else Color.LightGray
)
}
}
if (questionVisible) {
QuestionDialogue(
topic = topic,
questionData = questionData,
players = players,
doublePoints = doublePoints,
onResolve = {
questionResolved = true
questionVisible = false
onResolve()
},
onPointsChange = onPointsChange
)
}
}

View file

@ -0,0 +1,32 @@
package components
import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.*
import data.Game
import data.Player
@Suppress("FunctionName")
@Composable
fun QuestionGrid(
game: Game,
doublePoints: Boolean,
onPointsChange: (player: Player, points: Long) -> Unit,
onResolveQuestion: () -> Unit
) {
val users = game.players
val numberOfColumns = game.topics.size
Row {
for ((index, topic) in game.topics.withIndex()) {
val maxColumnFraction = 1f / (numberOfColumns - index)
TopicRow(
topic = topic,
doublePoints = doublePoints,
users = users,
columnFraction = maxColumnFraction,
onPointsChange = onPointsChange,
onResolveQuestion = onResolveQuestion
)
}
}
}

View file

@ -0,0 +1,62 @@
package components
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.em
import data.Player
import data.Topic
import data.toColor
@Composable
@Suppress("FunctionName")
fun TopicRow(
topic: Topic,
doublePoints: Boolean,
users: List<Player>,
columnFraction: Float,
onPointsChange: (Player, Long) -> Unit,
onResolveQuestion: () -> Unit
) {
val topicColor = topic.color.toColor()
Column(
modifier = Modifier.fillMaxHeight()
.fillMaxWidth(columnFraction)
) {
val numberOfButtons = topic.questions.size
Box(
modifier = Modifier.fillMaxWidth()
.fillMaxHeight(0.1f),
contentAlignment = Alignment.Center
) {
Text(
text = topic.topic,
fontSize = (2.5).em
)
}
Column(
modifier = Modifier.fillMaxWidth()
.fillMaxHeight(1f)
) {
for ((index, question) in topic.questions.withIndex()) {
val buttonHeightFraction = 1f / (numberOfButtons - index)
Question(
questionData = question,
topic = topic,
doublePoints = doublePoints,
players = users,
heightFraction = buttonHeightFraction,
color = topicColor,
onPointsChange = onPointsChange,
onResolve = onResolveQuestion
)
}
}
}
}

View file

@ -0,0 +1,22 @@
package components.common
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.TextUnit
@Suppress("FunctionName")
@Composable
fun DisabledButton(maxWidthFraction: Float, fontSize: TextUnit) {
Button(
onClick = {},
modifier = Modifier.fillMaxWidth(maxWidthFraction),
colors = ButtonDefaults.buttonColors(backgroundColor = Color.DarkGray)
) {
Text(text = "", fontSize = fontSize, color = Color.White)
}
}

View file

@ -0,0 +1,42 @@
package components.questiondialog
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.AlertDialog
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.UndecoratedWindowAlertDialogProvider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import components.QuestionDialogButtons
import data.Player
import data.QuestionData
import data.Topic
@Suppress("FunctionName")
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun QuestionDialogue(
topic: Topic,
questionData: QuestionData,
players: List<Player>,
doublePoints: Boolean,
onResolve: () -> Unit,
onPointsChange: (Player, Long) -> Unit
) {
AlertDialog(
dialogProvider = UndecoratedWindowAlertDialogProvider,
modifier = Modifier.fillMaxWidth()
.fillMaxHeight(),
onDismissRequest = { },
buttons = {
QuestionDialogButtons(
topic = topic,
questionData = questionData,
players = players,
onPointsChange = onPointsChange,
onResolve = onResolve,
doublePoints = doublePoints
)
}
)
}

View file

@ -0,0 +1,74 @@
package components.questiondialog
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.width
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import components.common.DisabledButton
import data.Player
import data.QuestionData
@Suppress("FunctionName")
@Composable
fun QuestionDialogPlayer(
player: Player,
fontSize: TextUnit,
questionData: QuestionData,
onPointsChange: (Player, Long) -> Unit,
questionPoints: Long
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
var hadSuccess by remember { mutableStateOf(false) }
var hadFail by remember { mutableStateOf(false) }
Text(
text = player.name,
modifier = Modifier.fillMaxWidth(0.6f),
fontSize = fontSize
)
Spacer(modifier = Modifier.width(5.dp))
if (hadFail || (hadSuccess && !questionData.isDeferredDouble)) {
DisabledButton(maxWidthFraction = 1f, fontSize = fontSize)
} else {
if (hadSuccess && questionData.isDeferredDouble) {
DisabledButton(0.7f, fontSize)
} else {
Button(
modifier = Modifier.fillMaxWidth(0.7f),
onClick = {
hadFail = true
onPointsChange(
player,
-questionPoints
)
},
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red)
) {
Text("Wrong", color = Color.White, fontSize = fontSize)
}
}
Spacer(modifier = Modifier.width(5.dp))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
hadSuccess = true
onPointsChange(player, questionPoints)
},
colors = ButtonDefaults.buttonColors(backgroundColor = Color.Green)
) {
Text("OK", color = Color.White, fontSize = fontSize)
}
}
}
}

View file

@ -0,0 +1,83 @@
package components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import components.questiondialog.QuestionDialogPlayer
import data.Player
import data.QuestionData
import data.Topic
import data.toColor
@Suppress("FunctionName")
@Composable
fun QuestionDialogButtons(
topic: Topic,
questionData: QuestionData,
players: List<Player>,
onPointsChange: (Player, Long) -> Unit,
onResolve: () -> Unit,
doublePoints: Boolean
) {
val fontSize = 5.em
Box(
modifier = Modifier.fillMaxHeight().fillMaxWidth()
.border(
border = BorderStroke(150.dp, topic.color.toColor())
)
.padding(150.dp),
contentAlignment = Alignment.Center,
) {
Column(
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceAround
) {
Text(
text = """"${topic.topic}" for ${questionData.actualUsagePoints(doublePoints)} Pts.""",
fontSize = 2.em
)
if (questionData.isDeferredDouble) {
Text(
text = """Deferred Double!""",
fontSize = 3.em
)
}
Text(
text = questionData.hint,
fontSize = 5.em
)
for (player in players) {
QuestionDialogPlayer(
player = player,
fontSize = fontSize,
questionData = questionData,
onPointsChange = onPointsChange,
questionPoints = questionData.actualUsagePoints(doublePoints).toLong()
)
Spacer(Modifier.height(20.dp))
}
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Button(
onClick = {
onResolve()
}
) {
Text("Dismiss", color = Color.LightGray, fontSize = fontSize)
}
}
}
}
}

View file

@ -0,0 +1,83 @@
package components.questiondialog.buttons
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import components.questiondialog.QuestionDialogPlayer
import data.Player
import data.QuestionData
import data.Topic
import data.toColor
@Suppress("FunctionName")
@Composable
fun QuestionDialogDeferredButtons(
topic: Topic,
questionData: QuestionData,
players: List<Player>,
onPointsChange: (Player, Long) -> Unit,
onResolve: () -> Unit,
doublePoints: Boolean
) {
val fontSize = 5.em
Box(
modifier = Modifier.fillMaxHeight().fillMaxWidth()
.border(
border = BorderStroke(150.dp, topic.color.toColor())
)
.padding(150.dp),
contentAlignment = Alignment.Center,
) {
Column(
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.SpaceAround
) {
Text(
text = """"${topic.topic}" for ${questionData.actualUsagePoints(doublePoints)} Pts.""",
fontSize = 2.em
)
if (questionData.isDeferredDouble) {
Text(
text = """Deferred Double!""",
fontSize = 3.em
)
}
Text(
text = questionData.hint,
fontSize = 5.em
)
for (player in players) {
QuestionDialogPlayer(
player = player,
fontSize = fontSize,
questionData = questionData,
onPointsChange = onPointsChange,
questionPoints = questionData.actualUsagePoints(doublePoints).toLong()
)
Spacer(Modifier.height(20.dp))
}
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Button(
onClick = {
onResolve()
}
) {
Text("Dismiss", color = Color.LightGray, fontSize = fontSize)
}
}
}
}
}

View file

@ -0,0 +1,12 @@
package data
import androidx.compose.ui.graphics.Color
class ColorData(
val red: Int,
val green: Int,
val blue: Int,
val alpha: Int = 0xFF
)
fun ColorData?.toColor() = this?.let { Color(it.red, it.green, it.blue, it.alpha) } ?: Color.Blue

View file

@ -0,0 +1,10 @@
package data
class Game(
val title: String,
val topics: List<Topic>,
val players: List<Player>,
val headerColor: ColorData? = null,
val doubleAfter: Long = Long.MAX_VALUE,
val endGameAfter: Long = Long.MAX_VALUE
)

View file

@ -0,0 +1,6 @@
package data
class Player(
val name: String,
val color: ColorData? = null
)

View file

@ -0,0 +1,12 @@
package data
class QuestionData(
val hint: String,
val answer: String,
val points: UInt,
val isDouble: Boolean = false,
val isDeferredDouble: Boolean = false
) {
fun actualDisplayPoints(double: Boolean) = points * (if (double) 2u else 1u)
fun actualUsagePoints(double: Boolean) = points * (if (double) 2u else 1u) * (if (isDouble) 2u else 1u)
}

View file

@ -0,0 +1,7 @@
package data
class Topic(
val topic: String,
val questions: List<QuestionData>,
val color: ColorData? = null
)