ラベル Python の投稿を表示しています。 すべての投稿を表示
ラベル Python の投稿を表示しています。 すべての投稿を表示

2021年2月7日日曜日

PyLSD 0.0.2 の修正 on Python 3.9.1

 Pythonで画像から直線を抽出する場合,Hough変換よりLSD(Line Segment Detector)を実装したpylsdの方が速くて精度が良いという話を見た.

pythonの直線検出は間違いなくOpenCVではなくPylsdが優れている

現在,python3.9.1でpylsdがpip3からインストールできるが,動かないので修正した.致命的なのは,テンポラリファイルのファイル名の文字列をPythonからCのライブラリに渡す所だった.パッチファイルを以下に掲載する.


diff -ru pylsd.old/__init__.py pylsd/__init__.py
--- pylsd.old/__init__.py	2021-02-07 23:07:21.000000000 +0900
+++ pylsd/__init__.py	2021-02-07 23:12:42.000000000 +0900
@@ -5,4 +5,4 @@
 # @Link    : https://github.com/primetang/pylsd
 # @Version : 0.0.1
 
-from lsd import lsd
+from . import lsd
diff -ru pylsd.old/bindings/__init__.py pylsd/bindings/__init__.py
--- pylsd.old/bindings/__init__.py	2021-02-07 23:07:21.000000000 +0900
+++ pylsd/bindings/__init__.py	2021-02-07 23:13:37.000000000 +0900
@@ -5,4 +5,4 @@
 # @Link    : https://github.com/primetang/pylsd
 # @Version : 0.0.1
 
-from lsd_ctypes import *
+from .lsd_ctypes import *
diff -ru pylsd.old/bindings/lsd_ctypes.py pylsd/bindings/lsd_ctypes.py
--- pylsd.old/bindings/lsd_ctypes.py	2021-02-07 23:07:21.000000000 +0900
+++ pylsd/bindings/lsd_ctypes.py	2021-02-07 23:14:03.000000000 +0900
@@ -31,7 +31,7 @@
             try:
                 lsdlib = ctypes.cdll[os.path.join(root_dir, libdir, libname)]
                 return lsdlib
-            except Exception, e:
+            except Exception as e:
                 pass
         tmp = os.path.dirname(root_dir)
         if tmp == root_dir:
diff -ru pylsd.old/lsd.py pylsd/lsd.py
--- pylsd.old/lsd.py	2021-02-07 23:07:21.000000000 +0900
+++ pylsd/lsd.py	2021-02-07 23:13:19.000000000 +0900
@@ -5,7 +5,7 @@
 # @Link    : https://github.com/primetang/pylsd
 # @Version : 0.0.1
 
-from bindings.lsd_ctypes import *
+from .bindings.lsd_ctypes import *
 
 
 def lsd(src):
@@ -17,7 +17,11 @@
 
     lens = len(src)
     src = (ctypes.c_double * lens)(*src)
-    lsdlib.lsdGet(src, ctypes.c_int(rows), ctypes.c_int(cols), temp)
+    #lsdlib.lsdGet(src, ctypes.c_int(rows), ctypes.c_int(cols), temp)
+    enc_temp = temp.encode('utf-8')
+    cstr_temp = ctypes.create_string_buffer(enc_temp)
+    lsdlib.lsdGet(src, ctypes.c_int(rows), ctypes.c_int(cols), cstr_temp)
+
 
     fp = open(temp, 'r')
     cnt = fp.read().strip().split(' ')

pip3 install pylsdした後で,上記のファイルをdiff.txtというファイルに保存し,pylsdのインストールされているディレクトリの中(python3をbrewでインストールしている場合は/usr/local/lib/python3.9/site_packages/pylsd)の中でpatch -p1 -u < diff.txtすれば良い.

追記: pylsd 0.0.3という新しいバージョンが出てた.

ocrd-fork-pylsd 0.0.3

pip3 install orcd-fork-pylsdとすると,ちゃんとPython3の場合には上記のファイル名の変更が反映されている.しかし,このページの中身が更新されていないので,pip3 install pylsdとすると,変更が反映されない.

2020年2月9日日曜日

SKKの辞書をCSVに変換してみた

SKKの辞書をCSVファイルに変換するためのPythonスクリプトを作ってみました.

ここにある辞書を落として来て,Shift-JISのCSVファイルで出力しました.CSVのライブラリーは使ってません.


#!python3
# 2020/2/9
# 実行方法: ./SKKtoCSV.py SKK-JISYO.file
# 語尾に.csvをつけたファイル名にSJISで出力

# SKK-JISYOをCSVに変換
# EUC-JP -> SJIS
# 先頭;がコメント行
# 先頭>は語尾
# 先頭#は数字の置き換え
# よみ /漢字1;コメント,コメント/漢字2/...
# よみの最後が>もしくはローマ字の場合がある

import sys,os,re
import codecs

def processLine( line, fout ):
    re_pattern_line = '^(\S+) /(.+/)$'
    #re_pattern_line = '^(\S+) /(\S+/)+$'
    re_pattern_kana = '^(.+)([a-z>])$'
    re_pattern_entry = '^(.+);(.+)$'

    print('[{}]'.format(line))
    sys.stdout.flush()
    if line == '':
        print('  -> ignore 1')
        return
    if line[0] == ';':
        print('  -> ignore 2')
        return
    if line[0] == '>':
        print('  -> ignore 3')
        return
    if line[0] == '#':
        print('  -> ignore 4')
        return
    # 処理する
    re_result = re.match(re_pattern_line, line)
    if re_result == None:
        print('  -> None')
        return

    print('  -> re result: {}, {}'.format(re_result.group(1),
                                          re_result.group(2)))
    sys.stdout.flush()
    # よみ
    kana = re_result.group(1)
    # 漢字/漢字/.../漢字/
    kanjistr = re_result.group(2)

    print('  yomi: {}'.format(kana))
    sys.stdout.flush()
    # 最後が子音ローマ字もしくは'>'を除く必要あり
    re_result2 = re.match(re_pattern_kana, kana)
    if re_result2:
        print('  end [{}][{}]'.format(re_result2.group(1),
                                      re_result2.group(2)))
        print('  -> ignore 5')
        sys.stdout.flush()
        return
    # ['漢字', '漢字', ..., '漢字', '']
    kanjilist = kanjistr.split('/')
    print('  kanjilist: {}'.format(kanjilist))
    if len(kanjilist) >= 10:
        print('  (many entry)')
    sys.stdout.flush()
    # ['漢字', '漢字', ..., '漢字', '']
    kanjilist = kanjilist[0:-1]
    print('  -> kanjilist: {}'.format(kanjilist))
    for kanji in kanjilist:
        # 注意: "漢字;こめんと,こめんと"の場合あり
        print('    kanji: {}'.format(kanji))
        sys.stdout.flush()
        re_result3 = re.match(re_pattern_entry, kanji)
        if re_result3:
            print('  comment:{}[{}]'.format(re_result3.group(1),
                                            re_result3.group(2)))
            print('  output: {},{}'.format(kana,
                                           re_result3.group(1)))
            fout.write('"{}","{}",\n'.format(kana, re_result3.group(1)))
        else:
            print('  output: {},{}'.format(kana, kanji))
            fout.write('"{}","{}",\n'.format(kana, kanji))

def SKKtoCSV( fileNameIn ):
    print('processing {}'.format(fileNameIn))
    fin = codecs.open(fileNameIn, 'r', 'euc_jp')
    dataAll = fin.read()
    linesAll = dataAll.split('\n')
    fin.close()
    fileNameOut = fileNameIn + '.csv'
    print('output file name is {}'.format(fileNameOut))
    fout = codecs.open(fileNameOut, 'w', 'shift_jis')
    
    for line in linesAll:
        processLine( line, fout )
    fout.close()

##------
if __name__ == '__main__':
    args = sys.argv
    if len(args) != 2:
        print('{} SKK-JISYO'.format(args[0]))
        sys.exit()
    SKKtoCSV( args[1] )


2019年8月12日月曜日

ESP-WROOM-02(ESP8266)で,9軸センサーBMX055から取得した値をパソコンにUDPで送信する.

前の記事で,ESP-WROOM-02 (ESP8266)9軸センサーBMX055からの値を取得できたので,これをUDPでパソコンのPythonプログラムに送信しました. 

IPアドレスの設定は以下の通りです.
  • パソコン側のIPアドレス:172.20.10.4
パソコンの受け側のUDPのポート番号を以下の値とします.
  • パソコン側のUDPポート番号:30000
パソコン側のPythonのプログラムは以下のとおりです.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Python 3.7.3 on macOS 10.15 Catalina Beta
# 2019/8/4
# receive JSON data by UDP

import argparse
import threading
import time
from socket import *

class ServerThread(threading.Thread):
    def __init__(self, ADDRESS="127.0.0.1", PORT=30000):
        threading.Thread.__init__(self)
        self.data = None
        self.kill_flag = False
        # line information
        self.ADDRESS = ADDRESS
        self.PORT = PORT
        self.BUFSIZE = 1024
        self.ADDR = (gethostbyname(self.ADDRESS), self.PORT)
        print(self.ADDR)
        # bind
        self.udpServSock = socket(AF_INET, SOCK_DGRAM)
        self.udpServSock.bind(self.ADDR) # HOST, PORTでbinding

    def run(self):
        while True:
            try:
                # データ受信
                data, self.addr = self.udpServSock.recvfrom(self.BUFSIZE)
                if len(data) > 0:
                    print("receive {} bytes".format(len(data)))
                    print("received data: {}".format(data))
                # OSCはデコードできなくてexceptionが起こる
                # JSONだとテキストなのでデコードできる
                self.data = data.decode()
                print("decoded data: {}".format(self.data))
            except:
                print("exception occrurred")
                #pass

