人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

プログラミングの質問です。
http://www.nposhifa.net/wp-content/uploads/2015/08/0f0d47b8a2a400cb0074e360e215a0bd.pdf
のようなトーナメント表をデータから表示するプログラムが可能なのかどうかおしえてください。
表の特徴は、シードチームが2回戦から登場、3回戦から登場、などがあります。
チーム数から自動的にトーナメント表を出力するプログラムは見たことがありますが、上記の条件では使用不可能と思われます。
回答方法は、URL、またはサンプルプログラムそのものでお願い致します。

●質問者: khaie
●カテゴリ:ウェブ制作
○ 状態 :終了
└ 回答数 : 2/2件

▽最新の回答へ

1 ● 牛乳先生(tukihatu)
●150ポイント

できないことはないですが…期待するようなプログラムにはならないと思います。(例はPHPっぽく)

$seed1 = 4;//第一シード高の数
$seed2 = 2;//第二シード高の数
$name = array("1高","2高","3高","4高" …);//高の名前


$seed1name = array("シード1高","強い高","特別高","常勝高");//第一シード高の名前
$seed2name = array("シード2高","主催高");//第二シード高の数

シード高が決まった高校ではない完全ランダムの場合は上、決まっている場合はしたのような処理も必要になります。
あとは、シードの数を考えてトーナメントを表示するプログラムを書けばいいとおもいます。

$team = 18;//全校数
$setfirst = ($set - $seed1 - $seed2)/$seed1 ;//シードを除いた数 割る シード1(小数切捨てしてその分はどこかに付け加える処理をつけないといけない)

$result = array();
for($i=0;$i<$seed1;$i++){//シード1は必ずバラけるのでその分会場を増やす
 $result[$i] = array();
 for($k=0;$k<$setfirst;$k++){//シードなし高の数だけまわす
 $rnd = rnd(0,$team-1);//ランダム数値発生
 $result[$i][] = array($name[$rnd],3);//高校名+シード順位
 delete $name[$rnd]//使った名前を配列から削除
 }
 //最後にシード高を追加
 $rnd = rnd(0,$seed1-1);//ランダム数値発生
 $result[$i][] = array($seed1name[$rnd],1);
 delete $name[$rnd]//使った名前を配列から削除
}

(このコードはdeleteが命令違うので動きませんがこんな感じ)
シード2が入ってませんが、シード1と同じように入れればいけると思います。
あとは作ったデータを使って、線を書いたりするコードを書けばOKかと。


ただ複雑なシードトーナメント表になるとプログラムの作りこみが大変だと思うので、自分で作ったほうが手っ取り早そうな気がします。


khaieさんのコメント
>>自分で作ったほうが… トーナメント表を作るのではなく、結果データからWEB上にトーナメント表を表示したいと考えています。 添付画像URLは静岡県サッカー協会のものですが、エクセルから画像を作って貼り付けてあります。 画像そのものが結果データとなっているので、記録として検索等で問題があります。 サッカーはほとんどの場合くじ引きで対戦が決まります。 剣道などのように組み合わせプログラムが使われることはほぼないと思います。 ですので、組み合わせは決定していて、それをデータとしてどう表記するのか。 そちらの方がめんどうな気がしてきました。。。

牛乳先生(tukihatu)さんのコメント
組み合わせ決定後や試合結果を表にしたいのですね。 どうでしょうね…勝ち負けなどのデータをプログラムで表記していくのはさらに難しそうですね… 配列("優勝高","シード1","第一試合結果5-3","4-2","決勝戦3-0") 配列("1高","シード3","第一試合結果3-5") とかデータ形式を決めて、配列に入っているデータによって表示を変える形になるのかな。

khaieさんのコメント
校名 第1回戦 第2回戦 第3回戦 第4回戦 A校 * b c d B校 a b c d C校 * * * d D校 a b c d E校 * * c d こんな感じの表はできたのですが…

2 ● tobeoscontinue
●150ポイント

トーナメント表は二進木によく似ていますので二進木に表現する方法を考えます。
二進木なら括弧を使うのが一般的だと思いますが一般の人には解りにくいと思うので
回戦とチーム名の表から二進木を表現する方法を考えます。
言語の指定がなかったので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
#print "#{@label}(#{x.label if x}, #{y.label if y}) #{@bounds.join(',')}\n"
end
attr_accessor :label, :round
 def cp() [@bounds[2], (@bounds[3]+@bounds[5])/2] end
def find(label) # Enumerableのfindはブロックが値を返すとその値ではなく要素を返すので再帰では意味をなさない。
@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(',')]
#["#{@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 # nのroundがrならば次も同じrでなければならない。
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にあるような綺麗なトーナメント表にするには条件の設定などが面倒になりあまり実用的ではないように感じました。

関連質問

●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