競馬であそぶ 5

健康が第一。なのに自殺者は増える 




3.   スクリプトを改造(1)  

Perl/Tk での、出馬表に挑戦したいと思います。

どんなにスクリプトの良さを勧めても、いまさら一からやるんじゃタマランゼ〜というのが 普通です。
sSeia はイロイロなサンプルを繋ぎ合わせてるうちに、のめりこんで我流のまま 仕事でも使うようになっ
たダケ。我流でも困らない自由さと、多様性のおかげで結構あそべました。

スクリプトの値打ちは、自分の考えや閃きをデータで確認し更に次のステップへの扉 を手軽かつ簡単に
開いてくれることだと思っています。
普通は競馬本を読んで実戦で確かめるのに2〜3週間かかりますが、スクリプトならデータ を検証するの
に3〜4日。更に浮かんだアイディアを練るのに一週間、全く誰も想いつかない必勝法が 見え隠れしたり、
しなかったりでホースプレイヤーとしての腕を上げること間違いありません、 かな。

シミュレートの怖さは、過去の都合のいい勝ちデータを集めることばかりに夢中になって、 気がつけば、
馬券で儲からずに馬券本を書けるくらいデータがたまるということくらいで、 まぁそれはそれで悪い話じゃ
ないけどアホっぽいと、、反省会で誰かがいってたケド。
その辺がデータベース&スクリプトの落とし穴かも。


そこでココからは、kbsTk_0017.pl について書いていきます。ぜひ実物を参照してください。

実物サンプル kbsTk_0017 (リンクを切りました)

実際、自分で書いたスクリプトでも3ヶ月もすると、どうしてこういう書き方に なるのだ?と悩むくらいに忘
れるのが普通で、殆ど自分のためだったりもするんですが、、
コメント行を除いても、サブルーチン込みで1500行を超える非効率なものですので ポイントをしぼって
いきます。

#!/usr/bin/perl

use strict ;
use Tk ;
use encoding "sjis" ;
use Time::Local ;
use Config::Simple ;
my $cfg = new Config::Simple( 'kbcfg.ini' ) ;
require 'kbsub.pl' ;				#

頑なに行頭のPerlパスを書き換えていたのに、ふと気が変って慣例どおりの表記に変更。
Config-Simple については難しいという印象は無いので検索ででも調べる。
use ⇒使うモジュールは使うよ!と断りを入れておく。
encoding は、操作画面での文字化け対策。すきなように、
kbsub.pl は他のスクリプトでも使うかもしれないので別にした。
{
    my @files = map { substr( $_, -12, 8) } glob "$pass/RADW*.DAT" ;
    @ymdfile = sort { $b <=> $a ; } @files ;
}
ここでは、ワイルドカード(*)を使っている。glob は便利なので注目。map も。
ソート⇒$b、$aがこの順だと、降順。( <=> 数値順、 cmp ASCII順 )
@files の前に my が付いている。この場合@files という名の配列は、{ から } までの間だけでイキ ている。
@ymdfile は、その前に { } の外で、my 付きで初期化されているネ。

関係ないけど⇒ $yama = 'Yama' という変数を使って、Yamada を探す場合、/$yamada/ では、
変数 $yama ではなく $yamada になるので /${yama}da/ と書くのが正しい。


★★★ さぁ、ここからが本番です。

my $mw = MainWindow->new( -title => "kbsTk_0017" ) ;
$mw->optionAdd( "*Font" => 'fixed 10' ) ;
my $f0 = $mw->Frame() ;
my $f1 = $mw->Frame() ;
my $f2 = $mw->Frame() ;

まず、Perl/Tk のメインウィンドを、$mw として作ります。
-title で指定した文字列が、ウィンドの左上に表示されます。
そのメインウィンドのなかに、フレームを3つ用意しました。$f0, $f1, $f2 です。

for my $i ( 0..2 ) { 	# 情報選択ボタン
    my $j = $selec[$i] ;
    $rb[$i] = $f0->Radiobutton( -text => $j,
                                -indicatoron => 'no',
                                -selectcolor => "#FFFFAB",
                                -value => $i,
                                -command => [ \&push_buttc, \$rb[$i] ] )
        ->pack( -side => 'left' ) ;
}
$rb[0]->invoke() ;
$f0->pack( -anchor => 'c' ) ;


一番上の三つのボタン(=ラジオボタン)
$f0 のフレームに属している。

ここが$f1 のフレームの部分

一番下が$f2 のフレーム部分

-anchor や -side、 その他の使い方は自信ないので、そのうち訂正。

