Imports System
Imports System.IO
Imports y4cs.ytl
Imports y4cs.aux
Imports y4cs.timer
Imports y4cs.math
Imports y4cs.draw
Imports y4cs.sound
Imports y4cs.input

Namespace Sample7

    Interface IPlay
        Function time() As Integer
        Function isPress(ByVal k As Integer) As Boolean
        Function update() As Boolean
        Sub close()
    End Interface

    Class Play
        Implements IPlay
        Private key As Key2
        Private timer As FixTimer
        Private bw As BinaryWriter


        Public Sub New()
            key = New Key2
            bw = New BinaryWriter(File.Create("replay.dat"))
        End Sub


        Public Function time() As Integer Implements IPlay.time '
            Return timer.get()
        End Function


        Public Function isPress(ByVal k As Integer) As Boolean Implements IPlay.isPress
            Return key.isPress(k)
        End Function

        Public Function update() As Boolean Implements IPlay.update
            If timer Is Nothing Then
                timer = New FixTimer
                timer.reset()
            Else
                timer.update()
            End If
            key.update()
            bw.Write(timer.get())

            Dim state As Byte = 0
            Dim i As Integer

            i = 0
            While i <= 5
                If key.isPress(i) Then
                    state = state Or CByte(1 << i)
                End If
                i += 1
            End While
            bw.Write(state)
            Return True
        End Function


        Public Sub close() Implements IPlay.close
            If Not (bw Is Nothing) Then
                bw.Close()
                bw = Nothing
            End If
        End Sub
    End Class

    Class Replay
        Implements IPlay
        Private br As BinaryReader
        Private key As Byte
        Private tm As Integer


        Public Sub New(ByVal filename As String)
            br = New BinaryReader(File.Open(filename, FileMode.Open))
            key = 0
            tm = 0
        End Sub


        Public Function time() As Integer Implements IPlay.time
            Return tm
        End Function


        Public Function isPress(ByVal k As Integer) As Boolean Implements IPlay.isPress
            Return (key And (1 << k)) <> 0
        End Function


        Public Function update() As Boolean Implements IPlay.update
            If br.PeekChar() = -1 Then
                Return False
            End If
            tm = br.ReadInt32()
            key = br.ReadByte()
            Return True
        End Function


        Public Sub close() Implements IPlay.close
            If Not (br Is Nothing) Then
                br.Close()
                br = Nothing
            End If
        End Sub
    End Class

    '/ <summary>
    '/ App の概要の説明です。
    '/ </summary>
    Class App
        Private Shared rand As rand
        Private Shared level As Integer '  ゲームレベル
        Structure Tama
            Public x, y As Single '  座標
            Public vx, vy As Single ' 速度ベクトル
            Public speed As Single ' 移動速度(vx,vyはこれで正規化される)
            Public ax, ay As Single ' 加速度ベクトル
            Public spin As Single ' 追尾度
            Public alive As Boolean ' この弾の生存フラグ
            Public r, g, b As Integer


            Public Sub reset()
                alive = False
            End Sub

            Public Sub setColor(ByVal r_ As Integer, ByVal g_ As Integer, ByVal b_ As Integer)
                r = r_
                g = g_
                b = b_
            End Sub

            Public Sub move(ByVal x_ As Single, ByVal y_ As Single)
                If Not alive Then
                    Return
                End If ' vx,vyを正規化する
                Dim v_dist As Single = CSng(Math.Sqrt(vx * vx + vy * vy))
                If v_dist < 0.1 Then
                    Return ' 正規化できナー
                End If
                vx = (vx / v_dist) * speed
                vy = (vy / v_dist) * speed
                x += vx
                y += vy
                vx += ax
                vy += ay
                Dim ax_ As Single = x_ - x
                Dim ay_ As Single = y_ - y
                ' ax_,ay_を正規化して、spinを掛ける
                Dim a_dist As Single = CSng(Math.Sqrt(ax_ * ax_ + ay_ * ay_))
                ax_ = (ax_ / a_dist) * spin
                ay_ = (ay_ / a_dist) * spin
                ax += ax_
                ay += ay_

                '  画面範囲外に消えたら死亡(ただし、画面外のト音記号の発射した♪が
                '  到達できるように配慮する
                If x < -100 Or y < -200 Or x > (640 + 100) Or y > (480 + 200) Then
                    alive = False
                End If
            End Sub

            Public Sub reflect(ByVal x_ As Single, ByVal y_ As Single)
                '  (x_,y_)と反対方向にふっとぶ
                vx = x - x_
                vy = y - y_
                Dim v_dist As Single = CSng(Math.Sqrt(vx * vx + vy * vy))
                If v_dist < 0.1 Then
                    Return ' 正規化できナー
                End If
                vx = vx / v_dist
                vy = vy / v_dist
                speed = speed * 4
            End Sub


            Public Sub onDraw(ByVal dst As Screen, ByVal src As Texture)
                If Not alive Then
                    Return
                End If
                dst.setColor(r, g, b)
                dst.blt(src, CInt(x) - 16, CInt(y) - 20)
            End Sub

            '  判定位置は、ハートのLOVEのOとVとの間と、
            '  ♪のおたまじゃくしのところ
            Public Sub set_(ByVal x_ As Single, ByVal y_ As Single, ByVal vx_ As Single, ByVal vy_ As Single, ByVal ax_ As Single, ByVal ay_ As Single, ByVal spin_ As Single, ByVal speed_ As Single)
                x = x_
                y = y_
                vx = vx_
                vy = vy_
                ax = ax_
                ay = ay_
                spin = spin_
                speed = speed_
                setColor(CInt(rand.get(128)), CInt(rand.get(128)), CInt(rand.get(128)))
                alive = True
            End Sub
        End Structure

        Private Shared tama_max As Integer = 256
        Private Shared speed As Single = 4
        ' 弾のスピード
        Shared Function search(ByVal tama() As Tama) As Integer
            '  空き番を返す。なければ-1。
            Dim i As Integer

            i = 0
            While i < tama_max
                If Not tama(i).alive Then
                    Return i
                End If
                i += 1
            End While
            Return -1
        End Function


        Shared Sub shot(ByVal tama() As Tama, ByVal from_x As Single, ByVal from_y As Single, ByVal to_x As Single, ByVal to_y As Single, ByVal type As Integer)
            Dim n As Integer
            n = search(tama)
            If n = -1 Then
                Return
            End If
            Dim vx As Single = to_x - from_x
            Dim vy As Single = to_y - from_y
            '  vx,vyを正規化
            Dim dist As Single = CSng(Math.Sqrt(vx * vx + vy * vy))
            If dist < 0.1 Then
                Return
            End If
            vx /= dist
            vy /= dist
            vx *= speed
            vy *= speed

            Dim way As Integer = 0
            Dim step_ As Integer
            step_ = 0
            Select Case type
                Case 1 ' level way 方向固定
                    step_ = 512 / level
                    way = step_
                    Dim i As Integer

                    i = 0
                    While i < level
                        tama(n).set_(from_x, from_y, SinTable.get().cos(way), SinTable.get().sin(way), 0, 0, 0, speed)
                        n = search(tama)
                        If n = -1 Then
                            Return
                        End If
                        way += step_
                        i += 1
                    End While
                Case 2 ' 誘導 5way
                    Try
                        way = SinTable.get().atan(CInt(vx), CInt(vy)) >> 7 ' 65536/512
                    Catch
                    End Try
                    tama(n).set_(from_x, from_y, SinTable.get().cos((way + 0)), SinTable.get().sin((way + 0)), 0, 0, 0.005F, speed)
                    n = search(tama)
                    If n = -1 Then
                        Return
                    End If
                    tama(n).set_(from_x, from_y, SinTable.get().cos((way + 30)), SinTable.get().sin((way + 50)), 0, 0, 0.005F, speed)
                    n = search(tama)
                    If n = -1 Then
                        Return
                    End If
                    tama(n).set_(from_x, from_y, SinTable.get().cos((way - 30)), SinTable.get().sin((way - 50)), 0, 0, 0.005F, speed)
                    If level <= 25 Then
                        Return ' level 25以下なら3way
                    End If
                    n = search(tama)
                    If n = -1 Then
                        Return
                    End If
                    tama(n).set_(from_x, from_y, SinTable.get().cos((way + 60)), SinTable.get().sin((way + 100)), 0, 0, 0.005F, speed)
                    n = search(tama)
                    If n = -1 Then
                        Return
                    End If
                    tama(n).set_(from_x, from_y, SinTable.get().cos((way - 60)), SinTable.get().sin((way - 100)), 0, 0, 0.005F, speed)
                Case 0 ' 5way
                    Try
                        way = SinTable.get().atan(CInt(vx), CInt(vy)) >> 7 ' 65536/512
                    Catch
                    End Try
                    tama(n).set_(from_x, from_y, SinTable.get().cos((way + 0)), SinTable.get().sin((way + 0)), 0, 0, 0, speed)
                    n = search(tama)
                    If n = -1 Then
                        Return
                    End If
                    tama(n).set_(from_x, from_y, SinTable.get().cos((way + 30)), SinTable.get().sin((way + 30)), 0, 0, 0, speed)
                    n = search(tama)
                    If n = -1 Then
                        Return
                    End If
                    tama(n).set_(from_x, from_y, SinTable.get().cos((way - 30)), SinTable.get().sin((way - 30)), 0, 0, 0, speed)
                    If level <= 15 Then
                        Return ' level 15以下なら3way
                    End If
                    n = search(tama)
                    If n = -1 Then
                        Return
                    End If
                    tama(n).set_(from_x, from_y, SinTable.get().cos((way + 60)), SinTable.get().sin((way + 60)), 0, 0, 0, speed)
                    n = search(tama)
                    If n = -1 Then
                        Return
                    End If
                    tama(n).set_(from_x, from_y, SinTable.get().cos((way - 60)), SinTable.get().sin((way - 60)), 0, 0, 0, speed)
                Case 3 ' 32 way 方向固定
                    Dim ways As Integer = 32
                    If level < 20 Then
                        ways /= 2
                    End If
                    If level < 10 Then
                        ways /= 2
                    End If
                    step_ = 512 / ways
                    way = step_
                    Dim i As Integer

                    i = 0
                    While i < ways
                        tama(n).set_(from_x, from_y, SinTable.get().cos(way), SinTable.get().sin(way), 0, 0, 0, speed)
                        n = search(tama)
                        If n = -1 Then
                            Return
                        End If
                        way += step_
                        i += 1
                    End While
            End Select
        End Sub


        Shared Function isCross(ByVal x1 As Single, ByVal y1 As Single, ByVal x2 As Single, ByVal y2 As Single, ByVal w As Single, ByVal h As Single) As Boolean
            '  (x1,y1)を左上とする、幅w,高さhの矩形に(x2,y2)が含まれればtrueを返す
            Return (x1 < x2) And (x2 < x1 + w) And (y1 < y2) And (y2 < y1 + h)
        End Function


        '/ <summary>
        '/ アプリケーションのメイン エントリ ポイントです。
        '/ </summary>
        Overloads Shared Function Main(ByVal args() As String) As Integer
            Try

                Dim screen As New Screen
                screen.setCaption("♪ゲー")

                Dim play As IPlay

                screen.beginScreenTest() '  いまからスクリーンテストをする
                screen.testVideoMode(640, 480, 0) ' ウィンドゥモード 640×480をテスト
                screen.endScreenTest() '  テスト終了
                Dim time As New FixTimer

                rand = New Rand
                Dim key As New Key2

                Dim mouse As New MouseInput
                mouse.hide() '  マウスカーソル消しておく
                '  Texture texture = new Texture;
                '  texture.load("Star.gif");
                Dim heart As New Texture
                heart.load("heart.png")

                Dim onpu As New Texture
                onpu.setColorKeyPos(0, 0)
                onpu.load("onpu.gif")

                Dim touon As New Texture
                touon.load("touon.png")

                '  20fps * 60sec * 5
                Dim timing_data() As Byte ' [20*60*5]
                timing_data = CType(FileSys.read("timingdata.bin"), Byte())

                Dim fpsTimer As New FpsTimer
                fpsTimer.setFps(60)

                Dim bgm As New Sound
                bgm.load("nyokiMIX_de44.ogg", 0)

                Dim tx(), ty() As Single ' ト音記号ちゃん
                tx = New Single(3) {}
                ty = New Single(3) {}
                Dim ton(3) As Integer ' ト音記号ちゃんの♪発射後のフェードレート
                Dim x, y As Single '  自機の位置(ハート)
                Dim tama(tama_max - 1) As Tama

                Dim last_scan As Integer ' 前回、弾発射を検査した位置
                Dim score As Integer ' スコア
                Dim myleft As Integer ' 残り機数
                Dim highscore As Integer = 0 ' ハイスコア
                Dim gameover As Boolean
                Dim deadtime As Integer ' 死亡後の処理時間
                Dim charge As Integer ' スペースキーでの貯め
                Dim bx As Integer = 0
                Dim by As Integer = 0
                Dim br As Integer = 0
                Dim bc As Integer = 0
                Dim bn As Integer = 0 ' 倍率の表示
                Dim extend As Integer '  自機のextend
                Dim charged As Boolean ' チャージ中か