def start_UDP_server(ip_address, port_number):
    print("starting on address {}, port {}".format(ip_address, port_number))
    th = ServerThread(ip_address, port_number)
    th.setDaemon(True)
    th.start()

    while True:
        time.sleep(1.0)
        if not th.data:
            continue
        print(th.data)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--ip", default="127.0.0.1",
                        help="IP address to listen on")
    parser.add_argument("--port", type=int, default=30000,
                        help="port to listen on")
    args = parser.parse_args()
    start_UDP_server(args.ip, args.port)


ESP-WROOM-02側のプログラムは以下のとおりです.

//================================================================//
//  AE-BMX055             ESP8266                            //
//    3V3                    +3.3V(1)                                  //
//    GND                    GND(17)                                  //
//    SDA                    IO4(12)                              //
//    SCL                    IO5(16)                              //
//                                                                //
//   (JP6,JP4,JP5はショートした状態)                              //
//   http://akizukidenshi.com/catalog/g/gK-13010/                 //
//   -> 3.3Vで使うのでJP6はショートしない
//================================================================//

#include 
#include 
#include 
#include 
#include  // sprintf()

WiFiUDP UDP;
IPAddress HOSTIP(172, 20, 10, 4);
const int remoteUdpPort = 30000;
IPAddress myIP(172, 20, 10, 10);
const int localUdpPort = 31000;
const char *ssid = "iPhone7PlusBlack";
const char *password = "qatacygd60y8u";

// BMX055 加速度センサのI2Cアドレス  
#define Addr_Accl 0x19  // (JP1,JP2,JP3 = Openの時)
// BMX055 ジャイロセンサのI2Cアドレス
#define Addr_Gyro 0x69  // (JP1,JP2,JP3 = Openの時)
// BMX055 磁気センサのI2Cアドレス
#define Addr_Mag 0x13   // (JP1,JP2,JP3 = Openの時)

// センサーの値を保存するグローバル関数
float xAccl = 0.00;
float yAccl = 0.00;
float zAccl = 0.00;
float xGyro = 0.00;
float yGyro = 0.00;
float zGyro = 0.00;
short int   xMag  = 0;
short int   yMag  = 0;
short int   zMag  = 0;

void connectWiFi() {
  Serial.println("WiFi setup start");
  WiFi.begin(ssid, password);
  WiFi.config(myIP, WiFi.gatewayIP(), WiFi.subnetMask());
  Serial.println("WiFi setup done");
  Serial.println("start_connect");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("CONNECTED!");
}

void sendWiFi(char byteData[]) {
  if (UDP.beginPacket(HOSTIP, remoteUdpPort)) {
    UDP.write(byteData);
    UDP.endPacket();
    Serial.println(byteData);
  }
}

void reconnectWiFi() {
  if (WiFi.status() != WL_CONNECTED) {
    WiFi.disconnect();
    Serial.println("disconnected! reconnect WiFi.");
    connectWiFi();
  }
}

void setup()
{
  // Wire(Arduino-I2C)の初期化
  Wire.begin();
  // デバック用シリアル通信は9600bps
  Serial.begin(9600);
  //BMX055 初期化
  BMX055_Init();
  delay(300);
  //WiFi初期化
  WiFi.mode(WIFI_STA);
  UDP.begin(localUdpPort);
  connectWiFi();
}

void loop()
{
  char buf[128];
  Serial.println("--------------------------------------"); 

  //BMX055 加速度の読み取り
  BMX055_Accl();
  sprintf( buf, "Accel= %f,%f,%f", xAccl, yAccl, zAccl);
  sendWiFi(buf);
  delay(500);
  reconnectWiFi();

  //BMX055 ジャイロの読み取り
  BMX055_Gyro();
  sprintf( buf, "Gyro= %f,%f,%f", xGyro, yGyro, zGyro);
  sendWiFi(buf);
  delay(500);
  reconnectWiFi();
  
  //BMX055 磁気の読み取り
  BMX055_Mag();
  sprintf( buf, "Mag= %d,%d,%d", xMag, yMag, zMag);
  sendWiFi(buf);
  delay(500);
  reconnectWiFi();  
}

