2015年9月11日金曜日

【python3】ルーレット的なやつ【kivy】

先輩「ルーレットとかあるといいね」
俺「作りましょう!」(と心の中で決意)



【概要】
  • 「Push Start!」ボタンを押すとルーレットが回り始める
  • 「Running...」中はルーレットが減速しながら回り続ける
  • 一定速度以下になると停止し、「Once More!」ボタンが現れる

4つのファイルを使ってます。
  • main.py(実行)
  • roulette.kv(設定)
  • make_figures.py(ルーレット図の作成)←要numpy,matplotlib
    • 実行すると、figuresディレクトリ内にfig_x_of_n.pngを作成する
    • nはルーレットの目の数で、xはその図の中でどの数字がハイライトされているか
    • main.pyの中で「filename_first + str(i) + filename_last」となっているのは読み込まれるファイルを指定している
  • make_sound.py(ルーレット音の作成)
    • 実行すると、ディレクトリ内にsound.wavを作成する
    • 録画では鳴ってないけどルーレットが回転するごとに800Hzの音が100ms流れてます
でも実行時に使うのはmain.pyとroulette.kvだけ
make_系は予め走らせておいて必要な図を作っておきます

<main.py>

# -*- coding: utf-8 -*-

import kivy
kivy.require('1.9.0')

from kivy.app import App
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.core.audio import SoundLoader
from kivy.properties import StringProperty, ObjectProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.button import Button
import sys
import os
import random

number_of_player = 5

sound = SoundLoader.load('sound.wav')
filename_first = 'figures/fig_'
filename_last = '_of_' + str(number_of_player) + '.png'

class StartButton(Button):
    state = StringProperty('Push Start!')

class RouletteImage(Widget):
    filename = StringProperty(filename_first + 'none' + filename_last)

    def filename_update(self, counter):
        value = counter % number_of_player + 1
        self.filename = filename_first + str(value) + filename_last

class Root(FloatLayout):
    sb = ObjectProperty(None)
    ri = ObjectProperty(None)
    val = 0; c_val = 0.02
    n_move = 0
    timer = 0
    update_fps = 1 / 60

    def update(self, dt):
        if self.sb.state == 'Push Start!':
            pass
        elif self.sb.state == 'Running...':
            self.when_running()
        elif self.sb.state == 'Once More!':
            self.when_once_more()

    def when_running(self):
        if self.val == 0:
            self.counter = random.randint(0,number_of_player - 1)

        self.update_val()
        if self.val >= self.c_val or self.timer >= 60:
            self.val -= self.c_val
            self.update_img()

    def update_val(self):
        self.val += self.update_fps / (self.n_move + 1) ** 1.1
        self.timer += self.update_fps

    def update_img(self):
        self.counter += 1
        self.ri.filename_update(self.counter)
        self.n_move += 1
        self.timer = self.timer % 60
        sound.play()
        if self.update_fps / (self.n_move + 1) < 0.001:
            if random.randint(0,5) == 0:
                self.sb.state = 'Once More!'

    def when_once_more(self):
        if self.val != 0:
            self.val = 0
            self.n_move = 0
            self.timer = 0

class rouletteApp(App):
    def build(self):
        root = Root()
        Clock.schedule_interval(root.update, root.update_fps)
        return root

if __name__ == '__main__':
    rouletteApp().run()

<roulette.kv>

#: kivy 1.9.0

<StartButton>:
    text: self.state
    on_press: self.state = 'Running...'
    on_release: self.state = 'Running...'

<RouletteImage>:
    Image:
        size: 300, 300
        center: self.parent.center
        source: self.parent.filename

<Root>:
    sb: sbval
    ri: rival

    canvas:
        Color:
            rgb: (0, 0.3, 0)
        Rectangle:
            size: self.size

    StartButton:
        id: sbval
        size_hint: 0.2, 0.1

    RouletteImage:
        id: rival

<make_figures.py>

# -*- coding: utf-8 -*-

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

class Settings:
    fig_size = 500

