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
use futures_util::{SinkExt, StreamExt};
use reqwest::Client as HttpClient;
use tokio_tungstenite::connect_async;
use tokio_tungstenite::tungstenite::protocol::Message;
use crate::messages::message_protocol::{ActionMessage, StateMessage};
use crate::player::player_actions::PlayerActions;
/// This Client is used to connect a Bot to the kuh-handel server
pub struct Client {
/// name of the client. Should be identical to the name the bot uses
pub name: String,
/// user defined token for authentication
pub token: String,
/// the actual bot that provides the actions for the game. When providing actions with its name, that name should be the same as the clients name
pub bot: Box<dyn PlayerActions + Send + Sync>,
/// the url to connect to where the game is hosted, e.g. s://ufuk-guenes.com or ://127.0.0.1:2000 if you want to connect to a locally hosted server
/// the url requires "s://" for a secure connection
pub base_url: String,
last_ranking: Vec<(String, usize)>,
illegal_moves_made: Vec<String>,
/// if true, a summary of illegal actions that have been corrected is printed to the terminal
pub raise_faulty_action_warning: bool,
}
impl Client {
/// Creates a new Client
pub fn new(
name: String,
token: String,
bot: Box<dyn PlayerActions + Send + Sync>,
base_url: String,
raise_faulty_action_warning: bool,
) -> Self {
Client {
name,
token,
bot,
base_url,
last_ranking: Vec::new(),
illegal_moves_made: Vec::default(),
raise_faulty_action_warning: raise_faulty_action_warning,
}
}
/// Registers a new player with the provided name and token. This only needs to be called once
pub async fn register(&self) -> Result<(), Box<dyn std::error::Error>> {
let http = HttpClient::new();
println!("Registering bot {} ...", self.name);
let response = http
.post(format!(
"http{}/kuh-handel/register?player_id={}&token={}",
self.base_url, self.name, self.token
))
.send()
.await?;
if !response.status().is_success() {
return Err(format!("Registration failed: {:?}", response.status()).into());
};
println!("Successfully registered bot {}", self.name);
Ok(())
}
/// Connects to the server and tries to play one round.
/// `game_type_url` - the type of game to connect to. Possible choices:
/// game: waits for other players to join and then plays against those. These games are counted in the results
/// server_bot_game: only play against random bots provided by the server. Use this for testing if your bot can play valid games. NOT counted in the results
pub async fn play_one_round(&mut self, game_type_url: String) {
let url = format!(
"ws{}/kuh-handel/{}?player_id={}&token={}&raise_faulty_action_warning={}",
self.base_url, game_type_url, self.name, self.token, self.raise_faulty_action_warning
);
let (ws_stream, _) = connect_async(url).await.expect("Failed to connect");
println!("Connected to server!");
let (mut send, mut recv) = ws_stream.split();
let ctrl_c_signal = tokio::signal::ctrl_c();
tokio::pin!(ctrl_c_signal);
// Spawn a task to listen for incoming messages
loop {
// println!("waiting for next action request");
let msg = tokio::select! {
msg = recv.next() => {
match msg {
Some(msg) => msg,
None => {
println!("game closed connection to game, ending loop {}", self.name);
break;
}
}
},
_ = &mut ctrl_c_signal => {
println!("keyboard interrupt, ending loop {}", self.name);
break;
}
};
let msg_type = match msg {
Ok(msg_type) => msg_type,
Err(e) => {
println!("error receiving from game: {}, {}", self.name, e);
break;
}
};
let text = match msg_type {
Message::Text(text) => text,
Message::Close(_) => {
println!("Connection closed by server");
break;
}
other => {
println!("Received other message: {}, {:?}", self.name, other);
break;
}
};
let action_msg: ActionMessage;
{
let state_message: StateMessage = serde_json::from_str(&text).unwrap();
// println!("bot {} received message: {}", self.name, state_message);
action_msg = self.bot.map_to_action(state_message);
let state_message: StateMessage = serde_json::from_str(&text).unwrap();
if let StateMessage::GameUpdate {
update:
crate::messages::game_updates::GameUpdate::End {
ranking,
illegal_moves_made,
},
} = &state_message
{
self.last_ranking = ranking.clone();
self.illegal_moves_made = illegal_moves_made.clone();
break;
};
}
let action_str = serde_json::to_string(&action_msg).unwrap();
// println!("bot {} picked action: {}", self.name, action_str);
let message: Message = Message::Text(action_str);
let send_status = send.send(message).await;
match send_status {
Ok(_) => (), //println!("action of bot {} has been send to game", self.name,),
Err(_) => {
println!(
"failure sending action of bot {} to game, closing connection",
self.name,
);
break;
}
}
// println!("bot {}, finished sending action", self.name);
}
let res = send.close().await;
println!("result {}: {:?}", self.name, self.last_ranking);
if self.raise_faulty_action_warning {
println!(
"illegal moves made {}: {:?}",
self.name, self.illegal_moves_made
);
}
}
}