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

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() }
}
}