WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit e21f64e

Browse files
committed
v0.2
1 parent 075607f commit e21f64e

File tree

5 files changed

+670
-266
lines changed

5 files changed

+670
-266
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
*.sixcells
2+
3+
14
# Byte-compiled / optimized / DLL files
25
__pycache__/
36
*.py[cod]

README.md

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@ Level editor for [Hexcells](http://store.steampowered.com/sub/50074/).
44

55
Work in progress.
66

7-
Right now it only allows creation of levels and outputs them in JSON. These levels can't even be played.
8-
It does not actually work with *Hexcells* in any way.
7+
*SixCells Editor* allows creation of levels and outputs them in a JSON format.
8+
These levels can be played using *SixCells Player*.
9+
It does not actually interact with *Hexcells* in any way.
910

1011
![Logo](https://raw.githubusercontent.com/BlaXpirit/sixcells/master/logo.png)
1112

1213
## How to Use
1314

15+
### Editor
16+
1417
Left click to add a cell.
1518
Left click a cell to toggle blue/black.
1619
Double click a cell to switch between 3 information display modes.
@@ -25,11 +28,18 @@ Press and drag mouse wheel to navigate.
2528
Scroll to zoom.
2629

2730

31+
### Player
32+
33+
*Open* a level created in the *Editor* and play it.
34+
35+
Some basic auto-solving capabilities are present (press *Solve* to attempt one action).
36+
37+
2838
## Level File Structure
2939

3040
```python
3141
{
32-
"hexs": [ # Hexagonal cells
42+
"cells": [ # Hexagonal cells
3343
{
3444
"id": integer,
3545
# Number that can be used to refer to this cell
@@ -59,7 +69,7 @@ Scroll to zoom.
5969
},
6070
...
6171
],
62-
"cols": [ # Column numbers
72+
"columns": [ # Column numbers
6373
{
6474
"members": [integers],
6575
# List of IDs of hexes that are in this column
@@ -72,6 +82,9 @@ Scroll to zoom.
7282
"y": number,
7383
# Absolute coordinates of the center of the hexagon that contains this number
7484

85+
"angle": number,
86+
# Angle of rotation in degrees (only -60, 0 and 60 are possible)
87+
7588
"value": integer
7689
# The number written on the column
7790
# This is redundant; it may be deduced from "members"

common.py

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
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 &lt;<a href="mailto:[email protected]">[email protected]</a>&gt;</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

Comments
 (0)