第31回全日本学生マイクロマウス大会で徹夜調整をした話

大会前日

いつぞやの記事では,学生大会でシードを取ると言っていましたが,そんな事を言っている場合ではないほど探索ができずに苦しんでいました.
そこで,先輩のご厚意に甘えて特別に徹夜で調整させていただく機会をもらいました.

探索の安定性を向上させ,きりが良くなったタイミングで大回りパスでの最短走行を実装.
うむ夫。くんに怒られたので,最短時には必ず壁切れを読むようにしました.
徹夜の結果,なんとか走るようになり,清々しい気分で朝の電車に飛び込んだ……ような気がします.正直フラフラで記憶がありません.

Read More

第26回マイクロマウス九州地区大会で再現性の高さを見せつけてきた

九州大会

どうしてこんな遠いところに来てしまったんだろうか……(遠い目)

今年2回めの飛行機で,初めての九州上陸です.
航空会社はSTAR FLYERを使いましたが,国内線なのにモニタがついていたりしていてとてもすごかったです(小並感).

屋台

着日の夜には頭の悪い人たちと天神の屋台に行きました.

ずーーーーーっと歩き続けた後,とりあえず入った照ちゃんが大当たりで最高でした.
おでんもうまい,肉がうまい,酒がうまい,ラーメンがうまい.

僕はアジの唐揚げ(800円)をしゃぶり尽くして満面の笑みを浮かべていたと思います.

戦績

さて,前の記事で目標を書いていたような気がします.
前回の東北大会では照明の影響が大きく,まともに走ることができませんでした.

今回の九州大会では,どうにかゴールをすることができました!
……が,なんと帰り探索でクラッシュ.5回連続で同じところで止まってしまい,最短走行をすることができませんでした.

反省点は,

  • スラロームの調整が甘い
  • 探索でコケた時に迷路データが消えるのはダメ
  • 前壁補正入れよう
  • 壁切れ補正入れよう

要するに準備不足なので,今週もがんばります.

進捗

出走が終わり,しょげていた時にぽちぽちとコードを書いていました.

おかげで迷路データのMRAMへの保存ができるようになりました!
これで,仮に探索でコケてもそれまでのデータが消えることはなくなりました!!

次の中部大会では,

  • 走行の安定
  • モード機能の実装

あたりを強化したいと思っています.

僕の考えた最強のパラメータ格納法

パラメータ格納

マイクロマウスではいろいろなパラメータ,調整項目があります.きれいに走るためには,たくさんのパラメータを真心込めて調整してあげる必要があります.ほんとうにしんどい.

さて,乱雑なコードを書いているといろんなよくない問題が発生します.
例えば,

  • 同じパラメータが複数ヶ所で定義されている
  • 使われていないパラメータが乱立している
  • 変更したいパラメータがどこに書かれているか見つからない

……などなど.ちなみに全て実話です

というわけで,パラメータの格納方法はこだわったほうがいいというのがこの記事で言いたいことです.

先駆者

某先輩がポインタまみれのスラロームパラメータ保存を紹介していました.全容を把握するのには苦労しますが,すべてのパラメータを#defineするよりも良い方法だと思います.
C言語を頑張りたい人はこの記事を読んで,ポインタとは何者なのか理解しちゃいましょう!

