Skip to content

Commit

Permalink
Comments and new game unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
epatrizio committed May 10, 2022
1 parent 9928fd7 commit ab443a4
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 15 deletions.
11 changes: 10 additions & 1 deletion src/Game.hs
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,17 @@ instance Show GameState where
(if l1 < l2 then "\ESC[31m" else "\ESC[32m") <> show n1 <> "\ESC[0m (" <> show l1 <> ") - " <>
(if l2 < l1 then "\ESC[31m" else "\ESC[32m") <> show n2 <> "\ESC[0m (" <> show l2 <> ")"

-- Life point must be positive
prop_inv_fighterState :: FighterState -> Bool
prop_inv_fighterState KO = True
prop_inv_fighterState (OK life) = life > 0

-- Fighter.touchF must b False if Fighter.actionF != Kick
prop_inv_fighterTouch :: Fighter -> Bool
prop_inv_fighterTouch Fighter { actionF = Kick } = True
prop_inv_fighterTouch Fighter { actionF = None, touchF = hasTouch } = not hasTouch
prop_inv_fighterTouch Fighter { actionF = _, touchF = hasTouch } = not hasTouch

-- | Invariant GameState (check fighters status and position in Zone)
prop_inv_gameState :: GameState -> Bool
prop_inv_gameState (GameOver _) = True
prop_inv_gameState GameIn { fighter1 = Fighter { stateF=KO }} = False
Expand All @@ -74,9 +77,11 @@ prop_inv_gameState GameIn {
prop_inv_zone_coord z c1 && prop_inv_zone_coord z c2 &&
prop_inv_fighterTouch f1 && prop_inv_fighterTouch f2

-- | Fighter smart constructor (Alive with 10 life point)
createFighter :: Integer -> String -> Integer -> Integer -> Hitbox -> Direction -> Fighter
createFighter id name x y h d = Fighter (FighterId id) name (Coord x y) h d None (OK 10) False

-- | Game smart constructor
createGameState :: Integer -> Integer -> String -> String -> GameState
createGameState sw sh name1 name2 =
GameIn
Expand All @@ -86,6 +91,8 @@ createGameState sw sh name1 name2 =
5
True

-- | Fighter movement (= GameState change)
-- Nb. Check Hitbox safe movement
moveD :: Integer -> Direction -> GameState -> GameState
moveD _ _ (GameOver fid) = GameOver fid
moveD 1 dir (GameIn f1@(Fighter i1 n1 c1 h1 d1 a1 s1 t1) f2 z s p) =
Expand All @@ -101,6 +108,8 @@ moveD 2 dir (GameIn f1 f2@(Fighter i2 n2 c2 h2 d2 a2 s2 t2) z s p) =
True -> GameIn f1 (Fighter i2 n2 c hb d2 a2 s2 t2) z s p
False -> GameIn f1 f2 z s p

-- | Fighter action management (central procedure!)
-- Nb. A fighter could touch the other one only if he is in Kick action and the other one in None position
action :: Integer -> FighterAction -> GameState -> GameState
action _ _ (GameOver fid) = GameOver fid
action 1 None (GameIn (Fighter i1 n1 c1 h1 d1 _ s1 _) f2 z s p) = GameIn (Fighter i1 n1 c1 h1 d1 None s1 False) f2 z s p
Expand Down
29 changes: 18 additions & 11 deletions src/Hitbox.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,50 @@ instance Show Hitbox where
show (Rect c w h) = "Top left:" <> (show c) <> " rect dim = w:" <> (show w) <> " h:" <> (show h)
show (Composite shb) = "HB1 (Normal): " <> (show (S.index shb 0)) <> " - (Kick): " <> (show (S.index shb 1))

-- | Hitbox not empty and well formed
prop_inv_hb :: Hitbox -> Bool
prop_inv_hb (Rect c w h) = prop_inv_coordinates c && w>0 && h>0
prop_inv_hb (Composite s) = case s of
Empty -> False
_ -> True
seq -> foldr (\rect res -> res && (prop_inv_hb rect)) True seq

-- | Specially for the game, Hitbox composed exactly 4 rectangles well formed
prop_inv_hitbox :: Hitbox -> Bool
prop_inv_hitbox (Rect _ _ _) = False
prop_inv_hitbox (Composite s) = (S.length s)==4 && foldr (\rect res -> res && (prop_inv_hb rect)) True s
prop_inv_hitbox (Composite s) = (S.length s)==4 && prop_inv_hb (Composite s)

-- Hitbox must be inside Zone
-- | Hitbox must be inside Zone
prop_inv_zone_hitbox :: Zone -> Hitbox -> Bool
prop_inv_zone_hitbox (Zone w h) (Rect (Coord x y) rw rh) =
prop_inv_zone_coord (Zone w h) (Coord x y) && prop_inv_zone_coord (Zone w h) (Coord (x+rw) (y+rh))
prop_inv_zone_hitbox (Zone w h) (Composite s) =
foldr (\rect res -> res && (prop_inv_zone_hitbox (Zone w h) rect)) True s

-- | Game Hitbox smart constructor
-- Warning : sizes are hardcoded
createHitbox :: Integer -> Integer -> Integer -> Hitbox
createHitbox 1 x y = Composite (S.fromList
[(Rect (Coord x y) 80 160), -- None
(Rect (Coord x y) 110 160), -- Kick
(Rect (Coord x (y-135)) 80 135), -- Jump
(Rect (Coord x y) 80 160)]) -- Protect
[(Rect (Coord x y) 80 160), -- 0: None
(Rect (Coord x y) 110 160), -- 1: Kick
(Rect (Coord x (y-135)) 80 135), -- 2: Jump
(Rect (Coord x y) 80 160)]) -- 3: Protect
createHitbox 2 x y = Composite (S.fromList
[(Rect (Coord x y) 80 160), -- None
(Rect (Coord (x-30) y) 110 160), -- Kick
(Rect (Coord x (y-135)) 80 135), -- Jump
(Rect (Coord x y) 80 160)]) -- Protect
[(Rect (Coord x y) 80 160), -- 0: None
(Rect (Coord (x-30) y) 110 160), -- 1: Kick
(Rect (Coord x (y-135)) 80 135), -- 2: Jump
(Rect (Coord x y) 80 160)]) -- 3: Protect

