cliptic 0.1.0

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.
@@ -0,0 +1,135 @@
1
+ module Cliptic
2
+ module Menus
3
+ class Main < Interface::Menu
4
+ def opts
5
+ {
6
+ "Play Today" => ->{Cliptic::Main::Player::Game.new.play},
7
+ "Select Date"=> ->{Select_Date.new.choose_opt},
8
+ "This Week" => ->{This_Week.new.choose_opt},
9
+ "Recent Puzzles" => ->{Recent_Puzzles.new.choose_opt},
10
+ "High Scores"=> ->{High_Scores.new.choose_opt},
11
+ "Quit" => ->{exit}
12
+ }
13
+ end
14
+ def title
15
+ "Main Menu"
16
+ end
17
+ end
18
+ class Select_Date < Interface::Menu_With_Stats
19
+ attr_reader :opts
20
+ def initialize
21
+ set_date(date:Date.today)
22
+ super(height:7, sel:Interface::Date_Selector)
23
+ end
24
+ def title
25
+ "Select Date"
26
+ end
27
+ def ctrls
28
+ super.merge({
29
+ ?h => ->{selector.cursor -= 1},
30
+ ?l => ->{selector.cursor += 1},
31
+ ?j => ->{inc_date(1)},
32
+ ?k => ->{inc_date(-1)},
33
+ 258 => ->{inc_date(1)},
34
+ 259 => ->{inc_date(-1)},
35
+ 260 => ->{selector.cursor -= 1},
36
+ 261 => ->{selector.cursor += 1}
37
+ })
38
+ end
39
+ def stat_date
40
+ Date.new(*@opts.reverse)
41
+ end
42
+ private
43
+ def set_date(date:)
44
+ @opts = [] unless @opts
45
+ @opts[0] = date.day
46
+ @opts[1] = date.month
47
+ @opts[2] = date.year
48
+ end
49
+ def next_date(n)
50
+ @opts.dup.tap{|d| d[selector.cursor] += n }
51
+ end
52
+ def inc_date(n)
53
+ case selector.cursor
54
+ when 0 then inc_day(n)
55
+ when 1 then inc_month(n)
56
+ when 2 then @opts[2] += n
57
+ end
58
+ check_in_range
59
+ end
60
+ def inc_day(n)
61
+ next_date(n).tap do |date|
62
+ if valid?(date)
63
+ @opts[0]+= n
64
+ elsif date[0] == 0
65
+ set_date(date:stat_date-1)
66
+ elsif date[0] > 28
67
+ set_date(date:stat_date+1)
68
+ end
69
+ end
70
+ end
71
+ def inc_month(n)
72
+ next_date(n).tap do |date|
73
+ if valid?(date)
74
+ @opts[1] += n
75
+ elsif date[1] == 0
76
+ set_date(date:stat_date << 1)
77
+ elsif date[1] == 13
78
+ set_date(date:stat_date >> 1)
79
+ elsif date[0] > 28
80
+ set_date(date:last_day_of_month(date:date))
81
+ end
82
+ end
83
+ end
84
+ def valid?(date)
85
+ Date.valid_date?(*date.reverse)
86
+ end
87
+ def last_day_of_month(date:)
88
+ Date.new(date[2], date[1]+1, 1)-1
89
+ end
90
+ def check_in_range
91
+ set_date(date:Date.today) if date_late?
92
+ set_date(date:Date.today << 9) if date_early?
93
+ end
94
+ def date_late?
95
+ stat_date > Date.today
96
+ end
97
+ def date_early?
98
+ stat_date < Date.today<<9
99
+ end
100
+ end
101
+ class This_Week < Interface::Menu_With_Stats
102
+ def days
103
+ @days || 7.times.map{|i| Date.today - i}
104
+ end
105
+ def stat_date
106
+ days[selector.cursor]
107
+ end
108
+ def opts
109
+ @opts || days.map{|d| d.strftime("%A").ljust(9).center(12) + tickbox(d)}
110
+ end
111
+ def title
112
+ "This Week"
113
+ end
114
+ def tickbox(date)
115
+ "[#{Database::State.new(date:date).done ? Chars::Tick : " "}]"
116
+ end
117
+ end
118
+ class Recent_Puzzles < Interface::SQL_Menu_With_Stats
119
+ def initialize
120
+ super(table:Database::Recents)
121
+ end
122
+ def title
123
+ "Recently Played"
124
+ end
125
+ end
126
+ class High_Scores < Interface::SQL_Menu_With_Stats
127
+ def initialize
128
+ super(table:Database::Scores)
129
+ end
130
+ def title
131
+ "High Scores"
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,72 @@
1
+ module Cliptic
2
+ module Terminal
3
+ class Command
4
+ def self.run
5
+ ARGV.size > 0 ?
6
+ parse_args : main_menu
7
+ rescue StandardError => e
8
+ Curses.close_screen
9
+ abort(e.message)
10
+ end
11
+ private
12
+ def self.setup
13
+ Config::Setter.new.set
14
+ Screen.setup
15
+ at_exit{close}
16
+ end
17
+ def self.main_menu
18
+ setup
19
+ Cliptic::Menus::Main.new.choose_opt
20
+ end
21
+ def self.parse_args
22
+ case arg = ARGV.shift
23
+ when "reset", "-r" then Reset_Stats.route.call
24
+ when "today", "-t" then play(ARGV.shift.to_i)
25
+ end
26
+ end
27
+ def self.play(offset)
28
+ setup
29
+ Cliptic::Main::Player::
30
+ Game.new(date:Date.today+offset).play
31
+ end
32
+ def self.close
33
+ Curses.close_screen
34
+ puts "Thanks for playing!"
35
+ end
36
+ end
37
+ class Reset_Stats
38
+ def self.route
39
+ if valid_options.include?(c = ARGV.shift)
40
+ ->{confirm_reset(c)}
41
+ else
42
+ ->{puts "Unknown option #{c}"}
43
+ end
44
+ end
45
+ private
46
+ def self.valid_options
47
+ ["scores", "all", "states", "recents"]
48
+ end
49
+ def self.confirm_reset(table)
50
+ puts prompt(table)
51
+ user_confirmed? ?
52
+ reset(table) :
53
+ puts("Wise choice")
54
+ end
55
+ def self.prompt(table)
56
+ <<~prompt
57
+ cliptic: Reset #{table}
58
+ Are you sure? This cannot be undone! [Y/n]
59
+ prompt
60
+ end
61
+ def self.user_confirmed?
62
+ gets.chomp === "Y"
63
+ end
64
+ def self.reset(table)
65
+ table == "all" ?
66
+ Database::Delete.all :
67
+ Database::Delete.table(table)
68
+ end
69
+ end
70
+ end
71
+ end
72
+
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cliptic
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,197 @@
1
+ module Cliptic
2
+ module Windows
3
+ class Window < Curses::Window
4
+ include Chars
5
+ attr_reader :y, :x, :line, :col
6
+ attr_reader :centered_y, :centered_x
7
+ def initialize(y:0, x:0, line:nil, col:nil)
8
+ @y, @x = wrap_dims(y:y, x:x)
9
+ @line,@col= center_pos(y:y,x:x,line:line,col:col)
10
+ @centered_y, @centered_x = [line, col]
11
+ .map{|pos| pos.nil?}
12
+ super(@y, @x, @line, @col)
13
+ keypad(true)
14
+ end
15
+ def draw(cp:$colors[:box]||0, clr:false)
16
+ erase if clr
17
+ setpos.color(cp)
18
+ 1.upto(y) do |i|
19
+ line = case i
20
+ when 1 then top_border
21
+ when y then bottom_border
22
+ else side_border
23
+ end
24
+ self << line
25
+ end; setpos.color.noutrefresh
26
+ self
27
+ end
28
+ def color(cp=0)
29
+ color_set(cp); self
30
+ end
31
+ def bold(on=true)
32
+ on ?
33
+ attron(Curses::A_BOLD) :
34
+ attroff(Curses::A_BOLD)
35
+ self
36
+ end
37
+ def wrap_str(str:, line:)
38
+ split_str(str).each_with_index do |l, i|
39
+ setpos(line+i, 2)
40
+ self << l
41
+ end; self
42
+ end
43
+ def setpos(y=0, x=0)
44
+ super(y,x); self
45
+ end
46
+ def add_str(str:,y:line,x:(@x-str.length)/2, cp:nil, bold:false)
47
+ color(cp) if cp
48
+ setpos(*wrap_str_dims(y:y, x:x, str:str))
49
+ bold(bold)
50
+ self << str
51
+ noutrefresh
52
+ reset_attrs
53
+ end
54
+ def clear
55
+ erase
56
+ noutrefresh
57
+ self
58
+ end
59
+ def time_str(str:, y:, x:(@x-str.length)/2, t:5, cp:nil, bold:false)
60
+ Thread.new{
61
+ add_str(str:str, y:y, x:x, cp:cp, bold:bold)
62
+ sleep(t)
63
+ add_str(str:" "*str.length, y:y, x:x, cp:cp, bold:bold)
64
+ }
65
+ self
66
+ end
67
+ def reset_attrs
68
+ color(0).bold(false)
69
+ self
70
+ end
71
+ def refresh
72
+ super; self
73
+ end
74
+ def reset_pos
75
+ move(
76
+ *[centered_y, centered_x]
77
+ .zip(total_dims, [y, x], [line, col])
78
+ .map{|cent, tot, dim, pos| cent ? (tot-dim)/2 : pos}
79
+ )
80
+ refresh
81
+ end
82
+ def move(line:nil, col:nil)
83
+ super(*center_pos(y:y, x:x, line:line, col:col))
84
+ end
85
+ private
86
+ def wrap_dims(y:, x:)
87
+ [y, x].zip(total_dims)
88
+ .map{|dim, tot| dim <= 0 ? dim+tot : dim}
89
+ end
90
+ def wrap_str_dims(y:, x:, str:)
91
+ [y, x].zip(total_dims)
92
+ .map{|pos,tot|pos<0 ? tot-str.length+pos : pos}
93
+ end
94
+ def total_dims
95
+ [Curses.lines, Curses.cols]
96
+ end
97
+ def center_pos(y:, x:, line:, col:)
98
+ [line, col].zip(total_dims, [y, x])
99
+ .map{|pos, tot, dim| pos || ((tot-dim)/2)}
100
+ end
101
+ def split_str(str)
102
+ str.gsub(/(.{1,#{x-4}})(\s+|$\n?)|(.{1,#{x-4}})/, "\\1\\3\n").split("\n")
103
+ end
104
+ def top_border
105
+ LU+(HL*(x-2))+RU
106
+ end
107
+ def bottom_border
108
+ LL+(HL*(x-2))+RL
109
+ end
110
+ def side_border
111
+ VL+(' '*(x-2))+VL
112
+ end
113
+ end
114
+ class Grid < Window
115
+ attr_reader :sq, :cells
116
+ def initialize(y:, x:, line:nil, col:nil)
117
+ @sq = Pos.mk(y,x)
118
+ @y, @x = sq_to_dims(y:y, x:x)
119
+ super(y:@y, x:@x, line:line, col:col)
120
+ @cells = make_cells(**sq)
121
+ end
122
+ def draw(cp:$colors[:grid]||0)
123
+ setpos.color(cp)
124
+ 1.upto(y) do |i|
125
+ line = case i
126
+ when 1 then top_border
127
+ when y then bottom_border
128
+ else i.even? ? side_border : inner_border
129
+ end
130
+ self << line
131
+ end; setpos.color
132
+ self
133
+ end
134
+ def add_str(str:, y:0, x:0)
135
+ str.chars.each_with_index do |char, i|
136
+ cell(y:y, x:x+i).write(char)
137
+ end
138
+ end
139
+ def cell(y:, x:)
140
+ cells[y][x]
141
+ end
142
+ private
143
+ def sq_to_dims(y:, x:)
144
+ [ (2*y)+1, (4*x)+1 ]
145
+ end
146
+ def make_cells(y:, x:)
147
+ y.times.map{|iy| x.times.map{|ix|
148
+ Cell.new(sq:Pos.mk(iy,ix), grid:self)}}
149
+ end
150
+ def top_border
151
+ LU+(HL*3+TD)*(sq[:x]-1)+HL*3+RU
152
+ end
153
+ def bottom_border
154
+ LL+(HL*3+TU)*(sq[:x]-1)+HL*3+RL
155
+ end
156
+ def side_border
157
+ (VL+" "*3)*sq[:x]+VL
158
+ end
159
+ def inner_border
160
+ TR+(HL*3+XX)*(sq[:x]-1)+HL*3+TL
161
+ end
162
+ end
163
+ class Cell
164
+ attr_reader :sq, :grid, :pos
165
+ def initialize(sq:, grid:)
166
+ @sq, @grid, @pos = sq, grid, calc_abs_pos(**sq)
167
+ end
168
+ def focus(y:0, x:0)
169
+ grid.setpos(*[y,x].zip(pos.values).map(&:sum))
170
+ self
171
+ end
172
+ def write(char)
173
+ focus.grid << char; self
174
+ end
175
+ protected
176
+ def calc_abs_pos(y:, x:)
177
+ { y:(2*y)+1, x:(4*x)+2 }
178
+ end
179
+ end
180
+ class Bar < Window
181
+ attr_reader :bg_col
182
+ def initialize(line:, bg_col:$colors[:bar])
183
+ super(y:1, x:0, line:line, col:0)
184
+ @bg_col = bg_col
185
+ end
186
+ def add_str(y:0, x:, str:, bold:false, cp:bg_col)
187
+ super(y:0, x:x, str:str, bold:bold, cp:cp)
188
+ end
189
+ def time_str(x:, str:, t:5, cp:bg_col, bold:false)
190
+ super(y:0, x:x, str:str, t:5, cp:cp, bold:bold)
191
+ end
192
+ def draw
193
+ bkgd(Curses.color_pair(bg_col)); self
194
+ end
195
+ end
196
+ end
197
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cliptic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Christian Welham
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-07-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: curses
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.4.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.4.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: curb
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.11
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.9.11
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.4.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.4.2
55
+ description: A terminal user interface to fetch and play cryptic crosswords
56
+ email:
57
+ - welhamm@gmail.com
58
+ executables:
59
+ - cliptic
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - CHANGELOG.md
65
+ - Gemfile
66
+ - Gemfile.lock
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - bin/cliptic
71
+ - bin/console
72
+ - bin/setup
73
+ - cliptic.gemspec
74
+ - lib/cliptic.rb
75
+ - lib/cliptic/config.rb
76
+ - lib/cliptic/database.rb
77
+ - lib/cliptic/interface.rb
78
+ - lib/cliptic/lib.rb
79
+ - lib/cliptic/main.rb
80
+ - lib/cliptic/menus.rb
81
+ - lib/cliptic/terminal.rb
82
+ - lib/cliptic/version.rb
83
+ - lib/cliptic/windows.rb
84
+ homepage: https://github.com/apexatoll/cliptic
85
+ licenses:
86
+ - MIT
87
+ metadata:
88
+ homepage_uri: https://github.com/apexatoll/cliptic
89
+ source_code_uri: https://github.com/apexatoll/cliptic
90
+ changelog_uri: https://github.com/apexatoll/cliptic/CHANGELOG.md
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: 2.4.0
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubygems_version: 3.2.15
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Terminal-based cryptic crossword player
110
+ test_files: []