nachof-zheng 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.rdoc +41 -0
- data/bin/zheng +11 -0
- data/lib/zheng.rb +6 -0
- data/lib/zheng/actions.rb +27 -0
- data/lib/zheng/actions/database.rb +11 -0
- data/lib/zheng/actions/game.rb +25 -0
- data/lib/zheng/actions/player.rb +42 -0
- data/lib/zheng/game.rb +35 -0
- data/lib/zheng/parameters.rb +29 -0
- data/lib/zheng/player.rb +38 -0
- data/lib/zheng/rating.rb +42 -0
- data/lib/zheng/util.rb +30 -0
- metadata +72 -0
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
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,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
|
data/lib/zheng/player.rb
ADDED
@@ -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
|
data/lib/zheng/rating.rb
ADDED
@@ -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
|
+
|