text2048 0.5.0 → 0.6.0

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