//=====================================================================================//
void BMX055_Init()
{
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x0F); // Select PMU_Range register
  Wire.write(0x03);   // Range = +/- 2g
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x10);  // Select PMU_BW register
  Wire.write(0x08);  // Bandwidth = 7.81 Hz
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Accl);
  Wire.write(0x11);  // Select PMU_LPW register
  Wire.write(0x00);  // Normal mode, Sleep duration = 0.5ms
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Gyro);
  Wire.write(0x0F);  // Select Range register
  Wire.write(0x04);  // Full scale = +/- 125 degree/s
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Gyro);
  Wire.write(0x10);  // Select Bandwidth register
  Wire.write(0x07);  // ODR = 100 Hz
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Gyro);
  Wire.write(0x11);  // Select LPM1 register
  Wire.write(0x00);  // Normal mode, Sleep duration = 2ms
  Wire.endTransmission();
  delay(100);
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4B);  // Select Mag register
  Wire.write(0x83);  // Soft reset
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4B);  // Select Mag register
  Wire.write(0x01);  // Soft reset
  Wire.endTransmission();
  delay(100);
  //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4C);  // Select Mag register
  Wire.write(0x00);  // Normal Mode, ODR = 10 Hz
  Wire.endTransmission();
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x4E);  // Select Mag register
  Wire.write(0x84);  // X, Y, Z-Axis enabled
  Wire.endTransmission();
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x51);  // Select Mag register
  Wire.write(0x04);  // No. of Repetitions for X-Y Axis = 9
  Wire.endTransmission();
 //------------------------------------------------------------//
  Wire.beginTransmission(Addr_Mag);
  Wire.write(0x52);  // Select Mag register
  Wire.write(0x16);  // No. of Repetitions for Z-Axis = 15
  Wire.endTransmission();
}
//=====================================================================================//
void BMX055_Accl()
{
  unsigned char data[6];
  for (int i = 0; i < 6; i++)
  {
    Wire.beginTransmission(Addr_Accl);
    Wire.write((2 + i));// Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Accl, 1);// Request 1 byte of data
    // Read 6 bytes of data
    // xAccl lsb, xAccl msb, yAccl lsb, yAccl msb, zAccl lsb, zAccl msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data to 12-bits
  xAccl = ((data[1] * 256) + (data[0] & 0xF0)) / 16;
  if (xAccl > 2047)  xAccl -= 4096;
  yAccl = ((data[3] * 256) + (data[2] & 0xF0)) / 16;
  if (yAccl > 2047)  yAccl -= 4096;
  zAccl = ((data[5] * 256) + (data[4] & 0xF0)) / 16;
  if (zAccl > 2047)  zAccl -= 4096;
  xAccl = xAccl * 0.0098; // renge +-2g
  yAccl = yAccl * 0.0098; // renge +-2g
  zAccl = zAccl * 0.0098; // renge +-2g
}
//=====================================================================================//
void BMX055_Gyro()
{
  unsigned char data[6];
  for (int i = 0; i < 6; i++)
  {
    Wire.beginTransmission(Addr_Gyro);
    Wire.write((2 + i));    // Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Gyro, 1);    // Request 1 byte of data
    // Read 6 bytes of data
    // xGyro lsb, xGyro msb, yGyro lsb, yGyro msb, zGyro lsb, zGyro msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data
  xGyro = (data[1] * 256) + data[0];
  if (xGyro > 32767)  xGyro -= 65536;
  yGyro = (data[3] * 256) + data[2];
  if (yGyro > 32767)  yGyro -= 65536;
  zGyro = (data[5] * 256) + data[4];
  if (zGyro > 32767)  zGyro -= 65536;

  xGyro = xGyro * 0.0038; //  Full scale = +/- 125 degree/s
  yGyro = yGyro * 0.0038; //  Full scale = +/- 125 degree/s
  zGyro = zGyro * 0.0038; //  Full scale = +/- 125 degree/s
}
//=====================================================================================//
void BMX055_Mag()
{
  unsigned short int data[8];
  for (int i = 0; i < 8; i++)
  {
    Wire.beginTransmission(Addr_Mag);
    Wire.write((0x42 + i));    // Select data register
    Wire.endTransmission();
    Wire.requestFrom(Addr_Mag, 1);    // Request 1 byte of data
    // Read 6 bytes of data
    // xMag lsb, xMag msb, yMag lsb, yMag msb, zMag lsb, zMag msb
    if (Wire.available() == 1)
      data[i] = Wire.read();
  }
  // Convert the data
  xMag = ((data[1] <<8 data="">>3));
  if (xMag > 4095)  xMag -= 8192;
  yMag = ((data[3] <<8 data="">>3));
  if (yMag > 4095)  yMag -= 8192;
  zMag = ((data[5] <<8 data="">>3));
  if (zMag > 16383)  zMag -= 32768;
}


パソコン側で以下のようにPythonプログラムを起動します.

% ./UDPserver1.py --ip 172.20.10.4 --port 30000


ESP-WROOM-02側のシリアルモニタでは,以下のようにデータが送信できています.

--------------------------------------
Accel= -0.098000,0.098000,9.780400
Gyro= -0.049400,-0.262200,0.095000
Mag= -503,11,-498
--------------------------------------


パソコン側では以下のようにデータが受信できています.

receive 34 bytes
received data: b'Accel= -0.098000,0.098000,9.780400'
decoded data: Accel= -0.098000,0.098000,9.780400
Accel= -0.098000,0.098000,9.780400
receive 34 bytes
received data: b'Gyro= -0.049400,-0.262200,0.095000'
decoded data: Gyro= -0.049400,-0.262200,0.095000
receive 17 bytes
received data: b'Mag= -503,11,-498'
decoded data: Mag= -503,11,-498
Mag= -503,11,-498


2019年8月7日水曜日

9軸センサー(加速度,ジャイロ,地磁気)のデータを使う (2)

前の投稿と並行して,クライアントの構築が一番簡単な,iPhoneとZIG SIMアプリを使った9軸データのやり取りをやってみました.

iPhoneのZIG SIMアプリで9軸データを送り,macのPythonプログラムで受信してみます.mac側のプログラムは以下のとおり.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Python 3.7.3 on macOS 10.15 Catalina Beta
# 2019/8/7
# receive OSC data by UDP

import argparse
from socket import *
from pythonosc import dispatcher
from pythonosc import osc_server

def handle_zigsim_osc_message(addr, *args):
    try:
        print("{0}: {1}".format(addr, args))
    except ValueError:
        print("ValueError occrurred")

def start_OSC_server(ip_addr, port_num):
    print("starting on address {}, port {}".format(ip_addr, port_num))

    # OSC dispatcher
    dispatch = dispatcher.Dispatcher()
    dispatch.map("/ZIGSIM/*", handle_zigsim_osc_message )

    server = osc_server.ThreadingOSCUDPServer(
        (ip_addr, port_num), dispatch)
    print("Serving on {}".format(server.server_address))
    server.serve_forever()

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--ip", default="127.0.0.1",
                        help="IP address to listen on")
    parser.add_argument("--port", type=int, default=30000,
                        help="port to listen on")
    args = parser.parse_args()
    start_OSC_server(args.ip, args.port)

クライアントのZIG SIMアプリの設定(送信データ)は以下のとおり.


ZIG SIMアプリの設定(プロトコル)は以下のとおり.IP ADDRESSにはパソコンのIPアドレスを入れます.ポート番号は30000にしました.


macでサーバープログラムを動かし,iPhoneでクライアントを動かすと,iPhoneから9軸データがサーバーに送られます.このときのクライアントの画面は以下のとおり.


サーバー側では,以下のようにデータが表示されて,ちゃんとデータがOSC形式で受信できていることが分かります.


現在のiPhone上でうごかしてZIG SIMを,「9軸センサー(加速度,ジャイロ,地磁気)のデータを使う (1)」で作成中のArduinoを使ったハードウェアで置き換えるのが目標です.

2019年3月24日日曜日

Pythonでゲーム作成(Hextrisゲーム)

「Pythonでテトリス」を書き換えてHextrisを作ってみました.

画面はこんな感じです.


昔,X68000で遊んでいたゲームです.最近は同じ名前の別のゲームがあるようですが...

以下がソースコードです.


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Python 2.7.5 on OS X 10.9.2
# by Mitsuharu Arimura 2015/3/14

import Tkinter as Tk
import datetime as dt
import time, random, math
import sys, os, csv

# ゲームフィールドのサイズ
FIELD_WIDTH = 17
FIELD_HEIGHT = 24

# ブロックのサイズ
PIECE_WIDTH = 4
PIECE_HEIGHT = 4

# 1個の六角形セルの1辺の長さ
CELL_SIZE = 15

SQRT3 = math.sqrt(3.0)

# 六角形を作る関数
def CreateHexagon( canvas, x, y, offset ):
    # (x0, y0): 六角形の中心
    x0 = CELL_SIZE * (x * 1.5 + 1) + offset
    y0 = CELL_SIZE * (y + 0.5 + x * 0.5) * SQRT3 + offset
    #print "(%d, %d): (%.1f, %.1f)" % (x, y, x0, y0)
    # vtx_x, vty_y: 六角形の頂点のx座標と座標の配列(6個)
    vtx_x = [ x0 + CELL_SIZE * math.cos(i*math.pi/3.0)
              for i in range(0, 6) ]
    vtx_y = [ y0 + CELL_SIZE * math.sin(i*math.pi/3.0)
              for i in range(0, 6) ]
    #print "x: ", vtx_x
    #print "y: ", vtx_y
    r = canvas.create_polygon( vtx_x[0], vtx_y[0],
                             vtx_x[1], vtx_y[1],
                             vtx_x[2], vtx_y[2],
                             vtx_x[3], vtx_y[3],
                             vtx_x[4], vtx_y[4],
                             vtx_x[5], vtx_y[5],
                             fill='white',
                             outline='gray',
                             width=1)
    return r

# ゲーム本体の画面 View
class Board( Tk.Canvas ):
    # フィールドのピクセル数
    BOARD_WIDTH = (FIELD_WIDTH + 0.5) * CELL_SIZE * 1.5
    BOARD_HEIGHT = (FIELD_HEIGHT + 0.5) * CELL_SIZE * SQRT3 + 3
    margin = 2

    def __init__( self, master ):
        self.width = self.BOARD_WIDTH
        self.height = self.BOARD_HEIGHT
        Tk.Canvas.__init__( self, master, relief=Tk.RAISED, bd=2, bg='gray',
                            width=self.BOARD_WIDTH, height=self.BOARD_HEIGHT )
        #print "Canvas width %d x %d" % ( self.BOARD_WIDTH, self.BOARD_HEIGHT )

        # Canvas上の rectangle の2次元配列
        # 周囲に1マスずつ余計に取って,グレーに塗り潰しておくのが簡単
        self.Field = [[0 for y in range(- int(x*0.5), FIELD_HEIGHT-int(x*0.5))]
                      for x in range(FIELD_WIDTH)]
        offset = self.margin * 2 + 3
        for x in range( FIELD_WIDTH ):
            for y in range( -int(x*0.5), FIELD_HEIGHT-int(x*0.5) ):
                self.Field[x][y] = CreateHexagon( self, x, y, offset )

        # PAUSEの文字
        TEXT_font = ('Helvetica','60','bold')
        TEXT_color = 'red'
        self.pause_text = self.create_text( self.BOARD_WIDTH/2, 100,
                                            text='PAUSE',
                                            font=TEXT_font,
                                            fill=TEXT_color,
                                            justify=Tk.CENTER)
        self.hidePauseText()

    def SetColor( self, x, y, value ):
        #print "SetColor:"
        if x < 0: return
        if x >= FIELD_WIDTH: return
        if y < -int(x * 0.5): return
        if y >= FIELD_HEIGHT - int(x * 0.5): return
        #print "(%d, %d)=>%d" % (x, y, value)
        if value == 1:
            self.itemconfigure( self.Field[x][y], fill='black')
        elif value == 2:
            self.itemconfigure( self.Field[x][y], fill='gray')
        elif value == 3:
            self.itemconfigure( self.Field[x][y], fill='slate gray')
        else:
            self.itemconfigure( self.Field[x][y], fill='white')

    # PAUSE中にPAUSEの文字を表示する
    def showPauseText( self ):
        self.itemconfigure( self.pause_text, state=Tk.NORMAL )

    # PAUSEが終わるときにPAUSEの文字を非表示にする
    def hidePauseText( self ):
        self.itemconfigure( self.pause_text, state=Tk.HIDDEN )

# 次のピースを表示する画面 View
class NextPieceBoard( Tk.Canvas ):
    # 次のピースを表示する場所のサイズ
    NEXT_BOARD_WIDTH = (PIECE_WIDTH + 0.5) * CELL_SIZE * 1.5
    NEXT_BOARD_HEIGHT = (PIECE_HEIGHT + 1.5) * CELL_SIZE * SQRT3 + 3
    margin = 2
    def __init__( self, master ):
        Tk.Canvas.__init__( self, master, relief=Tk.RAISED, bd=self.margin,
                            bg='white',
                            width=self.NEXT_BOARD_WIDTH,
                            height=self.NEXT_BOARD_HEIGHT )
        self.RectArray = [[0 for y in range(PIECE_HEIGHT)]
                          for x in range(PIECE_WIDTH)]
        #print self.RectArray
        offset = self.margin * 2 + 3
        for x in range( PIECE_WIDTH ):
            for y in range( PIECE_HEIGHT ):
                self.RectArray[x][y] = CreateHexagon( self, x, y, offset )

    def DisplayPiecePattern( self, pattern ):
        p = pattern
        for x in range( PIECE_WIDTH ):
            for y in range( PIECE_HEIGHT ):
                #print "p[%d][%d] = %d" % (x, y, p[x][y])
                if p[x][y] == 1:
                    # print "p[%d][%d] = red" % (x, y)
                    self.itemconfigure( self.RectArray[x][y], fill='black' )
                else:
                    # print "p[%d][%d] = blue" % (x, y)
                    self.itemconfigure( self.RectArray[x][y], fill='white' )

# ゲーム全体の画面および Controller
class Frame( Tk.Frame ):
    def __init__( self, master=None ):
        Tk.Frame.__init__( self, master, width=600, height=800 )
        self.master.title( 'Hextris' )

        #########################################
        # board frame
        #BOARD_WIDTH = (FIELD_WIDTH + 0.5) * CELL_SIZE * 1.5
        #BOARD_HEIGHT = (FIELD_HEIGHT + 0.5) * CELL_SIZE * SQRT3 + 3
        self.bframe = Tk.Frame( self, bd=1, relief=Tk.RIDGE )
        self.bframe.pack( side=Tk.LEFT, padx=10, pady=10 )
        self.board = Board( self.bframe )
        self.board.pack( side=Tk.LEFT, padx=0, pady=0 )

        #########################################
        # display frame
        frame = Tk.Frame( self, bd=1, relief=Tk.RIDGE )
        # exit button
        self.btExit = Tk.Button( frame, text='Exit', command=self.exitGame )
        self.btExit.pack( anchor=Tk.E, padx=10, pady=10 )
        # replay (not start) button
        #self.btReplay = Tk.Button( frame, text='Replay',
        #                           command=self.replayGame )
        #self.btReplay.pack( anchor=Tk.E, padx=10, pady=0 )
        # start button
        self.btStart = Tk.Button( frame, text='Start', command=self.startGame )
        self.btStart.pack( anchor=Tk.E, padx=10, pady=0 )
        # next piece display
        self.next_piece = NextPieceBoard( frame )
        self.next_piece.pack( anchor = Tk.E, padx=10, pady=10 )
        TEXT_font = ('Helvetica','12','italic')
        NUM_font = ('Helvetica','24','bold')
        # time text
        self.time_text = Tk.Label( frame, text='PLAY TIME', font=TEXT_font )
        self.time_text.pack( anchor = Tk.E, padx=10, pady=0 )
        # timer
        self.time_box = Tk.Label( frame, text='00\'00\"', font=NUM_font,
                                  bd=1, relief=Tk.RIDGE )
        self.time_box.pack( anchor = Tk.E, padx=10, pady=0 )
        frame.pack( side=Tk.RIGHT, padx=10, pady=10 )

        # key bind
        self.bind( "", self.keyPressed )
        self.bind( "", self.keyLeft )
        self.bind( "", self.keyRight )
        self.bind( "", self.keyUp )
        self.bind( "", self.keyDown )
        self.focus_set()

        self.hextris = Hextris( self )

        self.processing_down = False
        self.timerEventCount = 0
        self.timerCount = 0

        #self.DisableReplayButton()
        self.hextris.PrepareStartGame()

    # exit button
    def exitGame( self ):
        self.hextris.playing = False
        sys.exit()

    # start button
    def startGame( self ):
        self.hextris.playing = True
        # ボタンは押せなくする
        self.DisableStartButton()
        #self.DisableReplayButton()
        # カウンタをリセットしてタイマーをスタートする
        self.timerEventCount = 0
        self.timerCount = 0
        # これ以降,timerEventが0.5秒に一回呼び出される
        self.after( 500, self.timerEvent )

    # prepare replay
    def replayGame( self ):
        self.EnableStartButton()
        self.hextris.PrepareStartGame()
        self.timerEventCount = 0
        self.timerCount = 0
        self.DisplayTime( 0, 0 )

    # 0.5秒ごとに呼び出される
    def timerEvent( self ):
        if self.hextris.playing == False:
            return
        if self.hextris.pausing == True:
            return
        self.timerEventCount = self.timerEventCount + 1
        # 0.5秒に1回呼び出されるので2回→1秒に1回の点数アップ
        if self.timerEventCount % 2 == 0:
            self.timerCount = self.timerCount + 1
            self.DisplayTime( self.timerCount / 60, self.timerCount % 60 )
            # print "count %d" % self.timerEventCount
            # print "timer %d" % self.timerCount
        # ブロックを1段落とす
        self.keyDown( None )
        # 0.5秒後に自分を呼び出す
        self.after( 500, self.timerEvent )

    def endGame( self ):
        self.focus_set()
        self.replayGame()

    def DisableExitButton( self ):
        self.btExit.configure( state=Tk.DISABLED )

    def EnableExitButton( self ):
        self.btExit.configure( state=Tk.NORMAL )

    def DisableReplayButton( self ):
        #self.btReplay.configure( state=Tk.DISABLED )
        pass

    def EnableReplayButton( self ):
        #self.btReplay.configure( state=Tk.NORMAL )
        pass

    def DisableStartButton( self ):
        self.btStart.configure( state=Tk.DISABLED )

    def EnableStartButton( self ):
        self.btStart.configure( state=Tk.NORMAL )

    def DisplayTime( self, min=0, sec=0 ):
        self.time_box.configure( text="%02d\'%02d\"" % (min, sec) )

    # 以下はキー入力処理
    def keyPressed( self, event ):
        c = event.char
        # space: pause
        if self.hextris.playing == False:
            return
        if c == ' ':
            if self.hextris.pausing == False:
                print "PAUSE!!!"
                self.hextris.pausing = True
                # PAUSEを表示
                self.board.showPauseText()
            else:
                print "PAUSE END!!!"
                self.hextris.pausing = False
                # PAUSEを隠す
                self.board.hidePauseText()
                # 再度タイマーを開始
                self.after( 500, self.timerEvent )

    def keyLeft( self, event ):
        if self.hextris.playing == False:
            return
        if self.hextris.pausing == True:
            return
        # print "press Left"
        if self.hextris.CanMovePiece( move='left' ) == 0:
            self.hextris.MovePiece( move='left' )
            self.hextris.RedrawAllField()

    def keyRight( self, event ):
        if self.hextris.playing == False:
            return
        if self.hextris.pausing == True:
            return
        # print "press Right"
        if self.hextris.CanMovePiece( move='right' ) == 0:
            self.hextris.MovePiece( move='right' )
            self.hextris.RedrawAllField()

    # キーで呼ばれるのとタイマーで呼ばれるのが同時に重なる場合が
    # あるので再入防止している
    def keyDown( self, event ):
        #print "keyDown"
        if self.hextris.playing == False:
            return
        if self.hextris.pausing == True:
            return
        # print "press Down"
        if self.processing_down == True:
            return
        self.processing_down = True
        res = self.hextris.CanMovePiece( move='down' )
        if res == 0:
            self.hextris.MovePiece( move='down' )
            self.hextris.RedrawAllField()
        elif res == 2:
            self.hextris.FixPieceAndGetNextPiece()
        self.processing_down = False

    # 回転
    def keyUp( self, event ):
        if self.hextris.playing == False:
            return
        if self.hextris.pausing == True:
            return
        # print "press Up"
        if self.hextris.CanTurnPiece() == True:
            self.hextris.TurnPiece()
            self.hextris.RedrawAllField()

# Hextrisゲームの Data
class Hextris:
    # ゲームフィールド
    field = [[0 for y in range(FIELD_HEIGHT)]
             for x in range(FIELD_WIDTH)]
    # 現在移動中のブロック
    piece = [[0 for y in range(PIECE_HEIGHT)]
             for x in range(PIECE_WIDTH)]
    # ブロックの左上端の座標(フィールドは左上が(x,y) = (0,0)でyが下向き)
    location = [0, 0]

    # ブロック
    # 種類の数を揃えておくこと!!!
    # listにaddしていくと,ここで数を書く必要は無い
    ID_NUM = 10
    id = 0 # 0...ID_NUM-1の8種類
    id_next = 0 # 次のピース
    ORIENT_NUM = 6
    orientation = 0 # 0...ORIENT_NUM-1の6種類

    # 次のブロック
    nextPiece = [[[0 for y in range(PIECE_HEIGHT)]
                  for x in range(PIECE_WIDTH)]
                 for o in range(ORIENT_NUM)]

    # ブロックの全てのパターン index, orientation, x, y毎に1, 0
    piecePattern = [[[[0 for y in range(PIECE_HEIGHT)]
                      for x in range(PIECE_WIDTH)]
                     for o in range(ORIENT_NUM)]
                    for i in range(ID_NUM)]
    piecePattern[0] = [
        [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], # 方向0の[x][y]
        [[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]], # 方向1
        [[0,0,0,1],[0,0,1,0],[0,1,0,0],[1,0,0,0]], # 方向2
        [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], # 方向3
        [[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]], # 方向4
        [[0,0,0,1],[0,0,1,0],[0,1,0,0],[1,0,0,0]]] # 方向5
    piecePattern[1] = [
        [[0,0,0,0],[1,1,0,0],[0,1,1,0],[0,0,0,0]], # 方向0の[x][y]
        [[0,0,1,0],[0,0,1,0],[0,1,0,0],[0,1,0,0]], # 方向1
        [[0,0,0,1],[0,1,1,0],[1,0,0,0],[0,0,0,0]], # 方向2
        [[0,0,0,0],[1,1,0,0],[0,1,1,0],[0,0,0,0]], # 方向3
        [[0,0,1,0],[0,0,1,0],[0,1,0,0],[0,1,0,0]], # 方向4
        [[0,0,0,1],[0,1,1,0],[1,0,0,0],[0,0,0,0]]] # 方向5
    piecePattern[2] = [
        [[0,0,0,0],[0,0,1,1],[1,1,0,0],[0,0,0,0]], # 方向0の[x][y]
        [[0,1,0,0],[0,1,1,0],[0,0,1,0],[0,0,0,0]], # 方向1
        [[0,0,1,0],[0,1,0,0],[0,1,0,0],[1,0,0,0]], # 方向2
        [[0,0,0,0],[0,0,1,1],[1,1,0,0],[0,0,0,0]], # 方向3
        [[0,1,0,0],[0,1,1,0],[0,0,1,0],[0,0,0,0]], # 方向4
        [[0,0,1,0],[0,1,0,0],[0,1,0,0],[1,0,0,0]]] # 方向5
    piecePattern[3] = [
        [[0,1,0,0],[1,1,0,0],[1,0,0,0],[0,0,0,0]], # 方向0の[x][y]
        [[0,1,1,0],[1,1,0,0],[0,0,0,0],[0,0,0,0]], # 方向1
        [[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]], # 方向2
        [[0,1,0,0],[1,1,0,0],[1,0,0,0],[0,0,0,0]], # 方向3
        [[0,1,1,0],[1,1,0,0],[0,0,0,0],[0,0,0,0]], # 方向4
        [[0,1,1,0],[0,1,1,0],[0,0,0,0],[0,0,0,0]]] # 方向5
    piecePattern[4] = [
        [[0,0,0,0],[0,0,0,1],[1,1,1,0],[0,0,0,0]], # 方向0の[x][y]
        [[0,1,0,0],[0,1,0,0],[0,1,1,0],[0,0,0,0]], # 方向1
        [[0,0,0,1],[0,0,1,0],[0,1,0,0],[0,1,0,0]], # 方向2
        [[0,0,0,0],[0,1,1,1],[1,0,0,0],[0,0,0,0]], # 方向3
        [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]], # 方向4
        [[0,0,1,0],[0,0,1,0],[0,1,0,0],[1,0,0,0]]] # 方向5
    piecePattern[5] = [
        [[0,0,0,0],[1,1,1,0],[0,0,1,0],[0,0,0,0]], # 方向0の[x][y]
        [[0,1,0,0],[0,1,0,0],[0,1,0,0],[1,0,0,0]], # 方向1
        [[0,0,0,1],[0,0,1,0],[1,1,0,0],[0,0,0,0]], # 方向2
        [[0,0,0,0],[1,0,0,0],[1,1,1,0],[0,0,0,0]], # 方向3
        [[0,1,0,0],[1,0,0,0],[1,0,0,0],[1,0,0,0]], # 方向4
        [[0,0,1,1],[0,1,0,0],[1,0,0,0],[0,0,0,0]]] # 方向5
    piecePattern[6] = [
        [[0,0,0,0],[0,0,1,0],[1,1,1,0],[0,0,0,0]], # 方向0の[x][y]
        [[0,1,0,0],[0,1,1,0],[0,1,0,0],[0,0,0,0]], # 方向1
        [[0,0,1,0],[0,1,0,0],[1,1,0,0],[0,0,0,0]], # 方向2
        [[0,0,0,0],[0,1,1,1],[0,1,0,0],[0,0,0,0]], # 方向3
        [[0,0,1,0],[0,1,1,0],[0,0,1,0],[0,0,0,0]], # 方向4
        [[0,0,0,0],[0,0,1,1],[0,0,1,0],[0,1,0,0]]] # 方向5
    piecePattern[7] = [
        [[0,0,0,0],[0,1,1,1],[0,0,1,0],[0,0,0,0]], # 方向0の[x][y]
        [[0,0,1,0],[0,0,1,0],[0,1,1,0],[0,0,0,0]], # 方向1
        [[0,0,0,0],[0,0,1,0],[1,1,0,0],[1,0,0,0]], # 方向2
        [[0,0,0,0],[0,1,0,0],[1,1,1,0],[0,0,0,0]], # 方向3
        [[0,1,1,0],[0,1,0,0],[0,1,0,0],[0,0,0,0]], # 方向4
        [[0,0,1,0],[0,1,1,0],[1,0,0,0],[0,0,0,0]]] # 方向5
    piecePattern[8] = [
        [[0,1,1,0],[1,0,0,0],[1,0,0,0],[0,0,0,0]], # 方向0の[x][y]
        [[0,1,1,0],[1,0,1,0],[0,0,0,0],[0,0,0,0]], # 方向1
        [[0,1,1,0],[0,0,1,0],[0,1,0,0],[0,0,0,0]], # 方向2
        [[0,0,1,0],[0,0,1,0],[1,1,0,0],[0,0,0,0]], # 方向3
        [[0,0,0,0],[1,0,1,0],[1,1,0,0],[0,0,0,0]], # 方向4
        [[0,1,0,0],[1,0,0,0],[1,1,0,0],[0,0,0,0]]] # 方向5
    piecePattern[9] = [
        [[0,0,1,0],[1,1,0,0],[0,1,0,0],[0,0,0,0]], # 方向0の[x][y]
        [[0,1,0,0],[0,1,1,0],[1,0,0,0],[0,0,0,0]], # 方向1
        [[0,0,1,0],[1,1,0,0],[0,1,0,0],[0,0,0,0]], # 方向2
        [[0,1,0,0],[0,1,1,0],[1,0,0,0],[0,0,0,0]], # 方向3
        [[0,0,1,0],[1,1,0,0],[0,1,0,0],[0,0,0,0]], # 方向4
        [[0,1,0,0],[0,1,1,0],[1,0,0,0],[0,0,0,0]]] # 方向5

    def __init__( self, parent ):
        self.main_frame = parent
        self.main_board = parent.board
        self.next_piece = parent.next_piece

    # ゲームの開始準備
    def PrepareStartGame( self ):
        self.playing = False
        self.pausing = False
        self.game_end = False

        # 盤面の初期化
        # 全部を白で塗る
        for y in range( FIELD_HEIGHT ):
            for x in range( FIELD_WIDTH ):
                self.field[x][y] = 0
        # 周囲1列を3で塗る
        for y in range( FIELD_HEIGHT ):
            self.field[0][y] = 3
        for x in range( FIELD_WIDTH ):
            self.field[x][ -int(x*0.5)+FIELD_HEIGHT - 1 ] = 3
        for y in range( FIELD_HEIGHT ):
            self.field[ FIELD_WIDTH-1 ][-int((FIELD_WIDTH-1)*0.5)+y] = 3

        # 1個目のピースを準備する
        self.GetNextPiece( True )
        self.RedrawAllField()

    # 1行だけ消す処理
    # offset: 0 or 1
    def DeleteOneLine( self, line, offset ):
        # 上から1行落とす
        # 下から上に向かって1行ずつ,上の行を下の行にコピーする
        for y in range(line, 1, -1):
            for x in range(1,FIELD_WIDTH-1):
                # offsetが0か1かでジグザグのパターンが変わる
                y0 = y-int((x+offset)*0.5)
                self.field[x][y0] = self.field[x][y0-1]
        self.RedrawAllField()

    # 埋まっている行を全て消す処理
    def DeleteFilledLines( self ):
        #print "Start Delete Line!!!"
        # yとoffsetで両方のパターンのジグザグのカウントをする
        y = FIELD_HEIGHT - 2 # 一番下の行を除く.
        offset = 0
        deleted_line = 0 # 削除できた行の数
        all_clear = 0 # 残りが0になった場合
        # 一番上の行まで確認
        count_sum = 0
        while y >= 0:
            # 1行に埋まってるセルの個数を数える
            #print "count line %d, offset %d:" % (y, offset),
            # 1行のカウント
            count = 0
            for x in range(1, FIELD_WIDTH-1): # 両端を除く
                #print "%d" % self.field[x][y-int((x+offset)*0.5)],
                # offsetが0か1かでジグザグのパターンが変わる
                if self.field[x][y-int((x+offset)*0.5)] == 2:
                    count = count + 1
            #print "count %d" % count
            # 全部が埋まっていたら行の削除処理
            if count == FIELD_WIDTH - 2: # 両端を除いた数
                # 削除できた場合にはyは増えない offsetも増えないのが良い
                #print "clear line %d" % y
                self.DeleteOneLine(y, offset)
                deleted_line = deleted_line + 1
            else:
                count_sum = count_sum + count
                # 全部が埋まっていなかったら次の行へ行く
                if offset == 0:
                    offset = 1
                else: # offset == 1
                    offset = 0
                    y = y - 1
        print "end Delete Line!!! (%d lines)" % deleted_line
        print "%d cells left (including double counting)" % count_sum

        # count_sumが0だったらボーナス
        if count_sum == 0:
            all_clear == 1
            print '########################################'
            print '######## ALL CELLS CLEARED #############'
            print '########################################'

    # 現在のピースが落ち切ったので,ここに固定する
    # 行が埋まったら消す
    # 次のピースを開始する
    def FixPieceAndGetNextPiece( self ):
        # 現在のピースを現在の位置に固定する
        for x in range(PIECE_WIDTH):
            for y in range(PIECE_HEIGHT):
                if self.piece[x][y] == 1:
                    self.field[self.location[0]+x][self.location[1]+y] = 2
        # 埋まっている行を消す
        self.DeleteFilledLines()
        # 次のピース
        self.GetNextPiece( False )
        self.RedrawAllField()

    # next pieceの領域に次のピースを作成する
    def CreatePiece( self ):
        self.id_next = random.randint(0, len(self.piecePattern)-1)
        #self.id_next = 9
        #print "piece pattern [%d]" % self.id_next,
        self.orientation = 0
        #print "piece orientation [%d]" % self.orientation
        # パターンi
        self.nextPiece = self.piecePattern[self.id_next]
        #print "-->", self.nextPiece
        #print "orientation 0 -->", self.nextPiece[0]
        # 向き0を表示する
        self.next_piece.DisplayPiecePattern( self.nextPiece[0] )

    # next pieceの領域からピースを持ってきて一番上に置く
    # 既にここまでピースが積んであったらゲームオーバー判定
    def GetNextPiece( self, is_first ):
        if is_first:
            self.CreatePiece()
        #print self.nextPiece
        self.location[0] = 5
        self.location[1] = -2
        # 次のピースを持ってくる(向きは0方向)
        self.id = self.id_next
        self.piece = self.piecePattern[self.id][0]
        for x in range( PIECE_WIDTH ):
            for y in range( PIECE_HEIGHT ):
                # フィールドに置く
                self.main_board.SetColor( self.location[0]+x,
                                          self.location[1]+y,
                                          self.piece[x][y])
        # ゲーム終了判定
        for y in range( PIECE_HEIGHT ):
            # 内側のxのloopから抜けて来ていたら,ここでも抜ける
            if self.game_end == True:
                break
            for x in range( PIECE_WIDTH ):
                # フィールドに置いた瞬間,既にその場所にブロックがあったら
                if ( self.piece[x][y] == 1 and
                     self.field[self.location[0]+x][self.location[1]+y] == 2):
                    # 終わり
                    self.game_end = True
                    # 次回でタイマーでのloopを止める
                    self.playing = False
                    # 1個重なっていれば十分なので,ここで抜ける
                    break
        if self.game_end == False:
            # 終わっていなかったら続ける
            self.CreatePiece()
        else:
            # ゲームオーバー処理の開始
            print "GAME END !!!"
            self.main_frame.endGame()

    def MoveOrientation( self, move ):
        xmove = ymove = 0
        if move == 'down':
            ymove = 1
        elif move == 'left':
            xmove = -1
            if self.location[0] % 2 == 1:
                ymove = 1
        elif move == 'right':
            xmove = 1
            if self.location[0] % 2 == 0:
                ymove = -1
        return ( xmove, ymove )

    def MovePiece( self, move ):
        ( xmove, ymove ) = self.MoveOrientation( move )
        self.location[0] = self.location[0] + xmove
        self.location[1] = self.location[1] + ymove

    def TurnPiece( self ):
        self.orientation = (self.orientation + 1) % self.ORIENT_NUM
        self.piece = self.piecePattern[self.id][self.orientation]

    # 移動した先を計算して,fieldとかぶらないか,はみ出ないかを見る
    # return 1: 左右の壁に当たって移動できない
    # return 2: 積んであるフィールド上のセルと重なるか,
    #           下に当たっているので,これで固定する
    def CanMovePiece( self, move='down' ):
        ( xmove, ymove ) = self.MoveOrientation( move )
        ( x0, y0 ) = ( self.location[0], self.location[1] )
        # フィールド上のセルと重なっている場合
        flag = 0
        for x in range(PIECE_WIDTH):
            for y in range(PIECE_HEIGHT):
                if ( self.piece[x][y] == 1 and
                     self.field[x0+x+xmove][y0+y+ymove] >= 1 ):
                    flag = 1
                    #print "move NG (4) (%d,%d)->(%d,%d)" % (
                    #    self.location[0]+x, self.location[1]+y,
                    #    self.location[0]+x+xmove, self.location[1]+y+ymove )
                    break
        if flag == 1:
            if ymove == 1:
                return 2 # 下に動けない場合はこれで固定
            else:
                return 1 # 左右ぶつかっている場合は,動けないだけ
        # 以上のチェックに引っかからない場合は動ける
        return 0

    # 回転した先を計算して,fieldとかぶらないか,はみ出ないかを見る
    # True: 回転できる
    def CanTurnPiece( self ):
        turnedPiece = [[0 for y in range(PIECE_HEIGHT)]
                       for x in range(PIECE_WIDTH)]
        o = (self.orientation + 1) % self.ORIENT_NUM
        turnedPiece = self.piecePattern[self.id][o]
        # 確認
        flag = 0
        for x in range(PIECE_WIDTH):
            for y in range(PIECE_HEIGHT):
                # 添字が範囲を越えているかどうかの判定
                xx = self.location[0] + x
                yy = self.location[1] + y
                if ( xx < 0 or xx >= FIELD_WIDTH ):
                    flag = 1
                    print "turn NG (1)"
                    break
                if ( yy < -int((self.location[0]+x) * 0.5) or
                     yy >= FIELD_HEIGHT - int((self.location[0]+x) * 0.5) ):
                    flag = 1
                    print "turn NG (2)"
                    break
                # 重なっている場合
                if ( turnedPiece[x][y] == 1 and
                     self.field[xx][yy] >= 1 ):
                    flag = 1
                    print "turn NG (3)"
                    break
        if flag == 1:
            return False
        else:
            return True

    # 再描画
    def RedrawAllField( self ):
        # ゲームの終了後は実行しない
        # 開始前は表示する
        if self.game_end == True:
            return
        #print "RedrawAllField"
        # まず固定セル
        for x in range( FIELD_WIDTH ):
            for y in range( - int(x * 0.5), FIELD_HEIGHT - int(x * 0.5) ):
                self.main_board.SetColor( x, y, self.field[x][y] )
        # 次に移動中のpiece
        for x in range( PIECE_WIDTH ):
            for y in range( PIECE_HEIGHT ):
                xx = x + self.location[0]
                yy = y + self.location[1]
                if xx < 0: pass
                if xx >= FIELD_WIDTH: pass
                if yy < -int(x * 0.5): pass
                if yy >= FIELD_HEIGHT - int(x * 0.5): pass
                # 描かれていない場所まで塗らないようにする
                if self.piece[x][y] == 1:
                    self.main_board.SetColor( xx, yy, 1 )

