summaryrefslogtreecommitdiff
path: root/usr.bin/mrow
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/mrow')
-rw-r--r--usr.bin/mrow/Makefile6
-rw-r--r--usr.bin/mrow/mrow.c288
2 files changed, 294 insertions, 0 deletions
diff --git a/usr.bin/mrow/Makefile b/usr.bin/mrow/Makefile
new file mode 100644
index 0000000..ae17849
--- /dev/null
+++ b/usr.bin/mrow/Makefile
@@ -0,0 +1,6 @@
+include user.mk
+
+CFILES = $(shell find . -name "*.c")
+
+$(ROOT)/base/usr/bin/mrow:
+ gcc $(CFILES) -o $@ $(INTERNAL_CFLAGS)
diff --git a/usr.bin/mrow/mrow.c b/usr.bin/mrow/mrow.c
new file mode 100644
index 0000000..e1bbe16
--- /dev/null
+++ b/usr.bin/mrow/mrow.c
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Hyra nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/fbdev.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+
+#define IS_ASCII(C) ((C) > 0 && (C) < 127)
+
+#define PLAYER_BG 0x808080
+#define MOUSE_BG 0x404040
+#define GAME_BG 0x000000
+
+#define SPRITE_WIDTH 20
+#define SPRITE_HEIGHT 20
+#define MOUSE_SPEED 4
+#define PLAYER_SPEED 30
+
+#define SCR_WIDTH (fbattr.width)
+#define SCR_HEIGHT (fbattr.height)
+#define MAX_X (SCR_WIDTH - SPRITE_WIDTH)
+#define MAX_Y (SCR_HEIGHT - SPRITE_HEIGHT)
+
+/* Hit beep stuff */
+#define HIT_BEEP_MSEC 50
+#define HIT_BEEP_FREQ 600
+
+static struct fbattr fbattr;
+static uint32_t *framep;
+static int beep_fd;
+static size_t hit_count = 0;
+
+struct player {
+ int32_t x;
+ int32_t y;
+};
+
+struct mouse {
+ int32_t x;
+ int32_t y;
+ uint8_t x_inc : 1;
+ uint8_t y_inc : 1;
+};
+
+static inline size_t
+pixel_index(uint32_t x, uint32_t y)
+{
+ return x + y * (fbattr.pitch / 4);
+}
+
+static void
+draw_rect(uint32_t x, uint32_t y, uint32_t width, uint32_t height, uint32_t rgb)
+{
+ for (uint32_t xpos = x; xpos < x + width; ++xpos) {
+ for (uint32_t ypos = y; ypos < y + height; ++ypos) {
+ framep[pixel_index(xpos, ypos)] = rgb;
+ }
+ }
+}
+
+static void
+update_mouse(struct mouse *mouse)
+{
+ draw_rect(mouse->x, mouse->y, SPRITE_WIDTH, SPRITE_HEIGHT, GAME_BG);
+
+ /* Move the mouse in the x direction */
+ if (mouse->x_inc) {
+ mouse->x += rand() % MOUSE_SPEED;
+ } else {
+ mouse->x -= rand() % MOUSE_SPEED;
+ }
+
+ /* Move the mouse in the y direction */
+ if (mouse->y_inc) {
+ mouse->y += rand() % MOUSE_SPEED;
+ } else {
+ mouse->y -= rand() % MOUSE_SPEED;
+ }
+
+ if (mouse->x >= MAX_X) {
+ mouse->x = MAX_X;
+ mouse->x_inc = 0;
+ } else if (mouse->x <= 0) {
+ mouse->x = 0;
+ mouse->x_inc = 1;
+ }
+
+ if (mouse->y >= MAX_Y) {
+ mouse->y = MAX_Y;
+ mouse->y_inc = 0;
+ } else if (mouse->y <= 0) {
+ mouse->y = 0;
+ mouse->y_inc = 1;
+ }
+
+ draw_rect(mouse->x, mouse->y, SPRITE_WIDTH, SPRITE_HEIGHT, MOUSE_BG);
+}
+
+static void
+beep(uint16_t msec, uint16_t freq)
+{
+ uint32_t payload;
+
+ /* Can't beep :( */
+ if (beep_fd < 0) {
+ return;
+ }
+
+ payload = freq;
+ payload |= (msec << 16);
+ write(beep_fd, &payload, sizeof(payload));
+}
+
+static bool
+mouse_collide(struct player *p, struct mouse *m)
+{
+ bool detected = false;
+ bool x_overlap, y_overlap;
+
+ x_overlap = p->x < (m->x + SPRITE_WIDTH) &&
+ (p->x + SPRITE_WIDTH) > m->x;
+ y_overlap = p->y < (m->y + SPRITE_HEIGHT) &&
+ (p->y + SPRITE_HEIGHT) > m->y;
+ detected = x_overlap && y_overlap;
+
+ /*
+ * Play a little ACK sound and reset the game
+ * if we collide
+ */
+ if (detected) {
+ beep(HIT_BEEP_MSEC, HIT_BEEP_FREQ);
+
+ /* Clear the sprites */
+ draw_rect(m->x, m->y, SPRITE_WIDTH, SPRITE_HEIGHT, GAME_BG);
+ draw_rect(p->x, p->y, SPRITE_WIDTH, SPRITE_HEIGHT, GAME_BG);
+
+ m->x = 0;
+ m->y = 0;
+ m->x_inc ^= 1;
+ m->y_inc ^= 1;
+
+ ++hit_count;
+ printf("\033[31;40mSCORE: %d\033[0m\n", hit_count);
+ }
+
+ return detected;
+
+}
+
+static void
+game_loop(void)
+{
+ struct timespec ts;
+ struct mouse mouse;
+ struct player p;
+ char c;
+ bool running = true;
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 7000000;
+
+ /* Setup the player */
+ p.x = 0;
+ p.y = 0;
+
+ /* Setup the mouse */
+ mouse.x = MAX_X;
+ mouse.y = MAX_Y;
+ mouse.x_inc = 0;
+ mouse.y_inc = 0;
+
+ /* Draw player and mouse */
+ draw_rect(p.x, p.y, SPRITE_WIDTH, SPRITE_HEIGHT, PLAYER_BG);
+ draw_rect(mouse.x, mouse.y, SPRITE_WIDTH, SPRITE_HEIGHT, MOUSE_BG);
+
+ while (running) {
+ if (mouse_collide(&p, &mouse)) {
+ continue;
+ }
+
+ c = getchar();
+ sleep(&ts, &ts);
+ update_mouse(&mouse);
+
+ if (IS_ASCII(c)) {
+ draw_rect(p.x, p.y, SPRITE_WIDTH, SPRITE_HEIGHT, GAME_BG);
+ }
+
+ switch (c) {
+ case 'w':
+ p.y -= PLAYER_SPEED;
+ if (p.y <= 0) {
+ p.y = 0;
+ }
+ break;
+ case 'a':
+ p.x -= PLAYER_SPEED;
+ if (p.x <= 0) {
+ p.x = 0;
+ }
+ break;
+ case 's':
+ p.y += PLAYER_SPEED;
+ if (p.y > MAX_Y){
+ p.y = MAX_Y;
+ }
+ break;
+ case 'd':
+ p.x += PLAYER_SPEED;
+ if (p.x > MAX_X) {
+ p.x = MAX_X;
+ }
+ break;
+ case 'q':
+ running = false;
+ default:
+ continue;
+ }
+
+ draw_rect(p.x, p.y, SPRITE_WIDTH, SPRITE_HEIGHT, PLAYER_BG);
+ }
+}
+
+int
+main(void)
+{
+ int fb_fd, fbattr_fd, prot;
+ size_t fb_size;
+ char c;
+
+ fb_fd = open("/dev/fb0", O_RDWR);
+ if (fb_fd < 0) {
+ return fb_fd;
+ }
+
+ fbattr_fd = open("/ctl/fb0/attr", O_RDONLY);
+ if (fbattr_fd < 0) {
+ close(fb_fd);
+ return fbattr_fd;
+ }
+
+ beep_fd = open("/dev/beep", O_WRONLY);
+ read(fbattr_fd, &fbattr, sizeof(fbattr));
+ close(fbattr_fd);
+
+ fb_size = fbattr.height * fbattr.pitch;
+ prot = PROT_READ | PROT_WRITE;
+ framep = mmap(NULL, fb_size, prot, MAP_SHARED, fb_fd, 0);
+ game_loop();
+
+ /* Cleanup */
+ close(beep_fd);
+ munmap(framep, fb_size);
+ close(fb_fd);
+ return 0;
+}