札幌もかなりやばくて、気軽に外出できる雰囲気では無くなってきたので、

6000円ぐらいかな。
これに自転車の車輪の乗せて、ペダルに負荷をかけて、宅内でもトレーニングができるというヤツです。
自転車を乗せてみた。

外にあった自転車を持ってきたので、汚れ防止用に新聞紙敷いています。

かなりスペースを取ってしまいました。
宅内でトレーニングできるといえど、部屋の通路を完全に塞いでしまっていてトイレにも入れない状態なので。
ウチには無理でした。
メルカリで売却~。


これをラズパイに装着します。
これのマニュアルはgitHubにアップされています。
https://github.com/raspberrypilearning/astro-pi-guide
英語です。
チュートリアルに従って装着します。
https://github.com/raspberrypilearning/astro-pi-guide/blob/master/assemble.md
1.GPIO pin extension headerをラズパイに取り付けます。

2.六角柱のやつをラズパイに取り付けてしたからネジ止めします。

3.SENSE HATを取り付けます。

4.最後に上からネジ止めします。

電源ON!


正しくできたかどうか、本当に動くかどうか、次回はサンプルプログラムを作成してみます。
数独解析ツールはほぼ出来上がってしまったので、
今度はピクロス解析ツールを作成してみようと思います。
自信は無いです。
でもできるところまでやってみます。
数独の時はロジック→UIの順に作成しましたが、今回はUI→ロジックの順に作成していこうと思います。
まず、どんなUIにしようかまとめてみます。
ピクロスは絵のサイズが同じとは限らないので、サイズをフレキシブルに変更できるようにしなければなりません。
あと、数字入力は数独の時に適用したパレット方式が意外と使い勝手が良かったので、今回もその方式を取り入れようと思います。
問題データをセーブ/ロードできるようにした方が良いかなぁ(デバッグが楽)
まぁ、とりあえずやってみます。

さぁ、ラズパイをセットアップするぞ。
まずはマイクロSDカードにラズパイOSをインストールします。
マイクロSDカードをPCに接続して、以下のサイトからインストールツールをダウンロードします。

実行するとこんな画面が表示されるので、一番左のリストボックスから使用するOSを選びます。
今回はデフォのRASBIANで。
真ん中のリストボックスでは、インストール先のメディアを選択します。
これは当然SDカードを選択。
この二つを選択すると、一番右のWRITEボタンが有効になります。
WRITEボタンを押すとSDカードにOSが書き込まれます。

終わりました。
マイクロSDカードをラズパイに装着し、電源以外のケーブルなどを接続。
最後に電源ケーブルを接続します。
そうすれば勝手にラズパイが立ち上がります。


立ち上がりました。
初回セットアップに言語設定、Wi-Fi設定、アップデートを行って設定完了。
これで自動的に日本語も使用できます。
あと、設定を変えることろは、VNCを有効にする。

Raspberry Piの設定からVNCの項を有効にすると、他PCからVNCで接続できます。
基本的にはVNC接続で使用していきたいと思います。
さて、次回はアタッチメントをつなげてみましょうか。

