1+ #!/usr/bin/env python
2+
3+ # Copyright (C) 2014 Oleh Prypin <[email protected] > 4+ #
5+ # This file is part of SixCells.
6+ #
7+ # SixCells is free software: you can redistribute it and/or modify
8+ # it under the terms of the GNU General Public License as published by
9+ # the Free Software Foundation, either version 3 of the License, or
10+ # (at your option) any later version.
11+ #
12+ # SixCells is distributed in the hope that it will be useful,
13+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ # GNU General Public License for more details.
16+ #
17+ # You should have received a copy of the GNU General Public License
18+ # along with SixCells. If not, see <http://www.gnu.org/licenses/>.
19+
20+
21+ __version__ = '0.2'
22+
23+ import sys
24+ import math
25+ import collections
26+ import json
27+
28+ sys .path .insert (0 , 'universal-qt' )
29+ import qt
30+ qt .init ()
31+ from qt import Signal
32+ from qt .core import QPointF , QRectF , QSizeF , QTimer
33+ from qt .gui import QPolygonF , QPen , QBrush , QPainter , QColor , QMouseEvent , QTransform
34+ from qt .widgets import QApplication , QGraphicsScene , QGraphicsView , QGraphicsPolygonItem , QGraphicsSimpleTextItem , QMainWindow , QMessageBox , QFileDialog , QAction , QGraphicsRectItem
35+
36+ math .tau = 2 * math .pi
37+ cos30 = math .cos (math .tau / 12 )
38+
39+
40+
41+ class Color :
42+ yellow = QColor (255 , 175 , 41 )
43+ yellow_border = QColor (255 , 159 , 0 )
44+ blue = QColor (5 , 164 , 235 )
45+ blue_border = QColor (20 , 156 , 216 )
46+ black = QColor (62 , 62 , 62 )
47+ black_border = QColor (44 , 47 , 49 )
48+ light_text = QColor (255 , 255 , 255 )
49+ dark_text = QColor (73 , 73 , 73 )
50+ border = qt .white
51+ beam = QColor (220 , 220 , 220 , 128 )
52+ revealed_border = qt .red
53+
54+
55+ def all_connected (items ):
56+ try :
57+ connected = {next (iter (items ))}
58+ except StopIteration :
59+ return True
60+ anything_to_add = True
61+ while anything_to_add :
62+ anything_to_add = False
63+ for it in items - connected :
64+ if any (x .collidesWithItem (it ) for x in connected ):
65+ anything_to_add = True
66+ connected .add (it )
67+ return not (items - connected )
68+
69+
70+ def fit_inside (parent , it , k ):
71+ sb = parent .boundingRect ()
72+ it .setBrush (Color .light_text )
73+ tb = it .boundingRect ()
74+ it .setScale (sb .height ()/ tb .height ()* k )
75+ tb = it .mapRectToParent (it .boundingRect ())
76+ it .setPos (sb .center ()- QPointF (tb .size ().width ()/ 2 , tb .size ().height ()/ 2 ))
77+
78+
79+ class Hex (QGraphicsPolygonItem ):
80+ unknown = None
81+ empty = False
82+ full = True
83+
84+ def __init__ (self ):
85+ poly = QPolygonF ()
86+ l = 0.49 / cos30
87+ inner_poly = QPolygonF ()
88+ il = 0.77 * l
89+ for i in range (6 ):
90+ a = i * math .tau / 6 - math .tau / 12
91+ poly .append (QPointF (l * math .sin (a ), - l * math .cos (a )))
92+ inner_poly .append (QPointF (il * math .sin (a ), - il * math .cos (a )))
93+
94+ QGraphicsPolygonItem .__init__ (self , poly )
95+
96+ self .inner = QGraphicsPolygonItem (inner_poly , self )
97+ self .inner .setPen (qt .NoPen )
98+
99+ self .text = QGraphicsSimpleTextItem ('' , self )
100+
101+ pen = QPen (Color .border , 0.03 )
102+ pen .setJoinStyle (qt .MiterJoin )
103+ self .setPen (pen )
104+
105+ self ._kind = Hex .unknown
106+
107+ @property
108+ def kind (self ):
109+ return self ._kind
110+ @kind .setter
111+ def kind (self , value ):
112+ self ._kind = value
113+ self .upd ()
114+
115+ def upd (self , first = True ):
116+ if self .kind == Hex .unknown :
117+ self .setBrush (Color .yellow_border )
118+ self .inner .setBrush (Color .yellow )
119+ self .text .setText ("" )
120+ elif self .kind == Hex .empty :
121+ self .setBrush (Color .black_border )
122+ self .inner .setBrush (Color .black )
123+ elif self .kind == Hex .full :
124+ self .setBrush (Color .blue_border )
125+ self .inner .setBrush (Color .blue )
126+ if self .kind is not Hex .unknown and self .value is not None :
127+ txt = str (self .value )
128+ together = self .together
129+ if together is not None :
130+ txt = ('{{{}}}' if together else '-{}-' ).format (txt )
131+ else :
132+ txt = '?' if self .kind == Hex .empty else ''
133+
134+ self .text .setText (txt )
135+ if txt :
136+ fit_inside (self , self .text , 0.5 )
137+
138+
139+ class Col (QGraphicsPolygonItem ):
140+ def __init__ (self ):
141+ poly = QPolygonF ()
142+ poly .append (QPointF (- 0.25 , 0.48 ))
143+ poly .append (QPointF (- 0.25 , 0.02 ))
144+ poly .append (QPointF (0.25 , 0.02 ))
145+ poly .append (QPointF (0.25 , 0.48 ))
146+
147+ QGraphicsPolygonItem .__init__ (self , poly )
148+
149+ self .setBrush (QColor (255 , 255 , 255 , 0 ))
150+ self .setPen (qt .NoPen )
151+
152+ self .text = QGraphicsSimpleTextItem ('v' , self )
153+ fit_inside (self , self .text , 0.9 )
154+ #self.text.setY(self.text.y()+0.2)
155+ self .text .setBrush (Color .dark_text )
156+
157+ def upd (self ):
158+ try :
159+ list (self .members )
160+ except ValueError :
161+ txt = '!?'
162+ else :
163+ txt = str (self .value )
164+ together = self .together
165+ if together is not None :
166+ txt = ('{{{}}}' if together else '-{}-' ).format (txt )
167+ self .text .setText (txt )
168+ self .text .setX (- self .text .boundingRect ().width ()* self .text .scale ()/ 2 )
169+
170+
171+ def save (fn , scene ):
172+ hexs , cols = [], []
173+ for it in scene .items ():
174+ if isinstance (it , Hex ):
175+ hexs .append (it )
176+ elif isinstance (it , Col ):
177+ cols .append (it )
178+ hexs_j , cols_j = [], []
179+
180+ for i , it in enumerate (hexs ):
181+ j = collections .OrderedDict ()
182+ j ['id' ] = i
183+ if it .kind == Hex .empty :
184+ j ['kind' ] = 0
185+ if it .text .text ()!= '?' and it .show_info :
186+ j ['members' ] = [hexs .index (n ) for n in it .neighbors ()]
187+ elif it .kind == Hex .full :
188+ j ['kind' ] = 1
189+ if it .show_info :
190+ j ['members' ] = [hexs .index (n ) for n in it .circle_neighbors ()]
191+ if it .revealed :
192+ j ['revealed' ] = True
193+ _save_common (j , it )
194+ hexs_j .append (j )
195+
196+ for it in cols :
197+ j = collections .OrderedDict ()
198+ j ['members' ] = [hexs .index (n ) for n in it .members ]
199+ _save_common (j , it )
200+ j ['angle' ] = round (it .rotation ())
201+
202+ cols_j .append (j )
203+
204+ result = collections .OrderedDict ([('cells' , hexs_j ), ('columns' , cols_j )])
205+
206+ with open (fn , 'w' ) as f :
207+ json .dump (result , f , indent = 2 )
208+
209+ def _save_common (j , it ):
210+ s = it .text .text ()
211+ if s .startswith ('{' ):
212+ j ['together' ] = True
213+ elif s .startswith ('-' ):
214+ j ['together' ] = False
215+ j ['x' ] = it .x ()
216+ j ['y' ] = it .y ()
217+ if s and s != '?' :
218+ j ['value' ] = int (s .strip ('{-}' ))
219+
220+
221+ def load (fn , scene , Hex = Hex , Col = Col ):
222+ with open (fn ) as f :
223+ try :
224+ jj = json .load (f )
225+ except Exception as e :
226+ QMessageBox .warning (None , "Error" , "Error while parsing JSON:\n {}" .format (e ))
227+ return False
228+
229+ by_id = [None ]* len (jj ['cells' ])
230+
231+ for j in jj ['cells' ]:
232+ it = Hex ()
233+ by_id [j ['id' ]] = it
234+ it .kind = [Hex .empty , Hex .full , Hex .unknown ][j ['kind' ]]
235+ it ._members = j .get ('members' ) or []
236+ it .revealed = j .get ('revealed' , False )
237+ it .together = j .get ('together' , None )
238+ it .setX (j ['x' ])
239+ it .setY (j ['y' ])
240+ it .value = j .get ('value' )
241+ for it in by_id :
242+ try :
243+ it .members = [by_id [i ] for i in it ._members ]
244+ except AttributeError :
245+ pass
246+ del it ._members
247+ scene .addItem (it )
248+
249+ for j in jj ['columns' ]:
250+ it = Col ()
251+ try :
252+ it .members = [by_id [i ] for i in j ['members' ]]
253+ except AttributeError :
254+ pass
255+ it .together = j .get ('together' , None )
256+ it .setX (j ['x' ])
257+ it .setY (j ['y' ])
258+ it .setRotation (j .get ('angle' , 0 ))
259+ try :
260+ it .value = j ['value' ]
261+ except AttributeError :
262+ pass
263+ scene .addItem (it )
264+
265+ for it in scene .items ():
266+ if isinstance (it , (Hex , Col )):
267+ it .upd ()
268+
269+
270+ def about (app ):
271+ QMessageBox .information (None , "About" , """
272+ <h1>{}</h1>
273+ <h3>Version {}</h3>
274+
275+ <p>(C) 2014 Oleh Prypin <<a href="mailto:[email protected] ">[email protected] </a>></p> 276+
277+ <p>License: <a href="http://www.gnu.org/licenses/gpl.txt">GNU General Public License Version 3</a></p>
278+
279+ Using:
280+ <ul>
281+ <li>Python {}
282+ <li>Qt {}
283+ <li>{} {}
284+ </ul>
285+ """ .format (
286+ app , __version__ ,
287+ sys .version .split (' ' , 1 )[0 ],
288+ qt .version_str ,
289+ qt .module , qt .module_version_str ,
290+ ))
0 commit comments