├── .gitignore ├── .merlin ├── Makefile ├── README.md ├── _oasis ├── configure ├── opam ├── descr ├── files │ ├── _oasis_remove_.ml │ └── lambda-invaders.install └── opam ├── setup.ml ├── space_invader.gif └── src ├── l_utils.ml ├── main.ml └── objects.ml /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | *.native 3 | *.byte 4 | *.docdir 5 | 6 | tests/*.cmi 7 | tests/*.cmt 8 | tests/*.cmo 9 | tests/*.cma 10 | 11 | setup.data 12 | setup.log 13 | README.org 14 | scratch 15 | notes.txt 16 | dump_stats.ml 17 | -------------------------------------------------------------------------------- /.merlin: -------------------------------------------------------------------------------- 1 | B _build/src 2 | PKG lwt lambda-term camomile -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: a3c674b4239234cbbe53afe090018954) 3 | 4 | SETUP = ocaml setup.ml 5 | 6 | build: setup.data 7 | $(SETUP) -build $(BUILDFLAGS) 8 | 9 | doc: setup.data build 10 | $(SETUP) -doc $(DOCFLAGS) 11 | 12 | test: setup.data build 13 | $(SETUP) -test $(TESTFLAGS) 14 | 15 | all: 16 | $(SETUP) -all $(ALLFLAGS) 17 | 18 | install: setup.data 19 | $(SETUP) -install $(INSTALLFLAGS) 20 | 21 | uninstall: setup.data 22 | $(SETUP) -uninstall $(UNINSTALLFLAGS) 23 | 24 | reinstall: setup.data 25 | $(SETUP) -reinstall $(REINSTALLFLAGS) 26 | 27 | clean: 28 | $(SETUP) -clean $(CLEANFLAGS) 29 | 30 | distclean: 31 | $(SETUP) -distclean $(DISTCLEANFLAGS) 32 | 33 | setup.data: 34 | $(SETUP) -configure $(CONFIGUREFLAGS) 35 | 36 | configure: 37 | $(SETUP) -configure $(CONFIGUREFLAGS) 38 | 39 | .PHONY: build doc test all install uninstall reinstall clean distclean configure 40 | 41 | # OASIS_STOP 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **lambda invaders** is a OCaml based implementation of space-invaders. It 2 | uses the awesome lambda-term library, the same thing that powers 3 | `utop` and `ocp-browser`. 4 | 5 | ![img](./space_invader.gif) 6 | -------------------------------------------------------------------------------- /_oasis: -------------------------------------------------------------------------------- 1 | OASISFormat: 0.4 2 | OCamlVersion: >= 4.02.3 3 | Name: lambda-invaders 4 | Version: 0.1 5 | Maintainers: Edgar Aroutiounian 6 | Homepage: http://hyegar.com 7 | Synopsis: Aliens attack clonish game 8 | Authors: Edgar Aroutiounian 9 | License: BSD-3-clause 10 | Plugins: META (0.4), DevFiles (0.4) 11 | AlphaFeatures: ocamlbuild_more_args 12 | Description: 13 | Terminal based Alien invaders style game 14 | 15 | Executable maxminddb 16 | Path:src 17 | BuildTools:ocamlbuild 18 | BuildDepends:lwt.syntax, lambda-term, camomile 19 | install: true 20 | MainIs:main.ml 21 | CompiledObject: best 22 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # OASIS_START 4 | # DO NOT EDIT (digest: dc86c2ad450f91ca10c931b6045d0499) 5 | set -e 6 | 7 | FST=true 8 | for i in "$@"; do 9 | if $FST; then 10 | set -- 11 | FST=false 12 | fi 13 | 14 | case $i in 15 | --*=*) 16 | ARG=${i%%=*} 17 | VAL=${i##*=} 18 | set -- "$@" "$ARG" "$VAL" 19 | ;; 20 | *) 21 | set -- "$@" "$i" 22 | ;; 23 | esac 24 | done 25 | 26 | ocaml setup.ml -configure "$@" 27 | # OASIS_STOP 28 | -------------------------------------------------------------------------------- /opam/descr: -------------------------------------------------------------------------------- 1 | Aliens attack clonish game 2 | Terminal based Alien invaders style game 3 | 4 | -------------------------------------------------------------------------------- /opam/files/_oasis_remove_.ml: -------------------------------------------------------------------------------- 1 | open Printf 2 | 3 | let () = 4 | let dir = Sys.argv.(1) in 5 | (try Sys.chdir dir 6 | with _ -> eprintf "Cannot change directory to %s\n%!" dir); 7 | exit (Sys.command "ocaml setup.ml -uninstall") 8 | -------------------------------------------------------------------------------- /opam/files/lambda-invaders.install: -------------------------------------------------------------------------------- 1 | etc: [ 2 | "setup.ml" 3 | "setup.data" 4 | "setup.log" 5 | "_oasis_remove_.ml" 6 | ] 7 | -------------------------------------------------------------------------------- /opam/opam: -------------------------------------------------------------------------------- 1 | opam-version: "1.2" 2 | name: "lambda-invaders" 3 | version: "0.1" 4 | maintainer: "Edgar Aroutiounian " 5 | authors: [ "Edgar Aroutiounian " ] 6 | license: "BSD-3-clause" 7 | homepage: "http://hyegar.com" 8 | bug-reports: "http://hyegar.com" 9 | dev-repo: "http://github.com/fxfactorial/lambda-invaders.git" 10 | build: [ 11 | ["oasis" "setup"] 12 | ["ocaml" "setup.ml" "-configure" "--prefix" prefix] 13 | ["ocaml" "setup.ml" "-build"] 14 | ] 15 | install: ["ocaml" "setup.ml" "-install"] 16 | remove: [ 17 | ["ocaml" "%{etc}%/lambda-invaders/_oasis_remove_.ml" 18 | "%{etc}%/lambda-invaders"] 19 | ] 20 | build-test: [ 21 | ["oasis" "setup"] 22 | ["ocaml" "setup.ml" "-configure" "--enable-tests"] 23 | ["ocaml" "setup.ml" "-build"] 24 | ["ocaml" "setup.ml" "-test"] 25 | ] 26 | depends: [ 27 | "camomile" {build} 28 | "lambda-term" {build} 29 | "lwt" {build} 30 | "oasis" {build & >= "0.4"} 31 | "ocamlfind" {build} 32 | ] 33 | available: [ ocaml-version >= "4.02.3" ] 34 | -------------------------------------------------------------------------------- /setup.ml: -------------------------------------------------------------------------------- 1 | (* setup.ml generated for the first time by OASIS v0.4.5 *) 2 | 3 | (* OASIS_START *) 4 | (* DO NOT EDIT (digest: 9852805d5c19ca1cb6abefde2dcea323) *) 5 | (******************************************************************************) 6 | (* OASIS: architecture for building OCaml libraries and applications *) 7 | (* *) 8 | (* Copyright (C) 2011-2013, Sylvain Le Gall *) 9 | (* Copyright (C) 2008-2011, OCamlCore SARL *) 10 | (* *) 11 | (* This library is free software; you can redistribute it and/or modify it *) 12 | (* under the terms of the GNU Lesser General Public License as published by *) 13 | (* the Free Software Foundation; either version 2.1 of the License, or (at *) 14 | (* your option) any later version, with the OCaml static compilation *) 15 | (* exception. *) 16 | (* *) 17 | (* This library is distributed in the hope that it will be useful, but *) 18 | (* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *) 19 | (* or FITNESS FOR A PARTICULAR PURPOSE. See the file COPYING for more *) 20 | (* details. *) 21 | (* *) 22 | (* You should have received a copy of the GNU Lesser General Public License *) 23 | (* along with this library; if not, write to the Free Software Foundation, *) 24 | (* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *) 25 | (******************************************************************************) 26 | 27 | let () = 28 | try 29 | Topdirs.dir_directory (Sys.getenv "OCAML_TOPLEVEL_PATH") 30 | with Not_found -> () 31 | ;; 32 | #use "topfind";; 33 | #require "oasis.dynrun";; 34 | open OASISDynRun;; 35 | 36 | (* OASIS_STOP *) 37 | let () = setup ();; 38 | -------------------------------------------------------------------------------- /space_invader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fxfactorial/lambda-invaders/43dff0f67262809716adc6d2d55f10a94c02ac6d/space_invader.gif -------------------------------------------------------------------------------- /src/l_utils.ml: -------------------------------------------------------------------------------- 1 | let get_time () = 2 | let localtime = Unix.localtime (Unix.time ()) in 3 | Printf.sprintf "[%02u:%02u:%02u]: " 4 | localtime.Unix.tm_hour 5 | localtime.Unix.tm_min 6 | localtime.Unix.tm_sec 7 | 8 | (* let log message = *) 9 | (* let open Core.Std in *) 10 | (* let oc = open_out_gen [Open_wronly; *) 11 | (* Open_creat; *) 12 | (* Open_append] 0o666 "log" in *) 13 | (* output_string oc ((get_time ()) ^ message ^ "\n"); *) 14 | (* Out_channel.close oc *) 15 | 16 | -------------------------------------------------------------------------------- /src/main.ml: -------------------------------------------------------------------------------- 1 | open CamomileLibrary.UChar 2 | open LTerm_key 3 | open LTerm_geom 4 | 5 | open Objects 6 | 7 | let () = 8 | let prog = 9 | let do_run, push_layer, pop_layer, exit_ = 10 | LTerm_widget.prepare_simple_run () in 11 | 12 | let end_game = new LTerm_widget.modal_frame in 13 | end_game#on_event 14 | (function 15 | | LTerm_event.Key {code = LTerm_key.Char ch} 16 | when ch = of_char 'q' -> 17 | exit_ (); 18 | false 19 | | _ -> false ); 20 | 21 | end_game#set (new LTerm_widget.label "End game, press 'q' to quit"); 22 | 23 | let help_modal = new LTerm_widget.modal_frame in 24 | 25 | let game_frame = new game_frame exit_ 26 | (push_layer help_modal) 27 | (push_layer end_game) in 28 | 29 | ignore (Lwt_engine.on_timer 0.10 true 30 | (fun e -> game_frame#queue_event_draw e)); 31 | 32 | do_run game_frame 33 | in 34 | Lwt_main.run prog 35 | -------------------------------------------------------------------------------- /src/objects.ml: -------------------------------------------------------------------------------- 1 | open CamomileLibrary.UChar 2 | open LTerm_key 3 | open LTerm_geom 4 | open LTerm_widget 5 | open L_utils 6 | 7 | type alien = {index:int; 8 | spot:(int * int); 9 | drawing_char:CamomileLibrary.UChar.t} 10 | 11 | class game_frame exit_ show_help show_endgame = 12 | object(self) 13 | inherit LTerm_widget.frame as super 14 | 15 | val mutable init = false 16 | val mutable previous_location = None 17 | val mutable rockets = [||] 18 | val mutable aliens = [||] 19 | val hits = ref 0 20 | val go_down = ref 0 21 | (* 0 is down, 1 is left, 2 is right *) 22 | val direction = ref 1 23 | val max_cols = ref 0 24 | val mutable current_event = None 25 | 26 | val defender_style = LTerm_style.({bold = None; 27 | underline = None; 28 | blink = None; 29 | reverse = None; 30 | foreground = Some lblue; 31 | background = Some lgreen}) 32 | 33 | val rocket_style = LTerm_style.({bold = None; 34 | underline = None; 35 | blink = None; 36 | reverse = None; 37 | foreground = Some lred; 38 | background = None}) 39 | 40 | (* Not sure why this doesn't compile without the explicit type 41 | signature *) 42 | method queue_event_draw (event : Lwt_engine.event) = 43 | current_event <- Some event; 44 | self#queue_draw 45 | 46 | method draw ctx focused_widget = 47 | (* Calling super just for that frame wrapping, aka the |_| *) 48 | (* Make sure that row1 is smaller than row2 49 | and that col1 is smaller than col2, it goes: 50 | row1 51 | col1 col2 52 | row2 *) 53 | LTerm_draw.clear ctx; 54 | super#draw ctx focused_widget; 55 | LTerm_draw.draw_string ctx 0 0 ("Hits: " ^ (string_of_int !hits)); 56 | LTerm_draw.draw_string ctx 13 0 ~style:LTerm_style.({bold = None; 57 | underline = None; 58 | blink = Some true; 59 | reverse = None; 60 | foreground = Some lyellow; 61 | background = None}) 62 | "Game Over Line"; 63 | 64 | if not init 65 | then 66 | begin 67 | let this_size = LTerm_draw.size ctx in 68 | init <- true; 69 | max_cols := this_size.cols; 70 | 71 | previous_location <- Some {row = this_size.rows - 1; 72 | col = (this_size.cols / 2)}; 73 | 74 | let ctx_ = LTerm_draw.sub ctx {row1 = this_size.rows - 2; 75 | col1 = (this_size.cols / 2); 76 | row2 = this_size.rows - 1; 77 | col2 = (this_size.cols / 2) + 1} in 78 | 79 | (* NOTE Drawing outside of your context is a no op *) 80 | LTerm_draw.draw_string ctx_ 0 0 "λ"; 81 | (* TODO Pick smarter values as a function of terminal size? *) 82 | 83 | (* Rows*) 84 | for i = 3 to 10 do 85 | (* Columns *) 86 | for j = 10 to 44 do 87 | if (i mod 2 > 0) && (j mod 2 > 0) 88 | then 89 | aliens <- Array.append [|{index = Array.length aliens; 90 | spot = (i, j); 91 | drawing_char = (of_int 128125)}|] aliens; 92 | LTerm_draw.draw_char ctx i j (of_int 128125) 93 | done 94 | done 95 | 96 | end 97 | else 98 | if (fst (Array.get aliens 51).spot) = 12 99 | then (match current_event with 100 | | Some e -> 101 | Lwt_engine.stop_event e; 102 | self#show_endgame_modal () 103 | | None -> ()); 104 | 105 | begin 106 | (* Drawing the lambda defender *) 107 | (match previous_location with 108 | | Some c -> 109 | let ctx = LTerm_draw.sub ctx {row1 = c.row - 1; 110 | col1 = c.col; 111 | row2 = c.row ; 112 | col2 = c.col + 1 } in 113 | LTerm_draw.clear ctx; 114 | LTerm_draw.draw_styled ctx 0 0 115 | ~style:defender_style 116 | (LTerm_text.of_string "λ") 117 | | None -> ()); 118 | 119 | begin 120 | (* Aliens drawing *) 121 | let cp = Array.copy aliens in 122 | match !direction with 123 | (* 2 is right, 1 is left, 0 is down *) 124 | | 0 -> 125 | Array.iter (fun a -> 126 | match a with 127 | | {index = index; spot = (i, j); drawing_char = d} as p -> 128 | Array.set aliens index {p with spot = (i + 1, j)}; 129 | LTerm_draw.draw_char ctx 0 0 d) 130 | cp; 131 | go_down := !go_down mod 3; 132 | direction := !direction + 1; 133 | | 1 -> 134 | Array.iter (fun a -> 135 | match a with 136 | | {index = index; spot = (i, j); drawing_char = d} as p -> 137 | Array.set aliens index {p with spot = (i, j - 1)}; 138 | LTerm_draw.draw_char ctx i (j - 1) d) 139 | cp; 140 | | 2 -> 141 | Array.iter (fun a -> 142 | match a with 143 | | {index = index; spot = (i, j); drawing_char = d} as p -> 144 | Array.set aliens index {p with spot = (i, j + 1)}; 145 | LTerm_draw.draw_char ctx i (j + 1) d) 146 | cp; 147 | | _ -> (); 148 | 149 | (* Change directions *) 150 | end ; 151 | (* Setting the direction *) 152 | if !go_down = 3 153 | then 154 | direction := 0; 155 | 156 | begin 157 | match Array.get aliens 0 with 158 | | {index = index; spot = (row, column)} -> 159 | if column = 1 160 | then 161 | (direction := 2; 162 | go_down := !go_down + 1) 163 | else (match Array.get aliens ((Array.length aliens) - 1) with 164 | | {index = index; spot = (row, column)} -> 165 | if (column = ((LTerm_draw.size ctx).cols - 2)) 166 | then 167 | (direction := 1; 168 | go_down := !go_down +1); 169 | ) 170 | end; 171 | 172 | (* Rockets drawing *) 173 | Array.iter (fun (index, roc) -> 174 | let ctx = LTerm_draw.sub ctx {row1 = roc.row - 1; 175 | col1 = roc.col; 176 | row2 = roc.row; 177 | col2 = roc.col + 1} in 178 | if roc.row > 1 then 179 | begin 180 | Array.iter (fun r -> 181 | match r with 182 | | {index = index; spot = (row, column); drawing_char = d} as p -> 183 | if (roc.row = row) && 184 | (roc.col = column) && 185 | not (eq d (of_char ' ')) 186 | then 187 | begin 188 | Array.set aliens index {p with drawing_char = (of_char ' ' )}; 189 | hits := !hits + 1 190 | end 191 | ) 192 | aliens; 193 | LTerm_draw.draw_styled ctx 0 0 ~style:rocket_style 194 | (LTerm_text.of_string "↥"); 195 | Array.set rockets index (index , {roc with row = roc.row - 1}) 196 | end 197 | else 198 | Array.set rockets index (index , roc)) 199 | (* Need the copy otherwise mutating the array as 200 | you're iterating over it, a bug *) 201 | (Array.copy rockets) 202 | end 203 | 204 | method show_endgame_modal () = 205 | show_endgame () 206 | 207 | method move_left = 208 | match previous_location with 209 | | Some p -> 210 | if p.col > 2 then 211 | previous_location <- Some {p with col = p.col - 2} 212 | | None -> () 213 | 214 | method move_right = 215 | match previous_location with 216 | | Some p -> 217 | if p.col < !max_cols - 3 then 218 | previous_location <- Some {p with col = p.col + 2} 219 | | None -> () 220 | 221 | method fire_rocket = 222 | match previous_location with 223 | | Some p -> 224 | rockets <- Array.append [|(Array.length rockets, p)|] rockets 225 | | None -> (); 226 | self#queue_draw; 227 | 228 | initializer 229 | self#on_event 230 | (function 231 | | LTerm_event.Key {code = Left} -> 232 | self#move_left; 233 | true 234 | | LTerm_event.Key {code = Right} -> 235 | self#move_right; 236 | true 237 | | LTerm_event.Key 238 | {code = LTerm_key.Char ch} 239 | when ch = of_char ' ' -> 240 | self#fire_rocket; 241 | true 242 | | LTerm_event.Key 243 | {meta = true; code = LTerm_key.Char ch} 244 | when ch = of_char 'h' -> 245 | show_help (); 246 | true 247 | | LTerm_event.Key 248 | {code = LTerm_key.Char ch} 249 | when ch = of_char 'q' -> 250 | exit_ (); 251 | true 252 | | _ -> false) 253 | end 254 | --------------------------------------------------------------------------------