ELM: Prevent game elements from colliding / positioning over each other - elm

I'm looking at creating a game with 2 'ships'.
Ships can move up, down, left or right. One ship is controlled by arrows, the other WASD.
However I want to prevent ships from being positioned over each other.
So in my example, the blue ship should not position itself on top of the red ship. I would expect that if the red ship moved right and hit the blue ship - neither ship would move.
Any help is appreciated.
GIST
module Game (..) where
import Graphics.Element exposing (..)
import Graphics.Collage exposing (..)
import Color exposing (red, blue, gray, green)
import Keyboard
import Window
-- ALIAS
type alias Model =
{ color : Color.Color
, isFiring : Bool
, name : String
, y : Int
, x : Int
}
-- MODEL
initialShip : String -> Model
initialShip name =
let
color =
if name == "ship1" then
red
else
blue
in
{ color = color
, isFiring = False
, name = name
, y = 0
, x = 0
}
-- POSITIONS
moveLeft : Model -> Model
moveLeft model =
{ model | x = model.x - 1 }
moveRight : Model -> Model
moveRight model =
{ model | x = model.x + 1 }
moveDown : Model -> Model
moveDown model =
{ model | y = model.y - 1 }
moveUp : Model -> Model
moveUp model =
{ model | y = model.y + 1 }
-- ACTIONS
type Action
= NoOp
| Left
| Right
| Down
| Up
-- UPDATE
update : Action -> Model -> Model
update action model =
case action of
NoOp ->
model
Left ->
moveLeft model
Right ->
moveRight model
Down ->
moveDown model
Up ->
moveUp model
-- View
drawGame : Float -> Float -> Form
drawGame w h =
rect w h
|> filled gray
drawShip : Float -> Model -> Form
drawShip gameHeight ship =
let
shipColor =
if ship.isFiring then green else ship.color
initialPosition =
if ship.name == "ship1" then
(toFloat (ship.x - 50))
else
(toFloat (ship.x + 50))
in
ngon 3 30
|> filled shipColor
|> rotate (degrees 90)
|> move (initialPosition, (toFloat ship.y + 50))
view : (Int, Int) -> Model -> Model -> Element
view (w, h) ship1 ship2 =
let
(w', h') = (toFloat w, toFloat h)
in
collage w h
[ drawGame w' h'
, drawShip h' ship1
, drawShip h' ship2
]
-- SIGNALS
direction : Signal { x : Int, y : Int } -> Signal Action
direction input =
let
position =
Signal.map (\{ x, y } -> { x = x, y = y }) input
delta =
Time.fps 120
toAction { x, y } =
if x < 0 then
Left
else if x > 0 then
Right
else if y < 0 then
Down
else if y > 0 then
Up
else
NoOp
actions =
Signal.map toAction position
in
Signal.sampleOn delta actions
ship1 : Signal Model
ship1 =
Signal.foldp update (initialShip "ship1") (direction Keyboard.wasd)
ship2 : Signal Model
ship2 =
Signal.foldp update (initialShip "ship2") (direction Keyboard.arrows)
-- MAIN
main : Signal Element
main =
Signal.map3 view Window.dimensions ship1 ship2

I recommend having a single model encompassing both ships, then making a data type that has both arrows and wasd, then mapping both the arrows signal and the wasd signal and merging them into a single foldp.

Related

How to round and resize two rectangles and four circles creating a square shape in Kotlin?

