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 +4 -4
- data/Rakefile +55 -1
- data/bin/2048 +3 -1
- data/lib/text2048/app.rb +18 -34
- data/lib/text2048/board.rb +93 -50
- data/lib/text2048/curses_view.rb +23 -59
- data/lib/text2048/curses_view/colorize.rb +53 -0
- data/lib/text2048/curses_view/keyboard.rb +35 -0
- data/lib/text2048/curses_view/tile.rb +135 -0
- data/lib/text2048/curses_view/tile_effects.rb +35 -0
- data/lib/text2048/monkey_patch/array/tile.rb +1 -1
- data/lib/text2048/monkey_patch/hash.rb +8 -0
- data/lib/text2048/monkey_patch/hash/converter.rb +17 -0
- data/lib/text2048/version.rb +1 -1
- data/spec/array_spec.rb +24 -24
- data/spec/spec_helper.rb +3 -0
- data/spec/text2048/app_spec.rb +106 -0
- data/spec/text2048/board_spec.rb +332 -51
- metadata +11 -3
- data/lib/text2048/curses_tile.rb +0 -125
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b38ba8bba87211b11aec9d46187e5e67edb98e9
|
4
|
+
data.tar.gz: f8238c1b70544f21ed8a7d260947e513f5e248ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
-
|
9
|
+
attr_reader :board
|
10
|
+
attr_reader :view
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
21
|
-
@
|
22
|
-
@board
|
23
|
-
|
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
|
31
|
-
|
32
|
-
|
33
|
-
|
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.
|
44
|
+
generate if @board.generate?(last)
|
55
45
|
end
|
56
46
|
|
57
47
|
def move(command)
|
58
|
-
last = @board
|
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
|
data/lib/text2048/board.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
@
|
15
|
+
@all_tiles = tiles.to_h
|
15
16
|
@score = score
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
-
@tiles = board.tiles.dup
|
20
|
-
end
|
21
|
-
|
22
|
-
def [](coord)
|
23
|
-
@tiles[coord]
|
24
|
-
end
|
19
|
+
# @!group Move
|
25
20
|
|
26
|
-
|
27
|
-
|
28
|
-
|
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 =
|
42
|
-
|
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
|
-
|
36
|
+
flip_horizontal { right }
|
47
37
|
end
|
48
38
|
|
39
|
+
# @macro move
|
49
40
|
def up
|
50
|
-
transpose
|
41
|
+
transpose { left }
|
51
42
|
end
|
52
43
|
|
44
|
+
# @macro move
|
53
45
|
def down
|
54
|
-
transpose
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
85
|
-
|
86
|
-
|
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
|
91
|
-
|
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
|
-
@
|
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| @
|
144
|
+
[index].product([0, 1, 2, 3]).map { |each| @all_tiles[each] }
|
102
145
|
end
|
103
146
|
end
|
104
147
|
end
|
data/lib/text2048/curses_view.rb
CHANGED
@@ -1,65 +1,34 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require 'curses'
|
4
|
-
require '
|
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
|
-
|
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
|
-
|
39
|
-
|
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
|
-
(
|
40
|
+
(Tile.height(@scale) + 1) * 4 + 1
|
72
41
|
end
|
73
42
|
|
74
43
|
def width
|
75
|
-
(
|
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
|
-
|
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
|
-
|
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
|
-
|
119
|
-
|
87
|
+
return if @initialized
|
88
|
+
init_curses
|
89
|
+
@initialized = true
|
120
90
|
end
|
121
91
|
|
122
92
|
def init_curses
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
-
|
107
|
+
Tile.new(tile, row, col, color(tile.to_i), @scale).show
|
144
108
|
refresh
|
145
109
|
end
|
146
110
|
end
|