1#![doc = include_str!("../README.md")]
2
3use tokio::{io::AsyncWriteExt as _, net::TcpStream};
4
5use tokio_stream::StreamExt as _;
6use tokio_util::codec::{FramedRead, LinesCodec};
7
8use crate::packets::{ClientPacket, MoveDirection, ServerPacket};
9
10pub mod demobot;
11pub mod errors;
12pub mod packets;
13
14pub struct Bot {
15 instruction_queue: Vec<ClientPacket>,
16 world: WorldMap,
17 username: String,
18 user_id: Option<usize>,
19 current_position: Option<(usize, usize)>,
20}
21
22pub struct WorldMap {
24 cells: Vec<Vec<Option<usize>>>,
25 width: usize,
26 height: usize,
27}
28
29impl WorldMap {
30 fn new(width: usize, height: usize) -> Self {
31 Self {
32 cells: vec![vec![None; width]; height],
33 width,
34 height,
35 }
36 }
37
38 pub fn size(&self) -> (usize, usize) {
40 (self.width, self.height)
41 }
42
43 pub fn width(&self) -> usize {
44 self.width
45 }
46
47 pub fn height(&self) -> usize {
48 self.height
49 }
50
51 pub fn get(&self, x: usize, y: usize) -> Option<usize> {
53 if self.width == 0 || self.height == 0 {
54 return None;
55 }
56 let x = x % self.width;
57 let y = y % self.height;
58 self.cells[y][x]
59 }
60
61 pub fn set(&mut self, x: usize, y: usize, val: usize) {
62 if self.width == 0 || self.height == 0 {
63 return;
64 }
65 let x = x % self.width;
66 let y = y % self.height;
67 self.cells[y][x] = Some(val);
68 }
69
70 pub fn clear_player(&mut self, player_id: usize) {
72 for row in &mut self.cells {
73 for cell in row.iter_mut() {
74 if *cell == Some(player_id) {
75 *cell = None;
76 }
77 }
78 }
79 }
80
81 pub fn step(&self, x: usize, y: usize, direction: MoveDirection) -> (usize, usize) {
83 let (width, height) = (self.width, self.height);
84 match direction {
85 MoveDirection::Up => (x, (y + height - 1) % height),
86 MoveDirection::Down => (x, (y + 1) % height),
87 MoveDirection::Left => ((x + width - 1) % width, y),
88 MoveDirection::Right => ((x + 1) % width, y),
89 }
90 }
91
92 pub fn is_blocked(&self, x: usize, y: usize, direction: MoveDirection) -> bool {
94 let (nx, ny) = self.step(x, y, direction);
95 self.get(nx, ny).is_some()
96 }
97}
98
99impl Bot {
100 fn new(username: impl Into<String>) -> Self {
101 let username = username.into();
102 Self {
103 instruction_queue: Vec::new(),
104 world: WorldMap::new(0, 0),
105 username,
106 user_id: None,
107 current_position: None,
108 }
109 }
110
111 pub fn world(&self) -> &WorldMap {
113 &self.world
114 }
115
116 pub fn user_id(&self) -> Option<usize> {
118 self.user_id
119 }
120
121 pub fn position(&self) -> Option<(usize, usize)> {
123 self.current_position
124 }
125
126 pub fn safe_directions(&self) -> Vec<MoveDirection> {
128 let Some((x, y)) = self.current_position else {
129 return Vec::new();
130 };
131 [
132 MoveDirection::Up,
133 MoveDirection::Down,
134 MoveDirection::Left,
135 MoveDirection::Right,
136 ]
137 .into_iter()
138 .filter(|d| !self.world.is_blocked(x, y, *d))
139 .collect()
140 }
141
142 pub fn is_blocked(&self, direction: MoveDirection) -> bool {
145 match self.current_position {
146 Some((x, y)) => self.world.is_blocked(x, y, direction),
147 None => true,
148 }
149 }
150
151 pub fn write_packet(&mut self, packet: ClientPacket) {
153 self.instruction_queue.push(packet);
154 }
155
156 pub fn chat(&mut self, message: impl Into<String>) {
158 self.write_packet(ClientPacket::Chat(message.into()));
159 }
160
161 pub fn do_move(&mut self, direction: MoveDirection) {
163 self.write_packet(ClientPacket::Move(direction));
164 }
165}
166
167pub trait GpnTronBot: Sized {
168 fn handle_packet(&mut self, bot: &mut Bot, packet: ServerPacket);
169
170 #[allow(async_fn_in_trait)]
171 async fn start(
172 &mut self,
173 url: impl std::fmt::Display,
174 port: u16,
175 username: impl Into<String>,
176 password: impl Into<String>,
177 ) -> crate::errors::Result<()> {
178 let addr = format!("{url}:{port}");
179 println!("connecting to {addr}");
180 let stream =
181 TcpStream::connect(&addr)
182 .await
183 .map_err(|source| crate::errors::Error::Connect {
184 addr: addr.clone(),
185 source: std::sync::Arc::new(source),
186 })?;
187 let (read, mut write) = stream.into_split();
188 let username = username.into();
189 let join = ClientPacket::join(username.clone(), password).to_string();
190 write.write_all(join.as_bytes()).await?;
191 let decoder = LinesCodec::new();
192 let mut read = FramedRead::new(read, decoder);
193 let mut bot = Bot::new(username);
194 loop {
195 while let Some(next) = read.next().await {
196 let string = next?;
197 let packet = ServerPacket::parse(string)?;
198 match &packet {
199 ServerPacket::Game { width, height, .. } => {
200 bot.world = WorldMap::new(*width, *height);
201 bot.current_position = None;
202 }
203 ServerPacket::Position { player_id, x, y } => {
204 bot.world.set(*x, *y, *player_id);
205 if Some(*player_id) == bot.user_id {
206 bot.current_position = Some((*x, *y));
207 }
208 }
209 ServerPacket::Player { id, name } => {
210 if name == &bot.username {
211 bot.user_id.replace(*id);
212 }
213 }
214 ServerPacket::Die { user_ids } => {
215 for id in user_ids {
216 bot.world.clear_player(*id);
217 if Some(*id) == bot.user_id {
218 bot.current_position = None;
219 }
220 }
221 }
222 _ => {}
223 }
224 self.handle_packet(&mut bot, packet);
225 for packet in &bot.instruction_queue {
226 write.write_all(packet.to_string().as_bytes()).await?;
227 }
228 bot.instruction_queue.clear();
229 }
230 }
231 }
232}