I am learning Kotlin and I am facing a challenge here:
How can I round and resize two rectangles and four circles creating a square shape in Kotlin using canvas, until it gets a ball or a perfect square?
We have this code already:
import pt.isel.canvas.*
private fun Canvas.drawSquare(r: RoundSquare) {
erase()
val f = (r.side/2 * r.round/100f).toInt()
val pos = Position(r.center.x,r.center.y)
val square =
drawRect(pos.x-150, pos.y-100,r.side+100,r.side, r.color)
drawRect(pos.x-100, pos.y-150, r.side, r.side+100, r.color)
drawCircle(pos.x-100, pos.y-100, f, r.color)
drawCircle(pos.x+100, pos.y-100, f, r.color)
drawCircle(pos.x-100, pos.y+100, f, r.color)
drawCircle(pos.x+100, pos.y+100, f, r.color)
return square
}
fun main () {
onStart {
val cv = Canvas(600, 400, WHITE)
var roundSquare = RoundSquare(Position(300, 200), 200, 50, GREEN)
cv.drawSquare(roundSquare)
cv.drawText(10,400,"center=(${roundSquare.center.x},${roundSquare.center.y}) side=${roundSquare.side} round=${roundSquare.round}% color=0x${roundSquare.color.toString(16).padStart(6, '0').toUpperCase()}",BLACK,15)
cv.onMouseDown {
roundSquare = roundSquare.copy(center = Position(it.x, it.y))
cv.drawSquare(roundSquare)
return#onMouseDown cv.drawText(10,390,"center=(${roundSquare.center.x},${roundSquare.center.y}) side=${roundSquare.side} round=${roundSquare.round}% color=0x${roundSquare.color.toString(16).padStart(6, '0').toUpperCase()}",BLACK,15)
}
cv.onKeyPressed {
roundSquare = roundSquare.processKey(it.char)
cv.drawSquare(roundSquare)
return#onKeyPressed cv.drawText(10,400,"center=(${roundSquare.center.x},${roundSquare.center.y}) side=${roundSquare.side} round=${roundSquare.round}% color=0x${roundSquare.color.toString(16).padStart(6, '0').toUpperCase()}",BLACK,15)
}
onFinish { println("Bye") }
}
}
import pt.isel.canvas.BLACK
import pt.isel.canvas.WHITE
data class Position (val x:Int, val y:Int)
data class RoundSquare (val center:Position, val side:Int, val round:Int, val color:Int)
val RANGE_SIZE = 10..400
val ROUND = 0..100
val RANDOM_COLOR = BLACK..WHITE
fun RoundSquare.processKey(key: Char) = when {
key=='r' && round > ROUND.first -> copy(round = round - 1, side = side -1)
key=='R' && round < ROUND.last -> copy(round = round + 1, side = side + 1)
key=='s' && side > RANGE_SIZE.first -> copy(side = side - 1, round = round - 1)
key=='S' && side < RANGE_SIZE.last -> copy(side = side + 1, round = round + 1)
key == 'c' -> copy(color = RANDOM_COLOR.random())
else -> this
}
But it doesn't give me the output I need. This is the output:
Which can be resized till it shows a perfect ball or perfect square, by resizing sides and rounding circles.
If anyone could help me, I would really appreciate it.
Thanks in advance,
Let rounded shape center is (cx, cy), halfsize is hs.
Left x-coordinate is lx = cx - hs
Top y-coordinate is ty = cy - hs
Right x-coordinate is rx = cx + hs
Bottom y-coordinate is by = cy + hs
We want to change parameter t from 0 to 1 (or from 0 to 100%) to make needed roundness.
Circles radius is (round to integer if needed)
R = hs * t
Circle centers coordinates:
lx + R, ty + R
rx - R, ty + R
rx - R, by - R
lx + R, by - R
Two corners of rectangles:
(lx + R, ty) - (rx - R, by)
(lx, ty + R) - (rx, by - R)

Is there a way to prove stuff in Idris without a model?

I have been trying to implement the Incidence Axioms in geometry for Hilbert plane. And came up with the following axioms:
interface (Eq point) => Plane line point where
-- Abstract notion for saying three points lie on the same line.
colinear : point -> point -> point -> Bool
coplanar : point -> point -> point -> Bool
contains : line -> point -> Bool
-- Intersection between two lines
intersects_at : line -> line -> point -> Bool
intersection_def : (contains l a = True) -> (contains m a = True) -> (intersects_at l m a = True)
-- For any two distinct points there is a line that contains them.
line_contains_two_points : (a,b : point) -> (a /= b) = True -> (l : line ** (contains l a = True, contains l b = True ))
-- If two points are contained by l and m then l = m
two_pts_define_line : contains l a = True -> contains l b = True -> contains m a = True -> contains m b = True -> l = m
-- There exists 3 non-colinear points.
three_non_colinear_pts : (a : point ** b : point ** c : point ** (colinear a b c = False, (a /= b) = True, (b /= c) = True, (a /= c) = True))
-- Any lines contains at least two points.
contain_two_pts : (l : line) -> (a : point ** b : point ** (contains l a = True, contains l b = True))
I want to show that a line intersects with another line at most once. So I came up with the following statement:
intersect_at_most_one_point : (l, m : line) -> (a : point) -> (intersects_at l m a = True) -> (intersects_at l m b = True) -> a = b
Which reads:
Given two lines, if they intersect at two points a and b then it must be that a = b.
However I get the error:
When checking type of Main.intersect_at_most_one_point:
When checking argument x to type constructor =:
Can't find implementation for Plane line point
So what I suspect this means is that it wants some sort of data value that I can show satisfies the idea of an incidence geometry. I interpret this mathematically as I need a model for the system. The problem is there are a lot of of "geometries" which satisfy these axioms that are vastly different.
Is it possible to derive theorems about an interface without the need for any any explicit data to work with?
You need to add the Plane constraint to your type signature of intersect_at_most_one_point:
intersect_at_most_one_point : Plane line point =>
(l, m : line) -> (a : point) ->
(intersects_at l m a = True) -> (intersects_at l m b = True) ->
a = b