replay:
                If args.Length = 0 Then
                    play = New Play
                Else
                    play = New Replay(args(0))
                End If
                charged = False
                extend = 500000
                bc = 0
                bn = 0
                charge = 0
                deadtime = 0
                gameover = False
                myleft = 3
                score = 0
                last_scan = 0
                x = 320
                y = 240
                tx(0) = -touon.getWidth()
                ty(0) = 0
                tx(1) = 640
                ty(1) = 480 - touon.getHeight()
                tx(2) = 0
                ty(2) = 480
                tx(3) = 640 - 100
                ty(3) = -touon.getHeight()
                ton(0) = 0
                ton(1) = 0
                ton(2) = 0
                ton(3) = 0

                For i As Integer = 0 To tama.Length - 1
                    tama(i).reset()
                Next
                level = 0

                bgm.play()

                While GameFrame.pollEvent() = 0
                    ' isPress(0)だけ特別扱い
                    key.update()
                    If key.isPress(0) Then
                        Exit While
                    End If
                    If gameover Then
                        '  enter key で replay
                        play.close()
                        If key.isPush(6) Then
                            GoTo replay
                        End If
                        GoTo moveSkip
                    End If
                    play.update()

                    level = 1 + CInt(play.time() / 10000)
                    If (True) Then
                        Dim dist As Single = 4.0F ' 一回の移動可能距離
                        Dim vx As Single
                        Dim vy As Single
                        vx = 0
                        vy = 0
                        If play.isPress(1) Then
                            vy = -dist
                        End If
                        If play.isPress(2) Then
                            vy = +dist
                        End If
                        If play.isPress(3) Then
                            vx = -dist
                        End If
                        If play.isPress(4) Then
                            vx = +dist
                        End If
                        If vx <> 0 And vy <> 0 Then
                            vx *= 0.7F
                            vy *= 0.7F '  斜め移動のペナルティ
                        End If

                        If play.isPress(5) Then
                            charge = charge + 1
                            charged = True
                        Else
                            '  chargeの量に応じて、弾を反射
                            If charge <> 0 Then
                                Dim num As Integer
                                num = (charge + 59) / 60
                                If num >= 2 Then
                                    Dim d As Single
                                    If num > 20 Then
                                        d = 1 << 25
                                    Else
                                        d = 1 << (num + 6)
                                    End If
                                    bn = 0

                                    For i As Integer = 0 To tama.Length - 1
                                        If Not tama(i).alive Then
                                            GoTo ContinueWhile2
                                        End If
                                        If isCross(tama(i).x - (d / 2), tama(i).y - (d / 2), x, y, d, d) Then
                                            tama(i).reflect(x, y) ' 跳ね返す
                                            score += (200 * num)
                                            bn += 1
                                        End If
