import sys, random
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMainWindow, QDesktopWidget, QFrame, QMessageBox, QAction, QLineEdit, QGridLayout, QLabel
from PyQt5.QtCore import Qt, QBasicTimer, pyqtSignal
from PyQt5.QtGui import QIcon, QColor, QPainter
import datetime

class Connect4(QMainWindow):
    def __init__(self):
        self.dims = (800,700)
        super().__init__()
        self.initUI()
    
    def initUI(self):
        self.settings = getSettings(self)
        # self.settings.show()
        self.tboard = Board(self)
        self.setCentralWidget(self.tboard)

        self.statusbar = self.statusBar()
        self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
        self.initMenu()

        self.tboard.start()
        self.resize(*self.dims)
        self.center()
        self.setWindowTitle("Connect 4")
        self.show()
        pass

    def initMenu(self):
        newGame = QAction("&New",self)
        newGame.triggered.connect(self.tboard.clearBoard)

        exitAction = QAction("&Exit", self)
        exitAction.triggered.connect(self.closeEvent)

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(newGame)
        fileMenu.addAction(exitAction)

    def center(self):
        screen = QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width()-size.width())/2,
                    (screen.height()-size.height())/2)

class getSettings(QWidget):
    def __init__(self,parent):
        super().__init__()
        self.title = "Change settings"

        self.initUI()

    def initUI(self):
        grid = QGridLayout()
        self.setLayout(grid)

        fields = ["Time allowed for turn:","Colour 1","Colour 2"]

        for i,x in enumerate(fields):
            lbl = QLabel(x)
            qle = QLineEdit(self)
            grid.addWidget(lbl,i,0)
            grid.addWidget(qle,i,1)
            maxln = i

        btn = QPushButton("Submit")
        grid.addWidget(btn,maxln+1,0)

        self.setWindowTitle(self.title)
        self.show()

