ステーキの戯れ言

私のブログです。アドベントカレンダーを埋めるために作りました。

工学院大学システム数理学科 4年生はどんなことするの?

この記事はKogakuin Advent Calendar 2019,日目の記事です。

adventar.org

どうも、浦郷です。

世界に数十人しかいない、工学院大学情報学部システム数理学科の4年生です。
色々書いていきます。

始めに、大変申し訳ないですが卒論ばっかでそれっぽいネタ(卒論とそれに関連する内容が一番それっぽいけど、完成前に晒して何かあったら困るし…)がないです
なので日常的な事を連ねていきます。


はじめに

記事を書く前に、「研究室選びで悩んだことがあるか」というのを自分の心に問いかけたところ、なんと「ある」という回答が100%なんですね。
だから4年に入っての事と、研究室の話をしようと思います。

f:id:uragou:20191204193047p:plain
心のアンケート結果


4年生はどんなことするの?

という事で、今年で最後になる人柱としての記録を残していきます。

そもそも工学院大学って?などの疑問は過去の記事を見てね。
3年 uragou.hatenablog.com 2年 uragou.hatenablog.com


4年生の時間割

去年・一昨年とどんな講義取ってきたのかなどを晒してきましたが、今年は見せません。だって卒論研究以外に何も履修していないんだもん。
単位は3年まで(そもそも3年後期からスカスカ)で取り終えたし、便覧見てもPBLとか4年で取らなきゃいけない単位って卒論しか書いていないし

f:id:uragou:20191204181752p:plain
空っぽの時間割

ほら、卒論だけ!という事で卒論が何なのか知らない純粋無垢な君たちに卒論や研究室について書いていこう。


研究室って何するところなの?

研究するところ、それ以外ない

当たり前の事なんですが、研究室によってやり方・拘束時間・卒業条件は変わってきます。
一応学科としての卒業条件(しっかり卒論書こうね!)ってのがあるけど、他にも学会とか何かで発表したりなんてのも…。
ちなみに他学科ですが、研究室によってはずっと明かりが消えない研究室もありますね。


研究室を選ぶポイント

の前に…お前は選べる立場なのか?

教授陣にも限界があるため、当然ながら希望の所にいけない人もいます。
詳しい基準なんて知りませんが、希望の研究室にいけるような成績を取っておきましょう。私は第一希望でした。

それで…、研究室を選ぶポイントですが

  1. まずやりたい研究とマッチしている所 1 色んな講義を受けた中で、相性が良いと思った教授の研究室
  2. 生活範囲

まずはやりたいこと・興味のあること、無ければ人柄。これ基本。結局の所他に判断材料なんてないし。

もう一つ、これは工学院特有の事情ですが情報学部の1,2年は八王子、3年は新宿キャンパスで講義を受けます。
さらに八王子キャンパスの二号館に一部研究室が移動した関係で、新宿オンリーな研究室もあれば八王子でもやれる研究室もあります。
詳しい事情は研究室ごとに違うので、興味がある研究室はあらかじめ調べておこう。


卒論の内容について

私の所属している研究室はこれです。

工学院大学 分散アルゴリズム研究室

名前の通りアルゴリズム系の研究室で、ネットワーク内で安全に通信したいね!ってことで暗号理論とかも研究分野に入っています。

それでまぁ、暗号関連の研究とかやるかと思いますよね!
今までセキュリティやらネットワークやら学んできたのでやると思いますよね!

何をやっているかは言いませんが、私が研究内容を決める際。「今自分が特に関心を持っていること、なんとかしたいなぁと思っていること」を考えました。

という事で、研究内容を考える一時間前くらいにやっていたことが研究内容に決まりました。何なのかは2月くらいに分かるよ。

もう書くことねぇなぁ…よし、日常を晒そう!


今の生活

今の時間割の通り、私は卒論以外には単位になるものを取っていません。

なので生活は…

起きる→研究を考える→思いついたことをPC使って試す→ダメなので考える→夜中になるとゲームしたりする→寝る

そして週に一度は進捗をまとめて教授とお話にいきます。ほぼ引きこもりですね。

夏の頃は健康のために意味もなく外を歩きましたが、もう寒いので止めました。

今は野菜ジュースの力を頼って健康を維持しています。

短いですが以上にします。就活の事ととかも書こうと思いましたが、4年終わりという時期はなるべく穏便に済ませようとするチキン精神が働きました。

卒論終わったら卒論以外の事がしたいです。

Python3とTkinter&canvasでマインスイーパーを作る その2

前回

uragou.hatenablog.com

こんなの作ってました。
f:id:uragou:20190705153638p:plain

canvasのモジュールについて
canvas = tkinter.Canvas(...)したということで、canvas.〜〜として記述します。
コードは一番下

オブジェクトを作成する

四角形は
self.canvas.create_rectangle(0,0,self.x * 40 + 20, self.y * 40 + 20, fill = "white")
create_rectangleは左上の座標と右下の座標を最初の4つの引数に、fillにカラーコードなどを入れれば、その図形を作成してくれる。
上のは、canvasの範囲いっぱいに白の四角形の図形を置く。(背景が白になる)

文字も同じように座標を引数に渡すが、テキストの場合は表示したい場所の中心の座標が必要。
作りたいオブジェクトごとに座標の指定の仕方があるので注意。

大量のオブジェクトを管理する。

