forked from AdaGold/litter-patrol
-
Notifications
You must be signed in to change notification settings - Fork 45
/
Copy pathApp.js
201 lines (164 loc) · 5.46 KB
/
App.js
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
import React, { Component } from 'react';
import uuid from 'uuid';
import './App.css';
import GameItem from './components/GameItem.js';
import logo from './images/logo.png';
class App extends Component {
config = {
itemTypes: {
// type: spawn rate (weighting)
litter: 20,
rock: 5,
bush: 5,
flower: 5,
mushroom: 5,
},
spawnRate: 1.2, // Hz
spawnRateRnd: 1.79, // randomization factor
spawnHeight: 100, // height of item spawn area in pixels
spawnFloor: 0, // offset from bottom of game "level" in pixels
itemLifetime: 10 * 1000, // 10 seconds (should be longer than CSS animation time)
}
constructor() {
super();
this.state = {
items: [], /* item array keeps track of items and if they've been spotted */
points: 0,
};
// Uncomment this to spawn a single test item
// const testItem = this.spawnItem(Date.now());
// this.state.items.push(testItem);
// Uncomment this to automatically spawn new items
this.enableSpawner();
// console.log(this.state);
}
onItemClicked = (index) => {
// Fill this in!
// This click handler is passed via props to GameItem
console.log("Item was clicked");
// identify item clicked on
console.log(index);
// change it's state to spotted
let updatedItemData = this.state.items;
updatedItemData[index].wasSpotted = true;
this.setState({items: updatedItemData});
console.log(this.state.items[index]);
}
render() {
// mapping over item array from state to create array of GameItems
// array of GameItems to be returned in return statement
const items = this.state.items.map((item, i) => {
return <GameItem
height={item.height} // Height - used for a CSS style to position on the screen
layer={100 + i} // Layer - used for a CSS style to show items on-top of bg
key={item.id} // Key - to help React with performance
// Additional props (event callbacks, etc.) can be passed here
index={i}
type={item.type}
wasSpotted={false}
onClickCallback={this.onItemClicked}
/>;
});
return (
<div className="game">
<section className="hud">
<h2 className="score">Litter Spotted: { this.state.points }</h2>
<img className="logo" src={logo} alt="Litter Patrol logo" />
</section>
<section className="level">
{ this.levelBackground() }
{ items }
</section>
</div>
);
}
//////////////\\\\\\\\\\\\\\
// Implementation details \\
tick(time) {
const newState = {};
// Cull any items that are expired
const items = this.state.items.filter((item) => {
return item.expiration !== null && item.expiration > time;
});
if(items.length !== this.state.items.length) {
newState.items = items;
}
// Should we spawn a new item?
const {spawnRate, spawnRateRnd} = this.config;
if(this.spawnItems && spawnRate > 0) {
let spawnDelta = time - (this.lastSpawn || 0);
// Randomize spawn rate
if(spawnRateRnd > 0) {
const factor = 1 + Math.random() * spawnRateRnd;
spawnDelta *= factor;
}
if(spawnDelta >= (1 / spawnRate) * 1000) {
newState.items = [
...(newState.items || this.state.items),
this.spawnItem(time),
];
}
}
return newState;
}
spawnItem(time) {
this.lastSpawn = time;
// Figure out what kind of item to create
const id = uuid();
const type = this.randomType();
const expiration = time + this.config.itemLifetime;
const height = Math.random() * this.config.spawnHeight + this.config.spawnFloor;
return {id, type, expiration, height};
}
randomType() {
// Figure out the total of all the weighted types
const totalWeight = Object.values(this.config.itemTypes).reduce((l,r)=>l+r,0);
// Get a random value between zero and the total
let choice = Math.random() * totalWeight;
let selectedType = null;
// Loop through all item types and figure out which one we chose
Object.entries(this.config.itemTypes).forEach(([type, weight]) => {
if(selectedType !== null) return; // We've already found our choice
// If the random value was less than this type's weight
if(choice <= weight) {
selectedType = type; // then we've selected it
} else {
choice -= weight; // otherwise move past this entry
}
});
return selectedType;
}
enableSpawner() {
this.spawnItems = true;
}
levelBackground() {
const layers = ['clouds-1', 'clouds-2', 'clouds-3', 'clouds-4',
'hills-1','hills-2','bushes','trees-1','trees-2','ground'];
return (
<div className="level-bg">
{layers.map(layer => (<div className={`level-bg-${layer}`} key={layer} />))}
</div>
);
}
componentDidMount() {
// Update the game state (active items) at a fixed rate
const update = () => {
const callback = () => {
this.updateTimer = setTimeout(update, 1000 / 8);
};
const newState = this.tick(Date.now());
if(Object.keys(newState).length > 0) {
this.setState(newState, callback);
} else {
callback();
}
};
update();
}
componentWillUnmount() {
if(this.updateTimer !== null) {
clearInterval(this.updateTimer);
}
}
}
export default App;