summaryrefslogtreecommitdiff
path: root/sw-ui/src/main/kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'sw-ui/src/main/kotlin')
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/SevenWondersUi.kt47
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt52
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/GlobalStyles.kt48
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/errors/ErrorDialog.kt57
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Board.kt227
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/BoardSummary.kt211
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/CardImage.kt78
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameScene.kt310
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameStyles.kt86
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Hand.kt276
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/HandRotationIndicator.kt56
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PlayerPreparedCardPresenter.kt80
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PreparedMove.kt73
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/ScoreTable.kt188
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Tokens.kt155
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/TransactionsSelector.kt265
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/CreateGameForm.kt58
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowser.kt69
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameList.kt213
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/PlayerInfo.kt105
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/ChooseNameForm.kt65
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/Home.kt22
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/HomeStyles.kt15
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt272
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/LobbyStyles.kt20
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt117
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialMath.kt57
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt139
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Table.kt97
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/names/RandomNameGenerator.kt546
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Actions.kt32
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/ApiActions.kt34
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Reducers.kt95
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Store.kt29
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Utils.kt31
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/RouteBasedSagas.kt44
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/Sagas.kt131
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/SagasFramework.kt106
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/router/Router.kt48
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/CoroutinesUtils.kt15
-rw-r--r--sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/StyleUtils.kt43
-rw-r--r--sw-ui/src/main/kotlin/webpack/WebpackUtils.kt9
42 files changed, 0 insertions, 4621 deletions
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/SevenWondersUi.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/SevenWondersUi.kt
deleted file mode 100644
index 0bd3400e..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/SevenWondersUi.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-package org.luxons.sevenwonders.ui
-
-import kotlinx.browser.window
-import kotlinx.coroutines.*
-import org.luxons.sevenwonders.ui.components.*
-import org.luxons.sevenwonders.ui.redux.*
-import org.luxons.sevenwonders.ui.redux.sagas.*
-import react.*
-import react.dom.client.*
-import react.redux.*
-import redux.*
-import web.dom.document
-import web.html.*
-
-fun main() {
- window.onload = { init() }
-}
-
-private fun init() {
- val rootElement = document.getElementById("root")
- if (rootElement == null) {
- console.error("Element with ID 'root' was not found, cannot bootstrap react app")
- return
- }
- renderRoot(rootElement)
-}
-
-private fun renderRoot(rootElement: HTMLElement) {
- val store = initRedux()
- val connectedApp = Provider.create {
- this.store = store
- Application()
- }
- createRoot(rootElement).render(connectedApp)
-}
-
-@OptIn(DelicateCoroutinesApi::class)
-private fun initRedux(): Store<SwState, RAction, WrapperAction> {
- val sagaManager = SagaManager<SwState, RAction, WrapperAction>()
- val store = configureStore(sagaManager = sagaManager)
- GlobalScope.launch {
- sagaManager.launchSaga(this) {
- rootSaga()
- }
- }
- return store
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt
deleted file mode 100644
index 2cf8b4f1..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/Application.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.luxons.sevenwonders.ui.components
-
-import js.core.jso
-import org.luxons.sevenwonders.ui.components.errors.*
-import org.luxons.sevenwonders.ui.components.game.*
-import org.luxons.sevenwonders.ui.components.gameBrowser.*
-import org.luxons.sevenwonders.ui.components.home.*
-import org.luxons.sevenwonders.ui.components.lobby.*
-import org.luxons.sevenwonders.ui.router.*
-import react.*
-import react.router.*
-import react.router.dom.*
-
-val Application = FC("Application") {
- ErrorDialog()
- RouterProvider {
- router = hashRouter
- }
-}
-
-// Using plain jso objects instead of createRoutesFromElements
-// because of a broken Route external interface (no properties)
-// See https://github.com/JetBrains/kotlin-wrappers/issues/2024
-private val hashRouter = createHashRouter(
- routes = arrayOf(
- jso {
- path = SwRoute.GAME_BROWSER.path
- Component = GameBrowser
- },
- jso {
- path = SwRoute.GAME.path
- Component = GameScene
- },
- jso {
- path = SwRoute.LOBBY.path
- Component = Lobby
- },
- jso {
- path = SwRoute.HOME.path
- Component = Home
- },
- jso {
- path = "*"
- Component = FC {
- Navigate {
- to = "/"
- replace = true
- }
- }
- },
- ),
-)
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/GlobalStyles.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/GlobalStyles.kt
deleted file mode 100644
index ee9c17ab..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/GlobalStyles.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.luxons.sevenwonders.ui.components
-
-import emotion.css.*
-import org.luxons.sevenwonders.ui.utils.*
-import web.cssom.*
-
-
-object GlobalStyles {
-
- val preGameWidth = 60.rem
-
- val zeusBackground = ClassName {
- background = "url('images/backgrounds/zeus-temple.jpg') center no-repeat".unsafeCast<Background>()
- backgroundSize = BackgroundSize.cover
- }
-
- val fullscreen = ClassName {
- position = Position.fixed
- top = 0.px
- left = 0.px
- bottom = 0.px
- right = 0.px
- overflow = Overflow.hidden
- }
-
- val papyrusBackground = ClassName {
- background = "url('images/backgrounds/papyrus.jpg')".unsafeCast<Background>()
- backgroundSize = BackgroundSize.cover
- }
-
- val centerLeftTopTransform = ClassName {
- left = 50.pct
- top = 50.pct
- transform = translate((-50).pct, (-50).pct)
- }
-
- val fixedCenter = ClassName(centerLeftTopTransform) {
- position = Position.fixed
- }
-
- val centerInPositionedParent = ClassName(centerLeftTopTransform) {
- position = Position.absolute
- }
-
- val noPadding = ClassName {
- padding = Padding(all = 0.px)
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/errors/ErrorDialog.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/errors/ErrorDialog.kt
deleted file mode 100644
index c728d405..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/errors/ErrorDialog.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.luxons.sevenwonders.ui.components.errors
-
-import blueprintjs.core.*
-import blueprintjs.icons.*
-import kotlinx.browser.*
-import org.luxons.sevenwonders.ui.redux.*
-import org.luxons.sevenwonders.ui.router.*
-import react.*
-import react.dom.html.ReactHTML.p
-import react.redux.*
-import redux.*
-
-val ErrorDialog = FC {
- val dispatch = useDispatch<RAction, WrapperAction>()
-
- ErrorDialogPresenter {
- errorMessage = useSwSelector { it.fatalError }
- goHome = { dispatch(Navigate(SwRoute.HOME)) }
- }
-}
-
-private external interface ErrorDialogProps : Props {
- var errorMessage: String?
- var goHome: () -> Unit
-}
-
-private val ErrorDialogPresenter = FC<ErrorDialogProps>("ErrorDialogPresenter") { props ->
- val errorMessage = props.errorMessage
- BpDialog {
- isOpen = errorMessage != null
- titleText = "Oops!"
- icon = BpIcon.create {
- icon = IconNames.ERROR
- intent = Intent.DANGER
- }
- onClose = { goHomeAndRefresh() }
-
- BpDialogBody {
- p {
- +(errorMessage ?: "fatal error")
- }
- }
- BpDialogFooter {
- BpButton {
- icon = IconNames.LOG_OUT
- onClick = { goHomeAndRefresh() }
-
- +"HOME"
- }
- }
- }
-}
-
-private fun goHomeAndRefresh() {
- // we don't use a redux action here because we actually want to redirect and refresh the page
- window.location.href = SwRoute.HOME.path
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Board.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Board.kt
deleted file mode 100644
index 1eb5f6f0..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Board.kt
+++ /dev/null
@@ -1,227 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import csstype.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.boards.*
-import org.luxons.sevenwonders.model.cards.*
-import org.luxons.sevenwonders.model.wonders.*
-import react.*
-import react.dom.html.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.img
-import web.cssom.*
-import web.html.*
-
-// card offsets in % of their size when displayed in columns
-private const val xOffset = 20
-private const val yOffset = 21
-
-external interface BoardComponentProps : PropsWithClassName {
- var board: Board
-}
-
-val BoardComponent = FC<BoardComponentProps>("Board") { props ->
- div {
- className = props.className
- tableCards(cardColumns = props.board.playedCards)
- wonderComponent(wonder = props.board.wonder, military = props.board.military)
- }
-}
-
-private fun ChildrenBuilder.tableCards(cardColumns: List<List<TableCard>>) {
- div {
- css {
- display = Display.flex
- justifyContent = JustifyContent.spaceAround
- height = 45.pct
- width = 100.pct
- }
- cardColumns.forEach { cards ->
- TableCardColumn {
- this.key = cards.first().color.toString()
- this.cards = cards
- }
- }
- }
-}
-
-private external interface TableCardColumnProps : PropsWithClassName {
- var cards: List<TableCard>
-}
-
-private val TableCardColumn = FC<TableCardColumnProps>("TableCardColumn") { props ->
- div {
- css {
- height = 100.pct
- width = 13.pct
- marginRight = 4.pct
- position = Position.relative
- }
- props.cards.forEachIndexed { index, card ->
- TableCard {
- this.card = card
- this.indexInColumn = index
- this.key = card.name
- }
- }
- }
-}
-
-private external interface TableCardProps : PropsWithClassName {
- var card: TableCard
- var indexInColumn: Int
-}
-
-private val TableCard = FC<TableCardProps>("TableCard") { props ->
- val highlightColor = if (props.card.playedDuringLastMove) NamedColor.gold else null
- CardImage {
- this.card = props.card
- this.highlightColor = highlightColor
-
- css {
- position = Position.absolute
- zIndex = integer(props.indexInColumn + 2) // go above the board and the built wonder cards
- transform = translate(
- tx = (props.indexInColumn * xOffset).pct,
- ty = (props.indexInColumn * yOffset).pct,
- )
- maxWidth = 100.pct
- maxHeight = 70.pct
-
- hover {
- zIndex = integer(1000)
- maxWidth = 110.pct
- maxHeight = 75.pct
- hoverHighlightStyle()
- }
- }
- }
-}
-
-private fun ChildrenBuilder.wonderComponent(wonder: ApiWonder, military: Military) {
- div {
- css {
- position = Position.relative
- width = 100.pct
- height = 40.pct
- }
- div {
- css {
- position = Position.absolute
- left = 50.pct
- top = 0.px
- transform = translatex((-50).pct)
- height = 100.pct
- maxWidth = 95.pct // same as wonder
-
- // bring to the foreground on hover
- hover { zIndex = integer(1000) }
- }
- img {
- src = "/images/wonders/${wonder.image}"
- title = wonder.name
- alt = "Wonder ${wonder.name}"
-
- css {
- borderRadius = "0.5%/1.5%".unsafeCast<BorderRadius>()
- boxShadow = BoxShadow(color = NamedColor.black, offsetX = 0.2.rem, offsetY = 0.2.rem, blurRadius = 0.5.rem)
- maxHeight = 100.pct
- maxWidth = 100.pct
- zIndex = integer(1) // go above the built wonder cards, but below the table cards
-
- hover { hoverHighlightStyle() }
- }
- }
- div {
- css {
- position = Position.absolute
- top = 20.pct
- right = (-80).px
- display = Display.flex
- flexDirection = FlexDirection.column
- alignItems = AlignItems.start
- }
- victoryPoints(military.victoryPoints) {
- css {
- marginBottom = 5.px
- }
- }
- defeatTokenCount(military.nbDefeatTokens) {
- css {
- marginTop = 5.px
- }
- }
- }
- wonder.stages.forEachIndexed { index, stage ->
- WonderStageElement {
- this.stage = stage
- css {
- wonderCardStyle(index, wonder.stages.size)
- }
- }
- }
- }
- }
-}
-
-private fun ChildrenBuilder.victoryPoints(points: Int, block: HTMLAttributes<HTMLDivElement>.() -> Unit = {}) {
- boardToken("military/victory1", points, block)
-}
-
-private fun ChildrenBuilder.defeatTokenCount(nbDefeatTokens: Int, block: HTMLAttributes<HTMLDivElement>.() -> Unit = {}) {
- boardToken("military/defeat1", nbDefeatTokens, block)
-}
-
-private fun ChildrenBuilder.boardToken(tokenName: String, count: Int, block: HTMLAttributes<HTMLDivElement>.() -> Unit) {
- tokenWithCount(
- tokenName = tokenName,
- count = count,
- countPosition = TokenCountPosition.RIGHT,
- brightText = true,
- ) {
- css {
- filter = dropShadow(0.2.rem, 0.2.rem, 0.5.rem, NamedColor.black)
- height = 15.pct
- }
- block()
- }
-}
-
-private external interface WonderStageElementProps : PropsWithClassName {
- var stage: ApiWonderStage
-}
-
-private val WonderStageElement = FC<WonderStageElementProps>("WonderStageElement") { props ->
- val back = props.stage.cardBack
- if (back != null) {
- val highlightColor = if (props.stage.builtDuringLastMove) NamedColor.gold else null
- CardBackImage {
- this.cardBack = back
- this.highlightColor = highlightColor
- this.className = props.className
- }
- } else {
- CardPlaceholderImage {
- this.className = props.className
- }
- }
-}
-
-private fun PropertiesBuilder.wonderCardStyle(stageIndex: Int, nbStages: Int) {
- position = Position.absolute
- top = 60.pct // makes the cards stick out of the bottom of the wonder
- left = stagePositionPercent(stageIndex, nbStages).pct
- maxWidth = 24.pct // ratio of card width to wonder width
- maxHeight = 90.pct // ratio of card height to wonder height
- zIndex = integer(-1) // below wonder (somehow 0 is not sufficient)
-}
-
-private fun stagePositionPercent(stageIndex: Int, nbStages: Int): Double = when (nbStages) {
- 2 -> 37.5 + stageIndex * 29.8 // 37.5 (29.8) 67.3
- 4 -> -1.5 + stageIndex * 26.7 // -1.5 (26.6) 25.1 (26.8) 51.9 (26.7) 78.6
- else -> 7.9 + stageIndex * 30.0
-}
-
-private fun PropertiesBuilder.hoverHighlightStyle() {
- highlightStyle(NamedColor.palegoldenrod)
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/BoardSummary.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/BoardSummary.kt
deleted file mode 100644
index 37de113c..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/BoardSummary.kt
+++ /dev/null
@@ -1,211 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import blueprintjs.core.*
-import csstype.*
-import emotion.css.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.api.*
-import org.luxons.sevenwonders.model.boards.*
-import org.luxons.sevenwonders.ui.components.gameBrowser.*
-import org.luxons.sevenwonders.ui.utils.*
-import react.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.hr
-import web.cssom.*
-
-enum class BoardSummarySide(
- val tokenCountPosition: TokenCountPosition,
- val alignment: AlignItems,
- val popoverPosition: PopoverPosition,
-) {
- LEFT(TokenCountPosition.RIGHT, AlignItems.flexStart, PopoverPosition.RIGHT),
- TOP(TokenCountPosition.OVER, AlignItems.flexStart, PopoverPosition.BOTTOM),
- RIGHT(TokenCountPosition.LEFT, AlignItems.flexEnd, PopoverPosition.LEFT),
- BOTTOM(TokenCountPosition.OVER, AlignItems.flexStart, PopoverPosition.TOP),
-}
-
-external interface BoardSummaryWithPopoverProps : PropsWithClassName {
- var player: PlayerDTO
- var board: Board
- var side: BoardSummarySide
-}
-
-val BoardSummaryWithPopover = FC<BoardSummaryWithPopoverProps>("BoardSummaryWithPopover") { props ->
- BpPopover {
- content = BoardComponent.create {
- className = GameStyles.fullBoardPreview
- board = props.board
- }
- position = props.side.popoverPosition
- interactionKind = PopoverInteractionKind.HOVER
- popoverClassName = ClassName {
- val bgColor = GameStyles.sandBgColor.withAlpha(0.7)
- backgroundColor = bgColor
- borderRadius = 0.5.rem
- padding = Padding(all = 0.5.rem)
-
- children(".bp4-popover-content") {
- background = None.none // overrides default white background
- }
- descendants(".bp4-popover-arrow-fill") {
- set(Variable("fill"), bgColor.toString()) // overrides default white arrow
- }
- descendants(".bp4-popover-arrow::before") {
- // The popover arrow is implemented with a simple square rotated 45 degrees (like a rhombus).
- // Since we use a semi-transparent background, we can see the box shadow of the rest of the arrow through
- // the popover, and thus we see the square. This boxShadow(transparent) is to avoid that.
- boxShadow = BoxShadow(0.px, 0.px, 0.px, 0.px, NamedColor.transparent)
- }
- }.toString()
-
- BoardSummary {
- this.className = props.className
- this.player = props.player
- this.board = props.board
- this.side = props.side
- }
- }
-}
-
-external interface BoardSummaryProps : PropsWithClassName {
- var player: PlayerDTO
- var board: Board
- var side: BoardSummarySide
- var showPreparationStatus: Boolean?
-}
-
-val BoardSummary = FC<BoardSummaryProps>("BoardSummary") { props ->
- div {
- css(props.className) {
- display = Display.flex
- flexDirection = FlexDirection.column
- alignItems = props.side.alignment
- padding = Padding(all = 0.5.rem)
- backgroundColor = NamedColor.palegoldenrod.withAlpha(0.5)
- zIndex = integer(50) // above table cards
-
- hover {
- backgroundColor = NamedColor.palegoldenrod
- }
- }
-
- val showPreparationStatus = props.showPreparationStatus ?: true
- topBar(props.player, props.side, showPreparationStatus)
- hr {
- css {
- margin = Margin(vertical = 0.5.rem, horizontal = 0.rem)
- width = 100.pct
- }
- }
- bottomBar(props.side, props.board, props.player, showPreparationStatus)
- }
-}
-
-private fun ChildrenBuilder.topBar(player: PlayerDTO, side: BoardSummarySide, showPreparationStatus: Boolean) {
- val playerIconSize = 25
- if (showPreparationStatus && side == BoardSummarySide.TOP) {
- div {
- css {
- display = Display.flex
- flexDirection = FlexDirection.row
- justifyContent = JustifyContent.spaceBetween
- width = 100.pct
- }
- PlayerInfo {
- this.player = player
- this.iconSize = playerIconSize
- }
- PlayerPreparedCard {
- this.playerDisplayName = player.displayName
- this.username = player.username
- }
- }
- } else {
- PlayerInfo {
- this.player = player
- this.iconSize = playerIconSize
- }
- }
-}
-
-private fun ChildrenBuilder.bottomBar(side: BoardSummarySide, board: Board, player: PlayerDTO, showPreparationStatus: Boolean) {
- div {
- css {
- display = Display.flex
- flexDirection = if (side == BoardSummarySide.TOP || side == BoardSummarySide.BOTTOM) FlexDirection.row else FlexDirection.column
- alignItems = side.alignment
- if (side != BoardSummarySide.TOP) {
- width = 100.pct
- }
- }
- val tokenSize = 2.rem
- generalCounts(board, tokenSize, side.tokenCountPosition)
- BpDivider()
- scienceTokens(board, tokenSize, side.tokenCountPosition)
- if (showPreparationStatus && side != BoardSummarySide.TOP) {
- BpDivider()
- div {
- css {
- width = 100.pct
- alignItems = AlignItems.center
- display = Display.flex
- flexDirection = FlexDirection.column
- }
- PlayerPreparedCard {
- this.playerDisplayName = player.displayName
- this.username = player.username
- }
- }
- }
- }
-}
-
-private fun ChildrenBuilder.generalCounts(
- board: Board,
- tokenSize: Length,
- countPosition: TokenCountPosition,
-) {
- goldIndicator(amount = board.gold, imgSize = tokenSize, amountPosition = countPosition)
- tokenWithCount(
- tokenName = "laurel-blue",
- count = board.bluePoints,
- imgSize = tokenSize,
- countPosition = countPosition,
- brightText = countPosition == TokenCountPosition.OVER,
- )
- tokenWithCount(
- tokenName = "military/shield",
- count = board.military.nbShields,
- imgSize = tokenSize,
- countPosition = countPosition,
- brightText = countPosition == TokenCountPosition.OVER,
- )
-}
-
-private fun ChildrenBuilder.scienceTokens(
- board: Board,
- tokenSize: Length,
- sciencePosition: TokenCountPosition,
-) {
- tokenWithCount(
- tokenName = "science/compass",
- count = board.science.nbCompasses,
- imgSize = tokenSize,
- countPosition = sciencePosition,
- brightText = sciencePosition == TokenCountPosition.OVER,
- )
- tokenWithCount(
- tokenName = "science/cog",
- count = board.science.nbWheels,
- imgSize = tokenSize,
- countPosition = sciencePosition,
- brightText = sciencePosition == TokenCountPosition.OVER,
- )
- tokenWithCount(
- tokenName = "science/tablet",
- count = board.science.nbTablets,
- imgSize = tokenSize,
- countPosition = sciencePosition,
- brightText = sciencePosition == TokenCountPosition.OVER,
- )
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/CardImage.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/CardImage.kt
deleted file mode 100644
index cffd509f..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/CardImage.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import csstype.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.cards.*
-import react.*
-import react.dom.html.ReactHTML.img
-import web.cssom.*
-import web.cssom.Color
-
-external interface CardImageProps : PropsWithClassName {
- var card: Card
- var faceDown: Boolean?
- var highlightColor: Color?
-}
-
-val CardImage = FC<CardImageProps>("CardImage") { props ->
- if (props.faceDown == true) {
- CardBackImage {
- cardBack = props.card.back
- highlightColor = props.highlightColor
- }
- } else {
- img {
- src = "/images/cards/${props.card.image}"
- title = props.card.name
- alt = "Card ${props.card.name}"
-
- css(props.className) {
- cardImageStyle(props.highlightColor)
- }
- }
- }
-}
-
-external interface CardBackImageProps : PropsWithClassName {
- var cardBack: CardBack
- var highlightColor: Color?
-}
-
-val CardBackImage = FC<CardBackImageProps>("CardBackImage") { props ->
- img {
- src = "/images/cards/back/${props.cardBack.image}"
- alt = "Card back (${props.cardBack.image})"
- css(props.className) {
- cardImageStyle(props.highlightColor)
- }
- }
-}
-
-val CardPlaceholderImage = FC<PropsWithClassName>("CardPlaceholderImage") { props ->
- img {
- src = "/images/cards/back/placeholder.png"
- alt = "Card placeholder"
- css(props.className) {
- opacity = number(0.20)
- borderRadius = 5.pct
- }
- }
-}
-
-private fun PropertiesBuilder.cardImageStyle(highlightColor: Color?) {
- borderRadius = 5.pct
- boxShadow = BoxShadow(offsetX = 2.px, offsetY = 2.px, blurRadius = 5.px, color = NamedColor.black)
- highlightStyle(highlightColor)
-}
-
-internal fun PropertiesBuilder.highlightStyle(highlightColor: Color?) {
- if (highlightColor != null) {
- boxShadow = BoxShadow(
- offsetX = 0.px,
- offsetY = 0.px,
- blurRadius = 1.rem,
- spreadRadius = 0.1.rem,
- color = highlightColor,
- )
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameScene.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameScene.kt
deleted file mode 100644
index 622e3f6d..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameScene.kt
+++ /dev/null
@@ -1,310 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import blueprintjs.core.*
-import blueprintjs.icons.*
-import emotion.react.*
-import org.luxons.sevenwonders.client.*
-import org.luxons.sevenwonders.model.*
-import org.luxons.sevenwonders.model.api.*
-import org.luxons.sevenwonders.model.boards.*
-import org.luxons.sevenwonders.model.cards.*
-import org.luxons.sevenwonders.model.resources.*
-import org.luxons.sevenwonders.ui.components.*
-import org.luxons.sevenwonders.ui.redux.*
-import org.luxons.sevenwonders.ui.utils.*
-import org.luxons.sevenwonders.ui.utils.Padding
-import react.*
-import react.dom.html.ReactHTML.div
-import web.cssom.*
-import web.cssom.Position
-
-external interface GameSceneProps : Props {
- var currentPlayer: PlayerDTO?
- var players: List<PlayerDTO>
- var game: GameState
- var preparedMove: PlayerMove?
- var preparedCard: HandCard?
- var sayReady: () -> Unit
- var prepareMove: (move: PlayerMove) -> Unit
- var unprepareMove: () -> Unit
- var leaveGame: () -> Unit
-}
-
-data class TransactionSelectorState(
- val moveType: MoveType,
- val card: HandCard,
- val transactionsOptions: ResourceTransactionOptions,
-)
-
-val GameScene = FC("GameScene") {
-
- val player = useSwSelector { it.currentPlayer }
- val gameState = useSwSelector { it.gameState }
- val dispatch = useSwDispatch()
-
- div {
- css(GlobalStyles.papyrusBackground, GlobalStyles.fullscreen) {}
-
- if (gameState == null) {
- BpNonIdealState {
- icon = IconNames.ERROR
- titleText = "Error: no game data"
- }
- } else {
- GameScenePresenter {
- currentPlayer = player
- players = gameState.players
- game = gameState
- preparedMove = gameState.currentPreparedMove
- preparedCard = gameState.currentPreparedCard
-
- prepareMove = { move -> dispatch(RequestPrepareMove(move)) }
- unprepareMove = { dispatch(RequestUnprepareMove()) }
- sayReady = { dispatch(RequestSayReady()) }
- leaveGame = { dispatch(RequestLeaveGame()) }
- }
- }
- }
-}
-
-private val GameScenePresenter = FC<GameSceneProps>("GameScenePresenter") { props ->
- var transactionSelectorState by useState<TransactionSelectorState>()
-
- val game = props.game
- val board = game.getOwnBoard()
- div {
- val maybeRed = GameStyles.pulsatingRed.takeIf { game.everyoneIsWaitingForMe() }
- css(maybeRed) {
- height = 100.pct
- }
- val action = game.action
- if (action is TurnAction.WatchScore) {
- scoreTableOverlay(action.scoreBoard, props.players, props.leaveGame)
- }
- actionInfo(game.action.message)
- BoardComponent {
- this.board = board
- css {
- padding = Padding(vertical = 7.rem, horizontal = 7.rem) // to fit the action info message & board summaries
- width = 100.pct
- height = 100.pct
- }
- }
- transactionsSelectorDialog(
- state = transactionSelectorState,
- neighbours = playerNeighbours(props.currentPlayer, props.players),
- prepareMove = { move ->
- props.prepareMove(move)
- transactionSelectorState = null
- },
- cancelTransactionSelection = { transactionSelectorState = null },
- )
- boardSummaries(game)
- handRotationIndicator(game.handRotationDirection)
- handCards(
- game = game,
- prepareMove = props.prepareMove,
- startTransactionsSelection = {
- transactionSelectorState = it
- }
- )
- val card = props.preparedCard
- val move = props.preparedMove
- if (card != null && move != null) {
- BpOverlay {
- isOpen = true
- onClose = { props.unprepareMove() }
-
- preparedMove(card, move, props.unprepareMove) {
- css(GlobalStyles.fixedCenter) {}
- }
- }
- }
- if (game.action is TurnAction.SayReady) {
- SayReadyButton {
- currentPlayer = props.currentPlayer
- players = props.players
- sayReady = props.sayReady
- }
- }
- }
-}
-
-private fun GameState.everyoneIsWaitingForMe(): Boolean {
- val onlyMeInTheGame = players.count { it.isHuman } == 1
- if (onlyMeInTheGame || currentPreparedMove != null) {
- return false
- }
- return preparedCardsByUsername.values.count { it != null } == players.size - 1
-}
-
-private fun playerNeighbours(currentPlayer: PlayerDTO?, players: List<PlayerDTO>): Pair<PlayerDTO, PlayerDTO> {
- val me = currentPlayer?.username ?: error("we shouldn't be trying to display this if there is no player")
- val size = players.size
- val myIndex = players.indexOfFirst { it.username == me }
- return players[(myIndex - 1 + size) % size] to players[(myIndex + 1) % size]
-}
-
-private fun ChildrenBuilder.actionInfo(message: String) {
- div {
- css(ClassName(Classes.DARK)) {
- position = Position.fixed
- top = 0.pct
- left = 0.pct
- margin = Margin(vertical = 0.4.rem, horizontal = 0.4.rem)
- maxWidth = 25.pct // leave space for 4 board summaries when there are 7 players
- }
- BpCard {
- elevation = Elevation.TWO
- css {
- padding = Padding(all = 0.px)
- }
-
- BpCallout {
- intent = Intent.PRIMARY
- icon = IconNames.INFO_SIGN
- +message
- }
- }
- }
-}
-
-private fun ChildrenBuilder.boardSummaries(game: GameState) {
- val leftBoard = game.getBoard(RelativeBoardPosition.LEFT)
- val rightBoard = game.getBoard(RelativeBoardPosition.RIGHT)
- val topBoards = game.getNonNeighbourBoards().reversed()
- selfBoardSummary(game.getOwnBoard(), game.players)
- leftPlayerBoardSummary(leftBoard, game.players)
- rightPlayerBoardSummary(rightBoard, game.players)
- if (topBoards.isNotEmpty()) {
- topPlayerBoardsSummaries(topBoards, game.players)
- }
-}
-
-private fun ChildrenBuilder.leftPlayerBoardSummary(board: Board, players: List<PlayerDTO>) {
- div {
- css {
- position = Position.absolute
- left = 0.px
- bottom = 40.pct
- }
- BoardSummaryWithPopover {
- this.player = players[board.playerIndex]
- this.board = board
- this.side = BoardSummarySide.LEFT
-
- css {
- borderTopRightRadius = 0.4.rem
- borderBottomRightRadius = 0.4.rem
- }
- }
- }
-}
-
-private fun ChildrenBuilder.rightPlayerBoardSummary(board: Board, players: List<PlayerDTO>) {
- div {
- css {
- position = Position.absolute
- right = 0.px
- bottom = 40.pct
- }
- BoardSummaryWithPopover {
- this.player = players[board.playerIndex]
- this.board = board
- this.side = BoardSummarySide.RIGHT
-
- css {
- borderTopLeftRadius = 0.4.rem
- borderBottomLeftRadius = 0.4.rem
- }
- }
- }
-}
-
-private fun ChildrenBuilder.topPlayerBoardsSummaries(boards: List<Board>, players: List<PlayerDTO>) {
- div {
- css {
- position = Position.absolute
- top = 0.px
- left = 50.pct
- transform = translate((-50).pct)
- display = Display.flex
- flexDirection = FlexDirection.row
- }
- boards.forEach { board ->
- BoardSummaryWithPopover {
- this.player = players[board.playerIndex]
- this.board = board
- this.side = BoardSummarySide.TOP
-
- css {
- borderBottomLeftRadius = 0.4.rem
- borderBottomRightRadius = 0.4.rem
- margin = Margin(vertical = 0.rem, horizontal = 2.rem)
- }
- }
- }
- }
-}
-
-private fun ChildrenBuilder.selfBoardSummary(board: Board, players: List<PlayerDTO>) {
- div {
- css {
- position = Position.absolute
- bottom = 0.px
- left = 0.px
- }
- BoardSummary {
- this.player = players[board.playerIndex]
- this.board = board
- this.side = BoardSummarySide.BOTTOM
- this.showPreparationStatus = false
-
- css {
- borderTopLeftRadius = 0.4.rem
- borderTopRightRadius = 0.4.rem
- margin = Margin(vertical = 0.rem, horizontal = 2.rem)
- }
- }
- }
-}
-
-private external interface SayReadyButtonProps : Props {
- var currentPlayer: PlayerDTO?
- var players: List<PlayerDTO>
- var sayReady: () -> Unit
-}
-
-private val SayReadyButton = FC<SayReadyButtonProps>("SayReadyButton") { props ->
- val isReady = props.currentPlayer?.isReady == true
- val intent = if (isReady) Intent.SUCCESS else Intent.PRIMARY
- div {
- css {
- position = Position.absolute
- bottom = 6.rem
- left = 50.pct
- transform = translate(tx = (-50).pct)
- zIndex = integer(2) // go above the wonder (1) and wonder-upgrade cards (0)
- }
- BpButtonGroup {
- BpButton {
- this.large = true
- this.disabled = isReady
- this.intent = intent
- this.icon = if (isReady) IconNames.TICK_CIRCLE else IconNames.PLAY
- this.onClick = { props.sayReady() }
-
- +"READY"
- }
- // not really a button, but nice for style
- BpButton {
- this.large = true
- this.icon = IconNames.PEOPLE
- this.disabled = isReady
- this.intent = intent
-
- +"${props.players.count { it.isReady }}/${props.players.size}"
- }
- }
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameStyles.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameStyles.kt
deleted file mode 100644
index f5ec475e..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/GameStyles.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import emotion.css.*
-import org.luxons.sevenwonders.ui.utils.*
-import web.cssom.*
-
-object GameStyles {
-
- val totalScore = ClassName {
- fontWeight = FontWeight.bold
- }
-
- val civilScore = scoreTagColorCss(Color("#2a73c9"))
- val scienceScore = scoreTagColorCss(Color("#0f9960"))
- val militaryScore = scoreTagColorCss(Color("#d03232"))
- val tradeScore = scoreTagColorCss(Color("#e2c11b"))
- val guildScore = scoreTagColorCss(Color("#663399"))
- val wonderScore = scoreTagColorCss(NamedColor.darkcyan)
- val goldScore = scoreTagColorCss(NamedColor.goldenrod)
-
- val sandBgColor = NamedColor.palegoldenrod
-
-
- val fullBoardPreview = ClassName {
- width = 40.vw
- height = 50.vh
- }
-
- val dimmedCard = ClassName {
- filter = brightness(60.pct) + grayscale(50.pct)
- }
-
- val transactionsSelector = ClassName {
- backgroundColor = sandBgColor
- width = 40.rem // default is 500px, we want to fit players on the side
-
- children(".bp4-dialog-header") {
- background = None.none // overrides default white background
- }
- }
-
- val bestPrice = ClassName {
- fontWeight = FontWeight.bold
- color = rgb(50, 120, 50)
- transform = rotate((-20).deg)
- }
-
- val discardMoveText = ClassName {
- display = Display.flex
- alignItems = AlignItems.center
- height = 3.rem
- fontSize = 2.rem
- color = NamedColor.goldenrod
- fontWeight = FontWeight.bold
- borderTop = Border(0.2.rem, LineStyle.solid, NamedColor.goldenrod)
- borderBottom = Border(0.2.rem, LineStyle.solid, NamedColor.goldenrod)
- }
-
- val scoreBoard = ClassName {
- backgroundColor = sandBgColor
- }
-
- private fun scoreTagColorCss(color: Color) = ClassName {
- backgroundColor = color
- }
-
- val pulsatingRed = ClassName {
- animation = Animation(
- name = keyframes {
- to {
- boxShadow = BoxShadow(
- inset = BoxShadowInset.inset,
- offsetX = 0.px,
- offsetY = 0.px,
- blurRadius = 20.px,
- spreadRadius = 8.px,
- color = NamedColor.red,
- )
- }
- },
- duration = 2.s,
- )
- animationIterationCount = AnimationIterationCount.infinite
- animationDirection = AnimationDirection.alternate
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Hand.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Hand.kt
deleted file mode 100644
index da71ea0b..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Hand.kt
+++ /dev/null
@@ -1,276 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import blueprintjs.core.*
-import blueprintjs.icons.*
-import csstype.*
-import emotion.react.*
-import org.luxons.sevenwonders.client.*
-import org.luxons.sevenwonders.model.*
-import org.luxons.sevenwonders.model.boards.*
-import org.luxons.sevenwonders.model.cards.*
-import org.luxons.sevenwonders.model.resources.*
-import org.luxons.sevenwonders.model.wonders.*
-import org.luxons.sevenwonders.ui.utils.*
-import react.*
-import react.dom.html.ReactHTML.div
-import web.cssom.*
-import web.cssom.Position
-import kotlin.math.*
-
-fun ChildrenBuilder.handCards(
- game: GameState,
- prepareMove: (PlayerMove) -> Unit,
- startTransactionsSelection: (TransactionSelectorState) -> Unit,
-) {
- HandCards {
- this.action = game.action
- this.ownBoard = game.getOwnBoard()
- this.preparedMove = game.currentPreparedMove
- this.prepareMove = { moveType: MoveType, card: HandCard, transactionOptions: ResourceTransactionOptions ->
- when (transactionOptions.size) {
- 1 -> prepareMove(PlayerMove(moveType, card.name, transactionOptions.single()))
- else -> startTransactionsSelection(TransactionSelectorState(moveType, card, transactionOptions))
- }
- }
- }
-}
-
-private enum class HandAction(
- val buttonTitle: String,
- val moveType: MoveType,
- val icon: IconName,
-) {
- PLAY("PLAY", MoveType.PLAY, "play"),
- PLAY_FREE("Play as this age's free card", MoveType.PLAY_FREE, "star"),
- PLAY_FREE_DISCARDED("Play discarded card", MoveType.PLAY_FREE_DISCARDED, "star"),
- COPY_GUILD("Copy this guild card", MoveType.COPY_GUILD, "duplicate")
-}
-
-external interface HandCardsProps : Props {
- var action: TurnAction
- var ownBoard: Board
- var preparedMove: PlayerMove?
- var prepareMove: (MoveType, HandCard, ResourceTransactionOptions) -> Unit
-}
-
-private val HandCards = FC<HandCardsProps>("HandCards") { props ->
- val hand = props.action.cardsToPlay() ?: return@FC
- div {
- css {
- handStyle()
- }
- hand.filter { it.name != props.preparedMove?.cardName }.forEachIndexed { index, c ->
- HandCard {
- card = c
- action = props.action
- ownBoard = props.ownBoard
- prepareMove = props.prepareMove
- key = index.toString()
- }
- }
- }
-}
-
-private fun TurnAction.cardsToPlay(): List<HandCard>? = when (this) {
- is TurnAction.PlayFromHand -> hand
- is TurnAction.PlayFromDiscarded -> discardedCards
- is TurnAction.PickNeighbourGuild -> neighbourGuildCards
- is TurnAction.SayReady,
- is TurnAction.Wait,
- is TurnAction.WatchScore -> null
-}
-
-private external interface HandCardProps : Props {
- var card: HandCard
- var action: TurnAction
- var ownBoard: Board
- var prepareMove: (MoveType, HandCard, ResourceTransactionOptions) -> Unit
-}
-
-private val HandCard = FC<HandCardProps>("HandCard") { props ->
- div {
- css(ClassName("hand-card")) {
- alignItems = AlignItems.flexEnd
- display = Display.grid
- margin = Margin(all = 0.2.rem)
- }
- CardImage {
- css {
- val isPlayable = props.card.playability.isPlayable || props.ownBoard.canPlayAnyCardForFree
- handCardImgStyle(isPlayable)
- }
- this.card = props.card
- }
- actionButtons(props.card, props.action, props.ownBoard, props.prepareMove)
- }
-}
-
-private fun ChildrenBuilder.actionButtons(
- card: HandCard,
- action: TurnAction,
- ownBoard: Board,
- prepareMove: (MoveType, HandCard, ResourceTransactionOptions) -> Unit,
-) {
- div {
- css {
- justifyContent = JustifyContent.center
- alignItems = AlignItems.flexEnd
- display = None.none
- gridRow = integer(1)
- gridColumn = integer(1)
-
- ancestorHover(".hand-card") {
- display = Display.flex
- }
- }
- BpButtonGroup {
- when (action) {
- is TurnAction.PlayFromHand -> {
- playCardButton(card, HandAction.PLAY, prepareMove)
- if (ownBoard.canPlayAnyCardForFree) {
- playCardButton(card.copy(playability = CardPlayability.SPECIAL_FREE), HandAction.PLAY_FREE, prepareMove)
- }
- }
- is TurnAction.PlayFromDiscarded -> playCardButton(card, HandAction.PLAY_FREE_DISCARDED, prepareMove)
- is TurnAction.PickNeighbourGuild -> playCardButton(card, HandAction.COPY_GUILD, prepareMove)
- is TurnAction.SayReady,
- is TurnAction.Wait,
- is TurnAction.WatchScore -> error("unsupported action in hand card: $action")
- }
-
- if (action.allowsBuildingWonder()) {
- upgradeWonderButton(card, ownBoard.wonder.buildability, prepareMove)
- }
- if (action.allowsDiscarding()) {
- discardButton(card, prepareMove)
- }
- }
- }
-}
-
-private fun ChildrenBuilder.playCardButton(
- card: HandCard,
- handAction: HandAction,
- prepareMove: (MoveType, HandCard, ResourceTransactionOptions) -> Unit,
-) {
- BpButton {
- title = "${handAction.buttonTitle} (${cardPlayabilityInfo(card.playability)})"
- large = true
- intent = Intent.SUCCESS
- disabled = !card.playability.isPlayable
- onClick = { prepareMove(handAction.moveType, card, card.playability.transactionOptions) }
-
- BpIcon { icon = handAction.icon }
-
- if (card.playability.isPlayable && !card.playability.isFree) {
- priceInfo(card.playability.minPrice)
- }
- }
-}
-
-private fun ChildrenBuilder.upgradeWonderButton(
- card: HandCard,
- wonderBuildability: WonderBuildability,
- prepareMove: (MoveType, HandCard, ResourceTransactionOptions) -> Unit,
-) {
- BpButton {
- title = "UPGRADE WONDER (${wonderBuildabilityInfo(wonderBuildability)})"
- large = true
- intent = Intent.PRIMARY
- disabled = !wonderBuildability.isBuildable
- onClick = { prepareMove(MoveType.UPGRADE_WONDER, card, wonderBuildability.transactionsOptions) }
-
- BpIcon { icon = IconNames.KEY_SHIFT }
- if (wonderBuildability.isBuildable && !wonderBuildability.isFree) {
- priceInfo(wonderBuildability.minPrice)
- }
- }
-}
-
-private fun ChildrenBuilder.discardButton(card: HandCard, prepareMove: (MoveType, HandCard, ResourceTransactionOptions) -> Unit) {
- BpButton {
- title = "DISCARD (+3 coins)" // TODO remove hardcoded value
- large = true
- intent = Intent.DANGER
- icon = IconNames.CROSS
- onClick = { prepareMove(MoveType.DISCARD, card, singleOptionNoTransactionNeeded()) }
- }
-}
-
-private fun cardPlayabilityInfo(playability: CardPlayability) = when (playability.isPlayable) {
- true -> priceText(-playability.minPrice)
- false -> playability.playabilityLevel.message
-}
-
-private fun wonderBuildabilityInfo(buildability: WonderBuildability) = when (buildability.isBuildable) {
- true -> priceText(-buildability.minPrice)
- false -> buildability.playabilityLevel.message
-}
-
-private fun priceText(amount: Int) = when (amount.absoluteValue) {
- 0 -> "free"
- 1 -> "${pricePrefix(amount)}$amount coin"
- else -> "${pricePrefix(amount)}$amount coins"
-}
-
-private fun pricePrefix(amount: Int) = when {
- amount > 0 -> "+"
- else -> ""
-}
-
-private fun ChildrenBuilder.priceInfo(amount: Int) {
- goldIndicator(
- amount = amount,
- amountPosition = TokenCountPosition.OVER,
- imgSize = 1.rem,
- customCountStyle = {
- fontFamily = FontFamily.sansSerif
- fontSize = 0.8.rem
- },
- ) {
- css {
- position = Position.absolute
- top = (-0.2).rem
- left = (-0.2).rem
- }
- }
-}
-
-private fun PropertiesBuilder.handStyle() {
- alignItems = AlignItems.center
- bottom = 0.px
- display = Display.flex
- height = 345.px
- left = 50.pct
- maxHeight = 25.vw
- position = Position.absolute
- transform = translate(tx = (-50).pct, ty = 65.pct)
- transition = Transition(TransitionProperty.all, duration = 0.5.s, timingFunction = TransitionTimingFunction.ease)
- zIndex = integer(30)
-
- hover {
- bottom = 1.rem
- zIndex = integer(60)
- transform = translate(tx = (-50).pct, ty = 0.pct)
- }
-}
-
-private fun PropertiesBuilder.handCardImgStyle(isPlayable: Boolean) {
- gridRow = integer(1)
- gridColumn = integer(1)
- maxWidth = 13.vw
- maxHeight = 60.vh
- transition = Transition(TransitionProperty.all, duration = 0.1.s, timingFunction = TransitionTimingFunction.ease)
- width = 11.rem
-
- ancestorHover(".hand-card") {
- boxShadow = BoxShadow(offsetX = 0.px, offsetY = 10.px, blurRadius = 40.px, color = NamedColor.black)
- width = 14.rem
- maxWidth = 15.vw
- maxHeight = 90.vh
- }
-
- if (!isPlayable) {
- filter = grayscale(50.pct) + contrast(50.pct)
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/HandRotationIndicator.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/HandRotationIndicator.kt
deleted file mode 100644
index 72cb6b65..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/HandRotationIndicator.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import blueprintjs.core.*
-import blueprintjs.icons.*
-import csstype.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.cards.*
-import react.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.img
-import web.cssom.*
-import web.cssom.Position
-
-fun ChildrenBuilder.handRotationIndicator(direction: HandRotationDirection) {
- div {
- css {
- position = Position.absolute
- display = Display.flex
- alignItems = AlignItems.center
- bottom = 25.vh
- val sideDistance = 2.rem
- when (direction) {
- HandRotationDirection.LEFT -> left = sideDistance
- HandRotationDirection.RIGHT -> right = sideDistance
- }
- }
-
- title = "Your hand will be passed to the player on your $direction after playing this card."
-
- when (direction) {
- HandRotationDirection.LEFT -> {
- BpIcon {
- icon = IconNames.ARROW_LEFT
- size = 25
- }
- handCardsImg()
- }
- HandRotationDirection.RIGHT -> {
- handCardsImg()
- BpIcon {
- icon = IconNames.ARROW_RIGHT
- size = 25
- }
- }
- }
- }
-}
-
-private fun ChildrenBuilder.handCardsImg() {
- img {
- src = "images/hand-cards5.png"
- css {
- width = 4.rem
- }
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PlayerPreparedCardPresenter.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PlayerPreparedCardPresenter.kt
deleted file mode 100644
index 627693e1..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PlayerPreparedCardPresenter.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import csstype.*
-import emotion.css.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.cards.*
-import org.luxons.sevenwonders.ui.redux.*
-import org.luxons.sevenwonders.ui.utils.*
-import react.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.img
-import web.cssom.*
-
-external interface PlayerPreparedCardProps : Props {
- var playerDisplayName: String
- var username: String
-}
-
-val PlayerPreparedCard = FC<PlayerPreparedCardProps>("PlayerPreparedCard") { props ->
- val cardBack = useSwSelector { it.gameState?.preparedCardsByUsername?.get(props.username) }
-
- PlayerPreparedCardPresenter {
- this.playerDisplayName = props.playerDisplayName
- this.cardBack = cardBack
- }
-}
-
-external interface PlayerPreparedCardPresenterProps : Props {
- var playerDisplayName: String
- var cardBack: CardBack?
-}
-
-private val PlayerPreparedCardPresenter = FC<PlayerPreparedCardPresenterProps>("PlayerPreparedCardPresenter") { props ->
- val cardBack = props.cardBack
- val sideSize = 30.px
- div {
- css {
- width = sideSize
- height = sideSize
- }
- title = if (cardBack == null) {
- "${props.playerDisplayName} is still thinking…"
- } else {
- "${props.playerDisplayName} is ready to play this turn"
- }
-
- if (cardBack != null) {
- CardBackImage {
- this.cardBack = cardBack
- css {
- maxHeight = sideSize
- }
- }
- } else {
- RotatingGear {
- css {
- maxHeight = sideSize
- }
- }
- }
- }
-}
-
-private val RotatingGear = FC<PropsWithClassName> { props ->
- img {
- src = "images/gear-50.png"
- css(props.className) {
- animation = Animation(
- name = keyframes {
- to {
- transform = rotate(360.deg)
- }
- },
- duration = 1.5.s,
- timingFunction = cubicBezier(0.2, 0.9, 0.7, 1.3),
- )
- animationIterationCount = AnimationIterationCount.infinite
- }
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PreparedMove.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PreparedMove.kt
deleted file mode 100644
index 3ecdc741..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/PreparedMove.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import blueprintjs.core.*
-import blueprintjs.icons.*
-import csstype.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.*
-import org.luxons.sevenwonders.model.cards.*
-import org.luxons.sevenwonders.ui.components.*
-import react.*
-import react.dom.html.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.img
-import web.cssom.*
-import web.cssom.Position
-import web.html.*
-
-fun ChildrenBuilder.preparedMove(
- card: HandCard,
- move: PlayerMove,
- unprepareMove: () -> Unit,
- block: HTMLAttributes<HTMLDivElement>.() -> Unit,
-) {
- div {
- block()
- CardImage {
- this.card = card
- if (move.type == MoveType.DISCARD || move.type == MoveType.UPGRADE_WONDER) {
- this.className = GameStyles.dimmedCard
- }
- }
- if (move.type == MoveType.DISCARD) {
- discardText()
- }
- if (move.type == MoveType.UPGRADE_WONDER) {
- upgradeWonderSymbol()
- }
- div {
- css {
- position = web.cssom.Position.absolute
- top = 0.px
- right = 0.px
- }
- BpButton {
- icon = IconNames.CROSS
- title = "Cancel prepared move"
- small = true
- intent = Intent.DANGER
- onClick = { unprepareMove() }
- }
- }
- }
-}
-
-private fun ChildrenBuilder.discardText() {
- div {
- css(GlobalStyles.centerInPositionedParent, GameStyles.discardMoveText) {}
- +"DISCARD"
- }
-}
-
-private fun ChildrenBuilder.upgradeWonderSymbol() {
- img {
- src = "/images/wonder-upgrade-bright.png"
- css {
- width = 8.rem
- position = Position.absolute
- left = 10.pct
- top = 50.pct
- transform = translate((-50).pct, (-50).pct)
- }
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/ScoreTable.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/ScoreTable.kt
deleted file mode 100644
index cd54446f..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/ScoreTable.kt
+++ /dev/null
@@ -1,188 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import blueprintjs.core.*
-import blueprintjs.icons.*
-import csstype.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.api.*
-import org.luxons.sevenwonders.model.score.*
-import org.luxons.sevenwonders.ui.components.*
-import org.luxons.sevenwonders.ui.utils.*
-import react.*
-import react.dom.html.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.h1
-import react.dom.html.ReactHTML.sup
-import react.dom.html.ReactHTML.tbody
-import react.dom.html.ReactHTML.td
-import react.dom.html.ReactHTML.th
-import react.dom.html.ReactHTML.thead
-import react.dom.html.ReactHTML.tr
-import web.cssom.*
-
-fun ChildrenBuilder.scoreTableOverlay(scoreBoard: ScoreBoard, players: List<PlayerDTO>, leaveGame: () -> Unit) {
- BpOverlay {
- isOpen = true
-
- BpCard {
- css(GlobalStyles.fixedCenter, GameStyles.scoreBoard) {}
-
- div {
- // FIXME this doesn't look right, the scoreBoard class is applied at both levels
- css(GameStyles.scoreBoard) { // loads the styles so that they can be picked up by bpCard
- display = Display.flex
- flexDirection = FlexDirection.column
- alignItems = AlignItems.center
- }
- h1 {
- css {
- marginTop = 0.px
- }
- +"Score Board"
- }
- scoreTable(scoreBoard, players)
- div {
- css {
- marginTop = 1.rem
- }
- BpButton {
- intent = Intent.WARNING
- rightIcon = "log-out"
- large = true
- onClick = { leaveGame() }
-
- +"LEAVE"
- }
- }
- }
- }
- }
-}
-
-private fun ChildrenBuilder.scoreTable(scoreBoard: ScoreBoard, players: List<PlayerDTO>) {
- BpHTMLTable {
- bordered = false
- interactive = true
-
- thead {
- tr {
- th {
- fullCenterInlineStyle()
- +"Rank"
- }
- th {
- fullCenterInlineStyle()
- colSpan = 2
-
- +"Player"
- }
- th {
- fullCenterInlineStyle()
- +"Score"
- }
- ScoreCategory.values().forEach {
- th {
- fullCenterInlineStyle()
- +it.title
- }
- }
- }
- }
- tbody {
- scoreBoard.scores.forEachIndexed { index, score ->
- val player = players[score.playerIndex]
- tr {
- td {
- fullCenterInlineStyle()
- ordinal(scoreBoard.ranks[index])
- }
- td {
- fullCenterInlineStyle()
- BpIcon {
- icon = player.icon?.name ?: IconNames.USER
- size = 25
- }
- }
- td {
- inlineStyles {
- verticalAlign = VerticalAlign.middle
- }
- +player.displayName
- }
- td {
- fullCenterInlineStyle()
- BpTag {
- large = true
- round = true
- minimal = true
- className = GameStyles.totalScore
-
- +"${score.totalPoints}"
- }
- }
- ScoreCategory.values().forEach { cat ->
- td {
- fullCenterInlineStyle()
- BpTag {
- large = true
- round = true
- fill = true
- icon = cat.icon
- className = classNameForCategory(cat)
-
- +"${score.pointsByCategory[cat]}"
- }
- }
- }
- }
- }
- }
- }
-}
-
-private fun ChildrenBuilder.ordinal(value: Int) {
- +"$value"
- sup { +value.ordinalIndicator() }
-}
-
-private fun Int.ordinalIndicator() = when {
- this % 10 == 1 && this != 11 -> "st"
- this % 10 == 2 && this != 12 -> "nd"
- this % 10 == 3 && this != 13 -> "rd"
- else -> "th"
-}
-
-private fun HTMLAttributes<*>.fullCenterInlineStyle() {
- // inline styles necessary to overcome blueprintJS overrides
- inlineStyles {
- textAlign = TextAlign.center
- verticalAlign = VerticalAlign.middle
- }
-}
-
-private fun classNameForCategory(cat: ScoreCategory): ClassName = when (cat) {
- ScoreCategory.CIVIL -> GameStyles.civilScore
- ScoreCategory.SCIENCE -> GameStyles.scienceScore
- ScoreCategory.MILITARY -> GameStyles.militaryScore
- ScoreCategory.TRADE -> GameStyles.tradeScore
- ScoreCategory.GUILD -> GameStyles.guildScore
- ScoreCategory.WONDER -> GameStyles.wonderScore
- ScoreCategory.GOLD -> GameStyles.goldScore
-}
-
-private val ScoreCategory.icon: String
- get() = when (this) {
- ScoreCategory.CIVIL -> IconNames.OFFICE
- ScoreCategory.SCIENCE -> IconNames.LAB_TEST
- ScoreCategory.MILITARY -> IconNames.CUT
- ScoreCategory.TRADE -> IconNames.SWAP_HORIZONTAL
- ScoreCategory.GUILD -> IconNames.CLEAN // stars
- ScoreCategory.WONDER -> IconNames.SYMBOL_TRIANGLE_UP
- ScoreCategory.GOLD -> IconNames.DOLLAR
- }
-
-// Potentially useful emojis:
-// Greek temple: 🏛
-// Cog (science): ⚙️
-// Swords (war): ⚔️
-// Gold bag: 💰
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Tokens.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Tokens.kt
deleted file mode 100644
index 01975f7e..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/Tokens.kt
+++ /dev/null
@@ -1,155 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import csstype.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.resources.*
-import org.luxons.sevenwonders.ui.components.*
-import react.*
-import react.dom.html.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.img
-import react.dom.html.ReactHTML.span
-import web.cssom.*
-import web.html.*
-
-private fun getResourceTokenName(resourceType: ResourceType) = "resources/${resourceType.toString().lowercase()}"
-
-private fun getTokenImagePath(tokenName: String) = "/images/tokens/$tokenName.png"
-
-enum class TokenCountPosition {
- LEFT,
- RIGHT,
- OVER,
-}
-
-fun ChildrenBuilder.goldIndicator(
- amount: Int,
- amountPosition: TokenCountPosition = TokenCountPosition.OVER,
- imgSize: Length = 3.rem,
- customCountStyle: PropertiesBuilder.() -> Unit = {},
- block: HTMLAttributes<HTMLDivElement>.() -> Unit = {},
-) {
- tokenWithCount(
- tokenName = "coin",
- title = "$amount gold coins",
- imgSize = imgSize,
- count = amount,
- countPosition = amountPosition,
- customCountStyle = customCountStyle,
- block = block,
- )
-}
-
-fun ChildrenBuilder.resourceImage(
- resourceType: ResourceType,
- title: String = resourceType.toString(),
- size: Length?,
-) {
- TokenImage {
- this.tokenName = getResourceTokenName(resourceType)
- this.title = title
- this.size = size
- }
-}
-
-fun ChildrenBuilder.tokenWithCount(
- tokenName: String,
- count: Int,
- title: String = tokenName,
- imgSize: Length? = null,
- countPosition: TokenCountPosition = TokenCountPosition.RIGHT,
- brightText: Boolean = false,
- customCountStyle: PropertiesBuilder.() -> Unit = {},
- block: HTMLAttributes<HTMLDivElement>.() -> Unit = {},
-) {
- div {
- block()
- val tokenCountSize = if (imgSize != null) 0.6 * imgSize else 1.5.rem
- when (countPosition) {
- TokenCountPosition.RIGHT -> {
- TokenImage {
- this.tokenName = tokenName
- this.title = title
- this.size = imgSize
- }
- span {
- css {
- tokenCountStyle(tokenCountSize, brightText, customCountStyle)
- marginLeft = 0.2.rem
- }
- +"× $count"
- }
- }
-
- TokenCountPosition.LEFT -> {
- span {
- css {
- tokenCountStyle(tokenCountSize, brightText, customCountStyle)
- marginRight = 0.2.rem
- }
- +"$count ×"
- }
- TokenImage {
- this.tokenName = tokenName
- this.title = title
- this.size = imgSize
- }
- }
-
- TokenCountPosition.OVER -> {
- div {
- css {
- position = Position.relative
- // if container becomes large, this one stays small so that children stay on top of each other
- width = Length.fitContent
- }
- TokenImage {
- this.tokenName = tokenName
- this.title = title
- this.size = imgSize
- }
- span {
- css(GlobalStyles.centerInPositionedParent) {
- tokenCountStyle(tokenCountSize, brightText, customCountStyle)
- }
- +"$count"
- }
- }
- }
- }
- }
-}
-
-external interface TokenImageProps : Props {
- var tokenName: String
- var title: String?
- var size: Length?
-}
-
-val TokenImage = FC<TokenImageProps> { props ->
- img {
- src = getTokenImagePath(props.tokenName)
- title = props.title ?: props.tokenName
- alt = props.tokenName
-
- css {
- height = props.size ?: 100.pct
- if (props.size != null) {
- width = props.size
- }
- verticalAlign = VerticalAlign.middle
- }
- }
-}
-
-private fun PropertiesBuilder.tokenCountStyle(
- size: Length,
- brightText: Boolean,
- customStyle: PropertiesBuilder.() -> Unit = {},
-) {
- fontFamily = string("Acme")
- fontSize = size
- verticalAlign = VerticalAlign.middle
- color = if (brightText) NamedColor.white else NamedColor.black
- customStyle()
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/TransactionsSelector.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/TransactionsSelector.kt
deleted file mode 100644
index cdf97ad9..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/game/TransactionsSelector.kt
+++ /dev/null
@@ -1,265 +0,0 @@
-package org.luxons.sevenwonders.ui.components.game
-
-import blueprintjs.core.*
-import blueprintjs.icons.*
-import csstype.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.*
-import org.luxons.sevenwonders.model.api.*
-import org.luxons.sevenwonders.model.resources.*
-import org.luxons.sevenwonders.model.resources.Provider
-import org.luxons.sevenwonders.ui.components.gameBrowser.*
-import org.luxons.sevenwonders.ui.utils.*
-import org.luxons.sevenwonders.ui.utils.Margin
-import react.*
-import react.dom.html.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.p
-import react.dom.html.ReactHTML.tbody
-import react.dom.html.ReactHTML.td
-import react.dom.html.ReactHTML.tr
-import web.cssom.*
-import web.html.*
-
-fun ChildrenBuilder.transactionsSelectorDialog(
- state: TransactionSelectorState?,
- neighbours: Pair<PlayerDTO, PlayerDTO>,
- prepareMove: (PlayerMove) -> Unit,
- cancelTransactionSelection: () -> Unit,
-) {
- BpDialog {
- isOpen = state != null
- titleText = "Trading time!"
- canEscapeKeyClose = true
- canOutsideClickClose = true
- isCloseButtonShown = true
- onClose = { cancelTransactionSelection() }
-
- className = GameStyles.transactionsSelector
-
- BpDialogBody {
- p {
- +"You don't have enough resources to perform this move, but you can buy them from neighbours. "
- +"Please pick an option:"
- }
- if (state != null) { // should always be true when the dialog is rendered
- div {
- css {
- margin = Margin(all = Auto.auto)
- display = Display.flex
- alignItems = AlignItems.center
- }
- neighbour(neighbours.first)
- div {
- css {
- flexGrow = number(1.0)
- margin = Margin(vertical = 0.rem, horizontal = 0.5.rem)
- display = Display.flex
- flexDirection = FlexDirection.column
- alignItems = AlignItems.center
- }
- OptionsTable {
- this.state = state
- this.prepareMove = prepareMove
- }
- }
- neighbour(neighbours.second)
- }
- }
- }
- }
-}
-
-private fun ChildrenBuilder.neighbour(player: PlayerDTO) {
- div {
- css {
- width = 12.rem
-
- // center the icon
- display = Display.flex
- flexDirection = FlexDirection.column
- alignItems = AlignItems.center
- }
- PlayerInfo {
- this.player = player
- this.iconSize = 40
- this.orientation = FlexDirection.column
- this.ellipsize = false
- }
- }
-}
-
-private external interface OptionsTableProps : PropsWithChildren {
- var state: TransactionSelectorState
- var prepareMove: (PlayerMove) -> Unit
-}
-
-private val OptionsTable = FC<OptionsTableProps> { props ->
- val state = props.state
- val prepareMove = props.prepareMove
-
- var expanded by useState { false }
-
- val bestPrice = state.transactionsOptions.bestPrice
- val (cheapestOptions, otherOptions) = state.transactionsOptions.partition { it.totalPrice == bestPrice }
-
- BpHTMLTable {
- interactive = true
- tbody {
- cheapestOptions.forEach { transactions ->
- transactionsOptionRow(
- transactions = transactions,
- showBestPriceIndicator = expanded,
- onClick = { prepareMove(PlayerMove(state.moveType, state.card.name, transactions)) },
- )
- }
- if (expanded) {
- otherOptions.forEach { transactions ->
- transactionsOptionRow(
- transactions = transactions,
- showBestPriceIndicator = false,
- onClick = { prepareMove(PlayerMove(state.moveType, state.card.name, transactions)) },
- )
- }
- }
- }
- }
- if (otherOptions.isNotEmpty()) {
- val icon = if (expanded) "chevron-up" else "chevron-down"
- val text = if (expanded) "Hide expensive options" else "Show more expensive options"
- BpButton {
- this.minimal = true
- this.small = true
- this.icon = icon
- this.rightIcon = icon
- this.onClick = { expanded = !expanded }
-
- +text
- }
- }
-}
-
-private fun ChildrenBuilder.transactionsOptionRow(
- transactions: PricedResourceTransactions,
- showBestPriceIndicator: Boolean,
- onClick: () -> Unit,
-) {
- tr {
- css {
- cursor = Cursor.pointer
- alignItems = AlignItems.center
- }
- this.onClick = { onClick() }
- // there should be at most one of each
- val leftTr = transactions.firstOrNull { it.provider == Provider.LEFT_PLAYER }
- val rightTr = transactions.firstOrNull { it.provider == Provider.RIGHT_PLAYER }
- td {
- transactionCellCss()
- div {
- css { opacity = number(if (leftTr == null) 0.5 else 1.0) }
- transactionCellInnerCss()
- BpIcon {
- icon = IconNames.CARET_LEFT
- size = IconSize.LARGE
- }
- goldIndicator(leftTr?.totalPrice ?: 0, imgSize = 2.5.rem)
- }
- }
- td {
- transactionCellCss()
- if (leftTr != null) {
- resourceList(leftTr.resources)
- }
- }
- td {
- transactionCellCss()
- css { width = 1.5.rem }
- if (showBestPriceIndicator) {
- bestPriceIndicator()
- }
- }
- td {
- transactionCellCss()
- if (rightTr != null) {
- resourceList(rightTr.resources)
- }
- }
- td {
- transactionCellCss()
- div {
- css { opacity = number(if (rightTr == null) 0.5 else 1.0) }
- transactionCellInnerCss()
- goldIndicator(rightTr?.totalPrice ?: 0, imgSize = 2.5.rem)
- BpIcon {
- icon = IconNames.CARET_RIGHT
- size = IconSize.LARGE
- }
- }
- }
- }
-}
-
-private fun ChildrenBuilder.bestPriceIndicator() {
- div {
- css(GameStyles.bestPrice){}
- +"Best\nprice!"
- }
-}
-
-private fun HTMLAttributes<HTMLTableCellElement>.transactionCellCss() {
- // we need inline styles to win over BlueprintJS's styles (which are more specific than .class)
- inlineStyles {
- verticalAlign = VerticalAlign.middle
- textAlign = TextAlign.center
- }
-}
-
-private fun HTMLAttributes<HTMLDivElement>.transactionCellInnerCss() {
- css {
- display = Display.flex
- flexDirection = FlexDirection.row
- alignItems = AlignItems.center
- }
-}
-
-private fun ChildrenBuilder.resourceList(countedResources: List<CountedResource>) {
- val resources = countedResources.toRepeatedTypesList()
-
- // The biggest card is the Palace and requires 7 resources (1 of each).
- // We always have at least 1 resource on our wonder, so we'll never need to buy more than 6.
- // Therefore, 3 by row seems decent. When there are 4 items, it's visually better to have a 2x2 matrix, though.
- val rows = resources.chunked(if (resources.size == 4) 2 else 3)
-
- val imgSize = 1.5
- div {
- css {
- display = Display.flex
- flexDirection = FlexDirection.column
- alignItems = AlignItems.center
- justifyContent = JustifyContent.center
- flexGrow = number(1.0)
- // this ensures stable dimensions, no matter how many resources (up to 2x3 matrix)
- width = (imgSize * 3).rem
- height = (imgSize * 2).rem
- }
- rows.forEach { row ->
- div {
- resourceRowCss()
- row.forEach {
- resourceImage(it, size = imgSize.rem)
- }
- }
- }
- }
-}
-
-private fun HTMLAttributes<HTMLDivElement>.resourceRowCss() {
- css {
- display = Display.flex
- flexDirection = FlexDirection.row
- alignItems = AlignItems.center
- margin = Margin(vertical = 0.px, horizontal = Auto.auto)
- }
-}
-
-private fun List<CountedResource>.toRepeatedTypesList(): List<ResourceType> = flatMap { cr -> List(cr.count) { cr.type } }
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/CreateGameForm.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/CreateGameForm.kt
deleted file mode 100644
index e0f7dd21..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/CreateGameForm.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.luxons.sevenwonders.ui.components.gameBrowser
-
-import blueprintjs.core.*
-import blueprintjs.icons.*
-import emotion.react.*
-import org.luxons.sevenwonders.ui.names.*
-import org.luxons.sevenwonders.ui.redux.*
-import react.*
-import react.dom.html.ReactHTML.form
-import web.cssom.*
-
-val CreateGameForm = FC {
- var gameName by useState("")
-
- val dispatch = useSwDispatch()
- val createGame = { dispatch(RequestCreateGame(gameName)) }
-
- form {
- css {
- display = Display.flex
- flexDirection = FlexDirection.row
- }
- onSubmit = { e ->
- e.preventDefault()
- createGame()
- }
-
- BpInputGroup {
- large = true
- placeholder = "Game name"
- value = gameName
- onChange = { e ->
- val input = e.currentTarget
- gameName = input.value
- }
- rightElement = BpButton.create {
- title = "Generate random name"
- icon = IconNames.RANDOM
- minimal = true
- onClick = { gameName = randomGameName() }
- }
- }
- BpButton {
- title = "Create the game"
- intent = Intent.PRIMARY
- icon = IconNames.ARROW_RIGHT
- large = true
- onClick = { e ->
- e.preventDefault() // prevents refreshing the page when pressing Enter
- createGame()
- }
-
- css {
- marginLeft = 0.2.rem
- }
- }
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowser.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowser.kt
deleted file mode 100644
index 10fb9d81..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameBrowser.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package org.luxons.sevenwonders.ui.components.gameBrowser
-
-import blueprintjs.core.*
-import emotion.react.*
-import org.luxons.sevenwonders.ui.components.*
-import org.luxons.sevenwonders.ui.redux.*
-import org.luxons.sevenwonders.ui.utils.*
-import react.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.h1
-import react.dom.html.ReactHTML.h2
-import web.cssom.*
-
-val GameBrowser = FC {
- div {
- css(GlobalStyles.fullscreen, GlobalStyles.zeusBackground) {
- padding = Padding(all = 1.rem)
- }
- div {
- css(ClassName(Classes.DARK)) {
- margin = Margin(vertical = 0.px, horizontal = Auto.auto)
- maxWidth = GlobalStyles.preGameWidth
- }
- div {
- css {
- display = Display.flex
- justifyContent = JustifyContent.spaceBetween
- }
- h1 { +"Games" }
- CurrentPlayerInfo()
- }
-
- BpCard {
- css {
- marginBottom = 1.rem
- }
-
- h2 {
- css {
- marginTop = 0.px
- }
- +"Create a Game"
- }
- CreateGameForm()
- }
-
- BpCard {
- h2 {
- css {
- marginTop = 0.px
- }
- +"Join a Game"
- }
- GameList()
- }
- }
- }
-}
-
-val CurrentPlayerInfo = FC {
- val connectedPlayer = useSwSelector { it.connectedPlayer }
- PlayerInfo {
- player = connectedPlayer
- iconSize = 30
- showUsername = true
- orientation = FlexDirection.row
- ellipsize = false
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameList.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameList.kt
deleted file mode 100644
index 2919b065..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/GameList.kt
+++ /dev/null
@@ -1,213 +0,0 @@
-package org.luxons.sevenwonders.ui.components.gameBrowser
-
-import blueprintjs.core.*
-import blueprintjs.icons.*
-import csstype.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.api.*
-import org.luxons.sevenwonders.model.api.State
-import org.luxons.sevenwonders.ui.redux.*
-import org.luxons.sevenwonders.ui.utils.*
-import react.*
-import react.dom.html.ReactHTML.col
-import react.dom.html.ReactHTML.colgroup
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.span
-import react.dom.html.ReactHTML.tbody
-import react.dom.html.ReactHTML.td
-import react.dom.html.ReactHTML.th
-import react.dom.html.ReactHTML.thead
-import react.dom.html.ReactHTML.tr
-import web.cssom.*
-import react.State as RState
-
-external interface GameListStateProps : Props {
- var connectedPlayer: ConnectedPlayer
- var games: List<LobbyDTO>
-}
-
-external interface GameListDispatchProps : Props {
- var joinGame: (Long) -> Unit
-}
-
-external interface GameListProps : GameListStateProps, GameListDispatchProps
-
-val GameList = connectStateAndDispatch<GameListStateProps, GameListDispatchProps, GameListProps>(
- clazz = GameListPresenter::class,
- mapStateToProps = { state, _ ->
- connectedPlayer = state.connectedPlayer ?: error("there should be a connected player")
- games = state.games
- },
- mapDispatchToProps = { dispatch, _ ->
- joinGame = { gameId -> dispatch(RequestJoinGame(gameId = gameId)) }
- },
-)
-
-private class GameListPresenter(props: GameListProps) : Component<GameListProps, RState>(props) {
-
- override fun render() = Fragment.create {
- if (props.games.isEmpty()) {
- noGamesInfo()
- } else {
- gamesTable()
- }
- }
-
- private fun ChildrenBuilder.noGamesInfo() {
- BpNonIdealState {
- icon = IconNames.GEOSEARCH
- titleText = "No games to join"
-
- div {
- css(ClassName(Classes.RUNNING_TEXT)) {
- maxWidth = 35.rem
- }
- +"Nobody seems to be playing at the moment. "
- +"Don't be disappointed, you can always create your own game, and play with bots if you're alone."
- }
- }
- }
-
- private fun ChildrenBuilder.gamesTable() {
- BpHTMLTable {
- css {
- width = 100.pct
- }
-
- columnWidthsSpec()
- thead {
- gameListHeaderRow()
- }
- tbody {
- props.games.forEach {
- gameListItemRow(it)
- }
- }
- }
- }
-
- private fun ChildrenBuilder.columnWidthsSpec() {
- colgroup {
- col {
- css {
- width = 40.rem
- }
- }
- col {
- css {
- width = 5.rem
- textAlign = TextAlign.center
- }
- }
- col {
- css {
- width = 5.rem
- textAlign = TextAlign.center // use inline style on th instead to overcome blueprint style
- }
- }
- col {
- css {
- width = 3.rem
- textAlign = TextAlign.center
- }
- }
- }
- }
-
- private fun ChildrenBuilder.gameListHeaderRow() = tr {
- th {
- +"Name"
- }
- th {
- inlineStyles { gameTableHeaderCellStyle() }
- +"Status"
- }
- th {
- inlineStyles { gameTableHeaderCellStyle() }
- +"Players"
- }
- th {
- inlineStyles { gameTableHeaderCellStyle() }
- +"Join"
- }
- }
-
- private fun ChildrenBuilder.gameListItemRow(lobby: LobbyDTO) = tr {
- key = lobby.id.toString()
- // inline styles necessary to overcome BlueprintJS's verticalAlign=top
- td {
- inlineStyles { gameTableCellStyle() }
- +lobby.name
- }
- td {
- inlineStyles {
- textAlign = TextAlign.center
- gameTableCellStyle()
- }
- gameStatus(lobby.state)
- }
- td {
- inlineStyles { gameTableCellStyle() }
- playerCount(lobby.players.size)
- }
- td {
- inlineStyles { gameTableCellStyle() }
- joinButton(lobby)
- }
- }
-
- private fun PropertiesBuilder.gameTableHeaderCellStyle() {
- textAlign = TextAlign.center
- }
-
- private fun PropertiesBuilder.gameTableCellStyle() {
- verticalAlign = VerticalAlign.middle
- }
-
- private fun ChildrenBuilder.gameStatus(state: State) {
- val intent = when (state) {
- State.LOBBY -> Intent.SUCCESS
- State.PLAYING -> Intent.WARNING
- State.FINISHED -> Intent.DANGER
- }
- BpTag {
- this.minimal = true
- this.intent = intent
-
- +state.toString()
- }
- }
-
- private fun ChildrenBuilder.playerCount(nPlayers: Int) {
- div {
- css {
- display = Display.flex
- flexDirection = FlexDirection.row
- justifyContent = JustifyContent.center
- }
- title = "Number of players"
- BpIcon {
- icon = IconNames.PEOPLE
- title = null
- }
- span {
- css {
- marginLeft = 0.3.rem
- }
- +nPlayers.toString()
- }
- }
- }
-
- private fun ChildrenBuilder.joinButton(lobby: LobbyDTO) {
- val joinability = lobby.joinability(props.connectedPlayer.displayName)
- BpButton {
- minimal = true
- large = true
- title = joinability.tooltip
- icon = "arrow-right"
- disabled = !joinability.canDo
- onClick = { props.joinGame(lobby.id) }
- }
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/PlayerInfo.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/PlayerInfo.kt
deleted file mode 100644
index d7a9a80f..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/gameBrowser/PlayerInfo.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.luxons.sevenwonders.ui.components.gameBrowser
-
-import blueprintjs.core.*
-import csstype.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.api.*
-import react.*
-import react.State
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.span
-import web.cssom.*
-
-external interface PlayerInfoProps : PropsWithChildren {
- var player: BasicPlayerInfo?
- var showUsername: Boolean?
- var iconSize: Int?
- var orientation: FlexDirection?
- var ellipsize: Boolean?
-}
-
-val PlayerInfo = PlayerInfoPresenter::class.react
-
-private class PlayerInfoPresenter(props: PlayerInfoProps) : Component<PlayerInfoProps, State>(props) {
-
- override fun render() = div.create {
- val orientation = props.orientation ?: FlexDirection.row
- css {
- display = Display.flex
- alignItems = AlignItems.center
- flexDirection = orientation
- }
- props.player?.let {
- BpIcon {
- icon = it.icon?.name ?: "user"
- size = props.iconSize ?: 30
- }
- if (props.showUsername == true) {
- playerNameWithUsername(it.displayName, it.username) {
- iconSeparationMargin(orientation)
- }
- } else {
- playerName(it.displayName) {
- iconSeparationMargin(orientation)
- }
- }
- }
- }
-
- private fun ChildrenBuilder.playerName(displayName: String, style: PropertiesBuilder.() -> Unit = {}) {
- span {
- css {
- fontSize = 1.rem
- if (props.orientation == FlexDirection.column) {
- textAlign = TextAlign.center
- }
- style()
- }
- // TODO replace by BlueprintJS's Text elements (built-in ellipsize based on width)
- val maxDisplayNameLength = 15
- val ellipsize = props.ellipsize ?: true
- if (ellipsize && displayName.length > maxDisplayNameLength) {
- title = displayName
- +displayName.ellipsize(maxDisplayNameLength)
- } else {
- +displayName
- }
- }
- }
-
- private fun String.ellipsize(maxLength: Int) = take(maxLength - 1) + "…"
-
- private fun PropertiesBuilder.iconSeparationMargin(orientation: FlexDirection) {
- val margin = 0.4.rem
- when (orientation) {
- FlexDirection.row -> marginLeft = margin
- FlexDirection.column -> marginTop = margin
- FlexDirection.rowReverse -> marginRight = margin
- FlexDirection.columnReverse -> marginBottom = margin
- else -> error("Unsupported orientation '$orientation' for player info component")
- }
- }
-
- private fun ChildrenBuilder.playerNameWithUsername(
- displayName: String,
- username: String,
- style: PropertiesBuilder.() -> Unit = {}
- ) {
- div {
- css {
- display = Display.flex
- flexDirection = FlexDirection.column
- style()
- }
- playerName(displayName)
- span {
- css {
- marginTop = 0.1.rem
- color = NamedColor.lightgray
- fontSize = 0.8.rem
- }
- +"($username)"
- }
- }
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/ChooseNameForm.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/ChooseNameForm.kt
deleted file mode 100644
index ba37c09d..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/ChooseNameForm.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-package org.luxons.sevenwonders.ui.components.home
-
-import blueprintjs.core.*
-import blueprintjs.icons.*
-import emotion.react.*
-import org.luxons.sevenwonders.ui.names.*
-import org.luxons.sevenwonders.ui.redux.*
-import react.*
-import react.dom.html.ReactHTML.form
-import web.cssom.*
-
-val ChooseNameForm = FC {
- val dispatch = useSwDispatch()
- ChooseNameFormPresenter {
- chooseUsername = { name -> dispatch(RequestChooseName(name)) }
- }
-}
-
-private external interface ChooseNameFormPresenterProps : PropsWithChildren {
- var chooseUsername: (String) -> Unit
-}
-
-private val ChooseNameFormPresenter = FC<ChooseNameFormPresenterProps> { props ->
- var usernameState by useState("")
-
- form {
- css {
- display = Display.flex
- flexDirection = FlexDirection.row
- }
- onSubmit = { e ->
- e.preventDefault()
- props.chooseUsername(usernameState)
- }
- BpInputGroup {
- large = true
- placeholder = "Username"
- value = usernameState
- onChange = { e ->
- val input = e.currentTarget
- usernameState = input.value
- }
- rightElement = BpButton.create {
- title = "Generate random name"
- icon = IconNames.RANDOM
- minimal = true
- onClick = { usernameState = randomGreekName() }
- }
- }
- BpButton {
- title = "Start"
- icon = IconNames.ARROW_RIGHT
- intent = Intent.PRIMARY
- large = true
- onClick = { e ->
- e.preventDefault()
- props.chooseUsername(usernameState)
- }
-
- css {
- marginLeft = 0.2.rem
- }
- }
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/Home.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/Home.kt
deleted file mode 100644
index 81f4c736..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/Home.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.luxons.sevenwonders.ui.components.home
-
-import emotion.react.*
-import org.luxons.sevenwonders.ui.components.*
-import react.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.img
-
-private const val LOGO = "images/logo-7-wonders.png"
-
-val Home = FC("Home") {
- div {
- css(GlobalStyles.fullscreen, GlobalStyles.zeusBackground, HomeStyles.centerChildren) {}
-
- img {
- src = LOGO
- alt = "Seven Wonders"
- }
-
- ChooseNameForm()
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/HomeStyles.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/HomeStyles.kt
deleted file mode 100644
index 015e78d6..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/home/HomeStyles.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.luxons.sevenwonders.ui.components.home
-
-import csstype.*
-import emotion.css.*
-import web.cssom.*
-
-object HomeStyles {
-
- val centerChildren = ClassName {
- display = Display.flex
- flexDirection = FlexDirection.column
- alignItems = AlignItems.center
- justifyContent = JustifyContent.center
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt
deleted file mode 100644
index 0330a192..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Lobby.kt
+++ /dev/null
@@ -1,272 +0,0 @@
-package org.luxons.sevenwonders.ui.components.lobby
-
-import blueprintjs.core.*
-import blueprintjs.icons.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.api.*
-import org.luxons.sevenwonders.model.wonders.*
-import org.luxons.sevenwonders.ui.components.*
-import org.luxons.sevenwonders.ui.redux.*
-import org.luxons.sevenwonders.ui.utils.*
-import react.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.h1
-import react.dom.html.ReactHTML.h2
-import react.dom.html.ReactHTML.h3
-import react.dom.html.ReactHTML.h4
-import web.cssom.*
-import web.cssom.Position
-
-private val BOT_NAMES = listOf("Wall-E", "B-Max", "Sonny", "T-800", "HAL", "GLaDOS", "R2-D2", "Bender", "AWESOM-O")
-
-val Lobby = FC(displayName = "Lobby") {
- val lobby = useSwSelector { it.currentLobby }
- val player = useSwSelector { it.currentPlayer }
-
- val dispatch = useSwDispatch()
-
- if (lobby == null || player == null) {
- BpNonIdealState {
- icon = IconNames.ERROR
- titleText = "Error: no current game"
- }
- } else {
- LobbyPresenter {
- currentGame = lobby
- currentPlayer = player
-
- startGame = { dispatch(RequestStartGame()) }
- addBot = { name -> dispatch(RequestAddBot(name)) }
- leaveLobby = { dispatch(RequestLeaveLobby()) }
- disbandLobby = { dispatch(RequestDisbandLobby()) }
- reorderPlayers = { orderedPlayers -> dispatch(RequestReorderPlayers(orderedPlayers)) }
- reassignWonders = { wonders -> dispatch(RequestReassignWonders(wonders)) }
- }
- }
-}
-
-private external interface LobbyPresenterProps : Props {
- var currentGame: LobbyDTO
- var currentPlayer: PlayerDTO
- var startGame: () -> Unit
- var addBot: (displayName: String) -> Unit
- var leaveLobby: () -> Unit
- var disbandLobby: () -> Unit
- var reorderPlayers: (orderedPlayers: List<String>) -> Unit
- var reassignWonders: (wonders: List<AssignedWonder>) -> Unit
-}
-
-private val LobbyPresenter = FC<LobbyPresenterProps> { props ->
- div {
- css(GlobalStyles.fullscreen, GlobalStyles.zeusBackground) {
- padding = Padding(all = 1.rem)
- }
- div {
- css(ClassName(Classes.DARK), LobbyStyles.contentContainer) {
- margin = Margin(vertical = 0.rem, horizontal = Auto.auto)
- maxWidth = GlobalStyles.preGameWidth
- }
- h1 { +"${props.currentGame.name} — Lobby" }
-
- radialPlayerList(props.currentGame.players, props.currentPlayer) {
- css {
- // to make players more readable on the background
- background = "radial-gradient(closest-side, black 20%, transparent)".unsafeCast<Gradient>()
- // make it bigger so the background covers more ground
- width = 40.rem
- height = 40.rem
- }
- }
- actionButtons(props.currentPlayer, props.currentGame, props.startGame, props.leaveLobby, props.disbandLobby, props.addBot)
-
- if (props.currentPlayer.isGameOwner) {
- setupPanel(props.currentGame, props.reorderPlayers, props.reassignWonders)
- }
- }
- }
-}
-
-private fun ChildrenBuilder.actionButtons(
- currentPlayer: PlayerDTO,
- currentGame: LobbyDTO,
- startGame: () -> Unit,
- leaveLobby: () -> Unit,
- disbandLobby: () -> Unit,
- addBot: (String) -> Unit,
-) {
- div {
- css {
- position = Position.absolute
- bottom = 2.rem
- left = 50.pct
- transform = translate((-50).pct)
-
- width = 70.pct
- display = Display.flex
- justifyContent = JustifyContent.spaceAround
- }
- if (currentPlayer.isGameOwner) {
- BpButtonGroup {
- leaveButton(leaveLobby)
- disbandButton(disbandLobby)
- }
- BpButtonGroup {
- addBotButton(currentGame, addBot)
- startButton(currentGame.startability(currentPlayer.username), startGame)
- }
- } else {
- leaveButton(leaveLobby)
- }
- }
-}
-
-private fun ChildrenBuilder.startButton(startability: Actionability, startGame: () -> Unit) {
- BpButton {
- large = true
- intent = Intent.PRIMARY
- icon = IconNames.PLAY
- title = startability.tooltip
- disabled = !startability.canDo
- onClick = { startGame() }
-
- +"START"
- }
-}
-
-private fun ChildrenBuilder.setupPanel(
- currentGame: LobbyDTO,
- reorderPlayers: (usernames: List<String>) -> Unit,
- reassignWonders: (wonders: List<AssignedWonder>) -> Unit,
-) {
- div {
- className = LobbyStyles.setupPanel
-
- BpCard {
- elevation = Elevation.TWO
- className = ClassName(Classes.DARK)
-
- h2 {
- css {
- marginTop = 0.px
- }
- +"Game setup"
- }
- BpDivider()
- h3 {
- +"Players"
- }
- reorderPlayersButton(currentGame, reorderPlayers)
- h3 {
- +"Wonders"
- }
- WonderSettingsGroup {
- this.currentGame = currentGame
- this.reassignWonders = reassignWonders
- }
- }
- }
-}
-
-private fun ChildrenBuilder.addBotButton(currentGame: LobbyDTO, addBot: (String) -> Unit) {
- BpButton {
- large = true
- icon = IconNames.PLUS
- rightIcon = IconNames.DESKTOP
- intent = Intent.PRIMARY
- title = if (currentGame.maxPlayersReached) "Max players reached" else "Add a bot to this game"
- disabled = currentGame.maxPlayersReached
- onClick = { addBot(randomBotNameUnusedIn(currentGame)) }
- }
-}
-
-private fun randomBotNameUnusedIn(currentGame: LobbyDTO): String {
- val availableBotNames = BOT_NAMES.filter { name ->
- currentGame.players.none { it.displayName == name }
- }
- return availableBotNames.random()
-}
-
-private fun ChildrenBuilder.reorderPlayersButton(currentGame: LobbyDTO, reorderPlayers: (usernames: List<String>) -> Unit) {
- BpButton {
- icon = IconNames.RANDOM
- rightIcon = IconNames.PEOPLE
- title = "Re-order players randomly"
- onClick = { reorderPlayers(currentGame.players.map { it.username }.shuffled()) }
-
- +"Reorder players"
- }
-}
-
-private external interface WonderSettingsGroupProps : Props {
- var currentGame: LobbyDTO
- var reassignWonders: (List<AssignedWonder>) -> Unit
-}
-
-private val WonderSettingsGroup = FC<WonderSettingsGroupProps> { props ->
- val reassignWonders = props.reassignWonders
-
- BpButton {
- icon = IconNames.RANDOM
- title = "Re-assign wonders to players randomly"
- onClick = { reassignWonders(randomWonderAssignments(props.currentGame)) }
-
- +"Randomize wonders"
- }
- h4 {
- +"Select wonder sides:"
- }
- BpButtonGroup {
- BpButton {
- icon = IconNames.RANDOM
- title = "Re-roll wonder sides randomly"
- onClick = { reassignWonders(assignedWondersWithRandomSides(props.currentGame)) }
- }
- BpButton {
- title = "Choose side A for everyone"
- onClick = { reassignWonders(assignedWondersWithForcedSide(props.currentGame, WonderSide.A)) }
-
- +"A"
- }
- BpButton {
- title = "Choose side B for everyone"
- onClick = { reassignWonders(assignedWondersWithForcedSide(props.currentGame, WonderSide.B)) }
-
- +"B"
- }
- }
-}
-
-private fun randomWonderAssignments(currentGame: LobbyDTO): List<AssignedWonder> =
- currentGame.allWonders.deal(currentGame.players.size)
-
-private fun assignedWondersWithForcedSide(
- currentGame: LobbyDTO,
- side: WonderSide
-) = currentGame.players.map { currentGame.findWonder(it.wonder.name).withSide(side) }
-
-private fun assignedWondersWithRandomSides(currentGame: LobbyDTO) =
- currentGame.players.map { currentGame.findWonder(it.wonder.name) }.map { it.withRandomSide() }
-
-private fun ChildrenBuilder.leaveButton(leaveLobby: () -> Unit) {
- BpButton {
- large = true
- intent = Intent.WARNING
- icon = "arrow-left"
- title = "Leave the lobby and go back to the game browser"
- onClick = { leaveLobby() }
-
- +"LEAVE"
- }
-}
-
-private fun ChildrenBuilder.disbandButton(disbandLobby: () -> Unit) {
- BpButton {
- large = true
- intent = Intent.DANGER
- icon = IconNames.DELETE
- title = "Disband the group and go back to the game browser"
- onClick = { disbandLobby() }
-
- +"DISBAND"
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/LobbyStyles.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/LobbyStyles.kt
deleted file mode 100644
index 6b5dbe48..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/LobbyStyles.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.luxons.sevenwonders.ui.components.lobby
-
-import emotion.css.*
-import org.luxons.sevenwonders.ui.components.*
-import web.cssom.*
-
-object LobbyStyles {
-
- val contentContainer = ClassName {
- margin = Margin(vertical = 0.px, horizontal = Auto.auto)
- maxWidth = GlobalStyles.preGameWidth
- }
-
- val setupPanel = ClassName {
- position = Position.fixed
- top = 2.rem
- right = 1.rem
- width = 20.rem
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt
deleted file mode 100644
index 1f88bebe..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialList.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-package org.luxons.sevenwonders.ui.components.lobby
-
-import csstype.*
-import emotion.react.*
-import org.luxons.sevenwonders.ui.components.*
-import react.*
-import react.dom.html.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.li
-import react.dom.html.ReactHTML.ul
-import web.cssom.*
-import web.html.*
-
-fun <T> ChildrenBuilder.radialList(
- items: List<T>,
- centerElement: ReactElement<*>,
- renderItem: (T) -> ReactElement<*>,
- getKey: (T) -> String,
- itemWidth: Int,
- itemHeight: Int,
- options: RadialConfig = RadialConfig(),
- block: HTMLAttributes<HTMLDivElement>.() -> Unit = {},
-) {
- val containerWidth = options.diameter + itemWidth
- val containerHeight = options.diameter + itemHeight
-
- div {
- css(GlobalStyles.fixedCenter) {
- zeroMargins()
- width = containerWidth.px
- height = containerHeight.px
- }
- block()
- radialListItems(items, renderItem, getKey, options)
- radialListCenter(centerElement)
- }
-}
-
-private fun <T> ChildrenBuilder.radialListItems(
- items: List<T>,
- renderItem: (T) -> ReactElement<*>,
- getKey: (T) -> String,
- radialConfig: RadialConfig,
-) {
- val offsets = offsetsFromCenter(items.size, radialConfig)
- ul {
- css {
- zeroMargins()
- transition = Transition(
- property = TransitionProperty.all,
- duration = 500.ms,
- timingFunction = TransitionTimingFunction.easeInOut,
- )
- zIndex = integer(1)
- width = radialConfig.diameter.px
- height = radialConfig.diameter.px
- absoluteCenter()
- }
- // We ensure a stable order of the DOM elements so that position animations look nice.
- // We still respect the order of the items in the list when placing them along the circle.
- val indexByKey = buildMap {
- items.forEachIndexed { index, item -> put(getKey(item), index) }
- }
- items.sortedBy { getKey(it) }.forEach { item ->
- val key = getKey(item)
- radialListItem(renderItem(item), key, offsets[indexByKey.getValue(key)])
- }
- }
-}
-
-private fun ChildrenBuilder.radialListItem(item: ReactElement<*>, key: String, offset: CartesianCoords) {
- li {
- css {
- display = Display.block
- position = Position.absolute
- top = 50.pct
- left = 50.pct
- zeroMargins()
- listStyleType = Globals.unset
- transition = Transition(
- property = TransitionProperty.all,
- duration = 500.ms,
- timingFunction = TransitionTimingFunction.easeInOut,
- )
- zIndex = integer(1)
- transform = translate(offset.x.px - 50.pct, offset.y.px - 50.pct)
- }
- this.key = key
-
- child(item)
- }
-}
-
-private fun ChildrenBuilder.radialListCenter(centerElement: ReactElement<*>?) {
- if (centerElement == null) {
- return
- }
- div {
- css {
- zIndex = integer(0)
- absoluteCenter()
- }
- child(centerElement)
- }
-}
-
-private fun PropertiesBuilder.absoluteCenter() {
- position = Position.absolute
- left = 50.pct
- top = 50.pct
- transform = translate((-50).pct, (-50).pct)
-}
-
-private fun PropertiesBuilder.zeroMargins() {
- margin = Margin(vertical = 0.px, horizontal = 0.px)
- padding = Padding(vertical = 0.px, horizontal = 0.px)
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialMath.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialMath.kt
deleted file mode 100644
index 4b5eb509..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialMath.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-package org.luxons.sevenwonders.ui.components.lobby
-
-import kotlin.math.PI
-import kotlin.math.cos
-import kotlin.math.roundToInt
-import kotlin.math.sin
-
-data class CartesianCoords(
- val x: Int,
- val y: Int,
-)
-
-data class PolarCoords(
- val radius: Int,
- val angleDeg: Int,
-)
-
-private fun Int.toRadians() = (this * PI / 180.0)
-private fun Double.project(angleRad: Double, trigFn: (Double) -> Double) = (this * trigFn(angleRad)).roundToInt()
-private fun Double.xProjection(angleRad: Double) = project(angleRad, ::cos)
-private fun Double.yProjection(angleRad: Double) = project(angleRad, ::sin)
-
-private fun PolarCoords.toCartesian() = CartesianCoords(
- x = radius.toDouble().xProjection(angleDeg.toRadians()),
- y = radius.toDouble().yProjection(angleDeg.toRadians()),
-)
-
-// Y-axis is pointing down in the browser, so the directions need to be reversed
-// (positive angles are now clockwise)
-enum class Direction(private val value: Int) {
- CLOCKWISE(1),
- COUNTERCLOCKWISE(-1);
-
- fun toOrientedDegrees(deg: Int) = value * deg
-}
-
-data class RadialConfig(
- val radius: Int = 120,
- val spreadArcDegrees: Int = 360, // full circle
- val firstItemAngleDegrees: Int = 0, // 12 o'clock
- val direction: Direction = Direction.CLOCKWISE,
-) {
- val diameter: Int = radius * 2
-}
-
-private const val DEFAULT_START = -90 // Up, because Y-axis is reversed
-
-fun offsetsFromCenter(nbItems: Int, radialConfig: RadialConfig = RadialConfig()): List<CartesianCoords> {
- val startAngle = DEFAULT_START + radialConfig.direction.toOrientedDegrees(radialConfig.firstItemAngleDegrees)
- val angleStep = radialConfig.spreadArcDegrees / nbItems
- return List(nbItems) { itemCartesianOffsets(startAngle, angleStep, it, radialConfig) }
-}
-
-private fun itemCartesianOffsets(startAngle: Int, angleStep: Int, index: Int, config: RadialConfig): CartesianCoords {
- val itemAngle = startAngle + config.direction.toOrientedDegrees(angleStep) * index
- return PolarCoords(config.radius, itemAngle).toCartesian()
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt
deleted file mode 100644
index 645cf5f3..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/RadialPlayerList.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-package org.luxons.sevenwonders.ui.components.lobby
-
-import blueprintjs.core.*
-import blueprintjs.icons.*
-import csstype.*
-import emotion.react.*
-import org.luxons.sevenwonders.model.api.*
-import org.luxons.sevenwonders.model.api.actions.Icon
-import org.luxons.sevenwonders.model.wonders.*
-import org.luxons.sevenwonders.ui.utils.*
-import react.*
-import react.dom.html.*
-import react.dom.html.ReactHTML.div
-import react.dom.html.ReactHTML.span
-import web.cssom.*
-import web.html.*
-
-fun ChildrenBuilder.radialPlayerList(
- players: List<PlayerDTO>,
- currentPlayer: PlayerDTO,
- block: HTMLAttributes<HTMLDivElement>.() -> Unit = {},
-) {
- val playerItems = players //
- .map { PlayerItem.Player(it) }
- .growWithPlaceholders(targetSize = 3)
- .withUserFirst(currentPlayer)
-
- radialList(
- items = playerItems,
- centerElement = LobbyWoodenTable.create {
- diameter = 200.px
- borderSize = 15.px
- },
- renderItem = { PlayerElement.create { playerItem = it } },
- getKey = { it.key },
- itemWidth = 120,
- itemHeight = 100,
- options = RadialConfig(
- radius = 175,
- firstItemAngleDegrees = 180, // self at the bottom
- direction = Direction.COUNTERCLOCKWISE, // new players sit to the right of last player
- ),
- block = block,
- )
-}
-
-private fun List<PlayerItem>.growWithPlaceholders(targetSize: Int): List<PlayerItem> = when {
- size < targetSize -> this + List(targetSize - size) { PlayerItem.Placeholder(size + it) }
- else -> this
-}
-
-private fun List<PlayerItem>.withUserFirst(me: PlayerDTO): List<PlayerItem> {
- val nonUsersBeginning = takeWhile { (it as? PlayerItem.Player)?.player?.username != me.username }
- val userToEnd = subList(nonUsersBeginning.size, size)
- return userToEnd + nonUsersBeginning
-}
-
-private sealed class PlayerItem {
- abstract val key: String
- abstract val playerText: String
- abstract val opacity: Opacity
- abstract val icon: ReactElement<*>
-
- data class Player(val player: PlayerDTO) : PlayerItem() {
- override val key = player.username
- override val playerText = player.displayName
- override val opacity = number(1.0)
- override val icon = createUserIcon(
- icon = player.icon ?: when {
- player.isGameOwner -> Icon(IconNames.BADGE)
- else -> Icon(IconNames.USER)
- },
- title = if (player.isGameOwner) "Game owner" else null,
- )
- }
-
- data class Placeholder(val index: Int) : PlayerItem() {
- override val key = "player-placeholder-$index"
- override val playerText = "?"
- override val opacity = number(0.4)
- override val icon = createUserIcon(
- icon = Icon(IconNames.USER),
- title = "Waiting for player...",
- )
- }
-}
-
-private fun createUserIcon(icon: Icon, title: String?) = BpIcon.create {
- this.icon = icon.name
- this.size = 50
- this.title = title
-}
-
-private external interface PlayerElementProps : Props {
- var playerItem: PlayerItem
-}
-
-private val PlayerElement = FC<PlayerElementProps>(displayName = "PlayerElement") { props ->
- val playerItem = props.playerItem
- div {
- css {
- display = Display.flex
- flexDirection = FlexDirection.column
- alignItems = AlignItems.center
- opacity = playerItem.opacity
- }
- child(playerItem.icon)
- span {
- css {
- fontSize = if (playerItem is PlayerItem.Placeholder) 1.5.rem else 0.9.rem
- }
- +playerItem.playerText
- }
- if (playerItem is PlayerItem.Player) {
- div {
- val wonder = playerItem.player.wonder
-
- css {
- marginTop = 0.3.rem
-
- children(".wonder-tag") {
- color = Color("#f5f8fa") // blueprintjs dark theme color (removed by .bp4-tag)
- backgroundColor = when (wonder.side) {
- WonderSide.A -> NamedColor.seagreen
- WonderSide.B -> NamedColor.darkred
- }
- }
- }
-
- BpTag {
- round = true
- className = ClassName("wonder-tag")
-
- +"${wonder.name} ${wonder.side}"
- }
- }
- }
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Table.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Table.kt
deleted file mode 100644
index bfa43aa4..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/components/lobby/Table.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-package org.luxons.sevenwonders.ui.components.lobby
-
-import csstype.*
-import emotion.css.*
-import emotion.react.*
-import emotion.styled.*
-import org.luxons.sevenwonders.ui.utils.*
-import react.*
-import react.dom.html.ReactHTML.div
-import web.cssom.*
-
-private val FIRE_REFLECTION_COLOR = Color("#b85e00")
-
-private external interface CircleProps : PropsWithChildren, PropsWithClassName {
- var diameter: Length
-}
-
-private val Circle = FC<CircleProps>("Circle") { props ->
- div {
- css(props.className) {
- width = props.diameter
- height = props.diameter
- borderRadius = 50.pct
- }
- child(props.children)
- }
-}
-
-private val OverlayCircle = Circle.styled {
- position = Position.absolute
- top = 0.px
- left = 0.px
-}
-
-external interface LobbyWoodenTableProps : Props {
- var diameter: Length
- var borderSize: Length
-}
-
-val LobbyWoodenTable = FC<LobbyWoodenTableProps>("LobbyWoodenTable") { props ->
- Circle {
- diameter = props.diameter
-
- css {
- backgroundColor = Color("#3d1e0e")
- }
-
- Circle {
- diameter = props.diameter - props.borderSize
- css {
- position = Position.absolute
- top = props.borderSize / 2
- left = props.borderSize / 2
- background = linearGradient(45.deg, Color("#88541e"), Color("#995645"), Color("#52251a"))
- }
- }
-
- // flame reflection coming from bottom-right
- OverlayCircle {
- diameter = props.diameter
-
- css {
- background =
- linearGradient((-45).deg, stop(FIRE_REFLECTION_COLOR, 10.pct), stop(NamedColor.transparent, 50.pct))
- opacityAnimation(duration = 1.3.s)
- }
- }
- // flame reflection coming from bottom-left
- OverlayCircle {
- diameter = props.diameter
-
- css {
- background =
- linearGradient(45.deg, stop(FIRE_REFLECTION_COLOR, 20.pct), stop(NamedColor.transparent, 40.pct))
- opacityAnimation(duration = 0.8.s)
- }
- }
- }
-}
-
-private fun PropertiesBuilder.opacityAnimation(duration: Time) {
- val keyframes = keyframes {
- from {
- opacity = number(0.0)
- }
- to {
- opacity = number(0.35)
- }
- }
- animation = Animation(
- name = keyframes,
- duration = duration,
- timingFunction = cubicBezier(0.4, 0.4, 0.4, 2.0),
- )
- animationDirection = AnimationDirection.alternate
- animationIterationCount = AnimationIterationCount.infinite
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/names/RandomNameGenerator.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/names/RandomNameGenerator.kt
deleted file mode 100644
index 393df78d..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/names/RandomNameGenerator.kt
+++ /dev/null
@@ -1,546 +0,0 @@
-package org.luxons.sevenwonders.ui.names
-
-import kotlin.random.Random
-
-internal fun randomGameName(): String = gameNames.random()
-
-internal fun randomGreekName(): String {
- val randName = prefixes.random() + suffixes.random()
- return if (Random.nextBoolean()) randName else "$randName of ${cities.random()}"
-}
-
-private val gameNames = listOf(
- "Age of Antiquity",
- "Age of Civilization",
- "Age of Discovery",
- "Age of Empires",
- "Age of Wonders",
- "Ancient Capitals",
- "Ancient Kingdoms",
- "Ancient Wonders",
- "Cities of Antiquity",
- "City of Wonders",
- "Empire Builders",
- "Empires of the Past",
- "Great Monuments",
- "Legendary Cities",
- "Legends of the Past",
- "Lost Empires",
- "Magnificent Monuments",
- "Magnificent Seven",
- "Monuments of the Past",
- "Monuments of the World",
- "Mythical Kingdoms",
- "Secrets of the Past",
- "Seven Ancient Wonders",
- "Seven Colossi",
- "Seven Kingdoms",
- "Seven Marvels",
- "Seven Wonders Adventures",
- "Seven Wonders Chronicles",
- "Seven Wonders Enigma",
- "Seven Wonders Expedition",
- "Seven Wonders Frontier",
- "Seven Wonders Legacy",
- "Seven Wonders Odyssey",
- "Seven Wonders Quest",
- "Seven Wonders Saga",
- "Seven Wonders Treasures",
- "Seven Wonders Voyage",
- "Seven Wonders and Beyond",
- "The Great Discoveries",
- "The Legacy of Wonders",
- "The Magic of Seven",
- "The Marvelous Seven",
- "The Mysteries of Antiquity",
- "The Seven Continents",
- "The Seven Kingdoms",
- "The Seven Legends",
- "The Seven Secrets",
- "The Seven Treasures",
- "Wonders of Nature",
- "Wonders of the Ages",
- "Wonders of the World",
- "Wonders of Time",
- "World Treasures",
-)
-
-private val prefixes =
- listOf(
- "Aba",
- "Abde",
- "Abre",
- "Aby",
- "Aca",
- "Acle",
- "Acri",
- "Acro",
- "Adme",
- "Adra",
- "Aea",
- "Aegi",
- "Aei",
- "Aeo",
- "Aese",
- "Aeto",
- "Aga",
- "Age",
- "Agi",
- "Agri",
- "Aia",
- "Aka",
- "Akti",
- "Ala",
- "Alco",
- "Ale",
- "Alka",
- "Alki",
- "Alo",
- "Alphi",
- "Ama",
- "Ame",
- "Ami",
- "Amphi",
- "Ana",
- "Anchi",
- "Andro",
- "Ane",
- "Anta",
- "Anthe",
- "Anti",
- "Ape",
- "Aphi",
- "Apo",
- "Arca",
- "Arche",
- "Arci",
- "Arga",
- "Ari",
- "Arra",
- "Arte",
- "Asca",
- "Asta",
- "Asty",
- "Atro",
- "Atta",
- "Aute",
- "Bace",
- "Bae",
- "Bali",
- "Bio",
- "Boe",
- "Bria",
- "Care",
- "Carpo",
- "Casto",
- "Cea",
- "Cebri",
- "Cele",
- "Cephi",
- "Chae",
- "Chare",
- "Chari",
- "Choe",
- "Chromi",
- "Chryso",
- "Cine",
- "Cisse",
- "Clea",
- "Cleo",
- "Clyto",
- "Cnoe",
- "Coe",
- "Cordy",
- "Cory",
- "Crati",
- "Creti",
- "Croe",
- "Ctea",
- "Cyre",
- "Dae",
- "Dami",
- "Damo",
- "Dana",
- "Daphi",
- "Davo",
- "Dei",
- "Dema",
- "Demo",
- "Deo",
- "Derky",
- "Dexi",
- "Dia",
- "Dio",
- "Dithy",
- "Dore",
- "Dori",
- "Doro",
- "Drya",
- "Dymno",
- "Eche",
- "Eio",
- "Ela",
- "Elpe",
- "Empe",
- "Endy",
- "Enge",
- "Epa",
- "Epe",
- "Ephi",
- "Era",
- "Ere",
- "Ergi",
- "Erxa",
- "Euca",
- "Euche",
- "Eudo",
- "Eue",
- "Euge",
- "Euma",
- "Eune",
- "Eury",
- "Euthy",
- "Eva",
- "Eve",
- "Fae",
- "Gale",
- "Gany",
- "Gaua",
- "Genna",
- "Gera",
- "Glau",
- "Gorgo",
- "Gyra",
- "Hae",
- "Hagi",
- "Hali",
- "Harma",
- "Harmo",
- "Harpa",
- "Hege",
- "Heira",
- "Heiro",
- "Helge",
- "Heli",
- "Hera",
- "Hermo",
- "Hiero",
- "Hippo",
- "Hya",
- "Hype",
- "Hyrca",
- "Iatro",
- "Iby",
- "Ica",
- "Ido",
- "Illy",
- "Ina",
- "Iphi",
- "Iro",
- "Isa",
- "Isma",
- "Iso",
- "Ithe",
- "Kae",
- "Kale",
- "Kalli",
- "Kame",
- "Kapa",
- "Kari",
- "Karo",
- "Kau",
- "Keo",
- "Kera",
- "Kleo",
- "Krini",
- "Krito",
- "Labo",
- "Lae",
- "Lama",
- "Lamu",
- "Lao",
- "Laso",
- "Lea",
- "Lei",
- "Leo",
- "Linu",
- "Luko",
- "Lyca",
- "Lyco",
- "Lysa",
- "Lysi",
- "Maca",
- "Macha",
- "Mae",
- "Maia",
- "Maka",
- "Male",
- "Mante",
- "Marci",
- "Marsy",
- "Mega",
- "Megi",
- "Mela",
- "Mele",
- "Metho",
- "Midy",
- "Mise",
- "Mono",
- "Morsi",
- "Myrsi",
- "Naste",
- "Nausi",
- "Nea",
- "Nele",
- "Neri",
- "Nica",
- "Nico",
- "Nire",
- "Nomi",
- "Nycti",
- "Oche",
- "Ocho",
- "Oea",
- "Oene",
- "Oeno",
- "Oile",
- "Ona",
- "One",
- "Ophe",
- "Ori",
- "Orsi",
- "Ory",
- "Pae",
- "Pala",
- "Pana",
- "Pandi",
- "Pani",
- "Panta",
- "Para",
- "Pata",
- "Peiri",
- "Pele",
- "Peli",
- "Peri",
- "Phae",
- "Phala",
- "Philo",
- "Phyla",
- "Poe",
- "Poly",
- "Praxi",
- "Prota",
- "Pryta",
- "Saby",
- "Saty",
- "Scama",
- "Scytha",
- "Sele",
- "Sila",
- "Simo",
- "Sisy",
- "Sopho",
- "Stesa",
- "Sya",
- "Sylo",
- "Syne",
- "Tala",
- "Teba",
- "Tele",
- "Tene",
- "Theo",
- "Therse",
- "Thrasy",
- "Tima",
- "Tiry",
- "Trio",
- "Xanthi",
- "Xena",
- "Xeno",
- )
-
-private val suffixesMale =
- listOf(
- "ndros",
- "bios",
- "bulos",
- "chus",
- "cles",
- "cydes",
- "damos",
- "dides",
- "don",
- "doros",
- "dotus",
- "gnis",
- "goras",
- "kles",
- "kos",
- "krates",
- "laktos",
- "laus",
- "leon",
- "llias",
- "llos",
- "llus",
- "machos",
- "machus",
- "menes",
- "menos",
- "mos",
- "ndius",
- "nes",
- "neus",
- "nidas",
- "nides",
- "nos",
- "nthius",
- "patros",
- "phanes",
- "phantes",
- "phimus",
- "phnus",
- "phon",
- "phoros",
- "phorus",
- "phus",
- "pides",
- "pompos",
- "pompus",
- "pon",
- "ppos",
- "rax",
- "reas",
- "rides",
- "ros",
- "sias",
- "sides",
- "sius",
- "stius",
- "stor",
- "stos",
- "stus",
- "talos",
- "thenes",
- "theus",
- "tios",
- )
-
-private val suffixesFemale =
- listOf(
- "ndria",
- "boea",
- "casta",
- "caste",
- "cheia",
- "chis",
- "cleia",
- "dee",
- "deia",
- "dike",
- "dina",
- "doce",
- "dora",
- "dusa",
- "gaea",
- "kia",
- "laia",
- "lea",
- "line",
- "llis",
- "lope",
- "mache",
- "mathe",
- "meda",
- "mede",
- "meia",
- "mela",
- "mene",
- "mere",
- "mia",
- "mina",
- "mpias",
- "ndra",
- "ne",
- "neira",
- "nessa",
- "nia",
- "nice",
- "niera",
- "nike",
- "nippe",
- "nna",
- "nome",
- "nope",
- "nta",
- "nthia",
- "pe",
- "phae",
- "phana",
- "phane",
- "phile",
- "phobe",
- "phone",
- "pia",
- "polis",
- "pris",
- "pyle",
- "reia",
- "rine",
- "ris",
- "rista",
- "rpia",
- "sia",
- "ssa",
- "steia",
- "stis",
- "syne",
- "ta",
- "tea",
- "thea",
- "theia",
- "thia",
- "thippe",
- "thra",
- "thusa",
- "thyia",
- "tis",
- "trite",
- )
-
-private val suffixes = suffixesMale + suffixesFemale
-
-private val cities =
- listOf(
- "Argos",
- "Assos",
- "Astypalaia",
- "Carystus",
- "Chalcis",
- "Chios",
- "Corfu",
- "Corinth",
- "Eretria",
- "Erythrae",
- "Karpathos",
- "Kasos",
- "Kos",
- "Leros",
- "Lindos",
- "Marathon",
- "Megara",
- "Miletus",
- "Mytilene",
- "Naxos",
- "Oenoe",
- "Paros",
- "Patmos",
- "Patras",
- "Phocis",
- "Rhodes",
- "Salamis",
- "Skiathos",
- "Sparta",
- "Thasos",
- "Thebes",
- )
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Actions.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Actions.kt
deleted file mode 100644
index b0c56a79..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Actions.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.luxons.sevenwonders.ui.redux
-
-import org.luxons.sevenwonders.model.PlayerMove
-import org.luxons.sevenwonders.model.PlayerTurnInfo
-import org.luxons.sevenwonders.model.TurnAction
-import org.luxons.sevenwonders.model.api.ConnectedPlayer
-import org.luxons.sevenwonders.model.api.LobbyDTO
-import org.luxons.sevenwonders.model.api.events.GameListEvent
-import org.luxons.sevenwonders.model.cards.PreparedCard
-import redux.RAction
-
-data class FatalError(val message: String) : RAction
-
-data class SetCurrentPlayerAction(val player: ConnectedPlayer) : RAction
-
-data class UpdateGameListAction(val event: GameListEvent) : RAction
-
-data class UpdateLobbyAction(val lobby: LobbyDTO) : RAction
-
-data class EnterLobbyAction(val lobby: LobbyDTO) : RAction
-
-object LeaveLobbyAction : RAction
-
-data class EnterGameAction(val lobby: LobbyDTO, val turnInfo: PlayerTurnInfo<TurnAction.SayReady>) : RAction
-
-data class TurnInfoEvent(val turnInfo: PlayerTurnInfo<*>) : RAction
-
-data class PreparedMoveEvent(val move: PlayerMove) : RAction
-
-data class PreparedCardEvent(val card: PreparedCard) : RAction
-
-data class PlayerReadyEvent(val username: String) : RAction
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/ApiActions.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/ApiActions.kt
deleted file mode 100644
index 87bacf62..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/ApiActions.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.luxons.sevenwonders.ui.redux
-
-import org.luxons.sevenwonders.model.PlayerMove
-import org.luxons.sevenwonders.model.Settings
-import org.luxons.sevenwonders.model.wonders.AssignedWonder
-import redux.RAction
-
-data class RequestChooseName(val playerName: String) : RAction
-
-data class RequestCreateGame(val gameName: String) : RAction
-
-data class RequestJoinGame(val gameId: Long) : RAction
-
-data class RequestAddBot(val botDisplayName: String) : RAction
-
-data class RequestReorderPlayers(val orderedPlayers: List<String>) : RAction
-
-data class RequestReassignWonders(val wonders: List<AssignedWonder>) : RAction
-
-data class RequestUpdateSettings(val settings: Settings) : RAction
-
-class RequestStartGame : RAction
-
-class RequestLeaveLobby : RAction
-
-class RequestDisbandLobby : RAction
-
-class RequestLeaveGame : RAction
-
-class RequestSayReady : RAction
-
-data class RequestPrepareMove(val move: PlayerMove) : RAction
-
-class RequestUnprepareMove : RAction
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Reducers.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Reducers.kt
deleted file mode 100644
index e79b063e..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Reducers.kt
+++ /dev/null
@@ -1,95 +0,0 @@
-package org.luxons.sevenwonders.ui.redux
-
-import org.luxons.sevenwonders.client.GameState
-import org.luxons.sevenwonders.model.api.ConnectedPlayer
-import org.luxons.sevenwonders.model.api.LobbyDTO
-import org.luxons.sevenwonders.model.api.PlayerDTO
-import org.luxons.sevenwonders.model.api.events.GameListEvent
-import redux.RAction
-
-data class SwState(
- val connectedPlayer: ConnectedPlayer? = null,
- // they must be by ID to support updates to a sublist
- val gamesById: Map<Long, LobbyDTO> = emptyMap(),
- val currentLobby: LobbyDTO? = null,
- val gameState: GameState? = null,
- val fatalError: String? = null,
-) {
- val currentPlayer: PlayerDTO? = (gameState?.players ?: currentLobby?.players)?.first {
- it.username == connectedPlayer?.username
- }
- val games: List<LobbyDTO> = gamesById.values.toList()
-}
-
-fun rootReducer(state: SwState, action: RAction): SwState = state.copy(
- gamesById = gamesReducer(state.gamesById, action),
- connectedPlayer = currentPlayerReducer(state.connectedPlayer, action),
- currentLobby = currentLobbyReducer(state.currentLobby, action),
- gameState = gameStateReducer(state.gameState, action),
- fatalError = connectionLostReducer(action),
-)
-
-private fun gamesReducer(games: Map<Long, LobbyDTO>, action: RAction): Map<Long, LobbyDTO> = when (action) {
- is UpdateGameListAction -> when (action.event) {
- is GameListEvent.ReplaceList -> action.event.lobbies.associateBy { it.id }
- is GameListEvent.CreateOrUpdate -> games + (action.event.lobby.id to action.event.lobby)
- is GameListEvent.Delete -> games - action.event.lobbyId
- }
- else -> games
-}
-
-private fun currentPlayerReducer(currentPlayer: ConnectedPlayer?, action: RAction): ConnectedPlayer? = when (action) {
- is SetCurrentPlayerAction -> action.player
- else -> currentPlayer
-}
-
-private fun currentLobbyReducer(currentLobby: LobbyDTO?, action: RAction): LobbyDTO? = when (action) {
- is EnterLobbyAction -> action.lobby
- is LeaveLobbyAction -> null
- is UpdateLobbyAction -> action.lobby
- is PlayerReadyEvent -> currentLobby?.let { l ->
- l.copy(players = l.players.map { p -> if (p.username == action.username) p.copy(isReady = true) else p })
- }
- else -> currentLobby
-}
-
-private fun gameStateReducer(gameState: GameState?, action: RAction): GameState? = when (action) {
- is EnterGameAction -> GameState(
- gameId = action.lobby.id,
- players = action.lobby.players,
- playerIndex = action.turnInfo.playerIndex,
- currentAge = action.turnInfo.table.currentAge,
- boards = action.turnInfo.table.boards,
- handRotationDirection = action.turnInfo.table.handRotationDirection,
- action = action.turnInfo.action,
- preparedCardsByUsername = emptyMap(),
- currentPreparedMove = null,
- )
- is PreparedMoveEvent -> gameState?.copy(currentPreparedMove = action.move)
- is RequestUnprepareMove -> gameState?.copy(currentPreparedMove = null)
- is PreparedCardEvent -> gameState?.copy(
- preparedCardsByUsername = gameState.preparedCardsByUsername + (action.card.username to action.card.cardBack),
- )
- is PlayerReadyEvent -> gameState?.copy(
- players = gameState.players.map { p ->
- if (p.username == action.username) p.copy(isReady = true) else p
- },
- )
- is TurnInfoEvent -> gameState?.copy(
- players = gameState.players.map { p -> p.copy(isReady = false) },
- playerIndex = action.turnInfo.playerIndex,
- currentAge = action.turnInfo.table.currentAge,
- boards = action.turnInfo.table.boards,
- handRotationDirection = action.turnInfo.table.handRotationDirection,
- action = action.turnInfo.action,
- preparedCardsByUsername = emptyMap(),
- currentPreparedMove = null,
- )
- is LeaveLobbyAction -> null
- else -> gameState
-}
-
-private fun connectionLostReducer(action: RAction): String? = when (action) {
- is FatalError -> action.message
- else -> null
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Store.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Store.kt
deleted file mode 100644
index 71c5eec0..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Store.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.luxons.sevenwonders.ui.redux
-
-import kotlinx.browser.window
-import org.luxons.sevenwonders.ui.redux.sagas.SagaManager
-import redux.RAction
-import redux.Store
-import redux.WrapperAction
-import redux.applyMiddleware
-import redux.compose
-import redux.createStore
-import redux.rEnhancer
-
-val INITIAL_STATE = SwState()
-
-private fun <A, T1, R> composeWithDevTools(function1: (T1) -> R, function2: (A) -> T1): (A) -> R {
- val reduxDevtoolsExtensionCompose = window.asDynamic().__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
- if (reduxDevtoolsExtensionCompose == undefined) {
- return compose(function1, function2)
- }
- return reduxDevtoolsExtensionCompose(function1, function2) as Function1<A, R>
-}
-
-fun configureStore(
- sagaManager: SagaManager<SwState, RAction, WrapperAction>,
- initialState: SwState = INITIAL_STATE,
-): Store<SwState, RAction, WrapperAction> {
- val sagaEnhancer = applyMiddleware(sagaManager.createMiddleware())
- return createStore(::rootReducer, initialState, composeWithDevTools(sagaEnhancer, rEnhancer()))
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Utils.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Utils.kt
deleted file mode 100644
index eb182dc7..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/Utils.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.luxons.sevenwonders.ui.redux
-
-import react.*
-import react.redux.*
-import redux.*
-import kotlin.reflect.*
-
-fun <R> useSwSelector(selector: (SwState) -> R) = useSelector(selector)
-fun useSwDispatch() = useDispatch<RAction, WrapperAction>()
-
-fun <SP : Props, DP : Props, P : Props> connectStateAndDispatch(
- clazz: KClass<out Component<P, out State>>,
- mapStateToProps: SP.(SwState, Props) -> Unit,
- mapDispatchToProps: DP.((RAction) -> WrapperAction, Props) -> Unit,
-): ComponentClass<Props> = connectStateAndDispatch(
- component = clazz.react,
- mapStateToProps = mapStateToProps,
- mapDispatchToProps = mapDispatchToProps,
-)
-
-fun <SP : Props, DP : Props, P : Props> connectStateAndDispatch(
- component: ComponentClass<P>,
- mapStateToProps: SP.(SwState, Props) -> Unit,
- mapDispatchToProps: DP.((RAction) -> WrapperAction, Props) -> Unit,
-): ComponentClass<Props> {
- val connect = rConnect<SwState, RAction, WrapperAction, Props, SP, DP, P>(
- mapStateToProps = mapStateToProps,
- mapDispatchToProps = mapDispatchToProps,
- )
- return connect.invoke(component)
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/RouteBasedSagas.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/RouteBasedSagas.kt
deleted file mode 100644
index 3343e62e..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/RouteBasedSagas.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.luxons.sevenwonders.ui.redux.sagas
-
-import kotlinx.coroutines.flow.map
-import org.luxons.sevenwonders.client.SevenWondersSession
-import org.luxons.sevenwonders.ui.redux.*
-import org.luxons.sevenwonders.ui.router.Navigate
-import org.luxons.sevenwonders.ui.router.SwRoute
-
-suspend fun SwSagaContext.gameBrowserSaga(session: SevenWondersSession) {
- // browser navigation could have brought us here: we should leave the game/lobby
- ensureNoCurrentGameNorLobby(session)
- session.watchGames()
- .map { UpdateGameListAction(it) }
- .collect { dispatch(it) }
-}
-
-private suspend fun SwSagaContext.ensureNoCurrentGameNorLobby(session: SevenWondersSession) {
- if (reduxState.gameState != null) {
- console.warn("User left a game via browser navigation, telling the server...")
- session.leaveGame()
- } else if (reduxState.currentLobby != null) {
- console.warn("User left the lobby via browser navigation, telling the server...")
- session.leaveLobby()
- }
-}
-
-suspend fun SwSagaContext.lobbySaga(session: SevenWondersSession) {
- if (reduxState.gameState != null) {
- console.warn("User left a game via browser navigation, telling the server...")
- session.leaveGame()
- } else if (reduxState.currentLobby == null) {
- console.warn("User went to lobby page via browser navigation, redirecting to game browser...")
- dispatch(Navigate(SwRoute.GAME_BROWSER))
- }
-}
-
-suspend fun SwSagaContext.gameSaga(session: SevenWondersSession) {
- if (reduxState.gameState == null) {
- // TODO properly redirect somewhere
- error("Game saga run without a current game")
- }
- // notifies the server that the client is ready to receive the first hand
- session.sayReady()
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/Sagas.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/Sagas.kt
deleted file mode 100644
index 2ad98c8e..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/Sagas.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-package org.luxons.sevenwonders.ui.redux.sagas
-
-import kotlinx.browser.window
-import kotlinx.coroutines.*
-import org.hildan.krossbow.stomp.ConnectionException
-import org.hildan.krossbow.stomp.MissingHeartBeatException
-import org.hildan.krossbow.stomp.WebSocketClosedUnexpectedly
-import org.luxons.sevenwonders.client.*
-import org.luxons.sevenwonders.model.api.events.GameEvent
-import org.luxons.sevenwonders.ui.redux.*
-import org.luxons.sevenwonders.ui.router.Navigate
-import org.luxons.sevenwonders.ui.router.SwRoute
-import org.luxons.sevenwonders.ui.router.routerSaga
-import redux.RAction
-import redux.WrapperAction
-import webpack.isProdEnv
-
-typealias SwSagaContext = SagaContext<SwState, RAction, WrapperAction>
-
-suspend fun SwSagaContext.rootSaga() = try {
- coroutineScope {
- val action = next<RequestChooseName>()
- val serverUrl = sevenWondersWebSocketUrl()
- console.info("Connecting to Seven Wonders web socket API...")
- val session = SevenWondersClient().connect(serverUrl)
- console.info("Connected!")
-
- launch(start = CoroutineStart.UNDISPATCHED) {
- serverErrorSaga(session)
- }
-
- launchApiActionHandlersIn(this, session)
- launchApiEventHandlersIn(this, session)
-
- val player = session.chooseNameAndAwait(action.playerName)
- dispatch(SetCurrentPlayerAction(player))
-
- routerSaga(SwRoute.GAME_BROWSER) {
- when (it) {
- SwRoute.HOME -> Unit
- SwRoute.LOBBY -> lobbySaga(session)
- SwRoute.GAME_BROWSER -> gameBrowserSaga(session)
- SwRoute.GAME -> gameSaga(session)
- }
- }
- }
-} catch (e: Exception) {
- console.error(e)
- dispatchFatalError(e)
-}
-
-private fun SwSagaContext.dispatchFatalError(throwable: Throwable) {
- when (throwable) {
- is ConnectionException -> dispatch(FatalError(throwable.message ?: "Couldn't connect to the server."))
- is MissingHeartBeatException -> dispatch(FatalError("The server doesn't seem to be responding."))
- is WebSocketClosedUnexpectedly -> dispatch(FatalError("The connection to the server was closed unexpectedly."))
- else -> dispatch(FatalError("An unexpected error occurred: ${throwable.message}"))
- }
-}
-
-private fun sevenWondersWebSocketUrl(): String {
- if (!isProdEnv()) {
- return "ws://localhost:8000"
- }
- // prevents mixed content requests
- val scheme = if (window.location.protocol.startsWith("https")) "wss" else "ws"
- return "$scheme://${window.location.host}"
-}
-
-private suspend fun serverErrorSaga(session: SevenWondersSession) {
- session.watchErrors().collect { err ->
- // These are not an error for the user, but rather for the programmer
- console.error("${err.code}: ${err.message}")
- console.error(JSON.stringify(err))
- }
-}
-
-private fun SwSagaContext.launchApiActionHandlersIn(scope: CoroutineScope, session: SevenWondersSession) {
- scope.launchOnEach<RequestChooseName> { session.chooseName(it.playerName) }
-
- scope.launchOnEach<RequestCreateGame> { session.createGame(it.gameName) }
- scope.launchOnEach<RequestJoinGame> { session.joinGame(it.gameId) }
- scope.launchOnEach<RequestLeaveLobby> { session.leaveLobby() }
- scope.launchOnEach<RequestDisbandLobby> { session.disbandLobby() }
-
- scope.launchOnEach<RequestAddBot> { session.addBot(it.botDisplayName) }
- scope.launchOnEach<RequestReorderPlayers> { session.reorderPlayers(it.orderedPlayers) }
- scope.launchOnEach<RequestReassignWonders> { session.reassignWonders(it.wonders) }
- scope.launchOnEach<RequestStartGame> { session.startGame() }
-
- scope.launchOnEach<RequestSayReady> { session.sayReady() }
- scope.launchOnEach<RequestPrepareMove> { session.prepareMove(it.move) }
- scope.launchOnEach<RequestUnprepareMove> { session.unprepareMove() }
- scope.launchOnEach<RequestLeaveGame> { session.leaveGame() }
-}
-
-private fun SwSagaContext.launchApiEventHandlersIn(scope: CoroutineScope, session: SevenWondersSession) {
- scope.launch {
- session.watchGameEvents().collect { event ->
- when (event) {
- is GameEvent.NameChosen -> {
- dispatch(SetCurrentPlayerAction(event.player))
- dispatch(Navigate(SwRoute.GAME_BROWSER))
- }
- is GameEvent.LobbyJoined -> {
- dispatch(EnterLobbyAction(event.lobby))
- dispatch(Navigate(SwRoute.LOBBY))
- }
- is GameEvent.LobbyUpdated -> {
- dispatch(UpdateLobbyAction(event.lobby))
- }
- GameEvent.LobbyLeft -> {
- dispatch(LeaveLobbyAction)
- dispatch(Navigate(SwRoute.GAME_BROWSER))
- }
- is GameEvent.GameStarted -> {
- val currentLobby = reduxState.currentLobby ?: error("Received game started event without being in a lobby")
- dispatch(EnterGameAction(currentLobby, event.turnInfo))
- dispatch(Navigate(SwRoute.GAME))
- }
- is GameEvent.NewTurnStarted -> dispatch(TurnInfoEvent(event.turnInfo))
- is GameEvent.MovePrepared -> dispatch(PreparedMoveEvent(event.move))
- is GameEvent.CardPrepared -> dispatch(PreparedCardEvent(event.preparedCard))
- is GameEvent.PlayerIsReady -> dispatch(PlayerReadyEvent(event.username))
- // Currently the move is already unprepared when launching the unprepare request
- // TODO add a "unpreparing" state and only update redux when the move is successfully unprepared
- GameEvent.MoveUnprepared -> {}
- }
- }
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/SagasFramework.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/SagasFramework.kt
deleted file mode 100644
index 05c03b13..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/redux/sagas/SagasFramework.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-package org.luxons.sevenwonders.ui.redux.sagas
-
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.*
-import kotlinx.coroutines.flow.*
-import redux.Middleware
-import redux.MiddlewareApi
-import redux.RAction
-
-class SagaManager<S, A : RAction, R>(
- private val monitor: ((A) -> Unit)? = null,
-) {
- private lateinit var context: SagaContext<S, A, R>
-
- private val actions = MutableSharedFlow<A>(extraBufferCapacity = Channel.UNLIMITED)
-
- fun createMiddleware(): Middleware<S, A, R, A, R> = ::sagasMiddleware
-
- private fun sagasMiddleware(api: MiddlewareApi<S, A, R>): ((A) -> R) -> (A) -> R {
- context = SagaContext(api, actions)
- return { nextDispatch ->
- { action ->
- onActionDispatched(action)
- val result = nextDispatch(action)
- handleAction(action)
- result
- }
- }
- }
-
- private fun onActionDispatched(action: A) {
- monitor?.invoke(action)
- }
-
- private fun handleAction(action: A) {
- val emitted = actions.tryEmit(action)
- if (!emitted) {
- // should never happen since our buffer is 'unlimited' (in reality it's Int.MAX_VALUE)
- error("Couldn't dispatch redux action, buffer is full")
- }
- }
-
- fun launchSaga(coroutineScope: CoroutineScope, saga: suspend SagaContext<S, A, R>.() -> Unit): Job {
- checkMiddlewareApplied()
- return coroutineScope.launch {
- context.saga()
- }
- }
-
- suspend fun runSaga(saga: suspend SagaContext<S, A, R>.() -> Unit) {
- checkMiddlewareApplied()
- context.saga()
- }
-
- private fun checkMiddlewareApplied() {
- check(::context.isInitialized) {
- "Before running a Saga, you must mount the Saga middleware on the Store using applyMiddleware"
- }
- }
-}
-
-class SagaContext<S, A : RAction, R>(
- private val reduxApi: MiddlewareApi<S, A, R>,
- val reduxActions: SharedFlow<A>,
-) {
- /**
- * The current redux state.
- */
- val reduxState: S
- get() = reduxApi.getState()
-
- /**
- * Dispatches the given redux [action].
- */
- fun dispatch(action: A) {
- reduxApi.dispatch(action)
- }
-
- /**
- * Executes [handle] on every action dispatched of the type [T]. This runs forever until the current coroutine is
- * cancelled.
- */
- suspend inline fun <reified T : A> onEach(
- crossinline handle: suspend SagaContext<S, A, R>.(T) -> Unit,
- ) {
- reduxActions.filterIsInstance<T>().collect { handle(it) }
- }
-
- /**
- * Launches a coroutine in the receiver scope that executes [handle] on every action dispatched of the type [T].
- * The returned [Job] can be used to cancel that coroutine (just like a regular [launch])
- */
- inline fun <reified T : A> CoroutineScope.launchOnEach(
- crossinline handle: suspend SagaContext<S, A, R>.(T) -> Unit,
- ): Job = launch { onEach(handle) }
-
- /**
- * Suspends until the next action matching the given [predicate] is dispatched, and returns that action.
- */
- suspend fun next(predicate: (A) -> Boolean): A = reduxActions.first { predicate(it) }
-
- /**
- * Suspends until the next action of type [T] is dispatched, and returns that action.
- */
- suspend inline fun <reified T : A> next(): T = reduxActions.filterIsInstance<T>().first()
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/router/Router.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/router/Router.kt
deleted file mode 100644
index 1a0840cf..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/router/Router.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.luxons.sevenwonders.ui.router
-
-import kotlinx.browser.window
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.launch
-import org.luxons.sevenwonders.ui.redux.sagas.SwSagaContext
-import redux.RAction
-
-enum class SwRoute(val path: String) {
- HOME("/"),
- GAME_BROWSER("/games"),
- LOBBY("/lobby"),
- GAME("/game");
-
- companion object {
- private val all = values().associateBy { it.path }
-
- fun from(path: String) = all.getValue(path)
- }
-}
-
-data class Navigate(val route: SwRoute) : RAction
-
-suspend fun SwSagaContext.routerSaga(
- startRoute: SwRoute,
- runRouteSaga: suspend SwSagaContext.(SwRoute) -> Unit,
-) {
- coroutineScope {
- window.location.hash = startRoute.path
- launch { changeRouteOnNavigateAction() }
- var currentSaga: Job = launch { runRouteSaga(startRoute) }
- window.onhashchange = { event ->
- val route = SwRoute.from(event.newURL.substringAfter("#"))
- currentSaga.cancel()
- currentSaga = this@coroutineScope.launch {
- runRouteSaga(route)
- }
- Unit
- }
- }
-}
-
-suspend fun SwSagaContext.changeRouteOnNavigateAction() {
- onEach<Navigate> {
- window.location.hash = it.route.path
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/CoroutinesUtils.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/CoroutinesUtils.kt
deleted file mode 100644
index 600f08d3..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/CoroutinesUtils.kt
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.luxons.sevenwonders.ui.utils
-
-import kotlinx.coroutines.async
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.selects.select
-
-// Cannot inline or it crashes for some reason
-suspend fun <R> awaitFirst(f1: suspend () -> R, f2: suspend () -> R): R = coroutineScope {
- val deferred1 = async { f1() }
- val deferred2 = async { f2() }
- select<R> {
- deferred1.onAwait { deferred2.cancel(); it }
- deferred2.onAwait { deferred1.cancel(); it }
- }
-}
diff --git a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/StyleUtils.kt b/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/StyleUtils.kt
deleted file mode 100644
index 7ca67be4..00000000
--- a/sw-ui/src/main/kotlin/org/luxons/sevenwonders/ui/utils/StyleUtils.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.luxons.sevenwonders.ui.utils
-
-import csstype.*
-import js.core.*
-import react.dom.html.*
-import web.cssom.*
-
-/**
- * The cubic-bezier() function defines a Cubic Bezier curve.
- *
- * A Cubic Bezier curve is defined by four points P0, P1, P2, and P3. P0 and P3 are the start and the end of the curve
- * and, in CSS these points are fixed as the coordinates are ratios. P0 is (0, 0) and represents the initial time and
- * the initial state, P3 is (1, 1) and represents the final time and the final state.
- *
- * The x coordinates provided here must be between 0 and 1 (the bezier curve points should be between the start time
- * and end time, giving other values would make the curve go back in the past or further into the future).
- *
- * The y coordinates may be any value: the intermediate states can be below or above the start (0) or end (1) values.
- */
-fun cubicBezier(x1: Double, y1: Double, x2: Double, y2: Double) =
- "cubic-bezier($x1, $y1, $x2, $y2)".unsafeCast<AnimationTimingFunction>()
-
-fun Margin(all: AutoLength) = Margin(vertical = all, horizontal = all)
-
-fun Padding(all: Length) = Padding(vertical = all, horizontal = all)
-
-// this should work because NamedColor is ultimately a hex string in JS, not the actual name
-fun NamedColor.withAlpha(alpha: Double) = "$this${(alpha * 255).toInt().toString(16)}".unsafeCast<BackgroundColor>()
-
-operator fun FilterFunction.plus(other: FilterFunction) = "$this $other".unsafeCast<FilterFunction>()
-
-fun PropertiesBuilder.ancestorHover(selector: String, block: PropertiesBuilder.() -> Unit) =
- "$selector:hover &".invoke(block)
-
-fun PropertiesBuilder.children(selector: String, block: PropertiesBuilder.() -> Unit) =
- "& > $selector".invoke(block)
-
-fun PropertiesBuilder.descendants(selector: String, block: PropertiesBuilder.() -> Unit) =
- "& $selector".invoke(block)
-
-fun HTMLAttributes<*>.inlineStyles(block: PropertiesBuilder.() -> Unit) {
- style = jso(block)
-}
diff --git a/sw-ui/src/main/kotlin/webpack/WebpackUtils.kt b/sw-ui/src/main/kotlin/webpack/WebpackUtils.kt
deleted file mode 100644
index dde1140a..00000000
--- a/sw-ui/src/main/kotlin/webpack/WebpackUtils.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package webpack
-
-external val process: Process
-
-external interface Process {
- val env: dynamic
-}
-
-fun isProdEnv(): Boolean = process.env.NODE_ENV == "production"
bgstack15