Update model at each iteration of list

Update, see below
I'm currently learning Elm and this is my first functional language to begin with.
I have a List Resource and a List Converter in my model. A Converter takes in a List Resource and outputs another List Resource. The conversion only happens when there are enough inputs. I then want to iterate through each Converter and reduce/increase the number of resources.
I couldn't wrap my head around a solution. I have a feeling that List.foldr is what I should use but I'm not sure. How should I update the model so that it will get updated each iteration?
This is what I have so far:
type alias Resource =
{ resourcetype : ResourceType
, quantity: Int
}
type ResourceType
= Energy
| Water
| Metal
type alias Converter =
{ convertertype : ConverterType
, intakes : List (ResourceType, Int)
, outputs: List (ResourceType, Int)
}
type ConverterType
= SolarCollector
| Humidifer
| MetalCollector
type alias Model =
{ resources : List Resource
, converters : List Converter
}
type Msg
= Tick Time
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Tick time ->
(updateGame model, Cmd.none)
convert : List Resource -> Converter -> List Resource
convert resources converter =
let
intakes = converter.intakes
outputs = converter.outputs
-- TODO, remove intakes from resources and add outputs to resources
in
resources
updateGame : Model -> Model
updateGame model =
-- TODO, call convert on each converter and update model
model
subscriptions : Model -> Sub Msg
subscriptions model =
Time.every second Tick
Examples:
Resources won't deplete:
--Initial converters
[ SolarCollector [] [Energy 2]
, MetalCollector [Energy 1] [Metal 1]
]
--Initial resources
[ Energy 10, Metal 0]
--After 5 ticks
[ Energy 15, Metal 5]
Resources will deplete:
--Initial converters
[ MetalCollector [Energy 2] [Metal 1]
, SolarCollector [] [Energy 1]
]
--Initial resources
[ Energy 4, Metal 0]
--Tick 1
[ Energy 3, Metal 1] -- MetalCollector uses 2 Energy to get 1 Metal, SolarCollector will generate 1 Energy
--Tick 2
[ Energy 2, Metal 2] -- MC -2E,+1M; SC +1E
--Tick 3
[ Energy 1, Metal 3] -- MC -2E,+1M; SC +1E
--Tick 4
[ Energy 2, Metal 3] -- SC +1E
-- Notice how this tick the MetalCollector didn't run because of list order.
--Tick 5
[ Energy 1, Metal 4] -- MC -2E,+1M; SC +1E
Update:
I got it working! The order of the Converters matters now and it takes the right amount of resources at each tick. Here's the final, working code, thank you for helping me! Here you can try it out: https://ellie-app.com/RB3YsxwbGja1/0
module Main exposing (..)
import Html exposing (Html, div, text, program, ul, li)
import Time exposing (Time, second)
type alias Resources =
{ energy : Float
, water : Float
, metal : Float
}
resourcesToList : Resources -> List Float
resourcesToList resources =
[resources.energy, resources.water, resources.metal]
noResource : Resources
noResource = Resources 0 0 0
energyResource : Float -> Resources
energyResource energy =
{ noResource | energy = energy }
waterResource : Float -> Resources
waterResource water =
{ noResource | water = water }
metalResource : Float -> Resources
metalResource metal =
{ noResource | metal = metal }
type alias Converter =
{ convertertype : ConverterType
, quantity : Int
, intakes : Resources
, outputs: Resources
}
type ConverterType
= SolarCollector
| Humidifer
| MetalCollector
initialResources : Resources
initialResources =
{ noResource | energy = 10}
initialConverters : List Converter
initialConverters =
[ { convertertype = MetalCollector
, quantity = 2
, intakes = energyResource 1
, outputs = metalResource 1
}
, { convertertype = SolarCollector
, quantity = 2
, intakes = noResource
, outputs = energyResource 1
}
, { convertertype = Humidifer
, quantity = 1
, intakes = energyResource 1
, outputs = waterResource 1
}
]
convert : Converter -> Resources -> Resources
convert converter resources =
let
activatedQuantity =
toFloat (getActiveConverterQuantity converter resources)
getActiveConverterQuantity : Converter -> Resources -> Int
getActiveConverterQuantity converter resources =
let
resourcesList = resourcesToList resources
intakesList = resourcesToList converter.intakes
finalList =
List.map2 (,) resourcesList intakesList
|> List.filter (\(r,i) -> i > 0)
|> List.map (\(r,i) -> floor (r/i))
in
case List.maximum finalList of
Just q ->
min q converter.quantity
Nothing ->
converter.quantity
subtractIntakes : Converter -> Resources -> Resources
subtractIntakes converter resources =
{ resources
| energy = resources.energy - activatedQuantity * converter.intakes.energy
, water = resources.water - activatedQuantity * converter.intakes.water
, metal = resources.metal - activatedQuantity * converter.intakes.metal
}
addOutputs : Converter -> Resources -> Resources
addOutputs converter resources =
{ resources
| energy = resources.energy + activatedQuantity * converter.outputs.energy
, water = resources.water + activatedQuantity * converter.outputs.water
, metal = resources.metal + activatedQuantity * converter.outputs.metal
}
in
resources
|> subtractIntakes converter
|> addOutputs converter
-- MODEL
type alias Model =
{ resources : Resources
, converters : List Converter
}
init : ( Model, Cmd Msg )
init =
( { resources = initialResources
, converters = initialConverters
}
, Cmd.none
)
-- MESSAGES
type Msg
= Tick Time
-- VIEW
view : Model -> Html Msg
view model =
div []
[ div[] [text (toString model.resources)]
, div[]
[ model.converters
|> List.map
(\c -> li [] [text (toString (c.convertertype,c.quantity,c.intakes))])
|> ul []
]
]
-- UPDATE
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Tick time ->
(updateGame model, Cmd.none)
updateGame : Model -> Model
updateGame model =
let
newResources = model.converters |> List.foldr convert model.resources
in
{ model | resources = newResources }
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Time.every second Tick
-- MAIN
main : Program Never Model Msg
main =
program
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
I'd expect your model.resources will not have to hold multiple values of one ResourceType so a Record will make things easier for that ( -> assuming resources = [energy 2, water 1, energy 2] would make no sense )
type alias Resources =
{ energy : Int
, water : Int
, metal : Int
}
type alias Model =
{ resources : Resources
, converters : List Converter
}
now you can fold the intakes of every converter and accumulate the resources
convert : Converter -> Resources -> Resources
convert {intakes, outputs} resources =
let
substractResource : (ResourceType, Int) -> Resources -> Resources
substractResource (rType, quantity) res =
case rType of
Energy ->
{ res | energy = energy - quantity }
-- TODO: same for Water and Metal
-- TODO: addResource function
hasEnoughResources newRes =
case negativeResources newRes of
-- TODO: function to check if any value of the new resources Record is negative
True -> -- return original resources if converter can not run
resources
False -> -- add outputs to res and return new recources
outputs |> List.foldr addResource newRes
in
intakes
|> List.foldr substractResource resources
|> hasEnoughResources
Bonus: combine add and substract functions to 1 function of type : (Int -> Int) -> (ResourceType, Int) -> Resources -> Resources and call it like calcResource << (+)
updateGame : Model -> Model
updateGame model =
let
newResources = model.converters |> List.foldr convert model.resources
in
{ model | resources = newResources }

