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