ここで例えば、上のラジオボタンを6個に増やしたければ、一番上の行の 0..2 を 0..5 に すればいい。
しかしソレだと訳のわからないボタンになるので、配列 @selec に , '三連単', '三連複', 'ぽんちく'
などとボタンの名前を追加する。
すると、名前のついたボタンが3個追加されます。

for my $i ( 1..10 ) {		# 場名のついたボタン
    my $j = $ba_of[$i] ;
    $jon[$i] = $f1->Button( -text => $j, -command => [ \&push_buttb, $i ] )
                ->pack( -side => 'left' ) ;
}

# スクロールバー〜 レース日指定用リストBOX
my $yscrl = $f1->Scrollbar()->pack( -side => 'right', -fill => 'y') ;
my $ls = $f1->Listbox( -height           => 4,
                       -width            => 9,
                       -selectmode       => 'single',
                       -background       => 'white',
                       -foreground       => 'blue',
                       -selectbackground => 'darkgreen',
                       -selectforeground => 'yellow',
                       -yscrollcommand   => [ 'set', $yscrl ] )
    ->pack( -side   => 'right',
            -anchor => 'e',
            -fill   => 'both',
            -expand => 'yes' ) ;

$yscrl->configure( -command => [ 'yview', $ls ] ) ;
foreach ( @ymdfile ) {
    my $idx = $ls->index( 'end' ) ;
    $ls->insert( 'end', $_ ) ;
    if ( $idx % 2 ) {
        $ls->itemconfigure( $idx,
                            -background       => 'beige',
                            -selectbackground => 'darkolivegreen' ) ;
    }
}
$f1->pack( -anchor => 'c' ) ;
$ls->bind( "<ButtonRelease>", \&get_ymd ) ;

ここが2番目のフレーム、$f1 のところです。
場名を指定するボタンと、レース日を指定するリストボックスですね。
一番下は、リストボックスのところでマウスボタンが離されたときに get_ymd というサブルーチンが
実行されるチュウことです。Tk ってスゴイですね。

for my $i ( 1..12 ) { 	# レースNo.のついたボタン
    my $j = ' ' . $i . 'R ' ;
    $f2->Button( -text => $j, -command => [ \&push_butta, $i ] )
        ->pack( -side => 'left' ) ;
}
my $l1 = $f2->Label( -textvariable => \$buffer,
                     -foreground => "#800000",
                     -background => "#f0f0d0",
                     -font => [ "FixedSys", 10 ] )->pack() ;

$f2->pack( -anchor => 'c' ) ;
3番目のフレーム、$f2 のところです。
レースナンバーを指定するボタンですね。

MainLoop() ;

★★★ Tk はここまでです。あとはサブルーチンだけ、、
意外とメインの部分は少なくて済みました。
ここまでで表示されているボックス、ボタン、ラベルなどのWidget を使っていろいろ な仕事を
させようというわけです。

Widget は種類もタクサンあるので使いこなせば、なんでも出来そうです。
このへんの改造は、簡単にテストできるのでイロイロやってみましょう。

ここまでで操作用の画面の構成が終わったので、このあとはサブルーチンについて進めて い
きます。




4.   スクリプトを改造(2)   

サブルーチンについて説明しようと思ったけれど、実は自分はナニも知らない と気がついて愕然。
しかしまぁ、いい加減さと変わり身の速さだけが取り柄なので立ち直りは早い。。
要するに、、サブでしょ。サブ。

サブルーチンを実行するときには、普通アタマに & をつけます。引数あれば ( ) 内に。
実行する場所より前にサブルーチンを置いた場合はナニも付けなくてもいいのですが、別に格好つける
必要は無いので付けましょう。ってこれだけかい知ってることは〜

sub get_ymd {	# リストBOXから、レース日を指定
    $yymmdd = $ls->get( 'anchor' ) ;
    ($yyyy, $mm, $dd ) = unpack 'A4A2A2', $yymmdd ;
    $mmdd = sprintf( "%02d%02d", $mm, $dd ) ;
    # タイムローカル〜エポック数
    $tml = timelocal( 0, 0, 0, $dd, $mm - 1, $yyyy - 1900 ) ;
    $sa_day = 60 * 60 * 24 ;		# 86400 一日分
    &hrt_jyo() ;
    &rd_dat() ;
    &rd_base() ;
}

一番最初に働くサブルーチン get_ymd です。日付が決まらないと何も始まりません。
ココではリストBOXから持ってきた日付からイロイロ準備していますね。
エポック数は、結構使うので自力で調べた方がいいと思います。

