Commit Diff


commit - /dev/null
commit + a0b1acd8aa11bf5605815feb7311fde030f6a06e
blob - /dev/null
blob + 7a9681b36a6b2e8e23e8a7615c372217c16924b4 (mode 644)
--- /dev/null
+++ LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2022 Omar Polo

+Copyright (c) 2015 rxi

+

+Permission is hereby granted, free of charge, to any person obtaining a copy of

+this software and associated documentation files (the "Software"), to deal in

+the Software without restriction, including without limitation the rights to

+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies

+of the Software, and to permit persons to whom the Software is furnished to do

+so, subject to the following conditions:

+

+The above copyright notice and this permission notice shall be included in all

+copies or substantial portions of the Software.

+

+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

+SOFTWARE.

blob - /dev/null
blob + 8c0e3988bf12bedab0af8f259510032d7f77fec9 (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,18 @@
+FENNEL =	fennel-5.1
+LOVE =		love
+
+.PHONY: all run clean
+
+all: run
+
+.SUFFIXES: .lua .fnl
+.fnl.lua:
+	${FENNEL} --compile $< > $@ || (rm -f $@ && false)
+
+main.lua: main.fnl
+
+run: main.lua
+	${LOVE} .
+
+clean:
+	rm -f main.lua
blob - /dev/null
blob + 545b255393ad3e3bd4cc3c2d3252ca89a61bc360 (mode 644)
--- /dev/null
+++ README.md
@@ -0,0 +1,14 @@
+# SN4K3
+
+> a dead simple snake reimplementation written in fennel, for love2d
+
+Needs fennel and love installed.  Run:
+
+	$ make
+
+to "compile" the game and run it.  Path to the fennel compiler and
+love executable can be provided as:
+
+	$ make FENNEL=/path/to/fennel LOVE=/path/to/love
+
+and defaults to `fennel-5.1` and `love`.
blob - /dev/null
blob + d354fd9e6160b6bd1fdd2bc4e19f27b2e11fab41 (mode 644)
--- /dev/null
+++ main.fnl
@@ -0,0 +1,175 @@
+;; sn4k3
+;;
+;; Copyright (c) 2022 Omar Polo
+;;
+;; This program is free software; you can redistribute it and/or
+;; modify it under the terms of the MIT license. See LICENSE for
+;; details.
+
+(local tick (require :tick))
+
+(var game-state :menu) ;; :menu :game :pause :over
+(var tick-event nil)
+
+(var score 0)
+(var snake-body [])
+(var candies [])
+
+(var direction :right)
+
+(local ratio 20)
+
+(local width 30)
+(local height 20)
+
+(local window-width (* ratio width))
+(local window-height (* ratio height))
+
+(fn hit? [x y seq]
+  "Return the index of the element of seq that is on (x, y), or nil."
+  (accumulate [index nil
+               i r (ipairs seq)
+               &until index]
+    (if (and (= x (. r :x))
+             (= y (. r :y)))
+        i)))
+
+(fn place-candy []
+  (var done? false)
+  (while (not done?)
+    (let [x (math.random (- width 1))
+          y (math.random (- height 1))]
+      (when (and (not (hit? x y snake-body))
+                 (not (hit? x y candies)))
+        (set done? true)
+        (table.insert candies {: x : y})))))
+
+(fn wrap [val max]
+  (if (< val 0)
+      (- max 1)
+      (< val max)
+      val
+      0))
+
+(fn compute-step [{: x : y}]
+  (match direction
+    :up (values x (wrap (- y 1) height))
+    :left (values (wrap (- x 1) width) y)
+    :right (values (wrap (+ x 1) width) y)
+    :down (values x (wrap (+ y 1) height))))
+
+(fn move-snake []
+  (let [(x y) (compute-step (. snake-body 1))]
+    (if (hit? x y snake-body)
+        (do
+          (print "GAME OVER!")
+          (print "score:" score)
+          (set game-state :over))
+        (do
+          (match (hit? x y candies)
+            nil (table.remove snake-body)
+            i (do (table.remove candies i)
+                  (place-candy)
+                  (set score (+ 1 score))))
+          (table.insert snake-body 1 {: x : y})))))
+
+(fn centered-text [x y rectw recth scale text]
+  (let [font (love.graphics.getFont)
+        tw (font:getWidth text)
+        th (font:getHeight)]
+    (love.graphics.print text
+                         (+ (* x ratio) (* (/ rectw 2) ratio))
+                         (+ (* y ratio) (* (/ recth 2) ratio))
+                         0 scale scale
+                         (/ tw 2)
+                         (/ th 2))))
+
+(fn menu-draw []
+  (love.graphics.setColor 0 1 0 1)
+  (centered-text 0 0 width height 2 "SN4K3")
+  (centered-text 0 6 width height 1 "press any key to play"))
+
+(fn game-draw []
+  (love.graphics.setColor 255 255 255)
+  (each [_ r (ipairs snake-body)]
+    (let [{: x : y} r]
+      (love.graphics.rectangle :fill (* ratio x) (* ratio y) 20 20)))
+
+  (love.graphics.setColor 255 0 0)
+  (each [_ r (ipairs candies)]
+    (let [{: x : y} r]
+      (love.graphics.rectangle :fill (* ratio x) (* ratio y) 20 20))))
+
+(fn pause-draw []
+  (game-draw)
+  (love.graphics.setColor 0 1 0 1)
+  (centered-text 0 0 width height 2 "PAUSED")
+  (centered-text 0 6 width height 1 "press any key to resume"))
+
+(fn over-draw []
+  (game-draw)
+  (love.graphics.setColor 0 1 0 1)
+  (centered-text 0 0 width height 2 "GAME OVER!")
+  (centered-text 0 6 width height 1 (string.format "score: %03d" score)))
+
+(fn game-init []
+  (set snake-body [{:x 14 :y 9}
+                   {:x 13 :y 9}
+                   {:x 12 :y 9}
+                   {:x 11 :y 9}])
+  (set candies [])
+  (for [i 1 5]
+    (place-candy))
+  (when tick-event
+    (tick.remove tick-event))
+  (set tick-event (tick.recur move-snake .25)))
+
+(fn love.load []
+  (love.window.updateMode window-width window-height {:fullscreen false}))
+
+(fn love.draw []
+  (love.graphics.setColor 0 0 0)
+  (love.graphics.rectangle :fill 0 0 window-width window-height)
+
+  (if (= game-state :menu)
+      (menu-draw)
+
+      (= game-state :game)
+      (game-draw)
+
+      (= game-state :pause)
+      (pause-draw)
+
+      (= game-state :over)
+      (over-draw)))
+
+(fn key-ok? [key]
+  (and (not= key "lctrl")
+       (not= key "lshift")
+       (not= key "lgui")
+       (not= key "lalt")
+       (not= key "rctrl")
+       (not= key "rshift")
+       (not= key "rgui")
+       (not= key "ralt")
+       (not= key "scrollock")))
+
+(fn love.keypressed [key]
+  (if (or (= game-state :menu)
+          (= game-state :over))
+      (if (= key :q)     (love.event.quit 0)
+          (key-ok? key)  (do (set game-state :game)
+                             (game-init)))
+
+      (= game-state :game)
+      (match key
+        (where (or :up :down :left :right)) (set direction key)
+        (where (or :p :escape)) (set game-state :pause))
+
+      (= game-state :pause)
+      (when (key-ok? key)
+        (set game-state :game))))
+
+(fn love.update [dt]
+  (when (= game-state :game)
+    (tick.update dt)))
blob - /dev/null
blob + 57e5cb4d8c8fcb00ecf553193c6b04ec205aff17 (mode 644)
--- /dev/null
+++ tick.lua
@@ -0,0 +1,166 @@
+--

+-- tick

+--

+-- Copyright (c) 2015 rxi

+--

+-- This library is free software; you can redistribute it and/or modify it

+-- under the terms of the MIT license. See LICENSE for details.

+--

+

+local tick = { _version = "0.1.1" }

+tick.__index = tick

+

+

+local iscallable = function(x)

+  if type(x) == "function" then return true end

+  local mt = getmetatable(x)

+  return mt and mt.__call ~= nil

+end

+

+local noop = function()

+end

+

+

+local event = {}

+event.__index = event

+

+function event.new(parent, fn, delay, recur, err)

+  err = err or 0

+  -- Create and return event

+  return setmetatable({

+    parent  = parent,

+    delay   = delay,

+    timer   = delay + err,

+    fn      = fn,

+    recur   = recur,

+  }, event)

+end

+

+

+function event:after(fn, delay)

+  -- Error check

+  if self.recur then

+    error("cannot chain a recurring event")

+  end

+  -- Chain event

+  local oldfn = self.fn

+  local e = event.new(self.parent, fn, delay, false)

+  self.fn = function()

+    oldfn()

+    e.timer = e.timer + self.parent.err

+    self.parent:add(e)

+  end

+  return e

+end

+

+

+function event:stop()

+  tick.remove(self.parent, self)

+end

+

+

+

+function tick.group()

+  return setmetatable({ err = 0 }, tick)

+end

+

+

+function tick:add(e)

+  self[e] = true

+  table.insert(self, e)

+  return e

+end

+

+

+function tick:remove(e)

+  if type(e) == "number" then

+    -- Remove and return event

+    local idx = e

+    e = self[idx]

+    self[e] = nil

+    self[idx] = self[#self]

+    table.remove(self)

+    return e

+  end

+  self[e] = false

+  for i, v in ipairs(self) do

+    if v == e then

+      return self:remove(i)

+    end

+  end

+end

+

+

+function tick:update(dt)

+  for i = #self, 1, -1 do

+    local e = self[i]

+    e.timer = e.timer - dt

+    while e.timer <= 0 do

+      if e.recur then

+        e.timer = e.timer + e.delay

+      else

+        self:remove(i) 

+      end

+      self.err = e.timer

+      e.fn()

+      if not e.recur then

+        break

+      end

+    end

+  end

+  self.err = 0

+end

+

+

+function tick:event(fn, delay, recur)

+  delay = tonumber(delay)

+  -- Error check

+  if not iscallable(fn) then

+    error("expected `fn` to be callable")

+  end

+  if type(delay) ~= "number" then

+    error("expected `delay` to be a number")

+  end

+  if delay < 0 then

+    error("expected `delay` of zero or greater")

+  end

+  -- If, factoring in the timing error, the event should happen *now* the

+  -- function is immediately called and the error is temporarily carried

+  -- through. This assures nested events with delays shorter than the update()

+  -- delta-time do not accumulate error; several nested events with very small

+  -- delays may end up being called on the same frame. A dummy event is created

+  -- and returned so :after() still functions correctly.

+  local d = delay + self.err

+  if d < 0 then

+    local err = self.err

+    self.err = d

+    fn()

+    self.err = err

+    return self:add(event.new(self, noop, delay, recur, self.err))

+  end

+  -- Create, add and return a normal event

+  return self:add(event.new(self, fn, delay, recur, self.err))

+end

+

+

+function tick:delay(fn, delay)

+  return self:event(fn, delay, false)

+end

+

+

+function tick:recur(fn, delay)

+  return self:event(fn, delay, true)

+end

+

+

+local group = tick.group()

+

+local bound = {

+  update  = function(...) return tick.update(group, ...) end,

+  delay   = function(...) return tick.delay (group, ...) end,

+  recur   = function(...) return tick.recur (group, ...) end,

+  remove  = function(...) return tick.remove(group, ...) end,

+}

+setmetatable(bound, tick)

+

+return bound