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 ADDED
@@ -0,0 +1,3 @@
1
+ """Pynagram - Unjumble the letters and get addicted!"""
2
+
3
+ __version__ = "3.1.0"
pynagram/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from pynagram.main import main
4
+
5
+ sys.exit(main())
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"]]
@@ -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,10 @@
1
+ [Desktop Entry]
2
+ Type=Application
3
+ Name=Pynagram
4
+ GenericName=Anagram Game
5
+ Comment=Unjumble the letters and get addicted!
6
+ Exec=pynagram
7
+ Icon=pynagram
8
+ Terminal=false
9
+ Categories=Game;LogicGame;
10
+ Keywords=anagram;word;puzzle;letters;
@@ -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
+