Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Level progression #221

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions O21.Game/Engine/Entities.fs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ type Player = {
let newPlayer = { this with Scores = Math.Max(this.Scores + scores, 0) }

if this.Oxygen.IsEmpty then PlayerEffect.Die
else
else
match CheckCollision level this.Box enemies with
| Collision.OutOfBounds -> PlayerEffect.Update this // TODO[#28]: Level progression
| Collision.OutOfBounds -> PlayerEffect.OutOfBounds this
| Collision.CollidesBrick -> PlayerEffect.Die
| Collision.CollidesObject _ -> PlayerEffect.Die
| Collision.None -> PlayerEffect.Update newPlayer
Expand Down Expand Up @@ -121,6 +121,7 @@ and OxygenStorage = {

and [<RequireQualifiedAccess>] PlayerEffect =
| Update of Player
| OutOfBounds of Player
| Die

type Bullet = {
Expand Down
1 change: 1 addition & 0 deletions O21.Game/Engine/ExternalEffect.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ type EntityType =

type ExternalEffect =
| PlaySound of SoundType
| SwitchLevel of Vector
| PlayAnimation of AnimationType * EntityType
25 changes: 20 additions & 5 deletions O21.Game/Engine/GameEngine.fs
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,20 @@ type GameEngine = {
}

member this.ChangeLevel(level: Level): GameEngine =
let bombs = level.BombsCoordinates()
|> Array.map (fun point ->
Bomb.Create(
Point(GameRules.LevelWidth / level.LevelMap[0].Length * fst point,
GameRules.LevelHeight / level.LevelMap.Length * snd point)))
let bombs =
level.BombsCoordinates()
|> Array.map (fun point ->
Bomb.Create(
Point(GameRules.LevelWidth / level.LevelMap[0].Length * fst point,
GameRules.LevelHeight / level.LevelMap.Length * snd point)))

let player =
{ this.Player with
TopLeft = this.Player.TopLeft % Point(GameRules.LevelWidth, GameRules.LevelHeight) }

{ this with
CurrentLevel = level
Player = player
Bombs = bombs
Bullets = Array.empty
Fishes = Array.empty
Expand Down Expand Up @@ -148,6 +154,15 @@ type GameEngine = {
static member private ProcessPlayerEffect effect (engine: GameEngine) =
match effect with
| PlayerEffect.Update player -> { engine with Player = player }, Array.empty
| PlayerEffect.OutOfBounds player ->
let levelDelta =
match player.Box with
| upper when upper.BottomLeft.Y >= GameRules.LevelHeight -> Vector(0, 1)
| lower when lower.TopLeft.Y <= 0 -> Vector(0, -1)
| right when right.TopRight.X >= GameRules.LevelWidth -> Vector(1, 0)
| left when left.TopLeft.X <= 0 -> Vector(-1, 0)
| _ -> Vector(0, 0)
{ engine with Player = player }, [| SwitchLevel levelDelta |]
| PlayerEffect.Die ->
{ engine with
Player = { engine.Player with
Expand Down
4 changes: 4 additions & 0 deletions O21.Game/Engine/GameField.fs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ type Point =

static member (+) (Point(x1, y1), Vector(x2, y2)): Point = Point(x1 + x2, y1 + y2)
static member (-) (Point(x1, y1), Vector(x2, y2)): Point = Point(x1 - x2, y1 - y2)

static member (%) (Point(x1, y1), Point(x2, y2)): Point =
let modulo a b = ((a % b) + b) % b
Point(modulo x1 x2, modulo y1 y2)

member this.X: int = let (Point(x, _)) = this in x
member this.Y: int = let (Point(_, y)) = this in y
Expand Down
4 changes: 2 additions & 2 deletions O21.Game/Engine/GameRules.fs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ let NewBulletPosition(playerTopForwardCorner: Point, playerDirection: Horizontal
| HorizontalDirection.Left -> playerTopForwardCorner + Vector(-4 - BulletSize.X, 14)
| HorizontalDirection.Right -> playerTopForwardCorner + Vector(4, 14)

let StartingLevel = LevelCoordinates(2, 1)
let PlayerStartingPosition = Point(100, 140)
let StartingLevel = LevelCoordinates(1, 1)
let PlayerStartingPosition = Point(200, 140)
let LevelSizeInTiles = Vector(50, 25)

let NewParticlePosition(playerTopForwardCorner: Point, playerDirection: HorizontalDirection) =
Expand Down
1 change: 1 addition & 0 deletions O21.Game/HUD.fs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type HUD =
Score = gameEngine.Player.Scores
Oxy = gameEngine.Player.OxygenAmount
Pause = not gameEngine.IsActive
Level = gameEngine.CurrentLevel.Position
}

member this.UpdateScore(newScore:int):HUD =
Expand Down
16 changes: 15 additions & 1 deletion O21.Game/Scenes/PlayScene.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace O21.Game.Scenes

open Microsoft.FSharp.Core
open O21.Game.Animations
open type Raylib_CsLo.Raylib
open Raylib_CsLo
Expand Down Expand Up @@ -94,11 +95,24 @@ type PlayScene = {
let game, inputEffects = InputProcessor.ProcessKeys input hud gameEngine
let game', updateEffects = game.Update time
game', Array.concat [inputEffects; updateEffects]

static member private UpdateLevelOnRequest (game: GameEngine) (effects: ExternalEffect[]) (levels: Map<LevelCoordinates, Level>) =
effects
|> Array.tryPick (fun e ->
match e with
| SwitchLevel delta ->
let dx, dy = delta.X, delta.Y
let newCoords = LevelCoordinates(game.CurrentLevel.Coordinates.X + dx,
game.CurrentLevel.Coordinates.Y + dy)
Some (game.ChangeLevel levels[newCoords])
| _ -> None)
|> Option.defaultValue game

interface IScene with
member this.Camera: Camera2D = this.Camera
member this.Update(input, time, state) =
let game, effects = PlayScene.UpdateGame (input, time) (state.Game, this.HUD)
let game = PlayScene.UpdateLevelOnRequest game effects state.U95Data.Levels
let hud = this.HUD.SyncWithGame game
let animationHandler = this.AnimationHandler.Update (state, effects)
let state = { state with Game = game; Scene = { this with HUD = hud; AnimationHandler = animationHandler } }
Expand Down Expand Up @@ -128,7 +142,7 @@ type PlayScene = {
{ this.Window with RenderTargetSize = (GameRules.GameScreenWidth, GameRules.GameScreenHeight) }
&this.Camera

DrawTexture(sprites.Background[1], 0, 0, WHITE)
DrawTexture(sprites.Background[game.CurrentLevel.Position], 0, 0, WHITE)
let map = game.CurrentLevel.LevelMap
for i = 0 to map.Length-1 do
for j = 0 to map[i].Length-1 do
Expand Down
20 changes: 15 additions & 5 deletions O21.Game/U95/Level.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,24 @@ type LevelCoordinates =

type Level = {
LevelMap: MapOfLevel[][]
Position: int
Coordinates: LevelCoordinates
}
with
static member Empty = { LevelMap = Array.empty }
static member Empty = {
LevelMap = Array.empty
Position = 0
Coordinates = LevelCoordinates(0, 0)
}

static member private Load(directory:string) (level:int) (part:int): Task<Level> = task{
static member private Load(directory: string) (level: int) (part: int): Task<Level> = task{
let parser = Parser(directory)
let level = parser.LoadLevel level part
let coordinates = LevelCoordinates(part, level)
let pos, level = parser.LoadLevel level part
return {
LevelMap = level;
LevelMap = level
Position = pos
Coordinates = coordinates
}
}

Expand All @@ -50,7 +59,7 @@ type Level = {
|> Task.WhenAll
return Map.ofArray levelPairs
}

member private this.BombsCoordinatesLazy = lazy (
let mutable coords = Array.empty
this.LevelMap
Expand All @@ -65,3 +74,4 @@ type Level = {
)

member this.BombsCoordinates() = this.BombsCoordinatesLazy.Value

14 changes: 11 additions & 3 deletions O21.Game/U95/Parser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ type MapOfLevel =
| Empty

type Parser(directory) =
member private this.getLevelPosition (part: int) =
if part <= 4 then 1 else
if part <= 9 then 2 else
let d1, d2 = (part / 10, part % 10)
let pos = 2 * d1 + 1
if d2 < 5 then pos else pos + 1

member private this.readDatFile(level:int) (part:int): string[] =
let lines = File.ReadAllLines(Path.Combine(directory, $"U95_{level}_{part}.DAT"))
lines
Expand All @@ -21,7 +28,7 @@ type Parser(directory) =
let levelMap = Array.init line.Length (fun i ->
let brick =
match line[i] with
| t when t>='1' && t <= '9' -> Brick(int line[i] - int '0')
| t when t >='1' && t <= '9' -> Brick(int line[i] - int '0')
| 'b' -> Bomb
| 'a' -> Bonus
| _ -> Empty
Expand All @@ -36,6 +43,7 @@ type Parser(directory) =
)
level

member this.LoadLevel (level:int) (part:int): MapOfLevel[][] =
member this.LoadLevel (level:int) (part:int): int * MapOfLevel[][] =
let pos = this.getLevelPosition part
let lines = this.readDatFile level part
this.parseLevel lines
(pos, this.parseLevel lines)
71 changes: 32 additions & 39 deletions O21.Tests/GameEngineTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,9 @@ let private frameUpN time n gameEngine =

let private timeZero = { Total = 0.0; Delta = 0.0f }

let private emptyLevel = {
LevelMap = Array.init GameRules.LevelSizeInTiles.X (fun _ ->
Array.init GameRules.LevelSizeInTiles.Y (fun _ -> MapOfLevel.Empty)
)
}

let private emptyLevel = createEmptyLevel
GameRules.LevelSizeInTiles.X
GameRules.LevelSizeInTiles.Y
let private newEngine = GameEngine.Create(timeZero, emptyLevel)

module Ticks =
Expand Down Expand Up @@ -130,11 +127,11 @@ module Shooting =
module Player =
[<Fact>]
let ``Moving towards a brick kills the player``(): unit =
let level = {
LevelMap = [|
[| Empty; Brick 0 |]
|]
}
let level =
{ emptyLevel with
LevelMap = [|
[| Empty; Brick 0 |]
|] }
let player = { Player.Default with TopLeft = Point(GameRules.BrickSize.X - GameRules.PlayerSize.X, 0) }
let player' = player.Update(getEmptyPlayerEnvWithLevel level, 1)
Assert.True(match player' with | PlayerEffect.Update _ -> true | _ -> false)
Expand All @@ -161,7 +158,7 @@ module OxygenSystem =

[<Fact>]
let ``Player dies when oxygen amount is empty``(): unit =
let level = { LevelMap = [| [| Empty |] |] }
let level = createEmptyLevel 1 1
let player = Player.Default

let player = { player with Player.Oxygen.Amount = 1 }
Expand Down Expand Up @@ -227,33 +224,29 @@ module Bullets =

[<Fact>]
let ``Bullet is destroyed on a brick collision``(): unit =
let level = {
LevelMap = [|
[| Empty; Brick 0 |]
|]
}
let level =
{ emptyLevel with
LevelMap = [|
[| Empty; Brick 0 |]
|] }
let bullet = defaultBullet
let ticksToMove = GameRules.BrickSize.X / GameRules.BulletVelocity
Assert.Equal(None, bullet.Update(level, ticksToMove))

[<Fact>]
let ``Bullet doesn't pierce through a brick``(): unit =
let level = {
LevelMap = [|
[| Empty; Brick 0 |]
|]
}
let level =
{ emptyLevel with
LevelMap = [|
[| Empty; Brick 0 |]
|] }
let bullet = defaultBullet
let ticksToMove = GameRules.BrickSize.X * 3 / GameRules.BulletVelocity
Assert.Equal(None, bullet.Update(level, ticksToMove))

[<Fact>]
let ``Bullet is destroyed after the expiration of its lifetime``(): unit =
let level = {
LevelMap = [|
[| Empty |]
|]
}
let level = createEmptyLevel 1 1
let bullet = defaultBullet
let ticksToMove = GameRules.BulletLifetime
Assert.True(bullet.Update(level, ticksToMove).IsSome)
Expand Down Expand Up @@ -289,11 +282,11 @@ module Bullets =
module ScoreSystem =
[<Fact>]
let ``Adding scores for hit enemy``(): unit =
let level = {
LevelMap = [|
[| Empty; Bomb |]
|]
}
let level =
{ emptyLevel with
LevelMap = [|
[| Empty; Bomb |]
|] }

let engine = newEngine.ChangeLevel(level)
let engine =
Expand All @@ -316,20 +309,20 @@ module Geometry =

[<Fact>]
let ``Out of bounds check``(): unit =
let level = { LevelMap = Array.empty }
let level = emptyLevel
let box1 = { TopLeft = Point(-1, -1); Size = Vector(1, 1) }
Assert.Equal(Collision.OutOfBounds, CheckCollision level box1 [||])

let box2 = { TopLeft = Point(GameRules.LevelWidth, 0); Size = Vector(1, 1) }
let box2 = { TopLeft = Point(GameRules.LevelWidth, GameRules.LevelHeight); Size = Vector(1, 1) }
Assert.Equal(Collision.OutOfBounds, CheckCollision level box2 [||])

[<Fact>]
let ``Brick collision check``(): unit =
let level = {
LevelMap = [|
[| Empty; Brick 0 |]
|]
}
let level =
{ emptyLevel with
LevelMap = [|
[| Empty; Brick 0 |]
|] }
let box1 = { TopLeft = Point(0, 0); Size = Vector(1, 1) }
Assert.Equal(Collision.None, CheckCollision level box1 [||])

Expand Down
8 changes: 4 additions & 4 deletions O21.Tests/Helpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ let getEmptyPlayerEnvWithLevel (level: Level) =
BonusColliders = [||]
}

let createEmptyLevel width height = {
LevelMap =
Array.create height (Array.create width MapOfLevel.Empty)
}
let createEmptyLevel width height =
{ Level.Empty with
LevelMap =
Array.create height (Array.create width MapOfLevel.Empty) }