-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathgame.rs
executable file
·272 lines (257 loc) · 9.69 KB
/
game.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
use crate::rules::PiratesRules;
use crate::rules::*;
use rand::Rng;
use std::fs::{self, File};
use std::time::SystemTime;
use std::{env, io::Read};
use weasel::creature::CreatureId;
use weasel::team::TeamId;
use weasel::{
ActivateAbility, AlterStatistics, Battle, BattleController, BattleState, Character,
CreateCreature, CreateTeam, EndBattle, EndTurn, EntityId, EventKind, EventQueue, EventReceiver,
EventTrigger, EventWrapper, FlatVersionedEvent, RemoveCreature, ResetEntropy, Server,
StartTurn,
};
// Constants to identify teams.
const PLAYER_TEAM: &str = "player";
const ENEMY_TEAM: &str = "enemy";
// Constants to identify creatures (ships).
static PLAYER_SHIP: CreatureId<PiratesRules> = 0;
static ENEMY_SHIP: CreatureId<PiratesRules> = 1;
pub struct Game {
server: Server<PiratesRules>,
}
impl Game {
pub fn new() -> Self {
// Create a battle object with our game rules.
// We attach a callback to the battle, so that we can display a brief commentary
// when certain events happen!
let battle = Battle::builder(PiratesRules::new())
.event_callback(Box::new(commentary))
.build();
// Create a server to orchestrate the game.
let mut server = Server::builder(battle).build();
// Reset entropy with a 'random enough' seed.
let time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
ResetEntropy::trigger(&mut server)
.seed(time.as_secs())
.fire()
.unwrap();
// Create a team for the player.
// Changes to the battle are always performed through events. To fire an event
// just create a trigger object from the event itself, then call fire() on it.
//
// You must always give a valid 'EventProcessor' to a trigger, which can be either a
// server or a client.
CreateTeam::trigger(&mut server, PLAYER_TEAM.to_string())
// The objective of this team is to defeat ENEMY_TEAM.
.objectives_seed(ENEMY_TEAM.to_string())
.fire()
.unwrap();
// Now create one ship for the player.
CreateCreature::trigger(&mut server, PLAYER_SHIP, PLAYER_TEAM.to_string(), ())
.fire()
.unwrap();
// Do the same for the enemy.
CreateTeam::trigger(&mut server, ENEMY_TEAM.to_string())
.objectives_seed(PLAYER_TEAM.to_string())
.fire()
.unwrap();
CreateCreature::trigger(&mut server, ENEMY_SHIP, ENEMY_TEAM.to_string(), ())
.fire()
.unwrap();
// Return a game object.
Self { server }
}
pub fn fire_cannonball(&mut self) {
// Before our ship can attack we must start a player turn.
StartTurn::trigger(&mut self.server, EntityId::Creature(PLAYER_SHIP))
.fire()
.unwrap();
// Now activate the 'cannonball' ability of the player's ship.
// To do so we use an ActivateAbility event. We must specify the entity doing the ability,
// the id of the ability itself and as 'activation' who is the target.
ActivateAbility::trigger(
&mut self.server,
EntityId::Creature(PLAYER_SHIP),
ABILITY_CANNONBALL.to_string(),
)
.activation(EntityId::Creature(ENEMY_SHIP))
.fire()
.unwrap();
// After the ship's attack we just end the player turn.
EndTurn::trigger(&mut self.server).fire().unwrap();
}
pub fn fire_grapeshot(&mut self) {
// Some logic as fire_cannonball, but fire another ability.
StartTurn::trigger(&mut self.server, EntityId::Creature(PLAYER_SHIP))
.fire()
.unwrap();
ActivateAbility::trigger(
&mut self.server,
EntityId::Creature(PLAYER_SHIP),
ABILITY_GRAPESHOT.to_string(),
)
.activation(EntityId::Creature(ENEMY_SHIP))
.fire()
.unwrap();
EndTurn::trigger(&mut self.server).fire().unwrap();
}
pub fn enemy_turn(&mut self) {
// Before the enemy ship can attack we must start an enemy turn.
StartTurn::trigger(&mut self.server, EntityId::Creature(ENEMY_SHIP))
.fire()
.unwrap();
// Fire a random ability.
let mut rng = rand::thread_rng();
let rng_number = rng.gen_range(0, 2);
let ability = if rng_number == 0 {
ABILITY_CANNONBALL
} else {
ABILITY_GRAPESHOT
};
ActivateAbility::trigger(
&mut self.server,
EntityId::Creature(ENEMY_SHIP),
ability.to_string(),
)
.activation(EntityId::Creature(PLAYER_SHIP))
.fire()
.unwrap();
// After the ship's attack we just end the enemy turn.
EndTurn::trigger(&mut self.server).fire().unwrap();
}
/// Saves the battle's history as json in a temporary file.
pub fn save(&mut self) {
// Collect all events in a serializable format.
let events: Vec<FlatVersionedEvent<_>> = self
.server
.battle()
.versioned_events(std::ops::Range {
start: 0,
end: self.server.battle().history().len() as usize,
})
.map(|e| e.into())
.collect();
// Serialize as json.
let json = serde_json::to_string(&events).unwrap();
// Write the json into a temporary file.
let mut path = env::temp_dir();
path.push("savegame");
fs::write(path, json).unwrap();
println!("game saved!");
}
/// Restores the battle's history from a json temporary file.
pub fn load(&mut self) {
// Read the json stored in a temporary file.
let mut json = String::new();
let mut path = env::temp_dir();
path.push("savegame");
let file = File::open(path);
match file {
Ok(mut file) => {
file.read_to_string(&mut json).unwrap();
// Deserialize all events.
let events: Vec<FlatVersionedEvent<_>> = serde_json::from_str(&json).unwrap();
// Replay all events in a new instance of server.
let battle = Battle::builder(PiratesRules::new()).build();
self.server = Server::builder(battle).build();
for event in events {
self.server.receive(event.into()).unwrap();
}
// Attach the callback now to avoid invoking it while the events in history
// are replayed.
self.server.set_event_callback(Some(Box::new(commentary)));
println!("savegame loaded!");
}
Err(_) => println!("no savegame found!"),
}
}
// Returns the statistics of a ship.
fn ship_stats(&self, id: CreatureId<PiratesRules>) -> (i16, i16) {
// Retrieve the creature for the list of entities.
let creature = self.server.battle().entities().creature(&id);
// Be careful because the ship might have been destroyed.
match creature {
Some(creature) => (
creature.statistic(&STAT_HULL).unwrap().value(),
creature.statistic(&STAT_CREW).unwrap().value(),
),
None => (0, 0),
}
}
pub fn player_stats(&self) -> (i16, i16) {
self.ship_stats(PLAYER_SHIP)
}
pub fn enemy_stats(&self) -> (i16, i16) {
self.ship_stats(ENEMY_SHIP)
}
pub fn check_winner(&mut self) -> bool {
// We want to return whether or not there's a winner.
let winner: Vec<_> = self.server.battle().entities().victorious_id().collect();
if !winner.is_empty() {
println!("{} won!", pretty_team_id(&winner[0]));
// End the battle as well.
EndBattle::trigger(&mut self.server).fire().unwrap();
true
} else {
false
}
}
}
/// Event callback that prints some commentary out of the events happening in the battle.
fn commentary(
event: &EventWrapper<PiratesRules>,
_: &BattleState<PiratesRules>,
_: &mut Option<EventQueue<PiratesRules>>,
) {
match event.kind() {
EventKind::AlterStatistics => {
let event: &AlterStatistics<PiratesRules> =
match event.as_any().downcast_ref::<AlterStatistics<_>>() {
Some(e) => e,
None => panic!("incorrect cast!"),
};
let (hull_damage, crew_damage) = event.alteration();
if *hull_damage != 0 {
println!(
"{} took {} hull damage!",
pretty_creature_id(&event.id().creature().unwrap()),
hull_damage
);
}
if *crew_damage != 0 {
println!(
"{} took {} crew damage!",
pretty_creature_id(&event.id().creature().unwrap()),
crew_damage
);
}
}
EventKind::RemoveCreature => {
let event: &RemoveCreature<PiratesRules> =
match event.as_any().downcast_ref::<RemoveCreature<_>>() {
Some(e) => e,
None => panic!("incorrect cast!"),
};
println!("{} destroyed!", pretty_creature_id(event.id()));
}
_ => {} // Do nothing.
}
}
fn pretty_creature_id(id: &CreatureId<PiratesRules>) -> &'static str {
if *id == PLAYER_SHIP {
"Player ship"
} else {
"Enemy ship"
}
}
fn pretty_team_id(id: &TeamId<PiratesRules>) -> &'static str {
if id == PLAYER_TEAM {
"Player"
} else {
"Enemy"
}
}