Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??
### Memory.pm package Memory; # A class that implements a simple memory. Memory is stored by keying # "states" to "priorities"; a higher priority indicates that the # corresponding state is a better state than those of lower priority. use strict; use integer; # Constructor sub new { my $class = shift; my $self = {}; bless $self, $class; return $self; } # Loads the memory from the given filename. sub load { my ($self, $filename) = @_; open(MEMORY_FILE, $filename) || warn "could not load memory from $fi +lename: $!"; my @memory = <MEMORY_FILE>; close(MEMORY_FILE); for (my $i = 0; $i < @memory; $i++) { chomp $memory[$i]; } my %memory = @memory; foreach my $key (keys %memory) { $self->set($key, $memory{$key}); } } # Dumps the current state of memory to a file. sub save { my ($self, $filename) = @_; open(MEMORY_FILE, ">$filename") || warn "could not save memory to $f +ilename: $!"; foreach my $key (sort keys %$self) { my $value = $self->get($key); print MEMORY_FILE "$key\n$value\n"; } } # Takes in a string representing a state, and returns # the priority of that state. sub get { return $_[0]{$_[1]}; } # Takes in a string representing a state, and a numerical priority for # the state. Sets the priority of that state accordingly. sub set { $_[0]{$_[1]} = $_[2]; } # Takes in a string representing a state, and an amount to modify the # priority of that state by. Modifies the priority by the given amount +. sub modify { my ($self, $state, $priority) = @_; $self->{$state} = $self->get($state) + $priority; } # Takes in a reference to a list of all the valid states that can # currently occur, and returns the state with the highest priority. If # more than one state shares the highest priority, it randomly picks # one of the best states. sub get_best_state { my ($self, $states) = @_; # Find the highest priority of any of the states. my $highest_priority = -2**30; foreach (@$states) { if ($self->get($_) > $highest_priority) { $highest_priority = $self->get($_); } } # Find all of the states at the highest priority. my @best_states; foreach my $state (@$states) { my $priority = $self->get($state); if ($priority == $highest_priority) { push @best_states, $state; } } # Randomly choose a state out of our list of best states, and return + it. return $best_states[int(rand(@best_states))]; } 1; ### Random.pm package Random; # A game-player AI implementing a player who merely makes a # random move out of a list of available moves. # # Nothing very exciting is going on here. use strict; use integer; sub new { my $class = shift; my $self = {}; bless $self, $class; return $self; } sub make_move { my ($self, $valid_states) = @_; my @valid_states = keys %$valid_states; my $move = $valid_states[int(rand(@valid_states))]; return $$valid_states{$move}; } sub win {} sub lose {} sub tie {} 1; ### Defensive.pm package Defensive; # A class implementing a "defensive" AI game player. The defensive # player considers all states that have led to losses as "bad" and # makes no preference between a win and a tie. The defensive player is # the only type of player that can evolve to become an unbeatable # tic-tac-toe player. # # Although I've only tested this player in the game of tic-tac-toe, # it knows nothing about the rules of tic-tac-toe. At any # given time when it has to make a move, it just gets a list of the # valid states it can put the board into, and chooses the "best" state # out of memory. At the end of a game, it receives the result of the # game (win, loss, or tie) and modifies its memory to adjust for the # result. # # Adapting this player to a different game should be incredibly easy; # the game just has to send in a list of valid states and call the # appropriate win(), lose(), or tie() method at the end of the game. use Memory; @Defensive::ISA = ("Memory"); # Constructor sub new { my $class = shift; my $self = Memory::new($class); return $self; } # Do nothing if the result of a game was a win or a tie, except clear # the "states" entry. sub win { delete $_[0]->{"states"}; } sub tie { delete $_[0]->{"states"}; } # If we lost, decrease the priorities of all states that we put the # game into. States which occurred toward the end of the game are # weighted as "more bad" than states which occurred at the # beginning. sub lose { my $self = shift; my @states = @{$self->{"states"}}; my $score = -32; while (@states) { my $state = pop(@states); $self->modify($state, $score); $score /= 2; } delete $self->{"states"}; } # Uses Memory.pm's get_best_state() method to find and return the best # move out of those provided in the @$valid_states array. # # Keeps track of the moves it's made during this game, such that it # can modify their values accordingly at the end of the game. sub make_move { my ($self, $valid_states) = @_; my @valid_states = (keys %$valid_states); my $best_state = $self->get_best_state(\@valid_states); push @{$self->{"states"}}, $best_state; return $valid_states->{$best_state}; } 1; ### TicTacToe.pm package TicTacToe; use integer; use strict; # A class that implements a game of tic-tac-toe. # # The board is indexed as follows: # 0 1 2 # 3 4 5 # 6 7 8 # # Each tic-tac-toe state is represented as a nine-character string # where each character in the string corresponds to the given location # on the board. The character is a "0" if that square is empty, "1" is # that square has a mark by the current player, and "2" is that square # has a mark y the current player's opponent. # Constructor. sub new { my $class = shift; my $self = { board => [0, 0, 0, 0, 0, 0, 0, 0, 0], moves_made => 0, player => int(rand(2) + 1), winning_positions => [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, +3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]] }; bless $self, $class; return $self; } # Plays an entire game of tic-tac-toe. Takes in references to two # "player" objects; it alternates between these objects, asking each # for a move, until the game is over. At the end of the game, it # notifies each player of the result of the game, and returns the # result. 0 is a tie, 1 and 2 are wins by player 1 and player 2, # respectively. sub play { my ($self, $p1, $p2) = @_; my $result = undef; until (defined $result) { if (current_player($self) == 1) { $result = $self->request_move($p1); } else { $result = $self->request_move($p2); } $self->switch_player(); } if ($result == 1) { $p1->win(); $p2->lose(); } elsif ($result == 2) { $p2->win(); $p1->lose(); } else { $p1->tie(); $p2->tie(); } return $result; } # Takes in a reference to a player object, and requests (through the # player's make_make() method) that the player make a move. # # BUG: We send the player a list of the valid moves (actually, a list # of the valid states that the player can currently put the board # into), but never check to see whether the player actually made a # valid move. It's assumed that we have honest (and # correctly-programmed) players, which is probably a bad assumption to # make. sub request_move { my ($self, $ai) = @_; my $move = $ai->make_move($self->valid_states()); $$self{"board"}->[$move] = $self->current_player(); $self->{"moves_made"}++; return $self->check_for_win(); } # Checks to see if the game has been won. # Returns 1 or 2 if player 1 or 2 has won, undef otherwise. # # This sub is horribly non-optimized, but it works. sub check_for_win { my $self = shift; return undef if ($self->{moves_made} < 3); my $player = $self->current_player(); my $win = 0; my @winning_positions = @{$self->{"winning_positions"}}; for (my $i = 0; $win == 0 && $i < scalar(@winning_positions); $i++) +{ $win = 1; my @needed_moves = @{$winning_positions[$i]}; foreach my $move (@needed_moves) { unless ($self->{"board"}->[$move] == $player) { $win = 0; } } } if ($win) { return $player; } elsif ($self->{"moves_made"} == 9) { return 0; } else { return undef; } } # Returns a list of the currently-valid moves. sub valid_moves { my $self = shift; my @valid_moves; for (my $i = 0; $i < 9; $i++) { if ($self->{"board"}->[$i] == 0) { push(@valid_moves, $i); } } return @valid_moves; } # Returns the current player (1 or 2). sub current_player { my $self = shift; return $$self{"player"}; } # Switches the current player. sub switch_player { my $self = shift; if ($$self{"player"} == 1) { $$self{"player"} = 2; } else { $$self{"player"} = 1; } } # Returns the board. sub board { my $self = shift; return @{$self->{"board"}}; } # Returns a string representation of the current state of the board. # The nth character in the string corresponds to square n of the # board, where n is an integer from 0 to 8.The current player is # always denoted as "1", its opponent "2", and an empty space "0". sub current_state { my $self = shift; my $current_state = join("", board($self)); # If the current player is 2, we need to swap the 1's and 2's in the + board state. if (current_player($self) == 2) { $current_state =~ tr/12/21/; } return $current_state; } # Returns a reference to a hash of the states that the current player # can legally put the board into. The keys of the hash are the states # themselves; the values are the moves required to put the board into # each state. sub valid_states { my $self = shift; my $current_state = current_state($self); my %valid_states; my @valid_moves = $self->valid_moves(); foreach my $move (@valid_moves) { my $valid_state = $current_state; substr($valid_state, $move, 1) = 1; $valid_states{$valid_state} = $move; } return \%valid_states; } 1; ### main.pl #!/usr/bin/perl # Driver program for tic-tac-toe games. Plays a (basically) infinite # number of games, dying gracefully when interrupted. Prints out the # results so far, at every 100 games. Also handles loading and saving # of player memories. use strict; use integer; use TicTacToe; use Defensive; use Random; $SIG{INT} = \&sig_handler; my @record = (0, 0, 0); my $p1 = Defensive->new(); my $p2 = Random->new(); $p1->load("player-1-memory.txt"); my $dead = 0; my $num_games = 0; until ($dead) { $num_games++; my $ttt = TicTacToe->new(); my $result = $ttt->play($p1, $p2); $record[$result]++; if ($num_games % 100 == 0) { print "($num_games) $record[1]/$record[2]/$record[0]\n"; } } $p1->save("player-1-memory.txt"); print "Player 1 memory saved OK.\n"; sub sig_handler { print "\nCaught INT signal... shutting down.\n"; $dead = 1; }

In reply to A simple game AI that learns by Falkkin

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others cooling their heels in the Monastery: (8)
As of 2024-04-25 10:58 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found