-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLevelComponent.cs
More file actions
244 lines (196 loc) · 6 KB
/
LevelComponent.cs
File metadata and controls
244 lines (196 loc) · 6 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
239
240
241
242
243
244
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using nkast.Aether.Physics2D.Dynamics;
using Vector2 = nkast.Aether.Physics2D.Common.Vector2;
namespace PeggleAI
{
public class LevelComponent
{
// Input
private KeyboardState oldKbState;
private MouseState oldMouseState;
// Physics
public World world { get; private set; }
// Level Objects
private HashSet<Peg> pegs;
public string[] pegPositions { private get; set; } // Save the configuration of pegs for quick resetting
// Store the positions of remaining pegs after a shot.
// The algorithm tells the level to save this state if it has the best scoring shot.
public string[] previousResult { get; private set; }
private BallShooter shooter;
private OffScreenBox offScreenBox;
private Wall lWall;
private Wall rWall;
private bool firstShot = false;
// This variable tracks if the player has shot the ball.
// If so, the player should not be able to shoot until the turn is finished
public bool ballShot { get; set; }
public Queue<Peg> pegsHit { get; private set; } = new Queue<Peg>();
// Threading
public EventWaitHandle finishHandle;
// Store an integer for the score of the previous shot.
// The thread that started this shot will check this score when it is updated
// Shots are scored by the number of pegs hit. Triple score is rewarded for orange pegs
public int previousShotScore { get; private set; }
private static int OrangePegScore = 1;
private static int BluePegScore = 0;
public LevelComponent()
{
// Create a new world that holds all physics information
world = new World();
ballShot = false;
// Create all of the level objects
loadLevel();
shooter = new BallShooter(this);
offScreenBox = new OffScreenBox(0, -6, world, this);
lWall = new Wall(-5.5f, 0, world);
rWall = new Wall(5.5f, 0, world);
}
public void loadLevel()
{
// Load data from level1 file
pegPositions = File.ReadAllLines("../../../Level1.txt");
reset();
}
// Reset the level to the initial configuration
public void reset()
{
previousShotScore = 0;
// Remove all pegs from the world, if there are any
if (pegs is not null)
{
foreach (Peg peg in pegs)
{
world.Remove(peg.pegBody);
}
}
// Create all of the pegs in the level
pegs = new HashSet<Peg>();
bool isOrange;
foreach (string position in pegPositions)
{
string[] pos = position.Split(' ');
// Lines have the X pos, Y pos, and wether the peg is orange or blue
isOrange = pos[2] == "O";
pegs.Add( new Peg(float.Parse(pos[0]), float.Parse(pos[1]), this, isOrange) );
}
}
public void updatePreviousShot()
{
// After clearing the pegs, save which ones are remaining before the level gets reset
previousResult = new string[pegs.Count];
int i = 0;
foreach(Peg currentPeg in pegs)
{
string pegString = currentPeg.x + " " + currentPeg.y + " ";
pegString = currentPeg.isOrange ? pegString + "O" : pegString + "B";
previousResult[i] = pegString;
i++;
}
}
// This function takes in an angle in degrees as input, and shoots the ball at that angle
public void shootAtAngle(double angle)
{
shooter.shoot(angle);
ballShot = true;
firstShot = true;
}
private void shotFinished()
{
// Currently, this function runs every frame *before* a ball is shot, and that causes a lot of issues
// So, this ensures the function will notify the previous thread only when a shot has been made and finished
if(!firstShot)
return;
shooter.removeBall();
//ballShot = false;
previousShotScore += clearPegs();
// Notify the algorithm thread that the shot has finished.
finishHandle.Set();
}
private int clearPegs()
{
int score = 0;
Peg peg;
while (pegsHit.Count > 0)
{
peg = pegsHit.Dequeue();
// This is necessary to avoid attepting to remove the same peg from the world twice
if (pegs.Contains(peg))
{
score = peg.isOrange ? score + OrangePegScore : score + BluePegScore;
world.Remove(peg.pegBody);
pegs.Remove(peg);
}
}
return score;
}
public bool levelComplete()
{
foreach (Peg peg in pegs)
{
if (peg.isOrange)
return false;
}
return true;
}
public void Update(GameTime gameTime)
{
// Input
HandleKeyboard(gameTime);
//HandleMouse();
float sec = (float)gameTime.ElapsedGameTime.TotalSeconds;
// If the turn has ended, remove the ball and all the pegs that have been hit
// This will be checked every frame that the ball isn't active, may not be the best
if(ballShot == false)
{
shotFinished();
}
// Otherwise, update physics world
world.Step(sec);
}
private void HandleMouse()
{
MouseState state = Mouse.GetState();
oldMouseState = state;
}
private void HandleKeyboard(GameTime gameTime)
{
KeyboardState state = Keyboard.GetState();
float totalSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;
// Aim the arrow
if (state.IsKeyDown(Keys.Left))
shooter.moveLeft(totalSeconds);
if (state.IsKeyDown(Keys.Right))
shooter.moveRight(totalSeconds);
// Shoot ball
if (!ballShot && state.IsKeyDown(Keys.Space) && oldKbState.IsKeyUp(Keys.Space))
{
shooter.shoot();
ballShot = true;
firstShot = true;
}
oldKbState = state;
}
public void Draw(SpriteBatch spriteBatch)
{
// Draw each peg
foreach(Peg peg in pegs)
peg.draw(spriteBatch);
// Draw the ball shooter
shooter.draw(spriteBatch);
}
// For some reason I need to convert between Aether2D vectors to Microsoft Vectors manually
// This method is used by game objects such as pegs and balls when drawing
public static Microsoft.Xna.Framework.Vector2 convertVec(Vector2 aetherVec)
{
return new Microsoft.Xna.Framework.Vector2(aetherVec.X, aetherVec.Y);
}
}
}