Naomi's notebook

Naomi's notebook

🚧Unity&宴「ノベルゲーム」開発入門on2020.1.6f1

本書はとても親切なので本来詰まるところなどなさそうですが、Unityのバージョン違いにより詰まるところがあったので、ここでそれに対する試行錯誤を載せたいと思います。随時更新です。

環境

p61 新しい画像に「Packing Tag」を設定

Packing Tagはスプライトパッカー (古い機能) スプライトパッカー - Unity マニュアルと言う機能の一部で、描画を素早くするためにグラフィックを一つのテクスチャにまとめるためのものらしいです。

しかしこれは古い機能なので、もうPacking Tagが編集できなくなってしまっています。 Where is the "Packing tag"? - Unity Forum (このフォーラムの途中にある方法を試してももう編集できません。)

新しい機能であるスプライトアトラス スプライトアトラス - Unity マニュアルという機能を使ってテクスチャをまとめていきましょう。(宴との互換性は、知らん。)

Assets>Create>Sprite Atlasから新しい.spriteatlasファイルを作って、インスペクタのObjects for PackingにUtageJidaiHime/Textures/SpriteBGとUtageJidaiHime/Textures/SpriteUIを追加しておけば大丈夫な気がします。

このとき、SpriteAtlasの「Tight Packing」のチェックボックスからチェックを外さないと、画像が重なってしまい余計な画像が見えることがある。

p127 「プレハブ」と「インスタンス

Select Revert Applyの3つのボタンが表示されるとあるが、実際はOpenとSelectしかない。横にoverridesのドロップダウンがある。

インスタンスを通したプレハブの編集 - Unity マニュアル

これを読むに、overridesドロップダウンのapply allを押せばいいのだろう。

補助pdf 名前の入力

そのままではinputfieldに日本語が入力できない。

inputfieldオブジェクトを選択し、inspectorからLineTypeをMultiLineNewlineにすること。

abc184_D - increment of coins

Solution Overview

This problem can be solved by Probability Dynamic Programming. We can calculate the expected times of the operation from that of when one more coin is there.

if we define dp[i][j][k] as the expected times of the operation from the situation we have (i,j,k) coins, we can find this value by:

dp[i][j][k]=(i/i+j+k)(1+dp[i+1][j][k])+(j/i+j+k)(1+dp[i+1][j+1][k])+(k/i+j+k)(1+dp[i][j][k+1])

Code

int A,B,C;
double dp[105][105][105];

double dfs(int i,int j, int k){
    if(dp[i][j][k]) return dp[i][j][k];
    if(i==100 || j==100 || k==100)return 0.0;
    double ret=0.0;
    ret+=((double)i/(double)(i+j+k))*(1.0+dfs(i+1,j,k));
    ret+=((double)j/(double)(i+j+k))*(1.0+dfs(i,j+1,k));
    ret+=((double)k/(double)(i+j+k))*(1.0+dfs(i,j,k+1));
    dp[i][j][k]=ret;
    return ret;
}
signed main(){
    scanf("%lld %lld %lld",&A,&B,&C);
    printf("%.9lf\n",dfs(A,B,C));
}

テーブル

int A,B,C;
double dp[105][105][105];

signed main(){
    scanf("%lld %lld %lld",&A,&B,&C);
    for(int i=99;i>=0;i--){
        for(int j=99;j>=0;j--){
            for(int k=99;k>=0;k--){
                dp[i][j][k]=0.0;
                if(i<100)dp[i][j][k]+=((double)i/(double)(i+j+k))*(1.0+dp[i+1][j][k]);
                if(j<100)dp[i][j][k]+=((double)j/(double)(i+j+k))*(1.0+dp[i][j+1][k]);
                if(k<100)dp[i][j][k]+=((double)k/(double)(i+j+k))*(1.0+dp[i][j][k+1]);
            }
        }
    }
    printf("%.9lf\n",dp[A][B][C]);
}

ABC183_E Queen on Grid

E - Queen on Grid

Overall Solution

