Public Access
Games: Minesweeper, 2048 and Asteroids, and Pong by tilting #104
+14
-3
@@ -11,6 +11,17 @@ constexpr float kSteepest = 0.87f; // 50 degrees, in radians: off the very end
|
||||
float clampPaddle(float y) { return std::clamp(y, Pong::kPaddleH / 2, Pong::kHeight - Pong::kPaddleH / 2); }
|
||||
} // namespace
|
||||
|
||||
float tiltTarget(float pitch, float level) {
|
||||
constexpr float kFullTilt = 0.26f; // 15 degrees either way covers the whole field
|
||||
float travel = Pong::kHeight / 2 - Pong::kPaddleH / 2;
|
||||
return Pong::kHeight / 2 + std::clamp((pitch - level) / kFullTilt, -1.0f, 1.0f) * travel;
|
||||
}
|
||||
|
||||
float moveToward(float paddleY, float targetY) {
|
||||
constexpr float kSlowWithin = 10; // pixels: closer than that, it eases in
|
||||
return std::clamp((targetY - paddleY) / kSlowWithin, -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
void Pong::start() {
|
||||
playerScore_ = deviceScore_ = rally_ = longest_ = 0;
|
||||
player_ = device_ = kHeight / 2;
|
||||
@@ -39,7 +50,7 @@ void Pong::bounceOff(float paddleY, bool toRight) {
|
||||
vy_ = std::sin(where * kSteepest) * speed_;
|
||||
}
|
||||
|
||||
uint8_t Pong::step(float seconds, int move) {
|
||||
uint8_t Pong::step(float seconds, float move) {
|
||||
uint8_t events = 0;
|
||||
seconds = std::min(seconds, 0.25f); // a held-up loop is not a quarter of the field
|
||||
while (seconds > 0 && phase_ != Phase::Over) {
|
||||
@@ -50,8 +61,8 @@ uint8_t Pong::step(float seconds, int move) {
|
||||
return events;
|
||||
}
|
||||
|
||||
uint8_t Pong::tick(float dt, int move) {
|
||||
player_ = clampPaddle(player_ + (move < 0 ? -1 : move > 0 ? 1 : 0) * kPlayerSpeed * dt);
|
||||
uint8_t Pong::tick(float dt, float move) {
|
||||
player_ = clampPaddle(player_ + std::clamp(move, -1.0f, 1.0f) * kPlayerSpeed * dt);
|
||||
// The device watches the ball once it comes its way, and waits in the middle otherwise.
|
||||
float target = vx_ > 0 && phase_ == Phase::Play ? y_ : kHeight / 2;
|
||||
float reach = kDeviceSpeed * dt;
|
||||
|
||||
+13
-4
@@ -9,6 +9,15 @@ namespace roro::games {
|
||||
// Pong against the device (issue #101): the player's paddle on the left, the device's on the
|
||||
// right, first to 7. The ball goes faster with each return, and the device's paddle only moves so
|
||||
// fast: a steep, fast ball gets past it. Lengths are pixels of the 240 x 122 field, times seconds.
|
||||
// Where the paddle is asked to be when the device is tilted (issue #101's follow-up): its top
|
||||
// lowered, the paddle goes up, as a ball rolls to the low side (it felt the right way round on the
|
||||
// device); held as it was when the game began, the middle. `pitch` and
|
||||
// `level` are "up" along the device's top-bottom axis, -1 to 1, as the accelerometer gives it.
|
||||
float tiltTarget(float pitch, float level);
|
||||
// The move that brings the paddle at `paddleY` to `targetY` with no overshoot: full speed far away,
|
||||
// slower close by.
|
||||
float moveToward(float paddleY, float targetY);
|
||||
|
||||
class Pong {
|
||||
public:
|
||||
static constexpr float kWidth = 240, kHeight = 122;
|
||||
@@ -23,9 +32,9 @@ class Pong {
|
||||
|
||||
explicit Pong(uint32_t seed) : rng_(seed) { start(); }
|
||||
void start();
|
||||
// `seconds` later, with the player's paddle asked up (-1), down (1) or left alone (0).
|
||||
// What happened, as Events.
|
||||
uint8_t step(float seconds, int move);
|
||||
// `seconds` later, with the player's paddle asked up (-1), down (1) or left alone (0), or
|
||||
// anything between for a slower move. What happened, as Events.
|
||||
uint8_t step(float seconds, float move);
|
||||
|
||||
Phase phase() const { return phase_; }
|
||||
float ballX() const { return x_; }
|
||||
@@ -40,7 +49,7 @@ class Pong {
|
||||
int longestRally() const { return longest_; } // in this game
|
||||
|
||||
private:
|
||||
uint8_t tick(float dt, int move);
|
||||
uint8_t tick(float dt, float move);
|
||||
void serve(bool toPlayer);
|
||||
bool meets(float paddleY) const;
|
||||
void bounceOff(float paddleY, bool toRight);
|
||||
|
||||
@@ -121,6 +121,25 @@ void test_pong_the_device_can_be_beaten() {
|
||||
TEST_ASSERT_TRUE(playerPoints > 0);
|
||||
}
|
||||
|
||||
void test_pong_a_tilt_puts_the_paddle_where_it_points() {
|
||||
TEST_ASSERT_FLOAT_WITHIN(0.01, Pong::kHeight / 2, tiltTarget(0.3f, 0.3f)); // held as at the start: the middle
|
||||
TEST_ASSERT_TRUE(tiltTarget(0.2f, 0.3f) < Pong::kHeight / 2); // top lowered: up
|
||||
TEST_ASSERT_TRUE(tiltTarget(0.4f, 0.3f) > Pong::kHeight / 2);
|
||||
TEST_ASSERT_FLOAT_WITHIN(0.01, Pong::kPaddleH / 2, tiltTarget(-0.9f, 0.3f)); // and no further than the edge
|
||||
TEST_ASSERT_FLOAT_WITHIN(0.01, Pong::kHeight - Pong::kPaddleH / 2, tiltTarget(0.9f, 0.3f));
|
||||
TEST_ASSERT_EQUAL_FLOAT(1, moveToward(20, 80));
|
||||
TEST_ASSERT_EQUAL_FLOAT(-1, moveToward(80, 20));
|
||||
TEST_ASSERT_FLOAT_WITHIN(0.01, 0.5f, moveToward(60, 65));
|
||||
TEST_ASSERT_EQUAL_FLOAT(0, moveToward(60, 60));
|
||||
}
|
||||
|
||||
void test_pong_a_tilted_paddle_settles_on_its_target() {
|
||||
Pong p(3);
|
||||
float target = tiltTarget(0.4f, 0.3f);
|
||||
for (int i = 0; i < 200; i++) p.step(0.01f, moveToward(p.playerY(), target));
|
||||
TEST_ASSERT_FLOAT_WITHIN(0.5, target, p.playerY());
|
||||
}
|
||||
|
||||
void test_pong_the_device_paddle_has_a_top_speed() {
|
||||
Pong p(13);
|
||||
float before = p.deviceY();
|
||||
@@ -658,6 +677,8 @@ int main() {
|
||||
RUN_TEST(test_pong_a_miss_is_a_point_for_the_other_side);
|
||||
RUN_TEST(test_pong_ends_at_seven);
|
||||
RUN_TEST(test_pong_the_device_can_be_beaten);
|
||||
RUN_TEST(test_pong_a_tilt_puts_the_paddle_where_it_points);
|
||||
RUN_TEST(test_pong_a_tilted_paddle_settles_on_its_target);
|
||||
RUN_TEST(test_pong_the_device_paddle_has_a_top_speed);
|
||||
RUN_TEST(test_snake_starts_in_the_middle_three_long);
|
||||
RUN_TEST(test_snake_moves_at_its_pace);
|
||||
|
||||
Reference in New Issue
Block a user