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 @@
1
+*.gem
2
+*.rbc
3
+.bundle
4
+.config
5
+.yardoc
6
+Gemfile.lock
7
+InstalledFiles
8
+_yardoc
9
+coverage
10
+doc/
11
+lib/bundler/man
12
+pkg
13
+rdoc
14
+spec/reports
15
+test/tmp
16
+test/version_tmp
17
+tmp

+ 4
- 0
Gemfile View File

@@ -0,0 +1,4 @@
1
+source 'https://rubygems.org'
2
+
3
+# Specify your gem's dependencies in freeplay.gemspec
4
+gemspec

+ 22
- 0
LICENSE View File

@@ -0,0 +1,22 @@
1
+Copyright (c) 2012 Peter Jones <pjones@pmade.com>
2
+
3
+MIT License
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining
6
+a copy of this software and associated documentation files (the
7
+"Software"), to deal in the Software without restriction, including
8
+without limitation the rights to use, copy, modify, merge, publish,
9
+distribute, sublicense, and/or sell copies of the Software, and to
10
+permit persons to whom the Software is furnished to do so, subject to
11
+the following conditions:
12
+
13
+The above copyright notice and this permission notice shall be
14
+included in all copies or substantial portions of the Software.
15
+
16
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 1
- 0
README.md View File

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

+ 12
- 0
Rakefile View File

@@ -0,0 +1,12 @@
1
+################################################################################
2
+require('rake/testtask')
3
+require("bundler/gem_tasks")
4
+
5
+################################################################################
6
+Rake::TestTask.new do |t|
7
+  t.test_files = FileList['test/*_test.rb']
8
+  t.verbose = true
9
+end
10
+
11
+################################################################################
12
+task(:default => :test)

+ 17
- 0
freeplay.gemspec View File

@@ -0,0 +1,17 @@
1
+# -*- encoding: utf-8 -*-
2
+require File.expand_path('../lib/freeplay/version', __FILE__)
3
+
4
+Gem::Specification.new do |gem|
5
+  gem.authors       = ["Peter Jones"]
6
+  gem.email         = ["pjones@pmade.com"]
7
+  gem.description   = `head -1 README.md|sed 's/^# *//'`
8
+  gem.summary       = "The Freeplay client is used to connect a player to the server."
9
+  gem.homepage      = "git://pmade.com/freeplay"
10
+
11
+  gem.files         = `git ls-files`.split($\)
12
+  gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+  gem.test_files    = gem.files.grep(%r{^(test|spec|features)/})
14
+  gem.name          = "freeplay"
15
+  gem.require_paths = ["lib"]
16
+  gem.version       = Freeplay::VERSION
17
+end

+ 10
- 0
lib/freeplay.rb View File

@@ -0,0 +1,10 @@
1
+################################################################################
2
+module Freeplay
3
+
4
+  ##############################################################################
5
+  class Error < RuntimeError; end
6
+
7
+  ##############################################################################
8
+  autoload('VERSION', 'freeplay/version')
9
+  autoload('Board',   'freeplay/board')
10
+end

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

