#!/usr/bin/perl -w -s ################### # tictactoe_server - play tic tac toe... ################### use strict; use IO::Socket; my $localport = 3007; # Socket that this server listens on. my $kidpid; # process id of last forked child print STDOUT " - starting tic tac toe server \n"; my $sockListen = IO::Socket::INET->new( Proto => 'tcp', LocalPort => $localport, Listen => 5, Reuse => 1, ) or die "SERVER: Couldn't listen at port $localport; $@"; print STDOUT "SERVER: accepting clients at port $localport. \n"; $sockListen->autoflush(1); # write everything out immediately. $sockListen->blocking(1); # wait for the other end when accepting # The INET->accept() call, below, waits (i.e. "blocks") for a new connection. # Once we get one, a new process is forked off to deal with it. while ( my $sockClient = $sockListen->accept ) { $sockClient->autoflush(1); # write everything out immediately. $sockClient->blocking(1); # wait for the other end when reading my $kidpid = fork(); # This call doesn't return - we only call it in the child process, # and exit when we're done. talkToClient($sockClient) if defined($kidpid) and $kidpid==0; } # ------------------------------------------------------------------ our ($sock, $serverMark, $clientMark, @board); sub talkToClient { ($sock) = @_; @board = ('.')x9; ($serverMark, $clientMark) = ('O', 'X'); ########################### # # MTTTP Mindless Tic Tac Toe Protocol # v1.0, 1/11/02, Jim Mahoney # # The server waits for single line commands from client, # each identified by initial keyword. # The server responsds to each command with 4 lines of text. # Lines are terminated by unix \n. # Whitespace is ignored. # # The legal client commands are # START [x|o] # MOVE where # DONE # # The tic tac toe board is laid out like this. # a b c # d e f # g h i # so the legal values of "where" are one of the first nine letters. # Each spot is either X, O, or . (period for empty space). # X goes first. # # Commands and positions may be in either upper or lower case. # # The server always responds with 4 lines, # 1 line of information, followed by # 3 lines displaying the board. # # The information line starts with either "OK" or "OOPS", and may # contain key/value pairs in the form key='value'. Defined keys are # your_mark O or X , confirming which side the client will play, # your_move a letter, confirming a move the client has played, # my_move a letter, giving the move that the server has made. # The OOPS condition is returned when # the client command is not understood, or # the client sends an illegal move # # The server algorithm is (of course) up to the implementer - # depending on how mindless you'd like to be. # # The client may issue any of the three commands at any point. # ############################################################# my $response; while ( my $line = $sock->getline() ) { s/\012//g, s/\015//g for $line; # clean it up. for ($line) { $response = start($line), next if /^\s*START/i; $response = move($line), next if /^\s*MOVE/i; done() if /^\s*DONE/i; #this exits. $response = "OOPS - I didn't recognize that command."; } $sock->print($response . "\n"); printboard(); } # if we get here, then the connection has been closed on other end. close($sock); exit; # -- subrs ------------------------------------------------ sub start { my ($line) = @_; if ($line =~ /x/i){ ($clientMark, $serverMark) = ('X', 'O'); return "OK, your_mark='X'. Your turn."; } else { ($clientMark, $serverMark) = ('O', 'X'); clearboard(); my $where = doServerMove(); return "OK, your__mark='O', my_move='$where'. Your turn."; } } sub clearboard { $board[$_] = '.' for 0..8; } sub printboard { $sock->print( " $board[0] $board[1] $board[2] a b c \n"); $sock->print( " $board[3] $board[4] $board[5] d e f \n"); $sock->print( " $board[6] $board[7] $board[8] g h i \n"); } sub doServerMove { my $where = rand(9); my @letter = qw(a b c d e f g h i); return if boardIsFull(); $where = rand(9) until $board[$where] eq '.'; $board[$where] = $serverMark; return $letter[$where]; } sub boardIsFull { return not grep { $_ eq "." } @board; } sub findWinner { my @rows = ( [0,1,2], [3,4,5], [6,7,8], [0,3,6], [1,4,7], [2,5,8], [0,4,8], [2,4,6] ); foreach my $row (@rows) { my $mark = $board[$$row[0]]; next if $mark eq '.'; return $mark if $mark eq $board[$$row[1]] and $mark eq $board[$$row[2]]; } return ''; } sub move { my ($line) = @_; my $response; my $winner; if ($winner = findWinner()) { return "OOPS - $winner has already won. (use START or DONE.)"; } #print " line = '$line' \n"; (my $clientAt) = ($line =~ /([a-i])\s*$/ig); $clientAt = lc($clientAt); #print " clientAt = '$clientAt' \n"; return("OOPS - I didn't understand that move.") unless $clientAt; my $where = ord($clientAt) - ord('a'); #print "where = '$where'\n"; #print "board[where] = '" . $board[$where] . "'\n"; return("OOPS - that location is already marked.") if $board[$where] ne '.'; $board[$where] = $clientMark; $response = "OK, your_move='$clientAt'"; if ($winner = findWinner()) { $response .= ". You win!"; return $response; } my $serverAt = doServerMove(); $response .= ", my_move='$serverAt'" if $serverAt; if ($winner = findWinner()){ $response .= ". I win!"; return $response; } if (boardIsFull()) { $response .= ". The board is full."; } else { $response .= ". Your turn."; } return $response; } sub done { print $sock "OK, we're done. Signing off.\n"; printboard(); close($sock); exit; } }