Maximum in List of Records

Say I have a List of records in elm:
[ { id = 1, magnitude = 100 }
, { id = 3, magnitude = 300 }
, { id = 2, magnitude = 200 } ]
and I want to get the record with the greatest magnitude value (300). What is a good way of doing this?
The docs gives an example of using the "maximum" -method, but it uses a simple list of integers. How is it done with records?
Update based on recommendation from #robertjlooby
There is a function called maximumBy which does exactly this in elm-community/list-extra. Example:
List.Extra.maximumBy .magnitude list
Original Answer
There are a few ways to achieve this.
This first way is more concise but it involves sorting the whole list, reversing it, then taking the head.
maxOfField : (a -> comparable) -> List a -> Maybe a
maxOfField field =
List.head << List.reverse << List.sortBy field
If you want something that's more efficient and only traverses the list once, here's a more efficient version:
maxOfField : (a -> comparable) -> List a -> Maybe a
maxOfField field =
let f x acc =
case acc of
Nothing -> Just x
Just y -> if field x > field y then Just x else Just y
in List.foldr f Nothing
An example of it in use:
list =
[ { id = 1, magnitude = 100 }
, { id = 3, magnitude = 300 }
, { id = 2, magnitude = 200 } ]
main =
text <| toString <| maxOfField .magnitude list
Here is a version that uses foldl and a default record:
bigger =
let
choose x y =
if x.magnitude > y.magnitude then
x
else
y
in
List.foldl choose {id = 0, magnitude = 0} items
Sebastian's answer add an arbitrary start value which could cause a problem if all your magnitudes were negative. I would adjust to
bigger items =
case items of
[] -> []
(h :: []) -> h
(h :: tail) ->
let
choose x y =
if x.magnitude > y.magnitude then
x
else
y
in
List.foldl choose h tail

