feat: Gui added (not done, not reviewed)
@@ -0,0 +1,76 @@
|
|||||||
|
plugins {
|
||||||
|
id("scala")
|
||||||
|
id("org.scoverage") version "8.1"
|
||||||
|
application
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "de.nowchess"
|
||||||
|
version = "1.0-SNAPSHOT"
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val versions = rootProject.extra["VERSIONS"] as Map<String, String>
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
scala {
|
||||||
|
scalaVersion = versions["SCALA3"]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
scoverage {
|
||||||
|
scoverageVersion.set(versions["SCOVERAGE"]!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("org.scala-lang:scala3-compiler_3") {
|
||||||
|
version {
|
||||||
|
strictly(versions["SCALA3"]!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
implementation("org.scala-lang:scala3-library_3") {
|
||||||
|
version {
|
||||||
|
strictly(versions["SCALA3"]!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implementation(project(":modules:core"))
|
||||||
|
implementation(project(":modules:api"))
|
||||||
|
|
||||||
|
// ScalaFX dependencies
|
||||||
|
implementation("org.scalafx:scalafx_3:21.0.0-R32")
|
||||||
|
|
||||||
|
// JavaFX dependencies for the current platform
|
||||||
|
val javaFXVersion = "21.0.1"
|
||||||
|
val osName = System.getProperty("os.name").lowercase()
|
||||||
|
val platform = when {
|
||||||
|
osName.contains("win") -> "win"
|
||||||
|
osName.contains("mac") -> "mac"
|
||||||
|
osName.contains("linux") -> "linux"
|
||||||
|
else -> "linux"
|
||||||
|
}
|
||||||
|
|
||||||
|
listOf("base", "controls", "graphics", "media").forEach { module ->
|
||||||
|
implementation("org.openjfx:javafx-$module:$javaFXVersion:$platform")
|
||||||
|
}
|
||||||
|
|
||||||
|
testImplementation(platform("org.junit:junit-bom:5.13.4"))
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
|
testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}")
|
||||||
|
testImplementation("co.helmethair:scalatest-junit-runner:${versions["SCALATEST_JUNIT"]!!}")
|
||||||
|
|
||||||
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.test {
|
||||||
|
useJUnitPlatform {
|
||||||
|
includeEngines("scalatest")
|
||||||
|
testLogging {
|
||||||
|
events("passed", "skipped", "failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalizedBy(tasks.reportScoverage)
|
||||||
|
}
|
||||||
|
tasks.reportScoverage {
|
||||||
|
dependsOn(tasks.test)
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 161 B |
|
After Width: | Height: | Size: 188 B |
|
After Width: | Height: | Size: 188 B |
|
After Width: | Height: | Size: 286 B |
|
After Width: | Height: | Size: 245 B |
|
After Width: | Height: | Size: 266 B |
|
After Width: | Height: | Size: 297 B |
|
After Width: | Height: | Size: 258 B |
|
After Width: | Height: | Size: 263 B |
|
After Width: | Height: | Size: 313 B |
|
After Width: | Height: | Size: 251 B |
|
After Width: | Height: | Size: 275 B |
|
After Width: | Height: | Size: 305 B |
|
After Width: | Height: | Size: 281 B |
|
After Width: | Height: | Size: 280 B |
@@ -0,0 +1,30 @@
|
|||||||
|
/* Arabian Chess GUI Styles */
|
||||||
|
|
||||||
|
.root {
|
||||||
|
-fx-font-family: "Comic Sans MS", "Comic Sans", cursive;
|
||||||
|
-fx-background-color: #F3C8A0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
-fx-background-radius: 8;
|
||||||
|
-fx-padding: 8 16 8 16;
|
||||||
|
-fx-font-family: "Comic Sans MS", cursive;
|
||||||
|
-fx-font-size: 12px;
|
||||||
|
-fx-cursor: hand;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover {
|
||||||
|
-fx-opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
-fx-font-family: "Comic Sans MS", cursive;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-pane {
|
||||||
|
-fx-background-color: #F3C8A0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-pane .content {
|
||||||
|
-fx-font-family: "Comic Sans MS", cursive;
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
package de.nowchess.gui
|
||||||
|
|
||||||
|
import scalafx.Includes.*
|
||||||
|
import scalafx.application.Platform
|
||||||
|
import scalafx.geometry.{Insets, Pos}
|
||||||
|
import scalafx.scene.control.{Button, ButtonType, ChoiceDialog, Label}
|
||||||
|
import scalafx.scene.layout.{BorderPane, GridPane, HBox, VBox, StackPane}
|
||||||
|
import scalafx.scene.paint.Color as FXColor
|
||||||
|
import scalafx.scene.shape.Rectangle
|
||||||
|
import scalafx.scene.text.{Font, Text}
|
||||||
|
import scalafx.stage.Stage
|
||||||
|
import de.nowchess.api.board.{Board, Color, Piece, PieceType, Square, File, Rank}
|
||||||
|
import de.nowchess.api.move.PromotionPiece
|
||||||
|
import de.nowchess.chess.engine.GameEngine
|
||||||
|
|
||||||
|
/** ScalaFX chess board view that displays the game state.
|
||||||
|
* Uses Arabian chess sprites and color palette.
|
||||||
|
* Handles user interactions (clicks) and sends moves to GameEngine.
|
||||||
|
*/
|
||||||
|
class ChessBoardView(val stage: Stage, private val engine: GameEngine) extends BorderPane:
|
||||||
|
|
||||||
|
private val squareSize = 70.0
|
||||||
|
private val boardGrid = new GridPane()
|
||||||
|
private val messageLabel = new Label {
|
||||||
|
text = "Welcome to Arabian Chess!"
|
||||||
|
font = Font.font("Comic Sans MS", 16)
|
||||||
|
padding = Insets(10)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var currentBoard: Board = engine.board
|
||||||
|
private var currentTurn: Color = engine.turn
|
||||||
|
private var selectedSquare: Option[Square] = None
|
||||||
|
private val squareViews = scala.collection.mutable.Map[(Int, Int), StackPane]()
|
||||||
|
|
||||||
|
// Initialize UI
|
||||||
|
initializeBoard()
|
||||||
|
|
||||||
|
top = new VBox {
|
||||||
|
padding = Insets(10)
|
||||||
|
spacing = 5
|
||||||
|
alignment = Pos.Center
|
||||||
|
children = Seq(
|
||||||
|
new Label {
|
||||||
|
text = "Arabian Chess"
|
||||||
|
font = Font.font("Comic Sans MS", 24)
|
||||||
|
style = "-fx-font-weight: bold;"
|
||||||
|
},
|
||||||
|
messageLabel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
center = new VBox {
|
||||||
|
padding = Insets(20)
|
||||||
|
alignment = Pos.Center
|
||||||
|
style = s"-fx-background-color: ${PieceSprites.SquareColors.Border};"
|
||||||
|
children = boardGrid
|
||||||
|
}
|
||||||
|
|
||||||
|
bottom = new HBox {
|
||||||
|
padding = Insets(10)
|
||||||
|
spacing = 10
|
||||||
|
alignment = Pos.Center
|
||||||
|
children = Seq(
|
||||||
|
new Button("Undo") {
|
||||||
|
font = Font.font("Comic Sans MS", 12)
|
||||||
|
onAction = _ => if engine.canUndo then engine.undo()
|
||||||
|
style = "-fx-background-radius: 8; -fx-background-color: #B9DAD1;"
|
||||||
|
},
|
||||||
|
new Button("Redo") {
|
||||||
|
font = Font.font("Comic Sans MS", 12)
|
||||||
|
onAction = _ => if engine.canRedo then engine.redo()
|
||||||
|
style = "-fx-background-radius: 8; -fx-background-color: #B9C2DA;"
|
||||||
|
},
|
||||||
|
new Button("Reset") {
|
||||||
|
font = Font.font("Comic Sans MS", 12)
|
||||||
|
onAction = _ => engine.reset()
|
||||||
|
style = "-fx-background-radius: 8; -fx-background-color: #E1EAA9;"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def initializeBoard(): Unit =
|
||||||
|
boardGrid.padding = Insets(5)
|
||||||
|
boardGrid.hgap = 0
|
||||||
|
boardGrid.vgap = 0
|
||||||
|
|
||||||
|
// Create 8x8 board with rank/file labels
|
||||||
|
for
|
||||||
|
rank <- 0 until 8
|
||||||
|
file <- 0 until 8
|
||||||
|
do
|
||||||
|
val square = createSquare(rank, file)
|
||||||
|
squareViews((rank, file)) = square
|
||||||
|
boardGrid.add(square, file, 7 - rank) // Flip rank for proper display
|
||||||
|
|
||||||
|
updateBoard(currentBoard, currentTurn)
|
||||||
|
|
||||||
|
private def createSquare(rank: Int, file: Int): StackPane =
|
||||||
|
val isWhite = (rank + file) % 2 == 0
|
||||||
|
val baseColor = if isWhite then PieceSprites.SquareColors.White else PieceSprites.SquareColors.Black
|
||||||
|
|
||||||
|
val bgRect = new Rectangle {
|
||||||
|
width = squareSize
|
||||||
|
height = squareSize
|
||||||
|
fill = FXColor.web(baseColor)
|
||||||
|
arcWidth = 8
|
||||||
|
arcHeight = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
val square = new StackPane {
|
||||||
|
children = Seq(bgRect)
|
||||||
|
onMouseClicked = _ => handleSquareClick(rank, file)
|
||||||
|
style = "-fx-cursor: hand;"
|
||||||
|
}
|
||||||
|
|
||||||
|
square
|
||||||
|
|
||||||
|
private def handleSquareClick(rank: Int, file: Int): Unit =
|
||||||
|
if engine.isPendingPromotion then
|
||||||
|
return // Don't allow moves during promotion
|
||||||
|
|
||||||
|
val clickedSquare = Square(File.values(file), Rank.values(rank))
|
||||||
|
|
||||||
|
selectedSquare match
|
||||||
|
case None =>
|
||||||
|
// First click - select piece if it belongs to current player
|
||||||
|
currentBoard.pieceAt(clickedSquare).foreach { piece =>
|
||||||
|
if piece.color == currentTurn then
|
||||||
|
selectedSquare = Some(clickedSquare)
|
||||||
|
highlightSquare(rank, file, PieceSprites.SquareColors.Selected)
|
||||||
|
}
|
||||||
|
|
||||||
|
case Some(fromSquare) =>
|
||||||
|
// Second click - attempt move
|
||||||
|
if clickedSquare == fromSquare then
|
||||||
|
// Deselect
|
||||||
|
selectedSquare = None
|
||||||
|
updateBoard(currentBoard, currentTurn)
|
||||||
|
else
|
||||||
|
// Try to move
|
||||||
|
val moveStr = s"${fromSquare}$clickedSquare"
|
||||||
|
engine.processUserInput(moveStr)
|
||||||
|
selectedSquare = None
|
||||||
|
|
||||||
|
def updateBoard(board: Board, turn: Color): Unit =
|
||||||
|
currentBoard = board
|
||||||
|
currentTurn = turn
|
||||||
|
selectedSquare = None
|
||||||
|
|
||||||
|
// Update all squares
|
||||||
|
for
|
||||||
|
rank <- 0 until 8
|
||||||
|
file <- 0 until 8
|
||||||
|
do
|
||||||
|
squareViews.get((rank, file)).foreach { stackPane =>
|
||||||
|
val isWhite = (rank + file) % 2 == 0
|
||||||
|
val baseColor = if isWhite then PieceSprites.SquareColors.White else PieceSprites.SquareColors.Black
|
||||||
|
|
||||||
|
val bgRect = new Rectangle {
|
||||||
|
width = squareSize
|
||||||
|
height = squareSize
|
||||||
|
fill = FXColor.web(baseColor)
|
||||||
|
arcWidth = 8
|
||||||
|
arcHeight = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
val square = Square(File.values(file), Rank.values(rank))
|
||||||
|
val pieceOption = board.pieceAt(square)
|
||||||
|
|
||||||
|
val children = pieceOption match
|
||||||
|
case Some(piece) =>
|
||||||
|
Seq(bgRect, PieceSprites.loadPieceImage(piece, squareSize * 0.8))
|
||||||
|
case None =>
|
||||||
|
Seq(bgRect)
|
||||||
|
|
||||||
|
stackPane.children = children
|
||||||
|
}
|
||||||
|
|
||||||
|
private def highlightSquare(rank: Int, file: Int, color: String): Unit =
|
||||||
|
squareViews.get((rank, file)).foreach { stackPane =>
|
||||||
|
val bgRect = new Rectangle {
|
||||||
|
width = squareSize
|
||||||
|
height = squareSize
|
||||||
|
fill = FXColor.web(color)
|
||||||
|
arcWidth = 8
|
||||||
|
arcHeight = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
val square = Square(File.values(file), Rank.values(rank))
|
||||||
|
val pieceOption = currentBoard.pieceAt(square)
|
||||||
|
|
||||||
|
stackPane.children = pieceOption match
|
||||||
|
case Some(piece) =>
|
||||||
|
Seq(bgRect, PieceSprites.loadPieceImage(piece, squareSize * 0.8))
|
||||||
|
case None =>
|
||||||
|
Seq(bgRect)
|
||||||
|
}
|
||||||
|
|
||||||
|
def showMessage(msg: String): Unit =
|
||||||
|
messageLabel.text = msg
|
||||||
|
|
||||||
|
def showPromotionDialog(from: Square, to: Square): Unit =
|
||||||
|
val choices = Seq("Queen", "Rook", "Bishop", "Knight")
|
||||||
|
val dialog = new ChoiceDialog(defaultChoice = "Queen", choices = choices) {
|
||||||
|
initOwner(stage)
|
||||||
|
title = "Pawn Promotion"
|
||||||
|
headerText = "Choose promotion piece"
|
||||||
|
contentText = "Promote to:"
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = dialog.showAndWait()
|
||||||
|
result match
|
||||||
|
case Some("Queen") => engine.completePromotion(PromotionPiece.Queen)
|
||||||
|
case Some("Rook") => engine.completePromotion(PromotionPiece.Rook)
|
||||||
|
case Some("Bishop") => engine.completePromotion(PromotionPiece.Bishop)
|
||||||
|
case Some("Knight") => engine.completePromotion(PromotionPiece.Knight)
|
||||||
|
case _ => engine.completePromotion(PromotionPiece.Queen) // Default
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package de.nowchess.gui
|
||||||
|
|
||||||
|
import javafx.application.{Application => JFXApplication, Platform => JFXPlatform}
|
||||||
|
import javafx.stage.Stage as JFXStage
|
||||||
|
import scalafx.application.Platform
|
||||||
|
import scalafx.scene.Scene
|
||||||
|
import scalafx.stage.Stage
|
||||||
|
import de.nowchess.chess.engine.GameEngine
|
||||||
|
|
||||||
|
/** ScalaFX GUI Application for Arabian Chess.
|
||||||
|
* This is launched from Main alongside the TUI.
|
||||||
|
* Both subscribe to the same GameEngine via Observer pattern.
|
||||||
|
*/
|
||||||
|
class ChessGUIApp extends JFXApplication:
|
||||||
|
|
||||||
|
override def start(primaryStage: JFXStage): Unit =
|
||||||
|
val engine = ChessGUILauncher.getEngine
|
||||||
|
val stage = new Stage(primaryStage)
|
||||||
|
|
||||||
|
stage.title = "Arabian Chess"
|
||||||
|
stage.width = 700
|
||||||
|
stage.height = 800
|
||||||
|
stage.resizable = false
|
||||||
|
|
||||||
|
val boardView = new ChessBoardView(stage, engine)
|
||||||
|
val guiObserver = new GUIObserver(boardView)
|
||||||
|
|
||||||
|
// Subscribe GUI observer to engine
|
||||||
|
engine.subscribe(guiObserver)
|
||||||
|
|
||||||
|
stage.scene = new Scene {
|
||||||
|
root = boardView
|
||||||
|
// Load CSS if available
|
||||||
|
try {
|
||||||
|
val cssUrl = getClass.getResource("/styles.css")
|
||||||
|
if cssUrl != null then
|
||||||
|
stylesheets.add(cssUrl.toExternalForm)
|
||||||
|
} catch {
|
||||||
|
case _: Exception => // CSS is optional
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage.onCloseRequest = _ => {
|
||||||
|
// Unsubscribe when window closes
|
||||||
|
engine.unsubscribe(guiObserver)
|
||||||
|
}
|
||||||
|
|
||||||
|
stage.show()
|
||||||
|
|
||||||
|
/** Launcher object that holds the engine reference and launches GUI in separate thread. */
|
||||||
|
object ChessGUILauncher:
|
||||||
|
@volatile private var engine: GameEngine = scala.compiletime.uninitialized
|
||||||
|
|
||||||
|
def getEngine: GameEngine = engine
|
||||||
|
|
||||||
|
def launch(eng: GameEngine): Unit =
|
||||||
|
engine = eng
|
||||||
|
val guiThread = new Thread(() => {
|
||||||
|
JFXApplication.launch(classOf[ChessGUIApp])
|
||||||
|
})
|
||||||
|
guiThread.setDaemon(false)
|
||||||
|
guiThread.setName("ScalaFX-GUI-Thread")
|
||||||
|
guiThread.start()
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package de.nowchess.gui
|
||||||
|
|
||||||
|
import scalafx.application.Platform
|
||||||
|
import scalafx.scene.control.Alert
|
||||||
|
import scalafx.scene.control.Alert.AlertType
|
||||||
|
import de.nowchess.chess.observer.{Observer, GameEvent, *}
|
||||||
|
import de.nowchess.api.board.Board
|
||||||
|
|
||||||
|
/** GUI Observer that implements the Observer pattern.
|
||||||
|
* Receives game events from GameEngine and updates the ScalaFX UI.
|
||||||
|
* All UI updates must be done on the JavaFX Application Thread.
|
||||||
|
*/
|
||||||
|
class GUIObserver(private val boardView: ChessBoardView) extends Observer:
|
||||||
|
|
||||||
|
override def onGameEvent(event: GameEvent): Unit =
|
||||||
|
// Ensure UI updates happen on JavaFX thread
|
||||||
|
Platform.runLater {
|
||||||
|
event match
|
||||||
|
case e: MoveExecutedEvent =>
|
||||||
|
boardView.updateBoard(e.board, e.turn)
|
||||||
|
e.capturedPiece.foreach { piece =>
|
||||||
|
boardView.showMessage(s"Captured: $piece on ${e.toSquare}")
|
||||||
|
}
|
||||||
|
|
||||||
|
case e: CheckDetectedEvent =>
|
||||||
|
boardView.updateBoard(e.board, e.turn)
|
||||||
|
boardView.showMessage(s"${e.turn.label} is in check!")
|
||||||
|
|
||||||
|
case e: CheckmateEvent =>
|
||||||
|
boardView.updateBoard(e.board, e.turn)
|
||||||
|
showAlert(AlertType.Information, "Game Over", s"Checkmate! ${e.winner.label} wins.")
|
||||||
|
|
||||||
|
case e: StalemateEvent =>
|
||||||
|
boardView.updateBoard(e.board, e.turn)
|
||||||
|
showAlert(AlertType.Information, "Game Over", "Stalemate! The game is a draw.")
|
||||||
|
|
||||||
|
case e: InvalidMoveEvent =>
|
||||||
|
boardView.showMessage(s"⚠️ ${e.reason}")
|
||||||
|
|
||||||
|
case e: BoardResetEvent =>
|
||||||
|
boardView.updateBoard(e.board, e.turn)
|
||||||
|
boardView.showMessage("Board has been reset to initial position.")
|
||||||
|
|
||||||
|
case e: PromotionRequiredEvent =>
|
||||||
|
boardView.showPromotionDialog(e.from, e.to)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def showAlert(alertType: AlertType, titleText: String, content: String): Unit =
|
||||||
|
new Alert(alertType) {
|
||||||
|
initOwner(boardView.stage)
|
||||||
|
title = titleText
|
||||||
|
headerText = None
|
||||||
|
contentText = content
|
||||||
|
}.showAndWait()
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package de.nowchess.gui
|
||||||
|
|
||||||
|
import scalafx.scene.image.{Image, ImageView}
|
||||||
|
import de.nowchess.api.board.{Piece, PieceType, Color}
|
||||||
|
|
||||||
|
/** Utility object for loading Arabian chess piece sprites. */
|
||||||
|
object PieceSprites:
|
||||||
|
|
||||||
|
private val spriteCache = scala.collection.mutable.Map[String, Image]()
|
||||||
|
|
||||||
|
/** Load a piece sprite image from resources.
|
||||||
|
* Sprites are cached for performance.
|
||||||
|
*/
|
||||||
|
def loadPieceImage(piece: Piece, size: Double = 60.0): ImageView =
|
||||||
|
val key = s"${piece.color.label.toLowerCase}_${piece.pieceType.label.toLowerCase}"
|
||||||
|
val image = spriteCache.getOrElseUpdate(key, loadImage(key))
|
||||||
|
|
||||||
|
new ImageView(image) {
|
||||||
|
fitWidth = size
|
||||||
|
fitHeight = size
|
||||||
|
preserveRatio = true
|
||||||
|
smooth = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private def loadImage(key: String): Image =
|
||||||
|
val path = s"/sprites/pieces/$key.png"
|
||||||
|
val stream = getClass.getResourceAsStream(path)
|
||||||
|
if stream == null then
|
||||||
|
throw new RuntimeException(s"Could not load sprite: $path")
|
||||||
|
new Image(stream)
|
||||||
|
|
||||||
|
/** Get square colors for the board using Arabian theme. */
|
||||||
|
object SquareColors:
|
||||||
|
val White = "#F3C8A0" // Warm light beige
|
||||||
|
val Black = "#BA6D4B" // Warm terracotta
|
||||||
|
val Selected = "#C19EF5" // Purple highlight
|
||||||
|
val ValidMove = "#E1EAA9" // Light yellow-green
|
||||||
|
val Border = "#5A2C28" // Dark brown border
|
||||||
@@ -50,6 +50,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation(project(":modules:core"))
|
implementation(project(":modules:core"))
|
||||||
implementation(project(":modules:api"))
|
implementation(project(":modules:api"))
|
||||||
|
implementation(project(":modules:gui"))
|
||||||
|
|
||||||
testImplementation(platform("org.junit:junit-bom:5.13.4"))
|
testImplementation(platform("org.junit:junit-bom:5.13.4"))
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
|
|||||||
@@ -2,14 +2,20 @@ package de.nowchess.ui
|
|||||||
|
|
||||||
import de.nowchess.chess.engine.GameEngine
|
import de.nowchess.chess.engine.GameEngine
|
||||||
import de.nowchess.ui.terminal.TerminalUI
|
import de.nowchess.ui.terminal.TerminalUI
|
||||||
|
import de.nowchess.gui.ChessGUILauncher
|
||||||
|
|
||||||
/** Application entry point - starts the Terminal UI for the chess game. */
|
/** Application entry point - starts both GUI and Terminal UI for the chess game.
|
||||||
|
* Both views subscribe to the same GameEngine via Observer pattern.
|
||||||
|
*/
|
||||||
object Main:
|
object Main:
|
||||||
def main(args: Array[String]): Unit =
|
def main(args: Array[String]): Unit =
|
||||||
// Create the core game engine (single source of truth)
|
// Create the core game engine (single source of truth)
|
||||||
val engine = new GameEngine()
|
val engine = new GameEngine()
|
||||||
|
|
||||||
// Create and start the terminal UI
|
// Launch ScalaFX GUI in separate thread
|
||||||
|
ChessGUILauncher.launch(engine)
|
||||||
|
|
||||||
|
// Create and start the terminal UI (blocks on main thread)
|
||||||
val tui = new TerminalUI(engine)
|
val tui = new TerminalUI(engine)
|
||||||
tui.start()
|
tui.start()
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
rootProject.name = "NowChessSystems"
|
rootProject.name = "NowChessSystems"
|
||||||
include("modules:core", "modules:api", "modules:ui")
|
include("modules:core", "modules:api", "modules:ui", "modules:gui")
|
||||||