CG Library SkiaSharp「スターフィールド」

<FONT COLOR="BLUE">

#### ※点(星)が移動するだけです。以下、SF。

</FONT>

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f323832313838322f35393334663835372d303733632d383430392d643838622d3137396564393836353764332e6a706567.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821882/72dffdf8-05c1-2fa1-65fc-6e642ac6792d.jpeg)



<FONT COLOR="BLUE">
<B>
1982年 初代PC-9801用ゲーム、「VALIANT」でSFを見る。</FONT><BR>

- 当時では前代未聞の驚異的なCG。伝説の天才プログラマ、マークフリント作。
- C言語は存在してたものの、まだ使用できるコンパイラは皆無で事実上、BASICかASMの二択。BASICでは速度出ないので当然ASM。
- 三角比をルックアップテーブルとして用意、参照しているか、ALU駆使の自作のライブラリを使用していかのどちらかと推測。
- x86はMUL、DIV命令はあるがsin,cos等は実装さておらず、それらはi8087 浮動小数数値演算コ・プロセッサ(当時8万円位する)を入れないとH.W.(ASM)で計算不可。
- i8087にしてもNAND回路等で超越関数を設計するなんてこれまた凄い話。
<BR>

<FONT COLOR="BLUE">
<B> 
1989年 Xが動くようになったので同僚がSFを作る。自分も試みたができず。<BR></FONT>

- プラットフォーム SVR4,X11R4,Xlib(C言語)
- 完成させた記憶は無く、職務で作る訳でもないので放置したと思う。
　(これ以前にDOS版Turbo Cで作りかけた覚えはあるが定かでない)
  
<FONT COLOR="BLUE">

<B>2024年 35年ぶりに作ってみる。<B></FONT>