##------
if __name__ == '__main__':
    f = Frame()
    f.pack()
    f.mainloop()

# EOF

Pythonでゲーム作成(テトリス)

だいぶ前にPythonでテトリスを実装してみたのですが,使わなくなったので公開します.

参考にしたのはhttp://www13.plala.or.jp/kymats/study/game_other/SPACE_TETRIS/st1.html

実際には,ピースのデータ構造のみ参考にして,盤面はtkinterのCanvas上にrectangleを並べると,これがobjectとして後から色を変更できるようになるので,これを使って実装 しました.

画面はこんな感じです.


点数処理も書いたのですが,これだけで結構な分量になったので,最後に点数処理の部分を除いたソースを以下に掲載します.

これを応用して六角形にしたものを「PythonでHextris」に載せておきます.昔X68000で遊んでいたゲームを作ってみました.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Python 2.7.5 on OS X 10.9.2
# by Mitsuharu Arimura 2015/3/14

import Tkinter as Tk
import datetime as dt
import time, random
import sys, os, csv

# ゲームフィールドのサイズ
FIELD_WIDTH = 18
FIELD_HEIGHT = 30

# ブロックのサイズ
PIECE_WIDTH = 4
PIECE_HEIGHT = 4

# 1個のセルのピクセル数
CELL_WIDTH = 20
CELL_HEIGHT = 20

