-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathChessStore.swift
More file actions
133 lines (128 loc) · 5.35 KB
/
ChessStore.swift
File metadata and controls
133 lines (128 loc) · 5.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//
// ChessStore.swift
//
//
// Created by Douglas Pedley on 11/26/20.
//
import Foundation
import SwiftUI
import Combine
public final class ChessStore: ObservableObject, ChessGameDelegate {
@Published public var game: Chess.Game
@Published public var environment: ChessEnvironment
private let gameSubject = PassthroughSubject<Chess.Game, Never>()
private let gameReducer: ChessGameReducer
private let gameSemaphore = DispatchSemaphore(value: 1)
private let environmentSubject = PassthroughSubject<ChessEnvironment, Never>()
private let environmentReducer: ChessEnvironmentReducer
private let environmentSemaphore = DispatchSemaphore(value: 1)
private var cancellables: Set<AnyCancellable> = []
public init(
game: Chess.Game = Chess.Game(),
gameReducer: @escaping ChessGameReducer = ChessStore.gameReducer,
environmentReducer: @escaping ChessEnvironmentReducer = ChessStore.environmentReducer,
environment: ChessEnvironment = ChessEnvironment()
) {
self.game = game
self.gameReducer = gameReducer
self.environmentReducer = environmentReducer
self.environment = environment
// If the initial board is empty, let's set up the pieces.
if !self.game.board.squares.contains(where: { !$0.isEmpty }) {
self.game.board.resetBoard()
}
self.game.delegate = self
}
public func gameAction(_ action: Chess.GameAction) {
// Process the message on the background, then sink back to the main thread.
DispatchQueue.global().async {
self.gameSemaphore.wait()
self.gamePublisher
.receive(on: RunLoop.main)
.sink { [weak self] newGame in
self?.processGameChanges(newGame)
self?.gameSemaphore.signal()
}
.store(in: &self.cancellables)
self.gameReducer(self.game, action, self.environment, self.gameSubject)
}
}
public func environmentChange(_ change: ChessEnvironment.EnvironmentChange) {
// Process the message on the background, then sink back to the main thread.
DispatchQueue.global().async {
self.environmentSemaphore.wait()
self.environmentPublisher
.receive(on: RunLoop.main)
.sink { [weak self] newEnvironment in
self?.processEnvironmentChanges(newEnvironment)
self?.environmentSemaphore.signal()
}
.store(in: &self.cancellables)
self.environmentReducer(self.environment, change, self.environmentSubject)
}
}
}
extension ChessStore {
var gamePublisher: AnyPublisher<Chess.Game, Never> {
gameSubject.eraseToAnyPublisher()
}
private func processGameChanges(_ updatedGame: Chess.Game) {
let opposingSide = self.game.board.playingSide.opposingSide
self.game = updatedGame
guard opposingSide == self.game.board.playingSide else {
// The turn didn't update, no look further.
return
}
// Check the status and update our result if the game is over
let status = updatedGame.computeGameStatus()
let humanSide: Chess.Side? = (updatedGame.black as? Chess.HumanPlayer)?.side ??
(updatedGame.white as? Chess.HumanPlayer)?.side ?? nil
Chess.log.debug("Computed game status: \(status)")
switch status {
case .notYetStarted, .paused, .unknown:
// We do nothing.
break
case .active:
self.gameAction(.nextTurn)
case .drawByMoves,
.drawByRepetition,
.drawBecauseOfInsufficientMatingMaterial,
.stalemate:
Chess.Sounds().defeat()
self.gameAction(.gameResult(result: .draw, status: status))
case .mate:
let winningSide = opposingSide.opposingSide // The opposingSide lost, so...
let result: Chess.Game.PGNResult = winningSide == .black ? .blackWon : .whiteWon
// Play the end game sound
if winningSide.opposingSide != humanSide {
// We play victory for human wins, and if both are bots
Chess.Sounds().victory()
} else {
Chess.Sounds().defeat()
}
self.gameAction(.gameResult(result: result, status: status))
case .resign, .timeout:
// Resign and timeout have their side kept in last move.
guard let winningSide = self.game.board.lastMove?.side.opposingSide else {
return
}
// Play the end game sound
if winningSide.opposingSide != humanSide {
// We play victory for human wins, and if both are bots
Chess.Sounds().victory()
} else {
Chess.Sounds().defeat()
}
let result: Chess.Game.PGNResult = winningSide == .black ? .blackWon : .whiteWon
self.gameAction(.gameResult(result: result, status: status))
}
}
}
extension ChessStore {
var environmentPublisher: AnyPublisher<ChessEnvironment, Never> {
environmentSubject.eraseToAnyPublisher()
}
private func processEnvironmentChanges(_ updatedEnvironment: ChessEnvironment) {
self.environment = updatedEnvironment
}
}