@@ -0,0 +1,146 @@
1
+class Freeplay::Board
2
+
3
+  ##############################################################################
4
+  class OutOfBoundsError  < Freeplay::Error; end
5
+  class InvalidStoneError < Freeplay::Error; end
6
+  class InvalidMoveError  < Freeplay::Error; end
7
+
8
+  ##############################################################################
9
+  # The width and height of the game board.  Since the board must be a
10
+  # square the width and height will always be the same.
11
+  attr_reader(:size)
12
+
13
+  ##############################################################################
14
+  # Don't create a game board yourself.
15
+  def initialize (player, size=10) # :nodoc:
16
+    @player, @size = player, size
17
+    @grid = Array.new(@size) {Array.new(@size, :empty)}
18
+    @opponent = @player == :white ? :black : :white
19
+    @last_opponent_move = nil
20
+    stone_check(@player, [:white, :black])
21
+  end
22
+
23
+  ##############################################################################
24
+  # Returns the stone type for the given coordinates.  This method
25
+  # takes two parameters, the first is the x coordinate and the second
26
+  # is the y coordinate.
27
+  #
28
+  # The allowed stone types are :empty, :white, and :black.
29
+  #
30
+  # Example (get the stone in the lower left corner):
31
+  #
32
+  #   board = Freeplay::Board.new(...)
33
+  #   board[0, 0] # => :empty
34
+  #
35
+  def [] (x, y)
36
+    x, y = transform(x, y)
37
+    bounds_check(x, y)
38
+    @grid[x][y]
39
+  end
40
+
41
+  ##############################################################################
42
+  # This method is for internal use only.
43
+  def []= (x, y, val) # :nodoc:
44
+    x, y = transform(x, y)
45
+    bounds_check(x, y)
46
+    move_check(x, y)
47
+    stone_check(val)
48
+    @grid[x][y] = val
49
+  end
50
+
51
+  ##############################################################################
52
+  # This method is for internal use only.
53
+  def opponent_move (x, y) # :nodoc:
54
+    x, y = transform(x, y)
55
+    @last_opponent_move = [x, y]
56
+    @grid[x][y] = @opponent
57
+  end
58
+
59
+  ##############################################################################
60
+  # Dumps a grid to the given output stream.
61
+  def dump (io, options={})
62
+    options = {
63
+      :empty => 'E',
64
+      :white => 'W',
65
+      :black => 'B',
66
+      :error => 'X',
67
+    }.merge!(options)
68
+
69
+    @grid.each do |row|
70
+      trans = row.map {|col| options[col] || options[:error]}
71
+      io.puts(trans.join(' '))
72
+    end
73
+  end
74
+
75
+  ##############################################################################
76
+  private
77
+
78
+  ##############################################################################
79
+  # Ensure the given coordinates are within the grid.
80
+  def bounds_check (x, y)
81
+    if [x, y].any? {|c| c < 0 || c >= @size}
82
+      message = "(#{x},#{y}) is out of bounds, board is #{@size}x#{@size}"
83
+      raise(OutOfBoundsError, message)
84
+    end
85
+  end
86
+
87
+  ##############################################################################
88
+  # Ensure the move is valid.
89
+  def move_check (x, y)
90
+    if !@last_opponent_move.nil?
91
+      allowed = adjacent(*@last_opponent_move).select do |(x,y)|
92
+        @grid[x][y] == :empty
93
+      end
94
+
95
+      if !allowed.empty? && !allowed.include?([x, y])
96
+        allowed_str = allowed.map {|(x,y)| "(#{x},#{y})"}.join(', ')
97
+        message  = "move (#{x},#{y}) must be adjacent to "
98
+        message += "(#{@last_opponent_move[0]},#{@last_opponent_move[1]}), "
99
+        message += "allowed moves: " + allowed_str
100
+        raise(InvalidMoveError, message)
101
+      end
102
+    end
103
+
104
+    if @grid[x][y] != :empty
105
+      message = "space (#{x},#{y}) already taken with #{@grid[x][y].inspect}"
106
+      raise(InvalidMoveError, message)
107
+    end
108
+  end
109
+
110
+  ##############################################################################
111
+  # Ensure the given stone is one of the allowed stones.
112
+  def stone_check (val, allowed=@player)
113
+    allowed = Array(allowed)
114
+
115
+    if !allowed.include?(val)
116
+      message  = "invalid stone or player `#{val}' must be"
117
+      message += " one of" if allowed.size != 1
118
+      message += ": "
119
+      message += allowed.map(&:inspect).join(', ')
120
+      raise(InvalidStoneError, message)
121
+    end
122
+  end
123
+
124
+  ##############################################################################
125
+  # Transforms grid coordinates to array coordinates.
126
+  def transform (x, y)
127
+    [@size - 1 - y, x]
128
+  end
129
+
130
+  ##############################################################################
131
+  # Returns all the adjacent stones.
132
+  def adjacent (x, y)
133
+    transforms = [
134
+      [x    , y + 1], # North
135
+      [x + 1, y + 1], # Northeast
136
+      [x + 1, y    ], # East
137
+      [x + 1, y - 1], # Southeast
138
+      [x    , y - 1], # South
139
+      [x - 1, y - 1], # Southwest
140
+      [x - 1, y    ], # West
141
+      [x - 1, y + 1], # Northwest
142
+    ]
143
+
144
+    transforms.select {|t| t.all? {|p| p >= 0 && p < @size}}
145
+  end
146
+end

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

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

+ 47
- 0
test/board_test.rb View File

@@ -0,0 +1,47 @@
1
+################################################################################
2
+require(File.expand_path('helper', File.dirname(__FILE__)))
3
+
4
+################################################################################
5
+class BoardTest < Test::Unit::TestCase
6
+
7
+  ##############################################################################
8
+  def test_bounds_checking
9
+    board = Freeplay::Board.new(:white, 20)
10
+    assert_equal(:empty, board[0, 0])
11
+
12
+    board[0,0] = :white
13
+    assert_equal(:white, board[0,0])
14
+
15
+    assert_raise(Freeplay::Board::OutOfBoundsError) {board[-1, 0]}
16
+    assert_raise(Freeplay::Board::OutOfBoundsError) {board[20, 21]}
17
+  end
18
+
19
+  ##############################################################################
20
+  def test_stone_checking
21
+    assert_raise(Freeplay::Board::InvalidStoneError) do
22
+      Freeplay::Board.new(:empty)
23
+    end
24
+
25
+    board = Freeplay::Board.new(:white)
26
+    assert_raise(Freeplay::Board::InvalidStoneError) {board[0,0] = :black}
27
+    assert_raise(Freeplay::Board::InvalidStoneError) {board[0,0] = :empty}
28
+    assert_raise(Freeplay::Board::InvalidStoneError) {board[0,0] = 'white'}
29
+
30
+    board[0,0] = :white
31
+    assert_raise(Freeplay::Board::InvalidMoveError) {board[0,0] = :white}
32
+  end
33
+
34
+  ##############################################################################
35
+  def test_adjacency
36
+    board = Freeplay::Board.new(:white)
37
+    board.opponent_move(0, 0)
38
+    board[0, 1] = :white
39
+    board[1, 1] = :white
40
+    board[1, 0] = :white
41
+    board[1, 2] = :white
42
+
43
+    board = Freeplay::Board.new(:white)
44
+    board.opponent_move(0, 0)
45
+    assert_raise(Freeplay::Board::InvalidMoveError) {board[1,2] = :white}
46
+  end
47
+end

+ 3
- 0
test/helper.rb View File

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

Loading…
Cancel
Save