Visual Studioを始めて約半年。思うこと書いてく
まえがき
タイトルの通り、いろいろ思うことがあったので書きます。c++歴は半年ですが、プログラミング自体は高校2年生のときに授業でvbaをやってから勉強しました。
この記事に登場するコードは自由にコピペしてもらって構いません。まだ未熟者ですので間違ってたり、より効率的なのがあったら是非教えてください。
第Ground章 アドカレ
これは千葉大学の CCS Advent Calendar 2022 の5日目の記事でござる。
昨日の記事
夕 さんの記事。
めっちゃ絵上手い。設定がすごい凝ってる。トンボちゃんかわいい。もう擬人化されてないものない説。
明日の記事
「」と俺 さんの記事。
全部うまそう。腹減ってきた。あれ、麺類多くないすか。あれ、酒も多くないすか。
第一章 発見したこと
第一節 押下
さて、キーボード押下を検出するためにどうしていますか?
CheckHitKey(KEY_INPUT_RETURN)だと0と1しか返ってきませんよね!押した瞬間とか押してる時間を検出したいですよね!よね!
そこで私が使っているのがこのコード!
if (Buf[KEY_INPUT_RETURN] == 1) {
if (INPUT_RETURN <= 0) {
INPUT_RETURN = 1;
}
else {
INPUT_RETURN++;
}
}
else {
if (INPUT_RETURN >= 0) {
INPUT_RETURN = -1;
}
else {
INPUT_RETURN--;
}
}
INPUT_RETURNという変数が、enterが押された瞬間に1になり、押されている間は毎フレーム1が足されます。逆に、enterが離された瞬間に-1になり、離されている間は毎フレーム1が引かれるという仕様です。
尚、繰り返し前に
char Buf[256];//押下取得
int INPUT_RETURN = 0,
INPUT_R = 0,
INPUT_LCLICK = 0;//押下
のように定義して、繰り返しの中の最初に
GetHitKeyStateAll(Buf);
としておく必要があります。
でもキーごとにこの節の最初の長いコード書くの大変では?って思った人ォ!
if (Buf[KEY_INPUT_RETURN] == 1) { if (INPUT_RETURN <= 0) { INPUT_RETURN = 1; } else { INPUT_RETURN++; } }
else { if (INPUT_RETURN >= 0) { INPUT_RETURN = -1; } else { INPUT_RETURN--; } }
圧縮すればよかろうなのだァァァァ!!
え?インデント揃えないとかっこ悪い?
羅列するときはこっちの方が綺麗だと思わんかね!
え?一つ一つ書き換えるのは面倒?そんな人は次の第二節を見たまえ!
第二節 文字置き換え
プログラム書いてると時々置き換えなどの単純作業が出てきて、プログラムを書くプログラムが欲しいな~なんて思うことも多いと思います。
そんなときはこれ!私が発明した超☆便利なコードを見るがいい!
#include<iostream>
#include<math.h>
#include<string>
#include<cstdlib>
using namespace std;
int main() {
string beforechange[100];//置き換え前
string mainafterchange[1000] = { "0" };//メイン置き換え
string subafterchange[10][1000] = { "0" };//サブ置き換え
int position;//文字列内位置
int linecounter;//文字列カウント
int mainchangecounter;//メイン置き換え数
int subchangecounter;//サブ置き換え数
int beforechangecounter;//置き換え前数
string inputline[1000] = { "0" };//入力文字列
string outputline[1000] = { "0" };//出力文字列
if(true){
//初期化
linecounter = 1;
mainchangecounter = 1;
subchangecounter = 1;
beforechangecounter = 1;
//入力
if (true) {
cout <<endl<< "元の文字列を入力 finishinput で入力終了" << endl;
while (true) {
getline(cin, inputline[linecounter]);
if (inputline[linecounter] == "finishinput")break;
linecounter++;
}
cout << endl << "何から? finishinput で入力終了" << endl;
getline(cin, beforechange[0]);
while (true) {
getline(cin, beforechange[beforechangecounter]);
if (beforechange[beforechangecounter] == "finishinput")break;
beforechangecounter++;
}
cout << endl << beforechange[0] + " を何に?(メイン) finishinput で入力終了" << endl;
while (true) {
getline(cin, mainafterchange[mainchangecounter]);
if (mainafterchange[mainchangecounter] == "finishinput")break;
mainchangecounter++;
}
for (int i = 1; i <= beforechangecounter - 1; i++) {
cout << endl << beforechange[i] + " を何に?(サブ)" << endl;
for (int j = 1; j <= mainchangecounter - 1; j++) {
getline(cin, subafterchange[i][j]);
}
}
}
//出力
if (true) {
cout << "-------出力開始-------" << endl << endl;
for (int k = 1; k <= mainchangecounter - 1; k++) {
for (int i = 1; i <= linecounter; i++) {
outputline[i] = inputline[i];
}
for (int i = 1; i <= linecounter - 1; i++) {
//置き換えメイン
position = 1;
if (outputline[i].length() >= beforechange[0].length()) {
while (position + beforechange[0].length() - 1 <= outputline[i].length()) {
if (outputline[i].substr(position - 1, beforechange[0].length()) == beforechange[0]) {
outputline[i] = outputline[i].substr(0, position - 1) + mainafterchange[k] + outputline[i].substr(position + beforechange[0].length() - 1);
position += mainafterchange[k].length();
}
else {
position++;
}
}
}
//置き換えサブ
for (int j = 1; j <= beforechangecounter - 1; j++) {
position = 1;
if (outputline[i].length() >= beforechange[j].length()) {
while (position + beforechange[j].length() - 1 <= outputline[i].length()) {
if (outputline[i].substr(position - 1, beforechange[j].length()) == beforechange[j]) {
outputline[i] = outputline[i].substr(0, position - 1) + subafterchange[j][subchangecounter] + outputline[i].substr(position + beforechange[j].length() - 1);
position += subafterchange[j][subchangecounter].length();
}
else {
position++;
}
}
}
}
cout << endl << outputline[i];
}
subchangecounter++;
}
cout << endl << endl << endl << "-------出力終了-------" << endl;
}
}
return 0;
}
いや~美しいですね。このコードで何ができるかというと
これ。分かります?ここまで読み進められた聡明な読者なら分かると思いますが、簡単に言うと文字置き換え。元の文字列を入力して、置き換えたい前の文字(何から)を入力して、置き換えたい後の文字(何に)を入力すればあら不思議!簡単に!増殖できる!
このコード、結構複雑で、作るのに一週間くらいかかりました。マジで。地道に単純作業するのと比べてどっちが速かったんでしょうね。まあこれから取り返しますが。
第三節 中央揃え
画面に文字を表示するときに中央揃えしたいときありますよね!そこでこのコード!
DrawStringToHandle(windowwidth / 2 - 51 * word.length() / 4, 150, word.c_str(), GetColor(0, 0, 0), font50);
windowwidthには画面横のピクセル数をあらかじめ代入してください。wordっていうのはstring型の変数でこれに表示したい文字を代入しておきます。150って書いてあるのはy座標なので自由に動かしてください。font50という変数は、繰り返し前に
int font10 = CreateFontToHandle(NULL, 10, -1);//文字サイズ10
int font30 = CreateFontToHandle(NULL, 30, -1);//文字サイズ30
int font50 = CreateFontToHandle(NULL, 50, -1);//文字サイズ50
こんな感じで文字サイズ(ピクセル数)を定義しておけばOK。尚、例えばfont50をfont30にしたい場合はこの節の最初のコードの51って書いてあるのを31にしてください。font10にしたい場合は51を11にする。
第四節 変数名
変数名、どう書いてます?参考になるのは
これ。ソートしやすいようにして分かりやすく、英語名にするべし。このチャンネル、めtttttっちゃ参考になるので見てない人は今すぐ全部見てください。
ただ、私は変数名のイニシャルは小文字にしてますね。いちいちShift押したくないので。
第五節 四則演算
四則演算ですよ。1+1=2ですよ。2+2=4ですよ。4+4=8で(ry
intの上限ってたったの2,147,483,647なんですよね。少なすぎませんか?いや十分多いやろと思ったそこの君ィ!フィボナッチ数(0,1,1,2,…)の100項目、分かるか?分かんないよなァ!正解は218922995834555169026だッ!
え?コード見たい?しょうがないなぁ。整数の足し算だけだよ。ほれ。ちょっくら難しいから解説はしないよ。
string plus4(string pl1, string pl2) {
string ans;//出力
string tmppl1="0", tmppl2="0";
string kuri="0";//繰り上がり
string tmpans;
if (pl1.substr(0, 1) == "-" && pl2.substr(0, 1) == "-") {
ans = "-" + plus4(pl1.substr(1), pl2.substr(1));
}
else if (pl1.substr(0, 1) != "-" && pl2.substr(0, 1) == "-") {
ans = minus1(pl1, pl2.substr(1));
}
else if (pl1.substr(0, 1) == "-" && pl2.substr(0, 1) != "-") {
ans = minus1(pl2, pl1.substr(1));
}
else {
if (pl1.length() < pl2.length()) {
swap(pl1, pl2);
}
while (pl1.length() != pl2.length()) {
pl2 = "0" + pl2;
}
while (pl1.length() >= 9) {
tmppl1 = pl1.substr(pl1.length() - 8);
tmppl2 = pl2.substr(pl2.length() - 8);
while (tmppl1.substr(0, 1) == "0") {
tmppl1 = tmppl1.substr(1);
}
tmppl1 = to_string(atoi(tmppl1.c_str()) + atoi(kuri.c_str()));
while (tmppl2.substr(0, 1) == "0") {
tmppl2 = tmppl2.substr(1);
}
tmpans = to_string(atoi(tmppl1.c_str()) + atoi(tmppl2.c_str()));
if (tmpans.length() == 9) {
kuri = tmpans.substr(0, 1);
tmpans = tmpans.substr(1);
}
else {
kuri = "0";
}
while (tmpans.length() != 8) {
tmpans = "0" + tmpans;
}
ans = tmpans + ans;
pl1 = pl1.substr(0, pl1.length() - 8);
pl2 = pl2.substr(0, pl2.length() - 8);
}
//最後
while (pl2.substr(0, 1) == "0" && pl2.length() != 1) {
pl2 = pl2.substr(1);
}
tmpans = to_string(atoi(pl1.c_str()) + atoi(pl2.c_str()) + atoi(kuri.c_str()));
ans = tmpans + ans;
}
return ans;
}
この四則演算に夏休みを持ってかれたんだよなぁ。第一種カニンガム鎖とかコラッツ予想とか。結局解けなかったな。
第六節 私のコードフォーマット(DxLib)
#include "DxLib.h"
#include<iostream>
#include<string>
#include<math.h>
using namespace std;
int windowwidth = 720;//画面横
int windowheight = 480;//画面縦
//メイン
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
SetOutApplicationLogValidFlag(0);
SetMainWindowText("DxLib");
ChangeWindowMode(true);
SetGraphMode(windowwidth, windowheight, 16);
if (DxLib_Init() == -1) { return -1; }
SetDrawScreen(DX_SCREEN_BACK);
//宣言
int moux, mouy;//マウス位置 SetMouseDispFlag(TRUE);
int beforemoux, beforemouy;//前マウス位置
int frame = 0;//フレーム
int scene = 1;//シーン
char inputkeybool[256];//押下取得
int inputkey[256];//キー押下
int INPUT_LCLICK = 0, INPUT_RCLICK = 0;//マウス押下
for (int i = 0; i <= 255; i++)inputkey[i] = 0;//押下初期値
string word;//表示文字 DrawStringToHandle(windowwidth / 2 - 51 * word.length() / 4, 150, word.c_str(), GetColor(255, 255, 255), font50);
int font10 = CreateFontToHandle(NULL, 10, -1);//文字サイズ10
int font20 = CreateFontToHandle(NULL, 20, -1);//文字サイズ20
int font30 = CreateFontToHandle(NULL, 30, -1);//文字サイズ30
int font50 = CreateFontToHandle(NULL, 50, -1);//文字サイズ50
//画像読み込み int gazou = LoadGraph("resources/images/gazou.png"); DrawRotaGraph(100, 100, 1.0, 0, gazou, TRUE, FALSE); LoadGraphScreen(0, 0, "resources/images/gazou.png", TRUE);
//音読み込み int oto = LoadSoundMem("resources/sounds/oto.mp3"); PlaySoundMem(oto, DX_PLAYTYPE_BACK, TRUE); StopSoundMem(oto);
//繰り返し開始
while (ProcessMessage() == 0 && CheckHitKey(KEY_INPUT_ESCAPE) == 0) {
ClearDrawScreen();
GetMousePoint(&moux, &mouy);
//押下
if (true) {
GetHitKeyStateAll(inputkeybool);
if ((GetMouseInput() & MOUSE_INPUT_LEFT) != 0) { if (INPUT_LCLICK <= 0) { INPUT_LCLICK = 1; } else { INPUT_LCLICK++; } }
else { if (INPUT_LCLICK >= 0) { INPUT_LCLICK = -1; } else { INPUT_LCLICK--; } }
if ((GetMouseInput() & MOUSE_INPUT_RIGHT) != 0) { if (INPUT_RCLICK <= 0) { INPUT_RCLICK = 1; } else { INPUT_RCLICK++; } }
else { if (INPUT_RCLICK >= 0) { INPUT_RCLICK = -1; } else { INPUT_RCLICK--; } }
for (int i = 0; i <= 255; i++) {
if (inputkeybool[i] == 1) { if (inputkey[i] <= 0) { inputkey[i] = 1; } else { inputkey[i]++; } }
else { if (inputkey[i] >= 0) { inputkey[i] = -1; } else { inputkey[i]--; } }
}
}
//本文
if (true) {
//タイトル
if (scene == 1) {
LoadGraphScreen(0, 0, "resources/images/title.png", TRUE);
if (inputkey[KEY_INPUT_RETURN] == 1) {
scene = 2;
}
}
//マップ
else if (scene == 2) {
}
//ゲーム画面
else if (scene == 3) {
}
//クリア
else if (scene == 4) {
}
//ゲームオーバー
else if (scene == 5) {
}
}
frame++;
beforemoux = moux;
beforemouy = mouy;
ScreenFlip();
}
DxLib_End();
return 0;
}
第二章 嘆き
第一節 DxLibでしかできないこと①ランダム
GetRand()ってDxLibじゃないとできないんですね。
std::rand()って問題点多くないですか?なんか難しいし。時間でseed変えようとしたけど謎バグでできないし。
余談ですが、私はランダムなんて存在しないと思ってますね。ハイゼンベルクよりアインシュタイン派です。ランダムで計算したら上手くいったってだけで、速すぎて観測できないだけだと思います。あ、この話続けると長くなるのでここらへんでやめときますね。
第二節 DxLibでしかできないこと②バイト読み
IsDBCSLeadByteで文字のバイトを読むのにもDxLibが必要なんですね(違ったらスマソ)。マルコフ連鎖で文章作ろうと思ったけどIsDBCSLeadByteがめっちゃ難しいから挫折しました。文字列操作はただでさえ難しいのに全角と半角でバイト数が違うって何事ですか。頭パンクしますよ。
第三節 ポインタ?なにそれおいしいの?
ポインタってさ~言葉自体は知ってるんだよ。変数の住所でしょ?だけどどこで使えばいいの?わかんないよ…。(ドンッ!)わっかんないよ!ジュー君の言ってr
まあ私が全部メイン関数に書くのが悪いのかもしれんが(多分そう)。にしても、返り値が*charになる関数が多いんですよね。変換が面倒。なにこれ。
話変わるけどcharもさぁ、シングルクォーテーションの意味が分からない。キー押下選別子とか、どの数字を表してるんだ。
第四節 構造体って要らなくね?
構造体、要ります?定義が面倒。配列で良いと思います。
第五節 exeが保護される件
自作のゲームを友達に送っても
これとか
これとか出てくるし。いちいち友達に説明するのが面倒。
第六節 abort() has been called.
こいつに何度苦しめられてきたことか。分かる人には分かる。どこが間違ってるのか教えてください。本当に。
第七節 AtCoderがむずい・むずずず・ムズスンギ
完全に数学の問題。数学なんてもう忘れちゃったよ。疲れ果てた後に答えを見て解読する気力がないよ。負の連鎖。
第八節 変数を組み立てられない
moji = "resources/data/" + musictitletext[1] + ".txt";
fileName = moji.c_str();
ifstream ifs(fileName);
こういうのはいいんだよ。"resources/data/" + musictitletext[1] + ".txt"とかで組み立てられる。だけど変数は組み立てられない。配列で指定しても多分駄目だよね。まあしょうがないか。選別子も組み立てられない。
第九節 宣言を折りたたみたい
いつも、ある程度のまとまりになったらif(true)で囲って折りたたみ(最小化?)してるんだけど、宣言部分を折りたたんだらスコープが終わっちゃうから、宣言部分が長い時に困る。いちいちスクロールしなきゃならない。どうにかして折りたたみたいなあ。
第十節 謎バグ
なにこれ。
なんかインデント揃わない。
あとグローバル変数が白色になるのはどうして?分かりづらい。あと60fpsか120fpsか検知するにはどうすれば…
第±節 "プログラミング"が幼稚になった気がする
これとか、『ナビつき! つくってわかる はじめてゲームプログラミング』とか、なーんかね。まあプログラミングは教育としては良いんだろうけどさ。「貴様」とか「お前」とかと同じ変遷を辿ってる気がする。最初は丁寧な言葉だったのに大衆に知れ渡ると格が落ちる感じ。なんか新しい言葉欲しいな。コーディング?はちょっと違うか。
あと関係ないけど「プログラマー」じゃなくて「プログラマ」の方が表記が好みです。他にも「コンピュータ」とか「メモリ」とか。
あとがき
こんな文章をここまで読んでくれるなんて感謝の極み。書いてあることが狭すぎて何書いてあるか分からないと思います。とりあえず書きたいことは書けた。もっと良いコードや嘆きの解決法あったら教えてくんろ。