• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Jakob63 / WizardSE / 21119038205

18 Jan 2026 09:38PM UTC coverage: 33.439% (+33.4%) from 0.0%
21119038205

push

github

web-flow
Merge pull request #63 from Jakob63/fixing

remove debug lines and fix tests

124 of 486 branches covered (25.51%)

Branch coverage included in aggregate %.

509 of 1407 relevant lines covered (36.18%)

0.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
src/main/scala/wizard/aView/aView_GUI/WizardGUI.scala
1
package wizard.aView.aView_GUI
2

3
import scalafx.application.JFXApp3
4
import scalafx.geometry.{Insets, Pos}
5
import scalafx.scene.Scene
6
import scalafx.scene.control.{Button, Label, TextField, Tooltip, TableView, TableColumn}
7
import scalafx.beans.property.{StringProperty, IntegerProperty}
8
import scalafx.collections.ObservableBuffer
9
import scalafx.scene.image.{Image, ImageView}
10
import scalafx.scene.layout.{VBox, StackPane, HBox, BorderPane}
11
import scalafx.scene.layout.StackPane.setAlignment
12
import scalafx.scene.text.{Font, FontWeight}
13
import scalafx.Includes._
14
import scalafx.application.Platform
15
import javafx.beans.binding.Bindings
16
import wizard.controller.{GameLogic, PlayerSnapshot}
17
import wizard.actionmanagement.{Observer, InputRouter}
18
import wizard.model.player.{PlayerFactory, PlayerType, Player}
19
import wizard.model.cards.{Card, Color, Value}
20
import scalafx.scene.Node
21
import java.util.NoSuchElementException
22

