123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736 |
- // raboot of atari 2600 Boxing
- // TODO: computer player
- // TODO: gore (destructable nose, eyes)
- // TODO: ropes (and some padding)
- // TODO: gamepad controls
- // TODO: touch controls
- // TODO: finish music
- // TODO: menu - 1/2 players, audio levels
- // TODO: menu - gore level (in case become olympic sport),
- // TODO: menu - tutorial (for touchscreen plebs mostyl)
- /*
- #canvas {
- position: absolute;
- top: 0px;
- left: 0px;
- margin: 0px;
- width: 100%;
- height: 100%;
- overflow: hidden;
- display: block;
- }
- */
- #include "common/sketchbook.hpp"
- #include "common/math.hpp"
- float quadratic_out(float x) { return 1.f - motion::quadratic_curve(1.f - x); };
- float stairwave(float x)
- {
- // TODO: must be a way to just parameterize sinus to do less work and be less smooth, instead of doing more work to make it jagged
- constexpr auto levels = 12.f;
- return int(std::clamp(common::sinus(x) *levels, -levels,levels)) /levels;
- }
- struct key_vector
- {
- scancode k;
- float2 v;
- };
- struct keymap
- {
- std::array<key_vector,4> legwork;
- std::array<scancode,3> punch;
- };
- keymap wasd
- {
- {
- key_vector{scancode::w, -float2::j()},
- key_vector{scancode::a, -float2::i()},
- key_vector{scancode::s, float2::j()},
- key_vector{scancode::d, float2::i()}
- },
- { scancode::tab, scancode::f, scancode::v }
- };
- keymap ijkl
- {
- {
- key_vector{scancode::i, -float2::j()},
- key_vector{scancode::j, -float2::i()},
- key_vector{scancode::k, float2::j()},
- key_vector{scancode::l, float2::i()}
- },
- { scancode::h, scancode::semicolon, scancode::n}
- };
- keymap arrow_keys
- {
- {
- key_vector{scancode::up, -float2::j()},
- key_vector{scancode::left, -float2::i()},
- key_vector{scancode::down, float2::j()},
- key_vector{scancode::right, float2::i()}
- },
- {}
- };
- std::vector control_options {wasd, ijkl, arrow_keys}; // FIXME: numpad, gamepad, touchscreen, mouse
- rect ring{};
- constexpr float2 half = float2::one(0.5);
- constexpr float2 starting_pos = float2::one(0.1);
- constexpr auto scale(rect ratio, rect world)
- {
- ratio.size *= world.size;
- ratio.position *= world.size;
- ratio.position += world.position;
- return ratio;
- }
- constexpr auto faded_wave(float fade_factor, auto&& f)
- {
- return [fade_factor, f = std::forward<decltype(f)>(f)](auto ratio) mutable
- {
- auto [freq, ampl] = f(ratio);
- float fade = std::min(ratio*fade_factor, (1-ratio)*fade_factor);
- return way(0.f,common::sinus(ratio * freq) * ampl, std::min(fade, 1.f));
- };
- };
- struct blood_drop
- {
- rgb cola;
- movement<float2, motion::cubic_curve> veloc;
- float2 position;
- void update(frame& frame, auto delta)
- {
- veloc.advance(delta);
- auto oldpos = position;
- position += veloc.value();
- frame.begin_sketch()
- .line(ring.size*oldpos + ring.position, ring.size*position + ring.position)
- .line_width(0.01 * ring.size.x())
- .line_cap(sketch::cap::round)
- .outline(cola)
- ;
- }
- bool done()
- {
- return veloc.done();
- }
- };
- std::vector<blood_drop> blood;
- struct boxa
- {
- rgb cola;
- float2 position = {};
- float2 velocity = {};
- float speed = 0.5;
- movement<float, common::sinus> idle = {500ms, 0.f, 0.005f}; // FIXME: random delay per boxa to desync them
- movement<float2, quadratic_out> shovement = {0s, float2::zero(), float2::zero()};
- melody<movement<float2>, movement<float2>> squeezement = {};
- melody<movement<float2>, movement<float2>> glove_squeezement = {};
- boxa* target;
- std::optional<keymap> controls{};
- float punch = 0.0f;
- bool hit = false;
- unsigned closest_glove = 0;
- unsigned hit_glove = 0;
- unsigned damage = 0;
- auto direction()
- {
- const auto distance = target->position - position;
- auto clamped_distance = distance;
- if(clamped_distance.y() > 0.2)
- clamped_distance.y() = 0.2;
- else if(clamped_distance.y() < -0.2)
- clamped_distance.y() = -0.2;
- if(abs(clamped_distance.x()) > 0.18f)
- clamped_distance *= float2::i();
- else
- clamped_distance = support::average(clamped_distance, clamped_distance * float2::i());
- return common::normalize(clamped_distance);
- }
- rect body()
- {
- return {float2::one(0.1), position, half};
- };
- auto elbows()
- {
- const auto normal = common::rotate(this->direction(), common::protractor<>::tau(1/4.f));
- auto body = this->body();
- std::array<float2,2> elbows{};
- const auto spread = 1.7f;
- auto arm = 1;
- for(auto&& elbow : elbows)
- {
- elbow = body.position;
- elbow += body.size.x() * normal * spread * arm;
- elbow += - this->direction() * body.size.x()/2 * 1.3;
- if(elbow == elbows[closest_glove])
- elbow -= body.size.x() * normal * (0.7 * punch) * arm;
- arm = -arm;
- }
- elbows[closest_glove] += this->direction() * punch * 0.1;
- return elbows;
- };
- auto gloves()
- {
- const auto normal = common::rotate(this->direction(), common::protractor<>::tau(1/4.f));
- auto body = this->body();
- std::array<rect,2> gloves{};
- for(auto&& glove : gloves)
- {
- glove = body;
- glove.size *= 0.9;
- glove.position += body.size.x() * normal;
- glove.position += this->direction() * body.size.x()/2;
- }
- gloves[1].position -= body.size.x() * 2 * normal;
- gloves[closest_glove].position += this->direction() * punch * 0.2;
- return gloves;
- };
- void update(frame& f, auto delta_time)
- {
- { // draw
- const auto direction = this->direction();
- const auto normal = common::rotate(this->direction(), common::protractor<>::tau(1/4.f));
- const auto facingdom = float2x2{direction, normal};
- auto body = this->body();
- {
- float2 squeezement = float2::one();
- this->squeezement.move(squeezement, delta_time);
- const auto squeezed_body_size = body.size * squeezement;
- body.position += direction * (squeezed_body_size.x() - body.size.x());
- body.size = squeezed_body_size;
- }
- auto elbows = this->elbows();
- auto gloves = this->gloves();
- {
- float idle_sway = 0.f;
- auto idle_normal = normal;
- motion::loop(idle_sway, idle, delta_time);
- for(auto&& glove : gloves)
- {
- glove.position += idle_normal * idle_sway;
- idle_normal = -idle_normal;
- }
- body.position += direction * idle_sway;
- }
- {
- gloves[closest_glove].size += gloves[closest_glove].size * float2{0.1f, -0.1f} * punch;
- }
- {
- float2 squeezement = float2::one();
- this->glove_squeezement.move(squeezement, delta_time);
- const auto squeezed_glove_size = gloves[hit_glove].size * squeezement;
- gloves[hit_glove].position += direction * (squeezed_glove_size.x() - gloves[hit_glove].size.x());
- gloves[hit_glove].size = squeezed_glove_size;
- }
- auto scaled_body = scale(body,ring);
- for(auto&& [elbow, glove] : range{vbegin(elbows, gloves), vend(elbows,gloves)})
- {
- auto scaled_elbow = elbow * ring.size + ring.position;
- auto scaled_glove = scale(glove,ring);
- f.begin_sketch()
- .move(scaled_body.position)
- .bezier(scaled_elbow, scaled_glove.position)
- .line_width((0.04f) * ring.size.x()).outline(0x333333_rgb)
- ;
- }
- f.begin_sketch()
- .ellipse(scaled_body.position, facingdom * geom::column(scaled_body.size/2))
- .fill(rgb::white(0.5))
- ;
- auto thumb_offsetdom = geom::vector(
- -direction * 0.1f,
- -normal * 0.4f
- );
- auto glove_sketch = f.begin_sketch();
- for(auto&& glove : gloves)
- {
- auto scaled_glove = scale(glove,ring);
- glove_sketch
- .ellipse(scaled_glove.position, facingdom * geom::column(scaled_glove.size/2))
- .ellipse(scaled_glove.position + thumb_offsetdom(scaled_glove.size), facingdom * geom::column(scaled_glove.size/2/2))
- ;
- thumb_offsetdom[1] = -thumb_offsetdom[1];
- }
- glove_sketch.fill(cola);
- }
- { // move
- const range2f bounds {position - 0.05, position + 0.05};
- constexpr auto world_bounds = range2f{float2::zero(), float2::one()};
- const auto lower_overshoot = world_bounds.lower() - bounds.lower();
- const auto upper_overshoot = bounds.upper() - world_bounds.upper();
- constexpr float springback_factor = 20;
- velocity += springback_factor * max(lower_overshoot, float2::zero());
- velocity -= springback_factor * max(upper_overshoot, float2::zero());
- position += velocity * speed * delta_time.count();
- if(not shovement.done())
- {
- position += shovement.value() * (speed /2) * delta_time.count();
- shovement.advance(delta_time);
- }
- auto diameter = 0.2f;
- const auto distance = target->position - position;
- if(distance.quadrance() < diameter * diameter)
- {
- auto direction = common::normalize(distance);
- auto force_field = direction * diameter;
- auto correction = distance - force_field;
- position += correction;
- }
- if(punch > 0 || hit)
- {
- if(not hit)
- {
- punch += 10 * delta_time.count();;
- if(punch >= 1)
- {
- hit = true;
- punch = 1;
- }
- }
- else
- {
- if(punch > 0)
- {
- punch -= 10 * delta_time.count();
- }
- else
- {
- punch = 0;
- hit = false;
- }
- }
- }
- }
- }
- std::optional<rect> punching_glove()
- {
- if(punch > 0 && not hit)
- return gloves()[closest_glove];
- else
- return std::nullopt;
- }
- };
- std::array boxas
- {
- boxa{rgb::red(), starting_pos},
- boxa{rgb::blue(), float2::one() - starting_pos}
- };
- constexpr rect fit_square(float2 size)
- {
- float2 fit_size = float2::one(*min_element(size));
- return {fit_size, (size - fit_size) / 2}; // hmmm, does this sub/2 thing have a name? midpointish but not quite
- }
- void start(Program& program)
- {
- // program.frametime = framerate<60>::frametime;
- program.resizable = true;
- program.size = float2::one(400.f);
- ring = fit_square(program.size);
- if(program.argc > 2)
- {
- using support::ston;
- using seed_t = decltype(tiny_rand());
- tiny_rand.seed({ ston<seed_t>(program.argv[1]), ston<seed_t>(program.argv[2]) });
- }
- std::cout << "seed: " << std::hex << std::showbase << tiny_rand << '\n';
- program.key_up = [&](scancode code, keycode)
- {
- switch(code)
- {
- case scancode::leftbracket:
- case scancode::c:
- if(pressed(scancode::rctrl) || pressed(scancode::lctrl))
- case scancode::escape:
- program.end();
- break;
- default: break;
- }
- };
- program.key_down = [&](scancode code, keycode)
- {
- for(auto&& boxa : boxas)
- if(boxa.controls)
- if(support::find(boxa.controls->punch, code) != boxa.controls->punch.end())
- if(boxa.punch == 0)
- {
- auto hiss = [rand = make_trand_float({0,0.1})](auto ratio) mutable
- { return std::pair{rand(), ratio/5}; };
- program.request_wave({faded_wave(20, std::move(hiss)), 125ms});
- // program.request_wave({faded_wave(20, std::move(hiss)), 50ms});
- boxa.punch = 0.001;
- boxa.closest_glove = boxa.position.y() > boxa.target->position.y();
- boxa.closest_glove ^= boxa.position.x() > boxa.target->position.x();
- }
- };
- program.mouse_down = [&](float2, auto)
- {
- };
- program.size_changed = [&](float2 size)
- {
- std::cout << size << '\n';
- ring = fit_square(size);
- std::cout << ring << '\n';
- };
- program.draw_loop = [&](auto frame, auto delta_time)
- {
- frame.begin_sketch()
- .rectangle(rect{frame.size})
- .fill(rgb::white(0.4f))
- ;
- frame.begin_sketch()
- .rectangle(ring)
- .fill(rgb(0.3f, 0.4f, 0.0f))
- ;
- for(auto&& boxa : boxas)
- {
- boxa.velocity = float2::zero();
- if(boxa.controls)
- {
- for(auto&& kv : boxa.controls->legwork)
- if(pressed(kv.k))
- boxa.velocity += kv.v;
- if(boxa.velocity != float2::zero())
- boxa.velocity = common::normalize(boxa.velocity); // here length can only be root(2) or 1 so a bit of an overkill
- }
- else
- {
- auto selected = support::find_if(control_options, [](auto option)
- {
- return support::any_of(option.legwork, [](auto kv) { return pressed(kv.k); });
- });
- if(selected != control_options.end())
- {
- boxa.controls = *selected;
- control_options.erase(selected);
- }
- }
- }
- boxas[0].target = &boxas[1];
- boxas[1].target = &boxas[0];
- for(auto&& b : boxas) b.update(frame, delta_time);
- for(auto&& b : boxas)
- {
- auto glove = b.punching_glove();
- bool collided_target = false;
- bool collided_a_glove = false;
- bool collided_punching_glove = false;
- if(glove)
- {
- collided_target = (glove->position - b.target->position).quadrance() <= 0.095*0.095;
- if(not collided_target)
- {
- auto other_gloves = b.target->gloves();
- auto collided_glove = support::find_if(other_gloves, [pos = glove->position](auto x) {
- return (pos - x.position).quadrance() <= x.size.quadrance()/2;
- });
- collided_a_glove = collided_glove != other_gloves.end();
- auto other_punching_glove = b.target->punching_glove();
- if(collided_a_glove && other_punching_glove)
- {
- collided_punching_glove = (*collided_glove) == (*other_punching_glove);
- }
- if(collided_a_glove)
- {
- b.target->hit_glove = collided_glove - other_gloves.begin();
- }
- }
- }
- if(collided_target)
- {
- b.target->shovement.total = 250ms;
- b.target->shovement.start = (b.direction() + (b.position.y() < b.target->position.y() ? -float2::j(2) : float2::j(2))) * 5;
- b.target->shovement.reset();
- {
- constexpr auto squeezementat = float2{0.5f, 1.5f};
- b.target->squeezement = melody{
- movement<float2>{125ms, float2::one(), squeezementat},
- movement<float2>{125ms, squeezementat, float2::one()}
- };
- }
- ++b.target->damage; // FIXME: more damage for combos
- auto boink = [](auto ratio)
- { return std::pair{way(40.f, 20.f, ratio), 1}; };
- program.request_wave({faded_wave(20, std::move(boink)), 125ms});
- const auto splatter_direction = (b.target->position - b.position).signum();
- for(int i = 10; i --> 0;) blood.push_back({
- wayback(0x55cccc_rgb,0x660000_rgb, std::min(b.target->damage/50.f, 1.f)),
- {250ms, trand_float2() / 50 * splatter_direction, float2::zero()},
- b.target->position
- });
- }
- if(collided_a_glove)
- {
- constexpr auto squeezementat = float2{0.7f, 1.3f};
- b.target->glove_squeezement = melody{
- movement<float2>{60ms, float2::one(), squeezementat},
- movement<float2>{60ms, squeezementat, float2::one()}
- };
- auto bunk = [](auto ratio)
- { return std::pair{way(25.f, 0.f, ratio), .3f}; };
- program.request_wave({faded_wave(20, std::move(bunk)), 125ms});
- }
- if(collided_target || collided_a_glove)
- b.hit = true;
- if(collided_punching_glove)
- {
- b.target->hit = true;
- break;
- }
- }
- for(auto&& b : blood) b.update(frame, delta_time);
- blood.erase(std::remove_if(blood.begin(),blood.end(), [](auto& b) {return b.done();}), blood.end());
- };
- program.request_wave({
- [
- time = 0.f, freq = 0.f,
- musac = motion::melody
- {
- movement<float>{50ms, 0.f,120.f},
- movement<float>{50ms, 120.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,150.f},
- movement<float>{50ms, 150.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,100.f},
- movement<float>{50ms, 100.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,200.f},
- movement<float>{50ms, 200.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,220.f},
- movement<float>{50ms, 220.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,250.f},
- movement<float>{50ms, 250.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,200.f},
- movement<float>{50ms, 200.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,100.f},
- movement<float>{50ms, 100.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,120.f},
- movement<float>{50ms, 120.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,0.f},
- movement<float>{50ms, 0.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,100.f},
- movement<float>{50ms, 100.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,200.f},
- movement<float>{50ms, 200.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,0.f},
- movement<float>{50ms, 0.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,250.f},
- movement<float>{50ms, 250.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,200.f},
- movement<float>{50ms, 200.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- movement<float>{50ms, 0.f,0.f},
- movement<float>{50ms, 0.f,0.f},
- movement<float>{100ms, 0.f,0.f},
- }
- ](auto tick) mutable
- {
- motion::loop(freq, musac, Program::duration(tick));
- time += tick * freq;
- time = support::wrap(time,1.f);
- return stairwave(time) * 0.2f;
- },
- 0ms});
- program.request_wave({
- [
- time = 0.f, freq = 0.f,
- musac = motion::melody
- {
- movement<float, quadratic_out>{10ms, 0.f, 350.f},
- movement<float, quadratic_out>{90ms, 350.f,420.f},
- movement<float, motion::quadratic_curve>{200ms, 420.f,350.f},
- movement<float, quadratic_out>{100ms, 350.f,350.f},
- movement<float, quadratic_out>{100ms, 350.f,750.f},
- movement<float, motion::quadratic_curve>{200ms, 750.f,350.f},
- movement<float, quadratic_out>{100ms, 350.f,350.f},
- movement<float, motion::quadratic_curve>{200ms, 350.f,400.f},
- movement<float, quadratic_out>{100ms, 400.f,350.f},
- movement<float, quadratic_out>{500ms, 350.f,350.f},
- movement<float, quadratic_out>{100ms, 350.f,420.f},
- movement<float, motion::quadratic_curve>{200ms, 420.f,350.f},
- movement<float, quadratic_out>{100ms, 350.f,350.f},
- movement<float, quadratic_out>{100ms, 350.f,750.f},
- movement<float, motion::quadratic_curve>{200ms, 750.f,350.f},
- movement<float, quadratic_out>{90ms, 350.f,350.f},
- movement<float, quadratic_out>{10ms, 350.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- }
- ](auto tick) mutable
- {
- motion::loop(freq, musac, Program::duration(tick));
- time += tick * (freq/3);
- time = support::wrap(time,1.f);
- return stairwave(time) * 0.3f;
- },
- 0ms});
- program.request_wave({
- [
- time = 0.f, freq = 0.f,
- musac = motion::melody
- {
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float>{800ms, 0.f,0.f},
- movement<float, quadratic_out>{400ms, 0.f, 1350.f},
- movement<float, quadratic_out>{400ms, 1350.f,1000.f},
- movement<float, quadratic_out>{400ms, 1000.f, 1200.f},
- movement<float, quadratic_out>{400ms, 1200.f, 350.f},
- movement<float, quadratic_out>{400ms, 350.f,1000.f},
- movement<float, quadratic_out>{400ms, 1000.f, 0.f},
- movement<float>{800ms, 0.f,0.f},
- }
- ](auto tick) mutable
- {
- motion::loop(freq, musac, Program::duration(tick));
- time += tick * (freq/3);
- time = support::wrap(time,1.f);
- return stairwave(time) * 0.1f;
- },
- 0ms});
- }
|