pynagram3 3.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pynagram/__init__.py +3 -0
- pynagram/__main__.py +5 -0
- pynagram/backend/__init__.py +0 -0
- pynagram/backend/anagram.py +118 -0
- pynagram/backend/config.py +148 -0
- pynagram/data/icons/hicolor/128x128/apps/pynagram.png +0 -0
- pynagram/data/icons/hicolor/48x48/apps/pynagram.png +0 -0
- pynagram/data/icons/pixmaps/pynagram.xpm +37 -0
- pynagram/data/pynagram.desktop +10 -0
- pynagram/data/wordlist/README +17 -0
- pynagram/data/wordlist/wordlist-README +512 -0
- pynagram/data/wordlist/wordlist.txt +25805 -0
- pynagram/gui/__init__.py +0 -0
- pynagram/gui/about.ui +82 -0
- pynagram/gui/gui.py +327 -0
- pynagram/gui/mainwindow.ui +191 -0
- pynagram/gui/qt_about.py +55 -0
- pynagram/gui/qt_struct.py +118 -0
- pynagram/gui/qt_wl.py +68 -0
- pynagram/gui/wordlists.ui +109 -0
- pynagram/main.py +130 -0
- pynagram3-3.1.0.dist-info/METADATA +162 -0
- pynagram3-3.1.0.dist-info/RECORD +27 -0
- pynagram3-3.1.0.dist-info/WHEEL +5 -0
- pynagram3-3.1.0.dist-info/entry_points.txt +2 -0
- pynagram3-3.1.0.dist-info/licenses/COPYING +674 -0
- pynagram3-3.1.0.dist-info/top_level.txt +1 -0
pynagram/__init__.py
ADDED
pynagram/__main__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#
|
|
2
|
+
# anagram.py
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2009, 2010, 2011 Umang Varma <umang.me@gmail.com>.
|
|
5
|
+
#
|
|
6
|
+
# This file is part of Pynagram
|
|
7
|
+
#
|
|
8
|
+
# Pynagram is free software: you can redistribute it and/or modify
|
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
|
10
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# Pynagram is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with Pynagram. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
#
|
|
21
|
+
|
|
22
|
+
import random
|
|
23
|
+
from collections import Counter
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AnagramException(Exception): pass
|
|
27
|
+
class LengthException(AnagramException): pass
|
|
28
|
+
class WordException(AnagramException): pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Anagram:
|
|
32
|
+
"""The backend of pynagram that manages the current game.
|
|
33
|
+
|
|
34
|
+
This class performs all the back-end tasks such as generating anagrams,
|
|
35
|
+
checking if the input word exists, etc."""
|
|
36
|
+
|
|
37
|
+
LENGTH = 7
|
|
38
|
+
MIN_LENGTH = 3
|
|
39
|
+
|
|
40
|
+
def __init__(self):
|
|
41
|
+
self.letters = []
|
|
42
|
+
self.score = 0
|
|
43
|
+
self.words = {}
|
|
44
|
+
self.wordlist = []
|
|
45
|
+
self.scoring = {3: 3, 4: 5, 5: 8, 6: 14, 7: 20}
|
|
46
|
+
self.solved_all = False
|
|
47
|
+
self.qualified = False
|
|
48
|
+
|
|
49
|
+
def read_from_file(self, wordlist):
|
|
50
|
+
"""Reads a word list file (one word per line).
|
|
51
|
+
|
|
52
|
+
Raises OSError if the file cannot be read and WordException if it does
|
|
53
|
+
not contain any word long enough to build a game from."""
|
|
54
|
+
with open(wordlist, encoding="utf-8", errors="replace") as fsock:
|
|
55
|
+
words = self.filter_wordlist(fsock)
|
|
56
|
+
if not any(len(w) == self.LENGTH for w in words):
|
|
57
|
+
raise WordException(
|
|
58
|
+
"The word list %s does not contain any %d letter words."
|
|
59
|
+
% (wordlist, self.LENGTH))
|
|
60
|
+
self.wordlist = words
|
|
61
|
+
|
|
62
|
+
def filter_wordlist(self, lines):
|
|
63
|
+
"""Normalises raw lines into a sorted list of unique, playable words."""
|
|
64
|
+
words = {line.strip().lower() for line in lines}
|
|
65
|
+
return sorted(w for w in words
|
|
66
|
+
if w.isalpha() and self.MIN_LENGTH <= len(w) <= self.LENGTH)
|
|
67
|
+
|
|
68
|
+
def find_letters(self, length=LENGTH):
|
|
69
|
+
"""Picks the letters of a random word of the given length."""
|
|
70
|
+
candidates = [w for w in self.wordlist if len(w) == length]
|
|
71
|
+
if not candidates:
|
|
72
|
+
raise WordException("There are no %d letter words in the word list"
|
|
73
|
+
% length)
|
|
74
|
+
return list(random.choice(candidates))
|
|
75
|
+
|
|
76
|
+
def start_new(self, letters=None, length=LENGTH):
|
|
77
|
+
"""Starts a new game of Pynagram!"""
|
|
78
|
+
self.qualified = False
|
|
79
|
+
self.solved_all = False
|
|
80
|
+
if letters is not None and len(letters) == length:
|
|
81
|
+
letters = [x.lower() for x in letters]
|
|
82
|
+
elif letters is not None:
|
|
83
|
+
raise LengthException("Bad number of letters given.")
|
|
84
|
+
else:
|
|
85
|
+
letters = self.find_letters(length)
|
|
86
|
+
available = Counter(letters)
|
|
87
|
+
words = [w for w in self.wordlist if not Counter(w) - available]
|
|
88
|
+
if not any(len(w) == length for w in words):
|
|
89
|
+
raise WordException("There are no %d letter words that match "
|
|
90
|
+
"this anagram" % length)
|
|
91
|
+
self.letters = letters
|
|
92
|
+
self.words = {w: False for w in words}
|
|
93
|
+
|
|
94
|
+
def guess(self, word):
|
|
95
|
+
"""Attempts to solve (guess) a word.
|
|
96
|
+
|
|
97
|
+
Returns a tuple (valid, already_guessed)."""
|
|
98
|
+
word = word.lower()
|
|
99
|
+
if word not in self.words:
|
|
100
|
+
return (False, False)
|
|
101
|
+
typed = self.words[word]
|
|
102
|
+
self.words[word] = True
|
|
103
|
+
if not typed:
|
|
104
|
+
self.score += self.scoring[len(word)]
|
|
105
|
+
if not self.qualified and len(word) == self.LENGTH:
|
|
106
|
+
self.score += 30
|
|
107
|
+
self.qualified = True
|
|
108
|
+
if all(self.words.values()) and not self.solved_all:
|
|
109
|
+
self.solved_all = True
|
|
110
|
+
self.score += 30
|
|
111
|
+
return (True, typed)
|
|
112
|
+
|
|
113
|
+
def clear_all(self):
|
|
114
|
+
"""Clears all data being used for a new game."""
|
|
115
|
+
self.score = 0
|
|
116
|
+
self.letters = []
|
|
117
|
+
self.words = {}
|
|
118
|
+
self.qualified = False
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#
|
|
2
|
+
# config.py
|
|
3
|
+
#
|
|
4
|
+
# Copyright (c) 2010 Umang Varma <umang.me@gmail.com>.
|
|
5
|
+
#
|
|
6
|
+
# This file is part of Pynagram
|
|
7
|
+
#
|
|
8
|
+
# Pynagram is free software: you can redistribute it and/or modify
|
|
9
|
+
# it under the terms of the GNU General Public License as published by
|
|
10
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# Pynagram is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with Pynagram. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
#
|
|
21
|
+
"""Configuration handling.
|
|
22
|
+
|
|
23
|
+
The configuration is a small JSON file stored in
|
|
24
|
+
``$XDG_CONFIG_HOME/pynagram/config.json`` (normally ``~/.config/pynagram``).
|
|
25
|
+
It contains the list of word lists and the index of the selected one.
|
|
26
|
+
|
|
27
|
+
The word list bundled with the package is stored as the special value
|
|
28
|
+
``BUILTIN`` rather than as a file path, so that the configuration keeps working
|
|
29
|
+
when the package is upgraded, reinstalled or moved to another virtualenv.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import json
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
DATA_DIR = Path(__file__).resolve().parent.parent / "data"
|
|
38
|
+
BUILTIN = "builtin:english"
|
|
39
|
+
BUILTIN_PATH = DATA_DIR / "wordlist" / "wordlist.txt"
|
|
40
|
+
BUILTIN_LABEL = "Built-in English word list"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def config_dir():
|
|
44
|
+
"""Returns the directory holding the configuration file."""
|
|
45
|
+
if sys.platform == "win32":
|
|
46
|
+
base = Path(os.environ.get("APPDATA", Path.home()))
|
|
47
|
+
return base / "Pynagram"
|
|
48
|
+
base = os.environ.get("XDG_CONFIG_HOME") or Path.home() / ".config"
|
|
49
|
+
return Path(base) / "pynagram"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def legacy_config_file():
|
|
53
|
+
"""Config file used by Pynagram <= 3.0 (~/.pynagram/config.txt)."""
|
|
54
|
+
return Path.home() / ".pynagram" / "config.txt"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def resolve_wordlist(entry):
|
|
58
|
+
"""Turns a config entry into a file path."""
|
|
59
|
+
return BUILTIN_PATH if entry == BUILTIN else Path(entry)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def describe_wordlist(entry):
|
|
63
|
+
"""Human readable name of a config entry."""
|
|
64
|
+
return BUILTIN_LABEL if entry == BUILTIN else entry
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _is_old_bundled_list(path):
|
|
68
|
+
"""True for paths where older versions installed the default word list."""
|
|
69
|
+
p = Path(path)
|
|
70
|
+
return p.name == "wordlist.txt" and p.parent.name in ("pynagram", "wordlist")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class pynagram_config(dict):
|
|
74
|
+
"""Handles all configuration actions: read, write, defaults, etc"""
|
|
75
|
+
|
|
76
|
+
def __init__(self):
|
|
77
|
+
dict.__init__(self)
|
|
78
|
+
self.changed = False
|
|
79
|
+
|
|
80
|
+
def pathtoconfig(self):
|
|
81
|
+
"""Returns path to the configuration directory"""
|
|
82
|
+
return str(config_dir())
|
|
83
|
+
|
|
84
|
+
def pathtoconfigfile(self):
|
|
85
|
+
"""Returns path to the configuration file"""
|
|
86
|
+
return str(config_dir() / "config.json")
|
|
87
|
+
|
|
88
|
+
def _load(self):
|
|
89
|
+
path = Path(self.pathtoconfigfile())
|
|
90
|
+
if path.exists():
|
|
91
|
+
with open(path, encoding="utf-8") as fsock:
|
|
92
|
+
return json.load(fsock)
|
|
93
|
+
legacy = legacy_config_file()
|
|
94
|
+
if legacy.exists():
|
|
95
|
+
with open(legacy, encoding="utf-8") as fsock:
|
|
96
|
+
data = json.load(fsock)
|
|
97
|
+
# Drop the old, absolute path to the bundled list; the built-in
|
|
98
|
+
# entry is re-added below.
|
|
99
|
+
data["wordlists"] = [w for w in data.get("wordlists", [])
|
|
100
|
+
if not _is_old_bundled_list(w)]
|
|
101
|
+
data["wordlist"] = 0
|
|
102
|
+
return data
|
|
103
|
+
return {}
|
|
104
|
+
|
|
105
|
+
def readconfig(self):
|
|
106
|
+
"""Reads the configuration file, falling back to defaults."""
|
|
107
|
+
try:
|
|
108
|
+
data = self._load()
|
|
109
|
+
if not isinstance(data, dict):
|
|
110
|
+
raise ValueError("config is not a JSON object")
|
|
111
|
+
except (OSError, ValueError) as err:
|
|
112
|
+
print("pynagram: ignoring unreadable config (%s)" % err,
|
|
113
|
+
file=sys.stderr)
|
|
114
|
+
data = {}
|
|
115
|
+
self.clear()
|
|
116
|
+
self.update(data)
|
|
117
|
+
wordlists = [str(w) for w in self.get("wordlists", [])]
|
|
118
|
+
if BUILTIN in wordlists:
|
|
119
|
+
wordlists.remove(BUILTIN)
|
|
120
|
+
self["wordlists"] = [BUILTIN] + wordlists
|
|
121
|
+
if not isinstance(self.get("wordlist"), int) or \
|
|
122
|
+
not 0 <= self["wordlist"] < len(self["wordlists"]):
|
|
123
|
+
self["wordlist"] = 0
|
|
124
|
+
if not Path(self.pathtoconfigfile()).exists():
|
|
125
|
+
self.writeconfig()
|
|
126
|
+
|
|
127
|
+
def writeconfig(self):
|
|
128
|
+
"""Writes the configuration file."""
|
|
129
|
+
self.createdir()
|
|
130
|
+
self["wordlists"] = [w if w == BUILTIN else os.path.normpath(w)
|
|
131
|
+
for w in self["wordlists"]]
|
|
132
|
+
with open(self.pathtoconfigfile(), "w", encoding="utf-8") as fsock:
|
|
133
|
+
json.dump(self, fsock, indent=4)
|
|
134
|
+
|
|
135
|
+
def writedefault(self):
|
|
136
|
+
"""Writes default config file"""
|
|
137
|
+
self.clear()
|
|
138
|
+
self["wordlists"] = [BUILTIN]
|
|
139
|
+
self["wordlist"] = 0
|
|
140
|
+
self.writeconfig()
|
|
141
|
+
|
|
142
|
+
def createdir(self):
|
|
143
|
+
"""Creates the config directory if it doesn't already exist"""
|
|
144
|
+
os.makedirs(self.pathtoconfig(), exist_ok=True)
|
|
145
|
+
|
|
146
|
+
def selected_wordlist(self):
|
|
147
|
+
"""Config entry of the currently selected word list."""
|
|
148
|
+
return self["wordlists"][self["wordlist"]]
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* XPM */
|
|
2
|
+
static char * pynagram_xpm[] = {
|
|
3
|
+
"32 32 2 1",
|
|
4
|
+
" c None",
|
|
5
|
+
". c #000000",
|
|
6
|
+
" ",
|
|
7
|
+
" ",
|
|
8
|
+
" ",
|
|
9
|
+
" ",
|
|
10
|
+
" ..................... ",
|
|
11
|
+
" ...................... ",
|
|
12
|
+
" ...................... ",
|
|
13
|
+
" ... ... ... ",
|
|
14
|
+
" ... .... ... ",
|
|
15
|
+
" .... .... ",
|
|
16
|
+
" .... .... ",
|
|
17
|
+
" .... .... ",
|
|
18
|
+
" ... ... ",
|
|
19
|
+
" ... ... ",
|
|
20
|
+
" .... .... ",
|
|
21
|
+
" .... .... ",
|
|
22
|
+
" ... ... ",
|
|
23
|
+
" ... ... ",
|
|
24
|
+
" .... .... ",
|
|
25
|
+
" .... .... ",
|
|
26
|
+
" .... .... ",
|
|
27
|
+
" .... .... ",
|
|
28
|
+
" ... ..... . ",
|
|
29
|
+
" .... ....... ",
|
|
30
|
+
" .... ..... ",
|
|
31
|
+
" ",
|
|
32
|
+
" ",
|
|
33
|
+
" ",
|
|
34
|
+
" ",
|
|
35
|
+
" ",
|
|
36
|
+
" ",
|
|
37
|
+
" "};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
This word list has been compiled using the SCOWL word list. The word list
|
|
2
|
+
contains words from the english-words category upto level 55. The following
|
|
3
|
+
copyright notice applies to the word list, as mentioned in the original
|
|
4
|
+
package's README file (a copy of which is provided in this folder as
|
|
5
|
+
wordlist-README):
|
|
6
|
+
|
|
7
|
+
Copyright 2000-2004 by Kevin Atkinson
|
|
8
|
+
|
|
9
|
+
Permission to use, copy, modify, distribute and sell these word
|
|
10
|
+
lists, the associated scripts, the output created from the scripts,
|
|
11
|
+
and its documentation for any purpose is hereby granted without fee,
|
|
12
|
+
provided that the above copyright notice appears in all copies and
|
|
13
|
+
that both that copyright notice and this permission notice appear in
|
|
14
|
+
supporting documentation. Kevin Atkinson makes no representations
|
|
15
|
+
about the suitability of this array for any purpose. It is provided
|
|
16
|
+
"as is" without express or implied warranty.
|
|
17
|
+
|