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

/*!
 * Welcome to the Rust framework documentation for Schlangenprogrammiernacht players!
 *
 * This documentation will guide you while you program your Schlangenprogrammiernacht (SPN) bot in Rust.
 *
 * The code you write in the webinterface will replace the [`usercode`] module, which must provide
 * the [`usercode::init()`] and [`usercode::step()`] functions. These will be called by the
 * framework when requested to do so by the gameserver.
 *
 * We recommend that you start by reading the documentation of the [`api::Api`] struct,
 * which provides safe access to the data provided by the gameserver.
 */

extern crate uds;

#[macro_use]
extern crate num_derive;
extern crate num;

use num::FromPrimitive;

use uds::UnixSeqpacketConn;

use std::mem::{size_of, transmute};

pub mod api;
use api::ipc::{IpcRequestType, IpcResponse, IpcResponseType};

pub mod usercode;
use usercode::{init, step};

static SPN_SHM_FILE: &str = "/spnshm/shm";
static SPN_SOCKET_FILE: &str = "/spnshm/socket";

fn mainloop(mut api: api::Api, socket: UnixSeqpacketConn) -> Result<(), String> {
    let mut running = true;
    let mut rxbuf = [0u8; size_of::<api::ipc::IpcRequest>()];

    while running {
        // receive messages from the Gameserver
        let (len, _truncated) = socket
            .recv(&mut rxbuf)
            .map_err(|err| format!("Failed to receive data from unix socket: {err}"))?;

        // length check
        if len == 0 {
            println!("Socket connection terminated by server.");
            break;
        } else if len < rxbuf.len() {
            println!("Error: packet is too short! Expected {} bytes, but received only {}. Packet ignored.",
			         rxbuf.len(), len);
            continue;
        }

        // check whether the data contains a valid enum value
        let request_value = unsafe { *transmute::<*const u8, *const u32>(rxbuf.as_ptr()) };

        let request_type = match api::ipc::IpcRequestType::from_u32(request_value) {
            Some(x) => x,
            None => {
                println!("Request type cannot be decoded!");
                continue;
            }
        };

        /*
        // reinterpret the received data as struct IpcRequest
        let request: &api::ipc::IpcRequest = unsafe {
            & *transmute::<*const u8, *const api::ipc::IpcRequest>(rxbuf.as_ptr())
        };
        */

        let angle: f32;
        let boost: bool;

        // execute the user functions corresponding to the request type
        match request_type {
            IpcRequestType::Init => {
                running = init(&mut api);
                angle = 0.0;
                boost = false;
            }
            IpcRequestType::Step => {
                // unfortunately, destructuring is not stable yet.
                let (tmp_running, tmp_angle, tmp_boost) = step(&mut api);
                running = tmp_running;
                angle = tmp_angle;
                boost = tmp_boost;
            }
        }

        // build the response structure
        let response = IpcResponse {
            response_type: match running {
                true => IpcResponseType::Ok,
                false => IpcResponseType::Error,
            },
            data: api::ipc::ResponseData {
                step: api::ipc::IpcStepResponse {
                    delta_angle: angle,
                    boost,
                },
            },
        };

        // reinterpret the response structure as byte array
        let txdata: &[u8] = unsafe {
            std::slice::from_raw_parts(
                (&response as *const IpcResponse) as *const u8,
                std::mem::size_of::<IpcResponse>(),
            )
        };

        // send the result packet
        socket
            .send(txdata)
            .map_err(|err| format!("Failed to send data to unix socket: {err}"))?;
    }

    Ok(())
}

fn main() -> Result<(), String> {
    /*
    // For cross-checking structure layout between different programming languages.
    println!("sizeof(IpcSelfInfo)     = {:8}", size_of::<api::ipc::IpcSelfInfo>());
    println!("sizeof(IpcServerConfig) = {:8}", size_of::<api::ipc::IpcServerConfig>());
    println!("sizeof(IpcFoodInfo)     = {:8}", size_of::<api::ipc::IpcFoodInfo>());
    println!("sizeof(IpcBotInfo)      = {:8}", size_of::<api::ipc::IpcBotInfo>());
    println!("sizeof(IpcSegmentInfo)  = {:8}", size_of::<api::ipc::IpcSegmentInfo>());
    println!("sizeof(IpcColor)        = {:8}", size_of::<api::ipc::IpcColor>());
    println!("sizeof(IpcSharedMemory) = {:8}", size_of::<api::ipc::IpcSharedMemory>());
    println!("sizeof(IpcRequest)      = {:8}", size_of::<api::ipc::IpcRequest>());
    println!("sizeof(IpcStepResponse) = {:8}", size_of::<api::ipc::IpcStepResponse>());
    println!("sizeof(IpcResponse)     = {:8}", size_of::<api::ipc::IpcResponse>());
    println!("sizeof(bool)            = {:8}", size_of::<bool>());
    return;
    */

    let a = api::Api::new(SPN_SHM_FILE)?;

    let conn = UnixSeqpacketConn::connect(SPN_SOCKET_FILE)
        .map_err(|err| format!("Failed to connect to unix socket: {err}"))?;

    mainloop(a, conn)?;

    Ok(())
}