You can solve this problem by dynamic programming, because if the place of Queen was decided, you can determine the number of cases of the situation and that of visiting goal from that place, uniquely. You can memorize the number of cases when Queen is in (i,j) as dp[i][j], and do the transition like:

dp[i][j]=dp[i-1][j]+dp[i-2][j]+...+dp[i][j-1]+dp[i][j-2]+...+dp[i-1][j-1]+dp[i-2][j-2]+...

But the time complexity of this solution is O(HW(H+W)), so the time limit will be exceeded.

You can make time complexity lower by using cumulative sum. Define X[i][j],Y[i][j],Z[i][j] as:

X[i][j]=dp[i][j-1]+dp[i][j-2]+...
Y[i][j]=dp[i-1][j]+dp[i-2][j]+...
Z[i][j]=dp[i-1][j-1]+dp[i-2][j-2]+...

then, the transition can be represented as:

(when(i,j) is '.')
dp[i][j]=X[i][j]+Y[i][j]+Z[i][j]
X[i][j]=X[i][j-1]+dp[i][j-1]
Y[i][j]=Y[i-1][j]+dp[i-1][j]
Z[i][j]=Z[i-1][j-1]+dp[i-1][j-1]

(when(i,j) is '#')
dp[i][j]=X[i][j]+Y[i][j]+Z[i][j]
X[i][j]=0
Y[i][j]=0
Z[i][j]=0

Then, the time complexity of this solution is O(HW).

Notes

Don't forget to scanf until S[i][j] is # or . (because \n is read by scanf...)

Code

#include<cstdio>
#include<math.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<string>
#include<set>
#include<cstring>
#include<map>
 
 
using namespace std;
#define int long long int
#define rep(i,n) for(int i=0;i<n;i++)
#define INF 1001001001
#define LLINF 1001001001001001001
#define mp make_pair
#define pb push_back
#define mod 1000000007
//#define mod 998244353

int H,W;
char S[2000][2000];
int X[2000][2000];//dp[i][j-1]+dp[i][j-2]+...
int Y[2000][2000];
int Z[2000][2000];
int dp[2000][2000];
signed main(){
    scanf("%lld %lld",&H,&W);
    rep(i,H){
        rep(j,W){
            while(S[i][j]!='.' && S[i][j]!='#'){
                scanf("%c",&S[i][j]);
            }
            X[i][j]=0;Y[i][j]=0;Z[i][j]=0;
            dp[i][j]=0;
        }
    }
    //Queen is at (0,0)
    dp[0][0]=1;
    rep(y,H){
        rep(x,W){
            if(y==0&&x==0)continue;
            if(S[y][x]=='.'){
                if(x>0)X[y][x]=dp[y][x-1]+X[y][x-1];
                if(y>0)Y[y][x]=dp[y-1][x]+Y[y-1][x];
                if(x>0&&y>0)Z[y][x]=dp[y-1][x-1]+Z[y-1][x-1];
            }else{
                X[y][x]=0;
                Y[y][x]=0;
                Z[y][x]=0;
            }
            dp[y][x]=X[y][x]+Y[y][x]+Z[y][x];
            X[y][x]%=mod;
            Y[y][x]%=mod;
            Z[y][x]%=mod;
            dp[y][x]%=mod;
        }
    }
    //print the answer
    printf("%lld\n",dp[H-1][W-1]%mod);
    return 0;
}

ABC180_E Traveling Salesman among Aerial Cities

英語の練習で英語で解説記事を書いてみたいと思いますが、いつまで続くかは知りません。英語は話しているときくらいの気持ちで書くのでとてもbroken englishです…

atcoder.jp

Solution Overview

This problem can be solved by bitDP(Dynamic programming).

It is because when we know the cities where we have visited, the minimum cost so far can be uniquely decided. Memorize the minimum cost to visit S( it is a set of cities) and be at V(it is a city) , and make transition to next cities(the size is lower than N). The time complexity of this solution is O(2N*N2).

Code

#include<cstdio>
#include<math.h>
#include<algorithm>
#include<vector>
#include<queue>
#include<string>
#include<set>
#include<cstring>
#include<map>
 
 
using namespace std;
#define int long long int
#define rep(i,n) for(int i=0;i<n;i++)
#define INF 1001001001
#define LLINF 1001001001001001001
#define mp make_pair
#define pb push_back
#define mod 1000000007
//#define mod 998244353