sprintf の後の( )を、自分はあまり使わないのですがココでは使っています。どっちでも好き なように
してください。

更に、このサブルーチンのなかで三つのサブルーチンを実行させています。
最後の3行の hrt_jyo、rd_dat、rd_base です。
以下、順に説明します。

sub hrt_jyo {	############## 開催場の配列 ##########
    @kaisai = () ;
    my $schedF = "$pass/YSNW" . $yyyy . '9999.dat' ;
    open DAT, $schedF or die "?? $schedF ??:$!\n" ;
    while ( <DAT> ) {
        chomp ;
        my $record = $_ ;
        my ( $nngp, $jocd ) = unpack '@11A8@19A2', $record ;
        if ( $yymmdd == $nngp ) {
            push @kaisai, $jocd ;
        }
        last if $yymmdd < $nngp ;
    }
    close DAT ;
    &sep_k() ;
}

sub sep_k {
    for my $i ( 1..10 ) {
        $jon[$i]->configure( -state => 'disabled', -backgr => 'gray' ) ;
        for my $j ( @kaisai ) {
            $jon[$i]->configure( -state => 'normal' ) if $i == $j ;
        }
    }
}

開催スケジュールのファイルから開催場所(のコード)を、@kaisai という名の配列に取り込みます。
そして最後の sep_k というサブルーチンで、開催されていない場所のボタンを無効に しています。

別に $record に $_ を代入してから unpack する必要はありませんね。
my $record = $_ ; の行を削除して、次の行の $record を $_ に書き換えます。
この辺、カットアンドペーストの祟りかもしれませんね。

-background を -backgr と省略していますが、この辺は好きなように。-bg でもOKな筈です。
ルールに抵触しない範囲で省略してるつもりなんですが、あまり真似しない方がいいです。ハイ。

sub rd_dat {
    &disp_1() if $dance ;
    print	"\t ${yyyy}年 ${mm}月 ${dd}日 データ読み込み中..\n" ;
    {	# 出馬表と出走馬データ
        my $datRA = "$pass/RADW" . $yymmdd . '.DAT' ;		# 出馬表レースデータ
        -e $datRA or $datRA = "$pass/RABW" . $yymmdd . '.DAT' ;
        -e $datRA or $datRA = "$pass/RAGW" . $yymmdd . '.DAT'
                  or die "?? 出馬表データ RA〜 が見つからない ??:$!\n" ;
        my $datSE = "$pass/SEDW" . $yymmdd . '.DAT' ;		# 出走馬データ
        -e $datSE or $datSE = "$pass/SEBW" . $yymmdd . '.DAT' ;
        -e $datSE or $datSE = "$pass/SEGW" . $yymmdd . '.DAT'
                  or die  "?? 出走馬データ SE〜 が見つからない ??:$!\n" ;
        open DAT, $datRA ;
        while( <DAT> ) {
            chomp ;
            my ( $jcd, $rnum ) = unpack '@19 A2 @25 A2', $_ ;
            $racejho[$jcd][$rnum] = $_ ;
        }
        close DAT ;
        open DAT, $datSE ;
        while ( <DAT> ) {
            chomp ;
            my ( $jcd, $rnum, $umbn ) = unpack '@19 A2 @25 A2 @28 A2', $_ ;
            $umadata[$jcd][$rnum][$umbn] = $_ ;
        }
        close DAT ;
    }
}

最初の &disp_1() if $dance ; というのは、テキスト画面出力のテストですね。
kbcfg.ini で設定していれば、テスト画面が出ますが、暫らく使う予定はありません。
RADW、SEDW、だけでなく BW、GWのファイルも日付が一致すれば読み込めるようにしてあり ますが、
現行では対応させていません。前日前々日の前売りが必要ならコレを活かして、、

ここで、配列の配列とかの説明をしても混乱するだけなので省略。理屈より実行です。

