This repository has been archived by the owner on Sep 15, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
LiveGame.ts
286 lines (252 loc) · 10.1 KB
/
LiveGame.ts
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
import { ObserverClient } from './ObserverClient'
import { GameState } from '../rules/CurrentGame'
import { GameCreationOptions, GameType, Player, PlayerType } from '../rules/GameCreationOptions'
import { AsyncApi } from './AsyncApi'
import { Logger as SC_Logger, Logger } from '../Logger'
import { ConsoleMessage } from '../rules/ConsoleMessage'
import { ExecutableStatus } from '../rules/ExecutableStatus'
import { ExecutableClient } from './ExecutableClient'
import { PlayerClientOptions } from './PlayerClient'
import { HumanClient } from './HumanClient'
import { Game } from '../rules/Game'
import { Server } from './Server'
export class LiveGame extends Game {
observer: ObserverClient
client1: GameClient
client2: GameClient
private is_live: boolean
private has_finished: boolean
private roomId: Promise<string>
constructor(gco: GameCreationOptions) {
super(gco.gameId)
this.isReplay = false
let Logger = SC_Logger.getLogger().focus('Game', 'constructor')
Logger.log('Creating game ' + gco.gameId)
Logger.log('Options: ' + JSON.stringify(gco, null, 2))
let timeout = 10000
let gameStartSuccessful
let gameStartError
this.ready = new Promise<void>((res, rej) => {
gameStartSuccessful = res
gameStartError = rej
})
this.roomId =
AsyncApi.getServer().then(server => {
//Register hook to go offline
server.on('status', s => {
if (s == ExecutableStatus.Status.EXITED) {
//Server exited. Stop client processes, set game to not live
this.is_live = false
}
})
//Wait for server to start
return server.ready.then(() => server)
})
.then(server => {
Logger.log('API Server is ready')
//Create observer
Logger.log('Creating Observer Client')
this.observer = new ObserverClient(server.getHost(), server.getPort())
this.observer.once('state', s => {
Logger.log('First gamestate received, game successfully started')
gameStartSuccessful()
this.roomId.then(id => this.observer.setPaused(id, false))
})
this.observer.on('state', s => {
this.gameStates.push(s)
this.emit('state' + (this.gameStates.length - 1), s)
this.emit('state_update')
})
this.observer.on('result', r => {
gameStartSuccessful()
this.gameResult = r
this.is_live = false
})
this.observer.on('message', msg => {
let m: ConsoleMessage = {
sender: 'observer',
type: 'output',
text: msg,
}
this.messages.push(m)
// this.emit('message', m);
})
return Promise.all([server, this.observer.ready])
})
.then(results => {
let server = results[0]
let observer = results[1]
Logger.log('Observer ready')
if (gco.kind === GameType.Versus) {
let matchPlayerTypes = (kinds: Array<[PlayerType, PlayerType]>): boolean => {
return kinds.some(t => gco.firstPlayer.kind === t[0] && gco.secondPlayer.kind === t[1])
}
if (matchPlayerTypes([
[PlayerType.Computer, PlayerType.Computer],
[PlayerType.Human, PlayerType.Computer],
[PlayerType.Computer, PlayerType.Human],
[PlayerType.Human, PlayerType.Human],
])) {
//Reserve room, start clients, done
Logger.log('Automatic game')
const p1 = new PlayerClientOptions(gco.firstPlayer.name, gco.firstPlayer.timeoutPossible)
const p2 = new PlayerClientOptions(gco.secondPlayer.name, gco.secondPlayer.timeoutPossible)
Logger.log('Starting automatic game')
this.observer.prepareRoom(p1, p2).then(reservation => {
let roomId = reservation.roomId
Logger.log('Reserved room with id ' + roomId)
return this.observer.observeRoom(roomId).then(() => {
Logger.log('Observing room with id ' + roomId)
return reservation
})
}).then(reservation => {
this.client1 = this.createClient(gco.firstPlayer, server, reservation.reservation1)
Logger.log('Client 1 created')
this.client2 = this.createClient(gco.secondPlayer, server, reservation.reservation2)
Logger.log('Client 2 created')
// wait for clients to start
// NOTE that the order of resolution of the connect-promises is arbitrary
return Promise.all([
this.client1.start().catch(reason => { throw { client: 1, error: reason } }),
this.client2.start().catch(reason => { throw { client: 2, error: reason } }),
])
.then(() => {
Logger.log('LiveGame is live!')
this.is_live = true
return reservation.roomId
})
.catch(reason => gameStartError(reason))
})
} else if (matchPlayerTypes([
[PlayerType.Human, PlayerType.Manual],
[PlayerType.Manual, PlayerType.Human],
[PlayerType.Computer, PlayerType.Manual],
[PlayerType.Manual, PlayerType.Computer],
])) {
Logger.log('Starting half-automatic half-manual game')
//Wait for manual client to connect, start other client
let auto_client: GameClient
let auto_client_is_human_client: boolean = false
this.observer.awaitJoinGameRoom().then(roomId => {
return this.observer.observeRoom(roomId).then(() => {
Logger.log('Observing room with id ' + roomId)
return roomId
})
}).then(roomId => {
Logger.log('Configure automatic client')
//Configure one client
if (gco.firstPlayer.kind == PlayerType.Manual) {
this.client2 = this.createClient(gco.secondPlayer, server, undefined)
if (gco.secondPlayer.kind == PlayerType.Human) {
auto_client_is_human_client = true
}
auto_client = this.client2
} else {
this.client1 = this.createClient(gco.firstPlayer, server, undefined)
if (gco.firstPlayer.kind == PlayerType.Human) {
auto_client_is_human_client = true
}
auto_client = this.client1
}
//Add client to room
return auto_client.start().then(() => {
//Disable timeout if human
if (auto_client_is_human_client) {
Logger.log('Disabling timeout for human client in slot 1')
this.observer.setTimeoutEnabled(roomId, 1, false)
}
this.is_live = true
return roomId
})
})
} else if (matchPlayerTypes([
[PlayerType.Manual, PlayerType.Manual],
])) {
Logger.log('Starting manual game')
Logger.log('Waiting for room creation')
return this.observer.awaitJoinGameRoom().then(roomId => {
//Observe room
return this.observer.observeRoom(roomId).then(() => {
Logger.log('Observing room with id ' + roomId)
return roomId
})
})
} else {
let message = 'Something went wrong. Player types: ' + JSON.stringify([gco.firstPlayer.kind, gco.secondPlayer.kind])
Logger.log(message)
return Promise.reject(message)
}
}
})
}
createClient(player: Player, server: Server, reservation: string) {
return player.kind == PlayerType.Computer ?
new ExecutableClient(
player,
reservation,
server.getHost(),
server.getPort(),
)
: new HumanClient(
server.getHost(),
server.getPort(),
player.name,
reservation,
this.id,
)
}
getMessages(): ConsoleMessage[] {
return this.messages
}
/**
* Returns true, if the game is currently running
*/
isLive(): boolean {
return this.is_live
}
getState(n: number): Promise<GameState> {
if (this.gameStates[n]) { //If our next state is already buffered
return Promise.resolve(this.gameStates[n])
} else {//Wait for new state to be emitted
return new Promise((res, rej) => {
this.once('state' + n, s => {
res(s)
})
})
}
}
getStateNumber(state: GameState): number {
return this.gameStates.findIndex((s: GameState) => { return s.turn == state.turn })
}
requestNext() {
if (this.is_live) {
this.roomId.then(id => this.observer.requestStep(id))
}
}
getLog(): string {
return this.observer.log
}
saveReplay(path) {
const fs = require('fs')
let data = this.observer.getAllData()
let protocolTag = '</protocol>'
if (!data.endsWith(protocolTag)) {
data = data + protocolTag
}
fs.writeFile(path, data, function(err) {
if (err) {
Logger.getLogger().log('LiveGame', 'saveReplay', 'Error saving replay: ' + err)
return err
}
Logger.getLogger().log('LiveGame', 'saveReplay', 'Replay saved in ' + path)
})
}
cancel() {
this.roomId.then(id => this.observer.cancelGame(id))
}
}
export interface GameClient {
ready: Promise<void>;
start(): Promise<void>;
stop();
}