Compare commits

..

8 Commits

79 changed files with 528 additions and 544 deletions

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "knockoutwhist"]
path = knockoutwhist
branch = webapplication
url = https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist.git

1
knockoutwhist Submodule

Submodule knockoutwhist added at 48cd4d3956

View File

@@ -1,9 +1,13 @@
package components package components
import de.knockoutwhist.components.DefaultConfiguration import de.knockoutwhist.components.DefaultConfiguration
import controllers.Gamesession
import de.knockoutwhist.ui.UI
import de.knockoutwhist.utils.events.EventListener
//class WebApplicationConfiguration extends DefaultConfiguration { class WebApplicationConfiguration extends DefaultConfiguration {
//} override def uis: Set[UI] = super.uis + Gamesession
override def listener: Set[EventListener] = super.listener + Gamesession
}

View File

@@ -0,0 +1,57 @@
package controllers
import controllers.sessions.{AdvancedSession, PlayerSession, SimpleSession}
import de.knockoutwhist.cards.{Card, CardValue, Hand, Suit}
import de.knockoutwhist.control.GameLogic
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
import de.knockoutwhist.events.*
import de.knockoutwhist.events.player.{PlayCardEvent, PlayerEvent, ReceivedHandEvent, RequestTieChoiceEvent, RequestTrumpSuitEvent}
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.rounds.Match
import de.knockoutwhist.ui.UI
import de.knockoutwhist.utils.CustomThread
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
import java.util.UUID
import scala.collection.mutable
class Gamesession(id: UUID) extends CustomThread with EventListener with UI {
setName("Gamesession")
private val sessions: mutable.Map[UUID, PlayerSession] = mutable.Map()
var init = false
var logic: Option[GameLogic] = None
override def instance: CustomThread = this
override def listen(event: SimpleEvent): Unit = {
runLater {
event match {
case event: PlayCardEvent =>
PodGameManager.transmit(event.player.id, event)
case event: ReceivedHandEvent =>
PodGameManager.transmit(event.player.id, event)
case event: RequestTieChoiceEvent =>
PodGameManager.transmit(event.player.id, event)
case event: RequestTrumpSuitEvent =>
PodGameManager.transmit(event.player.id, event)
case _ =>
PodGameManager.transmitAll(event)
}
}
}
override def initial(gameLogic: GameLogic): Boolean = {
if (init) {
return false
}
this.logic = Some(gameLogic)
init = true
start()
true
}
def addSession(session: PlayerSession): Unit = {
sessions.put(session.id, session)
}
}

View File