ContinueWhile2:
                                    Next

                                    bx = CInt(x)
                                    by = CInt(y)
                                    br = num
                                    bc = 60
                                End If
                                charge = 0
                                charged = False
                            Else
                            End If '  no chargeなのでscoreをtimeに比例して加算
                        End If '        score += ft/100;
                        If charge = 0 Then
                            vx *= 2
                            vy *= 2
                        End If
                        x += vx
                        y += vy
                        If x < 0 Then
                            x = 0
                        End If
                        If x > 640 Then
                            x = 640
                        End If
                        If y < 0 Then
                            y = 0
                        End If
                        If y > 480 Then
                            y = 480
                        End If
                    End If
                    '  ト音記号の移動
                    tx(0) += 2
                    If tx(0) > 640 Then
                        tx(0) = -touon.getWidth()
                    End If
                    tx(1) -= 2
                    If tx(1) < (0 - touon.getWidth()) Then
                        tx(1) = 640
                    End If
                    ty(2) += 2
                    If ty(2) > 480 Then
                        ty(2) = -touon.getHeight()
                    End If
                    ty(3) -= 2
                    If ty(3) < (0 - touon.getHeight()) Then
                        ty(3) = 480
                    End If

                    For i As Integer = 0 To tama.Length - 1
                        tama(i).move(x, y)
                    Next
                    '  弾発生
                    Dim t As Integer
                    t = CInt(play.time() * 0.02)

                    For i As Integer = 0 To 3
                        ton(i) = 0
                    Next
                    '  フレームスキップを考慮して、発射されるはずであった弾をサーチ

                    While last_scan < t
                        ' ひとつ前で押されていなくて、今回押されているの意味
                        Dim d As Byte
                        d = CByte(timing_data(last_scan + 1) And (Not timing_data(last_scan)))
                        Dim w As Integer
                        w = (CInt(touon.getWidth()) / 2)
                        Dim h As Integer
                        h = CInt(touon.getHeight()) / 2
                        If (d And 1) <> 0 Then 'key.isPress(5)) {
                            shot(tama, tx(0) + w, ty(0) + h, x + 50, y + 40, 0)
                        End If
                        If (d And 2) <> 0 Then 'key.isPress(6)) {
                            shot(tama, tx(1) + w, ty(1) + h, x + 50, y + 40, 1)
                        End If
                        If (d And 4) <> 0 Then ' key.isPress(7)) {
                            shot(tama, tx(2) + w, ty(2) + h, x + 50, y + 40, 2)
                        End If
                        If (d And 8) <> 0 Then ' key.isPress(8)) {
                            shot(tama, tx(3) + w, ty(3) + h, x + 50, y + 40, 3)
                        End If
                        '  ♪発射フェーズでは、ト音記号を暗くする
                        Dim d2 As Byte
                        d2 = timing_data(last_scan)
                        If (d And 1) <> 0 Then
                            ton(0) = 128
                        End If
                        If (d And 2) <> 0 Then
                            ton(1) = 128
                        End If
                        If (d And 4) <> 0 Then
                            ton(2) = 128
                        End If
                        If (d And 8) <> 0 Then
                            ton(3) = 128
                        End If
                        last_scan += 1
                    End While
                    score += 10
                    If (True) Then
                        '
                        Dim w As Integer = CInt(touon.getWidth())
                        Dim h As Integer = CInt(touon.getHeight())

                        For i As Integer = 0 To 3
                            If isCross(tx(i), ty(i), x, y, w, h) Then
                                '  score += 1000; // もりもりアップ!
                                If charged Then
                                    charge += 2 ' チャージ中なら高速チャージ
                                End If
                            End If
                        Next
                    End If
                    '  当たり判定
                    If deadtime = 0 Then
                        Const d As Single = 10.0F ' 判定矩形サイズ

                        For i As Integer = 0 To tama.Length - 1
                            If Not tama(i).alive Then
                                GoTo ContinueWhile3
                            End If
                            If isCross(tama(i).x - d / 2, tama(i).y - d / 2, x, y, d, d) Then
                                deadtime = 100 ' 死亡カウント
                            End If
