Minimax algorithm behaving strangely - c

I am working on assignment. I want to create a game that is simulated and played by the computer. It is a logical game and the goal is to collect the most tokens with the highest value. Program starts by asking for input in this format('END' signals its the end of input, working on removing it):
N:1,2
W:3,5
E:9,1,1,1
S:1,7
END
Letters stand for direction like East, West, North, South. Numbers after the directions are tokens with value. You can only pick tokens from end of an direction. Player that collects most valuable tokens wins.
I need to make the game to play itself the most optimal way, found minimax algorithm. But I am completly clueless how to implement it correctly.
I am asking some kind soul, to help me make it work correctly. Give some tips at least :)
This is what I tried. Its working somehow but not the most optimal way. For example in the input I provided the most optimal result is:
A/B: 15/16
But I am getting:
A/B: 14/17
My code here.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <math.h>
#define MAX_TOKENS 100
struct Game
{
//lists with the values ​​of the tokens on the individual arms of the cross
int *north;
int *west;
int *east;
int *south;
//lengths of token value lists
int north_size;
int west_size;
int east_size;
int south_size;
//scores of players A and B
int score_a;
int score_b;
};
void remove_token(struct Game *game, int player, char direction)
{
//variable for the token taken
int token;
//according to the direction, remove the token and add its value to the player's score
switch (direction)
{
case 'N':
//remove the token from the N arm
token = game->north[game->north_size - 1];
//reduce the length of the token value list by 1
game->north_size--;
//add the chip value to the player's score
if (player == 0)
game->score_a += token;
else
game->score_b += token;
break;
case 'W':
//remove the token from the W arm
token = game->west[game->west_size - 1];
//reduce the length of the token value list by 1
game->west_size--;
//add the chip value to the player's score
if (player == 0)
game->score_a += token;
else
game->score_b += token;
break;
case 'E':
//remove the token from the E arm
token = game->east[game->east_size - 1];
//reduce the length of the token value list by 1
game->east_size--;
//add the chip value to the player's score
if (player == 0)
game->score_a += token;
else
game->score_b += token;
break;
case 'S':
//remove the token from the S arm
token = game->south[game->south_size - 1];
//reduce the length of the token value list by 1
game->south_size--;
//add the chip value to the player's score
if (player == 0)
game->score_a += token;
else
game->score_b += token;
break;
default:
//throw an error if the player entered an invalid direction
printf("Neplatný směr pro odebrání žetonu!\n");
}
}
char minimax(struct Game *game, int depth, int player, int alpha, int beta)
{
//if all arms are empty or we have reached the maximum depth, return the best direction
if (game->north_size == 0 && game->west_size == 0 && game->east_size == 0 && game->south_size == 0 || depth == 0)
return 'X';
//the best move value for player A
int best_score_a = INT_MIN;
//the best move value for player B
int best_score_b = INT_MAX;
//direction for best move
char best_direction;
//go through all arms to find the best move
if (game->north_size > 0)
{
//copy the game state
struct Game game_copy = *game;
//remove the token from the N arm
remove_token(&game_copy, player, 'N');
//find out the best move for your opponent
int score = minimax(&game_copy, depth - 1, player == 0 ? 1 : 0, alpha, beta);
//update the best move for player A
if (player == 0 && score > best_score_a)
{
best_score_a = score;
best_direction = 'N';
}
//update the best move for player B
if (player == 1 && score < best_score_b)
{
best_score_b = score;
best_direction = 'N';
}
//update alpha and beta
if (player == 0)
alpha = fmax(alpha, score);
else
beta = fmin(beta, score);
//if beta is less than alpha, we end traversing the tree
if (beta <= alpha)
return best_direction;
}
if (game->west_size > 0)
{
//copy the game state
struct Game game_copy = *game;
//remove the token from the W arm
remove_token(&game_copy, player, 'W');
//find out the best move for your opponent
int score = minimax(&game_copy, depth - 1, player == 0 ? 1 : 0, alpha, beta);
//update the best move for player A
if (player == 0 && score > best_score_a)
{
best_score_a = score;
best_direction = 'W';
}
//update the best move for player B
if (player == 1 && score < best_score_b)
{
best_score_b = score;
best_direction = 'W';
}
//update alpha and beta
if (player == 0)
alpha = fmax(alpha, score);
else
beta = fmin(beta, score);
//if beta is less than alpha, we end traversing the tree
if (beta <= alpha)
return best_direction;
}
if (game->east_size > 0)
{
//copy the game state
struct Game game_copy = *game;
//remove the token from the E arm
remove_token(&game_copy, player, 'E');
//find out the best move for your opponent
int score = minimax(&game_copy, depth - 1, player == 0 ? 1 : 0, alpha, beta);
//update the best move for player A
if (player == 0 && score > best_score_a)
{
best_score_a = score;
best_direction = 'E';
}
//update the best move for player B
if (player == 1 && score < best_score_b)
{
best_score_b = score;
best_direction = 'E';
}
//update alpha and beta
if (player == 0)
alpha = fmax(alpha, score);
else
beta = fmin(beta, score);
//if beta is less than alpha, we end traversing the tree
if (beta <= alpha)
return best_direction;
}
if (game->south_size > 0)
{
//copy the game state
struct Game game_copy = *game;
//remove the token from the S arm
remove_token(&game_copy, player, 'S');
//find out the best move for your opponent
int score = minimax(&game_copy, depth - 1, player == 0 ? 1 : 0, alpha, beta);
//update the best move for player A
if (player == 0 && score > best_score_a)
{
best_score_a = score;
best_direction = 'S';
}
//update as soon as possible
if (player == 1 && score < best_score_b)
{
best_score_b = score;
best_direction = 'S';
}
//update alpha and beta
if (player == 0)
alpha = fmax(alpha, score);
else
beta = fmin(beta, score);
//if beta is less than alpha, we end traversing the tree
if (beta <= alpha)
return best_direction;
}
//return the best move
return player == 0 ? best_direction : best_direction;
}
void read_tokens(int *north, int *west, int *east, int *south, int *north_size, int *west_size, int *east_size, int *south_size)
{
//buffer for reading in input
char buffer[MAX_TOKENS];
//read in the input line by line
while (fgets(buffer, MAX_TOKENS, stdin) != NULL)
{
//remove the newline character from the end of the line
buffer[strcspn(buffer, "\n")] = 0;
//check for the "END" string to end the input
if (strcmp(buffer, "END") == 0)
break;
//split the line at the colon character
char *direction = strtok(buffer, ":");
char *tokens = strtok(NULL, ":");
//split the tokens at each comma
char *token = strtok(tokens, ",");
//determine the direction and store the tokens in the appropriate array
if (strcmp(direction, "N") == 0)
{
while (token != NULL)
{
north[*north_size] = atoi(token);
(*north_size)++;
token = strtok(NULL, ",");
}
}
else if (strcmp(direction, "W") == 0)
{
while (token != NULL)
{
west[*west_size] = atoi(token);
(*west_size)++;
token = strtok(NULL, ",");
}
}
else if (strcmp(direction, "E") == 0)
{
while (token != NULL)
{
east[*east_size] = atoi(token);
(*east_size)++;
token = strtok(NULL, ",");
}
}
else if (strcmp(direction, "S") == 0)
{
while (token != NULL)
{
south[*south_size] = atoi(token);
(*south_size)++;
token = strtok(NULL, ",");
}
}
else
{
//invalid direction = error
printf("Nespravny vstup.\n");
}
}
}
void print_progress(struct Game *game, int player, char direction)
{
char letter_player = ' ';
if (player == 0)
{
letter_player = 'A';
}
else
letter_player = 'B';
//printing of individual steps
switch (direction)
{
case 'N':
printf("%c: %c[%d] (%d)\n", letter_player, direction, game->north_size, game->north[game->north_size - 1]);
break;
case 'W':
printf("%c: %c[%d] (%d)\n", letter_player, direction, game->west_size, game->west[game->west_size - 1]);
break;
case 'E':
printf("%c: %c[%d] (%d)\n", letter_player, direction, game->east_size, game->east[game->east_size - 1]);
break;
case 'S':
printf("%c: %c[%d] (%d)\n", letter_player, direction, game->south_size, game->south[game->south_size - 1]);
break;
default:
break;
}
}
void play(struct Game *game, int depth)
{
//variable for current player (A or B)
int player = 0;
//until all chips are taken, alternate players taking chips
while (game->north_size > 0 || game->west_size > 0 || game->east_size > 0 || game->south_size > 0)
{
//player A
if (player == 0)
{
//function on the selection of a token
char direction = minimax(game, depth, 0, INT_MIN, INT_MAX);
print_progress(game, player, direction);
//remove the token from the game
remove_token(game, player, direction);
}
//player B
else
{
//function on the selection of a token
char direction = minimax(game, depth, 1, INT_MIN, INT_MAX);
print_progress(game, player, direction);
//remove the token from the game
remove_token(game, player, direction);
}
//switch players
player = (player + 1) % 2;
}
}
int main(void)
{
//field for token values
int north[MAX_TOKENS], west[MAX_TOKENS], east[MAX_TOKENS], south[MAX_TOKENS];
//sizes of token value fields
int north_size = 0, west_size = 0, east_size = 0, south_size = 0;
printf("Input:\n");
//fetch token values ​​from input
read_tokens(north, west, east, south, &north_size, &west_size, &east_size, &south_size);
//creating a game
struct Game game;
game.north = north;
game.west = west;
game.east = east;
game.south = south;
game.north_size = north_size;
game.west_size = west_size;
game.east_size = east_size;
game.south_size = south_size;
game.score_a = 0;
game.score_b = 0;
//set the maximum depth of the minimax search tree
int depth = 1;
//start the game using the play() function
play(&game, depth);
//evaluation of the result of the game
printf("Celkem A/B: %d/%d\n", game.score_a, game.score_b);
return 0;
}