23
class WizardGUI(val gameController: GameLogic) extends JFXApp3 with Observer {
24
  
25
  private var rootPane: Option[StackPane] = None
26
  private var undoRedoBar: Option[HBox] = None
27
  private var pendingPlayerCount: Option[Int] = None
28
  private var selectedPlayerCount: Option[Int] = None
29
  private var contentBox: Option[VBox] = None
30
  private var currentScreen: String = "PlayerCount"
31
  private var navEpoch: Int = 0
32
  private var gameRoot: Option[BorderPane] = None
33
  private var trumpView: Option[ImageView] = None
34
  private var handBar: Option[HBox] = None
35
  private var bidOverlay: Option[HBox] = None
36
  private var currentPlayers: List[Player] = Nil
37
  private var scoresTable: Option[TableView[PlayerRow]] = None
38
  private var activePlayerName: Option[String] = None
39
  private var trickBar: Option[HBox] = None
40
  @volatile private var currentTrickCards: List[Card] = Nil
41

42
  case class PlayerRow(nameProp: String, bidProp: String, pointsProp: String) {
43
    val name = new StringProperty(this, "name", nameProp)
×
44
    val bid = new StringProperty(this, "bid", bidProp)
×
45
    val points = new StringProperty(this, "points", pointsProp)
×
46
  }
47
  private val inputFieldStyle: String = "-fx-control-inner-background: #2B2B2B; -fx-text-fill: white; -fx-prompt-text-fill: rgba(255,255,255,0.6);"
48
  private val buttonStyle: String = "-fx-background-color: #2B2B2B; -fx-text-fill: white;"
49
  gameController.add(this)
×
50

51
  private def hideUndoRedoBar(): Unit = {
×
52
    (for { rp <- rootPane; bar <- undoRedoBar } yield (rp, bar)) foreach { case (rp, bar) =>
×
53
      Platform.runLater {
×
54
        try { rp.children.remove(bar) } catch { case _: Throwable => () }
×
55
      }
56
    }
57
    undoRedoBar = None
58
  }
59
  private def createUndoRedoBar(): HBox = {
×
60
    import wizard.undo.UndoService
61
    val undoBtn = new Button("↶") {
62
      tooltip = Tooltip("Undo")
×
63
      style = buttonStyle + "; -fx-font-size: 10px; -fx-padding: 2 5 2 5;"
×
64
    }
65
    val redoBtn = new Button("↷") {
66
      tooltip = Tooltip("Redo")
×
67
      style = buttonStyle + "; -fx-font-size: 10px; -fx-padding: 2 5 2 5;"
×
68
    }
69
    val saveBtn = new Button("Save Game") {
70
      style = buttonStyle + "; -fx-font-size: 10px; -fx-padding: 2 5 2 5;"
×
71
      onAction = _ => showSaveDialog()
×
72
    }
73
    undoBtn.onAction = _ => {
×
74
      val t = new Thread(() => {
×
75
          try {
×
76
            if (currentScreen == "PlayerNames") {
×
77
              try { gameController.resetPlayerCountSelection() } catch { case _: Throwable => () }
×
78
            } else {
×
79
              gameController.undo()
×
80
            }
81
          } catch {
82
            case _: Throwable => ()
×
83
          }
84
      })
85
      t.setDaemon(true); t.start()
×
86
    }
87
    redoBtn.onAction = _ => {
×
88
      val t = new Thread(() => try { gameController.redo() } catch { case _: Throwable => () })
×
89
      t.setDaemon(true); t.start()
×
90
    }
91
    new HBox(6) { alignment = Pos.TopLeft; padding = Insets(8); children = Seq(undoBtn, redoBtn, saveBtn) }
×
92
  }
93
  private def ensureUndoRedoBarVisible(): Unit = {
×
94
    if (undoRedoBar.isEmpty) {
×
95
      val bar = createUndoRedoBar()
×
96
      try { bar.pickOnBounds = false } catch { case _: Throwable => () }
×
97
      undoRedoBar = Some(bar)
98
    }
×
99
    
100
    undoRedoBar.foreach { bar =>
×
101
      rootPane.foreach { rp =>
×
102
        Platform.runLater {
×
103
          val kids = rp.children
×
104
          if (!kids.contains(bar)) {
×
105
            try {
×
106
              val oldParent = bar.delegate.getParent
×
107
              if (oldParent != null && oldParent.isInstanceOf[javafx.scene.layout.Pane]) {
×
108
                oldParent.asInstanceOf[javafx.scene.layout.Pane].getChildren.remove(bar.delegate)
×
109
              }
×
110
            } catch { case _: Throwable => () }
×
111
            
112
            rp.children.add(bar)
×
113
            setAlignment(bar, Pos.TopLeft)
×
114
          }
×
115
          bar.toFront()
×
116
        }
117
      }
118
    }
119
  }
120

121
  private def goBackToPlayerCountLocal(): Unit = {
×
122
    navEpoch += 1
123
    val thisEpoch = navEpoch
124
    Platform.runLater {
×
125
      currentScreen = "PlayerCount"
126
      contentBox match {
127
        case Some(box) =>
×
128
          box.children = buildPlayerCountChildren(box)
×
129
        case None =>
×
130
          if (stage != null && stage.scene() != null) {
×
131
            stage.scene().root = createInitialScreen()
×
132
          }
×
133
      }
134
    }
135
  }
136

137
  private def showSaveDialog(): Unit = {
×
138
    Platform.runLater {
×
139
      var saveBoxRef: VBox = null
140
      saveBoxRef = new VBox(10) {
141
        alignment = Pos.Center
×
142
        padding = Insets(20)
×
143
        style = "-fx-background-color: rgba(0,0,0,0.8); -fx-background-radius: 10;"
×
144
        maxWidth = 300
×
145
        maxHeight = 200
×
146
        children = Seq(
×
147
          new Label("Game title") { style = "-fx-text-fill: white; -fx-font-size: 16px;" },
×
148
          new TextField {
×
149
            id = "saveTitleField"
×
150
            promptText = "Enter title..."
×
151
            style = inputFieldStyle
×
152
            onAction = _ => {
×
153
              val title = this.text.value
×
154
              if (title.nonEmpty) {
×
155
                gameController.save(title)
×
156
                rootPane.foreach(_.children.remove(saveBoxRef))
×
157
              }
×
158
            }
159
          },
160
          new HBox(10) {
161
            alignment = Pos.Center
×
162
            children = Seq(
×
163
              new Button("Save") {
164
                style = buttonStyle
×
165
                onAction = _ => {
×
166
                  val field = saveBoxRef.lookup("#saveTitleField")
×
167
                  val title = (field: Any) match {
168
                    case tf: javafx.scene.control.TextField => tf.getText
×
169
                    case sn: scalafx.scene.Node if sn.delegate.isInstanceOf[javafx.scene.control.TextField] => 
×
170
                      sn.delegate.asInstanceOf[javafx.scene.control.TextField].getText
×
171
                    case _ => ""
×
172
                  }
173
                  if (title != null && title.nonEmpty) {
×
174
                    gameController.save(title)
×
175
                    rootPane.foreach(_.children.remove(saveBoxRef))
×
176
                  }
×
177
                }
178
              },
179
              new Button("Cancel") {
180
                style = buttonStyle
×
181
                onAction = _ => rootPane.foreach(_.children.remove(saveBoxRef))
×
182
              }
183
            )
184
          }
185
        )
186
      }
187
      rootPane.foreach(_.children.add(saveBoxRef))
×
188
    }
189
  }
190

191
  override def start(): Unit = {
×
192
    stage = new JFXApp3.PrimaryStage {
193
      title = "Wizard Card Game"
×
194
      width = 600
×
195
      height = 400
×
196
      scene = new Scene {
×
197
        root = createInitialScreen()
×
198
      }
199
    }
200
    gameController.add(this)
×
201
    val controllerThread = new Thread(() => gameController.start())
×
202
    controllerThread.setDaemon(true)
×
203
    controllerThread.start()
×
204
    pendingPlayerCount.foreach { cnt =>
×
205
      Platform.runLater {
×
206
        val stageReady = stage != null && stage.scene() != null
×
207
        if (stageReady) {
×
208
          contentBox match {
209
            case Some(box) =>
×
210
              box.children = createPlayerNameScreen(cnt)
×
211
            case None =>
×
212
              stage.scene().root = createPlayerNameRoot(cnt)
×
213
          }
214
          pendingPlayerCount = None
215
        }
×
216
      }
217
    }
218
  }
219

220
  private def buildPlayerCountChildren(ui: VBox): List[scalafx.scene.Node] = {
×
221
    val titleLabel = new Label("Willkommen bei Wizard") {
222
      font = Font.font(null, FontWeight.Bold, 24)
×
223
      style = "-fx-text-fill: #39FF14;"
×
224
    }
225

226
    val playerCountLabel = new Label("Spieleranzahl (3-6)") {
227
      font = Font.font(20)
×
228
      style = "-fx-text-fill: black;"
×
229
      translateY = -12
×
230
    }
231

232
    val playerCountField = new TextField() {
×
233
      alignment = Pos.Center
×
234
      style = inputFieldStyle
×
235
    }
236

237
    val nextButton = new Button("Weiter") {
238
      style = buttonStyle
×
239
    }
240

241
    playerCountField.prefWidth <== ui.width * 0.3
×
242
    nextButton.prefWidth       <== ui.width * 0.3
×
243
    playerCountField.maxWidth  <== playerCountField.prefWidth
×
244
    nextButton.maxWidth        <== nextButton.prefWidth
×
245

246
    titleLabel.style <== Bindings.createStringBinding(
×
247
      () => s"-fx-text-fill: #39FF14; -fx-font-weight: bold; -fx-font-size: ${ui.width.value / 25}px;",
×
248
      ui.width
×
249
    )
250
    playerCountLabel.style <== Bindings.createStringBinding(
×
251
      () => s"-fx-text-fill: black; -fx-font-size: ${ui.width.value / 35}px;",
×
252
      ui.width
×
253
    )
254
    playerCountField.style <== Bindings.createStringBinding(
×
255
      () => s"$inputFieldStyle -fx-font-size: ${ui.width.value / 40}px;",
×
256
      ui.width
×
257
    )
258
    nextButton.style <== Bindings.createStringBinding(
×
259
      () => s"$buttonStyle -fx-font-size: ${ui.width.value / 40}px;",
×
260
      ui.width
×
261
    )
262

263
    nextButton.onAction = _ => {
×
264
      val playerCount = playerCountField.text.value
×
265
      if (playerCount.matches("[3-6]")) {
×
266
        val t = new Thread(() => gameController.playerCountSelected(playerCount.toInt))
×
267
        t.setDaemon(true)
×
268
        t.start()
×
269
        ui.children = createPlayerNameScreen(playerCount.toInt)
×
270
        currentScreen = "PlayerNames"
271
        ensureUndoRedoBarVisible()
×
272
      }
×
273
    }
274

275
    val resumeButton = new Button("Resume Game") {
276
      style = buttonStyle
×
277
      onAction = _ => showResumeDialog()
×
278
    }
279

280
    List(titleLabel, playerCountLabel, playerCountField, nextButton, resumeButton)
×
281
  }
282

283
  private def showResumeDialog(): Unit = {
×
284
    Platform.runLater {
×
285
      var resumeBoxRef: VBox = null
286
      resumeBoxRef = new VBox(10) {
287
        alignment = Pos.Center
×
288
        padding = Insets(20)
×
289
        style = "-fx-background-color: rgba(0,0,0,0.8); -fx-background-radius: 10;"
×
290
        maxWidth = 300
×
291
        maxHeight = 200
×
292
        children = Seq(
×
293
          new Label("Resume Game") { style = "-fx-text-fill: white; -fx-font-size: 16px;" },
×
294
          new Label("Enter save name:") { style = "-fx-text-fill: white;" },
×
295
          new TextField {
×
296
            id = "resumeTitleField"
×
297
            promptText = "Enter title..."
×
298
            style = inputFieldStyle
×
299
            onAction = _ => {
×
300
              val title = this.text.value
×
301
              if (title.nonEmpty) {
×
302
                gameController.load(title)
×
303
                rootPane.foreach(_.children.remove(resumeBoxRef))
×
304
              }
×
305
            }
306
          },
307
          new HBox(10) {
308
            alignment = Pos.Center
×
309
            children = Seq(
×
310
              new Button("Resume") {
311
                style = buttonStyle
×
312
                onAction = _ => {
×
313
                  val field = resumeBoxRef.lookup("#resumeTitleField")
×
314
                  val title = (field: Any) match {
315
                    case tf: javafx.scene.control.TextField => tf.getText
×
316
                    case sn: scalafx.scene.Node if sn.delegate.isInstanceOf[javafx.scene.control.TextField] => 
×
317
                      sn.delegate.asInstanceOf[javafx.scene.control.TextField].getText
×
318
                    case _ => ""
×
319
                  }
320
                  if (title != null && title.nonEmpty) {
×
321
                    gameController.load(title)
×
322
                    rootPane.foreach(_.children.remove(resumeBoxRef))
×
323
                  }
×
324
                }
325
              },
326
              new Button("Cancel") {
327
                style = buttonStyle
×
328
                onAction = _ => rootPane.foreach(_.children.remove(resumeBoxRef))
×
329
              }
330
            )
331
          }
332
        )
333
      }
334
      rootPane.foreach(_.children.add(resumeBoxRef))
×
335
    }
336
  }
337

338
  private def createInitialScreen(): StackPane = {
×
339
    val ui = new VBox(20) {
340
      alignment = Pos.Center
×
341
      padding = Insets(20)
×
342
    }
343
    contentBox = Some(ui)
344
    currentScreen = "PlayerCount"
345

346
    ui.children = buildPlayerCountChildren(ui)
×
347

348
    val bgRes = getClass.getResource("/images/Wizard_game_background2_GUI.png")
×
349
    val root = new StackPane
×
350
    rootPane = Some(root)
351
    if (bgRes != null) {
×
352
      val bgView = new ImageView(new Image(bgRes.toExternalForm)) { preserveRatio = false }
×
353
      root.children = Seq(bgView, ui)
×
354
      bgView.fitWidth  <== root.width
×
355
      bgView.fitHeight <== root.height
×
356
    } else {
×
357
      root.children = Seq(ui)
×
358
    }
359

360
    ui.prefWidth <== root.width * 0.8
×
361
    ui.maxWidth  <== root.width * 0.8
×
362
    ui.spacing   <== root.height * 0.03
×
363

364
    ensureUndoRedoBarVisible()
×
365

366
    root
367
  }
368

369
  private def createPlayerNameRoot(playerCount: Int): StackPane = {
×
370
    val ui = new VBox(20) {
371
      alignment = Pos.Center
×
372
      padding = Insets(20)
×
373
    }
374

375
    contentBox = Some(ui)
376
    currentScreen = "PlayerNames"
377
    ui.children = createPlayerNameScreen(playerCount)
×
378

379
    val bgRes = getClass.getResource("/images/Wizard_game_background2_GUI.png")
×
380
    val root = new StackPane
×
381
    rootPane = Some(root)
382
    if (bgRes != null) {
×
383
      val bgView = new ImageView(new Image(bgRes.toExternalForm)) { preserveRatio = false }
×
384
      root.children = Seq(bgView, ui)
×
385
      bgView.fitWidth <== root.width
×
386
      bgView.fitHeight <== root.height
×
387
    } else {
×
388
      root.children = Seq(ui)
×
389
    }
390

391
    ensureUndoRedoBarVisible()
×
392

393
    ui.prefWidth <== root.width * 0.8
×
394
    ui.maxWidth  <== root.width * 0.8
×
395
    ui.spacing   <== root.height * 0.03
×
396

397
    root
398
  }
399

400
  private def createPlayerNameScreen(playerCount: Int): List[scalafx.scene.Node] = {
×
401
    val titleLabel = new Label("Spielernamen:") {
402
      font = Font.font(null, FontWeight.Bold, 20)
×
403
    }
404

405
    val playerFields = (1 to playerCount).map { i =>
×
406
      new TextField() {
×
407
        promptText = s"Spieler $i"
×
408
        style = inputFieldStyle
×
409
      }
410
    }.toList
×
411

412
    val startButton = new Button("START") {
413
      style = buttonStyle
×
414
    }
415

416
    contentBox.foreach { box =>
×
417
      box.spacing <== box.height * 0.03
×
418
      val compWidth = box.width * 0.35
×
419
      playerFields.foreach { tf =>
×
420
        tf.prefWidth <== compWidth
×
421
        tf.maxWidth  <== tf.prefWidth
×
422
      }
423
      startButton.prefWidth <== compWidth
×
424
      startButton.maxWidth  <== startButton.prefWidth
×
425

426
      titleLabel.style <== Bindings.createStringBinding(
×
427
        () => s"-fx-font-weight: bold; -fx-font-size: ${box.width.value / 30}px;",
×
428
        box.width
×
429
      )
430
      playerFields.foreach(_.style <== Bindings.createStringBinding(
×
431
        () => s"$inputFieldStyle -fx-font-size: ${box.width.value / 40}px;",
×
432
        box.width
×
433
      ))
434
      startButton.style <== Bindings.createStringBinding(
×
435
        () => s"$buttonStyle -fx-font-size: ${box.width.value / 40}px;",
×
436
        box.width
×
437
      )
438
    }
439

440
    startButton.onAction = _ => {
×
441
      val playerNames = playerFields.map(_.text.value.trim).filter(_.nonEmpty)
×
442
      if (playerNames.length == playerCount) {
×
443
        val players = playerNames.map(name => PlayerFactory.createPlayer(Some(name), PlayerType.Human))
×
444
        gameController.setPlayers(players)
×
445
      }
×
446
    }
447

448
    List(titleLabel) ++ playerFields :+ startButton
×
449
  }
450

451
  private def updateScores(snapshot: Option[List[Any]] = None): Unit = {
×
452
    (scoresTable, Option(currentPlayers)) match {
×
453
      case (Some(table), Some(players)) =>
454
        val rows = snapshot match {
×
455
            case Some(list) =>
×
456
                list.map {
×
457
                    case p: Player =>
458
                        val displayBid = if (p.roundBids == -1) "0" else p.roundBids.toString
×
459
                        PlayerRow(p.name, displayBid, p.points.toString)
460
                    case s: PlayerSnapshot =>
461
                        val displayBid = if (s.roundBids == -1) "0" else s.roundBids.toString
×
462
                        PlayerRow(s.name, displayBid, s.points.toString)
463
                    case _ => PlayerRow("?", "?", "?")
×
464
                }
465
            case None =>
×
466
                players.map { p =>
×
467
                    val displayBid = if (p.roundBids == -1) "0" else p.roundBids.toString
×
468
                    PlayerRow(p.name, displayBid, p.points.toString)
469
                }
470
        }
471
        table.items = ObservableBuffer.from(rows)
×
472
        table.refresh()
×
473
      case _ => ()
×
474
    }
475
  }
476

477
  private def updateCurrentBids(p: Player): Unit = {
×
478
    updateScores()
×
479
  }
480

481
  private def ensureGameTableRoot(): Unit = {
×
482
    if (gameRoot.isDefined) return
×
483

484
    val trump = new ImageView() { preserveRatio = true }
×
485
    val hand = new HBox(10) { alignment = Pos.Center; padding = Insets(10) }
×
486

487
    val centerLabel = new Label("")
×
488
    val trickBox = new HBox(10) { alignment = Pos.Center }
×
489
    trickBar = Some(trickBox)
490

491
    val table = new TableView[PlayerRow]() {
×
492
      columns ++= List(
×
493
        new TableColumn[PlayerRow, String] {
×
494
          text = "Name"
×
495
          cellValueFactory = { _.value.name }
×
496
          prefWidth = 100
×
497
        },
498
        new TableColumn[PlayerRow, String] {
×
499
          text = "Bid"
×
500
          cellValueFactory = { _.value.bid }
×
501
          prefWidth = 50
×
502
        },
503
        new TableColumn[PlayerRow, String] {
×
504
          text = "Pkt"
×
505
          cellValueFactory = { _.value.points }
×
506
          prefWidth = 50
×
507
        }
508
      )
509
      prefHeight = 150
×
510
      maxWidth = 210
×
511
      columnResizePolicy = TableView.ConstrainedResizePolicy
×
512
      selectionModel().cellSelectionEnabled = false
×
513
      selectionModel().selectionMode = javafx.scene.control.SelectionMode.SINGLE
×
514
      
515
      rowFactory = { _ =>
×
516
        val row = new javafx.scene.control.TableRow[PlayerRow]()
×
517
        row.itemProperty().addListener((_, _, newItem) => {
×
518
          if (newItem != null && activePlayerName.contains(newItem.name.value)) {
×
519
            if (!row.getStyleClass.contains("current-player-row")) {
×
520
              row.getStyleClass.add("current-player-row")
×
521
            }
×
522
          } else {
×
523
            row.getStyleClass.remove("current-player-row")
×
524
          }
525
        })
526
        row
×
527
      }
528

529
      style = """
×
530
        -fx-background-color: transparent;
531
        -fx-control-inner-background: transparent;
532
        -fx-table-cell-border-color: transparent;
533
        -fx-table-header-border-color: transparent;
534
        -fx-padding: 0;
535
      """
536
    }
537
    val cssPath = getClass.getResource("/table_transparent.css")
×
538
    if (cssPath != null) {
×
539
      table.stylesheets.add(cssPath.toExternalForm)
×
540
    }
×
541
    scoresTable = Some(table)
542

543
    val tablePane = new BorderPane {
×
544
      top = new HBox { alignment = Pos.Center; padding = Insets(10); children = Seq(trump) }
×
545
      center = new StackPane { children = Seq(centerLabel, trickBox) }
×
546
      right = new VBox(20) { alignment = Pos.TopCenter; padding = Insets(-20, 10, 10, 10); children = Seq(table) }
×
547
      bottom = hand
×
548
      padding = Insets(10)
×
549
    }
550

551
    trumpView = Some(trump)
552
    handBar = Some(hand)
553

554
    currentScreen = "Game"
555

556
    val bgRes = getClass.getResource("/images/Wizard_game_background2_GUI.png")
×
557
    val root = new StackPane
×
558
    rootPane = Some(root)
559
    if (bgRes != null) {
×
560
      val bgView = new ImageView(new Image(bgRes.toExternalForm)) { preserveRatio = false }
×
561
      root.children = Seq(bgView, tablePane)
×
562
      bgView.fitWidth  <== root.width
×
563
      bgView.fitHeight <== root.height
×
564
    } else {
×
565
      root.children = Seq(tablePane)
×
566
    }
567

568
    ensureUndoRedoBarVisible()
×
569

570
    gameRoot = Some(tablePane)
571

572
    Platform.runLater {
×
573
      if (stage != null && stage.scene() != null) stage.scene().root = root
×
574
    }
575
  }
576

577
  private def setTrump(card: Card): Unit = {
×
578
    ensureGameTableRoot()
×
579
    val url = cardToImageUrl(card)
×
580
    trumpView.foreach { iv =>
×
581
      if (url != null) iv.image = new Image(url) else iv.image = null
×
582
      iv.fitHeight = 120
×
583
    }
584
  }
585

586
  private def renderHand(player: Player): Unit = {
×
587
    ensureGameTableRoot()
×
588
    val bar = handBar.get
×
589
    val images = player.hand.cards.zipWithIndex.map { case (c, idx) =>
×
590
      val iv = new ImageView()
×
591
      val url = cardToImageUrl(c)
×
592
      if (url != null) iv.image = new Image(url)
×
593
      iv.fitHeight = 140
×
594
      iv.preserveRatio = true
×
595
      iv.onMouseClicked = _ => {
×
596
        InputRouter.offer((idx + 1).toString)
×
597
        bar.children.clear()
×
598
      }
599
      iv
600
    }
601
    bar.children = images
×
602
  }
603

604
  private def renderTrick(): Unit = {
×
605
    ensureGameTableRoot()
×
606
    trickBar.foreach { bar =>
×
607
      val images = currentTrickCards.map { card =>
×
608
        val iv = new ImageView()
×
609
        val url = cardToImageUrl(card)
×
610
        if (url != null) iv.image = new Image(url)
×
611
        iv.fitHeight = 160
×
612
        iv.preserveRatio = true
×
613
        iv
614
      }
615
      bar.children = images
×
616
    }
617
  }
618

619
  private def showBidPrompt(player: Player): Unit = {
×
620
    ensureGameTableRoot()
×
621
    for { hb <- handBar; ov <- bidOverlay } yield hb.children.remove(ov)
×
622

623
    val tf = new TextField() { promptText = s"${player.name}: Stichzahl"; style = inputFieldStyle }
×
624
    val ok = new Button("OK") { style = buttonStyle }
×
625
    val overlay = new HBox(10) { alignment = Pos.Center; children = Seq(tf, ok) }
×
626
    
627
    ok.onAction = _ => {
×
628
      val text = tf.text.value
×
629
      if (text != null && text.matches("\\d+")) {
×
630
        InputRouter.offer(text)
×
631
        handBar.foreach(_.children.clear())
×
632
        bidOverlay = None
633
      }
×
634
    }
635
    handBar.foreach { hb => hb.children.insert(0, overlay) }
×
636
    bidOverlay = Some(overlay)
637
  }
638

639
  private def cardToImageUrl(card: Card): String = {
×
640
    val base = "/images/cards/"
641
    val name = card.value match {
642
      case Value.WizardKarte => "Wizard.png"
×
643
      case Value.Chester => "Jester.png"
×
644
      case v => s"${card.color.toString}_${v.cardType()}.png"
×
645
    }
646
    val res = getClass.getResource(base + name)
×
647
    if (res != null) res.toExternalForm else null
×
648
  }
649

650
  private def switchToPlayerNames(count: Int, capturedEpoch: Int): Unit = {
×
651
    if (capturedEpoch != navEpoch) {
×
652
      return
653
    }
×
654
    contentBox match {
655
      case Some(box) =>
656
        box.children = createPlayerNameScreen(count)
×
657
        currentScreen = "PlayerNames"
658
        ensureUndoRedoBarVisible()
×
659
      case None =>
×
660
        if (stage != null && stage.scene() != null) {
×
661
          stage.scene().root = createPlayerNameRoot(count)
×
662
        }
×
663
    }
664
  }
665

666
  override def update(updateMSG: String, obj: Any*): Any = {
×
667
    updateMSG match {
668
      case "AskForPlayerCount" =>
669
        navEpoch += 1
×
670
        val thisEpoch = navEpoch
671
        Platform.runLater {
×
672
          currentScreen = "PlayerCount"
673
          contentBox match {
674
            case Some(box) =>
×
675
              box.children = buildPlayerCountChildren(box)
×
676
            case None =>
×
677
              if (stage != null && stage.scene() != null) {
×
678
                stage.scene().root = createInitialScreen()
×
679
              }
×
680
          }
681
          ensureUndoRedoBarVisible()
×
682
        }
683
        ()
684
      case "AskForPlayerNames" =>
685
        Platform.runLater {
×
686
          gameRoot = None 
687
          currentScreen = "PlayerNames"
688
          val count = selectedPlayerCount.getOrElse(3)
×
689
          if (stage != null && stage.scene() != null) {
×
690
            stage.scene().root = createPlayerNameRoot(count)
×
691
          }
×
692
          ensureUndoRedoBarVisible()
×
693
        }
694
        ()
695
      case "PlayerCountSelected" =>
696
        val count = obj.headOption match {
×
697
          case Some(pcs: wizard.actionmanagement.PlayerCountSelected) => pcs.count
×
698
          case Some(i: Int) => i
×
699
          case _ => 0
×
700
        }
701
        if (count >= 3 && count <= 6) {
×
702
          selectedPlayerCount = Some(count)
703
          if (stage == null || stage.scene() == null) {
×
704
            pendingPlayerCount = Some(count)
705
          } else {
×
706
            val epoch = navEpoch
707
            Platform.runLater {
×
708
              switchToPlayerNames(count, epoch)
×
709
              ensureUndoRedoBarVisible()
×
710
            }
711
          }
712
        } else {
×
713
          ()
714
        }
715
        ()
716
      case "CardsDealt" =>
717
        obj.headOption.collect { case cd: wizard.actionmanagement.CardsDealt => cd.players }.foreach { ps => currentPlayers = ps }
×
718
        Platform.runLater({
×
719
          ensureGameTableRoot()
×
720
          updateScores()
×
721
          renderTrick()
×
722
          ensureUndoRedoBarVisible()
×
723
        })
724
        ()
725
      case "card played" =>
726
        obj.headOption.collect { case c: Card => c }.foreach { card =>
×
727
          Platform.runLater({
×
728
            if (!currentTrickCards.contains(card)) {
×
729
              currentTrickCards = currentTrickCards :+ card
×
730
            }
×
731
            renderTrick()
×
732

733
            handBar.foreach(_.children.clear())
×
734
          })
735
        }
736
        ()
737
      case "print trump card" =>
738
        obj.headOption.collect { case c: Card => c }.foreach { c => Platform.runLater(setTrump(c)) }
×
739
        ()
740
      case "ShowHand" =>
741
        val playerOpt: Option[Player] = obj.headOption match {
×
742
          case Some(sh: wizard.actionmanagement.ShowHand) => Some(sh.player)
×
743
          case Some(p: Player) => Some(p)
×
744
          case _ => None
×
745
        }
746
        playerOpt.foreach(p => Platform.runLater({
×
747
          activePlayerName = Some(p.name)
748
          updateCurrentBids(p)
×
749
          
750
          handBar.foreach { bar =>
×
751
            bar.children.clear()
×
752
            val nextBtn = new Button("Next Player: " + p.name) {
×
753
              style = buttonStyle
×
754
              onAction = _ => {
×
755
                gameController.setCanSave(false)
×
756
                renderHand(p)
×
757
              }
758
            }
759
            bar.children = Seq(nextBtn)
×
760
          }
761
        }))
762
        ()
763
      case "which card" =>
764
        obj.headOption.collect { case p: Player => p }.foreach { p => Platform.runLater({
×
765
          val targetText = "Next Player: " + p.name
×
766
          activePlayerName = Some(p.name)
767
          updateCurrentBids(p)
×
768
          
769
          handBar.foreach { bar =>
×
770
            val currentlyShowingNext = bar.children.exists(node => node.isInstanceOf[javafx.scene.control.Button] && node.asInstanceOf[javafx.scene.control.Button].getText.startsWith("Next Player"))
×
771
            val showingTarget = bar.children.exists(node => node.isInstanceOf[javafx.scene.control.Button] && node.asInstanceOf[javafx.scene.control.Button].getText == targetText)
×
772
            
773
            if (!showingTarget) {
×
774
                bar.children.clear()
×
775
                val nextBtn = new Button(targetText) {
776
                  style = buttonStyle
×
777
                  onAction = _ => {
×
778
                    gameController.setCanSave(false)
×
779
                    renderHand(p)
×
780
                  }
781
                }
782
                bar.children = Seq(nextBtn)
×
783
            } else {
×
784
            }
785
          }
786
        }) }
787
        ()
788
      case "which bid" =>
789
        obj.headOption.collect { case p: Player => p }.foreach { p =>
×
790
          Platform.runLater({
×
791
            activePlayerName = Some(p.name)
792
            updateCurrentBids(p)
×
793
            
794
            handBar.foreach { bar =>
×
795
              bar.children.clear()
×
796
              val nextBtn = new Button("Next Player: " + p.name) {
×
797
                style = buttonStyle
×
798
                onAction = _ => {
×
799
                  gameController.setCanSave(false)
×
800
                  renderHand(p)
×
801
                  showBidPrompt(p)
×
802
                }
803
              }
804
              bar.children = Seq(nextBtn)
×
805
            }
806
          })
807
        }
808
        ()
809
      case "invalid bid" =>
810
        val max = obj.headOption.collect { case i: Int => i }.getOrElse(0)
×
811
        val player = obj.drop(1).headOption.collect { case p: Player => p }
×
812
        Platform.runLater {
×
813
          val alert = new VBox(10) {
814
            alignment = Pos.Center
×
815
            padding = Insets(10)
×
816
            style = "-fx-background-color: rgba(200, 100, 0, 0.9); -fx-background-radius: 5;"
×
817
            maxWidth = 200
×
818
            maxHeight = 100
×
819
            children = Seq(
×
820
              new Label(s"Invalid bid! Max: $max") { style = "-fx-text-fill: white; -fx-font-weight: bold;" },
×
821
              new Button("OK") {
822
                style = buttonStyle
×
823
                onAction = _ => {
×
824
                  rootPane.foreach(_.children.remove(this.parent.value))
×
825
                  player.foreach { p =>
×
826
                    renderHand(p)
×
827
                    showBidPrompt(p)
×
828
                  }
829
                }
830
              }
831
            )
832
          }
833
          rootPane.foreach(_.children.add(alert))
×
834
        }
835
        ()
836
      case "TrickUpdated" =>
837
        obj.headOption.map(_.asInstanceOf[List[Card]]).foreach { cards =>
×
838
          Platform.runLater({
×
839
            currentTrickCards = cards
840
            renderTrick()
×
841
          })
842
        }
843
        ()
844
      case "LoadFailed" =>
845
        val title = obj.headOption.collect { case s: String => s }.getOrElse("Unknown")
×
846
        Platform.runLater {
×
847
          val alertBox = new VBox(10) {
848
            alignment = Pos.Center
×
849
            padding = Insets(20)
×
850
            style = "-fx-background-color: rgba(200,0,0,0.9); -fx-background-radius: 10;"
×
851
            maxWidth = 250
×
852
            children = Seq(
×
853
              new Label(s"'$title' not found") { style = "-fx-text-fill: white; -fx-font-weight: bold;" },
×
854
              new Button("OK") {
855
                style = buttonStyle
×
856
                onAction = _ => rootPane.foreach(_.children.remove(this.parent.value))
×
857
              }
858
            )
859
          }
860
          rootPane.foreach(_.children.add(alertBox))
×
861
        }
862
        ()
863
      case "SaveNotAllowed" =>
864
        Platform.runLater {
×
865
          val alertBox = new VBox(2) {
866
            alignment = Pos.Center
×
867
            padding = Insets(2, 10, 2, 10)
×
868
            style = "-fx-background-color: rgba(0,0,0,0.8); -fx-background-radius: 10; -fx-border-color: white; -fx-border-radius: 10;"
×
869
            maxWidth = 200
×
870
            children = Seq(
×
871
              new Label("Bitte spiele diese Runde erst zuende.") {
872
                  style = "-fx-text-fill: white; -fx-font-weight: bold; -fx-font-size: 11;"
×
873
                  wrapText = true
×
874
                  alignment = Pos.Center
×
875
              },
876
              new Button("OK") {
877
                style = buttonStyle + "; -fx-font-size: 10px; -fx-padding: 2 5 2 5;"
×
878
                onAction = _ => rootPane.foreach(_.children.remove(this.parent.value))
×
879
              }
880
            )
881
          }
882
          rootPane.foreach { rp =>
×
883
            StackPane.setAlignment(alertBox, Pos.Center)
×
884
            rp.children.add(alertBox)
×
885
          }
886
        }
887
        ()
888
      case "GameLoaded" =>
889
        obj.headOption.collect { case g: wizard.model.Game => g }.foreach { game =>
×
890
          Platform.runLater({
×
891
            currentPlayers = game.players
892
            currentTrickCards = game.currentTrick
893
            ensureGameTableRoot()
×
894
            updateScores()
×
895
            renderTrick()
×
896
            ensureUndoRedoBarVisible()
×
897
          })
898
        }
899
        ()
900
      case "UndoPerformed" | "RedoPerformed" =>
901
        Platform.runLater({
×
902
          updateScores()
×
903
          if (currentScreen == "Game") {
×
904
            currentPlayers.find(_.name == activePlayerName.getOrElse("")).foreach { p =>
×
905
              renderHand(p)
×
906
            }
907
          }
×
908
        })
909
        ()
910
      case "print points all players" =>
911
        val snapshot = obj.headOption.collect { case l: List[Any] => l }
×
912
        Platform.runLater {
×
913
          updateScores(snapshot)
×
914
        }
915
        ()
916
      case _ => ()
×
917
    }
918
  }
919

920
  private[aView] def testBuildPlayerNameRoot(count: Int): StackPane = createPlayerNameRoot(count)
×
921
  private[aView] def testUndoRedoBarPresent: Boolean = undoRedoBar.isDefined
×
922
  private[aView] def testContentBoxRef: AnyRef = contentBox.orNull
×
923
  private[aView] def testCurrentScreen: String = currentScreen
×
924
  private[aView] def testGetNavEpoch: Int = navEpoch
×
925
  private[aView] def testSwitchToPlayerNames(count: Int, capturedEpoch: Int): Unit = switchToPlayerNames(count, capturedEpoch)
×
926
  private[aView] def testSimulateUndoFromNames(): Unit = {
×
927
    if (currentScreen == "PlayerNames") {
×
928
      currentScreen = "PlayerCount"
929
      contentBox.foreach { box =>
×
930
        box.children = buildPlayerCountChildren(box)
×
931
      }
932
    }
×
933
  }
934
  private[aView] def testLocalBackToPlayerCount(): Unit = goBackToPlayerCountLocal()
×
935
}
936

937
object WizardGUI {
938
  def main(args: Array[String]): Unit = {
×
939
    val controller = new GameLogic
×
940
    val app = new WizardGUI(controller)
×
941
    app.main(args)
×
942
  }
943
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc