Scala Swing跳过了框架的重新绘制

时间:2019-05-31 12:25:17

标签: scala swing repaint

我目前正在为我大学的软件工程课程在Scala中实现游戏Othello的实现,到目前为止,它的运行情况还不错。但是,在实施GUI(使用Scala Swing)时,我偶然发现了一个似乎无法解决的问题。

与计算机对手比赛时,似乎只有在机器人完成移动动作时,才会重新绘制框架。

该游戏还可以通过终端进行游戏,因此无论玩家的配置(玩家vs玩家还是Player vs Computer),每次都能正确更新Frame。使用GUI进行播放器与播放器之间的交互也完全没有问题。

请澄清一下,观察者特征是我们的课程的发布者/反应者模式的实现。我们出于教学原因实施了它。 Observable(在这种情况下为控制器)调用update方法。将其更改为内置实现并不会改变其行为,因此导致我将其视为原因。

这可能是我的疏忽,但到目前为止,我找不到解决方案,非常感谢您的帮助。

到目前为止,我已经尝试了各种方法来重新验证和重绘各个面板,添加和删除侦听器,将我的反应堆模式的实现更改为Scala Swing提供的模式,添加Thread.sleep以查看是否可以进行调度各种冲突。

package de.htwg.se.othello.aview.gui

import java.awt.Color

import de.htwg.se.othello.controller.Controller
import javax.swing.ImageIcon
import javax.swing.border.LineBorder

import scala.swing.event.MouseClicked
import scala.swing.{BorderPanel, BoxPanel, Dimension, FlowPanel, GridPanel, Label, Orientation}

class TablePanel(controller: Controller) extends FlowPanel {

  val sides = 32
  val sidesColor: Color = Color.lightGray
  val squareSize = 52

  def tableSize: Int = controller.board.size

  def edgeLength: Int = tableSize * squareSize

  def rows: BoxPanel = new BoxPanel(Orientation.Vertical) {
    background = sidesColor
    preferredSize = new Dimension(sides, edgeLength)
    contents += new Label {
      preferredSize = new Dimension(sides, sides)
    }
    contents += new GridPanel(tableSize, 1) {
      background = sidesColor
      for { i <- 1 to rows } contents += new Label(s"$i")
    }
  }

  def columns: GridPanel = new GridPanel(1, tableSize) {
    background = sidesColor
    preferredSize = new Dimension(edgeLength, sides)
    for { i <- 0 until columns } contents += new Label(s"${(i + 65).toChar}")
  }

  def table: GridPanel = new GridPanel(tableSize, tableSize) {
    background = new Color(10, 90, 10)
    for {
      col <- 0 until columns
      row <- 0 until rows
    } contents += square(col, row)
  }

  def square(row: Int, col: Int): Label = new Label {
    border = new LineBorder(new Color(30, 30, 30, 140), 1)
    preferredSize = new Dimension(squareSize, squareSize)
    icon = controller.board.valueOf(col, row) match {
      case -1 => new ImageIcon("resources/big_dot.png")
      case 0  => new ImageIcon("resources/empty.png")
      case 1  => new ImageIcon("resources/black_shadow.png")
      case 2  => new ImageIcon("resources/white_shadow.png")
    }
    listenTo(mouse.clicks)
    reactions += {
      case _: MouseClicked =>
        if (controller.options.contains((col, row))) controller.set(col, row)
        else if (controller.board.gameOver) controller.newGame()
        else controller.highlight()
    }
  }

  def redraw(): Unit = {
    contents.clear
    contents += new BorderPanel {
      add(rows, BorderPanel.Position.West)
      add(new BoxPanel(Orientation.Vertical) {
        contents += columns
        contents += table
      }, BorderPanel.Position.East)
    }
    repaint
  }
}
package de.htwg.se.othello.aview.gui

import scala.swing._
import de.htwg.se.othello.controller._
import de.htwg.se.othello.util.Observer

import scala.swing.event.Key

class SwingGui(controller: Controller) extends Frame with Observer {

  controller.add(this)

  lazy val tablePanel = new TablePanel(controller)

  lazy val mainFrame: MainFrame = new MainFrame {
    title = "Othello"
    menuBar = menus
    contents = tablePanel
    centerOnScreen
    // peer.setAlwaysOnTop(true)
    resizable = false
    visible = true
  }

  def menus: MenuBar = new MenuBar {
    contents += new Menu("File") {
      mnemonic = Key.F
      contents += new MenuItem(Action("New Game") {
        controller.newGame()
      })
      contents += new MenuItem(Action("Quit") {
        controller.exit()
      })
    }
    contents += new Menu("Edit") {
      mnemonic = Key.E
      contents += new MenuItem(Action("Undo") {
        controller.undo()
      })
      contents += new MenuItem(Action("Redo") {
        controller.redo()
      })
    }
    contents += new Menu("Options") {
      mnemonic = Key.O
      contents += new MenuItem(Action("Highlight possible moves") {
        controller.highlight()
      })
      contents += new MenuItem(Action("Reduce board size") {
        controller.resizeBoard("-")
      })
      contents += new MenuItem(Action("Increase board size") {
        controller.resizeBoard("+")
      })
      contents += new MenuItem(Action("Reset board size") {
        controller.resizeBoard(".")
      })
      contents += new Menu("Game mode") {
        contents += new MenuItem(Action("Player vs. Computer") {
          controller.setupPlayers("1")
        })
        contents += new MenuItem(Action("Player vs. Player") {
          controller.setupPlayers("2")
        })
      }
    }
  }

  def update: Boolean = {
    tablePanel.redraw()
    mainFrame.pack
    mainFrame.centerOnScreen
    mainFrame.repaint
    true
  }
}

预期的行为是每转一圈都会重新粉刷一次。实际结果是仅在对手移动后重新绘制框架。仅当通过单击UI专门玩Player与Bot时,才会发生这种情况。

2 个答案:

答案 0 :(得分:1)

我不认为问题出在您显示的代码中,但是我敢打赌,您正在用计算机播放器的AI计算来阻塞“事件分配线程”(UI线程)。

在Swing应用程序中,有一个特殊的线程称为“事件分发线程”,该线程负责处理来自O / S的消息,包括处理重画消息。所有UI事件处理程序都将在此线程上调用。如果您使用该线程执行需要很长时间的任何计算(例如,计算机在这样的游戏中移动),则所有UI更新都将被阻止,直到该线程释放为止。

本教程具有更多信息:https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html

您需要将AI移至后台线程,然后释放事件分发线程以处理重绘。如果您不熟悉多线程程序,可能很难实现!祝你好运。

答案 1 :(得分:1)

正如Rich所建议的那样,解决方案是并发。使搜索算法成为“未来”类型目前可以解决此问题。这可能不是最干净的实现,因为这是我第一次使用Future,但这是现在的样子:

  def selectAndSet(): Future[_] = Future(if (!board.gameOver && player.isBot) {
    new MoveSelector(this).select() match {
      case Success(square) => set(square)
      case _ => omitPlayer()
    }
    selectAndSet()
  })(ExecutionContext.global)