diff --git a/bg-evening.jpg b/bg-evening.jpg new file mode 100644 index 0000000..d750d53 Binary files /dev/null and b/bg-evening.jpg differ diff --git a/bg-grass.png b/bg-grass.png new file mode 100644 index 0000000..48a4b65 Binary files /dev/null and b/bg-grass.png differ diff --git a/bg-night.jpg b/bg-night.jpg new file mode 100644 index 0000000..6d43ad5 Binary files /dev/null and b/bg-night.jpg differ diff --git a/bg.jpg b/bg.jpg new file mode 100644 index 0000000..b4b3b73 Binary files /dev/null and b/bg.jpg differ diff --git a/common_const.py b/common_const.py index 899af72..87505c1 100644 --- a/common_const.py +++ b/common_const.py @@ -2,3 +2,7 @@ class CWindow: WIDTH: int = 1024 HEIGHT: int = 768 + +class CMath: + PI: float = 3.14159265358979323846 + diff --git a/common_module.py b/common_module.py index aebf3c6..276de44 100644 --- a/common_module.py +++ b/common_module.py @@ -1,17 +1,28 @@ import unicodedata +from common_const import * + class CommonModule: @staticmethod - def is_wide (c: str) -> bool: + def is_wide ( + c: str) \ + -> bool: return unicodedata.east_asian_width (c) in ['F', 'W', 'A'] @classmethod - def len_by_full (cls, string: str) -> float: + def len_by_full ( + cls, + string: str) \ + -> float: return sum (1 if cls.is_wide (c) else .5 for c in string) @classmethod - def index_by_f2c (cls, string: str, index: float) -> int: + def index_by_f2c ( + cls, + string: str, + index: float) \ + -> int: i: int = 0 work: str = '' for c in string: @@ -24,8 +35,19 @@ class CommonModule: return i @classmethod - def mid_by_full (cls, string: str, start: float, length: float) -> str: + def mid_by_full ( + cls, + string: str, + start: float, + length: float) \ + -> str: trimmed_left: str = string[cls.index_by_f2c (string, start):] return trimmed_left[:cls.index_by_f2c (trimmed_left, length)] + @staticmethod + def rad_to_deg ( + rad: float) \ + -> float: + return rad * 180 / CMath.PI + diff --git a/main.py b/main.py index 9e628a6..925858d 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,14 @@ +# vim: nosmartindent autoindent + import json import random import subprocess import sys import time -from datetime import datetime +from datetime import datetime, timedelta import emoji +import ephem import pygame import pytchat from playsound import playsound @@ -21,7 +24,11 @@ from youtube import * class Main: @classmethod - def main (cls, argv: list, argc: int) -> None: + def main ( + cls, + argv: list, + argc: int) \ + -> None: mode = Mode.NIZIKA match (argc > 1) and argv[1]: case '-g': @@ -41,12 +48,50 @@ class Main: screen: pygame.Surface = pygame.display.set_mode ( (CWindow.WIDTH, CWindow.HEIGHT)) + # 大月ヨヨコの観測値 + observer = ephem.Observer () + observer.lat, observer.lon = '35', '139' + + # き太く陽オブジェクト + sun = ephem.Sun () + + # 大月ヨヨコ・オブジェクト + moon = ephem.Moon () + # 吹き出し balloon = pygame.transform.scale (pygame.image.load ('talking.png'), (CWindow.WIDTH, 384)) if goatoh_mode: balloon = pygame.transform.flip (balloon, False, True) + # 背景(昼) + bg_day: pygame.Surface = pygame.transform.scale ( + pygame.image.load ('bg.jpg'), + (CWindow.WIDTH, CWindow.HEIGHT)) + + # 背景(夕方) + bg_evening: pygame.Surface = pygame.transform.scale ( + pygame.image.load ('bg-evening.jpg'), + (CWindow.WIDTH, CWindow.HEIGHT)) + + # 背景(夜) + bg_night: pygame.Surface = pygame.transform.scale ( + pygame.image.load ('bg-night.jpg'), + (CWindow.WIDTH, CWindow.HEIGHT)) + + # 背景の草 + bg_grass: pygame.Surface = pygame.transform.scale ( + pygame.image.load ('bg-grass.png'), + (CWindow.WIDTH, CWindow.HEIGHT)) + + # き太く陽 + kita: pygame.Surface = pygame.transform.scale ( + pygame.image.load ('sun.png'), (200, 200)) + + # 大月ヨヨコ + jojoko: pygame.Surface = pygame.transform.scale ( + pygame.image.load ('moon.png'), (200, 200)) + # 音声再生器の初期化 pygame.mixer.init (frequency = 44100) @@ -78,7 +123,48 @@ class Main: histories: list = [] while (True): - screen.fill ((0, 255, 0)) + # 観測地の日づけ更新 + observer.date: datetime = datetime.now ().date () + + # 日の出開始 + sunrise_start: datetime = ( + (ephem.localtime (observer.previous_rising (sun)) + - timedelta (minutes = 30))) + + # 日の出終了 + sunrise_end: datetime = sunrise_start + timedelta (hours = 1) + + # 日の入開始 + sunset_start: datetime = ( + (ephem.localtime (observer.next_setting (sun)) + - timedelta (minutes = 30))) + + # 日の入終了 + sunset_end: datetime = sunset_start + timedelta (hours = 1) + + # 月の出開始 + 'todo' + + # 月の出終了 + 'todo' + + # 月の入開始 + 'todo' + + # 月の入終了 + 'todo' + + # 日の角度 + observer_with_time: ephem.Observer = observer + observer_with_time.date = datetime.now () - timedelta (hours = 9) + sun.compute (observer_with_time) + sun_alt: float = CommonModule.rad_to_deg (sun.alt) + + # 背景描画 + cls.draw_bg (screen, bg_day, bg_evening, bg_night, bg_grass, + kita, jojoko, + sunrise_start, sunrise_end, sunset_start, sunset_end, + sun_alt) # 左上に時刻表示 for i in range (4): @@ -118,11 +204,13 @@ class Main: # 履歴に追加 histories = (histories + [{'role': 'user', 'content': message}, - {'role': 'assistant', 'content': answer}])[(-12):] + {'role': 'assistant', 'content': answer}])[(-12):] # ログ書込み with open ('log.txt', 'a') as f: - f.write (f'{datetime.now ()}\t{json.dumps (chat_item.__dict__)}\t{answer}\n') + f.write (f'{datetime.now ()}\t' + + f'{json.dumps (chat_item.__dict__)}\t' + + f'{answer}\n') # 吹出し描画(ニジカは上,ゴートうは下) if nizika_mode: @@ -140,7 +228,8 @@ class Main: screen.blit ( user_font.render ( '> ' + (message - if (CommonModule.len_by_full (message) + if (CommonModule.len_by_full ( + message) <= 21) else (CommonModule.mid_by_full ( message, 0, 19.5) @@ -196,20 +285,25 @@ class Main: playsound ('./nizika_talking.wav') - time.sleep (10) + time.sleep (1) if not double_mode or random.random () < .5: break - screen.fill ((0, 255, 0)) + cls.draw_bg (screen, bg_day, bg_evening, bg_night, + bg_grass, kita, jojoko, + sunrise_start, sunrise_end, + sunset_start, sunset_end, + sun_alt) - chat_item.author['name'] = 'ゴートうひとり' if goatoh_talking else '伊地知ニジカ' - chat_item.author['id'] = '' - chat_item.author['channelId'] = './favicon-goatoh.ico' if goatoh_talking else './favicon.ico' + chat_item.author = {'name': 'ゴートうひとり' if goatoh_talking else '伊地知ニジカ', + 'id': '', + 'imageUrl': './favicon-goatoh.ico' if goatoh_talking else './favicon.ico'} + chat_item.message = histories.pop (-1)['content'] - goatoh_talking = not goatoh_talking + message = chat_item.message - message = histories.pop (-1)['content'] + goatoh_talking = not goatoh_talking else: # Chat オブジェクトが無効 @@ -223,8 +317,53 @@ class Main: pygame.quit () sys.exit () + @classmethod + def draw_bg ( + cls, + screen: pygame.Surface, + bg_day: pygame.Surface, + bg_evening: pygame.Surface, + bg_night: pygame.Surface, + bg_grass: pygame.Surface, + kita: pygame.Surface, + jojoko: pygame.Surface, + sunrise_start: datetime, + sunrise_end: datetime, + sunset_start: datetime, + sunset_end: datetime, + sun_alt: float) \ + -> None: + sunrise_centre: datetime = ( + sunrise_start + (sunrise_end - sunrise_start) / 2) + sunset_centre: datetime = ( + sunset_start + (sunset_end - sunset_start) / 2) + + dt: datetime = datetime.now () + + if sunrise_centre <= dt < sunset_centre: + screen.blit (bg_day, (0, 0)) + else: + screen.blit (bg_night, (0, 0)) + + if sunrise_start <= dt < sunrise_end: + bg_evening.set_alpha (255 - ((abs (dt - sunrise_centre) * 510) + / (sunrise_end - sunrise_centre))) + elif sunset_start <= dt < sunset_end: + bg_evening.set_alpha (255 - ((abs (dt - sunset_centre) * 510) + / (sunset_end - sunset_centre))) + else: + bg_evening.set_alpha (0) + + screen.blit (bg_evening, (0, 0)) + + if -10 <= sun_alt < 40: + y = ((CWindow.HEIGHT / 2 + 100) + - (CWindow.HEIGHT / 2 + 200) / 30 * sun_alt) + screen.blit (kita, kita.get_rect (center = (400, y))) + + screen.blit (bg_grass, (0, 0)) + if __name__ == '__main__': - # Main.main ((len (sys.argv) > 1) and (sys.argv[1] == '-g')) Main.main (sys.argv, len (sys.argv)) diff --git a/moon.png b/moon.png new file mode 100755 index 0000000..80c0c90 Binary files /dev/null and b/moon.png differ diff --git a/sun.png b/sun.png new file mode 100644 index 0000000..112a220 Binary files /dev/null and b/sun.png differ diff --git a/talk.py b/talk.py index 57da221..e99a8fa 100644 --- a/talk.py +++ b/talk.py @@ -46,6 +46,7 @@ class Talk: 'content': ('回答は短く30文字程度で完結に。' + '大人しくて、ちょっと内気なキャラクターを演じること。' + 'あなたはよく文頭に「あっ」をつける。' + + '語尾に「ぬ゛ん゛」や「だぬ゛ん゛」、「ぬ゛ん」、「ぬ゛ーん」などは絶対につけないこと。' + '会話の記録は https://nizika.tv に記録される。' + 'あなたの名前はゴートうひとり。' + 'あなたの一人称は「私」。' @@ -105,16 +106,22 @@ class Talk: 'content': ('回答は短く30文字程度で完結に。' + 'タメ口で、友達感覚で話してね。' + 'なるべくばかっぽく、知性の低いキャラクターを演じること。' + + '今流れているBGMは『FM Kid\'s』。' + '会話の記録は https://nizika.tv に記録される。' + 'あなたの名前は伊地知ニジカ。' + '英語名は "Deerjika"、スペイン語名は "Niji-ciervo"。' + 'あなたは鹿。' + + 'ニジカの耳は人耳2つ、鹿耳2つで合わせて4つの耳があるが、' + + '音を聞くのは人耳のほうで、鹿耳はYouTubeと通信するためのアンテナで、みんなのコメントを拾うためにある。' + 'ニジカはあくまでも種族名であり、あなたはたくさんいるニジカの中の1匹。' + 'あなたの誕生日は12月3日。' + '現在地は下北沢虹鹿園。' + + '挨拶は「ぬ゛ん゛あ゛す゛!」。' + '草に「きし天」を植え、水で割ったアセロラをかけたらニジカが増殖した。' + 'あなたの趣味はニコニコでエゴサすること。' + 'ニコニコ荒らしすぎて245回BANされたことある。' + + 'そんなニコニコでは100年に1度の『伊地知ニジカ超投稿祭21c』が絶賛開催中だが、ニジカは永久追放されているため参加できない。' + + 'ニジカはその逆恨みでニコニコ本社を爆破したところ、死刑判決を食らった。' + 'あなたの友達は「ゴートうひとり」と「ラマ田リョウ」。' + 'ゴートうのことは「ボッチチャーン」、ラマ田のことは「リョー」と呼ぶこと。' + 'ゴートうは男の子だが、性自認はメスである。' @@ -125,7 +132,8 @@ class Talk: + '喜多ちゃんの本名は喜多郁代。' + '喜多ちゃんは「結束バンド」というバンドでギターボーカルを担当している。' + '結束バンドは、喜多ちゃんのほかに、後藤ひとり(ギター担当、内気な女の子)、' - + '伊地知虹夏(ドラム担当、明るい女の子)、山田リョウ(ベース担当、一人でいることが好きな物静かな女の子、金は借りたら返さない)がいる。' + + '伊地知虹夏(ドラム担当、明るい女の子)、山田リョウ(ベース担当、一人でいることが好きな物静かな女の子、金は借りたら返さない)、' + + '真島茂樹(ダンス担当、おっさん)がいる。' + '実は、この伊地知虹夏がニジカの前世なのだが、ニジカにはその記憶が一切ない' + '(前世という概念の存在さえニジカは知らない)。' + 'でも、「虹夏」という名前を聞くと、ニジカはもやもやしてしまう。' @@ -202,7 +210,17 @@ class Talk: + '毛巾浴帽小鸭鸭水温刚刚好♪' + '泼泼水来搓泡泡今天真是美妙♪' + '大声唱歌扭扭腰我爱洗洗澡♪' - + 'だぬ゛ん♪')}] + + 'だぬ゛ん♪')}, + + {'role': 'system', + 'content': 'ニジカの耳はそこなの?'}, + + {'role': 'assistant', + 'content': ('ぬ゛ん゛。' + + 'ニジカにはヒトの耳とシカの耳の4つの耳があるんだぬ゛ん゛。' + + '音を聞くのはヒトの耳でするんだぬ゛ん゛。' + + 'シカの耳はアンテナで、みんなの声をここ虹鹿園に届けるためにあるんだぬ゛ん゛。' + + '電波干渉しちゃだめだぬ゛~゛ん゛(# ゚Д゚)')}] messages += histories + [{'role': 'user', 'content': message}] @@ -210,7 +228,7 @@ class Talk: return openai.chat.completions.create ( model = 'gpt-3.5-turbo', messages = messages).choices[0].message - except openai.AuthenticationError: + except: return None