なのログ

だから見てて下さい・・・俺の・・・変身

ぐだぐだクソコード vol.1-2 残念ながら中断です

「残念ながら・・・お子さんは・・・」

非ッ常に情けねえことになってしまいましたが、
例のテトリスのお話、一旦畳もう・・・というおはなしです。
時間ばかりが過ぎていってしまう。足りないものが多すぎる。

やろうとしたことは色々あるわけですけど、
それぞれを細かく学び直してからリベンジしていこうな・・・となりました。

  • 型拡張のtype クラスのtype
  • 非同期処理
  • コンピュテーション式(とりあえずOptionをモナドで)
  • そもそもF#(関数型)のイディオムとかお作法
    後方参照の中でうまく書く方法とかね
    immutableな世界でのやりかたとかね
  • あとそもそももうちょいプログラムを書け

とかとかそんなんです。
C#のお勉強に戻ろうと思います。

型システム入門とか、本の話はまた別でつづいていきます。
ではまたまた。

続きんとこに、書きかけコードでものせておきますね。
(ビルドもしていない状態な模様)

module App

open System
open FsXaml
open System.Windows
open System.Windows.Input

type MainView = XAML<"MainWindow.xaml">

// ミノの定義 T,Fは趣味
let T = true
let F = false
let MINO_I_MAP:bool[,] = array2D [[F;F;F;F;F];[F;F;F;F;F];[F;T;T;T;T];[F;F;F;F;F];[F;F;F;F;F]]
let MINO_L_MAP:bool[,] = array2D [[F;F;F;F;F];[F;F;F;T;F];[F;T;T;T;F];[F;F;F;F;F];[F;F;F;F;F]]
let MINO_J_MAP:bool[,] = array2D [[F;F;F;F;F];[F;T;F;F;F];[F;T;T;T;F];[F;F;F;F;F];[F;F;F;F;F]]
let MINO_Z_MAP:bool[,] = array2D [[F;F;F;F;F];[F;T;T;F;F];[F;F;T;T;F];[F;F;F;F;F];[F;F;F;F;F]]
let MINO_S_MAP:bool[,] = array2D [[F;F;F;F;F];[F;F;T;T;F];[F;T;T;F;F];[F;F;F;F;F];[F;F;F;F;F]]
let MINO_T_MAP:bool[,] = array2D [[F;F;F;F;F];[F;F;T;F;F];[F;T;T;T;F];[F;F;F;F;F];[F;F;F;F;F]]
let MINO_O_MAP:bool[,] = array2D [[F;F;F;F];[F;T;T;F];[F;T;T;F];[F;F;F;F]]
let MINO_MAP = [|MINO_I_MAP,'■';MINO_L_MAP,'▲';MINO_J_MAP,'△';MINO_Z_MAP,'★';MINO_S_MAP,'☆';MINO_T_MAP,'◆';MINO_O_MAP,'□'|]
// ↑のミノのメモ
//I          L          J          Z          S          T          O
//□□□□□ □□□□□ □□□□□ □□□□□ □□□□□ □□□□□ □□□□
//□□□□□ □□□■□ □■□□□ □■■□□ □□■■□ □□■□□ □■■□
//□■■■■ □■■■□ □■■■□ □□■■□ □■■□□ □■■■□ □■■□
//□□□□□ □□□□□ □□□□□ □□□□□ □□□□□ □□□□□ □□□□
//□□□□□ □□□□□ □□□□□ □□□□□ □□□□□ □□□□□ 

//初期座標
let NEXT_MINO_POS = (-1,3)
let START_MINO_POS = (1,3)

//画面情報 サイズとか
let DISPLAY_SIZE_Y,DISPLAY_SIZE_X = 23,12
let GAMEFIELD_SIZE_Y,GAMEFIELD_SIZE_X = 20,10
let MINO_SIZE_Y,MINO_SIZE_X = 5,5

let DISPLAY_DEFAULT = "\
            \n\
NEXT        \n\
┏━━      ━━┓\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┃          ┃\n\
┗━━━━━━━━━━┛"
let DISPLAYLINE_GAMEOVER = "※※GAMEOVER※※"

//他の記号(マップチップ・・・?)
let MAPCHIP_EMPTY = ' '
let MAPCHIP_ERASE = '※'

// TODO: 構造化する(まるでイメージできていない)
[<STAThread>]
[<EntryPoint>]
let main argv = 
    let view = MainView()
    
    // それぞれ座標は[y,x]になる 行列の添字と同じ
    // 背景付き これをそのままstringにして表示
    let gameDisplay:char[,] = array2D (seq {for s in DISPLAY_DEFAULT.Split('\n') -> seq {for c in s -> c} })
    // ミノが積まれていく範囲
    let gameField:char[,] = Array2D.create GAMEFIELD_SIZE_Y GAMEFIELD_SIZE_X MAPCHIP_EMPTY
    // 操作中のミノと座標、NEXTのミノ
    let mutable currentMino:char[,] = Array2D.create MINO_SIZE_Y MINO_SIZE_X MAPCHIP_EMPTY
    let mutable minoPosY,minoPosX = START_MINO_POS
    let mutable nextMino:char[,] = Array2D.create MINO_SIZE_Y MINO_SIZE_X MAPCHIP_EMPTY
    
    let mutable prevTime = DateTime.Now
    let rnd = new Random()

    // 初期処理
    view.lblMainField.Content <- DISPLAY_DEFAULT


    // 領域外判定
    let isIndexInArray ary idx = (0 <= idx) && (idx <= ((Array.length ary)-1))

    // 正方配列左90度回転
    let rotateAry ary =
        let isSquare = (Array2D.length1 ary) = (Array2D.length2 ary)
        isSquare |> function
        | false ->
            ary
            // 回転方法ワールドルール?に対応して正方でなくなる時があったらここで
            // InvalidArgでも投げたらよい?ゲーム止まるからダメでは・・・?異常だからそれはそういうもんか
        | true ->
            let size = (Array2D.length1 ary)
            let rotated = Array2D.zeroCreate size size
            Array2D.iteri (fun y x v -> (rotated.[(size-1)-x,y] <- v)) ary
            rotated

    // nextMinoPosを作る
    let newMinoGenerate (rnd:Random) (minoMap:(bool[,]*char)[]) =
        let katati,moji = minoMap.[minoMap |> Array.length |> rnd.Next]
        katati |> Array2D.map (fun x -> if x then moji else MAPCHIP_EMPTY)

    // 行揃った判定
    let eraseCheck (gameField:char[,]) =
        //let minoArea = Array2D.create (GAMEFIELD_SIZE_Y+1) GAMEFIELD_SIZE_X MAPCHIP_EMPTY
        //Array2D.blit mainField 0 0 minoArea 0 0 GAMEFIELD_SIZE_Y GAMEFIELD_SIZE_X
        //Array2D.iteri (fun y x v -> minoArea.[y,x] <- mainField.[y+3,x+1]) minoArea //blitにした
        
        //こんだけでいけるくない?↓
        //let rec 
        for y in 0..(Array2D.length1 gameField)-1 do
            match Array.tryFind (fun v -> v = MAPCHIP_EMPTY) gameField.[y,*] with
            | Some _ -> () // printfn "eraseCheck Some %s %A" (y.ToString()) minoArea.[y,*]
            | None -> Array.iteri (fun x v -> (gameField.[y,x] <- MAPCHIP_ERASE)) gameField.[y,*]
        done
        
        gameField

    // ※※の行を消す
    let lineErase (gameField:char[,]) =
    // REVIEW: なんか計算量多い気がする(雰囲気でものを言いがち)
    // > 実際、※がタテに続いているところがあったらそこはいっきに潰せる。
    // > 再帰でできそう
        let minoArea = Array2D.create (GAMEFIELD_SIZE_Y+1) GAMEFIELD_SIZE_X MAPCHIP_EMPTY
        Array2D.blit gameField 0 0 minoArea 1 0 GAMEFIELD_SIZE_Y GAMEFIELD_SIZE_X
        Array2D.iteri (fun y x v -> if v = MAPCHIP_ERASE
                                    then ( for y' in y..(-1)..1 do minoArea.[y',x] <- minoArea.[y'-1,x]
                                    )) minoArea
        Array2D.blit minoArea 1 0 gameField 0 0 GAMEFIELD_SIZE_Y GAMEFIELD_SIZE_X
        gameField
    
    // ぶつかり確認 操作前とか移動前とかにやる
    let collisionCheck (mainField:char[,]) (mino:char[,]) (minoPos:int*int) =
        let mutable result = Some (mino,minoPos)
        let collisionCheck' (offsetY,offsetX) idxY idxX elem =
            let y,x = offsetY+idxY,offsetX+idxX
            if isIndexInArray mainField.[*,0] y &&
               isIndexInArray mainField.[0,*] x &&
               (elem <> MAPCHIP_EMPTY) &&
               mainField.[y,x] <> MAPCHIP_EMPTY
            then result <- None

        Array2D.iteri (collisionCheck' minoPos) mino
        result

    // REVIEW: ↑こいつら↓抽象化してまとめれそうすぎる
    // 動いてるミノと動いてない画面を重ねる
    let projectionMino (mainField:char[,]) (mino:char[,]) (minoPos:(int*int)) =
        let projectedField = Array2D.rebase mainField
        let projectionAry (offsetY,offsetX) idxY idxX elem =
            let y,x = offsetY+idxY,offsetX+idxX
            if isIndexInArray projectedField.[*,0] y && 
               isIndexInArray projectedField.[0,*] x && 
               elem <> MAPCHIP_EMPTY
            then projectedField.[y,x] <- elem

        Array2D.iteri (projectionAry minoPos) mino
        projectedField

    // char[,]を改行付きStringにする
    let generatePrintString (sourceField:char[,]) =
        // TODO:なんか付け足し付け足しする再帰か何かでimmutableにできるとおもう
        let mutable printString = ""
        for i in 0..(Array2D.length1 sourceField)-1 do
            sourceField.[i,*] |> Array.iter (fun c -> (printString <- printString + (c|>string)))
            printString <- printString + ("\n")
        done
        printString.Trim('\n')

    // 背景と今minoと次minoを重ねて描画するやつ(中でgeneratePrintStringしている)
    let gamePrint mainField currentMino currentPos nextMino =
        let currentProjected = projectionMino mainField currentMino currentPos
        let bothProjected = projectionMino currentProjected nextMino NEXT_MINO_POS
        view.lblMainField.Content <- (generatePrintString bothProjected)

    // 進むやつ
    // TODO: 仕事を分ける
    let gameLoop = async {
        // TODO: こういうループも再帰で書けるわよ
        while true do
            let now = DateTime.Now
            let dt = now - prevTime

            if dt.TotalSeconds > 0.8
            then
                //printfn "prev: %A, now: %A,dt: %A" prevTime now dt.TotalSeconds
                prevTime <- now
                
                gameDisplay <- lineErase gameDisplay
                match collisionCheck gameDisplay currentMino (minoPosY+1,minoPosX) with
                | Some (m,(y,x)) -> minoPosY <- y
                | None -> gameDisplay <- projectionMino gameDisplay currentMino (minoPosY,minoPosX)
                          if minoPosY = fst START_MINO_POS
                          then
                          //このへんもう力尽きてなんの保守性もない
                            let gameDisplayCenterLine = gameDisplay |> Array2D.length1 |> float |> (*) 0.5 |> Math.Round |> int
                            DISPLAYLINE_GAMEOVER.ToCharArray() |> Array.iteri (fun x v -> gameDisplay.[gameDisplayCenterLine,x] <- v)
                          else
                            gameDisplay <- eraseCheck gameDisplay
                            currentMino <- nextMino
                            minoPosY <- fst START_MINO_POS 
                            minoPosX <- snd START_MINO_POS
                            nextMino <- newMinoGenerate rnd MINO_MAP

                view.Dispatcher.Invoke(fun () -> gamePrint gameDisplay currentMino (minoPosY,minoPosX) nextMino)
        done
        }

    // ループ発火
    Async.Start gameLoop

    // ここからイベント    
    view.btnStart.Click.Add(fun _ ->
        gameDisplay <- array2D (seq {for s in DEFAULT_DISPLAY.Split('\n') ->
                                   seq {for c in s -> c}
                             })
        currentMino <- newMinoGenerate rnd MINO_MAP
        minoPosY <- fst START_MINO_POS 
        minoPosX <- snd START_MINO_POS
        nextMino <- newMinoGenerate rnd MINO_MAP
        gamePrint gameDisplay currentMino (minoPosY,minoPosX) nextMino
    )

    //キー入力のイベント
    view.KeyDown.Add(fun arg ->
        match arg.Key with
        | Key.Up    -> match collisionCheck gameDisplay (rotateAry currentMino) (minoPosY,minoPosX) with
                        | Some (m,(y,x)) -> currentMino <- m
                        | None -> () // printfn("collision")
        | Key.Left  -> match collisionCheck gameDisplay currentMino (minoPosY,minoPosX-1) with
                        | Some (m,(y,x)) -> minoPosX <- x
                        | None -> () // printfn("collision")
        | Key.Right -> match collisionCheck gameDisplay currentMino (minoPosY,minoPosX+1) with
                        | Some (m,(y,x)) -> minoPosX <- x
                        | None -> () // printfn("collision")
        | Key.Down  -> while (collisionCheck gameDisplay currentMino (minoPosY+1,minoPosX) <> None) do
                        minoPosY <- minoPosY+1
                       done
        | _ -> () // printfn("non effect key")
        
        gamePrint gameDisplay currentMino (minoPosY,minoPosX) nextMino
    )
   
    let app = Application()
    app.Run(view)