ncurses tic tac toe (715 lines)
I have read your rules and in particular the part about bite-sized portions, so I am apprehensive about posting this but want to do it anyway. I have recently begun getting into C and in particular ncurses with the desire to make some games for the sake of historical appreciation (at least a Rogue clone, probably more). Python is the language I've messed with most extensively to this point and I'm far from an expert in that.
If anyone has the time and the desire, here is 715 lines of a complete and working Tic Tac Toe game that I wrote on my Ubuntu Laptop. The AI is no genius and I could definitely make it better but that was not my focus here... my concern was in writing structurally sound code and a portable, good-looking ncurses display. To that end I think I did alright. What I would like to hear are any and all ways that I could improve my coding style to better take advantage of the C language and the ncurses library while making future games. Don't hold back! Every little thing will help me write better games in the near future. I don't need anybody to go over the entire thing unless they really want to, simply giving me style tips is more than helpful.
So thank you to anyone who takes the time to review this. I really do appreciate it. Although I worked very hard on this I do recognize that it is amateur stuff.
Here you are:
// The mandatory tic tac toe game to practice ncurses stuff.
// Date Started: 21 DEC 2018
#include <ncurses.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// Global Variables
time_t t;
int row, col, x, y, px, py, slen, sx;
// constants to use with the COLOR_PAIR() attribute for easier remembering
const int Xs = 1;
const int Os = 2;
const int BG = 3;
/*
Board (7 x 7 character cells):
Line 1 = ------- (7 hyphens)
Line 2 = | | | | Spaces: 1=(1, 1) 2=(1, 3) 3=(1, 5)
Line 3 = -------
Line 4 = | | | | 4=(3, 1) 5=(3, 3) 6=(3, 5)
Line 5 = -------
Line 6 = | | | | 7=(5, 1) 8=(5, 3) 9=(5, 5)
Line 7 = -------
*/
const int line_len = 7; // constant int for the length of a board line
char break_lines = "-------"; // Strings representing the board lines, minus the pieces
char play_lines = "| | | |";
/*
These spaces are abstractions, since the board itself will need to vary based on the current size of the terminal.
They represent the current values of the "playable" spaces.
*/
char space_1 = ' ';
char space_2 = ' ';
char space_3 = ' ';
char space_4 = ' ';
char space_5 = ' ';
char space_6 = ' ';
char space_7 = ' ';
char space_8 = ' ';
char space_9 = ' ';
int space_1y, space_1x;
int space_2y, space_2x;
int space_3y, space_3x;
int space_4y, space_4x;
int space_5y, space_5x;
int space_6y, space_6x;
int space_7y, space_7x;
int space_8y, space_8x;
int space_9y, space_9x;
char *space_ptr = NULL; // Pointer to one of the playable spaces
// Player's side ('X' or 'O')
char side;
int running = 1; // Main menu control variable
int playing = 1; // Game loop control variable
int turn = 1; // Turn number
int game_over = 0;
// Function Declarations
void f_intro(); // Print elaborate "animated" intro splash
char f_setup(); // Prompt player to pick a side or pick random selection, returns char
void f_paint(); // Paint the board state on a given turn
void f_turn_input(); // Take player input and determine AI action (includes sub-functions probably)
void f_player_turn(); // Take player input and place the appropriate mark on the board
void f_AI_turn(); // Logic (such as it is) and Placement for AI turn
void f_evaluate_turn(); // Check for endgame state and either advance the turn or end the game (with sub-functions probably)
int f_AI_picker(); // Picks random spots until it finds an empty one or tries them all
void f_declare_winner(char symbol); // Takes the winning character and creates a splash screen declaring victory ('X', 'O', or 'T' for Tie)
// Main Function
int main(){
srand((unsigned) time(&t));
initscr();
clear();
cbreak();
keypad(stdscr, 1);
curs_set(0);
noecho();
start_color();
init_pair(Xs, COLOR_CYAN, COLOR_BLACK);
init_pair(Os, COLOR_RED, COLOR_BLACK);
init_pair(BG, COLOR_YELLOW, COLOR_BLACK);
f_intro(); // Print intro splash
getch();
while(running){
clear();
side = f_setup(); // Choose X's, O's, or RANDOM SIDE
playing = 1;
while(playing){
f_paint(); // Paint the board as it is that turn
f_turn_input(); // Take player input and if valid determine AI move + sub-functions
turn++;
}
// To-Do, a reset function
}
endwin();
return 0;
}
// Function Definitions
void f_intro(){
// Print elaborate "animated" intro splash
int which;
clear();
getmaxyx(stdscr, row, col);
// Print the background
for(y=0;y<=row;y++){
for(x=0;x<=col;x++){
which = rand() % 3;
if(which == 0){
// Print an "X" in the cell
attron(COLOR_PAIR(Xs));
mvprintw(y, x, "X");
attroff(COLOR_PAIR(Xs));
}else if(which == 1){
// Print an "O" in the cell
attron(COLOR_PAIR(Os));
mvprintw(y, x, "O");
attroff(COLOR_PAIR(Os));
}else if(which == 2){
// Print a blank black space in the cell
attron(COLOR_PAIR(BG));
mvprintw(y, x, " ");
attroff(COLOR_PAIR(BG));
}
}
}
// Print the Title
y = row / 2 - 1;
char intro_str = " NCURSES Tic Tac Toe! ";
char intro_str_padding = " ";
char intro_str2 = " any key to continue ";
slen = strlen(intro_str);
x = col / 2 - slen / 2;
mvprintw(y++, x, intro_str_padding);
mvprintw(y++, x, intro_str);
mvprintw(y++, x, intro_str2);
mvprintw(y, x, intro_str_padding);
refresh();
}
char f_setup(){
// Prompt player to pick a side or pick random selection, returns char
int input;
clear();
getmaxyx(stdscr, row, col);
char setup_str1 = "Pick a side!";
char setup_str2 = "Press 'X', 'O', or 'R' for Random!";
char *chose_x = "You chose X's! Any key to continue...";
char *chose_y = "You chose O's! Any key to continue...";
char *choice_ptr = NULL;
y = row / 2 - 1;
slen = strlen(setup_str1);
x = col / 2 - slen / 2;
mvprintw(y++, x, setup_str1);
slen = strlen(setup_str2);
x = col / 2 - slen / 2;
mvprintw(y++, x, setup_str2);
y++;
refresh();
input = getch();
if(input == 'X' || input == 'x'){
choice_ptr = chose_x;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'X';
}else if(input == 'O' || input == 'o'){
choice_ptr = chose_y;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'O';
}else if(input == 'R' || input == 'r'){
int r;
r = rand() % 2;
if(r == 0){
// Pick 'X'
choice_ptr = chose_x;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'X';
}else if(r == 1){
// Pick 'O'
choice_ptr = chose_y;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'O';
}
}else{
char err_str = "Input error! Any key to continue...";
slen = strlen(err_str);
x = col / 2 - slen / 2;
mvprintw(y, x, err_str);
refresh();
getch();
f_setup();
}
}
void f_paint(){
// Paint the board state on a given turn
/*
1. Clear screen.
2. Paint blank board.
3. Paint the contents of each playable cell.
4. Refresh screen
*/
clear(); // Clear screen
getmaxyx(stdscr, row, col); // Get current size of terminal
y = row / 2 - 3; // Board is 7x7 characters, so (y / 2 - 3) is a decent top edge
x = col / 2 - 3; // Ditto for (x / 2 - 3) being a decent left edge.
// Determine the locations of the 9 "playable" cells:
space_1y = y + 1; space_1x = x + 1;
space_2y = y + 1; space_2x = x + 3;
space_3y = y + 1; space_3x = x + 5;
space_4y = y + 3; space_4x = x + 1;
space_5y = y + 3; space_5x = x + 3;
space_6y = y + 3; space_6x = x + 5;
space_7y = y + 5; space_7x = x + 1;
space_8y = y + 5; space_8x = x + 3;
space_9y = y + 5; space_9x = x + 5;
// Paint the board roughly centered:
int yy, xx;
attron(COLOR_PAIR(BG));
for(yy = 0; yy < line_len; yy++){
if(yy == 0 || yy % 2 == 0){
mvprintw(y + yy, x, break_lines);
}else{
mvprintw(y + yy, x, play_lines);
}
}
attroff(COLOR_PAIR(BG));
// Insert appropriate characters into the "playable" cells:
if(space_1 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_1 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_1y, space_1x, space_1);
if(space_2 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_2 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_2y, space_2x, space_2);
if(space_3 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_3 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_3y, space_3x, space_3);
if(space_4 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_4 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_4y, space_4x, space_4);
if(space_5 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_5 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_5y, space_5x, space_5);
if(space_6 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_6 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_6y, space_6x, space_6);
if(space_7 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_7 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_7y, space_7x, space_7);
if(space_8 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_8 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_8y, space_8x, space_8);
if(space_9 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_9 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_9y, space_9x, space_9);
attroff(COLOR_PAIR(Xs));
attroff(COLOR_PAIR(Os));
refresh();
}
void f_turn_input(){
// Take player input and determine AI action (includes sub-functions probably)
/*
1. Determine who goes first.
- Using if/else to divide the function into two halves for each possibility.
2. Player/AI Takes turn. -> Refresh
3. Player/AI takes turn. -> Refresh
Note on AI: No real logic for this version. Just going to randomly pick from the available spaces.
*/
if(side == 'X'){
// if player is 'X':
f_player_turn();
f_evaluate_turn();
if(game_over == 0){
f_AI_turn();
f_evaluate_turn();
}
}else if(side == 'O'){
// If player is 'O':
f_AI_turn();
f_evaluate_turn();
if(game_over == 0){
f_player_turn();
f_evaluate_turn();
}
}
refresh();
}
void f_player_turn(){
// Take player input and place the appropriate mark on the board
int info_line = y + 10; // Determine the line that the info splash will show up on.
char move_splash = "Use arrow keys and press 'P' to place your piece!";
char done_splash = "Good move!";
char move_err_splash = "You can't move that way!";
char input_err_splash = "Invalid input!";
char full_err_splash = "Spot already claimed!";
slen = strlen(move_splash);
sx = col / 2 - slen / 2; // Center the info splash
mvprintw(info_line, sx, move_splash);
curs_set(1); // Enable the cursor for the player
int pos_y = space_1y; // Y position of the cursor
int pos_x = space_1x; // X position of the cursor
move(pos_y, pos_x); // Move it to space 1
refresh();
int inputting = 1;
while(inputting){
int input;
char spot;
int cx;
input = getch();
if(input == KEY_LEFT){
if(!(pos_x == space_1x)){
// If not on the left playable edge
pos_x -= 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == KEY_RIGHT){
if(!(pos_x == space_3x)){
// If not on the right playable edge
pos_x += 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == KEY_UP){
if(!(pos_y == space_1y)){
// If not on the top playable edge
pos_y -= 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == KEY_DOWN){
if(!(pos_y == space_9y)){
// If not on the bottom playable edge
pos_y += 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == 'P' || input == 'p'){
/*
1. Read contents of space.
2. If Empty -> Place player's symbol
3. Else, try again
*/
if(pos_y == space_1y && pos_x == space_1x){
space_ptr = &space_1;
}else if(pos_y == space_2y && pos_x == space_2x){
space_ptr = &space_2;
}else if(pos_y == space_3y && pos_x == space_3x){
space_ptr = &space_3;
}else if(pos_y == space_4y && pos_x == space_4x){
space_ptr = &space_4;
}else if(pos_y == space_5y && pos_x == space_5x){
space_ptr = &space_5;
}else if(pos_y == space_6y && pos_x == space_6x){
space_ptr = &space_6;
}else if(pos_y == space_7y && pos_x == space_7x){
space_ptr = &space_7;
}else if(pos_y == space_8y && pos_x == space_8x){
space_ptr = &space_8;
}else if(pos_y == space_9y && pos_x == space_9x){
space_ptr = &space_9;
}
if(*space_ptr == ' '){
if(side == 'X'){
*space_ptr = 'X';
}else{
*space_ptr = 'O';
}
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(done_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, done_splash);
move(pos_y, pos_x);
refresh();
inputting = 0;
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(full_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, full_err_splash);
move(pos_y, pos_x);
}
}else{
// If the user presses any other button
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(input_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, input_err_splash);
move(pos_y, pos_x);
}
}
}
int f_AI_picker(){
/*
1. Pick a number between 1 and 9
2. Randomly decide whether to check spaces from 1 to 9 or 9 to 1 for the sake of variety
3. Check them in the determined order until an open space is found.
4. Return number of open space.
Note: This version has no real strategic logic and will be easily beaten by any player.
Although a quick fix for some added challenge is to make it prioritize the center tile.
*/
int pick;
pick = rand() % 9 + 1;
int order; // 1 = Ascending, 2 = Descending
order = rand() % 2 + 1;
if(space_5 == ' '){
return 5;
}else{
if(order == 1){
if(space_1 == ' '){
return 1;
}else if(space_2 == ' '){
return 2;
}else if(space_3 == ' '){
return 3;
}else if(space_4 == ' '){
return 4;
}else if(space_6 == ' '){
return 6;
}else if(space_7 == ' '){
return 7;
}else if(space_8 == ' '){
return 8;
}else if(space_9 == ' '){
return 9;
}
}else if(order == 2){
if(space_9 == ' '){
return 9;
}else if(space_8 == ' '){
return 8;
}else if(space_7 == ' '){
return 7;
}else if(space_6 == ' '){
return 6;
}else if(space_4 == ' '){
return 4;
}else if(space_3 == ' '){
return 3;
}else if(space_2 == ' '){
return 2;
}else if(space_1 == ' '){
return 1;
}
}
}
}
void f_AI_turn(){
// Logic (such as it is) and Placement for AI turn
char AI_char;
if(side == 'X'){
AI_char = 'O';
}else{
AI_char = 'X';
}
int space_to_place;
space_to_place = f_AI_picker();
if(space_to_place == 1){
space_1 = AI_char;
}else if(space_to_place == 2){
space_2 = AI_char;
}else if(space_to_place == 3){
space_3 = AI_char;
}else if(space_to_place == 4){
space_4 = AI_char;
}else if(space_to_place == 5){
space_5 = AI_char;
}else if(space_to_place == 6){
space_6 = AI_char;
}else if(space_to_place == 7){
space_7 = AI_char;
}else if(space_to_place == 8){
space_8 = AI_char;
}else if(space_to_place == 9){
space_9 = AI_char;
}
f_paint();
refresh();
}
void f_declare_winner(char symbol){
// Takes the winning character and creates a splash screen declaring victory ('X', 'O', or 'T' for Tie)
char *x_wins = " X is the winner! ";
char *o_wins = " O is the winner! ";
char *tie_game = " The game is a tie! ";
char padding = " ";
char *win_splash_ptr = NULL;
// Paint background for victory splash:
if(symbol == 'X'){
win_splash_ptr = x_wins;
attron(COLOR_PAIR(Xs));
for(y = 0; y <= row; y++){
for(x = 0; x <= col; x++){
if(x == 0 || x % 2 == 0){
mvaddch(y, x, 'X');
}else{
mvaddch(y, x, ' ');
}
}
}
attroff(COLOR_PAIR(Xs));
}else if(symbol == 'O'){
win_splash_ptr = o_wins;
attron(COLOR_PAIR(Os));
for(y = 0; y <= row; y++){
for(x = 0; x <= col; x++){
if(x == 0 || x % 2 == 0){
mvaddch(y, x, 'O');
}else{
mvaddch(y, x, ' ');
}
}
}
attroff(COLOR_PAIR(Os));
}else if(symbol == 'T'){
win_splash_ptr = tie_game;
for(y = 0; y <= row; y++){
for(x = 0; x <= col; x++){
if(x == 0 || x % 2 == 0){
attron(COLOR_PAIR(Xs));
mvaddch(y, x, 'X');
attroff(COLOR_PAIR(Xs));
}else{
attron(COLOR_PAIR(Os));
mvaddch(y, x, 'O');
attroff(COLOR_PAIR(Os));
}
}
}
}
//Paint the prompt
y = row / 2 - 2;
slen = strlen(win_splash_ptr);
x = col / 2 - slen / 2;
mvprintw(y++, x, padding);
mvprintw(y++, x, win_splash_ptr);
mvprintw(y, x, padding);
curs_set(0);
refresh();
getch();
running = 0;
playing = 0;
}
void f_evaluate_turn(){
// Check for endgame state and either advance the turn or end the game (with sub-functions probably)
/*
1. Check for each possible victory condition. -> If so, declare appropriate victory.
2. Check if turn number is high enough to indicate a tie -> If so, declare a tie.
3. Else, continue.
*/
int winner;
winner = 'N'; // For none
if(space_1 == 'O' && space_2 == 'O' && space_3 == 'O'){
winner = 'O';
game_over++;
}else if(space_4 == 'O' && space_5 == 'O' && space_6 == 'O'){
winner = 'O';
game_over++;
}else if(space_7 == 'O' && space_8 == 'O' && space_9 == 'O'){
winner = 'O';
game_over++;
}else if(space_1 == 'O' && space_4 == 'O' && space_7 == 'O'){
winner = 'O';
game_over++;
}else if(space_2 == 'O' && space_5 == 'O' && space_8 == 'O'){
winner = 'O';
game_over++;
}else if(space_3 == 'O' && space_6 == 'O' && space_9 == 'O'){
winner = 'O';
game_over++;
}else if(space_1 == 'O' && space_5 == 'O' && space_9 == 'O'){
winner = 'O';
game_over++;
}else if(space_3 == 'O' && space_5 == 'O' && space_7 == 'O'){
winner = 'O';
game_over++;
}else if(space_1 == 'X' && space_2 == 'X' && space_3 == 'X'){
winner = 'X';
game_over++;
}else if(space_4 == 'X' && space_5 == 'X' && space_6 == 'X'){
winner = 'X';
game_over++;
}else if(space_7 == 'X' && space_8 == 'X' && space_9 == 'X'){
winner = 'X';
game_over++;
}else if(space_1 == 'X' && space_4 == 'X' && space_7 == 'X'){
winner = 'X';
game_over++;
}else if(space_2 == 'X' && space_5 == 'X' && space_8 == 'X'){
winner = 'X';
game_over++;
}else if(space_3 == 'X' && space_6 == 'X' && space_9 == 'X'){
winner = 'X';
game_over++;
}else if(space_1 == 'X' && space_5 == 'X' && space_9 == 'X'){
winner = 'X';
game_over++;
}else if(space_3 == 'X' && space_5 == 'X' && space_7 == 'X'){
winner = 'X';
game_over++;
}else if(turn >= 5){
winner = 'T';
game_over++;
}
if(winner != 'N'){
f_declare_winner(winner);
}
}
And of course it goes without saying but any fellow linux users who want to throw this in their home directory and play it themselves are more than welcome to do so! Just be aware that without more strategic thinking the AI is a total pushover. I only went so far as making it prioritize the center square.
beginner c game tic-tac-toe
New contributor
add a comment |
I have read your rules and in particular the part about bite-sized portions, so I am apprehensive about posting this but want to do it anyway. I have recently begun getting into C and in particular ncurses with the desire to make some games for the sake of historical appreciation (at least a Rogue clone, probably more). Python is the language I've messed with most extensively to this point and I'm far from an expert in that.
If anyone has the time and the desire, here is 715 lines of a complete and working Tic Tac Toe game that I wrote on my Ubuntu Laptop. The AI is no genius and I could definitely make it better but that was not my focus here... my concern was in writing structurally sound code and a portable, good-looking ncurses display. To that end I think I did alright. What I would like to hear are any and all ways that I could improve my coding style to better take advantage of the C language and the ncurses library while making future games. Don't hold back! Every little thing will help me write better games in the near future. I don't need anybody to go over the entire thing unless they really want to, simply giving me style tips is more than helpful.
So thank you to anyone who takes the time to review this. I really do appreciate it. Although I worked very hard on this I do recognize that it is amateur stuff.
Here you are:
// The mandatory tic tac toe game to practice ncurses stuff.
// Date Started: 21 DEC 2018
#include <ncurses.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// Global Variables
time_t t;
int row, col, x, y, px, py, slen, sx;
// constants to use with the COLOR_PAIR() attribute for easier remembering
const int Xs = 1;
const int Os = 2;
const int BG = 3;
/*
Board (7 x 7 character cells):
Line 1 = ------- (7 hyphens)
Line 2 = | | | | Spaces: 1=(1, 1) 2=(1, 3) 3=(1, 5)
Line 3 = -------
Line 4 = | | | | 4=(3, 1) 5=(3, 3) 6=(3, 5)
Line 5 = -------
Line 6 = | | | | 7=(5, 1) 8=(5, 3) 9=(5, 5)
Line 7 = -------
*/
const int line_len = 7; // constant int for the length of a board line
char break_lines = "-------"; // Strings representing the board lines, minus the pieces
char play_lines = "| | | |";
/*
These spaces are abstractions, since the board itself will need to vary based on the current size of the terminal.
They represent the current values of the "playable" spaces.
*/
char space_1 = ' ';
char space_2 = ' ';
char space_3 = ' ';
char space_4 = ' ';
char space_5 = ' ';
char space_6 = ' ';
char space_7 = ' ';
char space_8 = ' ';
char space_9 = ' ';
int space_1y, space_1x;
int space_2y, space_2x;
int space_3y, space_3x;
int space_4y, space_4x;
int space_5y, space_5x;
int space_6y, space_6x;
int space_7y, space_7x;
int space_8y, space_8x;
int space_9y, space_9x;
char *space_ptr = NULL; // Pointer to one of the playable spaces
// Player's side ('X' or 'O')
char side;
int running = 1; // Main menu control variable
int playing = 1; // Game loop control variable
int turn = 1; // Turn number
int game_over = 0;
// Function Declarations
void f_intro(); // Print elaborate "animated" intro splash
char f_setup(); // Prompt player to pick a side or pick random selection, returns char
void f_paint(); // Paint the board state on a given turn
void f_turn_input(); // Take player input and determine AI action (includes sub-functions probably)
void f_player_turn(); // Take player input and place the appropriate mark on the board
void f_AI_turn(); // Logic (such as it is) and Placement for AI turn
void f_evaluate_turn(); // Check for endgame state and either advance the turn or end the game (with sub-functions probably)
int f_AI_picker(); // Picks random spots until it finds an empty one or tries them all
void f_declare_winner(char symbol); // Takes the winning character and creates a splash screen declaring victory ('X', 'O', or 'T' for Tie)
// Main Function
int main(){
srand((unsigned) time(&t));
initscr();
clear();
cbreak();
keypad(stdscr, 1);
curs_set(0);
noecho();
start_color();
init_pair(Xs, COLOR_CYAN, COLOR_BLACK);
init_pair(Os, COLOR_RED, COLOR_BLACK);
init_pair(BG, COLOR_YELLOW, COLOR_BLACK);
f_intro(); // Print intro splash
getch();
while(running){
clear();
side = f_setup(); // Choose X's, O's, or RANDOM SIDE
playing = 1;
while(playing){
f_paint(); // Paint the board as it is that turn
f_turn_input(); // Take player input and if valid determine AI move + sub-functions
turn++;
}
// To-Do, a reset function
}
endwin();
return 0;
}
// Function Definitions
void f_intro(){
// Print elaborate "animated" intro splash
int which;
clear();
getmaxyx(stdscr, row, col);
// Print the background
for(y=0;y<=row;y++){
for(x=0;x<=col;x++){
which = rand() % 3;
if(which == 0){
// Print an "X" in the cell
attron(COLOR_PAIR(Xs));
mvprintw(y, x, "X");
attroff(COLOR_PAIR(Xs));
}else if(which == 1){
// Print an "O" in the cell
attron(COLOR_PAIR(Os));
mvprintw(y, x, "O");
attroff(COLOR_PAIR(Os));
}else if(which == 2){
// Print a blank black space in the cell
attron(COLOR_PAIR(BG));
mvprintw(y, x, " ");
attroff(COLOR_PAIR(BG));
}
}
}
// Print the Title
y = row / 2 - 1;
char intro_str = " NCURSES Tic Tac Toe! ";
char intro_str_padding = " ";
char intro_str2 = " any key to continue ";
slen = strlen(intro_str);
x = col / 2 - slen / 2;
mvprintw(y++, x, intro_str_padding);
mvprintw(y++, x, intro_str);
mvprintw(y++, x, intro_str2);
mvprintw(y, x, intro_str_padding);
refresh();
}
char f_setup(){
// Prompt player to pick a side or pick random selection, returns char
int input;
clear();
getmaxyx(stdscr, row, col);
char setup_str1 = "Pick a side!";
char setup_str2 = "Press 'X', 'O', or 'R' for Random!";
char *chose_x = "You chose X's! Any key to continue...";
char *chose_y = "You chose O's! Any key to continue...";
char *choice_ptr = NULL;
y = row / 2 - 1;
slen = strlen(setup_str1);
x = col / 2 - slen / 2;
mvprintw(y++, x, setup_str1);
slen = strlen(setup_str2);
x = col / 2 - slen / 2;
mvprintw(y++, x, setup_str2);
y++;
refresh();
input = getch();
if(input == 'X' || input == 'x'){
choice_ptr = chose_x;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'X';
}else if(input == 'O' || input == 'o'){
choice_ptr = chose_y;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'O';
}else if(input == 'R' || input == 'r'){
int r;
r = rand() % 2;
if(r == 0){
// Pick 'X'
choice_ptr = chose_x;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'X';
}else if(r == 1){
// Pick 'O'
choice_ptr = chose_y;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'O';
}
}else{
char err_str = "Input error! Any key to continue...";
slen = strlen(err_str);
x = col / 2 - slen / 2;
mvprintw(y, x, err_str);
refresh();
getch();
f_setup();
}
}
void f_paint(){
// Paint the board state on a given turn
/*
1. Clear screen.
2. Paint blank board.
3. Paint the contents of each playable cell.
4. Refresh screen
*/
clear(); // Clear screen
getmaxyx(stdscr, row, col); // Get current size of terminal
y = row / 2 - 3; // Board is 7x7 characters, so (y / 2 - 3) is a decent top edge
x = col / 2 - 3; // Ditto for (x / 2 - 3) being a decent left edge.
// Determine the locations of the 9 "playable" cells:
space_1y = y + 1; space_1x = x + 1;
space_2y = y + 1; space_2x = x + 3;
space_3y = y + 1; space_3x = x + 5;
space_4y = y + 3; space_4x = x + 1;
space_5y = y + 3; space_5x = x + 3;
space_6y = y + 3; space_6x = x + 5;
space_7y = y + 5; space_7x = x + 1;
space_8y = y + 5; space_8x = x + 3;
space_9y = y + 5; space_9x = x + 5;
// Paint the board roughly centered:
int yy, xx;
attron(COLOR_PAIR(BG));
for(yy = 0; yy < line_len; yy++){
if(yy == 0 || yy % 2 == 0){
mvprintw(y + yy, x, break_lines);
}else{
mvprintw(y + yy, x, play_lines);
}
}
attroff(COLOR_PAIR(BG));
// Insert appropriate characters into the "playable" cells:
if(space_1 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_1 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_1y, space_1x, space_1);
if(space_2 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_2 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_2y, space_2x, space_2);
if(space_3 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_3 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_3y, space_3x, space_3);
if(space_4 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_4 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_4y, space_4x, space_4);
if(space_5 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_5 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_5y, space_5x, space_5);
if(space_6 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_6 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_6y, space_6x, space_6);
if(space_7 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_7 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_7y, space_7x, space_7);
if(space_8 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_8 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_8y, space_8x, space_8);
if(space_9 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_9 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_9y, space_9x, space_9);
attroff(COLOR_PAIR(Xs));
attroff(COLOR_PAIR(Os));
refresh();
}
void f_turn_input(){
// Take player input and determine AI action (includes sub-functions probably)
/*
1. Determine who goes first.
- Using if/else to divide the function into two halves for each possibility.
2. Player/AI Takes turn. -> Refresh
3. Player/AI takes turn. -> Refresh
Note on AI: No real logic for this version. Just going to randomly pick from the available spaces.
*/
if(side == 'X'){
// if player is 'X':
f_player_turn();
f_evaluate_turn();
if(game_over == 0){
f_AI_turn();
f_evaluate_turn();
}
}else if(side == 'O'){
// If player is 'O':
f_AI_turn();
f_evaluate_turn();
if(game_over == 0){
f_player_turn();
f_evaluate_turn();
}
}
refresh();
}
void f_player_turn(){
// Take player input and place the appropriate mark on the board
int info_line = y + 10; // Determine the line that the info splash will show up on.
char move_splash = "Use arrow keys and press 'P' to place your piece!";
char done_splash = "Good move!";
char move_err_splash = "You can't move that way!";
char input_err_splash = "Invalid input!";
char full_err_splash = "Spot already claimed!";
slen = strlen(move_splash);
sx = col / 2 - slen / 2; // Center the info splash
mvprintw(info_line, sx, move_splash);
curs_set(1); // Enable the cursor for the player
int pos_y = space_1y; // Y position of the cursor
int pos_x = space_1x; // X position of the cursor
move(pos_y, pos_x); // Move it to space 1
refresh();
int inputting = 1;
while(inputting){
int input;
char spot;
int cx;
input = getch();
if(input == KEY_LEFT){
if(!(pos_x == space_1x)){
// If not on the left playable edge
pos_x -= 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == KEY_RIGHT){
if(!(pos_x == space_3x)){
// If not on the right playable edge
pos_x += 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == KEY_UP){
if(!(pos_y == space_1y)){
// If not on the top playable edge
pos_y -= 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == KEY_DOWN){
if(!(pos_y == space_9y)){
// If not on the bottom playable edge
pos_y += 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == 'P' || input == 'p'){
/*
1. Read contents of space.
2. If Empty -> Place player's symbol
3. Else, try again
*/
if(pos_y == space_1y && pos_x == space_1x){
space_ptr = &space_1;
}else if(pos_y == space_2y && pos_x == space_2x){
space_ptr = &space_2;
}else if(pos_y == space_3y && pos_x == space_3x){
space_ptr = &space_3;
}else if(pos_y == space_4y && pos_x == space_4x){
space_ptr = &space_4;
}else if(pos_y == space_5y && pos_x == space_5x){
space_ptr = &space_5;
}else if(pos_y == space_6y && pos_x == space_6x){
space_ptr = &space_6;
}else if(pos_y == space_7y && pos_x == space_7x){
space_ptr = &space_7;
}else if(pos_y == space_8y && pos_x == space_8x){
space_ptr = &space_8;
}else if(pos_y == space_9y && pos_x == space_9x){
space_ptr = &space_9;
}
if(*space_ptr == ' '){
if(side == 'X'){
*space_ptr = 'X';
}else{
*space_ptr = 'O';
}
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(done_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, done_splash);
move(pos_y, pos_x);
refresh();
inputting = 0;
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(full_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, full_err_splash);
move(pos_y, pos_x);
}
}else{
// If the user presses any other button
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(input_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, input_err_splash);
move(pos_y, pos_x);
}
}
}
int f_AI_picker(){
/*
1. Pick a number between 1 and 9
2. Randomly decide whether to check spaces from 1 to 9 or 9 to 1 for the sake of variety
3. Check them in the determined order until an open space is found.
4. Return number of open space.
Note: This version has no real strategic logic and will be easily beaten by any player.
Although a quick fix for some added challenge is to make it prioritize the center tile.
*/
int pick;
pick = rand() % 9 + 1;
int order; // 1 = Ascending, 2 = Descending
order = rand() % 2 + 1;
if(space_5 == ' '){
return 5;
}else{
if(order == 1){
if(space_1 == ' '){
return 1;
}else if(space_2 == ' '){
return 2;
}else if(space_3 == ' '){
return 3;
}else if(space_4 == ' '){
return 4;
}else if(space_6 == ' '){
return 6;
}else if(space_7 == ' '){
return 7;
}else if(space_8 == ' '){
return 8;
}else if(space_9 == ' '){
return 9;
}
}else if(order == 2){
if(space_9 == ' '){
return 9;
}else if(space_8 == ' '){
return 8;
}else if(space_7 == ' '){
return 7;
}else if(space_6 == ' '){
return 6;
}else if(space_4 == ' '){
return 4;
}else if(space_3 == ' '){
return 3;
}else if(space_2 == ' '){
return 2;
}else if(space_1 == ' '){
return 1;
}
}
}
}
void f_AI_turn(){
// Logic (such as it is) and Placement for AI turn
char AI_char;
if(side == 'X'){
AI_char = 'O';
}else{
AI_char = 'X';
}
int space_to_place;
space_to_place = f_AI_picker();
if(space_to_place == 1){
space_1 = AI_char;
}else if(space_to_place == 2){
space_2 = AI_char;
}else if(space_to_place == 3){
space_3 = AI_char;
}else if(space_to_place == 4){
space_4 = AI_char;
}else if(space_to_place == 5){
space_5 = AI_char;
}else if(space_to_place == 6){
space_6 = AI_char;
}else if(space_to_place == 7){
space_7 = AI_char;
}else if(space_to_place == 8){
space_8 = AI_char;
}else if(space_to_place == 9){
space_9 = AI_char;
}
f_paint();
refresh();
}
void f_declare_winner(char symbol){
// Takes the winning character and creates a splash screen declaring victory ('X', 'O', or 'T' for Tie)
char *x_wins = " X is the winner! ";
char *o_wins = " O is the winner! ";
char *tie_game = " The game is a tie! ";
char padding = " ";
char *win_splash_ptr = NULL;
// Paint background for victory splash:
if(symbol == 'X'){
win_splash_ptr = x_wins;
attron(COLOR_PAIR(Xs));
for(y = 0; y <= row; y++){
for(x = 0; x <= col; x++){
if(x == 0 || x % 2 == 0){
mvaddch(y, x, 'X');
}else{
mvaddch(y, x, ' ');
}
}
}
attroff(COLOR_PAIR(Xs));
}else if(symbol == 'O'){
win_splash_ptr = o_wins;
attron(COLOR_PAIR(Os));
for(y = 0; y <= row; y++){
for(x = 0; x <= col; x++){
if(x == 0 || x % 2 == 0){
mvaddch(y, x, 'O');
}else{
mvaddch(y, x, ' ');
}
}
}
attroff(COLOR_PAIR(Os));
}else if(symbol == 'T'){
win_splash_ptr = tie_game;
for(y = 0; y <= row; y++){
for(x = 0; x <= col; x++){
if(x == 0 || x % 2 == 0){
attron(COLOR_PAIR(Xs));
mvaddch(y, x, 'X');
attroff(COLOR_PAIR(Xs));
}else{
attron(COLOR_PAIR(Os));
mvaddch(y, x, 'O');
attroff(COLOR_PAIR(Os));
}
}
}
}
//Paint the prompt
y = row / 2 - 2;
slen = strlen(win_splash_ptr);
x = col / 2 - slen / 2;
mvprintw(y++, x, padding);
mvprintw(y++, x, win_splash_ptr);
mvprintw(y, x, padding);
curs_set(0);
refresh();
getch();
running = 0;
playing = 0;
}
void f_evaluate_turn(){
// Check for endgame state and either advance the turn or end the game (with sub-functions probably)
/*
1. Check for each possible victory condition. -> If so, declare appropriate victory.
2. Check if turn number is high enough to indicate a tie -> If so, declare a tie.
3. Else, continue.
*/
int winner;
winner = 'N'; // For none
if(space_1 == 'O' && space_2 == 'O' && space_3 == 'O'){
winner = 'O';
game_over++;
}else if(space_4 == 'O' && space_5 == 'O' && space_6 == 'O'){
winner = 'O';
game_over++;
}else if(space_7 == 'O' && space_8 == 'O' && space_9 == 'O'){
winner = 'O';
game_over++;
}else if(space_1 == 'O' && space_4 == 'O' && space_7 == 'O'){
winner = 'O';
game_over++;
}else if(space_2 == 'O' && space_5 == 'O' && space_8 == 'O'){
winner = 'O';
game_over++;
}else if(space_3 == 'O' && space_6 == 'O' && space_9 == 'O'){
winner = 'O';
game_over++;
}else if(space_1 == 'O' && space_5 == 'O' && space_9 == 'O'){
winner = 'O';
game_over++;
}else if(space_3 == 'O' && space_5 == 'O' && space_7 == 'O'){
winner = 'O';
game_over++;
}else if(space_1 == 'X' && space_2 == 'X' && space_3 == 'X'){
winner = 'X';
game_over++;
}else if(space_4 == 'X' && space_5 == 'X' && space_6 == 'X'){
winner = 'X';
game_over++;
}else if(space_7 == 'X' && space_8 == 'X' && space_9 == 'X'){
winner = 'X';
game_over++;
}else if(space_1 == 'X' && space_4 == 'X' && space_7 == 'X'){
winner = 'X';
game_over++;
}else if(space_2 == 'X' && space_5 == 'X' && space_8 == 'X'){
winner = 'X';
game_over++;
}else if(space_3 == 'X' && space_6 == 'X' && space_9 == 'X'){
winner = 'X';
game_over++;
}else if(space_1 == 'X' && space_5 == 'X' && space_9 == 'X'){
winner = 'X';
game_over++;
}else if(space_3 == 'X' && space_5 == 'X' && space_7 == 'X'){
winner = 'X';
game_over++;
}else if(turn >= 5){
winner = 'T';
game_over++;
}
if(winner != 'N'){
f_declare_winner(winner);
}
}
And of course it goes without saying but any fellow linux users who want to throw this in their home directory and play it themselves are more than welcome to do so! Just be aware that without more strategic thinking the AI is a total pushover. I only went so far as making it prioritize the center square.
beginner c game tic-tac-toe
New contributor
Long questions are fine: they just tend to lend themselves to less comprehensive reviews (which is not necessarily a bad thing), and more rounds of review if you revise your code using the suggestions given. You can really learn a great deal from improving a larger program. And if you revise your code, it may turn out to need less than 715 lines!
– Graham
2 mins ago
I don't really want to do too much more with this. Maybe improve the AI a little bit. Mostly I'm interested in tips that will lend themselves to making better games in the future. Also any ways in which I have obviously mis-used the language or the ncurses library (as I'm sure there are a few).
– some_guy632
32 secs ago
add a comment |
I have read your rules and in particular the part about bite-sized portions, so I am apprehensive about posting this but want to do it anyway. I have recently begun getting into C and in particular ncurses with the desire to make some games for the sake of historical appreciation (at least a Rogue clone, probably more). Python is the language I've messed with most extensively to this point and I'm far from an expert in that.
If anyone has the time and the desire, here is 715 lines of a complete and working Tic Tac Toe game that I wrote on my Ubuntu Laptop. The AI is no genius and I could definitely make it better but that was not my focus here... my concern was in writing structurally sound code and a portable, good-looking ncurses display. To that end I think I did alright. What I would like to hear are any and all ways that I could improve my coding style to better take advantage of the C language and the ncurses library while making future games. Don't hold back! Every little thing will help me write better games in the near future. I don't need anybody to go over the entire thing unless they really want to, simply giving me style tips is more than helpful.
So thank you to anyone who takes the time to review this. I really do appreciate it. Although I worked very hard on this I do recognize that it is amateur stuff.
Here you are:
// The mandatory tic tac toe game to practice ncurses stuff.
// Date Started: 21 DEC 2018
#include <ncurses.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// Global Variables
time_t t;
int row, col, x, y, px, py, slen, sx;
// constants to use with the COLOR_PAIR() attribute for easier remembering
const int Xs = 1;
const int Os = 2;
const int BG = 3;
/*
Board (7 x 7 character cells):
Line 1 = ------- (7 hyphens)
Line 2 = | | | | Spaces: 1=(1, 1) 2=(1, 3) 3=(1, 5)
Line 3 = -------
Line 4 = | | | | 4=(3, 1) 5=(3, 3) 6=(3, 5)
Line 5 = -------
Line 6 = | | | | 7=(5, 1) 8=(5, 3) 9=(5, 5)
Line 7 = -------
*/
const int line_len = 7; // constant int for the length of a board line
char break_lines = "-------"; // Strings representing the board lines, minus the pieces
char play_lines = "| | | |";
/*
These spaces are abstractions, since the board itself will need to vary based on the current size of the terminal.
They represent the current values of the "playable" spaces.
*/
char space_1 = ' ';
char space_2 = ' ';
char space_3 = ' ';
char space_4 = ' ';
char space_5 = ' ';
char space_6 = ' ';
char space_7 = ' ';
char space_8 = ' ';
char space_9 = ' ';
int space_1y, space_1x;
int space_2y, space_2x;
int space_3y, space_3x;
int space_4y, space_4x;
int space_5y, space_5x;
int space_6y, space_6x;
int space_7y, space_7x;
int space_8y, space_8x;
int space_9y, space_9x;
char *space_ptr = NULL; // Pointer to one of the playable spaces
// Player's side ('X' or 'O')
char side;
int running = 1; // Main menu control variable
int playing = 1; // Game loop control variable
int turn = 1; // Turn number
int game_over = 0;
// Function Declarations
void f_intro(); // Print elaborate "animated" intro splash
char f_setup(); // Prompt player to pick a side or pick random selection, returns char
void f_paint(); // Paint the board state on a given turn
void f_turn_input(); // Take player input and determine AI action (includes sub-functions probably)
void f_player_turn(); // Take player input and place the appropriate mark on the board
void f_AI_turn(); // Logic (such as it is) and Placement for AI turn
void f_evaluate_turn(); // Check for endgame state and either advance the turn or end the game (with sub-functions probably)
int f_AI_picker(); // Picks random spots until it finds an empty one or tries them all
void f_declare_winner(char symbol); // Takes the winning character and creates a splash screen declaring victory ('X', 'O', or 'T' for Tie)
// Main Function
int main(){
srand((unsigned) time(&t));
initscr();
clear();
cbreak();
keypad(stdscr, 1);
curs_set(0);
noecho();
start_color();
init_pair(Xs, COLOR_CYAN, COLOR_BLACK);
init_pair(Os, COLOR_RED, COLOR_BLACK);
init_pair(BG, COLOR_YELLOW, COLOR_BLACK);
f_intro(); // Print intro splash
getch();
while(running){
clear();
side = f_setup(); // Choose X's, O's, or RANDOM SIDE
playing = 1;
while(playing){
f_paint(); // Paint the board as it is that turn
f_turn_input(); // Take player input and if valid determine AI move + sub-functions
turn++;
}
// To-Do, a reset function
}
endwin();
return 0;
}
// Function Definitions
void f_intro(){
// Print elaborate "animated" intro splash
int which;
clear();
getmaxyx(stdscr, row, col);
// Print the background
for(y=0;y<=row;y++){
for(x=0;x<=col;x++){
which = rand() % 3;
if(which == 0){
// Print an "X" in the cell
attron(COLOR_PAIR(Xs));
mvprintw(y, x, "X");
attroff(COLOR_PAIR(Xs));
}else if(which == 1){
// Print an "O" in the cell
attron(COLOR_PAIR(Os));
mvprintw(y, x, "O");
attroff(COLOR_PAIR(Os));
}else if(which == 2){
// Print a blank black space in the cell
attron(COLOR_PAIR(BG));
mvprintw(y, x, " ");
attroff(COLOR_PAIR(BG));
}
}
}
// Print the Title
y = row / 2 - 1;
char intro_str = " NCURSES Tic Tac Toe! ";
char intro_str_padding = " ";
char intro_str2 = " any key to continue ";
slen = strlen(intro_str);
x = col / 2 - slen / 2;
mvprintw(y++, x, intro_str_padding);
mvprintw(y++, x, intro_str);
mvprintw(y++, x, intro_str2);
mvprintw(y, x, intro_str_padding);
refresh();
}
char f_setup(){
// Prompt player to pick a side or pick random selection, returns char
int input;
clear();
getmaxyx(stdscr, row, col);
char setup_str1 = "Pick a side!";
char setup_str2 = "Press 'X', 'O', or 'R' for Random!";
char *chose_x = "You chose X's! Any key to continue...";
char *chose_y = "You chose O's! Any key to continue...";
char *choice_ptr = NULL;
y = row / 2 - 1;
slen = strlen(setup_str1);
x = col / 2 - slen / 2;
mvprintw(y++, x, setup_str1);
slen = strlen(setup_str2);
x = col / 2 - slen / 2;
mvprintw(y++, x, setup_str2);
y++;
refresh();
input = getch();
if(input == 'X' || input == 'x'){
choice_ptr = chose_x;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'X';
}else if(input == 'O' || input == 'o'){
choice_ptr = chose_y;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'O';
}else if(input == 'R' || input == 'r'){
int r;
r = rand() % 2;
if(r == 0){
// Pick 'X'
choice_ptr = chose_x;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'X';
}else if(r == 1){
// Pick 'O'
choice_ptr = chose_y;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'O';
}
}else{
char err_str = "Input error! Any key to continue...";
slen = strlen(err_str);
x = col / 2 - slen / 2;
mvprintw(y, x, err_str);
refresh();
getch();
f_setup();
}
}
void f_paint(){
// Paint the board state on a given turn
/*
1. Clear screen.
2. Paint blank board.
3. Paint the contents of each playable cell.
4. Refresh screen
*/
clear(); // Clear screen
getmaxyx(stdscr, row, col); // Get current size of terminal
y = row / 2 - 3; // Board is 7x7 characters, so (y / 2 - 3) is a decent top edge
x = col / 2 - 3; // Ditto for (x / 2 - 3) being a decent left edge.
// Determine the locations of the 9 "playable" cells:
space_1y = y + 1; space_1x = x + 1;
space_2y = y + 1; space_2x = x + 3;
space_3y = y + 1; space_3x = x + 5;
space_4y = y + 3; space_4x = x + 1;
space_5y = y + 3; space_5x = x + 3;
space_6y = y + 3; space_6x = x + 5;
space_7y = y + 5; space_7x = x + 1;
space_8y = y + 5; space_8x = x + 3;
space_9y = y + 5; space_9x = x + 5;
// Paint the board roughly centered:
int yy, xx;
attron(COLOR_PAIR(BG));
for(yy = 0; yy < line_len; yy++){
if(yy == 0 || yy % 2 == 0){
mvprintw(y + yy, x, break_lines);
}else{
mvprintw(y + yy, x, play_lines);
}
}
attroff(COLOR_PAIR(BG));
// Insert appropriate characters into the "playable" cells:
if(space_1 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_1 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_1y, space_1x, space_1);
if(space_2 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_2 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_2y, space_2x, space_2);
if(space_3 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_3 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_3y, space_3x, space_3);
if(space_4 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_4 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_4y, space_4x, space_4);
if(space_5 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_5 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_5y, space_5x, space_5);
if(space_6 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_6 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_6y, space_6x, space_6);
if(space_7 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_7 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_7y, space_7x, space_7);
if(space_8 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_8 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_8y, space_8x, space_8);
if(space_9 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_9 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_9y, space_9x, space_9);
attroff(COLOR_PAIR(Xs));
attroff(COLOR_PAIR(Os));
refresh();
}
void f_turn_input(){
// Take player input and determine AI action (includes sub-functions probably)
/*
1. Determine who goes first.
- Using if/else to divide the function into two halves for each possibility.
2. Player/AI Takes turn. -> Refresh
3. Player/AI takes turn. -> Refresh
Note on AI: No real logic for this version. Just going to randomly pick from the available spaces.
*/
if(side == 'X'){
// if player is 'X':
f_player_turn();
f_evaluate_turn();
if(game_over == 0){
f_AI_turn();
f_evaluate_turn();
}
}else if(side == 'O'){
// If player is 'O':
f_AI_turn();
f_evaluate_turn();
if(game_over == 0){
f_player_turn();
f_evaluate_turn();
}
}
refresh();
}
void f_player_turn(){
// Take player input and place the appropriate mark on the board
int info_line = y + 10; // Determine the line that the info splash will show up on.
char move_splash = "Use arrow keys and press 'P' to place your piece!";
char done_splash = "Good move!";
char move_err_splash = "You can't move that way!";
char input_err_splash = "Invalid input!";
char full_err_splash = "Spot already claimed!";
slen = strlen(move_splash);
sx = col / 2 - slen / 2; // Center the info splash
mvprintw(info_line, sx, move_splash);
curs_set(1); // Enable the cursor for the player
int pos_y = space_1y; // Y position of the cursor
int pos_x = space_1x; // X position of the cursor
move(pos_y, pos_x); // Move it to space 1
refresh();
int inputting = 1;
while(inputting){
int input;
char spot;
int cx;
input = getch();
if(input == KEY_LEFT){
if(!(pos_x == space_1x)){
// If not on the left playable edge
pos_x -= 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == KEY_RIGHT){
if(!(pos_x == space_3x)){
// If not on the right playable edge
pos_x += 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == KEY_UP){
if(!(pos_y == space_1y)){
// If not on the top playable edge
pos_y -= 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == KEY_DOWN){
if(!(pos_y == space_9y)){
// If not on the bottom playable edge
pos_y += 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == 'P' || input == 'p'){
/*
1. Read contents of space.
2. If Empty -> Place player's symbol
3. Else, try again
*/
if(pos_y == space_1y && pos_x == space_1x){
space_ptr = &space_1;
}else if(pos_y == space_2y && pos_x == space_2x){
space_ptr = &space_2;
}else if(pos_y == space_3y && pos_x == space_3x){
space_ptr = &space_3;
}else if(pos_y == space_4y && pos_x == space_4x){
space_ptr = &space_4;
}else if(pos_y == space_5y && pos_x == space_5x){
space_ptr = &space_5;
}else if(pos_y == space_6y && pos_x == space_6x){
space_ptr = &space_6;
}else if(pos_y == space_7y && pos_x == space_7x){
space_ptr = &space_7;
}else if(pos_y == space_8y && pos_x == space_8x){
space_ptr = &space_8;
}else if(pos_y == space_9y && pos_x == space_9x){
space_ptr = &space_9;
}
if(*space_ptr == ' '){
if(side == 'X'){
*space_ptr = 'X';
}else{
*space_ptr = 'O';
}
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(done_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, done_splash);
move(pos_y, pos_x);
refresh();
inputting = 0;
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(full_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, full_err_splash);
move(pos_y, pos_x);
}
}else{
// If the user presses any other button
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(input_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, input_err_splash);
move(pos_y, pos_x);
}
}
}
int f_AI_picker(){
/*
1. Pick a number between 1 and 9
2. Randomly decide whether to check spaces from 1 to 9 or 9 to 1 for the sake of variety
3. Check them in the determined order until an open space is found.
4. Return number of open space.
Note: This version has no real strategic logic and will be easily beaten by any player.
Although a quick fix for some added challenge is to make it prioritize the center tile.
*/
int pick;
pick = rand() % 9 + 1;
int order; // 1 = Ascending, 2 = Descending
order = rand() % 2 + 1;
if(space_5 == ' '){
return 5;
}else{
if(order == 1){
if(space_1 == ' '){
return 1;
}else if(space_2 == ' '){
return 2;
}else if(space_3 == ' '){
return 3;
}else if(space_4 == ' '){
return 4;
}else if(space_6 == ' '){
return 6;
}else if(space_7 == ' '){
return 7;
}else if(space_8 == ' '){
return 8;
}else if(space_9 == ' '){
return 9;
}
}else if(order == 2){
if(space_9 == ' '){
return 9;
}else if(space_8 == ' '){
return 8;
}else if(space_7 == ' '){
return 7;
}else if(space_6 == ' '){
return 6;
}else if(space_4 == ' '){
return 4;
}else if(space_3 == ' '){
return 3;
}else if(space_2 == ' '){
return 2;
}else if(space_1 == ' '){
return 1;
}
}
}
}
void f_AI_turn(){
// Logic (such as it is) and Placement for AI turn
char AI_char;
if(side == 'X'){
AI_char = 'O';
}else{
AI_char = 'X';
}
int space_to_place;
space_to_place = f_AI_picker();
if(space_to_place == 1){
space_1 = AI_char;
}else if(space_to_place == 2){
space_2 = AI_char;
}else if(space_to_place == 3){
space_3 = AI_char;
}else if(space_to_place == 4){
space_4 = AI_char;
}else if(space_to_place == 5){
space_5 = AI_char;
}else if(space_to_place == 6){
space_6 = AI_char;
}else if(space_to_place == 7){
space_7 = AI_char;
}else if(space_to_place == 8){
space_8 = AI_char;
}else if(space_to_place == 9){
space_9 = AI_char;
}
f_paint();
refresh();
}
void f_declare_winner(char symbol){
// Takes the winning character and creates a splash screen declaring victory ('X', 'O', or 'T' for Tie)
char *x_wins = " X is the winner! ";
char *o_wins = " O is the winner! ";
char *tie_game = " The game is a tie! ";
char padding = " ";
char *win_splash_ptr = NULL;
// Paint background for victory splash:
if(symbol == 'X'){
win_splash_ptr = x_wins;
attron(COLOR_PAIR(Xs));
for(y = 0; y <= row; y++){
for(x = 0; x <= col; x++){
if(x == 0 || x % 2 == 0){
mvaddch(y, x, 'X');
}else{
mvaddch(y, x, ' ');
}
}
}
attroff(COLOR_PAIR(Xs));
}else if(symbol == 'O'){
win_splash_ptr = o_wins;
attron(COLOR_PAIR(Os));
for(y = 0; y <= row; y++){
for(x = 0; x <= col; x++){
if(x == 0 || x % 2 == 0){
mvaddch(y, x, 'O');
}else{
mvaddch(y, x, ' ');
}
}
}
attroff(COLOR_PAIR(Os));
}else if(symbol == 'T'){
win_splash_ptr = tie_game;
for(y = 0; y <= row; y++){
for(x = 0; x <= col; x++){
if(x == 0 || x % 2 == 0){
attron(COLOR_PAIR(Xs));
mvaddch(y, x, 'X');
attroff(COLOR_PAIR(Xs));
}else{
attron(COLOR_PAIR(Os));
mvaddch(y, x, 'O');
attroff(COLOR_PAIR(Os));
}
}
}
}
//Paint the prompt
y = row / 2 - 2;
slen = strlen(win_splash_ptr);
x = col / 2 - slen / 2;
mvprintw(y++, x, padding);
mvprintw(y++, x, win_splash_ptr);
mvprintw(y, x, padding);
curs_set(0);
refresh();
getch();
running = 0;
playing = 0;
}
void f_evaluate_turn(){
// Check for endgame state and either advance the turn or end the game (with sub-functions probably)
/*
1. Check for each possible victory condition. -> If so, declare appropriate victory.
2. Check if turn number is high enough to indicate a tie -> If so, declare a tie.
3. Else, continue.
*/
int winner;
winner = 'N'; // For none
if(space_1 == 'O' && space_2 == 'O' && space_3 == 'O'){
winner = 'O';
game_over++;
}else if(space_4 == 'O' && space_5 == 'O' && space_6 == 'O'){
winner = 'O';
game_over++;
}else if(space_7 == 'O' && space_8 == 'O' && space_9 == 'O'){
winner = 'O';
game_over++;
}else if(space_1 == 'O' && space_4 == 'O' && space_7 == 'O'){
winner = 'O';
game_over++;
}else if(space_2 == 'O' && space_5 == 'O' && space_8 == 'O'){
winner = 'O';
game_over++;
}else if(space_3 == 'O' && space_6 == 'O' && space_9 == 'O'){
winner = 'O';
game_over++;
}else if(space_1 == 'O' && space_5 == 'O' && space_9 == 'O'){
winner = 'O';
game_over++;
}else if(space_3 == 'O' && space_5 == 'O' && space_7 == 'O'){
winner = 'O';
game_over++;
}else if(space_1 == 'X' && space_2 == 'X' && space_3 == 'X'){
winner = 'X';
game_over++;
}else if(space_4 == 'X' && space_5 == 'X' && space_6 == 'X'){
winner = 'X';
game_over++;
}else if(space_7 == 'X' && space_8 == 'X' && space_9 == 'X'){
winner = 'X';
game_over++;
}else if(space_1 == 'X' && space_4 == 'X' && space_7 == 'X'){
winner = 'X';
game_over++;
}else if(space_2 == 'X' && space_5 == 'X' && space_8 == 'X'){
winner = 'X';
game_over++;
}else if(space_3 == 'X' && space_6 == 'X' && space_9 == 'X'){
winner = 'X';
game_over++;
}else if(space_1 == 'X' && space_5 == 'X' && space_9 == 'X'){
winner = 'X';
game_over++;
}else if(space_3 == 'X' && space_5 == 'X' && space_7 == 'X'){
winner = 'X';
game_over++;
}else if(turn >= 5){
winner = 'T';
game_over++;
}
if(winner != 'N'){
f_declare_winner(winner);
}
}
And of course it goes without saying but any fellow linux users who want to throw this in their home directory and play it themselves are more than welcome to do so! Just be aware that without more strategic thinking the AI is a total pushover. I only went so far as making it prioritize the center square.
beginner c game tic-tac-toe
New contributor
I have read your rules and in particular the part about bite-sized portions, so I am apprehensive about posting this but want to do it anyway. I have recently begun getting into C and in particular ncurses with the desire to make some games for the sake of historical appreciation (at least a Rogue clone, probably more). Python is the language I've messed with most extensively to this point and I'm far from an expert in that.
If anyone has the time and the desire, here is 715 lines of a complete and working Tic Tac Toe game that I wrote on my Ubuntu Laptop. The AI is no genius and I could definitely make it better but that was not my focus here... my concern was in writing structurally sound code and a portable, good-looking ncurses display. To that end I think I did alright. What I would like to hear are any and all ways that I could improve my coding style to better take advantage of the C language and the ncurses library while making future games. Don't hold back! Every little thing will help me write better games in the near future. I don't need anybody to go over the entire thing unless they really want to, simply giving me style tips is more than helpful.
So thank you to anyone who takes the time to review this. I really do appreciate it. Although I worked very hard on this I do recognize that it is amateur stuff.
Here you are:
// The mandatory tic tac toe game to practice ncurses stuff.
// Date Started: 21 DEC 2018
#include <ncurses.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// Global Variables
time_t t;
int row, col, x, y, px, py, slen, sx;
// constants to use with the COLOR_PAIR() attribute for easier remembering
const int Xs = 1;
const int Os = 2;
const int BG = 3;
/*
Board (7 x 7 character cells):
Line 1 = ------- (7 hyphens)
Line 2 = | | | | Spaces: 1=(1, 1) 2=(1, 3) 3=(1, 5)
Line 3 = -------
Line 4 = | | | | 4=(3, 1) 5=(3, 3) 6=(3, 5)
Line 5 = -------
Line 6 = | | | | 7=(5, 1) 8=(5, 3) 9=(5, 5)
Line 7 = -------
*/
const int line_len = 7; // constant int for the length of a board line
char break_lines = "-------"; // Strings representing the board lines, minus the pieces
char play_lines = "| | | |";
/*
These spaces are abstractions, since the board itself will need to vary based on the current size of the terminal.
They represent the current values of the "playable" spaces.
*/
char space_1 = ' ';
char space_2 = ' ';
char space_3 = ' ';
char space_4 = ' ';
char space_5 = ' ';
char space_6 = ' ';
char space_7 = ' ';
char space_8 = ' ';
char space_9 = ' ';
int space_1y, space_1x;
int space_2y, space_2x;
int space_3y, space_3x;
int space_4y, space_4x;
int space_5y, space_5x;
int space_6y, space_6x;
int space_7y, space_7x;
int space_8y, space_8x;
int space_9y, space_9x;
char *space_ptr = NULL; // Pointer to one of the playable spaces
// Player's side ('X' or 'O')
char side;
int running = 1; // Main menu control variable
int playing = 1; // Game loop control variable
int turn = 1; // Turn number
int game_over = 0;
// Function Declarations
void f_intro(); // Print elaborate "animated" intro splash
char f_setup(); // Prompt player to pick a side or pick random selection, returns char
void f_paint(); // Paint the board state on a given turn
void f_turn_input(); // Take player input and determine AI action (includes sub-functions probably)
void f_player_turn(); // Take player input and place the appropriate mark on the board
void f_AI_turn(); // Logic (such as it is) and Placement for AI turn
void f_evaluate_turn(); // Check for endgame state and either advance the turn or end the game (with sub-functions probably)
int f_AI_picker(); // Picks random spots until it finds an empty one or tries them all
void f_declare_winner(char symbol); // Takes the winning character and creates a splash screen declaring victory ('X', 'O', or 'T' for Tie)
// Main Function
int main(){
srand((unsigned) time(&t));
initscr();
clear();
cbreak();
keypad(stdscr, 1);
curs_set(0);
noecho();
start_color();
init_pair(Xs, COLOR_CYAN, COLOR_BLACK);
init_pair(Os, COLOR_RED, COLOR_BLACK);
init_pair(BG, COLOR_YELLOW, COLOR_BLACK);
f_intro(); // Print intro splash
getch();
while(running){
clear();
side = f_setup(); // Choose X's, O's, or RANDOM SIDE
playing = 1;
while(playing){
f_paint(); // Paint the board as it is that turn
f_turn_input(); // Take player input and if valid determine AI move + sub-functions
turn++;
}
// To-Do, a reset function
}
endwin();
return 0;
}
// Function Definitions
void f_intro(){
// Print elaborate "animated" intro splash
int which;
clear();
getmaxyx(stdscr, row, col);
// Print the background
for(y=0;y<=row;y++){
for(x=0;x<=col;x++){
which = rand() % 3;
if(which == 0){
// Print an "X" in the cell
attron(COLOR_PAIR(Xs));
mvprintw(y, x, "X");
attroff(COLOR_PAIR(Xs));
}else if(which == 1){
// Print an "O" in the cell
attron(COLOR_PAIR(Os));
mvprintw(y, x, "O");
attroff(COLOR_PAIR(Os));
}else if(which == 2){
// Print a blank black space in the cell
attron(COLOR_PAIR(BG));
mvprintw(y, x, " ");
attroff(COLOR_PAIR(BG));
}
}
}
// Print the Title
y = row / 2 - 1;
char intro_str = " NCURSES Tic Tac Toe! ";
char intro_str_padding = " ";
char intro_str2 = " any key to continue ";
slen = strlen(intro_str);
x = col / 2 - slen / 2;
mvprintw(y++, x, intro_str_padding);
mvprintw(y++, x, intro_str);
mvprintw(y++, x, intro_str2);
mvprintw(y, x, intro_str_padding);
refresh();
}
char f_setup(){
// Prompt player to pick a side or pick random selection, returns char
int input;
clear();
getmaxyx(stdscr, row, col);
char setup_str1 = "Pick a side!";
char setup_str2 = "Press 'X', 'O', or 'R' for Random!";
char *chose_x = "You chose X's! Any key to continue...";
char *chose_y = "You chose O's! Any key to continue...";
char *choice_ptr = NULL;
y = row / 2 - 1;
slen = strlen(setup_str1);
x = col / 2 - slen / 2;
mvprintw(y++, x, setup_str1);
slen = strlen(setup_str2);
x = col / 2 - slen / 2;
mvprintw(y++, x, setup_str2);
y++;
refresh();
input = getch();
if(input == 'X' || input == 'x'){
choice_ptr = chose_x;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'X';
}else if(input == 'O' || input == 'o'){
choice_ptr = chose_y;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'O';
}else if(input == 'R' || input == 'r'){
int r;
r = rand() % 2;
if(r == 0){
// Pick 'X'
choice_ptr = chose_x;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'X';
}else if(r == 1){
// Pick 'O'
choice_ptr = chose_y;
slen = strlen(choice_ptr);
x = col / 2 - slen / 2;
mvprintw(y, x, choice_ptr);
refresh();
getch();
return 'O';
}
}else{
char err_str = "Input error! Any key to continue...";
slen = strlen(err_str);
x = col / 2 - slen / 2;
mvprintw(y, x, err_str);
refresh();
getch();
f_setup();
}
}
void f_paint(){
// Paint the board state on a given turn
/*
1. Clear screen.
2. Paint blank board.
3. Paint the contents of each playable cell.
4. Refresh screen
*/
clear(); // Clear screen
getmaxyx(stdscr, row, col); // Get current size of terminal
y = row / 2 - 3; // Board is 7x7 characters, so (y / 2 - 3) is a decent top edge
x = col / 2 - 3; // Ditto for (x / 2 - 3) being a decent left edge.
// Determine the locations of the 9 "playable" cells:
space_1y = y + 1; space_1x = x + 1;
space_2y = y + 1; space_2x = x + 3;
space_3y = y + 1; space_3x = x + 5;
space_4y = y + 3; space_4x = x + 1;
space_5y = y + 3; space_5x = x + 3;
space_6y = y + 3; space_6x = x + 5;
space_7y = y + 5; space_7x = x + 1;
space_8y = y + 5; space_8x = x + 3;
space_9y = y + 5; space_9x = x + 5;
// Paint the board roughly centered:
int yy, xx;
attron(COLOR_PAIR(BG));
for(yy = 0; yy < line_len; yy++){
if(yy == 0 || yy % 2 == 0){
mvprintw(y + yy, x, break_lines);
}else{
mvprintw(y + yy, x, play_lines);
}
}
attroff(COLOR_PAIR(BG));
// Insert appropriate characters into the "playable" cells:
if(space_1 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_1 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_1y, space_1x, space_1);
if(space_2 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_2 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_2y, space_2x, space_2);
if(space_3 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_3 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_3y, space_3x, space_3);
if(space_4 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_4 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_4y, space_4x, space_4);
if(space_5 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_5 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_5y, space_5x, space_5);
if(space_6 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_6 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_6y, space_6x, space_6);
if(space_7 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_7 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_7y, space_7x, space_7);
if(space_8 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_8 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_8y, space_8x, space_8);
if(space_9 == 'X'){
attron(COLOR_PAIR(Xs));
}else if(space_9 == 'O'){
attron(COLOR_PAIR(Os));
}
mvaddch(space_9y, space_9x, space_9);
attroff(COLOR_PAIR(Xs));
attroff(COLOR_PAIR(Os));
refresh();
}
void f_turn_input(){
// Take player input and determine AI action (includes sub-functions probably)
/*
1. Determine who goes first.
- Using if/else to divide the function into two halves for each possibility.
2. Player/AI Takes turn. -> Refresh
3. Player/AI takes turn. -> Refresh
Note on AI: No real logic for this version. Just going to randomly pick from the available spaces.
*/
if(side == 'X'){
// if player is 'X':
f_player_turn();
f_evaluate_turn();
if(game_over == 0){
f_AI_turn();
f_evaluate_turn();
}
}else if(side == 'O'){
// If player is 'O':
f_AI_turn();
f_evaluate_turn();
if(game_over == 0){
f_player_turn();
f_evaluate_turn();
}
}
refresh();
}
void f_player_turn(){
// Take player input and place the appropriate mark on the board
int info_line = y + 10; // Determine the line that the info splash will show up on.
char move_splash = "Use arrow keys and press 'P' to place your piece!";
char done_splash = "Good move!";
char move_err_splash = "You can't move that way!";
char input_err_splash = "Invalid input!";
char full_err_splash = "Spot already claimed!";
slen = strlen(move_splash);
sx = col / 2 - slen / 2; // Center the info splash
mvprintw(info_line, sx, move_splash);
curs_set(1); // Enable the cursor for the player
int pos_y = space_1y; // Y position of the cursor
int pos_x = space_1x; // X position of the cursor
move(pos_y, pos_x); // Move it to space 1
refresh();
int inputting = 1;
while(inputting){
int input;
char spot;
int cx;
input = getch();
if(input == KEY_LEFT){
if(!(pos_x == space_1x)){
// If not on the left playable edge
pos_x -= 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == KEY_RIGHT){
if(!(pos_x == space_3x)){
// If not on the right playable edge
pos_x += 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == KEY_UP){
if(!(pos_y == space_1y)){
// If not on the top playable edge
pos_y -= 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == KEY_DOWN){
if(!(pos_y == space_9y)){
// If not on the bottom playable edge
pos_y += 2;
move(pos_y, pos_x);
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(move_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, move_err_splash);
move(pos_y, pos_x);
}
}else if(input == 'P' || input == 'p'){
/*
1. Read contents of space.
2. If Empty -> Place player's symbol
3. Else, try again
*/
if(pos_y == space_1y && pos_x == space_1x){
space_ptr = &space_1;
}else if(pos_y == space_2y && pos_x == space_2x){
space_ptr = &space_2;
}else if(pos_y == space_3y && pos_x == space_3x){
space_ptr = &space_3;
}else if(pos_y == space_4y && pos_x == space_4x){
space_ptr = &space_4;
}else if(pos_y == space_5y && pos_x == space_5x){
space_ptr = &space_5;
}else if(pos_y == space_6y && pos_x == space_6x){
space_ptr = &space_6;
}else if(pos_y == space_7y && pos_x == space_7x){
space_ptr = &space_7;
}else if(pos_y == space_8y && pos_x == space_8x){
space_ptr = &space_8;
}else if(pos_y == space_9y && pos_x == space_9x){
space_ptr = &space_9;
}
if(*space_ptr == ' '){
if(side == 'X'){
*space_ptr = 'X';
}else{
*space_ptr = 'O';
}
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(done_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, done_splash);
move(pos_y, pos_x);
refresh();
inputting = 0;
}else{
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(full_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, full_err_splash);
move(pos_y, pos_x);
}
}else{
// If the user presses any other button
for(cx = sx; cx <= col; cx++){
// Clear the info line
mvaddch(info_line, cx, ' ');
}
slen = strlen(input_err_splash);
sx = col / 2 - slen / 2;
mvprintw(info_line, sx, input_err_splash);
move(pos_y, pos_x);
}
}
}
int f_AI_picker(){
/*
1. Pick a number between 1 and 9
2. Randomly decide whether to check spaces from 1 to 9 or 9 to 1 for the sake of variety
3. Check them in the determined order until an open space is found.
4. Return number of open space.
Note: This version has no real strategic logic and will be easily beaten by any player.
Although a quick fix for some added challenge is to make it prioritize the center tile.
*/
int pick;
pick = rand() % 9 + 1;
int order; // 1 = Ascending, 2 = Descending
order = rand() % 2 + 1;
if(space_5 == ' '){
return 5;
}else{
if(order == 1){
if(space_1 == ' '){
return 1;
}else if(space_2 == ' '){
return 2;
}else if(space_3 == ' '){
return 3;
}else if(space_4 == ' '){
return 4;
}else if(space_6 == ' '){
return 6;
}else if(space_7 == ' '){
return 7;
}else if(space_8 == ' '){
return 8;
}else if(space_9 == ' '){
return 9;
}
}else if(order == 2){
if(space_9 == ' '){
return 9;
}else if(space_8 == ' '){
return 8;
}else if(space_7 == ' '){
return 7;
}else if(space_6 == ' '){
return 6;
}else if(space_4 == ' '){
return 4;
}else if(space_3 == ' '){
return 3;
}else if(space_2 == ' '){
return 2;
}else if(space_1 == ' '){
return 1;
}
}
}
}
void f_AI_turn(){
// Logic (such as it is) and Placement for AI turn
char AI_char;
if(side == 'X'){
AI_char = 'O';
}else{
AI_char = 'X';
}
int space_to_place;
space_to_place = f_AI_picker();
if(space_to_place == 1){
space_1 = AI_char;
}else if(space_to_place == 2){
space_2 = AI_char;
}else if(space_to_place == 3){
space_3 = AI_char;
}else if(space_to_place == 4){
space_4 = AI_char;
}else if(space_to_place == 5){
space_5 = AI_char;
}else if(space_to_place == 6){
space_6 = AI_char;
}else if(space_to_place == 7){
space_7 = AI_char;
}else if(space_to_place == 8){
space_8 = AI_char;
}else if(space_to_place == 9){
space_9 = AI_char;
}
f_paint();
refresh();
}
void f_declare_winner(char symbol){
// Takes the winning character and creates a splash screen declaring victory ('X', 'O', or 'T' for Tie)
char *x_wins = " X is the winner! ";
char *o_wins = " O is the winner! ";
char *tie_game = " The game is a tie! ";
char padding = " ";
char *win_splash_ptr = NULL;
// Paint background for victory splash:
if(symbol == 'X'){
win_splash_ptr = x_wins;
attron(COLOR_PAIR(Xs));
for(y = 0; y <= row; y++){
for(x = 0; x <= col; x++){
if(x == 0 || x % 2 == 0){
mvaddch(y, x, 'X');
}else{
mvaddch(y, x, ' ');
}
}
}
attroff(COLOR_PAIR(Xs));
}else if(symbol == 'O'){
win_splash_ptr = o_wins;
attron(COLOR_PAIR(Os));
for(y = 0; y <= row; y++){
for(x = 0; x <= col; x++){
if(x == 0 || x % 2 == 0){
mvaddch(y, x, 'O');
}else{
mvaddch(y, x, ' ');
}
}
}
attroff(COLOR_PAIR(Os));
}else if(symbol == 'T'){
win_splash_ptr = tie_game;
for(y = 0; y <= row; y++){
for(x = 0; x <= col; x++){
if(x == 0 || x % 2 == 0){
attron(COLOR_PAIR(Xs));
mvaddch(y, x, 'X');
attroff(COLOR_PAIR(Xs));
}else{
attron(COLOR_PAIR(Os));
mvaddch(y, x, 'O');
attroff(COLOR_PAIR(Os));
}
}
}
}
//Paint the prompt
y = row / 2 - 2;
slen = strlen(win_splash_ptr);
x = col / 2 - slen / 2;
mvprintw(y++, x, padding);
mvprintw(y++, x, win_splash_ptr);
mvprintw(y, x, padding);
curs_set(0);
refresh();
getch();
running = 0;
playing = 0;
}
void f_evaluate_turn(){
// Check for endgame state and either advance the turn or end the game (with sub-functions probably)
/*
1. Check for each possible victory condition. -> If so, declare appropriate victory.
2. Check if turn number is high enough to indicate a tie -> If so, declare a tie.
3. Else, continue.
*/
int winner;
winner = 'N'; // For none
if(space_1 == 'O' && space_2 == 'O' && space_3 == 'O'){
winner = 'O';
game_over++;
}else if(space_4 == 'O' && space_5 == 'O' && space_6 == 'O'){
winner = 'O';
game_over++;
}else if(space_7 == 'O' && space_8 == 'O' && space_9 == 'O'){
winner = 'O';
game_over++;
}else if(space_1 == 'O' && space_4 == 'O' && space_7 == 'O'){
winner = 'O';
game_over++;
}else if(space_2 == 'O' && space_5 == 'O' && space_8 == 'O'){
winner = 'O';
game_over++;
}else if(space_3 == 'O' && space_6 == 'O' && space_9 == 'O'){
winner = 'O';
game_over++;
}else if(space_1 == 'O' && space_5 == 'O' && space_9 == 'O'){
winner = 'O';
game_over++;
}else if(space_3 == 'O' && space_5 == 'O' && space_7 == 'O'){
winner = 'O';
game_over++;
}else if(space_1 == 'X' && space_2 == 'X' && space_3 == 'X'){
winner = 'X';
game_over++;
}else if(space_4 == 'X' && space_5 == 'X' && space_6 == 'X'){
winner = 'X';
game_over++;
}else if(space_7 == 'X' && space_8 == 'X' && space_9 == 'X'){
winner = 'X';
game_over++;
}else if(space_1 == 'X' && space_4 == 'X' && space_7 == 'X'){
winner = 'X';
game_over++;
}else if(space_2 == 'X' && space_5 == 'X' && space_8 == 'X'){
winner = 'X';
game_over++;
}else if(space_3 == 'X' && space_6 == 'X' && space_9 == 'X'){
winner = 'X';
game_over++;
}else if(space_1 == 'X' && space_5 == 'X' && space_9 == 'X'){
winner = 'X';
game_over++;
}else if(space_3 == 'X' && space_5 == 'X' && space_7 == 'X'){
winner = 'X';
game_over++;
}else if(turn >= 5){
winner = 'T';
game_over++;
}
if(winner != 'N'){
f_declare_winner(winner);
}
}
And of course it goes without saying but any fellow linux users who want to throw this in their home directory and play it themselves are more than welcome to do so! Just be aware that without more strategic thinking the AI is a total pushover. I only went so far as making it prioritize the center square.
beginner c game tic-tac-toe
beginner c game tic-tac-toe
New contributor
New contributor
edited 4 mins ago
New contributor
asked 18 mins ago
some_guy632
1
1
New contributor
New contributor
Long questions are fine: they just tend to lend themselves to less comprehensive reviews (which is not necessarily a bad thing), and more rounds of review if you revise your code using the suggestions given. You can really learn a great deal from improving a larger program. And if you revise your code, it may turn out to need less than 715 lines!
– Graham
2 mins ago
I don't really want to do too much more with this. Maybe improve the AI a little bit. Mostly I'm interested in tips that will lend themselves to making better games in the future. Also any ways in which I have obviously mis-used the language or the ncurses library (as I'm sure there are a few).
– some_guy632
32 secs ago
add a comment |
Long questions are fine: they just tend to lend themselves to less comprehensive reviews (which is not necessarily a bad thing), and more rounds of review if you revise your code using the suggestions given. You can really learn a great deal from improving a larger program. And if you revise your code, it may turn out to need less than 715 lines!
– Graham
2 mins ago
I don't really want to do too much more with this. Maybe improve the AI a little bit. Mostly I'm interested in tips that will lend themselves to making better games in the future. Also any ways in which I have obviously mis-used the language or the ncurses library (as I'm sure there are a few).
– some_guy632
32 secs ago
Long questions are fine: they just tend to lend themselves to less comprehensive reviews (which is not necessarily a bad thing), and more rounds of review if you revise your code using the suggestions given. You can really learn a great deal from improving a larger program. And if you revise your code, it may turn out to need less than 715 lines!
– Graham
2 mins ago
Long questions are fine: they just tend to lend themselves to less comprehensive reviews (which is not necessarily a bad thing), and more rounds of review if you revise your code using the suggestions given. You can really learn a great deal from improving a larger program. And if you revise your code, it may turn out to need less than 715 lines!
– Graham
2 mins ago
I don't really want to do too much more with this. Maybe improve the AI a little bit. Mostly I'm interested in tips that will lend themselves to making better games in the future. Also any ways in which I have obviously mis-used the language or the ncurses library (as I'm sure there are a few).
– some_guy632
32 secs ago
I don't really want to do too much more with this. Maybe improve the AI a little bit. Mostly I'm interested in tips that will lend themselves to making better games in the future. Also any ways in which I have obviously mis-used the language or the ncurses library (as I'm sure there are a few).
– some_guy632
32 secs ago
add a comment |
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
some_guy632 is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210217%2fncurses-tic-tac-toe-715-lines%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
some_guy632 is a new contributor. Be nice, and check out our Code of Conduct.
some_guy632 is a new contributor. Be nice, and check out our Code of Conduct.
some_guy632 is a new contributor. Be nice, and check out our Code of Conduct.
some_guy632 is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210217%2fncurses-tic-tac-toe-715-lines%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Long questions are fine: they just tend to lend themselves to less comprehensive reviews (which is not necessarily a bad thing), and more rounds of review if you revise your code using the suggestions given. You can really learn a great deal from improving a larger program. And if you revise your code, it may turn out to need less than 715 lines!
– Graham
2 mins ago
I don't really want to do too much more with this. Maybe improve the AI a little bit. Mostly I'm interested in tips that will lend themselves to making better games in the future. Also any ways in which I have obviously mis-used the language or the ncurses library (as I'm sure there are a few).
– some_guy632
32 secs ago