diff --git a/client/client.asd b/client/client.asd index 4b543d9..98c063c 100644 --- a/client/client.asd +++ b/client/client.asd @@ -6,10 +6,11 @@ :license "AGPLv3" :version "0.0.1" :serial t - :depends-on ("game" "cl-raylib" "3d-vectors") + :depends-on ("game" "cl-raylib" "3d-vectors" "usocket" "bordeaux-threads" "str") :pathname "src" :components ((:file "package") + (:file "constants") (:module "gui" :serial t :components ((:file "package") @@ -20,7 +21,10 @@ (:file "line"))) (:file "utils") (:file "scene") + (:file "client") + (:file "game-scene") + (:file "local-game") + (:file "online-game") (:file "main-menu") - (:file "game") - (:file "client"))) + (:file "main"))) diff --git a/client/src/client.lisp b/client/src/client.lisp index 44d71a6..5675835 100644 --- a/client/src/client.lisp +++ b/client/src/client.lisp @@ -1,44 +1,65 @@ (in-package :pong.client) -(defparameter *scene* nil) - -(defun open-main-menu% () - (setf *scene* (open-main-menu #'start-1-player-game - #'start-2-players-game - #'create-online-game - #'join-online-game))) - -(defun start-1-player-game () - (setf *scene* (open-game (make-instance 'local-game-1p) #'open-main-menu%))) - -(defun start-2-players-game () - (setf *scene* (open-game (make-instance 'local-game-2p) #'open-main-menu%))) - -(defun create-online-game () - (format t "~%Create online game.")) -(defun join-online-game () - (format t "~%Join online game.")) - -(defun main () - (let* ((last-time nil) - (current-time (get-internal-real-time))) - (r:with-window (800 600 "Pariatech's Pong") - (gui:with-gui - (open-main-menu%) - - (r:set-config-flags r:+flag-window-resizable+) - (r:set-target-fps 60) - (r:set-exit-key 0) - (loop - until (or (r:window-should-close) (scene-should-close *scene*)) - do (setf last-time current-time) - (setf current-time (/ (get-internal-real-time) internal-time-units-per-second)) - - (let ((timelapse (- current-time last-time))) - (on-update *scene* timelapse)) - (r:with-drawing - (r:clear-background r:+gray+) - (on-draw *scene*) - (r:draw-fps 20 20))))))) - -(main) +(defparameter *client* nil) + +(defclass client () + ((ip :initarg :ip :initform nil :reader client-ip) + (port :initarg :port :initform nil :reader client-port) + (running-p :initform nil :accessor client-running-p) + (socket :initform nil :accessor client-socket))) + +(defmethod listen-to ((self client)) + (with-slots (running-p socket) self + (when (and running-p (usocket:wait-for-input socket :timeout 5) running-p) + (let ((data (cpk:decode-stream (usocket:socket-stream socket)))) + (format t "[CLIENT] Data received: ~a~%" data))))) + +(defmethod start ((self client)) + (with-slots (ip port running-p socket) self + (format t "uh?~%") + (unless running-p + (setf running-p t) + (setf socket (usocket:socket-connect ip port :element-type 'unsigned-byte))))) + +(defmethod stop ((self client)) + (with-slots (running-p socket) self + (setf running-p nil) + (when socket + (usocket:socket-close socket)) + (setf socket nil))) + +(defmethod write-to ((self client) data) + (let ((stream (usocket:socket-stream (client-socket self)))) + (format t "[CLIENT] Data sent: ~a~%" data) + (cpk:encode data :stream stream) + (force-output stream))) + +(defun handle-client () + (loop while (and *client* (client-running-p *client*)) + do (let ((data (listen-to *client*)))))) + +(defun start-client (ip port) + (unless *client* + (setf *client* (make-instance 'client :ip ip :port port)) + (start *client*) + (bt:make-thread #'handle-client))) + +(defun stop-client () + (when *client* + (stop *client*)) + (setf *client* nil)) + +(defun send-data-to-server (data) + (write-to *client* data)) + +(defun test-client () + (start-client "127.0.0.1" 54321) + (write-to *client* "hello!") + (sleep 1.015) + (write-to *client* "bye!") + (stop-client)) + +;; (test-client) + +;; (stop-client) + diff --git a/client/src/constants.lisp b/client/src/constants.lisp new file mode 100644 index 0000000..5c30f07 --- /dev/null +++ b/client/src/constants.lisp @@ -0,0 +1,17 @@ +(in-package :pong.client) + +(defconstant +title-top-padding+ 30) +(defconstant +menu-padding+ 10) +(defconstant +menu-group-padding+ 20) +(defconstant +menu-font+ "assets/ComicMono.ttf") +(defconstant +menu-font-size+ 32) +(defconstant +menu-title-font-size+ 52) +(defconstant +menu-group-title-font-size+ 42) +(defconstant +menu-text-color+ r:+white+) +(defconstant +menu-local-1-player+ "1 Player") +(defconstant +menu-local-2-players+ "2 Players") +(defconstant +menu-online-create+ "Create Game") +(defconstant +menu-online-join+ "Join Game") +(defconstant +score-txt-size+ 32) +(defconstant +score-padding+ 20) +(defconstant +score-font+ "assets/ComicMono.ttf") diff --git a/client/src/create-online-game-scene.lisp b/client/src/create-online-game-scene.lisp new file mode 100644 index 0000000..27ac91c --- /dev/null +++ b/client/src/create-online-game-scene.lisp @@ -0,0 +1,59 @@ +(in-package :pong.client) + +(defclass create-online-game-scene (scene) + ((name :initform "" :accessor create-online-game-scene-name) + (title :initarg :title :initform nil :reader create-online-game-scene-title) + (name-field :initarg :name-field :initform nil :reader create-online-game-scene-name-field) + (prompt :initarg :prompt :initform nil :reader create-online-game-scene-prompt))) + +(defun open-create-online-game-scene () + (let ((root-element (gui:make-rectangle :color r:+darkgray+ + :h-align :center + :v-align :middle)) + (title (gui:make-text :color r:+white+ + :font +menu-font+ + :font-size +menu-title-font-size+ + :y +title-top-padding+ + :h-align :center + :text "Create online game")) + (name-field (gui:make-text :color r:+white+ + :font +menu-font+ + :font-size +menu-font-size+ + :v-align :middle)) + (prompt (gui:make-text :color r:+white+ + :font +menu-font+ + :font-size +menu-font-size+ + :h-align :right + :v-align :middle + :text "What is your name?:"))) + (gui:add-children root-element title name-field prompt) + + (set-scene (make-instance 'create-online-game-scene :root-element root-element + :name-field name-field + :title title + :prompt prompt)))) + +(defmethod on-update ((scene create-online-game-scene) timelapse) + (with-slots (name move-back) scene + ;; Might be interesting to add repeating keys, but I don't care. + (loop for key = (r:get-key-pressed) + while (plusp key) + do (format t "~a~%" key) + when (= key r:+key-escape+) + do (open-main-menu) + when (= key r:+key-backspace+) + do (setf name (str:substring 0 -1 name)) + when (= key r:+key-enter+) + do (start-online-game) + when (and (>= key 32) (<= key 96)) + do (setf name (str:concat name (string (code-char key))))))) + +(defmethod on-draw ((scene create-online-game-scene)) + (with-slots (name root-element title name-field prompt) scene + (position-root root-element) + (setf (gui:x title) (floor (gui:w root-element) 2)) + (setf (gui:text name-field) name) + (setf (gui:x name-field) (+ (floor (gui:w root-element) 2) +score-padding+)) + (setf (gui:x prompt) (- (floor (gui:w root-element) 2) +score-padding+)) + (setf (gui:y name-field) (floor (gui:h root-element) 2)) + (setf (gui:y prompt) (floor (gui:h root-element) 2)))) diff --git a/client/src/game.lisp b/client/src/game-scene.lisp similarity index 52% rename from client/src/game.lisp rename to client/src/game-scene.lisp index c7e66ac..b9513f8 100644 --- a/client/src/game.lisp +++ b/client/src/game-scene.lisp @@ -1,16 +1,5 @@ (in-package :pong.client) -(defconstant +score-txt-size+ 32) -(defconstant +score-padding+ 20) -(defconstant +score-font+ "assets/ComicMono.ttf") - -(defclass local-game-1p (g:game) - ((computer-paddle-target :initarg :computer-paddle-target - :initform nil - :accessor local-game-1p-computer-paddle-target))) - -(defclass local-game-2p (g:game) ()) - (defclass game-scene (scene) ((game :initarg :game :initform nil :reader game-scene-game) (left-score :initarg :left-score :initform nil :reader game-scene-left-score) @@ -21,13 +10,12 @@ (right-player :initarg :right-player :initform nil :reader game-scene-right-player) (ball :initarg :ball :initform nil :reader game-scene-ball) (line :initarg :line :initform nil :reader game-scene-line) - (move-back :initarg :move-back :initform nil :reader game-scene-move-back) (quit-menu :initarg :quit-menu :initform nil :reader game-scene-quit-menu) (quit-menu-text :initarg :quit-menu-text :initform nil :reader game-scene-quit-menu-text) (quit-menu-yes :initarg :quit-menu-yes :initform nil :reader game-scene-quit-menu-yes) (quit-menu-no :initarg :quit-menu-no :initform nil :reader game-scene-quit-menu-no))) -(defun open-game (game move-back) +(defun open-game (game) (let ((root-element (gui:make-rectangle :color r:+darkgray+ :h-align :center :v-align :middle)) @@ -109,7 +97,6 @@ :right-player right-player :ball ball :line line - :move-back move-back :quit-menu quit-menu :quit-menu-text quit-menu-text :quit-menu-yes quit-menu-yes @@ -157,16 +144,18 @@ (setf (gui:h menu) (+ (gui:y yes) (gui:h yes) +score-padding+))) (defmethod on-update ((scene game-scene) timelapse) - (unless (gui:visible (game-scene-quit-menu scene)) - (g:on-update (game-scene-game scene) timelapse)) + (g:on-update (game-scene-game scene) timelapse) + (update-text-if-hovered (game-scene-quit-menu-yes scene) "Yes") (update-text-if-hovered (game-scene-quit-menu-no scene) "No") (when (gui:clickedp (game-scene-quit-menu-no scene) r:+mouse-button-left+) + (g:resume (game-scene-game scene)) (setf (gui:visible (game-scene-quit-menu scene)) nil)) (when (r:is-key-pressed r:+key-escape+) + (g:pause (game-scene-game scene)) (setf (gui:visible (game-scene-quit-menu scene)) t)) (when (gui:clickedp (game-scene-quit-menu-yes scene) r:+mouse-button-left+) - (funcall (game-scene-move-back scene)))) + (open-main-menu))) (defmethod on-draw ((scene game-scene)) (with-slots (game root-element left-score right-score left-paddle right-paddle ball line left-player right-player quit-menu quit-menu-text quit-menu-yes quit-menu-no) @@ -185,155 +174,3 @@ (setf (gui:text right-player) (g:state-right-player game-state)) (position-right-player right-player) (position-quit-menu quit-menu quit-menu-text quit-menu-yes quit-menu-no)))) - -(defun handle-player-paddle (paddle timelapse upward-keys downward-keys) - (let ((paddle-y (g:paddle-y paddle)) - (paddle-vy (g:paddle-vy paddle))) - (setf (g:paddle-vy paddle) - (cond ((member-if #'r:is-key-down downward-keys) g:+paddle-speed+) - ((member-if #'r:is-key-down upward-keys) (- g:+paddle-speed+)) - (t 0.0))) - (setf (g:paddle-y paddle) - (min (max (+ paddle-y (* paddle-vy timelapse)) (/ g:+paddle-height+ 2)) - (- 1.0 (/ g:+paddle-height+ 2)))))) - -(defun handle-left-player (state timelapse upward-keys downward-keys) - (handle-player-paddle (g:state-left-paddle state) timelapse upward-keys downward-keys)) - -(defun handle-right-player (state timelapse upward-keys downward-keys) - (handle-player-paddle (g:state-right-paddle state) timelapse upward-keys downward-keys)) - -(defun handle-right-computer (state timelapse target) - (let* ((right-paddle (g:state-right-paddle state)) - (paddle-y (g:paddle-y right-paddle)) - (ball (g:state-ball state)) - (ball-xy (g:ball-xy ball)) - (ball-y (v:vy ball-xy)) - (paddle-target (and target (+ paddle-y (- target (/ g:+paddle-height+ 2)))))) - - - (setf (g:paddle-vy right-paddle) - (if paddle-target - (let ((delta (/ (- ball-y paddle-target) timelapse))) - (if (minusp delta) - (max delta (- g:+paddle-speed+)) - (min delta g:+paddle-speed+))) - 0.0)) - - (setf (g:paddle-y right-paddle) - (min (max (+ paddle-y (* (g:paddle-vy right-paddle) timelapse)) - (/ g:+paddle-height+ 2)) - (- 1.0 (/ g:+paddle-height+ 2)))))) - -(defun point-in-rect-p (px py rx ry rw rh) - (and (> px rx) - (< px (+ rx rw)) - (> py ry) - (< py (+ ry rh)))) - -(defun get-ball-paddle-collision (ball paddle paddle-x) - (let* ((ball-xy (g:ball-xy ball)) - (ball-x (v:vx ball-xy)) - (ball-y (v:vy ball-xy)) - (paddle-y (g:paddle-y paddle))) - (when (or (point-in-rect-p (- ball-x g:+ball-radius+) - (- ball-y g:+ball-radius+) - paddle-x - (- paddle-y (/ g:+paddle-height+ 2)) - g:+paddle-width+ - g:+paddle-height+) - (point-in-rect-p (- ball-x g:+ball-radius+) - (+ ball-y g:+ball-radius+) - paddle-x - (- paddle-y (/ g:+paddle-height+ 2)) - g:+paddle-width+ - g:+paddle-height+) - (point-in-rect-p (+ ball-x g:+ball-radius+) - (- ball-y g:+ball-radius+) - paddle-x - (- paddle-y (/ g:+paddle-height+ 2)) - g:+paddle-width+ - g:+paddle-height+) - (point-in-rect-p (+ ball-x g:+ball-radius+) - (+ ball-y g:+ball-radius+) - paddle-x - (- paddle-y (/ g:+paddle-height+ 2)) - g:+paddle-width+ - g:+paddle-height+)) - (- ball-y paddle-y)))) - -(defun handle-ball (state timelapse) - (let* ((ball (g:state-ball state)) - (ball-xy (g:ball-xy ball)) - (ball-x (v:vx ball-xy)) - (ball-y (v:vy ball-xy)) - (ball-vxy (g:ball-vxy ball)) - (ball-vy (v:vy ball-vxy)) - (left-paddle (g:state-left-paddle state)) - (right-paddle (g:state-right-paddle state)) - (ball-left-paddle-collision - (get-ball-paddle-collision ball left-paddle 0.0)) - (ball-right-paddle-collision - (get-ball-paddle-collision ball right-paddle (- 1.0 g:+paddle-width+)))) - (cond - (ball-left-paddle-collision - (g:launch-ball ball - (incf (g:state-bounces state)) - (* (/ ball-left-paddle-collision - (+ (/ g:+paddle-height+ 2) g:+ball-radius+)) - g:+max-launch-angle+) - (+ g:+paddle-width+ g:+ball-radius+) - ball-y)) - (ball-right-paddle-collision - (g:launch-ball ball - (incf (g:state-bounces state)) - (- pi - (* (/ ball-right-paddle-collision - (+ (/ g:+paddle-height+ 2) g:+ball-radius+)) - (/ g:+max-launch-angle+ 2))) - (- 1.0 g:+paddle-width+ g:+ball-radius+) - ball-y)) - ((< (+ ball-x g:+ball-radius+) 0) - (incf (g:state-right-score state)) - (g:random-launch-ball state)) - ((> (- ball-x g:+ball-radius+) 1) - (incf (g:state-left-score state)) - (g:random-launch-ball state)) - ((< (- ball-y g:+ball-radius+) 0) - (setf (v:vy ball-xy) g:+ball-radius+) - (setf (v:vy ball-vxy) (* -1 ball-vy))) - ((> (+ ball-y g:+ball-radius+) 1) - (setf (v:vy ball-xy) (- 1 g:+ball-radius+)) - (setf (v:vy ball-vxy) (* -1 ball-vy))) - (t - (setf (g:ball-xy ball) (v:v+ ball-xy (v:v* ball-vxy timelapse))))))) - -(defun computer-paddle-target (game) - (let* ((game-state (g:game-state game)) - (ball (g:state-ball game-state)) - (ball-vxy (g:ball-vxy ball))) - (if (plusp (v:vx ball-vxy)) - (or (local-game-1p-computer-paddle-target game) - (setf (local-game-1p-computer-paddle-target game) - (random g:+paddle-height+))) - (setf (local-game-1p-computer-paddle-target game) nil)))) - -(defmethod g:on-init ((game local-game-1p)) - (g:random-launch-ball (g:game-state game))) - -(defmethod g:on-update ((game local-game-1p) timelapse) - (let ((game-state (g:game-state game))) - (handle-left-player game-state timelapse - (list r:+key-w+ r:+key-up+) - (list r:+key-s+ r:+key-down+)) - (handle-right-computer game-state timelapse (computer-paddle-target game)) - (handle-ball game-state timelapse))) - -(defmethod g:on-init ((game local-game-2p)) - (g:random-launch-ball (g:game-state game))) - -(defmethod g:on-update ((game local-game-2p) timelapse) - (let ((game-state (g:game-state game))) - (handle-left-player game-state timelapse (list r:+key-w+) (list r:+key-s+)) - (handle-right-player game-state timelapse (list r:+key-up+) (list r:+key-down+)) - (handle-ball game-state timelapse))) diff --git a/client/src/local-game.lisp b/client/src/local-game.lisp new file mode 100644 index 0000000..e7541af --- /dev/null +++ b/client/src/local-game.lisp @@ -0,0 +1,168 @@ +(in-package :pong.client) + +(defclass local-game-1p (g:game) + ((computer-paddle-target :initarg :computer-paddle-target + :initform nil + :accessor local-game-1p-computer-paddle-target))) + +(defclass local-game-2p (g:game) ()) + +(defun start-1-player-game () + (setf *scene* (open-game (make-instance 'local-game-1p)))) + +(defun start-2-players-game () + (setf *scene* (open-game (make-instance 'local-game-2p)))) + +(defun handle-player-paddle (paddle timelapse upward-keys downward-keys) + (let ((paddle-y (g:paddle-y paddle)) + (paddle-vy (g:paddle-vy paddle))) + (setf (g:paddle-vy paddle) + (cond ((member-if #'r:is-key-down downward-keys) g:+paddle-speed+) + ((member-if #'r:is-key-down upward-keys) (- g:+paddle-speed+)) + (t 0.0))) + (setf (g:paddle-y paddle) + (min (max (+ paddle-y (* paddle-vy timelapse)) (/ g:+paddle-height+ 2)) + (- 1.0 (/ g:+paddle-height+ 2)))))) + +(defun handle-left-player (state timelapse upward-keys downward-keys) + (handle-player-paddle (g:state-left-paddle state) timelapse upward-keys downward-keys)) + +(defun handle-right-player (state timelapse upward-keys downward-keys) + (handle-player-paddle (g:state-right-paddle state) timelapse upward-keys downward-keys)) + +(defun handle-right-computer (state timelapse target) + (let* ((right-paddle (g:state-right-paddle state)) + (paddle-y (g:paddle-y right-paddle)) + (ball (g:state-ball state)) + (ball-xy (g:ball-xy ball)) + (ball-y (v:vy ball-xy)) + (paddle-target (and target (+ paddle-y (- target (/ g:+paddle-height+ 2)))))) + + + (setf (g:paddle-vy right-paddle) + (if paddle-target + (let ((delta (/ (- ball-y paddle-target) timelapse))) + (if (minusp delta) + (max delta (- g:+paddle-speed+)) + (min delta g:+paddle-speed+))) + 0.0)) + + (setf (g:paddle-y right-paddle) + (min (max (+ paddle-y (* (g:paddle-vy right-paddle) timelapse)) + (/ g:+paddle-height+ 2)) + (- 1.0 (/ g:+paddle-height+ 2)))))) + +(defun point-in-rect-p (px py rx ry rw rh) + (and (> px rx) + (< px (+ rx rw)) + (> py ry) + (< py (+ ry rh)))) + +(defun get-ball-paddle-collision (ball paddle paddle-x) + (let* ((ball-xy (g:ball-xy ball)) + (ball-x (v:vx ball-xy)) + (ball-y (v:vy ball-xy)) + (paddle-y (g:paddle-y paddle))) + (when (or (point-in-rect-p (- ball-x g:+ball-radius+) + (- ball-y g:+ball-radius+) + paddle-x + (- paddle-y (/ g:+paddle-height+ 2)) + g:+paddle-width+ + g:+paddle-height+) + (point-in-rect-p (- ball-x g:+ball-radius+) + (+ ball-y g:+ball-radius+) + paddle-x + (- paddle-y (/ g:+paddle-height+ 2)) + g:+paddle-width+ + g:+paddle-height+) + (point-in-rect-p (+ ball-x g:+ball-radius+) + (- ball-y g:+ball-radius+) + paddle-x + (- paddle-y (/ g:+paddle-height+ 2)) + g:+paddle-width+ + g:+paddle-height+) + (point-in-rect-p (+ ball-x g:+ball-radius+) + (+ ball-y g:+ball-radius+) + paddle-x + (- paddle-y (/ g:+paddle-height+ 2)) + g:+paddle-width+ + g:+paddle-height+)) + (- ball-y paddle-y)))) + +(defun handle-ball (state timelapse) + (let* ((ball (g:state-ball state)) + (ball-xy (g:ball-xy ball)) + (ball-x (v:vx ball-xy)) + (ball-y (v:vy ball-xy)) + (ball-vxy (g:ball-vxy ball)) + (ball-vy (v:vy ball-vxy)) + (left-paddle (g:state-left-paddle state)) + (right-paddle (g:state-right-paddle state)) + (ball-left-paddle-collision + (get-ball-paddle-collision ball left-paddle 0.0)) + (ball-right-paddle-collision + (get-ball-paddle-collision ball right-paddle (- 1.0 g:+paddle-width+)))) + (cond + (ball-left-paddle-collision + (g:launch-ball ball + (incf (g:state-bounces state)) + (* (/ ball-left-paddle-collision + (+ (/ g:+paddle-height+ 2) g:+ball-radius+)) + g:+max-launch-angle+) + (+ g:+paddle-width+ g:+ball-radius+) + ball-y)) + (ball-right-paddle-collision + (g:launch-ball ball + (incf (g:state-bounces state)) + (- pi + (* (/ ball-right-paddle-collision + (+ (/ g:+paddle-height+ 2) g:+ball-radius+)) + (/ g:+max-launch-angle+ 2))) + (- 1.0 g:+paddle-width+ g:+ball-radius+) + ball-y)) + ((< (+ ball-x g:+ball-radius+) 0) + (incf (g:state-right-score state)) + (g:random-launch-ball state)) + ((> (- ball-x g:+ball-radius+) 1) + (incf (g:state-left-score state)) + (g:random-launch-ball state)) + ((< (- ball-y g:+ball-radius+) 0) + (setf (v:vy ball-xy) g:+ball-radius+) + (setf (v:vy ball-vxy) (* -1 ball-vy))) + ((> (+ ball-y g:+ball-radius+) 1) + (setf (v:vy ball-xy) (- 1 g:+ball-radius+)) + (setf (v:vy ball-vxy) (* -1 ball-vy))) + (t + (setf (g:ball-xy ball) (v:v+ ball-xy (v:v* ball-vxy timelapse))))))) + +(defun computer-paddle-target (game) + (let* ((game-state (g:game-state game)) + (ball (g:state-ball game-state)) + (ball-vxy (g:ball-vxy ball))) + (if (plusp (v:vx ball-vxy)) + (or (local-game-1p-computer-paddle-target game) + (setf (local-game-1p-computer-paddle-target game) + (random g:+paddle-height+))) + (setf (local-game-1p-computer-paddle-target game) nil)))) + +(defmethod g:on-init ((game local-game-1p)) + (g:random-launch-ball (g:game-state game))) + +(defmethod g:on-update ((game local-game-1p) timelapse) + (let ((game-state (g:game-state game))) + (unless (g:state-paused game-state) + (handle-left-player game-state timelapse + (list r:+key-w+ r:+key-up+) + (list r:+key-s+ r:+key-down+)) + (handle-right-computer game-state timelapse (computer-paddle-target game)) + (handle-ball game-state timelapse)))) + +(defmethod g:on-init ((game local-game-2p)) + (g:random-launch-ball (g:game-state game))) + +(defmethod g:on-update ((game local-game-2p) timelapse) + (let ((game-state (g:game-state game))) + (unless (g:state-paused game-state) + (handle-left-player game-state timelapse (list r:+key-w+) (list r:+key-s+)) + (handle-right-player game-state timelapse (list r:+key-up+) (list r:+key-down+)) + (handle-ball game-state timelapse)))) diff --git a/client/src/main-menu.lisp b/client/src/main-menu.lisp index d736e32..bc80c63 100644 --- a/client/src/main-menu.lisp +++ b/client/src/main-menu.lisp @@ -1,18 +1,5 @@ (in-package :pong.client) -(defconstant +title-top-padding+ 30) -(defconstant +menu-padding+ 10) -(defconstant +menu-group-padding+ 20) -(defconstant +menu-font+ "assets/ComicMono.ttf") -(defconstant +menu-font-size+ 32) -(defconstant +menu-title-font-size+ 52) -(defconstant +menu-group-title-font-size+ 42) -(defconstant +menu-text-color+ r:+white+) -(defconstant +menu-local-1-player+ "1 Player") -(defconstant +menu-local-2-players+ "2 Players") -(defconstant +menu-online-create+ "Create Game") -(defconstant +menu-online-join+ "Join Game") - (defclass main-menu (scene) ((title :initarg :title :initform nil :reader main-menu-title) (items-group :initarg :items-group :initform nil :reader main-menu-items-group) @@ -34,7 +21,7 @@ `(make-instance 'menu-item :action ,action :text (gui:make-text ,@text-args))) -(defun open-main-menu (start-1-player-game start-2-players-game create-online-game join-online-game) +(defun open-main-menu () (let* ((title (gui:make-text :text "Pariatech's Pong Game" :font +menu-font+ :font-size +menu-title-font-size+ @@ -54,7 +41,7 @@ :font-size +menu-font-size+ :color +menu-text-color+ :h-align :center) - :action start-1-player-game)) + :action #'start-1-player-game)) (2-players (make-menu-item (:y (+ (gui:y (menu-item-text 1-player)) (gui:h (menu-item-text 1-player)) +menu-padding+) @@ -63,7 +50,7 @@ :font-size +menu-font-size+ :color +menu-text-color+ :h-align :center) - :action start-2-players-game)) + :action #'start-2-players-game)) (online-title (gui:make-text :y (+ (gui:y (menu-item-text 2-players)) (gui:h (menu-item-text 2-players)) +menu-group-padding+) @@ -80,7 +67,7 @@ :font-size +menu-font-size+ :color +menu-text-color+ :h-align :center) - :action create-online-game)) + :action #'open-create-online-game-scene)) (online-join (make-menu-item (:y (+ (gui:y (menu-item-text online-create)) (gui:h (menu-item-text online-create)) +menu-padding+) @@ -89,7 +76,7 @@ :font-size +menu-font-size+ :color +menu-text-color+ :h-align :center) - :action join-online-game)) + :action #'open-create-online-game-scene)) (items-group (gui:make-element :h (+ (gui:y (menu-item-text online-join)) (gui:h (menu-item-text online-join))) :h-align :center @@ -105,16 +92,16 @@ (menu-item-text online-create) (menu-item-text online-join)) (gui:add-children root-element title items-group) - (make-main-menu :title title - :items-group items-group - :local-title local-title - :local-1-player 1-player - :local-2-players 2-players - :online-title online-title - :online-create online-create - :online-join online-join - :items-group items-group - :root-element root-element))) + (set-scene (make-main-menu :title title + :items-group items-group + :local-title local-title + :local-1-player 1-player + :local-2-players 2-players + :online-title online-title + :online-create online-create + :online-join online-join + :items-group items-group + :root-element root-element)))) (defun position-title (title) (setf (gui:x title) (floor (gui:w (gui:parent title)) 2))) diff --git a/client/src/main.lisp b/client/src/main.lisp new file mode 100644 index 0000000..3ecb81e --- /dev/null +++ b/client/src/main.lisp @@ -0,0 +1,30 @@ +(in-package :pong.client) + + +(defun create-online-game () + (setf *scene* (open-create-online-game-scene))) + +(defun join-online-game () + (format t "~%Join online game.")) + +(defun main () + (let* ((last-time nil) + (current-time (get-internal-real-time))) + (r:with-window (800 600 "Pariatech's Pong") + (gui:with-gui + (open-main-menu) + + (r:set-config-flags r:+flag-window-resizable+) + (r:set-target-fps 60) + (r:set-exit-key 0) + (loop + until (or (r:window-should-close) (scene-should-close *scene*)) + do (setf last-time current-time) + (setf current-time (/ (get-internal-real-time) internal-time-units-per-second)) + + (let ((timelapse (- current-time last-time))) + (on-update *scene* timelapse)) + (r:with-drawing + (r:clear-background r:+gray+) + (on-draw *scene*) + (r:draw-fps 20 20))))))) diff --git a/client/src/online-game.lisp b/client/src/online-game.lisp new file mode 100644 index 0000000..fbb1925 --- /dev/null +++ b/client/src/online-game.lisp @@ -0,0 +1,55 @@ +(in-package :pong.client) + +(defclass online-game (g:game) + ((actions :initarg :actions :initform nil :accessor online-game-actions) + (keyboard :initform (make-hash-table) :reader online-game-keyboard) + (paddle :initarg :paddle :initform :left :reader online-game-paddle))) + +(defun start-online-game (&optional (paddle :left)) + (set-scene (open-game (make-instance 'online-game :paddle paddle)))) + +(defun open-create-online-game-scene () + (start-online-game :left)) + +(defmethod g:on-init ((game online-game)) + (g:random-launch-ball (g:game-state game))) + +(defun is-first-key-down-p (key keyboard) + (and (r:is-key-down key) (not (gethash key keyboard)))) + +(defun is-first-key-released-p (key keyboard) + (and (r:is-key-released key) (gethash key keyboard))) + +(defun set-key-down (key keyboard) + (setf (gethash key keyboard) (r:is-key-down key))) + +(defmethod g:on-update ((game online-game) timelapse) + (with-slots (actions keyboard paddle) game + (when (or (is-first-key-down-p r:+key-s+ keyboard) + (is-first-key-down-p r:+key-down+ keyboard)) + (set-key-down r:+key-s+ keyboard) + (set-key-down r:+key-down+ keyboard) + (let ((action (make-instance 'g:start-down-action :paddle paddle))) + (g:apply-action action game) + (push action actions))) + (when (or (is-first-key-released-p r:+key-s+ keyboard) + (is-first-key-released-p r:+key-down+ keyboard)) + (set-key-down r:+key-s+ keyboard) + (set-key-down r:+key-down+ keyboard) + (let ((action (make-instance 'g:stop-down-action :paddle paddle))) + (g:apply-action action game) + (push action action))) + (when (or (is-first-key-down-p r:+key-w+ keyboard) + (is-first-key-down-p r:+key-up+ keyboard)) + (set-key-down r:+key-w+ keyboard) + (set-key-down r:+key-up+ keyboard) + (let ((action (make-instance 'g:start-up-action :paddle paddle))) + (g:apply-action action game) + (push action actions))) + (when (or (is-first-key-released-p r:+key-w+ keyboard) + (is-first-key-released-p r:+key-up+ keyboard)) + (set-key-down r:+key-w+ keyboard) + (set-key-down r:+key-up+ keyboard) + (let ((action (make-instance 'g:stop-up-action :paddle paddle))) + (g:apply-action action game) + (push action actions))))) diff --git a/client/src/scene.lisp b/client/src/scene.lisp index d9b26ec..643d462 100644 --- a/client/src/scene.lisp +++ b/client/src/scene.lisp @@ -1,5 +1,7 @@ (in-package #:pong.client) +(defparameter *scene* nil) + (defclass scene () ((root-element :initarg :root-element :initform (make-instance 'gui:element) @@ -13,3 +15,6 @@ (defmethod on-draw :after ((scene scene)) (gui:draw (scene-root-element scene))) + +(defun set-scene (scene) + (setf *scene* scene)) diff --git a/game/game.asd b/game/game.asd index 4ab2bff..00680f6 100644 --- a/game/game.asd +++ b/game/game.asd @@ -6,9 +6,10 @@ :license "AGPLv3" :version "0.0.1" :serial t - :depends-on ("3d-vectors") + :depends-on ("3d-vectors" "cl-conspack") :pathname "src" :components ((:file "package") - (:file "game"))) + (:file "game") + (:file "action"))) diff --git a/game/src/action.lisp b/game/src/action.lisp new file mode 100644 index 0000000..b417e46 --- /dev/null +++ b/game/src/action.lisp @@ -0,0 +1,55 @@ +(in-package :pong.game) + +(defclass action () + ((timestamp :initarg :timestamp + :initform (/ (get-internal-real-time) internal-time-units-per-second) + :reader action-timestamp))) + +(defgeneric apply-action (action game)) + +(defclass start-game-action (action) + ((name :initarg :name :initform nil :accessor name))) + +(defclass start-down-action (action) + ((paddle :initarg :paddle :initform nil :reader paddle))) + +(defclass stop-down-action (action) + ((paddle :initarg :paddle :initform nil :reader paddle))) + +(defclass start-up-action (action) + ((paddle :initarg :paddle :initform nil :reader paddle))) + +(defclass stop-up-action (action) + ((paddle :initarg :paddle :initform nil :reader paddle))) + +(cpk:defencoding action timestamp) +(cpk:defencoding start-game-action name) +(cpk:defencoding start-down-action paddle) +(cpk:defencoding stop-down-action paddle) +(cpk:defencoding start-up-action paddle) +(cpk:defencoding stop-up-action paddle) + +(defun get-paddle (paddle state) + (case paddle + (:left (state-left-paddle state)) + (:right (state-right-paddle state))) ) + +(defmethod apply-action ((action start-down-action) (game game)) + (let* ((state (game-state game)) + (paddle (get-paddle (paddle action) state))) + (incf (paddle-vy paddle) +paddle-speed+))) + +(defmethod apply-action ((action stop-down-action) (game game)) + (let* ((state (game-state game)) + (paddle (get-paddle (paddle action) state))) + (decf (paddle-vy paddle) +paddle-speed+))) + +(defmethod apply-action ((action start-up-action) (game game)) + (let* ((state (game-state game)) + (paddle (get-paddle (paddle action) state))) + (decf (paddle-vy paddle) +paddle-speed+))) + +(defmethod apply-action ((action stop-up-action) (game game)) + (let* ((state (game-state game)) + (paddle (get-paddle (paddle action) state))) + (incf (paddle-vy paddle) +paddle-speed+))) diff --git a/game/src/game.lisp b/game/src/game.lisp index 3d5a84c..2af1ad3 100644 --- a/game/src/game.lisp +++ b/game/src/game.lisp @@ -42,8 +42,10 @@ :accessor state-left-player) (right-player :initarg :right-player :initform "Opponent" - :accessor state-right-player))) - + :accessor state-right-player) + (paused :initarg :paused + :initform nil + :reader state-paused))) (defclass game () ((state :initarg :game-state @@ -53,6 +55,105 @@ (defgeneric on-update (game timelapse)) (defgeneric on-init (game)) +(defun update-paddle (paddle timelapse) + (setf (paddle-y paddle) + (min (max (+ (paddle-y paddle) (* (paddle-vy paddle) timelapse)) + (/ +paddle-height+ 2)) + (- 1 (/ +paddle-height+ 2))))) + +(defun point-in-rect-p (px py rx ry rw rh) + (and (> px rx) + (< px (+ rx rw)) + (> py ry) + (< py (+ ry rh)))) + +(defun get-ball-paddle-collision (ball paddle paddle-x) + (let* ((ball-xy (ball-xy ball)) + (ball-x (v:vx ball-xy)) + (ball-y (v:vy ball-xy)) + (paddle-y (paddle-y paddle))) + (when (or (point-in-rect-p (- ball-x +ball-radius+) + (- ball-y +ball-radius+) + paddle-x + (- paddle-y (/ +paddle-height+ 2)) + +paddle-width+ + +paddle-height+) + (point-in-rect-p (- ball-x +ball-radius+) + (+ ball-y +ball-radius+) + paddle-x + (- paddle-y (/ +paddle-height+ 2)) + +paddle-width+ + +paddle-height+) + (point-in-rect-p (+ ball-x +ball-radius+) + (- ball-y +ball-radius+) + paddle-x + (- paddle-y (/ +paddle-height+ 2)) + +paddle-width+ + +paddle-height+) + (point-in-rect-p (+ ball-x +ball-radius+) + (+ ball-y +ball-radius+) + paddle-x + (- paddle-y (/ +paddle-height+ 2)) + +paddle-width+ + +paddle-height+)) + (- ball-y paddle-y)))) + +(defun update-ball (state ball left-paddle right-paddle timelapse) + (with-slots (xy vxy) ball + (incf (v:vx xy) (* (v:vx vxy) timelapse)) + (incf (v:vy xy) (* (v:vy vxy) timelapse)) + (let ((ball-left-paddle-collision + (get-ball-paddle-collision ball left-paddle 0.0)) + (ball-right-paddle-collision + (get-ball-paddle-collision ball right-paddle (- 1.0 +paddle-width+)))) + (cond + (ball-left-paddle-collision + (launch-ball ball + (incf (state-bounces state)) + (* (/ ball-left-paddle-collision + (+ (/ +paddle-height+ 2) +ball-radius+)) + +max-launch-angle+) + (+ +paddle-width+ +ball-radius+) + (v:vy xy))) + (ball-right-paddle-collision + (launch-ball ball + (incf (state-bounces state)) + (- pi + (* (/ ball-right-paddle-collision + (+ (/ +paddle-height+ 2) +ball-radius+)) + (/ +max-launch-angle+ 2))) + (- 1.0 +paddle-width+ +ball-radius+) + (v:vy xy))) + ((minusp (+ (v:vx xy) +ball-radius+)) + (incf (state-right-score state)) + (random-launch-ball state)) + ((> (- (v:vx xy) +ball-radius+) 1) + (incf (state-left-score state)) + (random-launch-ball state)) + ((minusp (- (v:vy xy) +ball-radius+)) + (setf (v:vy xy) (+ (- (v:vy xy)) (* +ball-radius+ 2))) + (setf (v:vy vxy) (- (v:vy vxy)))) + ((> (+ (v:vy xy) +ball-radius+) 1) + (setf (v:vy xy) (- 2 (v:vy xy) (* +ball-radius+ 2))) + (setf (v:vy vxy) (- (v:vy vxy)))))))) + +(defmethod on-update :after ((game game) timelapse) + (let* ((state (game-state game)) + (left-paddle (state-left-paddle state)) + (right-paddle (state-right-paddle state)) + (ball (state-ball state)) + (paused (state-paused state))) + (unless paused + (update-paddle left-paddle timelapse) + (update-paddle right-paddle timelapse) + (update-ball state ball left-paddle right-paddle timelapse)))) + +(defmethod pause ((game game)) + (setf (slot-value (game-state game) 'paused) t)) + +(defmethod resume ((game game)) + (setf (slot-value (game-state game) 'paused) nil)) + (defgeneric handle-action (game action)) (defmethod launch-ball ((ball ball) bounces rad x y) diff --git a/game/src/package.lisp b/game/src/package.lisp index bcab310..8783a93 100644 --- a/game/src/package.lisp +++ b/game/src/package.lisp @@ -28,6 +28,15 @@ #:+paddle-height+ #:+paddle-width+ #:+max-launch-angle+ - #:+ball-radius+)) - - + #:+ball-radius+ + #:action-timestamp + #:start-game-action + #:start-down-action + #:stop-down-action + #:start-up-action + #:stop-up-action + #:apply-action + #:name + #:state-paused + #:pause + #:resume)) diff --git a/server/server.asd b/server/server.asd index c92a65a..3589535 100644 --- a/server/server.asd +++ b/server/server.asd @@ -1,12 +1,12 @@ (require :asdf) -(asdf:defsystem #:pong.server +(asdf:defsystem #:server :description "Pariatech's Pong game client" :author "Gabriel Pariat " :license "AGPLv3" :version "0.0.1" :serial t - :depends-on ("pong.game") + :depends-on ("game" "usocket" "bordeaux-threads") :pathname "src" :components ((:file "package") diff --git a/server/src/server.lisp b/server/src/server.lisp index 24d028d..c33a6df 100644 --- a/server/src/server.lisp +++ b/server/src/server.lisp @@ -3,3 +3,78 @@ (defclass online-game (game) ((actions :initarg :actions :initform nil))) + +(defparameter *server* nil) +(defparameter *server-running* nil) +(defparameter *connections* nil) +(defparameter *games* nil) +(defparameter *client-game-map* (make-hash-table)) + +(defmacro loop-while-server-running (&body body) + `(loop while *server-running* + do (progn ,@body))) + +(defun get-time () + (/ (get-internal-real-time) internal-time-units-per-second)) + +(defun send-data-to-client (data client) + (let ((stream (usocket:socket-stream client))) + (cpk:encode data :stream stream) + (force-output stream))) + +(defun update-games () + (let ((last-time nil) + (current-time (get-time))) + (loop-while-server-running + (setf last-time current-time) + (setf current-time (get-time)) + (let ((timelapse (- current-time last-time))) + (loop for client in *connections* + do (send-data-to-client "hello!" client))) + (sleep .100)))) + +(defun handle-client (client) + (let* ((stream (usocket:socket-stream client)) + (msg (cpk:decode-stream stream))) + (format t "[SERVER] Message echoed: ~a~%" msg) + (cpk:encode msg :stream stream) + (force-output stream))) + +(defun open-server-socket (port) + (usocket:socket-listen "127.0.0.1" port :reuse-address t :element-type 'unsigned-byte)) + +(defun wait-for-sockets-ready (connections) + (usocket:wait-for-input connections :ready-only t :timeout 5)) + +(defun close-connection (connection) + (usocket:socket-close connection) + (setf *connections* (remove connection *connections*))) + +(defun start-server (port) + (unless *server-running* + (setf *server-running* t) + + (bt:make-thread #'update-games) + (let* ((server-socket (open-server-socket port))) + (unwind-protect + (loop-while-server-running + (loop for ready in (wait-for-sockets-ready (cons server-socket *connections*)) + while *server-running* + do (if (usocket:stream-server-usocket-p ready) + (push (usocket:socket-accept ready) *connections*) + (handler-case (handle-client ready) + (stream-error () + (format t "Socket closed?~%") + (close-connection ready)) + ;; (t () (close-connection ready)) + )))) + (loop for c in (cons server-socket *connections*) + do (close-connection c)) + (stop-server))))) + +(defun stop-server () + (setf *server-running* nil)) + +;; (start-server 54321) +;; (stop-server) +