350 likes | 434 Views
2D1350 Programmeringsparadigm. Pointers Arrays Structures Artificial intelligence and game playing Lab assignment. Games as Search Problems. The behavior / actions of the opponent are unpredictable, therefore search for a “worst-case”-plan.
E N D
2D1350 Programmeringsparadigm • Pointers • Arrays • Structures • Artificial intelligence and game playing • Lab assignment
Games as Search Problems • The behavior / actions of the opponent are unpredictable, therefore search for a “worst-case”-plan. • Time limit, therefore complete search is not feasible and an approximation is needed • Algorithm for perfect play (van Neumann 1944) • Finite horizon, approximate evaluation (Zuse 1945, Shannon 1950, Samuel 1952) • Pruning search tree (McCarthy 1956)
Min-Max-Search • Optimal strategy for deterministic, perfect-information game • Idea: Choose move that results in position with highest min-max-value = best achievable payoff against best opponents play 5 Max: A3 A1 A2 Min: 3 5 2 A11 A13 A21 A23 A31 A33 A12 A22 A32 3 12 8 5 7 9 4 2 7
Min-Max-Search Function MINMAX-DECISION(game state) returns a move for each move in PossibleMoves(game state) do value[move] <- MINIMAX-VALUE(apply(move, game state)) end return the move with the highest value[move] Function MINMAX-VALUE(game state) returns a utility value if TERMINAL-TEST(game state) then return UTILITY(game state) else if MAX is to move in game state return the highest MINMAX-VALUE of SUCCESSORS(game state) else return the lowest MINMAX-VALUE of SUCCESSORS(game state)
Min-Max Properties • Complete: yes, if search tree is finite • Optimal : yes, if opponent plays optimal • Time complexity : O(bm) • Space complexity : O(bm) depth first search • Chess b~35 possible moves in each state, m~100 moves per game -> exact solution infeasible • Standard solution • cutoff test for search (e.g. depth limit) • evaluation function : approximates utility of board position
Evaluation Scheme • For chess for example typically linear weighted sum of features • Utility(s) = w1 f1(s) + w2 f2(s) + …wn fn(s) w1=9 f1(s)= #white queens - #black queens w2=5 f2(s) = #white rooks - #black rooks etc.
Cutting of Search • Min-Max-Search with Cut-Off requires • 1. CUTOFF criterion, usually based on search depth • 2. UTILITY function needs an evaluation scheme for non-terminal game states • Ply = one half-move (move by one player) • Chess: • 4-ply = novice • 8-ply = PC, human master • 12-ply = Deep Blue, Kasparov
Min-Max-Search with Cut-Off Function MINMAX-DECISION(game) returns a move for each move in PossibleMoves(game state) do value[move] <- MINIMAX-VALUE(apply(move, game state)) end return the move with the highest value[move] Function MINMAX-VALUE(game state, depth) returns a utility value if CUTOFF-TEST(depth) or TERMINAL-TEST(game state) return UTILITY(game state) else if MAX is to move in game state return the highest MINMAX-VALUE(SUCCESSOR(game state),depth+1) else return the lowest MINMAX-VALUE(SUCCESSOR(game state), depth+1)
Connect-4 • two player game • 7x6 rectangular board placed vertically • 21 red, 21 yellow tokens • players alternate by dropping a token into one of the seven columns, the token falls down to the lowest unoccupied square • a player wins if she connects four token vertically, horizontally or diagonally • if the board is filled (42 tokens played) and no player has aligned four tokens the game ends in a draw
Connect-4 win for red win for yellow draw
Connect-4 Utility Function • Odd square: is a square belonging to an odd row (1,3,5) • Even square: is a square belonging to an even row (2,4,6) • Threats: A threat is a group of three tokens of the same color which has the fourth square empty and also the square below the empty square is empty • Odd threat: is a threat in which the empty square is odd • Even threat: is a threat in which the empty square is even • If red (moves first) has an odd threat and black cannot connect four tokens anywhere else red will win. • If black (moves second) has an even threat and black cannot connect four tokens anywhere else black will win. • At the beginning of the game it is advantageous to place tokens in the central columns.
Lab Assignment • Code for connect-4 available in game.h and game.c • Copy files game.h, game.c, Makefile into your local directory • To add gcc to your list of modules type module add gcc/3.1 or add this line to .modules • To compile game.c into executable type make
Lab Assignment • Design and implement a game playing program for the deterministic two player game Connect-4 • Features • The program should be able to play against itself or a human opponent. • The program should visualize the evolution of the game and print out its own estimate of the utility of the current game state. • The program should determine who won or if the game ended in a draw • Implement the min-max-search algorithm • Implement a proper utility function for Connect-4 • Test the program by letting it play against itself and against a human opponent
Connect-4 Game State #define ROWS 6 /* number of rows in connect-4 */ #define COLS 7 /* number of columns in connect-4 */ #define RED -1 /* value for red tokens and red player */ #define BLACK 1 /* value for black tokens and black player */ struct Game { int board[ROWS][COLS]; /* -1 token red player, 1 token black player, 0 empty square */ int currentplayer; /* -1 red player, 1 black player */ int tokensonboard; /* counts the number of tokens on the board */ };
Connect-4 Move struct Move { int row; /* row coordinate of square */ int col; /* column coordinate of square */ int token; /* -1 red token, 1 black token */ };
void InitGame() void InitGame(struct Game *game) /* pointer reference to game state */ { int i; int j; for (i=0; i < ROWS; i++) for (j=0; j < COLS; j++) (*game).board[i][j]=0; /* empty board */ (*game).currentplayer=RED; /* red player to start game */ (*game).tokensonboard=0; };
void MakeMove() void MakeMove(struct Game *game, struct Move move) { #if DEBUG assert((*game).board[move.row][move.col]==0); /* assert square is empty */ if (move.row>0) assert((*game).board[move.row-1][move.col]!=0); /* assert square below is occupied */ assert((*game).currentplayer==move.token); /* assert correct player moves */ #endif (*game).board[move.row][move.col]=move.token; /* place token at square */ (*game).currentplayer*=-1; /* switch player */ (*game).tokensonboard++; /* increment number of tokens on board */ }
void UndoMove() void UndoMove(struct Game *game, struct Move move) { #if DEBUG assert((*game).board[move.row][move.col]!=0); /* assert square is occupied */ if (move.row<ROWS-1) assert((*game).board[move.row+1][move.col]!=0); /* assert square above is empty */ assert((*game).currentplayer!=move.token); /* assert correct player moves */ #endif (*game).board[move.row][move.col]=0; /* remove token from square */ (*game).currentplayer*=-1; /* switch player */ (*game).tokensonboard--; /* decrement number of tokens on board */ }
int Win() int Win(struct Game *game, int player) { int i; int j; for (j=0;j<COLS;j++) for(i=0;i<ROWS-3;i++) /* check for group of four vertical tokens */ { int count=0; /* counts number of consecutive tokens */ while((count < 4) && ((*game).board[i+count][j]==player)) /* check if token is owned by player and not 4 tokens yet */ count++; if (count==4) /* four tokens in column */ return 1; /* win for player*/ } … /* check for horizontal and diagonal groups of four */
int Win() for (j=0;j<COLS-3;j++) for(i=0;i<ROWS;i++) /* check for group of four horizontal four tokens */ { int count=0; /* counts number of consecutive tokens */ while((count < 4) && ((*game).board[i][j+count]==player)) /* check if token is owned by player and not 4 tokens yet */ count++; if (count==4) /* four tokens in a row */ return 1; /* win for player */ }
int Win() for (j=0;j<COLS-3;j++) for(i=0;i<ROWS-3;i++) /* check for four tokens in an upward diagonal */ { int count=0; /* counts number of consecutive tokens */ while((count < 4) && ((*game).board[i+count][j+count]==player)) /* check if token is owned by player and not 4 tokens yet */ count++; if (count==4) /* four tokens in a diagonal */ return 1; /* win for player */ }
int Win() for (j=0;j<COLS-3;j++) for(i=3;i<ROWS;i++) /* check for four tokens in a downward diagonal */ { int count=0; /* counts number of consecutive tokens */ while((count < 4) && ((*game).board[i-count][j+count]==player)) /* check if token is owned by player and not 4 tokens yet */ count++; if (count==4) /* four tokens in a diagonal */ return 1; /* win for player */ } return 0; /* no win for player */ }
int Draw() int Draw(struct Game *game) { if ((*game).tokensonboard<42) return 0; else return (!Win(game,RED) && !Win(game,BLACK)); }
int Row() int Row(struct Game *game, int col) /* computes the row on which token ends when dropped in column col */ { int row=0; while((row<ROWS) && (*game).board[row][col]!=0) row++; return row; }
void PossibleMoves() void PossibleMoves(struct Game *game, int *number_of_moves, struct Move moves[]) /* computes the possible moves , number_of_moves returns the number of available moves, moves[] contains array of moves */ { int i; *number_of_moves=0; for (i=0;i<COLS;i++) { int row=Row(game,i); /* computes first empty square in col i */ if (row<ROWS) /* column has an empty square */ { moves[*number_of_moves].row=row; moves[*number_of_moves].col=i; moves[*number_of_moves].token=(*game).currentplayer; (*number_of_moves)++; } } }
int Utility() int Utility(struct Game *game) { if (Draw(game)) return 0; if (Win(game,RED)) return 1000; /* maximum utility for winning */ if (Win(game,BLACK)) return -1000; /* minimum utility for loosing */ /* non-terminal game state */ /* HERE GOES YOUR CODE TO COMPUTE UTILITY OF NON-TERMINAL BOARD STATES */ return 0; }
Input / Output Functions void DisplayBoard(struct Game *game); /* print board state on screen */ void DisplayMove(struct Move move); /* print move on screen */ void EnterMove(struct Move *move, struct Game *game); /* reads in a move from the keyboard */
int main() int main(int argc, char *argv[]) { int i; struct Game game; /* variable to store the game state */ struct Move moves[COLS]; /* array to store possible moves */ int number_of_moves; int playagainsthuman=0; /* computer plays against itself (0) or against human (1) */ …
int main() for (i=1; i<argc; i++) /* iterate through all command line arguments */ { if(strcmp(argv[i],"-p")==0) /* if command line argument -p human opponent */ { playagainsthuman=1; printf("Human player plays black\n"); } if(strcmp(argv[i],"-h")==0) /* if command line argument -h print help */ { printf("game [-p] [-h]\n-p for play against human player\n-h for help\n"); return 0; /* quit program */ } }
int main() InitGame(&game); /* set up board */ while( !Draw(&game) && !Win(&game,RED) && !Win(&game,BLACK)) /* no draw or win */ { int rand; struct Move move; DisplayBoard(&game); /* display board state */ PossibleMoves(&game,&number_of_moves,moves); /* calculate available moves */ rand = (int) (drand48()*number_of_moves); /* pick a random move */ MakeMove(&game,moves[rand]); /* make move */ DisplayMove(moves[rand]); /* display move */ …
int main() if (playagainsthuman) /* play against human opponent */ { DisplayBoard(&game); /* show board state after computer moved */ if (!Draw(&game) && !Win(&game,RED)) /* no draw and no computer win */ { EnterMove(&move,&game); /* human player enters her move */ MakeMove(&game,move); /* make move */ } } /* end of human player play */ } /* end of while not draw or win */
int main() DisplayBoard(&game); /* display board state */ if (Draw(&game)) printf("the game ended in a draw\n"); if (Win(&game, RED)) printf("player red won the game\n"); if (Win(&game, BLACK)) printf("player black won the game\n"); return 0; } /* end of main */
Makefile OBJS = game.o # for gcc compiler .... CC = gcc CFLAGS = -g -Wall #naming executable EXEC = game all: $(EXEC) %.o:%.c $(CC) $(CFLAGS) -c $< $(EXEC): $(OBJS) $(CC) -o $@ $(OBJS)
Question of the Day • Draw a closed path of four straight lines that connects all nine dots.