text2048 0.5.0 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f364841e360557b497346d9675f24e39bdbb89d1
4
- data.tar.gz: 1031bd3cea22c6120431459873d7631720578a30
3
+ metadata.gz: 3b38ba8bba87211b11aec9d46187e5e67edb98e9
4
+ data.tar.gz: f8238c1b70544f21ed8a7d260947e513f5e248ca
5
5
  SHA512:
6
- metadata.gz: e4a9a739cac549134076522d78d58bd573ce241af8ff57d92e27672db25f3acbe2e078adeda68f3efdb405558075d253c9c92eb5731eaaa84d26c8b03dfa8803
7
- data.tar.gz: 400794ed86e36d7ecb25fabe136d24c4c2cf0cbc22475abb5d214cc975549027e335fea4e9a4a082e84fdc85c6aa63ffebc489540ab551f4f9de0665f3b082dd
6
+ metadata.gz: e6dd1f5d927c70135e72be6c56a76870b545a77455f0582d17f20c14b2226be70b09878d5e76a4ba1ae8a5f1b74cf9dc13bc37d4f61e61f2a370b88314bacd35
7
+ data.tar.gz: f24978440f3041a49956726d910cdc472f7b618181910cdd34195b6f1cc07b675dac8e7fe5dd8e1bb6b08a04ffabf72110b58448db731f91567308c2d3e79715
data/Rakefile CHANGED
@@ -3,7 +3,7 @@
3
3
  require 'bundler/gem_tasks'
4
4
  require 'coveralls/rake/task'
5
5
 
6
- task default: [:test, :reek, :rubocop]
6
+ task default: [:test, :reek, :flog, :flay, :rubocop]
7
7
  task test: [:spec, :cucumber, 'coveralls:push']
8
8
  task travis: :default
9
9
 
@@ -49,3 +49,57 @@ rescue LoadError
49
49
  $stderr.puts 'Reek is disabled'
50
50
  end
51
51
  end
