#/usr/bin/env python


#Import Modules
import os, pygame
from pygame.locals import *
import time
import sys

if not pygame.font: print 'Warning, fonts disabled'
if not pygame.mixer: print 'Warning, sound disabled'

TICKS_PER_SECOND = 30
TIME_SLICE       = 1 / float(TICKS_PER_SECOND)
BOARD_SIZE       = 23 
SQUARE_SIZE      = 26 # board segment h/w in pixels
SEGMENT_TARGET   = [3, 28]  # Used to control mouse pos. vs cursor pos
GAME_HEIGHT      = SQUARE_SIZE * BOARD_SIZE
GAME_WIDTH       = SQUARE_SIZE * BOARD_SIZE
BOARD_CIRCLE_COLOR     = (128,128,128)

PLAYER_0_COLOR = (0, 0, 200)
PLAYER_1_COLOR = (200, 0, 0)

def load_image(name, colorkey=None):
    #fullname = os.path.join('data', name)
    fullname = name
    try:
        image = pygame.image.load(fullname)
    except pygame.error, message:
        print 'Cannot load image:', fullname
        raise SystemExit, message
    image = image.convert()
    if colorkey is not None:
        if colorkey is -1:
            colorkey = image.get_at((0,0))
        image.set_colorkey(colorkey, RLEACCEL)
    return image


def display_string(surface, s, c, fs, x, y):
    if pygame.font:
        font = pygame.font.Font(None, fs)
        text = font.render(s, 1, c, (0,0,0))
        textpos = text.get_rect()
        if x == -1:
            textpos.centerx = surface.get_rect().centerx
        else:
            textpos.centerx = x
        textpos.centery = y
        surface.blit(text, textpos)

class Piece:
    
    def __init__(self, coord, player):
        self.x = coord[0]
        self.y = coord[1]
        self.player = player

    def can_connect(self, piece):
        if (self.player == piece.player
            and ((abs(self.x-piece.x) == 1 and abs(self.y-piece.y) == 2)
                  or abs(self.x-piece.x) == 2 and abs(self.y-piece.y) == 1)):
            return 1

    def __cmp__(self, piece):
#        if self.player != piece.player:
#            return self.player - piece.player
        if self.x != piece.x:
            return self.x - piece.x
        if self.y != piece.y:
            return self.y - piece.y
        return 0

    def __str__(self):
        if self.player == 0:
            return "(%d, %d, %s)" % (self.x,  self.y, "player 0)")
        else:
            return "(%d, %d, %s)" % (self.x,  self.y, "player 1)")

class Connection:
    def __init__(self,p1,p2):
        # sort the points by x
        if p1.x <= p2.x:
            self.p1 = p1
            self.p2 = p2
        else:
            self.p1 = p2
            self.p2 = p1

    def __str__(self):
        return "(%s, %s)" % (self.p1, self.p2)
    
    def crosses(self, conn):
        x1 = self.p1.x
        x2 = self.p2.x
        x3 = conn.p1.x
        x4 = conn.p2.x
        y1 = self.p1.y
        y2 = self.p2.y
        y3 = conn.p1.y
        y4 = conn.p2.y
        #print "Checking for crossing between %s and %s" % (self, conn)
        denom = float((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1))
        if denom == 0:
            #print "Parallel line segments"
            return 0
        ua = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / denom
        ub = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / denom
        #print "ua, ub =", ua, ub
        if ua > 0 and ua < 1 and ub > 0 and ub < 1:
            #print "Crosses"
            return 1
        else:
            return 0
        
