tic-tac-toe-ruby 0.0.1
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 +7 -0
- data/LICENSE +22 -0
- data/README.md +38 -0
- data/bin/tic-tac-toe +34 -0
- data/lib/tic_tac_toe.rb +69 -0
- data/lib/tic_tac_toe/board.rb +73 -0
- data/lib/tic_tac_toe/cursor.rb +41 -0
- data/lib/tic_tac_toe/player.rb +38 -0
- data/lib/tic_tac_toe/rules.rb +27 -0
- data/lib/tic_tac_toe/tui.rb +57 -0
- data/lib/tic_tac_toe/tui/board.rb +72 -0
- data/lib/tic_tac_toe/tui/cursor.rb +34 -0
- data/lib/tic_tac_toe/tui/setup.rb +39 -0
- data/lib/tic_tac_toe/tui/status.rb +51 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# Tic-tac-toe
|
2
|
+
|
3
|
+
Play a game of tic-tac-toe in your Terminal.
|
4
|
+
|
5
|
+

|
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.
|
data/bin/tic-tac-toe
ADDED
@@ -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
|
data/lib/tic_tac_toe.rb
ADDED
@@ -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:
|