注意:現在最適化中につき、まだ未公開です。近日公開予定です。

 

概要


FakeHSP for C++は、SDL2を使用したC++上でHSP3(Hot Soup Processor)となるべく同じ文法で、同じような動作をすることを目的とし、作られた関数群です。

FakeHSP.hをC++でincludeすることによって使用できます。

関数名はなるべくHSPに似せており、引数の順序等もなるべくHSPに近づけています(一部除く)。

オブジェクト指向のC++上で動くので、クラスが使えるHSPとも言えます。

 

SDL2を使用しているため、C++の開発環境に加え、SDL2の導入が必須です。

FakeHSPでは、以下のSDL2ライブラリを使用しています:SDL2, SDL2_mixer, SDL2_image, SDL2_ttf, SDL2_net

 

動作確認済み環境


以下の環境で動作を確認済みです。

OS Windows 10 Home ver.1803 (64bit) / macOS 10.14 mojave (64bit)
統合開発環境 JetBrains CLion 2018.3
CMake 3.12.3
ツールチェイン(Windowsのみ必要) MinGW w64 6.0
C++ 14
SDL2 2.0.9
SDL2_mixer 2.0.4
SDL2_image 2.0.4
SDL2_ttf 2.0.14
SDL2_net 2.0.1

 

 

 

 

 

 

 

 

使用準備


まずはC++の開発環境、およびSDL2を導入してください。

(ここらへんは後日掲載します)

 

導入したら、FakeHSPを導入してみましょう。

#include “FakeHSP.h”;

 

これでFakeHSPの導入は完了です。

FakeHSPのすべての関数は名前空間「hsp」に含まれます。

関数やクラスを呼び出す際は、必ず関数名・クラス名の前に「hsp::」を

hsp::window mainWindow;

 

のように名前空間を指定するか、プログラムの冒頭に

using namespace hsp;

 

と記述してください。

using namespaceを記述した場合は、関数・クラス呼び出し時に「hsp::」を記述する必要はありません。

 

なお、macOS 10.14 mojaveで使用する際は、SDL2特有のバグを避けるため、main関数の冒頭に以下の行を追加してください:

SDL_PumpEvents();

これを行わないと、ウィンドウが真っ黒なままで何も描画されません。いわばおまじないみたいなものです。

SDL_PumpEventsは、10.13以前のmacOSやWindowsで行っても、特に問題はありません。

 

 

FakeHSPのクラス一覧


クラス名 概要
window ウィンドウへの描画に関するクラス。一つのクラスにつき一つのウィンドウを対象とするため、

複数のウィンドウを使用する際はHSP_Windowクラスを複数呼び出す必要があります。

mixer 音楽ファイルに関するクラス。mm系の関数はこちらに含まれます。
net インターネット接続に関するクラス。netURLなどが含まれます。
note テキストの読み込み、取得、保存など。note系の関数が含まれます。

 

 

 

関数一覧


現在実装済みの関数の一覧です。現在56個あります。

独自関数を除き、関数名はHSPの命令名とほとんど同じですが、C++の命名規則(の一派)に従い、単語の区切れ目は大文字にしています(例:celLoad)。

 

実装済みの関数

各項目の説明をします。

関数名 関数の名前です。
クラス その関数を含んでいる関数です。「なし」と書かれている関数は、そのまま呼び出すことができます(例:hsp::exist(“ABC.txt”);)。
概要 その関数の機能を説明しています。
その関数の記述例です。
HSPでの命令名  本物のHSPではどのような名前の命令なのかを示します。HSPでは大文字・小文字を区別しないため、すべて小文字で記述しています。

 

関数名の項目は、以下のように分けています:

太字:主要な関数(独断と偏見による)

普通:使うこともあるし使わないこともある関数

薄字:ほとんど使うことはない関数(FakeHSP.h内でしか使わないような関数)

 

説明が下手くそなので、本物のHSPをある程度習得してから読むことをおすすめします。

 

関数名 クラス 概要 HSPでの命令名
boxf window 塗りつぶされた四角形を表示します。塗りつぶす色はcolor関数で指定します。

引数を省略すると、画面全体が塗りつぶされます。

1)boxf (0, 0, 100, 100); // 座標(0,0)から座標(100,100)まで四角形で塗りつぶされる

2) boxf(); // 画面全体が塗りつぶされる

boxf
boxfA window 半透明色で塗りつぶされた四角形を表示します。塗りつぶす色、および半透明度はcolor関数で指定します。

引数を省略すると、画面全体が塗りつぶされます。

1) boxfA (0, 0, 100, 100); // 座標(0,0)から座標(100,100)まで半透明の四角形で塗りつぶされる

2) boxfA(); // 画面全体が半透明で塗りつぶされる

なし(独自関数)

boxfの改造版

buffer window バッファ番号を指定します。buffer関数が呼び出されて以降の描画処理は、すべて指定したバッファ領域に描画されます。

gSel (0);を呼び出すことで、再びウィンドウの描画されるようになります。

buffer(1); // 描画先にバッファ番号1のバッファ領域を選択 buffer
button window 疑似オブジェクトとしてボタンが表示されます。通常はcolor関数で指定した色でボタンが塗りつぶされます。

objImageで画像が指定されている場合、その画像をボタンの背景画像として用います。

戻り値として、設置されたボタンのIDが返されます。

button (“ボタン”); // 「ボタン」と書かれたボタンが表示される button
buttonGetInfo window ボタンの上にマウスカーソルがあるか否かを戻り値として返します。 buttonGetInfo(1) // ID 1のボタンの上にカーソルがあれば1, なければ0を返す なし(独自関数)
buttonNextID window 次に生成されるボタンのIDを返します。button関数内で使われ、通常は使用しません。 buttonNextID(); なし(独自関数)
celLoad window 画像をバッファ領域に読み込みます。バッファ領域に読み込むことで、同じ画像を複数回表示させるときに、何度も画像を読み込む必要がなくなります。

バッファ領域に読み込んだ画像は、gCopy関数やgZoom関数で表示できます。

また、objImage関数でバッファIDを指定し、button関数でボタンを表示させることで、画像を背景としたボタンが表示できます。

