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
// vim: noet

extern crate memmap2;

use std::fs::OpenOptions;
use std::mem::{size_of, transmute};
use std::result::Result;

use memmap2::MmapRaw;

pub mod ipc;
use ipc::IpcSharedMemory;

/**
 * Your bot’s interface to the game data.
 *
 * This class provides a safe interface to the shared memory used for fast communication with the
 * gameserver. Use the provided methods to obtain information about your snake’s surroundings or
 * the server configuration, and to access persistent memory.
 *
 * Please note that most methods return direct references to the shared memory structures. These
 * are specified and documented in the ipc module.
 */
pub struct Api<'a> {
    #[allow(dead_code)]
    mmap: MmapRaw,
    ipcdata: &'a mut IpcSharedMemory,
}

impl<'i> Api<'i> {
    /**
     * Construct a new Api instance from the given shared memory file.
     *
     * This function is used internally by the bot framework. Do not worry about it.
     */
    pub fn new(shmfilename: &str) -> Result<Api, String> {
        // open and map the shared memory
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .open(shmfilename)
            .map_err(|err| format!("Could not open shared memory file {shmfilename}: {err}"))?;

        let mmap = MmapRaw::map_raw(&file)
            .map_err(|err| format!("Could not create shared memory mmap: {err}"))?;

        // check the size of the shared memory. It must be large enough to hold one complete
        // IpcSharedMemory structure.
        if mmap.len() < size_of::<ipc::IpcSharedMemory>() {
            return Err(format!(
                "Shared memory contains only {} bytes where {} bytes are required.",
                mmap.len(),
                size_of::<ipc::IpcSharedMemory>()
            ));
        }

        // map the shared memory to a IpcSharedMemory structure.
        let ipcdata =
            unsafe { &mut *transmute::<*mut u8, *mut ipc::IpcSharedMemory>(mmap.as_mut_ptr()) };

        Ok(Api { mmap, ipcdata })
    }

    /**
     * Get a reference to the server config data.
     *
     * The returned structure contains information about the server
     * configuration and static world information.
     */
    pub fn get_server_config(&self) -> &ipc::IpcServerConfig {
        &self.ipcdata.server_config
    }

    /**
     * Get a pointer to the self information.
     *
     * The returned structure contains information about your snake and
     * parameters of the world.
     */
    pub fn get_self_info(&self) -> &ipc::IpcSelfInfo {
        &self.ipcdata.self_info
    }

    /**
     * Get a reference to the array of food items around your snake’s head.
     *
     * The items are sorted by the distance from your snake’s head, so the first entry is the
     * closest item.
     *
     * Only the valid entries in the shared memory are returned.
     */
    pub fn get_food(&self) -> &[ipc::IpcFoodInfo] {
        &self.ipcdata.food_info[0..self.ipcdata.food_count as usize]
    }

    /**
     * Get a reference to the array of snake segments around your snake’s head.
     *
     * The items are sorted by the distance from your snake’s head, so the first entry is the
     * closest item.
     *
     * Only the valid entries in the shared memory are returned.
     */
    pub fn get_segments(&self) -> &[ipc::IpcSegmentInfo] {
        &self.ipcdata.segment_info[0..self.ipcdata.segment_count as usize]
    }

    /**
     * Get a reference to the array of bot information structures.
     *
     * Only the valid entries in the shared memory are returned.
     */
    pub fn get_bot_info(&self) -> &[ipc::IpcBotInfo] {
        &self.ipcdata.bot_info[0..self.ipcdata.bot_count as usize]
    }

    /**
     * Remove all color entries from the shared memory.
     *
     * This must be called in your [`crate::usercode::init()`] function to remove the default color
     * in case you want to set your own.
     */
    pub fn clear_colors(&mut self) {
        self.ipcdata.color_count = 0;
    }

    /**
     * Add a color to your snake’s color sequence.
     *
     * Call this multiple times from [`crate::usercode::init()`] to set up your snake’s colors. The
     * provided sequence will be repeated along your snake if it has more sequence than colors were
     * specified. You can set up to [`ipc::IPC_COLOR_MAX_COUNT`] colors.
     */
    pub fn add_color(&mut self, r: u8, g: u8, b: u8) {
        self.ipcdata.colors[self.ipcdata.color_count as usize] = ipc::IpcColor { r, g, b };
        self.ipcdata.color_count += 1;
    }

    /**
     * Get a reference to the persistent memory.
     *
     * You can use persistent memory to remember things across multiple lives
     * of your snake. It is saved after your snake dies (even when your code
     * crashes) and restored when it respawns.
     *
     * Note that the size this memory is very limited (given by the
     * [`ipc::IPC_PERSISTENT_MAX_BYTES`] constant). Use it wisely.
     */
    pub fn get_persistent_memory(&mut self) -> &mut [u8] {
        &mut self.ipcdata.persistent_data
    }

    /**
     * Send a log message.
     *
     * These messages will appear on the web interface and in the World update
     * stream when you provide your viewer key to the server.
     *
     * Rate limiting is enforced by the gameserver, so messages are dropped
     * when you send too many of them.
     */
    pub fn log(&mut self, text: &str) -> Result<(), String> {
        // determine length of stored data
        let startpos = self
            .ipcdata
            .log_data
            .iter()
            .position(|&b| b == b'\0')
            .ok_or_else(|| "Log memory is not properly initialized or corrupt".to_string())?;

        if startpos + text.len() + 2 > self.ipcdata.log_data.len() {
            return Err("Log memory too full to append the log message".to_owned());
        }

        self.ipcdata.log_data[startpos..startpos + text.len()].copy_from_slice(text.as_bytes());
        self.ipcdata.log_data[startpos + text.len()] = b'\n';
        self.ipcdata.log_data[startpos + text.len() + 1] = b'\0';

        Ok(())
    }
}