@@ -1,11 +1,19 @@
package controllers package controllers
import javax.inject.* import controllers.sessions.SimpleSession
import controllers.sessions.AdvancedSession
import com.google.inject.{Guice, Injector}
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.components.Configuration
import de.knockoutwhist.rounds.Match
import di.KnockOutWebConfigurationModule
import play.api.* import play.api.*
import play.api.mvc.* import play.api.mvc.*
import de.knockoutwhist.KnockOutWhist import play.twirl.api.Html
import de.knockoutwhist.control.ControlHandler
import de.knockoutwhist.ui.tui.TUIMain import java.util.UUID
import javax.inject.*
/** /**
* This controller creates an `Action` to handle HTTP requests to the * This controller creates an `Action` to handle HTTP requests to the
@@ -15,6 +23,7 @@ import de.knockoutwhist.ui.tui.TUIMain
class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController { class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {
private var initial = false private var initial = false
private val injector: Injector = Guice.createInjector(KnockOutWebConfigurationModule())
/** /**
* Create an Action to render an HTML page. * Create an Action to render an HTML page.
@@ -26,21 +35,33 @@ class HomeController @Inject()(val controllerComponents: ControllerComponents) e
def index(): Action[AnyContent] = { def index(): Action[AnyContent] = {
if (!initial) { if (!initial) {
initial = true initial = true
ControlHandler.addListener(WebUI) KnockOutWhist.entry(injector.getInstance(classOf[Configuration]))
KnockOutWhist.main(new Array[String](_length = 0))
} }
Action { implicit request => Action { implicit request =>
Ok(views.html.index.apply()) Redirect("/sessions")
} }
} }
def ingame(): Action[AnyContent] = { def sessions(): Action[AnyContent] = {
Action { implicit request => Action { implicit request =>
Ok(views.html.tui.apply(WebUI.latestOutput)) Ok(views.html.sessions.apply(PodGameManager.listSessions().map(f => f.toString)))
} }
} }
def showTUI(): Action[AnyContent] = Action { implicit request => def ingame(id: String): Action[AnyContent] = {
Ok(views.html.tui.render(WebUI.latestOutput)) val uuid: UUID = UUID.fromString(id)
if (PodGameManager.identify(uuid).isEmpty) {
Action { implicit request =>
NotFound(views.html.tui.apply(List(Html(s"<p>Session with id $id not found!</p>"))))
}
} else {
val gamesession = PodGameManager.identify(uuid).get
val player = session.asInstanceOf[AdvancedSession].player
val logic =
Action { implicit request =>
Ok(views.html.matchy.apply(player, ))
}
}
} }
} }

View File

@@ -0,0 +1,41 @@
package controllers
import controllers.sessions.PlayerSession
import de.knockoutwhist.utils.events.SimpleEvent
import java.util.UUID
import scala.collection.mutable
object PodGameManager {
private val gamesession: mutable.Map[UUID, Gamesession] = mutable.Map()
def addGame(session: PlayerSession, gamesession: Gamesession): Unit = {
gamesession.put(session.id, gamesession)
}
def createGame(player: String): Unit = {
val game = Gamesession(UUID.randomUUID())
}
def clearSessions(): Unit = {
gamesession.clear()
}
def identify(id: UUID): Option[PlayerSession] = {
gamesession.get(id)
}
def transmit(id: UUID, event: SimpleEvent): Unit = {
identify(id).foreach(_.updatePlayer(event))
}
def transmitAll(event: SimpleEvent): Unit = {
gamesession.foreach(session => session._2.updatePlayer(event))
}
def listSessions(): List[UUID] = {
sessions.keys.toList
}
}

View File

@@ -1,521 +0,0 @@
package controllers
import de.knockoutwhist.events.directional.RequestPickTrumpsuitEvent
import de.knockoutwhist.events.round.ShowCurrentTrickEvent
import de.knockoutwhist.ui.UI
import de.knockoutwhist.ui.tui.TUIMain.{init, runLater, start}
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.cards.{Card, CardValue, Hand, Suit}
import de.knockoutwhist.control.controllerBaseImpl.{PlayerLogic, TrickLogic}
import de.knockoutwhist.control.{ControlHandler, ControlThread}
import de.knockoutwhist.events.*
import de.knockoutwhist.events.ERROR_STATUS.*
import de.knockoutwhist.events.GLOBAL_STATUS.*
import de.knockoutwhist.events.PLAYER_STATUS.*
import de.knockoutwhist.events.ROUND_STATUS.{PLAYERS_OUT, SHOW_START_ROUND, WON_ROUND}
import de.knockoutwhist.events.cards.{RenderHandEvent, ShowTieCardsEvent}
import de.knockoutwhist.events.directional.*
import de.knockoutwhist.events.round.ShowCurrentTrickEvent
import de.knockoutwhist.events.ui.GameState.MAIN_MENU
import de.knockoutwhist.events.ui.{GameState, GameStateUpdateEvent}
import de.knockoutwhist.events.util.DelayEvent
import de.knockoutwhist.player.Playertype.HUMAN
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory}
import de.knockoutwhist.ui.UI
import de.knockoutwhist.undo.{UndoManager, UndoneException}
import de.knockoutwhist.utils.CustomThread
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
import java.io.{BufferedReader, InputStreamReader}
import java.util.concurrent.atomic.AtomicBoolean
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
object WebUI extends CustomThread with EventListener with UI {
override def initial: Boolean = {
if (init) {
return false
}
init = true
start()
true
}
setName("WebUI")
override def instance: CustomThread = WebUI
var init = false
override def runLater[R](op: => R): Unit = {
interrupted.set(true)
super.runLater(op)
}
var latestOutput: String = ""
private var internState: GameState = GameState.NO_SET
override def listen(event: SimpleEvent): Unit = {
runLater {
event match {
case event: RenderHandEvent =>
renderhandmethod(event)
case event: ShowTieCardsEvent =>
showtiecardseventmethod(event)
case event: ShowGlobalStatus =>
showglobalstatusmethod(event)
case event: ShowPlayerStatus =>
showplayerstatusmethod(event)
case event: ShowRoundStatus =>
showroundstatusmethod(event)
case event: ShowErrorStatus =>
showerrstatmet(event)
case event: RequestTieNumberEvent =>
reqnumbereventmet(event)
case event: RequestCardEvent =>
reqcardeventmet(event)
case event: RequestDogPlayCardEvent =>
reqdogeventmet(event)
case event: RequestPickTrumpsuitEvent =>
reqpicktevmet(event)
case event: ShowCurrentTrickEvent =>
showcurtrevmet(event)
case event: GameStateUpdateEvent =>
if (internState != event.gameState) {
internState = event.gameState
if (event.gameState == GameState.MAIN_MENU) {
mainMenu()
} else if (event.gameState == GameState.PLAYERS) {
reqplayersevent()
}
Some(true)
}
case _ => None
}
}
}
object WebUICards {
def renderCardAsString(card: Card): Vector[String] = {
val lines = "│ │"
if (card.cardValue == CardValue.Ten) {
latestOutput += Vector(
s"┌─────────┐",
s"${cardColour(card.suit)}${Console.BOLD}${card.cardValue.cardType()}${Console.RESET}",
lines,
s"${cardColour(card.suit)}${Console.BOLD}${card.suit.cardType()}${Console.RESET}",
lines,
s"${cardColour(card.suit)}${Console.BOLD}${card.cardValue.cardType()}${Console.RESET}",
s"└─────────┘"
)
return Vector(
s"┌─────────┐",
s"${cardColour(card.suit)}${Console.BOLD}${card.cardValue.cardType()}${Console.RESET}",
lines,
s"${cardColour(card.suit)}${Console.BOLD}${card.suit.cardType()}${Console.RESET}",
lines,
s"${cardColour(card.suit)}${Console.BOLD}${card.cardValue.cardType()}${Console.RESET}",
s"└─────────┘"
)
}
latestOutput += Vector(
s"┌─────────┐",
s"${cardColour(card.suit)}${Console.BOLD}${card.cardValue.cardType()}${Console.RESET}",
lines,
s"${cardColour(card.suit)}${Console.BOLD}${card.suit.cardType()}${Console.RESET}",
lines,
s"${cardColour(card.suit)}${Console.BOLD}${card.cardValue.cardType()}${Console.RESET}",
s"└─────────┘"
)
return Vector(
s"┌─────────┐",
s"${cardColour(card.suit)}${Console.BOLD}${card.cardValue.cardType()}${Console.RESET}",
lines,
s"${cardColour(card.suit)}${Console.BOLD}${card.suit.cardType()}${Console.RESET}",
lines,
s"${cardColour(card.suit)}${Console.BOLD}${card.cardValue.cardType()}${Console.RESET}",
s"└─────────┘"
)
}
private def cardColour(suit: Suit): String = suit match {
case Suit.Hearts | Suit.Diamonds => Console.RED
case Suit.Clubs | Suit.Spades => Console.BLACK
}
def renderHandEvent(hand: Hand, showNumbers: Boolean): Vector[String] = {
val cardStrings = hand.cards.map(WebUICards.renderCardAsString)
var zipped = cardStrings.transpose
if (showNumbers) zipped = {
List.tabulate(hand.cards.length) { i =>
s" ${i + 1} "
}
} :: zipped
latestOutput += zipped.map(_.mkString(" ")).toVector
zipped.map(_.mkString(" ")).toVector
}
}
//override def initial: Boolean = {
//if (init) {
//return false
//}
//init = true
//start()
//true
//}
@tailrec
private def mainMenu(): Unit = {
latestOutput = ""
latestOutput += "Welcome to Knockout Whist\n"
latestOutput += "Please select an option:\n"
latestOutput += "1. Start a new match\n"
latestOutput += "2. Exit\n"
Try {
input().toInt
} match {
case Success(value) =>
value match {
case 1 =>
ControlThread.runLater {
KnockOutWhist.config.maincomponent.startMatch()
}
case 2 =>
println("Exiting the game.")
System.exit(0)
case _ =>
showerrstatmet(ShowErrorStatus(INVALID_NUMBER))
ControlThread.runLater {
ControlHandler.invoke(DelayEvent(500))
ControlHandler.invoke(GameStateUpdateEvent(MAIN_MENU))
}
mainMenu()
}
case Failure(exception) =>
exception match {
case undo: UndoneException =>
case _ =>
showerrstatmet(ShowErrorStatus(NOT_A_NUMBER))
ControlThread.runLater {
ControlHandler.invoke(DelayEvent(500))
ControlHandler.invoke(GameStateUpdateEvent(MAIN_MENU))
}
}
}
}
private def renderhandmethod(event: RenderHandEvent): Option[Boolean] = {
WebUICards.renderHandEvent(event.hand, event.showNumbers).foreach(println)
Some(true)
}
private def showtiecardseventmethod(event: ShowTieCardsEvent): Option[Boolean] = {
val a: Array[String] = Array("", "", "", "", "", "", "", "")
for ((player, card) <- event.card) {
val playerNameLength = player.name.length
a(0) += " " + player.name + ":" + (" " * (playerNameLength - 1))
val rendered = WebUICards.renderCardAsString(card)
a(1) += " " + rendered(0)
a(2) += " " + rendered(1)
a(3) += " " + rendered(2)
a(4) += " " + rendered(3)
a(5) += " " + rendered(4)
a(6) += " " + rendered(5)
a(7) += " " + rendered(6)
}
a.foreach(println)
Some(true)
}
private def showglobalstatusmethod(event: ShowGlobalStatus): Option[Boolean] = {
event.status match {
case SHOW_TIE =>
println("It's a tie! Let's cut to determine the winner.")
Some(true)
case SHOW_TIE_WINNER =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[AbstractPlayer]) {
None
} else {
println(s"${event.objects.head.asInstanceOf[AbstractPlayer].name} wins the cut!")
Some(true)
}
case SHOW_TIE_TIE =>
println("It's a tie again! Let's cut again.")
Some(true)
case SHOW_START_MATCH =>
latestOutput = ""
println("Starting a new match...")
wait(1000)
latestOutput = ""
Some(true)
case SHOW_TYPE_PLAYERS =>
latestOutput += "Please enter the names of the players, separated by a comma."
Some(true)
case SHOW_FINISHED_MATCH =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[AbstractPlayer]) {
None
} else {
latestOutput = ""
latestOutput += s"The match is over. The winner is ${event.objects.head.asInstanceOf[AbstractPlayer]}"
Some(true)
}
}
}
private def showplayerstatusmethod(event: ShowPlayerStatus): Option[Boolean] = {
val player = event.player
event.status match {
case SHOW_TURN =>
println("It's your turn, " + player.name + ".")
Some(true)
case SHOW_PLAY_CARD =>
println("Which card do you want to play?")
Some(true)
case SHOW_DOG_PLAY_CARD =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[Boolean]) {
None
} else {
println("You are using your dog life. Do you want to play your final card now?")
if (event.objects.head.asInstanceOf[Boolean]) {
println("You have to play your final card this round!")
println("Please enter y to play your final card.")
Some(true)
} else {
println("Please enter y/n to play your final card.")
Some(true)
}
}
case SHOW_TIE_NUMBERS =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[Int]) {
None
} else {
println(s"${player.name} enter a number between 1 and ${event.objects.head.asInstanceOf[Int]}.")
Some(true)
}
case SHOW_TRUMPSUIT_OPTIONS =>
println("Which suit do you want to pick as the next trump suit?")
println("1: Hearts")
println("2: Diamonds")
println("3: Clubs")
println("4: Spades")
println()
Some(true)
case SHOW_NOT_PLAYED =>
println(s"Player ${event.player} decided to not play his card")
Some(true)
case SHOW_WON_PLAYER_TRICK =>
println(s"${event.player.name} won the trick.")
wait(2000)
latestOutput = ""
Some(true)
}
}
private def showroundstatusmethod(event: ShowRoundStatus): Option[Boolean] = {
event.status match {
case SHOW_START_ROUND =>
latestOutput = ""
println(s"Starting a new round. The trump suit is ${event.currentRound.trumpSuit}.")
wait(2000)
latestOutput = ""
Some(true)
case WON_ROUND =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[AbstractPlayer]) {
None
} else {
println(s"${event.objects.head.asInstanceOf[AbstractPlayer].name} won the round.")
Some(true)
}
case PLAYERS_OUT =>
println("The following players are out of the game:")
event.currentRound.playersout.foreach(p => {
println(p.name)
})
Some(true)
}
}
private def showerrstatmet(event: ShowErrorStatus): Option[Boolean] = {
event.status match {
case INVALID_NUMBER =>
println("Please enter a valid number.")
Some(true)
case NOT_A_NUMBER =>
println("Please enter a number.")
Some(true)
case INVALID_INPUT =>
latestOutput += "Please enter a valid input"
Some(true)
case INVALID_NUMBER_OF_PLAYERS =>
latestOutput += "Please enter at least two names."
Some(true)
case IDENTICAL_NAMES =>
latestOutput += "Please enter unique names."
Some(true)
case INVALID_NAME_FORMAT =>
latestOutput += "Please enter valid names. Those can not be empty, shorter than 2 or longer then 10 characters."
Some(true)
case WRONG_CARD =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[Card]) {
None
} else {
latestOutput += f"You have to play a card of suit: ${event.objects.head.asInstanceOf[Card].suit}\n"
Some(true)
}
}
}
private def reqnumbereventmet(event: RequestTieNumberEvent): Option[Boolean] = {
val tryTie = Try {
val number = input().toInt
if (number < 1 || number > event.remaining) {
throw new IllegalArgumentException(s"Number must be between 1 and ${event.remaining}")
}
number
}
if (tryTie.isFailure && tryTie.failed.get.isInstanceOf[UndoneException]) {
return Some(true)
}
ControlThread.runLater {
KnockOutWhist.config.playerlogcomponent.selectedTie(event.winner, event.matchImpl, event.round, event.playersout, event.cut, tryTie, event.currentStep, event.remaining, event.currentIndex)
}
Some(true)
}
private def reqcardeventmet(event: RequestCardEvent): Option[Boolean] = {
val tryCard = Try {
val card = input().toInt - 1
if (card < 0 || card >= event.hand.cards.length) {
throw new IllegalArgumentException(s"Number has to be between 1 and ${event.hand.cards.length}")
} else {
event.hand.cards(card)
}
}
if (tryCard.isFailure && tryCard.failed.get.isInstanceOf[UndoneException]) {
return Some(true)
}
ControlThread.runLater {
KnockOutWhist.config.trickcomponent.controlSuitplayed(tryCard, event.matchImpl, event.round, event.trick, event.currentIndex, event.player)
}
Some(true)
}
private def reqdogeventmet(event: RequestDogPlayCardEvent): Option[Boolean] = {
val tryDogCard = Try {
val card = input()
if (card.equalsIgnoreCase("y")) {
Some(event.hand.cards.head)
} else if (card.equalsIgnoreCase("n") && !event.needstoplay) {
None
} else {
throw new IllegalArgumentException("Didn't want to play card but had to")
}
}
if (tryDogCard.isFailure && tryDogCard.failed.get.isInstanceOf[UndoneException]) {
return Some(true)
}
ControlThread.runLater {
KnockOutWhist.config.trickcomponent.controlDogPlayed(tryDogCard, event.matchImpl, event.round, event.trick, event.currentIndex, event.player)
}
Some(true)
}
private def reqplayersevent(): Option[Boolean] = {
showglobalstatusmethod(ShowGlobalStatus(SHOW_TYPE_PLAYERS))
val names = Try {
input().split(",")
}
if (names.isFailure && names.failed.get.isInstanceOf[UndoneException]) {
return Some(true)
}
if (names.get.length < 2) {
showerrstatmet(ShowErrorStatus(INVALID_NUMBER_OF_PLAYERS))
return reqplayersevent()
}
if (names.get.distinct.length != names.get.length) {
showerrstatmet(ShowErrorStatus(IDENTICAL_NAMES))
return reqplayersevent()
}
if (names.get.count(_.trim.isBlank) > 0
|| names.get.count(_.trim.length <= 2) > 0
|| names.get.count(_.trim.length > 10) > 0) {
showerrstatmet(ShowErrorStatus(INVALID_NAME_FORMAT))
return reqplayersevent()
}
ControlThread.runLater {
KnockOutWhist.config
.maincomponent
.enteredPlayers(names.get
.map(s => PlayerFactory.createPlayer(s, playertype = HUMAN))
.toList)
}
Some(true)
}
private def reqpicktevmet(event: RequestPickTrumpsuitEvent): Option[Boolean] = {
val trySuit = Try {
val suit = input().toInt
suit match {
case 1 => Suit.Hearts
case 2 => Suit.Diamonds
case 3 => Suit.Clubs
case 4 => Suit.Spades
case _ => throw IllegalArgumentException("Didn't enter a number between 1 and 4")
}
}
if (trySuit.isFailure && trySuit.failed.get.isInstanceOf[UndoneException]) {
return Some(true)
}
ControlThread.runLater {
KnockOutWhist.config.playerlogcomponent.trumpSuitSelected(event.matchImpl, trySuit, event.remaining_players, event.firstRound, event.player)
}
Some(true)
}
private def showcurtrevmet(event: ShowCurrentTrickEvent): Option[Boolean] = {
latestOutput = ""
val sb = new StringBuilder()
sb.append("Current Trick:\n")
sb.append("Trump-Suit: " + event.round.trumpSuit + "\n")
if (event.trick.firstCard.isDefined) {
sb.append(s"Suit to play: ${event.trick.firstCard.get.suit}\n")
}
for ((card, player) <- event.trick.cards) {
sb.append(s"${player.name} played ${card.toString}\n")
}
latestOutput += sb.toString()
//println(sb.toString())
Some(true)
}
private val isInIO: AtomicBoolean = new AtomicBoolean(false)
private val interrupted: AtomicBoolean = new AtomicBoolean(false)
private def input(): String = {
interrupted.set(false)
val reader = new BufferedReader(new InputStreamReader(System.in))
while (!interrupted.get()) {
if (reader.ready()) {
val in = reader.readLine()
if (in.equals("undo")) {
UndoManager.undoStep()
throw new UndoneException("Undo")
} else if (in.equals("redo")) {
UndoManager.redoStep()
throw new UndoneException("Redo")
} else if (in.equals("load")
&& KnockOutWhist.config.persistenceManager.canLoadfile("currentSnapshot")) {
KnockOutWhist.config.persistenceManager.loadFile("currentSnapshot.json")
throw new UndoneException("Load")
} else if (in.equals("save")) {
KnockOutWhist.config.persistenceManager.saveFile("currentSnapshot.json")
}
return in
}
Thread.sleep(50)
}
throw new UndoneException("Skipped")
}
}

View File

@@ -0,0 +1,13 @@
package controllers.sessions
import de.knockoutwhist.rounds.Match
import de.knockoutwhist.rounds.Round
import de.knockoutwhist.utils.events.SimpleEvent
import de.knockoutwhist.player.AbstractPlayer
import java.util.UUID
case class AdvancedSession(player: AbstractPlayer) extends PlayerSession {
override def id(): UUID = {
player.id
}
}

View File

@@ -0,0 +1,12 @@
package controllers.sessions
import de.knockoutwhist.utils.events.SimpleEvent
import java.util.UUID
trait PlayerSession {
def id: UUID
def updatePlayer(event: SimpleEvent): Unit
}

View File

@@ -0,0 +1,260 @@
package controllers.sessions
import de.knockoutwhist.cards.{Card, CardValue, Hand}
import de.knockoutwhist.events.ERROR_STATUS.*
import de.knockoutwhist.events.GLOBAL_STATUS.*
import de.knockoutwhist.events.PLAYER_STATUS.*
import de.knockoutwhist.events.ROUND_STATUS.*
import de.knockoutwhist.events.{ShowErrorStatus, ShowGlobalStatus, ShowPlayerStatus, ShowRoundStatus}
import de.knockoutwhist.events.cards.{RenderHandEvent, ShowTieCardsEvent}
import de.knockoutwhist.events.round.ShowCurrentTrickEvent
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
import play.twirl.api.Html
import scalafx.scene.image.Image
import util.WebUIUtils
import java.util.UUID
case class SimpleSession(id: UUID, private var output: List[Html]) extends PlayerSession {
def get(): List[Html] = {
output
}
override def updatePlayer(event: SimpleEvent): Unit = {
event match {
case event: RenderHandEvent =>
renderHand(event)
case event: ShowTieCardsEvent =>
showtiecardseventmethod(event)
case event: ShowGlobalStatus =>
showglobalstatusmethod(event)
case event: ShowPlayerStatus =>
showplayerstatusmethod(event)
case event: ShowRoundStatus =>
showroundstatusmethod(event)
case event: ShowErrorStatus =>
showerrstatmet(event)
case event: ShowCurrentTrickEvent =>
showcurtrevmet(event)
}
}
private def clear(): Unit = {
output = List()
}
private def renderHand(event: RenderHandEvent): Unit = {
output = output :++ WebUICards.renderHandEvent(event.hand)
output = output :+ Html("<br>")
}
private def showtiecardseventmethod(event: ShowTieCardsEvent): Option[Boolean] = {
var l = List[Html]()
for ((player, card) <- event.card) {
l = l :+ Html(s"<p>${player.name}:</p>")
l = l :+ WebUIUtils.cardtoImage(card)
l = l :+ Html("<br>")
}
output = output :++ l
output = output :+ Html("<br>")
Some(true)
}
private def showglobalstatusmethod(event: ShowGlobalStatus): Option[Boolean] = {
event.status match {
case SHOW_TIE =>
println("It's a tie! Let's cut to determine the winner.")
Some(true)
case SHOW_TIE_WINNER =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[AbstractPlayer]) {
None
} else {
println(s"${event.objects.head.asInstanceOf[AbstractPlayer].name} wins the cut!")
Some(true)
}
case SHOW_TIE_TIE =>
println("It's a tie again! Let's cut again.")
Some(true)
case SHOW_START_MATCH =>
clear()
println("Starting a new match...")
output = output :+ Html("<br><br>")
Some(true)
case SHOW_TYPE_PLAYERS =>
println("Please enter the names of the players, separated by a comma.")
Some(true)
case SHOW_FINISHED_MATCH =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[AbstractPlayer]) {
None
} else {
clear()
println(s"The match is over. The winner is ${event.objects.head.asInstanceOf[AbstractPlayer]}")
Some(true)
}
}
}
private def showplayerstatusmethod(event: ShowPlayerStatus): Option[Boolean] = {
val player = event.player
event.status match {
case SHOW_PLAY_CARD =>
println("Which card do you want to play?")
Some(true)
case SHOW_DOG_PLAY_CARD =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[Boolean]) {
None
} else {
println("You are using your dog life. Do you want to play your final card now?")
if (event.objects.head.asInstanceOf[Boolean]) {
println("You have to play your final card this round!")
println("Please enter y to play your final card.")
Some(true)
} else {
println("Please enter y/n to play your final card.")
Some(true)
}
}
case SHOW_TIE_NUMBERS =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[Int]) {
None
} else {
println(s"${player.name} enter a number between 1 and ${event.objects.head.asInstanceOf[Int]}.")
Some(true)
}
case SHOW_TRUMPSUIT_OPTIONS =>
println("Which suit do you want to pick as the next trump suit?")
println("1: Hearts")
println("2: Diamonds")
println("3: Clubs")
println("4: Spades")
println()
Some(true)
case SHOW_NOT_PLAYED =>
println(s"Player ${event.player} decided to not play his card")
Some(true)
case SHOW_WON_PLAYER_TRICK =>
println(s"${event.player.name} won the trick.")
output = output :+ Html("<br><br>")
Some(true)
}
}
private def showroundstatusmethod(event: ShowRoundStatus): Option[Boolean] = {
event.status match {
case SHOW_TURN =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[AbstractPlayer]) {
None
} else {
println(s"It's ${event.objects.head.asInstanceOf[AbstractPlayer].name} turn.")
Some(true)
}
case SHOW_START_ROUND =>
clear()
println(s"Starting a new round. The trump suit is ${event.currentRound.trumpSuit}.")
output = output :+ Html("<br><br>")
Some(true)
case WON_ROUND =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[AbstractPlayer]) {
None
} else {
println(s"${event.objects.head.asInstanceOf[AbstractPlayer].name} won the round.")
Some(true)
}
case PLAYERS_OUT =>
println("The following players are out of the game:")
event.currentRound.playersout.foreach(p => {
println(p.name)
})
Some(true)
}
}
private def showerrstatmet(event: ShowErrorStatus): Option[Boolean] = {
event.status match {
case INVALID_NUMBER =>
println("Please enter a valid number.")
Some(true)
case NOT_A_NUMBER =>
println("Please enter a number.")
Some(true)
case INVALID_INPUT =>
println("Please enter a valid input")
Some(true)
case INVALID_NUMBER_OF_PLAYERS =>
println("Please enter at least two names.")
Some(true)
case IDENTICAL_NAMES =>
println("Please enter unique names.")
Some(true)
case INVALID_NAME_FORMAT =>
println("Please enter valid names. Those can not be empty, shorter than 2 or longer then 10 characters.")
Some(true)
case WRONG_CARD =>
if (event.objects.length != 1 || !event.objects.head.isInstanceOf[Card]) {
None
} else {
println(f"You have to play a card of suit: ${event.objects.head.asInstanceOf[Card].suit}\n")
Some(true)
}
}
}
private def showcurtrevmet(event: ShowCurrentTrickEvent): Option[Boolean] = {
clear()
val sb = new StringBuilder()
sb.append("Current Trick:\n")
sb.append("Trump-Suit: " + event.round.trumpSuit + "\n")
if (event.trick.firstCard.isDefined) {
sb.append(s"Suit to play: ${event.trick.firstCard.get.suit}\n")
}
for ((card, player) <- event.trick.cards) {
sb.append(s"${player.name} played ${card.toString}\n")
}
println(sb.toString())
Some(true)
}
private def println(s: String): Unit = {
var html = List[Html]()
for (line <- s.split("\n")) {
html = html :+ Html(line)
html = html :+ Html("<br>")
}
output = output :++ html
}
private def println(): Unit = {
output = output :+ Html("<br>")
}
object WebUICards {
def renderCardAsString(card: Card): Vector[String] = {
val lines = "│ │"
if (card.cardValue == CardValue.Ten) {
return Vector(
s"┌─────────┐",
s"${card.cardValue.cardType()}",
lines,
s"${card.suit.cardType()}",
lines,
s"${card.cardValue.cardType()}",
s"└─────────┘"
)
}
Vector(
s"┌─────────┐",
s"${card.cardValue.cardType()}",
lines,
s"${card.suit.cardType()}",
lines,
s"${card.cardValue.cardType()}",
s"└─────────┘"
)
}
def renderHandEvent(hand: Hand): List[Html] = {
hand.cards.map(WebUIUtils.cardtoImage)
}
}
}

View File

@@ -0,0 +1,13 @@
package di
import com.google.inject.AbstractModule
import components.WebApplicationConfiguration
import de.knockoutwhist.components.Configuration
class KnockOutWebConfigurationModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[Configuration]).to(classOf[WebApplicationConfiguration])
}
}

View File

@@ -0,0 +1,34 @@
package util
import de.knockoutwhist.cards.Card
import de.knockoutwhist.cards.CardValue.{Ace, Eight, Five, Four, Jack, King, Nine, Queen, Seven, Six, Ten, Three, Two}
import de.knockoutwhist.cards.Suit.{Clubs, Diamonds, Hearts, Spades}
import play.twirl.api.Html
import scalafx.scene.image.Image
object WebUIUtils {
def cardtoImage(card: Card): Html = {
val s = card.suit match {
case Spades => "S"
case Hearts => "H"
case Clubs => "C"
case Diamonds => "D"
}
val cv = card.cardValue match {
case Ace => "A"
case King => "K"
case Queen => "Q"
case Jack => "J"
case Ten => "T"
case Nine => "9"
case Eight => "8"
case Seven => "7"
case Six => "6"
case Five => "5"
case Four => "4"
case Three => "3"
case Two => "2"
}
views.html.output.card.apply(f"images/cards/$cv$s.png")(card.toString)
}
}

View File

@@ -1,10 +1,25 @@
@(title: String)(content: play.twirl.api.Html) @*
* This template is called from the `index` template. This template
* handles the rendering of the page header and body tags. It takes
* two arguments, a `String` for the title of the page and an `Html`
* object to insert into the body of the page.
*@
@(title: String)(content: Html)
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
@* Here's where we render the page title `String`. *@
<title>@title</title> <title>@title</title>
<link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
<link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
</head> </head>
<body> <body>
@* And here's where we render the `Html` object containing
* the page content. *@
@content @content
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,9 @@
@(toRender: List[Any])
@main("Match") {
<div id="match">
@for(line <- toRender) {
@line
}
</div>
}

View File

@@ -0,0 +1,2 @@
@(src: String)(alt: String)
<img src="@routes.Assets.versioned(src)" alt="@alt"/>

View File

@@ -0,0 +1,3 @@
@(text: String)
<p>@text</p>

View File

@@ -0,0 +1,10 @@
@(toRender: List[String])
@main("Sessions") {
<div id="sessions">
@for(line <- toRender) {
<a href="@routes.HomeController.ingame(line)">@line</a><br>
}
</div>
}

View File

@@ -1,5 +1,10 @@
@(renderTUI: String) @(toRender: List[Html])
@main("Welcome to Play") { @main("Tui") {
<h1>@renderTUI</h1> <div id="tui">
@for(line <- toRender) {
@line
}
</div>
} }

View File

@@ -4,8 +4,9 @@
# ~~~~ # ~~~~
# An example controller showing a sample home page # An example controller showing a sample home page
GET / controllers.HomeController.index() GET / controllers.HomeController.index()
GET /ingame controllers.HomeController.ingame() GET /sessions controllers.HomeController.sessions()
GET /showTUI controllers.HomeController.showTUI() GET /ingame/:id controllers.HomeController.ingame(id: String)
# Map static resources from the /public folder to the /assets URL path # Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB