トーナメント表は二進木によく似ていますので二進木に表現する方法を考えます。
二進木なら括弧を使うのが一般的だと思いますが一般の人には解りにくいと思うので
回戦とチーム名の表から二進木を表現する方法を考えます。
言語の指定がなかったのでrubyを使いました。
表の表示はこれも指定がなかったのでHTML5のCANVASを使います。確認はDebianのiceweaselで行ないました。
IEではscriptの違いなどで表示できないかもしれません。
データとして
3,藤枝順心高校
1,清水FC女子
1,富士見FCガイア
1,静岡レディース
1,蒲原クラブ女子
2,桐陽高校
1,島田プリンセス
1,アスルクラロ沼津
3,磐田東高校
3,常葉橘高校
1,浜松泉FC
1,静岡大成高校
2,常葉橘中学校
1,東海大静岡翔洋高校
1,御殿場レディース
1,磐田北高校
1,ルクレMYFC
3,清水第八プレアデス
を用意します。
手順としてはデータを読み込み込んで二進木を生成します。この時に配置まで決めてしまいます。
本来なら二進木からCANVAS用の出力をすればいいのですが処理と表示のコードがごちゃまぜになるのでいったん文字列で出力しそれを今度はCANVAS用scriptに変換するという手順を取ります。
class Node
@@org = Struct.new(:x, :y).new(0,0)
def initialize(l, r=0, x=nil, y=nil)
@label = l
@round = r
@childs = x ? [x, y] : []
if x
lcp = x.cp
rcp = y.cp
@bounds = lcp+[ 160+48*r,lcp[1], 160+48*r,rcp[1], ]+rcp
else
@bounds = [ 0,@@org.y, 160,@@org.y, 160,@@org.y+64, 0,@@org.y+64, 0,@@org.y ]
@@org.y = @@org.y+64
end
end
attr_accessor :label, :round
def cp() [@bounds[2], (@bounds[3]+@bounds[5])/2] end
def find(label)
@label == label ? self : @childs.inject(nil) {|f, c| f ? f : c.find(label) }
end
def flatten
@childs.map {|c| c.flatten }.flatten+
[([@label,@round]+@bounds).join(',')]
end
end
クラスNodeが二進木の要素です。座標を覚えておく必要があるので多少ごちゃごちゃしています。16を基調とした定数を使っているので状況に合わせて変更する必要があります。
@boundsは表示に必要な座標値で都合があってパスで表しています。ので四角は5ポイントになります。
cp()はチーム名ボックスの右中央の座標です。
find()は二進木の中からlabelのNodeを探すものです。
flatten()は各Nodeの内容を文字にして出力し下記のようになります。
藤枝順心高校,3,0,0,160,0,160,64,0,64,0,0
清水FC女子,1,0,64,160,64,160,128,0,128,0,64
富士見FCガイア,1,0,128,160,128,160,192,0,192,0,128
M1,2,160,96,256,96,256,160,160,160
静岡レディース,1,0,192,160,192,160,256,0,256,0,192
蒲原クラブ女子,1,0,256,160,256,160,320,0,320,0,256
M2,2,160,224,256,224,256,288,160,288
M7,3,256,128,304,128,304,256,256,256
M11,4,160,32,352,32,352,192,304,192
桐陽高校,2,0,320,160,320,160,384,0,384,0,320
島田プリンセス,1,0,384,160,384,160,448,0,448,0,384
アスルクラロ沼津,1,0,448,160,448,160,512,0,512,0,448
M3,2,160,416,256,416,256,480,160,480
M8,3,160,352,304,352,304,448,256,448
磐田東高校,3,0,512,160,512,160,576,0,576,0,512
M12,4,304,400,352,400,352,544,160,544
M15,5,352,112,400,112,400,472,352,472
def parse(t)
agenda = t.split("\n").select {|s| s != ''}.map {|line|
r, l = line.chomp.split(',')
Node.new(l, r.to_i)
}
gcnt = Math.log2(agenda.inject(0) {|r, n| r + 2**(n.round-1) })
m = 0
1.upto(gcnt) {|r|
agenda.each_with_index {|n, i|
if n.round == r then
y = agenda.delete_at(i+1)
agenda[i] = Node.new("M#{m=m+1}", r+1, n, y)
end
} }
agenda[0]
end
parseはトーナメントのデータから二進木ほ生成する部分です。
まずチームごとにNodeを生成してagendaへ追加していきます。
Math.log2(agenda.inject(0) {|r, n| r + 2**(n.round-1) })
は何回戦必要か計算します。
次にagendaから1回戦ペアーを取り出し2回戦として登録していきます。
同じくagendaから2回戦ペアーを取り出し3回戦として登録していくことを必要な回戦分繰り返すことで二進木として全体が登録されます。
エラーチェックはしていないのでdataが変だと止まります。
def ptpath(path, rvs)
path.each_slice(2).map {|p|
rvs ? [ rvs[0]-p[0].to_i, p[1].to_i-rvs[1] ] : [ p[0].to_i, p[1].to_i ]
}
end
def canvas_to_html(games, rvs=nil)
cout = ['context.font = "16px sans-serif";']
puts games.join("\n")
puts "\n"
games.each {|game|
label, round, *path = game.split(',')
path = ptpath(path, rvs)
x, y = path.shift
cout << "context.beginPath();context.moveTo(#{x},#{y});"
path.each {|x, y| cout << "context.lineTo(#{x},#{y});" }
cout << "context.stroke();"
if path.size == 3
x, y = path.shift
x = rvs ? x-8-label.length*8 : x+8
y = (y+path[0][1])/2-4
else
if rvs
x = x-160/2-label.length*16/2
y = y+32
else
x = x+160/2-label.length*16/2
y = y+32
end
end
cout << "context.fillText(\"#{label}\",#{x},#{y});"
}
cout.join("\n")
end
ptpathは座標値データで文字で入っているのでそれを数値に直すものです。
canvas_to_htmlはgamesにNodeの情報が文字列の配列で格納されているのでそれを展開しながらcanvas用のscriptに変更します。
反転も考慮しているので煩雑になっています。
root = parse ARGF.readlines.join
canvas_script = canvas_to_html(root.find('M15').flatten)+
canvas_to_html(root.find('M16').flatten, [900,576])
template = <<"html"
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>トーナメント表</title>
<script type="text/javascript">
<!--
function draw(context) { #{canvas_script} }
function drawcanvas(name) {
//描画コンテキストの取得
var canvas = document.getElementById(name);
if (canvas.getContext)
draw(canvas.getContext('2d'));
}
//-->
</script>
</head>
<body onLoad="drawcanvas('canvas0')">
<h2>トーナメント表</h2>
<h3 style="text-align: center;">平成27年度 第37回 全日本女子サッカー選手権 静岡県決勝大会<br>組 合 せ</h3>
<canvas id="canvas0" width="1024" height="1280" style="background-color:pink;">
図形を表示するには、canvasタグをサポートしたブラウザが必要です。
</canvas>
</body>
</html>
html
open("index.html", "w") {|f| f.write(template) }
root = parse ARGF.readlines.join
メインの流れはARGF.readlines.joinでデータを取り込みそれをparseします。二進木はrootに格納されます。
canvas_script = canvas_to_html(root.find('M15').flatten)+
canvas_to_html(root.find('M16').flatten, [900,576])
canvas_to_html(root.find('M15').flatten)はM15からのトーナメント表のcanvas用scriptを出力します。
canvas_to_html(root.find('M16').flatten, [900,576])はM16からのトーナメント表を反転移動したcanvas用scriptを出力します。
canvas_script = canvas_to_html(root.flatten)とすることで全体のトーナメント表を表示させるとこも可能です。
ヒアドキュメントによってcanvas_scriptが埋め込まれたhtmlがtemplateに格納され
open("index.html", "w") {|f| f.write(template) }
でindex.htmlファイルに出力されます。
動作確認用ですのでコードは汚いです。結果も反映させたいのであれば入力データはカンマで区切られたCSVファイルのようなものなので情報の追加してNodeに反映させることで可能になるかと。
pdfにあるような綺麗なトーナメント表にするには条件の設定などが面倒になりあまり実用的ではないように感じました。