This is not an answer. The OP did ask for 'tips', though. Here are a few:
Don't drag-in the floating point library (math.h) for a single function ( fmax() ) that you could/should implement, especially as you are dealing with integer values and results.
Here are two 'branchless' statements returning the min or max of two integers: i & j.
int minVal = j ^ ((i^j) & -(i<j));
int maxVal = j ^ ((i^j) & -(i>j));
You can safely drop END from your data file. fgets() will find 4 lines and then return NULL, finishing the loading of the initial values.
As it stands, the initial values are single ASCII digits. Rather than calling atoi(), merely subtracting the ASCII character '0' from each ASCII digit will yield the (binary) integer value intended. (You might consider using 'A'-'Z' in the initial data, instead of '0' - '9', to both increase the range of points and obscure the best path to winning the game.
3a) Instead of "N:1,5,6,4", the data file could contain "N1564" obviating the need for using strtok()... Simply use each character in sequence.
It's quite understandable that you, as a beginner, have coded to the "literal" game involving 4 points of a compass. You've written code that would be very difficult to change to having 5 or 6 (or more) 'stack's of values to choose from.
If, instead of discrete variables named 'north...', 'west...', etc., had you used an array of 4 elements, most of the copy/paste/adapt that has bloated the source code's size would melt down to processing each of the 4 (or 5 or 6) 'stacks' in a loop. imo, the art of coding is balancing the drive to "make something work" with the necessity of code re-use wherever possible. Look for redundancy and strive to eliminate that.
Your code eliminates the trailing '\n' after fgets() has filled the input buffer. You then go on to translate an array of characters into arrays of discrete integer values. Then, the minimax function recusively trims copies of those arrays as it plays the game.
Consider keeping and working with the original array of characters (the string) instead. When "the user" picks the last 'character' (top of the stack) as their 'move', simply shorten the string of that stack. (Use C's string.h string handling capabilities instead of doing the work yourself with your own arrays of ints.) (The string "" has no more available tokens. Your code doesn't need to maintain a counter for each 'direction'.)
(This might be easiest.) When you've moved to using 4 arrays - one for each of 'N', 'W', 'E' and 'S' - you will see that the fixation of compass points has blinded you to using 'A', 'B', 'C' and 'D' instead... Given the "non-special" nature of "ABCD", it becomes a goal to write code that would work for "ABCDE" or "ABCDEF".
Summary: By replacing replicated code with re-used code, your program will shrink down to something easily manageable. When functionality is re-used and made 'general purpose', you will likely be able to improve the minimax processing. Time spent finding these 'improvements' will be re-paid in abundance.
Putting my money where my mouth is, below is the 28-line version of one of the OP's posted 59-line function. This still uses the discrete variables for each "compass direction". Were those bundled into an array, this, too, could be written in 1/2 again as many lines.
void remove_token( struct Game *game, int player, char direction ) {
int *arr, arr_sz;
//according to the direction, remove the token and add its value to the player's score
switch( direction ) {
case 'N':
arr = game->north;
arr_sz = --game->north_size;
break;
case 'W':
arr = game->west;
arr_sz = --game->west_size;
break;
case 'E':
arr = game->east;
arr_sz = --game->east_size;
break;
case 'S':
arr = game->south;
arr_sz = --game->south_size;
break;
}
//add the chip value to the player's score
if (player == 0)
game->score_a += arr[ arr_sz ];
else
game->score_b += arr[ arr_sz ];
}

