glimmer-dsl-libui 0.2.20 → 0.2.24
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/CHANGELOG.md +25 -0
- data/README.md +360 -24
- data/VERSION +1 -1
- data/examples/area_gallery.rb +1 -1
- data/examples/area_gallery2.rb +1 -1
- data/examples/area_gallery3.rb +1 -1
- data/examples/area_gallery4.rb +1 -1
- data/examples/meta_example.rb +1 -1
- data/examples/snake/model/apple.rb +33 -0
- data/examples/snake/model/game.rb +51 -0
- data/examples/snake/model/snake.rb +95 -0
- data/examples/snake/model/vertebra.rb +22 -0
- data/examples/snake/presenter/cell.rb +27 -0
- data/examples/snake/presenter/grid.rb +47 -0
- data/examples/snake.rb +90 -0
- data/examples/tetris/model/game.rb +1 -1
- data/examples/tetris.rb +23 -3
- data/examples/tic_tac_toe/board.rb +145 -0
- data/examples/tic_tac_toe/cell.rb +48 -0
- data/examples/tic_tac_toe.rb +85 -0
- data/glimmer-dsl-libui.gemspec +0 -0
- data/lib/glimmer/libui.rb +35 -6
- metadata +12 -2
@@ -0,0 +1,33 @@
|
|
1
|
+
class Snake
|
2
|
+
module Model
|
3
|
+
class Apple
|
4
|
+
attr_reader :game
|
5
|
+
attr_accessor :row, :column
|
6
|
+
|
7
|
+
def initialize(game)
|
8
|
+
@game = game
|
9
|
+
end
|
10
|
+
|
11
|
+
# generates a new location from scratch or via dependency injection of what cell is (for testing purposes)
|
12
|
+
def generate(initial_row: nil, initial_column: nil)
|
13
|
+
if initial_row && initial_column
|
14
|
+
self.row, self.column = initial_row, initial_column
|
15
|
+
else
|
16
|
+
self.row, self.column = @game.height.times.zip(@game.width.times).reject do |row, column|
|
17
|
+
@game.snake.vertebrae.map {|v| [v.row, v.column]}.include?([row, column])
|
18
|
+
end.sample
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def remove
|
23
|
+
self.row = nil
|
24
|
+
self.column = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# inspect is overridden to prevent printing very long stack traces
|
28
|
+
def inspect
|
29
|
+
"#{super[0, 120]}... >"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
require_relative 'snake'
|
4
|
+
require_relative 'apple'
|
5
|
+
|
6
|
+
class Snake
|
7
|
+
module Model
|
8
|
+
class Game
|
9
|
+
WIDTH_DEFAULT = 40
|
10
|
+
HEIGHT_DEFAULT = 40
|
11
|
+
FILE_HIGH_SCORE = File.expand_path(File.join(Dir.home, '.glimmer-snake'))
|
12
|
+
|
13
|
+
attr_reader :width, :height
|
14
|
+
attr_accessor :snake, :apple, :over, :score, :high_score
|
15
|
+
alias over? over
|
16
|
+
|
17
|
+
def initialize(width = WIDTH_DEFAULT, height = HEIGHT_DEFAULT)
|
18
|
+
@width = width
|
19
|
+
@height = height
|
20
|
+
@snake = Snake.new(self)
|
21
|
+
@apple = Apple.new(self)
|
22
|
+
FileUtils.touch(FILE_HIGH_SCORE)
|
23
|
+
@high_score = File.read(FILE_HIGH_SCORE).to_i rescue 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def score=(new_score)
|
27
|
+
@score = new_score
|
28
|
+
self.high_score = @score if @score > @high_score
|
29
|
+
end
|
30
|
+
|
31
|
+
def high_score=(new_high_score)
|
32
|
+
@high_score = new_high_score
|
33
|
+
File.write(FILE_HIGH_SCORE, @high_score.to_s)
|
34
|
+
rescue => e
|
35
|
+
puts e.full_message
|
36
|
+
end
|
37
|
+
|
38
|
+
def start
|
39
|
+
self.over = false
|
40
|
+
self.score = 0
|
41
|
+
self.snake.generate
|
42
|
+
self.apple.generate
|
43
|
+
end
|
44
|
+
|
45
|
+
# inspect is overridden to prevent printing very long stack traces
|
46
|
+
def inspect
|
47
|
+
"#{super[0, 75]}... >"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require_relative 'vertebra'
|
2
|
+
|
3
|
+
class Snake
|
4
|
+
module Model
|
5
|
+
class Snake
|
6
|
+
SCORE_EAT_APPLE = 50
|
7
|
+
RIGHT_TURN_MAP = {
|
8
|
+
north: :east,
|
9
|
+
east: :south,
|
10
|
+
south: :west,
|
11
|
+
west: :north
|
12
|
+
}
|
13
|
+
LEFT_TURN_MAP = RIGHT_TURN_MAP.invert
|
14
|
+
|
15
|
+
attr_accessor :collided
|
16
|
+
alias collided? collided
|
17
|
+
|
18
|
+
attr_reader :game
|
19
|
+
# vertebrae and joins are ordered from tail to head
|
20
|
+
attr_accessor :vertebrae
|
21
|
+
|
22
|
+
def initialize(game)
|
23
|
+
@game = game
|
24
|
+
end
|
25
|
+
|
26
|
+
# generates a new snake location and orientation from scratch or via dependency injection of what head_cell and orientation are (for testing purposes)
|
27
|
+
def generate(initial_row: nil, initial_column: nil, initial_orientation: nil)
|
28
|
+
self.collided = false
|
29
|
+
initial_vertebra = Vertebra.new(snake: self, row: initial_row, column: initial_column, orientation: initial_orientation)
|
30
|
+
self.vertebrae = [initial_vertebra]
|
31
|
+
end
|
32
|
+
|
33
|
+
def length
|
34
|
+
@vertebrae.length
|
35
|
+
end
|
36
|
+
|
37
|
+
def head
|
38
|
+
@vertebrae.last
|
39
|
+
end
|
40
|
+
|
41
|
+
def tail
|
42
|
+
@vertebrae.first
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove
|
46
|
+
self.vertebrae.clear
|
47
|
+
self.joins.clear
|
48
|
+
end
|
49
|
+
|
50
|
+
def move
|
51
|
+
@old_tail = tail.dup
|
52
|
+
@new_head = head.dup
|
53
|
+
case @new_head.orientation
|
54
|
+
when :east
|
55
|
+
@new_head.column = (@new_head.column + 1) % @game.width
|
56
|
+
when :west
|
57
|
+
@new_head.column = (@new_head.column - 1) % @game.width
|
58
|
+
when :south
|
59
|
+
@new_head.row = (@new_head.row + 1) % @game.height
|
60
|
+
when :north
|
61
|
+
@new_head.row = (@new_head.row - 1) % @game.height
|
62
|
+
end
|
63
|
+
if @vertebrae.map {|v| [v.row, v.column]}.include?([@new_head.row, @new_head.column])
|
64
|
+
self.collided = true
|
65
|
+
@game.over = true
|
66
|
+
else
|
67
|
+
@vertebrae.append(@new_head)
|
68
|
+
@vertebrae.delete(tail)
|
69
|
+
if head.row == @game.apple.row && head.column == @game.apple.column
|
70
|
+
grow
|
71
|
+
@game.apple.generate
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def turn_right
|
77
|
+
head.orientation = RIGHT_TURN_MAP[head.orientation]
|
78
|
+
end
|
79
|
+
|
80
|
+
def turn_left
|
81
|
+
head.orientation = LEFT_TURN_MAP[head.orientation]
|
82
|
+
end
|
83
|
+
|
84
|
+
def grow
|
85
|
+
@game.score += SCORE_EAT_APPLE
|
86
|
+
@vertebrae.prepend(@old_tail)
|
87
|
+
end
|
88
|
+
|
89
|
+
# inspect is overridden to prevent printing very long stack traces
|
90
|
+
def inspect
|
91
|
+
"#{super[0, 150]}... >"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Snake
|
2
|
+
module Model
|
3
|
+
class Vertebra
|
4
|
+
ORIENTATIONS = %i[north east south west]
|
5
|
+
# orientation is needed for snake occuppied cells (but not apple cells)
|
6
|
+
attr_reader :snake
|
7
|
+
attr_accessor :row, :column, :orientation
|
8
|
+
|
9
|
+
def initialize(snake: , row: , column: , orientation: )
|
10
|
+
@row = row || rand(snake.game.height)
|
11
|
+
@column = column || rand(snake.game.width)
|
12
|
+
@orientation = orientation || ORIENTATIONS.sample
|
13
|
+
@snake = snake
|
14
|
+
end
|
15
|
+
|
16
|
+
# inspect is overridden to prevent printing very long stack traces
|
17
|
+
def inspect
|
18
|
+
"#{super[0, 150]}... >"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Snake
|
2
|
+
module Presenter
|
3
|
+
class Cell
|
4
|
+
COLOR_CLEAR = :white
|
5
|
+
COLOR_SNAKE = :green
|
6
|
+
COLOR_APPLE = :red
|
7
|
+
|
8
|
+
attr_reader :row, :column, :grid
|
9
|
+
attr_accessor :color
|
10
|
+
|
11
|
+
def initialize(grid: ,row: ,column: )
|
12
|
+
@row = row
|
13
|
+
@column = column
|
14
|
+
@grid = grid
|
15
|
+
end
|
16
|
+
|
17
|
+
def clear
|
18
|
+
self.color = COLOR_CLEAR unless color == COLOR_CLEAR
|
19
|
+
end
|
20
|
+
|
21
|
+
# inspect is overridden to prevent printing very long stack traces
|
22
|
+
def inspect
|
23
|
+
"#{super[0, 150]}... >"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'glimmer/data_binding/observer'
|
2
|
+
require_relative '../model/game'
|
3
|
+
require_relative 'cell'
|
4
|
+
|
5
|
+
class Snake
|
6
|
+
module Presenter
|
7
|
+
class Grid
|
8
|
+
attr_reader :game, :cells
|
9
|
+
|
10
|
+
def initialize(game = Model::Game.new)
|
11
|
+
@game = game
|
12
|
+
@cells = @game.height.times.map do |row|
|
13
|
+
@game.width.times.map do |column|
|
14
|
+
Cell.new(grid: self, row: row, column: column)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
Glimmer::DataBinding::Observer.proc do |new_vertebrae|
|
18
|
+
occupied_snake_positions = @game.snake.vertebrae.map {|v| [v.row, v.column]}
|
19
|
+
@cells.each_with_index do |row_cells, row|
|
20
|
+
row_cells.each_with_index do |cell, column|
|
21
|
+
if [@game.apple.row, @game.apple.column] == [row, column]
|
22
|
+
cell.color = Cell::COLOR_APPLE
|
23
|
+
elsif occupied_snake_positions.include?([row, column])
|
24
|
+
cell.color = Cell::COLOR_SNAKE
|
25
|
+
else
|
26
|
+
cell.clear
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end.observe(@game.snake, :vertebrae)
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear
|
34
|
+
@cells.each do |row_cells|
|
35
|
+
row_cells.each do |cell|
|
36
|
+
cell.clear
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# inspect is overridden to prevent printing very long stack traces
|
42
|
+
def inspect
|
43
|
+
"#{super[0, 75]}... >"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/examples/snake.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'glimmer-dsl-libui'
|
2
|
+
require 'glimmer/data_binding/observer'
|
3
|
+
|
4
|
+
require_relative 'snake/presenter/grid'
|
5
|
+
|
6
|
+
class Snake
|
7
|
+
CELL_SIZE = 15
|
8
|
+
SNAKE_MOVE_DELAY = 0.1
|
9
|
+
include Glimmer
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@game = Model::Game.new
|
13
|
+
@grid = Presenter::Grid.new(@game)
|
14
|
+
@game.start
|
15
|
+
create_gui
|
16
|
+
register_observers
|
17
|
+
end
|
18
|
+
|
19
|
+
def launch
|
20
|
+
@main_window.show
|
21
|
+
end
|
22
|
+
|
23
|
+
def register_observers
|
24
|
+
@game.height.times do |row|
|
25
|
+
@game.width.times do |column|
|
26
|
+
Glimmer::DataBinding::Observer.proc do |new_color|
|
27
|
+
@cell_grid[row][column].fill = new_color
|
28
|
+
end.observe(@grid.cells[row][column], :color)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
Glimmer::DataBinding::Observer.proc do |game_over|
|
33
|
+
Glimmer::LibUI.queue_main do
|
34
|
+
if game_over
|
35
|
+
msg_box('Game Over!', "Score: #{@game.score} | High Score: #{@game.high_score}")
|
36
|
+
@game.start
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end.observe(@game, :over)
|
40
|
+
|
41
|
+
Glimmer::LibUI.timer(SNAKE_MOVE_DELAY) do
|
42
|
+
unless @game.over?
|
43
|
+
@game.snake.move
|
44
|
+
@main_window.title = "Glimmer Snake (Score: #{@game.score} | High Score: #{@game.high_score})"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_gui
|
50
|
+
@cell_grid = []
|
51
|
+
@main_window = window('Glimmer Snake', @game.width * CELL_SIZE, @game.height * CELL_SIZE) {
|
52
|
+
resizable false
|
53
|
+
|
54
|
+
vertical_box {
|
55
|
+
padded false
|
56
|
+
|
57
|
+
@game.height.times do |row|
|
58
|
+
@cell_grid << []
|
59
|
+
horizontal_box {
|
60
|
+
padded false
|
61
|
+
|
62
|
+
@game.width.times do |column|
|
63
|
+
area {
|
64
|
+
@cell_grid.last << path {
|
65
|
+
square(0, 0, CELL_SIZE)
|
66
|
+
|
67
|
+
fill Presenter::Cell::COLOR_CLEAR
|
68
|
+
}
|
69
|
+
|
70
|
+
on_key_up do |area_key_event|
|
71
|
+
orientation_and_key = [@game.snake.head.orientation, area_key_event[:ext_key]]
|
72
|
+
case orientation_and_key
|
73
|
+
in [:north, :right] | [:east, :down] | [:south, :left] | [:west, :up]
|
74
|
+
@game.snake.turn_right
|
75
|
+
in [:north, :left] | [:west, :down] | [:south, :right] | [:east, :up]
|
76
|
+
@game.snake.turn_left
|
77
|
+
else
|
78
|
+
# No Op
|
79
|
+
end
|
80
|
+
end
|
81
|
+
}
|
82
|
+
end
|
83
|
+
}
|
84
|
+
end
|
85
|
+
}
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
Snake.new.launch
|
data/examples/tetris.rb
CHANGED
@@ -153,6 +153,8 @@ class Tetris
|
|
153
153
|
end
|
154
154
|
}
|
155
155
|
radio_menu_item('Rotate Left on Up Arrow') {
|
156
|
+
checked true
|
157
|
+
|
156
158
|
on_clicked do
|
157
159
|
@game.rotate_left_on_up = true
|
158
160
|
end
|
@@ -235,7 +237,19 @@ class Tetris
|
|
235
237
|
on_key_down do |key_event|
|
236
238
|
case key_event
|
237
239
|
in ext_key: :down
|
238
|
-
|
240
|
+
if OS.windows?
|
241
|
+
# rate limit downs in Windows as they go too fast when key is held
|
242
|
+
@queued_downs ||= 0
|
243
|
+
if @queued_downs < 2
|
244
|
+
@queued_downs += 1
|
245
|
+
Glimmer::LibUI.timer(0.01, repeat: false) do
|
246
|
+
@game.down! if @queued_downs < 2
|
247
|
+
@queued_downs -= 1
|
248
|
+
end
|
249
|
+
end
|
250
|
+
else
|
251
|
+
@game.down!
|
252
|
+
end
|
239
253
|
in key: ' '
|
240
254
|
@game.down!(instant: true)
|
241
255
|
in ext_key: :up
|
@@ -319,8 +333,11 @@ class Tetris
|
|
319
333
|
end
|
320
334
|
|
321
335
|
def start_moving_tetrominos_down
|
322
|
-
|
323
|
-
@
|
336
|
+
unless @tetrominos_start_moving_down
|
337
|
+
@tetrominos_start_moving_down = true
|
338
|
+
Glimmer::LibUI.timer(@game.delay) do
|
339
|
+
@game.down! if !@game.game_over? && !@game.paused?
|
340
|
+
end
|
324
341
|
end
|
325
342
|
end
|
326
343
|
|
@@ -333,6 +350,8 @@ class Tetris
|
|
333
350
|
|
334
351
|
def show_high_scores
|
335
352
|
Glimmer::LibUI.queue_main do
|
353
|
+
game_paused = !!@game.paused
|
354
|
+
@game.paused = true
|
336
355
|
if @game.high_scores.empty?
|
337
356
|
high_scores_string = "No games have been scored yet."
|
338
357
|
else
|
@@ -341,6 +360,7 @@ class Tetris
|
|
341
360
|
end.join("\n")
|
342
361
|
end
|
343
362
|
msg_box('High Scores', high_scores_string)
|
363
|
+
@game.paused = game_paused
|
344
364
|
end
|
345
365
|
end
|
346
366
|
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# Copyright (c) 2007-2021 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require_relative 'cell'
|
23
|
+
|
24
|
+
class TicTacToe
|
25
|
+
class Board
|
26
|
+
DRAW = :draw
|
27
|
+
IN_PROGRESS = :in_progress
|
28
|
+
WIN = :win
|
29
|
+
attr :winning_sign
|
30
|
+
attr_accessor :game_status
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@sign_state_machine = {nil => "X", "X" => "O", "O" => "X"}
|
34
|
+
build_grid
|
35
|
+
@winning_sign = Cell::EMPTY
|
36
|
+
@game_status = IN_PROGRESS
|
37
|
+
end
|
38
|
+
|
39
|
+
#row and column numbers are 1-based
|
40
|
+
def mark(row, column)
|
41
|
+
self[row, column].mark(current_sign)
|
42
|
+
game_over? #updates winning sign
|
43
|
+
end
|
44
|
+
|
45
|
+
def current_sign
|
46
|
+
@current_sign = @sign_state_machine[@current_sign]
|
47
|
+
end
|
48
|
+
|
49
|
+
def [](row, column)
|
50
|
+
@grid[row-1][column-1]
|
51
|
+
end
|
52
|
+
|
53
|
+
def game_over?
|
54
|
+
win? or draw?
|
55
|
+
end
|
56
|
+
|
57
|
+
def win?
|
58
|
+
win = (row_win? or column_win? or diagonal_win?)
|
59
|
+
self.game_status=WIN if win
|
60
|
+
win
|
61
|
+
end
|
62
|
+
|
63
|
+
def reset!
|
64
|
+
(1..3).each do |row|
|
65
|
+
(1..3).each do |column|
|
66
|
+
self[row, column].reset!
|
67
|
+
end
|
68
|
+
end
|
69
|
+
@winning_sign = Cell::EMPTY
|
70
|
+
@current_sign = nil
|
71
|
+
self.game_status=IN_PROGRESS
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def build_grid
|
77
|
+
@grid = []
|
78
|
+
3.times do |row_index| #0-based
|
79
|
+
@grid << []
|
80
|
+
3.times { @grid[row_index] << Cell.new }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def row_win?
|
85
|
+
(1..3).each do |row|
|
86
|
+
if row_has_same_sign(row)
|
87
|
+
@winning_sign = self[row, 1].sign
|
88
|
+
return true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
false
|
92
|
+
end
|
93
|
+
|
94
|
+
def column_win?
|
95
|
+
(1..3).each do |column|
|
96
|
+
if column_has_same_sign(column)
|
97
|
+
@winning_sign = self[1, column].sign
|
98
|
+
return true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
false
|
102
|
+
end
|
103
|
+
|
104
|
+
#needs refactoring if we ever decide to make the board size dynamic
|
105
|
+
def diagonal_win?
|
106
|
+
if (self[1, 1].sign == self[2, 2].sign) and (self[2, 2].sign == self[3, 3].sign) and self[1, 1].marked
|
107
|
+
@winning_sign = self[1, 1].sign
|
108
|
+
return true
|
109
|
+
end
|
110
|
+
if (self[3, 1].sign == self[2, 2].sign) and (self[2, 2].sign == self[1, 3].sign) and self[3, 1].marked
|
111
|
+
@winning_sign = self[3, 1].sign
|
112
|
+
return true
|
113
|
+
end
|
114
|
+
false
|
115
|
+
end
|
116
|
+
|
117
|
+
def draw?
|
118
|
+
@board_full = true
|
119
|
+
3.times do |x|
|
120
|
+
3.times do |y|
|
121
|
+
@board_full = false if self[x, y].empty
|
122
|
+
end
|
123
|
+
end
|
124
|
+
self.game_status = DRAW if @board_full
|
125
|
+
@board_full
|
126
|
+
end
|
127
|
+
|
128
|
+
def row_has_same_sign(number)
|
129
|
+
row_sign = self[number, 1].sign
|
130
|
+
[2, 3].each do |column|
|
131
|
+
return false unless row_sign == (self[number, column].sign)
|
132
|
+
end
|
133
|
+
true if self[number, 1].marked
|
134
|
+
end
|
135
|
+
|
136
|
+
def column_has_same_sign(number)
|
137
|
+
column_sign = self[1, number].sign
|
138
|
+
[2, 3].each do |row|
|
139
|
+
return false unless column_sign == (self[row, number].sign)
|
140
|
+
end
|
141
|
+
true if self[1, number].marked
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Copyright (c) 2007-2021 Andy Maleh
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
class TicTacToe
|
23
|
+
class Cell
|
24
|
+
EMPTY = ""
|
25
|
+
attr_accessor :sign, :empty
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
reset!
|
29
|
+
end
|
30
|
+
|
31
|
+
def mark(sign)
|
32
|
+
self.sign = sign
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset!
|
36
|
+
self.sign = EMPTY
|
37
|
+
end
|
38
|
+
|
39
|
+
def sign=(sign_symbol)
|
40
|
+
@sign = sign_symbol
|
41
|
+
self.empty = sign == EMPTY
|
42
|
+
end
|
43
|
+
|
44
|
+
def marked
|
45
|
+
!empty
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|