class MakePolygon:

    def __init__(self, n):
        settings = Settings()
        def make_info_table(n, settings):
            dat = pd.DataFrame({'number_of_angles':[n],
                'fig_size':[settings.fig_size]})
            dat.index = ['value']
            return dat.T
        self.info = make_info_table(n, settings)

    def make_vertex_table(self):
        n = self.info.ix['number_of_angles','value']
        vertex_angle = np.array([])
        for i in np.arange(n + 1):
            vertex_angle = np.r_[vertex_angle, np.pi * 2 * i / n]
        vertex_x = np.cos(vertex_angle)
        vertex_y = np.sin(vertex_angle)
        return np.c_[vertex_x, vertex_y]

    def make_figure(self, fill):
        size = self.info.ix['fig_size','value'] / 100
        vertex_table = self.make_vertex_table()
        plt.rc('axes', edgecolor="none")
        fig = plt.figure(figsize=(size, size))
        ax = fig.add_subplot(111, xlim = (-1.1, 1.1),
                ylim = (-1.1, 1.1), xticks = [], yticks = [])

        for i in np.arange(vertex_table.shape[0] - 1):
            ax.plot([vertex_table[i, 0], vertex_table[i + 1, 0]],
                    [vertex_table[i, 1], vertex_table[i + 1, 1]],
                    color = 'white')
            ax.plot([0, vertex_table[i, 0]],
                    [0, vertex_table[i, 1]],
                    color = 'white')

        def make_triangle_table(n):
            triangle_table = pd.DataFrame({'x': [0,
                vertex_table[n, 0], vertex_table[n + 1, 0]],
                'y1': [0, vertex_table[n, 1], vertex_table[n + 1, 1]],
                'y2': [0, vertex_table[n, 1], vertex_table[n + 1, 1]]})
            triangle_table = triangle_table.sort('y1')
            triangle_table = triangle_table.sort('x')
            triangle_table.index = np.sort(np.array(triangle_table.index))

            m_val = np.abs(triangle_table.x[0]-triangle_table.x[1])
            n_val = np.abs(triangle_table.x[2]-triangle_table.x[1])
            y1_1 = (n_val * triangle_table.y1[0] + \
                    m_val * triangle_table.y1[2]) / (m_val + n_val)

            triangle_table.y1[1] = y1_1
            return triangle_table

        for i in np.arange(self.info.ix['number_of_angles', 'value']):
            triangle_table = make_triangle_table(i)
            ax.fill_between(np.array(triangle_table.x),
                    np.array(triangle_table.y1),
                    np.array(triangle_table.y2),
                    color = 'white', alpha = 0.8)
            ax.text(np.mean(triangle_table.x),
                    np.mean(triangle_table.y2),
                    i + 1, horizontalalignment = 'center',
                    verticalalignment = 'center', fontsize = 20)

        if fill == 'none':
            filename = 'figures/fig_none_of_' + \
                    str(self.info.ix['number_of_angles', 'value']) + '.png'
        else:
            triangle_table = make_triangle_table(fill)
            ax.fill_between(np.array(triangle_table.x),
                    np.array(triangle_table.y1),
                    np.array(triangle_table.y2),
                    color = 'yellow', alpha = 0.8)
            filename = 'figures/fig_' + str(fill + 1) + '_of_' + \
                    str(self.info.ix['number_of_angles', 'value']) + '.png'
        fig.savefig(filename, transparent = True)

    def make_figure_all(self):
        self.make_figure('none')
        for i in np.arange(self.info.ix['number_of_angles', 'value']):
            self.make_figure(i)

if __name__ == '__main__':
    for i in np.arange(3,7): #この設定では、目の数3~6の時に使う図を作成
        MakePolygon(i).make_figure_all()

<make_sound.py>

# -*- coding: utf-8 -*-

import wave
import numpy as np

sec = 0.05
freq = 44100
t = np.linspace(0.0, sec, freq * sec)
amp = 15000
hz = 800

w = amp * np.sin(hz * 2 * np.pi * t) / 2
data = w.astype(np.int16)

with wave.open('sound.wav', 'w') as out:
        out.setnchannels(1)
        out.setsampwidth(2)
        out.setframerate(freq)
        out.writeframes(data.tostring())

もちろんだけど、make_soundやmake_figuresは、カレントディレクトリをrouletteに移動してから実行しないとエラーが出る。
ファイル置き場はこちら

0 件のコメント:

コメントを投稿