commit f234077cd79e9f1d915969273850c0702c571c57 Author: Gabriel Pariat Date: Sun Aug 14 18:48:27 2022 -0400 first diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fcd61ea --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +.git +/resources/public/js/compiled/** +figwheel_server.log +pom.xml +*jar +/lib/ +/classes/ +/out/ +/target/ +.lein-deps-sum +.lein-repl-history +.lein-plugins/ +.repl +.nrepl-port \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cdd1de3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +/resources/public/js/compiled/** +figwheel_server.log +pom.xml +*jar +/lib/ +/classes/ +/out/ +/target/ +.lein-deps-sum +.lein-repl-history +.lein-plugins/ +.repl +.nrepl-port diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7c5d01a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +#stage 1 +FROM debian as debian +WORKDIR /app +COPY . . +RUN apt update && apt install -y leiningen +RUN lein cljsbuild once + +#stage 2 +FROM nginx +COPY --from=debian /app/resources/public /usr/share/nginx/html \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..be04db0 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,11 @@ +pipeline { + agent any + options { + skipStagesAfterUnstable() + } + stages { + stage('Build') { + sh 'echo "Hello!"' + } + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b575669 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# pong + +FIXME: Write a one-line description of your library/project. + +## Overview + +FIXME: Write a paragraph about the library/project and highlight its goals. + +## Setup + +To get an interactive development environment run: + + + lein figwheel + +and open your browser at [localhost:3449](http://localhost:3449/). +This will auto compile and send all changes to the browser without the +need to reload. After the compilation process is complete, you will +get a Browser Connected REPL. An easy way to try it is: + + (js/alert "Am I connected?") + +and you should see an alert in the browser window. + +To clean all compiled files: + + lein clean + +To create a production build run: + + lein do clean, cljsbuild once min + +And open your browser in `resources/public/index.html`. You will not +get live reloading, nor a REPL. + +## License + +Copyright © 2014 FIXME + +Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. diff --git a/dev/user.clj b/dev/user.clj new file mode 100644 index 0000000..77dcc03 --- /dev/null +++ b/dev/user.clj @@ -0,0 +1,42 @@ +(ns user + (:require + [figwheel-sidecar.repl-api :as f])) + +;; user is a namespace that the Clojure runtime looks for and +;; loads if its available + +;; You can place helper functions in here. This is great for starting +;; and stopping your webserver and other development services + +;; The definitions in here will be available if you run "lein repl" or launch a +;; Clojure repl some other way + +;; You have to ensure that the libraries you :require are listed in your dependencies + +;; Once you start down this path +;; you will probably want to look at +;; tools.namespace https://github.com/clojure/tools.namespace +;; and Component https://github.com/stuartsierra/component + + +(defn fig-start + "This starts the figwheel server and watch based auto-compiler." + [] + ;; this call will only work as long as your :cljsbuild and + ;; :figwheel configurations are at the top level of your project.clj + ;; and are not spread across different lein profiles + + ;; otherwise you can pass a configuration into start-figwheel! manually + (f/start-figwheel!)) + +(defn fig-stop + "Stop the figwheel server and watch based auto-compiler." + [] + (f/stop-figwheel!)) + +;; if you are in an nREPL environment you will need to make sure you +;; have setup piggieback for this to work +(defn cljs-repl + "Launch a ClojureScript REPL that is connected to your build and host environment." + [] + (f/cljs-repl)) diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..fe4c242 --- /dev/null +++ b/project.clj @@ -0,0 +1,95 @@ +(defproject pong "0.1.0-SNAPSHOT" + :description "FIXME: write this!" + :url "http://example.com/FIXME" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + + :min-lein-version "2.9.1" + + :dependencies [[org.clojure/clojure "1.10.0"] + [org.clojure/clojurescript "1.10.773"] + [org.clojure/core.async "0.4.500"]] + + :plugins [[lein-figwheel "0.5.20"] + [lein-cljsbuild "1.1.7" :exclusions [[org.clojure/clojure]]]] + + :source-paths ["src"] + + :cljsbuild {:builds + [{:id "dev" + :source-paths ["src"] + + ;; The presence of a :figwheel configuration here + ;; will cause figwheel to inject the figwheel client + ;; into your build + :figwheel {:on-jsload "pong.core/on-js-reload" + ;; :open-urls will pop open your application + ;; in the default browser once Figwheel has + ;; started and compiled your application. + ;; Comment this out once it no longer serves you. + :open-urls ["http://localhost:3449/index.html"]} + + :compiler {:main pong.core + :asset-path "js/compiled/out" + :output-to "resources/public/js/compiled/pong.js" + :output-dir "resources/public/js/compiled/out" + :source-map-timestamp true + ;; To console.log CLJS data-structures make sure you enable devtools in Chrome + ;; https://github.com/binaryage/cljs-devtools + :preloads [devtools.preload]}} + ;; This next build is a compressed minified build for + ;; production. You can build this with: + ;; lein cljsbuild once min + {:id "min" + :source-paths ["src"] + :compiler {:output-to "resources/public/js/compiled/pong.js" + :main pong.core + :optimizations :advanced + :pretty-print false}}]} + + :figwheel {;; :http-server-root "public" ;; default and assumes "resources" + ;; :server-port 3449 ;; default + ;; :server-ip "127.0.0.1" + + :css-dirs ["resources/public/css"] ;; watch and update CSS + + ;; Start an nREPL server into the running figwheel process + ;; :nrepl-port 7888 + + ;; Server Ring Handler (optional) + ;; if you want to embed a ring handler into the figwheel http-kit + ;; server, this is for simple ring servers, if this + + ;; doesn't work for you just run your own server :) (see lein-ring) + + ;; :ring-handler hello_world.server/handler + + ;; To be able to open files in your editor from the heads up display + ;; you will need to put a script on your path. + ;; that script will have to take a file path and a line number + ;; ie. in ~/bin/myfile-opener + ;; #! /bin/sh + ;; emacsclient -n +$2 $1 + ;; + ;; :open-file-command "myfile-opener" + + ;; if you are using emacsclient you can just use + ;; :open-file-command "emacsclient" + + ;; if you want to disable the REPL + ;; :repl false + + ;; to configure a different figwheel logfile path + ;; :server-logfile "tmp/logs/figwheel-logfile.log" + + ;; to pipe all the output to the repl + ;; :server-logfile false + } + + :profiles {:dev {:dependencies [[binaryage/devtools "1.0.0"] + [figwheel-sidecar "0.5.20"]] + ;; need to add dev source path here to get user.clj loaded + :source-paths ["src" "dev"] + ;; need to add the compiled assets to the :clean-targets + :clean-targets ^{:protect false} ["resources/public/js/compiled" + :target-path]}}) diff --git a/resources/public/css/style.css b/resources/public/css/style.css new file mode 100644 index 0000000..12cddf0 --- /dev/null +++ b/resources/public/css/style.css @@ -0,0 +1,8 @@ +/* some style */ + +body { + margin: 0; + display: flex; + background-color: black; +} + diff --git a/resources/public/favicon.ico b/resources/public/favicon.ico new file mode 100644 index 0000000..470268d Binary files /dev/null and b/resources/public/favicon.ico differ diff --git a/resources/public/index.html b/resources/public/index.html new file mode 100644 index 0000000..deaf6f7 --- /dev/null +++ b/resources/public/index.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/pong/core.cljs b/src/pong/core.cljs new file mode 100644 index 0000000..4be3a51 --- /dev/null +++ b/src/pong/core.cljs @@ -0,0 +1,371 @@ +(ns pong.core + (:require [pong.menu :as menu])) + +(enable-console-print!) + +;; define your app data so that it doesn't get over-written on reload + +(declare game-draw next-frame relaunch-ball game-loop) + +(defonce keyboard (atom {})) + +(def canvas (.getElementById js/document "game")) +(def context (.getContext canvas "2d")) +(def paddle-height 320) +(def paddle-width 40) +(def paddle-speed 1) +(def ball-radius 20) +(def ball-speed 0.75) + + +(defn window-height + [] + (.-innerHeight js/window)) + +(defn window-width + [] + (.-innerWidth js/window)) + +(defn unit-vector + [v] + (let [length (Math.sqrt (reduce + (map #(Math.pow % 2) v)))] + (map #(/ % length) v))) + +(defn contains-point? + [rect x y] + (and (> x (:x rect)) + (< x (+ (:x rect) (:w rect))) + (> y (:y rect)) + (< y (+ (:y rect) (:h rect))))) + +(defn collision? + [ball paddle] + (or (contains-point? paddle (- (:x ball) (:radius ball)) (:y ball)) + (contains-point? paddle (+ (:x ball) (:radius ball)) (:y ball)))) + +(defn apply-ball-velocity + [{:keys [ball progress] :as state}] + (let [velocity (map #(* % progress) (:velocity ball))] + (assoc state :ball + (assoc ball + :x (+ (first velocity) (:x ball)) + :y (+ (second velocity) (:y ball)))))) + +(defn bounce-ball-off-top-wall + [{{y :y radius :radius [vx vy] :velocity} :ball :as state}] + (if (< (- y radius) 0) + (as-> state state + (assoc-in state [:ball :y] radius) + (assoc-in state [:ball :velocity] [vx (- vy)])) + state)) + +(defn bounce-ball-off-bottom-wall + [{{y :y radius :radius [vx vy] :velocity} :ball :as state}] + (if (> (+ y radius) (window-height)) + (as-> state state + (assoc-in state [:ball :y] (- (window-height) radius)) + (assoc-in state [:ball :velocity] [vx (- vy)])) + state)) + +(defn bounce-ball-off-paddle + [{ball-y :y [dx] :velocity} {paddle-y :y paddle-h :h}] + (unit-vector [(* (- (/ dx (Math.abs dx))) (window-width)) + (* (- (/ (- ball-y paddle-y) paddle-h) 0.5) (window-height))])) + +(defn bounce-ball-off-left-paddle + [{paddle :left-paddle ball :ball :as state}] + (assoc-in state [:ball :velocity] (bounce-ball-off-paddle ball paddle))) + +(defn bounce-ball-off-right-paddle + [{paddle :right-paddle ball :ball :as state}] + (assoc-in state [:ball :velocity] (bounce-ball-off-paddle ball paddle))) + +(defn put-ball-infront-of-left-paddle + [{{:keys [radius]} :ball {:keys [x w]} :left-paddle :as state}] + (assoc-in state [:ball :x] (+ x w radius))) + +(defn put-ball-infront-of-right-paddle + [{{:keys [radius]} :ball {:keys [x w]} :right-paddle :as state}] + (assoc-in state [:ball :x] (- x radius))) + +(defn bounce-ball-off-left-paddle? + [{:keys [ball left-paddle] :as state}] + (if (collision? ball left-paddle) + (-> state + put-ball-infront-of-left-paddle + bounce-ball-off-left-paddle) + state)) + +(defn bounce-ball-off-right-paddle? + [{:keys [ball right-paddle] :as state}] + (if (collision? ball right-paddle) + (-> state + put-ball-infront-of-right-paddle + bounce-ball-off-right-paddle) + state)) + +(defn launch-ball-left + [state] + (assoc state :ball (relaunch-ball -1))) + +(defn launch-ball-right + [state] + (assoc state :ball (relaunch-ball 1))) + +(defn increase-right-player-score + [state] + (update-in state [:score :right] inc)) + +(defn increase-left-player-score + [state] + (update-in state [:score :left] inc)) + +(defn left-player-scored + [{{:keys [x radius]} :ball :as state}] + (if (> (- x radius) (window-width)) + (-> state + launch-ball-right + increase-left-player-score) + state)) + +(defn right-player-scored + [{{:keys [x radius]} :ball :as state}] + (if (< (+ x radius) 0) + (-> state + launch-ball-left + increase-right-player-score) + state)) + +(defn apply-paddle-speed + [paddle direction progress] + (direction (:y paddle) (* paddle-speed progress))) + +(defn handle-left-player-paddle-up-movement + [{:keys [progress left-paddle] :as state}] + (if (:KeyW @keyboard) + (assoc-in state + [:left-paddle :y] + (apply-paddle-speed left-paddle - progress)) + state)) + +(defn handle-left-player-paddle-down-movement + [{:keys [progress left-paddle] :as state}] + (if (:KeyS @keyboard) + (assoc-in state + [:left-paddle :y] + (apply-paddle-speed left-paddle + progress)) + state)) + +(defn keep-left-paddle-in-bounds + [{:keys [left-paddle] :as state}] + (assoc-in state + [:left-paddle :y] + (max 0 (min (- (window-height) (:h left-paddle)) + (:y left-paddle))))) + +(defn update-left-paddle + [{:keys [progress left-paddle] :as state}] + (-> state + handle-left-player-paddle-down-movement + handle-left-player-paddle-up-movement + keep-left-paddle-in-bounds)) + +(defn stick-right-paddle-to-right-of-screen + [{:keys [right-paddle] :as state}] + (assoc-in state [:right-paddle :x] (- (window-width) (:w right-paddle)))) + +(defn handle-right-player-paddle-up-movement + [{:keys [progress right-paddle] :as state}] + (if (:ArrowUp @keyboard) + (assoc-in state + [:right-paddle :y] + (apply-paddle-speed right-paddle - progress)) + state)) + +(defn handle-right-player-paddle-down-movement + [{:keys [progress right-paddle] :as state}] + (if (:ArrowDown @keyboard) + (assoc-in state + [:right-paddle :y] + (apply-paddle-speed right-paddle + progress)) + state)) + +(defn player-update-right-paddle + [state] + (-> state + handle-right-player-paddle-up-movement + handle-right-player-paddle-down-movement)) + +(defn computer-update-right-paddle + [{:keys [progress right-paddle ball] :as state}] + (let [paddle-middle (+ (:y right-paddle) (/ (:h right-paddle) 2)) + speed (* paddle-speed progress)] + (assoc-in state [:right-paddle :y] + (cond + (< (:y ball) (- paddle-middle speed)) + (max (- (:y right-paddle) speed) 0) + (> (:y ball) (+ paddle-middle speed)) + (min (+ (:y right-paddle) speed) + (- (window-height) (:h right-paddle))) + :else (:y right-paddle))))) + +(defn draw-rect + [{x :x y :y w :w h :h}] + (.fillRect context x y w h)) + +(defn fill-circle + [{x :x y :y radius :radius}] + (.beginPath context) + (.arc context x y radius 0 (* 2 Math.PI)) + (.fill context)) + +(defn draw-score + [{:keys [left right]}] + (set! (.-font context) "30px monospace") + (.fillText context (str left " | " right) (/ (window-width) 2) 30)) + +(defn game-draw + [{:keys [left-paddle right-paddle ball score] :as state}] + (.clearRect context 0 0 (window-width) (window-height)) + (set! (.-fillStyle context) "#FFF") + (draw-rect left-paddle) + (draw-rect right-paddle) + (fill-circle ball) + (draw-score score) + state) + +(defn handle-return-to-menu + [{:keys [keyboard] :as state}] + (if (:Escape keyboard) + (assoc state :on-loop menu/scene) + state)) + +(defn game-loop + [{:keys [update-right-paddle] :as state}] + (-> state + update-left-paddle + update-right-paddle + stick-right-paddle-to-right-of-screen + apply-ball-velocity + bounce-ball-off-top-wall + bounce-ball-off-bottom-wall + bounce-ball-off-left-paddle? + bounce-ball-off-right-paddle? + left-player-scored + right-player-scored + handle-return-to-menu + game-draw)) + +(defn next-frame + [last-render state] + (.requestAnimationFrame js/window + #(next-frame % ((:on-loop state) + (merge state + {:window-width (.-innerWidth js/window) + :window-height (.-innerHeight js/window) + :keyboard @keyboard + :progress (- % last-render)}))))) + +(defn vec* + [v d] + (map #(* % d) v)) + +(defn one-or-minus-one + [] + (let [n (- (rand 2) 1)] (/ n (Math.abs n)))) + +(defn launch-ball [x y dx dy] + {:x x + :y y + :radius ball-radius + :velocity (vec* (unit-vector [dx dy]) ball-speed)}) + +(defn relaunch-ball + [direction] + (launch-ball (/ (window-width) 2) + (rand (window-height)) + (* direction (/ (window-width) 2)) + (- (rand (window-height)) (/ (window-height) 2)))) + +(defn start-game + [state update-right-paddle] + (merge state + {:left-paddle {:x 0.0 + :y (/ (- (window-height) paddle-height) 2) + :w paddle-width + :h paddle-height} + :right-paddle {:x (- (window-width) paddle-width) + :y (/ (- (window-height) paddle-height) 2) + :w paddle-width + :h paddle-height} + :update-right-paddle update-right-paddle + :ball (relaunch-ball (one-or-minus-one)) + :score {:left 0 :right 0} + :on-loop game-loop})) + +(defn start-one-player-game + [state] + (println "start one player game") + (start-game state computer-update-right-paddle)) + +(defn start-two-players-game + [state] + (println "start two players game") + (start-game state player-update-right-paddle)) + + +(next-frame (.now js/performance) + {:on-loop menu/scene + :menu-item-selected 0 + :menu-items [{:txt "1 Player" + :action start-one-player-game} + {:txt "2 Players" + :action start-two-players-game}] + :context context}) + +(defn set-canvas-size-to-window-size + [] + (set! (.-width canvas) (.-innerWidth js/window)) + (set! (.-height canvas) (.-innerHeight js/window))) + +(set-canvas-size-to-window-size) + +(set! (.-onresize js/window) + set-canvas-size-to-window-size) + +;; (def update-game-state (partial swap! game-state assoc)) +;; (def update-left-paddle-movement (partial update-game-state :left-paddle-movement)) +;; (def update-right-paddle-movement (partial update-game-state :right-paddle-movement)) + +;; (swap! game-state assoc :on-loop menu-loop) +;; (fn [event] +;; (let [code (.-code event)] +;; (case code +;; "KeyW" (update-left-paddle-movement :up) +;; "KeyS" (update-left-paddle-movement :down) +;; "ArrowUp" (update-right-paddle-movement :up) +;; "ArrowDown" (update-right-paddle-movement :down) +;; "Escape" (swap! game-state assoc :on-loop menu-loop) +;; nil))) + +;; (fn [event] +;; (let [code (.-code event)] +;; (case code +;; "KeyW" (and (= (:left-paddle-movement @game-state) :up) +;; (update-left-paddle-movement nil)) +;; "KeyS" (and (= (:left-paddle-movement @game-state) :down) +;; (update-left-paddle-movement nil)) +;; "ArrowUp" (and (= (:right-paddle-movement @game-state) :up) +;; (update-right-paddle-movement nil)) +;; "ArrowDown" (and (= (:right-paddle-movement @game-state) :down) +;; (update-right-paddle-movement nil)) +;; nil))) + +(.addEventListener js/document "keydown" #(swap! keyboard assoc (keyword (.-code %)) %)) +(.addEventListener js/document "keyup" #(swap! keyboard dissoc (keyword (.-code %)))) + +(defn on-js-reload [] + ;; optionally touch your app-state to force rerendering depending on + ;; your application + ;; (swap! app-state update-in [:__figwheel_counter] inc) +) diff --git a/src/pong/menu.cljs b/src/pong/menu.cljs new file mode 100644 index 0000000..c4d3c44 --- /dev/null +++ b/src/pong/menu.cljs @@ -0,0 +1,78 @@ +(ns pong.menu) + +(def next-key-ms 200) + +(defn decrease-next-key + [{:keys [next-key progress] :as state}] + (if (> next-key 0) + (assoc state :next-key (- next-key progress)) + state)) + +(defn handle-menu-up + [{:keys [menu-item-selected menu-items keyboard next-key] :as state}] + (if (and (or (:KeyW keyboard) (:ArrowUp keyboard)) (<= next-key 0)) + (merge state + {:menu-item-selected (mod (dec (+ menu-item-selected (count menu-items))) + (count menu-items)) + :next-key next-key-ms}) + state)) + +(defn handle-menu-down + [{:keys [menu-item-selected menu-items keyboard next-key] :as state}] + (if (and (or (:KeyS keyboard) (:ArrowDown keyboard)) (<= next-key 0)) + (merge state + {:menu-item-selected (mod (inc menu-item-selected) (count menu-items)) + :next-key next-key-ms}) + state)) + +(defn handle-menu-selected + [{:keys [menu-item-selected menu-items keyboard] :as state}] + (if (:Enter keyboard) + ((:action (nth menu-items menu-item-selected)) + state) + state)) + +(defn draw-text-centered + [context txt x y] + (let [width (.-width (.measureText context txt))] + (.fillText context txt (- x (/ width 2)) y))) + +(defn draw-menu-item + [context item selected x y] + (draw-text-centered context + (str (when selected ">") + (:txt item) + (when selected "<")) + x + y)) + +(defn draw + [{:keys [context menu-item-selected window-width window-height menu-items] :as state}] + (.clearRect context 0 0 + window-width + window-height) + + (set! (.-fillStyle context) "#FFF") + (set! (.-font context) "30px monospace") + + (draw-text-centered context "Pariatech's Pong" (/ window-width 2) 60) + + + (let [middle-horizontally (/ window-width 2) + offset-vertically (/ (- window-height (* (count menu-items) 40)) 2)] + (doall (map-indexed #(draw-menu-item context + %2 + (= %1 (Math/floor menu-item-selected)) + middle-horizontally + (+ offset-vertically (* %1 40))) + menu-items))) + state) + +(defn scene + [state] + (-> state + decrease-next-key + handle-menu-up + handle-menu-down + handle-menu-selected + draw))