class Cursor:        
    def __init__(self, surf, pos, color):
        self.surf = surf
        self.pos = (0, 0) # Board square we are on
        self.size = (SQUARE_SIZE, SQUARE_SIZE)
        self.saved_surf = pygame.Surface(self.size)
        self.__extent()
        self.saved_surf_invalid_flg = 1
        self.color = color
        self.on = 0
        self.time = 0
        pygame.mouse.set_pos((SQUARE_SIZE / 2,
                             SQUARE_SIZE / 2))

    def __extent(self):
        # Refigure extent
        x = self.pos[0] * SQUARE_SIZE
        y = self.pos[1] * SQUARE_SIZE
        self.extent = [x, y, self.size[0], self.size[1]]

    def save_surf(self):
        # Save what the cursor overwrites
        self.saved_surf.blit(self.surf, (0,0), self.extent)
        self.saved_surf_invalid_flg = 0

    def make_saved_surf_invalid(self):
        self.saved_surf_invalid_flg = 1
                
    def undraw(self):
        if self.on:
            if self.saved_surf_invalid_flg:
                print "Fatal...cannot undraw a cursor when the surface was invalidated"
                sys.exit(1)
            self.surf.blit(self.saved_surf, self.extent)
            self.on = 0
        
    def draw(self):
        if not self.on:
            if self.saved_surf_invalid_flg:
                # Before drawing, we need to save the background
                self.save_surf()
            pygame.draw.rect(self.surf,
                             self.color,
                             self.extent)
            self.on = 1

    def blink(self):
        if self.time > .5:
            if self.on:
                self.undraw()
            else:
                self.draw()
            self.time = 0
        self.time += TIME_SLICE

    # Position is the board position (x,y) not the pixel based (x,y)        
    def move_xy(self, new_pos):
        # Undraw
        # Move
        if new_pos[0] >= 0 and new_pos[0] <= GAME_WIDTH \
           and new_pos[1] >= 0 and new_pos[1] <= GAME_HEIGHT:
            self.undraw()
            self.pos = new_pos
            # Calculate new extent
            self.__extent()
            #self.time = 0
            self.make_saved_surf_invalid()

    def mouse_move(self):
        pos = pygame.mouse.get_pos()

        # See how many segments mouse is from cursors position
        move_seg_x = pos[0] / SQUARE_SIZE - self.pos[0]
        move_seg_y = pos[1] / SQUARE_SIZE - self.pos[1]
        if move_seg_x or move_seg_y:
            # Lets make sure we are not too close to the edge
            x_offset = pos[0] % SQUARE_SIZE
            y_offset = pos[1] % SQUARE_SIZE
            if (x_offset >= SEGMENT_TARGET[0]
                and x_offset <= SEGMENT_TARGET[1]
                and y_offset >= SEGMENT_TARGET[0]
                and y_offset <= SEGMENT_TARGET[1]):
                # Calculate board position we are on
                self.move_xy((pos[0] / SQUARE_SIZE,
                              pos[1] / SQUARE_SIZE))
    
class Board:
    def __init__(self):        
        self.piece_list = []
        self.surf = pygame.display.set_mode((GAME_WIDTH, GAME_HEIGHT))
        self.back = pygame.Surface((GAME_WIDTH, GAME_HEIGHT))
        self.create_board()
        self.surf.blit(self.back, (0, 0))
        self.cursor = Cursor(self.surf, (0,0), PLAYER_0_COLOR)
        self.player = 0   # 0 or 1
        self.connections = []
        # Used to determine a win.  Keeps track of x extent of player 0's
        # connections
        self.player0_extent = [BOARD_SIZE,-1] 
        # Used to determine a win.  Keeps track of y extent of player 1's
        # connections
        self.player1_extent = [BOARD_SIZE,-1]
        self.game_over = 0
        
    def valid_square(self, pos):
        if (pos == (0,0) or pos == (0, BOARD_SIZE-1)
            or pos == (BOARD_SIZE-1,0) or pos == (BOARD_SIZE-1,BOARD_SIZE-1)):
            return 0
        else:
            return 1
        
    def create_board(self):
#        board_seg = load_image('bdpc.bmp', -1)
        rad = SQUARE_SIZE / 2
        for x in range(BOARD_SIZE):
            for y in range(BOARD_SIZE):                
                if self.valid_square((x,y)):
                    # Draw circle centered at position
                    cpos = (x * SQUARE_SIZE + rad,
                            y * SQUARE_SIZE + rad)
                    if x == 0 or x == BOARD_SIZE - 1:
                        color = (0,0,128)
                    elif y == 0 or y == BOARD_SIZE - 1:
                        color = (128,0,0)
                    else:
                        color = BOARD_CIRCLE_COLOR
                    pygame.draw.circle(self.back, color,
                                       cpos,
                                       int(rad * .5), 1)