第3引数以降にRGB値を指定すると、そのRGB値のピクセルは透過されます。

1) celLoad (“image.bmp”, 1) // バッファID1にimage.bmpが読み込まれる

2) celLoad (“image.bmp”, 1, 0, 0, 0); // RGB値(0,0,0)が透過されバッファID1にimage.bmpが読み込まれる

celload
closeMixer mixer SDL2_Mixerを終了します。

mixerクラスに含まれるmm系関数を使用している場合は、必ずウィンドウを閉じる際(終了時)に呼び出してください。

closeMixer(); なし(独自関数)
closeWindow window SDL2全体を終了します。

ウィンドウを閉じる際に使用してください。

stop関数やwait関数が実行されている間にウィンドウを閉じるボタンが押された場合は、

stop関数内またはwait関数内でcloseWindowが呼び出されます。

closeWindow(); なし(独自関数)

endに類似

cls window 画面をクリアします。

color関数で指定した色で画面全体が塗りつぶされ、ボタンなどのオブジェクトも削除されます。

cls(); cls
cMes window 文字列を中央寄せで表示させます。中央寄せの基準値、つまり左上の座標はpos関数で指定します。

第4引数は描画モードの指定で、0を指定するときれいな文字列の描画、1を指定すると通常の文字列の描画になります。モード0の場合は文字列がきれいに描画されますが、モード1に比べ動作が遅くなります。

第4引数を省略すると、モード0で描画されます。また、第3引数を省略すると、y方向の範囲は、文字の大きさに合わせ自動で調節されます。

cMes (“あいうえお”, 100, 30) // pos関数で指定したカレントポジションから(100,30)の範囲内で中央寄せで「あいうえお」と表示 なし(独自関数)

mesの改造版

color window 色をRGB値で指定します。引数はR, G, Bの順です。

color関数で指定した色は、boxf、mes、cMes、buttonなど、様々な関数で使用されます。

color (0, 162, 232) // RGB(0,162,232)の色を指定(水色) color
colorA window 色をアルファチャンネル付きRGB値で指定します。引数はR, G, B, Aの順です。

colorA関数で指定した色は、boxfAなどで使用されます。

color (0, 162, 232, 128) // 透過度128で、RGB(0,162,232)の色を指定(水色半透明) なし(独自関数)

colorの改造版

convStringToCharP なし string型から*char型に変換します。 convStringToCharP(“あいうえお”); なし(独自関数)
contain なし 文字列に検索文字列が含まれるか判定します。 <string>contain(“ABCDEFG”, str) // string型変数strに”ABCDEFG”が含まれるか判定 なし(instrに似た動作)
dialog window ダイアログを表示します。第1引数に文字列を、第2引数にダイアログのモードを、第3引数にダイアログのタイトルを指定します。

モード0のとき情報ダイアログで、モード1のとき警告ダイアログです。

dialog (“こんにちは”, 0, “ダイアログです”); // タイトル「ダイアログです」、内容「こんにちは」の情報ウィンドウを表示 dialog
exist なし ファイルの有無を確認します。 exist(“ABC.txt”) // ABC.txtが存在するか判定 exist
font window 文字列を表示する際のフォントを指定します。文字列を表示する際は、必ず指定してください。 font (“sampleFont.otf”, 14); // sampleFont.otfのフォントをサイズ14で使用 font
gCopy window バッファ画像を表示します。主にbuffer関数やcelLoad関数で登録したものが表示できます。 gCopy (1, 10, 0, 100, 100); // バッファ番号1の画像の座標(10,0)から(100,100)までを表示 gcopy
getEvent window SDL2のプライベートメンバ ev を返します。通常は使用しません。 getEvent(); なし(独自関数)
getRender window SDL2のRendererを返します。通常は使用しません。 getRender(); なし(独自関数)
getStringSize window 文字列を描画する際に使用する領域の大きさを指定します。

第1引数に文字列、第2引数に縦(h) / 横(w)どちらの大きさを返すかを指定します。

getStringSize (“あいうえお”, ‘w’); // 「あいうえお」と表示した際の文字列の横幅を返す

getStringSize (“あいうえお”, ‘h’); // 「あいうえお」と表示した際の文字列の縦幅を返す

なし(ginfoのモード14, 15に近い)
gInfo window ウィンドウやマウスなどの状態を返します。第1引数にはモードを指定します。

モード0 : マウスのx座標

モード1 : マウスのy座標

モード3 : ウィンドウのウィンドウID

モード4 : ウィンドウの左上のx座標

モード5 : ウィンドウの左上のy座標

モード6 : ウィンドウの右下のx座標

モード7 : ウィンドウの右下のy座標

モード10 : ウィンドウの大きさ(横)

モード11 : ウィンドウの大きさ(縦)

gInfo(10); // ウィンドウのx座標が返されます。 ginfo
gSel window 描画先のバッファ番号を指定します。

0を指定すると、ウィンドウ自体が描画先に指定されます。

gSel(1); // バッファ番号1の画像に描画 gsel
gZoom window バッファ画像を拡大・縮小して表示します。主にbuffer関数やcelLoad関数で登録したものが表示できます。 gZoom (200, 200, 1, 10, 0, 100, 100); // バッファ番号1の画像の座標(10,0)から(100,100)までを(200,200)に拡大して表示 gzoom
init window 起動時の処理で、SDL2, SDL2_image, SDL2_ttfを呼び出します。

HSP::windowクラスを宣言したときに、コンストラクタ内で自動的に呼び出されますので、メインプログラム内で使うことはほぼありません。

init(); なし(独自関数)
line window 線を描画します。 line (10, 10, 30, 40); // 座標(10,10)から(30,40)に向かって線を引く line
mes window 文字列をウィンドウ上に描画します。

第2引数に0を指定するときれいな文字列の描画(遅い)、1を指定すると通常の文字列の描画(早い)になります。

第2引数を省略するとモード0になります。

mes(“あいうえお”); // あいうえお と表示される mes(printも同様)
mmLoad mixer 音楽ファイルを読み込みます。対応形式はwavです。第1引数にはファイルパスを指定します。第2引数には読み込み先IDを指定します。