# ゲーム本体の画面 View
class Board( Tk.Canvas ):
    # フィールドのピクセル数
    BOARD_WIDTH = FIELD_WIDTH * CELL_WIDTH
    BOARD_HEIGHT = FIELD_HEIGHT * CELL_HEIGHT
    margin = 2

    def __init__( self, master ):
        self.width = self.BOARD_WIDTH
        self.height = self.BOARD_HEIGHT
        Tk.Canvas.__init__( self, master, relief=Tk.RAISED, bd=self.margin,
                            bg='white',
                            width=self.BOARD_WIDTH, height=self.BOARD_HEIGHT )

        # Canvas上の rectangle の2次元配列
        self.Field = [[0 for y in range(FIELD_HEIGHT)]
                      for x in range(FIELD_WIDTH)]
        offset = self.margin * 2 + 3
        for x in range( FIELD_WIDTH ):
            for y in range( FIELD_HEIGHT ):
                x0 = x * CELL_WIDTH + offset
                y0 = y * CELL_HEIGHT + offset
                r = self.create_rectangle( x0, y0,
                                           x0 + CELL_WIDTH,
                                           y0 + CELL_HEIGHT,
                                           fill='white',
                                           width=0)
                self.Field[x][y]=r

        # PAUSEの文字
        TEXT_font = ('Helvetica','60','bold')
        TEXT_color = 'red'
        self.pause_text = self.create_text( self.BOARD_WIDTH/2, 100,
                                            text='PAUSE',
                                            font=TEXT_font,
                                            fill=TEXT_color,
                                            justify=Tk.CENTER)
        self.HidePauseText()

    def SetColor( self, x, y, value ):
        #print "SetColor:"
        if x < 0: return
        if x >= FIELD_WIDTH: return
        if y < 0: return
        if y >= FIELD_HEIGHT: return
        #print "(%d, %d)=>%d" % (x, y, value)
        if value == 1:
            self.itemconfigure( self.Field[x][y], fill='black')
        elif value == 2:
            self.itemconfigure( self.Field[x][y], fill='gray')
        elif value == 3:
            self.itemconfigure( self.Field[x][y], fill='slate gray')
        else:
            self.itemconfigure( self.Field[x][y], fill='white')

    # PAUSE中にPAUSEの文字を表示する
    def ShowPauseText( self ):
        self.itemconfigure( self.pause_text, state=Tk.NORMAL )

    # PAUSEが終わるときにPAUSEの文字を非表示にする
    def HidePauseText( self ):
        self.itemconfigure( self.pause_text, state=Tk.HIDDEN )