#                    self.back.blit(board_seg, (x * SQUARE_SIZE,
#                                               y * SQUARE_SIZE))

    def piece_color(self):
        if self.player == 0:
            return PLAYER_0_COLOR
        else:
            return PLAYER_1_COLOR

    def illegal_piece(self, p):
        # Corners are illegal
        if p.x == 0 and p.y == 0:
            return 1
        elif p.x == 0 and p.y == BOARD_SIZE - 1:
            return 1
        elif p.x == BOARD_SIZE - 1 and p.y == 0:
            return 1
        elif p.x == BOARD_SIZE - 1 and p.y == BOARD_SIZE - 1:
            return 1
        if p.player == 0 and (p.y == 0 or p.y == BOARD_SIZE - 1):
            return 1
        elif p.player == 1 and (p.x == 0 or p.x == BOARD_SIZE - 1):
            return 1

        return 0
        
    def add_piece(self):
        piece = Piece(self.cursor.pos, self.player)
        if self.illegal_piece(piece):
            return
        if piece in self.piece_list:
            return # Already a piece in this spot

        self.cursor.undraw() # Before changing the board, undraw the cursor
        #time.sleep(1)
        rad = SQUARE_SIZE / 2
        # Draw circle centered at cursor position
        cpos = (self.cursor.pos[0] * SQUARE_SIZE + rad,
                               self.cursor.pos[1] * SQUARE_SIZE + rad)
        pygame.draw.circle(self.surf, self.piece_color(),
                           cpos,
                           int(rad * .60))
        
        # Connect bridges to new piece and add it to piece list
        self.connect(piece)
        # Appending piece
        self.piece_list.append(piece)

        if self.won():
            if self.player == 0:
                disp_string = "Blue player won!"
                color = (0, 0, 200)
            else:
                disp_string = "Red player won!"
                color = (200, 0, 0)

            self.surf.blit(self.back, (0,0))    
            display_string(self.surf, disp_string,
                           color,
                           48,
                           -1,
                           BOARD_SIZE * SQUARE_SIZE / 2)
            
        # Lets cursor know board has been changed 
        self.cursor.make_saved_surf_invalid()
        
        pygame.display.flip()

        # Switch players
        if self.player == 0:
            self.player = 1
            self.cursor.color = PLAYER_1_COLOR
        else:
            self.player = 0
            self.cursor.color = PLAYER_0_COLOR

    def draw_new_connection(self, conn):
        pygame.draw.line(self.surf,
                         self.piece_color(),
                         (conn.p1.x * SQUARE_SIZE + SQUARE_SIZE / 2,
                          conn.p1.y * SQUARE_SIZE + SQUARE_SIZE / 2),
                         (conn.p2.x * SQUARE_SIZE + SQUARE_SIZE / 2,
                          conn.p2.y * SQUARE_SIZE + SQUARE_SIZE / 2), 3)
        self.cursor.save_surf()
        
    def add_connection(self, conn):
        self.connections.append(conn)
        self.draw_new_connection(conn)

    def connection_crosses(self, conn):
        for curr_conn in self.connections:
            if curr_conn.crosses(conn):
                return 1
        return 0
            
    def connect(self, piece):
        # Try to connect new piece to existing pieces
        for p in self.piece_list:
            # piece.can_connect() only checks that the distance
            # is valid.
            if piece.can_connect(p):
                conn = Connection(piece, p)
                if not self.connection_crosses(conn):
                    if self.player == 0:
                        # note: connections points are sorted on x
                        if conn.p1.x < self.player0_extent[0]:
                            self.player0_extent[0] = conn.p1.x
                        if conn.p2.x > self.player0_extent[0]:
                            self.player0_extent[1] = conn.p2.x
                    else:
                        self.player1_extent[0] = min(self.player1_extent[0],
                                                     min(conn.p1.y, conn.p2.y))
                        self.player1_extent[1] = max(self.player1_extent[1],
                                                     max(conn.p1.y, conn.p2.y))                        
                    self.add_connection(conn)

    def won(self):
        # Check to see if their is a unbroken path from start to finish
        # for either player
        if (self.player0_extent == [0, BOARD_SIZE-1]
            or self.player1_extent == [0, BOARD_SIZE-1]):
            self.game_over = 1
            return 1
        else:
            return 0
            
    def cursor_mouse_move(self):
        self.cursor.mouse_move()
        pygame.display.flip()

    def update_display(self):
        self.cursor.blink()
        pygame.display.flip()

        
def main():
    """this function is called when the program starts.
       it initializes everything it needs, then runs in
       a loop until the function returns."""
#Initialize Everything
    pygame.init()
#    screen = pygame.display.set_mode((GAME_WIDTH, GAME_HEIGHT))
    pygame.display.set_caption('Connect')
    pygame.mouse.set_pos((0,0))
    pygame.mouse.set_visible(1)
    
# Create the board
    game_board = Board()
    game_board.update_display()    
    
#Main Loop
    clock = pygame.time.Clock()
    while 1:
        clock.tick(TICKS_PER_SECOND)

        game_board.cursor_mouse_move()    
        for event in pygame.event.get():
            if event.type is QUIT:
                return
            elif (event.type is KEYDOWN and
                  event.key == K_r):
                game_board = Board()
                
                
            if not game_board.game_over:
                if event.type is MOUSEBUTTONDOWN:
                    game_board.add_piece()

        game_board.update_display() # Updates display

        
#this calls the 'main' function when this script is executed
if __name__ == '__main__': main()

