From 2f64fdd2098197633ff8b32d0eaf6393424c2ad5 Mon Sep 17 00:00:00 2001 From: En Yi Date: Fri, 6 Jul 2018 14:13:33 +0800 Subject: [PATCH] First Commit --- .gitignore | 3 + .../sudoku_gameplay.cpython-35.pyc | Bin 0 -> 4363 bytes gameplay/sudoku_gameplay.py | 120 +++++++++++ gameplay/test_board.txt | 9 + general/__pycache__/extras.cpython-35.pyc | Bin 0 -> 278 bytes general/extras.py | 2 + .../__pycache__/board.cpython-35.pyc | Bin 0 -> 7620 bytes .../__pycache__/buttons.cpython-35.pyc | Bin 0 -> 4597 bytes graphic_components/board.py | 203 ++++++++++++++++++ graphic_components/buttons.py | 142 ++++++++++++ main.py | 67 ++++++ requirements.txt | 3 + 12 files changed, 549 insertions(+) create mode 100644 .gitignore create mode 100644 gameplay/__pycache__/sudoku_gameplay.cpython-35.pyc create mode 100644 gameplay/sudoku_gameplay.py create mode 100644 gameplay/test_board.txt create mode 100644 general/__pycache__/extras.cpython-35.pyc create mode 100644 general/extras.py create mode 100644 graphic_components/__pycache__/board.cpython-35.pyc create mode 100644 graphic_components/__pycache__/buttons.cpython-35.pyc create mode 100644 graphic_components/board.py create mode 100644 graphic_components/buttons.py create mode 100644 main.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..acf12e7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +venv/ +demos/ +.idea/ diff --git a/gameplay/__pycache__/sudoku_gameplay.cpython-35.pyc b/gameplay/__pycache__/sudoku_gameplay.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7997b70e3d62fc7fe71324cabb2d9a880d0a52a GIT binary patch literal 4363 zcmb7H&2!t<5#I+8APG?)78NV1WLxp1onfm;j_XO&R<)h6oQyju>sA`eVXb0l0+L99 z0QCUI5{Ei;+#Y)B^wet)nNEA`z4!KSc-NeK%vGoU_JN95w03LpV0-~B=yk?MRP|YOIq*{)=9MvrHEUMY$=gG6_#31>A zygbzk6x!q!82QXAlIPIxah5}`u^+F3b}KV)a06A}2*5W61HOAt6A^wdnl4+1w%>Pa_N^U>j< zA9~I3$YpY@ELjhad}Y@=9ne*N@A2;5lj>)xeB_6|tS7!Z_KHeQ`dvqJ9Mti&N=&GGi)ZV zdkv?>d?blg{1ru6*cjKul3|P9JCpX^9PSwznths$tqpw&1U6!m!Qv?b6FsLy+g;o4 z=WM&PPbNt})Q0@NwnmQnf>vOt73pONupwLO^ry6cdKcps?Q5%u;?jx2bgjifnQGzc@yt3ZzpJl^->l~qPIMy zk@4yV>wzEsKhnuB@dD@Xo9ue*Z-Dq2bZF2vsL&VEO8l{&GX`@=BO)wo68u>&Pjd9y z9Qdh`N?fs&3Fk{CUTlV6)`O;}oy=_<`;8N|2&aInrKHpyKPh83E)PZsc2I?KRM5@75ICZ=3F0E0VqcTPQMhkbBJ2 z|5s$XaFhZ#_$&-<*B=9cyTi*F!k+<=;&ZIZ0d6r44@W%64|T0X;@{O8HLH7{R&2=* zrp$T})SMtXYBuVD8_9vDpMCQAlO6dUR=`zsBxgKhp%Jw^fuA&^Fb#trVQp*!koZ94 zjNXlDJ!e*rv^IqjA;3(Gy68-hsDcappRmQlAgXG(~NI;VbDc^E{bfE zK5~jK%3xIfM=%9BHbW^)Q!K3q+%*i_?X$mV5^fx3?tgM+=XyIJv~UIy-|W!E44vN| zZg9ZZ46UFBkB-CjVn1A?T;kA)L-LOTPl9$s^D4dw1w9{}KG+?v>g!6>cL=8?5CMtu zbXDy0wv^vlWTIrP#2fs86wT;1*piCVKEwdhA;{S1_ z-Y~qfZRjf}Y#JY!XTPIlf%*mZpXEMmk*8(oYUkj1Y_pN@okerZ)i)DdWfd{B?bV6^ zS7SwdW9&n^2?e4w>U>}iN_!4*C^;$h7OT;E+-)4Mb1$`i7l!cmE-%VI zsiujCvw|{v%}!-@Ttm}jUqlT}Gfu6DR&9u)ybjR0AmUWUEuxDBwaIs^sx3{->jJJ< zy-nqUEu<2STJdE)E%nH$0sx5ccON2|9i zuAAKUE#e(zUEBm^8h>BBo6?O;p?bUTXbissxD^|v(68}=nt+taZM;E$LbHx=&>0lL ziC%smHviSHpK~ypboLW=^qqsdgxrZ-iYkJ1=9&EUftYOR?=wUexMS>{%g>V=BtvMk;YI7?6x_KU5uDL3i9F3gGCk}3V{Z2?Vk-gwP{zI5rvIi zwqht_^N!>uG-KUMXFF)d2^YHi+;i)CU8LTp?mruk-aQ3~n)4XTT*~mEt+YIh8;8U-< aP8GIOLGH7&4eqoN#`uGdN}_1m#(x34bZ`m) literal 0 HcmV?d00001 diff --git a/gameplay/sudoku_gameplay.py b/gameplay/sudoku_gameplay.py new file mode 100644 index 0000000..33df84f --- /dev/null +++ b/gameplay/sudoku_gameplay.py @@ -0,0 +1,120 @@ +import numpy as np + +EMPTY = 0 +VALID = 1 +INVALID = 2 +FIXED = 3 + +TESTING = True +if __name__ == "__main__": + test_dir = './test_board.txt' +else: + + test_dir = './gameplay/test_board.txt' + +class SudokuSystem: + + def __init__(self): + self.number_grid = np.zeros((9, 9), dtype=np.uint8) + self.cell_status = np.zeros((9, 9), dtype=np.uint8) + self.offending_cells = [] + for i in range(9): + row = [] + for j in range(9): + row.append([]) + self.offending_cells.append(row) + + if TESTING: + self.generate_test_board() + + def clear_grid(self): + self.number_grid = 0 + self.cell_status = EMPTY + for i in range(9): + for j in range(9): + while self.offending_cells[i][j]: + self.offending_cells[i][j].pop() + + def replace_cell_number(self, row, col, val): + self.number_grid[row, col] = int(val) + if not val == 0: + self.invalid_cell_check(row, col) + else: + self.change_cell_status(row, col, EMPTY) + + def get_cell_number(self, row, col): + return self.number_grid[row, col] + + def change_cell_status(self, row, col, new_status): + if not self.cell_status[row, col] == FIXED: + self.cell_status[row, col] = new_status + + def get_cell_status(self, row, col): + return self.cell_status[row, col] + + def completion_check(self): + if np.all(np.logical_or(self.cell_status == VALID, self.cell_status == FIXED)): + return True + else: + return False + + def invalid_cell_check(self, row, col): + val_check = self.number_grid[row, col] + + row_check = np.where(self.number_grid[row, :] == val_check)[0] + col_check = np.where(self.number_grid[:, col] == val_check)[0] + local_grid_row = int(row / 3) * 3 + local_grid_col = int(col / 3) * 3 + local_grid_check_row, local_grid_check_col = np.where( + self.number_grid[local_grid_row:local_grid_row + 3, local_grid_col:local_grid_col + 3] == val_check) + + if len(row_check) == 1 and len(col_check) == 1 and len(local_grid_check_row) == 1: + self.cell_status[row, col] = VALID + while self.offending_cells[row][col]: + r, c = self.offending_cells[row][col].pop() + try: + self.offending_cells[r][c].remove((row, col)) + except ValueError: + print('No such cell found') + if not self.offending_cells[r][c]: + self.change_cell_status(r, c, VALID) + print('Completion?', self.completion_check()) + + else: + self.cell_status[row, col] = INVALID + bad_cells = [] + if not len(row_check) == 1: + for c in row_check: + if not c == col: + bad_cells.append((row, c)) + self.offending_cells[row][c].append((row, col)) + self.change_cell_status(row, c, INVALID) + if not len(col_check) == 1: + for r in col_check: + if not r == row: + bad_cells.append((r, col)) + self.offending_cells[r][col].append((row, col)) + self.change_cell_status(r, col, INVALID) + if not len(local_grid_check_row) == 1: + for r, c in zip(local_grid_check_row + local_grid_row, local_grid_check_col + local_grid_col): + if not (c == col or r == row): + bad_cells.append((r, c)) + self.offending_cells[r][c].append((row, col)) + self.change_cell_status(r, c, INVALID) + + self.offending_cells[row][col] = bad_cells + + def generate_test_board(self): + with open(test_dir, 'r') as f: + lines = f.readlines() + + values = [] + for line in lines: + values.append([int(val) for val in line.strip('\n').split(',')]) + + self.number_grid[:] = values + self.cell_status[:] = FIXED + row, col = np.where(self.number_grid == 0) + + for r, c in zip(row, col): + self.cell_status[r, c] = EMPTY diff --git a/gameplay/test_board.txt b/gameplay/test_board.txt new file mode 100644 index 0000000..58b36d3 --- /dev/null +++ b/gameplay/test_board.txt @@ -0,0 +1,9 @@ +1,2,3,4,5,6,7,8,9 +4,5,6,7,8,9,1,2,3 +7,8,9,1,2,3,4,5,6 +2,3,4,5,6,7,8,9,1 +5,6,7,8,9,1,2,3,4 +8,9,1,2,3,4,5,6,7 +3,4,5,6,7,8,9,1,2 +6,7,8,9,1,2,3,4,5 +9,1,2,3,4,5,6,7,0 \ No newline at end of file diff --git a/general/__pycache__/extras.cpython-35.pyc b/general/__pycache__/extras.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2e932dadd833f2be251e13adc0dcd328ce06df5 GIT binary patch literal 278 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 b`ETGkW7>Gdx+VdC84xmSO`n_YPN)9^MXvpP literal 0 HcmV?d00001 diff --git a/graphic_components/__pycache__/buttons.cpython-35.pyc b/graphic_components/__pycache__/buttons.cpython-35.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0d56e505d81b728f639d982d17ff780836b0a5d GIT binary patch literal 4597 zcmbVPS#ul55$;_qZh+tcUZP|jwqij^L_4R;Q5+o>WycCbhAJzyDc1(X4uPfifMy5c zQB>tYE~P3}sq$0)K_2oK@&oda*SyVBs`AAAggoWzSuA)+(L+E?qo=25`k3yX=0dYs ztNi_^zxhL(=wEc|;{d;dk^URN$Je5eD6+`4D6+}5DJqa#pvWQDp{Pi1ku2u3DJ)S` zCbvvch1^O;El^mcs77v$qB^;CiW=lLC~A`1q-cuVDT-R;wkVn=cbcLZa%Zxg4u!K6 z&5=7tQJdU0Mf2p&Q?x+t0!54DE>g5a?h-}IM2H=;- zy+jZF68UBN8tRrwwCE*Fd58QWZFMTgu$MYTUD@2|2C))Smp1RoQ997h=7xwdyPt%K z)WyxUBvu_;H#YAMhhfm`svzOz$3Y}yr=q7eAIk1<5cJZmo`{8RPUc?*V(--Sb0s1@ zGg*BU#Nr(3SE8p3sWsvH<@PR!dUitK^@lPU4No1l(NzPccdB5#yPYc8?WwzQ5M{cS zH^--xH#ZX4x~A<-1q)c*8h%-mHSFAw$xujjaI&*8Jb0#Zvaa!LE9l4FaJ|Rw%Rd}M z{7L@-Ac$iJA%fTu1$qg@$&n%>qC_tdUuBN9-xKYC*(Se0uRyGj@9+iT%G@eVt`Fhl zW2tKBb>8TMQm3!6r9q-eVv5i78{{{chtF^EjqgsAKXryUme?YHnqEQ5Os>NW`Ll*H zOa2`BZ6j-rSGPLzy4;03?o(bAiLagK`#&LtY(*H)CIuC*#wunhlW39^k92uj|sVD@Cl1ZQR^(5n%1H#Qjmq*BOequLi+4 zARIICxCYRp&Ox0$&YuX&{3BKxs#beWjbdK=ww_j)0h+ zkPc4JbzkgsN1^hDB6cgOP^P>{)MJ#?y+NRat|ucEA{QqtPt4gBUnpd%@O5K58O44O z_c?QQWn0Cb1SS_)fD?)xM7Rgkr=6Ff~>x(7>F>bfIds>;`cB*;nmmv1-p+)A(DkZdixYx8(17@hGq~FJN_vMc<&A>;%blofnk1e?kLdvy^tdg~0&)J7_xq+v+ z&e+H)Vr*o0a$cD>L0PnmR5sOnsg5GkMvg76Y;~M<`3@NS82J42XUBX4oq~KHKia{Y zG|&aHKOAeFMa5Z#<@dq*03$W7whrfW70*OjkMom1{5w(dGA1a+tf#5ItR!$#cX#1< z2SosWF3Nc@z9}X>7?v&uJec0DPS1C{Rs#h1;1rlaWUHe zz)ZNVU8fi3J>Xz5u6^o|M7hKcLudA97wE~!5m{~Jxhb!^=B>c{z{QWLHFI`W^>|)4 zb37ZF22I0gBF@e(t25=65OHXHWCzO1RR$jbxD{V^_qaolH_UV=2tzJE@;l7JYlY$0P-e?7xlskQ3_L*8~MDBWo(A_P0?@i^k#sVs&(m3W+0 z=fd6O+gCU*hOyq;~<> zSfZBvuE9_Z?9B2>yk?$5>x4R^P$4mg!?O9fNIb^7bQ5Rk+Q4)U<{u_UM1hhYgDkJ` zTI*!Zv=-wEuZw~k4dffY!Y9v?{0!6IVWc$xvTCrXigCD@r`);o-fZF>?9EI>Nn+&x zvv4i9u$oEThiMOS^lSD#65Z$Gw0)9ah1ae70@PO+W9!(mH&}Oa+H2O`=gr)dW!6oS zmaoZN3tq8;Zf5Pyef($oRQQ?7+*IYyD=T*{&~z(GMyc44B2A4Ue`HA 1: + painter.drawLine(line) + +class SudokuGrid(QGraphicsObject): + # Prepare the signal + buttonClicked = pyqtSignal(float, float) + + def __init__(self, width, height, parent=None): + super().__init__(parent) + self.width = width + self.height = height + + self.default_pen = QPen() + self.default_pen.setColor(Qt.white) + self.default_pen.setWidth(1) + self.thick_pen = QPen() + self.thick_pen.setColor(Qt.white) + self.thick_unit = 5 + self.thick_pen.setWidth(self.thick_unit) + + self.horiz_gridlines = [] + self.vert_gridlines = [] + + self.thinlines = [] + self.thicklines = [] + + self.cell_width = self.width / 9 + self.cell_height = self.height /9 + + for i in range(1, 9): + delta_h = self.cell_height * i + delta_w = self.cell_width * i + if i%3 == 0: + self.thicklines.append(QLineF(0, delta_h, self.width, delta_h)) + self.thicklines.append(QLineF(delta_w, 0, delta_w, self.height)) + else: + self.thinlines.append(QLineF(0, delta_h, self.width, delta_h)) + + self.thinlines.append(QLineF(delta_w, 0, delta_w, self.height)) + + self.sudoku_grid = sdk.SudokuSystem() + + self.mouse_w = 0 + self.mouse_h = 0 + self.selection_unit = 8 + self.selection_pen = QPen() + self.selection_pen.setColor(Qt.white) + self.selection_pen.setWidth(self.selection_unit) + self.selection_box = QRectF(0, 0, self.cell_width, self.cell_height) + + self.setAcceptHoverEvents(True) + self.setAcceptedMouseButtons(Qt.LeftButton) + + self.selected = False + + self.invalid_pen = QPen() + self.invalid_pen.setColor(Qt.lightGray) + self.invalid_unit = 8 + self.invalid_pen.setWidth(self.thick_unit) + + def replace_cell_number(self, val): + self.sudoku_grid.replace_cell_number(self.mouse_h, self.mouse_w, val) + self.update() + + def _draw_number_cell(self, w, h, painter): + val = self.sudoku_grid.get_cell_number(h, w) + if val == 0: + val = '' + else: + if self.sudoku_grid.get_cell_status(h, w) == sdk.VALID: + painter.setPen(self.default_pen) + else: + painter.setPen(self.invalid_pen) + + painter.drawText((w+0.5)*self.cell_width-5, + (h+0.5)*self.cell_height+5, + str(val)) + + def boundingRect(self): + return QRectF(-5, -5, self.width+10, self.height+10) + + # Reimplemented paint + def paint(self, painter, style, widget=None): + painter.setPen(self.default_pen) + for line in self.thinlines: + painter.drawLine(line) + + for i in range(9): + for j in range(9): + self._draw_number_cell(i, j, painter) + + painter.setPen(self.thick_pen) + for line in self.thicklines: + painter.drawLine(line) + + painter.setPen(self.selection_pen) + painter.drawRect(self.selection_box) + + def hoverMoveEvent(self, event): + box_w = bound_value(0, int(event.pos().x()/self.cell_width), 8) + box_h = bound_value(0, int(event.pos().y() / self.cell_height), 8) + if not self.selected: + if box_w != self.mouse_w or box_h != self.mouse_h: + self.mouse_w = box_w + self.mouse_h = box_h + self.selection_box.moveTopLeft(QPointF(box_w*self.cell_width, box_h*self.cell_height)) + self.update() + + def mousePressEvent(self, event): + w = (self.mouse_w + 0.5) * self.cell_width - 5 + h = (self.mouse_h + 0.5) * self.cell_height + 5 + + if not self.sudoku_grid.get_cell_status(self.mouse_h, self.mouse_w) == sdk.FIXED: + self.buttonClicked.emit(w, h) + +class NumberGrid(QGraphicsItem): + def __init__(self, parent): + super().__init__(parent=parent) + + def paint(self, painter, style, widget=None): + pass + +class NumberRing(QGraphicsItem): + + def __init__(self): + super().__init__() + + self.setVisible(False) + self.radius = 48 + self.cell_width = 24 + self.cell_height = 24 + + self.cell_buttons = [] + for i in range(10): + cell_x = self.radius * np.sin(np.deg2rad(360/10*i)) - self.cell_width/2 + cell_y = - self.radius * np.cos(np.deg2rad(360 / 10 * i)) - self.cell_height/2 + if i == 0: + cell_string = 'X' + else: + cell_string = str(i) + btn = buttons.animBox(cell_x, cell_y, self.cell_width, + self.cell_height, cell_string, self) + + self.cell_buttons.append(btn) + + def boundingRect(self): + return QRectF(-5, -5, self.cell_width+self.radius*2+10, + self.cell_height + self.radius * 2 + 10) + + # Reimplemented paint + def paint(self, painter, style, widget=None): + pass + + def connect_button_signals(self, func): + for btn in self.cell_buttons: + btn.buttonClicked.connect(func) + + def mousePressEvent(self, event): + print('Yes') diff --git a/graphic_components/buttons.py b/graphic_components/buttons.py new file mode 100644 index 0000000..d37d15e --- /dev/null +++ b/graphic_components/buttons.py @@ -0,0 +1,142 @@ +from PyQt5.QtGui import QPainter, QBrush, QPen, QColor, QFont +from PyQt5.Qt import QApplication, QTimer +from PyQt5.QtWidgets import (QGraphicsScene, QGraphicsView, QGraphicsItem, + QGraphicsLineItem, QGraphicsRectItem, QGraphicsObject, + QGraphicsItemGroup, QGraphicsPathItem) +from PyQt5.QtCore import (QAbstractAnimation, QObject, QPointF, Qt, QRectF,QLineF, + QPropertyAnimation, pyqtProperty, pyqtSignal) +import sys, math + + +class animBox(QGraphicsObject): + # Prepare the signal + hoverEnter = pyqtSignal() + hoverExit = pyqtSignal() + buttonClicked = pyqtSignal(str) + + # Initialisation + def __init__(self, x, y, width, height, text, parent=None): + super().__init__(parent=parent) + self.x = x + self.y = y + self.width = width + self.height = height + self.text = text + self.circumference = 2*(width+height) + + # Set up pens for drawing + self.default_pen = QPen() + self.default_pen.setColor(Qt.white) + self.outline_pen = QPen() + self.outline_pen.setColor(Qt.white) + self.outline_pen.setWidth(5) + + # Whether the mouse hover over the box + self.detected = False + self.btn_rect = self.boundingRect() + # The 4 lines to construct the box + self.left = QLineF() + self.down = QLineF() + self.right = QLineF() + self.up = QLineF() + + self.line_order = [self.up, self.right, self.down, self.left] + + self.setAcceptHoverEvents(True) + #self.hoverEnter.connect(lambda: self.toggle_anim(True)) + #self.hoverExit.connect(lambda: self.toggle_anim(False)) + + # Length of the box to be drawn + self.length = 0 + # Set up the length to be animated + self.anim = QPropertyAnimation(self, b'length') + self.anim.setDuration(400) # Animation speed + self.anim.setStartValue(0) + for t in range(1, 10): + self.anim.setKeyValueAt(t / 10, self.logistic_func(t / 10)) + self.anim.setEndValue(self.circumference) + + # Toggle the animation to be play forward or backward + def toggle_anim(self, toggling): + if toggling: + self.anim.setDirection(QAbstractAnimation.Forward) + else: + self.anim.setDirection(QAbstractAnimation.Backward) + + self.anim.start() + + # The logistic function that determines the animation motion + def logistic_func(self, x): + return self.circumference / (1+math.exp(-(x-0.5)*18)) + + # Reimplemented boundingRect + def boundingRect(self): + return QRectF(self.x, self.y, self.width, self.height) + + # Reimplemented paint + def paint(self, painter, style, widget=None): + painter.setPen(self.outline_pen) + for line in self.line_order: + if line.length() > 1: + painter.drawLine(line) + painter.setPen(self.default_pen) + painter.fillRect(self.btn_rect, Qt.black) + painter.drawRect(self.btn_rect) + painter.drawText(self.boundingRect(),self.text) + + # Defining the length to be drawn as a pyqtProperty + @pyqtProperty(float) + def length(self): + return self._length + + # Determine the length of the four lines to be drawn + @length.setter + def length(self, value): + self._length = value + remaining_length = value + if remaining_length >= 2 * self.width + self.height: + length_to_draw = remaining_length - (2 * self.width + self.height) + remaining_length -= length_to_draw + else: + length_to_draw = 0 + + self.line_order[3].setLine(self.x, self.y + self.height, + self.x, self.y + self.height - length_to_draw) + if remaining_length >= self.width + self.height: + length_to_draw = remaining_length - (self.width + self.height) + remaining_length -= length_to_draw + else: + length_to_draw = 0 + self.line_order[2].setLine(self.x + self.width, self.y + self.height, + self.x + self.width - length_to_draw, + self.y + self.height) + + if remaining_length >= self.width: + length_to_draw = remaining_length - self.width + remaining_length -= length_to_draw + else: + length_to_draw = 0 + + self.line_order[1].setLine(self.x + self.width, self.y, + self.x + self.width, self.y + length_to_draw) + self.line_order[0].setLine(self.x, self.y, + self.x + remaining_length, self.y) + self.update() + + # Reimplemented hoverEvents to detect the mouse and toggle the animation + def hoverEnterEvent(self, event): + if ~self.detected: + self.hoverEnter.emit() + self.detected = True + self.toggle_anim(True) + super().hoverEnterEvent(event) + + def hoverLeaveEvent(self, event): + if self.detected: + self.hoverExit.emit() + self.detected = False + self.toggle_anim(False) + super().hoverLeaveEvent(event) + + def mousePressEvent(self, event): + self.buttonClicked.emit(self.text) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..e165b35 --- /dev/null +++ b/main.py @@ -0,0 +1,67 @@ +from PyQt5.QtGui import QPainter, QBrush, QPen, QColor, QFont +from PyQt5.Qt import QApplication, QTimer +from PyQt5.QtWidgets import (QGraphicsScene, QGraphicsView, QGraphicsItem, + QGraphicsLineItem, QGraphicsRectItem, QGraphicsObject, + QGraphicsItemGroup, QGraphicsPathItem) +from PyQt5.QtCore import (QAbstractAnimation, QObject, QPoint, QPointF, Qt, QRectF,QLineF, + QPropertyAnimation, pyqtProperty, pyqtSignal) +import sys, math + +from graphic_components import buttons, board + + +class SudokuWindow(QGraphicsView): + + def __init__(self): + super().__init__() + + # Set up the Scene to manage the GraphicItems + self.scene = QGraphicsScene(0, 0, 500, 500, self) + self.setScene(self.scene) + self.setSceneRect(self.scene.sceneRect()) + + #self.button1 = buttons.animBox(0, 0, 20, 20, 'a') + #self.scene.addItem(self.button1) + + self.gameboard = board.BoxBoard(450, 450) + self.menuboard = board.BoxBoard(400, 100) + self.gamegrid = board.SudokuGrid(450, 450) + self.numring = board.NumberRing() + self.scene.addItem(self.gameboard) + self.scene.addItem(self.gamegrid) + self.scene.addItem(self.numring) + self.setBackgroundBrush(QBrush(Qt.black)) + self.setRenderHint(QPainter.Antialiasing) + self.setGeometry(0, 0, 600, 600) + + self.gamegrid.buttonClicked.connect(self.show_number_ring) + self.numring.connect_button_signals(self.select_ring_number) + self.gameboard + + self.ensureVisible(self.scene.sceneRect(), 50, 50) + self.fitInView(self.gameboard.boundingRect(), Qt.KeepAspectRatio) + self.show() + + def show_number_ring(self, x=0, y=0): + if not self.gamegrid.selected: + self.numring.setPos(x, y) + self.numring.setVisible(True) + self.gamegrid.selected = True + else: + self.numring.setVisible(False) + self.gamegrid.selected = False + + def select_ring_number(self, val): + if val == 'X': + val = 0 + self.gamegrid.replace_cell_number(int(val)) + self.show_number_ring() + + +if __name__ == "__main__": + app = 0 + app = QApplication(sys.argv) + + ex = SudokuWindow() + + sys.exit(app.exec_()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..81bbbb5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +numpy==1.14.5 +PyQt5==5.9.2 +sip==4.19.6