ユーニックス総合研究所

  • home
  • archives
  • python-rain

Pythonで雨を降らせるプログラムを作る

  • 作成日: 2023-08-01
  • 更新日: 2023-12-25
  • カテゴリ: Python

Pythonで雨を降らせるプログラムを作る

2023年の8月ですが、異常な暑さです。
埼玉の熊谷などは39度を記録しており、過去に類を見ない異常気象になっています。

筆者の仕事部屋にはクーラーがなく、そのため仕事をすると死んでしまいます。
ですので今は仕事部屋から退避してクーラーのある所でこの記事を書いていますが、天気はカンカン晴れです。
雨でも降ってくれれば少しは暑さもマシになるのですが、なかなかそうもいきません。

そこで今回はPythonで雨を降らせるプログラムを作ってみました。
このプログラムで涼めば少しはマシになるかも・・・?

ということでそのプログラムの解説を行いたいと思います。

関連記事
Djangoでオブジェクトを一括作成・更新【bulk_create, bulk_update】
DjangoのModel.objects.filter()の使い方【QuerySet】
Djangoのmodelのcreate()の使い方【Python】
Django入門: ルートの設定 ~ 簡単な一行掲示板アプリを作る その4【Windows10】
NumPyのappend()の使い方: 配列の末尾に要素を追加
Numpyのarangeの使い方: 指定範囲の数列を生成する
Python3でYoutube Data APIを使ってキーワード検索する
PythonからC言語(my.puts)を呼び出して実行する

プログラムの実行風景

今回のプログラムを実行すると以下のような結果になります。

夜の雨って感じで涼しい風景ですね。

ソースコード全文

以下がソースコード全文です。

import tkinter as tk  
import random  


class Rain:  
    def __init__(self):  
        self.id = None  
        self.ay = 1  
        self.length = 1  


class App(tk.Tk):  
    def __init__(self):  
        super().__init__()  
        self.title('PyRain')  
        self.width = 800  
        self.height = 600  
        self.geometry(f'{self.width}x{self.height}')  

        self.canvas = tk.Canvas(self, bg="black")  
        self.canvas.pack(expand=True, fill=tk.BOTH)  

        self.rains = []  
        self.nrains = 100  

        for _ in range(self.nrains):  
            rain = Rain()  
            x = random.randint(0, self.width)  
            y = random.randint(0, self.height)  
            rain.length = random.randint(10, 50)  
            x2 = x  
            y2 = y + rain.length  
            rain.id = self.canvas.create_line(x, y, x2, y2, fill=self.get_color())  
            rain.ay = random.randint(5, 15)  
            self.rains.append(rain)  

        self.after(33, self.update)  

    def get_color(self):  
        colors = ['white', 'blue', 'cyan']  
        return random.choice(colors)  

    def update(self):  
        for rain in self.rains:  
            self.canvas.move(rain.id, 0, rain.ay)  
            pos = self.canvas.coords(rain.id)  
            if pos[1] >= self.height:  
                x = random.randint(0, self.width)  
                y = -rain.length  
                self.canvas.moveto(rain.id, x, y)  
        self.after(33, self.update)  


App().mainloop()  

このソースコードの解説をしていきたいと思います。

プログラム全体の構成

プログラムの全体の構成としてはまずTkinterでウィンドウを作ります。
それからCanvasウィジェットでウィンドウ全体をキャンバスにします。
そしてCanvasに雨粒に見立てた線を描画し、その線を上から下に移動させます。
線がウィンドウからはみ出したら線の位置をリセットして繰り返します。

今回はこの方針で実装していきます。

Rainクラスの作成

雨粒を表すRainクラスを作ります。

class Rain:  
    def __init__(self):  
        self.id = None  
        self.ay = 1  
        self.length = 1  
  • id ... CanvasのオブジェクトのID
  • ay ... y座標の移動量
  • length ... 雨粒の縦の長さ

Appクラスの作成

今回はGUIアプリなのでPythonの標準ライブラリのTkinterを使います。
ですのでtk.Tkクラスを継承したAppクラスを作ります。

tk.TkクラスはTkinterのウィンドウを表すクラスで、これを継承することでウィンドウを定義できます。

class App(tk.Tk):  
    def __init__(self):  
        super().__init__()  
        ...  

App().mainloop()  

App().mainloop()としてmainloop()メソッドを呼び出すことでウィンドウを起動しています。

App.__init__()の実装

App.__init__()でプログラムの初期化を行います。

    def __init__(self):  
        super().__init__()  
        self.title('PyRain')  
        self.width = 800  
        self.height = 600  
        self.geometry(f'{self.width}x{self.height}')  
        ...  

