MULTITHREADED FOR EPICNESSSSSS
This commit is contained in:
BIN
deel2.tar.gz
BIN
deel2.tar.gz
Binary file not shown.
@@ -12,6 +12,10 @@
|
|||||||
|
|
||||||
#include "rooster.h"
|
#include "rooster.h"
|
||||||
|
|
||||||
|
int same_coordinate(coordinate a, coordinate b) {
|
||||||
|
return a.x == b.x && a.y == b.y;
|
||||||
|
}
|
||||||
|
|
||||||
int modulo(const int number, const int mod) {
|
int modulo(const int number, const int mod) {
|
||||||
int result = number % mod;
|
int result = number % mod;
|
||||||
if (result < 0) {
|
if (result < 0) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ typedef struct {
|
|||||||
int y;
|
int y;
|
||||||
} coordinate;
|
} coordinate;
|
||||||
|
|
||||||
|
// Start at 1 since the color 0 is reserved for no_color.
|
||||||
typedef enum {
|
typedef enum {
|
||||||
BLACK = 1,
|
BLACK = 1,
|
||||||
WHITE = 2,
|
WHITE = 2,
|
||||||
@@ -32,6 +33,19 @@ typedef struct {
|
|||||||
rooster *game_map;
|
rooster *game_map;
|
||||||
} game_maps;
|
} game_maps;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks if 2 coordinates are the same.
|
||||||
|
*
|
||||||
|
* Input:
|
||||||
|
* a: coordinate 1
|
||||||
|
* b: coordinate 2
|
||||||
|
*
|
||||||
|
* Returns:
|
||||||
|
* If they point to the same location: 1
|
||||||
|
* Otherwise: 0
|
||||||
|
*/
|
||||||
|
int same_coordinate(coordinate a, coordinate b);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A proper modulo function.
|
* A proper modulo function.
|
||||||
* The one provided by c: '%' doesn't work according to the mathematical definition.
|
* The one provided by c: '%' doesn't work according to the mathematical definition.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ static rooster *get_maze(void) {
|
|||||||
FILE *fh = fopen("assets/maze.txt", "r");
|
FILE *fh = fopen("assets/maze.txt", "r");
|
||||||
if (fh == NULL) {
|
if (fh == NULL) {
|
||||||
perror("loading maze");
|
perror("loading maze");
|
||||||
exit(EXIT_FAILURE);
|
return NULL;
|
||||||
}
|
}
|
||||||
rooster *rp = grid_from_file(fh);
|
rooster *rp = grid_from_file(fh);
|
||||||
fclose(fh);
|
fclose(fh);
|
||||||
@@ -29,7 +29,7 @@ static rooster *get_maze(void) {
|
|||||||
// 3. Bepaal of het lezen van het rooster is gelukt.
|
// 3. Bepaal of het lezen van het rooster is gelukt.
|
||||||
if (rp == NULL) {
|
if (rp == NULL) {
|
||||||
fprintf(stderr, "Kan rooster niet maken.\n");
|
fprintf(stderr, "Kan rooster niet maken.\n");
|
||||||
exit(EXIT_FAILURE);
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rp;
|
return rp;
|
||||||
@@ -53,7 +53,8 @@ static void maze_runner_beweeg(rooster *rp, int dx, int dy) {
|
|||||||
|
|
||||||
if (playerx == -1 || playery == -1) {
|
if (playerx == -1 || playery == -1) {
|
||||||
printf("Player not found!");
|
printf("Player not found!");
|
||||||
exit(1);
|
rooster_zet_toestand(rp, STATE_BEGIN);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rooster_bevat(rp, playerx + dx, playery + dy) == 1) {
|
if (rooster_bevat(rp, playerx + dx, playery + dy) == 1) {
|
||||||
@@ -113,11 +114,16 @@ static void speel_maze(rooster *rp) {
|
|||||||
void maze_runner(void) {
|
void maze_runner(void) {
|
||||||
// Voorbereiding.
|
// Voorbereiding.
|
||||||
rooster *rp = get_maze();
|
rooster *rp = get_maze();
|
||||||
|
if (rp == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
show_grid(rp);
|
show_grid(rp);
|
||||||
rooster_zet_toestand(rp, STATE_AAN_HET_SPELEN);
|
rooster_zet_toestand(rp, STATE_AAN_HET_SPELEN);
|
||||||
|
|
||||||
// Game zelf.
|
// Game zelf.
|
||||||
speel_maze(rp);
|
while (rooster_vraag_toestand(rp) == STATE_AAN_HET_SPELEN) {
|
||||||
|
speel_maze(rp);
|
||||||
|
}
|
||||||
|
|
||||||
// Exit game.
|
// Exit game.
|
||||||
game_exit_screen(rp);
|
game_exit_screen(rp);
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ static int OFFSET_X = 5;
|
|||||||
* menu: A pointer to the menu grid.
|
* menu: A pointer to the menu grid.
|
||||||
* game: The game you want to launch.
|
* game: The game you want to launch.
|
||||||
*/
|
*/
|
||||||
static void launch_game(rooster *menu, const game game) {
|
static void launch_game(const game game) {
|
||||||
switch (game) {
|
switch (game) {
|
||||||
case GAME_MAZE_RUNNER:
|
case GAME_MAZE_RUNNER:
|
||||||
maze_runner();
|
maze_runner();
|
||||||
@@ -131,12 +131,13 @@ static int navigate_menu(rooster *menu) {
|
|||||||
menu_move(1);
|
menu_move(1);
|
||||||
break;
|
break;
|
||||||
case KEY_ENTER:
|
case KEY_ENTER:
|
||||||
|
case '\n':
|
||||||
case 'f':
|
case 'f':
|
||||||
case 'F':
|
case 'F':
|
||||||
if (SELECTED_GAME == GAME_QUIT) {
|
if (SELECTED_GAME == GAME_QUIT) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
launch_game(menu, SELECTED_GAME);
|
launch_game(SELECTED_GAME);
|
||||||
break;
|
break;
|
||||||
case KEY_BACKSPACE:
|
case KEY_BACKSPACE:
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
206
snake.c
206
snake.c
@@ -6,8 +6,9 @@
|
|||||||
|
|
||||||
#include <ncurses.h>
|
#include <ncurses.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "grid_game_engine.h"
|
#include "grid_game_engine.h"
|
||||||
|
|
||||||
@@ -38,6 +39,8 @@ static coordinate SNAKE_TAIL;
|
|||||||
static coordinate MESSAGE_LOCATION = {0,0};
|
static coordinate MESSAGE_LOCATION = {0,0};
|
||||||
static int MAP_HEIGHT = 20;
|
static int MAP_HEIGHT = 20;
|
||||||
static int MAP_WIDTH = 20;
|
static int MAP_WIDTH = 20;
|
||||||
|
static rooster *GRID;
|
||||||
|
static pthread_mutex_t MUTEX;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create a snake body part.
|
* Create a snake body part.
|
||||||
@@ -75,11 +78,11 @@ static direction get_body_part_direction(const char body_part) {
|
|||||||
* Returns:
|
* Returns:
|
||||||
* A pointer to the grid.
|
* A pointer to the grid.
|
||||||
*/
|
*/
|
||||||
static rooster *create_grid(void) {
|
static void create_grid(void) {
|
||||||
const int grid_size = (MAP_WIDTH + 1) * MAP_HEIGHT + 1;
|
const int grid_size = (MAP_WIDTH + 1) * MAP_HEIGHT + 1;
|
||||||
char *map = malloc(grid_size * sizeof(char));
|
char *map = malloc(grid_size * sizeof(char));
|
||||||
if (map == NULL) {
|
if (map == NULL) {
|
||||||
return NULL;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 1; i <= (MAP_WIDTH + 1) * MAP_HEIGHT; i++) {
|
for (int i = 1; i <= (MAP_WIDTH + 1) * MAP_HEIGHT; i++) {
|
||||||
@@ -105,17 +108,16 @@ static rooster *create_grid(void) {
|
|||||||
|
|
||||||
map[grid_size - 1] = '\0';
|
map[grid_size - 1] = '\0';
|
||||||
|
|
||||||
rooster *grid = grid_from_string(map);
|
GRID = grid_from_string(map);
|
||||||
free(map);
|
free(map);
|
||||||
|
|
||||||
rooster_plaats(grid, rooster_breedte(grid) / 2, rooster_hoogte(grid) / 2, get_body_part(CURRENT_DIRECTION));
|
rooster_plaats(GRID, rooster_breedte(GRID) / 2, rooster_hoogte(GRID) / 2, get_body_part(CURRENT_DIRECTION));
|
||||||
return grid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static void generate_food(rooster *gp) {
|
static void generate_food() {
|
||||||
coordinate empty_spots[MAP_HEIGHT * MAP_WIDTH];
|
coordinate empty_spots[MAP_HEIGHT * MAP_WIDTH];
|
||||||
|
|
||||||
// Usable as index when initialized like this.
|
// Usable as index when initialized like this.
|
||||||
@@ -123,7 +125,7 @@ static void generate_food(rooster *gp) {
|
|||||||
|
|
||||||
for (int x = 0; x < MAP_WIDTH; x++) {
|
for (int x = 0; x < MAP_WIDTH; x++) {
|
||||||
for (int y = 0; y < MAP_HEIGHT; y++) {
|
for (int y = 0; y < MAP_HEIGHT; y++) {
|
||||||
if (rooster_kijk(gp, x, y) == CELL_EMPTY) {
|
if (rooster_kijk(GRID, x, y) == CELL_EMPTY) {
|
||||||
coordinate new_spot = {x, y};
|
coordinate new_spot = {x, y};
|
||||||
empty_spots[available_spots] = new_spot;
|
empty_spots[available_spots] = new_spot;
|
||||||
available_spots++;
|
available_spots++;
|
||||||
@@ -135,7 +137,7 @@ static void generate_food(rooster *gp) {
|
|||||||
|
|
||||||
const coordinate food_location = empty_spots[modulo(rand(), available_spots)];
|
const coordinate food_location = empty_spots[modulo(rand(), available_spots)];
|
||||||
|
|
||||||
rooster_plaats(gp, food_location.x, food_location.y, CELL_FOOD);
|
rooster_plaats(GRID, food_location.x, food_location.y, CELL_FOOD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -147,30 +149,33 @@ static void generate_food(rooster *gp) {
|
|||||||
* Side Effects:
|
* Side Effects:
|
||||||
* Seed random with the current time.
|
* Seed random with the current time.
|
||||||
*/
|
*/
|
||||||
static rooster *initialize(void) {
|
static void initialize(void) {
|
||||||
srand(time(NULL));
|
srand(time(NULL));
|
||||||
|
|
||||||
rooster *grid = create_grid();
|
create_grid();
|
||||||
|
|
||||||
if (grid == NULL) {
|
if (GRID == NULL) {
|
||||||
return NULL;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CURRENT_DIRECTION = DIRECTION_DOWN;
|
||||||
|
PREVIOUS_DIRECTION = DIRECTION_DOWN;
|
||||||
|
|
||||||
// Set snake head and snake tail.
|
// Set snake head and snake tail.
|
||||||
rooster_zoek(grid, get_body_part(CURRENT_DIRECTION), &SNAKE_HEAD.x, &SNAKE_HEAD.y);
|
rooster_zoek(GRID, get_body_part(CURRENT_DIRECTION), &SNAKE_HEAD.x, &SNAKE_HEAD.y);
|
||||||
SNAKE_TAIL.x = SNAKE_HEAD.x;
|
SNAKE_TAIL.x = SNAKE_HEAD.x;
|
||||||
SNAKE_TAIL.y = SNAKE_HEAD.y;
|
SNAKE_TAIL.y = SNAKE_HEAD.y;
|
||||||
|
|
||||||
generate_food(grid);
|
generate_food();
|
||||||
|
|
||||||
if (SNAKE_HEAD.x == -1) {
|
if (SNAKE_HEAD.x == -1) {
|
||||||
return NULL;
|
free(GRID);
|
||||||
|
GRID = NULL;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MESSAGE_LOCATION.y = rooster_hoogte(grid) + OFFSET_Y + 5;
|
MESSAGE_LOCATION.y = rooster_hoogte(GRID) + OFFSET_Y + 5;
|
||||||
MESSAGE_LOCATION.x = OFFSET_X - 5 >= 0 ? OFFSET_X - 5 : 0;
|
MESSAGE_LOCATION.x = OFFSET_X - 5 >= 0 ? OFFSET_X - 5 : 0;
|
||||||
|
|
||||||
return grid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -196,16 +201,16 @@ static snake_action collision_check(char c) {
|
|||||||
return SNAKE_DIE;
|
return SNAKE_DIE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_snake(rooster *gp, coordinate new_head, snake_action action) {
|
static void update_snake(coordinate new_head, snake_action action) {
|
||||||
if (action == SNAKE_DIE) {
|
if (action == SNAKE_DIE) {
|
||||||
rooster_zet_toestand(gp, STATE_VERLOREN);
|
rooster_zet_toestand(GRID, STATE_VERLOREN);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == SNAKE_MOVE) {
|
if (action == SNAKE_MOVE) {
|
||||||
coordinate new_tail = SNAKE_TAIL;
|
coordinate new_tail = SNAKE_TAIL;
|
||||||
|
|
||||||
switch (get_body_part_direction(rooster_kijk(gp, SNAKE_TAIL.x, SNAKE_TAIL.y))) {
|
switch (get_body_part_direction(rooster_kijk(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y))) {
|
||||||
case DIRECTION_UP:
|
case DIRECTION_UP:
|
||||||
new_tail.y--;
|
new_tail.y--;
|
||||||
break;
|
break;
|
||||||
@@ -220,105 +225,128 @@ static void update_snake(rooster *gp, coordinate new_head, snake_action action)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
rooster_plaats(gp, SNAKE_TAIL.x, SNAKE_TAIL.y, CELL_EMPTY);
|
rooster_plaats(GRID, SNAKE_TAIL.x, SNAKE_TAIL.y, CELL_EMPTY);
|
||||||
SNAKE_TAIL = new_tail;
|
SNAKE_TAIL = new_tail;
|
||||||
}
|
}
|
||||||
|
|
||||||
// New head placed after tail moves. It can occupy the empty space created by the tail moving.
|
// New head placed after tail moves. It can occupy the empty space created by the tail moving.
|
||||||
rooster_plaats(gp, new_head.x, new_head.y, get_body_part(CURRENT_DIRECTION));
|
rooster_plaats(GRID, new_head.x, new_head.y, get_body_part(CURRENT_DIRECTION));
|
||||||
SNAKE_HEAD = new_head;
|
SNAKE_HEAD = new_head;
|
||||||
PREVIOUS_DIRECTION = CURRENT_DIRECTION;
|
PREVIOUS_DIRECTION = CURRENT_DIRECTION;
|
||||||
|
|
||||||
if (action == SNAKE_EAT) {
|
if (action == SNAKE_EAT) {
|
||||||
generate_food(gp);
|
generate_food();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void snake_move(rooster *gp) {
|
static void *snake_move(void *arg) {
|
||||||
coordinate new_head = SNAKE_HEAD;
|
while (rooster_vraag_toestand(GRID) == STATE_AAN_HET_SPELEN) {
|
||||||
switch (CURRENT_DIRECTION) {
|
usleep(250000);
|
||||||
case DIRECTION_UP:
|
pthread_mutex_lock(&MUTEX);
|
||||||
new_head.y--;
|
coordinate new_head = SNAKE_HEAD;
|
||||||
break;
|
switch (CURRENT_DIRECTION) {
|
||||||
case DIRECTION_RIGHT:
|
case DIRECTION_UP:
|
||||||
new_head.x++;
|
new_head.y--;
|
||||||
break;
|
break;
|
||||||
case DIRECTION_DOWN:
|
case DIRECTION_RIGHT:
|
||||||
new_head.y++;
|
new_head.x++;
|
||||||
break;
|
break;
|
||||||
case DIRECTION_LEFT:
|
case DIRECTION_DOWN:
|
||||||
new_head.x--;
|
new_head.y++;
|
||||||
break;
|
break;
|
||||||
|
case DIRECTION_LEFT:
|
||||||
|
new_head.x--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
snake_action action = collision_check(rooster_kijk(GRID, new_head.x, new_head.y));
|
||||||
|
|
||||||
|
update_snake(new_head, action);
|
||||||
|
show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y);
|
||||||
|
pthread_mutex_unlock(&MUTEX);
|
||||||
}
|
}
|
||||||
|
return NULL;
|
||||||
snake_action action = collision_check(rooster_kijk(gp, new_head.x, new_head.y));
|
|
||||||
|
|
||||||
update_snake(gp, new_head, action);
|
|
||||||
show_grid_on_offset(gp, OFFSET_X, OFFSET_Y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void turn_snake(rooster *gp, direction dir) {
|
static void turn_snake(direction dir) {
|
||||||
if ((direction)modulo(dir + 2, DIRECTION_COUNT) == PREVIOUS_DIRECTION) {
|
if ((direction)modulo(dir + 2, DIRECTION_COUNT) == PREVIOUS_DIRECTION
|
||||||
|
&& !same_coordinate(SNAKE_HEAD, SNAKE_TAIL)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
rooster_plaats(gp, SNAKE_HEAD.x, SNAKE_HEAD.y, get_body_part(dir));
|
rooster_plaats(GRID, SNAKE_HEAD.x, SNAKE_HEAD.y, get_body_part(dir));
|
||||||
CURRENT_DIRECTION = dir;
|
CURRENT_DIRECTION = dir;
|
||||||
show_grid_on_offset(gp, OFFSET_X, OFFSET_Y);
|
show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void play_snake(rooster *gp) {
|
static void *play_snake(void *arg) {
|
||||||
timeout(500);
|
while (rooster_vraag_toestand(GRID) == STATE_AAN_HET_SPELEN) {
|
||||||
|
timeout(1);
|
||||||
switch (getch()) {
|
int c = getch();
|
||||||
case KEY_UP: // fallthrough
|
pthread_mutex_lock(&MUTEX);
|
||||||
case 'w':
|
switch (c) {
|
||||||
case 'W':
|
case KEY_UP: // fallthrough
|
||||||
turn_snake(gp, DIRECTION_UP);
|
case 'w':
|
||||||
break;
|
case 'W':
|
||||||
case KEY_DOWN: // fallthrough
|
turn_snake(DIRECTION_UP);
|
||||||
case 's':
|
break;
|
||||||
case 'S':
|
case KEY_DOWN: // fallthrough
|
||||||
turn_snake(gp, DIRECTION_DOWN);
|
case 's':
|
||||||
break;
|
case 'S':
|
||||||
case KEY_LEFT: // fallthrough
|
turn_snake(DIRECTION_DOWN);
|
||||||
case 'a':
|
break;
|
||||||
case 'A':
|
case KEY_LEFT: // fallthrough
|
||||||
turn_snake(gp, DIRECTION_LEFT);
|
case 'a':
|
||||||
break;
|
case 'A':
|
||||||
case KEY_RIGHT: // fallthrough
|
turn_snake(DIRECTION_LEFT);
|
||||||
case 'd':
|
break;
|
||||||
case 'D':
|
case KEY_RIGHT: // fallthrough
|
||||||
turn_snake(gp, DIRECTION_RIGHT);
|
case 'd':
|
||||||
break;
|
case 'D':
|
||||||
case KEY_BACKSPACE:
|
turn_snake(DIRECTION_RIGHT);
|
||||||
rooster_zet_toestand(gp, STATE_QUIT);
|
break;
|
||||||
break;
|
case KEY_BACKSPACE:
|
||||||
case ERR:
|
rooster_zet_toestand(GRID, STATE_QUIT);
|
||||||
snake_move(gp);
|
break;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&MUTEX);
|
||||||
}
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//todo: ?? Win condition?
|
||||||
void snake(void) {
|
void snake(void) {
|
||||||
rooster* const gp = initialize();
|
initialize();
|
||||||
if (gp == NULL) {
|
if (GRID == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Created necessary threads.
|
||||||
|
pthread_mutex_init(&MUTEX, NULL);
|
||||||
|
pthread_t user_input;
|
||||||
|
pthread_t game_tick;
|
||||||
|
|
||||||
|
// Show game.
|
||||||
erase();
|
erase();
|
||||||
|
show_grid_on_offset(GRID, OFFSET_X, OFFSET_Y);
|
||||||
|
mvaddstr(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, "Press SPACEBAR to start playing.");
|
||||||
|
|
||||||
//todo: ?? Win condition?
|
rooster_zet_toestand(GRID, STATE_AAN_HET_SPELEN);
|
||||||
|
|
||||||
show_grid_on_offset(gp, OFFSET_X, OFFSET_Y);
|
// Allow turning before you let the snake move.
|
||||||
|
pthread_create(&user_input, NULL, play_snake, NULL);
|
||||||
|
|
||||||
mvprintw(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, "Press SPACEBAR to start playing.");
|
|
||||||
while (getch() != ' ') {}
|
while (getch() != ' ') {}
|
||||||
|
|
||||||
rooster_zet_toestand(gp, STATE_AAN_HET_SPELEN);
|
// Cleanup the start playing message.
|
||||||
while (rooster_vraag_toestand(gp) == STATE_AAN_HET_SPELEN) {
|
mvaddstr(MESSAGE_LOCATION.y, MESSAGE_LOCATION.x, " ");
|
||||||
play_snake(gp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// game_exit_screen(gp);
|
pthread_create(&game_tick, NULL, snake_move, NULL);
|
||||||
|
|
||||||
rooster_klaar(gp);
|
// Cleanup game thread logic.
|
||||||
|
pthread_join(game_tick, NULL);
|
||||||
|
pthread_join(user_input, NULL);
|
||||||
|
pthread_mutex_destroy(&MUTEX);
|
||||||
|
|
||||||
|
rooster_klaar(GRID);
|
||||||
graceful_exit();
|
graceful_exit();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user