# 次のピースを表示する画面 View
class NextPieceBoard( Tk.Canvas ):
    # 次のピースを表示する場所のサイズ
    NEXT_BOARD_WIDTH = PIECE_WIDTH * CELL_WIDTH
    NEXT_BOARD_HEIGHT = PIECE_HEIGHT * CELL_HEIGHT
    margin = 2
    def __init__( self, master ):
        Tk.Canvas.__init__( self, master, relief=Tk.RAISED, bd=self.margin,
                            bg='white',
                            width=self.NEXT_BOARD_WIDTH,
                            height=self.NEXT_BOARD_HEIGHT )
        self.RectArray = [[0 for y in range(PIECE_HEIGHT)]
                          for x in range(PIECE_WIDTH)]
        #print self.RectArray
        offset = self.margin * 2 + 3
        for x in range( PIECE_WIDTH ):
            for y in range( PIECE_HEIGHT ):
                x0 = x * CELL_WIDTH + offset
                y0 = y * CELL_HEIGHT + offset
                r = self.create_rectangle( x0, y0,
                                           x0 + CELL_WIDTH,
                                           y0 + CELL_HEIGHT,
                                           fill='white',
                                           width=0)
                self.RectArray[x][y]=r

    def DisplayPiecePattern( self, pattern ):
        p = pattern
        for x in range( PIECE_WIDTH ):
            for y in range( PIECE_HEIGHT ):
                # print "p[%d][%d] = %d" % (x, y, p[x][y])
                if p[x][y] == 1:
                    # print "p[%d][%d] = red" % (x, y)
                    self.itemconfigure( self.RectArray[x][y], fill='black' )
                else:
                    # print "p[%d][%d] = blue" % (x, y)
                    self.itemconfigure( self.RectArray[x][y], fill='white' )

