gomoku 0.1.0 → 0.2.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/CHANGES.md +9 -0
- data/README.md +18 -4
- data/assets/black_hover.png +0 -0
- data/assets/screenshot.png +0 -0
- data/assets/white_hover.png +0 -0
- data/bin/gomoku +1 -1
- data/lib/gomoku.rb +8 -184
- data/lib/gomoku/board.rb +97 -94
- data/lib/gomoku/computer.rb +34 -23
- data/lib/gomoku/human.rb +22 -14
- data/lib/gomoku/player.rb +23 -9
- data/lib/gomoku/utility.rb +32 -16
- data/lib/gomoku/version.rb +2 -1
- data/lib/gomoku/window.rb +193 -0
- metadata +23 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88d248a2fc1b72df046fde108c79b03565f66e1b
|
4
|
+
data.tar.gz: f66907aafbe1cd6c713b5a958fea7d94ad68c53d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29c840f0620434b79d8f583cdf072fc9f2a82cbff1ae98d1cba6fde434a6232b979e5a1d5a3d74e20f8340735d71b9f909218316dd6e27ad77ce6f385b0b9c9d
|
7
|
+
data.tar.gz: 0f9b6263e30be9eb827fd3915e692fd73d92c0aba8b4ef7e6d5b5f6aa8e733fffbcfa7bce3322b5f49c62351656bfc1a772e33b72c13901950f4c01c74fe0e3e
|
data/CHANGES.md
ADDED
data/README.md
CHANGED
@@ -1,13 +1,27 @@
|
|
1
|
-
# Gomoku
|
1
|
+
# Gomoku [](https://rubygems.org/gems/gomoku) [](https://travis-ci.org/dtcristo/gomoku) [](https://codeclimate.com/github/dtcristo/gomoku) [](https://codeclimate.com/github/dtcristo/gomoku/coverage)
|
2
2
|
|
3
|
-
|
3
|
+
Gomoku is the classic five-in-a-row board game. Challenge a friend or take on the AI. Gomoku is built with [Gosu](https://www.libgosu.org/), the 2D game library for Ruby.
|
4
|
+
|
5
|
+

|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
7
|
-
|
9
|
+
Gosu dependencies (OS X):
|
10
|
+
|
11
|
+
$ brew install sdl2 libogg libvorbis
|
12
|
+
|
13
|
+
For Linux dependencies, read [Getting Started on Linux](https://github.com/gosu/gosu/wiki/Getting-Started-on-Linux).
|
14
|
+
|
15
|
+
Install as a gem:
|
8
16
|
|
9
17
|
$ gem install gomoku
|
10
18
|
|
11
|
-
And
|
19
|
+
And play:
|
12
20
|
|
13
21
|
$ gomoku
|
22
|
+
|
23
|
+
## Todo
|
24
|
+
|
25
|
+
* Build basic AI
|
26
|
+
* Add menus
|
27
|
+
* Add network play
|
Binary file
|
Binary file
|
Binary file
|
data/bin/gomoku
CHANGED
data/lib/gomoku.rb
CHANGED
@@ -1,188 +1,12 @@
|
|
1
1
|
require 'gosu'
|
2
2
|
require 'gomoku/version'
|
3
|
-
require 'gomoku/utility'
|
4
|
-
require 'gomoku/board'
|
5
|
-
require 'gomoku/human'
|
6
|
-
require 'gomoku/computer'
|
7
3
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# Start a new game
|
18
|
-
new_game
|
19
|
-
end
|
20
|
-
|
21
|
-
def new_game
|
22
|
-
# Reset the board
|
23
|
-
@board.reset
|
24
|
-
|
25
|
-
# Black goes first
|
26
|
-
@turn = :black
|
27
|
-
|
28
|
-
# Setup flag to indicate a turn needs to be processed
|
29
|
-
@process_turn = false
|
30
|
-
|
31
|
-
@winner = false
|
32
|
-
end
|
33
|
-
|
34
|
-
def needs_cursor?
|
35
|
-
true
|
36
|
-
end
|
37
|
-
|
38
|
-
def button_down id
|
39
|
-
case id
|
40
|
-
when Gosu::KbEscape
|
41
|
-
close
|
42
|
-
when Gosu::MsLeft
|
43
|
-
# If no winner, attempt a move
|
44
|
-
if !@winner
|
45
|
-
# Get the location of the click
|
46
|
-
click_r = Utility.y_to_r mouse_y
|
47
|
-
click_c = Utility.x_to_c mouse_x
|
48
|
-
|
49
|
-
# Check for black space
|
50
|
-
if @board.state[[click_r, click_c]] == :empty
|
51
|
-
# Perform move
|
52
|
-
@board.state[[click_r, click_c]] = @turn
|
53
|
-
# Update turn
|
54
|
-
if @turn == :black
|
55
|
-
@turn = :white
|
56
|
-
else
|
57
|
-
@turn = :black
|
58
|
-
end
|
59
|
-
|
60
|
-
# Update flag
|
61
|
-
@process_turn = true
|
62
|
-
end
|
63
|
-
else
|
64
|
-
# We already have a winner, so start new game
|
65
|
-
new_game
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def button_up id
|
71
|
-
end
|
72
|
-
|
73
|
-
def update
|
74
|
-
case @turn
|
75
|
-
when :black
|
76
|
-
@black_player.update
|
77
|
-
when :white
|
78
|
-
@white_player.update
|
79
|
-
end
|
80
|
-
|
81
|
-
# If a move has been made, process the turn
|
82
|
-
if @process_turn
|
83
|
-
# Loop through board cells and check winner, break when found
|
84
|
-
catch :break do
|
85
|
-
for r in (1..19)
|
86
|
-
for c in (1..19)
|
87
|
-
win = @board.check_win r, c
|
88
|
-
if win != :none
|
89
|
-
@winner = true
|
90
|
-
@winner_direction = win
|
91
|
-
@winner_r = r
|
92
|
-
@winner_c = c
|
93
|
-
throw :break
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
# Done processing, reset flag
|
100
|
-
@process_turn = false
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
def draw
|
105
|
-
@board.draw
|
106
|
-
|
107
|
-
if !@winner
|
108
|
-
case @turn
|
109
|
-
when :black
|
110
|
-
@black_player.draw
|
111
|
-
when :white
|
112
|
-
@white_player.draw
|
113
|
-
end
|
114
|
-
else
|
115
|
-
# Winner, mark the winning sequence
|
116
|
-
|
117
|
-
# Draw three adjacent 1px lines to make a single 3px line.
|
118
|
-
# Worst code I ever wrote.
|
119
|
-
case @winner_direction
|
120
|
-
when :horizontal
|
121
|
-
line_x1_1 = Utility.c_to_x(@winner_c)
|
122
|
-
line_y1_1 = Utility.r_to_y(@winner_r) + 20
|
123
|
-
line_x1_2 = Utility.c_to_x(@winner_c + 5)
|
124
|
-
line_y1_2 = Utility.r_to_y(@winner_r) + 20
|
125
|
-
|
126
|
-
line_x2_1 = line_x1_1
|
127
|
-
line_y2_1 = line_y1_1 + 1
|
128
|
-
line_x2_2 = line_x1_2
|
129
|
-
line_y2_2 = line_y1_2 + 1
|
130
|
-
|
131
|
-
line_x3_1 = line_x1_1
|
132
|
-
line_y3_1 = line_y1_1 + 2
|
133
|
-
line_x3_2 = line_x1_2
|
134
|
-
line_y3_2 = line_y1_2 + 2
|
135
|
-
when :vertical
|
136
|
-
line_x1_1 = Utility.c_to_x(@winner_c) + 20
|
137
|
-
line_y1_1 = Utility.r_to_y(@winner_r)
|
138
|
-
line_x1_2 = Utility.c_to_x(@winner_c) + 20
|
139
|
-
line_y1_2 = Utility.r_to_y(@winner_r + 5)
|
140
|
-
|
141
|
-
line_x2_1 = line_x1_1 + 1
|
142
|
-
line_y2_1 = line_y1_1
|
143
|
-
line_x2_2 = line_x1_2 + 1
|
144
|
-
line_y2_2 = line_y1_2
|
145
|
-
|
146
|
-
line_x3_1 = line_x1_1 + 2
|
147
|
-
line_y3_1 = line_y1_1
|
148
|
-
line_x3_2 = line_x1_2 + 2
|
149
|
-
line_y3_2 = line_y1_2
|
150
|
-
when :diagonal_up
|
151
|
-
line_x1_1 = Utility.c_to_x(@winner_c)
|
152
|
-
line_y1_1 = Utility.r_to_y(@winner_r + 1)
|
153
|
-
line_x1_2 = Utility.c_to_x(@winner_c + 5)
|
154
|
-
line_y1_2 = Utility.r_to_y(@winner_r - 5)
|
155
|
-
|
156
|
-
line_x2_1 = line_x1_1
|
157
|
-
line_y2_1 = line_y1_1 + 1
|
158
|
-
line_x2_2 = line_x1_2 + 1
|
159
|
-
line_y2_2 = line_y1_2
|
160
|
-
|
161
|
-
line_x3_1 = line_x1_1 + 1
|
162
|
-
line_y3_1 = line_y1_1 + 1
|
163
|
-
line_x3_2 = line_x1_2 + 1
|
164
|
-
line_y3_2 = line_y1_2 + 1
|
165
|
-
when :diagonal_down
|
166
|
-
line_x1_1 = Utility.c_to_x(@winner_c)
|
167
|
-
line_y1_1 = Utility.r_to_y(@winner_r)
|
168
|
-
line_x1_2 = Utility.c_to_x(@winner_c + 5)
|
169
|
-
line_y1_2 = Utility.r_to_y(@winner_r + 5)
|
170
|
-
|
171
|
-
line_x2_1 = line_x1_1 + 1
|
172
|
-
line_y2_1 = line_y1_1
|
173
|
-
line_x2_2 = line_x1_2
|
174
|
-
line_y2_2 = line_y1_2 - 1
|
175
|
-
|
176
|
-
line_x3_1 = line_x1_1
|
177
|
-
line_y3_1 = line_y1_1 + 1
|
178
|
-
line_x3_2 = line_x1_2 - 1
|
179
|
-
line_y3_2 = line_y1_2
|
180
|
-
end
|
181
|
-
|
182
|
-
# Render the three lines
|
183
|
-
draw_line(line_x1_1, line_y1_1, Gosu::Color.argb(0xffff0000), line_x1_2, line_y1_2, Gosu::Color.argb(0xffff0000), 2, :default)
|
184
|
-
draw_line(line_x2_1, line_y2_1, Gosu::Color.argb(0xffff0000), line_x2_2, line_y2_2, Gosu::Color.argb(0xffff0000), 2, :default)
|
185
|
-
draw_line(line_x3_1, line_y3_1, Gosu::Color.argb(0xffff0000), line_x3_2, line_y3_2, Gosu::Color.argb(0xffff0000), 2, :default)
|
186
|
-
end
|
187
|
-
end
|
4
|
+
# Main Gomoku module
|
5
|
+
module Gomoku
|
6
|
+
autoload :Board, 'gomoku/board'
|
7
|
+
autoload :Computer, 'gomoku/computer'
|
8
|
+
autoload :Human, 'gomoku/human'
|
9
|
+
autoload :Player, 'gomoku/player'
|
10
|
+
autoload :Utility, 'gomoku/utility'
|
11
|
+
autoload :Window, 'gomoku/window'
|
188
12
|
end
|
data/lib/gomoku/board.rb
CHANGED
@@ -1,123 +1,126 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
module Gomoku
|
2
|
+
# Defines the board state and rendering
|
3
|
+
class Board
|
4
|
+
attr_reader :state
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
end
|
13
|
-
|
14
|
-
def reset
|
15
|
-
# Default value in @state is invalid move (outside grid)
|
16
|
-
@state = Hash.new(:invalid)
|
6
|
+
def initialize(window)
|
7
|
+
# Setup sprites
|
8
|
+
@grid = Gosu::Image.new(window, Utility.asset_path('grid.png'), true)
|
9
|
+
@black = Gosu::Image.new(window, Utility.asset_path('black.png'), true)
|
10
|
+
@white = Gosu::Image.new(window, Utility.asset_path('white.png'), true)
|
11
|
+
@black_hover = Gosu::Image.new(window, Utility.asset_path('black_hover.png'), true)
|
12
|
+
@white_hover = Gosu::Image.new(window, Utility.asset_path('white_hover.png'), true)
|
13
|
+
end
|
17
14
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
def reset
|
16
|
+
# Default value in @state is invalid move (outside grid)
|
17
|
+
@state = Hash.new(:invalid)
|
18
|
+
# Setup blank spaces within grid range
|
19
|
+
Board.each_r_c do |r, c|
|
20
|
+
@state[[r, c]] = :empty
|
22
21
|
end
|
23
22
|
end
|
24
|
-
end
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
def draw
|
25
|
+
# Draw the empty grid
|
26
|
+
@grid.draw(0, 0, 0)
|
27
|
+
# Loop through board cells and render stones
|
28
|
+
Board.each_r_c do |r, c|
|
29
|
+
color = @state[[r, c]]
|
30
|
+
draw_stone(color, r, c)
|
31
|
+
end
|
32
|
+
end
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
34
|
+
def draw_stone(color, r, c)
|
35
|
+
case color
|
36
|
+
when :black
|
37
|
+
@black.draw(Utility.c_to_x(c), Utility.r_to_y(r), 1)
|
38
|
+
when :white
|
39
|
+
@white.draw(Utility.c_to_x(c), Utility.r_to_y(r), 1)
|
35
40
|
end
|
36
41
|
end
|
37
|
-
end
|
38
42
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
def draw_hover(color, r, c)
|
44
|
+
case color
|
45
|
+
when :black
|
46
|
+
@black_hover.draw(Utility.c_to_x(c), Utility.r_to_y(r), 1)
|
47
|
+
when :white
|
48
|
+
@white_hover.draw(Utility.c_to_x(c), Utility.r_to_y(r), 1)
|
49
|
+
end
|
45
50
|
end
|
46
|
-
end
|
47
51
|
|
48
|
-
|
49
|
-
|
52
|
+
def check_win(r, c)
|
53
|
+
current = @state[[r, c]]
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
return :none
|
54
|
-
end
|
55
|
+
# No winner if we're on an empty cell
|
56
|
+
return :none if current == :empty
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
58
|
+
# Test horizontal sequence
|
59
|
+
if c < 16
|
60
|
+
count = 0
|
61
|
+
(0..4).each do |offset|
|
62
|
+
if @state[[r, c + offset]] == current
|
63
|
+
count += 1
|
64
|
+
else break
|
65
|
+
end
|
63
66
|
end
|
67
|
+
# Winner found
|
68
|
+
return :horizontal if count == 5
|
64
69
|
end
|
65
|
-
# Winner found
|
66
|
-
if count == 5
|
67
|
-
return :horizontal
|
68
|
-
end
|
69
|
-
end
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
71
|
+
# Test vertical sequence
|
72
|
+
if r < 16
|
73
|
+
count = 0
|
74
|
+
(0..4).each do |offset|
|
75
|
+
if @state[[r + offset, c]] == current
|
76
|
+
count += 1
|
77
|
+
else break
|
78
|
+
end
|
78
79
|
end
|
80
|
+
# Winner found
|
81
|
+
return :vertical if count == 5
|
79
82
|
end
|
80
|
-
# Winner found
|
81
|
-
if count == 5
|
82
|
-
return :vertical
|
83
|
-
end
|
84
|
-
end
|
85
83
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
84
|
+
# Test diagonal-up sequence
|
85
|
+
if r > 4 && c < 16
|
86
|
+
count = 0
|
87
|
+
(0..4).each do |offset|
|
88
|
+
if @state[[r - offset, c + offset]] == current
|
89
|
+
count += 1
|
90
|
+
else break
|
91
|
+
end
|
93
92
|
end
|
93
|
+
# Winner found
|
94
|
+
return :diagonal_up if count == 5
|
94
95
|
end
|
95
|
-
# Winner found
|
96
|
-
if count == 5
|
97
|
-
return :diagonal_up
|
98
|
-
end
|
99
|
-
end
|
100
96
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
97
|
+
# Test diagonal-down sequence
|
98
|
+
if r < 16 && c < 16
|
99
|
+
count = 0
|
100
|
+
(0..4).each do |offset|
|
101
|
+
if @state[[r + offset, c + offset]] == current
|
102
|
+
count += 1
|
103
|
+
else break
|
104
|
+
end
|
108
105
|
end
|
106
|
+
# Winner found
|
107
|
+
return :diagonal_down if count == 5
|
109
108
|
end
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
109
|
+
|
110
|
+
# No winner
|
111
|
+
:none
|
114
112
|
end
|
115
113
|
|
116
|
-
|
117
|
-
|
118
|
-
|
114
|
+
def empty?(r, c)
|
115
|
+
state[[r, c]] == :empty
|
116
|
+
end
|
119
117
|
|
120
|
-
|
121
|
-
|
118
|
+
def self.each_r_c
|
119
|
+
(1..19).each do |r|
|
120
|
+
(1..19).each do |c|
|
121
|
+
yield r, c
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
122
125
|
end
|
123
126
|
end
|
data/lib/gomoku/computer.rb
CHANGED
@@ -1,32 +1,43 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
class Computer < Player
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def update
|
9
|
-
catch :break do
|
10
|
-
for r in (1..19)
|
11
|
-
for c in (1..19)
|
1
|
+
module Gomoku
|
2
|
+
# Computer controlled player
|
3
|
+
class Computer < Player
|
4
|
+
def human?
|
5
|
+
false
|
6
|
+
end
|
12
7
|
|
13
|
-
|
8
|
+
def pick_move
|
9
|
+
@hover_r ||= 1
|
10
|
+
@hover_c ||= 1
|
14
11
|
|
15
|
-
|
16
|
-
# Update turn
|
17
|
-
if @window.turn == :black
|
18
|
-
@window.turn = :white
|
19
|
-
else
|
20
|
-
@window.turn = :black
|
21
|
-
end
|
12
|
+
# Test position and maybe return move here
|
22
13
|
|
23
|
-
|
24
|
-
|
14
|
+
# Update hover location
|
15
|
+
unless hover_next
|
16
|
+
# No new hover
|
17
|
+
move_r = @hover_r
|
18
|
+
move_c = @hover_c
|
19
|
+
@hover_r, @hover_c = 1
|
20
|
+
end
|
21
|
+
[move_r, move_c]
|
22
|
+
end
|
25
23
|
|
26
|
-
|
27
|
-
|
24
|
+
def hover_next
|
25
|
+
r = @hover_r
|
26
|
+
c = @hover_c
|
27
|
+
while r <= 19 && c <= 19
|
28
|
+
if c < 19
|
29
|
+
c += 1
|
30
|
+
else
|
31
|
+
r += 1
|
32
|
+
c = 1
|
33
|
+
end
|
34
|
+
if @board.empty?(r, c)
|
35
|
+
@hover_r = r
|
36
|
+
@hover_c = c
|
37
|
+
return true
|
28
38
|
end
|
29
39
|
end
|
40
|
+
false
|
30
41
|
end
|
31
42
|
end
|
32
43
|
end
|
data/lib/gomoku/human.rb
CHANGED
@@ -1,20 +1,28 @@
|
|
1
|
-
|
1
|
+
module Gomoku
|
2
|
+
# Human controlled player
|
3
|
+
class Human < Player
|
4
|
+
def human?
|
5
|
+
true
|
6
|
+
end
|
2
7
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
8
|
+
def click(click_r, click_c)
|
9
|
+
@click_r = click_r
|
10
|
+
@click_c = click_c
|
11
|
+
end
|
7
12
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
+
def update
|
14
|
+
# Update mouse hover location
|
15
|
+
@hover_r = Utility.y_to_r(@window.mouse_y)
|
16
|
+
@hover_c = Utility.x_to_c(@window.mouse_x)
|
17
|
+
end
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
def pick_move
|
20
|
+
return unless @click_r && @click_c
|
21
|
+
return unless @board.empty?(@click_r, @click_c)
|
22
|
+
move_r = @click_r
|
23
|
+
move_c = @click_c
|
24
|
+
@click_r, @click_c = nil
|
25
|
+
[move_r, move_c]
|
18
26
|
end
|
19
27
|
end
|
20
28
|
end
|
data/lib/gomoku/player.rb
CHANGED
@@ -1,13 +1,27 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
module Gomoku
|
2
|
+
# Defines common functionality for all players
|
3
|
+
class Player
|
4
|
+
def initialize(window, board, color)
|
5
|
+
@window = window
|
6
|
+
@board = board
|
7
|
+
@color = color
|
8
|
+
end
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
+
def click(_click_r, _click_c)
|
11
|
+
end
|
12
|
+
|
13
|
+
def update
|
14
|
+
end
|
15
|
+
|
16
|
+
def pick_move
|
17
|
+
end
|
10
18
|
|
11
|
-
|
19
|
+
def draw
|
20
|
+
return unless @hover_r && @hover_c
|
21
|
+
return unless Utility.in_range?(@hover_r, @hover_c) &&
|
22
|
+
@board.empty?(@hover_r, @hover_c)
|
23
|
+
# Draw the hover for next piece
|
24
|
+
@board.draw_hover(@color, @hover_r, @hover_c)
|
25
|
+
end
|
12
26
|
end
|
13
27
|
end
|
data/lib/gomoku/utility.rb
CHANGED
@@ -1,22 +1,38 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
end
|
1
|
+
module Gomoku
|
2
|
+
# Helper methods
|
3
|
+
module Utility
|
4
|
+
module_function
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
def r_to_y(r)
|
7
|
+
r * 40 - 20
|
8
|
+
end
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
def c_to_x(c)
|
11
|
+
c * 40 - 20
|
12
|
+
end
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
def x_to_c(x)
|
15
|
+
(x.to_i + 20) / 40
|
16
|
+
end
|
17
|
+
|
18
|
+
def y_to_r(y)
|
19
|
+
(y.to_i + 20) / 40
|
20
|
+
end
|
21
|
+
|
22
|
+
def in_range?(r, c)
|
23
|
+
r >= 1 && r <= 19 && c >= 1 && c <= 19
|
24
|
+
end
|
25
|
+
|
26
|
+
def toggle_color(color)
|
27
|
+
if color == :black
|
28
|
+
:white
|
29
|
+
else
|
30
|
+
:black
|
31
|
+
end
|
32
|
+
end
|
18
33
|
|
19
|
-
|
20
|
-
|
34
|
+
def asset_path(asset)
|
35
|
+
File.expand_path("../../../assets/#{asset}", __FILE__)
|
36
|
+
end
|
21
37
|
end
|
22
38
|
end
|
data/lib/gomoku/version.rb
CHANGED
@@ -0,0 +1,193 @@
|
|
1
|
+
module Gomoku
|
2
|
+
# Main Gomoku window and game loop
|
3
|
+
class Window < Gosu::Window
|
4
|
+
def initialize
|
5
|
+
super 800, 800, false
|
6
|
+
self.caption = 'Gomoku'
|
7
|
+
@board = Board.new(self)
|
8
|
+
@black_player = Human.new(self, @board, :black)
|
9
|
+
@white_player = Human.new(self, @board, :white)
|
10
|
+
# @white_player = Computer.new(self, @board, :white)
|
11
|
+
# Start a new game
|
12
|
+
new_game
|
13
|
+
end
|
14
|
+
|
15
|
+
def new_game
|
16
|
+
# Reset the board
|
17
|
+
@board.reset
|
18
|
+
# Black goes first
|
19
|
+
@turn = :black
|
20
|
+
# Setup flag to indicate a turn needs to be processed
|
21
|
+
@done_turn = false
|
22
|
+
@winner = false
|
23
|
+
end
|
24
|
+
|
25
|
+
def needs_cursor?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def button_down(id)
|
30
|
+
case id
|
31
|
+
when Gosu::KbEscape
|
32
|
+
close
|
33
|
+
when Gosu::MsLeft
|
34
|
+
process_click
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def process_click
|
39
|
+
if @winner
|
40
|
+
# We already have a winner, so start new game
|
41
|
+
new_game
|
42
|
+
else
|
43
|
+
# Get the location of the click
|
44
|
+
click_r = Utility.y_to_r(mouse_y)
|
45
|
+
click_c = Utility.x_to_c(mouse_x)
|
46
|
+
case @turn
|
47
|
+
when :black
|
48
|
+
@black_player.click(click_r, click_c)
|
49
|
+
when :white
|
50
|
+
@white_player.click(click_r, click_c)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def button_up(_id)
|
56
|
+
end
|
57
|
+
|
58
|
+
def update
|
59
|
+
case @turn
|
60
|
+
when :black
|
61
|
+
@black_player.update
|
62
|
+
move = @black_player.pick_move
|
63
|
+
when :white
|
64
|
+
@white_player.update
|
65
|
+
move = @white_player.pick_move
|
66
|
+
end
|
67
|
+
# Perform the move
|
68
|
+
do_move(move) if move
|
69
|
+
# Process the turn if done
|
70
|
+
process_turn if @done_turn
|
71
|
+
end
|
72
|
+
|
73
|
+
def do_move(move)
|
74
|
+
r = move[0]
|
75
|
+
c = move[1]
|
76
|
+
# Return if not blank
|
77
|
+
return unless @board.state[[r, c]] == :empty
|
78
|
+
# Perform move
|
79
|
+
@board.state[[r, c]] = @turn
|
80
|
+
# Update turn
|
81
|
+
@turn = Utility.toggle_color(@turn)
|
82
|
+
# Update flag
|
83
|
+
@done_turn = true
|
84
|
+
end
|
85
|
+
|
86
|
+
# Loop through board cells and check winner, break when found
|
87
|
+
def process_turn
|
88
|
+
Board.each_r_c do |r, c|
|
89
|
+
win = @board.check_win(r, c)
|
90
|
+
unless win == :none
|
91
|
+
@winner = true
|
92
|
+
@winner_direction = win
|
93
|
+
@winner_r = r
|
94
|
+
@winner_c = c
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
# Done processing, reset flag
|
99
|
+
@done_turn = false
|
100
|
+
end
|
101
|
+
|
102
|
+
def draw
|
103
|
+
@board.draw
|
104
|
+
if @winner
|
105
|
+
# Mark the winning sequence
|
106
|
+
draw_winner
|
107
|
+
else
|
108
|
+
# No winner, draw the current player
|
109
|
+
case @turn
|
110
|
+
when :black
|
111
|
+
@black_player.draw
|
112
|
+
when :white
|
113
|
+
@white_player.draw
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Draw three adjacent 1px lines to make a single 3px line.
|
119
|
+
# Worst code I ever wrote.
|
120
|
+
def draw_winner
|
121
|
+
case @winner_direction
|
122
|
+
when :horizontal
|
123
|
+
line_x1_1 = Utility.c_to_x(@winner_c)
|
124
|
+
line_y1_1 = Utility.r_to_y(@winner_r) + 20
|
125
|
+
line_x1_2 = Utility.c_to_x(@winner_c + 5)
|
126
|
+
line_y1_2 = Utility.r_to_y(@winner_r) + 20
|
127
|
+
|
128
|
+
line_x2_1 = line_x1_1
|
129
|
+
line_y2_1 = line_y1_1 + 1
|
130
|
+
line_x2_2 = line_x1_2
|
131
|
+
line_y2_2 = line_y1_2 + 1
|
132
|
+
|
133
|
+
line_x3_1 = line_x1_1
|
134
|
+
line_y3_1 = line_y1_1 + 2
|
135
|
+
line_x3_2 = line_x1_2
|
136
|
+
line_y3_2 = line_y1_2 + 2
|
137
|
+
when :vertical
|
138
|
+
line_x1_1 = Utility.c_to_x(@winner_c) + 20
|
139
|
+
line_y1_1 = Utility.r_to_y(@winner_r)
|
140
|
+
line_x1_2 = Utility.c_to_x(@winner_c) + 20
|
141
|
+
line_y1_2 = Utility.r_to_y(@winner_r + 5)
|
142
|
+
|
143
|
+
line_x2_1 = line_x1_1 + 1
|
144
|
+
line_y2_1 = line_y1_1
|
145
|
+
line_x2_2 = line_x1_2 + 1
|
146
|
+
line_y2_2 = line_y1_2
|
147
|
+
|
148
|
+
line_x3_1 = line_x1_1 + 2
|
149
|
+
line_y3_1 = line_y1_1
|
150
|
+
line_x3_2 = line_x1_2 + 2
|
151
|
+
line_y3_2 = line_y1_2
|
152
|
+
when :diagonal_up
|
153
|
+
line_x1_1 = Utility.c_to_x(@winner_c)
|
154
|
+
line_y1_1 = Utility.r_to_y(@winner_r + 1)
|
155
|
+
line_x1_2 = Utility.c_to_x(@winner_c + 5)
|
156
|
+
line_y1_2 = Utility.r_to_y(@winner_r - 4)
|
157
|
+
|
158
|
+
line_x2_1 = line_x1_1
|
159
|
+
line_y2_1 = line_y1_1 + 1
|
160
|
+
line_x2_2 = line_x1_2 + 1
|
161
|
+
line_y2_2 = line_y1_2
|
162
|
+
|
163
|
+
line_x3_1 = line_x1_1 + 1
|
164
|
+
line_y3_1 = line_y1_1 + 1
|
165
|
+
line_x3_2 = line_x1_2 + 1
|
166
|
+
line_y3_2 = line_y1_2 + 1
|
167
|
+
when :diagonal_down
|
168
|
+
line_x1_1 = Utility.c_to_x(@winner_c)
|
169
|
+
line_y1_1 = Utility.r_to_y(@winner_r)
|
170
|
+
line_x1_2 = Utility.c_to_x(@winner_c + 5)
|
171
|
+
line_y1_2 = Utility.r_to_y(@winner_r + 5)
|
172
|
+
|
173
|
+
line_x2_1 = line_x1_1 + 1
|
174
|
+
line_y2_1 = line_y1_1
|
175
|
+
line_x2_2 = line_x1_2
|
176
|
+
line_y2_2 = line_y1_2 - 1
|
177
|
+
|
178
|
+
line_x3_1 = line_x1_1
|
179
|
+
line_y3_1 = line_y1_1 + 1
|
180
|
+
line_x3_2 = line_x1_2 - 1
|
181
|
+
line_y3_2 = line_y1_2
|
182
|
+
end
|
183
|
+
|
184
|
+
# Render the three lines
|
185
|
+
draw_line(line_x1_1, line_y1_1, Gosu::Color.argb(0xffff0000),
|
186
|
+
line_x1_2, line_y1_2, Gosu::Color.argb(0xffff0000), 2, :default)
|
187
|
+
draw_line(line_x2_1, line_y2_1, Gosu::Color.argb(0xffff0000),
|
188
|
+
line_x2_2, line_y2_2, Gosu::Color.argb(0xffff0000), 2, :default)
|
189
|
+
draw_line(line_x3_1, line_y3_1, Gosu::Color.argb(0xffff0000),
|
190
|
+
line_x3_2, line_y3_2, Gosu::Color.argb(0xffff0000), 2, :default)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gomoku
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Cristofaro
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-07-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gosu
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: codeclimate-test-reporter
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
description:
|
84
98
|
email:
|
85
99
|
executables:
|
@@ -87,11 +101,15 @@ executables:
|
|
87
101
|
extensions: []
|
88
102
|
extra_rdoc_files: []
|
89
103
|
files:
|
104
|
+
- CHANGES.md
|
90
105
|
- LICENSE
|
91
106
|
- README.md
|
92
107
|
- assets/black.png
|
108
|
+
- assets/black_hover.png
|
93
109
|
- assets/grid.png
|
110
|
+
- assets/screenshot.png
|
94
111
|
- assets/white.png
|
112
|
+
- assets/white_hover.png
|
95
113
|
- bin/gomoku
|
96
114
|
- lib/gomoku.rb
|
97
115
|
- lib/gomoku/board.rb
|
@@ -100,6 +118,7 @@ files:
|
|
100
118
|
- lib/gomoku/player.rb
|
101
119
|
- lib/gomoku/utility.rb
|
102
120
|
- lib/gomoku/version.rb
|
121
|
+
- lib/gomoku/window.rb
|
103
122
|
homepage: https://github.com/dtcristo/gomoku
|
104
123
|
licenses:
|
105
124
|
- MIT
|
@@ -110,9 +129,9 @@ require_paths:
|
|
110
129
|
- lib
|
111
130
|
required_ruby_version: !ruby/object:Gem::Requirement
|
112
131
|
requirements:
|
113
|
-
- - "
|
132
|
+
- - "~>"
|
114
133
|
- !ruby/object:Gem::Version
|
115
|
-
version: '
|
134
|
+
version: '2'
|
116
135
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
136
|
requirements:
|
118
137
|
- - ">="
|