まぁいろいろありましたが、数独解析ツールが完成しましたので、ご報告いたします。
ダウンロードはこちら(gitHub)
https://github.com/takishita2nd/sudokuGUI/releases/tag/1.02
zipファイルを解凍してsudokuGUI.exeを実行してください。
操作はマウスでクリックするだけなのでわかりやすくなっていると思います。
何かありましたらコメントください。
あと、ネタもください。
前回までの状況はこちら。
最新ソースはこちら(gitHub)
https://github.com/takishita2nd/sudokuGUI
やることの三つ目を解決します。
今のままでは、入力データが不完全な状態で解析ボタンを押してしまうと、アプリが固まってしまいます。
原因は、仮置きロジックの再起処理が無限に実行されてしまっているようです。
なので、仮置きロジックが一定回数呼ばれたら失敗として処理終了させてしまいましょう。
class Sudoku
{
private Square[,] _square;
private int kariokiCount = 0;
private Square doKarioki(Square[,] squares)
{
Square ret = null;
List<Square> kariokiList = searchKariokiSquare(squares);
kariokiCount++;
if(kariokiCount >= 100)
{
return null;
}
続いて、エラーを表示させる処理を追加します。
エラーは新しいオブジェクトを画面に追加して表示させます。
なので、フォントデータも作り直しです。
class Message : ObjectBase
{
private string _text;
private const int fontOffsetX = 0;
private const int fontOffsetY = 0;
public Message(int x, int y, string text)
{
width = 256;
height = 64;
_x = x;
_y = y;
_text = text;
_backTexture = new asd.TextureObject2D();
_backTexture.Position = new asd.Vector2DF(_x, _y);
_valueText = new asd.TextObject2D();
_valueText.Text = _text;
_valueText.Font = Resource.getTextFont();
_valueText.Position = new asd.Vector2DF(_x + fontOffsetX, _y + fontOffsetY);
}
public void show()
{
_valueText.Text = _text;
}
public void hide()
{
_valueText.Text = "";
}
}
テクスチャーは使わないので、書かなくても良かったんですけどね。
// メッセージ
Message message = new Message(10, 640, "解析に失敗しました");
asd.Engine.AddObject2D(message.getTextObject());
message.hide();
こんな感じでAltseedエンジンにテキストを追加します。
最後に、このメッセージの表示をON/OFFする処理を追加します。
class Button : ObjectBase
{
public virtual void onClick(SquareObject[,] squareObjects, Message message)
class AnalyzeButton : Button
{
public override void onClick(SquareObject[,] squareObjects, Message message)
{
if(enable == false)
{
return;
}
Square[,] squares = new Square[9, 9];
for (int row = 0; row < 9; row++)
{
for (int col = 0; col < 9; col++)
{
squares[row, col] = new Square(squareObjects[row, col].getValue(), row, col);
}
}
Sudoku sudoku = new Sudoku(squares);
var ret = sudoku.run();
if(ret == null)
{
message.show();
return;
}
for (int row = 0; row < 9; row++)
{
for (int col = 0; col < 9; col++)
{
squareObjects[row, col].setValue(ret[row, col].GetValue());
}
}
}
}
class ClearButton : Button
{
public override void onClick(SquareObject[,] squareObjects, Message message)
{
message.hide();
for (int row = 0; row < 9; row++)
{
for (int col = 0; col < 9; col++)
{
squareObjects[row, col].setValue(0);
}
}
}
}
bool isButtonClisk = false;
foreach (Button button in buttons)
{
if (button.isClick(pos))
{
button.onClick(squareObjects, message);
isButtonClisk = true;
}
}
実装に悩みましたが、onClickにmessageオブジェクトを渡すことで対応させました。
もっとスマートな方法があると思うんですが、次回の開発までの課題にします。
たぶん、ほぼほぼこれで完成だと思います。

買いました。

Raspberry Pi 4 Model B 2GB RAM。
これの一個前がRaspberry Pi 2 Model Bだったから、それより大幅なパワーアップです。
メモリもプロセッサーも強化されていますが、わかりやすいところで言うと、

HDMIポート1個から、Micro HDMIのポート2つになりました。
4K出力もできるみたいよぉ。
Micro HDMI対応のケーブルなんてほとんど出回っていないので、本体と一緒に注文しました。

Micro HDMI – HDMIのケーブルです。
これが無いと、ディスプレイに画面を表示できません。

そして、もう一つ、左側にあるのが電源です。
端子はUSB-Cなのですが、これを動かすのに3Aの出力が必要なので、一般に出回っているUSB-Cケーブルでは動きません。
これも本体と同時に対応電源を注文しました。

あとは、ストレージとなるMicro SDカード。

これだけでこのラズパイは動かせるのですが、ついでなので、もうちょっと遊べるパーツを買いました。

このボードに温度計、湿度計などなど、あと右下にジョイスティック、8×8のLEDディスプレイが付いているパーツです。
ラズパイ4でも動くはず。
さあ、セットアップするぞ。
この一式はこちらのサイトで購入しました。
https://raspberry-pi.ksyic.com
前回までの状況はこちら。
最新ソースはこちら(gitHub)
https://github.com/takishita2nd/sudokuGUI
やることの二つ目を解決します。
入力データに誤りがある場合は、誤りであると分かった時点でユーザーに知らせることが必要になります。
数独の場合、

オレンジの数字と同じ数字が黄色の範囲に合った場合、すでに数独のルールから逸脱しているため、それが判明した時点で誤りと判断します。
どうやってユーザーに知らせるのかというと、数字フォントの色を赤に変えることで知らせようと思います。
それと同時に解析ボタンも押せないようにガードをかけようと思います。
まずは、フォントの色を変える処理を実装。
リソースの追加。
static class Resource
{
private static asd.Font _fontRed = null;
public static asd.Font getFontRed()
{
if (_fontRed == null)
{
_fontRed = asd.Engine.Graphics.CreateFont("numberRed.aff");
}
return _fontRed;
}
フォントの色を変える処理。
class SquareObject : ObjectBase
{
public enum FontColor
{
Black,
Red
}
public bool isSetValue()
{
if(_value == 0)
{
return false;
}
else
{
return true;
}
}
public void setFontColor(FontColor color)
{
switch (color)
{
case FontColor.Black:
_valueText.Font = Resource.getFont();
break;
case FontColor.Red:
_valueText.Font = Resource.getFontRed();
break;
default:
break;
}
}
ボタンを有効・無効を切り替える処理。
class Button : ObjectBase
{
protected bool enable = true;
public void setEnable(bool enable)
{
this.enable = enable;
}
class AnalyzeButton : Button
{
public override void onClick(SquareObject[,] squareObjects)
{
if(enable == false)
{
return;
}
これを実装。
class SudokuUI
{
private bool checkInputParameter(SquareObject[,] squareObjects)
{
bool conflict = false;
for(int row = 0; row < 9; row++)
{
for(int col = 0; col < 9; col++)
{
if(squareObjects[row,col].isSetValue() == true)
{
int value = squareObjects[row,col].getValue();
bool ret = checkRowNumber(squareObjects, row, col, value);
ret |= checkColNumber(squareObjects, row, col, value);
ret |= check9AreaNumber(squareObjects, row, col, value);
if(ret == true)
{
squareObjects[row, col].setFontColor(SquareObject.FontColor.Red);
conflict = true;
}
}
}
}
return conflict;
}
private bool checkRowNumber(SquareObject[,] squareObjects, int row, int col, int value)
{
for(int c = 0; c < 9; c++)
{
if(c != col &&
squareObjects[row, c].getValue() == value)
{
squareObjects[row, c].setFontColor(SquareObject.FontColor.Red);
return true;
}
}
return false;
}
private bool checkColNumber(SquareObject[,] squareObjects, int row, int col, int value)
{
for (int r = 0; r < 9; r++)
{
if (r != row &&
squareObjects[r, col].getValue() == value)
{
squareObjects[r, col].setFontColor(SquareObject.FontColor.Red);
return true;
}
}
return false;
}
private bool check9AreaNumber(SquareObject[,] squareObjects, int row, int col, int value)
{
int rowStart;
int colStart;
getRowCol9Area(row, col, out rowStart, out colStart);
for (int r = rowStart; r < rowStart + 3; r++)
{
for (int c = colStart; c < colStart + 3; c++)
{
if (r != row && c != col &&
squareObjects[r, c].getValue() == value)
{
squareObjects[r, c].setFontColor(SquareObject.FontColor.Red);
return true;
}
}
}
return false;
}
private void getRowCol9Area(int row, int col, out int rowStart, out int colStart)
{
if (row >= 0 && row <= 2)
{
rowStart = 0;
}
else if (row >= 3 && row <= 5)
{
rowStart = 3;
}
else
{
rowStart = 6;
}
if (col >= 0 && col <= 2)
{
colStart = 0;
}
else if (col >= 3 && col <= 5)
{
colStart = 3;
}
else
{
colStart = 6;
}
}
if (asd.Engine.Mouse.LeftButton.ButtonState == asd.ButtonState.Push)
{
if (mouseHold)
{
if (!palette.isClick(pos))
{
palette.hide();
mouseHold = false;
}
else
{
int value = palette.getClickValue(pos);
if(clickedSquareObject != null)
{
clickedSquareObject.setValue(value);
palette.hide();
mouseHold = false;
}
bool conflict = checkInputParameter(squareObjects);
if(conflict == true)
{
start.setEnable(false);
}
else
{
for (int row = 0; row < 9; row++)
{
for (int col = 0; col < 9; col++)
{
squareObjects[row, col].setFontColor(SquareObject.FontColor.Black);
}
}
start.setEnable(true);
}
}
}
else
ちょっと動作が怪しい気がするけど、、、後で見直す。
checkInputParameter()で数値の重複が無いかを確認し、あれば選択中のマスと同じ値のマスを赤くします。もし重複が無ければ全て黒に戻します。
そして、重複があればボタンを無効化、無ければボタンを有効化します。
動作結果はこうなりました。