-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMain.hs
More file actions
238 lines (210 loc) · 11.8 KB
/
Main.hs
File metadata and controls
238 lines (210 loc) · 11.8 KB
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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import Game (playGame)
import Robot (createBasicRobot, Robot(..))
import GameState
import RandomUtils (generatePositionFromSeed, generateSafeRobotPosition, generateRandomObstaclesWithRobots)
import Entities (Obstacle(..))
import qualified Data.Map as Map (Map, fromList, insert, empty, toList)
import qualified Data.Set as Set
import Data.Time.Clock.POSIX (getPOSIXTime)
import Graphics.Gloss(Picture)
import Graphics.Gloss.Juicy(loadJuicy)
import Rendering(usedImages)
import Torneos (parseConfig, runTournamentsFromConfig)
import System.IO.Unsafe (unsafePerformIO)
import Data.Default
import UIButton
import Control.Monad (void)
import Control.Concurrent (forkIO)
availableBehaviors :: [String]
availableBehaviors = ["aggressive", "defensive", "sniper"]
-- Ciclar comportamiento a la derecha
cycleBehavior :: String -> String
cycleBehavior b = case b of
"aggressive" -> "defensive"
"defensive" -> "sniper"
"sniper" -> "aggressive"
_ -> "aggressive"
-- Ciclar comportamiento a la izquierda
cycleBehaviorPrev :: String -> String
cycleBehaviorPrev b = case b of
"aggressive" -> "sniper"
"defensive" -> "aggressive"
"sniper" -> "defensive"
_ -> "aggressive"
-- Punto de entrada al juego.
main :: IO()
main = do
-- Estado inicial del torneo: robots en posiciones aleatorias
let window = (1000, 700) -- Tamaño de la ventana en píxeles (ancho, alto)
let stageSize = (200, 140) -- Tamaño del escenario de juego en unidades internas (ancho, alto) - AUMENTADO
-- Obtener semilla inicial del tiempo del sistema
time <- getPOSIXTime
let seedBase = realToFrac time -- Convertir el tiempo a Float para usarlo como semilla
-- Calcular los límites (bounds) del área jugable
-- bounds = (mitad del ancho, mitad del alto) → si stageSize = (200, 140), entonces bounds = (100, 70)
-- Esto define que los robots pueden aparecer entre -100 y +100 en X, y entre -70 y +70 en Y
let bounds = (fst stageSize / 2, snd stageSize / 2)
-- Generar posiciones aleatorias para cada robot verificando que no se solapen
-- Distancia mínima entre robots: 15 unidades
let minRobotDistance = 15.0 :: Float
let generateRobotPositions :: Int -> Int -> [(Float, Float)] -> [(Float, Float)]
generateRobotPositions currentId maxId existingPositions
| currentId > maxId = []
| otherwise =
let candidatePos = findSafeRobotPos currentId existingPositions 0
in candidatePos : generateRobotPositions (currentId + 1) maxId (candidatePos : existingPositions)
findSafeRobotPos :: Int -> [(Float, Float)] -> Int -> (Float, Float)
findSafeRobotPos rid existingPos attempt
| attempt >= 500 = generatePositionFromSeed bounds (seedBase * fromIntegral rid) rid -- Fallback después de 500 intentos
| otherwise =
let candidatePos = generatePositionFromSeed bounds (seedBase + fromIntegral attempt * 0.137) rid
tooClose = any (\pos -> distanceBetween candidatePos pos < minRobotDistance) existingPos
in if tooClose then findSafeRobotPos rid existingPos (attempt + 1) else candidatePos
distanceBetween (x1, y1) (x2, y2) = sqrt ((x2 - x1)^2 + (y2 - y1)^2)
let [pos1, pos2, pos3] = generateRobotPositions 1 3 []
-- Robots iniciales con comportamientos variados
let robots = Map.fromList
[
(1, createBasicRobot pos1 "aggressive" 1),
(2, createBasicRobot pos2 "defensive" 2),
(3, createBasicRobot pos3 "sniper" 3)
]
imagesMap <- loadImages usedImages
let initialState = def
{
gameWindowSize = window,
gameRobots = robots,
gameStageSize = stageSize, -- Usar el stageSize definido arriba (200, 140)
gameImages = imagesMap,
gameSeed = seedBase,
gameBotConfigs = [(1,"aggressive"),(2,"defensive"),(3,"sniper")],
gameButtons = makeButtons def { gameBotConfigs = [(1,"aggressive"),(2,"defensive"),(3,"sniper")], gameWindowSize = window },
gameTotalRobotCount = 3 -- Iniciar con 3 robots por defecto
}
playGame initialState
-- Carga varias imágenes y devuelve un diccionario con las rutas como clave
loadImages :: [String] -> IO (Map.Map String Picture)
loadImages [] = pure Map.empty -- Como IO es una mónada hay que envolver los valores
loadImages (p:ps) = do
maybePic <- loadJuicy p
rest <- loadImages ps
case maybePic of
Just pic -> pure $ Map.insert p pic rest
Nothing -> do
putStrLn $ "[X] No se pudo cargar: " ++ p
pure rest
makeButtons :: GameState -> [UIButton GameState]
makeButtons gs =
concat
[ [decCountBtn, incCountBtn]
, concatMap rowButtons (zip [0..] (gameBotConfigs gs))
, [playButton, tournamentButton]
]
where
-- reconstruye botones tras cualquier cambio
rebuild :: GameState -> GameState
rebuild s = s { gameButtons = makeButtons s }
-- Control del número de tanques
decCountBtn = UIButton
{ buttonPosition = (-0.25, 0.6)
, buttonSize = (0.08, 0.10)
, buttonText = "<"
, buttonHandler = \s -> let n = length (gameBotConfigs s)
in if n > 1
then rebuild $ s { gameBotConfigs = take (n-1) (gameBotConfigs s)
, gameTotalRobotCount = n-1 }
else s
}
incCountBtn = UIButton
{ buttonPosition = (0.25, 0.6)
, buttonSize = (0.08, 0.10)
, buttonText = ">"
, buttonHandler = \s ->
let n = length (gameBotConfigs s)
nextId = if null (gameBotConfigs s) then 1 else (maximum (map fst (gameBotConfigs s)) + 1)
in rebuild $ s { gameBotConfigs = gameBotConfigs s ++ [(nextId, "aggressive")]
, gameTotalRobotCount = n+1 }
}
-- Botones por fila para ciclar el tipo de cada tanque
rowButtons :: (Int, (Int, String)) -> [UIButton GameState]
rowButtons (idx, (rid, beh)) =
let n = max 1 (length (gameBotConfigs gs))
-- Usar el mismo cálculo de espaciado que en Rendering.hs
maxSpacing = 0.50 / fromIntegral (max 3 n)
minSpacing = 0.12
spacing = max minSpacing maxSpacing
yTop = 0.12
y = yTop - fromIntegral idx * spacing
-- Botones completamente a la derecha del panel, agrupados juntos
in [ UIButton { buttonPosition = (0.28, y), buttonSize = (0.08, 0.10), buttonText = "<"
, buttonHandler = \s -> let upd = map (\(i,b) -> if i==rid then (i, cycleBehaviorPrev b) else (i,b)) (gameBotConfigs s)
in rebuild $ s { gameBotConfigs = upd } }
, UIButton { buttonPosition = (0.38, y), buttonSize = (0.08, 0.10), buttonText = ">"
, buttonHandler = \s -> let upd = map (\(i,b) -> if i==rid then (i, cycleBehavior b) else (i,b)) (gameBotConfigs s)
in rebuild $ s { gameBotConfigs = upd } }
]
playButton = UIButton
{ buttonPosition = (0, -0.65) -- Movido un poco hacia arriba
, buttonSize = (0.8, 0.2)
, buttonText = "Jugar"
, buttonHandler = startGame
}
-- Botón para iniciar un torneo leyendo la configuración de `config.txt`.
-- Usa `unsafePerformIO` para leer el archivo dentro del handler puro de botones.
tournamentButton = UIButton
{ buttonPosition = (0, -0.80)
, buttonSize = (0.8, 0.16)
, buttonText = "Iniciar Torneo"
, buttonHandler = \s ->
let _ = unsafePerformIO (writeFile "estadisticas.txt" "")
cfgContent = unsafePerformIO (readFile "config.txt")
(cfgs, stageSize', tournaments) = parseConfig cfgContent
s' = s { gameBotConfigs = cfgs
, gameStageSize = stageSize'
, gameTotalRobotCount = length cfgs
, gameTournamentActive = True
, gameTournamentRemaining = tournaments
, gameTournamentSeed = gameSeed s
, gameTournamentConfigs = cfgs
, gameTournamentStatsFile = Just "estadisticas.txt"
, gameTournamentFileCleared = False
, gameTournamentCurrentIndex = 1
, gameTournamentStatsHistory = []
}
in startGame s'
}
-- Inicia juego a partir de gameBotConfigs
startGame :: GameState -> GameState
startGame s =
let seedBase = gameSeed s
stageSize = gameStageSize s
bounds = (fst stageSize / 2, snd stageSize / 2)
cfgs = if null (gameBotConfigs s)
then [(1, "aggressive")]
else gameBotConfigs s
-- Función auxiliar para verificar distancia entre robots
minRobotDist = 15.0 :: Float
distanceBetween (x1, y1) (x2, y2) = sqrt ((x2 - x1)^2 + (y2 - y1)^2)
-- PASO 1: Generar robots primero (sin obstáculos aún)
generateRobotsSequentially :: [(Int, String)] -> [(Float, Float)] -> [(Int, Robot)]
generateRobotsSequentially [] _ = []
generateRobotsSequentially ((rid, behavior):rest) existingPositions =
let safePos = findSafePosition rid 0 existingPositions
robot = createBasicRobot safePos behavior rid
in (rid, robot) : generateRobotsSequentially rest (safePos : existingPositions)
findSafePosition :: Int -> Int -> [(Float, Float)] -> (Float, Float)
findSafePosition rid attempt existingPos
| attempt >= 500 = generatePositionFromSeed bounds (seedBase * fromIntegral rid) rid -- Fallback sin obstáculos
| otherwise =
let candidatePos = generatePositionFromSeed bounds (seedBase + fromIntegral attempt * 0.137) rid
tooCloseToRobot = any (\pos -> distanceBetween candidatePos pos < minRobotDist) existingPos
in if tooCloseToRobot then findSafePosition rid (attempt + 1) existingPos else candidatePos
newRobots = Map.fromList (generateRobotsSequentially cfgs [])
-- PASO 2: Ahora generar obstáculos verificando posiciones de robots
robotPositions = [robotPosition r | (_, r) <- Map.toList newRobots]
newObstaclesList = generateRandomObstaclesWithRobots stageSize (realToFrac seedBase) robotPositions
newObstacles = Map.fromList [ (obstacleID o, o) | o <- newObstaclesList ]
in s { gameIsInMenu = False
, gameRobots = newRobots
, gameObstacles = newObstacles
}