Browse Source

Initial import

master
Peter J. Jones 7 years ago
commit
2615937a09
11 changed files with 282 additions and 0 deletions
  1. 17
    0
      .gitignore
  2. 4
    0
      Gemfile
  3. 22
    0
      LICENSE
  4. 1
    0
      README.md
  5. 12
    0
      Rakefile
  6. 17
    0
      freeplay.gemspec
  7. 10
    0
      lib/freeplay.rb
  8. 146
    0
      lib/freeplay/board.rb
  9. 3
    0
      lib/freeplay/version.rb
  10. 47
    0
      test/board_test.rb
  11. 3
    0
      test/helper.rb

+ 17
- 0
.gitignore View File

@@ -0,0 +1,17 @@
*.gem
*.rbc
.bundle
.config
.yardoc
Gemfile.lock
InstalledFiles
_yardoc
coverage
doc/
lib/bundler/man
pkg
rdoc
spec/reports
test/tmp
test/version_tmp
tmp

+ 4
- 0
Gemfile View File

@@ -0,0 +1,4 @@
source 'https://rubygems.org'

# Specify your gem's dependencies in freeplay.gemspec
gemspec

+ 22
- 0
LICENSE View File

@@ -0,0 +1,22 @@
Copyright (c) 2012 Peter Jones <pjones@pmade.com>

MIT License

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 1
- 0
README.md View File

@@ -0,0 +1 @@
# Freeplay: A board game for programmers new to Ruby

+ 12
- 0
Rakefile View File

@@ -0,0 +1,12 @@
################################################################################
require('rake/testtask')
require("bundler/gem_tasks")

################################################################################
Rake::TestTask.new do |t|
t.test_files = FileList['test/*_test.rb']
t.verbose = true
end

################################################################################
task(:default => :test)

+ 17
- 0
freeplay.gemspec View File

@@ -0,0 +1,17 @@
# -*- encoding: utf-8 -*-
require File.expand_path('../lib/freeplay/version', __FILE__)

Gem::Specification.new do |gem|
gem.authors = ["Peter Jones"]
gem.email = ["pjones@pmade.com"]
gem.description = `head -1 README.md|sed 's/^# *//'`
gem.summary = "The Freeplay client is used to connect a player to the server."
gem.homepage = "git://pmade.com/freeplay"

gem.files = `git ls-files`.split($\)
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.name = "freeplay"
gem.require_paths = ["lib"]
gem.version = Freeplay::VERSION
end

+ 10
- 0
lib/freeplay.rb View File

@@ -0,0 +1,10 @@
################################################################################
module Freeplay

##############################################################################
class Error < RuntimeError; end

##############################################################################
autoload('VERSION', 'freeplay/version')
autoload('Board', 'freeplay/board')
end

+ 146
- 0
lib/freeplay/board.rb View File

@@ -0,0 +1,146 @@
class Freeplay::Board

##############################################################################
class OutOfBoundsError < Freeplay::Error; end
class InvalidStoneError < Freeplay::Error; end
class InvalidMoveError < Freeplay::Error; end

##############################################################################
# The width and height of the game board. Since the board must be a
# square the width and height will always be the same.
attr_reader(:size)

##############################################################################
# Don't create a game board yourself.
def initialize (player, size=10) # :nodoc:
@player, @size = player, size
@grid = Array.new(@size) {Array.new(@size, :empty)}
@opponent = @player == :white ? :black : :white
@last_opponent_move = nil
stone_check(@player, [:white, :black])
end

##############################################################################
# Returns the stone type for the given coordinates. This method
# takes two parameters, the first is the x coordinate and the second
# is the y coordinate.
#
# The allowed stone types are :empty, :white, and :black.
#
# Example (get the stone in the lower left corner):
#
# board = Freeplay::Board.new(...)
# board[0, 0] # => :empty
#
def [] (x, y)
x, y = transform(x, y)
bounds_check(x, y)
@grid[x][y]
end

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

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

##############################################################################
# Dumps a grid to the given output stream.
def dump (io, options={})
options = {
:empty => 'E',
:white => 'W',
:black => 'B',
:error => 'X',
}.merge!(options)

@grid.each do |row|
trans = row.map {|col| options[col] || options[:error]}
io.puts(trans.join(' '))
end
end

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

##############################################################################
# Ensure the given coordinates are within the grid.
def bounds_check (x, y)
if [x, y].any? {|c| c < 0 || c >= @size}
message = "(#{x},#{y}) is out of bounds, board is #{@size}x#{@size}"
raise(OutOfBoundsError, message)
end
end

##############################################################################
# Ensure the move is valid.
def move_check (x, y)
if !@last_opponent_move.nil?
allowed = adjacent(*@last_opponent_move).select do |(x,y)|
@grid[x][y] == :empty
end

if !allowed.empty? && !allowed.include?([x, y])
allowed_str = allowed.map {|(x,y)| "(#{x},#{y})"}.join(', ')
message = "move (#{x},#{y}) must be adjacent to "
message += "(#{@last_opponent_move[0]},#{@last_opponent_move[1]}), "
message += "allowed moves: " + allowed_str
raise(InvalidMoveError, message)
end
end

if @grid[x][y] != :empty
message = "space (#{x},#{y}) already taken with #{@grid[x][y].inspect}"
raise(InvalidMoveError, message)
end
end

##############################################################################
# Ensure the given stone is one of the allowed stones.
def stone_check (val, allowed=@player)
allowed = Array(allowed)

if !allowed.include?(val)
message = "invalid stone or player `#{val}' must be"
message += " one of" if allowed.size != 1
message += ": "
message += allowed.map(&:inspect).join(', ')
raise(InvalidStoneError, message)
end
end

##############################################################################
# Transforms grid coordinates to array coordinates.
def transform (x, y)
[@size - 1 - y, x]
end

##############################################################################
# Returns all the adjacent stones.
def adjacent (x, y)
transforms = [
[x , y + 1], # North
[x + 1, y + 1], # Northeast
[x + 1, y ], # East
[x + 1, y - 1], # Southeast
[x , y - 1], # South
[x - 1, y - 1], # Southwest
[x - 1, y ], # West
[x - 1, y + 1], # Northwest
]

transforms.select {|t| t.all? {|p| p >= 0 && p < @size}}
end
end

+ 3
- 0
lib/freeplay/version.rb View File

@@ -0,0 +1,3 @@
module Freeplay
VERSION = "0.0.1"
end

+ 47
- 0
test/board_test.rb View File

@@ -0,0 +1,47 @@
################################################################################
require(File.expand_path('helper', File.dirname(__FILE__)))

################################################################################
class BoardTest < Test::Unit::TestCase

##############################################################################
def test_bounds_checking
board = Freeplay::Board.new(:white, 20)
assert_equal(:empty, board[0, 0])

board[0,0] = :white
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 = Freeplay::Board.new(:white)
board.opponent_move(0, 0)
assert_raise(Freeplay::Board::InvalidMoveError) {board[1,2] = :white}
end
end

+ 3
- 0
test/helper.rb View File

@@ -0,0 +1,3 @@
################################################################################
require('test/unit')
require('freeplay')

Loading…
Cancel
Save