int N;
int X[20],Y[20],Z[20];
int caluculate_cost(int from,int to){
    return abs(X[to]-X[from])+abs(Y[to]-Y[from])+max((int)0,Z[to]-Z[from]);
}
int dp[1<<20][20];
signed main(){
    scanf("%lld",&N);
    rep(i,N){
        scanf("%lld %lld %lld",&X[i],&Y[i],&Z[i]);
    }
    rep(i,1<<N)rep(j,N)dp[i][j]=INF;
    //go to first town
    dp[1<<0][0]=0;
    //do transition
    rep(i,1<<N){
        rep(j,N){
            rep(k,N){
                //transition from j to k
                if(i&(1<<k))continue;//check if k hasn't be visited
                dp[i|(1<<k)][k]=min(dp[i|(1<<k)][k],dp[i][j]+caluculate_cost(j,k));
            }
        }
    }
    //return to first city and print answer
    int ans=INF;
    rep(i,N){
        ans=min(ans,dp[(1<<N)-1][i]+caluculate_cost(i,0));
    }
    printf("%lld\n",ans);
}

pythonに2種類のswitch文を追加する

概要

本記事は東京大学工学部電子情報工学科の「大規模ソフトウェアを手探る」という実験の報告記事です。(2020年度前半組)

私達の班はpython3.10に新しい三項演算子・switch文・前置インクリメントを追加してみました。この記事ではswitch文について説明します。

概要 qiita.com

三項演算子 qiita.com

前置インクリメントkkti4216.hatenablog.com

環境