Related

Infix to postfix conversion and evaluation including whitespaces and double quotes

I have the code below, but I need the code to account for white spaces and double digits, for example if i enter (7-3)/(2+2) it should come out 73-22+/ result: 1. If i enter (7 - 3) / (2 + 2) it should come out 7 3 - 2 2 + / result 1. If I enter (22 - 10)/(2 + 2) it should come out 22 10 - 2 2 + / Result: 3
Here is the code I have:
#include<stdio.h>
char stack[100];
int top = 0;
int eval_top = -1;
int eval_stack[100];
void push(char x) // Push char into stack
{
stack[top++] = x;
}
char pop() // Pop char to top of stack
{
if (top == -1)
return -1;
else
return stack[top--];
}
/* functions for evaluation of postfix expression */
// push function
void eval_push(int x) { // Find push result
eval_stack[++eval_top] = x;
}
// pop function
int eval_pop() { // Find pop result
if (eval_top == -1) {
return -1;
} else {
return eval_stack[eval_top--];
}
}
int priority(char x) // check priority order
{
if (x == '(')
return 0;
if (x == '+' || x == '-')
return 1;
if (x == '*' || x == '/')
return 2;
}
// function to evaluate the postfix expression
void EvalPostfix(char postfix[]) {
int A, B;
int val;
char ch;
int i;
//find postfix
for (i = 0; postfix[i] != ')'; i++) {
ch = postfix[i];
if (isdigit(ch)) {
eval_push(ch - '0');
} else if (ch == '+' || ch == '-' || ch == '*' || ch == '/') {
A = eval_pop();
B = eval_pop();
switch (ch) {
case '*':
val = B * A;
break;
case '/':
val = B / A;
break;
case '+':
val = B + A;
break;
case '-':
val = B - A;
break;
}
eval_push(val); //send value on top of stack
}
}
printf("\n Result: %d \n", eval_pop());
}
main() {
int i = 0;
char * e, x;
char postfix[100]; // store postfix for later evaluation
char exp[100];
printf("Infix expression : ");
scanf("%s", exp); // asking the user to enter the infix expression
printf("Postfix expression: ");
e = exp;
while ( * e != '\0') {
if (isalnum( * e)) { // if character is alphabet or number , it is printed
printf("%c", * e);
postfix[i++] = * e;
} else if ( * e == '(') // if it is open parenthesis, it is pushed into the stack without any priority
push( * e);
else if ( * e == ')') // if it is closed parenthesis , pop the elements in the stack and print them until the we see ( symbol
{
while ((x = pop()) != '(') {
printf("%c", x);
postfix[i++] = x;
}
} else // if character is symbol like +, -, *, / then based on their priority character is pushed if it high priority otherwise high priority symbols are popped and it is pushed
{
while (priority(stack[top]) >= priority( * e)) {
x = pop();
printf("%c", x);
postfix[i++] = x;
}
push( * e);
}
e++;
}
while (top != -1) // printing remaining elements in the stack
{
x = pop();
printf("%c", x);
postfix[i++] = x;
}
postfix[i] = ')'; // this is to add at the end for detecting end by the evaluation function
EvalPostfix(postfix);
}
There are some problems in your code
your pop is not symmetric with your push, push post increment the index so pop must pre decrement the index, and because of that the first invalid index is not -1 but 0 :
char pop() // Pop char to top of stack
{
if (top == 0)
return -1;
else
return stack[--top];
}
priority does not return a value if all the tests are false, but probably the last test is useless
In
while (priority(stack[top]) >= priority( * e))
you missed to check if the stack is empty, must be :
while ((top != 0) && (priority(stack[top]) >= priority( * e))) {
Because the first invalid index for stack is 0 rather than -1
while (top != -1) // printing remaining elements in the stack
must be
while (top != 0) // printing remaining elements in the stack
When you make the postfix expression there is no separation between the numbers, for instance "12+3" becomes "123+" like "1+23", and in EvalPostfix you consider a number has only one digit (eval_push(ch - '0');), so you do not manage the numbers having more than 1 digit. To manage several digits add a separator after all numbers, for instance a space to have "12 3 +" or "1 23 +" and read the number with scanf etc
You do not make the right postfix expression in all the cases, for instance for 1+2*3 you make 12+3* but it must be 123*+
You do not detect the invalid infix expressions
in
while (priority(stack[top]) >= priority( * e))
I missed to say the top element is not stack[top] but stack[top - 1] so it must be replaced by
while ((top != 0) && (priority(stack[top - 1]) >= priority( * e))) {
adding that correction 1+2*3 produces the right postfix expression 123*+
Note it is more clear to introduce the function empty() and tops(), and in case of an invalid access into the stack print a message and exit rather than to return -1 as a char
int empty()
{
return (top == 0);
}
char tops()
{
if (top == 0) {
fputs("top() on the empty stack, abort", stderr);
exit(-1);
}
return stack[top - 1];
}
char pop() // Pop char to top of stack
{
if (top == 0) {
fputs("pop() on the empty stack, abort", stderr);
exit(-1);
}
return stack[--top];
}
also detect a possible overflow of the stack :
void push(char x) // Push char into stack
{
if (top == sizeof(stack)) {
fputs("stack overflow", stderr);
exit(-1);
}
stack[top++] = x;
}
so now you can do
while (!empty() && (priority(tops()) >= priority( * e))) {
Of course this is the same for the other stack
I need the code to account for white spaces and double digits
double digits is too restrictive, just manage any integer, for that you can extract the number using strtol. You cannot also read the full expression using scanf("%s", exp); because is stops on the first space, use fgets.

segmentation fault because of character changement

I'm writing a board game detailed information. I get segmentation fault just changing problem_ch from ' '(space) to '.'. When I play by the moves and with ' ', I don't get but when I play by the moves and with '.'I get. I really and really don't understand what happening.
Solved: Because of negative index, I get the segmentation fault. By
handling, It is solved.
Following moves:
3d
2d
5f
2e
1e
4b
4c
4a
3b
at 4b segmentation fault
Code:
void make_move(char board[][SIZE], size_t row, size_t col, char player)
{
int rowdelta = 0; // Row increment
int coldelta = 0; // Column increment
size_t x = 0; // Row index for searching
size_t y = 0; // Column index for searching
// Identify opponent
char opponent = (player == player_ch) ? computer_ch : player_ch;
board[row][col] = player; // Place the player counter
// Check all squares around this square for opponents counter
for(rowdelta = -1 ; rowdelta <= 1 ; ++rowdelta)
for(coldelta = -1; coldelta <= 1; ++coldelta)
{
// Don’t check off the board, or the current square
if((row == 0 && rowdelta == -1) || row + rowdelta >= SIZE ||
(col == 0 && coldelta == -1) || col + coldelta >= SIZE ||
(rowdelta == 0 && coldelta == 0))
continue;
// Now check the square
if(board[row + rowdelta][col + coldelta] == opponent)
{ // Found opponent so search in same direction for player counter
x = row + rowdelta; // Move to opponent
y = col + coldelta; // square
for(;;)
{
x += rowdelta; // Move to the
y += coldelta; // next square
if(board[x][y] == problem_ch) // If square is blank...
break; // ...give up
// If we find the player counter, go backward from here
// changing all the opponents counters to player
if(board[x][y] == player)
{
while(board[x -= rowdelta][y -= coldelta] == opponent) // Opponent?
board[x][y] = player; // Yes, change it
break; // We are done
}
}
}
}
}

Obtain the best move from MiniMax algorithm in Tic-Tac-Toe game

I'm trying to implement MiniMax algorithm based on Wikipedia pseudocode in Tic-Tac-Toe game written in C. However, I can't manage to obtain the best possible move. Here's my code:
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
// compile and run: gcc minimax.c -std=c99 && ./a.out
int max(int x, int y) {
return x > y ? x : y;
}
int min(int x, int y) {
return x < y ? x : y;
}
int whoWon(char ch) {
switch (ch) {
case 'O':
return -1;
break;
case 'X':
return 1;
break;
}
}
void printArray(char array[]) {
printf("# START\n"
"%c | %c | %c\n"
"--|---|--\n"
"%c | %c | %c\n"
"--|---|--\n"
"%c | %c | %c\n"
"# END\n\n", array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7], array[8]);
}
int anyWinners(char board[])
{
int i;
/* check every row */
for(i = 0; i < 7; i += 3)
if(board[i] != ' ' && board[i] == board[i+1] && board[i] == board[i+2])
return whoWon(board[i]);
/* check every column */
for(i = 0; i < 3; i++)
if(board[i] != ' ' && board[i] == board[i+3] && board[i] == board[i+6])
return whoWon(board[i]);
/* check diagonals */
if(board[4] != ' ' && ((board[0] == board[4] && board[0] == board[8]) || (board[2] == board[4] && board[2] == board[6])))
return whoWon(board[4]);
return 0;
}
int fullBoard(char board[]) {
for (int i = 0; i < 9; ++i) {
if (board[i] == ' ')
return 0;
}
return 1;
}
int minimax(char node[], int depth, bool maximizingPlayer, int * move) {
int terminalNode = anyWinners(node);
if (depth == 0 || terminalNode || fullBoard(node)) {
printf("################## END OF SUBTREE ##################\n");
return terminalNode;
}
int bestValue, val;
if (maximizingPlayer) {
bestValue = -2;
for (int i = 0; i < 9; ++i) {
if (node[i] == ' ') {
char child[9];
strcpy(child, node);
child[i] = 'X';
// debug
printArray(child);
val = minimax(child, depth - 1, false, move);
// debug
printf("X: ^^ i = %d ^^ depth = %d ^^ val = %d\n", i, depth, val);
//bestValue = max(bestValue, val);
if (val > bestValue) {
bestValue = val;
if (depth == 9) *move = i;
}
}
}
return bestValue;
} else {
bestValue = 2;
for (int i = 0; i < 9; ++i) {
if (node[i] == ' ') {
char child[9];
strcpy(child, node);
child[i] = 'O';
// debug
printArray(child);
val = minimax(child, depth - 1, true, move);
// debug
printf("O: ^^ i = %d ^^ depth = %d ^^ val = %d\n", i, depth, val);
bestValue = min(bestValue, val);
}
}
return bestValue;
}
}
int main() {
int move = -999; // initialize only for debug
// X will always win no matter what, first best move for X is 8
// char board[] = {'O', ' ', ' ',
// ' ', ' ', ' ',
// 'X', 'X', ' '};
// best move for X is 3
char board[] = {'O', 'O', ' ',
' ', 'X', 'X',
' ', ' ', ' '};
// Initial call for maximizing player
int result = minimax(board, 9, true, &move);
printf("minimax returned: %d\n", result);
printf("chosen move: %d\n", move);
return 0;
}
Code prints board for each move with state of all variables. There are also two failing tests commented out in main. Right now algorithm returns bad moves and I can't find the error.
I see two problems:
The heuristic is wrong
There is a problem with strcpy.
The heuristic is wrong
The Wikipedia pseudo-code says:
if depth = 0 or node is a terminal node
return the heuristic value of node
Your implementation does this:
if depth = 0 or node is a terminal node
return 1 if X wins, -1 if O wins, 0 if it is a draw
But that isn't a very good heuristic. With that heuristic, all the possible ways that X could win are equally weighted. So if X finds a way to win in 3 moves, that is weighted just the same as if X finds a way to win in 2 moves, and that is weighted just the same as if X finds a way to win in 1 move.
So, here is what happens in your test case:
X tries position 2.
O tries position 3.
X tries position 6.
This is a terminal node. X wins. So return positive 1.
Heuristic for this decision path = 1
Another possibility it hits is:
X tries position 3.
This is a terminal node. X wins. So return positive 1.
Heuristic for this decision path = 1
Since both of these solutions have the same heuristic, so they are both of equal value. You probably meant for this solution to be suboptimal, because it took too many moves to win. I suggest a heuristic based on the number of moves it took to get here, multiplied who the winner is. So if X wins in 1 moves, heuristic is 5000. If X wins in 2 moves, then heuristic is 2500. If O wins in 2 moves, heuristic is -2500. Something like that.
There is a problem with strcpy
This line:
strcpy(child, node);
should be:
memcpy(child, node, 9*sizeof(char));
Because "node" is not a null terminated string. When I run this on VS2013/Windows 8.1 my output is garbage because. You might be getting lucky on your platform.

Random walk on 10x10 Array

i am having a problem figuring out an algorithm for this problem,been trying for few days without success,here is a pic of what im trying to obtain:
http://i.stack.imgur.com/X70nX.png
Here is my code tried many differents solutions but always get stuck at the same point:(Sorry for mixed language the important part is in english)
ps
im not supposed to use functions to solve this problem only loops and array.
EDIT
after much fixing it does the walk but seldomly crashes
any idea?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void){
char box[10][10];
int i,j;
int move,row,col;
char letter='A';
srand(time(NULL));
printf("\n\tSTART\n\n");
for(i=0;i < 10 ;i++)/* righe */
{
for(j=0;j < 10;j++) /* colonne */
{
box[i][j] = '.'; /* assegno . a tutti gli elementi dell array */
if(j == 9)
printf("%c%c\n", box[i][j]); /* giustifico ogni 10 elementi dell array j(0-9) */
else
printf("%c%c", box[i][j]);
}
}
/* LETS START */
printf("\n\n Inizia il gioco\n\n");
/* random place to start */
row = rand() % 9;
col = rand() % 9;
box[row][col]= 'A';
while(letter <= 'Z')
{
if(box[row+1][col] == '.' || box[row-1][col] == '.' || box[row][col+1] == '.' || box[row][col-1] == '.' )
{
move=rand() % 4;
switch(move){
case 0: /* Going UP */
if((row != 0) && (box[row-1][col] == '.'))
{
box[row-1][col]=++letter;
box[row--][col];
}else{
move=rand() % 4;
}
case 1:/* Going Down */
if((row != 9) && (box[row+1][col] == '.'))
{
box[row+1][col]=++letter;
box[row++][col];
}else{
move=rand() % 4;
}
case 2: /*Going Left */
if((col != 0) && (box[row][col-1] == '.'))
{
box[row][col-1]=++letter;
box[row][col--];
}else{
move=rand() % 4;
}
case 3: /* Going Right */
if((col != 9) && (box[row][col+1] == '.') )
{
box[row][col+1]=++letter;
box[row][col++];
}else{
move=rand() % 4;
}
}
}else{
printf("\n\nBloccato a %c\n\n", letter);
break;
}
}
/* FINE */
for(i=0;i<10;i++)/* righe */
{
for(j=0;j<10;j++) /* colonne */
{
if(j == 9)
printf("%c%c\n", box[i][j]); /* giustifico ogni 10 elementi dell array j(0-9) */
else
printf("%c%c", box[i][j]);
}
}
return 0;
}
You need to update row and col inside the loop.
Otherwise you'll always attempt to walk from the position of the 'A'.
... and once all 4 directions are filled, you're stuck in a infinite loop
. . . . .
. . B . .
. E A C .
. . D . .
Even when you update row and col inside the loop (and correct the == mistake), you have to handle a problem: suppose the first spot (the 'A') is the top left corner and the next random directions are East, South, South, West, and North. ... now what? :)
A B .
F C .
E D .
. . .
It's not a good idea to "reroll" the random number when you discover that you cannot go in some direction, because if you have bad luck, you get the same number twice (or even 3 or 4 or more times) - so even if you generated 4 random numbers and they all failed, that doesn't mean that you're stuck.
You can solve this problem by generating one number, and trying all 4 possible directions starting from it:
If the random number generator returned 0: check 0, 1, 2, 3
If the random number generator returned 1: check 1, 2, 3, 0
If the random number generator returned 2: check 2, 3, 0, 1
If the random number generator returned 3: check 3, 0, 1, 2
Implemented by the following code:
desired_move = rand();
success = 0;
for (i = 0; i < 4 && !success; ++i)
{
move = (desired_move + i) % 4;
switch (move)
{
case 0: // Go up
if (row > 0 && box[row - 1][col] == '.')
{
row = row - 1;
success = 1;
}
break;
case 1: // Go down
...
}
}
if (!success) // Tried all 4 directions but failed! You are stuck!
{
goto START_OVER; // or whatever else
}
Note that this algorithm is not very random: if you cannot go up, there is a greater chance that you go down than right or left. If you want to fix it, you can pick a random permutation of 4 directions instead of checking the directions sequentially:
const int permutation_table[24][4] = {
{0, 1, 2, 3},
{0, 1, 3, 2},
{0, 2, 1, 3},
...
{3, 2, 1, 0}
};
index = rand() % 24;
for (i = 0; i < 4; ++i)
{
move = permutation_table[index][i];
switch (move) {
... // As above
}
}
When you're in for loop.
Draw a possible direction
int direction = rand()%4;
Check all possible directions if the drawed one is invalid (not in array or not a ".")
int i=-1;
while( ++i < 4 )
{
switch(direction)
{
case 0:
if( row-1 >= 0 && box[row-1][col] == '.' ) {
--row;
i = -1;
}
break;
case 1:
if( col+1 < 10 && box[row][col+1] == '.' ) {
++col;
i = -1;
}
break;
case 2:
if( row+1 < 10 && box[row+1][col] == '.' ) {
++row;
i = -1;
}
break;
case 3:
if( col-1 >= 0 && box[row][col-1] == '.' ) {
--col;
i = -1;
}
break;
}
if( i != -1 ) {
direction = (direction+1)%4;
}
else {
break;
}
}
If there's no valid move end the for loop>
if( i == 4 ) {
break;
}
Otherwise write a letter to the table cell and update row/col position.
box[row][col] = letter;
And... that's all I guess. This is greedy algorithm so you don't need any optimizations (at least I don't see any in exercise requirements.
It looks like you are breaking out of your switch statement if you try to go in a direction that isn't valid, but you increment your counter anyway. Try to check another random direction if that happens.
where exactly does it break?
from what I can see at a glance is that you have a chance that It_that_walks gets in position from witch it cant go anywhere:
A B C D .
. I J E .
. H G F .
where after J?
There is no need for the && (box[row][col-1]= '.')
Allso, it is wrong (assignment instead of comparison), it should be: && (box[row][col-1]== '.') (but you dont need it alltogether)

Embedded C code review

I am required to write a function that uses a look up table for ADC values for temperature sensor analog input, and it finds out the temperature given an ADC value by "interpolating" - linear approximation.
I have created a function and written some test cases for it, I want to know if there is something which you guys can suggest to improve the code, since this is supposed to be for an embedded uC, probably stm32.
I am posting my code and attaching my C file, it will compile and run.
Please let me know if you have any comments/suggestions for improvement.
I also want to know a bit about the casting from uint32_t to float that I am doing, if it is efficient way to code.
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define TEMP_ADC_TABLE_SIZE 15
typedef struct
{
int8_t temp;
uint16_t ADC;
}Temp_ADC_t;
const Temp_ADC_t temp_ADC[TEMP_ADC_TABLE_SIZE] =
{
{-40,880}, {-30,750},
{-20,680}, {-10,595},
{0,500}, {10,450},
{20,410}, {30,396},
{40,390}, {50,386},
{60,375}, {70,360},
{80,340}, {90,325},
{100,310}
};
// This function finds the indices between which the input reading lies.
// It uses an algorithm that doesn't need to loop through all the values in the
// table but instead it keeps dividing the table in two half until it finds
// the indices between which the value is or the exact index.
//
// index_low, index_high, are set to the indices if a value is between sample
// points, otherwise if there is an exact match then index_mid is set.
//
// Returns 0 on error, 1 if indices found, 2 if exact index is found.
uint8_t find_indices(uint16_t ADC_reading,
const Temp_ADC_t table[],
int8_t dir,
uint16_t* index_low,
uint16_t* index_high,
uint16_t* index_mid,
uint16_t table_size)
{
uint8_t found = 0;
uint16_t mid, low, high;
low = 0;
high = table_size - 1;
if((table != NULL) && (table_size > 0) && (index_low != NULL) &&
(index_mid != NULL) && (index_high != NULL))
{
while(found == 0)
{
mid = (low + high) / 2;
if(table[mid].ADC == ADC_reading)
{
// exact match
found = 2;
}
else if(table[mid].ADC < ADC_reading)
{
if(table[mid + dir].ADC == ADC_reading)
{
// exact match
found = 2;
mid = mid + dir;
}
else if(table[mid + dir].ADC > ADC_reading)
{
// found the two indices
found = 1;
low = (dir == 1)? mid : (mid + dir);
high = (dir == 1)? (mid + dir) : mid;
}
else if(table[mid + dir].ADC < ADC_reading)
{
low = (dir == 1)? (mid + dir) : low;
high = (dir == 1) ? high : (mid + dir);
}
}
else if(table[mid].ADC > ADC_reading)
{
if(table[mid - dir].ADC == ADC_reading)
{
// exact match
found = 2;
mid = mid - dir;
}
else if(table[mid - dir].ADC < ADC_reading)
{
// found the two indices
found = 1;
low = (dir == 1)? (mid - dir) : mid;
high = (dir == 1)? mid : (mid - dir);
}
else if(table[mid - dir].ADC > ADC_reading)
{
low = (dir == 1)? low : (mid - dir);
high = (dir == 1) ? (mid - dir) : high;
}
}
}
*index_low = low;
*index_high = high;
*index_mid = mid;
}
return found;
}
// This function uses the lookup table provided as an input argument to find the
// temperature for a ADC value using linear approximation.
//
// Temperature value is set using the temp pointer.
//
// Return 0 if an error occured, 1 if an approximate result is calculate, 2
// if the sample value match is found.
uint8_t lookup_temp(uint16_t ADC_reading, const Temp_ADC_t table[],
uint16_t table_size ,int8_t* temp)
{
uint16_t mid, low, high;
int8_t dir;
uint8_t return_code = 1;
float gradient, offset;
low = 0;
high = table_size - 1;
if((table != NULL) && (temp != NULL) && (table_size > 0))
{
// Check if ADC_reading is out of bound and find if values are
// increasing or decreasing along the table.
if(table[low].ADC < table[high].ADC)
{
if(table[low].ADC > ADC_reading)
{
return_code = 0;
}
else if(table[high].ADC < ADC_reading)
{
return_code = 0;
}
dir = 1;
}
else
{
if(table[low].ADC < ADC_reading)
{
return_code = 0;
}
else if(table[high].ADC > ADC_reading)
{
return_code = 0;
}
dir = -1;
}
}
else
{
return_code = 0;
}
// determine the temperature by interpolating
if(return_code > 0)
{
return_code = find_indices(ADC_reading, table, dir, &low, &high, &mid,
table_size);
if(return_code == 2)
{
*temp = table[mid].temp;
}
else if(return_code == 1)
{
gradient = ((float)(table[high].temp - table[low].temp)) /
((float)(table[high].ADC - table[low].ADC));
offset = (float)table[low].temp - gradient * table[low].ADC;
*temp = (int8_t)(gradient * ADC_reading + offset);
}
}
return return_code;
}
int main(int argc, char *argv[])
{
int8_t temp = 0;
uint8_t x = 0;
uint16_t u = 0;
uint8_t return_code = 0;
uint8_t i;
//Print Table
printf("Lookup Table:\n");
for(i = 0; i < TEMP_ADC_TABLE_SIZE; i++)
{
printf("%d,%d\n", temp_ADC[i].temp, temp_ADC[i].ADC);
}
// Test case 1
printf("Test case 1: Find the temperature for ADC Reading of 317\n");
printf("Temperature should be 95 Return Code should be 1\n");
return_code = lookup_temp(317, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Temperature: %d C\n", temp);
printf("Return code: %d\n\n", return_code);
// Test case 2
printf("Test case 2: Find the temperature for ADC Reading of 595 (sample value)\n");
printf("Temperature should be -10, Return Code should be 2\n");
return_code = lookup_temp(595, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Temperature: %d C\n", temp);
printf("Return code: %d\n\n", return_code);
// Test case 3
printf("Test case 3: Find the temperature for ADC Reading of 900 (out of bound - lower)\n");
printf("Return Code should be 0\n");
return_code = lookup_temp(900, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Return code: %d\n\n", return_code);
// Test case 4
printf("Test case 4: Find the temperature for ADC Reading of 300 (out of bound - Upper)\n");
printf("Return Code should be 0\n");
return_code = lookup_temp(300, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Return code: %d\n\n", return_code);
// Test case 5
printf("Test case 5: NULL pointer (Table pointer) handling\n");
printf("Return Code should be 0\n");
return_code = lookup_temp(595, NULL, TEMP_ADC_TABLE_SIZE, &temp);
printf("Return code: %d\n\n", return_code);
// Test case 6
printf("Test case 6: NULL pointer (temperature result pointer) handling\n");
printf("Return Code should be 0\n");
return_code = lookup_temp(595, temp_ADC, TEMP_ADC_TABLE_SIZE, NULL);
printf("Return code: %d\n", return_code);
// Test case 7
printf("Test case 7: Find the temperature for ADC Reading of 620\n");
printf("Temperature should be -14 Return Code should be 1\n");
return_code = lookup_temp(630, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Temperature: %d C\n", temp);
printf("Return code: %d\n\n", return_code);
// Test case 8
printf("Test case 8: Find the temperature for ADC Reading of 880 (First table element test)\n");
printf("Temperature should be -40 Return Code should be 2\n");
return_code = lookup_temp(880, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Temperature: %d C\n", temp);
printf("Return code: %d\n\n", return_code);
// Test case 9
printf("Test case 9: Find the temperature for ADC Reading of 310 (Last table element test)\n");
printf("Temperature should be 100 Return Code should be 2\n");
return_code = lookup_temp(310, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
printf("Temperature: %d C\n", temp);
printf("Return code: %d\n\n", return_code);
printf("Press ENTER to continue...\n");
getchar();
return 0;
}
I generally compute the lookup table offline and the runtime code boils down to:
temp = table[dac_value];
Particularly if going embedded, you dont want floating point, often dont need it. Pre-computing the table solves that problem as well.
Pre-computing also solves the problem of having an efficient algorithm, you can be as sloppy and slow as you want, you only have to do this computation rarely. No algorithm is going to be able to compete with the lookup table at runtime. So long as you have room for the look up table it is a win-win. If you dont have say 256 locations in prom for an 8 bit dac for example you might have 128 locations, and you can do a little real-time interpolation:
//TODO add special case for max dac_value and max dac_value-1 or make the table 129 entries deep
if(dac_value&1)
{
temp=(table[(dac_value>>1)+0]+table[(dac_value>>1)+1])>>1;
}
else
{
temp=table[dac_value>>1];
}
I often find that the table being fed in can and will change. Yours may be cast in stone, but this same kind of computation comes about with calibrated devices. And you have done the right thing by checking that the data is in the right general direction (decreasing relative to the dac increasing or increasing relative to dac values increasing) and more importantly check for divide by zero. Despite being a hard coded table develop habits with the expectation that it will change to a different hard coded table with the desire to not have to change your interpolation code every time.
I also believe that the raw dac value is the most important value here, the computed temperature can happen at any time. Even if the conversion to degrees of some flavor has been cast in stone, it is a good idea to display or store the raw dac value along with the computed temperature. You can always re-compute the temperature from the dac value, but you cannot always accurately reproduce the raw dac value from the computed value. It depends on what you are building naturally, if this is a thermostat for public use in their homes they dont want to have some hex value on the display. But if this is any kind of test or engineering environment where you are collecting data for later analysis or verification that some product is good or bad, carrying around that dac value can be a good thing. It only takes once or twice for a situation where the engineer that provided you with the table, claims it was the final table then changes it. Now you have to go back to all the logs that used the incorrect table, compute back to the dac value using the prior table and re-compute the temp using the new table and write a new log file. If you had the raw dac value there and everyone was trained to think in terms of dac values and that the temperature was simply a reference, you might not have to repair older log values for each new calibration table. The worst case is having only the temperature in the log file and not being able to determine which cal table was used for that log file, the log file becomes invalid the unit tested becomes a risk item, etc.
Why does your bisection search have to handle both ascending and descending tables? The table is hard coded anyhow, so prepare your table offline, and you halve the complexity. You also generally don't need to do half your comparisons - just stop when high and low are adjacent or equal.
In such as small program, there is very little point checking for non-null table and other inputs and silently reporting no match - either return and error code, or make sure that the one place the function is called it's not being called with invalid pointers ( there is no reason to assume that an invalid pointer is NULL, just because NULL may be an invalid pointer on some systems).
Without the extra complexities, the search would become something like:
enum Match { MATCH_ERROR, MATCH_EXACT, MATCH_INTERPOLATE, MATCH_UNDERFLOW, MATCH_OVERFLOW };
enum Match find_indices (uint16_t ADC_reading,
const Temp_ADC_t table[],
uint16_t* index_low,
uint16_t* index_high )
{
uint16_t low = *index_low;
uint16_t high = *index_high;
if ( low >= high ) return MATCH_ERROR;
if ( ADC_reading < table [ low ].ADC ) return MATCH_UNDERFLOW;
if ( ADC_reading > table [ high ].ADC ) return MATCH_OVERFLOW;
while ( low < high - 1 )
{
uint16_t mid = ( low + high ) / 2;
uint16_t val = table [ mid ].ADC;
if ( ADC_reading > val)
{
low = mid;
continue;
}
if ( ADC_reading < val )
{
high = mid;
continue;
}
low = high = mid;
break;
}
*index_low = low;
*index_high = high;
if ( low == high )
return MATCH_EXACT;
else
return MATCH_INTERPOLATE;
}
Since the table has been pre-prepared to be ascending, and search returns a meaningful enum rather than an integer code, you don't need that much in lookup_temp:
enum Match lookup_temp ( uint16_t ADC_reading, const Temp_ADC_t table[],
uint16_t table_size, int8_t* temp)
{
uint16_t low = 0;
uint16_t high = table_size - 1;
enum Match match = find_indices ( ADC_reading, table, &low, &high );
switch ( match ) {
case MATCH_INTERPOLATE:
{
float gradient = ((float)(table[high].temp - table[low].temp)) /
((float)(table[high].ADC - table[low].ADC));
float offset = (float)table[low].temp - gradient * table[low].ADC;
*temp = (int8_t)(gradient * ADC_reading + offset);
break;
}
case MATCH_EXACT:
*temp = table[low].temp;
break;
}
return match;
}
Given all terms in the gradient calculation are 16 bit ints, you could perform the interpolation in 32 bits, as long as you calculate all terms of the numerator before dividing:
*temp = temp_low + uint16_t ( ( uint32_t ( ADC_reading - adc_low ) * uint32_t ( temp_high - temp_low ) ) / uint32_t ( adc_high - adc_low ) );
There is a lot you can improve.
First of all the best integer data type depends on the machine (word size). I don't know how are your int8_t and uint16_t declared.
Also, not for performance but for readability, I usually don't use "cascading" ifs, like
if condition
{
if another_condition
{
if third condition
{
but instead:
if not condition
return false;
// Here the condition IS true, thus no reason to indent
Another point of attention:
low = (dir == 1)? mid : (mid + dir);
high = (dir == 1)? (mid + dir) : mid;
you do the dir==1 twice, better to use ifs:
int sum = mid+dir;
if dir == 1
{
low = mid;
high = sum;
}
else
{
low=sum;
high=mid;
}
But there's more to say. For example you could use a faster searching algorithm.
It is good that you include the test framework, but your test framework lacks rigour and abuses the DRY (Don't Repeat Yourself) principle.
static const struct test_case
{
int inval; /* Test reading */
int rcode; /* Expected return code */
int rtemp; /* Expected temperature */
} test[] =
{
{ 317, 1, 95 },
{ 595, 1, -10 },
{ 900, 0, 0 }, // Out of bound - lower
{ 300, 0, 0 }, // Out of bound - upper
{ 620, 1, -14 },
{ 880, 2, -40 }, // First table element
{ 310, 2, 100 }, // Last table element
};
Now you can write the test code for a single test in a function:
static int test_one(int testnum, const struct test_case *test)
{
int result = 0;
int temp;
int code = lookup_temp(test->inval, temp_ADC, TEMP_ADC_TABLE_SIZE, &temp);
if (temp == test->rtemp && code == test->rcode)
printf("PASS %d: reading %d, code %d, temperature %d\n",
testnum, test->inval, code, temp);
else
{
printf("FAIL %d: reading %d, code (got %d, wanted %d), "
"temperature (got %d, wanted %d)\n",
testnum, test->inval, code, test->rcode, temp, test->rtemp);
result = 1;
}
}
And then the main program can have a loop that drives the test function:
#define DIM(x) (sizeof(x)/sizeof(*(x)))
int failures = 0;
int i;
for (i = 0; i < DIM(test); i++)
failures += test_one(i + 1, &test[i]);
if (failures != 0)
printf("!! FAIL !! (%d of %d tests failed)\n", failures, (int)DIM(test));
else
printf("== PASS == (%d tests passed)\n", (int)DIM(test));
Now if there is a problem with any of the tests, it is going to be hard to excuse not spotting the problem. With your original code, someone could overlook the mistake.
Clearly, if you want the comments about the tests too, you can add a const char *tag to the array and supply and print those tags. If you really want to get fancy, you can even encode the null pointer tests (kudos for including those) in the regime by including appropriately initialized pointers in the array - you might add a pair of bit-flags for 'table is null' and 'temperature pointer is null' and conditional code in the function.

Resources