瀏覽代碼

Basic network client working, fixes to dummy player

master
Peter J. Jones 7 年之前
父節點
當前提交
06a5a9b10f
共有 8 個檔案被更改,包括 240 行新增31 行删除
  1. 74
    0
      bin/freeplay
  2. 8
    5
      example/dummy.rb
  3. 2
    0
      freeplay.gemspec
  4. 4
    0
      lib/freeplay.rb
  5. 12
    5
      lib/freeplay/board.rb
  6. 130
    0
      lib/freeplay/client.rb
  7. 4
    0
      lib/freeplay/player.rb
  8. 6
    21
      test/board_test.rb

+ 74
- 0
bin/freeplay 查看文件

@@ -0,0 +1,74 @@
#!/usr/bin/env ruby

################################################################################
require('freeplay')
require('ostruct')
require('optparse')
require('logger')

################################################################################
class Configuration

##############################################################################
DEFAULT_OPTIONS = {
:host => 'localhost',
:port => 5678,
}

##############################################################################
attr_reader(:options)

##############################################################################
attr_reader(:player)

##############################################################################
attr_reader(:logger)

##############################################################################
def initialize
@options = OpenStruct.new(DEFAULT_OPTIONS)

OptionParser.new do |p|
p.banner = "Usage: freeplay [options] ruby-file"

p.on('-h', '--help', 'This message') do
$stdout.puts(p)
exit
end
end.parse!(ARGV)

if ARGV.size != 1 or !File.exist?(ARGV.first)
raise("expected a single file name, see --help")
end

require(File.expand_path(ARGV.first))

if Freeplay::Player.players.size != 1
error = "expected to find a class that inherits from Freeplay::Player "
error += "after loading #{ARGV.first}, please check your code"
raise(error)
end

@player = Freeplay::Player.players.first
@logger = Logger.new($stdout)
end
end

################################################################################
begin
config = Configuration.new
options = config.options

EventMachine.run do
Signal.trap("INT") { EventMachine.stop }
Signal.trap("TERM") { EventMachine.stop }

Freeplay::Client.player_class = config.player
Freeplay::Client.logger = config.logger
Freeplay::Client.username = ENV['USER']
EventMachine.connect(options.host, options.port, Freeplay::Client)
end
rescue RuntimeError => e
$stderr.puts(File.basename($0) + ": ERROR: #{e}")
exit(1)
end

+ 8
- 5
example/dummy.rb 查看文件

@@ -9,6 +9,8 @@ class Dummy < Freeplay::Player

# First try to move to a space adjacent to my opponent's last move.
if board.last_opponent_move
logger.info("searching for an open adjacent space")

allowed = board.adjacent(*board.last_opponent_move)
match = allowed.detect {|(ax, ay)| board[ax, ay] == :empty}
x, y = match if match
@@ -16,11 +18,12 @@ class Dummy < Freeplay::Player

# If that didn't work just take the first available space.
if x.nil? or y.nil?
board.size.times do |bx|
board.size.times do |by|
if board[bx, by] == :empty
x, y = bx, by
break
logger.info("searching for first available space")

x, y = catch(:found_empty_space) do
board.size.times do |bx|
board.size.times do |by|
throw(:found_empty_space, [bx, by]) if board[bx, by] == :empty
end
end
end

+ 2
- 0
freeplay.gemspec 查看文件

@@ -14,4 +14,6 @@ Gem::Specification.new do |gem|
gem.name = "freeplay"
gem.require_paths = ["lib"]
gem.version = Freeplay::VERSION

gem.add_dependency('eventmachine', '~> 0.12.10')
end

+ 4
- 0
lib/freeplay.rb 查看文件

@@ -1,3 +1,6 @@
################################################################################
require('eventmachine')

################################################################################
module Freeplay

@@ -7,5 +10,6 @@ module Freeplay
##############################################################################
autoload('VERSION', 'freeplay/version')
autoload('Board', 'freeplay/board')
autoload('Client', 'freeplay/client')
autoload('Player', 'freeplay/player')
end

+ 12
- 5
lib/freeplay/board.rb 查看文件

@@ -62,19 +62,19 @@ class Freeplay::Board

##############################################################################
# This method is for internal use only.
def []= (x, y, val) # :nodoc:
def player_move (x, y) # :nodoc:
x, y = transform(x, y)
bounds_check(x, y)
move_check(x, y)
stone_check(val)
@grid[x][y] = val
@grid[x][y] = @player
end

##############################################################################
# This method is for internal use only.
def opponent_move (x, y) # :nodoc:
x, y = transform(x, y)
@last_opponent_move = [x, y]

x, y = transform(x, y)
@grid[x][y] = @opponent
end

@@ -128,7 +128,8 @@ class Freeplay::Board
# Ensure the move is valid.
def move_check (x, y)
if !@last_opponent_move.nil?
allowed = adjacent(*@last_opponent_move).select do |(x,y)|
last = transform(*@last_opponent_move)
allowed = adjacent(*last).select do |(x,y)|
@grid[x][y] == :empty
end

@@ -164,6 +165,12 @@ class Freeplay::Board
##############################################################################
# Transforms grid coordinates to array coordinates.
def transform (x, y)
if !x.is_a?(Fixnum) or !y.is_a?(Fixnum)
message = "(#{x.inspect},#{y.inspect}) doesn't appear to "
message += "be valid coordinates, both should be integers"
raise(OutOfBoundsError, message)
end