ContinueWhile3:
                        Next

                    Else
                        deadtime -= 1
                        If deadtime = 0 Then
                            ' 死亡
                            myleft -= 1
                            charge = 200 ' 復活サービス
                        End If
                    End If

                    If myleft < 0 Then
                        gameover = True
                        bgm.stop()
                    End If

                    If bc <> 0 Then
                        bc = bc - 1
                    End If
                    '  ----- ↑↑ 遅いマシンでも移動処理だけは保証する
moveSkip:

                    fpsTimer.waitFrame()
                    If fpsTimer.toBeSkip() Then
                        GoTo ContinueWhile1 ' 描画キャンセル
                    End If
                    screen.setClearColor(255, 255, 255)
                    screen.clear()
                    screen.blendSrcAlpha()
                    '    screen.disableBlend();
                    '  自機のextend (every 500000)
                    If score > extend Then
                        myleft += 1
                        extend += 500000
                    End If

                    '  残機
                    Dim hs As New Size
                    Dim r As New Rect
                    hs.setSize(CInt(heart.getWidth()) / 2, CInt(heart.getHeight()) / 2)
                    screen.setColor(255, 255, 255)

                    For i As Integer = 0 To myleft - 1
                        screen.blt(heart, CInt(i * heart.getWidth() / 2), 50, r, hs)
                    Next

                    '  ト音記号の描画
                    Dim faderate As Single = CSng(SinTable.get().cos0_1(CInt(play.time() / 4)))
                    Dim fade127 As Single = faderate * 127

                    If (True) Then
                        Dim i As Integer
                        i = 0
                        While i < 4
                            Dim tn As Integer
                            tn = ton(i)
                            screen.blendSrcAlpha()
                            Select Case i
                                Case 0
                                    GoTo Case1
                                Case 1
