text2048 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.
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ text2048
2
+ ========
3
+
4
+ Text mode 2048 game.
5
+
6
+ How to Play
7
+ ===========
8
+
9
+ ```
10
+ $ 2048
11
+ ```
12
+
13
+ - Use your arrow keys to move the tiles.
14
+ - +/- for increase or decrease the size of the tiles displayed.
15
+
16
+ [![Screenshot](https://raw.github.com/yasuhito/text2048/develop/screen_shot.png)][screenshot]
17
+ [screenshot]: https://raw.github.com/yasuhito/text2048/develop/screen_shot.png
18
+
19
+ Installation
20
+ ============
21
+
22
+ ```
23
+ $ gem install text2048
24
+ ```
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ task default: [:spec, :cucumber, :rubocop]
6
+
7
+ begin
8
+ require 'rspec/core/rake_task'
9
+ RSpec::Core::RakeTask.new
10
+ rescue LoadError
11
+ task :spec do
12
+ $stderr.puts 'RSpec is disabled'
13
+ end
14
+ end
15
+
16
+ begin
17
+ require 'cucumber/rake/task'
18
+ Cucumber::Rake::Task.new
19
+ rescue LoadError
20
+ task :cucumber do
21
+ $stderr.puts 'Cucumber is disabled'
22
+ end
23
+ end
24
+
25
+ begin
26
+ require 'rubocop/rake_task'
27
+ Rubocop::RakeTask.new
28
+ rescue LoadError
29
+ task :rubocop do
30
+ $stderr.puts 'Rubocop is disabled'
31
+ end
32
+ end
data/bin/2048 ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.dirname(File.realpath(__FILE__)) + '/../lib')
4
+
5
+ require 'curses'
6
+ require 'text2048'
7
+
8
+ include Curses
9
+
10
+ KEYS = {
11
+ 'h' => :left!, 'l' => :right!, 'k' => :up!, 'j' => :down!,
12
+ Key::LEFT => :left!, Key::RIGHT => :right!,
13
+ Key::UP => :up!, Key::DOWN => :down!,
14
+ '+' => :larger!, '-' => :smaller!,
15
+ 'q' => :quit
16
+ }
17
+
18
+ game = Text2048::Game.new(Text2048::CursesView.new)
19
+ game.start
20
+ game.draw
21
+
22
+ loop do
23
+ game.game_over if game.lose?
24
+ command = KEYS[Curses.getch]
25
+ game.input(command) if command
26
+ case command
27
+ when :left!, :right!, :up!, :down!
28
+ game.generate
29
+ game.draw
30
+ else
31
+ # noop
32
+ end
33
+ end
@@ -0,0 +1,72 @@
1
+ Feature: Move down
2
+ Scenario: Move empty board
3
+ Given a board:
4
+ """
5
+ _ _ _ _
6
+ _ _ _ _
7
+ _ _ _ _
8
+ _ _ _ _
9
+ """
10
+ When I move the board down
11
+ Then the board is:
12
+ """
13
+ _ _ _ _
14
+ _ _ _ _
15
+ _ _ _ _
16
+ _ _ _ _
17
+ """
18
+ And the score is 0
19
+
20
+ Scenario: Move numbers
21
+ Given a board:
22
+ """
23
+ 2 _ _ _
24
+ _ 2 _ _
25
+ _ _ 2 _
26
+ _ _ _ 2
27
+ """
28
+ When I move the board down
29
+ Then the board is:
30
+ """
31
+ _ _ _ _
32
+ _ _ _ _
33
+ _ _ _ _
34
+ 2 2 2 2
35
+ """
36
+ And the score is 0
37
+
38
+ Scenario: Merge numbers
39
+ Given a board:
40
+ """
41
+ 2 _ _ _
42
+ _ 2 2 _
43
+ 2 _ _ 2
44
+ _ _ 2 _
45
+ """
46
+ When I move the board down
47
+ Then the board is:
48
+ """
49
+ _ _ _ _
50
+ _ _ _ _
51
+ _ _ _ _
52
+ 4 2 4 2
53
+ """
54
+ And the score is 8
55
+
56
+ Scenario: Move and merge mix of numbers
57
+ Given a board:
58
+ """
59
+ 2 _ _ 2
60
+ _ 2 4 _
61
+ 4 _ _ _
62
+ _ _ 2 2
63
+ """
64
+ When I move the board down
65
+ Then the board is:
66
+ """
67
+ _ _ _ _
68
+ _ _ _ _
69
+ 2 _ 4 _
70
+ 4 2 2 4
71
+ """
72
+ And the score is 4
@@ -0,0 +1,72 @@
1
+ Feature: Move left
2
+ Scenario: Move empty board
3
+ Given a board:
4
+ """
5
+ _ _ _ _
6
+ _ _ _ _
7
+ _ _ _ _
8
+ _ _ _ _
9
+ """
10
+ When I move the board to the left
11
+ Then the board is:
12
+ """
13
+ _ _ _ _
14
+ _ _ _ _
15
+ _ _ _ _
16
+ _ _ _ _
17
+ """
18
+ And the score is 0
19
+
20
+ Scenario: Move numbers
21
+ Given a board:
22
+ """
23
+ _ _ _ 2
24
+ _ _ 2 _
25
+ _ 2 _ _
26
+ 2 _ _ _
27
+ """
28
+ When I move the board to the left
29
+ Then the board is:
30
+ """
31
+ 2 _ _ _
32
+ 2 _ _ _
33
+ 2 _ _ _
34
+ 2 _ _ _
35
+ """
36
+ And the score is 0
37
+
38
+ Scenario: Merge numbers
39
+ Given a board:
40
+ """
41
+ _ 2 _ 2
42
+ _ _ 2 _
43
+ 2 _ 2 _
44
+ _ 2 _ _
45
+ """
46
+ When I move the board to the left
47
+ Then the board is:
48
+ """
49
+ 4 _ _ _
50
+ 2 _ _ _
51
+ 4 _ _ _
52
+ 2 _ _ _
53
+ """
54
+ And the score is 8
55
+
56
+ Scenario: Move and merge mix of numbers
57
+ Given a board:
58
+ """
59
+ _ 4 _ 2
60
+ 2 _ 2 _
61
+ 2 _ 4 _
62
+ _ 2 _ _
63
+ """
64
+ When I move the board to the left
65
+ Then the board is:
66
+ """
67
+ 4 2 _ _
68
+ 4 _ _ _
69
+ 2 4 _ _
70
+ 2 _ _ _
71
+ """
72
+ And the score is 4
@@ -0,0 +1,72 @@
1
+ Feature: Move right
2
+ Scenario: Move empty board
3
+ Given a board:
4
+ """
5
+ _ _ _ _
6
+ _ _ _ _
7
+ _ _ _ _
8
+ _ _ _ _
9
+ """
10
+ When I move the board to the right
11
+ Then the board is:
12
+ """
13
+ _ _ _ _
14
+ _ _ _ _
15
+ _ _ _ _
16
+ _ _ _ _
17
+ """
18
+ And the score is 0
19
+
20
+ Scenario: Move numbers
21
+ Given a board:
22
+ """
23
+ 2 _ _ _
24
+ _ 2 _ _
25
+ _ _ 2 _
26
+ _ _ _ 2
27
+ """
28
+ When I move the board to the right
29
+ Then the board is:
30
+ """
31
+ _ _ _ 2
32
+ _ _ _ 2
33
+ _ _ _ 2
34
+ _ _ _ 2
35
+ """
36
+ And the score is 0
37
+
38
+ Scenario: Merge numbers
39
+ Given a board:
40
+ """
41
+ 2 _ 2 _
42
+ _ 2 _ _
43
+ _ 2 _ 2
44
+ _ _ _ 2
45
+ """
46
+ When I move the board to the right
47
+ Then the board is:
48
+ """
49
+ _ _ _ 4
50
+ _ _ _ 2
51
+ _ _ _ 4
52
+ _ _ _ 2
53
+ """
54
+ And the score is 8
55
+
56
+ Scenario: Move and merge mix of numbers
57
+ Given a board:
58
+ """
59
+ 4 _ 2 _
60
+ _ 2 _ 2
61
+ _ 2 _ 4
62
+ _ _ _ 2
63
+ """
64
+ When I move the board to the right
65
+ Then the board is:
66
+ """
67
+ _ _ 4 2
68
+ _ _ _ 4
69
+ _ _ 2 4
70
+ _ _ _ 2
71
+ """
72
+ And the score is 4
@@ -0,0 +1,72 @@
1
+ Feature: Move up
2
+ Scenario: Move empty board
3
+ Given a board:
4
+ """
5
+ _ _ _ _
6
+ _ _ _ _
7
+ _ _ _ _
8
+ _ _ _ _
9
+ """
10
+ When I move the board up
11
+ Then the board is:
12
+ """
13
+ _ _ _ _
14
+ _ _ _ _
15
+ _ _ _ _
16
+ _ _ _ _
17
+ """
18
+ And the score is 0
19
+
20
+ Scenario: Move numbers
21
+ Given a board:
22
+ """
23
+ 2 _ _ _
24
+ _ 2 _ _
25
+ _ _ 2 _
26
+ _ _ _ 2
27
+ """
28
+ When I move the board up
29
+ Then the board is:
30
+ """
31
+ 2 2 2 2
32
+ _ _ _ _
33
+ _ _ _ _
34
+ _ _ _ _
35
+ """
36
+ And the score is 0
37
+
38
+ Scenario: Merge numbers
39
+ Given a board:
40
+ """
41
+ 2 _ _ _
42
+ _ 2 2 _
43
+ 2 _ _ 2
44
+ _ _ 2 _
45
+ """
46
+ When I move the board up
47
+ Then the board is:
48
+ """
49
+ 4 2 4 2
50
+ _ _ _ _
51
+ _ _ _ _
52
+ _ _ _ _
53
+ """
54
+ And the score is 8
55
+
56
+ Scenario: Move and merge mix of numbers
57
+ Given a board:
58
+ """
59
+ 2 _ _ 2
60
+ _ 2 4 _
61
+ 4 _ _ _
62
+ _ _ 2 2
63
+ """
64
+ When I move the board up
65
+ Then the board is:
66
+ """
67
+ 2 2 4 4
68
+ 4 _ 2 _
69
+ _ _ _ _
70
+ _ _ _ _
71
+ """
72
+ And the score is 4
@@ -0,0 +1,33 @@
1
+ require 'text2048'
2
+
3
+ Given(/^a board:$/) do |string|
4
+ layout = string.split("\n").reduce([]) do |result, row|
5
+ result << row.split(' ').map(&:to_i)
6
+ end
7
+ @game = Text2048::Game.new(Text2048::TextView.new(output), layout)
8
+ end
9
+
10
+ When(/^I move the board to the right$/) do
11
+ @game.right!
12
+ end
13
+
14
+ When(/^I move the board to the left$/) do
15
+ @game.left!
16
+ end
17
+
18
+ When(/^I move the board up$/) do
19
+ @game.up!
20
+ end
21
+
22
+ When(/^I move the board down$/) do
23
+ @game.down!
24
+ end
25
+
26
+ Then(/^the board is:$/) do |string|
27
+ @game.draw
28
+ output.messages.should eq(string)
29
+ end
30
+
31
+ Then(/^the score is (\d+)$/) do |score|
32
+ @game.score.should eq(score.to_i)
33
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+
3
+ # Dummy output
4
+ class Output
5
+ def messages
6
+ @messages ||= ''
7
+ end
8
+
9
+ def puts(message)
10
+ messages << message
11
+ end
12
+ end
13
+
14
+ def output
15
+ @output ||= Output.new
16
+ end
@@ -0,0 +1,116 @@
1
+ # encoding: utf-8
2
+
3
+ require 'text2048/tile'
4
+ require 'text2048/tiles'
5
+
6
+ module Text2048
7
+ # Game board
8
+ class Board
9
+ attr_reader :tiles
10
+
11
+ def initialize(tiles = nil)
12
+ @tiles = Array.new(4) { Array.new(4) { Tile.new(0) } }
13
+ if tiles
14
+ load_tiles(tiles)
15
+ else
16
+ 2.times { generate }
17
+ end
18
+ end
19
+
20
+ def initialize_copy(board)
21
+ @tiles = board.tiles.dup
22
+ end
23
+
24
+ def layout
25
+ @tiles.map do |row|
26
+ row.map { |each| each.to_i }
27
+ end
28
+ end
29
+
30
+ def right!
31
+ move! :right
32
+ end
33
+
34
+ def left!
35
+ move! :left
36
+ end
37
+
38
+ def up!
39
+ transpose { move! :left }
40
+ end
41
+
42
+ def down!
43
+ transpose { move! :right }
44
+ end
45
+
46
+ def ==(other)
47
+ @tiles.zip(other.tiles).reduce(true) do |result, each|
48
+ result && Tiles.new(each[0]) == Tiles.new(each[1])
49
+ end
50
+ end
51
+
52
+ def merged_tiles
53
+ result = []
54
+ @tiles.each_with_index do |row, y|
55
+ row.each_with_index do |each, x|
56
+ result << [y, x] if each.status == :merged
57
+ end
58
+ end
59
+ result
60
+ end
61
+
62
+ def generated_tiles
63
+ result = []
64
+ @tiles.each_with_index do |row, y|
65
+ row.each_with_index do |each, x|
66
+ result << [y, x] if each.status == :generated
67
+ end
68
+ end
69
+ result
70
+ end
71
+
72
+ def generate
73
+ loop do
74
+ x = rand(4)
75
+ y = rand(4)
76
+ if @tiles[y][x] == 0
77
+ @tiles[y][x] = Tile.new(rand < 0.8 ? 2 : 4, :generated)
78
+ return
79
+ end
80
+ end
81
+ end
82
+
83
+ def numbers
84
+ tiles.reduce([]) do |result, row|
85
+ result + row.select { |each| each != 0 }
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def move!(direction)
92
+ score = 0
93
+ @tiles.map! do |each|
94
+ row, sc = Tiles.new(each).__send__ direction
95
+ score += sc
96
+ row
97
+ end
98
+ score
99
+ end
100
+
101
+ def transpose
102
+ @tiles = @tiles.transpose
103
+ score = yield
104
+ @tiles = @tiles.transpose
105
+ score
106
+ end
107
+
108
+ def load_tiles(tiles)
109
+ tiles.each_with_index do |row, y|
110
+ row.each_with_index do |number, x|
111
+ @tiles[y][x] = Tile.new(number)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,120 @@
1
+ # encoding: utf-8
2
+
3
+ require 'curses'
4
+
5
+ module Text2048
6
+ # Shows tiles in curses.
7
+ class CursesTile
8
+ attr_reader :width
9
+ attr_reader :height
10
+ attr_reader :color
11
+
12
+ include Curses
13
+
14
+ DEFAULT_HEIGHT = 3
15
+ DEFAULT_WIDTH = 5
16
+
17
+ def initialize(value, y, x, color, scale = 1)
18
+ @value = value.to_i
19
+ @y = y
20
+ @x = x
21
+ @color = color
22
+ @height = (DEFAULT_HEIGHT * scale).to_i
23
+ @width = (DEFAULT_WIDTH * scale).to_i
24
+ end
25
+
26
+ def show
27
+ draw_box
28
+ attron(color_pair(@color + 100)) { fill }
29
+ attron(color_pair(@color)) { draw_number }
30
+ self
31
+ end
32
+
33
+ def pop1
34
+ setpos(yc - 1, xc - 1)
35
+ addstr('.' * (@width + 2))
36
+
37
+ (0..(@height - 1)).each do |dy|
38
+ setpos(yc + dy, xc - 1)
39
+ addstr('.')
40
+ setpos(yc + dy, xc + @width)
41
+ addstr('.')
42
+ end
43
+
44
+ setpos(yc + @height, xc - 1)
45
+ addstr('.' * (@width + 2))
46
+ end
47
+
48
+ def pop2
49
+ draw_box
50
+ refresh
51
+ end
52
+
53
+ def zoom1
54
+ attron(color_pair(COLOR_BLACK + 100)) { fill }
55
+ attron(color_pair(@color)) { draw_number }
56
+ refresh
57
+ end
58
+
59
+ def zoom2
60
+ setpos(yc + @height / 2 - 1, xc + @width / 2 - 1)
61
+ attron(color_pair(@color + 100)) { addstr('...') }
62
+ setpos(yc + @height / 2, xc + @width / 2 - 1)
63
+ attron(color_pair(@color + 100)) { addstr('...') }
64
+ setpos(yc + @height / 2 + 1, xc + @width / 2 - 1)
65
+ attron(color_pair(@color + 100)) { addstr('...') }
66
+
67
+ attron(color_pair(@color)) { draw_number }
68
+
69
+ refresh
70
+ end
71
+
72
+ def zoom3
73
+ attron(color_pair(@color + 100)) { fill }
74
+ attron(color_pair(@color)) { draw_number }
75
+ refresh
76
+ end
77
+
78
+ private
79
+
80
+ def yc
81
+ (@height + 1) * @y + 2
82
+ end
83
+
84
+ def xc
85
+ (@width + 1) * @x + 1
86
+ end
87
+
88
+ def draw_box
89
+ setpos(yc - 1, xc - 1)
90
+ addstr("+#{'-' * @width}+")
91
+
92
+ (0..(@height - 1)).each do |dy|
93
+ setpos(yc + dy, xc - 1)
94
+ addstr('|')
95
+ setpos(yc + dy, xc + @width)
96
+ addstr('|')
97
+ end
98
+
99
+ setpos(yc + @height, xc - 1)
100
+ addstr("+#{'-' * @width}+")
101
+ end
102
+
103
+ def fill
104
+ (0..(@height - 1)).each do |dy|
105
+ setpos(yc + dy, xc)
106
+ if @value != 0 && dy == @height / 2
107
+ addstr @value.to_s.center(@width)
108
+ else
109
+ addstr('.' * @width)
110
+ end
111
+ end
112
+ end
113
+
114
+ def draw_number
115
+ return if @value == 0
116
+ setpos(yc + @height / 2, xc)
117
+ addstr @value.to_s.center(@width)
118
+ end
119
+ end
120
+ end