toaruos/apps/pong.c

306 lines
7.3 KiB
C

/**
* @brief pong - Window Manager Pong
*
* Play pong where the paddles and ball are all windows.
* Use the WM bindings to drag the left paddle to play.
* Press `q` to quit.
*
* Rendering updates are all done by the compositor, while the game
* only renders to the windows once at start up.
*
* Window movement tracking keeps the game logic aware of the paddle
* position, and window moves for the ball and other paddle keep
* things in the right place visually.
*
* @copyright
* This file is part of ToaruOS and is released under the terms
* of the NCSA / University of Illinois License - see LICENSE.md
* Copyright (C) 2015 K. Lange
*/
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <math.h>
#include <sched.h>
#include <sys/time.h>
#include <toaru/yutani.h>
#include <toaru/graphics.h>
#define GAME_PATH "/usr/share/games/pong"
#define PADDLE_WIDTH 50
#define PADDLE_HEIGHT 300
#define BALL_SIZE 50
static yutani_t * yctx;
static int spkr = 0;
struct object {
double x;
double y;
int width;
int height;
double vel_x;
double vel_y;
sprite_t sprite;
};
static yutani_window_t * paddle_left;
static yutani_window_t * paddle_right;
static yutani_window_t * ball_win;
static struct object left;
static struct object right;
static struct object ball;
static gfx_context_t * paddle_left_ctx;
static gfx_context_t * paddle_right_ctx;
static gfx_context_t * ball_ctx;
static int should_exit = 0;
static int left_score = 0;
static int right_score = 0;
struct spkr {
int length;
int frequency;
};
static void note(int frequency) {
struct spkr s = {
.length = 2,
.frequency = frequency,
};
write(spkr, &s, sizeof(s));
}
static uint32_t current_time() {
static uint32_t start_time = 0;
static uint32_t start_subtime = 0;
struct timeval t;
gettimeofday(&t, NULL);
if (!start_time) {
start_time = t.tv_sec;
start_subtime = t.tv_usec;
}
uint32_t sec_diff = t.tv_sec - start_time;
uint32_t usec_diff = t.tv_usec - start_subtime;
if (t.tv_usec < (int)start_subtime) {
sec_diff -= 1;
usec_diff = (1000000 + t.tv_usec) - start_subtime;
}
return (uint32_t)(sec_diff * 1000 + usec_diff / 1000);
}
static int colliding(struct object * a, struct object * b) {
if (a->x >= b->x + b->width) return 0;
if (a->y >= b->y + b->height) return 0;
if (b->x >= a->x + a->width) return 0;
if (b->y >= a->y + a->height) return 0;
return 1;
}
void redraw(void) {
draw_fill(paddle_left_ctx, rgba(0,0,0,0));
draw_fill(paddle_right_ctx, rgba(0,0,0,0));
draw_fill(ball_ctx, rgba(0,0,0,0));
draw_sprite(paddle_left_ctx, &left.sprite, 0, 0);
draw_sprite(paddle_right_ctx, &right.sprite, 0, 0);
draw_sprite(ball_ctx, &ball.sprite, 0, 0);
yutani_flip(yctx, paddle_left);
yutani_flip(yctx, paddle_right);
yutani_flip(yctx, ball_win);
}
void update_left(void) {
yutani_window_move(yctx, paddle_left, left.x, left.y);
}
void update_right(void) {
yutani_window_move(yctx, paddle_right, right.x, right.y);
}
void update_ball(void) {
yutani_window_move(yctx, ball_win, ball.x, ball.y);
}
void update_stuff(void) {
right.vel_y = (right.y + right.height / 2 < ball.y + ball.height / 2) ? 2.0 : -2.0;
right.y += right.vel_y;
update_right();
ball.x += ball.vel_x;
ball.y += ball.vel_y;
if (ball.y < 0) {
ball.vel_y = -ball.vel_y;
ball.y = 0;
}
if (ball.y > yctx->display_height - ball.height) {
ball.vel_y = -ball.vel_y;
ball.y = yctx->display_height - ball.height;
}
if (ball.x < 0) {
ball.x = yctx->display_width / 2 - ball.width / 2;
ball.y = yctx->display_height / 2 - ball.height / 2;
ball.vel_x = -10.0;
ball.vel_y = ((double)rand() / RAND_MAX) * 6.0 - 3.0;
note(10000);
right_score++;
printf("%d : %d\n", left_score, right_score);
}
if (ball.x > yctx->display_width - ball.width ) {
ball.x = yctx->display_width / 2 - ball.width / 2;
ball.y = yctx->display_height / 2 - ball.height / 2;
ball.vel_x = 10.0;
ball.vel_y = ((double)rand() / RAND_MAX) * 6.0 - 3.0;
note(17000);
left_score++;
printf("%d : %d\n", left_score, right_score);
}
if (colliding(&ball, &left)) {
ball.x = left.x + left.width + 2;
ball.vel_x = (fabs(ball.vel_x) < 8.0) ? -ball.vel_x * 1.05 : -ball.vel_x;
double intersect = ((ball.y + ball.height/2) - (left.y)) / ((double)left.height) - 0.5;
ball.vel_y = intersect * 8.0;
note(15680);
}
if (colliding(&ball, &right)) {
ball.x = right.x - ball.width - 2;
ball.vel_x = (fabs(ball.vel_x) < 8.0) ? -ball.vel_x * 1.05 : -ball.vel_x;
double intersect = ((ball.y + ball.height/2) - (right.y)) / ((double)right.height/2.0);
ball.vel_y = intersect * 3.0;
note(11747);
}
update_ball();
}
int main (int argc, char ** argv) {
yctx = yutani_init();
left.width = PADDLE_WIDTH;
left.height = PADDLE_HEIGHT;
right.width = PADDLE_WIDTH;
right.height = PADDLE_HEIGHT;
ball.width = BALL_SIZE;
ball.height = BALL_SIZE;
ball.x = yctx->display_width / 2 - ball.width / 2;
ball.y = yctx->display_height / 2 - ball.height / 2;
left.x = 10;
left.y = yctx->display_height / 2 - left.height / 2;
right.x = yctx->display_width - right.width - 10;
right.y = yctx->display_height / 2 - right.height / 2;
paddle_left = yutani_window_create(yctx, PADDLE_WIDTH, PADDLE_HEIGHT);
paddle_right = yutani_window_create(yctx, PADDLE_WIDTH, PADDLE_HEIGHT);
ball_win = yutani_window_create(yctx, BALL_SIZE, BALL_SIZE);
paddle_left_ctx = init_graphics_yutani(paddle_left);
paddle_right_ctx = init_graphics_yutani(paddle_right);
ball_ctx = init_graphics_yutani(ball_win);
srand(time(NULL));
ball.vel_y = ((double)rand() / RAND_MAX) * 6.0 - 3.0;
ball.vel_x = -10.0;
fprintf(stderr, "Loading sprites...\n");
load_sprite(&left.sprite, GAME_PATH "/paddle-red.png");
load_sprite(&right.sprite,GAME_PATH "/paddle-blue.png");
load_sprite(&ball.sprite, GAME_PATH "/ball.png");
redraw();
update_left();
update_right();
update_ball();
uint32_t last_tick = current_time();
spkr = open("/dev/spkr", O_WRONLY);
while (!should_exit) {
uint32_t t = current_time();
if (t > last_tick + 10) {
last_tick += 10;
update_stuff();
}
yutani_msg_t * m = yutani_poll_async(yctx);
if (m) {
switch (m->type) {
case YUTANI_MSG_KEY_EVENT:
{
struct yutani_msg_key_event * ke = (void*)m->data;
if (ke->event.key == 'q' && ke->event.action == KEY_ACTION_DOWN) {
should_exit = 1;
}
}
break;
case YUTANI_MSG_WINDOW_MOVE:
{
struct yutani_msg_window_move * wm = (void*)m->data;
if (wm->wid == paddle_left->wid) {
/* Update paddle speed and position */
left.y = (double)wm->y;
if (wm->x != (int)left.x) {
update_left();
}
}
}
break;
case YUTANI_MSG_WINDOW_MOUSE_EVENT:
{
struct yutani_msg_window_mouse_event * me = (void*)m->data;
if (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) {
if (me->wid == paddle_left->wid) {
yutani_window_drag_start(yctx, paddle_left);
}
}
}
break;
case YUTANI_MSG_SESSION_END:
should_exit = 1;
break;
default:
break;
}
free(m);
} else {
sched_yield();
}
}
yutani_close(yctx, paddle_left);
yutani_close(yctx, paddle_right);
yutani_close(yctx, ball_win);
return 0;
}