--- /dev/null
+/build*/
+/compile_commands.json
+/.cache/
--- /dev/null
+project(RockPaperScissorsDuelNative)
+cmake_minimum_required(VERSION 3.18.4)
+
+set(CMAKE_C_FLAGS "-Wall -Wextra -Wpedantic")
+set(CMAKE_C_FLAGS_DEBUG "-O0 -g")
+set(CMAKE_C_FLAGS_RELEASE "-O3 -D NDEBUG")
+set(CMAKE_CXX_FLAGS "-Wall -Wextra -Wpedantic")
+set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g")
+set(CMAKE_CXX_FLAGS_RELEASE "-O3 -D NDEBUG")
+
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ message(STATUS "Setting build type to 'Debug', none was specified.")
+ set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
+endif()
+
+set(RPSDuelNative_SOURCES
+ src/main.cc
+)
+
+add_executable(RPSDuelNative ${RPSDuelNative_SOURCES})
+
+target_link_libraries(RPSDuelNative PUBLIC
+ raylib
+)
--- /dev/null
+set tabstop=2
+set softtabstop=2
+set shiftwidth=2
--- /dev/null
+#ifndef ROCK_PAPER_SCISSORS_DUEL_CONSTANTS_H_
+#define ROCK_PAPER_SCISSORS_DUEL_CONSTANTS_H_
+
+constexpr int DEFAULT_SCREEN_WIDTH = 500;
+constexpr int DEFAULT_SCREEN_HEIGHT = 800;
+
+#endif
--- /dev/null
+// emscripten includes
+#ifdef __EMSCRIPTEN__
+#include <emscripten.h>
+#include <emscripten/html5.h>
+#else
+#include <random>
+#endif
+
+// third party includes
+#include <raylib.h>
+
+// local includes
+#include "constants.h"
+
+#ifdef __EMSCRIPTEN__
+// em exposed fns
+extern "C" {
+
+void EMSCRIPTEN_KEEPALIVE game_visual_update(
+ const char *playerOne, const char *playerTwo, const char *currentPlayer,
+ char first_first, char first_second, char first_third, char second_first,
+ char second_second, char second_third, bool first_ready, bool second_ready,
+ int pos) {
+ // TODO
+}
+
+} // end em exposed functions
+
+EM_JS(void, js_set_ready, (), { Rune.actions.set_ready(); });
+
+EM_JS(void, js_set_choices,
+ (const char *first, const char *second, const char *third), {
+ Rune.actions.set_choices(UTF8ToString(first), UTF8ToString(second),
+ UTF8ToString(third));
+ });
+#endif
+
+int main() {
+#ifdef __EMSCRIPTEN__
+ InitWindow(DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT,
+ "RockPaperScissorsDuel");
+#else
+ InitWindow(DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT, "RPSDuel_Native");
+#endif
+
+#ifdef __EMSCRIPTEN__
+#else
+ SetTargetFPS(60);
+
+ while (!WindowShouldClose()) {
+ BeginDrawing();
+ ClearBackground(BLACK);
+ DrawText("Testing...", 100, 100, 30, RAYWHITE);
+ EndDrawing();
+ }
+
+ CloseAudioDevice();
+ CloseWindow();
+#endif
+
+ return 0;
+}
--- /dev/null
+/rock_paper_scissors_duel.html
+/rock_paper_scissors_duel.js
+/rock_paper_scissors_duel.wasm
+/rock_paper_scissors_duel.data
--- /dev/null
+ifdef RELEASE
+ OTHER_FLAGS = -DNDEBUG -O3
+else
+ OTHER_FLAGS = -O0
+endif
+
+SOURCES = \
+ ../src/main.cc
+
+HEADERS = \
+ ../src/constants.h
+
+CXX = source ${HOME}/git/emsdk/emsdk_env.sh && em++
+
+all: | format rock_paper_scissors_duel.html
+
+rock_paper_scissors_duel.html: ${SOURCES} ${HEADERS}
+ ${CXX} -o rock_paper_scissors_duel.html \
+ -s USE_GLFW=3 -I../wasm_includes -L../wasm_libs -lraylib \
+ --shell-file custom_shell.html \
+ -sEXPORTED_FUNCTIONS=_main,_game_visual_update \
+ -sEXPORTED_RUNTIME_METHODS=ccall \
+ ${OTHER_FLAGS} \
+ ${SOURCES}
+
+.PHONY: clean format
+
+clean:
+ rm -f rock_paper_scissors_duel.html
+ rm -f rock_paper_scissors_duel.js
+ rm -f rock_paper_scissors_duel.wasm
+ rm -f rock_paper_scissors_duel.data
+
+format:
+ clang-format -i --style=file ${SOURCES} ${HEADERS}
--- /dev/null
+Rune.initClient({
+ visualUpdate: ({ newGame, yourPlayerId}) => {
+ const { player1, player2, first_choices, second_choices, first_ready, second_ready, pos } = newGame;
+
+ Module.ccall('_game_visual_update',
+ undefined,
+ ['string', 'string', 'string',
+ 'number', 'number', 'number',
+ 'number', 'number', 'number',
+ 'boolean', 'boolean',
+ 'number'],
+ [player1, player2, yourPlayerId === undefined ? 'undefined' : yourPlayerId,
+ first_choices[0], first_choices[1], first_choices[2],
+ second_choices[0], second_choices[1], second_choices[2],
+ first_ready, second_ready,
+ pos]);
+ },
+});
--- /dev/null
+<!doctype html>
+<html lang="en-us">
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"/>
+ <title>Hexagonal Crawler</title>
+ <style>
+ body { margin: 0; background-color: black }
+ .emscripten {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ margin: 0px;
+ border: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ display: block;
+ image-rendering: optimizeSpeed;
+ image-rendering: -moz-crisp-edges;
+ image-rendering: -o-crisp-edges;
+ image-rendering: -webkit-optimize-contrast;
+ image-rendering: optimize-contrast;
+ image-rendering: crisp-edges;
+ image-rendering: pixelated;
+ -ms-interpolation-mode: nearest-neighbor;
+ }
+ </style>
+ </head>
+ <body>
+<!-- <script src="https://cdn.jsdelivr.net/npm/rune-games-sdk@2.5.2/dist/browser.min.js"></script> -->
+ <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()"></canvas>
+ <script type='text/javascript'>
+ var Module = {
+ preRun: [],
+ postRun: [],
+ print: (function() {
+ return function(text) {
+ text = Array.prototype.slice.call(arguments).join(' ');
+ console.log(text);
+ };
+ })(),
+ printErr: function(text) {
+ text = Array.prototype.slice.call(arguments).join(' ');
+ console.error(text);
+ },
+ canvas: (function() {
+ var canvas = document.getElementById('canvas');
+ //canvas.addEventListener("webglcontextlost", function(e) { alert('FIXME: WebGL context lost, please reload the page'); e.preventDefault(); }, false);
+ return canvas;
+ })(),
+ setStatus: function(text) {
+ console.log("status: " + text);
+ },
+ monitorRunDependencies: function(left) {
+ // no run dependencies to log
+ }
+ };
+ window.onerror = function() {
+ console.log("onerror: " + event);
+ };
+ </script>
+ {{{ SCRIPT }}}
+ </body>
+</html>
--- /dev/null
+Rune.initlogic({
+ minPlayers: 2,
+ maxPlayers: 2,
+ setup: (players) => ({
+ player1: players[0],
+ player2: players[1],
+ first_choices: new Array(3).fill(null),
+ second_choices: new Array(3).fill(null),
+ first_ready: false,
+ second_ready: false,
+ pos: 0,
+ }),
+ actions: {
+ set_choices: (first, second, third, { game, playerId }) => {
+ if (!game.first_ready || !game.second_ready) {
+ throw Rune.invalidAction();
+ }
+
+ let is_first = game.player1 === playerId;
+
+ function is_choices_filled(choices) {
+ for (let i = 0; i < 3; ++i) {
+ if (choices[i] === null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if (is_choices_filled(is_first ? game.first_choices : game.second_choices)) {
+ throw Rune.invalidAction();
+ }
+
+ function is_valid_choice(choice) {
+ if (choice !== 'r' && choice !== 'p' && choice !== 's') {
+ return false;
+ }
+ return true;
+ }
+
+ if (!is_valid_choice(first) || !is_valid_choice(second)
+ || !is_valid_choice(third)) {
+ throw Rune.invalidAction();
+ }
+
+ if (is_first) {
+ game.first_choices[0] = first;
+ game.first_choices[1] = second;
+ game.first_choices[2] = third;
+ } else {
+ game.second_choices[0] = first;
+ game.second_choices[1] = second;
+ game.second_choices[2] = third;
+ }
+
+
+ if (!is_choices_filled(game.first_choices)
+ || !is_choices_filled(game.second_choices)) {
+ return;
+ }
+
+ // Both sides are ready, iterate through matchups
+
+ let has_remaining = false;
+
+ for (let i = 0; i < 3; ++i) {
+ // Get next matchup
+ if (game.first_choices[i] === 'r'
+ || game.first_choices[i] === 'p'
+ || game.first_choices[i] === 's') {
+ // check if first won the matchup
+ if ((game.first_choices[i] === 'r' && game.second_choices[i] === 's')
+ || (game.first_choices[i] === 'p' && game.second_choices[i] === 'r')
+ || (game.first_choices[i] === 's' && game.second_choices[i] === 'p')) {
+ game.first_choices[i] = 'w';
+ game.second_choices[i] = 'l';
+ game.pos = game.pos + 1;
+ }
+ // check if second won the matchup
+ else if ((game.first_choices[i] === 'r' && game.second_choices[i] === 'p')
+ || (game.first_choices[i] === 'p' && game.second_choices[i] === 's')
+ || (game.first_choices[i] === 's' && game.second_choices[i] === 'r')) {
+ game.first_choices[i] = 'l';
+ game.second_choices[i] = 'w';
+ game.pos = game.pos - 1;
+ }
+ // matchup was a draw
+ else {
+ game.first_choices[i] = 'd';
+ game.second_choices[i] = 'd';
+ }
+ has_remaining = i === 2 ? false : true;
+ }
+ }
+
+ game.first_ready = false;
+ game.second_ready = false;
+ if (!has_remaining) {
+ if (pos <= -3) {
+ // second won
+ Rune.gameOver();
+ } else if (pos >= 3) {
+ // first won
+ Rune.gameOver();
+ } else {
+ // game is still going
+ for (let i = 0; i < 3; ++i) {
+ game.first_choices[i] = null;
+ game.second_choices[i] = null;
+ }
+ }
+ }
+ },
+ set_ready: ({ game, playerId }) => {
+ let is_first = game.player1 === playerId;
+
+ if (is_first) {
+ game.first_ready = true;
+ } else {
+ game.second_ready = true;
+ }
+ },
+ },
+})