52
+
53
+ begin
54
+ require 'flog'
55
+ desc 'Analyze for code complexity'
56
+ task :flog do
57
+ flog = Flog.new(continue: true)
58
+ flog.flog(*FileList['lib/**/*.rb'])
59
+ threshold = 19
60
+
61
+ bad_methods = flog.totals.select do |name, score|
62
+ !(/##{flog.no_method}$/ =~ name) && score > threshold
63
+ end
64
+ bad_methods.sort { |a, b| a[1] <=> b[1] }.reverse.each do |name, score|
65
+ printf "%8.1f: %s\n", score, name
66
+ end
67
+ unless bad_methods.empty?
68
+ fail "#{bad_methods.size} methods have a complexity > #{threshold}"
69
+ end
70
+ end
71
+ rescue LoadError
72
+ task :flog do
73
+ $stderr.puts 'Flog is disabled'
74
+ end
75
+ end
76
+
77
+ begin
78
+ require 'rake/tasklib'
79
+ require 'flay'
80
+ require 'flay_task'
81
+
82
+ FlayTask.new do |t|
83
+ t.dirs = FileList['lib/**/*.rb'].map do |each|
84
+ each[/[^\/]+/]
85
+ end.uniq
86
+ t.threshold = 0
87
+ t.verbose = true
88
+ end
89
+ rescue LoadError
90
+ task :flay do
91
+ $stderr.puts 'Flay is disabled'
92
+ end
93
+ end
94
+
95
+ begin
96
+ require 'yard'
97
+ YARD::Rake::YardocTask.new do |t|
98
+ t.options = ['--no-private']
99
+ t.options << '--debug' << '--verbose' if verbose == true
100
+ end
101
+ rescue LoadError
102
+ task :yard do
103
+ $stderr.puts 'YARD is disabled'
104
+ end
105
+ end
data/bin/2048 CHANGED
@@ -5,4 +5,6 @@ $LOAD_PATH.unshift(File.dirname(File.realpath(__FILE__)) + '/../lib')
5
5
 
6
6
  require 'text2048/app'
7
7
 
8
- Text2048::App.new.start
8
+ app = Text2048::App.new
9
+ app.generate(2)
10
+ loop { app.step }
data/lib/text2048/app.rb CHANGED
@@ -1,49 +1,39 @@
1
1
  # encoding: utf-8
2
2
 
3
- require 'curses'
4
3
  require 'text2048'
5
4
 
6
5
  # This module smells of :reek:UncommunicativeModuleName
7
6
  module Text2048
8
7
  # Controller class.
9
8
  class App
10
- include Curses
9
+ attr_reader :board
10
+ attr_reader :view
11
11
 
12
- KEYS = {
13
- 'h' => :left, 'l' => :right, 'k' => :up, 'j' => :down,
14
- Key::LEFT => :left, Key::RIGHT => :right,
15
- Key::UP => :up, Key::DOWN => :down,
16
- '+' => :larger, '-' => :smaller,
17
- 'q' => :quit
18
- }
12
+ def initialize(view = CursesView.new, board = Board.new)
13
+ @view = view
14
+ @board = board
15
+ end
19
16
 
20
- def initialize
21
- @view = CursesView.new
22
- @board = Board.new
23
- init_screen
24
- curs_set(0)
25
- noecho
26
- stdscr.keypad(true)
27
- at_exit { close_screen }
17
+ def generate(num_tiles = 1)
18
+ num_tiles.times { @board = @board.generate }
19
+ @view.update(@board)
20
+ @view.zoom_tiles(@board.generated_tiles)
28
21
  end
29
22
 
30
- def start
31
- generate 2
32
- loop do
33
- @view.win if @board.win?
34
- @view.game_over if @board.lose?
35
- input KEYS[Curses.getch]
36
- end
23
+ def step
24
+ @view.win if @board.win?
25
+ @view.game_over if @board.lose?
26
+ input @view.command
37
27
  end
38
28
 
39
29
  private
40
30
 
41
31
  def input(command)
42
32
  case command
43
- when :left, :right, :up, :down
44
- move_and_generate(command)
45
33
  when :larger, :smaller
46
34
  @view.__send__ command, @board
35
+ when :left, :right, :up, :down
36
+ move_and_generate(command)
47
37
  when :quit
48
38
  exit 0
49
39
  end
@@ -51,21 +41,15 @@ module Text2048
51
41
 
52
42
  def move_and_generate(command)
53
43
  last = move(command)
54
- generate if @board.to_a != last.to_a
44
+ generate if @board.generate?(last)
55
45
  end
56
46
 
57
47
  def move(command)
58
- last = @board.dup
48
+ last = @board
59
49
  @board = @board.__send__(command)
60
50
  @view.update(@board)
61
51
  @view.pop_tiles(@board.merged_tiles)
62
52
  last
63
53
  end
64
-
65
- def generate(num_tiles = 1)
66
- num_tiles.times { @board.generate }
67
- @view.update(@board)
68
- @view.zoom_tiles(@board.generated_tiles)
69
- end
70
54
  end
71
55
  end
@@ -1,104 +1,147 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'text2048/monkey_patch/array'
4
+ require 'text2048/monkey_patch/hash'
4
5
  require 'text2048/tile'
5
6
 
6
7
  # This module smells of :reek:UncommunicativeModuleName
7
8
  module Text2048
8
- # Game board
9
+ # 2048 game board
9
10
  class Board
11
+ # @return [Number] returns the current score
10
12
  attr_reader :score
11
- attr_reader :tiles
12
13
 
13
14
  def initialize(tiles = Array.new(4) { Array.new(4, 0) }, score = 0)
14
- @tiles = tiles.to_h
15
+ @all_tiles = tiles.to_h
15
16
  @score = score
16
17
  end
17
18
 
18
- def initialize_copy(board)
19
- @tiles = board.tiles.dup
20
- end
21
-
22
- def [](coord)
23
- @tiles[coord]
24
- end
19
+ # @!group Move
25
20
 
26
- def to_a
27
- [0, 1, 2, 3].map { |each| row(each) }
28
- end
29
-
30
- def win?
31
- @tiles.any? { |_key, value| value.to_i >= 2048 }
32
- end
33
-
34
- def lose?
35
- right.left.up.down.tiles.select do |_key, each|
36
- each.to_i > 0
37
- end.size == 4 * 4
38
- end
21
+ # @!macro [new] move
22
+ # Move the tiles to the $0.
23
+ # @return [Board] returns a new board
39
24
 
25
+ # @macro move
40
26
  def right
41
- board, score = move_right
42
- self.class.new board, @score + score
27
+ board, score = to_a.reduce([[], @score]) do |(rows, sc), each|
28
+ row, row_sc = each.right
29
+ [rows << row, sc + row_sc]
30
+ end
31
+ new_board(board, score)
43
32
  end
44
33
 
34
+ # @macro move
45
35
  def left
46
- reverse :right
36
+ flip_horizontal { right }
47
37
  end
48
38
 
39
+ # @macro move
49
40
  def up
50
- transpose :left
41
+ transpose { left }
51
42
  end
52
43
 
44
+ # @macro move
53
45
  def down
54
- transpose :right
46
+ transpose { right }
47
+ end
48
+
49
+ # @!endgroup
50
+
51
+ # @!group Tiles
52
+
53
+ # @return [Array<Tile>] the list of tiles
54
+ def tiles
55
+ @all_tiles.select { |_key, each| each.to_i > 0 }
55
56
  end
56
57
 
58
+ # @return [Array] the list of +[row, col]+ of the merged tiles
57
59
  def merged_tiles
58
60
  find_tiles :merged
59
61
  end
60
62
 
63
+ # @return [Array] the list of +[row, col]+ of the newly generated tiles
61
64
  def generated_tiles
62
65
  find_tiles :generated
63
66
  end
64
67
 
68
+ # Need to generate a new tile?
69
+ # @param other [Board] the previous {Board} object
70
+ # @return [Boolean] generate a new tile?
71
+ def generate?(other)
72
+ to_a != other.to_a
73
+ end
74
+
75
+ # Generates a new tile
76
+ # @return [Board] a new board
65
77
  def generate
66
- loop do
67
- sample = @tiles.keys.sample
68
- if @tiles[sample] == 0
69
- @tiles[sample] = Tile.new(rand < 0.8 ? 2 : 4, :generated)
70
- return
71
- end
72
- end
78
+ tiles = @all_tiles.dup
79
+ tiles[sample_zero_tile] = Tile.new(rand < 0.8 ? 2 : 4, :generated)
80
+ new_board(tiles, @score)
81
+ end
82
+
83
+ # @!endgroup
84
+
85
+ # @!group Win/Lose
86
+
87
+ def win?
88
+ @all_tiles.any? { |_key, value| value.to_i >= 2048 }
89
+ end
90
+
91
+ def lose?
92
+ right.left.up.down.tiles.size == 4 * 4
93
+ end
94
+
95
+ # @!endgroup
96
+
97
+ # @!group Conversion
98
+
99
+ # @return [Array] a 2D array of tiles.
100
+ def to_a
101
+ [0, 1, 2, 3].map { |each| row(each) }
73
102
  end
74
103
 
104
+ # @!endgroup
105
+
75
106
  private
76
107
 
77
- def move_right
78
- to_a.reduce([[], 0]) do |(board, score), each|
79
- row, row_score = each.rmerge
80
- [board << row, score + row_score]
81
- end
108
+ def flip_horizontal(&block)
109
+ board = flipped_board.instance_eval(&block)
110
+ new_board(board.to_a.map(&:reverse), board.score)
111
+ end
112
+
113
+ def flipped_board
114
+ new_board(to_a.map(&:reverse), @score)
115
+ end
116
+
117
+ def transpose(&block)
118
+ board = transposed_board.instance_eval(&block)
119
+ new_board(board.to_a.transpose, board.score)
120
+ end
121
+
122
+ def transposed_board
123
+ new_board(to_a.transpose, @score)
124
+ end
125
+
126
+ def zero_tiles
127
+ @all_tiles.select { |_key, each| each.to_i == 0 }
82
128
  end
83
129
 
84
- def transpose(direction)
85
- klass = self.class
86
- board = klass.new(to_a.transpose, @score).__send__(direction)
87
- klass.new board.to_a.transpose, board.score
130
+ def sample_zero_tile
131
+ fail if zero_tiles.empty?
132
+ zero_tiles.keys.shuffle.first
88
133
  end
89
134
 
90
- def reverse(direction)
91
- klass = self.class
92
- board = klass.new(to_a.map(&:reverse), @score).__send__(direction)
93
- klass.new board.to_a.map(&:reverse), board.score
135
+ def new_board(tiles, score)
136
+ self.class.new(tiles, score)
94
137
  end
95
138
 
96
139
  def find_tiles(status)
97
- @tiles.select { |_key, each| each.status == status }.keys
140
+ @all_tiles.select { |_key, each| each.status == status }.keys
98
141
  end
99
142
 
100
143
  def row(index)
101
- [index].product([0, 1, 2, 3]).map { |each| @tiles[each] }
144
+ [index].product([0, 1, 2, 3]).map { |each| @all_tiles[each] }
102
145
  end
103
146
  end
104
147
  end
@@ -1,65 +1,34 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'curses'
4
- require 'text2048/curses_tile'
4
+ require 'forwardable'
5
+ require 'text2048/curses_view/colorize'
6
+ require 'text2048/curses_view/keyboard'
7
+ require 'text2048/curses_view/tile'
8
+ require 'text2048/curses_view/tile_effects'
5
9
 
6
10
  # This module smells of :reek:UncommunicativeModuleName
7
11
  module Text2048
8
12
  # Curses UI
9
13
  class CursesView
10
- # Curses tile effects
11
- module TileEffects
12
- def pop_tiles(list)
13
- [:pop, :draw_box].each do |each|
14
- list_do each, list
15
- refresh
16
- sleep 0.1
17
- end
18
- end
19
-
20
- def zoom_tiles(list)
21
- [:fill_black, :draw_number, :show].each do |each|
22
- list_do each, list
23
- refresh
24
- sleep 0.05
25
- end
26
- end
27
-
28
- private
29
-
30
- def list_do(name, list)
31
- list.each { |each| @tiles[each].__send__ name }
32
- end
33
- end
34
-
14
+ include Colorize
35
15
  include Curses
36
16
  include TileEffects
17
+ extend Forwardable
37
18
 
38
- COLORS = {
39
- 0 => COLOR_BLACK,
40
- 2 => COLOR_WHITE,
41
- 4 => COLOR_GREEN,
42
- 8 => COLOR_GREEN,
43
- 16 => COLOR_CYAN,
44
- 32 => COLOR_CYAN,
45
- 64 => COLOR_BLUE,
46
- 128 => COLOR_BLUE,
47
- 256 => COLOR_YELLOW,
48
- 512 => COLOR_YELLOW,
49
- 1024 => COLOR_MAGENTA,
50
- 2048 => COLOR_RED
51
- }
52
-
53
- DEFAULT_WIDTH = (CursesTile::DEFAULT_WIDTH + 1) * 4 + 1
54
- DEFAULT_HEIGHT = (CursesTile::DEFAULT_HEIGHT + 1) * 4 + 2
19
+ DEFAULT_WIDTH = (Tile::DEFAULT_WIDTH + 1) * 4 + 1
20
+ DEFAULT_HEIGHT = (Tile::DEFAULT_HEIGHT + 1) * 4 + 2
55
21
 
56
22
  def initialize
57
23
  @tiles = {}
58
24
  @scale = 2
59
25
  @scale_min = 1
60
26
  @scale_step = 0.5
27
+ @keyboard = Keyboard.new
61
28
  end
62
29
 
30
+ def_delegator :@keyboard, :read, :command
31
+
63
32
  def update(board)
64
33
  maybe_init_curses
65
34
  draw_score(board.score)
@@ -68,11 +37,11 @@ module Text2048
68
37
  end
69
38
 
70
39
  def height
71
- (CursesTile.height(@scale) + 1) * 4 + 1
40
+ (Tile.height(@scale) + 1) * 4 + 1
72
41
  end
73
42
 
74
43
  def width
75
- (CursesTile.width(@scale) + 1) * 4 + 1
44
+ (Tile.width(@scale) + 1) * 4 + 1
76
45
  end
77
46
 
78
47
  def larger(board)
@@ -89,12 +58,12 @@ module Text2048
89
58
 
90
59
  def win
91
60
  setpos(rows_center, cols_center - 1)
92
- attron(color_pair(COLOR_RED)) { addstr('WIN!') }
61
+ colorize(COLOR_RED) { addstr('WIN!') }
93
62
  end
94
63
 
95
64
  def game_over
96
65
  setpos(rows_center, cols_center - 4)
97
- attron(color_pair(COLOR_RED)) { addstr('GAME OVER') }
66
+ colorize(COLOR_RED) { addstr('GAME OVER') }
98
67
  end
99
68
 
100
69
  private
@@ -115,20 +84,15 @@ module Text2048
115
84
  end
116
85
 
117
86
  def maybe_init_curses
118
- @curses_initialized || init_curses
119
- @curses_initialized = true
87
+ return if @initialized
88
+ init_curses
89
+ @initialized = true
120
90
  end
121
91
 
122
92
  def init_curses
123
- start_color
124
- init_color_pairs
125
- end
126
-
127
- def init_color_pairs
128
- COLORS.each_pair do |_key, value|
129
- init_pair value, COLOR_BLACK, value
130
- init_pair value + 100, value, value
131
- end
93
+ init_screen
94
+ curs_set(0)
95
+ at_exit { close_screen }
132
96
  end
133
97
 
134
98
  def draw_score(score)
@@ -140,7 +104,7 @@ module Text2048
140
104
  [0, 1, 2, 3].product([0, 1, 2, 3]).each do |row, col|
141
105
  tile = tiles[row][col]
142
106
  @tiles[[row, col]] =
143
- CursesTile.new(tile, row, col, COLORS[tile.to_i], @scale).show
107
+ Tile.new(tile, row, col, color(tile.to_i), @scale).show
144
108
  refresh
145
109
  end
146
110
  end