|
| 1 | +/* Simple networked game example using ENet (http://enet.bespin.org/). |
| 2 | + * |
| 3 | + * You will need enet installed to run this demo. |
| 4 | + * |
| 5 | + * This example is based on http://enet.bespin.org/Tutorial.html |
| 6 | + * |
| 7 | + * To try this example, first run ex_enet_server. |
| 8 | + * Then start multiple instances of ex_enet_client. |
| 9 | + */ |
| 10 | +#include <stdio.h> |
| 11 | +#include <stdlib.h> |
| 12 | +#include <enet/enet.h> |
| 13 | +#include "allegro5/allegro.h" |
| 14 | +#include "allegro5/allegro_primitives.h" |
| 15 | + |
| 16 | +#include "common.c" |
| 17 | +#include "enet_common.h" |
| 18 | + |
| 19 | +static ENetHost* create_client(void) |
| 20 | +{ |
| 21 | + ENetHost * client; |
| 22 | + client = enet_host_create(NULL /* create a client host */, |
| 23 | + 1 /* only allow 1 outgoing connection */, |
| 24 | + 2 /* allow up 2 channels to be used, 0 and 1 */, |
| 25 | + 57600 / 8 /* 56K modem with 56 Kbps downstream bandwidth */, |
| 26 | + 14400 / 8 /* 56K modem with 14 Kbps upstream bandwidth */); |
| 27 | + |
| 28 | + if (client == NULL) |
| 29 | + abort_example("Client: Failed to create the client.\n"); |
| 30 | + |
| 31 | + return client; |
| 32 | +} |
| 33 | + |
| 34 | +static void disconnect_client(ENetHost *client, ENetPeer *server) |
| 35 | +{ |
| 36 | + enet_peer_disconnect(server, 0); |
| 37 | + |
| 38 | + /* Allow up to 3 seconds for the disconnect to succeed |
| 39 | + * and drop any packets received packets. |
| 40 | + */ |
| 41 | + ENetEvent event; |
| 42 | + while (enet_host_service (client, &event, 3000) > 0) { |
| 43 | + switch (event.type) { |
| 44 | + case ENET_EVENT_TYPE_RECEIVE: |
| 45 | + enet_packet_destroy(event.packet); |
| 46 | + break; |
| 47 | + case ENET_EVENT_TYPE_DISCONNECT: |
| 48 | + puts("Client: Disconnect succeeded."); |
| 49 | + return; |
| 50 | + case ENET_EVENT_TYPE_NONE: |
| 51 | + case ENET_EVENT_TYPE_CONNECT: |
| 52 | + break; |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + // failed to disconnect gracefully, force the connection closed |
| 57 | + enet_peer_reset(server); |
| 58 | +} |
| 59 | + |
| 60 | +static ENetPeer* connect_client(ENetHost *client, int port) |
| 61 | +{ |
| 62 | + ENetAddress address; |
| 63 | + ENetEvent event; |
| 64 | + ENetPeer *server; |
| 65 | + enet_address_set_host(&address, "localhost"); |
| 66 | + address.port = port; |
| 67 | + /* Initiate the connection, allocating the two channels 0 and 1. */ |
| 68 | + server = enet_host_connect(client, & address, 2, 0); |
| 69 | + if (server == NULL) |
| 70 | + abort_example("Client: No available peers for initiating an ENet connection.\n"); |
| 71 | + |
| 72 | + /* Wait up to 5 seconds for the connection attempt to succeed. */ |
| 73 | + if (enet_host_service(client, & event, 5000) > 0 && |
| 74 | + event.type == ENET_EVENT_TYPE_CONNECT) |
| 75 | + { |
| 76 | + printf("Client: Connected to %x:%u.\n", |
| 77 | + event.peer->address.host, |
| 78 | + event.peer->address.port); |
| 79 | + } |
| 80 | + else |
| 81 | + { |
| 82 | + /* Either the 5 seconds are up or a disconnect event was */ |
| 83 | + /* received. Reset the peer in the event the 5 seconds */ |
| 84 | + /* had run out without any significant event. */ |
| 85 | + enet_peer_reset(server); |
| 86 | + abort_example("Client: Connection to server failed."); |
| 87 | + } |
| 88 | + |
| 89 | + return server; |
| 90 | +} |
| 91 | + |
| 92 | +static void send_receive(ENetHost *client) |
| 93 | +{ |
| 94 | + ENetEvent event; |
| 95 | + ServerMessage *msg; |
| 96 | + |
| 97 | + // Check if we have any queued incoming messages, but do not wait otherwise. |
| 98 | + // This also sends outgoing messages queued with enet_peer_send. |
| 99 | + while (enet_host_service(client, &event, 0) > 0) { |
| 100 | + // clients only care about incoming packets, they will not receive |
| 101 | + // connect/disconnect events. |
| 102 | + if (event.type == ENET_EVENT_TYPE_RECEIVE) { |
| 103 | + msg = (ServerMessage*)event.packet->data; |
| 104 | + |
| 105 | + switch (msg->type) { |
| 106 | + case POSITION_UPDATE: |
| 107 | + players[msg->player_id].x = msg->x; |
| 108 | + players[msg->player_id].y = msg->y; |
| 109 | + break; |
| 110 | + case PLAYER_JOIN: |
| 111 | + printf("Client: player #%d joined\n", msg->player_id); |
| 112 | + players[msg->player_id].active = true; |
| 113 | + players[msg->player_id].x = msg->x; |
| 114 | + players[msg->player_id].y = msg->y; |
| 115 | + players[msg->player_id].color = msg->color; |
| 116 | + break; |
| 117 | + case PLAYER_LEAVE: |
| 118 | + printf("Client: player #%d left\n", msg->player_id); |
| 119 | + players[msg->player_id].active = false; |
| 120 | + break; |
| 121 | + } |
| 122 | + |
| 123 | + /* Clean up the packet now that we're done using it. */ |
| 124 | + enet_packet_destroy(event.packet); |
| 125 | + } |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | +int main(int argc, char **argv) |
| 130 | +{ |
| 131 | + ALLEGRO_DISPLAY *display; |
| 132 | + ALLEGRO_TIMER *timer; |
| 133 | + ALLEGRO_EVENT_QUEUE *queue; |
| 134 | + ALLEGRO_EVENT event; |
| 135 | + ENetHost *client; |
| 136 | + ENetPeer *server; |
| 137 | + bool update = true; // when true, update positions and render |
| 138 | + bool done = false; // when true, client exits |
| 139 | + int dx = 0, dy = 0; // movement direction |
| 140 | + int port = DEFAULT_PORT; |
| 141 | + |
| 142 | + if (argc == 2) { |
| 143 | + port = atoi(argv[1]); |
| 144 | + } |
| 145 | + else if (argc > 2) |
| 146 | + abort_example("Usage: %s [portnum]", argv[0]); |
| 147 | + |
| 148 | + |
| 149 | + // --- allegro setup --- |
| 150 | + if (!al_init()) |
| 151 | + abort_example("Could not init Allegro.\n"); |
| 152 | + |
| 153 | + init_platform_specific(); |
| 154 | + |
| 155 | + al_install_keyboard(); |
| 156 | + al_init_primitives_addon(); |
| 157 | + |
| 158 | + // Create a new display that we can render the image to. |
| 159 | + display = al_create_display(SCREEN_W, SCREEN_H); |
| 160 | + if (!display) |
| 161 | + abort_example("Error creating display\n"); |
| 162 | + |
| 163 | + timer = al_create_timer(1.0 / FPS); // Run at 30FPS |
| 164 | + queue = al_create_event_queue(); |
| 165 | + |
| 166 | + al_register_event_source(queue, al_get_keyboard_event_source()); |
| 167 | + al_register_event_source(queue, al_get_display_event_source(display)); |
| 168 | + al_register_event_source(queue, al_get_timer_event_source(timer)); |
| 169 | + al_start_timer(timer); |
| 170 | + |
| 171 | + // --- enet setup --- |
| 172 | + if (enet_initialize() != 0) |
| 173 | + abort_example("An error occurred while initializing ENet.\n"); |
| 174 | + |
| 175 | + client = create_client(); |
| 176 | + server = connect_client(client, port); |
| 177 | + |
| 178 | + // --- game loop --- |
| 179 | + bool direction_changed = false; |
| 180 | + while (!done) { |
| 181 | + al_wait_for_event(queue, &event); // Wait for and get an event. |
| 182 | + |
| 183 | + switch (event.type) { |
| 184 | + case ALLEGRO_EVENT_DISPLAY_CLOSE: |
| 185 | + done = true; |
| 186 | + break; |
| 187 | + case ALLEGRO_EVENT_KEY_DOWN: |
| 188 | + switch (event.keyboard.keycode) { |
| 189 | + case ALLEGRO_KEY_W: dy -= 1; direction_changed = true; break; |
| 190 | + case ALLEGRO_KEY_S: dy += 1; direction_changed = true; break; |
| 191 | + case ALLEGRO_KEY_A: dx -= 1; direction_changed = true; break; |
| 192 | + case ALLEGRO_KEY_D: dx += 1; direction_changed = true; break; |
| 193 | + } |
| 194 | + break; |
| 195 | + case ALLEGRO_EVENT_KEY_UP: |
| 196 | + switch (event.keyboard.keycode) { |
| 197 | + case ALLEGRO_KEY_W: dy += 1; direction_changed = true; break; |
| 198 | + case ALLEGRO_KEY_S: dy -= 1; direction_changed = true; break; |
| 199 | + case ALLEGRO_KEY_A: dx += 1; direction_changed = true; break; |
| 200 | + case ALLEGRO_KEY_D: dx -= 1; direction_changed = true; break; |
| 201 | + } |
| 202 | + break; |
| 203 | + case (ALLEGRO_EVENT_TIMER): |
| 204 | + update = true; |
| 205 | + break; |
| 206 | + } |
| 207 | + |
| 208 | + // update, but only if the event queue is empty |
| 209 | + if (update && al_is_event_queue_empty(queue)) { |
| 210 | + update = false; |
| 211 | + |
| 212 | + // if player changed direction this frame, notify the server. |
| 213 | + // only check once per frame to stop clients from flooding the server |
| 214 | + if (direction_changed) { |
| 215 | + direction_changed = false; |
| 216 | + |
| 217 | + ClientMessage msg = { dx, dy }; |
| 218 | + |
| 219 | + ENetPacket *packet = enet_packet_create(&msg, |
| 220 | + sizeof(msg), |
| 221 | + ENET_PACKET_FLAG_RELIABLE); |
| 222 | + |
| 223 | + enet_peer_send(server, 0, packet); |
| 224 | + } |
| 225 | + |
| 226 | + // this will send our queued direction message if we have one, and get |
| 227 | + // position updates for other clients |
| 228 | + send_receive(client); |
| 229 | + |
| 230 | + // draw each player |
| 231 | + al_clear_to_color(al_map_rgb_f(0, 0, 0)); |
| 232 | + for (int i = 0; i < MAX_PLAYER_COUNT; i++) { |
| 233 | + if (!players[i].active) continue; |
| 234 | + |
| 235 | + int x = players[i].x; |
| 236 | + int y = players[i].y; |
| 237 | + ALLEGRO_COLOR color = players[i].color; |
| 238 | + al_draw_filled_circle(x, y, PLAYER_SIZE, color); |
| 239 | + } |
| 240 | + al_flip_display(); |
| 241 | + } |
| 242 | + } |
| 243 | + |
| 244 | + disconnect_client(client, server); |
| 245 | + enet_host_destroy(client); |
| 246 | + enet_deinitialize(); |
| 247 | +} |
| 248 | + |
| 249 | +/* vim: set sts=3 sw=3 et: */ |
0 commit comments