オブジェクト生成時に実は各関数にて返り値があり、その図形のkeyが帰ってきている。
ただ、自分のわかりやすい規則でオブジェクトを管理したい場合はオブジェクト生成時にtagをつけることができる。
(create_rectangle(x1,y1,x2,y2,tag = str ,...)

タグとして文字列を作っておけば、生成順番関係なく各オブジェクトにアクセスしやすい。 タグでできることは

  • 任意のタグ付きオブジェクトの状態変更
  • 任意のタグ付きオブジェクトへの関数登録
  • 任意のタグ付きオブジェクトの削除
  • etc

オブジェクトの重なりの管理

基本的にオブジェクトは先に作成したものが下に来るようになる。
(環境依存でないことを祈るばかり)
ゲームが終わったときや、万が一を考え生成時に重なりが変わるようにcanvas.tag_raiseを使用している。
canvas.tag_raise(tag)
タグで指定されたオブジェクトは重なりが上に来る。
反対に重なりを下にする関数もあるが今回は使わなかった。

オブジェクトの削除

canvas.delete(tag) 指定したタグのついたオブジェクトを削除する。
オブジェクトを全削除する場合は
canvas.delete("all")

オブジェクトへのイベント

canvas.tag_bind(tag,event,function)
オブジェクトのタグ、発火条件、作動する関数オブジェクトを指定する。
登録できる条件などは逐一探せばいい。今回使ったのは

  • canvas.tag_bind(Mtag , "<Button-1>" , self.Bopen)(左クリック)
  • canvas.tag_bind(Mtag , "<Button-3>" , self.Flag)(右クリック)
  • canvas.tag_bind(Mtag , "<Enter>" , self.Enter)(マウスカーソルがオブジェクトを指した時)
  • canvas.tag_bind(Mtag , "<Leave>" , self.Leave)(マウスカーソルがオブジェクトから離れた時)

それぞれ、ブロック削除・フラグ設置・ブロック強調などをする関数を実行する。

ゲームオーバー時など、オブジェクトについた設定したイベントを削除する場合は
canvas.tag_unbind(tag , event)

イベント実行時

イベントにて実行された関数には引数としてイベントが起こったときの情報が渡される。(今回は特には使っていない)
複数のオブジェクトに同一の関数を登録していると、イベント発生時にどのタグのついたオブジェクトから呼ばれたのかは引数にはないが、関数実行時に関数内では、イベント呼び出し元のオブジェクトに current というタグがつく。
これにより「クリックされたオブジェクトを削除」などの操作を実現できる。

イベントでの色の変更

canvas.itemconfigure(tag,...)
引数のタグ以降には変更したい要素(fillとか)を書くと、該当オブジェクトの色などを変更できる。
今回のマインスイーパーの場合は、ブロックの色によって動作が変わるが(フラグの設置・削除など)オブジェクトの色を判定したい場合、色々やってみた結果…

for lop in self.canvas.itemconfig("current"):
            print(lop)

するとオプションなどが表示される。
print(self.canvas.itemconfig("current")["fill"])すると
('fill', '', '', '', '#d3d3d3')のような結果が返ってきたため
if canvas.itemconfig("current")["fill"][4] == color:
でオブジェクトからイベント実行時に、オブジェクトの色を判定して処理を変えることができるようになった。

プログラム内でのタイマー作成

コードを見てもらえば分かるが、timeモジュールを使用していない。 canvasにはcanvas.after(time,function) 第一引数には時間(ms)、第二引数に関数を指定すると
指定時間後にその関数を実行してくれる。

基本的なタイマー

import tkinter

def Timer():
        global time

        print(time)
        time += 1
        
        canvas.after(1000,Timer)

a = tkinter.Tk()
canvas = tkinter.Canvas(master=a)
canvas.pack()

time = 0
Timer()

a.mainloop()

もちろんこれでは止まらないが、条件によりループを止めたり、外部からtime変数を変更するなどすればストップ・リセット機能を作成可能。

感想など

オブジェクト管理・イベントの登録・タイマーの設定、これに加えてマインスイーパーのプログラムを組み合わせることにより完成しました。
割とやりたいことが実現する機能があったのでとても良き

コード

# coding: utf-8 
import tkinter
import random

class Field():
    def __init__(self,x,y,mine):
        
        self.x = x
        self.y = y
        self.start_mine = mine

        #ゲームが終わったかどうか
        self.game_finish = False

        self.nomal_color = "#808080"
        self.flag_color = "#87cefa"
        self.enter_color = "#d3d3d3"

        self.main = tkinter.Tk()
        self.main.title(u"マインスイーパー")
        #self.main.geometry( str(self.x * 40 + 20) + "x" +  str(self.y * 40 + 170) )
        
        self.main.grid_columnconfigure((0,1,2),weight=1)

        self.message_mine = tkinter.IntVar()
        tkinter.Label(master=self.main,font=("",20),bd=1,fg = "red",textvariable= self.message_mine).pack(pady = 0)
        
        self.message_time = tkinter.StringVar()
        tkinter.Label(master=self.main,font=("",20),textvariable= self.message_time).pack(pady = 0)

        tkinter.Button(master=self.main,text=u"リセット",command=self.Reset).pack(pady = 0)
        
        self.message_game = tkinter.StringVar()
        tkinter.Label(master=self.main,font=("",20),textvariable= self.message_game).pack(pady = 0)

        self.canvas = tkinter.Canvas(master=self.main,width = self.x * 40 + 20, height = self.y * 40 + 20)
        self.canvas.pack()

        self.Game_Init()

        self.canvas.after(1000,self.Loop)

    def Bopen(self,ev):
        if self.canvas.itemconfig("current")["fill"][4] == self.flag_color:
            return

        map_data = self.canvas.gettags("current")[0].split("-")
        self.active_field -= 1
        self.canvas.delete("current")

        if self.Mine_map[ int( map_data[1] ) ][ int( map_data[2] ) ] == 0:
            self.Copen( int( map_data[1] ) , int( map_data[2] ) )
        elif self.Mine_map[ int( map_data[1] ) ][ int( map_data[2] ) ] == 9:
            self.Gameover("bomb")

        if self.active_field - self.start_mine == 0:
            self.Gameover("clear")


    def Copen(self,y,x):
        if x > 0:
            Next_map = self.canvas.gettags( "field-" + str(y) + "-" + str(x-1) )

            if not( Next_map == () ) and not(self.Mine_map[y][x-1] == 9):
                self.active_field -= 1
                self.canvas.delete(Next_map)
                if self.Mine_map[y][x-1] == 0:
                    self.Copen(y,x-1)

        if x < self.x - 1:
            Next_map = self.canvas.gettags( "field-" + str(y) + "-" + str(x+1) )

            if not( Next_map == () ) and not(self.Mine_map[y][x+1] == 9):
                self.active_field -= 1
                self.canvas.delete(Next_map)
                if self.Mine_map[y][x+1] == 0:
                    self.Copen(y,x+1)
        
        if y > 0:
            Next_map = self.canvas.gettags( "field-" + str(y-1) + "-" + str(x) )

            if not( Next_map == () ) and not(self.Mine_map[y-1][x] == 9):
                self.active_field -= 1
                self.canvas.delete(Next_map)
                if self.Mine_map[y-1][x] == 0:
                    self.Copen(y-1,x)
        
        if y < self.y - 1:
            Next_map = self.canvas.gettags( "field-" + str(y+1) + "-" + str(x) )

            if not( Next_map == () ) and not(self.Mine_map[y+1][x] == 9):
                self.active_field -= 1
                self.canvas.delete(Next_map)
                if self.Mine_map[y+1][x] == 0:
                    self.Copen(y+1,x)

        

    def Enter(self,ev):
        if self.canvas.itemconfig("current")["fill"][4] == self.nomal_color:
            self.canvas.itemconfigure("current" , fill = self.enter_color)
        

    def Fadd(self,y,x):

        if y > 0:
            self.Mine_map[y-1][x] = self.Mine_map[y-1][x] + 1 if not(self.Mine_map[y-1][x] == 9) else self.Mine_map[y-1][x] 
            
        if y < self.y - 1:
            self.Mine_map[y+1][x] = self.Mine_map[y+1][x] + 1 if not(self.Mine_map[y+1][x] == 9) else self.Mine_map[y+1][x]

        if x > 0:
            self.Mine_map[y][x-1] = self.Mine_map[y][x-1] + 1 if not(self.Mine_map[y][x-1] == 9) else self.Mine_map[y][x-1]

        if x < self.x - 1:
            self.Mine_map[y][x+1] = self.Mine_map[y][x+1] + 1 if not(self.Mine_map[y][x+1] == 9) else self.Mine_map[y][x+1]

        if x > 0 and y > 0:
            self.Mine_map[y-1][x-1] = self.Mine_map[y-1][x-1] + 1 if not(self.Mine_map[y-1][x-1]== 9) else self.Mine_map[y-1][x-1]
        
        if x > 0 and y < self.y - 1:
            self.Mine_map[y+1][x-1] = self.Mine_map[y+1][x-1] + 1 if not(self.Mine_map[y+1][x-1] == 9) else self.Mine_map[y+1][x-1]

        if y > 0 and x < self.x - 1:
            self.Mine_map[y-1][x+1] = self.Mine_map[y-1][x+1] + 1 if not(self.Mine_map[y-1][x+1] == 9) else self.Mine_map[y-1][x+1]

        if x < self.x - 1 and y < self.y - 1:
            self.Mine_map[y+1][x+1] = self.Mine_map[y+1][x+1] + 1 if not(self.Mine_map[y+1][x+1] == 9) else self.Mine_map[y+1][x+1]

    def Field_init(self):

        self.Field_tag = []        

        for lop in range(self.y):
            Tbuf = []

            for lop2 in range(self.x):
                Mtag = "field-" + str(lop) + "-" + str(lop2)
                Tbuf.append( Mtag )
                self.canvas.create_rectangle(lop2 * 40 + 10, lop * 40 + 10, lop2 * 40 + 50, lop * 40 + 50,tag = Mtag , fill = self.nomal_color , outline = "white" , width = 2)
                self.canvas.tag_bind(Mtag , "<Button-1>" , self.Bopen)
                self.canvas.tag_bind(Mtag , "<Button-3>" , self.Flag)
                self.canvas.tag_bind(Mtag , "<Enter>" , self.Enter)
                self.canvas.tag_bind(Mtag , "<Leave>" , self.Leave)

            self.Field_tag.append(Tbuf)

    def Flag(self,ev):
        if self.canvas.itemconfig("current")["fill"][4] == self.enter_color:
            self.canvas.itemconfigure("current" , fill = self.flag_color)

            mine = self.message_mine.get()
            self.message_mine.set( mine - 1 )
        else:
            self.canvas.itemconfigure("current" , fill = self.enter_color)

            mine = self.message_mine.get()
            self.message_mine.set( mine + 1 )

    def Game_Init(self):
        
        self.canvas.create_rectangle(0,0,self.x * 40 + 20, self.y * 40 + 20, fill = "white")
        self.message_game.set(u"")
        self.message_mine.set(self.start_mine)

        self.active_field = self.x * self.y

        self.Mine_set()
        self.Map_load()
        self.Field_init()

        for lop in range(self.y):
            for lop2 in range(self.x):
                self.canvas.tag_raise("field-" + str(lop) + "-" + str(lop2))
                #チート
                #self.canvas.tag_raise("map-" + str(lop) + "-" + str(lop2))

        if self.game_finish == True:
            self.game_finish = False
            self.canvas.after(1000,self.Loop)
            
        self.message_time.set(u"time 0 : 00")
        self.time = 0

        

    def Gameover(self,res):
        
        if res == "clear":
            self.message_game.set(u"ゲームクリア")
        elif res == "bomb":
            self.message_game.set(u"ボムりました")
        
        for lop in range(self.y):
            for lop2 in range(self.x):
                Mtag = "field-" + str(lop) + "-" + str(lop2)

                if not(self.canvas.gettags( Mtag ) == () ):

                    if self.Mine_map[lop][lop2] == 9:

                        if self.canvas.itemconfig(Mtag)["fill"][4] == self.flag_color:
                            self.canvas.tag_raise("map-" + str(lop) + "-" + str(lop2))
                        else:
                            self.canvas.delete(Mtag)

                    self.canvas.tag_unbind(Mtag , "<Button-1>")
                    self.canvas.tag_unbind(Mtag , "<Button-3>")
                    self.canvas.tag_unbind(Mtag , "<Enter>")
                    self.canvas.tag_unbind(Mtag , "<Leave>")
        self.game_finish = True

    def Leave(self,ev):
        if self.canvas.itemconfig("current")["fill"][4] == self.enter_color:
            self.canvas.itemconfigure("current" , fill = self.nomal_color)

    def Loop(self):
        if self.game_finish:
            return

        self.time += 1
        message = u"time " + str( int( (self.time - (self.time % 60) ) / 60 ) ) + " : "
        if self.time % 60 < 10:
            message += u"0" + str(self.time % 60)
        else:
            message +=  str( self.time % 60)
        self.message_time.set( message )

        self.canvas.after(1000,self.Loop)

    def Map_load(self):
        num_color = ["","black","midnightblue","blue","darkturquoise","darkgreen","green","orange","blueviolet","red"]

        for lop in range(len(self.Mine_map)):

            for lop2 in range(len(self.Mine_map[0])):
                Mtag = "map-" + str(lop) + "-" + str(lop2)
                
                if self.Mine_map[lop][lop2] == 9:
                    self.canvas.create_text(lop2 * 40 + 30 , lop * 40 + 30,tag = Mtag ,font = ("",20),text = "B" , fill = num_color[self.Mine_map[lop][lop2]])
                elif self.Mine_map[lop][lop2] > 0:
                    self.canvas.create_text(lop2 * 40 + 30 , lop * 40 + 30,tag = Mtag ,font = ("",20),text = str(self.Mine_map[lop][lop2]) , fill = num_color[self.Mine_map[lop][lop2]])
                

    def Mine_set(self):
        self.Mine_map = []
        Mine_place = sorted( random.sample( range(self.x * self.y) , self.start_mine ) ) 
        cnt = 0

        for lop in range(self.y):
            buf = []
            for lop2 in range(self.x):
                if len(Mine_place) > 0 and Mine_place[0] == cnt :
                    del Mine_place[0]
                    buf.append(9)
                else:
                    buf.append(0)
                cnt += 1
            self.Mine_map.append(buf)
             
        for lop in range(self.y):
            for lop2 in range(self.x):
                if self.Mine_map[lop][lop2] == 9:
                    self.Fadd(lop,lop2)

    def Reset(self):
        self.canvas.delete("all")
        
        self.Game_Init()



def set_func():

    if size_x.get().isdecimal() == False or size_y.get().isdecimal() == False or mine_num.get().isdecimal() == False:
        message_err.set(u"文字や負数を入れるな")
        return
    x = int(size_x.get())
    y = int(size_y.get())
    mine = int(mine_num.get())

    if x < 4 or y < 4 or mine < 1:
        message_err.set(u"少ない")

    #マス数*0.8 まで許容
    elif x > 20 or y > 20 or x * y  < int( mine / 0.8 ) :
        message_err.set(u"多い")

    else:
        config.destroy()
        
        game = Field(x,y,mine)
        game.main.mainloop()

config = tkinter.Tk()
config.title("config")

tkinter.Label(master=config,text=u"サイズ").grid(row = 0,column = 0)

size_x = tkinter.Entry(master=config)
size_x.insert(tkinter.END,u"12")
size_x.grid(row = 0,column = 1)

tkinter.Label(master=config,text="×").grid(row = 0,column = 2)

size_y = tkinter.Entry(master=config)
size_y.insert(tkinter.END,u"8")
size_y.grid(row = 0,column = 3)

tkinter.Label(master=config,text=u"地雷数").grid(row = 1,column = 0)

mine_num = tkinter.Entry(master=config)
mine_num.insert(tkinter.END,u"10")
mine_num.grid(row = 1,column = 1)

tkinter.Button(master=config,text=u"スタート",command=set_func).grid(row=2,columnspan=3)

message_err = tkinter.StringVar()
tkinter.Label(master=config,textvariable=message_err).grid(row = 3,columnspan=3)

config.mainloop()

このコードは最新でない可能性もあるので、github

github.com

Python3とTkinter&canvasでマインスイーパーを作る

おはようございます。

長い事ブログ書いていないのは良くないということで、少し前にTkinterの練習用に作っていたプログラムをGitHubにあげてブログを書きました。
Python 3.7.0のwindows10です。

Tkinterとは?

超簡単にGUIが作れるお手軽ツール、Python2ではTkinter・ Python3ではtkinter

なぜTkinterを使っているのか?

色々あってPythonGUI使いたかったので、機能を一通り使ってみるということで作って見ました。

canvasって?

Tkinterウィジェット(画面構成をする部品)の一つであり、かなり自由に配置とかができる。

どこら辺が簡単なのか

詳しい使い方や関数はネットで調べましょう。
ただ本当に簡単で  

import tkinter

a = tkinter.Tk()
a.mainloop()

だけで

f:id:uragou:20190705153039p:plain
画面生成

画面ができる。

作ったマインスイーパ

GitHubをご覧ください。テストが終わったらここに貼ります。
Pythonバージョンが同じでも、環境によって見た目とか色々変わるため
どの環境でも動くと保証できないのが欠点(カラーコードとか大丈夫かな?)

github.com

起動するとこうなって…

f:id:uragou:20190705153922p:plain
ゲーム設定

こんな風に遊べる。

f:id:uragou:20190705153638p:plain
ゲーム画面

どんなことをしたか

1.画面の生成と削除・設定

上にも書いた通りtkinter.Tk()とmainloop()さえあれば画面はできる。
変数を複数用意すればその分画面ができる。

ちなみに一つをメイン画面にして、もう一つをサブ画面にするには

a = tkinter.Tk()
b = tkinter.Toplevel(master=a)

a.mainloop()

とすると、親画面が死ぬと子画面も死ぬようになる。

他には( a = tkinter.Tk() した後に)

ウィンドウのタイトル設定

a.title("マインスイーパー")

ウィンドウの初期サイズ

a.geometry("1200x720")
これは無くても画面内の部品によって大きさを調整してくれる。

ウィンドウの削除

a.destroy()

ちなみに画面にスクロールバーはつかない。
マインスイーパーには使わなかったのでここでは省略するが、本命の方ではかなり苦労した。
canvasモジュール内の部品で実現する。

画面を構成する部品を配置する。

import tkinter

def func():
    a_str.set( str( a_num.get() ) )

a = tkinter.Tk()

a_str = tkinter.StringVar()
tkinter.Label(master=a,textvariable= a_str).pack()

a_str.set("aaaa")
tkinter.Label(master=a,text= "bbbb").pack()

tkinter.Button(master=a,text="cccc",command=func).pack()

a_num = tkinter.Entry(master=a)
a_num.insert(tkinter.END,"dddd")
a_num.pack()

a_canvas = tkinter.Canvas(master=a,width = 100, height = 100)
a_canvas.pack()
a_canvas.create_rectangle(0,0,100,100,fill="red")

a.mainloop()

f:id:uragou:20190705162148p:plain
test2.py

画面だけがあってもしょうがない。
今回使った部品は

テキスト tkinter.Label

テキストはtext で内容を直接書くか、tkinter.StringVar()を入れた変数を,textvariable で指定することで配置できる。
setした内容は後からもう一度setすれば別の内容に書き換えられる。ちなみにStringVar以外にもIntVarとか色々ある。

ボタン tkinter.Button

text ボタンに文字などを書くことができ、command でボタンを押したときに実行される関数オブジェクトとかを指定できる。

入力欄 tkinter.Entry

Entryを作った後にinsertで初期入力値を指定、(第一引数は挿入される位置らしい。)
入力値は .get() でとることができる。

キャンバス tkinter.Canvas

高さと幅を指定すれば作成できる。
中には図形を書いたりスクロールバーを作れたりと色々便利

共通する事

複数画面を作ったりするときなどは、masterとしてどの画面の部品なのかをちゃんと指定してあげる事
変更とかが特にない部品については、変数を作る必要もない

pack()

各部品は作成した後に.pack()をすることで画面に配置される。
pack()はpackされた順に、下に新しく部品を追加していく。
色々とオプションをつければもう少し複雑な配置ができるが、画面に配置する機能は他にも

  • 表のように列と行を指定して部品を配置する.grid()
  • xとy座標を指定することで配置する.place()

がある。生成するだけなら引数とか特にいらない.pack()が便利

tkinter.Canvas

これが一番便利な部分。

  • ボタンに限らず様々な部品にイベントをつけることで様々な動作を実現
  • 大量のオブジェクトもタグを使って楽々管理
  • 一定時間ごとの処理をしながら入力イベントを受け付けられる。

マインスイーパーのテストが終わったら、プログラム貼り付けて記事を書く。

工学院大学システム数理学科のリアル人柱

この記事はKogakuin Advent Calendar 2018,16日目の記事です。

adventar.org

どうも、浦郷です。

これから工学院大学に入る人、入るか迷っている人、特にシステム数理学科に興味を持っている人、情報学部総合1年生たち向けの記事です。
該当しない奴は読まなくていいので宣伝でもしてください

私は工学院大学情報学部システム数理学科に所属する3年生です。
新しくできたシステム数理学科に入って生き残った男として、
後世のために色々と書いていこうと思います。


システム数理学科とは

熊のように力強い人間に成長するための学科

概要は検索してください。
あるいはこれ
uragou.hatenablog.com 他の学科と比べると、ともかく新しい分野をやりたいから学科を作ったらしいです。


工学院大学とは

熊のように力強い人間を育成するための大学

調べて♡
新宿の立地のいい場所にある大学です。上層階からの景色は割といいですよー
ですが、情報学部の学生は最初の二年間を八王子キャンパスで過ごすことになります。
最初の1年半は学科で共通のカリキュラムで情報の基礎を学びます。
そして学科で別れ、それぞれの学科独自の講義が増えていく感じです。
もちろん他学科の講義とか受けても問題ないですよ。出る単位は限られるけど。


システム数理学科の良い所

宣伝という訳でないですが


システム数理学科に関係ある所

新しい所

新しい学科なんで、当然新しい分野を学んでいますよ。
機械学習とかそこら辺の講義もありますし、社会・企業系の講義もあるし、数学系の講義もある。
私はコンピュータ科学科には所属していないので比較はできませんが、わりかし差別化できているようです。
ただ、どうやらより人に近い部分をやる学科みたいです。

課題の量が常識的

理系の大学というと、課題・レポート・発表まみれで気が狂った学生がエナドリをキメながら呻いているイメージですが、システム数理学科では課題の量が常識的な量です。
決して少ないわけでなく毎日課題に追われることになりますが、逆に言えば毎日ちゃんとやればちゃんと終わるレベルの量です。
ていうか他学科ではえぐい量の課題がでるとか聞きますが、それ学科・研究室選びをミスったか、そいつの処理能力が低いだけです。
例外として、たまに何時間かけても分からない課題が某教授によって出てきますが、やっぱり何とかなります。

ちなみに私の二年後期の課題量はこっから確認して♡
uragou.hatenablog.com 3年は課題量は減りますが、課題の難易度は上がります。

座学でも、実験でもない講義が多い

個人的にはこれが一番良い所ですね。座学ってどうしても眠くなるもん。
ていうか105分も座学とかやる方もきついだろうし
手を動かしながらやる講義は、寝ることないですし受ける身としてはとても良いものです。

例えば
Webプログラミング
クラウドコンピューティング
多変量解析演習
パターン認識演習

などなど、ここまででなくても授業の最後に少し演習したりするのもあります。
あと、PBLと言ってもっと実践的な事をやる講義もあります。まぁ私はセミナー以外のPBLはとってないですが
たしか卒業には関係なかったよね?

システム数理学科に関係ない所

立地

工学院大学の利点と言ったら、これは外せないでしょう!
新宿駅から徒歩数分、しかも地下を通れば外に出ないで済む。最高ですよ。
美味しいもの(高いけど)も多いし、良いもの(高いけど)もたくさん売っている。
金さえあれば一生住めますね。
あとは

交通手段の多さ
工学院大学に通っているというと立地で覚えている人が多い
外からの景色

などなど、要は立地が全てです。


システム数理学科に行くなら必要なもの

やる気と根性だけで何とかなりますが学科に行く前 (入学後から2年前期まで) にやっておくと良いもの

プログラミング

プログラミングばっかやる学科ではないですが、それでもプログラミングはできておいた方がいい。
中でもpythonという言語は必修でも実験でも使ったため、ちょいちょい覚えていると課題が楽になるかも?

数学(特に線形代数学)

システム数理学科を極めるには、微積分はもちろんのこと、特に線形代数学の知識はかなり必要になっていきます。
情報学部の線形代数学の授業は2年の2Qまでに、「線形代数学1」「線形代数学2」「線形代数学3」「線形代数学4」があり2までが必修ですが、線形代数学2まででは理解できない内容が出てくるので入ってから苦しむくらいなら4まで履修しとけ。

健康

この世の大多数のオタクは多分不健康です。

しっかり睡眠
ちゃんと食事
たまには運動

大体どれかはできなくなるので、なるべく健康な状態で入ってきてください。

最後に


どうでしょうか?
何かの参考になれば幸いです。
ちなみにシステム数理学科では、熊の話が時々でてきます。
気になった方はお気軽に私以外の誰かに聞いてみてください。
最後に、何の関係も無い神戸の写真でもどうぞ。

f:id:uragou:20181215233715j:plain
神戸の写真
はい、ありがとうございました。

ハフマン符号によるデータをうまい具合にいい感じにするあれ

この記事はKogakuin Advent Calendar 2018,9日目の記事です。

adventar.org

皆さんこんばんは 
一部マニアからは神と呼ばれる男、浦郷です
私が神と呼ばれているのは、浦郷という名前をローマ字で「URAGOU」とした時
URAGOU → GOU → GOD → 神
なだけです。それ以外に理由はありません

さて、アドベントカレンダーなので一応技術的な事を書かなきゃいけないそうです。それっぽいことを書きます。
今回選んだのはハフマン符号 うん、それっぽい

注意

私は別にその分野のプロとかではないので、ちゃんと知りたい方は自分で調べてください。

ハフマン符号とは?

詳しい歴史やら何やらは自分で調べてください。
要はデータを圧縮する際に、ファイルなどのデータ列の頻出度によって分類する方法。
たくさん出てくるデータ列は小さく、あまり出てこないデータ列は大きくなります。

  • 特徴
    • 可逆圧縮(データを完全に元の状態に戻せる。ちなみに戻せない圧縮は不可逆圧縮という)
    • ビット列が偏っているデータをいい感じに圧縮できる

では図にして見ましょう。

f:id:uragou:20181207224111p:plain
データ列

バイト列で考えるのは人間には厳しそうなので、ここでは1バイト=3ビットという体で進めていきます。
この8パターンで構成されているデータがあるとします。意図して作らない限り、構成しているデータの中では特定のバイト列が多くなったり少なくなったりします。
あるファイルAではバイト列101がすごいたくさん出てきて、バイト列001バイト列100があまり出てこないということにしましょう。その他は適当に
元の状態なら、たくさん出てきても少なくても同じ3ビットで構成されていますが、ハフマン符号ではバイト列ごとに新しいパターンを付与します。

f:id:uragou:20181207234825p:plain
こんな感じ

例えば、 101 101 000 101 110 101 011 000 … なら
圧縮後は 0 0 100 0 1101 0 101 100 ... という風にでき、この部分だけでも24ビットを17ビットにまで短くできていますね。

ここでのポイントは変換後のパターンを他のパターンと被らないようにすること
パターンとして1010と10がある場合、 1010 10 10 1010 .... と続いてしまうと、復元時に10なのか1010なのか分からなくなりますからね。

ハフマン木

変換後のパターンを他のパターンと被らないようにするために、一意に分かるようなデータ列に変換するアルゴリズムが必要です。それがハフマン木

  • 手順としては
    1. データ列を出現率順に並べる
    2. 出現頻度が一番少ないペアを木にして、木にしたデータの出現率の合計値を出します。
    3. 木の親も含め、また出現頻度が一番少ないペアを木にして木にしたデータの出現率の合計値を出します。
    4. 木が一つになるまで続けます。

f:id:uragou:20181208005544p:plain
1

f:id:uragou:20181208005919p:plain
2

f:id:uragou:20181208010135p:plain
3

f:id:uragou:20181208010826p:plain
4

就活中なのに俺は何をやってんだろう。

こうして出来た木を辿っていくと、うまい具合のデータ列ができます。
例えば110なら上からこんな風にたどっていって、1101が得られますね

f:id:uragou:20181208011108p:plain
データ列110のパターンの取得

プログラムにする

頑張ってpython3で書きました。(Python 3.7.0)
いつも通り自己流で、アルゴリズムだけ確認してなんとなくで書いているため他とは大きく違うと思います。
私以外には読めないと思うので、意味ないけどコメント文ごと貼りました。

流れとしては

  • ファイルを取り込んでバイト列を取り出す
  • 出現率でソートして、木構造にする
  • 得られたパターンデータを.himnに格納(適当な拡張子で意味はない)
  • 圧縮したデータを.hhmnに格納(適当な拡張子で意味はない)
import os.path

Fname = "test.bmp"
ifp = open(Fname,"rb")

siz = os.path.getsize(Fname)
Btable = {}
Htable = []
Wtable = None
Hnum = 0

#木構造したい
class HTree:
    def __init__(self,lval,lname,rval,rname,oya = None):

        self.oya = oya
        self.lval  = lval
        self.lname = lname
        self.rval  = rval
        self.rname = rname
        if not (lval == None and rval == None ):
            self.hi = lval + rval
        else:
            self.hi = None

    def __str__(self): 
        return str( self.oya )

    def Lmarge(self,lval,lname,rnode):

        root = HTree(lval,lname,rnode.hi,rnode)
        rnode.oya = root 
        return root

    def Rmarge(self,lnode,rval,rname):
        root = HTree(lnode.hi,lnode,rval,rname)
        lnode.oya = root 
        return root

    def marge(self,lnode,rnode):
        root = HTree(lnode.hi,lnode,rnode.hi,rnode)
        rnode.oya = root 
        lnode.oya = root 
        return root
    
    def seek(self,tar):
        creData = ""
        
        if self.lname == tar:
            print(self.lname)
            return str(0)
        elif self.rname == tar:
            return str(1)
        else:
            creData = self.lname.subseek(tar,"0")
            if creData == str(-1) :
                creData = self.rname.subseek(tar,"1")
        return creData

    def subseek(self,tar,data):
        if self.lname == tar:
            return data + str(0)
        elif self.rname == tar:
            return data + str(1)
        
        buf = ""
        if not (type(self.lname) is str):
            buf = self.lname.subseek(tar,data + str(0))
            if not (buf == "-1"):
                return  buf
        if not (type(self.rname) is str):
            buf = self.rname.subseek(tar,data + str(1))
            if not (buf == "-1"):
                return  buf
        
        return "-1"
    
    def __str__(self):
        return "edge"



#2つデータとを取ってそいつを木にしたい。
#途中で木が複数できたり統合したりする関係でおかしなことになっている
#奇数のときには一つ目のデータを取っている部分のため、ここでデータが一つしかないかを判断する
#2つ目の部分ではデータが複数個あるのは確定なので木にしている

#終了条件 元のテーブルが空になって木を入れた配列の長さが1のとき
#つまりは全てのデータが一つの木に入った時
#if文のそれぞれは
"""
    1. 木がある無い場合
    1.1 元データが一種しかない場合 : そのまま木にデータにぶち込む (多分整合性エラー)
    1.2 元データが複数ある場合    
    2. 元データが無くて、木がある場合 : 木の中で色々する
    3. 両方ある場合
"""
def MakeTree(TBL):
    Itree = []
    cnt = 0
    
    lis = TBL[:]

    if not TBL:
        print("not data")
        return 
    while True:
        if not Itree:
            if len(TBL) == 1:
                print("data error")
                exit()
            else:
                Tbuf = HTree(TBL[0][0] , TBL[0][1] , TBL[1][0] , TBL[1][1])
                TBL.remove(TBL[1])
                TBL.remove(TBL[0])
                Itree.append( [Tbuf.hi , Tbuf] )
        else:
            if not TBL:
                if len(Itree) == 1:
                    break
                else:
                    Tbuf = HTree(None,None,None,None)
                    Tbuf = Tbuf.marge(Itree[0][1],Itree[1][1])
                    Itree.remove(Itree[1])
                    Itree.remove(Itree[0])
                    Itree.append( [Tbuf.hi , Tbuf] )
            else:
                if len(Itree) == 1 and len(Htable) == 1 :
                    buf1 = TBL[0]
                    buf2 = Itree[0]
                    Itree.remove(Itree[0])
                    TBL.remove(TBL[0])
                    Tbuf = HTree(None,None,None,None)
                    Tbuf = Tbuf.Rmarge(buf2[1],buf1[0],buf1[1])
                    Itree.append( [Tbuf.hi , Tbuf] )
                else :
                    if Itree[0][0] <= TBL[0][0]:
                        
                        buf1 = Itree[0]
                        Itree.remove(Itree[0])

                        if not Itree:
                            buf2 = TBL[0]
                            TBL.remove(TBL[0])
                            Tbuf = HTree(None,None,None,None)
                            Tbuf = Tbuf.Rmarge(buf1[1],buf2[0],buf2[1])
                            Itree.append( [Tbuf.hi , Tbuf] )

                        elif Itree[0][0] <= TBL[0][0]:
                            
                            buf2 = Itree[0]
                            Itree.remove(Itree[0])

                            Tbuf = HTree(None,None,None,None)
                            Tbuf = Tbuf.marge(buf1[1],buf2[1])
                            Itree.append( [Tbuf.hi , Tbuf] )
                        else:
                            buf2 = TBL[0]
                            TBL.remove(TBL[0])

                            Tbuf = HTree(None,None,None,None)
                            Tbuf = Tbuf.Lmarge(buf2[0],buf2[1],buf1[1])
                            Itree.append( [Tbuf.hi , Tbuf] )

                    else:
                        buf1 = TBL[0]
                        TBL.remove(TBL[0])

                        if not TBL:
                            buf2 = Itree[0]
                            Itree.remove(Itree[0])
                            Tbuf = HTree(None,None,None,None)
                            Tbuf = Tbuf.Lmarge(buf1[0],buf1[1],buf2[1])
                            Itree.append( [Tbuf.hi , Tbuf] )
                        elif Itree[0][0] <= TBL[0][0]:
                            buf2 = Itree[0]
                            Itree.remove(Itree[0])

                            Tbuf = HTree(None,None,None,None)
                            Tbuf = Tbuf.Rmarge(buf2[1],buf1[0],buf1[1])
                            Itree.append( [Tbuf.hi , Tbuf] )
                        else:
                            buf2 = TBL[0]
                            TBL.remove(TBL[0])

                            Tbuf = HTree(buf1[0] , buf1[1] , buf2[0] , buf2[1])
                            Itree.append( [Tbuf.hi , Tbuf] )
        cnt += 1

    HAtable = {}
    for lop in range(len(lis)):
        HAtable[ lis[lop][1] ] = Itree[0][1].seek(lis[lop][1]) 
    return HAtable


#データ取り出し部分
#1バイトごと取って数を数えている
print("open " + Fname)


for lop in range(siz):
    data = ifp.read(1)
    Bget = "b" + str(ord(data))
    if Bget in Btable:
        Btable[Bget] = Btable[Bget] + 1
    else:
        Btable[Bget] = 1



BStable = sorted(Btable.items() , key = lambda x:x[1])


BStable = sorted(Btable.items() , key = lambda x:x[1])


#単純に数えていたバイト列を出現率に変えている
for lop in range( len( BStable ) ):
    Htable.append( [ Btable[ BStable[lop][0] ] / siz , BStable[lop][0] ] )

Htable = MakeTree(Htable)
ofp = open(Fname + ".hhmn" , "w+b")
tfp = open(Fname + ".himn" , "w+")
ifp.seek(0)
brry = ""

print("create file 1/2")
for lop in range(siz):
    data = ifp.read(1)
    Bget = "b" + str(ord(data))
    brry += Htable[ Bget ]
    while len(brry) >= 8:
        ofp.write( int(brry[0:8] , 2).to_bytes(1, byteorder='big' ) )
        brry = brry[8:]


if not(brry == ""):
    while len(brry) < 8:
        brry += "0"
    ofp.write( int(brry[0:8] , 2).to_bytes(1, byteorder='big' ) )
    brry = brry[8:]

print("create file 2/2")
for num , mo in Htable.items():
    tfp.write( num + "," + mo + "\n" )

ifp.close()
tfp.close()
ofp.close()

続いて、復元します。

import os.path

Fname = "test.bmp"
hhfp = open(Fname + ".hhmn","rb")
hifp = open(Fname + ".himn","r")
txtfp = open(Fname ,"w+b")

siz = os.path.getsize(Fname + ".hhmn") 

Htable = {}

def fufile(strbin):
    nowbin = ""
    for lop in range( len(strbin) ):
        nowbin = nowbin + strbin[lop]
        if nowbin in Htable:        
            wribin = Htable[ nowbin ]
            txtfp.write( int(wribin , 2).to_bytes(1, byteorder='big') )
            nowbin = ""
    
    return nowbin

data = hifp.readline()
while data:
    cha , num = data.split(",")
    num = num.strip("\n")

    cha = str( bin( int(cha[1:]) ) )
    cha = cha[2:]
    while len(cha) < 8 :
        cha = "0" + cha 
    Htable[ num ] = cha
    data = hifp.readline()
cnt = int(siz/10)

strbyte = ""

for lop in range( siz ):
    if lop > cnt :
        if siz > 10:
            print("read " + str( int(cnt / int(siz/10)) ) +"/ 10")
            cnt += int(siz/10)

    bite = hhfp.read(1)
    lopbyte = bin( int.from_bytes(bite , "big") ) 
    lopbyte = lopbyte[2:] 
    while len(lopbyte) < 8 :
        lopbyte = "0" + lopbyte

    strbyte = strbyte + lopbyte
    strbyte = fufile(strbyte)

hhfp.close()
hifp.close()
txtfp.close()

では、実際にやって見ましょう。
単純なデータtest.bmpを用意します。

f:id:uragou:20181208013022j:plain
test.bmp

実行すると、test.bmp.himnとtest.bmp.hhmnが作成されます。ちゃんと圧縮されていますね

f:id:uragou:20181208023802p:plain

削除して・・・

f:id:uragou:20181208024200p:plain

実行すると・・・

f:id:uragou:20181208013937p:plain

できました‼(画像じゃよく分からないけど、test.bmpの更新時刻が変わっているでしょ?)

f:id:uragou:20181208024350p:plain

今回のケースではtest.bmpのデータ列が大幅に偏っているためかなり圧縮されましたが、普通のファイルならほんの少しです。
ていうかデータ列が均等に出てくる(大体のデータ)では、そもそも圧縮してないってこともあります。
後、でかいファイルは処理が遅いです
少なくとも私の環境ならうまくいきました。多分大丈夫だと思います。
プログラムを実行して復元してみたファイルを実行したり、バイナリエディタで元データと比較してみてください。

あとがき

このプログラムは結構前に作っていて、アドベントカレンダーのネタが思いつかなかった用に取っといてましたが
いざ実行してみたら動かない・圧縮しない・微妙に元データと違う・圧縮後のデータ量の方が大きいなど、散々な目にあいました。
まぁ原因はケアレスミスだったり、頭で考えていたコードをうまく反映できてなかったり、そもそもデバック用に途中で終了するようにしていたのを忘れていたりなどなど・・・こんな時間(夜中の3時)にやるもんじゃないですね。

さぁ、これでそれっぽいことは書いたので来週はポエム全開、ポエポエブログを書きます。
ちなみにネスぺ受けましたが、合否が分かる前に記事投稿しちゃうので、せっかくのネスぺ合否芸ができないです。まぁ結果はTwitterにでも書きます。

追記

世の中の大体の画像は圧縮されている状態ですが、bmp形式(つまり非圧縮画像)ならうまくできますよ

Fortigate 1000CでPPPOE接続をする

我がプロジェクト団体にFortigate1000C(中古)がやってきた。

 こいつを我がネットワークの柱とするために、こいつの設定をしなければいけない。 まずはこいつでPPPOEでの接続を・・・




f:id:uragou:20180620222059p:plain
初期状態のインタフェース情報




項目がないじゃないか!!!

 色々なサイトで調べても、何か簡単にGUIから設定できるようだけど・・・?

どうやら販売終了したFortigate1000Cでは簡単に設定できないみたい、現行製品ならすぐできるようだけど

アドレッシングモードにPPPOEの項目がない。

この機器に関する本とかはまだ無いし、素人学生にはさっぱり分からん。

うーんうーん・・・configをくまなく探してみるかぁ











f:id:uragou:20180620223206p:plain
初期状態のconfig

あった!!!







set mode pppoe というコマンド

set mode pppoeでpppoe接続ができるようになる模様

GUIにはないが、CLI(コマンド)で入力できるっぽい

WAN1のインタフェースに

set mode pppoe set username <ISPの情報>
set password <パスワード>

ISPから送られてきたIDとパスワードを設定して・・・

f:id:uragou:20180620222104p:plain
PPPOEができた状態

できたぁ!!

分かりにくいけど、アドレッシングモードの「マニュアル」も「DHCP」も緑色で表示されてなく、 アドレス部分(黒塗りだけど)にアドレスが返ってきたんですよ!!

まぁ、他の情報がまた初期状態なんですけどね。 これから次第

PPPOE自体も最低限接続のための情報を書いただけでまだまだ項目がありそうだし

無意識に罪を背負い罰を受ける(チリソース)

 工学院大学八王子の近くにはインドカレー屋がある。そこのインドカレー屋はとても美味しく、雰囲気もいい知る人ぞ知る工学院大学近辺の名店である。先日、浦郷はプロジェクトの代表とともにそのカレー屋に行ったんだが、事件はその店で起こった。

 

 


 浦郷はこの店に初めて入った時、前菜で出されたサラダにかかっているソースが気になって店員に

「サラダのソースは何ですか?」

と聞いたところ、

「サラダのソースはNoです」

と聞こえたらしく、その場でサラダを取り上げられ、ソースのかかっていないサラダを出された。


 それがKogCoder内で話題になり、それ以来注文時には必ず、「サラダのソースはNoで」と言うようにしている。


 その日も「ソースはNoで」と言ったが何故かサラダにソースがかかった状態できた。俺は代表と顔を見合わせ首をかしげたが、ソース付きの方がうまいなーと思いつつ、気にしないで食べた。


 サラダとサイドメニューで頼んだチキンがきた後、メインのカレーとナンが到着した。その時、店員が「カレーソースです」と言い頼んだ覚えのない小さなカップに入った何かもテーブルに置かれた。


 その何かはカレーの器を小さくしたような容器で、中にはカレーソースによく似た焦げ茶色のペーストが入っていた。これまで何度もこの店に来ていたが初めて見るその謎の何かに俺と代表は戸惑った。

 

 新しい付け合せかな?、サービスかな?何かは分からなかったが 見た目は美味しそうなカレーソースだったので、俺はナンにその謎のソースをたっぷりつけて口に入れた。

 

 

 

 

 

 

 

 

 

 

 

 

 

入れた瞬間俺を待っていたのは舌を駆け巡る激しい痛みだった。

 

 

 


 代表に「どう?どんな味?」と言われても口が開かない、本能が命ずるままにテーブルにあった水を飲んだ。口の中で水と何かが激しく主張あう。口の中に僅かな余裕ができている間に頭をフル回転させて自分の身に何が起こったのかを必死に考えた。

痛み、熱、そして苦しみ、やっと理解した。何か辛いものを食べてしまった。


 俺はなんとかして代表にソースがとても辛いことを伝えた。普通じゃない表情とジェスチャー。彼は少し考えた後、今までに見たこともないスピードでメニューを手に取りこう言った。「これチリソースじゃね?」


チリソースとはカレーの辛さが足りないときに使うものである。この店ではチリソースは店員に頼んだときにのみ出てくるサービスであるが、なぜ店員は持ってきたのだろう。

 

 

 

 

 

 

 

 

 

その時に注文時の自分で言った言葉を思い出した。「ソースはNoで」

 

 

 

 

 


 初来店時に聞き間違えられたように、どうやら店員は俺がチリソースを頼んでいるとと思ったようだ。


 そっかー、チリソースってこんなに辛いのかーとか思っていたら気がつくとコップの水が無くなりかけていた。俺はコップの水を代表にアピールしながら水を飲まずに口に含んで時間を稼いだ。

 口の中の水は最初はほのかな甘みを感じるほどに美味しいが、しばらくすると口の中にある辛味成分が解けていき、優しい水は凶悪なチリソース水に変わってしまう。舌はすでに限界を迎えいて、思考がとぎれとぎれになっていく。
 店員が新しい水を注ぐ、俺は少量の水を口に含み大きく息を吸う。舌に纏わりつく痛みが引いていく。
 ようやく落ち着いて話ができるようになった。代表と2、3会話をした後、カレーを一口食べる。

 このカレー屋のカレーはそこまで辛くなく、今回頼んだのは豆カレーなので水なしでも余裕で食べられるはずだったが、食べても痛みしか感じなかった。

 

もはや熱を持つもの全てを辛いと感じてしまう。

 

結局カレーをちゃんと食べられるようになるまでかなりの時間がかかった。 

 

ここから学んだ教訓として、

 

  • 分からなかったら店員に聞く
  • わからないものを口にいれてはいけない
 
チリソース大量摂取は罰ゲームのときだけで十分である。