-
Notifications
You must be signed in to change notification settings - Fork 1
/
Kalevala.elm
144 lines (129 loc) · 5.71 KB
/
Kalevala.elm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
module Kalevala where
import Color
import Dict
import Graphics.Element exposing (Element)
import Graphics.Input.Field as Field
import Json.Decode
import Json.Encode
import Mouse
import Random
import Signal exposing (..)
import Time exposing (Time)
import Window
import Deprecated.WebSocket as WebSocket
import Helpers exposing (..)
import GameTypes exposing (..)
import Log
import State exposing (isGameOver, mustPass)
import Display exposing (..)
import Display.Board exposing (mouseToBoardPosition)
import Game
import Player
import Serialize
import Deserialize
import Debug
isRemoteSignal : Signal Bool
isRemoteSignal =
let getIsRemoteFromClickSignal event =
case event of
StartSinglePlayer -> Just False
StartTwoPlayerHotseat -> Just False
StartTwoPlayerOnline -> Just True
otherwise -> Nothing
in
filterMap getIsRemoteFromClickSignal False clickMailbox.signal
{- Perform an action on the current state and return the resulting state. -}
performAction : Action -> State -> State
performAction action state =
let newState =
case action of
PickUpPiece player idx -> Game.tryToPickUpPiece player idx state
PlacePiece mousePos dims -> Game.tryMove (mouseToBoardPosition mousePos state dims) state
StartGame gameType deck player playerName -> Game.startGame gameType deck player playerName
StartNewGame deck player playerName -> Game.startGame state.gameType deck player playerName
GameStarted deck startPlayer localPlayer opponentName -> Game.gameStarted deck startPlayer localPlayer opponentName state
Pass -> Game.pass state
Switch -> { state | turn <- Player.next state.turn }
MoveToMainMenu -> Game.startState
MoveToRemoteGameMenu -> { state | gameType <- HumanVsHumanRemote, gameState <- NotStarted }
OpponentDisconnected -> { state | gameState <- Disconnected
, log <- Log.addSystemMsg "Opponent disconnected." state.log }
CpuAction -> Game.tryAIMove state
NoAction -> state
ParseError e -> state
in
if | isGameOver newState -> { newState | gameState <- GameOver
, log <- Log.addSystemMsg ("Game over!" ++ State.endStateMsg newState) newState.log }
| otherwise -> newState
{- Turn a ClickEvent into an Action -}
constructAction : ClickEvent -> Random.Seed -> MousePos -> WindowDims -> Field.Content -> Action
constructAction clickType seed mousePos dims playerName =
let
pos = Debug.watch "Mouse.position" mousePos
click = Debug.watch "clickInput.signal" clickType
deck = shuffle Game.deckContents seed
in
case clickType of
StartSinglePlayer -> StartGame HumanVsCpu deck (Player.random seed) playerName.string
StartTwoPlayerOnline -> StartGame HumanVsHumanRemote deck (Player.random seed) playerName.string
StartTwoPlayerHotseat -> StartGame HumanVsHumanLocal deck (Player.random seed) playerName.string
StartNewGameButton -> StartNewGame deck (Player.random seed) playerName.string
BoardClick -> PlacePiece mousePos dims
PieceInHand player idx -> PickUpPiece player idx
PassButton -> Pass
SwitchButton -> Switch
MainMenuButton -> MoveToMainMenu
StartRemoteGameButton -> MoveToRemoteGameMenu
None -> NoAction
{- Turn a signal of ClickEvents into a signal of Actions -}
processClick : Signal ClickEvent -> Signal Action
processClick signal =
let seedSignal = (Random.initialSeed << round << fst) <~ Time.timestamp signal
sampledMouse = sampleOn signal Mouse.position
sampledPlayerName = sampleOn signal <| playerNameSignal
in
constructAction <~ signal ~ seedSignal ~ sampledMouse ~ Window.dimensions ~ sampledPlayerName
{- Path to a Voluspa game server
(see https://github.com/neunenak/voluspa-server) -}
server : String
server = "wss://kalevalagame.com"
{- Encodes an Action as a JSON string -}
encode : Action -> String
encode action = Json.Encode.encode 0 (Serialize.action action)
{- Decodes an Action from a JSON string -}
decode : String -> Action
decode actionJson =
case Json.Decode.decodeString Deserialize.action actionJson of
Ok action -> action
Err e -> ParseError e
getRemoteResponse : Signal Action -> Signal Action
getRemoteResponse playerAction =
let actionForRemote = filterOn playerAction isRemoteSignal NoAction
request = Debug.watch "request" <~ (encode <~ actionForRemote)
response = Debug.watch "response" <~ WebSocket.connect server request
in
Debug.watch "deserialized" <~ (decode <~ response)
getCpuResponse : Signal Action -> Time -> Signal Action
getCpuResponse playerAction delayTime =
let processCpuResponse a =
case a of
PlacePiece mp wd -> CpuAction
Pass -> CpuAction
otherwise -> NoAction
in
Time.delay delayTime (processCpuResponse <~ playerAction)
{- The main game loop.
The state is folded over time over a stream of Actions, coming from the client and from the server (for an online game.)
Actions from the client are constructed by processing a signal of ClickEvents from clickMailbox.
In the case the the game type is HumanVsHumanRemote, Actions are also sent to the server over a websocket,
and the Actions that are received from the server are merged into the signal of client Actions.
For each Action in this resulting signal, (performAction action state) returns the new state. -}
main : Signal Element
main =
let
playerAction = processClick clickMailbox.signal
remoteAction = getRemoteResponse playerAction
cpuAction = getCpuResponse playerAction Time.second
state = foldp performAction Game.startState (mergeMany [playerAction, remoteAction, cpuAction])
in
render <~ state ~ Window.dimensions ~ playerNameSignal