1+ #!/usr/bin/env python
2+
3+ from functools import reduce
4+ import argparse
5+ from zipfile import ZipFile
6+ import sys
7+ import os
8+ import re
9+ from picross .grid import Grid
10+ from picross .solver import show , solve_puzzle , derive_constraints , read_image_from_file
11+
12+
13+ def _parse_constraints (string ):
14+ return [ [ int (n ) for n in part .split (',' ) ] if part else [] for part in string .split (';' ) ]
15+
16+
17+ def _solve (args ):
18+ row_constraints = _parse_constraints (args .row )
19+ column_constraints = _parse_constraints (args .column )
20+ print (show (solve_puzzle (column_constraints , row_constraints )))
21+
22+
23+ def _derive_constraints (args ):
24+ filename = args .filename
25+ grid = read_image_from_file (filename )
26+ column_constraints , row_constraints = derive_constraints (grid )
27+ print (';' .join ( ',' .join ( str (n ) for n in ns ) for ns in column_constraints ))
28+ print (';' .join ( ',' .join ( str (n ) for n in ns ) for ns in row_constraints ))
29+
30+
31+ def _show (args ):
32+ with ZipFile (args .filename , 'r' ) as zip :
33+ names = zip .namelist ()
34+ players = [ name for name in names if name .startswith ('players/' )]
35+ puzzles = [ name for name in names if name .startswith ('library/' )]
36+
37+ for player in players :
38+ player_name = player .split ('/' )[1 ].split ('.' )[0 ]
39+ print (f'Player { player_name } ' )
40+
41+ for puzzle in puzzles :
42+ lines = zip .read (puzzle ).decode ('utf-8' ).strip ().split ("\n " )
43+ author = lines [0 ]
44+ width , height = map (int , lines [1 ].split (' ' ))
45+ solution = "\n " .join (lines [2 :])
46+ print (f'Puzzle ({ width } x{ height } ) by { author } ' )
47+ if args .show_solution :
48+ print (solution )
49+ print ()
50+
51+
52+ def _create (args ):
53+ path = args .filename
54+ overwrite = args .force
55+
56+ if os .path .exists (path ):
57+ if overwrite :
58+ os .remove (path )
59+ else :
60+ print (f'Error: { path } already exists; use -f to force' )
61+ sys .exit (- 1 )
62+
63+ with ZipFile (path , 'w' ) as zip :
64+ pass
65+
66+
67+ def _add_player (args ):
68+ print (f'Adding player { args .name } ' )
69+
70+
71+ def _extract_puzzle_index (filename ):
72+ match = re .search (r'entry(\d+)\.txt' , filename )
73+ return int (match .group (1 ))
74+
75+
76+ def _add_puzzle (args ):
77+ archive = args .archive
78+ author = args .author
79+ row_constraints = _parse_constraints (vars (args )['row-constraints' ])
80+ column_constraints = _parse_constraints (vars (args )['column-constraints' ])
81+ solution = solve_puzzle (row_constraints = row_constraints , column_constraints = column_constraints )
82+ width = solution .width
83+ height = solution .height
84+
85+ with ZipFile (archive , 'r' ) as zip :
86+ names = zip .namelist ()
87+
88+ next_index = max ([- 1 ] + [ _extract_puzzle_index (name ) for name in names if name .startswith ('library/' ) ]) + 1
89+ filename = f'library/entry{ str (next_index ).rjust (5 , "0" )} .txt'
90+ data = f'{ author } \n { width } { height } \n { show (solution )} '
91+
92+ with ZipFile (archive , 'w' ) as zip :
93+ zip .writestr (filename , data )
94+
95+
96+
97+ def _process_command_line_arguments ():
98+ def create_archive_parsers (subparsers ):
99+ subparser = subparsers .add_parser ('create' , help = 'create empty archive' )
100+ subparser .add_argument ('filename' , help = 'archive' , action = 'store' )
101+ subparser .add_argument ('-f' , '--force' , help = 'overwrite existing archive' , action = 'store_true' )
102+ subparser .set_defaults (func = _create )
103+
104+ subparser = subparsers .add_parser ('show' , help = 'show contents of archive' )
105+ subparser .add_argument ('filename' , help = 'archive' , action = 'store' )
106+ subparser .add_argument ('--show-solution' , help = 'show solution' , action = 'store_true' )
107+ subparser .set_defaults (func = _show )
108+
109+ subparser = subparsers .add_parser ('add-puzzle' , help = 'adds puzzle to archive' )
110+ subparser .add_argument ('archive' , help = 'archive' , action = 'store' )
111+ subparser .add_argument ('author' , help = 'author' , action = 'store' )
112+ subparser .add_argument ('row-constraints' , help = 'row constraints (use ; to separate rows and , to separate values)' , action = 'store' )
113+ subparser .add_argument ('column-constraints' , help = 'column constraints (use ; to separate columns and , to separate values' , action = 'store' )
114+ subparser .set_defaults (func = _add_puzzle )
115+
116+ def create_puzzle_parsers (subparsers ):
117+ subparser = subparsers .add_parser ('solve' , help = 'solves PiCross puzzle given its constraints' )
118+ subparser .add_argument ('row' , help = 'row constraints (use ; to separate rows and , to separate values)' , action = 'store' )
119+ subparser .add_argument ('column' , help = 'column constraints (use ; to separate columns and , to separate values' , action = 'store' )
120+ subparser .set_defaults (func = _solve )
121+
122+ subparser = subparsers .add_parser ('constraints' , help = 'derives constraints from image' )
123+ subparser .add_argument ('filename' , help = 'file containing image' , action = 'store' )
124+ subparser .set_defaults (func = _derive_constraints )
125+
126+ parser = argparse .ArgumentParser (prog = 'picross' )
127+ parser .set_defaults (func = lambda args : parser .print_help ())
128+ subparsers = parser .add_subparsers (help = 'sub-command help' )
129+
130+ subparser = subparsers .add_parser ('archive' , help = 'archive-related functionality' )
131+ create_archive_parsers (subparser .add_subparsers ())
132+
133+ subparser = subparsers .add_parser ('puzzle' , help = 'puzzle-related functionality' )
134+ create_puzzle_parsers (subparser .add_subparsers ())
135+
136+
137+ args = parser .parse_args ()
138+ args .func (args )
139+
140+
141+ _process_command_line_arguments ()
0 commit comments