Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit d6826791086c6beec1a93efb0e736e06bf6760f6 0 parents
@HenJi authored
9 Makefile
@@ -0,0 +1,9 @@
+EXE = opacman.exe
+
+all: $(EXE)
+
+$(EXE): src/*.opa #resources/*
+ opa src/*.opa -o $(EXE)
+
+clean:
+ rm -Rf *.exe _build _tracks *.log **/#*#
44 src/base.opa
@@ -0,0 +1,44 @@
+@client Base = {{
+
+ Dir = {{
+
+ facing_angle(dir:Base.direction) =
+ match dir with
+ | {up} -> -Math.PI/2.
+ | {down} -> Math.PI/2.
+ | {left} -> Math.PI
+ | {right} -> 0.
+ | {still} -> 0.
+
+ deltas(dir:Base.direction) =
+ match dir with
+ | {up} -> (0, -1)
+ | {down} -> (0, 1)
+ | {left} -> (-1, 0)
+ | {right} -> (1, 0)
+ | {still} -> (0, 0)
+
+ back(dir:Base.direction):Base.direction =
+ match dir with
+ | {up} -> {down}
+ | {down} -> {up}
+ | {left} -> {right}
+ | {right} -> {left}
+ | x -> x
+
+ }}
+
+ @both make(x, y, dir, max_steps) = {
+ pos = ~{x y}
+ cur_step = 0
+ ~dir ~max_steps
+ }
+
+ center(b:Base.t) =
+ w = base_size
+ d = (w*b.cur_step) / b.max_steps
+ (dx, dy) = Dir.deltas(b.dir)
+ (1+w/2+w*b.pos.x+d*dx,
+ 1+w/2+w*b.pos.y+d*dy)
+
+}}
54 src/food.opa
@@ -0,0 +1,54 @@
+(initial_food, walls) = [
+ [1,1,1,1,1,1,1,1,1,1,1,1,8,8,1,1,1,1,1,1,1,1,1,1,1,1],
+ [2,8,8,8,8,1,8,8,8,8,8,1,8,8,1,8,8,8,8,8,1,8,8,8,8,2],
+ [1,8,8,8,8,1,8,8,8,8,8,1,8,8,1,8,8,8,8,8,1,8,8,8,8,1],
+ [1,8,8,8,8,1,8,8,8,8,8,1,8,8,1,8,8,8,8,8,1,8,8,8,8,1],
+ [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
+ [1,8,8,8,8,1,8,8,1,8,8,8,8,8,8,8,8,1,8,8,1,8,8,8,8,1],
+ [1,8,8,8,8,1,8,8,1,8,8,8,8,8,8,8,8,1,8,8,1,8,8,8,8,1],
+ [1,1,1,1,1,1,8,8,1,1,1,1,8,8,1,1,1,1,8,8,1,1,1,1,1,1],
+ [8,8,8,8,8,1,8,8,8,8,8,0,8,8,0,8,8,8,8,8,1,8,8,8,8,8],
+ [8,8,8,8,8,1,8,8,8,8,8,0,8,8,0,8,8,8,8,8,1,8,8,8,8,8],
+ [8,8,8,8,8,1,8,8,0,0,0,0,0,0,0,0,0,0,8,8,1,8,8,8,8,8],
+ [8,8,8,8,8,1,8,8,0,8,8,8,0,0,8,8,8,0,8,8,1,8,8,8,8,8],
+ [8,8,8,8,8,1,8,8,0,8,0,0,8,8,0,0,8,0,8,8,1,8,8,8,8,8],
+ [0,0,0,0,8,1,0,0,0,8,0,0,0,0,0,0,8,0,0,0,1,8,0,0,0,0],
+ [8,8,8,8,8,1,8,8,0,8,0,0,0,0,0,0,8,0,8,8,1,8,8,8,8,8],
+ [8,8,8,8,8,1,8,8,0,8,8,8,8,8,8,8,8,0,8,8,1,8,8,8,8,8],
+ [8,8,8,8,8,1,8,8,0,0,0,0,0,0,0,0,0,0,8,8,1,8,8,8,8,8],
+ [8,8,8,8,8,1,8,8,0,8,8,8,8,8,8,8,8,0,8,8,1,8,8,8,8,8],
+ [8,8,8,8,8,1,8,8,0,8,8,8,8,8,8,8,8,0,8,8,1,8,8,8,8,8],
+ [1,1,1,1,1,1,1,1,1,1,1,1,8,8,1,1,1,1,1,1,1,1,1,1,1,1],
+ [1,8,8,8,8,1,8,8,8,8,8,1,8,8,1,8,8,8,8,8,1,8,8,8,8,1],
+ [2,8,8,8,8,1,8,8,8,8,8,1,8,8,1,8,8,8,8,8,1,8,8,8,8,2],
+ [1,1,1,8,8,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,8,8,1,1,1],
+ [8,8,1,8,8,1,8,8,1,8,8,8,8,8,8,8,8,1,8,8,1,8,8,1,8,8],
+ [8,8,1,8,8,1,8,8,1,8,8,8,8,8,8,8,8,1,8,8,1,8,8,1,8,8],
+ [1,1,1,1,1,1,8,8,1,1,1,1,8,8,1,1,1,1,8,8,1,1,1,1,1,1],
+ [1,8,8,8,8,8,8,8,8,8,8,1,8,8,1,8,8,8,8,8,8,8,8,8,8,1],
+ [1,8,8,8,8,8,8,8,8,8,8,1,8,8,1,8,8,8,8,8,8,8,8,8,8,1],
+ [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
+] |> List.foldi(
+ y, l, (food, walls) ->
+ List.foldi(
+ x, v, (food, walls) ->
+ if v == 1 then (Set.add(~{x y}, food), walls)
+ else if v == 8 then (food, Set.add(~{x y}, walls))
+ else (food, walls),
+ l, (food, walls)),
+ _, (Set.empty:set(Base.pos), Set.empty:set(Base.pos)))
+
+@client Food = {{
+
+ draw(ctx:Canvas.context) =
+ food = game.get().food
+ w = base_size
+ do Canvas.save(ctx)
+ do Canvas.set_fill_style(ctx, {color=Color.red})
+ do Set.iter(
+ ~{x y} -> Canvas.fill_rect(ctx, w/2+x*w-2, w/2+y*w-2, 6, 6),
+ food)
+ do Canvas.restore(ctx)
+ void
+
+}}
125 src/ghost.opa
@@ -0,0 +1,125 @@
+@client Ghost = {{
+
+ @server default = [
+ { ai = {dumb}
+ base = Base.make(5, 4, {right}, 10)
+ color = Color.orange },
+ { ai = {guard}
+ base = Base.make(20, 4, {down}, 10)
+ color = Color.darkred },
+ { ai = {dumb}
+ base = Base.make(20, 22, {left}, 10)
+ color = Color.gold },
+ { ai = {guard}
+ base = Base.make(5, 22, {up}, 10)
+ color = Color.green }
+ ] : list(Ghost.t)
+
+ invert_color(c:color) =
+ Color.set_r(c, 255-Color.r(c))
+ |> Color.set_g(_, 255-Color.g(c))
+ |> Color.set_b(_, 255-Color.b(c))
+
+ draw_one(ctx:Canvas.context, g:Ghost.t) =
+ w = base_size
+
+ do Canvas.save(ctx)
+ do Canvas.set_fill_style(ctx, {color=g.color})
+ (center_x, center_y) = Base.center(g.base)
+ do Canvas.translate(ctx, center_x, center_y)
+
+ do Canvas.begin_path(ctx)
+ do Canvas.move_to(ctx, w/2, 0)
+ do Canvas.quadratic_curve_to(ctx, w/2, -w/2, 0, -w/2)
+ do Canvas.quadratic_curve_to(ctx, -w/2, -w/2, -w/2, 0)
+ do Canvas.line_to(ctx, -w/2, w/2)
+ do Canvas.line_to(ctx, -w/6, w/3)
+ do Canvas.line_to(ctx, 0, w/2)
+ do Canvas.line_to(ctx, w/6, w/3)
+ do Canvas.line_to(ctx, w/2, w/2)
+ do Canvas.fill(ctx)
+
+ do Canvas.clear_rect(ctx, -w/4, -w/4, w/2, w/4)
+ do Canvas.set_fill_style(ctx, {color=invert_color(g.color)})
+ dx =
+ base = g.base.max_steps
+ step =
+ if g.base.cur_step > base/2 then base - g.base.cur_step
+ else g.base.cur_step
+ (w*step)/(2*base)
+ do Canvas.fill_rect(ctx, dx-w/4, -w/4, w/4, w/4)
+
+ do Canvas.restore(ctx)
+ void
+
+ @private build_move_options(b:Base.t, no_back) =
+ all_options = [] : list(Base.direction)
+ |> (if Wall.at(b.pos.x+1, b.pos.y) then identity
+ else List.add({right}, _))
+ |> (if Wall.at(b.pos.x-1, b.pos.y) then identity
+ else List.add({left}, _))
+ |> (if Wall.at(b.pos.x, b.pos.y+1) then identity
+ else List.add({down}, _))
+ |> (if Wall.at(b.pos.x, b.pos.y-1) then identity
+ else List.add({up}, _))
+ if List.length(all_options) == 1 then all_options
+ else if no_back then
+ back = Base.Dir.back(b.dir)
+ List.filter(x -> x!=back, all_options)
+ else all_options
+
+ @private move_one_generic(g:Ghost.t, move_fun) =
+ cur_step = g.base.cur_step + 1
+ cur_step =
+ if cur_step >= g.base.max_steps then 0
+ else cur_step
+ if cur_step != 0 then {g with base = {g.base with ~cur_step}}
+ else
+ (dx, dy) = Base.Dir.deltas(g.base.dir)
+ pos = {
+ x = g.base.pos.x + dx
+ y = g.base.pos.y + dy
+ }
+ g = {g with base = {g.base with ~pos}}
+ dirs = move_fun(g.base)
+ dir = List.get(Random.int(List.length(dirs)), dirs) ? {down}
+ {g with base = {g.base with ~dir ~cur_step}}
+
+ @private move_one_dumb(ghost:Ghost.t) =
+ move_one_generic(ghost, build_move_options(_, true))
+
+ @private move_one_guard(ghost:Ghost.t, bp:Base.t) =
+ move_fun(bg) =
+ opts = build_move_options(bg, false)
+ can_see(dir) =
+ if bg.pos.x != bp.pos.x && bg.pos.y != bp.pos.y then false
+ else if bg.pos.x == bp.pos.x && bg.pos.y > bp.pos.y
+ && dir == {up} then true
+ else if bg.pos.x == bp.pos.x && bg.pos.y < bp.pos.y
+ && dir == {down} then true
+ else if bg.pos.y == bp.pos.y && bg.pos.x > bp.pos.x
+ && dir == {left} then true
+ else if bg.pos.y == bp.pos.y && bg.pos.x < bp.pos.x
+ && dir == {right} then true
+ else false
+ bias = List.filter(can_see, opts)
+ if bias == [] then
+ back = Base.Dir.back(bg.dir)
+ List.filter(x -> x!=back, opts)
+ else bias
+ move_one_generic(ghost, move_fun)
+
+ move() =
+ g = game.get()
+ ghosts = List.map(
+ ghost -> match ghost.ai with
+ | {dumb} -> move_one_dumb(ghost)
+ | {guard} -> move_one_guard(ghost, g.pacman.base),
+ g.ghosts)
+ game.set({g with ~ghosts})
+
+ draw(ctx:Canvas.context) =
+ g = game.get()
+ List.iter(draw_one(ctx, _), g.ghosts)
+
+}}
131 src/opacman.opa
@@ -0,0 +1,131 @@
+/* Config */
+
+fps = 60
+base_size = 32
+grid_width = 26
+grid_heigth = 29
+
+/* Defaults */
+
+default_game = {
+ pacman = Pacman.default
+ ghosts = Ghost.default
+ food = initial_food
+ score = 0
+} : Game.status
+
+/* Game */
+
+@client game = Mutable.make(default_game)
+
+@client draw_grid(ctx:Canvas.context) =
+ w = base_size
+ do Canvas.save(ctx)
+ do Canvas.set_stroke_style(ctx, {color=Color.pink})
+ do Canvas.set_line_width(ctx, 1.)
+ do Canvas.begin_path(ctx)
+ // lh = List.init(identity, grid_heigth)
+ do List.iter(
+ x ->
+ do Canvas.move_to(ctx, x*w, 1)
+ do Canvas.line_to(ctx, x*w, 1+w*grid_heigth)
+ void,
+ List.init(x->x+1, grid_width-1))
+ do List.iter(
+ y ->
+ do Canvas.move_to(ctx, 1, y*w)
+ do Canvas.line_to(ctx, 1+w*grid_width, y*w)
+ void,
+ List.init(y->y+1, grid_heigth-1))
+ do Canvas.stroke(ctx)
+ do Canvas.restore(ctx)
+ void
+
+@client clean_frame(ctx:Canvas.context) =
+ Canvas.clear_rect(
+ ctx, 0, 0,
+ 2+2*base_size*grid_width,
+ 2+2*base_size*grid_heigth)
+
+@client print_infos(g:Game.status) =
+ p = g.pacman
+ cont =
+ <>
+ Pacman at ({p.base.pos.x},{p.base.pos.y}), moving {"{p.base.dir}"}
+ - {Set.size(g.food)} food left
+ - Score: {g.score}
+ </>
+ Dom.transform([#info <- cont])
+
+@client next_frame(ctx:Canvas.context)() =
+ do clean_frame(ctx)
+ do Pacman.move()
+ do Ghost.move()
+ do Wall.draw(ctx)
+ do Food.draw(ctx)
+ do Pacman.draw(ctx)
+ do Ghost.draw(ctx)
+ void
+
+@client keyfun(e) =
+ g = game.get()
+ p = g.pacman
+ p = match (p.base.dir, e.key_code) with
+ // z
+ | ({down}, {some=122}) ->
+ {p with next_dir={up}
+ base={p.base with dir={up}
+ cur_step=-p.base.cur_step}}
+ | (_, {some=122}) -> {p with next_dir={up}}
+
+ // q
+ | ({right}, {some=113}) ->
+ {p with next_dir={left}
+ base={p.base with dir={left}
+ cur_step=-p.base.cur_step}}
+ | (_, {some=113}) -> {p with next_dir={left}}
+
+ // s
+ | ({up}, {some=115}) ->
+ {p with next_dir={down}
+ base={p.base with dir={down}
+ cur_step=-p.base.cur_step}}
+ | (_, {some=115}) -> {p with next_dir={down}}
+
+ // d
+ | ({left}, {some=100}) ->
+ {p with next_dir={right}
+ base={p.base with dir={left}
+ cur_step=-p.base.cur_step}}
+ | (_, {some=100}) -> {p with next_dir={right}}
+
+ // space (pause)
+ | (_, {some=32}) -> {p with next_dir={still}}
+ | _ -> p
+ game.set({g with pacman=p})
+
+@client init() =
+ match Canvas.get(#game_holder) with
+ | {none} -> void
+ | {some=canvas} ->
+ ctx = Canvas.get_context_2d(canvas) |> Option.get
+ t = Scheduler.make_timer(1000/fps, next_frame(ctx))
+ _ = Dom.bind(Dom.select_document(), {keypress}, keyfun)
+ t.start()
+
+body() =
+ <>
+ <canvas id="game_holder"
+ width="{2+base_size*grid_width}"
+ height="{2+base_size*grid_heigth}">
+ You can't see canvas, upgrade your browser !
+ </canvas>
+ <div>
+ <span id="info" onready={_ -> init()}></span>
+ </div>
+ </>
+
+server = one_page_server("OPAcman", body)
+
+css = css
+ canvas { border: 1px solid black; }
93 src/pacman.opa
@@ -0,0 +1,93 @@
+@client Pacman = {{
+
+ @server default = {
+ base = Base.make(0, 0, {right}, 10)
+ next_dir = {right}
+ mouth_state = 0
+ mouth_incr = 1
+ mouth_steps = 10
+ } : Pacman.t
+
+ draw(ctx:Canvas.context) =
+ g = game.get()
+ p = g.pacman
+ w = base_size
+
+ mouth = p.mouth_state
+ dmouth = p.mouth_incr
+ steps = p.mouth_steps
+
+ do Canvas.save(ctx)
+ do Canvas.set_fill_style(ctx, {color=Color.black})
+ (center_x, center_y) = Base.center(p.base)
+ do Canvas.translate(ctx, center_x, center_y)
+ alpha = Base.Dir.facing_angle(p.base.dir)
+ do Canvas.rotate(ctx, alpha)
+
+ angle = Math.PI*Int.to_float((steps-mouth)/(3*steps))
+ x = Int.of_float(Float.of_int(w)*Math.cos(angle)/2.)-1
+ y = (w*(steps-mouth))/(4*steps)
+
+ do Canvas.begin_path(ctx)
+ do Canvas.move_to(ctx, -w/10, 0)
+ // Could replace all curves but currently not available in OPA :(
+ // do Canvas.arc(0, 0, w/2, -angle, angle, 1)
+ do Canvas.line_to(ctx, x, y)
+ do Canvas.quadratic_curve_to(ctx, w/2, w/2, 0, w/2)
+ do Canvas.quadratic_curve_to(ctx, -w/2, w/2, -w/2, 0)
+ do Canvas.quadratic_curve_to(ctx, -w/2, -w/2, 0, -w/2)
+ do Canvas.quadratic_curve_to(ctx, w/2, -w/2, x, -y)
+ do Canvas.fill(ctx)
+ do Canvas.restore(ctx)
+
+ mouth = mouth + dmouth;
+ dmouth =
+ if (mouth == steps-1 || mouth == 0) then -dmouth
+ else dmouth
+ do game.set({g with pacman = {
+ p with
+ mouth_state = mouth
+ mouth_incr = dmouth
+ }})
+ void
+
+ move() =
+ g = game.get()
+ p = g.pacman
+ ignore_incr = p.base.cur_step < 0
+ cur_step = p.base.cur_step + 1
+ cur_step = if cur_step >= p.base.max_steps then 0
+ else if p.base.dir == {still} then 0
+ else cur_step
+ test_wall(on_ok, on_err, x, y) =
+ if Wall.at(x,y) then on_err
+ else on_ok
+ (dir, dx, dy) =
+ if cur_step != 0 || ignore_incr then (p.base.dir, 0, 0)
+ else
+ do print_infos(g)
+ (dx, dy) = Base.Dir.deltas(p.base.dir)
+ (ddx, ddy) = Base.Dir.deltas(p.next_dir)
+ dir = test_wall(p.next_dir, {still},
+ p.base.pos.x+dx+ddx, p.base.pos.y+dy+ddy)
+ (dx, dy, dir) =
+ test_wall((dx,dy,dir), (0,0,{still}),
+ p.base.pos.x+dx, p.base.pos.y+dy)
+ (dir, dx, dy)
+ pos = {
+ x = p.base.pos.x + dx
+ y = p.base.pos.y + dy
+ }
+ (food, score) =
+ if cur_step != p.base.max_steps/2 then (g.food, g.score)
+ else
+ if Set.mem(pos, g.food) then
+ food = Set.remove(pos, g.food)
+ if food == Set.empty then (initial_food, g.score+1010)
+ else (food, g.score+10)
+ else (g.food, g.score)
+ pacman = {p with base = { p.base with
+ ~pos ~dir ~cur_step }}
+ game.set({g with ~pacman ~food ~score})
+
+}}
38 src/types.opa
@@ -0,0 +1,38 @@
+type Base.direction = {still} / {up} / {down} / {left} / {right}
+
+type Base.pos = {
+ x : int
+ y : int
+}
+
+type Base.t = {
+ pos : Base.pos
+ dir : Base.direction
+ cur_step : int /* Current step */
+ max_steps : int /* Max steps in the move (determines speed) */
+}
+
+type Pacman.t = {
+ base : Base.t
+ next_dir : Base.direction
+ mouth_state : int
+ mouth_incr : int
+ mouth_steps : int
+}
+
+type Ghost.ai =
+ {dumb}
+ / {guard}
+
+type Ghost.t = {
+ ai : Ghost.ai
+ base : Base.t
+ color : color
+}
+
+type Game.status = {
+ pacman : Pacman.t
+ ghosts : list(Ghost.t)
+ food : set(Base.pos)
+ score : int
+}
18 src/wall.opa
@@ -0,0 +1,18 @@
+@client Wall = {{
+
+ draw(ctx:Canvas.context) =
+ w = base_size
+ do Canvas.save(ctx)
+ do Canvas.set_fill_style(ctx, {color=Color.darkblue})
+ do Set.iter(
+ ~{x y} -> Canvas.fill_rect(ctx, 1+x*w, 1+y*w, w, w),
+ walls)
+ do Canvas.restore(ctx)
+ void
+
+ at(x, y) =
+ x >= grid_width || y >= grid_heigth
+ || x < 0 || y < 0
+ || Set.mem(~{x y}, walls)
+
+}}
Please sign in to comment.
Something went wrong with that request. Please try again.