nachof-zheng 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 Nacho Facello
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,41 @@
1
+ = Zheng
2
+
3
+ Zheng is a small utility to manage go ratings.
4
+
5
+ Zheng is based on the method used by the European Go Federation (described in http://gemma.ujf.cas.cz/~cieply/GO/gor.html).
6
+
7
+ == About the name
8
+ The name Zheng comes from the chinese 证 (zhèng), which, according to Sensei's Library (http://senseis.xmp.net/?ChineseGoTerms), means a rank certificate or diploma.
9
+
10
+ == Dependencies
11
+ You will need rubygems installed. Also, you will need the following gems:
12
+ * sequel
13
+ * sqlite3
14
+ * shellwords
15
+
16
+ == Usage
17
+
18
+ === Adding players
19
+ To create a player, do
20
+ bin/zheng player add "Player Name" 1k
21
+ You can also use the numeric rating for the initial rating:
22
+ bin/zheng player add "Player Name" 2000
23
+ Given that 1k is 2000 points in the rating system, both commands are equivalent.
24
+
25
+ To see a list of players:
26
+ bin/zheng player list
27
+
28
+ === Adding a game
29
+ To add a game, do
30
+ bin/zheng game add "First Player" "Second Player" winner
31
+ where _winner_ is either left, right, first, or second. Left and first are the same, and right and second are the same, too.
32
+
33
+ When a game is added the rank of its participants is immediately updated to reflect the result.
34
+
35
+ === External players
36
+ You can also add "external players". External players are there to allow to record games played against other people, outside the system you're managing. The idea of this is to keep a certain correspondence to global ranks, and avoid too much drifting, either by deflation or inflation. To add an external player, do
37
+ bin/zheng player add_external "Player Name" 1d
38
+
39
+ External players do not normally show up on player listings, and their rank is not changed by games. If you want to see a list including also external players, use
40
+ bin/zheng player list all
41
+
data/bin/zheng ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'init'))
4
+
5
+ include Zheng
6
+
7
+ if ARGV.size > 0
8
+ Actions::call(*ARGV)
9
+ else
10
+ Actions::script $stdin
11
+ end
data/lib/zheng.rb ADDED
@@ -0,0 +1,6 @@
1
+ require File.join(File.dirname(__FILE__), 'zheng', 'parameters')
2
+ require File.join(File.dirname(__FILE__), 'zheng', 'util')
3
+ require File.join(File.dirname(__FILE__), 'zheng', 'rating')
4
+ require File.join(File.dirname(__FILE__), 'zheng', 'player')
5
+ require File.join(File.dirname(__FILE__), 'zheng', 'game')
6
+ require File.join(File.dirname(__FILE__), 'zheng', 'actions')
@@ -0,0 +1,27 @@
1
+ module Zheng
2
+ module Actions
3
+ module_function
4
+ def call(mod, action, *params)
5
+ "Actions::#{mod.camelize}".constantize.send(action.to_sym, *params)
6
+ rescue NameError
7
+ raise NoActionFound.new("#{mod} #{action} (#{$!})")
8
+ end
9
+
10
+ def script text
11
+ text.each_line do |l|
12
+ begin
13
+ call *Shellwords.shellwords(l) unless l.strip.empty? || l.strip[0,1] == '#'
14
+ rescue
15
+ Zheng::output("An error occured: #{$!}")
16
+ end
17
+ end
18
+ end
19
+
20
+ class NoActionFound < Exception; end
21
+ end
22
+
23
+ end
24
+
25
+ require File.join(File.dirname(__FILE__), 'actions', 'player')
26
+ require File.join(File.dirname(__FILE__), 'actions', 'game')
27
+ require File.join(File.dirname(__FILE__), 'actions', 'database')
@@ -0,0 +1,11 @@
1
+ module Zheng
2
+ module Actions
3
+ module Database
4
+ module_function
5
+ def clear
6
+ Zheng::Player.delete
7
+ Zheng::Game.delete
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ module Zheng
2
+ module Actions
3
+ module Game
4
+ module_function
5
+ def add lname, rname, winner
6
+ left, right = [ lname, rname ].map { |n| Zheng::Player.named(n) }
7
+ game = Zheng::Game.create :left => left, :right => right, :winner => winner_sym(winner)
8
+ unless left.external?
9
+ left.rating = left.rating + game.rating_change_for(:left)
10
+ left.save
11
+ end
12
+ unless right.external?
13
+ right.rating = right.rating + game.rating_change_for(:right)
14
+ right.save
15
+ end
16
+ end
17
+
18
+ def winner_sym winner
19
+ return :left if winner == "first"
20
+ return :right if winner == "second"
21
+ return winner.to_sym
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,42 @@
1
+ module Zheng
2
+ module Actions
3
+ module Player
4
+ module_function
5
+
6
+ def add(name, rank)
7
+ if Zheng::Rating.is_rank? rank
8
+ Zheng::Player.create(:name => name, :rank => rank)
9
+ else
10
+ Zheng::Player.create(:name => name, :rating => rank.to_i)
11
+ end
12
+ end
13
+
14
+ def add_external(name, rank)
15
+ p = add(name, rank)
16
+ p.set_external!
17
+ p.save
18
+ end
19
+
20
+ def list what="local"
21
+ what_to_list = (what == "all") ? :all : :local
22
+ Zheng::Player.list(what_to_list).each do |p|
23
+ Zheng::output(sprintf "%-20.20s %s (%s)", p.name, p.rank, p.rating)
24
+ end
25
+ end
26
+
27
+ def delete name
28
+ Zheng::Player.named(name).destroy
29
+ end
30
+
31
+ def set_rating name, rank
32
+ p = Zheng::Player.named(name)
33
+ if Zheng::Rating.is_rank? rank
34
+ p.rank = rank
35
+ else
36
+ p.rating = rank.to_i
37
+ end
38
+ p.save
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/zheng/game.rb ADDED
@@ -0,0 +1,35 @@
1
+ module Zheng
2
+ class Game < Sequel::Model
3
+ set_schema do
4
+ primary_key :id
5
+ foreign_key :left_id, :players
6
+ foreign_key :right_id, :players
7
+ text :winner, :null => false
8
+ end
9
+
10
+ include Util
11
+
12
+ belongs_to :left, :class => :Player
13
+ belongs_to :right, :class => :Player
14
+
15
+ sequel_accessor :right_id, :left_id
16
+
17
+ def winner
18
+ @values[:winner].to_sym
19
+ end
20
+ def winner= v
21
+ @values[:winner] = v.to_s
22
+ end
23
+
24
+ def rating_change_for which_player
25
+ which_opponent = (which_player == :left) ? :right : :left
26
+ player, opponent = [which_player, which_opponent].map { |p| send(p) }
27
+ return Parameters::Con(player.rating) * (result(which_player) - Parameters::expected(player.rating, opponent.rating))
28
+ end
29
+
30
+ def result which_player
31
+ (which_player == winner) ? 1 : 0
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,29 @@
1
+ module Zheng
2
+ module Parameters
3
+ module_function
4
+ # These values are taken from the European Go Rating. See http://gemma.ujf.cas.cz/~cieply/GO/gor.html
5
+ E = 0.014
6
+ def A rating
7
+ return 70 if rating >= 2700
8
+ return rating * (-1.0/20) + (205)
9
+ end
10
+ def Con rating
11
+ return 10 if rating > 2700
12
+ return 116 if rating < 100
13
+ parts = [ { :from => 100, :to => 200, :p => -6.0/100, :d => 122 },
14
+ { :from => 200, :to => 1300, :p => -5.0/100, :d => 120 },
15
+ { :from => 1300, :to => 2000, :p => -4.0/100, :d => 107 },
16
+ { :from => 2000, :to => 2400, :p => -3.0/100, :d => 87 },
17
+ { :from => 2400, :to => 2600, :p => -2.0/100, :d => 63 },
18
+ { :from => 2600, :to => 2700, :p => -1.0/100, :d => 37 } ]
19
+ parts.each do |f|
20
+ return f[:p] * rating + f[:d] if rating >= f[:from] && rating <= f[:to]
21
+ end
22
+ end
23
+
24
+ def expected ratingA, ratingB
25
+ return (1 - E) - expected(ratingB, ratingA) if ratingA > ratingB
26
+ return 1.0 / (Math::E ** ((ratingB - ratingA) / A(ratingA)) + 1)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ module Zheng
2
+ class Player < Sequel::Model
3
+ set_schema do
4
+ primary_key :id
5
+ text :name, :null => false, :unique => true
6
+ integer :rating, :null => false
7
+ boolean :external, :default => false
8
+ end
9
+
10
+ include Util
11
+
12
+ sequel_accessor :name, :rating
13
+
14
+ def rank
15
+ Rating.new(rating).rank
16
+ end
17
+ def rank= rank
18
+ self.rating = Rating.new(rank).to_i
19
+ end
20
+
21
+ def external?
22
+ return @values[:external]
23
+ end
24
+ def set_external!
25
+ @values[:external] = true
26
+ end
27
+
28
+ def self.named name
29
+ raise "Can't find player #{name}" if (found = self[:name => name]).nil?
30
+ found
31
+ end
32
+
33
+ def self.list what=:local
34
+ what_to_order = (what == :all) ? self : filter(:external => false)
35
+ what_to_order.reverse_order(:rating)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,42 @@
1
+ module Zheng
2
+ class Rating
3
+ def initialize(rating)
4
+ @rating = rating.respond_to?(:match) ? from_rank(rating) : rating.to_i
5
+ end
6
+
7
+ def from_rank(rank)
8
+ parts = rank.match(/(\d+)(k|d)/)
9
+ return 2000 + (parts[1].to_i * 100) if parts[2] == 'd'
10
+ return 2100 - (parts[1].to_i * 100) if parts[2] == 'k'
11
+ raise "Invalid rank"
12
+ end
13
+
14
+ def to_i
15
+ @rating
16
+ end
17
+
18
+ def rank
19
+ level = (@rating / 100.0).round
20
+ return (level - 20).to_s + 'd' if level > 20
21
+ return (21 - level).to_s + 'k'
22
+ end
23
+
24
+ def + other
25
+ self.class.new(@rating + other.to_i)
26
+ end
27
+
28
+ def == other
29
+ @rating == other.to_i
30
+ end
31
+
32
+ def eql? other
33
+ @rating == other.to_i
34
+ end
35
+
36
+ def self.is_rank? string
37
+ return false unless string.respond_to? :match
38
+ return false unless string.match /\d+(k|d)/
39
+ return true
40
+ end
41
+ end
42
+ end
data/lib/zheng/util.rb ADDED
@@ -0,0 +1,30 @@
1
+ module Zheng
2
+ module Util
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def sequel_accessor *params
10
+ params.each do |method|
11
+ class_eval <<-end_eval
12
+ def #{method.to_s}
13
+ @values[:#{method.to_s}]
14
+ end
15
+ def #{method.to_s}= v
16
+ @values[:#{method.to_s}] = v
17
+ end
18
+ end_eval
19
+ end
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ module_function
26
+ # I put it here so I can easily silence it for tests
27
+ def output text
28
+ puts text
29
+ end
30
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nachof-zheng
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nacho Facello
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-09 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: sequel
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.8.0
23
+ version:
24
+ description:
25
+ email: nacho@nucleartesuji.com
26
+ executables:
27
+ - zheng
28
+ extensions: []
29
+
30
+ extra_rdoc_files: []
31
+
32
+ files:
33
+ - lib/zheng/actions/database.rb
34
+ - lib/zheng/actions/game.rb
35
+ - lib/zheng/actions/player.rb
36
+ - lib/zheng/actions.rb
37
+ - lib/zheng/game.rb
38
+ - lib/zheng/parameters.rb
39
+ - lib/zheng/player.rb
40
+ - lib/zheng/rating.rb
41
+ - lib/zheng/util.rb
42
+ - lib/zheng.rb
43
+ - README.rdoc
44
+ - LICENSE
45
+ has_rdoc: false
46
+ homepage:
47
+ post_install_message:
48
+ rdoc_options: []
49
+
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.2.0
68
+ signing_key:
69
+ specification_version: 2
70
+ summary: A go ratings calculator, based on the method published by the European Go Federation
71
+ test_files: []
72
+