- プラットフォーム WPF(C#),SkiaSharp
- 特に難しい事はやってません。とても簡単なコードです。
- 配列データを順次シフトさせて表示してます。
 
<FONT COLOR="BLUE">

#### 動画1. 順方向(27インチ WQHD 2560x1440,自作機 i5-7600K 3.80GHz)

<iframe width="560" height="315" src="https://www.youtube.com/embed/QJoZB9bkNao?si=umDENyDH-kf4XWzS" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

#### 動画2. 逆方向</FONT>


<iframe width="560" height="315" src="https://www.youtube.com/embed/BGp39gIuijY?si=27Bhe0WbXYxofo3q" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

::: note info

特徴

- 星は色が違うので4パターン用意してランダムに可変。大きさも複数用意。
- 最初に表示する星の大きさ、拡大比率等はコード先頭のconst値で可変。
- 画面中央に表示させて眺めていると段々眠くなってくる催眠効果(?)も持ち合わせてます。
:::

<FONT COLOR="BLUE">

#### 1.キー操作説明</FONT>
- <B><font COLOR="RED" >[PageUp,Dw]</B>........速度可変
- <B><font COLOR="RED" >[カーソル上下]</B>......星の数可変
- <B><font COLOR="RED" >[マウス右]</B>...............表示を中央固定とマウス追従の切り替え
- <B><font COLOR="RED" >[マウス左]</B>...............終了


<FONT COLOR="BLUE">

#### 2.全体的な流れ </FONT>
1.SfMain()でSTAR_ANGLE[0,h]にどちらの方向に移動するかの角度を入れる
2.SfPaintSurface()全ての星表示　
3.画面消す
4.DataShift()でデータを[0]->[1]、[1]->[2]、[2]->[3]...にシフト
5. 1に戻る
これらを連続的に繰り返す事により星が移動している様に見える
 
<FONT COLOR="BLUE">

#### 3.星のXY値の算出</FONT>

STAR_ANGLE配列に入っている角度から順次XY値を計算してその位置にCircle()で描く。
ttは中心部からの半径増加分。

```C#
X=(float)(this.Width /2 + (tt+L)* Math.Sin (STAR_ANGLE[tt,h] * Math.PI/180) );
Y=(float)(this.Height/2 - (tt+L)* Math.Cos (STAR_ANGLE[tt,h] * Math.PI/180) );
```


<FONT COLOR="BLUE">

#### 4.WPF (C#) 順方向版(前から後へ) </FONT>
2パターン用意。動く方向が違うだけでコードは殆ど同じです。


```C#
/////////////////////////////////////////
// STARFIELD (NORMAL) WPF+SKiaSharp. 
// (c)inf102 S.H 2024.
// Use PageUp,Dw / CursolUp,Dw Key.
/////////////////////////////////////////

using SkiaSharp;
using SkiaSharp.Views.Desktop;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace SF {

    public partial class MainWindow : Window {
        
        // 速度、星数の表示
        static int      PARAM_SIZE=0;    
        static bool     PARAM_SIZE_FLG;     // 設定値表示中に変更キーを押した際のキャンセル用
        static bool     OP_FLG;             // OP_Message表示フラグ

        // マウス制御
        static bool     MOUSE_FLG;

        // マウス位置
        static int      MX,MY;

        // 描画を始めてから外枠までの繰返しのカウンタ FIELD_MAXまでaddされる　[0固定]
        static int      FIELD_CNT=0;

        ////////////////// [ ]内は設定参考値 /////////////////////////
        // 1フィードに表示する最大星数。STARより大きい必要がある。起動中にSTARを可変させるので最大値を最初に設定する必要がある。
        const int       STAR_MAX = 30;  // [50]

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        static int      STAR=2;                 // 1フィールド中の星描画数 1～STAR_MAX   [3]
        static int      WAIT=39;                // [35]
        const int       CENTER_SPACE = 15;      // 星が最初に表示される空白エリアの広さ [15]
        const int       FIELD_MAX = 83;         // 最大広がりエリア。画面端に星が来ない場合は大きくする。
                                                // 中心部を画面中央にして70で画面いっぱいに表示されたとしても表示位置を端にすると反対には表示
                                                // されない事がある。その場合も数値を上げるが大きすぎると余計な計算が増える。 [83] 

        const float     FIELD_RATE =1.060f;     // 拡大割合。WAIT後にどれだけ接近するか。大きいとカクカクになる。　[1.060f]　
        
        static int[,]   STAR_ANGLE = new int[FIELD_MAX, STAR_MAX]; // 星が移動する角度 

        // 最初に表示する星の大きさ
        const float     No1_DEFAULT_SIZE  = 1.32f; // [1.05f]
        const float     No2_DEFAULT_SIZE  = 1.05f; // [1.05f]
        const float     No3_DEFAULT_SIZE  = 1.74f; // [1.74f]
        const float     No4_DEFAULT_SIZE  = 2.35f; // [2.40f]

        // 星の拡大率
        const float     No1_ZOOM_RATE = 1.0093f; // [1.0093f]
        const float     No2_ZOOM_RATE = 1.0299f; // [1.0299f]
        const float     No3_ZOOM_RATE = 1.0069f; // [1.0069f]
        const float     No4_ZOOM_RATE = 1.0085f; // [1.0215f]

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        public MainWindow() {
            InitializeComponent();
        }

        // 配列データシフト
        public void DataShift(){

            int []bk1 = new int [STAR_MAX];
            int []bk2 = new int [STAR_MAX];

            for ( int x=0;x<STAR;x++ ) bk1[x]=STAR_ANGLE[0,x];
           
            int flg=0;
            for ( int x=1;x<FIELD_MAX;x++ ){
                if ( flg == 0 ){
                    for ( int tm=0; tm<STAR; tm++ ){
                        bk2[tm] = STAR_ANGLE[x,tm];  
                        STAR_ANGLE[x,tm] = bk1[tm];
                    }
                    flg=1;
                    continue;
                }

                for ( int tm=0; tm<STAR; tm++ ){
                    bk1[tm] = STAR_ANGLE[x,tm];
                    STAR_ANGLE[x,tm] = bk2[tm];
                }
                flg=0;
            }
        }

        private async void Window_Loaded(object sender, RoutedEventArgs e) {
            
            // 非同期メインループ
            _= Task.Run(() => SfMain());

            // OP Msg
            OP_FLG = true;
    
            await Task.Run( async ()=>{
            
                for (int ex=1;ex<130;ex++){
                    PARAM_SIZE=ex;
                    this.Dispatcher.Invoke((Action)(() =>{
                        Param.InvalidateVisual();
                    }));
                    await Task.Delay(8);
                }
                
                // 表示を消す
                PARAM_SIZE=999; 
                this.Dispatcher.Invoke((Action)(() =>{
                    Param.InvalidateVisual();
                }));
            });
            OP_FLG = false;
        }

        // 非同期メインループ
        void  SfMain(){
            Random rnd = new Random();
            
            while (true){
            
                // 星が移動する方向(角度)設定 (新たな星の生成)
                for ( int h=0;h<STAR;h++ ){
                    STAR_ANGLE[0,h]=rnd.Next(0, 360);
                }

                // 星表示
                this.Dispatcher.Invoke ((Action)(() =>{
                    Sf.InvalidateVisual();
                }));
                    
                // 星の配列データをシフト
                DataShift();

                // 何処まで展開するか
                FIELD_CNT++;
                if (FIELD_CNT >= FIELD_MAX) FIELD_CNT=FIELD_MAX;
                
                Thread.Sleep(WAIT);            
            }

        }

        // マウスの位置を星の中心位置にする
        private void Window_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) {
            System.Windows.Point point = e.GetPosition(this);     
            MX=(int)point.X;
            MY=(int)point.Y;
        }
        
        // マウス左ボタン　アプリシャットダウン
        private void Window_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
            Application.Current.Shutdown();
        }
       
        // マウス右ボタン 表示位置をマウスに追従させるか
        private void Window_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
            MOUSE_FLG=!MOUSE_FLG;
        }

        // 1フィールド中の星数可変カーソル↑↓ , 速度可変 PageUp,Dw
        private async void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) {

            if ( (!(e.Key == System.Windows.Input.Key.Up     || e.Key == System.Windows.Input.Key.Down) 
               && !(e.Key == System.Windows.Input.Key.PageUp || e.Key == System.Windows.Input.Key.PageDown)) ) return;
            
            // 星数
            if ( e.Key == System.Windows.Input.Key.Up   && (STAR < STAR_MAX) ) STAR++;
            if ( e.Key == System.Windows.Input.Key.Down && (STAR >= 2) ) STAR--;

            // 速度
            if ( e.Key == System.Windows.Input.Key.PageUp ) WAIT-=3;
            if ( WAIT <=0 ) WAIT=2;
            if ( e.Key == System.Windows.Input.Key.PageDown ) WAIT+=3;
           
            // 設定値を画面に出す
            if (PARAM_SIZE_FLG == false ){
                PARAM_SIZE_FLG = true;
                
                await Task.Run( async ()=>{
                    for (int ex=6;ex<80;ex++){
                        PARAM_SIZE=ex;
                        this.Dispatcher.Invoke((Action)(() =>{
                            Param.InvalidateVisual();
                        }));
                        await Task.Delay(15);
                    }

                    // 表示を消す
                    PARAM_SIZE=999; 

                    this.Dispatcher.Invoke((Action)(() =>{
                        Param.InvalidateVisual();
                    }));
                });
                
                PARAM_SIZE_FLG = false;
            }
        }

        // 星数,速度を画面に出す
        void ParamSfPaintSurface(object sender, SKPaintSurfaceEventArgs args) {

            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;

            SKPaint A_LINE= new SKPaint{
	            Style =  SKPaintStyle.StrokeAndFill,
			    Color = new SKColor(255,0, 0,255),
                IsAntialias=false,
                TextSize=PARAM_SIZE,
                TextAlign=SKTextAlign.Center,
                StrokeWidth = 1
            };

            canvas.Clear();

            if(OP_FLG) {
                canvas.DrawText("STAR FIELD",(float)this.Width /2,(float)this.Height /2  ,A_LINE);
            }
            else{
                canvas.DrawText("STAR " +STAR.ToString()+",WAIT "+ WAIT.ToString(),(float)this.Width /2,(float)this.Height /2  ,A_LINE);
            }

            if (PARAM_SIZE ==999 ) canvas.Clear();
        }

        // 星描画
        void SfPaintSurface(object sender, SKPaintSurfaceEventArgs args) {

            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;

            SKPaint A_LINE= new SKPaint{
	            Style =  SKPaintStyle.StrokeAndFill,
		        IsAntialias=true,
                StrokeWidth = 1
            };
            
            canvas.Clear(SKColors.Black);
            float  X, Y;
            
            // 初めに表示する星の大きさ
            float R1=No1_DEFAULT_SIZE;
            float R2=No2_DEFAULT_SIZE;     
            float R3=No3_DEFAULT_SIZE;    
            float R4=No4_DEFAULT_SIZE;    

            float L=CENTER_SPACE;
            string ANGLE_TXT = "";
            char LAST_INT=(char)'0';

            for (int tt = 0; tt < FIELD_CNT; tt++) {
                for (int h=0;h<STAR;h++){
                
                    // マウス追従
                    if( MOUSE_FLG ) {
                        X=(float)(MX + (tt+L)* Math.Sin (STAR_ANGLE[tt,h] * Math.PI/180) );
                        Y=(float)(MY - (tt+L)* Math.Cos (STAR_ANGLE[tt,h] * Math.PI/180) );
                    }
                    // 表示を画面中心固定にする
                    else {
                        X=(float)(this.Width /2 + (tt+L)* Math.Sin (STAR_ANGLE[tt,h] * Math.PI/180) );
                        Y=(float)(this.Height/2 - (tt+L)* Math.Cos (STAR_ANGLE[tt,h] * Math.PI/180) );
                    }

                    // 角度の一桁目 -> xx   (一桁目によって表示する色を変える)
                    ANGLE_TXT=STAR_ANGLE[tt,h].ToString();
                    if (ANGLE_TXT.Length==3) LAST_INT=ANGLE_TXT[2];
                    if (ANGLE_TXT.Length==2) LAST_INT=ANGLE_TXT[1];
                    if (ANGLE_TXT.Length==1) LAST_INT=ANGLE_TXT[0];

                    if (LAST_INT=='0') {
                        A_LINE.Color = new SKColor(255,127, 80,155);
                        canvas.DrawCircle (X,Y, R1,A_LINE);
                    }
                   
                    if (LAST_INT == '1' || LAST_INT == '2' || LAST_INT == '3') {
                        A_LINE.Color = new SKColor(255,255, 255,230);
                        canvas.DrawCircle (X,Y, R2, A_LINE);
                    }

                    if ( LAST_INT == '4' || LAST_INT=='5' || LAST_INT == '6' || LAST_INT == '7' ) {
                        A_LINE.Color = new SKColor(125,125, 125,255);
                        canvas.DrawCircle (X,Y, R3,A_LINE);
                    }

                    if (LAST_INT =='8'|| LAST_INT == '9' ) {
                        A_LINE.Color = new SKColor(135,206, 235,185);
                        canvas.DrawCircle (X,Y, R4,A_LINE);
                    }
                }

                // 次フィールドの拡大率
                L *= FIELD_RATE;

                // 拡大
                R1 *= No1_ZOOM_RATE;
                R2 *= No2_ZOOM_RATE;
                R3 *= No3_ZOOM_RATE;
                R4 *= No4_ZOOM_RATE;
            }
        }
    }
}


```
<FONT COLOR="BLUE">

#### 5.WPF (C#) 逆方向版(後から前へ)</FONT>

```c#
////////////////////////////////////////
// STARFIELD (Revers) WPF+SkiaSharp
// (c)inf102 2024.
// PageUp/Down, CursolUp/Down
////////////////////////////////////////

using SkiaSharp;
using SkiaSharp.Views.Desktop;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace SF {

    public partial class MainWindow : Window {
        
        // 速度、星数の表示
        static int  PARAM_SIZE=0;    
        static bool PARAM_SIZE_FLG;     // 設定値表示中に変更キーを押した際のキャンセル用

        static bool OP_FLG;             // OP_Message表示フラグ

        // マウス制御
        static bool MOUSE_FLG;

        // マウス位置
        static int MX,MY;

        // 描画を始めてから収束するまでの繰返しのカウンタ FIELD_MAXまでaddされる　[0固定]
        static int FIELD_CNT=0;

        ////////////////// [ ]内は設定参考値 /////////////////////////

        // 1フィードに表示する最大星数。STARより大きい必要がある。起動中にSTARを可変させるので最大値を最初に設定する必要がある。
        const  int      STAR_MAX = 30;          // [30]
        static int      STAR=1;                 // 1フィールド中の星描画数 1～STAR_MAX   [2]
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        static int      WAIT=35;                // [35]
        const int       CENTER_SPACE = 6;       // 星が収束する中央エリアの広さ [7]

        const int       FIELD_MAX = 83;         // 最大広がりエリア。画面端に星が来ない場合は大きくする。
                                                // 中心部を画面中央にして70で画面いっぱいに表示されたとしても表示位置を端にすると反対には表示
                                                // されない事がある。その場合も数値を上げるが大きすぎると余計な計算が増える。 [83] 
                                                // FIELD_RATEを下げるとFIELD_MAXを上げる必要がある
                                                 
        const float     FIELD_RATE =1.080f;     // 拡大割合。WAIT後にどれだけ接近するか。大きいとカクカクになる。　[1.080f]　
        
        static int[,]   STAR_ANGLE = new int[FIELD_MAX, STAR_MAX]; // 星が移動する角度 

        // 最初に表示する星の大きさ
        const float     No1_DEFAULT_SIZE  = 1.42f; // [1.412f]
        const float     No2_DEFAULT_SIZE  = 1.05f; // [1.05f]
        const float     No3_DEFAULT_SIZE  = 1.74f; // [1.74f]
        const float     No4_DEFAULT_SIZE  = 2.01f; // [2.25f]

        // 星の拡大比率
        const float     No1_ZOOM_RATE = 1.0033f; // [1.0033f]
        const float     No2_ZOOM_RATE = 1.0299f; // [1.0299f]
        const float     No3_ZOOM_RATE = 1.0069f; // [1.0069f]
        const float     No4_ZOOM_RATE = 1.0033f; // [1.0085f]

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public MainWindow() {
            InitializeComponent();
        }

        // 配列データシフト
        // 後ろからコピーして行けばbk[]は不要だった
        public void DataShift(){
            
            int []bk1 = new int [STAR_MAX];
            int []bk2 = new int [STAR_MAX];

            for ( int x=0;x<STAR;x++ ) bk1[x]=STAR_ANGLE[0,x];
           
            int flg=0;
            for ( int x=1;x<FIELD_MAX;x++ ){

                if ( flg == 0 ){
                    for ( int tm=0; tm<STAR; tm++ ){
                        bk2[tm] = STAR_ANGLE[x,tm];  
                        STAR_ANGLE[x,tm] = bk1[tm];
                    }
                    flg=1;
                    continue;
                }

                for ( int tm=0; tm<STAR; tm++ ){
                    bk1[tm] = STAR_ANGLE[x,tm];
                    STAR_ANGLE[x,tm] = bk2[tm];
                }
                flg=0;
            }
        }

        private async void Window_Loaded(object sender, RoutedEventArgs e) {
            
            _= Task.Run(() => SfMain());

            // OP Meesage
            OP_FLG = true;
            await Task.Run( async ()=>{

                for (int ex=250;ex>6;ex--){
                    //ex--;
                    PARAM_SIZE=ex;

                    this.Dispatcher.Invoke((Action)(() =>{
                        Param.InvalidateVisual();
                    }));

                    await Task.Delay(5);
                }
                
                // 表示を消す
                PARAM_SIZE=999; 
                this.Dispatcher.Invoke((Action)(() =>{
                    Param.InvalidateVisual();
                }));
            });
            OP_FLG = false;
        }

        // MAIN-LOOP
        void  SfMain(){
            Random rnd = new Random();

            while (true){
                         
                // 星が移動する方向(角度)設定 (新たな星の生成)
                for ( int h=0;h<STAR;h++ ){
                    STAR_ANGLE[0,h]=rnd.Next(0, 360);
                }

                // 星表示
                this.Dispatcher.Invoke ((Action)(() =>{
                    Sf.InvalidateVisual();
                }));
                    
                // 星の配列データをシフト
                DataShift();

                // 何処まで展開するか
                FIELD_CNT++;
                if (FIELD_CNT >= FIELD_MAX) FIELD_CNT=FIELD_MAX;
                
                Thread.Sleep(WAIT);            
           }

        }

        // マウスの位置を星の中心位置にする
        private void Window_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) {
            System.Windows.Point point = e.GetPosition(this);     
            
            MX=(int)point.X;
            MY=(int)point.Y;
        }
        
        // マウス左ボタン　アプリシャットダウン
        private void Window_PreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
            Application.Current.Shutdown();
        }

       
        // マウス右ボタン 表示位置をマウスに追従させるか
        private void Window_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e) {
            MOUSE_FLG=!MOUSE_FLG;
        }

        // 1フィールド中の星数可変カーソル↑↓ , 速度可変 PageUp,Dw
        private async void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) {

            if ( (!(e.Key == System.Windows.Input.Key.Up     || e.Key == System.Windows.Input.Key.Down) 
               && !(e.Key == System.Windows.Input.Key.PageUp || e.Key == System.Windows.Input.Key.PageDown)) ) return;

            // 星数
            if ( e.Key == System.Windows.Input.Key.Up   && (STAR < STAR_MAX) ) STAR++;
            if ( e.Key == System.Windows.Input.Key.Down && (STAR >= 2) ) STAR--;

            // 速度
            if ( e.Key == System.Windows.Input.Key.PageUp ) WAIT-=3;
            if ( WAIT <=0 ) WAIT=2;
            if ( e.Key == System.Windows.Input.Key.PageDown ) WAIT+=3;
           
            // 設定値を画面に出す
            if (PARAM_SIZE_FLG == false ){
                PARAM_SIZE_FLG = true;
                
                await Task.Run( async ()=>{

                    for (int ex=140;ex>2;ex--){
                        PARAM_SIZE=ex;
                        this.Dispatcher.Invoke((Action)(() =>{
                            Param.InvalidateVisual();
                        }));
                        await Task.Delay(15);
                    }
                
                    // 表示を消す
                    PARAM_SIZE=999; 
                    this.Dispatcher.Invoke((Action)(() =>{
                        Param.InvalidateVisual();
                    }));
                });
                PARAM_SIZE_FLG = false;
            }
        }

        // 星数,速度を画面に出す
        void ParamSfPaintSurface(object sender, SKPaintSurfaceEventArgs args) {

            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;

            SKPaint A_LINE= new SKPaint{
	            Style =  SKPaintStyle.StrokeAndFill,
			    Color = new SKColor(255,0, 0,255),
                IsAntialias=false,
                TextSize=PARAM_SIZE,
                TextAlign=SKTextAlign.Center,
                StrokeWidth = 1
            };
            
            canvas.Clear();
            // OP
            if ( OP_FLG ) {
                A_LINE.Color= new SKColor(0,255,0);
                canvas.DrawText("STAR FIELD Rev.",(float)this.Width /2,(float)this.Height /2  ,A_LINE);
            }
            else{

                canvas.DrawText("STAR " +STAR.ToString()+",WAIT "+ WAIT.ToString(),(float)this.Width /2,(float)this.Height /2  ,A_LINE);
            }

            if (PARAM_SIZE ==999 ) canvas.Clear();
        }

        // 星描画
        void SfPaintSurface(object sender, SKPaintSurfaceEventArgs args) {

            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;

            SKPaint A_LINE= new SKPaint{
	            Style =  SKPaintStyle.StrokeAndFill,
		        IsAntialias=true,
                StrokeWidth = 1
            };
            
            canvas.Clear(SKColors.Black);

            float  X, Y;
            
            // 初めに表示する星の大きさ
            float R1=No1_DEFAULT_SIZE;
            float R2=No2_DEFAULT_SIZE;     
            float R3=No3_DEFAULT_SIZE;    
            float R4=No4_DEFAULT_SIZE;    

            float L=5;
            string ANGLE_TXT = "";
            char LAST_INT=(char)'0';
     
            for (int tt = FIELD_MAX-1; tt > 00; tt--) { //68

                for (int h=0;h<STAR;h++){
          
                    // マウス追従
                    if( MOUSE_FLG ) {
                        X=(float)(MX + (L+CENTER_SPACE)* Math.Sin (STAR_ANGLE[tt,h]*3.14/180) );
                        Y=(float)(MY - (L+CENTER_SPACE)* Math.Cos (STAR_ANGLE[tt,h]*3.14/180) );
                    }
                    else{
                        X=(float)(this.Width /2 + (L+CENTER_SPACE)* Math.Sin (STAR_ANGLE[tt,h]*3.14/180) );
                        Y=(float)(this.Height/2 - (L+CENTER_SPACE)* Math.Cos (STAR_ANGLE[tt,h]*3.14/180) );
                    }

                    // 角度の一桁目 -> xx   (一桁目によって表示する色を変える)
                    ANGLE_TXT=STAR_ANGLE[tt,h].ToString();
                    if (ANGLE_TXT.Length==3) LAST_INT=ANGLE_TXT[2];
                    if (ANGLE_TXT.Length==2) LAST_INT=ANGLE_TXT[1];
                    if (ANGLE_TXT.Length==1) LAST_INT=ANGLE_TXT[0];

                    if (LAST_INT=='0') {
                        A_LINE.Color = new SKColor(255,127, 80,155);
                        canvas.DrawCircle (X,Y, R1,A_LINE);
                    }
                   
                    if (LAST_INT == '1' || LAST_INT == '2' || LAST_INT == '3') {
                        A_LINE.Color = new SKColor(255,255, 255,230);
                        canvas.DrawCircle (X,Y, R2, A_LINE);
                    }

                    if ( LAST_INT == '4' || LAST_INT=='5' || LAST_INT == '6' || LAST_INT == '7' ) {
                        A_LINE.Color = new SKColor(125,125, 125,255);
                        canvas.DrawCircle (X,Y, R3,A_LINE);
                    }

                    if (LAST_INT =='8'|| LAST_INT == '9' ) {
                        A_LINE.Color = new SKColor(135,206, 235,185);
                        canvas.DrawCircle (X,Y, R4,A_LINE);
                    }

                }

                // フィールドの拡大率
                L *= FIELD_RATE;
                
                // 縮小率
                R1 *= No1_ZOOM_RATE;
                R2 *= No2_ZOOM_RATE;
                R3 *= No3_ZOOM_RATE;
                R4 *= No4_ZOOM_RATE;
            }
        }
    }
}
```



<FONT COLOR="BLUE">

#### 6.WPF (共通XAML) </FONT>

```xml
<Window x:Class="SF.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wpf="clr-namespace:SkiaSharp.Views.WPF;assembly=SkiaSharp.Views.WPF"
        xmlns:local="clr-namespace:SF" mc:Ignorable="d" WindowStyle="None" 
        Title="StatField" Height="200" Width="200" Loaded="Window_Loaded" 
    	WindowState="Maximized" MouseMove="Window_MouseMove" PreviewMouseLeftButtonDown="Window_PreviewMouseLeftButtonDown" PreviewMouseRightButtonDown="Window_PreviewMouseRightButtonDown" KeyDown="Window_KeyDown" >
  
    <Grid>
        <wpf:SKElement x:Name="Sf"    PaintSurface="SfPaintSurface"  />
        <wpf:SKElement x:Name="Param" PaintSurface="ParamSfPaintSurface"  />
    </Grid>

</Window>
```

<FONT COLOR="BLUE">

#### 7.参考 (所有する初代98のメインボード CLK5MHz,MEM 640KBytes)</FONT>

・上、V30に換装したMPU　下、浮動小数数値演算コ・プロセッサ i8087
　i8087は現在の物価換算で20万円以上。その為、別売りとなっていた。
　MEMも標準128KBytesから計640KBytesに拡張済。

https://ja.wikipedia.org/wiki/Intel_8087

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f323832313838322f33333563383661642d396136372d656530642d633162372d6336613532633931303261612e6a706567.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821882/eb89b56e-81ec-8f37-db61-67def655cd59.jpeg)


・上部白いものがNEC μPD7220 グラフィック・ディスプレイ・コントローラ(GDC)
　GDC https://www.richis-lab.de/GraKa02.htm
![P1260565.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821882/f59e2fe3-b193-2568-c152-8a1d1e3813ce.jpeg)

・裏面。多層基板の制限で元々ジャンパ線使う設計だったのか、後日修正で直したのか不明。
![P1260566.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821882/96159d46-50a4-dede-22ea-fa9a5330200a.jpeg)