tic-tac-toe-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1fc892ec07147aa303cc7bd17d4c6c18519ce938
4
+ data.tar.gz: 800d2eda86be8a1502d37a942682c42fb3e68ea0
5
+ SHA512:
6
+ metadata.gz: 9f632dbe6cea72d57d0f3de6403d9e720ea04893996ac6d69dc118cd98863a9e8865b2dcea129477d8e275153e9e8bfd445ed6048b68ef380f231bb533ac86ed
7
+ data.tar.gz: 67735825fc81fda8cd6564af3f3f12d4fdcb36e5274acecb162b435fcdb517d160274bc6cd27a4df9f1e3f58b626ca8843b6a3eab0971d690f89ef4c153ef7d0
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Janko Marohnić
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,38 @@
1
+ # Tic-tac-toe
2
+
3
+ Play a game of tic-tac-toe in your Terminal.
4
+
5
+ ![Tic-tac-toe preview](/preview.png)
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ $ gem install tic-tac-toe-ruby
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```sh
16
+ $ tic-tac-toe --size 10 --players 3 --goal 4
17
+ ```
18
+ ```
19
+ $ tic-tac-toe --help
20
+ USAGE: tic-tac-toe [options]
21
+
22
+ Moving: Arrow keys or h/j/k/l
23
+ Placing symbol: <Space>
24
+ Exit: Ctrl-C
25
+
26
+ -n, --size N Size of the board (default: 3)
27
+ -p, --players N The number of players (default: 2, max: 5)
28
+ -g, --goal N The number symbols in a row is required (default: 3)
29
+ ```
30
+
31
+ ## Implementation
32
+
33
+ The game was implemented in Ruby using the [`curses`](https://github.com/ruby/curses)
34
+ gem, which binds to Curses C library.
35
+
36
+ ## License
37
+
38
+ MIT.
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "optparse"
4
+
5
+ options = {}
6
+
7
+ opt_parser = OptionParser.new do |opts|
8
+ opts.banner = <<-EOS
9
+ USAGE: tic-tac-toe [options]
10
+
11
+ Moving: Arrow keys or h/j/k/l
12
+ Placing symbol: <Space>
13
+ Exit: Ctrl-C
14
+
15
+ EOS
16
+
17
+ opts.on("-n", "--size N", "Size of the board (default: 3)") do |n|
18
+ options[:size] = Integer(n)
19
+ end
20
+
21
+ opts.on("-p", "--players N", "The number of players (default: 2, max: 5)") do |n|
22
+ options[:number_of_players] = Integer(n)
23
+ end
24
+
25
+ opts.on("-g", "--goal N", "The number symbols in a row is required (default: 3)") do |n|
26
+ options[:goal] = Integer(n)
27
+ end
28
+ end
29
+
30
+ opt_parser.parse!(ARGV)
31
+
32
+ require "tic_tac_toe"
33
+
34
+ TicTacToe.new(options).start
@@ -0,0 +1,69 @@
1
+ require "tic_tac_toe/board"
2
+ require "tic_tac_toe/player"
3
+ require "tic_tac_toe/cursor"
4
+ require "tic_tac_toe/rules"
5
+ require "tic_tac_toe/tui"
6
+
7
+ class TicTacToe
8
+ def initialize(size: 3, number_of_players: 2, goal: 3)
9
+ @board = TicTacToe::Board.new(size)
10
+ @players = TicTacToe::PlayerList.new(number_of_players)
11
+ @cursor = TicTacToe::Cursor.new(@board)
12
+ @rules = TicTacToe::Rules.new(goal)
13
+ end
14
+
15
+ def start
16
+ @tui = TicTacToe::TUI.new
17
+ setup
18
+
19
+ @tui.listen do |key|
20
+ handle_keypress(key) unless game_over?
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def setup
27
+ @tui.board!(@board.to_s)
28
+ @tui.cursor!(@cursor.field)
29
+ @tui.status!(@players.to_a, @players.current)
30
+ end
31
+
32
+ def handle_keypress(key)
33
+ case key
34
+ when :UP, :DOWN, :RIGHT, :LEFT
35
+ move(key.downcase)
36
+ when "h", "j", "k", "l"
37
+ direction = {"h" => :left, "j" => :down, "k" => :up, "l" => :right}[key]
38
+ move(direction)
39
+ when " "
40
+ mark
41
+ if game_over?
42
+ @tui.cursor!(@cursor.field, hidden: true)
43
+ @tui.status!(@players.to_a)
44
+ end
45
+ when :RESIZE
46
+ @tui.clear
47
+ setup
48
+ end
49
+ end
50
+
51
+ def move(direction)
52
+ @cursor = @cursor.move(direction)
53
+ @tui.cursor!(@cursor.field)
54
+ end
55
+
56
+ def mark
57
+ return if @board.marked?(@cursor.field)
58
+
59
+ @board = @board.mark(@cursor.field, @players.current.symbol)
60
+ @tui.board!(@board.to_s)
61
+
62
+ @players.next!
63
+ @tui.status!(@players.to_a, @players.current)
64
+ end
65
+
66
+ def game_over?
67
+ @rules.game_over?(@board, @players)
68
+ end
69
+ end
@@ -0,0 +1,73 @@
1
+ require "matrix"
2
+ require "tty"
3
+
4
+ class TicTacToe
5
+ class Board
6
+ BLANK_SYMBOL = " "
7
+
8
+ def initialize(matrix_or_size)
9
+ @matrix =
10
+ case matrix_or_size
11
+ when Matrix then matrix_or_size
12
+ when Integer then Matrix.build(matrix_or_size) { BLANK_SYMBOL }
13
+ end
14
+ end
15
+
16
+ def fields
17
+ @matrix.each_with_index.map { |_, *field| field }
18
+ end
19
+
20
+ def size
21
+ @matrix.row_count
22
+ end
23
+
24
+ def mark(field, symbol)
25
+ new_matrix = @matrix.dup
26
+ new_matrix.send(:[]=, *field, symbol)
27
+ TicTacToe::Board.new(new_matrix)
28
+ end
29
+
30
+ def marked?(field)
31
+ @matrix[*field] != BLANK_SYMBOL
32
+ end
33
+
34
+ def to_s
35
+ table = TTY::Table.new(@matrix.to_a)
36
+ table.render(:unicode) do |renderer|
37
+ renderer.border.separator = :each_row
38
+ renderer.padding = [0, 1, 0, 1]
39
+ end
40
+ end
41
+
42
+ def rows
43
+ @matrix.to_a
44
+ end
45
+
46
+ def columns
47
+ @matrix.transpose.to_a
48
+ end
49
+
50
+ def diagonals
51
+ (right_diagonals + left_diagonals)
52
+ .map { |diagonal| diagonal.map { |field| @matrix[*field] } }
53
+ end
54
+
55
+ private
56
+
57
+ # - - - -
58
+ # x - - -
59
+ # - x - -
60
+ # - - x -
61
+ def right_diagonals
62
+ fields.group_by { |x, y| x - y }.values
63
+ end
64
+
65
+ # - - x -
66
+ # - x - -
67
+ # x - - -
68
+ # - - - -
69
+ def left_diagonals
70
+ fields.group_by { |x, y| x + y }.values
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,41 @@
1
+ class TicTacToe
2
+ class Cursor
3
+ OutOfBounds = Class.new(StandardError)
4
+
5
+ attr_reader :field
6
+
7
+ def initialize(field = nil, board)
8
+ @field = field || board.fields.first
9
+ @board = board
10
+
11
+ raise OutOfBounds unless @board.fields.include?(@field)
12
+ end
13
+
14
+ def move(direction)
15
+ send(direction)
16
+ rescue TicTacToe::Cursor::OutOfBounds
17
+ self
18
+ end
19
+
20
+ private
21
+
22
+ def up
23
+ TicTacToe::Cursor.new([x - 1, y], @board)
24
+ end
25
+
26
+ def down
27
+ TicTacToe::Cursor.new([x + 1, y], @board)
28
+ end
29
+
30
+ def right
31
+ TicTacToe::Cursor.new([x, y + 1], @board)
32
+ end
33
+
34
+ def left
35
+ TicTacToe::Cursor.new([x, y - 1], @board)
36
+ end
37
+
38
+ def x; field[0]; end
39
+ def y; field[1]; end
40
+ end
41
+ end
@@ -0,0 +1,38 @@
1
+ class TicTacToe
2
+ Player = Struct.new(:symbol, :color)
3
+
4
+ class PlayerList
5
+ AVAILABLE_PLAYERS = [
6
+ Player.new("x", :red),
7
+ Player.new("○", :blue),
8
+ Player.new("△", :yellow),
9
+ Player.new("□", :green),
10
+ Player.new("◇", :magenta),
11
+ ]
12
+
13
+ include Enumerable
14
+
15
+ def initialize(number)
16
+ @players = AVAILABLE_PLAYERS.first(number)
17
+ @current_idx = 0
18
+ end
19
+
20
+ def each(&block)
21
+ @players.each(&block)
22
+ end
23
+
24
+ def current
25
+ @players[@current_idx]
26
+ end
27
+
28
+ def next!
29
+ @current_idx = (@current_idx + 1) % @players.count
30
+ end
31
+
32
+ def colors
33
+ @players.inject({}) do |colors, player|
34
+ colors.update(player.symbol => player.color)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,27 @@
1
+ class TicTacToe
2
+ class Rules
3
+ def initialize(goal)
4
+ @goal = goal
5
+ end
6
+
7
+ def game_over?(board, players)
8
+ players.any? { |player| player_won?(player, board) } or board_full?(board)
9
+ end
10
+
11
+ private
12
+
13
+ def board_full?(board)
14
+ board.fields.all? { |field| board.marked?(field) }
15
+ end
16
+
17
+ def player_won?(player, board)
18
+ (board.rows + board.columns + board.diagonals).any? do |sequence|
19
+ winning_adjacency?(player.symbol, sequence)
20
+ end
21
+ end
22
+
23
+ def winning_adjacency?(symbol, sequence)
24
+ /#{symbol}{#{@goal},}/ === sequence.join
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,57 @@
1
+ require "curses"
2
+
3
+ require "tic_tac_toe/tui/setup"
4
+ require "tic_tac_toe/tui/board"
5
+ require "tic_tac_toe/tui/cursor"
6
+ require "tic_tac_toe/tui/status"
7
+
8
+ class TicTacToe
9
+ class TUI # Textual User Inteface
10
+ def initialize
11
+ TUI::Setup.call
12
+ end
13
+
14
+ def listen
15
+ loop do
16
+ key = getch
17
+ yield key
18
+ end
19
+ rescue Interrupt
20
+ end
21
+
22
+ def board!(*args)
23
+ @board ||= TUI::Board.new(*args)
24
+ @board.update(*args)
25
+ end
26
+
27
+ def cursor!(*args)
28
+ @cursor ||= TUI::Cursor.new(@board.window)
29
+ @cursor.update(*args)
30
+ end
31
+
32
+ def status!(*args)
33
+ @status ||= TUI::Status.new(@board.window)
34
+ @status.update(*args)
35
+ end
36
+
37
+ def clear
38
+ @board.window.close
39
+ Curses.clear
40
+ Curses.refresh
41
+
42
+ @board = @cursor = @status = nil
43
+ end
44
+
45
+ private
46
+
47
+ def getch
48
+ case key = @board.window.getch
49
+ when String # printable character
50
+ key
51
+ when Integer # special key
52
+ Curses::Key.constants
53
+ .find { |name| Curses::Key.const_get(name) == key }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,72 @@
1
+ require "strscan"
2
+
3
+ class TicTacToe
4
+ class TUI
5
+ class Board
6
+ SYMBOL_COLORS = {
7
+ "x" => Curses::COLOR_RED,
8
+ "○" => Curses::COLOR_BLUE,
9
+ "△" => Curses::COLOR_YELLOW,
10
+ "□" => Curses::COLOR_GREEN,
11
+ "◇" => Curses::COLOR_MAGENTA,
12
+ }
13
+ COLOR_GRAY = 8
14
+
15
+ attr_reader :window
16
+
17
+ def initialize(board)
18
+ @window = new_window(board)
19
+ update(board)
20
+ end
21
+
22
+ def update(board)
23
+ symbols = SYMBOL_COLORS.keys
24
+ scanner = StringScanner.new(board)
25
+
26
+ redraw do
27
+ loop do
28
+ box_part = scanner.scan_until(/(?=#{Regexp.union(*symbols, /\Z/)})/)
29
+ write_box(box_part)
30
+
31
+ break if scanner.eos?
32
+
33
+ symbol = scanner.getch
34
+ write_symbol(symbol, SYMBOL_COLORS.fetch(symbol))
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def new_window(board)
42
+ height = board.lines.count
43
+ width = board.lines.first.length
44
+ top = (Curses.lines / 2) - (height / 2)
45
+ left = (Curses.cols / 2) - (width / 2)
46
+
47
+ Curses::Window.new(height, width, top, left).tap do |window|
48
+ window.keypad(true)
49
+ end
50
+ end
51
+
52
+ def write_box(string)
53
+ @window.attron(Curses.color_pair(COLOR_GRAY)) do
54
+ @window.addstr(string)
55
+ end
56
+ end
57
+
58
+ def write_symbol(symbol, color)
59
+ @window.attron(Curses.color_pair(color)) do
60
+ @window.addstr(symbol)
61
+ end
62
+ end
63
+
64
+ def redraw
65
+ cursor_position = [@window.cury, @window.curx]
66
+ @window.clear
67
+ yield
68
+ @window.setpos(*cursor_position)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,34 @@
1
+ class TicTacToe
2
+ class TUI
3
+ class Cursor
4
+ X_SPACING = 2
5
+ X_MARGIN = 1
6
+
7
+ Y_SPACING = 4
8
+ Y_MARGIN = 2
9
+
10
+ def initialize(window)
11
+ @window = window
12
+ end
13
+
14
+ def update(field, hidden: false)
15
+ update_position(field)
16
+ update_visibility(hidden)
17
+ end
18
+
19
+ private
20
+
21
+ def update_position(field)
22
+ x, y = field
23
+ position = [x * X_SPACING + X_MARGIN, y * Y_SPACING + Y_MARGIN]
24
+ @window.setpos(*position)
25
+ end
26
+
27
+ def update_visibility(hidden)
28
+ if hidden
29
+ Curses.curs_set(0)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ class TicTacToe
2
+ class TUI
3
+ class Setup
4
+ def self.call
5
+ Curses.init_screen
6
+ at_exit { Curses.close_screen }
7
+
8
+ setup_keypress_input!
9
+ enable_colors!
10
+ setup_basic_colors!
11
+ end
12
+
13
+ private
14
+
15
+ def self.setup_keypress_input!
16
+ Curses.cbreak # listen for single keypresses (instead of newline)
17
+ Curses.noecho # don't echo the entered keys
18
+
19
+ Curses.nonl # recognize the "return" key
20
+ Curses.stdscr.keypad(true) # recognize special keys (e.g. arrow keys)
21
+ end
22
+
23
+ def self.enable_colors!
24
+ Curses.start_color # enable colors
25
+ Curses.use_default_colors # use terminal's default colors (e.g. background)
26
+ end
27
+
28
+ def self.setup_basic_colors!
29
+ Curses.init_pair(Curses::COLOR_RED, Curses::COLOR_RED, -1)
30
+ Curses.init_pair(Curses::COLOR_BLUE, Curses::COLOR_BLUE, -1)
31
+ Curses.init_pair(Curses::COLOR_GREEN, Curses::COLOR_GREEN, -1)
32
+ Curses.init_pair(Curses::COLOR_YELLOW, Curses::COLOR_YELLOW, -1)
33
+ Curses.init_pair(Curses::COLOR_MAGENTA, Curses::COLOR_MAGENTA, -1)
34
+ Curses.init_pair(Curses::COLOR_CYAN, Curses::COLOR_CYAN, -1)
35
+ Curses.init_pair(8, 235, -1) # gray
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,51 @@
1
+ require "strscan"
2
+
3
+ class TicTacToe
4
+ class TUI
5
+ class Status
6
+ def initialize(window)
7
+ @window = window
8
+ end
9
+
10
+ def update(players, current_player = nil)
11
+ cursor_position = [@window.cury, @window.curx]
12
+ do_update(players, current_player)
13
+ Curses.refresh
14
+ @window.setpos(*cursor_position)
15
+ end
16
+
17
+ private
18
+
19
+ def do_update(players, current_player = nil)
20
+ max_cols = (Curses.lines / 2 - @window.maxy / 2)
21
+ start = (max_cols - (players.count * 2 - 1)) / 2
22
+
23
+ players.each.with_index do |player, idx|
24
+ line = "Player #{player.symbol}".center(Curses.cols)[1..-1]
25
+ scanner = StringScanner.new(line)
26
+
27
+ Curses.setpos(start + idx * 2, 0)
28
+
29
+ Curses.attron(Curses::A_BOLD) if player == current_player
30
+
31
+ text = scanner.scan_until(/(?=#{player.symbol})/)
32
+ Curses.addstr(text)
33
+
34
+ write_symbol(scanner.getch, player.color)
35
+
36
+ text = scanner.scan_until(/\Z/)
37
+ Curses.addstr(text)
38
+
39
+ Curses.attroff(Curses::A_BOLD) if player == current_player
40
+ end
41
+ end
42
+
43
+ def write_symbol(symbol, color_name)
44
+ color = Curses.const_get("COLOR_#{color_name.upcase}")
45
+ Curses.attron(Curses.color_pair(color)) do
46
+ Curses.addstr(symbol)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tic-tac-toe-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Janko Marohnić
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-11 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.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.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tty
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 0.1.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: cucumber
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 1.3.17
76
+ - - "<"
77
+ - !ruby/object:Gem::Version
78
+ version: '2.0'
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 1.3.17
86
+ - - "<"
87
+ - !ruby/object:Gem::Version
88
+ version: '2.0'
89
+ description: Play the tic-tac-toe game in the Terminal. Supports fields of any size,
90
+ and can be played with up to 5 players.
91
+ email:
92
+ - janko.marohnic@gmail.com
93
+ executables:
94
+ - tic-tac-toe
95
+ extensions: []
96
+ extra_rdoc_files: []
97
+ files:
98
+ - LICENSE
99
+ - README.md
100
+ - bin/tic-tac-toe
101
+ - lib/tic_tac_toe.rb
102
+ - lib/tic_tac_toe/board.rb
103
+ - lib/tic_tac_toe/cursor.rb
104
+ - lib/tic_tac_toe/player.rb
105
+ - lib/tic_tac_toe/rules.rb
106
+ - lib/tic_tac_toe/tui.rb
107
+ - lib/tic_tac_toe/tui/board.rb
108
+ - lib/tic_tac_toe/tui/cursor.rb
109
+ - lib/tic_tac_toe/tui/setup.rb
110
+ - lib/tic_tac_toe/tui/status.rb
111
+ homepage: https://github.com/janko-m/tic-tac-toe
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '2.0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.2.2
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Play the tic-tac-toe game in the Terminal.
135
+ test_files: []
136
+ has_rdoc: