Compare commits

...

32 Commits

Author SHA1 Message Date
bef96ba7e3 fix(ui): changed backgrounds (#33)
Reviewed-on: #33
2025-10-30 10:47:40 +01:00
c0dadf8927 feat(ui): CSS Animations #18 (#27)
Added css animations on reload

Co-authored-by: LQ63 <lkhermann@web.de>
Reviewed-on: #27
Reviewed-by: Janis <janis-e@gmx.de>
Co-authored-by: lq64 <lq@blackhole.local>
Co-committed-by: lq64 <lq@blackhole.local>
2025-10-30 08:03:00 +01:00
1f377de0f4 feat(ui): implement CSS variables for theme support (#26)
#17

Reviewed-on: #26
Reviewed-by: lq64 <lq@blackhole.local>
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-29 16:52:32 +01:00
6c31fa0538 fix(ui): added dark mode (#25)
Reviewed-on: #25
Reviewed-by: lq64 <lq@blackhole.local>
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-29 10:37:17 +01:00
729a4eec6b fix(ui): add light mode styles, update font families and fixed ui (#24)
Reviewed-on: #24
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-28 19:06:42 +01:00
72fcf783b8 feat(ui): LESS Integration
#15-Create-a-default-theme-with-Less- (#23)
Added LESS and created a light mode

Co-authored-by: LQ63 <lkhermann@web.de>
Reviewed-on: #23
Reviewed-by: Janis <janis-e@gmx.de>
Co-authored-by: lq64 <lq@blackhole.local>
Co-committed-by: lq64 <lq@blackhole.local>
2025-10-28 18:34:29 +01:00
1517d0c006 fix(imports): reorganized import statements for clarity and consistency (#22)
Reviewed-on: #22
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-26 18:09:36 +01:00
7f765b4514 fix(base): fixed persistence logic (#21)
Reviewed-on: #21
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-26 18:04:03 +01:00
03f1811ab4 refactor(ui): refactored the CSS into a separate stylesheet (#20)
#14 [Subtask] CSS in separate stylesheet

Reviewed-on: #20
Reviewed-by: lq64 <lq@blackhole.local>
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-24 13:55:03 +02:00
63689b7a26 refactor(ui): Fixed UI Code Smell (#19)
Reviewed-on: #19
Reviewed-by: lq64 <lq@blackhole.local>
2025-10-23 11:08:08 +02:00
92e4851219 feat(ui): added rules (#12)
added /rules to webui

Co-authored-by: LQ63 <lkhermann@web.de>
Reviewed-on: #12
2025-10-23 09:39:13 +02:00
c168ae7dc0 feat(ui): UI now shows player names instead of their id (#11)
Reviewed-on: #11
2025-10-23 08:17:11 +02:00
ccf44ede41 fix(config): modified git module to use the main branch (#10)
Reviewed-on: #10
2025-10-23 08:05:44 +02:00
d71809d6f4 feat(config): add issue templates for Epics, User Stories, and Subtasks (#4)
Reviewed-on: #4
2025-10-22 21:16:52 +02:00
82245d6bcc test(base): added some tests to improve the coverage (#47) (#2)
Reviewed-on: KnockOutWhist/KnockOutWhist#47
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
Reviewed-on: #2
2025-10-22 20:14:22 +02:00
d8576f669a ci(config): added conventional commit json (#1)
Reviewed-on: #1
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-10-22 17:47:12 +02:00
LQ63
cfe27f1e78 added picture 2025-10-22 08:42:17 +02:00
LQ63
b33ab184d2 Added rendering for Web Page 2025-10-22 08:31:25 +02:00
LQ63
b17c5160e9 Added rendering for Web Page 2025-10-22 08:24:58 +02:00
7458464dd6 Various fixes 2025-10-22 08:16:22 +02:00
f8c337fad1 Rename PlayCardEvent to RequestCardEvent for clarity in event handling 2025-10-19 20:49:26 +02:00
3357fb7310 Refactor game logic to track player input state; add CardPlayedEvent and update RoundEndEvent with trick count 2025-10-19 20:48:46 +02:00
dad604186e Update submodule configuration for knockoutwhist to track webapplication branch 2025-10-17 10:48:47 +02:00
8e415390e7 Add HTML rendering for sessions and card images; refactor output handling in SimpleSession 2025-10-17 10:26:30 +02:00
fc751af1ef Add HTML rendering for sessions and card images; refactor output handling in SimpleSession 2025-10-16 08:12:26 +02:00
LQ63
9aa447f2f6 Added link to click for player pov, started rendering with images 2025-10-13 22:39:21 +02:00
2b89f3d161 Add WebUICards object for rendering cards and hands; update render methods in SimpleSession 2025-10-13 15:25:31 +02:00
c77eeff123 Refactor match handling and session management; add player session functionality and update event handling in WebUI 2025-10-13 15:16:41 +02:00
7cbb6e6ab7 Refactor Match class and rename WebUI to WebUIMain; update HomeController for new UI structure 2025-10-12 22:56:25 +02:00
LQ63
6c57abb1db Added WebUI observer, added route to see latestOutput. For some reason the WebUI Observer doesn't execute it's methods for writing stuff into latestOutput. This has something to do with how the threads work 2025-10-12 15:21:20 +02:00
LQ63
854189967f Added WebUI observer, added route to see latestOutput. For some reason the WebUI Observer doesn't execute it's methods for writing stuff into latestOutput. This has something to do with how the threads work 2025-10-12 15:18:55 +02:00
LQ63
9098b7c4e3 Added WebUI observer, added route to see latestOutput. For some reason the WebUI Observer doesn't execute it's methods for writing stuff into latestOutput. This has something to do with how the threads work 2025-10-12 15:05:40 +02:00
94 changed files with 842 additions and 30 deletions

View File

@@ -0,0 +1,46 @@
---
name: "Epic"
about: "A large initiative or feature that consists of multiple User Stories or Subtasks"
title: "[Epic] <Epic title>"
labels: ["Type/Epic"]
---
## 🧩 Epic Summary
Provide a clear summary of what this Epic aims to achieve.
---
## 🎯 Goals
- [ ] Goal 1
- [ ] Goal 2
- [ ] Goal 3
---
## 📋 Description
Describe the high-level context, purpose, and expected outcome of this Epic.
---
## 🧵 Related User Stories / Subtasks
Link to related issues here:
- [ ] #<user-story-1>
- [ ] #<user-story-2>
- [ ] #<subtask-1>
---
## 📅 Milestone / Timeline
If applicable, note any key dates or milestones:
- Target Start:
- Target Completion:
---
## ⚠️ Risks / Dependencies
List any major risks or dependencies that could affect delivery.
---
## ✅ Acceptance Criteria
Define the success metrics or completion definition for the Epic.

View File

@@ -0,0 +1,35 @@
---
name: "Subtask"
about: "A smaller task that contributes to a User Story or Epic"
title: "[Subtask] <Task title>"
labels: ["Type/Subtask"]
---
## 🧾 Description
Briefly describe what needs to be done for this subtask.
---
## 🔗 Linked Issues
- Parent Story: #<user-story-number>
- Related Epic: #<epic-number>
---
## ⚙️ Steps / Tasks
- [ ] Task 1
- [ ] Task 2
- [ ] Task 3
---
## ✅ Definition of Done
What must be true for this subtask to be considered complete:
- [ ] Code implemented
- [ ] Tests passed
- [ ] Reviewed and merged
---
## 🧩 Estimated Size
Use label: `Size/XS` | `Size/S` | `Size/M` | `Size/L` | `Size/XL`

View File

@@ -0,0 +1,42 @@
---
name: "User Story"
about: "A feature or requirement from the user's perspective"
title: "[Story] <User story title>"
labels: ["Type/User Story"]
---
## 🧍‍♂️ User Story
**As a** [type of user]
**I want to** [perform an action]
**So that** [achieve a goal or value]
---
## 📋 Description
Provide additional context or business logic for this story.
---
## 🎯 Acceptance Criteria
List the specific, measurable criteria that define when this story is done:
- [ ] Criterion 1
- [ ] Criterion 2
- [ ] Criterion 3
---
## 🧱 Implementation Notes
Include technical notes, design references, or constraints.
---
## 🧵 Linked Issues
- Parent Epic: #<epic-number>
- Related Subtasks:
- [ ] #<subtask-1>
- [ ] #<subtask-2>
---
## 🧩 Estimated Size
Use label: `Size/XS` | `Size/S` | `Size/M` | `Size/L` | `Size/XL`

4
.gitmodules vendored Normal file
View File

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

View File

@@ -19,7 +19,6 @@ lazy val commonSettings = Seq(
.map(m => "org.openjfx" % s"javafx-$m" % "21" classifier osName)
},
libraryDependencies += guice,
Test / testOptions += Tests.Filter(_.equals("de.knockoutwhist.TestSequence")),
coverageEnabled := true,
coverageFailOnMinimum := true,
coverageMinimumStmtTotal := 85,
@@ -29,7 +28,8 @@ lazy val commonSettings = Seq(
lazy val knockoutwhist = project.in(file("knockoutwhist"))
.settings(
commonSettings,
mainClass := Some("de.knockoutwhist.KnockOutWhist")
mainClass := Some("de.knockoutwhist.KnockOutWhist"),
coverageExcludedPackages := "de.knockoutwhist.ui.*;de.knockoutwhist.utils.gui.*"
)
lazy val knockoutwhistweb = project.in(file("knockoutwhistweb"))

116
conventionalcommit.json Normal file
View File

@@ -0,0 +1,116 @@
{
"$schema": "https://github.com/lppedd/idea-conventional-commit/raw/master/src/main/resources/defaults/conventionalcommit.schema.json",
"types": {
"refactor": {
"description": "Changes which neither fix a bug nor add a feature"
},
"fix": {
"description": "Changes which patch a bug"
},
"feat": {
"description": "Changes which introduce a new feature"
},
"build": {
"description": "Changes which affect the build system or external dependencies.<br>Example scopes: gulp, broccoli, npm",
"scopes": {
"npm": {},
"gulp": {},
"broccoli": {}
}
},
"chore": {
"description": "Changes which are not user-facing"
},
"style": {
"description": "Changes which do not affect code logic, such as whitespaces, formatting, missing semicolons"
},
"test": {
"description": "Changes which add missing tests or fix existing ones"
},
"docs": {
"description": "Changes which affect documentation"
},
"perf": {
"description": "Changes which improve performance"
},
"ci": {
"description": "Changes which affect CI configuration files and scripts.<br>Example scopes: travis, circle, browser-stack, sauce-labs"
},
"revert": {
"description": "Changes which revert a previous commit"
}
},
"commonScopes": {
"api": {
"description": "Changes related to the API"
},
"auth": {
"description": "Changes related to authentication"
},
"config": {
"description": "Changes related to configuration"
},
"db": {
"description": "Changes related to the database"
},
"docs": {
"description": "Changes related to documentation"
},
"ui": {
"description": "Changes related to the user interface"
},
"ux": {
"description": "Changes related to the user experience"
},
"build": {
"description": "Changes related to the build system"
},
"ci": {
"description": "Changes related to continuous integration"
},
"deps": {
"description": "Changes related to dependencies"
},
"promotion": {
"description": "Kargo promotion changes"
},
"base": {
"description": "Changes related to the core functionality"
},
"release": {
"description": "A release commit"
}
},
"footerTypes": {
"BREAKING-CHANGE": {
"description": "The commit introduces breaking changes"
},
"Closes": {
"description": "The commit closes issues or pull requests"
},
"Implements": {
"description": "The commit implements new features"
},
"Author": {
"description": "The commit's author"
},
"Co-authored-by": {
"description": "The specified person co-authored the commit's changes"
},
"Signed-off-by": {
"description": "Certifies the committer has the rights to submit the work under the project's license or agrees to some contributor representation, such as a Developer Certificate of Origin"
},
"Reviewed-by": {
"description": "The specified person reviewed and is completely satisfied with the commit's changes"
},
"Tested-by": {
"description": "The specified person applied the commit's changes and found them to have the desired effect"
},
"Acked-by": {
"description": "The specified person liked the commit's changes"
},
"Refs": {
"description": "The commit references another commit by its hash ID.<br>For multiple hash IDs, use a comma as separator"
}
}
}

1
knockoutwhist Submodule

Submodule knockoutwhist added at fbc0ea2277

View File

@@ -0,0 +1,6 @@
@media (prefers-color-scheme: dark) {
:root {
--background-image: url('/assets/images/background.png');
--color: white;
}
}

View File

@@ -0,0 +1,4 @@
:root {
--background-image: url('/assets/images/img.png');
--color: black;
}

View File

@@ -0,0 +1,133 @@
@import "light-mode.less";
@import "dark-mode.less";
@background-image: var(--background-image);
@color: var(--color);
@keyframes slideIn {
0% { transform: translateX(-100vw); }
100% { transform: translateX(0); }
}
body {
background-image: @background-image;
background-size: 100vw 100vh;
}
html, body {
height: 100vh;
margin: 0;
}
#sessions {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
text-align: center;
h1 {
animation: slideIn 0.5s ease-out forwards;
animation-fill-mode: backwards;
}
}
#textanimation {
animation: slideIn 0.5s ease-out forwards;
animation-fill-mode: backwards;
animation-delay: 1s;
}
#sessions a, h1, p {
color: @color;
font-size: 40px;
font-family: Arial, serif;
}
#ingame {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
height: 100%;
}
#playercards {
display: flex;
flex-direction: row;
justify-content: center;
height: 20%;
img {
animation: slideIn 0.5s ease-out forwards;
animation-fill-mode: backwards;
&:nth-child(1) { animation-delay: 0.5s; }
&:nth-child(2) { animation-delay: 1s; }
&:nth-child(3) { animation-delay: 1.5s; }
&:nth-child(4) { animation-delay: 2s; }
&:nth-child(5) { animation-delay: 2.5s; }
&:nth-child(6) { animation-delay: 3s; }
&:nth-child(7) { animation-delay: 3.5s; }
}
}
#cardsplayed {
display: flex;
flex-direction: row;
height: 10%;
min-height: 10%
}
#playedcardplayer {
display: flex;
flex-direction: column;
justify-content: flex-end;
}
#playedcardplayer p {
font-size: 12px;
height: 4%;
}
#playedcardplayer img {
height: 90%;
}
#firstCard {
display: flex;
flex-direction: row;
height: 20%;
width: 100%;
justify-content: space-between;
}
#firstCardObject {
display: flex;
flex-direction: column;
margin-right: 4%;
}
#firstCardObject img{
height: 90%;
}
#firstCardObject p{
height: 10%;
font-size: 20px;
}
#trumpsuit {
display: flex;
flex-direction: row;
margin-left: 4%;
}
#nextPlayers {
display: flex;
flex-direction: column;
align-items: center;
height: 0;
p {
margin-top: 0;
margin-bottom: 0;
}
}
#invisible {
visibility: hidden;
}
#selecttrumpsuit {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
}
#rules {
color: @color;
font-size: 1.5em;
font-family: Arial, serif;
}