ぼくのかんがえた(以下略

では,僕の考えた最強のパラメータ格納法をででんと紹介します.

あ,予め言っておきますがC++11とSTLを使いまくっているので,用法用量にはお気をつけください.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
namespace slalomparams{
enum class RunType : uint8_t {
SLALOM90SML_RIGHT,
SLALOM90SML_LEFT,
SLALOM90_RIGHT,
SLALOM90_LEFT,
SLALOM180_RIGHT,
SLALOM180_LEFT,
SLALOM45IN_RIGHT,
SLALOM45IN_LEFT,
SLALOM45OUT_RIGHT,
SLALOM45OUT_LEFT,
SLALOM135IN_RIGHT,
SLALOM135IN_LEFT,
SLALOM135OUT_RIGHT,
SLALOM135OUT_LEFT,
SLALOM90OBL_RIGHT,
SLALOM90OBL_LEFT,
TRAPACCEL,
PIVOTTURN,
};

struct pack{
float d_before;
float d_after;
float acc_rad;
float deg;
float const_deg;
float in_vel;
float out_vel;
float min_vel;
float acc_lin;
};
typedef struct pack pack_t;
}

array< map<float,pack_t>* , 16> slalomparams::getParams(){
static map<float, pack_t> SLALOM90SML_RIGHT = {
// { vel, {d_bef, d_aft, a_rad, deg, cnsdeg, in_vel, out_vel, min_vel, acc_lin}},
{0.25, {0.010, 0.012, 9000,95.5, 30, 0.25, 0.25, 0.25, 0.0}},
};
static map<float, pack_t> SLALOM90SML_LEFT = {
// { vel, {d_bef, d_aft, a_rad, deg, cnsdeg, in_vel, out_vel, min_vel, acc_lin}},
{0.25, {0.010, 0.012, 9000,95.5, 30, 0.25, 0.25, 0.25, 0.0}},
};
static map<float, pack_t> SLALOM90_RIGHT = {};
static map<float, pack_t> SLALOM90_LEFT = {};
static map<float, pack_t> SLALOM180_RIGHT = {};
static map<float, pack_t> SLALOM180_LEFT = {};
static map<float, pack_t> SLALOM45IN_RIGHT = {};
static map<float, pack_t> SLALOM45IN_LEFT = {};
static map<float, pack_t> SLALOM45OUT_RIGHT = {};
static map<float, pack_t> SLALOM45OUT_LEFT = {};
static map<float, pack_t> SLALOM135IN_RIGHT = {};
static map<float, pack_t> SLALOM135IN_LEFT = {};
static map<float, pack_t> SLALOM135OUT_RIGHT = {};
static map<float, pack_t> SLALOM135OUT_LEFT = {};
static map<float, pack_t> SLALOM90OBL_RIGHT = {};
static map<float, pack_t> SLALOM90OBL_LEFT = {};

static array< map<float,pack_t>* , 16> params = {
&SLALOM90SML_RIGHT,
&SLALOM90SML_LEFT,
&SLALOM90_RIGHT,
&SLALOM90_LEFT,
&SLALOM180_RIGHT,
&SLALOM180_LEFT,
&SLALOM45IN_RIGHT,
&SLALOM45IN_LEFT,
&SLALOM45OUT_RIGHT,
&SLALOM45OUT_LEFT,
&SLALOM135IN_RIGHT,
&SLALOM135IN_LEFT,
&SLALOM135OUT_RIGHT,
&SLALOM135OUT_LEFT,
&SLALOM90OBL_RIGHT,
&SLALOM90OBL_LEFT,
};

return params;
}

struct pack がスラロームの1パラメータを格納した構造体です.
スラロームには,代表的なターンが16種類あるので,
1種類のスラロームパラメータをまとめたもの(A) へのポインタを,サイズ16の配列(B) にしています
(A)map<float, pack_t>で,(B)array< map<float, pack_t>*, 16>です.

(A)にstd::mapを使うことで,パラメータの存在確認を可能にしました.
スラロームのターン速を引数に取ることで,欲しいパラメータを参照することができます.

具体的には,次のようなコードでターンパラメータを取得・参照できます.

1
2
3
4
5
6
using namespace slalomparams;
auto it = getParams().at(static_cast<uint16_t>(RunType::SLALOM90SML_RIGHT))->find(0.25f);
if(it == getParams().at(static_cast<uint16_t>(RunType::SLALOM90SML_RIGHT))->end()) printf("Undefined parameter");

it->second.deg; //95.5
it->second.d_before; //0.01

ちなみに,諸事情あってconstにはしていませんが,問題なくconstをつけられると思います.

第29回マイクロマウス東北地区大会で足立法を実装した話

東北大会

マイクロマウス東北地区大会は,我々Miceが毎年大変お世話になっている大会です.
毎年多くのフレッシュマンが精神と時の部屋でとてつもない成長を遂げます.

本年度も6人のフレッシュマンがエントリーし,うち3名が完走.そしてその3名が上位1〜3位を独占という結果になりました! まずは良い結果になり,ホッとしています.正直自分の出走より何倍もドキドキしていました.

反省点も色々と考えているようなので,次の大会も期待しています.博多ラーメン食べられるのが今週の活力.

精神と時の部屋での成長

調整時間にあまり他の人に構えなくて申し訳なく思っていますが,なんせ自分の進捗が怪しいので頑張っていました.

今年のマウスはマイコンを変えただけでなく,プログラムをイチから書き直しています.というのも,コンパイラを変えたことでC++14が使えるようになったので,色々と直したいところが出てきたためです.
ついでにクラス設計も考え直しました.今のところ,いい感じにコードが動いているように思います.

さて,そのせいもあり,容易には昨年度のプログラムを流用することができなくなっています.
精神と時の部屋では,壁情報の保存,歩数マップの展開,足立法の実装を終えることができました.(あれっ,進捗遅くない???)
そういえばモードを実装しようと思っていましたが,間に合いませんでした.そのうちがんばります.

さて,ここからは技術的な話.毎度言っていますが,参考にするときは完全に自己責任で.より良い方法があればご教示いただきたいです.
足立法で走行するプログラムは,こんな感じになっています.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
VelocityControl vc;
Position pos;

Adachi adachi;
Map map;
Walldata walldata;
WallSensor wall;

adachi.setGoal(11, 11);
vc->runTrapAccel(0.0f, 0.25f, 0.25f, 0.045f, 2.0f); while(vc->isRunning());

while(true){
walldata = wall->getWall();
map.addWall(pos.getPositionX(), pos.getPositionY(), pos.getAngle(), walldata);

adachi.setMap(map);
adachi.renewFootmap();
slalomparams::RunType runtype = adachi.getNextMotion(pos.getPositionX(), pos.getPositionY(), pos.getAngle(), walldata);

if(runtype == slalomparams::RunType::TRAPACCEL){
vc->runTrapAccel(0.25f, 0.25f, 0.25f, 0.078f, 2.0f); while(vc->isRunning());
} else if(runtype == slalomparams::RunType::PIVOTTURN){
vc->runTrapAccel(0.25f, 0.25f, 0.0f, 0.045f, 2.0f); while(vc->isRunning());
vc->runPivotTurn(360, 180, 1000); while(vc->isRunning());
vc->runTrapAccel(0.0f, 0.25f, 0.25f, 0.045f, 2.0f); while(vc->isRunning());
} else if(runtype == slalomparams::RunType::SLALOM90SML_RIGHT){
vc->runSlalom(RunType::SLALOM90SML_RIGHT, 0.25f); while(vc->isRunning());
} else if(runtype == slalomparams::RunType::SLALOM90SML_LEFT){
vc->runSlalom(RunType::SLALOM90SML_LEFT, 0.25f); while(vc->isRunning());
} else {
vc->runTrapAccel(0.25f, 0.25f, 0.0f, 0.045f, 2.0f); while(vc->isRunning());
break;
}

pos.setNextPosition(runtype);

if(pos.getPositionX() == 11 && pos.getPositionY() == 11){
vc->runTrapAccel(0.25f, 0.25f, 0.0f, 0.045f, 2.0f); while(vc->isRunning());
break;
}
}
adachi.setMap(map);

壁情報を Walldata クラスでやり取りできるので,記述がなかなか楽になっています.
それから,足立法と座標管理部分と実際のセンサやモータ駆動部分を分離したため,シミュレータにそのまま移植できるはずです.

第34回マイクロマウス東日本地区大会を見に行ってきた

本年度の予定

マイクロマウスシーズンが始まりました.
今年は,これだけの大会に参加しようと思います.

日程 大会 目標(または結果)
7月10日 関西大会 スタートセンサを切った
9月11日 台湾大会 台湾旅行を楽しんだ
10月2日 東日本大会 団体特別賞をいただいた
10月9日 東北大会 ゴールする
10月16日 九州大会 最短ゴールする
10月23日 中部大会 速度を上げる
10月30日 学生大会 シードを取る
11月6日 北信越大会 賢いパスを考える
11月19・20日 全日本大会 決勝で最短ゴールする

と,なかなか旅行三昧かつ高めの目標を立てていますが,頑張っていきたいと思います.
(なんせ就活に使うには今年の大会で成績を残しておかないといけない)

他の目標

成績面での目標は挙げましたが,他に実装したい機能がいくつかあります.
どこまでできるかわかりませんが,とりあえず書き留めておきます.

  • マウス単体でのパラメータ調整
    僅かな数値調整のためにいちいちコンパイル&書き込みするのが鬱陶しいので,マウスで調整したいと思いました.
    一応案はあるので,そのうち実装したいと思っています.
  • 最短経路の時間ベース導出
    ターンに要する時間を考慮した上での最短経路を出したいと思っていました.
    ひとまずは,なんとか松先輩から聞いた方法で試そうかなと考えています.
  • 位置補正
    海外勢がやっている,壁センサのみで区画中心に戻る動作をやりたいです.
    尻当てができない形状なので,格好良くシュッと補正したいと思っています.
  • 最短経路導出プログラムのライブラリ化
    シミュレータと同じコードで動かしたいので,ライブラリにしたいです.
    動的リンクでなかなか苦しんでいるけど…….

以下完全に妄想

  • 迷路中の自己位置推定
    探索でコケた際,それまでに記録した迷路データと周囲の壁情報から自己位置が推定できそうな気がしています.
    壁情報の比較アルゴリズムの最適化と,区画中心に戻る方法をこれから考えていきたいところ.
  • 最短パラメータの自動設定
    最短パラメータを人間が設定しているのは正直イマイチ.
    ロボットを置くことと,スタートボタンを押すことだけにしたいなあと妄想中.

東日本地区大会のまとめ

今更まとめることもないと思いますが,感想はいくつか.

  • 今年のフレッシュマンは強い
    一昨年のフレッシュマン(大嘘)は詐欺です.
    それに比べ,今年のフレッシュマンは未経験からのスタートで本当に頑張ってるなと思います.クォーターではいろいろなバッヂを手に入れ,エキスパートマウスも何台か誕生しました.部室で苦しんでいる姿をよく見ますが,これからの活躍にとても期待しています!
    あ,一応補足しておきますが FND,お前はフレッシュマンじゃねえぞ (エキスパートとして応援しています)
  • 中学生すごい
    中学3年生でハーフサイズゴールをキメていました.本当にすごいと思います.(授業では加速度を習っているところだそう)
    ただでさえ難しい競技の上,ノウハウも全然ないそうなので,相当苦労されているんじゃないかと思います.是非今度遊びに来てください.
  • 副部長つよい
    しかし部長が副部長には負けてられない.
    今シーズンも頑張っていきます.

関数を作る

この記事の目的

この記事は部員向けに書いています.初めて壁情報を保存しようと頑張っている人,使いやすい関数を作りたい人,キーの打ち間違えが多くて困っている人は読んでみてください.
壁の追加関数を作りながら,関数の作り方を学習していきましょう.

この記事ではC言語の関数のみを扱います.また,あくまで僕の考えをまとめたものなので,すべての内容が正しいことは保証されません.

関数とは

関数とはなんでしょうか?
具体的なコードを見てみましょう.

1
2
3
4
5
6
7
8
int sum(int a, int b){
return a+b;
}

int main(){
int n = sum(3, 5);
printf("%d", n);
}
1
8

関数sumは,与えた2つの引数の和を返す関数です.超便利な関数ですよね!
「’+’使えばいいじゃん」
……なるほど,ではこれはどうでしょうか?

1
2
3
4
5
6
7
8
9
10
11
12
13
int sum2(int a, int b){
int i;
int w=0;
for(i=a; i<=b; ++i){
w += i;
}
return w;
}

int main(){
int n = sum2(3, 5);
printf("%d", n);
}
1
12

関数sum2は,与えた引数a以上b以下の数字の和を返す関数です.なるほどこれは便利だ!

という感じで,ある一つの機能を持った小さなプログラムが関数だと思ってください.

関数を作る

作る関数を決める

では,関数を作ってみることにしましょう.今作りたい機能は,マイクロマウスを走らせながら迷路(壁)情報を保存し,読み出すことができる機能だとします.
初めにすべきことは,機能の分割です.今言った機能はすごく大雑把な表現なので,どのような関数が必要か考えてみましょう.

壁を何らかの変数(おそらく配列)に格納すると思います.この変数に,任意の1枚の壁を追加する関数が必要そうです.同時に,この変数から任意の1枚の壁が存在するかどうかを調べる関数も必要でしょう.
というわけで,まずは1枚の壁を追加する関数を作っていきたいと思います.

さて,関数を作る時に意識すべきことは,使いやすい引数を設定することです.
壁を追加する時に,どのような命令で壁を指定すると便利でしょうか?

僕は「壁の面する座標と,壁の存在する方角」を引数にすると便利だと考えました.というわけで,関数のプロトタイプ宣言はこのようにしてみます.

1
int addSingleWall(int x, int y, int dir);

引数として追加したい壁のx,y座標,壁の存在する方角を取り,
返り値は壁の追加に成功した場合0,失敗した場合それ以外の数字を返すようにします.

そういえば,引数にある int dir は具体的にどうやって指定すればいいのでしょうか? いまいちわかりにくいので,改良しておきましょう.

1
2
3
4
5
6
7
8
enum Direction {
DIR_NORTH,
DIR_EAST,
DIR_SOUTH,
DIR_WEST
}

int addSingleWall(int x, int y, enum Direction dir);

関数を実装する

では,addSingleWallを実装していきましょう!
まずはとりあえず実装してみます.
(壁情報の持ち方については,旧ブログ記事を参照してください)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned short wall_column[15];
unsigned short wall_row[15];

int addSingleWall(int x, int y, enum Direction dir){
switch(dir){
case DIR_NORTH:
wall_row[y] |= (0x1 << (15-x));
break;
case DIR_EAST:
wall_column[x] |= (0x1 << (15-y));
break;
case DIR_SOUTH:
wall_row[y-1] |= (0x1 << (15-x));
break;
case DIR_WEST:
wall_column[x-1] |= (0x1 << (15-y));
break;
}
return 0;
}

さて,このプログラムは正しく動作するでしょうか?
もちろんお気づきかと思いますが,問題がありますね.引数値として,例えば(0, 0, DIR_WEST)を与えた場合に配列の領域外にアクセスすることになります.もしくは,(-100, 150, DIR_NORTH)などの範囲外を与えた場合にもエラーが返って来ず,恐ろしいことになりそうです.

今回気をつけなければいけなかったことは,次のようなことです.

  • 正しい処理をする
    言うまでもなく,適切な壁を追加してくれないと困ります.
  • 誤った引数が来ても問題なくする
    今回の場合,壁が追加できないような引数が来た場合には何もせずにエラーコードを返すべきです.
    誤った壁を追加したり,配列の領域外にアクセスするといったことは絶対にしないように気をつけます.

以上を踏まえた上で実装を行いました.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
unsigned short wall_column[15];
unsigned short wall_row[15];

int isInRange(int x, int min, int max){
if(x >= min && x <= max) return 1;
else return 0;
}

int addSingleWall(int x, int y, enum Direction dir){
switch(dir){
case DIR_NORTH:
if(!isInRange(x, 0, 15)) return 1;
if(!isInRange(y, 0, 14)) return 1;
wall_row[y] |= (0x1 << (15-x));
break;
case DIR_EAST:
if(!isInRange(x, 0, 14)) return 1;
if(!isInRange(y, 0, 15)) return 1;
wall_column[x] |= (0x1 << (15-y));
break;
case DIR_SOUTH:
if(!isInRange(x, 0, 15)) return 1;
if(!isInRange(y, 1, 15)) return 1;
wall_row[y-1] |= (0x1 << (15-x));
break;
case DIR_WEST:
if(!isInRange(x, 1, 15)) return 1;
if(!isInRange(y, 0, 15)) return 1;
wall_column[x-1] |= (0x1 << (15-y));
break;
default:
return 1;
break;
}
return 0;
}

壁が存在するかを読み取る関数や,他に必要と思った関数は自分で実装をしてみてください.

関数を拡張する

さて,先程作ったaddSingleWall関数を改良してみましょう.

壁情報を複数個持ちたい場合があるかもしれません.まずは壁情報であるunsigned short wall_column[15]unsigned short wall_row[15]をまとめて構造体にしておきます.

1
2
3
4
struct WallData{
unsigned short column[15],
unsigned short row[15]
}

では,安直に次のように実装してみます.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
struct WallData wd1;
struct WallData wd2;

int isInRange(int x, int min, int max){
if(x >= min && x <= max) return 1;
else return 0;
}

int addSingleWall1(int x, int y, enum Direction dir){
switch(dir){
case DIR_NORTH:
if(!isInRange(x, 0, 15)) return 1;
if(!isInRange(y, 0, 14)) return 1;
wd1.row[y] |= (0x1 << (15-x));
break;
case DIR_EAST:
if(!isInRange(x, 0, 14)) return 1;
if(!isInRange(y, 0, 15)) return 1;
wd1.column[x] |= (0x1 << (15-y));
break;
case DIR_SOUTH:
if(!isInRange(x, 0, 15)) return 1;
if(!isInRange(y, 1, 15)) return 1;
wd1.row[y-1] |= (0x1 << (15-x));
break;
case DIR_WEST:
if(!isInRange(x, 1, 15)) return 1;
if(!isInRange(y, 0, 15)) return 1;
wd1.column[x-1] |= (0x1 << (15-y));
break;
default:
return 1;
break;
}
return 0;
}

int addSingleWall2(int x, int y, enum Direction dir){
switch(dir){
case DIR_NORTH:
if(!isInRange(x, 0, 15)) return 1;
if(!isInRange(y, 0, 14)) return 1;
wd2.row[y] |= (0x1 << (15-x));
break;
case DIR_EAST:
if(!isInRange(x, 0, 14)) return 1;
if(!isInRange(y, 0, 15)) return 1;
wd2.column[x] |= (0x1 << (15-y));
break;
case DIR_SOUTH:
if(!isInRange(x, 0, 15)) return 1;
if(!isInRange(y, 1, 15)) return 1;
wd2.row[y-1] |= (0x1 << (15-x));
break;
case DIR_WEST:
if(!isInRange(x, 1, 15)) return 1;
if(!isInRange(y, 0, 15)) return 1;
wd2.column[x-1] |= (0x1 << (15-y));
break;
default:
return 1;
break;
}
return 0;
}

……どう考えても冗長なコードです(先日部員がこのようなコードを書いていて驚愕しました).
これでは満足行かないので,addSingleWallの引数を次のように工夫してみます.

1
int addSinglewall(int x, int y, enum Direction, struct WallData *);

実装すると次のような感じでしょうか.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
struct WallData wd;

int isInRange(int x, int min, int max){
if(x >= min && x <= max) return 1;
else return 0;
}

int addSingleWall(int x, int y, enum Direction dir, struct WallData *wall){
switch(dir){
case DIR_NORTH:
if(!isInRange(x, 0, 15)) return 1;
if(!isInRange(y, 0, 14)) return 1;
wall->row[y] |= (0x1 << (15-x));
break;
case DIR_EAST:
if(!isInRange(x, 0, 14)) return 1;
if(!isInRange(y, 0, 15)) return 1;
wall->column[x] |= (0x1 << (15-y));
break;
case DIR_SOUTH:
if(!isInRange(x, 0, 15)) return 1;
if(!isInRange(y, 1, 15)) return 1;
wall->row[y-1] |= (0x1 << (15-x));
break;
case DIR_WEST:
if(!isInRange(x, 1, 15)) return 1;
if(!isInRange(y, 0, 15)) return 1;
wall->column[x-1] |= (0x1 << (15-y));
break;
default:
return 1;
break;
}
return 0;
}

まとめ

関数は使いやすさが第一です.自分の使いやすい関数を作り,楽に開発を進めましょう.
機能ごとに関数を分割することでデバッグも楽になるはずです.

2016年マイクロマウス台湾大会に付き添ってきた話

出発まで

すべての元凶であるF先輩が台湾大会に行きたいと言い出し,僕とM先輩が巻き込まれました.そして大勢のマウス関係者のご協力のおかげで無事参加できることになりました.ところがどっこい,F先輩が「金ないから行かない」とか言い出したのでLCCを使った最高のプランを提案し,無事3人で参加することになりました.まったく迷惑かけさせんなよ

初めての台湾

Vanilla Airを使って格安で台湾に行くことができました.現地では,予め契約しておいたルーターのおかげでインターネットに困ることはありませんでした.

台湾で先輩たちと合流した後,ノープラン台湾旅行1日目が始まりました.僕とF先輩は全く分からなかったので,何かしらの情報を持っていたM先輩の案内のもと九份に行くことにしました.
これは九份にあったセブンイレブンの様子です.

九份でお茶を飲んだときの様子です.

台湾の交通系ICカードの写真です.

台北101でちびっているF先輩の様子です.

夜に遊びに行ったときのM先輩の様子です.

たまこまグッズを見つけてテンションが上がったときの僕の様子です.

大会

ものすごい坂道に建っている大会会場に到着しました.
無事に二人とも最短走行が成功してよかったです.僕も頑張らないと.

帰国

最終日はマウス関係者の皆さんに誘われて淡水観光をしました.どでかい川があったり,アニキ撮影会があったり,よくわからない文化遺産があったりして,とても楽しかったです.

今回ご協力いただいた皆さん,本当にありがとうございました.これに懲りず,また参加しようと目論んでいるのでそのときはよろしくお願いします.
無謀な計画に付き合ってくれたみなさん,ありがとうございました.僕は全く反省していないので,どんどん連れ回しますがお付き合いください.