Skip to content

Commit a6e56a3

Browse files
committed
Initial commit
0 parents  commit a6e56a3

19 files changed

+1598
-0
lines changed

.dir-locals.el

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
((nil . ((clojure-directory-prefixes . ("\\`clj[sc]?\\."))
2+
(cider-preferred-build-tool . clojure-cli)
3+
(cider-default-cljs-repl . custom)
4+
(cider-custom-cljs-repl-init-form . "(do (user/cljs-repl))")
5+
(cider-clojure-cli-aliases . "-A:dev")
6+
(eval . (progn
7+
(make-variable-buffer-local 'cider-jack-in-nrepl-middlewares)
8+
(add-to-list 'cider-jack-in-nrepl-middlewares "shadow.cljs.devtools.server.nrepl/middleware"))))))

.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
pom.xml
2+
pom.xml.asc
3+
*.jar
4+
*.class
5+
/lib/
6+
/classes/
7+
/target/
8+
/checkouts/
9+
.lein-deps-sum
10+
.lein-repl-history
11+
.lein-plugins/
12+
.lein-failures
13+
.nrepl-port
14+
.cpcache/
15+
.clj-kondo/
16+
/node_modules/
17+
/resources/public/js/
18+
/resources/public/css/
19+
/.shadow-cljs/

.tool-versions

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
clojure 1.11.1.1273
2+
java openjdk-20.0.1
3+
nodejs 18.16.0

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 Mark Stuart
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# tetris-reagent
2+
3+
Tetris implemented using reagent with game-logic from [tetris-clj](https://github.com/codeasone/tetris-clj).

deps.edn

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{:paths ["src/cljs" "resources"]
2+
3+
:deps {org.clojars.codeasone/tetris-clj {:mvn/version "0.1.1"}
4+
reagent/reagent {:mvn/version "1.2.0"}
5+
ch.qos.logback/logback-classic {:mvn/version "1.4.7"}}
6+
7+
:aliases {:dev
8+
{:extra-paths ["dev"]
9+
:extra-deps {thheller/shadow-cljs {:mvn/version "2.23.3"}
10+
binaryage/devtools {:mvn/version "1.0.7"}}}}}

dev/user.clj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
(ns user
2+
(:require [shadow.cljs.devtools.api :as shadow]
3+
[shadow.cljs.devtools.server :as server]))
4+
5+
(def cljs-repl
6+
(fn []
7+
(server/start!)
8+
(shadow/watch :app)
9+
(shadow/nrepl-select :app)))

package.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "tetris-reagent",
3+
"version": "0.1.0",
4+
"repository": "[email protected]:codeasone/tetris-reagent.git",
5+
"author": "Mark Stuart <[email protected]>",
6+
"license": "MIT",
7+
"private": true,
8+
"scripts": {
9+
"compile:dev": "shadow-cljs compile app",
10+
"dev": "shadow-cljs watch app & yarn styles:dev",
11+
"release": "shadow-cljs release app && yarn styles:release",
12+
"serve": "shadow-cljs server",
13+
"styles:dev": "tailwindcss -i ./src/css/main.css -o resources/public/css/main.css --watch",
14+
"styles:release": "tailwindcss -i ./src/css/main.css -o resources/public/css/main.css --minify",
15+
"deps:tree": "clj -X:deps tree :aliases '[:test]'"
16+
},
17+
"devDependencies": {
18+
"react": "^18.2.0",
19+
"react-dom": "^18.2.0",
20+
"react-refresh": "^0.14.0",
21+
"shadow-cljs": "2.23.3",
22+
"tailwindcss": "^3.3.2"
23+
}
24+
}

resources/logback.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<configuration>
3+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
4+
<encoder>
5+
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
6+
</encoder>
7+
</appender>
8+
9+
<logger name="io.undertow" level="ERROR" />
10+
<logger name="org.xnio" level="ERROR" />
11+
<logger name="org.jboss.threads" level="ERROR" />
12+
<logger name="org.eclipse.jetty" level="ERROR" />
13+
14+
<root level="INFO">
15+
<appender-ref ref="STDOUT" />
16+
</root>
17+
</configuration>

resources/public/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>Tetris</title>
7+
<link rel="stylesheet" href="/css/main.css" />
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script src="/js/main.js"></script>
12+
</body>
13+
</html>

shadow-cljs.edn

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{:deps {:aliases [:dev]}
2+
:dev-http {3000 "resources/public"}
3+
:builds {:app
4+
{:target :browser
5+
:dev {:compiler-options
6+
{:closure-defines {'goog.DEBUG true}}}
7+
:release {}
8+
:output-dir "resources/public/js"
9+
:asset-path "/js"
10+
:modules {:main {:entries [tetris.app]
11+
:init-fn tetris.app/init}}
12+
:devtools {:preloads [devtools.preload]}}}}

src/cljs/tetris/app.cljs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
(ns tetris.app
2+
(:require [reagent.dom.client :as rdom]
3+
[tetris.components :as components]))
4+
5+
(defonce root (rdom/create-root (js/document.getElementById "root")))
6+
7+
(defn ^:export ^:dev/after-load init []
8+
(rdom/render root [components/tetris]))

src/cljs/tetris/components.cljs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
(ns tetris.components
2+
(:require [clojure.string :as str]
3+
[tetris.events :as events]
4+
[tetris.logic :as logic]
5+
[tetris.timers :as timers]))
6+
7+
(defn classes
8+
[& strings]
9+
(str/join " " strings))
10+
11+
(defn grid-cell
12+
[num-value]
13+
(let [common-cell-classes "border h-6 w-6"]
14+
(case num-value
15+
0 [:div {:class (classes common-cell-classes "bg-white")}]
16+
1 [:div {:class (classes common-cell-classes "bg-cyan")}]
17+
2 [:div {:class (classes common-cell-classes "bg-yellow")}]
18+
3 [:div {:class (classes common-cell-classes "bg-magenta")}]
19+
4 [:div {:class (classes common-cell-classes "bg-orange")}]
20+
5 [:div {:class (classes common-cell-classes "bg-blue")}]
21+
6 [:div {:class (classes common-cell-classes "bg-green")}]
22+
7 [:div {:class (classes common-cell-classes "bg-red")}])))
23+
24+
(defn message [{:keys [game-status] :as game-state}]
25+
[:h1 {:class "font-bold"}
26+
(if (logic/game-over? game-state)
27+
"Reload 🔁 to play another round..."
28+
(case game-status
29+
:game-status/initialised "Press ENTER key to play"
30+
:game-status/playing "TETRIS"))])
31+
32+
(defn statistics [{:keys [game-level game-score]}]
33+
[:div {:class "flex"}
34+
[:h1 {:class "font-bold mt-3"} (str "Level: " game-level)]
35+
[:h1 {:class "font-bold mt-3 ml-6"} (str "Score: " game-score)]])
36+
37+
(defn grid [game-state]
38+
[:div {:class "w-60 relative mt-3"}
39+
(into
40+
[:div {:class "flex flex-col border"}]
41+
(for [row (drop logic/lead-in-grid-height (logic/compose-current-tetrimino-into-game-grid game-state))]
42+
(into
43+
[:div {:class "flex"}]
44+
(for [cell-value row]
45+
[grid-cell cell-value]))))
46+
47+
(when (logic/game-over? game-state)
48+
(timers/clear-all!)
49+
50+
[:div {:class (classes "absolute top-1/2 left-1/2 z-10 px-4 py-2"
51+
"bg-black text-white text-center rounded-lg"
52+
"transform -translate-x-1/2 -translate-y-1/2")}
53+
"Game Over"])])
54+
55+
(defn tetris []
56+
(let [game-state @events/game-state]
57+
(events/listen!)
58+
[:div {:class "bg-gray-100 h-screen"}
59+
[:div {:class "flex flex-col items-center pt-6"}
60+
[message game-state]
61+
[grid game-state]
62+
[statistics game-state]]]))

src/cljs/tetris/events.cljs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
(ns tetris.events
2+
(:require [reagent.core :as r]
3+
[tetris.events :as events]
4+
[tetris.keys :as keys]
5+
[tetris.logic :as logic]
6+
[tetris.timers :as timers]))
7+
8+
(defonce game-state (r/atom (logic/initial-game-state)))
9+
10+
(defn handle-key-event! [event]
11+
(when-not (logic/game-over? @game-state)
12+
(let [key-code (.-keyCode event)]
13+
(cond
14+
(= key-code keys/enter)
15+
(do
16+
(timers/establish-initial-step-timer!)
17+
(timers/establish-speed-up-timer! game-state)
18+
(reset! game-state (logic/start-playing @game-state)))
19+
20+
;; ESC/ENTER are like PAUSE/RESUME - helpful during exploratory testing
21+
(= key-code keys/escape)
22+
(do
23+
(timers/clear-all!)
24+
@game-state)
25+
26+
(= key-code keys/space)
27+
(when-not @timers/next-tetrimino-scheduled
28+
(reset! game-state (logic/handle-events @game-state [::logic/drop]
29+
(partial timers/play-next-tetrimino game-state))))
30+
31+
(= key-code keys/down)
32+
(when-not @timers/next-tetrimino-scheduled
33+
(reset! game-state (logic/handle-events @game-state [::logic/move-down]
34+
(partial timers/play-next-tetrimino game-state))))
35+
36+
:else
37+
(do
38+
(timers/play-next-tetrimino-debounce! game-state)
39+
(cond
40+
(= key-code keys/left)
41+
(reset! game-state (logic/handle-events @game-state [::logic/move-left]))
42+
43+
(= key-code keys/up)
44+
(reset! game-state (logic/handle-events @game-state [::logic/rotate]))
45+
46+
(= key-code keys/right)
47+
(reset! game-state (logic/handle-events @game-state [::logic/move-right]))
48+
49+
:else
50+
@game-state))))))
51+
52+
(defn listen! []
53+
(set! (.-onkeydown js/document) handle-key-event!))

src/cljs/tetris/keys.cljs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(ns tetris.keys)
2+
3+
(def enter 13)
4+
(def escape 27)
5+
(def space 32)
6+
(def left 37)
7+
(def up 38)
8+
(def right 39)
9+
(def down 40)
10+
11+
(defn dispatch [key-code]
12+
(let [synthetic-event (js/KeyboardEvent. "keydown" #js {:keyCode key-code})]
13+
(.dispatchEvent js/document synthetic-event)))
14+
15+
(comment
16+
(let [synthetic-event (js/KeyboardEvent. "keydown" #js {:keyCode 40})]
17+
(.dispatchEvent js/document synthetic-event))
18+
;;
19+
)

src/cljs/tetris/timers.cljs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
(ns tetris.timers
2+
(:require [tetris.keys :as keys]
3+
[tetris.logic :as logic]))
4+
5+
(def step-timer (atom nil))
6+
(def speed-up-timer (atom nil))
7+
8+
(def initial-step-interval-ms 1000)
9+
(def step-interval-ms (atom initial-step-interval-ms))
10+
(def speed-up-interval-ms 30000)
11+
(def speed-up-factor 0.75)
12+
13+
(defn establish-initial-step-timer! []
14+
(when-not @step-timer
15+
(reset! step-timer (.setInterval
16+
js/window
17+
(fn [] (keys/dispatch keys/down))
18+
initial-step-interval-ms))))
19+
20+
(defn speed-up-by-20-percent! [game-state]
21+
(when-let [active-step-timer @step-timer]
22+
(swap! game-state #(update-in % [:game-level] inc))
23+
(.clearTimeout js/window active-step-timer)
24+
(reset! step-timer (.setInterval
25+
js/window
26+
(fn [] (keys/dispatch keys/down))
27+
(swap! step-interval-ms #(Math/floor (* @step-interval-ms speed-up-factor)))))))
28+
29+
(defn establish-speed-up-timer! [game-state]
30+
(when-not @speed-up-timer
31+
(reset! speed-up-timer (.setInterval
32+
js/window
33+
(partial speed-up-by-20-percent! game-state)
34+
speed-up-interval-ms))))
35+
36+
(def next-tetrimino-scheduled (atom nil))
37+
38+
(def next-tetrimino-grace-ms 500)
39+
40+
(defn play-next-tetrimino
41+
[game-state before-game-state]
42+
(reset! next-tetrimino-scheduled (.setTimeout
43+
js/window
44+
#(do
45+
(reset! game-state (logic/play-next-tetrimino @game-state))
46+
(reset! next-tetrimino-scheduled nil))
47+
next-tetrimino-grace-ms))
48+
before-game-state)
49+
50+
(defn play-next-tetrimino-debounce! [game-state]
51+
(when @next-tetrimino-scheduled
52+
(.clearTimeout js/window @next-tetrimino-scheduled)
53+
(reset! next-tetrimino-scheduled (.setTimeout
54+
js/window
55+
#(do
56+
(reset! game-state (logic/play-next-tetrimino @game-state))
57+
(reset! next-tetrimino-scheduled nil))
58+
next-tetrimino-grace-ms))))
59+
60+
(defn clear-all! []
61+
(when-let [active-step-timer @step-timer]
62+
(.clearTimeout js/window active-step-timer)
63+
(reset! step-timer nil))
64+
65+
(when-let [active-speed-up-timer @speed-up-timer]
66+
(.clearTimeout js/window active-speed-up-timer)
67+
(reset! speed-up-timer nil))
68+
69+
(when-let [active-next-tetrimino-scheduled @next-tetrimino-scheduled]
70+
(.clearTimeout js/window active-next-tetrimino-scheduled)
71+
(reset! next-tetrimino-scheduled nil)))

src/css/main.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;

tailwind.config.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module.exports = {
2+
content: ["./src/**/*.{clj,cljc,cljs}"],
3+
theme: {
4+
extend: {
5+
colors: {
6+
cyan: "#01EDFA",
7+
blue: "#0341AE",
8+
orange: "#FF971C",
9+
yellow: "#FFD500",
10+
green: "#72CB3B",
11+
red: "#FF3213",
12+
magenta: "#DD0AB2",
13+
},
14+
},
15+
},
16+
plugins: [],
17+
};

0 commit comments

Comments
 (0)