[@size - 1 - y, x]
end
end

+ 130
- 0
lib/freeplay/client.rb 查看文件

@@ -0,0 +1,130 @@
class Freeplay::Client < EM::Connection

##############################################################################
include(EM::Protocols::LineText2)

##############################################################################
COMMANDS = {
'nonce' => :authenticate_with_nonce,
'not-authorized' => :not_authorized,
'opponent' => :announce_opponent,
'board' => :create_board_and_player,
'move' => :move,
'game' => :game,
'quit' => :quit,
}

##############################################################################
def self.player_class= (klass)
@@player_class = klass
end

##############################################################################
def self.logger= (logger)
@@logger = logger
end

##############################################################################
def self.username= (username)
@@username = username
end

##############################################################################
def post_init
@@logger.info("connected to server, initiating authentication")
send_data("authenticate: #{@@username}\n")
end

##############################################################################
def unbind
@@logger.info("disconnected from the server")
end

##############################################################################
def receive_line (data)
match = data.match(/^([^:]+):\s+(.+)$/)

if match and COMMANDS.has_key?(match[1])
send(COMMANDS[match[1]], match[2])
else
error("received an invalid message from the server: #{data}")
end
end

##############################################################################
private

##############################################################################
def authenticate_with_nonce (nonce)
send_data("nonce-reply: FOOBAR\n")
end

##############################################################################
def not_authorized (message)
error(message)
end

##############################################################################
def announce_opponent (opponent)
@@logger.info("your opponent is #{opponent}")
end

##############################################################################
def create_board_and_player (info)
if m = info.match(/^(white|black)\s+(\d+)$/)
@board = Freeplay::Board.new(m[1].to_sym, m[2].to_i)
@player = @@player_class.new
@player.board = @board
@player.logger = @@logger
else
error("server send over invalid game board parameters")
end
end

##############################################################################
def move (opponent_move)
match = opponent_move.match(/^(\d+|none),(\d+|none)$/)

if match && @player
@@logger.info("it's your move")

if match[1] != "none" and match[2] != "none"
ox, oy = match[1].to_i, match[2].to_i
@board.opponent_move(ox, oy)
@@logger.info("opponent's last move was (#{ox},#{oy})")
end

player_move = @player.move

if player_move.is_a?(Array) and player_move.size == 2
@board.player_move(player_move.first, player_move.last)
@@logger.info("your move is (#{player_move.first},#{player_move.last})")
send_data("move: #{player_move.first},#{player_move.last}\n")
else
error("#{@@player_class}#move did not return a 2 element array")
end
else
error("server sent an invalid opponent move")
end
rescue Freeplay::Error => e
error(e.message)
end

##############################################################################
def game (status)
@@logger.info(status)
end

##############################################################################
def quit (message)
@@logger.info("quitting: #{message}")
EventMachine.stop
end

##############################################################################
def error (message)
@@logger.error(message)
$stderr.puts(message)
EventMachine.stop
end
end

+ 4
- 0
lib/freeplay/player.rb 查看文件

@@ -19,6 +19,10 @@ class Freeplay::Player
#
attr_accessor(:board)

##############################################################################
# You can use the logger object to write entries to the log file.
attr_accessor(:logger)

##############################################################################
# The +move+ method is called on your player object when it's your
# turn to move. The +move+ method is required to return an array of

+ 6
- 21
test/board_test.rb 查看文件

@@ -9,39 +9,24 @@ class BoardTest < Test::Unit::TestCase
board = Freeplay::Board.new(:white, 20)
assert_equal(:empty, board[0, 0])

board[0,0] = :white
board.player_move(0,0)
assert_equal(:white, board[0,0])

assert_raise(Freeplay::Board::OutOfBoundsError) {board[-1, 0]}
assert_raise(Freeplay::Board::OutOfBoundsError) {board[20, 21]}
end

##############################################################################
def test_stone_checking
assert_raise(Freeplay::Board::InvalidStoneError) do
Freeplay::Board.new(:empty)
end

board = Freeplay::Board.new(:white)
assert_raise(Freeplay::Board::InvalidStoneError) {board[0,0] = :black}
assert_raise(Freeplay::Board::InvalidStoneError) {board[0,0] = :empty}
assert_raise(Freeplay::Board::InvalidStoneError) {board[0,0] = 'white'}

board[0,0] = :white
assert_raise(Freeplay::Board::InvalidMoveError) {board[0,0] = :white}
end

##############################################################################
def test_adjacency
board = Freeplay::Board.new(:white)
board.opponent_move(0, 0)
board[0, 1] = :white
board[1, 1] = :white
board[1, 0] = :white
board[1, 2] = :white
board.player_move(0, 1)
board.player_move(1, 1)
board.player_move(1, 0)
board.player_move(1, 2)

board = Freeplay::Board.new(:white)
board.opponent_move(0, 0)
assert_raise(Freeplay::Board::InvalidMoveError) {board[1,2] = :white}
assert_raise(Freeplay::Board::InvalidMoveError) {board.player_move(1,2)}
end
end

Loading…
取消
儲存