Case1:

                                    screen.setColor(CInt(128 + fade127 - tn), CInt(128 + fade127 - tn), CInt(128 + fade127 - tn))
                                Case 2
                                    screen.setColor(CInt(128 + fade127 - tn), 0, 0)
                                Case 3
                                    screen.setColor(0, 0, CInt(128 + fade127 - tn))
                            End Select
                            screen.blt(touon, CInt(tx(i)), CInt(ty(i)))
                            i += 1
                        End While
                    End If

                    '  自機
                    If Not gameover Then
                        screen.setColor(255, 255, 255)
                        If deadtime = 0 Then
                            screen.blt(heart, CInt(x) - 50, CInt(y) - 40) ' ハートの中心が指定座標となるように
                            If charge <> 0 Then
                                Dim s As New Size
                                Dim dmy As New Rect
                                s.setSize(CInt(heart.getWidth() / 4), CInt(heart.getHeight() / 4))
                                Dim offset As Integer = CInt(play.time()) / 2 ' 小さなハートくるくるまわす
                                Dim num As Integer = (charge + 59) / 60 ' 小さなハートの数
                                If num > 10 Then
                                    num = 10
                                End If

                                If (True) Then
                                    Dim i As Integer
                                    i = 0
                                    While i < num And i < 10
                                        Dim xx As Single = SinTable.get().cos((i * 512 / num + offset)) >> 10
                                        Dim yy As Single = SinTable.get().sin((i * 512 / num + offset)) >> 10
                                        screen.blt(heart, CInt(x - 50 / 4 + xx), CInt(y - 40 / 4 + yy), dmy, s)
                                        i += 1
                                    End While
                                End If
                            End If
                        Else
                            Dim s As New Size
                            Dim dmy As New Rect
                            s.setSize(CInt(heart.getWidth() * deadtime / 100), CInt(heart.getHeight() * deadtime / 100))
                            screen.blt(heart, CInt(x - 50 * deadtime / 100), CInt(y - 40 * deadtime / 100), dmy, s)
                        End If
                    End If

                    '  ♪の描画
                    screen.blendSrcAlpha()
                    For i As Integer = 0 To tama.Length - 1
                        tama(i).onDraw(screen, onpu)
                    Next
                    screen.setColor(0, 0, 200)
                    screen.setLineWidth(5)
                    screen.drawString(StringConv.toConvHelpperU(score, 10, 8, " "c), 10, 10, 30)

                    '    if (!bgm.isPlay()) break;
                    If play.time() >= 1000 * (60 * 4 + 2 + 5) Then
                        '  goto replay; // 曲end
                        If Not gameover Then
                            gameover = True
                            extend = Integer.MaxValue ' extendしない
                            score = score * 2 + myleft * 1000000
                        End If
                    End If

                    If highscore < score Then
                        highscore = score
                    End If
                    screen.setColor(0, 200, 0)
                    screen.drawString(StringConv.toConvHelpperU(highscore, 10, 8, " "c), 320, 10, 30)

                    If bc <> 0 Then
                        screen.setColor(255, 0, 0)
                        screen.drawString(StringConv.toDec(bn) + "x" + StringConv.toDec(br), bx, by - 50, 30)
                    End If

                    time.update()
                    screen.update()
ContinueWhile1:
                End While

            Catch e As Exception
                Console.WriteLine("例外が発生 : {0}", e.Message)
            End Try

            Return 0
        End Function
    End Class
End Namespace