sub rd_base {
    my @mfile = grep { -e and /UMPW|KSPW|RAQW|SEQW/i } glob "$pass/*.DAT";
                # -T で取りこぼすので -e 。。ファイル名を限定しているが
    for my $file ( @mfile ) {
        my $fn = ( split '/', $file )[-1] ;
        my ( $head, $nn, $gg, $pp ) = unpack '@0A4@4A4@8A2@10A2', $fn ;
        next if $gg > 12 ;
    # タイムローカル〜エポック数で計算。
        my $hdksa = $tml - timelocal( 0, 0, 0, $pp, $gg - 1, $nn - 1900 ) ;
    # 日付がレース日と同じ or 前で6日未満でなければスキップ。 以下手抜き
        next if ( $hdksa < 0 or $hdksa > $sa_day * 5 ) ;

rd_base は少し長いので幾つかに分割し、ファイルの特定方法も変更してしまうつもり ですが、現状で
説明します。
この最初の部分で、 @mfile というこのサブルーチン内だけに棲息している配列に UMPW か KSPW
か RAQW か SEQW かが含まれているファイル名を(パスも一緒に)保存します。
そのあと対象のレース日に対応したファイルを抽出、という所までが上の部分です。

        if ( $head eq 'RAQW' ) {
            open DAT, $file or die "?? $file ??:$!\n" ;
            until ( eof( DAT ) ) {
                my $record ;
                my $dm = read ( DAT, $record, 1271 ) ;
                my ( $raceID, $grcd, $ksoshcd, $ksokgcd, $jkcd, $kyo, $trcd, $ssto ) =
                unpack '@11 A16 @614 A1 @616 A2 @618 A3 @634 A3 @697 A4
                        @705 A2 @883 A2', $record ;
                my ( $nen, $gat, $pii ) = unpack '@0 A4 @4 A2 @6 A2', $raceID ;
                my $rtml = timelocal( 0, 0, 0, $pii, $gat - 1, $nen - 1900 ) ;
                next if $tml - $rtml > $sa_day * 400 ;
                # レースIDをキーに
                $kakora{ $raceID } =
                    [ $rtml, $grcd, $ksoshcd, $ksokgcd, $jkcd, $kyo, $trcd, $ssto ] ;
            }
            close DAT ;
        }

ここでは rd_base の中の、RAQW が名前に含まれているファイル処理の部分だけを取り 上げること
にします。
早速、目的のファイルを一行づつ読み込みます。必要なデータを変数に収めてから、raceID をキー
に配列のリファレンスの形でハッシュとして保存します。

難しく言ってますが、この場合は開催年月日とレース場、レースナンバがわかる部分を切り取ってそれ
をraceID にしています。その raceID 用の箱の中に、必要なデータを入れておいたので、あとで使う
ときに、raceID をキーにして取り出すことができるよ!ということなんです。

あ、、ミスがありますね。 next if の行の $sa_day * 400 は $sa_day * $itumade の間違いです。
訂正しておいてください。折角 kbcfg.ini で設定してもこの部分では無意味になってました。

他の部分も、基本的には似たようなモンです。ということで、 rd_base の説明を終わりにします。

sub push_buttb {
    $bbcd = shift ;
    for my $i ( @kaisai ) {
        if ( $i == $bbcd ) {
            $jon[$i]->configure( -backgr => "#FFFFAB" ) ;
        } else {
            $jon[$i]->configure( -backgr => 'gray' ) ;
        }
    }
}

push_buttb 場所ボタンを押したあと、ボタンの色が変るヤツです。
引数は直ちに、変数 $bbcd に入れられます。

sub push_butta {
    $racenum = shift ;
    $buffer = "${racenum}R" ;
    &step1() ;
}

sub step1 {
    if ( $racenum and $bbcd and $yymmdd ) {
        &kbs() ;
    }
}

push_butta レースナンバーボタンを押すと右側にレースナンバーが出ます。
引数は直ちに、変数 $racenum に入れられ、そして、これがスイッチになります。
step1 は要件が整っていれば、一番肝心なサブルーチンkbs を起動させます。

sub push_buttc {	# $rad の値で分岐
    my $w = ${$_[0]} ;
    $rad = ${$w->cget(-variable)} ;
}

push_buttc 一番上の条件分岐ボタン用です。
選択されると、ラジオボタンの何番目が押されたのかが、変数 $rad に入れられます。

普通サブルーチンの引数は、例えば abcd というサブルーチンの場合
&abcd( $qwert, $asdf, $zxcv ) とか &abcd( @qwerty ) のように渡されるのですが、
Tk の部分に関しては、上のスクリプトのように [ \&push_butta, $i ] というような特殊な
形、それも各場面でリファレンスが多用されたりします。
中途半端に、Perl を知っているより知らないほうがスンナリ行けるかもしれません。


残っている最大のサブルーチン? kbs は殆ど前の denm.pl をそっくり { } で囲った だけです。
手入れ手直し見直し厚化粧が必要なので、全体の説明はしません。
ただし、encoding 後の固定長ファイルの扱い方で困った点などについて、次回にでも少し触
れておこうかなと思っています。


INDEXへ トップページへ 次へ