第2引数を省略すると、自動的にIDが割り当てられます。

mmLoad (“test.wav”, 1); // ID 1にtest.wavを読み込み mmload
mmPlay mixer mmLoadで読み込んだ音楽ファイルを再生します。第1引数にIDを指定します。第1引数を省略すると、最もIDが大きいファイルが再生されます。 mmPlay (1); // ID 1の曲を再生 mmplay
mmSet mixer HSP_Mixerのコンストラクタで使用します。引数はありません。 mmSet(); なし(独自関数)
netGet net インターネット上のファイルから文字列を読み込みます。

第1引数には読み込む元のファイル名、第2引数には読み込む文字列の長さを指定します。読み込んだ文字列を返します。

第2引数を省略すると、文字列の長さは1024になります。

netGet (“index.html”, 512); // index.htmlの先頭から512バイト分を読み込む なし(独自関数)
netTerm net インターネット接続を切断します。引数はありません。 netTerm(); netterm
netURL net インターネット上の接続先のURLを指定します。

第1引数にはURLを、第2引数にはポート番号(httpの場合は大抵80)を指定します。第2引数を省略すると、ポート番号は80に指定されます。

netURL (“https://google.com/”, 80); // https://google.com/のポート80に接続 neturl
noteGet note noteSelで指定したバッファから、第2引数に指定された行を一行分読み込み、第1引数で指定したstring型の文字列に書き込みます。 netGet (str, 1); // バッファstrの2行目を読み込み netget
noteLoad note テキストファイルを読み込み、noteSelで指定したバッファに内容を書き込みます。 netLoad (“ABC.txt”); // ABC.txtを読み込み、バッファに書き込む netload
noteSave note noteSelで指定したバッファの内容をファイルに保存します。 netSave (“ABC.txt”); // ABC.txtにバッファの内容を書き込む netsave
noteSel note 対象となる変数バッファを指定します。

指定した後は、noteLoad, noteGet, noteSaveでは指定した変数バッファ上で操作します。

変数バッファはstring型で、C++の性質上、予め変数バッファを宣言する必要があります。

netSel (str); // 変数バッファにstrを指定 netsel
objClear window 画面上にあるオブジェクト(ボタンなど)をすべて削除します。引数はありません。 objClear(); clrobj
objEnable window IDによって指定したオブジェクト(ボタンなど)を無効化または有効化します。第2引数はfalseで無効化、trueで有効化です。

無効化すると、ボタンは表示されても操作できなくなります。

objEnable(1, false); // ID 1のオブジェクトを無効化 objenable
objImage window オブジェクト(ボタンなど)に使う画像を指定します。画像はbufferまたはcelLoadによって読み込まれた画像のみ使えます。

呼び出されると、その後設置したすべての対応オブジェクトに適用されます。

−1を指定すると、設定がリセットされます。

objImage(1); // ID 1の画像をオブジェクトに使う画像に指定 objimage
objSize window オブジェクト(ボタンなど)の大きさを指定します。 objSize(100, 20); // ボタンなどの大きさを(100, 20)に指定 objsize
picLoad window 画像を読み込んで表示します。 picLoad(“./img/picture.bmp”); // imgフォルダ内のpicload.bmpを表示 picload
point window 点を描画します。 point(1, 1); // 座標(1, 1)に点を描画する point
pos window 座標を指定します。指定したあと、画像やボタンは指定された座標を左上として設置します。 pos(10, 10); // 座標を(10, 10)に指定する pos
randomize なし 乱数を不定にします。 randomize(); randomize
reDraw window 画面に描画、または画面を初期化します。 reDraw(0); // 画面を初期化する

reDraw(1); // 画面に描画する

redraw
setSurfaceOnBuffer window バッファに合成要素のサーフェスを追加(合成)します。 setSurfaceOnBuffer(surface); なし(独自関数)
setRectOnBuffer window バッファに合成要素の図形を追加(合成)します。第二引数が”normal”のとき通常、”alpha”のとき半透明合成します。 setRectOnBuffer(rectP, “normal”); なし(独自関数)
split なし 文字列を区切ります。区切られた文字列はvector型で出力されます。

また、独自機能として、区切られた文字列のうち特定の番号の要素を出力する際は、その番号を第3引数に渡します。

split (“あいう/えお”, “/”); // 出力 [あいう][えお]

split (“あいう/えお”, “/”, 1); // 出力 えお

split
stop window 一時停止機能を含む終端処理です。ウィンドウを閉じるボタンの操作待ちを行います。ボタンが押されたら、ボタンのIDを返します。 stop(); stop
title window ウィンドウのタイトルを設定します。string型で指定してください。 title(“FakeHSP Window”); // ウィンドウのタイトルを「FakeHSP Window」に設定 title
wait なし ミリ秒単位で動作を一時停止します。

windowWait関数と異なる点は、ただ単純にウェイト処理を行うだけで、他の動作は一切しないことです。

ボタンIDを取得したり、ウィンドウの状態を取得したい場合は、windowWait関数をお使いください。

wait(100); // 約100ミリ秒(1秒)間一時停止 wait
waitLoop window stop関数およびwindowWait関数から呼び出されます。

ウェイト処理中のウィンドウの操作、およびボタンの操作を検知します。

ウィンドウを閉じるボタンの操作待ちを行います。ボタンが押されたら、ボタンのIDを返します。

waitLoop(true); wait
width window ウィンドウサイズを変更します。 width(320, 480); width
windowWait window ミリ秒単位で動作を一時停止します。

ウィンドウを閉じるボタンが押されたらウィンドウを閉じます。ボタンが押されたら、ボタンのIDを返します。

上記のような処理を行っているため、環境によってはwait関数に比べると若干遅延が発生するかもしれません。

wait関数と異なる点は、ウェイト処理を行うだけでなく、ウィンドウを閉じる処理や、ボタンのIDを返すことです。

windowWait(100);// 約100ミリ秒(1秒)間一時停止 wait

 

 

未実装の関数

HSPの思いつく限りの命令で未実装のものは以下のとおりです。

HSP 命令名 言い訳理由
input SDL2での実装が難しい。でもできないこともないので今後実装予定
circle SDL2に円を描く機能がない。ぶっちゃけ記号文字「○」や「●」の表示で代用可
celdiv 特になし。今後実装予定
gsquare 実装方法考えただけでめまいがする…
noteadd 特になし。今後実装予定
bload, bsave C++でバイナリ操作をしたことがないから。面白そうなのでやってみたい気もする
repeat, loop for文で代用可、そもそも関数で実装不可
print mesと同じなので

 

 

定数の一覧


FakeHSPで扱う定数を紹介します。これらの定数は、予めFakeHSP.hにて登録してあります。

定数名 意味 主に使う関数や用途
HSP_WINDOW_CLOSE -1 ウィンドウを閉じるボタンが押された stop

ウィンドウを閉じるボタンが押されたことを検知するために使う

HSP_WINODW_CHANGED_SIZE -2 ウィンドウの大きさが変更された stop

ウィンドウの大きさが変更されたことを検知するために使う

HSP_FUNC_SUCCESED 0 関数内の処理に成功した、あるいはファイルが存在することを示す exist -> ファイルが存在する

mmLoad -> ファイルの読み込みに成功

mmPlay -> ファイルの再生に成功

netURL -> IPアドレスの指定に成功

screen -> ウィンドウの作成に成功

celLoad -> 画像ファイルの読み込みに成功

mes, cMes -> 文字の描画に成功

dialog -> ダイアログの表示に成功

gCopy -> バッファ画像の描画に成功

picLoad -> 画像の描画に成功

line -> 線の描画に成功

HSP_FUNC_FAILED -1 関数内の処理に失敗した、あるいはファイルが存在しないことを示す exist -> ファイルが存在しない(これ自体はエラーではない)

mmLoad -> ファイルの読み込みに失敗

mmPlay -> ファイルの再生に失敗

netURL -> IPアドレスの指定に失敗

screen -> ウィンドウの作成に失敗

celLoad -> 画像ファイルが存在しない

mes, cMes -> 文字の描画に失敗

dialog -> ダイアログの表示に失敗

gCopy -> バッファ画像の描画に失敗

picLoad -> 画像の描画に失敗

line -> 線の描画に失敗

HSP_CURSOR_ON_RANGE_OF_BUTTO 1 ボタンの範囲内(ボタンの上)にカーソルがある buttonGetInfo, stop
HSP_CURSOR_NOT_ON_RANGE_OF_BUTTON 0 ボタンの範囲内(ボタンの上)にカーソルがない buttonGetInfo, stop

 

 

エラー文一覧


FakeHSPでは、エラーが発生すると、コンソールにエラー文が表示されます。

エラー文は、C++標準のcoutで表示しています。

 

エラー文は、次の形式で表示されます:

[FakeHSP Error : (エラーID) in (関数)] (エラー内容):(SDL2のエラー原文、ない場合もある)"

 

エラーIDは、大文字アルファベット1文字と2桁の数字から成ります。

大文字アルファベットの大まかな区分は以下のとおりです。

文字 区分
D Drowから、描画関連のエラー
F Fileから、ファイル関連のエラー(I/Oエラー)
I Initから、SDL2の呼び出し時のエラー
M MusicまたはMixerから、音楽関連のエラー(mm系関数)
N Networkから、ネットワーク関連のエラー(net系関数)
R Redrawから、画面初期化関連のエラー(reDraw関数)
S Setから、設定・指定関連のエラー(color関数など)
T Textから、note系関数のエラー
W Windowから、ウィンドウ関連のエラー

 

エラー一覧は以下のとおりです。

エラーID 内容 対処法 関数
D01 図形の描写に失敗しました SDL2内部の問題です。ウィンドウは描画可能な状態か、ご確認ください。 boxf, boxfA
D02 文字の描画に失敗しました SDL2内部の問題です。ウィンドウは描画可能な状態か、ご確認ください。 cMes, mes
D03 ダイアログの表示に失敗しました SDL2内部の問題です。ウィンドウはダイアログ表示が可能な状態か、ご確認ください。 dialog
F01 画像ファイルが存在しません 画像ファイルを置くか、画像ファイルのパスは正確かご確認ください。 celLoad, picLoad
F02 音楽ファイルが存在しません 音楽ファイルを置くか、音楽ファイルのパスは正確かご確認ください。 mmLoad
I01 SDLの初期化に失敗しました SDL2が正常に導入できているかご確認ください。 HSP::window呼び出し時
I02 SDL_TTFの初期化に失敗しました SDL2_ttfが正常に導入できているかご確認ください。 HSP::window呼び出し時
I03 SDL_IMGの初期化に失敗しました SDL2_imageが正常に導入できているかご確認ください。 HSP::window呼び出し時
I04 SDL_IMGのローダーの初期化に失敗しました SDL2_imageの内部の問題です。後ろに続くSDL2_imageのエラー文をご参照ください。 HSP::window呼び出し時
I05 SDL_netの初期化に失敗しました SDL2_netが正常に導入できているかご確認ください。 HSP::net呼び出し時
I06 SDL_mixerの初期化に失敗しました SDL2_mixerが正常に導入できているかご確認ください。 HSP::mixer呼び出し時
I07 SDL_mixerのローダーの初期化に失敗しました SDL2_mixer内部の問題です。後ろに続くSDL2_mixerのエラー文をご参照ください。 HSP::mixer呼び出し時
M01 音楽ファイルの読み込みに失敗しました SDL2_mixer内部の問題です。後ろに続くSDL2_mixerのエラー文をご参照ください。 mmLoad
N01 URLまたはIPアドレスの指定に失敗しました SDL2_net内部の問題です。後ろに続くSDL2_netのエラー文をご参照ください。 netURL
N02 ネットワークからの文字列の取得に失敗しました SDL2_net内部の問題です。後ろに続くSDL2_netのエラー文をご参照ください。 netGet
R01 画面の初期化に失敗しました SDL2内部の問題です。ウィンドウが初期化可能かご確認ください。 reDraw
S01 描写色の指定に失敗しました 指定したRGB値が有効かご確認ください。 color, colorA
T01 noteSelで対象となるノート(string型)を指定してください noteSelで文字列バッファを指定せずに、note系関数で操作しようとしたときに出るエラーです。 noteLoad, noteGet, noteSave
W01 ウィンドウの作成に失敗しました SDL内部の問題です。後に続くSDL2のエラー文をご参照ください。 screen

 

 

サンプル


ウィンドウの表示

実行すると、真っ黒なウィンドウが出てきます。

真っ白なHSPとは違い、初期状態では真っ黒な画面になります。

#include "FakeHSP.h"

int main(int argc, char *argv[]) {
    hsp::window mainWindow;               // ウィンドウ作成
    mainWindow.screen(640, 480, 1);       // ウィンドウの大きさ 640, 480で、可変ウィンドウにする
    mainWindow.title("FakeHSP Window");   // ウィンドウタイトル設定

    SDL_PumpEvents();                     // macOS 10.14以降で使用する場合は必須

    mainWindow.stop();                    // 一時停止処理(閉じるボタンの操作待ち)

    return 0;
}

 

Hello World

ウィンドウ上に「Hello World」と表示してみます。

注意:HSPとは違い、文字を表示する前にかならずfont関数でフォントを指定する必要があります。

また、フォントはフォントファイル名(拡張子付き)を相対パスまたは絶対パスで指定してください。

さらに、ウィンドウ上になにか描画する際は、reDraw(0);とreDraw(1);で囲む必要があります。HSP3Dishと同じです。

#include "FakeHSP.h"

int main(int argc, char *argv[]) {
    hsp::window mainWindow;             // ウィンドウ作成
    mainWindow.screen(640, 480, 1);     // ウィンドウの大きさ 640, 480で、可変ウィンドウにする
    mainWindow.title("FakeHSP Window"); // ウィンドウタイトル設定

    SDL_PumpEvents();                   // macOS 10.14以降で使用する場合は必須

    mainWindow.reDraw(0);

    mainWindow.color(255, 255, 255);    // 背景色 白
    mainWindow.cls();                   // 塗りつぶし mainWindow.boxf();でも可

    mainWindow.font("../NotoSansCJKjp-Bold.otf", 14);   // フォント(フォントファイルをパスで指定)
    mainWindow.pos(10, 10);                             // ウィンドウ上の座標(10, 10)を左上として描画する
    mainWindow.color(0, 0, 0);                          // 文字色 黒
    mainWindow.mes("Hello World!");                     // Hello World! と表示

    mainWindow.reDraw(1);

    mainWindow.stop();                  // 一時停止処理(閉じるボタンの操作待ち)

    return 0;
}

 

疑似ボタン設置

ボタンの設置には少し注意が必要です。なぜなら、HSPとだいぶ異なる点があるからです。

C++には、HSPのような「ラベル」の概念がありません。

さらに、SDL2にはボタンのような「配置オブジェクト」の概念がありません。

 

FakeHSPでは、疑似ボタンを描画し、

疑似ボタン上にカーソルが合ったときに、押すことが可能であることを示すカーソル(手の形をしたカーソル)を表示し、

疑似ボタン上でクリックされたときに、そのボタンのIDを返すことで、ボタンを擬似的に再現しています。

 

ボタンのIDは、疑似ボタン設置時にbutton関数の返り値として与えられます。

よって、疑似ボタンを設置する際は、ボタンIDを格納するint型の変数が必要になります。

ボタンIDをもとに条件分岐することによって、ボタンが押された後の処理を実装できます。

 

疑似ボタンが押されたときに、どの疑似ボタンが押されたかの判断は、stop関数内で行います。

stop関数から返り値として、押された疑似ボタンのボタンIDが与えられます。

“windowクラスの”wait関数も同じように動作します(wait関数は2つあり、windowクラスでない方のwait関数とは動作が異なるので注意してください)。

また、stop関数は、疑似ボタンが押されるか、ウィンドウが閉じられるまでの間ずっとループします。

ウィンドウを閉じたときには、定数である「HSP_WINDOW_CLOSE」の値がstop関数から返されます。

#include "FakeHSP.h"

int main(int argc, char *argv[]) {
    hsp::window mainWindow;             // ウィンドウ作成
    mainWindow.screen(640, 480, 1);     // ウィンドウの大きさ 640, 480で、可変ウィンドウにする
    mainWindow.title("FakeHSP Window"); // ウィンドウタイトル設定

    SDL_PumpEvents();                   // macOS 10.14以降で使用する場合は必須

    mainWindow.reDraw(0);

    mainWindow.color(255, 255, 255);    // 背景色 白
    mainWindow.cls();                   // 塗りつぶし mainWindow.boxf();でも可

    mainWindow.font("../NotoSansCJKjp-Bold.otf", 14);   // フォント(フォントファイルをパスで指定)
    mainWindow.pos(270, 200);                           // ウィンドウ上の座標(10, 10)を左上とする

    mainWindow.objSize(100, 20);                        // ボタンの大きさ 100x20
    mainWindow.color(128, 128, 128);                    // ボタンの背景色
    int pushButton = mainWindow.button("push");         // ボタン設置(pushButtonにボタンIDを格納する)

    mainWindow.reDraw(1);

    int stopID;                                         // stop関数のID取得用
    while(1) {
        stopID = mainWindow.stop();                     // 一時停止処理(疑似ボタン、あるいは閉じるボタンが押されるまで)

        if (stopID == pushButton) {                     // 「push」ボタン(ボタンID==pushButton)が押されたら…
            mainWindow.reDraw(0);

            mainWindow.color(255, 255, 255);
            mainWindow.cls();                           // 画面初期化

            mainWindow.pos(10, 10);
            mainWindow.color(0, 0, 0);
            mainWindow.mes("ボタンが押されました");        // 文字列表示

            mainWindow.reDraw(1);
        }
        if (stopID == HSP_WINDOW_CLOSE) {               // ウィンドウを閉じるボタンが押されたら…
            break;                                      // ループから抜ける(=終了する)
        }
    }

    return 0;
}

ボタンを押す前

ボタンを押した後

動画(クリックすると再生します)

 

 

オセロゲーム

FakeHSPを使ってオセロゲームを作ります。

AIと対戦できますが、AIは単純に一番多く石を獲得できるマスを選んでいるだけです。

MinMax法とか、αβ法のような凝ったものは実装していません。

#include <iostream>
#include <unistd.h>
#include "FakeHSP.h"

#define SQUARES     8

struct player {
    int points;
    string color;
} typedef player;

struct position {
    int x;
    int y;
} typedef position;

class field {
    int status[SQUARES][SQUARES];
    position directions[8] = {
            {-1, -1},{0, -1},{1, -1},{1, 0},{1, 1},{0, 1},{-1, 1},{-1, 0}
    };
    player playersData[2];
public:
    // コンストラクタ 初期配置を行う
    field() {
        for (int x=0; x<SQUARES; x++) {
            for (int y=0; y<SQUARES; y++) {
                status[y][x] = -1;
            }
        }

        status[SQUARES/2-1][SQUARES/2-1] = 0;
        status[SQUARES/2-1][SQUARES/2] = 1;
        status[SQUARES/2][SQUARES/2-1] = 1;
        status[SQUARES/2][SQUARES/2] = 0;

        playersData[0] = (player){2, "white"};
        playersData[1] = (player){2, "black"};
    }

    // コピーコンストラクタ
    field& operator=(field& f) {
        for (int i=0; i<SQUARES; i++) {
            for (int j=0; j<SQUARES; j++) {
                status[j][i] = f.getStatus((position){i, j});
            }
        }
        playersData[0] = f.getPlayerData(0);
        playersData[1] = f.getPlayerData(1);

        return *this;
    }

    // 指定されたプレイヤーのplayer構造体を返す
    player getPlayerData(int playerNum) {
        return playersData[playerNum];
    }

    // 指定された座標のstatusを返す
    // プレイヤーの駒があれば1, AIのコマがあれば0, 何もなければ-1を返す
    int getStatus(position pos) {
        if (pos.x < 0 || pos.y < 0 || pos.x >= SQUARES || pos.y >= SQUARES) {
            return -1;
        }

        return status[pos.y][pos.x];
    }

    // player構造体の設定
    void setPlayerData(player pAI, player pPlayer) {
        playersData[0] = pAI;
        playersData[1] = pPlayer;
    }

    // 指定された座標にstatusを設定する
    bool setStatus(position pos, int beStatusNum) {
        vector<int> canPutDirections;
        if (!judgement(pos, beStatusNum, canPutDirections))
            return false;

        for (int i : canPutDirections) {
            upset(pos, i, beStatusNum);
        }

        return true;
    }

    // 違反であればfalse, 違反でなければtrueを返す
    bool judgement(position pos, int beStatusNum, vector<int> &canPutDirections) {
        // すでにコマが置かれていたら違法
        if (getStatus(pos) != -1) {
            return false;
        }

        // 周囲に相手の駒がなければ違法
        int j = 0;
        for (int i=0; i<8; i++) {
            if (getStatus((position){pos.x+directions[i].x, pos.y+directions[i].y}) == !(bool)beStatusNum) {
                j++;
            }
        }
        if (j == 0) {
            return false;
        }

        // 相手の駒を挟めていなければ違法
        position posTemp, searchingDirection;
        int sameColorPieces = 0;
        for (int i=0; i<8; i++) {
            posTemp = pos;

            if (getStatus((position){posTemp.x+directions[i].x, posTemp.y+directions[i].y}) == beStatusNum) {
                continue;
            }

            int j=0;
            do {
                posTemp.x += directions[i].x;
                posTemp.y += directions[i].y;
                j++;

                // 置かれた駒がフィールドの端に置かれた駒でなければ、フィールドの端の駒はひっくり返さない
                // ので、フィールドの橋に座標が移動した時点でdo~while文から抜ける
                if (pos.x > 0 && pos.y > 0 && pos.x < SQUARES-1 && pos.y < SQUARES-1) {
                    if (posTemp.x == 0 || posTemp.y == 0 || posTemp.x == SQUARES-1 || posTemp.y == SQUARES-1) {
                        break;
                    }
                }

            } while (getStatus(posTemp) == !(bool)beStatusNum);

            if (getStatus(posTemp) == beStatusNum && j > 0) {
                sameColorPieces ++;
                canPutDirections.push_back(i);
            }
        }
        if (sameColorPieces == 0) {
            return false;
        }
        return true;
    }

    // 駒をひっくり返す
    void upset(position pos, int directionNum, int beStatusNum) {
        position posStart = pos;
        do {
            if (posStart.x > 0 && posStart.y > 0 && posStart.x < SQUARES-1 && posStart.y < SQUARES-1) {
                if (pos.x == 0 && pos.y == 0 || pos.x == 0 && pos.y == SQUARES - 1 ||
                    pos.x == SQUARES - 1 && pos.y == 0 || pos.x == SQUARES - 1 && pos.y == SQUARES - 1) {
                    continue;
                }
            }

            if (status[pos.y][pos.x] == !beStatusNum)
                playersData[!beStatusNum].points --;

            if (status[pos.y][pos.x] != beStatusNum)
                playersData[beStatusNum].points ++;

            status[pos.y][pos.x] = beStatusNum;

            pos.x += directions[directionNum].x;
            pos.y += directions[directionNum].y;
        } while (getStatus(pos) != beStatusNum
                 && pos.x >= 0 && pos.y >= 0 && pos.x < SQUARES && pos.y < SQUARES);
    }
};

// 描画処理
void drow(hsp::window &mainWindow, int windowSizeW, int windowSizeH, field &gameField, int borderLineLength, bool turn) {
    mainWindow.reDraw(0);

    mainWindow.color(0, 200, 100);
    mainWindow.boxf();       // 背景塗りつぶし

    // マス目の描画
    mainWindow.color(255, 255, 255);

    // マス目->外枠
    mainWindow.boxf(windowSizeW / 2 - borderLineLength / 2, 48,
                    windowSizeW / 2 + borderLineLength / 2, 50);
    mainWindow.boxf(windowSizeW / 2 - borderLineLength / 2 - 2, 48, windowSizeW / 2 - borderLineLength / 2,
                    windowSizeH - 48);
    mainWindow.boxf(windowSizeW / 2 - borderLineLength / 2, windowSizeH - 50,
                    windowSizeW / 2 + borderLineLength / 2, windowSizeH - 48);
    mainWindow.boxf(windowSizeW - windowSizeW / 2 + borderLineLength / 2, 48,
                    windowSizeW - windowSizeW / 2 + borderLineLength / 2 + 2, windowSizeH - 48);

    // マス目->内枠
    mainWindow.color(0, 0, 0);

    for (int w = 0; w < SQUARES - 1; w++) {
        mainWindow.line(windowSizeW / 2 - borderLineLength / 2, 50 + (w + 1) * borderLineLength / SQUARES,
                        windowSizeW / 2 + borderLineLength / 2, 50 + (w + 1) * borderLineLength / SQUARES);
    }

    for (int h = 0; h < SQUARES - 1; h++) {
        mainWindow.line(windowSizeW / 2 - borderLineLength / 2 + (h + 1) * borderLineLength / SQUARES, 50,
                        windowSizeW / 2 - borderLineLength / 2 + (h + 1) * borderLineLength / SQUARES, windowSizeH - 50);
    }

    // 概要を表示
    mainWindow.font("NotoSansCJKjp-Bold.otf", 28);

    // 概要->AI
    if (!turn) {
        mainWindow.color(255, 165, 0);
    } else {
        mainWindow.color(255, 255, 255);
    }
    mainWindow.pos(50, 10);
    mainWindow.mes("AI");

    if (gameField.getPlayerData(0).color == "white") {
        mainWindow.color(255, 255, 255);
    }
    else {
        mainWindow.color(0, 0, 0);
    }

    mainWindow.pos(20, 10);
    mainWindow.mes("●");        // ●:オセロの駒を示す

    if (gameField.getPlayerData(0).points > gameField.getPlayerData(1).points) {
        mainWindow.color(199, 220, 104);
    }
    else {
        mainWindow.color(0, 162, 255);
    }
    mainWindow.pos(90, 10); mainWindow.mes(to_string(gameField.getPlayerData(0).points));

    // 概要->Player
    if (turn) {
        mainWindow.color(255, 165, 0);
    }
    else {
        mainWindow.color(255, 255, 255);
    }
    mainWindow.pos(mainWindow.gInfo(10)-130, mainWindow.gInfo(11)-50); mainWindow.mes("Player");

    if (gameField.getPlayerData(1).color == "white") {
        mainWindow.color(255, 255, 255);
    }
    else {
        mainWindow.color(0, 0, 0);
    }

    mainWindow.pos(mainWindow.gInfo(10)-160, mainWindow.gInfo(11)-50); mainWindow.mes("●");        // ●:オセロの駒を示す

    if (gameField.getPlayerData(1).points > gameField.getPlayerData(0).points) {
        mainWindow.color(199, 220, 104);
    }
    else {
        mainWindow.color(0, 162, 255);
    }
    mainWindow.pos(mainWindow.gInfo(10)-30, mainWindow.gInfo(11)-50); mainWindow.mes(to_string(gameField.getPlayerData(1).points));

    // どちらのターンか表示
    mainWindow.font("NotoSansCJKjp-Bold.otf", 14);
    mainWindow.color(255, 255, 255);
    mainWindow.pos(10, mainWindow.gInfo(11) - 30);
    if (!turn) {
        mainWindow.mes("AI のターンです (画面をクリックで続ける)");
    }
    else {
        mainWindow.mes("プレイヤー のターンです");
    }

    // 駒を表示
    mainWindow.font("NotoSansCJKjp-Bold.otf", 28);
    position posTemp;
    for (posTemp.y=0; posTemp.y < SQUARES; posTemp.y++) {
        for (posTemp.x=0; posTemp.x < SQUARES; posTemp.x++) {
            if (gameField.getStatus(posTemp) == 0) {
                mainWindow.pos(windowSizeW/2-borderLineLength/2 + borderLineLength/SQUARES*posTemp.x,
                               windowSizeH/2-borderLineLength/2 + borderLineLength/SQUARES*posTemp.y);
                if (gameField.getPlayerData(0).color == "white") {
                    mainWindow.color(255, 255, 255);
                }
                else {
                    mainWindow.color(0, 0, 0);
                }
                mainWindow.cMes("●", borderLineLength/SQUARES+5, borderLineLength/SQUARES+5);
            }
            if (gameField.getStatus(posTemp) == 1) {
                mainWindow.pos(windowSizeW/2-borderLineLength/2 + borderLineLength/SQUARES*posTemp.x,
                               windowSizeH/2-borderLineLength/2 + borderLineLength/SQUARES*posTemp.y);
                if (gameField.getPlayerData(1).color == "white") {
                    mainWindow.color(255, 255, 255);
                }
                else {
                    mainWindow.color(0, 0, 0);
                }
                mainWindow.cMes("●", borderLineLength/SQUARES+5, borderLineLength/SQUARES+5);
            }
        }
    }

    mainWindow.reDraw(1);
}

// AIがコマを置く場所を選ぶ
// AIがコマを置く座標を返す(position型)
position putAI(hsp::window &mainWindow, field &gameField) {
    position pos, bestPos;
    bool canPut;
    field gameFieldTemp;
    vector<int> searchedPoints;
    int maxPoints = 0;

    // コマを置くと最高点が得られるマスを探す
    for (int y=0; y<SQUARES; y++) {
        for (int x=0; x<SQUARES; x++) {
            pos = (position){x, y};
            gameFieldTemp = gameField;
            canPut = gameFieldTemp.setStatus(pos, false);

            if (!canPut) {
                continue;
            }

            if (gameFieldTemp.getPlayerData(0).points > maxPoints) {
                maxPoints = gameFieldTemp.getPlayerData(0).points;
                bestPos = pos;          // bestPosに駒を置く座標を代入
            }
        }
    }

    if (maxPoints == 0) {
        cout << "AI : パス" << endl;             // 駒を置けるマスがなければパス
        return (position){-1, -1};              // 座標(-1,-1)を返すことによりパスであることを示す
    }
    else {
        return bestPos;                         // 駒を置けるなら、最も良い座標を返す
    }
}

// main関数
int main(int argc, char *argv[]) {
    /*------------------起動---------------------*/
    hsp::window mainWindow;
    mainWindow.screen(640, 480, 1);
    mainWindow.title("reversiCpp");

    SDL_PumpEvents();

    /*----------------データ初期化----------------*/
    // playerデータの宣言
    player pAI;
    pAI.points = 2;
    pAI.color = "white";
    player pPlayer;
    pPlayer.points = 2;
    pPlayer.color = "black";

    // フィールドの初期化・配置
    field gameField;
    gameField.setPlayerData(pAI, pPlayer);

    // ウィンドウの大きさ
    int windowSizeW = mainWindow.gInfo(10);
    int windowSizeH = mainWindow.gInfo(11);

    // 外枠の長さを計算
    int borderLineLength;
    if (windowSizeH < windowSizeW) {
        borderLineLength = windowSizeH-100;
    }
    else {
        borderLineLength = windowSizeW-100/SQUARES;
    }

    // 各マスに透明なボタンを配置
    int buttonIDs[SQUARES][SQUARES];

    for (int y=0; y<SQUARES; y++) {
        for (int x=0; x<SQUARES; x++) {
            mainWindow.pos(windowSizeW/2-borderLineLength/2 + borderLineLength/SQUARES*x,
                           windowSizeH/2-borderLineLength/2 + borderLineLength/SQUARES*y);
            mainWindow.objSize(borderLineLength/SQUARES, borderLineLength/SQUARES);
            buttonIDs[y][x] = mainWindow.button("");
        }
    }

    // 画面全体に透明なボタンを設置する
    int continueButtonID;

    mainWindow.objSize(windowSizeW, windowSizeH);
    mainWindow.pos(0, 0); continueButtonID = mainWindow.button("");

    /*-----------------ゲーム本体-----------------*/
    int stopFuncReturnedID = SQUARES*SQUARES;
    bool turn = true;            // プレイヤーのターンであればtrue
    bool canPut = true;          // プレイヤーが置けないマスに駒を置こうとしたらfalse

    // 描画処理(初期状態のみ)
    drow(mainWindow, windowSizeW, windowSizeH, gameField, borderLineLength, turn);

    while(stopFuncReturnedID != HSP_WINDOW_CLOSE) {
        // AIのターンなら、マス目のボタンを無効化
        // プレイヤーのターンなら、マス目のボタンを有効化
        for (int i=0; i<SQUARES*SQUARES; i++) {
            mainWindow.objEnable(i, turn);
        }
        mainWindow.objEnable(continueButtonID, !turn);

        // AIのターンなら、自動でコマを置く
        if (!turn) {
            while (stopFuncReturnedID != continueButtonID)
                stopFuncReturnedID = mainWindow.stop();

            gameField.setStatus(putAI(mainWindow, gameField), false);    // 駒を実際に置く作業
        }

        // プレイヤーのターンの時
        // コマが置かれたとき(マス目のボタンが押された時)の処理
        if (turn) {
            stopFuncReturnedID = mainWindow.stop();

            if (stopFuncReturnedID >= 0 && stopFuncReturnedID <= SQUARES*SQUARES-1) {
                // 押された場所を推定
                position posTemp;
                posTemp.x = stopFuncReturnedID % SQUARES;
                posTemp.y = stopFuncReturnedID / SQUARES;

                // コマを置く(違反している場合は置かれない)
                canPut = gameField.setStatus(posTemp, (int)turn);
            }
        }

        // ウィンドウの大きさが変更されたときの処理
        if (stopFuncReturnedID == HSP_WINODW_CHANGED_SIZE) {
            // ウィンドウの大きさに反映
            windowSizeW = mainWindow.gInfo(10);
            windowSizeH = mainWindow.gInfo(11);

            // 外枠の長さを計算
            if (windowSizeH < windowSizeW) {
                borderLineLength = windowSizeH-100;
            }
            else {
                borderLineLength = windowSizeW-50;
            }

            // 各マスに透明なボタンを配置
            mainWindow.objClear();
            for (int y=0; y<SQUARES; y++) {
                for (int x=0; x<SQUARES; x++) {
                    mainWindow.pos(windowSizeW/2-borderLineLength/2 + borderLineLength/SQUARES*x,
                                   windowSizeH/2-borderLineLength/2 + borderLineLength/SQUARES*y);
                    mainWindow.objSize(borderLineLength/SQUARES, borderLineLength/SQUARES);
                    buttonIDs[y][x] = mainWindow.button("");
                }
            }
            mainWindow.objSize(windowSizeW, windowSizeH);
            mainWindow.pos(0, 0); continueButtonID = mainWindow.button("");

            drow(mainWindow, windowSizeW, windowSizeH, gameField, borderLineLength, turn);
        }

        // ターン切り替えと描画処理
        if (canPut && stopFuncReturnedID != HSP_WINODW_CHANGED_SIZE) {
            // ターン切り替え
            turn = !turn;

            // 描画処理
            if (stopFuncReturnedID != HSP_WINDOW_CLOSE) {
                drow(mainWindow, windowSizeW, windowSizeH, gameField, borderLineLength, turn);
            }
        }
    }

    return 0;
}

プレイヤーが黒い石、AIが白い石です。

実行すると、このようなウィンドウが表示されます。

 

マスの上でクリックすると、石を置くことができます。

 

石をおいたあとにウィンドウ上でクリックすると、AIが石を置きます。

 

動画(クリックすると再生します)

AIは大して強くないのですが負けています…

 

 

ライセンス


FakeHSPはMITライセンスです。

改造、再配布OKですので、自作ソフトに組み込んで公開できます。

また、FakeHSPを自分で改良・改造してもよいです。

ただし、再配布物で問題が起きても、作者は責任を負いません。

MITライセンスの全文は、FakeHSP.hの冒頭に記載してあります。