View File

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

View File

@@ -1,9 +1,19 @@
package controllers
import javax.inject._
import play.api._
import play.api.mvc._
import com.google.inject.{Guice, Injector}
import controllers.sessions.AdvancedSession
import de.knockoutwhist.KnockOutWhist
import de.knockoutwhist.components.Configuration
import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak}
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
import di.KnockOutWebConfigurationModule
import play.api.mvc.*
import play.api.*
import play.twirl.api.Html
import java.util.UUID
import javax.inject.*
/**
* This controller creates an `Action` to handle HTTP requests to the
@@ -13,6 +23,7 @@ import de.knockoutwhist.KnockOutWhist
class HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController {
private var initial = false
private val injector: Injector = Guice.createInjector(KnockOutWebConfigurationModule())
/**
* Create an Action to render an HTML page.
@@ -24,18 +35,59 @@ class HomeController @Inject()(val controllerComponents: ControllerComponents) e
def index(): Action[AnyContent] = {
if (!initial) {
initial = true
KnockOutWhist.main(new Array[String](_length = 0))
KnockOutWhist.entry(injector.getInstance(classOf[Configuration]))
}
Action { implicit request: Request[AnyContent] => {
Ok(views.html.main.apply("KnockoutWhist")(views.html.))
Action { implicit request =>
Redirect("/sessions")
}
}
def rules(): Action[AnyContent] = {
Action { implicit request =>
Ok(views.html.rules.apply())
}
def ingame(): Action[AnyContent] = {
Action { implicit request: Request[AnyContent] => {
Ok(views.html.tui.apply())
}
def sessions(): Action[AnyContent] = {
Action { implicit request =>
Ok(views.html.sessions.apply(PodGameManager.listSessions()))
}
}
def ingame(id: String): Action[AnyContent] = {
val uuid: UUID = UUID.fromString(id)
if (PodGameManager.identify(uuid).isEmpty) {
return Action { implicit request =>
NotFound(views.html.tui.apply(List(Html(s"<p>Session with id $id not found!</p>"))))
}
} else {
val session = PodGameManager.identify(uuid).get
val player = session.asInstanceOf[AdvancedSession].player
val logic = WebUI.logic.get.asInstanceOf[BaseGameLogic]
if (logic.getCurrentState == Lobby) {
} else if (logic.getCurrentState == InGame) {
return Action { implicit request =>
Ok(views.html.ingame.apply(player, logic))
}
} else if (logic.getCurrentState == SelectTrump) {
return Action { implicit request =>
Ok(views.html.selecttrump.apply(player, logic))
}
} else if (logic.getCurrentState == TieBreak) {
return Action { implicit request =>
Ok(views.html.tie.apply(player, logic))
}
}
}
Action { implicit request =>
InternalServerError("Oops")
}
//if (logic.getCurrentState == Lobby) {
//Action { implicit request =>
//Ok(views.html.tui.apply(player, logic))
//}
//} else {
//Action { implicit request =>
//Ok(views.html.tui.apply(player, logic))
//}
}
}

View File

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

View File

@@ -1,12 +1,49 @@
package controllers
import controllers.sessions.AdvancedSession
import de.knockoutwhist.cards.{Card, CardValue, Hand, Suit}
import de.knockoutwhist.control.GameLogic
import de.knockoutwhist.control.GameState.{InGame, Lobby}
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
import de.knockoutwhist.events.*
import de.knockoutwhist.events.global.GameStateChangeEvent
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}
object WebUI extends UI {
object WebUI extends CustomThread with EventListener with UI {
override def initial: Boolean = true
setName("WebUI")
var init = false
var logic: Option[GameLogic] = None
var latestOutput: String = ""
override def instance: CustomThread = WebUI
override def listen(event: SimpleEvent): Unit = {
event match {
case event: GameStateChangeEvent =>
if (event.oldState == Lobby && event.newState == InGame) {
val match1: Option[Match] = logic.get.asInstanceOf[BaseGameLogic].getCurrentMatch
val players: List[AbstractPlayer] = match1.get.totalplayers
players.map(player => PodGameManager.addSession(AdvancedSession(player.id, player)))
}
case _ =>
}
}
override def initial(gameLogic: GameLogic): Boolean = {
if (init) {
return false
}
init = true
this.logic = Some(gameLogic)
start()
true
}
}

View File

@@ -0,0 +1,14 @@
package controllers.sessions
import de.knockoutwhist.player.AbstractPlayer
import de.knockoutwhist.utils.events.SimpleEvent
import java.util.UUID
case class AdvancedSession(id: UUID, player: AbstractPlayer) extends PlayerSession {
def name: String = player.name
override def updatePlayer(event: SimpleEvent): Unit = {
}
}

View File

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

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.*
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,5 +1,3 @@
@(output: String)
@main("Welcome to Play") {
<h1>Welcome to Play!</h1>
}

View File

@@ -0,0 +1,50 @@
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic)
@main("Ingame") {
<div id="ingame">
<h1>Knockout Whist</h1>
<div id="nextPlayers">
<p>Next Player:</p>
<p>@logic.getPlayerQueue.get.duplicate().nextPlayer()</p>
</div>
<div id="firstCard">
<div id="trumpsuit">
<p>Trumpsuit: </p>
<p>@logic.getCurrentRound.get.trumpSuit</p>
</div>
<div id="firstCardObject">
<p>First Card</p>
@if(logic.getCurrentTrick.get.firstCard.isDefined) {
@util.WebUIUtils.cardtoImage(logic.getCurrentTrick.get.firstCard.get)
} else {
@views.html.output.card.apply("images/cards/1B.png")("Blank Card")
}
</div>
</div>
<p>@logic.getCurrentPlayer.get has to play a card!</p>
@if(logic.getCurrentTrick.get.cards.nonEmpty) {
<p>Cards played</p>
} else {
<p id="invisible">Cards played</p>
}
<div id="cardsplayed">
@for((cardplayed, player) <- logic.getCurrentTrick.get.cards) {
<div id="playedcardplayer">
<p>@player</p>
@util.WebUIUtils.cardtoImage(cardplayed)
</div>
}
</div>
<p>Your cards</p>
<div id="playercards">
@for(card <- player.currentHand().get.cards) {
@util.WebUIUtils.cardtoImage(card)
}
</div>
</div>
}

View File

@@ -0,0 +1,25 @@
@*
* 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>
<html lang="en">
<head>
@* Here's where we render the page title `String`. *@
<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>
<body>
@* And here's where we render the `Html` object containing
* the page content. *@
@content
<script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
</body>
</html>

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,63 @@
@()
@main("Rules") {
<div id="rules">
<table>
<caption>Rules Overview and Equipment</caption>
<thead>
<tr>
<th>Section</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<tr>
<td>Players</td>
<td>Two to seven players. The aim is to be the last player left in the game.</td>
</tr>
<tr>
<td>Aim</td>
<td>To be the last player left in at the end of the game, with the object in each hand being to win a majority of tricks.</td>
</tr>
<tr>
<td>Equipment</td>
<td>A standard 52-card pack is used.</td>
</tr>
<tr>
<td>Card Ranks</td>
<td>In each suit, cards rank from highest to lowest: A K Q J 10 9 8 7 6 5 4 3 2.</td>
</tr>
<tr>
<td>Deal (First Hand)</td>
<td>The dealer deals seven cards to each player. One card is turned up to determine the trump suit for the round.</td>
</tr>
<tr>
<td>Deal (Subsequent Hands)</td>
<td>The deal rotates clockwise. The player who took the most tricks in the previous hand selects the trump suit. If there's a tie for the highest number of tricks, players cut cards to decide who calls trumps. One fewer card is dealt in each successive hand until the final hand consists of one card each.</td>
</tr>
<tr>
<td>Play</td>
<td>The player to the dealer's left (eldest hand) leads the first trick. Any card can be led. Other players must follow suit if possible. A player with no cards of the suit led may play any card.</td>
</tr>
<tr>
<td>Winning a Trick</td>
<td>The highest card of the suit led wins, unless a trump is played, in which case the highest trump wins. The winner of the trick leads the next.</td>
</tr>
<tr>
<td>Leading Trumps</td>
<td>Some rules disallow leading trumps before the suit has been 'broken' (a trump has been played to the lead of another suit). Leading trumps is always permissible if a player holds only trumps.</td>
</tr>
<tr>
<td>Knockout</td>
<td>At the end of each hand, any player who took no tricks is knocked out and takes no further part in the game.</td>
</tr>
<tr>
<td>Winning the Game</td>
<td>The game is won when a player takes all the tricks in a round, as all other players are knocked out, leaving only one player remaining.</td>
</tr>
</tbody>
<td>Dog Life</td>
<td>The first player who takes no tricks is awarded a "dog's life". In the next hand, that player is dealt one card and can decide which trick to play it to. Each time a trick is played the "dog" may either play the card or knock on the table and wait to play it later. If the dog wins a trick, the player to the left leads to the next and the dog re-enters the game properly in the next hand. If the dog fails, they are knocked out.</td>
</table>
</div>
}

View File

@@ -0,0 +1,27 @@
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic)
@main("Selecting Trumpsuit...") {
<div id="selecttrumpsuit">
@if(player.equals(logic.getCurrentMatch.get.roundlist.last.winner.get)) {
<h1>Knockout Whist</h1>
<p>You (@player.toString) have won the last round! Select a trumpsuit for the next round!</p>
<p>Available trumpsuits are displayed below:</p>
<div id="playercards">
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Spades))
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Clubs))
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Hearts))
@util.WebUIUtils.cardtoImage(de.knockoutwhist.cards.Card(de.knockoutwhist.cards.CardValue.Ace, de.knockoutwhist.cards.Suit.Diamonds))
</div>
<p>Your cards</p>
<div id="playercards">
@for(card <- player.currentHand().get.cards) {
@util.WebUIUtils.cardtoImage(card)
}
</div>
} else {
<h1>Knockout Whist</h1>
<p>@player.toString is choosing a trumpsuit. Starting new round when @player.toString picked a trumpsuit...</p>
}
</div>
}

View File

@@ -0,0 +1,12 @@
@(sessions: List[controllers.sessions.PlayerSession])
@main("Sessions") {
<div id="sessions">
<h1>Knockout Whist sessions</h1>
<p id="textanimation">Please select your session to jump inside the game!</p>
@for(session <- sessions) {
<a id="textanimation" href="@routes.HomeController.ingame(session.id.toString)">@session.name</a><br>
}
</div>
}

View File

@@ -0,0 +1,27 @@
@(player: de.knockoutwhist.player.AbstractPlayer, logic: de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic)
@main("Tie") {
<div id="tie">
<h1>Knockout Whist</h1>
<p>The last Round was tied between
@for(players <- logic.playerTieLogic.getTiedPlayers) {
@players
}
</p>
@if(player.equals(logic.playerTieLogic.currentTiePlayer())) {
<p>Pick a card between 1 and @logic.playerTieLogic.highestAllowedNumber()! The resulting card will be your card for the cut.</p>
} else {
<p>@logic.playerTieLogic.currentTiePlayer() is currently picking his number for the cut.</p>
<p>Currently picked Cards:</p>
<div id="cardsplayed">
@for((player, card) <- logic.playerTieLogic.getSelectedCard) {
<div id="playedcardplayer">
<p>@player</p>
@util.WebUIUtils.cardtoImage(card)
</div>
}
</div>
}
</div>
}

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

View File

@@ -1,9 +1,9 @@
package controllers
import org.scalatestplus.play._
import org.scalatestplus.play.guice._
import play.api.test._
import play.api.test.Helpers._
import org.scalatestplus.play.*
import org.scalatestplus.play.guice.*
import play.api.test.*
import play.api.test.Helpers.*
/**
* Add your spec here.

View File

@@ -4,3 +4,5 @@ addSbtPlugin("org.foundweekends.giter8" % "sbt-giter8-scaffold" % "0.18.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.2.1")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.0")
addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.3.1")
addSbtPlugin("com.typesafe.sbt" % "sbt-less" % "1.1.2")