詳しいビルド方法などは概要の記事に書かれると思いますが、cpython3.10(https://github.com/python/cpython/tree/master)をcloneして、所定の方法(https://devguide.python.org/setup/)でビルドしました。

手探る準備

いじるのに使ったツール

gdb (+ emacs)

emacs: M-x gud-gdb

grep

grep hogehoge -r -I .

-m dis(バイトコードの表示)

python3 -m dis filename

-d(パーサーのデバッグ出力を有効にする)

python3 -d 

参考にしたサイト

公式ドキュメント

24. Changing CPython’s Grammar — Python Developer's Guide

ほぼほぼこのドキュメントに従って改造することが出来ました。

dis --- Python バイトコードの逆アセンブラ — Python 3.9.0 ドキュメント

こちらも参考にしました。

pythonを改造した先人たち

山のようにあります。

CPythonに後置インクリメントを加えてみた 概要とまとめ - Qiita

Pythonを改造してみた はじめに - 開拓馬の厩

Pythonにプライベート変数を実装しようと試みた話。 - Qiita

Modifying the Python language in 6 minutes | Hacker Noon

Python処理系入門 〜1 + 1 で学ぶ処理系解読の基礎〜 - yigarashi のブログ

Pythonを改造してみた unless文を追加してみた - 開拓馬の厩

Pythonを改造してみた 排他的論理のブール演算子作った - 開拓馬の厩

その他

Python/バイトコード生成を読む - Code Reading Wiki

手探る

python.gram:構文を定義する

Grammer/python.gram:L157にある、if...elif...elseの構文の定義(以下)を真似して追加します。

if_stmt[stmt_ty]:
    | 'if' a=named_expression ':' b=block c=elif_stmt { _Py_If(a, b, CHECK((asdl_stmt_seq*)_PyPegen_singleton_seq(p, c)), EXTRA) }
    | 'if' a=named_expression ':' b=block c=[else_block] { _Py_If(a, b, c, EXTRA) }
elif_stmt[stmt_ty]:
    | 'elif' a=named_expression ':' b=block c=elif_stmt { _Py_If(a, b, CHECK(_PyPegen_singleton_seq(p, c)), EXTRA) }
    | 'elif' a=named_expression ':' b=block c=[else_block] { _Py_If(a, b, c, EXTRA) }
else_block[asdl_stmt_seq*]: 'else' ':' b=block { b }

具体的には、 Grammer/python.gram:L82

#ifdef DOSS_SWITCH
    | &'switcha' switcha_stmt
#endif

L168

#ifdef DOSS_SWITCH
switcha_stmt[stmt_ty]:
    | 'switcha' a=なにか1 ':' NEWLINE INDENT b=case_block DEDENT {_Py_Switcha(a,CHECK((asdl_stmt_seq*)_PyPegen_singleton_seq(p, b)),EXTRA)}
case_block[stmt_ty]:
    | 'case' a=なにか2 ':'  b=block c=case_block {_Py_Case(a,b, CHECK(_PyPegen_singleton_seq(p, c)),EXTRA)}
    | 'case' a=なにか2 ':'  b=block c=[default_block] {_Py_Case(a,b,c,EXTRA)}
default_block[asdl_stmt_seq*]: 'defaulta' ':' b=block {b}
#endif

のように追加すれば良いと推定できます。ここで、「なにか1」と「なにか2」は、何か値を返すものであるということなどいろいろなことを考慮して、それぞれdisjunctionとatomを指定しました。C言語JAVAのswitch文のような、評価した結果が整数値でないといけないとか、case文の右は定数のみとするなどの制約はありません。(ここはもっと考えればより適切なものがあるかもしれません。)

なお、switch,defaultではなくswitcha,defaultaとしたのは、テストで用いられるpythonで書かれたコードの中にswitchやdefaultという名前の関数や変数が存在するようで、make installをしたときにエラーになってしまうためです。

変更が出来たら、make regen-pegenを行い、文をパースして抽象構文木を構築するプログラム(Parser/parser.c)を自動生成します。

python.asdl:関数を定義する

さきほどのpython.gramで新しい関数Py_SwitchaとPy_Caseを使ったので、それを定義します。 Parser/Python. L38

--#ifdef DOSS_SWITCH
          | Switcha(expr test,stmt* cases)
      | Case(expr test, stmt* body,stmt* orelse)
--#endif

変更できたらmake regen-astを行いInclude/Python-ast.hとPython/Python-ast.cを自動生成します。

compile.c:関数の中身を作る(バイトコードに変換する)

この時点で、(後述のようにast.cを変更する必要はあるが)switch文を実行しても構文エラーが出ないようになっているはずです。ただし何も帰ってきません。

まず、どのようなバイトコードで実現できそうかを考えます。 -m disというコマンドをつけて実行すると、python仮想マシン上で実行されているバイトコードを逆アセンブルして表示してくれるようなので、if...elif...else文やfor文でどのような動作をしているのか調べてみましょう。 f:id:Naomi_Lilienthal:20201020183152p:plain これを見ると、switch文の動作もLOAD_NAME→LOAD_CONST→COMARE_OP→POP_JUMP_IF_FALSE…といったように実現できそうなことが解ります。

また、switch文の右の式を評価した結果は何度も(case文が出てくるたびに)使わなければなりませんが、これはdis --- Python バイトコードの逆アセンブラ — Python 3.9.0 ドキュメントバイトコード命令の一覧を眺めると、DUP_TOPなどで実現できそうです。

以上のことをまとめると、だいたい以下のような実装をしたいということが想像できます。

LOAD_NAME
 DUP_TOP
 LOAD_CONST(仕様上LOAD_NAMEなどのこともある)
 COMPARE_OP
 POP_JUMP_IF_FALSE
 (処理)
 JUMP_ABSOLUTE
 DUP_TOP
 LOAD_CONST
 COMPARE_OP
 POP_JUMP_IF_FALSE
 (処理)
 JUMP_ABSOLUTE
   ...(繰り返し)
POP_TOP

自然な実装になるように、switch文の仕様は以下のようにしてあります。

  • switch文に入ったときに式を一回評価する
  • その値とcase文の式の値を==で比較し、Trueとなった一番上のcase文の中身を実行し、switch文を抜ける
  • どのcase文も実行されなかった場合defaultを実行する

あとはcompile.cの中でそれぞれのバイトコードを使っているところを検索し、それを真似して書くということを繰り返します。 以下に具体的な実装を載せておきます。

Python/compile.c L2746

#ifdef DOSS_SWITCH
static int
compiler_case(struct compiler *c, stmt_ty s)
{
  basicblock *end, *next;
  next = compiler_new_block(c);
  end = compiler_new_block(c);
  if (next == NULL || end == NULL)return 0;
  assert(s->kind == Case_kind);

  ADDOP(c, DUP_TOP);
  VISIT(c,expr,s->v.Case.test);
  cmpop_ty op=Eq;
  ADDOP_COMPARE(c,op);
  ADDOP_JUMP(c, POP_JUMP_IF_FALSE, next);
  VISIT_SEQ(c, stmt, s->v.Case.body);
  ADDOP_JUMP(c, JUMP_ABSOLUTE, end);
  compiler_use_next_block(c, next);
  if(s->v.Case.orelse){ 
    VISIT_SEQ(c,stmt,s->v.Case.orelse);
  }
  compiler_use_next_block(c, end);
  return 1;
}
static int
compiler_switch(struct compiler *c, stmt_ty s)
{
   basicblock *end, *next;
   next = compiler_new_block(c);
   end = compiler_new_block(c);
   if (next == NULL || end == NULL) {
        return 0;
    }
   assert(s->kind == Switcha_kind);
   VISIT(c,expr,s->v.Switcha.test);
   compiler_use_next_block(c, next);
   VISIT_SEQ(c,stmt,s->v.Switcha.cases);
   ADDOP(c, POP_TOP);
   compiler_use_next_block(c, end);
   return 1;
}

#endif

L3457

#ifdef DOSS_SWITCH
    case Switcha_kind:
        return compiler_switch(c, s);
    case Case_kind:
        return compiler_case(c, s);
 #endif

実際には以下のように動きます f:id:Naomi_Lilienthal:20201020195401p:plain

ceval.c:バイトコードの動作を定義する

既存のバイトコードの羅列により実装が出来てしまったので、今回この部分は触らなくてもよかったです。(前置インクリメントを作る際はこの部分も変更する必要がありました。)

その他

シンボルテーブルの作成

python.asdlを変更したあとにmake regen-allをすると、以下のようなwarningが出ます。

Python/symtable.c: In function 'symtable_visit_stmt':
Python/symtable.c:1178:5: warning: enumeration value 'Switch_kind' not handled in switch [-Wswitch]
     switch (s->kind) {
     ^~~~~~
Python/symtable.c:1178:5: warning: enumeration value 'Case_kind' not handled in switch [-Wswitch]

これはsymtable.cという「シンボルテーブル」なるものを作っているプログラムの中に、switch文とcase文に対応する動作が記されていないことに起因します。シンボルテーブルは、バイトコードを生成する前に抽象構文木から生成され、識別子のスコープを計算するのに使う表らしいです。 よくわからない点もありますが、とりあえず他の場所を真似して、以下のようにswitch文、case文から訪れるすべての行き先を記しておきます。 Python/symtable.c L1294

#ifdef DOSS_SWITCH
    case Switcha_kind:
        VISIT(st, expr, s->v.Switcha.test);
        VISIT_SEQ(st, stmt, s->v.Switcha.cases);
        break;
    case Case_kind:
      VISIT(st,expr,s->v.Case.test)
      VISIT_SEQ(st, stmt, s->v.Case.body);
        if (s->v.Case.orelse)
            VISIT_SEQ(st, stmt, s->v.Case.orelse);
    break;
#endif

抽象構文木の検証

python.asdlまで変更した時点でmake&make-installし、switch文を実行すると、以下のようなエラーが出ます。

python3: Parser/pegen.c:1146: _PyPegen_run_parser: Assertion `PyAST_Validate(res)' failed.

これをgdbで追っていくと、Python/ast.c:L539

case Interactive_kind:
        res = validate_stmts(mod->v.Interactive.body);

でvalidate_stmtsの返り値が0になっていることが問題なようです。さらにこれをgdbで追うと、validate_stmtのなかでSwitcha_kindとCase_kindがないことが問題だとわかります。よって以下のように変更すればとりあえず動きます。

Python/ast.c L404(要検証)

#ifdef DOSS_SWITCH
    case Switcha_kind:
      return 1;
    case Case_kind:
      return 1;
#endif

Python/ast.cが何をしているかというと、構文木が正しいかどうかを検証しているらしい。よってここは本来は以下のように記すことで構文木の検証をするのが正しいです。

Python/ast.c L404

#ifdef DOSS_SWITCH
    case Switcha_kind:
      return validate_expr(stmt->v.Switcha.test, Load) &&
      validate_stmts(stmt->v.Switcha.cases);
    case Case_kind:
      return validate_expr(stmt->v.Case.test, Load) &&
      validate_stmts(stmt->v.Case.body) &&
      validate_stmts(stmt->v.Case.orelse);
#endif

全変更点一覧(eqによる比較のみ)

switch

Grammer/python.gram L82

#ifdef DOSS_SWITCH
    | &'switcha' switcha_stmt
#endif

L168

#ifdef DOSS_SWITCH
switcha_stmt[stmt_ty]:
    | 'switcha' a=disjunction ':' NEWLINE INDENT b=case_block DEDENT {_Py_Switcha(a,CHECK((asdl_stmt_seq*)_PyPegen_singleton_seq(p, b)),EXTRA)}
case_block[stmt_ty]:
    | 'case' a=atom ':'  b=block c=case_block {_Py_Case(a,b, CHECK(_PyPegen_singleton_seq(p, c)),EXTRA)}
    | 'case' a=atom ':'  b=block c=[default_block] {_Py_Case(a,b,c,EXTRA)}
default_block[asdl_stmt_seq*]: 'defaulta' ':' b=block {b}
#endif

Parser/Python. L38

--#ifdef DOSS_SWITCH
          | Switcha(expr test,stmt* cases)
      | Case(expr test, stmt* body,stmt* orelse)
--#endif

Python/ast.c L404

#ifdef DOSS_SWITCH
    case Switcha_kind:
      return validate_expr(stmt->v.Switcha.test, Load) &&
      validate_stmts(stmt->v.Switcha.cases);
    case Case_kind:
      return validate_expr(stmt->v.Case.test, Load) &&
      validate_stmts(stmt->v.Case.body) &&
      validate_stmts(stmt->v.Case.orelse);
#endif

Python/compile.c L2746

#ifdef DOSS_SWITCH
static int
compiler_case(struct compiler *c, stmt_ty s)
{
  basicblock *end, *next;
  next = compiler_new_block(c);
  end = compiler_new_block(c);
  if (next == NULL || end == NULL)return 0;
  assert(s->kind == Case_kind);

  ADDOP(c, DUP_TOP);
  VISIT(c,expr,s->v.Case.test);
  cmpop_ty op=Eq;
  ADDOP_COMPARE(c,op);
  ADDOP_JUMP(c, POP_JUMP_IF_FALSE, next);
  VISIT_SEQ(c, stmt, s->v.Case.body);
  ADDOP_JUMP(c, JUMP_ABSOLUTE, end);
  compiler_use_next_block(c, next);
  if(s->v.Case.orelse){ 
    VISIT_SEQ(c,stmt,s->v.Case.orelse);
  }
  compiler_use_next_block(c, end);
  return 1;
}
static int
compiler_switch(struct compiler *c, stmt_ty s)
{
   basicblock *end, *next;
   next = compiler_new_block(c);
   end = compiler_new_block(c);
   if (next == NULL || end == NULL) {
        return 0;
    }
   assert(s->kind == Switcha_kind);
   VISIT(c,expr,s->v.Switcha.test);
   compiler_use_next_block(c, next);
   VISIT_SEQ(c,stmt,s->v.Switcha.cases);
   ADDOP(c, POP_TOP);
   compiler_use_next_block(c, end);
   return 1;
}

#endif

L3457

#ifdef DOSS_SWITCH
    case Switcha_kind:
        return compiler_switch(c, s);
    case Case_kind:
        return compiler_case(c, s);
 #endif

Python/symtable.c L1294

#ifdef DOSS_SWITCH
    case Switcha_kind:
        VISIT(st, expr, s->v.Switcha.test);
        VISIT_SEQ(st, stmt, s->v.Switcha.cases);
        break;
    case Case_kind:
      VISIT(st,expr,s->v.Case.test)
      VISIT_SEQ(st, stmt, s->v.Case.body);
        if (s->v.Case.orelse)
            VISIT_SEQ(st, stmt, s->v.Case.orelse);
    break;
#endif

おまけ:is比較を行うswitchb文を追加する

TAさんに指摘されたので、==演算子で比較するswitcha文のみでなく、is演算子で比較するswitchb文を実装してみました。

実装

大まかな流れは変わりませんが、switchの右の式の評価と一緒に評価方法(0ならeq,1ならis)をスタックにプッシュし、case文が呼ばれるたびにDUP_TOP_TWOしてそれらを使うといった感じの実装にしました。

switcha文からの変更点は以下です。

Grammer/python.gram:L84

#ifdef DOSS_SWITCH
    | &'switcha' switcha_stmt
    | &'switchb' switchb_stmt
#endif

L179

#ifdef DOSS_SWITCH
switcha_stmt[stmt_ty]:
    | 'switcha' a=disjunction ':' NEWLINE INDENT b=case_block DEDENT {_Py_Switcha(a,CHECK((asdl_stmt_seq*)_PyPegen_singleton_seq(p, b)),0,EXTRA)}
switchb_stmt[stmt_ty]:
    | 'switchb' a=disjunction ':' NEWLINE INDENT b=case_block DEDENT {_Py_Switcha(a,CHECK((asdl_stmt_seq*)_PyPegen_singleton_seq(p, b)),1,EXTRA)}
case_block[stmt_ty]:
    | 'case' a=atom ':'  b=block c=case_block {_Py_Case(a,b, CHECK(_PyPegen_singleton_seq(p, c)),EXTRA)}
    | 'case' a=atom ':'  b=block c=[default_block] {_Py_Case(a,b,c,EXTRA)}

Python/compile.c:L2746

#ifdef DOSS_SWITCH
static int
compiler_case(struct compiler *c, stmt_ty s)
{
  basicblock *start,*is,*end, *next;
  start = compiler_new_block(c);
  is = compiler_new_block(c);
  next = compiler_new_block(c);
  end = compiler_new_block(c);
  if (next == NULL || end == NULL)return 0;
  assert(s->kind == Case_kind);

  ADDOP(c, DUP_TOP_TWO);

  ADDOP_JUMP(c, POP_JUMP_IF_TRUE,is);
  //if top=0
  cmpop_ty op=Eq;
  VISIT(c,expr,s->v.Case.test);
  ADDOP_COMPARE(c,op);
  ADDOP_JUMP(c, JUMP_ABSOLUTE, start);

  //if top=1
  compiler_use_next_block(c,is);
  op=Is;
  VISIT(c,expr,s->v.Case.test);
  ADDOP_COMPARE(c,op);
  
  compiler_use_next_block(c,start);
  ADDOP_JUMP(c, POP_JUMP_IF_FALSE, next);
  VISIT_SEQ(c, stmt, s->v.Case.body);
  ADDOP_JUMP(c, JUMP_ABSOLUTE, end);
  compiler_use_next_block(c, next);
  if(s->v.Case.orelse){ 
    VISIT_SEQ(c,stmt,s->v.Case.orelse);
  }
  compiler_use_next_block(c, end);
  return 1;
}
static int
compiler_switch(struct compiler *c, stmt_ty s)
{
   basicblock *end, *next;
   next = compiler_new_block(c);
   end = compiler_new_block(c);
   if (next == NULL || end == NULL) {
        return 0;
    }
   assert(s->kind == Switcha_kind);
   VISIT(c,expr,s->v.Switcha.test);
   ADDOP_LOAD_CONST_NEW(c, PyLong_FromLong(s->v.Switcha.compare_type));
   compiler_use_next_block(c, next);
   VISIT_SEQ(c,stmt,s->v.Switcha.cases);
   ADDOP(c, POP_TOP);
   ADDOP(c, POP_TOP);
   compiler_use_next_block(c, end);
   return 1;
}

#endif

動作

上がswitcha文、下がswitchb文を使ったときの動作です。 f:id:Naomi_Lilienthal:20201020200723p:plain

感想

switch文の追加には試行錯誤含め全部で5時間程度しかかかっていないと思います。ドキュメントが整理されており、またプログラムの自動生成などが整備されており、それに従って変更すればよかったことがとても大きかったです。

また、バイトコードに変換して考えるととても考えやすかったので、文法や機能を追加したいときはこの方法を取ることをおすすめします。

サーバーでunityのゲーム(?)を公開する

ノベルゲームを公開できないかなあと思い,とりあえず

【C#】Unity 2Dでできるだけ簡単にアドベンチャーゲーム(ノベルゲーム)を作る その1 - Kanchiの日記

この文字が流れるだけのアプリを公開できるか試してみました

バージョン

Unity 2020.1.6f1 Personal

設定

safariの人もプレイできるようにしたいのでとりあえずWebGL2.0ではなくWebGL1.0を使いました.

  • Edit->Build Settings からPlatformをWebGL
  • さらにEdit->Build Settings->Player Settings->Playerで,
    • Publishing Settings->Compression FormatをDisabledに(ファイルの容量は増えてしまいますが,これをしないとこのバージョンではUncaught ReferenceError: unityFramework is not definedエラーが起こります)
    • Other Settings->Graphics APIsの+ボタンを押してWebGL 1.0を追加,上に持っていく
    • Other Settings->Color SpaceをLinerからGammaに(WebGL1.0では対応していないため)
  • CanvasのCanvasScalerでScreenMatchModeをShrinkへ

これでbuild&runして,できたフォルダをサーバーに移動してindex.htmlを参照すれば見ることができます.

例: naomiatlibrary.com/unity_novelgame

備考:ProxyPassで/以下にアクセスするとreactのサーバ(ポートフォリオ)に飛ばすようになっていたので,httpd.confを編集して

ProxyPass /unity_novelgame/ !

を追加し除外しました.

スマホへの対応

macsafari, Chrome, Edge, firefoxで確認したところちゃんと見ることができましたが,Androidで確認するとレイアウトが崩れていました.

とりあえずこれを参考にして,ちょっとビルドした後のindex.htmlを変更しました. Unity WebGL ブラウザの大きさに合わせる - Qiita

以下の部分を次のように変更します.

<div id="unity-container" class="unity-desktop" style="text-align: center;">

(styleの追加)

if (/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
        //container.className = "unity-mobile";
        config.devicePixelRatio = window.devicePixelRatio;
        container.style.overflow = 'hidden';
        container.style.width = window.innerWidth + 'px';
        container.style.height = window.innerHeight + 'px';
        canvas.style.width  =Math.min(window.innerHeight*960/600,window.innerWidth)+"px";
        canvas.style.height =Math.min(window.innerWidth*600/960,window.innerHeight)+"px";
      } else { 

(スマホなどからのアクセスの時の処理)

こうすると,スマホなどからアクセスした時に以下のようになり,とりあえずレイアウトが崩れなくなりました.(これがいいやり方なのかは知りませんが)

(Androidで検証)

f:id:Naomi_Lilienthal:20200926191445j:plain
f:id:Naomi_Lilienthal:20200926191506j:plain

reactでポートフォリオサイトを作りました

今までの作品や経歴をまとめたページが欲しかったので,reactで超簡単なサイトを作りました.

サイト

naomiatlibrary.com

コード

感想

ドキュメントを一通り読んだ後にほぼ何も見ないで作れました.このドキュメントは結構わかりやすかったです.

せっかくなので検索機能をつけてみました(検索機能がいるほど作品があるかは微妙ですが)デザインについては細かいところはあまり気にせず,できるだけシンプルなページになるように心がけました.(CSSをやるのがめんどくさかったとも言う……)

反省

流石にHOMEはもうちょっとデザインを考えたいところです.