class Board(QFrame):
    msg2Statusbar = pyqtSignal(str)
    def __init__(self, parent):
        super().__init__(parent)
        self.initBoard()
        self.dims = parent.dims
        self.setMouseTracking(True)
        self.mouseX = 0
        self.connect_count = 4 # Since it's connect 4
        self.max_turn_time = 10
        # self.positions = [[0 for _ in range(7)] for _ in range(6)]


    def initBoard(self):
        self._make_colors()
        self.timer = QBasicTimer()
        self.piece_r = 95

        self.setFocusPolicy(Qt.StrongFocus)
        pass

    def clearBoard(self):
        self.turn = random.randint(1,2)
        self.board = [[0 for _ in range(7)] for _ in range(6)]
        self.turn_start_time = datetime.datetime.now()
        
    def start(self):
        # Start the gameloop
        self.clearBoard()
        self.timer.start(50, self)
        pass
        
    
    def paintEvent(self,e):
        qp = QPainter(self)
        # qp.begin(self)
        qp.setPen(self.colors["blue"])
        qp.setBrush(self.colors["blue"])
        qp.drawRect(20,20,730,630)
        for y,j in enumerate(self.board):
            for x,i in enumerate(j):
                x2 = x*100 + 30
                y2 = y*100 + 30
                self.drawPiece(qp,x2,y2,i)
        # Draw preview piece
        pos = int((self.mouseX-30)/100) * 100 + 30
        if pos> 630: pos=630
        self.drawPiece(qp,pos,-55,self.turn)

    def mouseMoveEvent(self,e):
        x = e.x()
        self.mouseX = x

    def mousePressEvent(self,e):
        x = e.x()
        # y = e.y()
        pos = int((x-30)/100)
        if pos>6:pos=6
        # print(x,y)
        self.drop(pos,self.turn)

    def checkWinner(self,x,y,board):
        # Actually the most painful part of the entire game
        # Requires the game state as a parameter incase I need to check "hypothetical" states ;)
        colour = board[y][x]

        apply = lambda x,y,i,j: ((x+i),(y+j))
        verify = lambda x,y: (x>=0 and x<len(board[0])) and (y>=0 and y<len(board))
        
        # starts from right, anticlockwise
        directions = [(1,0),(1,1),(0,1),(-1,1),(-1,0),(-1,-1),(0,-1),(1,-1)]
        sums = [0 for _ in range(self.connect_count)]
        for di,direc in enumerate(directions):
            cx,cy = x,y
            for _ in range(self.connect_count-1):
                cx,cy = apply(cx,cy,*direc)
                if not verify(cx,cy): break

                if board[cy][cx] == colour:
                    sums[di%4]+=1
                else:
                    break

        if any([x>=self.connect_count-1 for x in sums]): self.winner(colour)
    
    def winner(self, colour):
        self.timer.stop()
        reply = QMessageBox.question(self, "Game Over", "{0} wins! Would you like to play again?".format(["Red","Yellow"][colour-1]), QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

        if reply == QMessageBox.Yes:
            self.start()
        else:
            self.closeEvent()

    def CloseEvent(self, event):
        event.accept()

    def timerEvent(self, e):
        
        if e.timerId() == self.timer.timerId():
            self.update()
        else:
            super(Board,self).timerEvent(e)
        now = datetime.datetime.now()
        b = self.max_turn_time-(now - self.turn_start_time).seconds
        if b>0:
            self.msg2Statusbar.emit("%i"%b)
        else:
            self.drop(random.randint(0,6),self.turn)


    def drop(self,col,colour):

        invert = lambda x: -x+5
        for i,x in enumerate(self.board[::-1]):
            if x[col]==0:
                i2 = invert(i)
                self.board[i2][col] = colour
                self.turn_start_time = datetime.datetime.now()
                self.turn = swap(self.turn,1,2)
                self.checkWinner(col,i2,self.board)
                break
        

    def drawPiece(self,painter,x,y,col):
        c = ["white","red","yellow"]
        color = self.colors[c[col]]
        painter.setPen(self.colors["blue"])
        painter.setBrush(color)
        painter.drawEllipse(x,y,self.piece_r,self.piece_r)


    def _make_colors(self):
        colors={"white":0xFFFFFF,"red":0xF01C1C,"yellow":0xE7D61A,"blue":0x1A24E7}
        self.colors = {}
        for key,value in colors.items():
            self.colors[key] = QColor(value)
        

def swap(val, x1, x2):
    if val==x1:
        return x2
    else: return x1

if __name__=="__main__":
    app = QApplication([])
    con4 = Connect4()
    sys.exit(app.exec_())
import sys, random
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMainWindow, QDesktopWidget, QFrame, QMessageBox, QAction, QLineEdit, QGridLayout, QLabel
from PyQt5.QtCore import Qt, QBasicTimer, pyqtSignal
from PyQt5.QtGui import QIcon, QColor, QPainter
import datetime

class Connect4(QMainWindow):
    def __init__(self):
        self.dims = (800,700)
        super().__init__()
        self.initUI()
    
    def initUI(self):
        self.settings = getSettings(self)
        # self.settings.show()
        self.tboard = Board(self)
        self.setCentralWidget(self.tboard)

        self.statusbar = self.statusBar()
        self.tboard.msg2Statusbar[str].connect(self.statusbar.showMessage)
        self.initMenu()

        self.tboard.start()
        self.resize(*self.dims)
        self.center()
        self.setWindowTitle("Connect 4")
        self.show()
        pass

    def initMenu(self):
        newGame = QAction("&New",self)
        newGame.triggered.connect(self.tboard.clearBoard)

        exitAction = QAction("&Exit", self)
        exitAction.triggered.connect(self.closeEvent)

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(newGame)
        fileMenu.addAction(exitAction)

    def center(self):
        screen = QDesktopWidget().screenGeometry()
        size = self.geometry()
        self.move((screen.width()-size.width())/2,
                    (screen.height()-size.height())/2)

class getSettings(QWidget):
    def __init__(self,parent):
        super().__init__()
        self.title = "Change settings"

        self.initUI()

    def initUI(self):
        grid = QGridLayout()
        self.setLayout(grid)

        fields = ["Time allowed for turn:","Colour 1","Colour 2"]

        for i,x in enumerate(fields):
            lbl = QLabel(x)
            qle = QLineEdit(self)
            grid.addWidget(lbl,i,0)
            grid.addWidget(qle,i,1)
            maxln = i

        btn = QPushButton("Submit")
        grid.addWidget(btn,maxln+1,0)

        self.setWindowTitle(self.title)
        self.show()

class Board(QFrame):
    msg2Statusbar = pyqtSignal(str)
    def __init__(self, parent):
        super().__init__(parent)
        self.initBoard()
        self.dims = parent.dims
        self.setMouseTracking(True)
        self.mouseX = 0
        self.connect_count = 4 # Since it's connect 4
        self.max_turn_time = 10
        # self.positions = [[0 for _ in range(7)] for _ in range(6)]


    def initBoard(self):
        self._make_colors()
        self.timer = QBasicTimer()
        self.piece_r = 95

        self.setFocusPolicy(Qt.StrongFocus)
        pass

    def clearBoard(self):
        self.turn = random.randint(1,2)
        self.board = [[0 for _ in range(7)] for _ in range(6)]
        self.turn_start_time = datetime.datetime.now()
        
    def start(self):
        # Start the gameloop
        self.clearBoard()
        self.timer.start(50, self)
        pass
        
    
    def paintEvent(self,e):
        qp = QPainter(self)
        # qp.begin(self)
        qp.setPen(self.colors["blue"])
        qp.setBrush(self.colors["blue"])
        qp.drawRect(20,20,730,630)
        for y,j in enumerate(self.board):
            for x,i in enumerate(j):
                x2 = x*100 + 30
                y2 = y*100 + 30
                self.drawPiece(qp,x2,y2,i)
        # Draw preview piece
        pos = int((self.mouseX-30)/100) * 100 + 30
        if pos> 630: pos=630
        self.drawPiece(qp,pos,-55,self.turn)

    def mouseMoveEvent(self,e):
        x = e.x()
        self.mouseX = x

    def mousePressEvent(self,e):
        x = e.x()
        # y = e.y()
        pos = int((x-30)/100)
        if pos>6:pos=6
        # print(x,y)
        self.drop(pos,self.turn)

    def checkWinner(self,x,y,board):
        # Actually the most painful part of the entire game
        # Requires the game state as a parameter incase I need to check "hypothetical" states ;)
        colour = board[y][x]

        apply = lambda x,y,i,j: ((x+i),(y+j))
        verify = lambda x,y: (x>=0 and x<len(board[0])) and (y>=0 and y<len(board))
        
        # starts from right, anticlockwise
        directions = [(1,0),(1,1),(0,1),(-1,1),(-1,0),(-1,-1),(0,-1),(1,-1)]
        sums = [0 for _ in range(self.connect_count)]
        for di,direc in enumerate(directions):
            cx,cy = x,y
            for _ in range(self.connect_count-1):
                cx,cy = apply(cx,cy,*direc)
                if not verify(cx,cy): break

                if board[cy][cx] == colour:
                    sums[di%4]+=1
                else:
                    break

        if any([x>=self.connect_count-1 for x in sums]): self.winner(colour)
    
    def winner(self, colour):
        self.timer.stop()
        reply = QMessageBox.question(self, "Game Over", "{0} wins! Would you like to play again?".format(["Red","Yellow"][colour-1]), QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

        if reply == QMessageBox.Yes:
            self.start()
        else:
            self.closeEvent()

    def CloseEvent(self, event):
        event.accept()

    def timerEvent(self, e):
        
        if e.timerId() == self.timer.timerId():
            self.update()
        else:
            super(Board,self).timerEvent(e)
        now = datetime.datetime.now()
        b = self.max_turn_time-(now - self.turn_start_time).seconds
        if b>0:
            self.msg2Statusbar.emit("%i"%b)
        else:
            self.drop(random.randint(0,6),self.turn)


    def drop(self,col,colour):

        invert = lambda x: -x+5
        for i,x in enumerate(self.board[::-1]):
            if x[col]==0:
                i2 = invert(i)
                self.board[i2][col] = colour
                self.turn_start_time = datetime.datetime.now()
                self.turn = swap(self.turn,1,2)
                self.checkWinner(col,i2,self.board)
                break
        

    def drawPiece(self,painter,x,y,col):
        c = ["white","red","yellow"]
        color = self.colors[c[col]]
        painter.setPen(self.colors["blue"])
        painter.setBrush(color)
        painter.drawEllipse(x,y,self.piece_r,self.piece_r)


    def _make_colors(self):
        colors={"white":0xFFFFFF,"red":0xF01C1C,"yellow":0xE7D61A,"blue":0x1A24E7}
        self.colors = {}
        for key,value in colors.items():
            self.colors[key] = QColor(value)
        

def swap(val, x1, x2):
    if val==x1:
        return x2
    else: return x1

if __name__=="__main__":
    app = QApplication([])
    con4 = Connect4()
    sys.exit(app.exec_())
# self.settings.show() # Since it's connect 4 # self.positions = [[0 for _ in range(7)] for _ in range(6)] # Start the gameloop # qp.begin(self) # Draw preview piece # y = e.y() # print(x,y) # Actually the most painful part of the entire game # Requires the game state as a parameter incase I need to check "hypothetical" states ;) # starts from right, anticlockwise