Skip to content

Instantly share code, notes, and snippets.

@Jamesscn
Last active February 3, 2024 23:04
Show Gist options
  • Save Jamesscn/af5e189aaf60e2afdf43b93b78a21690 to your computer and use it in GitHub Desktop.
Save Jamesscn/af5e189aaf60e2afdf43b93b78a21690 to your computer and use it in GitHub Desktop.
Linux Tic Tac Toe Driver
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#define DRIVER_NAME "tictactoe"
#define MAJOR_NUMBER 168
/*
Create the character device with the following commands:
$ sudo mknod /dev/tictactoe c 168 0
$ sudo chmod 666 /dev/tictactoe
Then play with the cat and echo commands!
*/
struct cdev tictactoe_cdev;
dev_t tictactoe_dev_t;
char command[8];
int board[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
int winner = 0;
int message_code = 0;
//checks the status of the game and determines if there is a winner
void check_board(void) {
if( //checks if the user has a line of three
(board[0] == 1 && board[1] == 1 && board[2] == 1) ||
(board[3] == 1 && board[4] == 1 && board[5] == 1) ||
(board[6] == 1 && board[7] == 1 && board[8] == 1) ||
(board[0] == 1 && board[3] == 1 && board[6] == 1) ||
(board[1] == 1 && board[4] == 1 && board[7] == 1) ||
(board[2] == 1 && board[5] == 1 && board[8] == 1) ||
(board[0] == 1 && board[4] == 1 && board[8] == 1) ||
(board[2] == 1 && board[4] == 1 && board[6] == 1)
) {
winner = 1;
} else if ( //checks if the opponent has a line of three
(board[0] == -1 && board[1] == -1 && board[2] == -1) ||
(board[3] == -1 && board[4] == -1 && board[5] == -1) ||
(board[6] == -1 && board[7] == -1 && board[8] == -1) ||
(board[0] == -1 && board[3] == -1 && board[6] == -1) ||
(board[1] == -1 && board[4] == -1 && board[7] == -1) ||
(board[2] == -1 && board[5] == -1 && board[8] == -1) ||
(board[0] == -1 && board[4] == -1 && board[8] == -1) ||
(board[2] == -1 && board[4] == -1 && board[6] == -1)
) {
winner = -1;
} else { //checks if there are any remaining spots
int board_full = 1;
for(int i = 0; i < 9; i++) {
if(board[i] == 0) {
board_full = 0;
break;
}
}
if(board_full) {
winner = -2;
}
}
}
//reads the output of the character driver and gives it to the user
ssize_t tictactoe_read(struct file *file_pointer, char __user *buffer, size_t length, loff_t *offset) {
char status_playing[] = "It's your turn...\n";
char status_winner[] = "You won! Reset to play again.\n";
char status_loser[] = "You lost! Reset to play again.\n";
char status_draw[] = "It's a draw! Reset to play again.\n";
char empty[] = "";
char message_1[] = "Invalid command!\n";
char message_2[] = "The tile you selected is already occupied!\n";
char message_3[] = "Invalid tile number (Choose a tile from 1 to 9)!\n";
char message_4[] = "Valid commands:\n- reset\n- tile <1-9>\n- help\n";
char board_string[] = " | | \n-----\n | | \n-----\n | | \n";
char* status = empty;
char* message = empty;
int indices[] = {0, 2, 4, 12, 14, 16, 24, 26, 28};
int status_size = 0;
int message_size = 0;
int chars_printed = 0;
if(*offset > 0) {
return 0;
}
//prints the winner of the game
if(winner == 0) {
status = status_playing;
status_size = sizeof(status_playing);
} else if (winner == 1) {
status = status_winner;
status_size = sizeof(status_winner);
} else if (winner == -1) {
status = status_loser;
status_size = sizeof(status_loser);
} else if (winner == -2) {
status = status_draw;
status_size = sizeof(status_draw);
}
if(copy_to_user(buffer + chars_printed, status, status_size) != 0) {
return -EFAULT;
}
chars_printed += status_size;
*offset += status_size;
//prints a help or error message
if(message_code == 1) {
message = message_1;
message_size = sizeof(message_1);
} else if (message_code == 2) {
message = message_2;
message_size = sizeof(message_2);
} else if (message_code == 3) {
message = message_3;
message_size = sizeof(message_3);
} else if (message_code == 4) {
message = message_4;
message_size = sizeof(message_4);
}
if(copy_to_user(buffer + chars_printed, message, message_size) != 0) {
return -EFAULT;
}
chars_printed += message_size;
*offset += message_size;
//prints the board
for(int i = 0; i < 9; i++) {
char char_to_display = ' ';
if(board[i] == 1) {
char_to_display = 'X';
} else if (board[i] == -1) {
char_to_display = 'O';
}
board_string[indices[i]] = char_to_display;
}
if(copy_to_user(buffer + chars_printed, board_string, sizeof(board_string)) != 0) {
return -EFAULT;
}
chars_printed += sizeof(board_string);
*offset += sizeof(board_string);
return chars_printed;
}
//writes the input from the user and gives it to the character driver
ssize_t tictactoe_write(struct file *file_pointer, const char __user *buffer, size_t length, loff_t *offset) {
//reads the input into the command buffer
size_t original_length = length;
if(length > 8) {
length = 8;
}
if(copy_from_user(command, buffer, length) != 0) {
return -EFAULT;
}
command[7] = '\0';
message_code = 0;
//checks which command is run
if(strncmp(command, "reset", 5) == 0) { //reset command
for(int i = 0; i < 9; i++) {
board[i] = 0;
}
winner = 0;
return original_length;
} else if (strncmp(command, "tile ", 5) == 0) { //tile <1-9> command
if(winner != 0) {
return original_length;
}
if(command[5] >= '1' && command[5] <= '9') {
int tile = command[5] - '1';
if(board[tile] == 0) {
board[tile] = 1;
} else {
message_code = 2;
return original_length;
}
} else {
message_code = 3;
return original_length;
}
} else if (strncmp(command, "help", 4) == 0) { //help command
message_code = 4;
return original_length;
} else { //any other invalid command
message_code = 1;
return original_length;
}
check_board();
//makes a random move for the opponent
if(winner == 0) {
int random;
int random_indices[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
for(int i = 8; i > 0; i--) {
int tmp, j;
get_random_bytes(&random, sizeof(random));
j = random % (i + 1);
tmp = random_indices[i];
random_indices[i] = random_indices[j];
random_indices[j] = tmp;
}
for(int i = 0; i < 9; i++) {
if(board[random_indices[i]] == 0) {
board[random_indices[i]] = -1;
break;
}
}
}
check_board();
*offset += original_length;
return original_length;
}
const struct file_operations tictactoe_fops = {
.write = tictactoe_write,
.read = tictactoe_read,
.owner = THIS_MODULE
};
//initializes the module
int __init tictactoe_init(void) {
int err;
tictactoe_dev_t = MKDEV(MAJOR_NUMBER, 0);
err = register_chrdev_region(tictactoe_dev_t, 1, DRIVER_NAME);
if(err != 0) {
return err;
}
cdev_init(&tictactoe_cdev, &tictactoe_fops);
cdev_add(&tictactoe_cdev, tictactoe_dev_t, 1);
return 0;
}
//unloads the module
void __exit tictactoe_exit(void) {
cdev_del(&tictactoe_cdev);
unregister_chrdev_region(tictactoe_dev_t, 1);
}
module_init(tictactoe_init);
module_exit(tictactoe_exit);
MODULE_LICENSE("GPL");
@Jamesscn
Copy link
Author

Jamesscn commented Nov 29, 2022

I decided to make a character device driver that allows you to play Tic Tac Toe, with the sole purpose of learning how to program kernel modules in Linux.

To play, first create a Makefile in the same directory as the module with the following contents:

obj-m += tictactoe.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

Then compile and run the kernel module by running the following commands:

$ make
$ sudo insmod tictactoe.ko

Next, create the device driver with mknod:

$ sudo mknod /dev/tictactoe c 168 0
$ sudo chmod 666 /dev/tictactoe

You should now be able to play Tic Tac Toe. As an example, try running the following commands:

$ echo "help" > /dev/tictactoe
$ cat /dev/tictactoe
$ echo "tile 5" > /dev/tictactoe
$ cat /dev/tictactoe
$ echo "reset" > /dev/tictactoe
$ cat /dev/tictactoe

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment