--- Begin Message ---
Package: xword
Version: 1.0-3
Severity: wishlist
Tags: patch
xword assumes that crosswords it reads are American-style, i.e. that any
square that has a black square above it or to its left is at the beginning of
a new solution, which isn't true of British-style crosswords (see
http://en.wikipedia.org/wiki/Crossword#Types_of_grid). Hence it gets very
confused when loading a British-style crossword.
Here is a series of patches that add support for British-style layouts, plus
one fix for keypress handling and and one patch that adds a test suite. These
are taken from my git repository at (http://rhydd.org/git/xword/).
>From fdfb5a210da7f4596323330e72b1fc190611f40e Mon Sep 17 00:00:00 2001
From: Dafydd Harries <d...@rhydd.org>
Date: Sun, 3 May 2009 16:39:40 +0300
Subject: [PATCH 1/6] handle British-style crossword layouts
When allocating numbers to cells, allocate a new number to a cell if the cell:
- is white;
- has not already been allocated a number;
- and it it followed by white cell either to the right or downwards.
When allocating a number, allocate the same number to consecutive white cells
to the right or downwards. Previously, a new number was always allocated if
the cell above or to the left was black.
---
xword | 61 ++++++++++++++++++++++++++++++++++++-------------------------
1 files changed, 36 insertions(+), 25 deletions(-)
diff --git a/xword b/xword
index 1fde0fa..8da2492 100755
--- a/xword
+++ b/xword
@@ -263,35 +263,39 @@ class Puzzle:
number = 1
for y in range(self.height):
for x in range(self.width):
- is_fresh_x = self.is_black(x-1, y)
- is_fresh_y = self.is_black(x, y-1)
+ new_across = False
+ new_down = False
if not self.is_black(x, y):
- if is_fresh_x:
- self.across_map[x, y] = number
- if self.is_black(x+1, y):
- self.across_clues[number] = ''
- else:
- self.across_clues[number] = self.clues.pop(0)
- else: self.across_map[x, y] = self.across_map[x-1, y]
+ if ((x, y) not in self.across_map and
+ not self.is_black(x+1, y)):
+ new_across = True
+ self.across_clues[number] = self.clues.pop(0)
+
+ for x_ in range(x, self.width):
+ if self.is_black(x_, y):
+ break
+
+ self.across_map[x_, y] = number
- if is_fresh_y:
- self.down_map[x, y] = number
- if self.is_black(x, y+1): # see April 30, 2006 puzzle
- self.down_clues[number] = ''
- else:
- self.down_clues[number] = self.clues.pop(0)
- else: self.down_map[x, y] = self.down_map[x, y-1]
-
- if is_fresh_x or is_fresh_y:
- self.is_across[number] = is_fresh_x
- self.is_down[number] = is_fresh_y
+ if ((x, y) not in self.down_map and
+ # see April 30, 2006 puzzle
+ not self.is_black(x, y+1)):
+ new_down = True
+ self.down_clues[number] = self.clues.pop(0)
+
+ for y_ in range(y, self.height):
+ if self.is_black(x, y_):
+ break
+
+ self.down_map[x, y_] = number
+
+ if new_across or new_down:
+ self.is_across[number] = new_across
+ self.is_down[number] = new_down
self.number_map[number] = (x, y)
self.number_rev_map[x, y] = number
number += 1
- else:
- self.across_map[x, y] = 0
- self.down_map[x, y] = 0
self.max_number = number-1
def hashcode(self):
@@ -996,8 +1000,15 @@ class PuzzleController:
self.do_update('box-update', xp, yp)
self.do_update('title-update')
- self.do_update('across-update', self.puzzle.number(x, y, ACROSS))
- self.do_update('down-update', self.puzzle.number(x, y, DOWN))
+
+ if (x, y) not in self.puzzle.mode_maps[self.mode]:
+ self.switch_mode()
+
+ if (x, y) in self.puzzle.across_map:
+ self.do_update('across-update', self.puzzle.number(x, y,
ACROSS))
+
+ if (x, y) in self.puzzle.down_map:
+ self.do_update('down-update', self.puzzle.number(x, y, DOWN))
def select_word(self, mode, n):
if mode <> self.mode: self.switch_mode()
--
1.6.2.4
>From 5a2ae0ef3d6c64384863a37df5b371df49c3884f Mon Sep 17 00:00:00 2001
From: Dafydd Harries <d...@rhydd.org>
Date: Sun, 3 May 2009 16:45:38 +0300
Subject: [PATCH 2/6] when switching modes, jump to the first clue of the other
mode, not clue 1
In US-style crosswords, both across and down modes have a clue 1, but this
assumption breaks in British-style crosswords.
---
xword | 10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)
diff --git a/xword b/xword
index 8da2492..da69d8c 100755
--- a/xword
+++ b/xword
@@ -382,6 +382,14 @@ class Puzzle:
if mode == DOWN and self.is_down[n]: break
return n
+ def first_number(self, mode):
+ n = 1
+ while True:
+ if mode == ACROSS and self.is_across[n]: break
+ if mode == DOWN and self.is_down[n]: break
+ n += 1
+ return n
+
def final_number(self, mode):
n = self.max_number
while True:
@@ -1048,7 +1056,7 @@ class PuzzleController:
n = self.puzzle.incr_number(self.x, self.y, self.mode, incr)
if n == 0:
self.switch_mode()
- if incr == 1: n = 1
+ if incr == 1: n = self.puzzle.first_number(self.mode)
else: n = self.puzzle.final_number(self.mode)
(x, y) = self.puzzle.number_map[n]
(x, y) = self.puzzle.find_blank_cell(x, y, self.mode, 1)
--
1.6.2.4
>From 7d9e8dd3b6fef2d2ffbfebc1c512d39639df7bd1 Mon Sep 17 00:00:00 2001
From: Dafydd Harries <d...@rhydd.org>
Date: Sun, 3 May 2009 16:44:15 +0300
Subject: [PATCH 3/6] when opening a puzzle, move to the first across clue
instead of the first white cell
In US-style crosswords, the first white cell is always in an across clue, but
this assumption breaks in British-style crosswords.
---
xword | 4 +---
1 files changed, 1 insertions(+), 3 deletions(-)
diff --git a/xword b/xword
index da69d8c..2bcd2b5 100755
--- a/xword
+++ b/xword
@@ -946,9 +946,7 @@ class PuzzleController:
self.selection = []
self.mode = ACROSS
- (x, y) = (0, 0)
- if puzzle.is_black(x, y):
- ((x, y), _) = puzzle.next_cell(0, 0, ACROSS, 1, True)
+ x, y = self.puzzle.number_map[self.puzzle.first_number(ACROSS)]
self.move_to(x, y)
def connect(self, ev, handler):
--
1.6.2.4
>From d8b3cf71ceea05b765f4379867d45a295b6beb7a Mon Sep 17 00:00:00 2001
From: Dafydd Harries <d...@rhydd.org>
Date: Sun, 3 May 2009 17:24:53 +0300
Subject: [PATCH 4/6] don't switch mode when moving unless the current cell
exists in the other mode
In British-style puzzles, some cells only exist in one of the two modes.
---
xword | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/xword b/xword
index 2bcd2b5..996870d 100755
--- a/xword
+++ b/xword
@@ -1040,7 +1040,7 @@ class PuzzleController:
((x, y), _) = self.puzzle.next_cell(self.x, self.y,
self.mode, amt, skip_black)
self.move_to(x, y)
- else:
+ elif (self.x, self.y) in self.puzzle.mode_maps[1 - self.mode]:
self.switch_mode()
def back_space(self):
--
1.6.2.4
>From a886fea203e96e6fd0ec1c15170867e4943215c6 Mon Sep 17 00:00:00 2001
From: Dafydd Harries <d...@rhydd.org>
Date: Sun, 3 May 2009 17:24:25 +0300
Subject: [PATCH 5/6] ignore keypresses that can't be named
---
xword | 2 ++
1 files changed, 2 insertions(+), 0 deletions(-)
diff --git a/xword b/xword
index 996870d..a0775c6 100755
--- a/xword
+++ b/xword
@@ -1700,6 +1700,8 @@ class PuzzleWindow:
def puzzle_key_event(self, item, event):
name = gtk.gdk.keyval_name(event.keyval)
+ if name is None:
+ return False
c = self.control
if len(name) is 1 and name.isalpha():
c.input_char(self.skip_filled, name)
--
1.6.2.4
>From 068c8f2d17e26a57a8ac35e3679a12aea6fb2e41 Mon Sep 17 00:00:00 2001
From: Dafydd Harries <d...@rhydd.org>
Date: Sun, 3 May 2009 19:46:18 +0300
Subject: [PATCH 6/6] add a rudimentary test suite
---
test.py | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 128 insertions(+), 0 deletions(-)
create mode 100644 test.py
diff --git a/test.py b/test.py
new file mode 100644
index 0000000..6d558c8
--- /dev/null
+++ b/test.py
@@ -0,0 +1,128 @@
+
+import unittest
+
+def read_file(path):
+ fh = file(path)
+ contents = fh.read()
+ fh.close()
+ return contents
+
+def load_module(name, path):
+ # hacketty hack
+
+ mod = __builtins__.__class__(name)
+ code = compile(read_file(path), path, 'exec')
+
+ g, l = {}, {}
+ exec code in g, l
+
+ for k, v in l.iteritems():
+ setattr(mod, k, v)
+
+ return mod
+
+xword = load_module('xword', 'xword')
+
+class TestPuzzle(xword.Puzzle):
+ def __init__(self):
+ # Override Puzzle.__init__ to avoid having to read a file.
+ pass
+
+def dump_map(width, height, m):
+ return ''.join([
+ ' '.join([
+ '%s' % m.get((x, y), '#')
+ for x in xrange(width)]) + '\n'
+ for y in xrange(height)])
+
+class SetupTest(unittest.TestCase):
+ def test_american(self):
+ puzzle = TestPuzzle()
+ puzzle.width = 5
+ puzzle.height = 5
+ puzzle.clues = [
+ 'a1', 'd1', 'd2', 'd3', 'a4', 'd5', 'a6', 'd7', 'a8', 'a9']
+ puzzle.responses = dict([
+ ((x, y), ' .'[int(x + y == 4 and x != 2 and y != 2)])
+ for x in xrange(5)
+ for y in xrange(5)])
+
+ puzzle.setup()
+
+ self.assertEquals([], puzzle.clues)
+
+ self.assertEquals(
+ {1: True, 2: False, 3: False, 4: True, 5: False, 6: True, 7: False,
+ 8: True, 9: True},
+ puzzle.is_across)
+ self.assertEquals(
+ {8: 'a8', 1: 'a1', 4: 'a4', 6: 'a6', 9: 'a9'},
+ puzzle.across_clues)
+ self.assertEquals(
+ '1 1 1 1 #\n'
+ '4 4 4 # #\n'
+ '6 6 6 6 6\n'
+ '# # 8 8 8\n'
+ '# 9 9 9 9\n',
+ dump_map(puzzle.width, puzzle.height, puzzle.across_map))
+
+ self.assertEquals(
+ {1: True, 2: True, 3: True, 4: False, 5: True, 6: False, 7: True,
+ 8: False, 9: False},
+ puzzle.is_down)
+ self.assertEquals(
+ {1: 'd1', 2: 'd2', 3: 'd3', 5: 'd5', 7: 'd7'},
+ puzzle.down_clues)
+ self.assertEquals(
+ '1 2 3 # #\n'
+ '1 2 3 # 5\n'
+ '1 2 3 7 5\n'
+ '1 # 3 7 5\n'
+ '# # 3 7 5\n',
+ dump_map(puzzle.width, puzzle.height, puzzle.down_map))
+
+ def test_british(self):
+ puzzle = TestPuzzle()
+ puzzle.width = 5
+ puzzle.height = 5
+ puzzle.clues = ['a1', 'd1', 'd2', 'd3', 'a4', 'a5']
+ puzzle.responses = dict([
+ ((x, y), ' .'[int(x % 2 != 0 and y % 2 != 0)])
+ for x in xrange(5)
+ for y in xrange(5)])
+
+ puzzle.setup()
+
+ self.assertEquals([], puzzle.clues)
+
+ self.assertEquals(
+ {1: True, 2: False, 3: False, 4: True, 5: True},
+ puzzle.is_across)
+ self.assertEquals(
+ {1: 'a1', 4: 'a4', 5: 'a5'},
+ puzzle.across_clues)
+ self.assertEquals(
+ '1 1 1 1 1\n'
+ '# # # # #\n'
+ '4 4 4 4 4\n'
+ '# # # # #\n'
+ '5 5 5 5 5\n',
+ dump_map(puzzle.width, puzzle.height, puzzle.across_map))
+
+ self.assertEquals(
+ {1: True, 2: True, 3: True, 4: False, 5: False},
+ puzzle.is_down)
+ self.assertEquals(
+ {1: 'd1', 2: 'd2', 3: 'd3'},
+ puzzle.down_clues)
+ self.assertEquals(
+ '1 # 2 # 3\n'
+ '1 # 2 # 3\n'
+ '1 # 2 # 3\n'
+ '1 # 2 # 3\n'
+ '1 # 2 # 3\n',
+ dump_map(puzzle.width, puzzle.height, puzzle.down_map))
+
+if __name__ == '__main__':
+ unittest.main()
+
--
1.6.2.4
--- End Message ---