我目前正在为我大学的软件工程课程在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时,才会发生这种情况。
答案 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)