Haskellでポン!(4)
今回はポンの完成偏です。
まずGUI部分だけを作って、ロジックは後からつくろうとも思いましたが、
分割するほどコード量が多くないので、結局一度に書きます。
本当に最小限のポンです。ボールのスピードを変化させたりする前に力尽きました。
基本
ウィジェットの定義やコールバックは、IO()をdo文を使って書きます。
リストの各要素に対して、アクションを実行したいときは、"mapM_"を使います。
たとえば、次は
do mapM_ print ["Spam", "Egg", "Ham"]
は以下と同等です。
do print "Spam" print "Egg" print "Ham"
mapM_はHaskellにもともと入っている物なので、WxHaskell以外でも使います。
変数
状態変化が必要な場所ではvar***を使います。
| 初期化 | var <- varCreate value |
| 参照 | varGet value |
| 代入 | varSet var value |
| 更新 | varUpdate var f |
ここでvarUpdateは、varに格納された値をfに渡し、返り値をvarに代入します。
また、varGet以外は、全てdo文の中でしか使えません。
コールバックの設定
コールバック関数や表示方法などの設定は、
f <- frame [on paint := paintPon vPon
,clientSize := size]のように、ウィジェットの初期化時に設定するか、
set t [on command := update t vPon vKeyboard f]
set で設定します。
Windowsでの不具合
Windowsでは on (charKey char)が使えません。
そのため、sampleのBouncingBallsでキーボードが効きません。
かわりにGraphics.UI.WXCoreをimportして、windowOnKeyDownやwindowOnKeyUpを使います。
感想
HaskellはGUIに向いていない?
キーボード処理の不具合など、WxHaskellはwxPythonに比べて未完成なようです。
GUI部分を書く場合には、HaskellがPythonなどの手続き型言語よりも、
特に優れているとは感じませんでした。
do で順番に書いていくだけなので。
一方でロジック部を書く場合は、破壊的な操作が必要ないので書きやすいと感じました。
完成したコード
import Graphics.UI.WX
import Graphics.UI.WXCore
import Data.List
data Ball
= Ball{
ballPos::Vector,
ballRadius::Int,
ballVel::Vector
}
data Bar
= Bar {
barPos::Vector,
barSize::Int
}
data Pon
= Pon {
ponBalls::[Ball],
ponBars::[Bar],
ponSize::Size
}
main = start mainFrame
mainFrame =
do let pon = ponCreate
size = ponSize pon
vKeyboard <- varCreate []
vPon <- varCreate pon
f <- frame [on paint := paintPon vPon
,clientSize := size]
t <- timer f [interval := 20]
set t [on command := update t vPon vKeyboard f]
windowOnKeyDown f $ onKeyDown vKeyboard
windowOnKeyUp f $ onKeyUp vKeyboard
return ()
where
paintPon vPon dc view
= do pon <- varGet vPon
mapM_ (drawABall dc) $ ponBalls pon
mapM_ (drawABar dc) $ ponBars pon
drawABall dc ball
= do set dc [brushColor := red, brushKind := BrushSolid]
circle dc (pointFromVec $ ballPos ball) (ballRadius ball) []
drawABar dc bar
= do let x = vecX (barPos bar)
top = vecY (barPos bar) - barSize bar
bottom = vecY (barPos bar) + barSize bar
line dc (pt x top) (pt x bottom) []
update timer vPon vKeyboard view
= do pon <- varGet vPon
if ponIsGameOver pon
then
do
set timer [enabled := False]
beep
else
do keyboard <- varGet vKeyboard
mapM_ (moveBar vPon) keyboard
varUpdate vPon ponUpdate
repaint view
beep
= wxcBell --putStr "\a"
onKeyDown vKeyboard evt
= do varUpdate vKeyboard $ union [keyKey evt]
return ()
onKeyUp vKeyboard evt
= do varUpdate vKeyboard $ delete $ keyKey evt
return ()
moveBar vPon k
= case k of
KeyChar 'Q' -> moveBar' vPon 0 (-5)
KeyChar 'A' -> moveBar' vPon 0 5
KeyUp -> moveBar' vPon 1 (-5)
KeyDown -> moveBar' vPon 1 5
_ -> skipCurrentEvent
moveBar' vPon i dy
= do varUpdate vPon (\pon -> ponMoveBar pon i dy)
return ()
{- ここから、ゲームのロジック部-}
-- 初期状態
ponCreate = let ball = Ball (vec 150 150) 5 (vec 5 5)
bar1 = Bar (vec 30 150) 30
bar2 = Bar (vec 570 150) 30
size = sz 600 300
in Pon [ball] [bar1, bar2] size
ponUpdate pon
= pon {ponBalls=(map (\ball -> ballPosUpdate ball pon) (ponBalls pon))}
ballPosUpdate ball pon
= let pos = ballPos ball
vel = ballVel ball
line = (pos, vecAdd pos vel, vel)
bars = ponBars pon
reflects = [reflect_top pon, reflect_bottom pon] ++ map reflect_bar bars
(from, to, vel1) = foldr (\f li -> f li) line reflects
in ball {ballPos=to, ballVel=vel1}
reflect_top::Pon -> (Vector, Vector, Vector) -> (Vector, Vector, Vector)
reflect_top pon line
= let (from, to, vel) = line
Vector {vecX=x1, vecY=y1} = to
Vector {vecX=x0, vecY=y0} = from
in
if y1 >= 0
then line
else
let y = -y1
x = div (y0 * x1 - y * x0) (y0 + y)
in (vec x 0, vec x1 y, vecInvY vel)
vecInvX v = v {vecX= - vecX v}
vecInvY v = v {vecY= - vecY v}
reflect_bottom::Pon -> (Vector, Vector, Vector) -> (Vector, Vector, Vector)
reflect_bottom pon line
= let (from, to, vel) = line
Vector {vecX=x1, vecY=y1} = to
Vector {vecX=x0, vecY=y0} = from
h = sizeH $ ponSize pon
in
if y1 <= h
then line
else
let y = 2 * h - y1
x = div ((h - y0) * x1 + (h - y) * x0) ((h - y0) + (h - y))
in (vec x h, vec x1 y, vecInvY vel)
reflect_bar::Bar -> (Vector, Vector, Vector) -> (Vector, Vector, Vector)
reflect_bar bar line
= let (from, to, vel) = line
Vector {vecX=x1, vecY=y1} = to
Vector {vecX=x0, vecY=y0} = from
Vector {vecX=bx, vecY=by} = barPos bar
crossY
= if x1 - x0 == 0
then y1
else (div (bx - x0) (x1 - x0)) * (y1 - y0) + y0
in
if ((abs (crossY - by)) <= (barSize bar)) &&
((x1 == bx) || (x1 < bx && bx < x0) || (x1 > bx && bx > x0))
then (vec bx 0, vec (2 * bx - x1) y1, vecInvX vel)
else line
ponMoveBar pon i dy
= if i < (length $ ponBars pon)
then ponMoveBar' pon i dy
else pon
ponMoveBar' pon i dy
= let height= sizeH $ ponSize pon
bars = (ponBars pon)
bar = bars !! i
bar1 = barMove bar dy height
bars1 = (take i bars) ++ [bar1] ++ (drop (i + 1) bars)
in pon {ponBars=bars1}
barMove bar dy height
= let pos = barPos bar
y = vecY pos
size = barSize bar
y1 = max size $ min (height - size) (y + dy)
pos1 = pos {vecY=y1}
in bar {barPos=pos1}
ponIsGameOver pon
= let xList = map (\ball -> vecX $ ballPos ball) $ ponBalls pon
width = sizeW $ ponSize pon
in all (\x -> x <= 0 || width <= x) xList