-- move = create a new one at a specific position (Coordinates)
-- movement intelligence lies in the function Game.moveD
moveHitbox :: Integer -> Coordinates -> Hitbox
moveHitbox fid (Coord x y) = createHitbox fid x y

-- Warning (naive simplification, cf. touchHitbox function), Hitbox intersection = Rectangle (1/0 - 0/1) intersection
rectIntersect :: Hitbox -> Hitbox -> Bool
rectIntersect (Rect (Coord x1 y1) w1 h1) (Rect (Coord x2 y2) w2 h2) = not ( x1+w1<x2 || x2+w2<x1 || y1+h1<y2 || y2+h2<y1 )
rectIntersect _ _ = False

-- Warning (naive simplification), this is only for fighting (KICK) action : only touch case = Kick image intersect with None image
touchHitbox :: Hitbox -> Hitbox -> Bool
touchHitbox (Composite shb1) (Composite shb2) =
let (Rect r1topLeft w1 h1) = S.index shb1 1 in
Expand Down
24 changes: 21 additions & 3 deletions test/GameSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,32 @@ gameUT = do
prop_inv_zone_hitbox z (hitboxF f1) &&
prop_inv_zone_hitbox z (hitboxF f2)
`shouldBe` True
it "Specific action KO" $
it "Specific action KO (hb don't touch - action 1)" $ -- At the beginning, fighters don't touch each other
let g@(GameIn { fighter2 = f2@(Fighter { stateF = OK l2 }) }) = createGameState 1024 531 "name1" "name2" in
let GameIn { fighter2 = Fighter { stateF = OK al2 } } = action 1 Kick g in
al2 `shouldBe` l2
it "Specific action OK" $
it "Specific action KO (hb don't touch - action 2)" $
let g@(GameIn { fighter1 = f1@(Fighter { stateF = OK l1 }) }) = createGameState 1024 531 "name1" "name2" in
let GameIn { fighter1 = Fighter { stateF = OK al1 } } = action 2 Kick g in
al1 `shouldBe` l1
it "Specific action OK (hb touch)" $ -- Move before action (for touch)
let GameIn f1 (Fighter i2 n2 c2 h2 d2 a2 (OK l2) t2) z s i = createGameState 1024 531 "name1" "name2" in
let GameIn { fighter2 = Fighter { stateF = OK al2 } } = action 1 Kick (GameIn f1 (Fighter i2 n2 c2 (moveHitbox 2 (Coord 350 350)) d2 a2 (OK l2) t2) z s i) in
let GameIn { fighter2 = Fighter { stateF = OK al2 } } = action 1 Kick (GameIn f1 (Fighter i2 n2 c2 (moveHitbox 2 (Coord 350 350)) d2 None (OK l2) t2) z s i) in
al2 `shouldBe` (l2-1)
it "Specific action KO (hb touch, but Protect)" $
let GameIn f1 (Fighter i2 n2 c2 h2 d2 a2 (OK l2) t2) z s i = createGameState 1024 531 "name1" "name2" in
let GameIn { fighter2 = Fighter { stateF = OK al2 } } = action 1 Kick (GameIn f1 (Fighter i2 n2 c2 (moveHitbox 2 (Coord 350 350)) d2 Protect (OK l2) t2) z s i) in
al2 `shouldBe` l2
it "Specific action KO (hb touch, but Jump)" $
let GameIn f1 (Fighter i2 n2 c2 h2 d2 a2 (OK l2) t2) z s i = createGameState 1024 531 "name1" "name2" in
let GameIn { fighter2 = Fighter { stateF = OK al2 } } = action 1 Kick (GameIn f1 (Fighter i2 n2 c2 (moveHitbox 2 (Coord 350 350)) d2 Jump (OK l2) t2) z s i) in
al2 `shouldBe` l2
it "Specific action OK (> GameOver fid:1 win)" $
let GameIn f1 (Fighter i2 n2 c2 h2 d2 a2 (OK l2) t2) z s i = createGameState 1024 531 "name1" "name2" in
let GameOver (FighterId fid) = action 1 Kick (GameIn f1 (Fighter i2 n2 c2 (moveHitbox 2 (Coord 350 350)) d2 None (OK 1) t2) z s i) in
fid `shouldBe` 1
it "Specific action on GameOver" $
let GameOver (FighterId fid) = action 1 Kick (GameOver (FighterId 1)) in fid `shouldBe` 1

-- QuickCheck auto tests
gameQCT = do
Expand Down

0 comments on commit ab443a4

Please sign in to comment.