我正在学习Swift,并试图开发第一款应用-Tic Tac Toe.我想同时实现PVP和AI,并坚持显示谁是下一个移动的(X/O).AI模式(目前只是随机模式)运行良好,PVP也运行良好,但它总是显示"X to move".如果我在TicTacToe文件中将变量playerToMove
设置为@Published
,这样文本就可以更新,应用程序就不能工作.内容视图显示所有内容,但我无法单击任何内容.如果我开发这个应用程序,它只是一个白屏.
下面我显示的是应该更新的文本:
struct ContentView: View {
@ObservedObject var ticTacToe = TicTacToeModel()
var currPlayer: String {
return self.ticTacToe.playerToMove == false ? "X" : "O"
}
var body: some View {
Text(self.selection == true ? "\(currPlayer) to move" : " ")
}
以下是TicTacToeModel:
class TicTacToeModel: ObservableObject {
var playerToMove : Bool = false // App breaks when set to @Published
}
这里有完整的源代码:(也可以在https://pastebin.com/srDNZHnU下载)
// ComponentView:
import SwiftUI
import Combine
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
@ObservedObject var ticTacToe = TicTacToeModel()
@State var selection : Bool = false
@State var move : Bool = false
@State var gameOver : Bool = false
func buttonAction(_ index : Int) {
if self.ticTacToe.makeMove(index: index, gameType: selection) {
self.gameOver = self.ticTacToe.gameOver.1
}
}
var currPlayer: String {
print("runned", self.ticTacToe.playerToMove)
return self.ticTacToe.playerToMove == false ? "X" : "O"
}
var body: some View {
VStack {
Picker(selection: $selection, label: Text("Game")) {
Text("AI").tag(false)
Text("PVP").tag(true)
}
.pickerStyle(SegmentedPickerStyle())
.padding(.horizontal, 89)
.onReceive(Just(selection)) { _ in
self.ticTacToe.resetGame()
}
Spacer()
Text(self.selection == false ? "Tic Tac Toe - AI" : "Tic Tac Toe - PVP")
.bold()
.font(.title)
//.padding(.bottom)
.foregroundColor(colorScheme == .dark ? Color.white.opacity(0.7) : Color.black.opacity(0.7))
Text(self.selection == true ? "\(currPlayer) to move" : " ")
.bold()
.font(.title2)
.padding(.bottom)
.foregroundColor(colorScheme == .dark ? Color.white.opacity(0.7) : Color.black.opacity(0.7))
ForEach(0 ..< ticTacToe.squares.count / 3, id: \.self, content: { row in
HStack {
ForEach(0 ..< 3, content: { column in
let index = row * 3 + column
SquareView(dataSouce: ticTacToe.squares[index], action: { self.buttonAction(index) })
})
}
})
Spacer()
Button(action: {
self.ticTacToe.resetGame()
}, label: {
Text("Reset")
.foregroundColor(Color.red.opacity(0.7))
})
}.alert(isPresented: self.$gameOver, content: {
Alert(title: Text("Game Over"),
message: Text(self.ticTacToe.gameOver.0 != .empty ?
self.ticTacToe.gameOver.0 == .x ? "You Won!" : "AI Won!" : "Draw!"),
dismissButton: Alert.Button.destructive(Text("Ok"), action: {
self.ticTacToe.resetGame()
}))
})
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// ---------------------------------------------------------------------
// TicTacToe:
import Foundation
class TicTacToeModel: ObservableObject {
@Published var squares = [Square]()
var playerToMove : Bool = false // App breaks when set to @Published
init() {
for _ in 0...8 {
squares.append(Square(status: .empty))
}
}
func resetGame() {
for i in 0...8 {
squares[i].squareStatus = .empty
playerToMove = false
}
}
var gameOver : (SquareStatus, Bool) {
get {
if winner != .empty {
return (winner, true)
} else {
for i in 0...8 {
if squares[i].squareStatus == .empty {
return (.empty, false)
}
}
return (.empty, true)
}
}
}
func makeMove(index: Int, gameType: Bool) -> Bool {
var player: SquareStatus
if playerToMove == false {
player = .x
} else {
player = .o
}
if squares[index].squareStatus == .empty {
squares[index].squareStatus = player
if playerToMove == false && gameType == false {
moveAI()
playerToMove = true
}
playerToMove.toggle()
return true
}
return false
}
private func moveAI() {
var index = Int.random(in: 0...8)
playerToMove = true
while makeMove(index: index, gameType: true) == false && gameOver.1 == false {
index = Int.random(in: 0...8)
}
}
private var winner: SquareStatus {
get {
if let check = self.checkIndexes([0, 1, 2]) {
return check
} else if let check = self.checkIndexes([3, 4, 5]) {
return check
} else if let check = self.checkIndexes([6, 7, 8]) {
return check
} else if let check = self.checkIndexes([0, 3, 6]) {
return check
} else if let check = self.checkIndexes([1, 4, 7]) {
return check
} else if let check = self.checkIndexes([2, 5, 8]) {
return check
} else if let check = self.checkIndexes([0, 4, 8]) {
return check
} else if let check = self.checkIndexes([2, 4, 6]) {
return check
}
return .empty
}
}
private func checkIndexes(_ indexes : [Int]) -> SquareStatus? {
var xCount : Int = 0
var oCount : Int = 0
for index in indexes {
let square = squares[index]
if square.squareStatus == .x {
xCount += 1
} else if square.squareStatus == .o {
oCount += 1
}
}
if xCount == 3 {
return .x
} else if oCount == 3 {
return .o
}
return nil
}
}
// ---------------------------------------------------------------------
// SquareView:
import Foundation
import SwiftUI
enum SquareStatus {
case empty
case x
case o
}
class Square : ObservableObject {
@Published var squareStatus : SquareStatus
init(status: SquareStatus) {
self.squareStatus = status
}
}
struct SquareView : View {
@Environment(\.colorScheme) var colorScheme
@ObservedObject var dataSouce : Square
var action: () -> Void
var body: some View {
Button(action: {
self.action()
}, label: {
Text(self.dataSouce.squareStatus == .x ?
"X" : self.dataSouce.squareStatus == .o ? "O" : " ")
.font(.system(size: 60))
.bold()
.foregroundColor(colorScheme == .dark ? Color.white.opacity(0.9) : Color.black.opacity(0.9))
.frame(width: 90, height: 90, alignment: .center)
.background(colorScheme == .dark ? Color.white.opacity(0.3).cornerRadius(10) : Color.gray.opacity(0.3).cornerRadius(10))
.padding(4)
})
}
}
我原以为如果向该变量加@Published
,"下一步"就能正常工作,但我的代码没有出错.我try 使用@Published
创建新的var进行测试,它一直在运行,直到我在任何函数中将其设置为不同的值.