# ゲーム全体 Controller
class Frame( Tk.Frame ):
    def __init__( self, master=None ):
        Tk.Frame.__init__( self, master )
        self.master.title( 'Tetris' )

        #########################################
        # board frame
        self.bframe = Tk.Frame( self, bd=1, relief=Tk.RIDGE )
        self.bframe.pack( side=Tk.LEFT, padx=10, pady=10 )
        self.board = Board( self.bframe )
        self.board.pack( side=Tk.LEFT, padx=0, pady=0 )

        #########################################
        # frame start
        frame = Tk.Frame( self, bd=1, relief=Tk.RIDGE )
        # exit button
        self.btExit = Tk.Button( frame, text='Exit', command=self.exitGame )
        self.btExit.pack( anchor=Tk.E, padx=10, pady=10 )
        # replay (not start) button
        #self.btReplay = Tk.Button( frame, text='Replay',
        #                           command=self.replayGame )
        #self.btReplay.pack( anchor=Tk.E, padx=10, pady=0 )
        # start button
        self.btStart = Tk.Button( frame, text='Start', command=self.startGame )
        self.btStart.pack( anchor=Tk.E, padx=10, pady=0 )
        # next piece display
        self.next_piece = NextPieceBoard( frame )
        self.next_piece.pack( anchor = Tk.E, padx=10, pady=10 )
        TEXT_font = ('Helvetica','12','italic')
        NUM_font = ('Helvetica','24','bold')
        # time text
        self.time_text = Tk.Label( frame, text='PLAY TIME', font=TEXT_font )
        self.time_text.pack( anchor = Tk.E, padx=10, pady=0 )
        # timer
        self.time_box = Tk.Label( frame, text='00\'00\"', font=NUM_font,
                                  bd=1, relief=Tk.RIDGE )
        self.time_box.pack( anchor = Tk.E, padx=10, pady=0 )
        frame.pack( side=Tk.RIGHT, padx=10, pady=10 )

        # key bind
        self.bind( "", self.keyPressed )
        self.bind( "", self.keyLeft )
        self.bind( "", self.keyRight )
        self.bind( "", self.keyUp )
        self.bind( "", self.keyDown )
        self.focus_set()
                    
        self.tetris = Tetris( self )

        self.processing_down = False
        self.timerEventCount = 0
        self.timerCount = 0

        self.tetris.PrepareStartGame()

    # exit button
    def exitGame( self ):
        self.tetris.playing = False
        sys.exit()

    # start button
    def startGame( self ):
        self.tetris.playing = True
        # ボタンは押せなくする
        self.DisableStartButton()
        #self.DisableReplayButton()
        # カウンタをリセットしてタイマーをスタートする
        self.timerEventCount = 0
        self.timerCount = 0
        # これ以降,timerEventが0.5秒に一回呼び出される
        self.after( 500, self.timerEvent )

    # replay
    def replayGame( self ):
        self.EnableStartButton()
        self.tetris.PrepareStartGame()
        self.timerEventCount = 0
        self.timerCount = 0
        self.DisplayTime( 0, 0 )

    # 0.5秒ごとに呼び出される
    def timerEvent( self ):
        if self.tetris.playing == False:
            return
        if self.tetris.pausing == True:
            return
        self.timerEventCount = self.timerEventCount + 1
        # 0.5秒に1回呼び出されるので2回→1秒に1回の点数アップ
        if self.timerEventCount % 2 == 0:
            self.timerCount = self.timerCount + 1
            self.DisplayTime( self.timerCount / 60, self.timerCount % 60 )
            # print "count %d" % self.timerEventCount
            # print "timer %d" % self.timerCount
        # ブロックを1段落とす
        self.keyDown( None )
        # 0.5秒後に自分を呼び出す
        self.after( 500, self.timerEvent )

    def endGame( self ):
        self.focus_set()
        self.replayGame()

    def DisableExitButton( self ):
        self.btExit.configure( state=Tk.DISABLED )

    def EnableExitButton( self ):
        self.btExit.configure( state=Tk.NORMAL )

    def DisableReplayButton( self ):
        #self.btReplay.configure( state=Tk.DISABLED )
        pass

    def EnableReplayButton( self ):
        #self.btReplay.configure( state=Tk.NORMAL )
        pass

    def DisableStartButton( self ):
        self.btStart.configure( state=Tk.DISABLED )

    def EnableStartButton( self ):
        self.btStart.configure( state=Tk.NORMAL )

    def DisplayTime( self, min=0, sec=0 ):
        self.time_box.configure( text="%02d\'%02d\"" % (min, sec) )

    # 以下はキー入力処理
    def keyPressed( self, event ):
        c = event.char
        # space: pause
        if self.tetris.playing == False:
            return
        if c == ' ':
            if self.tetris.pausing == False:
                print "PAUSE!!!"
                self.tetris.pausing = True
                # PAUSEを表示
                self.board.ShowPauseText()
            else:
                print "PAUSE END!!!"
                self.tetris.pausing = False
                # PAUSEを隠す
                self.board.HidePauseText()
                # 再度タイマーを開始
                self.after( 500, self.timerEvent )

    def keyLeft( self, event ):
        if self.tetris.playing == False:
            return
        if self.tetris.pausing == True:
            return
        # print "press Left"
        if self.tetris.CanMovePiece( move='left' ) == 0:
            self.tetris.MovePiece( move='left' )
            self.tetris.RedrawAllField()

    def keyRight( self, event ):
        if self.tetris.playing == False:
            return
        if self.tetris.pausing == True:
            return
        # print "press Right"
        if self.tetris.CanMovePiece( move='right' ) == 0:
            self.tetris.MovePiece( move='right' )
            self.tetris.RedrawAllField()

    # キーで呼ばれるのとタイマーで呼ばれるのが同時に重なる場合が
    # あるので再入防止している
    def keyDown( self, event ):
        if self.tetris.playing == False:
            return
        if self.tetris.pausing == True:
            return
        # print "press Down"
        if self.processing_down == True:
            return
        self.processing_down = True
        res = self.tetris.CanMovePiece( move='down' )
        if res == 0:
            self.tetris.MovePiece( move='down' )
            self.tetris.RedrawAllField()
        elif res == 2:
            self.tetris.FixPieceAndGetNextPiece()
        self.processing_down = False

    # 回転
    def keyUp( self, event ):
        if self.tetris.playing == False:
            return
        if self.tetris.pausing == True:
            return
        # print "press Up"
        if self.tetris.CanTurnPiece() == True:
            self.tetris.TurnPiece()
            self.tetris.RedrawAllField()