Elm drawing over list

I am trying to draw a list of objects but cannot get it to work.
Secondary question is how do I make a "for loop" using ELM.
I have a
type Object a = { a | x:Float, y:Float, vx:Float, vy:Float }
type Car = Object {}
type Cars = [Car]
displayCar = move (car.x,car.y) (filled white (rect 30 20))
displayCars = ?????
I am trying to get somethign liek this to work
collage 100 100 [displayCar (head cars) -- does work
, displayCars cars -- does not work
]
In particular, the collage has multiple things it needs to plot:
[ filled pongGreen (rect gameWidth gameHeight)
, displayObjHouse (game.houses !! 0) -- so ugly code
, displayObjHouse (game.houses !! 1) -- so ugly code
, displayObjHouse (game.houses !! 2) -- so ugly code
, displayObjHouse (game.houses !! 3) -- so ugly code
, displayObjHouse (game.houses !! 4) -- so ugly code
, displayCars cars -- does not work
]
You are looking for the function map.
map : (a -> b) -> [a] -> [b]
This means that you can apply some function to a list of things and get back a list of the results.
You are very close with what you have here. I've filled in some blanks to help you keep making progress! Good luck!
type Object a = { a | x:Float, y:Float, vx:Float, vy:Float }
type Car = Object {}
type House = Object { residents : Int }
displayCar : Car -> Form
displayCar car = move (car.x,car.y) (filled black (rect 30 20))
displayCars : [Car] -> [Form]
displayCars cars = map displayCar cars
-- map : (a -> b) -> [a] -> [b]
-- In our particular example, we plug in our functions
-- displayCar : Car -> Form
-- since display car is the first argument to map, all a's become Car
-- and all b's become Form
-- So the type of map in this instance is ((Car -> Form) -> [Car] -> [Form]
someCars : [Car]
someCars = [ { x = 100, y = 10, vx = 0, vy = 0 }
, { x = 35, y = 100, vx = 0, vy = 0 }
, { x = 0, y = 0, vx = 0, vy = 0 }
]
someHouses : [House]
someHouses = [ { x = 20, y = -100, vx = 0, vy = 0, residents = 3 }
, { x = -20, y = -50, vx = 0, vy = 0, residents = 3 }
, { x = 160, y = -150, vx = 0, vy = 0, residents = 3 }
]
displayHouse : House -> Form
displayHouse house = move (house.x, house.y) (filled blue (rect 30 50))
main : Element
main =
let houses = map displayHouse someHouses
cars = map displayCar someCars
in collage 400 400 (houses ++ cars)