まず上記では親のイニシャライザの呼び出しとウィンドウのタイトルの設定をself.title()で行っています。
それからself.widthにウィンドウの横幅、self.heightにウィンドウの高さを定義します。
そしてself.geometry()でウィンドウのサイズを設定しています。

        self.canvas = tk.Canvas(self, bg="black")  
        self.canvas.pack(expand=True, fill=tk.BOTH)  

        self.rains = []  
        self.nrains = 100  

上記ではself.canvasCanvasウィジェットを代入しています。
Canvasbgblackで、こうするとキャンバス全体が黒色になります。
そしてself.canvas.pack()でウィジェットをウィンドウ上に配置します。
expand=Trueで親ウィジェットに合わせて伸縮する設定に、fill=tk.BOTHで上下左右にウィジェットが埋められるようになります。

self.rainsRainのオブジェクトを保存するリストです。
self.nrainsは雨粒の個数でこれは100個にしています。

        for _ in range(self.nrains):  
            rain = Rain()  
            x = random.randint(0, self.width)  
            y = random.randint(0, self.height)  
            rain.length = random.randint(10, 50)  
            x2 = x  
            y2 = y + rain.length  
            rain.id = self.canvas.create_line(x, y, x2, y2, fill=self.get_color())  
            rain.ay = random.randint(5, 15)  
            self.rains.append(rain)  

上記では100個の雨粒を初期化しています。
for _ in range(self.nrains):でループをself.nrainsの数だけ回します。
それからrain = Rain()で雨粒のオブジェクトを作成。
x = random.randint(0, self.width)0からself.widthの間でランダムにxを定義します。yについても同様です。

それからrain.lengthに雨粒の縦の長さをランダムに設定します。
x2y2には線の終端の座標を保存します。xyは線の始点の座標です。

そしてself.canvas.create_line()で線オブジェクトを作成します。
xyが線の始点の座標、x2y2が線の終点の座標、fillが線の色です。
線の色はself.get_color()でランダムに生成しています。
self.canvas.create_line()はオブジェクトの作成に成功するとオブジェクトを表すIDを返すのでそれをrain.idに保存します。

rain.ayにもランダムに線のy座標の移動量を代入しています。
rainオブジェクトを初期化したらself.rainsリストにrainオブジェクトをself.rains.append()で追加します。

        self.after(33, self.update)  

self.after()は指定ミリ秒後に指定の関数を実行するとtk.Tkのメソッドです。
このメソッドで33ミリ秒後にself.update()を実行するように設定しています。
こうすると初期化の33ミリ秒後にself.update()が一回実行されます。
self.update()のプログラムのループ処理が書いてあるメソッドです。雨粒の移動などをこのメソッドで行います。

App.get_color()の実装

get_color()メソッドはランダムに雨粒の色を生成するメソッドです。

    def get_color(self):  
        colors = ['white', 'blue', 'cyan']  
        return random.choice(colors)  

self.canvas.create_line()fillに設定できる色は16進数のほかwhiteblueなどの名前でも設定できます。
上記の実装ではcolorswhite, blue, cyanの色を格納し、そこからrandom.choice()でランダムに1つ選択して返しています。

App.update()の実装

update()メソッドにはループ中の処理を書いていきます。

    def update(self):  
        for rain in self.rains:  
            self.canvas.move(rain.id, 0, rain.ay)  
            pos = self.canvas.coords(rain.id)  
            if pos[1] >= self.height:  
                x = random.randint(0, self.width)  
                y = -rain.length  
                self.canvas.moveto(rain.id, x, y)  
        self.after(33, self.update)  

まずself.rainsをfor文で回してrainオブジェクトを取り出します。
self.canvas.move()rain.idを指定してrain.ayの量だけrain.idy座標を移動させます。
rain.ayがプラスなのでこの移動は上から下に行われます。
キャンバス上の座標軸は左上がx=0, y=0で右下がx=self.width, y=self.heightになります。

self.canvas.coords()rain.idのキャンバス上の座標を取り出します。
pos[1]にオブジェクトのy座標が入ってますのでこれの値がself.height以上になったら、つまりy座標がキャンバス外にはみ出したら、rain.idの座標を初期化します。
rain.idの座標はself.canvas.moveto()で絶対座標で設定します。

self.canvas.move()は相対的な座標の移動、self.canvas.moveto()は絶対的な座標の指定になります。

self.update()が完了したら最後にself.after()self.update()を再帰的に呼び出しておきます。
こうすると再帰的な呼び出しが33ミリ秒ごとに続き、ゲームループのようになります。

おわりに

今回はPythonで雨を降らせてみました。
この記事を書き終わった直後に実際に雨が降ってきました。
これは祈祷の効果がある祈祷プログラムかもしれません。。
なにか参考になれば幸いです。

🦝 < 祈祷プログラム!

🦝 < 雨よ降れ!