-
Notifications
You must be signed in to change notification settings - Fork 4
/
main.rs
executable file
·341 lines (324 loc) · 11.6 KB
/
main.rs
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
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
use crate::rules::{CustomRules, CARD_VALUE_STAT, PLAY_CARD_ABILITY};
use crate::tcp::{TcpClient, TcpServer};
use rand::{seq::SliceRandom, thread_rng};
use std::convert::TryInto;
use std::sync::{Arc, Mutex};
use std::{io::Read, thread, time};
use weasel::round::TurnsCount;
use weasel::team::TeamId;
use weasel::{
ActivateAbility, Actor, Battle, BattleController, BattleState, Character, CreateCreature,
CreateTeam, Creature, EndRound, EndTurn, EntityId, EventKind, EventProcessor, EventQueue,
EventTrigger, EventWrapper, Id, RemoveEntity, ResetObjectives, Server, StartTurn,
};
mod rules;
mod tcp;
fn main() {
// Get the server's address from command line args. If empty, it means we are also the server.
let args: Vec<String> = std::env::args().collect();
if args.len() == 2 {
// A tcp client contains a weasel Client. It forwards events registered in the latter
// to the remote server and automatically dumps events coming from the server into the client.
let client = TcpClient::new(&args[1]);
// Attach the event callback.
client
.game_client
.lock()
.unwrap()
.set_event_callback(Some(Box::new(event_callback)));
print_intro();
// Run the main game loop.
client_game_loop(client);
} else {
// Create a battle object with our game rules.
let battle = Battle::builder(CustomRules::new())
.event_callback(Box::new(event_callback))
.build();
// Create a server to handle the game state.
let mut game_server = Server::builder(battle).build();
// Initialize the game.
initialize_game(&mut game_server);
// Tcp server contains a weasel Server. It sends local events to all clients and also
// receives and validates their events.
let server = TcpServer::new(game_server);
print_intro();
// Run the main game loop.
server_game_loop(server);
}
// When this point is reached, the game has ended.
println!("\nGoodbye!");
}
// Here we initialize the deck and players.
fn initialize_game(server: &mut Server<CustomRules>) {
// First of all create three teams. Each player will take control of a team.
for n in 0..=2 {
CreateTeam::trigger(server, n).fire().unwrap();
}
// Prepare a deck of nine cards, shuffled randomly.
let mut cards: [u8; 9] = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let mut rng = thread_rng();
cards.shuffle(&mut rng);
// Then give three cards to each team. We'll use weasel 'Creature' to represent cards.
for (i, card) in cards.iter().enumerate() {
CreateCreature::trigger(
server,
i.try_into().unwrap(), // for the card's id we use the incrementing counter
(i % 3).try_into().unwrap(), // assign team in round-robin fashion
false, // the card starts in the player's hand
)
.statistics_seed(*card)
.fire()
.unwrap();
}
}
/// Game loop for the server.
fn server_game_loop(mut server: TcpServer) {
let id = 0;
game_status(server.game_server.lock().unwrap().battle(), id);
loop {
// Check for the game's end.
if completed_rounds(&server.game_server) == 3 {
println!("The game has ended!");
return;
}
if let Some(key) = read_input() {
match key {
'1' | '2' | '3' => {
if play_card(&mut server.game_server, key.to_digit(10).unwrap(), id) {
server_end_turn(&mut server.game_server);
game_status(server.game_server.lock().unwrap().battle(), id);
}
}
'h' => print_controls(),
'q' => break,
_ => {}
}
}
}
}
/// Game loop for the client.
fn client_game_loop(mut client: TcpClient) {
game_status(client.game_client.lock().unwrap().battle(), client.id);
loop {
let round = completed_rounds(&client.game_client);
// Check for the game's end.
if round == 3 {
println!("The game has ended!");
return;
}
if let Some(key) = read_input() {
match key {
'1' | '2' | '3' => {
if play_card(
&mut client.game_client,
key.to_digit(10).unwrap(),
client.id,
) {
// Wait for the round completion.
wait(|| {
client
.game_client
.lock()
.unwrap()
.battle()
.rounds()
.completed_rounds()
> round
});
game_status(client.game_client.lock().unwrap().battle(), client.id);
}
}
'h' => print_controls(),
'q' => break,
_ => {}
}
}
}
}
/// Checks and prints the game status.
fn game_status(battle: &Battle<CustomRules>, id: TeamId<CustomRules>) {
// Print the game state.
print_separator();
for team in battle.entities().teams() {
println!("Player {} points: {}", team.id() + 1, team.objectives());
}
// Print the player's hand.
println!("\nYour hand:");
for (i, creature_id) in battle.entities().team(&id).unwrap().creatures().enumerate() {
let creature = battle.entities().creature(creature_id).unwrap();
println!(
" {} - Card of value {}",
i + 1,
creature.statistic(&CARD_VALUE_STAT).unwrap().value()
);
}
print_separator();
}
/// Method to make the player play the card in the given slot.
fn play_card<T>(controller: &mut Arc<Mutex<T>>, card_index: u32, id: TeamId<CustomRules>) -> bool
where
T: BattleController<CustomRules> + EventProcessor<CustomRules>,
T: EventProcessor<CustomRules, ProcessOutput = weasel::WeaselResult<(), CustomRules>>,
{
// Retrieve the id of the card we want to play.
// card_index contains the 'index' of the selected card in our hand, we have to retrive the id.
let card_id = match controller
.lock()
.unwrap()
.battle()
.entities()
.team(&id)
.unwrap()
.creatures()
.nth(card_index as usize - 1)
.map(|e| EntityId::Creature(*e))
{
Some(id) => id,
None => {
println!("Invalid command!");
return false;
}
};
println!("Waiting for all players to make their move...");
// Wait for our turn.
wait(|| {
// We have defined the rounds model to be equal to the id of the player who should act.
*controller.lock().unwrap().battle().rounds().model() == id
});
// Perform the play.
// Everything is server based, so we can't just fire events one after the other because
// TcpClient and TcpServer are asynchronous. The quick and dirty solution is to wait for
// each event to be acknowledged. A proper solution would be to have the server sending
// messages to the clients and the clients themselves having a state machine.
let last_event = controller.lock().unwrap().battle().history().len();
StartTurn::trigger(&mut *controller.lock().unwrap(), card_id)
.fire()
.unwrap();
// Wait to receive the StartTurn event validation.
wait(|| controller.lock().unwrap().battle().history().len() > last_event);
let last_event = controller.lock().unwrap().battle().history().len();
ActivateAbility::trigger(&mut *controller.lock().unwrap(), card_id, PLAY_CARD_ABILITY)
.fire()
.unwrap();
// Wait to receive the ActivateAbility and MoveEntity events validation.
wait(|| controller.lock().unwrap().battle().history().len() > last_event + 1);
EndTurn::trigger(&mut *controller.lock().unwrap())
.fire()
.unwrap();
true
}
fn server_end_turn(server: &mut Arc<Mutex<Server<CustomRules>>>) {
wait(|| server.lock().unwrap().battle().rounds().completed_rounds() % 3 == 0);
// Decide the winner.
let winner = winner(server);
// Update the winner's score.
let new_score = server
.lock()
.unwrap()
.battle()
.entities()
.team(&winner)
.unwrap()
.objectives()
+ 1;
ResetObjectives::trigger(&mut *server.lock().unwrap(), winner)
.seed(new_score)
.fire()
.unwrap();
// Remove all played cards.
let cards = *server.lock().unwrap().battle().space().model();
for card in cards.iter() {
RemoveEntity::trigger(&mut *server.lock().unwrap(), card.unwrap())
.fire()
.unwrap();
}
// Close the round.
EndRound::trigger(&mut *server.lock().unwrap())
.fire()
.unwrap();
}
/// Returns the number of completed rounds.
fn completed_rounds<T>(controller: &Arc<Mutex<T>>) -> TurnsCount
where
T: BattleController<CustomRules> + EventProcessor<CustomRules>,
{
controller
.lock()
.unwrap()
.battle()
.rounds()
.completed_rounds()
}
/// Method to decide who won a turn.
fn winner<T>(controller: &mut Arc<Mutex<T>>) -> TeamId<CustomRules>
where
T: BattleController<CustomRules> + EventProcessor<CustomRules>,
{
let controller = controller.lock().unwrap();
let table = controller.battle().space().model();
let mut highest: Option<&Creature<CustomRules>> = None;
for card_id in table.iter() {
let card_id = card_id.unwrap().creature().unwrap();
let card = controller.battle().entities().creature(&card_id).unwrap();
let card_value = card.statistic(&CARD_VALUE_STAT).unwrap().value();
if let Some(a) = highest {
if card_value > a.statistic(&CARD_VALUE_STAT).unwrap().value() {
highest = Some(card);
}
} else {
highest = Some(card);
}
}
*highest.unwrap().team_id()
}
/// Event callback that prints the result of the battle.
fn event_callback(
event: &EventWrapper<CustomRules>,
_: &BattleState<CustomRules>,
_: &mut Option<EventQueue<CustomRules>>,
) {
if let EventKind::ResetObjectives = event.kind() {
let event: &ResetObjectives<CustomRules> =
match event.as_any().downcast_ref::<ResetObjectives<_>>() {
Some(e) => e,
None => panic!("incorrect cast!"),
};
println!("Player {} won a turn!", event.id() + 1);
}
}
// Helper methods.
/// Read a char from stdin.
fn read_input() -> Option<char> {
std::io::stdin()
.bytes()
.next()
.and_then(|result| result.ok())
.map(|byte| byte as char)
}
/// Blocks until `func` returns true.
fn wait<F>(func: F)
where
F: Fn() -> bool,
{
loop {
if func() {
break;
}
thread::sleep(time::Duration::from_millis(10));
}
}
fn print_intro() {
println!("\nWelcome to King of the hill!\n");
println!("You are given three cards numbered from 1 to 9.");
println!("Each turn all players play one card, the highest wins!\n");
print_controls();
}
fn print_controls() {
println!(" Controls:");
println!(" 1,2,3 - Play the corresponding card");
println!(" h - Display the controls");
println!(" q - Quit");
}
fn print_separator() {
println!("--------------------------------------------------------------------------------");
}