You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
5.3 KiB
170 lines
5.3 KiB
import SpriteKit
|
|
import Combine
|
|
import Connect4Core
|
|
|
|
public class GameScene: SKScene {
|
|
public static let boardTopMargin: CGFloat = PieceNode.pieceSize / 2
|
|
|
|
private let vm: IngameVM
|
|
|
|
private var boardNode: BoardNode
|
|
private var cells: [PieceNode]
|
|
private var draggablePiece: PieceNode
|
|
|
|
private var cancellables: [AnyCancellable] = []
|
|
|
|
init(viewModel vm: IngameVM) {
|
|
self.vm = vm
|
|
|
|
let boardHeight = CGFloat(vm.height) * PieceNode.pieceSize
|
|
let gWidth = CGFloat(vm.width) * PieceNode.pieceSize
|
|
let gHeight = boardHeight + Self.boardTopMargin + PieceNode.pieceSize
|
|
|
|
boardNode = BoardNode(cells: (vm.width, vm.height), size: CGSize(width: gWidth, height: boardHeight))
|
|
|
|
draggablePiece = PieceNode()
|
|
draggablePiece.piece = .init(withOwner: .noOne)
|
|
|
|
cells = []
|
|
|
|
super.init(size: CGSize(width: gWidth, height: gHeight))
|
|
|
|
// anchorPoint = CGPoint(x: 0.5, y: 0.5)
|
|
backgroundColor = .clear
|
|
|
|
addChild(boardNode)
|
|
|
|
draggablePiece.position = draggablePiecePlacement
|
|
self.cancellables.append(vm.$currentPlayer.sink {
|
|
self.draggablePiece.piece = .init(withOwner: $0)
|
|
})
|
|
self.cancellables.append(vm.$currentBoard.sink(receiveValue: self.updateBoard))
|
|
self.cancellables.append(vm.$paused.sink { paused in
|
|
self.draggablePiece.onDragHandler = if paused {
|
|
nil
|
|
} else {
|
|
self.onPieceDragEnd
|
|
}
|
|
})
|
|
|
|
vm.game.addGameStartedListener { _ in
|
|
DispatchQueue.main.async {
|
|
self.addChild(self.draggablePiece)
|
|
}
|
|
}
|
|
vm.game.addMoveChosenCallbacksListener { board, move, player in
|
|
DispatchQueue.main.async {
|
|
self.onMove(board: board, move: move, player: player)
|
|
}
|
|
}
|
|
vm.game.addInvalidMoveCallbacksListener { board, move, player, result in
|
|
DispatchQueue.main.async {
|
|
self.onInvalidMove(board: board, move: move, player: player, result: result)
|
|
}
|
|
}
|
|
vm.game.addPlayerNotifiedListener { _, player in
|
|
DispatchQueue.main.async {
|
|
self.onPlayerTurn(player: player)
|
|
}
|
|
}
|
|
vm.game.addGameOverListener { board, result, player in
|
|
DispatchQueue.main.async {
|
|
self.onGameOver(board: board, result: result, player: player)
|
|
}
|
|
}
|
|
}
|
|
|
|
public required init?(coder aDecoder: NSCoder) {
|
|
fatalError("Unreachable")
|
|
}
|
|
|
|
private var draggablePiecePlacement: CGPoint { CGPoint(
|
|
x: self.size.width / 2,
|
|
y: self.size.height - PieceNode.pieceSize / 2
|
|
) }
|
|
|
|
private func onPlayerTurn(player: Player) {
|
|
self.draggablePiece.onDragHandler = if player is HumanPlayer {
|
|
self.onPieceDragEnd
|
|
} else {
|
|
nil
|
|
}
|
|
}
|
|
|
|
private func onPieceDragEnd(_: PieceNode) {
|
|
draggablePiece.onDragHandler = nil
|
|
|
|
let at = draggablePiece.clampedPosition()
|
|
if (0..<vm.currentBoard.nbRows).contains(at.row) &&
|
|
(0..<vm.currentBoard.nbColumns).contains(at.column) {
|
|
vm.pieceDropped(at: at)
|
|
} else {
|
|
self.cancelMove(mayRetry: true)
|
|
}
|
|
}
|
|
|
|
private func updateBoard(board: Board) {
|
|
assert(vm.columns == board.nbColumns && vm.rows == board.nbRows, "Board size mismatch")
|
|
|
|
for cell in cells {
|
|
cell.removeFromParent()
|
|
}
|
|
cells.removeAll()
|
|
|
|
for row in 0..<board.nbRows {
|
|
for column in 0..<board.nbColumns {
|
|
guard let piece = board[row, column] else { continue }
|
|
|
|
let node = PieceNode()
|
|
node.boardPosition = (column, row)
|
|
node.piece = piece
|
|
cells.append(node)
|
|
|
|
addChild(node)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func onMove(board: Board, move: Move, player: Player) {
|
|
draggablePiece.position = draggablePiecePlacement
|
|
}
|
|
|
|
private func onInvalidMove(board: Board, move: Move, player: Player, result: Bool) {
|
|
if result { return }
|
|
|
|
self.cancelMove(mayRetry: player is HumanPlayer)
|
|
}
|
|
|
|
private func cancelMove(mayRetry: Bool) {
|
|
draggablePiece.run(SKAction.move(to: draggablePiecePlacement, duration: 0.25)) {
|
|
if mayRetry {
|
|
self.draggablePiece.onDragHandler = self.onPieceDragEnd
|
|
}
|
|
}
|
|
}
|
|
|
|
private func onGameOver(board: Board, result: Connect4Core.Result, player: Player?) {
|
|
self.draggablePiece.removeFromParent()
|
|
|
|
switch (result) {
|
|
case .winner(_, let alignment):
|
|
for cell in cells {
|
|
if alignment.contains(where: {
|
|
$0.row == cell.boardPosition!.y && $0.col == cell.boardPosition!.x
|
|
}) {
|
|
continue
|
|
}
|
|
|
|
cell.alpha = 0.25
|
|
}
|
|
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
for c in cancellables { c.cancel() }
|
|
}
|
|
}
|