1 /**
2  * Space shooter demo.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.demo.pewpew.pewpew;
15 
16 import std.math;
17 import std.random;
18 import std.datetime;
19 import std.algorithm : min;
20 import std.conv;
21 import std.traits, std.typecons;
22 
23 import ae.ui.app.application;
24 import ae.ui.app.posix.main;
25 import ae.ui.shell.shell;
26 import ae.ui.shell.sdl2.shell;
27 import ae.ui.video.video;
28 import ae.ui.video.sdl2.video;
29 import ae.ui.video.renderer;
30 import ae.utils.graphics.draw;
31 import ae.utils.graphics.gamma;
32 import ae.utils.fps;
33 
34 import ae.demo.pewpew.objects;
35 
36 final class MyApplication : Application
37 {
38 	override string getName() { return "Demo/PewPew"; }
39 	override string getCompanyName() { return "CyberShadow"; }
40 
41 	Shell shell;
42 	uint ticks;
43 	alias GammaRamp!(COLOR.ChannelType, ubyte) MyGamma;
44 	MyGamma gamma;
45 	FPSCounter fps;
46 
47 	static uint currentTick() { return TickDuration.currSystemTick().to!("msecs", uint)(); }
48 
49 	enum InputSource { keyboard, joystick, max }
50 	enum GameKey { up, down, left, right, fire, none }
51 
52 	int[InputSource.max][GameKey.max] inputMatrix;
53 
54 	override void render(Renderer s)
55 	{
56 		fps.tick(&shell.setCaption);
57 
58 		auto screenCanvas = s.lock();
59 		scope(exit) s.unlock();
60 
61 		if (initializing)
62 		{
63 			gamma = MyGamma(ColorSpace.sRGB);
64 			new Game();
65 			foreach (i; 0..1000) step(10);
66 			ticks = currentTick();
67 			initializing = false;
68 		}
69 
70 		foreach (i, key; EnumMembers!GameKey[0..$-1])
71 		{
72 			enum name = __traits(allMembers, GameKey)[i];
73 			bool pressed;
74 			foreach (input; inputMatrix[key])
75 				if (input)
76 					pressed = true;
77 			mixin(name ~ " = pressed;");
78 		}
79 
80 		//auto destTicks = ticks+deltaTicks;
81 		uint destTicks = currentTick();
82 		// step(deltaTicks);
83 		while (ticks < destTicks)
84 			ticks++,
85 			step(1);
86 
87 		auto canvasSize = min(screenCanvas.w, screenCanvas.h);
88 		canvas.size(canvasSize, canvasSize);
89 		canvas.fill(canvas.COLOR.init);
90 		foreach (ref plane; planes)
91 			foreach (obj; plane)
92 				obj.render();
93 
94 		auto x = (screenCanvas.w-canvasSize)/2;
95 		auto y = (screenCanvas.h-canvasSize)/2;
96 		auto dest = screenCanvas.crop(x, y, x+canvasSize, y+canvasSize);
97 
98 		import std.parallelism;
99 		import std.range;
100 		foreach (j; taskPool.parallel(iota(canvasSize)))
101 		{
102 			auto src = canvas.crop(0, j, canvasSize, j+1);
103 			auto ramp = gamma.lum2pixValues.ptr;
104 			src.colorMap!(c => Renderer.COLOR.monochrome(ramp[c.l])).blitTo(dest, 0, j);
105 		}
106 	}
107 
108 	void step(uint deltaTicks)
109 	{
110 		foreach (ref plane; planes)
111 			foreach (obj; plane)
112 				obj.step(deltaTicks);
113 	}
114 
115 	GameKey keyToGameKey(Key key)
116 	{
117 		switch (key)
118 		{
119 			case Key.up   : return GameKey.up   ;
120 			case Key.down : return GameKey.down ;
121 			case Key.left : return GameKey.left ;
122 			case Key.right: return GameKey.right;
123 			case Key.space: return GameKey.fire ;
124 			default       : return GameKey.none ;
125 		}
126 	}
127 
128 	override void handleKeyDown(Key key, dchar character)
129 	{
130 		auto gameKey = keyToGameKey(key);
131 		if (gameKey != GameKey.none)
132 			inputMatrix[gameKey][InputSource.keyboard]++;
133 		else
134 		if (key == Key.esc)
135 			shell.quit();
136 	}
137 
138 	override void handleKeyUp(Key key)
139 	{
140 		auto gameKey = keyToGameKey(key);
141 		if (gameKey != GameKey.none)
142 			inputMatrix[gameKey][InputSource.keyboard] = 0;
143 	}
144 
145 	override bool needJoystick() { return true; }
146 
147 	int[2] axisInitial;
148 	bool[2] axisCalibrated;
149 
150 	override void handleJoyAxisMotion(int axis, short svalue)
151 	{
152 		if (axis >= 2) return;
153 
154 		int value = svalue;
155 		if (!axisCalibrated[axis]) // assume first input event is inert
156 			axisInitial[axis] = value,
157 			axisCalibrated[axis] = true;
158 		value -= axisInitial[axis];
159 
160 		import ae.utils.math;
161 		if (abs(value) > short.max/2) // hack?
162 			useAnalog = true;
163 		auto fvalue = bound(cast(float)value / short.max, -1f, 1f);
164 		(axis==0 ? analogX : analogY) = fvalue;
165 	}
166 
167 	JoystickHatState lastState = cast(JoystickHatState)0;
168 
169 	override void handleJoyHatMotion (int hat, JoystickHatState state)
170 	{
171 		void checkDirection(JoystickHatState direction, GameKey key)
172 		{
173 			if (!(lastState & direction) && (state & direction)) inputMatrix[key][InputSource.joystick]++;
174 			if ((lastState & direction) && !(state & direction)) inputMatrix[key][InputSource.joystick]--;
175 		}
176 		checkDirection(JoystickHatState.up   , GameKey.up   );
177 		checkDirection(JoystickHatState.down , GameKey.down );
178 		checkDirection(JoystickHatState.left , GameKey.left );
179 		checkDirection(JoystickHatState.right, GameKey.right);
180 		lastState = state;
181 	}
182 
183 	override void handleJoyButtonDown(int button)
184 	{
185 		inputMatrix[GameKey.fire][InputSource.joystick]++;
186 	}
187 
188 	override void handleJoyButtonUp  (int button)
189 	{
190 		inputMatrix[GameKey.fire][InputSource.joystick]--;
191 	}
192 
193 	override int run(string[] args)
194 	{
195 		shell = new SDL2Shell(this);
196 		shell.video = new SDL2SoftwareVideo();
197 		shell.run();
198 		shell.video.shutdown();
199 		return 0;
200 	}
201 
202 	override void handleQuit()
203 	{
204 		shell.quit();
205 	}
206 }
207 
208 shared static this()
209 {
210 	createApplication!MyApplication();
211 }