# テトリスゲームの Data
class Tetris:
    # ゲームフィールド
    field = [[0 for y in range(FIELD_HEIGHT)]
             for x in range(FIELD_WIDTH)]
    # 現在移動中のブロック
    piece = [[0 for y in range(PIECE_HEIGHT)]
             for x in range(PIECE_WIDTH)]
    # ブロックの左上端の座標(フィールドは左上が(x,y) = (0,0)でyが下向き)
    location = [0, 0]
    # 次のブロック
    nextPiece = [[0 for y in range(PIECE_HEIGHT)]
                 for x in range(PIECE_WIDTH)]
    # ブロックの全てのパターン
    piecePattern = [
        [[0,0,0,0],[0,1,1,0],[0,1,1,0],[0,0,0,0]], # 0
        [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], # 1
        [[0,0,0,0],[0,1,1,1],[0,0,1,0],[0,0,0,0]], # 2
        [[0,0,0,0],[0,1,1,1],[0,1,0,0],[0,0,0,0]], # 3
        [[0,0,0,0],[0,1,0,0],[0,1,1,1],[0,0,0,0]], # 4
        [[0,0,0,0],[0,0,1,1],[0,1,1,0],[0,0,0,0]], # 5
        [[0,0,0,0],[0,1,1,0],[0,0,1,1],[0,0,0,0]]] # 6

    def __init__( self, parent ):
        self.main_frame = parent
        self.main_board = parent.board
        self.next_piece = parent.next_piece

    # ゲームの開始準備
    def PrepareStartGame( self ):
        self.playing = False
        self.pausing = False
        self.game_end = False

        # 盤面の初期化
        # 全部を白で塗る
        for y in range( FIELD_HEIGHT ):
            for x in range( FIELD_WIDTH ):
                self.field[x][y] = 0
        # 周囲1列を3で塗る
        for y in range( FIELD_HEIGHT ):
            self.field[0][y] = 3
            self.field[ FIELD_WIDTH - 1 ][y] = 3
        for x in range( FIELD_WIDTH ):
            self.field[x][ FIELD_HEIGHT - 1 ] = 3

        # 障害物を置いておく
        self.field[1][22] = 2
        self.field[2][22] = 2
        self.field[3][21] = 2
        self.field[4][21] = 2
        self.field[5][20] = 2
        self.field[6][20] = 2

        # 1個目のピースを準備する
        self.GetNextPiece( True )
        self.RedrawAllField()

    # 1行だけ消す処理
    def DeleteOneLine( self, line ):
        # 上から落とす
        for y in range(line, 1, -1):
            for x in range(1, FIELD_WIDTH-1):
                self.field[x][y] = self.field[x][y-1]
        self.RedrawAllField()

    # 埋まっている行を全て消す処理
    def DeleteFilledLines( self ):
        #print "Start Delete Line!!!"
        y = FIELD_HEIGHT - 2 # 一番下の行を除く
        deleted_line = 0 # 削除できた行の数
        all_clear = 0
        # 一番上の行まで確認
        count_sum = 0
        while y >= 0:
            # 1行に埋まってるセルの個数を数える
            #print "count line %d:" % y,
            count = 0
            for x in range(1, FIELD_WIDTH-1): # 両端を除く
                if self.field[x][y] == 2:
                    count = count + 1
            #print "count %d" % count
            # 全部が埋まっていたら行の削除処理
            if count == FIELD_WIDTH - 2: # 両端を除いた数
                # 削除できた場合にはyは増えない
                print "clear line %d" % y
                self.DeleteOneLine(y)
                deleted_line = deleted_line + 1
            else:
                count_sum = count_sum + count
                # 全部が埋まっていなかったら次の行へ行く
                y = y - 1
        print "end Delete Line!!! (%d lines)" % deleted_line
        print "%d cells left" % count_sum

        # count_sumが0だったらボーナス
        if count_sum == 0:
            all_clear == 1
            print '########################################'
            print '######## ALL CELLS CLEARED #############'
            print '########################################'

    # 現在のピースが落ち切ったので,ここに固定する
    # 行が埋まったら消す
    # 次のピースを開始する
    def FixPieceAndGetNextPiece( self ):
        # 現在のピースを現在の位置に固定する
        for x in range(PIECE_WIDTH):
            for y in range(PIECE_HEIGHT):
                if self.piece[x][y] == 1:
                    self.field[self.location[0]+x][self.location[1]+y] = 2
        # 埋まっている行を消す
        self.DeleteFilledLines()
        # 次のピース
        self.GetNextPiece( False )
        self.RedrawAllField()

    # next pieceの領域に次のピースを作成する
    def CreatePiece( self ):
        i = random.randint(0, len(self.piecePattern)-1)
        # print "[%d]" % i,
        self.nextPiece = self.piecePattern[i]
        # print "-->", self.nextPiece
        self.next_piece.DisplayPiecePattern( self.nextPiece )

    # next pieceの領域からピースを持ってきて一番上に置く
    # 既にここまでピースが積んであったらゲームオーバー判定
    def GetNextPiece( self, is_first ):
        if is_first:
            self.CreatePiece()
        #print self.nextPiece
        self.location[0] = 5
        self.location[1] = 0
        for y in range( PIECE_HEIGHT ):
            for x in range( PIECE_WIDTH ):
                # 次のピースを持ってくる
                self.piece[x][y] = self.nextPiece[x][y]
                # フィールドに置く
                self.main_board.SetColor( self.location[0]+x,
                                          self.location[1]+y,
                                          self.piece[x][y])
        # ゲーム終了判定
        for y in range( PIECE_HEIGHT ):
            # 内側のxのloopから抜けて来ていたら,ここでも抜ける
            if self.game_end == True:
                break
            for x in range( PIECE_WIDTH ):
                # フィールドに置いた瞬間,既にその場所にブロックがあったら
                if ( self.piece[x][y] == 1 and
                     self.field[self.location[0]+x][self.location[1]+y] == 2):
                    # 終わり
                    self.game_end = True
                    # タイマーでのloopを止める
                    self.playing = False
                    # 1個重なっていれば十分なので,ここで抜ける
                    break
        if self.game_end == False:
            # 終わっていなかったら続ける
            self.CreatePiece()
        else:
            # ゲームオーバー処理の開始
            print "GAME END !!!"
            self.main_frame.endGame()

    def MoveOrientation( self, move ):
        xmove = ymove = 0
        if move == 'down':
            ymove = 1
        elif move == 'left':
            xmove = -1
        elif move == 'right':
            xmove = 1
        return ( xmove, ymove )

    def MovePiece( self, move ):
        ( xmove, ymove ) = self.MoveOrientation( move )
        self.location[0] = self.location[0] + xmove
        self.location[1] = self.location[1] + ymove
        
    def TurnPiece( self ):
        turnedPiece = [[0 for y in range(PIECE_HEIGHT)]
                       for x in range(PIECE_WIDTH)]
        for x in range(PIECE_WIDTH):
            for y in range(PIECE_HEIGHT):
                turnedPiece[y][PIECE_HEIGHT-1-x] = self.piece[x][y]
        self.piece = turnedPiece

    # 移動した先を計算して,fieldとかぶらないか,はみ出ないかを見る
    # return 1: 左右の壁に当たって移動できない
    # return 2: 積んであるフィールド上のセルと重なるか,
    #           下に当たっているので,これで固定する
    def CanMovePiece( self, move='down' ):
        ( xmove, ymove ) = self.MoveOrientation( move )
        ( x0, y0 ) = ( self.location[0], self.location[1] )
        # フィールド上のセルと重なっている場合
        flag = 0
        for x in range(PIECE_WIDTH):
            for y in range(PIECE_HEIGHT):
                if ( self.piece[x][y] == 1 and
                     self.field[x0+x+xmove][y0+y+ymove] >= 1 ):
                    flag = 1
                    #print "move NG (4) (%d,%d)->(%d,%d)" % (
                    #    self.location[0]+x, self.location[1]+y,
                    #    self.location[0]+x+xmove, self.location[1]+y+ymove )
                    break
        if flag == 1:
            if ymove == 1:
                return 2 # 下に動けない場合はこれで固定
            else:
                return 1 # 左右ぶつかっている場合は,動けないだけ
        # 以上のチェックに引っかからない場合は動ける
        return 0

    # 回転した先を計算して,fieldとかぶらないかを見る
    # True: 回転できる
    def CanTurnPiece( self ):
        turnedPiece = [[0 for y in range(PIECE_HEIGHT)]
                       for x in range(PIECE_WIDTH)]
        for x in range(PIECE_WIDTH):
            for y in range(PIECE_HEIGHT):
                turnedPiece[y][PIECE_HEIGHT-1-x] = self.piece[x][y]
        # 確認
        flag = 0
        for x in range(PIECE_WIDTH):
            for y in range(PIECE_HEIGHT):
                # 添字が範囲を越えているかどうかの判定
                xx = self.location[0] + x
                yy = self.location[1] + y
                if ( xx < 0 or xx >= FIELD_WIDTH ):
                    flag = 1
                    print "turn NG (1)"
                    break
                if ( yy < 0 or yy >= FIELD_HEIGHT ):
                    flag = 1
                    print "turn NG (2)"
                    break
                # 重なっている場合
                if ( turnedPiece[x][y] == 1 and
                     self.field[xx][yy] >= 1 ):
                    flag = 1
                    print "turn NG (3)"
                    break
        if flag == 1:
            return False
        else:
            return True

    # 再描画
    def RedrawAllField( self ):
        # ゲームの終了後は実行しない
        # 開始前は表示する
        if self.game_end == True:
            return
        #print "RedrawAllField"
        # まず固定セル
        for x in range( FIELD_WIDTH ):
            for y in range( FIELD_HEIGHT ):
                self.main_board.SetColor( x, y, self.field[x][y] )
        # 次に移動中のpiece
        for x in range( PIECE_WIDTH ):
            for y in range( PIECE_HEIGHT ):
                xx = x + self.location[0]
                yy = y + self.location[1]
                if xx < 0: pass
                if xx >= FIELD_WIDTH: pass
                if yy < 0: pass
                if yy >= FIELD_HEIGHT: pass
                # 描かれていない場所まで塗らない
                if self.piece[x][y] == 1:
                    self.main_board.SetColor( xx, yy, self.piece[x][y] )

##------
if __name__ == '__main__':
    f = Frame()
    f.pack()
    f.mainloop()

# EOF