1use std::{
2 ffi::{c_char, c_int, CString},
3 slice,
4};
5
6#[no_mangle]
21pub extern "C" fn version(buffer: *mut u8, len: usize) -> isize {
22 let version = env!("CARGO_PKG_VERSION");
23 let version_bytes = version.as_bytes();
24 let required_len = version_bytes.len() + 1; if buffer.is_null() {
27 return required_len as isize;
28 }
29
30 if len < version_bytes.len() {
31 return -1;
32 }
33
34 unsafe {
35 let buffer_slice = slice::from_raw_parts_mut(buffer, required_len);
36 buffer_slice[..version_bytes.len()].copy_from_slice(version_bytes);
37 buffer_slice[version_bytes.len()] = 0; }
39
40 required_len as isize
41}
42
43type Callback = unsafe extern "C" fn(*const c_char, c_int);
44
45#[no_mangle]
53pub unsafe extern "C" fn version_string(callback: Callback) {
54 let version = env!("CARGO_PKG_VERSION");
55 let c_string = CString::new(version).expect("CString::new failed");
56 callback(c_string.as_ptr(), (version.len() + 1) as c_int);
57}
58
59#[no_mangle]
64pub extern "C" fn new_game_board(dimension: u32) -> *mut tictactoe::GameBoard {
65 Box::into_raw(Box::new(tictactoe::GameBoard::new(dimension)))
66}
67
68#[no_mangle]
69pub unsafe extern "C" fn free_game_board(game_board: *mut tictactoe::GameBoard) {
70 drop(Box::from_raw(game_board));
71}
72
73#[no_mangle]
74pub unsafe extern "C" fn get_game_board_dimension(game_board: *mut tictactoe::GameBoard) -> u32 {
75 (*game_board).get_dimension()
76}
77
78#[no_mangle]
79pub unsafe extern "C" fn get_game_board_value_at_index(
80 game_board: *mut tictactoe::GameBoard,
81 index: u32,
82) -> u32 {
83 (*game_board).get_with_index(index)
84}
85
86#[no_mangle]
87pub unsafe extern "C" fn get_game_board_with_value_at_index(
88 game_board: *mut tictactoe::GameBoard,
89 index: u32,
90 value: u32,
91) -> *mut tictactoe::GameBoard {
92 Box::into_raw(Box::new((*game_board).with_value_at_index(index, value)))
93}
94
95#[no_mangle]
96pub unsafe extern "C" fn get_game_board_is_full(game_board: *mut tictactoe::GameBoard) -> bool {
97 (*game_board).is_full()
98}
99
100#[no_mangle]
101pub unsafe extern "C" fn get_game_board_has_chain(
102 game_board: *mut tictactoe::GameBoard,
103 value: u32,
104) -> bool {
105 (*game_board).has_chain(value)
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_ffi_can_manage_game_board_lifecycle_with_raw_pointer() {
114 let board_ptr = new_game_board(3);
115 assert!(!board_ptr.is_null());
116 unsafe {
117 free_game_board(board_ptr);
118 }
119 }
120
121 #[test]
122 fn test_ffi_can_get_game_board_value_at_index() {
123 let board_ptr = new_game_board(3);
124 assert!(!board_ptr.is_null());
125 unsafe {
126 assert_eq!(get_game_board_value_at_index(board_ptr, 0), 0);
127 free_game_board(board_ptr);
128 }
129 }
130
131 #[test]
132 fn test_ffi_can_update_a_gameboard_immutably() {
133 let board_ptr = new_game_board(3);
134 unsafe {
135 let updated_board_ptr = get_game_board_with_value_at_index(board_ptr, 4, 2);
136 assert_eq!(get_game_board_value_at_index(board_ptr, 4), 0);
137 assert_eq!(get_game_board_value_at_index(updated_board_ptr, 4), 2);
138 free_game_board(board_ptr);
139 free_game_board(updated_board_ptr);
140 }
141 }
142
143 #[test]
144 fn test_ffi_can_check_empty_game_board_has_available_moves() {
145 let board_ptr = new_game_board(3);
146 unsafe {
147 assert!(!get_game_board_is_full(board_ptr));
148 free_game_board(board_ptr);
149 }
150 }
151
152 #[test]
153 fn test_ffi_can_check_full_game_board_has_no_available_moves() {
154 let mut board_ptr = new_game_board(3);
155 unsafe {
156 for i in 0..9 {
157 board_ptr = get_game_board_with_value_at_index(board_ptr, i, i % 2 + 1);
158 }
159 assert!(get_game_board_is_full(board_ptr));
160 }
161 }
162
163 #[test]
164 fn test_ffi_can_check_for_winning_chain() {
165 let mut board_ptr = new_game_board(3);
166 unsafe {
167 for i in 0..3 {
168 board_ptr = get_game_board_with_value_at_index(board_ptr, i, 1);
169 }
170 assert!(get_game_board_has_chain(board_ptr, 1));
171 free_game_board(board_ptr);
172 }
173 }
174}
175
176mod tictactoe {
181
182 #[derive(Clone)]
183 pub struct GameBoard {
184 dimension: u32,
185 content: Vec<Vec<u32>>,
186 }
187
188 impl GameBoard {
189 pub fn new(dimension: u32) -> Self {
190 let mut content = Vec::new();
191 for _ in 0..dimension {
192 let mut row = Vec::new();
193 for _ in 0..dimension {
194 row.push(0);
195 }
196 content.push(row);
197 }
198 GameBoard { dimension, content }
199 }
200
201 pub fn get_dimension(&self) -> u32 {
202 self.dimension
203 }
204
205 pub fn get(&self, row: u32, col: u32) -> u32 {
206 self.content[row as usize][col as usize]
207 }
208
209 pub fn get_with_index(&self, index: u32) -> u32 {
210 let row = index / self.dimension;
211 let col = index % self.dimension;
212 self.get(row, col)
213 }
214
215 pub fn with_value_at(&self, row: u32, col: u32, value: u32) -> Self {
216 let mut new_board = self.clone();
217 new_board.set(row, col, value);
218 new_board
219 }
220
221 pub fn with_value_at_index(&self, index: u32, value: u32) -> Self {
222 let mut new_board = self.clone();
223 new_board.set_with_index(index, value);
224 new_board
225 }
226
227 pub fn is_empty_at(&self, row: u32, col: u32) -> bool {
228 self.get(row, col) == 0
229 }
230
231 pub fn is_empty_at_index(&self, index: u32) -> bool {
232 self.get_with_index(index) == 0
233 }
234
235 fn set(&mut self, row: u32, col: u32, value: u32) {
236 self.content[row as usize][col as usize] = value;
237 }
238
239 fn set_with_index(&mut self, index: u32, value: u32) {
240 let row = index / self.dimension;
241 let col = index % self.dimension;
242 self.set(row, col, value)
243 }
244
245 pub fn is_full(&self) -> bool {
246 for row in &self.content {
247 for value in row {
248 if *value == 0 {
249 return false;
250 }
251 }
252 }
253 true
254 }
255
256 pub fn has_chain(&self, player: u32) -> bool {
257 let mut chain: u32;
259 for row_index in 0..self.dimension {
260 chain = 0;
261 for col_index in 0..self.dimension {
262 if self.get(row_index, col_index) == player {
263 chain += 1;
264 } else {
265 chain = 0;
266 }
267 if chain >= self.dimension {
268 return true;
269 }
270 }
271 }
272 for col_index in 0..self.dimension {
274 chain = 0;
275 for row_index in 0..self.dimension {
276 if self.get(row_index, col_index) == player {
277 chain += 1;
278 } else {
279 chain = 0;
280 }
281 if chain >= self.dimension {
282 return true;
283 }
284 }
285 }
286 chain = 0;
288 for offset in 0..self.dimension {
289 if self.get(offset, offset) == player {
290 chain += 1;
291 } else {
292 chain = 0;
293 }
294 if chain >= self.dimension {
295 return true;
296 }
297 }
298 chain = 0;
299 for offset in 0..self.dimension {
300 if self.get(offset, self.dimension - offset - 1) == player {
301 chain += 1;
302 } else {
303 chain = 0;
304 }
305 if chain >= self.dimension {
306 return true;
307 }
308 }
309 false
310 }
311 }
312
313 #[cfg(test)]
314 mod tests {
315 use super::*;
316
317 #[test]
318 fn test_new_game_board_creation() {
319 let board = GameBoard::new(3);
320 assert_eq!(board.dimension, 3);
321 assert_eq!(board.content.len(), 3);
322 for row in board.content {
323 assert_eq!(row.len(), 3);
324 for cell in row {
325 assert_eq!(cell, 0);
326 }
327 }
328 }
329
330 #[test]
331 fn test_new_game_board_empty() {
332 let board = GameBoard::new(0);
333 assert_eq!(board.dimension, 0);
334 assert!(board.content.is_empty());
335 }
336
337 #[test]
338 fn test_new_game_board_large() {
339 let dimension = 100;
340 let board = GameBoard::new(dimension);
341 assert_eq!(board.dimension, dimension);
342 assert_eq!(board.content.len(), dimension as usize);
343 for row in board.content {
344 assert_eq!(row.len(), dimension as usize);
345 for cell in row {
346 assert_eq!(cell, 0);
347 }
348 }
349 }
350
351 #[test]
352 fn test_set_and_retrieve_with_index() {
353 let mut board = GameBoard::new(3);
354 board.set_with_index(0, 99);
355 assert_eq!(board.get_with_index(0), 99);
356 }
357
358 #[test]
359 fn test_immutably_set_with_index() {
360 let board = GameBoard::new(3);
361 let new_board = board.with_value_at_index(0, 99);
362 assert_eq!(board.get_with_index(0), 0);
363 assert_eq!(new_board.get_with_index(0), 99);
364 }
365
366 #[test]
367 fn test_can_check_for_empty() {
368 let board = GameBoard::new(1);
369 assert_eq!(board.is_empty_at(0, 0), true);
370 assert_eq!(board.is_empty_at_index(0), true);
371 }
372
373 #[test]
374 fn test_can_check_for_full() {
375 let mut board = GameBoard::new(1);
376 board.set(0, 0, 1);
377 assert_eq!(board.is_full(), true);
378 }
379
380 #[test]
381 fn test_empty_board_has_no_chain() {
382 let board = GameBoard::new(3);
383 assert_eq!(board.has_chain(1), false);
384 }
385
386 #[test]
387 fn test_can_detect_winning_row_chain() {
388 let mut board = GameBoard::new(3);
389 for r in 0..3 {
390 board.set(r, 0, 1);
391 }
392 assert_eq!(board.has_chain(1), true);
393 }
394
395 #[test]
396 fn test_can_detect_winning_column_chain() {
397 let mut board = GameBoard::new(3);
398 for c in 0..3 {
399 board.set(1, c, 1);
400 }
401 assert_eq!(board.has_chain(1), true);
402 }
403
404 #[test]
405 fn test_can_detect_diagonal_winning_column_chain() {
406 let mut board = GameBoard::new(3);
407 for o in 0..3 {
408 board.set(o, o, 1);
409 }
410 assert_eq!(board.has_chain(1), true);
411 }
412 }
413}