cliptic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []