「残念ながら・・・お子さんは・・・」
非ッ常に情けねえことになってしまいましたが、
例のテトリスのお話、一旦畳もう・・・というおはなしです。
時間ばかりが過ぎていってしまう。足りないものが多すぎる。
やろうとしたことは色々あるわけですけど、
それぞれを細かく学び直してからリベンジしていこうな・・・となりました。
- 型拡張の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)