Skip to content

Commit 9a87ec1

Browse files
committed
Implement connect-four
1 parent e5fdd37 commit 9a87ec1

File tree

7 files changed

+243
-2
lines changed

7 files changed

+243
-2
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ source 'http://rubygems.org'
22

33
gem 'rspec'
44
gem 'pry'
5+
gem 'debugger'
56

67
# Specify your gem's dependencies in percival.gemspec
78
gemspec

Rakefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ task :start do
3535
c.plugins.plugins = [ClockPlugin,
3636
LoggerPlugin,
3737
ChannelChangerPlugin,
38-
NameChangerPlugin]
38+
NameChangerPlugin,
39+
ConnectFourPlugin]
3940
end
4041
end
41-
4242
bot.start
4343
end

lib/percival.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require 'percival/logger'
66
require 'percival/channel_changer'
77
require 'percival/name_changer'
8+
require 'percival/connect_four'
89

910
PERCIVAL_ROOT = File.dirname(File.dirname(__FILE__))
1011

lib/percival/connect_four.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
require 'percival/connect_four/plugin'
2+
require 'percival/connect_four/connect_four'
3+
require 'percival/connect_four/board_score'
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
class BoardScore
2+
3+
WIN_VALUE= 10000
4+
DEPTH = 4
5+
attr_accessor :score, :win
6+
7+
def initialize board
8+
@board = board
9+
@win = false
10+
@score = 0
11+
score_board
12+
end
13+
14+
def terminal
15+
return true if @win
16+
end
17+
18+
19+
def score_board
20+
@board.each_with_index do |column,i|
21+
column.each_with_index do |color, j|
22+
# up
23+
count = counter(color) do |k|
24+
@board[i][j+k]
25+
end
26+
return if won? count, color
27+
@score += color * (count ** 2)
28+
29+
#up diag
30+
count = counter(color) do |k|
31+
i+k < @board.size ? @board[i+k][j+k] : nil
32+
end
33+
return if won? count, color
34+
@score += color * (count ** 2)
35+
36+
#right
37+
count = counter(color) do |k|
38+
i+k < @board.size ? @board[i+k][j] : nil
39+
end
40+
return if won? count, color
41+
42+
@score += color * (count ** 2)
43+
44+
#down right diag
45+
count = counter(color) do |k|
46+
j-k >= 0 and i+k < @board.size ? @board[i+k][j-k] : nil
47+
end
48+
return if won? count, color
49+
@score += color * (count ** 2)
50+
51+
#down
52+
count = counter(color) do |k|
53+
j-k >= 0 ? @board[i][j-k] : nil
54+
end
55+
return if won? count, color
56+
@score += color * (count ** 2)
57+
58+
#down left diag
59+
count = counter(color) do |k|
60+
j-k >= 0 and i-k >= 0 ? @board[i-k][j-k] : nil
61+
end
62+
return if won? count, color
63+
@score += color * (count ** 2)
64+
65+
# left
66+
count = counter(color) do |k|
67+
i-k >= 0 ? @board[i-k][j] : nil
68+
end
69+
return if won? count, color
70+
@score += color * (count ** 2)
71+
72+
#up left diag
73+
count = counter(color) do |k|
74+
i-k >= 0 ? @board[i-k][j+k] : nil
75+
end
76+
return if won? count, color
77+
@score += color * (count ** 2)
78+
79+
end
80+
end
81+
end
82+
83+
def counter e_color
84+
count = 0
85+
(0..3).each do |k|
86+
color = yield k
87+
if color == - e_color
88+
count = 0
89+
break
90+
end
91+
count += 1 if color == e_color
92+
end
93+
count
94+
end
95+
96+
def won? count, color
97+
if count == 4
98+
@win = color
99+
@score = @win * WIN_VALUE
100+
return true
101+
end
102+
false
103+
end
104+
end
105+
106+
107+
108+
109+
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
require 'debugger'
2+
3+
class ConnectFour
4+
INF = 10000
5+
DEPTH = 5
6+
attr_accessor :board
7+
8+
def initialize width, height
9+
@width, @height = width, height
10+
@my_board = []
11+
@your_board = []
12+
@width.times { @my_board.push [] }
13+
end
14+
15+
def your_move move
16+
@your_board = new_board @my_board, move, -1
17+
rendering = "
18+
You dropped a piece at #{move + 1}
19+
#{board_to_string(@your_board)}"
20+
21+
bs = BoardScore.new(@your_board)
22+
if bs.win
23+
return "You win" + rendering
24+
end
25+
return rendering
26+
end
27+
28+
def my_move
29+
my_move = get_move(@your_board)
30+
@my_board = new_board(@your_board, my_move, 1)
31+
rendering = "
32+
I dropped a piece at #{my_move + 1}
33+
#{board_to_string(@my_board)}"
34+
35+
bs = BoardScore.new(@my_board)
36+
if bs.win
37+
return "I win!!\n\n" + rendering
38+
end
39+
return rendering
40+
end
41+
42+
def you_won
43+
"You Won!!! #can't happen"
44+
end
45+
46+
def i_won
47+
"I Won!!!"
48+
end
49+
50+
def get_move board
51+
idx = 0
52+
min = INF
53+
(0..board.size - 1).each do |i|
54+
nb = new_board board, i, 1
55+
val = negamax nb, DEPTH, -INF, INF, -1
56+
57+
if val < min
58+
min = val
59+
idx = i
60+
end
61+
end
62+
return idx
63+
end
64+
65+
#negamax alpha beta pruning http://en.wikipedia.org/wiki/Negamax
66+
#much better than my mickey mouse implementation of minimax
67+
def negamax board, depth, alpha, beta, color
68+
bs = BoardScore.new board
69+
if depth == 0 or bs.terminal
70+
return color * bs.score
71+
end
72+
(0..(board.size - 1)).each do |i|
73+
nb = new_board board, i, color
74+
val = - negamax( nb, depth - 1, - beta, - alpha, - color)
75+
return val if val >= beta
76+
alpha = val if val >= alpha
77+
end
78+
return alpha
79+
end
80+
81+
def new_board board, move, color
82+
nb = Marshal.load(Marshal.dump(board))
83+
nb[move].push color
84+
nb
85+
end
86+
87+
def board_to_string b
88+
l = []
89+
(0..@height-1).to_a.reverse.each do |i|
90+
e = []
91+
(0..@width - 1).each do |j|
92+
if b[j][i].nil?
93+
e << ' '
94+
elsif b[j][i] > 0
95+
e << 'X'
96+
elsif b[j][i] < 0
97+
e << '0'
98+
end
99+
end
100+
l << e.join(' ')
101+
end
102+
return '|' + l.join("|\n|") + "|\n|1 2 3 4 5 6 7|"
103+
end
104+
end

lib/percival/connect_four/plugin.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class ConnectFourPlugin
2+
include Cinch::Plugin
3+
4+
def initialize *args
5+
super
6+
@games = { }
7+
end
8+
9+
match /cf\s+(\S+)?/, :method => :connect_four
10+
11+
def connect_four m, command
12+
if /new/.match command
13+
@games[m.user.name] = ConnectFour.new 7,6
14+
m.reply "Awaiting your move sir"
15+
elsif /[1-7]/.match command
16+
@games[m.user.name] ||= ConnectFour.new 7,6
17+
m.reply @games[m.user.name].your_move(command.to_i - 1)
18+
m.reply @games[m.user.name].my_move
19+
else
20+
m.reply "command must be in [new|[1-7]]"
21+
end
22+
end
23+
end

0 commit comments

Comments
 (0)